Commit graph

167 commits

Author SHA1 Message Date
549ae4bdfb feat(env): NWS weather alerts, NOAA space weather, tropospheric ducting
- Environmental feed system with tick-based adapters
- NWS Active Alerts: polls api.weather.gov, zone-based filtering
- NOAA SWPC: Kp, SFI, R/S/G scales, band assessment, alert detection
- Tropospheric ducting: Open-Meteo GFS refractivity profile, duct classification
- !alerts command for active weather warnings
- !solar / !hf commands for RF propagation (HF + UHF ducting)
- Alert engine integration: severe weather, R3+ blackout, ducting events
- LLM context injection for weather/propagation queries
- Dashboard RF Propagation card with HF + UHF ducting display
- EnvironmentalConfig with per-feed toggles in config.yaml
2026-05-12 17:21:43 +00:00
374fb835c5 fix(dashboard): content scroll overflow bug 2026-05-12 16:46:51 +00:00
4331bcb7e1 feat(dashboard): React frontend scaffold with overview page
- Vite + React 18 + TypeScript + Tailwind CSS
- Dashboard overview with health gauge, pillar bars, alerts
- WebSocket hook for real-time updates
- Layout with sidebar navigation and live indicator
- Placeholder pages for Mesh, Environment, Config, Alerts
- Dark theme ops center aesthetic with JetBrains Mono

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 10:28:12 -06:00
3ec09ad158 feat(dashboard): embedded FastAPI backend with REST API + WebSocket
- FastAPI runs in MeshAI asyncio loop (no separate process)
- REST API: /api/status, /api/health, /api/nodes, /api/edges,
  /api/regions, /api/sources, /api/config, /api/alerts
- WebSocket at /ws/live pushes health updates and alerts
- Config CRUD: GET/PUT per section with validation and save
- DashboardConfig with port/host in config.yaml
2026-05-12 15:47:58 +00:00
914c21e167 fix: Switch to delay-based delivery — wantAck firmware retries cause duplicates 2026-05-07 21:50:09 +00:00
a80ed6cc7c fix: Dedup packets across sources, translate numeric port numbers to names
Packet dedup: track seen packet IDs across all sources. Same packet from
Meshview + MeshMonitor counted once, not twice. Fixes inflated counts.
Portnum: numeric values (3, 4, 71) mapped to names (Position, NodeInfo, Neighbors).
LLM prompt: guidance on normal vs abnormal packet rates per type.
2026-05-07 03:08:08 +00:00
MeshAI Claude
1d97854319 docs: Update README — Qdrant/RECON knowledge backend, alert engine, architecture 2026-05-06 16:29:03 +00:00
b11874f016 feat(knowledge): add Qdrant backend with SQLite fallback
- Add QdrantKnowledgeSearch class for hybrid dense+sparse vector search
- Query RECON's 2.8M vector database via TEI embeddings + Qdrant
- Uses RRF (Reciprocal Rank Fusion) for hybrid search merging
- Extended KnowledgeConfig with Qdrant/TEI settings
- Auto backend tries Qdrant first, falls back to SQLite FTS5
- Graceful degradation if RECON infrastructure unreachable

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 15:54:43 +00:00
6119f33f57 fix: Rename ACK callback to onAckNak — meshtastic lib filters ACKs for other names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 14:30:28 +00:00
34f894ea79 fix: ACK-accelerated delivery — immediate on ACK, retry once, abort on double fail
Delays 1.5-2.5s (was 3-5s, only for broadcasts now).
DMs: send → ACK → next immediately. No ACK → retry once → abort.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 14:10:16 +00:00
736d6a313a feat: Full alert engine — 17 conditions, scaling cooldown, per-condition TUI toggles
Alert conditions across all 5 pillars:
  Infrastructure: offline, recovery, new router
  Power: battery 50/25/10%, 7-day trend, USB→battery, solar not charging
  Utilization: sustained >20% for 6h, packet flood >500/24h
  Coverage: infra single gateway, feeder offline, region blackout
  Scores: mesh <70, region <60

Scaling cooldown: immediate → 12h → 24h → 48h → stop
Recovery notifications when conditions resolve
Per-condition on/off toggles in TUI
Battery trend queries SQLite node_snapshots for 7-day history

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 05:39:11 +00:00
c8400233bd feat: alert detection and dispatch system for real-time mesh alerts
- Add AlertEngine to detect infra down/recovery, battery critical, critical node offline
- Add alert_channel, critical_nodes, alert_cooldown_minutes config options
- Wire alerts to channel broadcast and DM subscribers
- Add TUI options for critical nodes, alert channel, cooldown

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 04:48:41 +00:00
0da697855e fix: delay_max 3.0 → 5.0 for randomized message pacing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 04:24:12 +00:00
b7237469e4 feat: ACK-based message delivery, markdown stripping, prompt fixes
- Connector: send_and_wait_ack() waits for ACK before returning
- Responder: ACK waiting for DMs with retry, delay-based for broadcasts
- Chunker: strip_markdown() removes bold/italic/headers/lists from LLM output
- Router: applies strip_markdown before chunking
- Prompt: stronger no-markdown rules, no manual continuation prompt, gateway explanation
- Config: delays 3-5s (was 2.2-3), max_length 200 (was 150), max_messages 3 (was 2)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 04:17:44 +00:00
MeshAI Claude
d5fc69e9a0 docs: Complete README rewrite — mesh intelligence, data sources, updated architecture 2026-05-06 01:28:59 +00:00
6bf0e3427e fix: !health packs into 2-3 messages with newlines inside
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 00:57:44 +00:00
7670b7c0b9 feat: !health returns list for separate LoRa messages, partial name distance matching
- build_lora_compact returns list[str] instead of str
- Each line is a separate LoRa message (no chunking needed)
- main.py handles list responses from commands
- _try_compute_distance supports partial names (TVM Pearl → TVM Pearl Relay)
- Ambiguous names detected (TVM → asks which node)
- Max message size: 54 bytes (well under 228 byte limit)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-06 00:11:57 +00:00
525da64d85 chore: Change freq51 to Mesh in !health output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 23:50:19 +00:00
5839fdeb18 feat: !health colored dots no numbers, distance from AIDA, router distance detection
!health: 🔵 perfect, 🟢 healthy, 🟠 warning, 🔴 critical — no /100 scores.
Each line ends with period for separate LoRa messages.
Uses long_name to avoid emoji shortnames (📡 → TVM Tablerock Relay).
Distance from AIDA shown on every infra node in Tier 1.
Router detects how far questions and injects computed distance.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 23:47:48 +00:00
5be1d20b24 fix: Command output goes through chunker, byte-safe for LoRa
- Commands now chunk output same as LLM responses
- split_sentences splits on newlines first for !health output
- chunk_response uses byte counting instead of character counting
- Emojis and UTF-8 properly counted for 228-byte LoRa limit
- !health 274 bytes now splits into 2 messages (195 + 74 bytes)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 23:07:25 +00:00
1662d80f02 feat: !health personality with emojis, skip empty regions, AIDA physical node identity
- Replace build_lora_compact with personality version using emojis
- Add _region_lora_compact helper for region-specific display
- Skip empty regions in 3 loops (build_tier1_summary, utilization, list_regions_compact)
- Update AIDA identity: she IS a physical node (!27780c47 AIDA-N2)
- AIDA now knows she has real GPS coordinates and radio connections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 22:35:00 +00:00
a97d346449 feat: Add node-to-node distance calculation
- Add build_distance() method to MeshReporter
- Uses _haversine_km() for GPS distance calculation
- Formats output with both km and miles
- Handles missing nodes or GPS positions gracefully

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 22:16:43 +00:00
79ff756a38 fix: Remove hard-coded token limits on LLM responses
- Add max_response_tokens config (8192) to LLMConfig
- Use config value in router.py instead of hardcoded 500
- Update base.py default from 300 to 8192
- Lets LLM generate full responses; chunker handles size limits

Fixes truncated responses like Here are three nodes in the freq

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 22:06:50 +00:00
70c0ab3047 fix: Restore build_lora_compact, list_regions_compact, build_region_compact, build_node_compact
These methods were called by commands/health.py and main.py but missing
from mesh_reporter.py, causing crashes on !health and !region commands.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 21:50:18 +00:00
99c952b432 feat: Show hop count and GPS distance in node detail and single-gw listings
- Added hops_away to Connectivity section in node detail
- Added nearest infra distance after Position in node detail
- Added distance from reference infra to single-gw client listings
- Added _haversine_km and _format_distance helper functions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 21:16:55 +00:00
6ac21d5f0e fix: LLM prompt — use local names, concise node listings, no redundant regions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 21:09:21 +00:00
29a57b459a fix: 6 reporter bugs — missing methods, undefined var, indentation, key types
1. Added _find_node() — delegates to health_engine.get_node()
2. Added _find_region() — fuzzy match with config aliases
3. Fixed undefined unified var in _node_recommendations
4. Fixed env recommendation indentation (was inside MQTT uplink check)
5. Fixed 6 string-vs-int key mismatches on health.nodes lookups
6. Fixed estimated_position_interval — compute inline from packets_by_type

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 20:58:41 +00:00
cc474e3bb3 feat: Show source name for single-gateway nodes
Single-gateway nodes now display "via {source_name}" to indicate
which data source they depend on. This helps identify coverage gaps
and understand node visibility.

Adds source info to:
- Tier 1 region summary (infra and client nodes)
- Node distribution section (infrastructure nodes)
- Region detail view (single gateway list)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 20:23:18 +00:00
1d9c90911b feat: Feeder-level gateway awareness from /api/packets_seen
Samples recent packets and calls /api/packets_seen to discover which
physical MQTT gateways hear each node. Per-gateway RSSI and SNR.

UnifiedNode:
- feeder_gateways list with gateway_id, gateway_name, avg_rssi, avg_snr
- feeder_count, feeder_best (strongest signal), feeder_worst

MeshviewSource:
- Added feeders to ENDPOINT_SCHEDULE (every 20 ticks / 10 min)
- _fetch_feeders() samples 20 packets and queries packets_seen
- Auto-disables if endpoint returns 404

MeshDataStore:
- _enrich_feeder_data() aggregates gateway data across all sources
- _normalize_node_id() helper for hex/decimal conversion
- get_feeder_map() shows per-gateway coverage statistics
- get_node_feeders() returns sorted gateway list for a node

MeshReporter:
- Node detail shows feeder gateways with signal strength
- Tier 1 shows total unique gateways and avg per node

Discovered gateways: AIDA, BKBS, STLR, N7MH, stor, JTS

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 19:55:23 +00:00
b3c79f12da feat: Tick-based staggered polling for all sources
Both MeshviewSource and MeshMonitorDataSource now use tick-based
staggered polling instead of batch-every-5-minutes:

MeshviewSource (30s ticks):
- Packets: every tick (30s)
- Nodes: every 4 ticks (2 min)
- Stats/Edges: every 6 ticks (3 min)
- Traceroutes: every 10 ticks (5 min)

MeshMonitorDataSource (30s ticks):
- Packets: every 2 ticks (60s)
- Nodes/Telemetry: every 4 ticks (2 min)
- Traceroutes/Channels/Network/Topology: every 10 ticks (5 min)
- Solar: every 20 ticks (10 min)

Features:
- Source health status (avg_response_ms, tick_count, backed_off)
- Source coverage analysis (unique vs shared nodes)
- Tier 1 DATA SOURCES section shows all source health
- Node detail shows source visibility
- Incremental packets and telemetry with dedup
- Rate limit detection (429) with backoff
- Consecutive error exponential backoff
- polite_mode config option for shared instances

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 19:16:00 +00:00
bc644b3ac2 fix: Name single-gateway client nodes in Tier 1 (top 10 per region)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 17:04:49 +00:00
aef78996a9 feat: Named single-gw nodes, infra-only monitoring, commands overhaul
Coverage:
- Single-gateway infra nodes named as critical risks per region
- Client single-gw nodes counted but not individually named
- Mesh-wide single-gw infra summary

Monitoring rules by node type:
- Infrastructure: full detail - battery, offline, coverage, neighbors, hardware
- Clients causing problems: named - high util, top senders
- Clients otherwise: counted per region, not individually tracked
- POWER breakdown now infra-only

Commands:
- Removed hardcoded command list from config.py system_prompt
- Dynamic command list in router.py from dispatcher (only enabled commands)
- MeshMonitor commands no longer listed as MeshAI commands
- !help overhaul: grouped by category, per-command detailed help
- LLM explicitly told to only mention listed commands

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 15:33:14 +00:00
56536f55c8 feat: Rich Tier 1 data - named infra per region, problem nodes, expanded recommendations
Tier 1 now includes:
- Every infrastructure node BY NAME per region with status/battery/util/gateways
- Problem nodes section: offline infra, critical battery, high util, coverage risks
- Per-region coverage with gateway counts and single-gw counts
- Environmental data per region
- All 5 pillars with weights
- Expanded recommendations with specifics (10 max, up from 5)
- LLM prompt simplified: data speaks for itself

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 14:32:43 +00:00
8d1a48ea08 fix: Short sentence instruction + chunker splits instead of truncating
- Added CRITICAL instruction to keep sentences under 150 chars
- Chunker now splits long sentences at word boundaries instead of truncating
- No words lost when splitting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 07:22:52 +00:00
51969050da fix: Replace deleted REGION_CONTEXT with config method, replace hardcoded city map with config aliases
- Line 763: REGION_CONTEXT.get() → self._region_context() (same method used elsewhere)
- Deleted _CITY_TO_REGION hardcoded dict
- Scope detection now uses config aliases/cities from RegionAnchor
- Fixed Sun Valley/Ketchum geography (was Central ID, should be South Central ID)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 07:11:08 +00:00
de400068dd feat(mesh): config-driven regions with stale purge and coverage fix
- Extended RegionAnchor with local_name, description, aliases, cities
- Moved region geographic context from hardcoded Python to config.yaml
- Added 7-day stale node purge in _do_refresh (556 → 267 nodes)
- Fixed coverage lookup: str(node_num) → node_num (int key)
- Added bidirectional neighbor lookup for better region assignment
- Dynamic geography building in router from config
- Reporter reads region context from config instead of hardcoded dict

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 06:33:38 +00:00
abcf2d88e2 feat: Geographic context — local names, accurate Idaho/Utah geography, no Unknown gaps
- Magic Valley, Treasure Valley, Panhandle, Dixie — local names in all output
- Southern Idaho correctly maps to Magic Valley, not lumped with Boise/IF
- Unlocated nodes excluded from coverage gap recommendations
- LLM response rules override brevity for mesh questions
- Node detail shows region with local context

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 06:08:58 +00:00
45630f2cc6 fix: Override LLM brevity for mesh questions — give detailed data responses
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 05:29:09 +00:00
69f3d5d284 fix: Replace 5 remaining node_id refs with node_id_hex after NodeHealth removal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 05:24:26 +00:00
4183abe755 refactor: DELETE NodeHealth — reporter uses UnifiedNode directly
NodeHealth is gone. MeshHealth.nodes is now dict[int, UnifiedNode].
Reporter reads all fields from UnifiedNode: coverage, environment,
neighbors, hw_model — everything available without cross-referencing.
This eliminates the entire category of field missing on NodeHealth bugs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 05:12:45 +00:00
a384fd7a20 Implement 5-pillar health scoring with coverage
- Update HealthScore with coverage pillar (20% weight)
- Adjust weights: Infra 30%, Util 25%, Coverage 20%, Behavior 15%, Power 10%
- Add coverage metrics: avg_gateways, single_gw_count, full_count
- Add health score fields to UnifiedNode for direct sync
- compute() now syncs scores back to UnifiedNode objects
- Coverage scoring penalizes single-gateway nodes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 04:59:41 +00:00
ac2bb87473 fix: Coverage breakdown now uses str(node_num) for health node lookup
Health engine stores node IDs as decimal strings, but UnifiedNode.node_num
is an integer. The lookup health.nodes.get(n.node_num) was failing, causing
all nodes to show as Unlocated. Fixed by converting to string first.
2026-05-05 04:40:12 +00:00
d8189e2e4d fix: Add hw_model and missing fields to NodeHealth
- hw_model, neighbor_count, packets_sent_24h fields
- node_id_hex, battery_trend, packets_by_type, predicted_depletion_hours properties
- Populate hw_model from node data when creating NodeHealth
- Fixes reporter crash on node detail view
2026-05-05 04:26:52 +00:00
e9ddfa7e26 fix: Add node_num property to NodeHealth class
NodeHealth stored node_id as hex string but mesh_reporter.py
expected node_num integer. Added computed property to convert.
2026-05-05 04:16:00 +00:00
cb61c4199c feat: Geographic coverage breakdown in mesh reporter
- Show per-region coverage stats in tier1 summary
- List single-gateway nodes in region detail
- Add coverage status to node detail view
- Add coverage gap recommendations
2026-05-05 04:07:19 +00:00
62f04a3e09 fix: Align parameter names between data_store and source_manager
- create_dispatcher uses data_store=
- MessageRouter uses source_manager=
- Added get_all_traceroutes() and get_all_channels() compat methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 03:50:58 +00:00
800e4d9e7e fix: Add get_all_traceroutes and get_all_channels compat methods
Completes backwards compatibility layer for mesh_health.py.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 03:45:48 +00:00
c756727cad feat: Node source overlap for gateway coverage metrics
Replaces broken per-packet gateway sampling with node-level source counting.
Each Meshview/MeshMonitor source represents a gateway view of the mesh.
If a node is seen by N sources, its packets are reaching N gateways.

- Removed _sample_gateway_coverage() (required non-existent API)
- Rewrote _enrich_deliverability() to use node.sources count
- Per-node: avg_gateways, max_gateways, source_reach, deliverability_score
- Mesh-wide: avg 4.16 gateways/node with 7 sources
- Fixed edge.timestamp -> edge.last_seen in get_all_edges()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 03:31:48 +00:00
f30cd0a8bf fix: Add backwards compatibility methods for mesh_health.py
- Add hasattr check for fetch_recent_packets in gateway sampling
- Add get_all_nodes(), get_all_telemetry(), get_all_packets(), get_all_edges() methods
- These methods return data in dict format expected by mesh_health.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-05 03:09:30 +00:00
af2f66d71d Merge subscriptions from main into feature branch (with full data pipeline) 2026-05-05 02:57:41 +00:00