Commit Graph

371 Commits

Author SHA1 Message Date
MarekWo
d6a7354f06 fix(channels): use device-reported max_channels instead of hardcoded 8
Firmware reports MAX_GROUP_CHANNELS (typically 40 for companion builds)
in the DEVICE_INFO response. Fetch it at startup and use it in all
channel iteration loops. Previously hardcoded range(8) prevented
channels 8+ from appearing and blocked adding new channels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:38:15 +01:00
MarekWo
9b206beeac fix(echo): validate channel hash before correlating echo with sent message
Pending echo correlation was assigning ANY first echo to the sent message,
even if it came from a different channel. This caused cross-channel mismatches
(e.g., Public channel echo assigned to #krakow message).

Fix: check that pkt_payload's first byte (channel_hash = sha256(secret)[0])
matches the channel we sent on before accepting correlation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:18:32 +01:00
MarekWo
ac1667bd01 fix(ui): fall back to echo SNR when message SNR is missing
meshcore library doesn't always provide SNR in CHANNEL_MSG_RECV events.
Use the first echo path's SNR as fallback for inline display.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:25:25 +01:00
MarekWo
ba990b155f fix(ui): fix leading pipe separator when SNR is missing from message
Use array-based metaParts.join(' | ') instead of string concatenation
to avoid ugly leading "| Hops: 0" when meshcore lib doesn't provide SNR.
Also revert temporary INFO-level debug logging back to DEBUG.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:19:09 +01:00
MarekWo
7bcd6bd216 debug: add INFO logging for RX_LOG_DATA events and subscriptions 2026-03-02 16:56:43 +01:00
MarekWo
9f249a4521 feat(echoes): add RX_LOG_DATA echo tracking + sent message pkt_payload correlation
- Subscribe to RX_LOG_DATA events to capture repeated radio packets
- Parse GRP_TXT (0x05) payload to extract pkt_payload and path
- Classify echoes as sent (pending echo correlation) or incoming
- Register pending echo when sending channel messages for pkt_payload capture
- Add update_message_pkt_payload() DB method for sent message correlation
- Return echo_paths/echo_snrs for ALL messages (not just own) in GET /messages
- Frontend: build paths from echo_paths for incoming message route display
- Emit SocketIO 'echo' event for real-time badge updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:53:12 +01:00
MarekWo
44832ada5e fix(analyzer): fix pkt_payload computation for block-aligned text + trailing whitespace
Two bugs in Analyzer URL generation:
1. Firmware omits null+padding when header+text exactly fills AES block boundary
   (len % 16 == 0), but our code always added \0+padding → wrong MAC → wrong hash
2. content.strip() in event handler removed trailing whitespace that was part of
   the original packet. Now uses raw_text from raw_json (preserves original text)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:53:59 +01:00
MarekWo
2a3a00e654 fix(contacts): refresh contacts on unknown advert + cleanup fixes
When an ADVERTISEMENT arrives for a pubkey not in mc.contacts, the
firmware has auto-added a new contact. Trigger ensure_contacts() to
refresh the contact list and get the name. Also: remove contacts from
mc.contacts cache on delete, add reject/clear pending API endpoints,
promote advert logging to INFO level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:30:07 +01:00
MarekWo
37694dde09 fix(contacts): remove from pending list after approve + add reject/clear
approve_contact now removes the contact from mc.pending_contacts after
successful approval. Added reject_contact (remove without adding) and
clear_pending_contacts methods with API endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:14:10 +01:00
MarekWo
2c547ee1fc fix(perf): disable auto_update_contacts to prevent serial blocking
auto_update_contacts=True triggers ensure_contacts() on every
ADVERTISEMENT event, fetching 324+ contacts over serial (several seconds).
This blocks the serial port and delays MESSAGES_WAITING processing,
causing 10-30s message reception delays. Contacts are synced at startup
and updated individually via NEW_CONTACT events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:48:08 +01:00
MarekWo
63f2473933 fix(contacts): respect manual approval setting in NEW_CONTACT handler
When manual approval is enabled in settings, _on_new_contact now skips
DB upsert and emits SocketIO event for pending contacts. When auto mode
is on, contacts are added to DB immediately as before. Also enriched
approve_contact and get_pending_contacts with full contact data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:45:44 +01:00
MarekWo
499759931c 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>
2026-03-02 07:38:44 +01:00
MarekWo
e37ab4243c Revert "fix(contacts): respect manual approval + fix message delay"
This reverts commit 6bb985f9c4.
2026-03-01 21:57:37 +01:00
MarekWo
6bb985f9c4 fix(contacts): respect manual approval + fix message delay
1. Manual approval: _on_new_contact now checks self_info
   manual_add_contacts flag. When enabled, new contacts stay in
   mc.pending_contacts for UI approval instead of auto-adding to DB.

2. Message delay: disable auto_update_contacts which was triggering
   full contact list refresh (270+ records over serial) on every
   ADVERTISEMENT event, blocking message reception for seconds.
   Contact names for adverts are looked up from cached mc.contacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:46:08 +01:00
MarekWo
db5aac084c fix(adverts): fix empty names + enable auto_update_contacts
- ADVERTISEMENT events only contain public_key — look up name/type/lat/lon
  from mc.contacts instead of the empty payload
- Enable mc.auto_update_contacts so meshcore refreshes contacts after adverts
- Fix NEW_CONTACT handler to store lat/lon/last_advert (was only storing
  name and type)
- Fix type field: NEW_CONTACT uses 'type' not 'adv_type'
- Graceful handling of set_manual_add_contacts firmware incompatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:02:49 +01:00
MarekWo
1df8fa03f9 debug: add ADVERT/NEW_CONTACT payload logging 2026-03-01 18:42:29 +01:00
MarekWo
b034a181ce feat(retention): add message retention scheduling (Task 2.6)
- Add daily retention job that deletes old channel messages, DMs, and
  advertisements based on configurable age threshold
- Add GET/POST /api/retention-settings endpoints
- Extend cleanup_old_messages() to optionally include DMs and adverts
- Wire up APScheduler in create_app() (also enables existing archiving
  and contact cleanup schedulers that were never started in v2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:28:54 +01:00
MarekWo
d89e276054 feat(api): add advertisement history API endpoint (Task 2.8)
Add GET /api/advertisements with optional pubkey filter and limit.
Enriches results with contact name lookup from cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:26:37 +01:00
MarekWo
5df10f0ab9 feat(v2): Expand console router with status, channels, help commands
- Add 'status' command: connection, name, battery, contacts count
- Add 'channels' command: list configured channels (0-7)
- Add 'help' command: list all available commands with descriptions
- Update unknown command message to suggest 'help'

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:23:40 +01:00
MarekWo
f8c7bfb115 feat(v2): Add SocketIO real-time push for channel messages page
- Add SocketIO /chat client to app.js for real-time message updates
- Listen for new_message (channel) events, refresh current channel
- Update unread badges for other channels in real-time
- Listen for device_status to update connection indicator
- Reduce polling interval from 10s to 60s (fallback only)
- Include socket.io.min.js in base.html template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:21:56 +01:00
MarekWo
ebfe383190 feat(v2): Add SocketIO real-time push for DM page
- Add SocketIO /chat client to dm.js for real-time DM and ACK updates
- Listen for new_message (dm), ack, device_status events
- Remove 5x cascading refresh after send (replaced by SocketIO ACK)
- Reduce polling interval from 10s to 60s (fallback only)
- Add data-ack attribute to status icons for real-time ACK updates
- Enrich ACK emission with snr/rssi/route_type (device_manager.py)
- Include socket.io.min.js in dm.html template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:19:54 +01:00
MarekWo
e18ad0f7a3 fix(v2): Fix echo enrichment bug + add analyzer URL to channel messages
Bug: echo enrichment at api.py:384 used leaked `row` variable from
previous loop — all messages got echo data from the LAST DB row.

Fix: include pkt_payload in message dict during conversion loop,
then enrich each message with its own echo data and analyzer URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:16:50 +01:00
MarekWo
7f9aa4ac58 fix(v2): Truncate pubkey in DM placeholder using displayName() helper
currentRecipient keeps full value for sending, displayName() used for
all placeholder/dropdown display to truncate 64-char pubkeys to 8+...

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:31:21 +01:00
MarekWo
3e81eeeae7 fix(v2): Fix last_advert timestamps and DM placeholder display
- Sync last_advert from device contacts as Unix timestamp (was missing)
- Convert _on_advertisement to store Unix timestamp (was ISO string)
- Add _parse_last_advert() to handle both ISO and Unix formats in API
- Truncate full pubkey to short prefix in DM placeholder and dropdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:23:45 +01:00
MarekWo
c20e7c20ad fix(v2): Fix DM contact names showing as pubkey prefix
- DM handler: don't overwrite contact name with prefix when name unknown
- Migration: upsert contact with sender name from v1 PRIV entries
- Fixes conversations showing "4e45565e" instead of "demo mc-webui"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:58:15 +01:00
MarekWo
4b463fecfa fix(v2): Resolve DM sender pubkey prefix to full key from contacts
Incoming DM events only contain a short pubkey_prefix. Now resolves it
to the full public_key via mc.get_contact_by_key_prefix() so incoming
and outgoing messages end up in the same conversation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:46:10 +01:00
MarekWo
95dcf38d06 fix(v2): Handle bytes expected_ack from meshcore + DM prefix matching
- Convert bytes to hex string for expected_ack and pkt_payload via _to_str()
- Support pubkey prefix matching in get_dm_messages() (LIKE for short keys)
- Fixes "Object of type bytes is not JSON serializable" error on DM view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:36:37 +01:00
MarekWo
752c60f02d feat(v2): Import archive .msgs files + DB-based message history
Migration now imports all archive files (oldest first) in addition to the
live .msgs file, with deduplication. Archives endpoint and message history
now query SQLite by date instead of reading .msgs files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:05:33 +01:00
MarekWo
97a2014af2 feat(v2): Auto-migrate v1 .msgs data to SQLite on first startup
Reads the existing .msgs JSONL file and imports channel messages and DMs
into the v2 SQLite database. Runs automatically when device connects and
DB is empty. Handles sender parsing, pubkey resolution, and FK constraints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:32:28 +01:00
MarekWo
64860ba178 fix(v2): Parse sender name from channel message text format
Channel messages from meshcore arrive as "SenderName: message text".
The library doesn't provide sender name separately. Now parsing it
from the text (split on first colon), matching v1 parser behavior.

Also:
- Look up DM sender names from mc.contacts instead of event payload
- Fix SNR field name (uppercase 'SNR' from meshcore library)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:10:13 +01:00
MarekWo
65eb44d0ff fix(v2): Use event.payload instead of event.data throughout
The meshcore Event class has 'payload' not 'data'. All event handlers
were silently getting empty dicts, causing:
- Channel messages showing 'Unknown' sender
- Channel info not returning name/secret
- Sent message event data being lost

Also normalizes channel_name/channel_secret keys from CHANNEL_INFO
events and converts secret bytes to hex string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:01:42 +01:00
MarekWo
a7b9b74fa2 fix(v2): Use mc.commands for meshcore command methods
MeshCore library exposes command methods (get_channel, send_msg,
send_advert, etc.) on mc.commands, not directly on the MeshCore
instance. Updated all DeviceManager calls accordingly.

Fixes: channels not loading, message sending, advert, battery, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:49:04 +01:00
MarekWo
1b142b26f2 fix(v2): Use Flask current_app for DeviceManager lookup in cli.py
The module-level 'from app.main import device_manager' was returning
None in Flask request context even though device_manager was set.
Now tries current_app.device_manager first (Flask app context),
falling back to module import for non-request contexts.

Fixes 500 errors on /api/contacts, /api/contacts/pending,
/api/contacts/detailed, and /api/channels endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:33:07 +01:00
MarekWo
adf17d2d54 fix(v2): Add connection retry logic and self_info null guard
- Add _connect_with_retry() with exponential backoff (10 attempts)
- Guard against self_info being None after meshcore library disconnects
  due to unresponsive device
- Prevents crash when device is busy (e.g. held by orphan container)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:23:05 +01:00
MarekWo
2e95bbf9b5 fix(v2): Serial port auto-detection and channel_messages query
- Add _detect_serial_port() to DeviceManager — resolves 'auto' to
  actual device via /dev/serial/by-id with common path fallbacks
- Make channel_idx optional in get_channel_messages() so status and
  channel-updates endpoints can query across all channels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:15:44 +01:00
MarekWo
e98acf6afa feat(v2): Add pkt_payload to DMs, update watchdog for single container
- Add pkt_payload column to direct_messages table for stable packet
  hash generation and Analyzer URL linking
- Update insert_direct_message() and DeviceManager to store pkt_payload
- Add test for DM pkt_payload storage (43 tests pass)
- Update watchdog to monitor only mc-webui (meshcore-bridge removed)
- USB reset trigger now fires for mc-webui container failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:01:43 +01:00
MarekWo
df8e2d2218 feat(v2): Route API endpoints through Database, remove bridge
- Update api.py: messages, contacts, DM endpoints read from SQLite DB
- Add DB fallback paths for parser.py backward compatibility
- Replace bridge echo registration with DeviceManager event handling
- Update status endpoint to use db.get_stats()
- Update channel updates/DM updates endpoints for DB queries
- Delete channel messages via DB instead of parser
- Remove meshcore-bridge/ directory (no longer needed in v2)
- Remove MC_BRIDGE_URL from config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:28:14 +01:00
MarekWo
badf67cf74 feat(v2): Rewrite main.py and cli.py for direct device communication
main.py: Initialize Database + DeviceManager in create_app(), replace
bridge-dependent startup code, simplified console command router.
cli.py: All functions now delegate to DeviceManager instead of HTTP
bridge calls. Same signatures preserved for api.py compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:23:59 +01:00
MarekWo
a8a0becb13 feat(v2): Complete DeviceManager with event handlers and commands
Event handlers: channel messages, DMs, ACKs, adverts, path updates,
new contacts, disconnection — all write to Database + emit SocketIO.
Command methods: send_channel_message, send_dm, get/delete contacts,
get/set/remove channels, send_advert, check_connection, battery,
manual_add_contacts, pending contacts approval.
Auto message fetching and initial contact sync on connect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:21:25 +01:00
MarekWo
8959261aca test(v2): Add 42 integration tests for Database class
Tests cover: schema init, WAL mode, device info, contacts CRUD
(with protection, GPS, upsert semantics), channels, channel messages
(limit/offset/filter), DMs with conversations, ACKs, echoes,
FTS5 search (channel+DM+combined), read status, muting,
backup/restore, cleanup, advertisements, and paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:04:28 +01:00
MarekWo
bd825f48c3 feat(v2): Single container Docker setup with direct USB access
Replace two-container bridge architecture with single container.
Dockerfile adds udev for serial device support.
docker-compose.yml: one service with cgroup rules for ttyUSB/ttyACM,
SQLite DB path, backup settings, optional TCP mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:02:11 +01:00
MarekWo
c9cf37e8d5 feat(v2): Add DeviceManager skeleton with connect/disconnect
Background thread runs meshcore async event loop. Supports both
serial and TCP transports. Flask routes bridge sync→async via
execute() method. Event subscriptions marked as TODO for Phase 1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:01:16 +01:00
MarekWo
68b14434ca feat(v2): Add Database class with full CRUD and backup
Sync SQLite wrapper with WAL mode, connection-per-call thread safety.
Methods for: device info, contacts (upsert/get/delete/protect),
channels, channel messages, DMs, ACKs, echoes, paths, advertisements,
read status, FTS5 search, stats, cleanup, and sqlite3.backup().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:59:39 +01:00
MarekWo
9f9b6e7ed7 feat(v2): Add SQLite schema with 10 tables, indexes and FTS5
Tables: device, contacts, channels, channel_messages, direct_messages,
acks, echoes, paths, advertisements, read_status.
Includes schema_version for migrations, FTS5 virtual tables with
auto-sync triggers for full-text search on messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:58:19 +01:00
MarekWo
ebfc3c9845 feat(v2): Add v2 config settings (DB, TCP, backup)
New environment variables for Phase 0:
- MC_DB_PATH: SQLite database location
- MC_TCP_HOST/MC_TCP_PORT: TCP transport (meshcore-proxy)
- MC_BACKUP_ENABLED/HOUR/RETENTION_DAYS: automated backup
- MC_AUTO_RECONNECT, MC_LOG_LEVEL: connection management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:57:08 +01:00
MarekWo
a0eb590baa chore(v2): Add meshcore dependency and gitignore docs/v2
- Add meshcore>=2.2.0 for direct device communication (Phase 0.1)
- Exclude docs/v2/ from git (local working notes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:56:32 +01:00
MarekWo
2254580f01 chore(v2): Initialize v2 branch with status tracking
- Create v2 branch for mc-webui direct device communication migration
- Add docs/v2/STATUS.md for development progress tracking
- Exclude PRD documents from git (local-only planning docs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 20:42:24 +01:00
MarekWo
39f4a71538 fix(dm): Fix PATH_UPDATE race condition and confirm all retry acks
The retry thread was removing pending_flood_acks immediately after
exhausting retries. PATH_UPDATE arriving even 1 second later would
find no pending entry to match, leaving the message undelivered.

Changes:
- Don't clean up pending_flood_acks in retry threads, keep entries
  alive for late PATH_UPDATE arrivals
- Add TTL-based cleanup (2 min) in _process_path_update
- When PATH_UPDATE confirms delivery, save synthetic ACK for the
  original ack code AND all retry group ack codes so the frontend
  ack_status polling finds a match regardless of which code it checks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:02:42 +01:00
MarekWo
66b553a8b5 fix(dm): Retry flood DM when initial send fails with device error
When sending a DM to a no-path contact, meshcli can return
"Error sending message" if the device-level flood send fails.
Previously this was treated as a terminal failure with no retry.

Now detects the error and starts a background flood retry thread
that re-attempts up to auto_retry_flood_only times (default 3),
waiting between attempts for a PATH_UPDATE to establish the route.
If a retry succeeds, registers for normal delivery tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:58:48 +01:00
MarekWo
2764e1c551 feat(dm): Add PATH-based delivery tracking for flood DMs
When sending DM to contacts without a known path (flood mode), ACK is
piggybacked inside the PATH response packet and not visible on stdout.
This adds PATH_UPDATE event parsing to confirm flood DM delivery.

Changes:
- Enable print_path_updates in meshcli init for PATH_UPDATE events
- Parse PATH echoes from json_log_rx -> .path.jsonl log (diagnostics)
- Parse PATH_UPDATE events to confirm flood DM delivery by public_key
- Replace "skip retry for no-path contacts" with proper flood retry
  (max 3 attempts, matching standard Meshcore app behavior)
- Split _retry_send into _retry_send_flood and _retry_send_direct
- Refactor _get_contact_path_len -> _get_contact_info (returns both
  out_path_len and public_key)
- Add GET /paths endpoint for diagnostics
- Add flood_only config param for auto-retry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:33:39 +01:00