dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
telemetrymonitor.cpp
Go to the documentation of this file.
1 
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful, but
21  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  * for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, see <http://www.gnu.org/licenses/>
27  *
28  * Additional note on redistribution: The copyright and license notices above
29  * must be maintained in each individual source file that is a derivative work
30  * of this source file; otherwise redistribution is prohibited.
31  */
32 
33 #include "telemetrymonitor.h"
35 #include "coreplugin/icore.h"
36 #include "firmwareiapobj.h"
37 
38 // Timeout for the object fetching phase, the system will stop fetching objects and emit connected
39 // after this
40 #define OBJECT_RETRIEVE_TIMEOUT 20000
41 // IAP object is very important, retry if not able to get it the first time
42 #define IAP_OBJECT_RETRIES 3
43 
44 #ifdef TELEMETRYMONITOR_DEBUG
45 #define TELEMETRYMONITOR_QXTLOG_DEBUG(...) qDebug() << __VA_ARGS__
46 #else // TELEMETRYMONITOR_DEBUG
47 #define TELEMETRYMONITOR_QXTLOG_DEBUG(...)
48 #endif // TELEMETRYMONITOR_DEBUG
49 
50 bool TelemetryMonitor::queueCompare(UAVObject *left, UAVObject *right)
51 {
52  /* Should return true if right should be dequeued before left. */
53 
54  /* It's advantageous to dequeue multi-instance objs first, so if
55  * there's many IDs we can be doing the round trips while other
56  * objects arrive (instead of getting stuck round-trip bound */
57 
58  if (left->isSingleInstance() && (!right->isSingleInstance())) {
59  return true;
60  } else if ((!left->isSingleInstance() && right->isSingleInstance())) {
61  return false;
62  }
63 
64  /* It's advantageous to dequeue metaobjects last, because we
65  * probably know about their existance by this stage. */
66  UAVDataObject *leftD = dynamic_cast<UAVDataObject *>(left);
67  UAVDataObject *rightD = dynamic_cast<UAVDataObject *>(right);
68 
69  if (leftD && (!rightD)) {
70  /* Right is a metaobj */
71  return false;
72  } else if ((!leftD) && (rightD)) {
73  /* Right is NOT a metaobj */
74  return true;
75  }
76 
77  /* At this point either both are meta or both are not meta */
78  if (leftD) {
79  /* It's advantageous to dequeue settings next, since they're
80  * not auto-telemetered and we won't find out about them by
81  * serendipity */
82  if ((!leftD->isSettings()) && (rightD->isSettings())) {
83  return true;
84  } else if ((leftD->isSettings()) && (!rightD->isSettings())) {
85  return false;
86  }
87  }
88 
89  /* Fall back to name last, because it's pleasing to retrieve in order */
90  return left->getName() > right->getName();
91 }
92 
97  : connectionStatus(CON_DISCONNECTED)
98  , objMngr(objMngr)
99  , tel(tel)
100  , queue(decltype(queue)(queueCompare))
101  , requestsInFlight(0)
102 {
103  this->connectionTimer = new QTime();
104  // Get stats objects
105  gcsStatsObj = GCSTelemetryStats::GetInstance(objMngr);
106  flightStatsObj = FlightTelemetryStats::GetInstance(objMngr);
107 
108  // Listen for flight stats updates
109  connect(flightStatsObj, &UAVObject::objectUpdated, this, &TelemetryMonitor::flightStatsUpdated);
110 
111  // Start update timer
112  statsTimer = new QTimer(this);
113  objectRetrieveTimeout = new QTimer(this);
114  objectRetrieveTimeout->setSingleShot(true);
115  connect(statsTimer, &QTimer::timeout, this, &TelemetryMonitor::processStatsUpdates);
116  connect(objectRetrieveTimeout, &QTimer::timeout, this,
117  &TelemetryMonitor::objectRetrieveTimeoutCB);
118  statsTimer->start(STATS_CONNECT_PERIOD_MS);
119 
122  connect(this, &TelemetryMonitor::disconnected, cm,
124  connect(this, &TelemetryMonitor::telemetryUpdated, cm,
126  connect(objMngr, &UAVObjectManager::newInstance, this, &TelemetryMonitor::newInstanceSlot);
127 }
128 
130 {
131  // Before saying goodbye, set the GCS connection status to disconnected too:
132  GCSTelemetryStats::DataFields gcsStats = gcsStatsObj->getData();
133  gcsStats.Status = GCSTelemetryStats::STATUS_DISCONNECTED;
134 
135  foreach (UAVObjectManager::ObjectMap map, objMngr->getObjects()) {
136  foreach (UAVObject *obj, map.values()) {
137  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
138  if (dobj)
139  dobj->resetIsPresentOnHardware();
140  }
141  }
142 
143  // Set data
144  gcsStatsObj->setData(gcsStats);
145 }
146 
150 void TelemetryMonitor::startRetrievingObjects()
151 {
152  TELEMETRYMONITOR_QXTLOG_DEBUG(
153  QString("%0 connectionStatus changed to CON_RETRIEVING_OBJECT").arg(Q_FUNC_INFO));
154  connectionStatus = CON_RETRIEVING_OBJECTS;
155 
156  /* Clear the queue */
157  queue = decltype(queue)(queueCompare);
158 
159  objectRetrieveTimeout->start(OBJECT_RETRIEVE_TIMEOUT);
160  foreach (UAVObjectManager::ObjectMap map, objMngr->getObjects().values()) {
161  UAVObject *obj = map.first();
162 
163  /* Enqueue everything; decide later whether to bother retrieving. */
164  queue.push(obj);
165  }
166 
167  // Start retrieving
168  TELEMETRYMONITOR_QXTLOG_DEBUG(
169  QString(
170  tr("Starting to retrieve objects from the autopilot (%1 objects)"))
171  .arg(queue.size()));
172  retrieveNextObject();
173 }
174 
178 void TelemetryMonitor::retrieveNextObject()
179 {
180  // If queue is empty return
181  if (queue.empty() && requestsInFlight == 0) {
182  TELEMETRYMONITOR_QXTLOG_DEBUG(QString("%0 Object retrieval completed").arg(Q_FUNC_INFO));
183  TELEMETRYMONITOR_QXTLOG_DEBUG(
184  QString("%0 connectionStatus set to CON_CONNECTED_MANAGED( %1 )")
185  .arg(Q_FUNC_INFO)
186  .arg(connectionStatus));
187  connectionStatus = CON_CONNECTED_MANAGED;
188  emit connected();
189  objectRetrieveTimeout->stop();
190  return;
191  }
192 
193  objectRetrieveTimeout->stop();
194  objectRetrieveTimeout->start(OBJECT_RETRIEVE_TIMEOUT);
195 
196  while (requestsInFlight < MAX_REQUESTS_IN_FLIGHT) {
197  if (queue.empty()) {
198  return;
199  }
200 
201  // Get next object from the queue
202  UAVObject *obj = queue.top();
203  queue.pop();
204 
205  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
206 
207  if (dobj) {
208  if (dobj->getReceived() && dobj->isSingleInstance()) {
209  /* We got it already. That's easy! */
210  continue;
211  }
212 
213  if (dobj->getPresenceKnown() && (!dobj->getIsPresentOnHardware())) {
214  TELEMETRYMONITOR_QXTLOG_DEBUG(QString("%0 %1 not present on hardware, skipping")
215  .arg(Q_FUNC_INFO)
216  .arg(obj->getName()));
217  continue;
218  }
219  } else {
220  /* For metaobjects, skip if data object not there */
221  UAVMetaObject *mobj = dynamic_cast<UAVMetaObject *>(obj);
222 
223  if (mobj) {
224  UAVDataObject *pobj = dynamic_cast<UAVDataObject *>(
225  mobj->getParentObject());
226  if (pobj->getPresenceKnown() && (!pobj->getIsPresentOnHardware())) {
227  TELEMETRYMONITOR_QXTLOG_DEBUG(QString("%0 %1 not present on hardware, skipping associated meta obj")
228  .arg(Q_FUNC_INFO)
229  .arg(obj->getName()));
230  continue;
231  }
232  }
233  }
234 
235  // Connect to object
236  TELEMETRYMONITOR_QXTLOG_DEBUG(QString("%0 requesting %1 from board INSTID:%2")
237  .arg(Q_FUNC_INFO)
238  .arg(obj->getName())
239  .arg(obj->getInstID()));
240 
241  requestsInFlight++;
242 
243  connect(obj, QOverload<UAVObject *, bool, bool>::of(&UAVObject::transactionCompleted), this,
245  // Request update
246  obj->requestUpdate();
247  }
248 }
249 
253 void TelemetryMonitor::transactionCompleted(UAVObject *obj, bool success, bool nacked)
254 {
255  TELEMETRYMONITOR_QXTLOG_DEBUG(QString("%0 received %1 OBJID:%2 result:%3")
256  .arg(Q_FUNC_INFO)
257  .arg(obj->getName())
258  .arg(obj->getObjID())
259  .arg(success));
260  Q_UNUSED(success);
261 
262  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
263 
264  if (nacked) {
265  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
266  if (dobj) {
267  if (dobj->isSingleInstance()) {
268  dobj->setIsPresentOnHardware(false);
269  } else {
270  int instId = dobj->getInstID();
271 
272  if (instId <= 0) {
273  dobj->setIsPresentOnHardware(false);
274  } else {
275  /* Multi-instance, shrink instance count down. Inst
276  * ID must be 1, so getNumInstances should be 2 or
277  * larger */
278  int idx;
279 
280  idx = objMngr->getNumInstances(dobj->getObjID());
281 
282  while (idx > instId) {
283  idx--;
284  qInfo() << QString("Got nak instID %1 removing %2").arg(instId).arg(idx);
285  UAVObject *objR = objMngr->getObject(dobj->getObjID(),
286  idx);
287  UAVDataObject *dobjR = dynamic_cast<UAVDataObject *>(objR);
288  if (dobjR) {
289  objMngr->unRegisterObject(dobjR);
290  }
291  }
292  }
293  }
294  }
295  } else if (success) {
296  if (dobj && (!dobj->isSingleInstance())) {
297  /* multi-instance... temporarily register and enqueue next
298  * instance
299  */
300  int new_idx = dobj->getInstID() + 1;
301 
302  UAVObject *instObj = objMngr->getObject(dobj->getObjID(),
303  new_idx);
304 
305  UAVDataObject *instdObj;
306  if (instObj == NULL) {
307  instdObj = dobj->clone(new_idx);
308  objMngr->registerObject(instdObj);
309  } else {
310  instdObj = dynamic_cast<UAVDataObject *>(instObj);
311  }
312 
313  queue.push(instdObj);
314  }
315  }
316 
317  // Disconnect from sending object
318  requestsInFlight--;
319  obj->disconnect(this);
320 
321  // Process next object if telemetry is still available
322  GCSTelemetryStats::DataFields gcsStats = gcsStatsObj->getData();
323  if (gcsStats.Status == GCSTelemetryStats::STATUS_CONNECTED) {
324  connectionStatus = CON_RETRIEVING_OBJECTS;
325 
326  retrieveNextObject();
327  } else {
328  qInfo() <<
329  QString("%0 connection lost while retrieving objects, stopped object retrievel")
330  .arg(Q_FUNC_INFO);
331  /* Clear the queue */
332  queue = decltype(queue)(queueCompare);
333 
334  objectRetrieveTimeout->stop();
335  connectionStatus = CON_DISCONNECTED;
336  }
337 }
338 
343 {
344  Q_UNUSED(obj);
345 
346  // Force update if not yet connected
347  GCSTelemetryStats::DataFields gcsStats = gcsStatsObj->getData();
348  FlightTelemetryStats::DataFields flightStats = flightStatsObj->getData();
349  if (gcsStats.Status != GCSTelemetryStats::STATUS_CONNECTED
350  || flightStats.Status != FlightTelemetryStats::STATUS_CONNECTED) {
352  }
353 }
354 
355 void TelemetryMonitor::objectRetrieveTimeoutCB()
356 {
357  qInfo() <<
358  QString("%0 reached timeout for object retrieval, clearing queue")
359  .arg(Q_FUNC_INFO);
360 
361  /* Clear the queue */
362  queue = decltype(queue)(queueCompare);
363 }
364 
365 void TelemetryMonitor::newInstanceSlot(UAVObject *obj)
366 {
367  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
368  if (!dobj)
369  return;
370  dobj->setIsPresentOnHardware(true);
371 }
372 
377 {
378  // Get telemetry stats
379  GCSTelemetryStats::DataFields gcsStats = gcsStatsObj->getData();
380  FlightTelemetryStats::DataFields flightStats = flightStatsObj->getData();
381  Telemetry::TelemetryStats telStats = tel->getStats();
382 
383  // Update stats object
384  gcsStats.RxDataRate = (float)telStats.rxBytes / ((float)statsTimer->interval() / 1000.0);
385  gcsStats.TxDataRate = (float)telStats.txBytes / ((float)statsTimer->interval() / 1000.0);
386  gcsStats.RxFailures += telStats.rxErrors;
387  gcsStats.TxFailures += telStats.txErrors;
388  gcsStats.TxRetries += telStats.txRetries;
389 
390  // Check for a connection timeout
391  bool connectionTimeout;
392  if (telStats.rxObjects > 0) {
393  connectionTimer->start();
394  }
395  if (connectionTimer->elapsed() > CONNECTION_TIMEOUT_MS) {
396  connectionTimeout = true;
397  } else {
398  connectionTimeout = false;
399  }
400 
401  // Update connection state
402  int oldStatus = gcsStats.Status;
403  if (gcsStats.Status == GCSTelemetryStats::STATUS_DISCONNECTED) {
404  // Request connection
405  gcsStats.Status = GCSTelemetryStats::STATUS_HANDSHAKEREQ;
406  } else if (gcsStats.Status == GCSTelemetryStats::STATUS_HANDSHAKEREQ) {
407  // Check for connection acknowledge
408  if (flightStats.Status == FlightTelemetryStats::STATUS_HANDSHAKEACK) {
409  gcsStats.Status = GCSTelemetryStats::STATUS_CONNECTED;
410  }
411  } else if (gcsStats.Status == GCSTelemetryStats::STATUS_CONNECTED) {
412  // Check if the connection is still active and the the autopilot is still connected
413  if (flightStats.Status == FlightTelemetryStats::STATUS_DISCONNECTED || connectionTimeout) {
414  gcsStats.Status = GCSTelemetryStats::STATUS_DISCONNECTED;
415  }
416  }
417 
418  emit telemetryUpdated((double)gcsStats.TxDataRate, (double)gcsStats.RxDataRate);
419 
420  // Set data
421  gcsStatsObj->setData(gcsStats);
422 
423  // Force telemetry update if not yet connected
424  if (gcsStats.Status != GCSTelemetryStats::STATUS_CONNECTED
425  || flightStats.Status != FlightTelemetryStats::STATUS_CONNECTED) {
426  gcsStatsObj->updated();
427  }
428 
429  // Act on new connections or disconnections
430  if (gcsStats.Status == GCSTelemetryStats::STATUS_CONNECTED && gcsStats.Status != oldStatus) {
431  statsTimer->setInterval(STATS_UPDATE_PERIOD_MS);
432  qDebug() << "Connection with the autopilot established";
433  connectionStatus = CON_INITIALIZING;
434  startRetrievingObjects();
435  } else if (gcsStats.Status == GCSTelemetryStats::STATUS_DISCONNECTED && gcsStats.Status != oldStatus) {
436  statsTimer->setInterval(STATS_CONNECT_PERIOD_MS);
437  connectionStatus = CON_DISCONNECTED;
438  foreach (UAVObjectManager::ObjectMap map, objMngr->getObjects()) {
439  foreach (UAVObject *obj, map.values()) {
440  UAVDataObject *dobj = dynamic_cast<UAVDataObject *>(obj);
441  if (dobj)
442  dobj->resetIsPresentOnHardware();
443  }
444  }
445 
446  emit disconnected();
447  }
448 }
void transactionCompleted(UAVObject *obj, bool success, bool nacked)
TelemetryMonitor(UAVObjectManager *objMngr, Telemetry *tel)
void requestUpdate()
Definition: uavobject.cpp:163
QMap< quint32, UAVObject * > ObjectMap
bool registerObject(UAVDataObject *obj)
UAVObject * getParentObject()
void newInstance(UAVObject *obj)
void flightStatsUpdated(UAVObject *obj)
virtual ConnectionManager * connectionManager() const =0
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
quint32 getInstID()
Definition: uavobject.cpp:115
void transactionCompleted(UAVObject *obj, bool success)
transactionCompleted. Triggered by a call to emitTransactionCompleted - done in telemetry.cpp whenever a transaction finishes.
bool isSingleInstance()
Definition: uavobject.cpp:123
static ICore * instance()
Definition: coreimpl.cpp:46
void telemetryUpdated(double txRate, double rxRate)
bool getIsPresentOnHardware() const
QHash< quint32, QMap< quint32, UAVObject * > > getObjects()
void resetIsPresentOnHardware()
quint32 getObjID()
Definition: uavobject.cpp:107
void telemetryUpdated(double txRate, double rxRate)
QString getName()
Definition: uavobject.cpp:131
virtual UAVDataObject * clone(quint32 instID=0)=0
bool getPresenceKnown() const
UAVObject * getObject(const QString &name, quint32 instId=0)
TelemetryStats getStats()
Definition: telemetry.cpp:702
bool unRegisterObject(UAVDataObject *obj)
unregisters an object instance and all instances bigger than the one passed as argument from the mana...
void setIsPresentOnHardware(bool value=true)
qint32 getNumInstances(const QString &name)