/**************************************************************************** * * (c) 2009-2016 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ #include "MAVLinkInspectorController.h" #include "QGCApplication.h" #include "MultiVehicleManager.h" #include QGC_LOGGING_CATEGORY(MAVLinkInspectorLog, "MAVLinkInspectorLog") QT_CHARTS_USE_NAMESPACE Q_DECLARE_METATYPE(QAbstractSeries*) //----------------------------------------------------------------------------- QGCMAVLinkMessageField::QGCMAVLinkMessageField(QGCMAVLinkMessage *parent, QString name, QString type) : QObject(parent) , _type(type) , _name(name) , _msg(parent) { qCDebug(MAVLinkInspectorLog) << "Field:" << name << type; } //----------------------------------------------------------------------------- QString QGCMAVLinkMessageField::label() { return QString(_msg->name() + ": " + _name); } //----------------------------------------------------------------------------- void QGCMAVLinkMessageField::setSelectable(bool sel) { if(_selectable != sel) { _selectable = sel; emit selectableChanged(); } } //----------------------------------------------------------------------------- void QGCMAVLinkMessageField::setSelected(bool sel) { if(_selected != sel) { _selected = sel; emit selectedChanged(); _values.clear(); _times.clear(); _rangeMin = 0; _rangeMax = 0; _dataIndex = 0; emit rangeMinChanged(); emit rangeMaxChanged(); if(_selected) { _msg->msgCtl()->addChartField(this); } else { _msg->msgCtl()->delChartField(this); } _msg->select(); } } //----------------------------------------------------------------------------- void QGCMAVLinkMessageField::updateValue(QString newValue, qreal v) { if(_value != newValue) { _value = newValue; emit valueChanged(); } if(_selected) { int count = _values.count(); //-- Arbitrary limit of 1 minute of data at 50Hz for now if(count < (50 * 60)) { _values.append(v); _times.append(QGC::groundTimeMilliseconds()); } else { if(_dataIndex >= count) _dataIndex = 0; _values[_dataIndex] = v; _times[_dataIndex] = QGC::groundTimeMilliseconds(); _dataIndex++; } qreal vmin = std::numeric_limits::max(); qreal vmax = std::numeric_limits::min(); for(int i = 0; i < _values.count(); i++) { qreal v = _values[i]; if(vmax < v) vmax = v; if(vmin > v) vmin = v; } if(std::abs(_rangeMin - vmin) > 0.000001) { _rangeMin = vmin; emit rangeMinChanged(); } if(std::abs(_rangeMax - vmax) > 0.000001) { _rangeMax = vmax; emit rangeMaxChanged(); } _msg->msgCtl()->updateXRange(); _updateSeries(); } } //----------------------------------------------------------------------------- void QGCMAVLinkMessageField::_updateSeries() { int count = _values.count(); if (count > 1) { _series.clear(); int idx = _dataIndex; for(int i = 0; i < count; i++, idx++) { if(idx >= count) idx = 0; QPointF p(_times[idx], _values[idx]); _series.append(p); } emit seriesChanged(); } } //----------------------------------------------------------------------------- QGCMAVLinkMessage::QGCMAVLinkMessage(MAVLinkInspectorController *parent, mavlink_message_t* message) : QObject(parent) , _msgCtl(parent) { _message = *message; const mavlink_message_info_t* msgInfo = mavlink_get_message_info(message); if (!msgInfo) { qWarning() << QStringLiteral("QGCMAVLinkMessage NULL msgInfo msgid(%1)").arg(message->msgid); return; } _name = QString(msgInfo->name); qCDebug(MAVLinkInspectorLog) << "New Message:" << _name; for (unsigned int i = 0; i < msgInfo->num_fields; ++i) { QString type = QString("?"); switch (msgInfo->fields[i].type) { case MAVLINK_TYPE_CHAR: type = QString("char"); break; case MAVLINK_TYPE_UINT8_T: type = QString("uint8_t"); break; case MAVLINK_TYPE_INT8_T: type = QString("int8_t"); break; case MAVLINK_TYPE_UINT16_T: type = QString("uint16_t"); break; case MAVLINK_TYPE_INT16_T: type = QString("int16_t"); break; case MAVLINK_TYPE_UINT32_T: type = QString("uint32_t"); break; case MAVLINK_TYPE_INT32_T: type = QString("int32_t"); break; case MAVLINK_TYPE_FLOAT: type = QString("float"); break; case MAVLINK_TYPE_DOUBLE: type = QString("double"); break; case MAVLINK_TYPE_UINT64_T: type = QString("uint64_t"); break; case MAVLINK_TYPE_INT64_T: type = QString("int64_t"); break; } QGCMAVLinkMessageField* f = new QGCMAVLinkMessageField(this, msgInfo->fields[i].name, type); _fields.append(f); } } //----------------------------------------------------------------------------- void QGCMAVLinkMessage::select() { bool sel = false; for (int i = 0; i < _fields.count(); ++i) { QGCMAVLinkMessageField* f = qobject_cast(_fields.get(i)); if(f) { if(f->selected()) { sel = true; break; } } } if(sel != _selected) { _selected = sel; emit selectedChanged(); } } //----------------------------------------------------------------------------- void QGCMAVLinkMessage::updateFreq() { quint64 msgCount = _count - _lastCount; _messageHz = (0.2 * _messageHz) + (0.8 * msgCount); _lastCount = _count; emit freqChanged(); } //----------------------------------------------------------------------------- void QGCMAVLinkMessage::update(mavlink_message_t* message) { _message = *message; _count++; const mavlink_message_info_t* msgInfo = mavlink_get_message_info(message); if (!msgInfo) { qWarning() << QStringLiteral("QGCMAVLinkMessage::update NULL msgInfo msgid(%1)").arg(message->msgid); return; } if(_fields.count() != static_cast(msgInfo->num_fields)) { qWarning() << QStringLiteral("QGCMAVLinkMessage::update msgInfo field count mismatch msgid(%1)").arg(message->msgid); return; } uint8_t* m = reinterpret_cast(&message->payload64[0]); for (unsigned int i = 0; i < msgInfo->num_fields; ++i) { QGCMAVLinkMessageField* f = qobject_cast(_fields.get(static_cast(i))); if(f) { switch (msgInfo->fields[i].type) { case MAVLINK_TYPE_CHAR: f->setSelectable(false); if (msgInfo->fields[i].array_length > 0) { char* str = reinterpret_cast(m+ msgInfo->fields[i].wire_offset); // Enforce null termination str[msgInfo->fields[i].array_length - 1] = '\0'; QString v(str); f->updateValue(v, 0); } else { // Single char char b = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); QString v(b); f->updateValue(v, 0); } break; case MAVLINK_TYPE_UINT8_T: if (msgInfo->fields[i].array_length > 0) { uint8_t* nums = m+msgInfo->fields[i].wire_offset; // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value uint8_t u = *(m+msgInfo->fields[i].wire_offset); f->updateValue(QString::number(u), static_cast(u)); } break; case MAVLINK_TYPE_INT8_T: if (msgInfo->fields[i].array_length > 0) { int8_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value int8_t n = *(reinterpret_cast(m+msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(n), static_cast(n)); } break; case MAVLINK_TYPE_UINT16_T: if (msgInfo->fields[i].array_length > 0) { uint16_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value uint16_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(n), static_cast(n)); } break; case MAVLINK_TYPE_INT16_T: if (msgInfo->fields[i].array_length > 0) { int16_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value int16_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(n), static_cast(n)); } break; case MAVLINK_TYPE_UINT32_T: if (msgInfo->fields[i].array_length > 0) { uint32_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value uint32_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); //-- Special case if(_message.msgid == MAVLINK_MSG_ID_SYSTEM_TIME) { QDateTime d = QDateTime::fromMSecsSinceEpoch(static_cast(n),Qt::UTC,0); f->updateValue(d.toString("HH:mm:ss"), static_cast(n)); } else { f->updateValue(QString::number(n), static_cast(n)); } } break; case MAVLINK_TYPE_INT32_T: if (msgInfo->fields[i].array_length > 0) { int32_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value int32_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(n), static_cast(n)); } break; case MAVLINK_TYPE_FLOAT: if (msgInfo->fields[i].array_length > 0) { float* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(static_cast(nums[j])); } string += QString::number(static_cast(nums[msgInfo->fields[i].array_length - 1])); f->updateValue(string, static_cast(nums[0])); } else { // Single value float fv = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(static_cast(fv)), static_cast(fv)); } break; case MAVLINK_TYPE_DOUBLE: if (msgInfo->fields[i].array_length > 0) { double* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(static_cast(nums[msgInfo->fields[i].array_length - 1])); f->updateValue(string, static_cast(nums[0])); } else { // Single value double d = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(d), static_cast(d)); } break; case MAVLINK_TYPE_UINT64_T: if (msgInfo->fields[i].array_length > 0) { uint64_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value uint64_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); //-- Special case if(_message.msgid == MAVLINK_MSG_ID_SYSTEM_TIME) { QDateTime d = QDateTime::fromMSecsSinceEpoch(n/1000,Qt::UTC,0); f->updateValue(d.toString("yyyy MM dd HH:mm:ss"), static_cast(n)); } else { f->updateValue(QString::number(n), static_cast(n)); } } break; case MAVLINK_TYPE_INT64_T: if (msgInfo->fields[i].array_length > 0) { int64_t* nums = reinterpret_cast(m + msgInfo->fields[i].wire_offset); // Enforce null termination QString tmp("%1, "); QString string; for (unsigned int j = 0; j < msgInfo->fields[i].array_length - 1; ++j) { string += tmp.arg(nums[j]); } string += QString::number(nums[msgInfo->fields[i].array_length - 1]); f->updateValue(string, static_cast(nums[0])); } else { // Single value int64_t n = *(reinterpret_cast(m + msgInfo->fields[i].wire_offset)); f->updateValue(QString::number(n), static_cast(n)); } break; } } } emit messageChanged(); } //----------------------------------------------------------------------------- QGCMAVLinkVehicle::QGCMAVLinkVehicle(QObject* parent, quint8 id) : QObject(parent) , _id(id) { qCDebug(MAVLinkInspectorLog) << "New Vehicle:" << id; } //----------------------------------------------------------------------------- QGCMAVLinkMessage* QGCMAVLinkVehicle::findMessage(uint32_t id, uint8_t cid) { for(int i = 0; i < _messages.count(); i++) { QGCMAVLinkMessage* m = qobject_cast(_messages.get(i)); if(m) { if(m->id() == id && m->cid() == cid) { return m; } } } return nullptr; } //----------------------------------------------------------------------------- static bool messages_sort(QObject* a, QObject* b) { QGCMAVLinkMessage* aa = qobject_cast(a); QGCMAVLinkMessage* bb = qobject_cast(b); if(!aa || !bb) return false; if(aa->id() == bb->id()) return aa->cid() < bb->cid(); return aa->id() < bb->id(); } //----------------------------------------------------------------------------- void QGCMAVLinkVehicle::append(QGCMAVLinkMessage* message) { _messages.append(message); //-- Sort messages by id and then cid if(_messages.count() > 0) { std::sort(_messages.objectList()->begin(), _messages.objectList()->end(), messages_sort); for(int i = 0; i < _messages.count(); i++) { QGCMAVLinkMessage* m = qobject_cast(_messages.get(i)); if(m) { emit m->indexChanged(); } } _checkCompID(message); } emit messagesChanged(); } //----------------------------------------------------------------------------- void QGCMAVLinkVehicle::_checkCompID(QGCMAVLinkMessage* message) { if(_compIDsStr.isEmpty()) { _compIDsStr << tr("All"); } if(!_compIDs.contains(static_cast(message->cid()))) { int cid = static_cast(message->cid()); _compIDs.append(cid); _compIDsStr << QString::number(cid); emit compIDsChanged(); } } //----------------------------------------------------------------------------- MAVLinkInspectorController::MAVLinkInspectorController() { MultiVehicleManager* multiVehicleManager = qgcApp()->toolbox()->multiVehicleManager(); connect(multiVehicleManager, &MultiVehicleManager::vehicleAdded, this, &MAVLinkInspectorController::_vehicleAdded); connect(multiVehicleManager, &MultiVehicleManager::vehicleRemoved, this, &MAVLinkInspectorController::_vehicleRemoved); MAVLinkProtocol* mavlinkProtocol = qgcApp()->toolbox()->mavlinkProtocol(); connect(mavlinkProtocol, &MAVLinkProtocol::messageReceived, this, &MAVLinkInspectorController::_receiveMessage); connect(&_updateTimer, &QTimer::timeout, this, &MAVLinkInspectorController::_refreshFrequency); _updateTimer.start(1000); MultiVehicleManager *manager = qgcApp()->toolbox()->multiVehicleManager(); connect(manager, &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkInspectorController::_setActiveVehicle); _rangeXMax = QDateTime::fromMSecsSinceEpoch(0); _rangeXMin = QDateTime::fromMSecsSinceEpoch(std::numeric_limits::max()); _timeScales << tr("5 Sec"); _timeScales << tr("10 Sec"); _timeScales << tr("30 Sec"); _timeScales << tr("60 Sec"); } //----------------------------------------------------------------------------- MAVLinkInspectorController::~MAVLinkInspectorController() { _reset(); } //---------------------------------------------------------------------------------------- void MAVLinkInspectorController::_setActiveVehicle(Vehicle* vehicle) { if(vehicle) { QGCMAVLinkVehicle* v = _findVehicle(static_cast(vehicle->id())); if(v) { _activeVehicle = v; } else { _activeVehicle = nullptr; } } else { _activeVehicle = nullptr; } emit activeVehiclesChanged(); } //----------------------------------------------------------------------------- QGCMAVLinkVehicle* MAVLinkInspectorController::_findVehicle(uint8_t id) { for(int i = 0; i < _vehicles.count(); i++) { QGCMAVLinkVehicle* v = qobject_cast(_vehicles.get(i)); if(v) { if(v->id() == id) { return v; } } } return nullptr; } //----------------------------------------------------------------------------- void MAVLinkInspectorController::_refreshFrequency() { for(int i = 0; i < _vehicles.count(); i++) { QGCMAVLinkVehicle* v = qobject_cast(_vehicles.get(i)); if(v) { for(int i = 0; i < v->messages()->count(); i++) { QGCMAVLinkMessage* m = qobject_cast(v->messages()->get(i)); if(m) { m->updateFreq(); } } } } } //----------------------------------------------------------------------------- void MAVLinkInspectorController::_vehicleAdded(Vehicle* vehicle) { QGCMAVLinkVehicle* v = _findVehicle(static_cast(vehicle->id())); if(v) { v->messages()->clearAndDeleteContents(); emit v->messagesChanged(); } else { v = new QGCMAVLinkVehicle(this, static_cast(vehicle->id())); _vehicles.append(v); _vehicleNames.append(tr("Vehicle %1").arg(vehicle->id())); } emit vehiclesChanged(); } //----------------------------------------------------------------------------- void MAVLinkInspectorController::_vehicleRemoved(Vehicle* vehicle) { QGCMAVLinkVehicle* v = _findVehicle(static_cast(vehicle->id())); if(v) { v->deleteLater(); _vehicles.removeOne(v); QString vs = tr("Vehicle %1").arg(vehicle->id()); _vehicleNames.removeOne(vs); emit vehiclesChanged(); } } //----------------------------------------------------------------------------- void MAVLinkInspectorController::_receiveMessage(LinkInterface*, mavlink_message_t message) { QGCMAVLinkMessage* m = nullptr; QGCMAVLinkVehicle* v = _findVehicle(message.sysid); if(!v) { v = new QGCMAVLinkVehicle(this, message.sysid); _vehicles.append(v); _vehicleNames.append(tr("Vehicle %1").arg(message.sysid)); emit vehiclesChanged(); if(!_activeVehicle) { _activeVehicle = v; emit activeVehiclesChanged(); } } else { m = v->findMessage(message.msgid, message.compid); } if(!m) { m = new QGCMAVLinkMessage(this, &message); v->append(m); } else { m->update(&message); } } //----------------------------------------------------------------------------- void MAVLinkInspectorController::_reset() { } //----------------------------------------------------------------------------- void MAVLinkInspectorController::addChartField(QGCMAVLinkMessageField* field) { QVariant f = QVariant::fromValue(field); for(int i = 0; i < _chartFields.count(); i++) { if(_chartFields.at(i) == f) { return; } } _chartFields.append(f); emit chartFieldCountChanged(); } //----------------------------------------------------------------------------- void MAVLinkInspectorController::delChartField(QGCMAVLinkMessageField* field) { QVariant f = QVariant::fromValue(field); for(int i = 0; i < _chartFields.count(); i++) { if(_chartFields.at(i) == f) { _chartFields.removeAt(i); emit chartFieldCountChanged(); if(_chartFields.count() == 0) { _rangeXMax = QDateTime::fromMSecsSinceEpoch(0); _rangeXMin = QDateTime::fromMSecsSinceEpoch(std::numeric_limits::max()); } return; } } } //----------------------------------------------------------------------------- void MAVLinkInspectorController::updateSeries(int index, QAbstractSeries* series) { if(index < _chartFields.count() && series) { QGCMAVLinkMessageField* f = qvariant_cast(_chartFields.at(index)); QLineSeries* lineSeries = static_cast(series); lineSeries->replace(f->series()); } } //----------------------------------------------------------------------------- void MAVLinkInspectorController::setTimeScale(quint32 t) { _timeScale = t; emit timeScaleChanged(); updateXRange(); } //----------------------------------------------------------------------------- void MAVLinkInspectorController::updateXRange() { int ts = 5 * 1000; switch(_timeScale) { case 1: ts = 10 * 1000; break; case 2: ts = 30 * 1000; break; case 3: ts = 60 * 1000; break; } qint64 t = static_cast(QGC::groundTimeMilliseconds()); _rangeXMax = QDateTime::fromMSecsSinceEpoch(t); _rangeXMin = QDateTime::fromMSecsSinceEpoch(t - ts); emit rangeMinXChanged(); emit rangeMaxXChanged(); }