Always used shortest path in advert burst

This commit is contained in:
Jack Kingsman
2026-01-14 16:10:29 -08:00
parent e272be88ca
commit 92e7cd24e6
4 changed files with 126 additions and 16 deletions
+17 -11
View File
@@ -531,13 +531,12 @@ The `POST /api/contacts/{key}/telemetry` endpoint fetches status, neighbors, and
### Request Flow
1. Verify contact exists and is a repeater (type=2)
2. Sync contacts from radio with `ensure_contacts()`
3. Remove and re-add contact with flood mode (clears stale auth state)
4. Send login with password
5. Request status with retries (3 attempts, 10s timeout)
6. Fetch neighbors with `fetch_all_neighbours()` (handles pagination)
7. Fetch ACL with `req_acl_sync()`
8. Resolve pubkey prefixes to contact names from database
2. Add contact to radio with stored path data (from advertisements)
3. Send login with password
4. Request status with retries (3 attempts, 10s timeout)
5. Fetch neighbors with `fetch_all_neighbours()` (handles pagination)
6. Fetch ACL with `req_acl_sync()`
7. Resolve pubkey prefixes to contact names from database
### ACL Permission Levels
@@ -626,7 +625,14 @@ if txt_type == 1:
### Helper Function
`prepare_repeater_connection()` handles the login dance:
1. Sync contacts from radio
2. Remove contact if exists (clears stale auth)
3. Re-add with flood mode (`out_path_len=-1`)
4. Send login with password
1. Add contact to radio with stored path from DB (`out_path`, `out_path_len`)
2. Send login with password
3. Wait for key exchange to complete
### Contact Path Tracking
When advertisements are received, path data is extracted and stored:
- `last_path`: Hex string of routing path bytes
- `last_path_len`: Number of hops (-1=flood/unknown, 0=direct, >0=hops through repeaters)
**Shortest path selection**: When receiving echoed advertisements within 60 seconds, the shortest path is kept. This ensures we use the most efficient route when multiple paths exist.
+29 -5
View File
@@ -309,17 +309,41 @@ async def _process_advertisement(
return
# Extract path info from packet
path_len = packet_info.path_length
path_hex = packet_info.path.hex() if packet_info.path else ""
new_path_len = packet_info.path_length
new_path_hex = packet_info.path.hex() if packet_info.path else ""
# Try to find existing contact
existing = await ContactRepository.get_by_key(advert.public_key)
# Determine which path to use: keep shorter path if heard recently (within 60s)
# This handles advertisement echoes through different routes
PATH_FRESHNESS_SECONDS = 60
use_existing_path = False
if existing and existing.last_seen:
path_age = timestamp - existing.last_seen
existing_path_len = existing.last_path_len if existing.last_path_len >= 0 else float('inf')
# Keep existing path if it's fresh and shorter (or equal)
if path_age <= PATH_FRESHNESS_SECONDS and existing_path_len <= new_path_len:
use_existing_path = True
logger.debug(
"Keeping existing shorter path for %s (existing=%d, new=%d, age=%ds)",
advert.public_key[:12], existing_path_len, new_path_len, path_age
)
if use_existing_path:
path_len = existing.last_path_len
path_hex = existing.last_path or ""
else:
path_len = new_path_len
path_hex = new_path_hex
logger.debug(
"Parsed advertisement from %s: %s (role=%d, lat=%s, lon=%s, path_len=%d)",
advert.public_key[:12], advert.name, advert.device_role, advert.lat, advert.lon, path_len
)
# Try to find existing contact
existing = await ContactRepository.get_by_key(advert.public_key)
# Use device_role from advertisement for contact type (1=Chat, 2=Repeater, 3=Room, 4=Sensor)
# Use advert.timestamp for last_advert (sender's timestamp), receive timestamp for last_seen
contact_type = advert.device_role if advert.device_role > 0 else (existing.type if existing else 0)