feat(analyzer): compute pkt_payload from channel secrets for Analyzer URLs

meshcore v2 doesn't provide pkt_payload in events, so compute it
lazily in the API from channel secrets + message data. Analyzer URLs
now appear for ALL messages (own and incoming), not just own.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-02 07:38:44 +01:00
parent e37ab4243c
commit 499759931c
2 changed files with 59 additions and 8 deletions
+21
View File
@@ -25,6 +25,7 @@ def _to_str(val) -> str:
return str(val)
class DeviceManager:
"""
Manages MeshCore device connection.
@@ -47,6 +48,7 @@ class DeviceManager:
self._device_name = None
self._self_info = None
self._subscriptions = [] # active event subscriptions
self._channel_secrets = {} # {channel_idx: secret_hex} for pkt_payload
@property
def is_connected(self) -> bool:
@@ -176,6 +178,9 @@ class DeviceManager:
await self.mc.ensure_contacts()
self._sync_contacts_to_db()
# Cache channel secrets for pkt_payload computation
await self._load_channel_secrets()
# Start auto message fetching (events fire on new messages)
await self.mc.start_auto_message_fetching()
@@ -183,6 +188,22 @@ class DeviceManager:
logger.error(f"Device connection failed: {e}")
self._connected = False
async def _load_channel_secrets(self):
"""Load channel secrets from device for pkt_payload computation."""
try:
for idx in range(8): # MeshCore supports channels 0-7
event = await self.mc.commands.get_channel(idx)
if event:
data = getattr(event, 'payload', None) or {}
secret = data.get('channel_secret', data.get('secret', b''))
if isinstance(secret, bytes):
secret = secret.hex()
if secret and len(secret) == 32:
self._channel_secrets[idx] = secret
logger.info(f"Cached {len(self._channel_secrets)} channel secrets")
except Exception as e:
logger.error(f"Failed to load channel secrets: {e}")
async def _subscribe_events(self):
"""Subscribe to all relevant device events."""
from meshcore.events import EventType
+38 -8
View File
@@ -411,10 +411,39 @@ def get_messages():
days=days,
)
# Build channel secret lookup for pkt_payload computation
channel_secrets = {}
_, channels_list = get_channels_cached()
if channels_list:
for ch_info in channels_list:
ch_key = ch_info.get('key', '')
ch_idx = ch_info.get('index')
if ch_key and ch_idx is not None:
channel_secrets[ch_idx] = ch_key
# Convert DB rows to frontend-compatible format
messages = []
for row in db_messages:
pkt_payload = row.get('pkt_payload')
ch_idx = row.get('channel_idx', 0)
sender_ts = row.get('sender_timestamp')
txt_type = row.get('txt_type', 0)
# Compute pkt_payload if not stored (v2: meshcore doesn't provide it)
if not pkt_payload and sender_ts and ch_idx in channel_secrets:
# Reconstruct raw_text as firmware sends it: "SenderName: message"
sender = row.get('sender', '')
content = row.get('content', '')
is_own = bool(row.get('is_own', 0))
if is_own:
device_name = runtime_config.get_device_name() or ''
raw_text = f"{device_name}: {content}" if device_name else content
else:
raw_text = f"{sender}: {content}" if sender else content
pkt_payload = compute_pkt_payload(
channel_secrets[ch_idx], sender_ts, txt_type, raw_text
)
msg = {
'sender': row.get('sender', ''),
'content': row.get('content', ''),
@@ -423,19 +452,20 @@ def get_messages():
'is_own': bool(row.get('is_own', 0)),
'snr': row.get('snr'),
'path_len': row.get('path_len'),
'channel_idx': row.get('channel_idx', 0),
'sender_timestamp': row.get('sender_timestamp'),
'txt_type': row.get('txt_type', 0),
'channel_idx': ch_idx,
'sender_timestamp': sender_ts,
'txt_type': txt_type,
'raw_text': row.get('content', ''),
'pkt_payload': pkt_payload,
}
# Enrich own messages with echo data and analyzer URL
if msg['is_own'] and pkt_payload:
echoes = db.get_echoes_for_message(pkt_payload)
msg['echo_count'] = len(echoes)
msg['echo_paths'] = [e.get('path', '') for e in echoes]
# Enrich with echo data and analyzer URL
if pkt_payload:
msg['analyzer_url'] = compute_analyzer_url(pkt_payload)
if msg['is_own']:
echoes = db.get_echoes_for_message(pkt_payload)
msg['echo_count'] = len(echoes)
msg['echo_paths'] = [e.get('path', '') for e in echoes]
messages.append(msg)
else: