import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; import 'mqtt_service.dart'; /// A single trace entry capturing a message-handling event. class TraceEntry { final DateTime timestamp; final String event; final String details; const TraceEntry({ required this.timestamp, required this.event, required this.details, }); @override String toString() => '[${timestamp.toIso8601String().substring(11, 23)}] $event — $details'; } /// Singleton ring-buffer trace service. /// /// Captures message-handling events from MQTT, chat screen, and other /// components. The buffer is capped at [maxEntries] (default 200). /// Works in both debug and release builds. /// /// When an MqttService is attached via [attachMqtt], trace entries are /// automatically published to the server on `pailot/control/in` so they /// can be read from the daemon log. class TraceService { TraceService._(); static final TraceService instance = TraceService._(); static const int maxEntries = 200; final List _entries = []; MqttService? _mqtt; /// Attach an MQTT service for auto-publishing traces to the server. void attachMqtt(MqttService mqtt) { _mqtt = mqtt; } /// All entries, oldest first. List get entries => List.unmodifiable(_entries); /// Add a trace entry. Oldest entry is evicted once the buffer is full. /// If MQTT is attached and connected, the entry is also published to the server. void addTrace(String event, String details) { _entries.add(TraceEntry( timestamp: DateTime.now(), event: event, details: details, )); if (_entries.length > maxEntries) { _entries.removeAt(0); } debugPrint('[TRACE] $event — $details'); // Auto-publish to server if MQTT is connected _publishTrace(event, details); } void _publishTrace(String event, String details) { final mqtt = _mqtt; if (mqtt == null || !mqtt.isConnected) return; try { final payload = jsonEncode({ 'type': 'command', 'command': 'app_trace', 'event': event, 'details': details, 'ts': DateTime.now().millisecondsSinceEpoch, }); final builder = MqttClientPayloadBuilder(); builder.addString(payload); mqtt.publishRaw('pailot/control/in', builder.payload!, MqttQos.atMostOnce); } catch (_) { // Non-fatal — don't let trace logging break the app } } /// Clear all entries. void clear() => _entries.clear(); }