fix(health): use real telemetry, fix hardcoded thresholds

- Utilization pillar reads firmware channel_utilization (max of infra
  nodes) instead of estimating from packet counts × 200ms
- is_online uses configured threshold, not hardcoded 24 hours
- Updated defaults: offline 2h (was 24h), battery warning 30% (was 20%)
- Utilization thresholds: 20/25/35/45% matching real Meshtastic behavior
- Behavior pillar threshold aligned with notification config (7200/day)
- has_solar marked as dead code pending Solar Quality Engine

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-13 23:06:31 +00:00
commit c6b4a64163
4 changed files with 1576 additions and 1567 deletions

File diff suppressed because it is too large Load diff

View file

@ -282,9 +282,9 @@ class MeshIntelligenceConfig:
enabled: bool = False
regions: list[RegionAnchor] = field(default_factory=list) # Fixed region anchors
locality_radius_miles: float = 8.0 # Radius for locality clustering within regions
offline_threshold_hours: int = 24 # Hours before node considered offline
offline_threshold_hours: int = 2 # Hours before node considered offline
packet_threshold: int = 500 # Non-text packets per 24h to flag
battery_warning_percent: int = 20 # Battery level for warnings
battery_warning_percent: int = 30 # Battery level for warnings
# Alert settings
critical_nodes: list[str] = field(default_factory=list) # Short names of critical nodes (e.g., ["MHR", "HPR"])

View file

@ -745,9 +745,11 @@ class MeshDataStore:
node.last_heard = ts or 0.0
# Is online (computed from last_heard)
now = time.time()
node.is_online = (now - node.last_heard) < 86400 if node.last_heard else False
# NOTE: is_online is set by MeshHealthEngine.compute() using the
# configured offline_threshold_hours. Don't set it here with a
# hardcoded value - let the health engine determine online status.
# The health engine runs on every refresh cycle and will set is_online
# based on: (now - last_heard) < (offline_threshold_hours * 3600)
# Hops, SNR, RSSI (MM)
node.hops_away = raw.get("hopsAway")

File diff suppressed because it is too large Load diff