QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
StateHistoryRecorder.cc
Go to the documentation of this file.
2#include "QGCStateMachine.h"
4
5#include <QtStateMachine/QAbstractState>
6
8{
9 QJsonObject obj;
10 obj["timestamp"] = timestamp.toString(Qt::ISODateWithMs);
11 obj["stateName"] = stateName;
12 obj["reason"] = static_cast<int>(reason);
13 if (!details.isEmpty()) {
14 obj["details"] = details;
15 }
16 return obj;
17}
18
20{
21 QString reasonStr;
22 switch (reason) {
23 case Entered: reasonStr = "ENTER"; break;
24 case Exited: reasonStr = "EXIT"; break;
25 case Timeout: reasonStr = "TIMEOUT"; break;
26 case Error: reasonStr = "ERROR"; break;
27 case Signal: reasonStr = "SIGNAL"; break;
28 case Event: reasonStr = "EVENT"; break;
29 default: reasonStr = "?"; break;
30 }
31
32 QString result = QStringLiteral("[%1] %2 %3")
33 .arg(timestamp.toString("HH:mm:ss.zzz"), reasonStr, stateName);
34 if (!details.isEmpty()) {
35 result += QStringLiteral(" (%1)").arg(details);
36 }
37 return result;
38}
39
41 : QObject(machine)
42 , _machine(machine)
43 , _maxEntries(maxEntries)
44{
45}
46
48{
49 if (_enabled == enabled) {
50 return;
51 }
52
53 _enabled = enabled;
54
55 if (_enabled) {
56 // Connect to all existing states
57 const auto states = _machine->findChildren<QAbstractState*>();
58 for (QAbstractState* state : states) {
59 _connectToState(state);
60 }
61 } else {
62 // Disconnect all
63 for (const auto& conn : _connections) {
64 disconnect(conn);
65 }
66 _connections.clear();
67 }
68}
69
70void StateHistoryRecorder::_connectToState(QAbstractState* state)
71{
72 auto enterConn = connect(state, &QAbstractState::entered,
73 this, &StateHistoryRecorder::_onStateEntered);
74 auto exitConn = connect(state, &QAbstractState::exited,
75 this, &StateHistoryRecorder::_onStateExited);
76 _connections.append(enterConn);
77 _connections.append(exitConn);
78}
79
81{
82 _maxEntries = max;
83
84 // Trim if necessary
85 while (_history.size() > _maxEntries) {
86 _history.removeFirst();
87 }
88}
89
91{
92 _history.clear();
93}
94
95QList<StateHistoryRecorder::HistoryEntry> StateHistoryRecorder::lastEntries(int n) const
96{
97 if (n >= _history.size()) {
98 return _history;
99 }
100 return _history.mid(_history.size() - n);
101}
102
103QList<StateHistoryRecorder::HistoryEntry> StateHistoryRecorder::entriesForState(const QString& stateName) const
104{
105 QList<HistoryEntry> result;
106 for (const auto& entry : _history) {
107 if (entry.stateName == stateName) {
108 result.append(entry);
109 }
110 }
111 return result;
112}
113
115{
116 QStringList lines;
117 lines << QStringLiteral("=== State History: %1 ===").arg(_machine->objectName());
118 lines << QStringLiteral("Entries: %1 / %2").arg(_history.size()).arg(_maxEntries);
119 lines << QString();
120
121 for (const auto& entry : _history) {
122 lines << entry.toString();
123 }
124
125 return lines.join('\n');
126}
127
129{
130 QJsonArray array;
131 for (const auto& entry : _history) {
132 array.append(entry.toJson());
133 }
134 return array;
135}
136
138{
139 const auto lines = dumpHistory().split('\n');
140 for (const QString& line : lines) {
141 if (!line.isEmpty()) {
142 qCDebug(QGCStateMachineLog) << line;
143 }
144 }
145}
146
147void StateHistoryRecorder::addEntry(const QString& stateName, TransitionReason reason,
148 const QString& details)
149{
150 if (!_enabled) return;
151
152 HistoryEntry entry;
153 entry.timestamp = QDateTime::currentDateTime();
154 entry.stateName = stateName;
155 entry.reason = reason;
156 entry.details = details;
157
158 _addEntry(entry);
159}
160
161void StateHistoryRecorder::_addEntry(const HistoryEntry& entry)
162{
163 _history.append(entry);
164
165 // Maintain circular buffer
166 while (_history.size() > _maxEntries) {
167 _history.removeFirst();
168 }
169}
170
171void StateHistoryRecorder::_onStateEntered()
172{
173 if (!_enabled) return;
174
175 auto* state = qobject_cast<QAbstractState*>(sender());
176 if (!state) return;
177
178 HistoryEntry entry;
179 entry.timestamp = QDateTime::currentDateTime();
180 entry.stateName = state->objectName();
181 entry.reason = Entered;
182
183 _addEntry(entry);
184}
185
186void StateHistoryRecorder::_onStateExited()
187{
188 if (!_enabled) return;
189
190 auto* state = qobject_cast<QAbstractState*>(sender());
191 if (!state) return;
192
193 HistoryEntry entry;
194 entry.timestamp = QDateTime::currentDateTime();
195 entry.stateName = state->objectName();
196 entry.reason = Exited;
197
198 _addEntry(entry);
199}
QGroundControl specific state machine with enhanced error handling.
void setMaxEntries(int max)
Set the maximum number of entries to keep (circular buffer)
QString dumpHistory() const
Get a human-readable dump of the history.
void addEntry(const QString &stateName, TransitionReason reason, const QString &details=QString())
Manually add an entry (for custom transition types)
QList< HistoryEntry > entriesForState(const QString &stateName) const
Get entries for a specific state.
QJsonArray toJson() const
Export history as JSON array.
StateHistoryRecorder(QGCStateMachine *machine, int maxEntries=1000)
void clear()
Clear all recorded history.
void setEnabled(bool enabled)
Enable or disable recording.
QList< HistoryEntry > lastEntries(int n) const
Get the last N entries.
TransitionReason
Reason for a state transition.
@ Timeout
Transition due to timeout.
@ Error
Transition due to error.
@ Signal
Transition triggered by signal.
@ Event
Transition triggered by event.
@ Entered
State was entered.
@ Exited
State was exited.
void logHistory() const
Log history to debug output.
A recorded state transition entry.