diff --git a/docs/cli_commands.md b/docs/cli_commands.md index d9a225e8..5c5ee670 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -28,25 +28,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Usage:** - `reboot` -**Note:** No reply is sent. - ---- - -### Power-off the node -**Usage:** -- `poweroff`, or -- `shutdown` - -**Note:** No reply is sent. - --- ### Reset the clock and reboot **Usage:** - `clkreboot` -**Note:** No reply is sent. - --- ### Sync the clock with the remote device @@ -128,66 +115,6 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -### Send flood text to `#repeaters` channel - -**Usage:** -- `send text.flood ` - -**Notes:** -- Sends a `PAYLOAD_TYPE_GRP_TXT` flood message using the built-in `#repeaters` channel key. -- Message format is `: `. - ---- - -### View or change automatic low-battery alerts to `#repeaters` - -**Usage:** -- `get battery.alert` -- `set battery.alert ` -- `get battery.alert.low` -- `set battery.alert.low ` -- `get battery.alert.critical` -- `set battery.alert.critical ` - -**Parameters:** -- `state`: `on` (enable) or `off` (disable) -- `percent`: Battery percentage threshold - -**Default:** `off` - -**Default thresholds:** `20` for `battery.alert.low`, `10` for `battery.alert.critical` - -**Notes:** -- When enabled, sends a `#repeaters` flood text warning if voltage is above `1 V` and the battery estimate is below `battery.alert.low`. -- Warnings repeat every `24` hours, or every `12` hours below `battery.alert.critical`. -- `battery.alert.critical` must be lower than `battery.alert.low`. - ---- - -### Get or set recent repeater prefix/SNR -**Usage:** -- `get recent.repeater` -- `get recent.repeater ` -- `get recent.repeater page ` -- `set recent.repeater ` - -**Parameters:** -- `prefix_hex_6`: Exactly 3 bytes of next-hop prefix in hex (6 chars) -- `snr_db`: SNR in dB (supports decimals; stored at x4 precision) -- `page`: 1-based page number - -**Notes:** -- `set` stores or updates the prefix in the recent repeater table. -- Output rows are `prefix,snr` with optional `,l` for locked manual entries. -- Rows are sorted by prefix width (3-byte, 2-byte, 1-byte), then SNR descending. -- A full direct retry failure lowers the stored SNR by `0.25 dB`. -- If a full failure has no row yet, it first seeds the row at the active retry cutoff + `2.5 dB`, then applies the `0.25 dB` penalty. -- Serial CLI page size is fixed at `128` rows; choose page with `get recent.repeater `. -- Over LoRa remote CLI, page size is fixed at `7` rows; choose page with `get recent.repeater `. -- Repeaters can use adjacent entries in this table to short-circuit non-TRACE direct packets when this node appears later in the direct path. - ---- - ## Statistics ### Clear Stats @@ -292,20 +219,6 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or change the boosted receive gain mode -**Usage:** -- `get radio.rxgain` -- `set radio.rxgain ` - -**Parameters:** -- `state`: `on`|`off` - -**Default:** `off` - -**Note:** Only available on SX1262 and SX1268 based boards. - ---- - #### Change the radio parameters for a set duration **Usage:** - `tempradio ,,,,` @@ -336,7 +249,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or change this node's rx boosted gain mode (SX12xx only, v1.14.1+) +#### View or change this node's rx boosted gain mode (SX12xx and LR1110, v1.14.1+) **Usage:** - `get radio.rxgain` - `set radio.rxgain ` @@ -469,7 +382,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Note:** `|` characters are translated to newlines -**Note:** Requires firmware 1.12.+ +**Note:** Requires firmware 1.12+ --- @@ -518,18 +431,6 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or set reboot interval (Repeater and room server) -**Usage:** -- `get reboot.interval` -- `set reboot.interval ` - -**Parameters:** -- `hours`: 0-255. 0 is disabled - -**Default:** `0` (disabled) - ---- - ### Routing #### View or change this node's repeat flag @@ -560,7 +461,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 -**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are suppored. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. +**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propagation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are supported. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. --- @@ -578,7 +479,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `off` -**Note:** When it is enabled, repeaters will now reject flood packets which look like they are in a loop. This has been happening recently in some meshes when there is just a single 'bad' repeater firmware out there (prob some forked or custom firmware). If the payload is messed with, then forwarded, the same packet ends up causing a packet storm, repeated up to the max 64 hops. This feature was added in firmware 1.14 +**Note:** When it is enabled, repeaters will now reject flood packets which look like they are in a loop. This has been happening recently in some meshes when there is just a single 'bad' repeater firmware out there (probably some forked or custom firmware). If the payload is messed with, then forwarded, the same packet ends up causing a packet storm, repeated up to the max 64 hops. This feature was added in firmware 1.14 **Example:** If preference is `loop.detect minimal`, and a 1-byte path size packet is received, the repeater will see if its own ID/hash is already in the path. If it's already encoded 4 times, it will reject the packet. If the packet uses 2-byte path size, and repeater's own ID/hash is already encoded 2 times, it rejects. If the packet uses 3-byte path size, and the repeater's own ID/hash is already encoded 1 time, it rejects. @@ -594,6 +495,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `0.5` +**Note:** When multiple nearby repeaters all hear the same flood packet, each waits a random amount of time before retransmitting to avoid simultaneous collisions. This factor scales the size of that random window. Higher values reduce collision risk at the cost of added latency. `0` disables the window entirely. + --- #### View or change the retransmit delay factor for direct traffic @@ -604,116 +507,9 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Parameters:** - `value`: Direct transmit delay factor (0-2) -**Default:** `0.3` +**Default:** `0.2` -**Note:** Direct retry waits include the same airtime-based randomized delay calculation as direct retransmits, so this factor also controls retry echo windows. - ---- - -#### View or change whether direct retries use the recent repeater SNR gate -**Usage:** -- `get direct.retry.heard` -- `set direct.retry.heard ` - -**Parameters:** -- `state`: `on`|`off` - -**Default:** `on` - -**Note:** When enabled, the recent repeater table is the only direct retry eligibility gate. Prefixes missing from the table are assumed reachable; prefixes in the table below the active SNR gate are blocked. Neighbor data is not used. - ---- - -#### View or change adaptive coding rate for direct retry packets -**Usage:** -- `get direct.retry.cr` -- `set direct.retry.cr ,,,` -- `set direct.retry.cr off` - -**Parameters:** -- `cr4_min`: SNR in dB where retry packets use `CR4` -- `cr5_min`: SNR in dB where retry packets use `CR5` -- `cr7_min`: SNR in dB where retry packets use `CR7` -- `cr8_max`: SNR in dB where retry packets use `CR8` - -**Default:** `10.0,7.5,2.5,2.5` - -**Note:** DM retry packets use the next-hop SNR from a recent repeater table entry to pick a local transmit coding rate; if no recent entry is available, retry packets use `CR5`. With the default, SNR `10.0 dB` and up uses `CR4`, SNR `7.5 dB` and up uses `CR5`, SNR `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. `CR6` is never selected. Use `set direct.retry.cr off` to disable adaptive coding-rate overrides. If adaptive selection chooses `CR4`, retries after the third attempt use `CR5`. - ---- - -#### View or change the SNR margin used for direct retry eligibility -**Usage:** -- `get direct.retry.margin` -- `set direct.retry.margin ` - -**Parameters:** -- `value`: Rooftop preset margin in dB above the SF-specific receive floor (minimum `0`, maximum `40`, quarter-dB precision, default `5.0`) - -**Default:** `5.0` - -**Note:** `get direct.retry.margin` returns the active preset's effective margin. The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin. - ---- - -#### View or change the retry preset -**Usage:** -- `get retry.preset` -- `set retry.preset ` - -**Parameters:** -- `value`: `infra`|`rooftop`|`mobile` or `0`|`1`|`2` - -**Default:** `rooftop` (`1`) - -**Presets:** -- `infra` (`0`): `275 ms` direct base wait, `4` direct retries, `150 ms` added per direct retry, SNR gate is SF floor + `15 dB`; flood retry defaults to `1` retry and path gate `1` -- `rooftop` (`1`): `175 ms` direct base wait, `15` direct retries, `100 ms` added per direct retry, SNR gate is SF floor + `5 dB`; flood retry defaults to `3` retries and path gate `2` -- `mobile` (`2`): `175 ms` direct base wait, `15` direct retries, `50 ms` added per direct retry, SNR gate is the SF floor; flood retry defaults to `3` retries and path gate `1` - -**Note:** Selecting a preset copies those values into the direct retry settings and resets flood retry defaults. You can refine `direct.retry.margin`, `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `flood.retry.count`, or `flood.retry.path` afterward. Retry delay is `direct.txdelay` jitter + base wait + packet-length airtime wait + per-attempt step. - ---- - -#### View or change the number of direct retry attempts -**Usage:** -- `get direct.retry.count` -- `set direct.retry.count ` - -**Parameters:** -- `value`: Maximum retry attempts after initial TX (`1`-`15`) - -**Default:** `15` - -**Note:** The effective value is capped by total direct path length: paths of `3` hops or less use at most `8` retries, `4` hops use at most `12`, and `5+` hops use at most `15`. A queued resend is canceled early when the next-hop echo is heard. - ---- - -#### View or change the base direct retry wait (milliseconds) -**Usage:** -- `get direct.retry.base` -- `set direct.retry.base ` - -**Parameters:** -- `value`: Base wait in milliseconds (`10`-`5000`) - -**Default:** `175` - -**Note:** The configured base is added to packet-length airtime and `direct.txdelay` jitter. Preset defaults are already reduced to account for the added `direct.txdelay` component. - ---- - -#### View or change the direct retry per-attempt add time (milliseconds) -**Usage:** -- `get direct.retry.step` -- `set direct.retry.step ` - -**Parameters:** -- `value`: Milliseconds added per retry attempt (`0`-`5000`) - -**Default:** `100` - -**Note:** This controls the linear add after the first retry wait. For example, `base=300` and `step=150` adds `0`, `150`, `300`, ... ms across retry attempts. +**Note:** Same collision-avoidance random window as `txdelay`, but applied to direct (non-flood, routed) traffic. The default is lower because direct packets are addressed to a specific next hop, so far fewer nodes compete to retransmit them. --- @@ -727,6 +523,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `0.0` +**Note:** When enabled, repeaters that received a flood packet with a weak signal are held in a delay queue before processing, while those that received it with a strong signal process it immediately. This gives strong-signal paths forwarding priority. By the time weak-signal nodes process their copy, the packet may have already propagated and will be suppressed as a duplicate, reducing redundant retransmissions. + --- #### View or change the duty cycle limit @@ -854,114 +652,18 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or change the number of flood retry attempts +#### Limit the number of hops for an unscoped flood message **Usage:** -- `get flood.retry.count` -- `set flood.retry.count ` +- `get flood.max.unscoped` +- `set flood.max.unscoped ` **Parameters:** -- `value`: Maximum retry attempts after initial flood TX (`0`-`15`) +- `value`: Maximum flood hop count (0-64) for a packet without a scope (no region set) -**Default:** `3` for `rooftop` and `mobile`, `1` for `infra` +**Default:** `0xFF` - indicates it hasn't been set, will track flood.max until it is. -**Note:** `0` disables flood retry. +**Note:** An alternative to `region denyf *`, setting `flood.max.unscoped` to a lower value such as `3` would allow for local unscoped messages to propagate, while preventing noisy neighbors from flooding a local region. ---- - -#### View or change the flood retry path gate -**Usage:** -- `get flood.retry.path` -- `set flood.retry.path ` - -**Parameters:** -- `value`: Maximum flood path length eligible for retry (`0`-`63`), or `off` to disable the gate - -**Default:** `2` for `rooftop`, `1` for `infra` and `mobile` - -**Note:** Prefixes in `flood.retry.ignore` do not count toward this path length. - ---- - -#### View or change whether advert packets are flood-retried -**Usage:** -- `get flood.retry.advert` -- `set flood.retry.advert ` - -**Parameters:** -- `state`: `on` or `off` - -**Default:** `off` - -**Note:** When this is `off`, node advert packets (`PAYLOAD_TYPE_ADVERT`, type `4`) are not queued for flood retry. - ---- - -#### View or change flood retry target prefixes -**Usage:** -- `get flood.retry.prefixes` -- `set flood.retry.prefixes ` - -**Parameters:** -- `prefixes`: Comma-separated 3-byte hex prefixes, such as `A1B2C3,D4E5F6`; use `none` or `off` to clear - -**Default:** `none` - -**Note:** Prefixes are stored as 3 bytes. Flood retry skips packets whose path already contains a matching target prefix. When prefixes are configured, only a downstream echo from one of those target prefixes cancels a queued retry; when no prefixes are configured, any downstream echo cancels it. Matching works with 3-byte, 2-byte, or 1-byte flood paths by comparing the matching leading bytes. - ---- - -#### View or change flood retry bridge mode -**Usage:** -- `get flood.retry.bridge` -- `set flood.retry.bridge ` - -**Parameters:** -- `state`: `on` or `off` - -**Default:** `off` - -**Note:** Bridge mode uses bucket definitions instead of the single `flood.retry.prefixes` target list. It also has an implicit unconfigured catch-all bucket. If a flood comes from one fresh configured bucket, retry continues until every other fresh configured bucket plus the catch-all bucket has been heard or `flood.retry.count` is exhausted. If a flood comes from an unconfigured or pathless source, retry targets every fresh configured bucket. This means one configured bucket bridges between that bucket and everything else. Prefixes in `flood.retry.ignore` never count as heard bridge targets. - ---- - -#### View or change flood retry bridge buckets -**Usage:** -- `get flood.retry.bucket.` -- `set flood.retry.bucket ` - -**Parameters:** -- `bucket`: Bucket number (`1`-`6`) -- `prefixes`: Up to 17 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear - -**Default:** all buckets empty - -**Note:** Prefixes are stored as 3 bytes but match 3-byte, 2-byte, and 1-byte flood paths by comparing leading bytes. Bucket prefixes are included in bridge retry logic only if they were heard in the recent repeater table within the last hour. - ---- - -#### View or change flood retry ignored prefixes -**Usage:** -- `get flood.retry.ignore` -- `set flood.retry.ignore ` - -**Parameters:** -- `prefixes`: Up to 8 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear - -**Default:** empty - -**Note:** In non-bridge retry, an echo whose last hop matches an ignored prefix does not cancel a queued retry as successful. In bridge mode, ignored prefixes do not count as a heard bridge bucket or as the implicit catch-all bucket when bridge retry decides whether every target has repeated the flood. - ---- - -#### Limit the number of hops for an advert flood message -**Usage:** -- `get flood.max.advert` -- `set flood.max.advert ` - -**Parameters:** -- `value`: Maximum flood hop count (0-64) for an advert packet - -**Default:** `8` --- @@ -991,32 +693,6 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or set direct path overrides for the current remote client -**Usage:** -- `get outpath` -- `set outpath ` -- `set outpath direct` -- `set outpath clear` -- `set outpath flood` -- `get altpath` -- `set altpath ` -- `set altpath clear` - -**Parameters:** -- `hopN_hex`: Hop hash, `2`, `4`, or `6` hex characters. All hops must use the same width. - -**Notes:** -- These commands require remote client context; they target the caller's ACL entry. -- The path hash size is inferred from the hop hash width. -- `outpath` overrides the primary direct route used for replies to the caller. -- `direct` sets a zero-hop direct route for a caller reachable without repeaters. -- `clear` forgets the current direct path and allows normal path discovery to repopulate it. -- `flood` forces replies to use flood packets until the client logs in again. -- `altpath` is an optional second direct route used for duplicate response attempts. -- `set altpath clear` removes the duplicate route so only one reply is sent. - ---- - #### View or change this room server's 'read-only' flag **Usage:** - `get allow.read.only` @@ -1104,27 +780,6 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -#### View or set the direct path override for the current remote client -**Usage:** -- `get outpath` -- `set outpath ` -- `set outpath direct` -- `set outpath clear` -- `set outpath flood` - -**Parameters:** -- `hopN_hex`: Hop hash, `2`, `4`, or `6` hex characters. All hops must use the same width. - -**Notes:** -- These commands require remote client context (they target the caller's ACL entry). -- The path hash size is inferred from the hop hash width. -- `outpath` overrides the primary direct route used for replies to the caller. -- `direct` sets a zero-hop direct route for a caller reachable without repeaters. -- `clear` forgets the current direct path and allows normal path discovery to repopulate it. -- `flood` forces replies to use flood packets until the client logs in again. - ---- - #### Create a new region **Usage:** - `region put [parent_name]` @@ -1135,6 +790,47 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### Define region hierarchy (single line) +**Usage:** +- `region def [ ...]` + +**Parameters (tokens):** Space-separated. A logical **cursor** starts at the wildcard `*`. + +- **`name`** — Create `name` as a child of the current cursor (equivalent to `region put name` with the cursor as parent). Cursor moves to `name`. +- **`name|jump`** *(or `name,jump`)* — Create `name` as a child of the current cursor, then move the cursor to `jump` (must already exist on the node, or have been created earlier in this command). `jump` is **not** the parent of `name`; use this form to pop back up and start another branch. + +**Behavior:** Each created region defaults to flood-allowed (same as `region put`). The reply is the resulting region tree (same format as bare `region`); review it before running `region save` to persist. On error, the reply is `Err - ...` and any regions placed before the failure remain on the node, just like a partial chain of `region put`. + +**Existing regions:** `region def` does not clear the existing tree — if a name already exists, its parent is updated to the current cursor; otherwise a new region is created. To start from scratch, `region remove` the unwanted regions first. + +**Limits:** Repeater serial accepts one line up to **160 characters**. For larger trees, split across multiple `region def` commands; the cursor resets to `*` between commands, so lead the next command with `child|ancestor` to reposition. Each token splits at most once on `|` — `region def a|b|c|d` is not a flat-list shorthand; see the flat-list example below. + +**Example — linear chain** (each token becomes a child of the previous): +``` +region def a b c d e +region save +``` + +**Example — branched tree** (equivalent to `region put a`, `region put b a`, `region put c b`, `region put d c`, `region put e b`, `region put f e`): +``` +region def a b c d|b e f +region save +``` + +**Example — error and partial state:** +``` +region def a b c|nope d +``` +The reply is `Err - unknown jump: nope`. `a`, `b`, and `c` were placed before the failure; `d` was not. Run `region` to inspect, then re-run with a corrected jump or repair with `region remove` / `region put`. + +**Example — flat list** (each region a child of `*`). Use `|*` after each token to pop the cursor back to the root before the next token: +``` +region def a|* b|* c|* d|* e|* f +region save +``` + +--- + #### Remove a region **Usage:** - `region remove ` @@ -1155,7 +851,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Parameters:** - `filter`: `allowed`|`denied` -**Note:** Requires firmware 1.12.+ +**Note:** Requires firmware 1.12+ --- @@ -1312,7 +1008,7 @@ region save --- -#### View or change thevalue of a sensor +#### View or change the value of a sensor **Usage:** - `sensor get ` - `sensor set ` @@ -1432,3 +1128,71 @@ region save **Note:** Returns an error on boards without power management support. --- + +--- + +## Halo Direct Retry Commands + +These commands are available on the Halo direct-message retry branch. See `docs/halo_settings.md` for operating guidance and examples. + +### View or change the retry preset +**Usage:** +- `get retry.preset` +- `set retry.preset ` + +**Parameters:** +- `value`: `infra`|`rooftop`|`mobile` or `0`|`1`|`2` + +--- + +### View or change whether direct retries use the recent repeater SNR gate +**Usage:** +- `get direct.retry.heard` +- `set direct.retry.heard ` + +**Parameters:** +- `state`: `on`|`off` + +--- + +### View or change direct retry timing and count +**Usage:** +- `get direct.retry.margin` +- `set direct.retry.margin ` +- `get direct.retry.count` +- `set direct.retry.count <1-15>` +- `get direct.retry.base` +- `set direct.retry.base <10-5000>` +- `get direct.retry.step` +- `set direct.retry.step <0-5000>` + +--- + +### View or change adaptive coding rate for direct retry packets +**Usage:** +- `get direct.retry.cr` +- `set direct.retry.cr ,,,` +- `set direct.retry.cr off` + +--- + +### Get or set recent repeater prefix/SNR +**Usage:** +- `get recent.repeater` +- `get recent.repeater ` +- `get recent.repeater page ` +- `set recent.repeater ` +- `clear recent.repeater` + +--- + +### View or change direct reply path overrides +**Usage:** +- `get outpath` +- `set outpath ` +- `set outpath direct` +- `set outpath clear` +- `set outpath flood` +- `get altpath` +- `set altpath ` +- `set altpath clear` diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 36c66c3f..bb39c077 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -85,9 +85,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint32_t last_millis; uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; - unsigned long next_battery_alert_check; - unsigned long last_battery_alert_sent; - bool battery_alert_sent; bool _logging; NodePrefs _prefs; ClientACL acl; @@ -102,15 +99,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionEntry* recv_pkt_region; TransportKey default_scope; RateLimiter discover_limiter, anon_limiter; - struct FloodRetryBridgeState { - uint8_t key[MAX_HASH_SIZE]; - uint8_t source_bucket; - uint8_t target_mask; - uint8_t heard_mask; - uint8_t progress_marker; - bool active; - }; - mutable FloodRetryBridgeState flood_retry_bridge_states[MAX_FLOOD_RETRY_SLOTS]; uint32_t pending_discover_tag; unsigned long pending_discover_until; bool region_load_active; @@ -140,25 +128,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t getDirectRetryPreset() const; uint8_t getDirectRetryConfiguredMaxAttempts() const; uint32_t getDirectRetryAttemptStepMillis() const; - bool hasFloodRetryPrefixes() const; - bool floodRetryPrefixMatches(const mesh::Packet* packet) const; - bool floodRetryLastHopMatches(const mesh::Packet* packet) const; - bool floodRetryPrefixIgnored(const uint8_t* prefix, uint8_t prefix_len) const; - uint8_t floodRetryEffectivePathLength(const mesh::Packet* packet, uint8_t max_hops = 0xFF) const; - bool floodRetryPrefixFresh(const uint8_t* prefix, uint8_t prefix_len) const; - int floodRetryBucketForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh, - bool include_other) const; - int floodRetryBucketForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop, - uint8_t progress_marker) const; - int floodRetrySourceBucket(const mesh::Packet* packet) const; - uint8_t floodRetryBridgeTargetMask(uint8_t source_bucket) const; - uint8_t floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_bucket, - uint8_t progress_marker) const; - FloodRetryBridgeState* floodRetryBridgeStateFor(const mesh::Packet* packet, bool create) const; - void clearFloodRetryBridgeState(const mesh::Packet* packet); - void refreshFloodRetryHeardRecent(const mesh::Packet* packet); - void formatFloodRetryPath(char* dest, size_t dest_len, const mesh::Packet* packet) const; - bool formatFloodRetryHeard(char* dest, size_t dest_len, const mesh::Packet* packet) const; void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); @@ -166,8 +135,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); - bool sendRepeatersFloodText(const char* text); - void checkBatteryAlert(); File openAppend(const char* fname); bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]); @@ -196,12 +163,6 @@ protected: uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override; uint32_t getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) override; void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override; - bool allowFloodRetry(const mesh::Packet* packet) const override; - void onFloodRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override; - bool hasFloodRetryTargetPrefix(const mesh::Packet* packet) const override; - uint8_t getFloodRetryMaxPathLength(const mesh::Packet* packet) const override; - uint8_t getFloodRetryMaxAttempts(const mesh::Packet* packet) const override; - bool isFloodRetryEchoTarget(const mesh::Packet* packet, uint8_t progress_marker) const override; int getInterferenceThreshold() const override { return _prefs.interference_threshold; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index b9b1ab4d..03a89dd4 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -159,9 +159,4 @@ void loop() { } #endif } - - if (the_mesh.getNodePrefs()->reboot_interval > 0 && - the_mesh.millisHasNowPassed(the_mesh.getNodePrefs()->reboot_interval * 3600000)) { - board.reboot(); - } } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 4dccd45a..a9f7aac0 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -5,8 +5,6 @@ namespace mesh { static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 15; static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX = 15; -static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT = 3; -static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX = 15; static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) { uint8_t code = flags & 0x03; @@ -44,19 +42,6 @@ void Mesh::begin() { _direct_retries[i].queued = false; _direct_retries[i].active = false; } - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - _flood_retries[i].packet = NULL; - _flood_retries[i].trigger_packet = NULL; - _flood_retries[i].retry_started_at = 0; - _flood_retries[i].retry_at = 0; - _flood_retries[i].retry_delay = 0; - _flood_retries[i].retry_attempts_sent = 0; - _flood_retries[i].priority = 0; - _flood_retries[i].progress_marker = 0; - _flood_retries[i].waiting_final_echo = false; - _flood_retries[i].queued = false; - _flood_retries[i].active = false; - } Dispatcher::begin(); } @@ -93,37 +78,6 @@ void Mesh::loop() { clearDirectRetrySlot(i); } } - - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - if (!_flood_retries[i].active) { - continue; - } - - if (_flood_retries[i].waiting_final_echo) { - if (!millisHasNowPassed(_flood_retries[i].retry_at)) { - continue; - } - - uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0 - ? 0 - : (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at); - onFloodRetryEvent("failed_all_tries", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent); - onFloodRetryEvent("failure", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent); - clearFloodRetrySlot(i); - continue; - } - - if (!_flood_retries[i].queued || !millisHasNowPassed(_flood_retries[i].retry_at)) { - continue; - } - - if (!isFloodRetryQueued(_flood_retries[i].packet)) { - if (_flood_retries[i].packet == getOutboundInFlight()) { - continue; - } - clearFloodRetrySlot(i); - } - } } bool Mesh::allowPacketForward(const mesh::Packet* packet) { @@ -152,39 +106,16 @@ uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_ // Keep the historical linear spacing while allowing the base wait to vary by platform/profile. return base + ((uint32_t)attempt_idx * 100UL); } -bool Mesh::allowFloodRetry(const Packet* packet) const { - return true; -} -bool Mesh::hasFloodRetryTargetPrefix(const Packet* packet) const { - return false; -} -uint8_t Mesh::getFloodRetryMaxPathLength(const Packet* packet) const { - return 2; -} -uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const { - return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT; -} -uint32_t Mesh::getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) { - if (packet == NULL) { - return _radio->getEstAirtimeFor(MAX_TRANS_UNIT); - } - - uint32_t max_packet_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT); - uint32_t packet_airtime = _radio->getEstAirtimeFor(packet->getRawLength()); - return max_packet_airtime + (20UL * packet_airtime); -} uint8_t Mesh::getExtraAckTransmitCount() const { return 0; } void Mesh::onSendComplete(Packet* packet) { armDirectRetryOnSendComplete(packet); - armFloodRetryOnSendComplete(packet); } void Mesh::onSendFail(Packet* packet) { clearPendingDirectRetryOnSendFail(packet); - clearPendingFloodRetryOnSendFail(packet); } uint32_t Mesh::getCADFailRetryDelay() const { @@ -202,8 +133,6 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->isRouteDirect()) { cancelDirectRetryOnEcho(pkt); - } else if (pkt->isRouteFlood()) { - cancelFloodRetryOnEcho(pkt); } if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { @@ -523,7 +452,6 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) { uint32_t d = getRetransmitDelay(packet); uint8_t priority = packet->getPathHashCount(); - maybeScheduleFloodRetry(packet, priority); // as this propagates outwards, give it lower and lower priority return ACTION_RETRANSMIT_DELAYED(priority, d); // give priority to closer sources, than ones further away } @@ -952,242 +880,6 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) { _direct_retries[slot_idx].active = true; } -void Mesh::clearFloodRetrySlot(int idx) { - if (_flood_retries[idx].waiting_final_echo && _flood_retries[idx].packet != NULL) { - releasePacket(_flood_retries[idx].packet); - } - _flood_retries[idx].packet = NULL; - _flood_retries[idx].trigger_packet = NULL; - _flood_retries[idx].retry_started_at = 0; - _flood_retries[idx].retry_at = 0; - _flood_retries[idx].retry_delay = 0; - _flood_retries[idx].retry_attempts_sent = 0; - _flood_retries[idx].priority = 0; - _flood_retries[idx].progress_marker = 0; - _flood_retries[idx].waiting_final_echo = false; - _flood_retries[idx].queued = false; - _flood_retries[idx].active = false; -} - -bool Mesh::isFloodRetryQueued(const Packet* packet) const { - for (int i = 0; i < _mgr->getOutboundTotal(); i++) { - if (_mgr->getOutboundByIdx(i) == packet) { - return true; - } - } - return false; -} - -bool Mesh::isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const { - return packet->isRouteFlood() && packet->getPathHashCount() > progress_marker; -} - -bool Mesh::cancelFloodRetryOnEcho(const Packet* packet) { - uint8_t recv_key[MAX_HASH_SIZE]; - packet->calculatePacketHash(recv_key); - - bool cleared = false; - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - if (!_flood_retries[i].active || memcmp(recv_key, _flood_retries[i].retry_key, MAX_HASH_SIZE) != 0) { - continue; - } - if (!isFloodRetryEchoTarget(packet, _flood_retries[i].progress_marker)) { - continue; - } - - uint32_t echo_millis = _flood_retries[i].retry_started_at == 0 - ? 0 - : (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at); - uint8_t retry_attempt = _flood_retries[i].waiting_final_echo - ? _flood_retries[i].retry_attempts_sent - : _flood_retries[i].retry_attempts_sent + 1; - onFloodRetryEvent("good", packet, echo_millis, retry_attempt); - - if (_flood_retries[i].queued) { - for (int j = 0; j < _mgr->getOutboundTotal(); j++) { - if (_mgr->getOutboundByIdx(j) == _flood_retries[i].packet) { - Packet* pending = _mgr->removeOutboundByIdx(j); - if (pending) { - releasePacket(pending); - } - break; - } - } - } - clearFloodRetrySlot(i); - cleared = true; - } - - return cleared; -} - -void Mesh::armFloodRetryOnSendComplete(const Packet* packet) { - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - if (!_flood_retries[i].active) { - continue; - } - - if (_flood_retries[i].queued) { - if (_flood_retries[i].packet != packet) { - continue; - } - - uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0 - ? 0 - : (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at); - onFloodRetryEvent("resent", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1); - _flood_retries[i].retry_attempts_sent++; - - uint8_t max_attempts = getFloodRetryMaxAttempts(packet); - if (max_attempts < 1) { - max_attempts = 1; - } else if (max_attempts > FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX) { - max_attempts = FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX; - } - if (_flood_retries[i].retry_attempts_sent >= max_attempts) { - Packet* final_wait = obtainNewPacket(); - if (final_wait == NULL) { - onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent); - onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent); - clearFloodRetrySlot(i); - continue; - } - - *final_wait = *packet; - _flood_retries[i].packet = final_wait; - _flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay); - _flood_retries[i].waiting_final_echo = true; - _flood_retries[i].queued = false; - continue; - } - - Packet* retry = obtainNewPacket(); - if (retry == NULL) { - onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1); - onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1); - clearFloodRetrySlot(i); - continue; - } - - *retry = *packet; - uint32_t retry_delay = getFloodRetryAttemptDelay(packet, _flood_retries[i].retry_attempts_sent); - if (queueOutboundPacket(retry, _flood_retries[i].priority, retry_delay)) { - _flood_retries[i].packet = retry; - _flood_retries[i].retry_delay = retry_delay; - _flood_retries[i].retry_at = futureMillis(retry_delay); - _flood_retries[i].retry_started_at = _ms->getMillis(); - _flood_retries[i].waiting_final_echo = false; - onFloodRetryEvent("queued", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1); - } else { - onFloodRetryEvent("dropped_queue_full", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1); - onFloodRetryEvent("failure", retry, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1); - releasePacket(retry); - clearFloodRetrySlot(i); - } - continue; - } - - if (_flood_retries[i].trigger_packet != packet) { - continue; - } - - Packet* retry = obtainNewPacket(); - if (retry == NULL) { - onFloodRetryEvent("dropped_no_packet", packet, _flood_retries[i].retry_delay, 1); - onFloodRetryEvent("failure", packet, 0, 1); - clearFloodRetrySlot(i); - continue; - } - - *retry = *packet; - if (queueOutboundPacket(retry, _flood_retries[i].priority, _flood_retries[i].retry_delay)) { - unsigned long now = _ms->getMillis(); - _flood_retries[i].packet = retry; - _flood_retries[i].trigger_packet = NULL; - _flood_retries[i].queued = true; - _flood_retries[i].waiting_final_echo = false; - _flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay); - _flood_retries[i].retry_started_at = now; - onFloodRetryEvent("queued", retry, _flood_retries[i].retry_delay, 1); - } else { - onFloodRetryEvent("dropped_queue_full", retry, _flood_retries[i].retry_delay, 1); - onFloodRetryEvent("failure", retry, 0, 1); - releasePacket(retry); - clearFloodRetrySlot(i); - } - } -} - -void Mesh::clearPendingFloodRetryOnSendFail(const Packet* packet) { - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - if (!_flood_retries[i].active) { - continue; - } - - if (_flood_retries[i].queued) { - if (_flood_retries[i].packet == packet) { - onFloodRetryEvent("dropped_send_fail", packet, 0, _flood_retries[i].retry_attempts_sent + 1); - onFloodRetryEvent("failure", packet, 0, _flood_retries[i].retry_attempts_sent + 1); - clearFloodRetrySlot(i); - } - continue; - } - - if (_flood_retries[i].trigger_packet == packet) { - onFloodRetryEvent("dropped_send_fail", packet, 0, 1); - onFloodRetryEvent("failure", packet, 0, 1); - clearFloodRetrySlot(i); - } - } -} - -void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) { - if (packet == NULL || !packet->isRouteFlood() || hasFloodRetryTargetPrefix(packet)) { - return; - } - - uint8_t max_path_len = getFloodRetryMaxPathLength(packet); - if (max_path_len != FLOOD_RETRY_PATH_GATE_DISABLED && packet->getPathHashCount() > max_path_len) { - return; - } - - uint8_t max_attempts = getFloodRetryMaxAttempts(packet); - if (max_attempts == 0) { - return; - } - - int slot_idx = -1; - for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) { - if (!_flood_retries[i].active) { - slot_idx = i; - break; - } - } - if (slot_idx < 0) { - onFloodRetryEvent("dropped_no_slot", packet, 0, 0); - onFloodRetryEvent("failure", packet, 0, 0); - return; - } - - if (!allowFloodRetry(packet)) { - return; - } - - uint32_t retry_delay = getFloodRetryAttemptDelay(packet, 0); - packet->calculatePacketHash(_flood_retries[slot_idx].retry_key); - _flood_retries[slot_idx].packet = NULL; - _flood_retries[slot_idx].trigger_packet = const_cast(packet); - _flood_retries[slot_idx].retry_started_at = 0; - _flood_retries[slot_idx].retry_at = 0; - _flood_retries[slot_idx].retry_delay = retry_delay; - _flood_retries[slot_idx].retry_attempts_sent = 0; - _flood_retries[slot_idx].priority = priority; - _flood_retries[slot_idx].progress_marker = packet->getPathHashCount(); - _flood_retries[slot_idx].waiting_final_echo = false; - _flood_retries[slot_idx].queued = false; - _flood_retries[slot_idx].active = true; -} - Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) { if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL; diff --git a/src/Mesh.h b/src/Mesh.h index 6ae26642..314db237 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -8,14 +8,6 @@ namespace mesh { #define MAX_DIRECT_RETRY_SLOTS 6 #endif -#ifndef MAX_FLOOD_RETRY_SLOTS - #define MAX_FLOOD_RETRY_SLOTS 6 -#endif - -#ifndef FLOOD_RETRY_PATH_GATE_DISABLED - #define FLOOD_RETRY_PATH_GATE_DISABLED 0xFF -#endif - class GroupChannel { public: uint8_t hash[PATH_HASH_SIZE]; @@ -54,26 +46,7 @@ class Mesh : public Dispatcher { bool active; }; - struct FloodRetryEntry { - Packet* packet; - Packet* trigger_packet; - unsigned long retry_started_at; - unsigned long retry_at; - uint32_t retry_delay; - uint8_t retry_attempts_sent; - uint8_t retry_key[MAX_HASH_SIZE]; - uint8_t priority; - uint8_t progress_marker; - bool waiting_final_echo; - bool queued; - bool active; - }; - - RTCClock* _rtc; - RNG* _rng; - MeshTables* _tables; DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS]; - FloodRetryEntry _flood_retries[MAX_FLOOD_RETRY_SLOTS]; void removePathPrefix(Packet* packet, uint8_t prefix_count); void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis); @@ -87,12 +60,6 @@ class Mesh : public Dispatcher { uint8_t& progress_marker, bool& expect_path_growth) const; bool canDecodeDirectPayloadForSelf(const Packet* packet); void maybeScheduleDirectRetry(const Packet* packet, uint8_t priority); - void clearFloodRetrySlot(int idx); - bool isFloodRetryQueued(const Packet* packet) const; - bool cancelFloodRetryOnEcho(const Packet* packet); - void armFloodRetryOnSendComplete(const Packet* packet); - void clearPendingFloodRetryOnSendFail(const Packet* packet); - void maybeScheduleFloodRetry(const Packet* packet, uint8_t priority); //void routeRecvAcks(Packet* packet, uint32_t delay_millis); DispatcherAction forwardMultipartDirect(Packet* pkt); @@ -156,41 +123,6 @@ protected: */ virtual uint32_t getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx); - /** - * \brief Decide whether a FLOOD packet should retry when no downstream echo is overheard. - */ - virtual bool allowFloodRetry(const Packet* packet) const; - - /** - * \brief Return true when this FLOOD packet already carries an application-defined target prefix. - */ - virtual bool hasFloodRetryTargetPrefix(const Packet* packet) const; - - /** - * \returns maximum flood path hash count eligible for retry, or FLOOD_RETRY_PATH_GATE_DISABLED. - */ - virtual uint8_t getFloodRetryMaxPathLength(const Packet* packet) const; - - /** - * \returns maximum number of FLOOD retry transmissions after the initial TX. - */ - virtual uint8_t getFloodRetryMaxAttempts(const Packet* packet) const; - - /** - * \brief Return true when a received FLOOD echo is enough to cancel a pending retry. - */ - virtual bool isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const; - - /** - * \returns delay before a specific flood retry attempt, where attempt_idx=0 is the first retry. - */ - virtual uint32_t getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx); - - /** - * \brief Optional hook for logging flood-retry lifecycle events. - */ - virtual void onFloodRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { } - /** * \returns number of extra (Direct) ACK transmissions wanted. */