Matthias Nott
4 days ago c9739ac4a22733d45167173446c1d3ce65a767eb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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<TraceEntry> _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<TraceEntry> 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();
}