mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-07 01:03:34 +02:00
Hopefully once and for all, use smarter pathing data on all direct contacts
This commit is contained in:
+4
-1
@@ -68,6 +68,7 @@ class PacketInfo:
|
||||
payload_type: PayloadType
|
||||
payload_version: int
|
||||
path_length: int
|
||||
path: bytes # The routing path (empty if path_length is 0)
|
||||
payload: bytes
|
||||
|
||||
|
||||
@@ -149,9 +150,10 @@ def parse_packet(raw_packet: bytes) -> PacketInfo | None:
|
||||
path_length = raw_packet[offset]
|
||||
offset += 1
|
||||
|
||||
# Skip path data
|
||||
# Extract path data
|
||||
if len(raw_packet) < offset + path_length:
|
||||
return None
|
||||
path = raw_packet[offset:offset + path_length]
|
||||
offset += path_length
|
||||
|
||||
# Rest is payload
|
||||
@@ -162,6 +164,7 @@ def parse_packet(raw_packet: bytes) -> PacketInfo | None:
|
||||
payload_type=payload_type,
|
||||
payload_version=payload_version,
|
||||
path_length=path_length,
|
||||
path=path,
|
||||
payload=payload,
|
||||
)
|
||||
except (ValueError, IndexError):
|
||||
|
||||
+22
-8
@@ -19,8 +19,8 @@ import time
|
||||
from app.decoder import (
|
||||
PayloadType,
|
||||
parse_packet,
|
||||
parse_advertisement,
|
||||
try_decrypt_packet_with_channel_key,
|
||||
try_parse_advertisement,
|
||||
)
|
||||
from app.models import CONTACT_TYPE_REPEATER, RawPacketBroadcast, RawPacketDecryptedInfo
|
||||
from app.repository import ChannelRepository, ContactRepository, MessageRepository, RawPacketRepository
|
||||
@@ -176,7 +176,7 @@ async def process_raw_packet(
|
||||
result.update(decrypt_result)
|
||||
|
||||
elif payload_type == PayloadType.ADVERT:
|
||||
await _process_advertisement(raw_bytes, ts)
|
||||
await _process_advertisement(raw_bytes, ts, packet_info)
|
||||
|
||||
# TODO: Add TEXT_MESSAGE (direct message) decryption when private key is available
|
||||
# elif payload_type == PayloadType.TEXT_MESSAGE:
|
||||
@@ -288,6 +288,7 @@ async def _process_group_text(
|
||||
async def _process_advertisement(
|
||||
raw_bytes: bytes,
|
||||
timestamp: int,
|
||||
packet_info=None,
|
||||
) -> None:
|
||||
"""
|
||||
Process an advertisement packet.
|
||||
@@ -295,14 +296,25 @@ async def _process_advertisement(
|
||||
Extracts contact info and updates the database/broadcasts to clients.
|
||||
For non-repeater contacts, triggers sync of recent contacts to radio for DM ACK support.
|
||||
"""
|
||||
advert = try_parse_advertisement(raw_bytes)
|
||||
if not advert:
|
||||
# Parse packet to get path info if not already provided
|
||||
if packet_info is None:
|
||||
packet_info = parse_packet(raw_bytes)
|
||||
if packet_info is None:
|
||||
logger.debug("Failed to parse advertisement packet")
|
||||
return
|
||||
|
||||
advert = parse_advertisement(packet_info.payload)
|
||||
if not advert:
|
||||
logger.debug("Failed to parse advertisement payload")
|
||||
return
|
||||
|
||||
# Extract path info from packet
|
||||
path_len = packet_info.path_length
|
||||
path_hex = packet_info.path.hex() if packet_info.path else ""
|
||||
|
||||
logger.debug(
|
||||
"Parsed advertisement from %s: %s (role=%d, lat=%s, lon=%s)",
|
||||
advert.public_key[:12], advert.name, advert.device_role, advert.lat, advert.lon
|
||||
"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
|
||||
@@ -320,6 +332,8 @@ async def _process_advertisement(
|
||||
"lon": advert.lon,
|
||||
"last_advert": advert.timestamp if advert.timestamp > 0 else timestamp,
|
||||
"last_seen": timestamp,
|
||||
"last_path": path_hex,
|
||||
"last_path_len": path_len,
|
||||
}
|
||||
|
||||
await ContactRepository.upsert(contact_data)
|
||||
@@ -330,8 +344,8 @@ async def _process_advertisement(
|
||||
"name": advert.name,
|
||||
"type": contact_type,
|
||||
"flags": existing.flags if existing else 0,
|
||||
"last_path": existing.last_path if existing else None,
|
||||
"last_path_len": existing.last_path_len if existing else -1,
|
||||
"last_path": path_hex,
|
||||
"last_path_len": path_len,
|
||||
"last_advert": advert.timestamp if advert.timestamp > 0 else timestamp,
|
||||
"lat": advert.lat,
|
||||
"lon": advert.lon,
|
||||
|
||||
+12
-8
@@ -45,15 +45,17 @@ async def prepare_repeater_connection(mc, contact: Contact, password: str) -> No
|
||||
Raises:
|
||||
HTTPException: If login fails
|
||||
"""
|
||||
# Add contact to radio with flood mode
|
||||
logger.info("Adding repeater %s to radio", contact.public_key[:12])
|
||||
# Add contact to radio with path from DB (or flood if no path known)
|
||||
path_len = contact.last_path_len if contact.last_path_len >= 0 else -1
|
||||
path_str = "direct" if path_len == 0 else f"{path_len} hops" if path_len > 0 else "flood"
|
||||
logger.info("Adding repeater %s to radio (%s)", contact.public_key[:12], path_str)
|
||||
contact_data = {
|
||||
"public_key": contact.public_key,
|
||||
"adv_name": contact.name or "",
|
||||
"type": contact.type,
|
||||
"flags": contact.flags,
|
||||
"out_path": "",
|
||||
"out_path_len": -1, # Flood mode
|
||||
"out_path": contact.last_path or "",
|
||||
"out_path_len": path_len,
|
||||
"adv_lat": contact.lat or 0.0,
|
||||
"adv_lon": contact.lon or 0.0,
|
||||
"last_advert": contact.last_advert or 0,
|
||||
@@ -380,15 +382,17 @@ async def send_repeater_command(public_key: str, request: CommandRequest) -> Com
|
||||
|
||||
# Pause message polling to prevent it from stealing our response
|
||||
async with pause_polling():
|
||||
# Add contact to radio with flood mode
|
||||
logger.info("Adding repeater %s to radio", contact.public_key[:12])
|
||||
# Add contact to radio with path from DB (or flood if no path known)
|
||||
path_len = contact.last_path_len if contact.last_path_len >= 0 else -1
|
||||
path_str = "direct" if path_len == 0 else f"{path_len} hops" if path_len > 0 else "flood"
|
||||
logger.info("Adding repeater %s to radio (%s)", contact.public_key[:12], path_str)
|
||||
contact_data = {
|
||||
"public_key": contact.public_key,
|
||||
"adv_name": contact.name or "",
|
||||
"type": contact.type,
|
||||
"flags": contact.flags,
|
||||
"out_path": "",
|
||||
"out_path_len": -1, # Flood mode
|
||||
"out_path": contact.last_path or "",
|
||||
"out_path_len": path_len,
|
||||
"adv_lat": contact.lat or 0.0,
|
||||
"adv_lon": contact.lon or 0.0,
|
||||
"last_advert": contact.last_advert or 0,
|
||||
|
||||
+10
-10
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-BqCjcbH8.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-B6Dj5zg2.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DZ67iE5i.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -204,6 +204,10 @@ class TestAdvertisementPipeline:
|
||||
assert contact.lon is not None
|
||||
assert abs(contact.lat - expected["lat"]) < 0.001
|
||||
assert abs(contact.lon - expected["lon"]) < 0.001
|
||||
# This advertisement has path_len=6 (6 hops through repeaters)
|
||||
assert contact.last_path_len == 6
|
||||
assert contact.last_path is not None
|
||||
assert len(contact.last_path) == 12 # 6 bytes = 12 hex chars
|
||||
|
||||
# Verify WebSocket broadcast
|
||||
contact_broadcasts = [b for b in broadcasts if b["type"] == "contact"]
|
||||
@@ -213,6 +217,7 @@ class TestAdvertisementPipeline:
|
||||
assert broadcast["data"]["public_key"] == expected["public_key"]
|
||||
assert broadcast["data"]["name"] == expected["name"]
|
||||
assert broadcast["data"]["type"] == expected["type"]
|
||||
assert broadcast["data"]["last_path_len"] == 6
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_advertisement_updates_existing_contact(self, test_db, captured_broadcasts):
|
||||
@@ -244,6 +249,10 @@ class TestAdvertisementPipeline:
|
||||
assert contact.type == expected["type"] # Type updated
|
||||
assert contact.lat is not None # GPS added
|
||||
assert contact.lon is not None
|
||||
# This advertisement has path_len=0 (direct neighbor)
|
||||
assert contact.last_path_len == 0
|
||||
# Empty path stored as None or ""
|
||||
assert contact.last_path in (None, "")
|
||||
|
||||
|
||||
class TestAckPipeline:
|
||||
|
||||
Reference in New Issue
Block a user