File indexing completed on 2024-12-29 05:20:09

0001 /*
0002  * Copyright 2019 Patrick José Pereira <patrickjp@kde.org>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) version 3, or any
0008  * later version accepted by the membership of KDE e.V. (or its
0009  * successor approved by the membership of KDE e.V.), which shall
0010  * act as a proxy defined in Section 6 of version 3 of the license.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019  */
0020 
0021 #include "mavlinkvehicle.h"
0022 #include "parametermodel.h"
0023 
0024 #include <QDataStream>
0025 #include <QDateTime>
0026 #include <QStandardPaths>
0027 
0028 #include "mavlink_plugin_debug.h"
0029 
0030 MAVLinkVehicle::MAVLinkVehicle(Configuration configuration, MAVLinkConnection *connection, QObject *parent)
0031     : Kirogi::AbstractVehicle(parent)
0032     , m_connection(connection)
0033     , m_configuration(configuration)
0034     , m_name(configuration.name) // TODO: name field is duplicated.
0035     , m_roll(0.0)
0036     , m_pitch(0.0)
0037     , m_yaw(0.0)
0038     , m_signalStrength(-1)
0039     , m_batteryLevel(-1)
0040     , m_gpsFix(false)
0041 {
0042     QObject::connect(m_connection, &MAVLinkUdpConnection::stateChanged, this, [this]() {
0043         switch (m_connection->state()) {
0044         case Kirogi::AbstractConnection::State::Disconnected:
0045             setConnectionState(Kirogi::AbstractVehicle::Disconnected);
0046             break;
0047         case Kirogi::AbstractConnection::State::Connected:
0048             setConnectionState(Kirogi::AbstractVehicle::Connected);
0049             break;
0050         }
0051     });
0052     QObject::connect(this, &MAVLinkVehicle::connectionStateChanged, this, &MAVLinkVehicle::fetchParameters);
0053     QObject::connect(m_connection, &MAVLinkConnection::messageReceived, this, &MAVLinkVehicle::processMessage);
0054 
0055     m_commandResendTimer.setSingleShot(true);
0056     m_commandResendTimer.setInterval(3000);
0057     QObject::connect(&m_commandResendTimer, &QTimer::timeout, this, &MAVLinkVehicle::sendCommandInQueue);
0058 }
0059 
0060 MAVLinkVehicle::~MAVLinkVehicle()
0061 {
0062 }
0063 
0064 void MAVLinkVehicle::processMessage(MAVLinkConnection *connection, const mavlink_message_t &message)
0065 {
0066     Q_UNUSED(connection);
0067 
0068     if (connectionState() < Kirogi::AbstractVehicle::ConnectionState::Ready) {
0069         setConnectionState(Kirogi::AbstractVehicle::ConnectionState::Ready);
0070     }
0071 
0072     switch (message.msgid) {
0073     case MAVLINK_MSG_ID_HEARTBEAT:
0074         handleHeartbeat(message);
0075         break;
0076     case MAVLINK_MSG_ID_COMMAND_ACK:
0077         handleCommandAck(message);
0078         break;
0079     case MAVLINK_MSG_ID_RC_CHANNELS_SCALED:
0080         handleRcChannelsScaled(message);
0081         break;
0082     case MAVLINK_MSG_ID_RC_CHANNELS_RAW:
0083         handleRcChannelsRaw(message);
0084         break;
0085     case MAVLINK_MSG_ID_RC_CHANNELS:
0086         handleRcChannels(message);
0087         break;
0088     case MAVLINK_MSG_ID_ALTITUDE:
0089         handleAltitude(message);
0090         break;
0091     case MAVLINK_MSG_ID_ATTITUDE:
0092         handleAttitude(message);
0093         break;
0094     case MAVLINK_MSG_ID_BATTERY_STATUS:
0095         handleBatteryStatus(message);
0096         break;
0097     case MAVLINK_MSG_ID_GLOBAL_POSITION_INT:
0098         handleGlobalPositionInt(message);
0099         break;
0100     case MAVLINK_MSG_ID_PARAM_VALUE:
0101         handleParamValue(message);
0102         break;
0103     }
0104 }
0105 
0106 QString MAVLinkVehicle::name() const
0107 {
0108     return m_name;
0109 }
0110 
0111 QString MAVLinkVehicle::iconName() const
0112 {
0113     switch (m_configuration.type) {
0114     case MAV_TYPE_QUADROTOR:
0115         return QStringLiteral("uav-quadcopter");
0116         break;
0117     default:
0118         return QStringLiteral("uav-quadcopter");
0119     }
0120 }
0121 
0122 Kirogi::AbstractVehicle::VehicleType MAVLinkVehicle::vehicleType() const
0123 {
0124     switch (m_configuration.type) {
0125     case MAV_TYPE_QUADROTOR:
0126         return Kirogi::AbstractVehicle::QuadCopter;
0127         break;
0128     default:
0129         return Kirogi::AbstractVehicle::UnknownVehicleType;
0130     }
0131 }
0132 
0133 QList<Kirogi::AbstractVehicle::VehicleAction> MAVLinkVehicle::supportedActions() const
0134 {
0135     // TODO: Improve this to work with mavlink infrastructure
0136     QList<Kirogi::AbstractVehicle::VehicleAction> actions;
0137 
0138     actions << Kirogi::AbstractVehicle::TakeOff;
0139     actions << Kirogi::AbstractVehicle::Land;
0140     actions << Kirogi::AbstractVehicle::FlipForward;
0141     actions << Kirogi::AbstractVehicle::FlipBackward;
0142     actions << Kirogi::AbstractVehicle::FlipLeft;
0143     actions << Kirogi::AbstractVehicle::FlipRight;
0144     actions << Kirogi::AbstractVehicle::SwitchPerformanceMode;
0145 
0146     return actions;
0147 }
0148 
0149 void MAVLinkVehicle::requestAction(Kirogi::AbstractVehicle::VehicleAction action)
0150 {
0151     if (!ready()) {
0152         qWarning() << name()
0153                    << "Requested an action while not ready, aborting. Current "
0154                       "connection state:"
0155                    << connectionState();
0156 
0157         return;
0158     }
0159 
0160     switch (action) {
0161     case TakeOff: {
0162         if (flying()) {
0163             return;
0164         }
0165 
0166         // Set the vehicle in stabilize mode and after that arm
0167         CommandQueueItem commandQueueItem;
0168 
0169         commandQueueItem.command_int = false;
0170         commandQueueItem.target_system = m_configuration.sysid;
0171         commandQueueItem.target_component = m_configuration.compid;
0172         commandQueueItem.command = MAV_CMD_DO_SET_MODE;
0173         commandQueueItem.confirmation = 0;
0174         commandQueueItem.params[0] = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
0175         commandQueueItem.params[1] = 0;
0176         commandQueueItem.params[2] = 0; // 0 for stabilize
0177         commandQueueItem.params[3] = 0;
0178         commandQueueItem.params[4] = 0;
0179         commandQueueItem.params[5] = 0;
0180         commandQueueItem.params[6] = 0;
0181 
0182         m_commandQueue.append(commandQueueItem);
0183 
0184         commandQueueItem.command_int = false;
0185         commandQueueItem.target_system = m_configuration.sysid;
0186         commandQueueItem.target_component = m_configuration.compid;
0187         commandQueueItem.command = MAV_CMD_COMPONENT_ARM_DISARM;
0188         commandQueueItem.confirmation = 0;
0189         commandQueueItem.params[0] = 1;
0190         commandQueueItem.params[1] = 0;
0191         commandQueueItem.params[2] = 0;
0192         commandQueueItem.params[3] = 0;
0193         commandQueueItem.params[4] = 0;
0194         commandQueueItem.params[5] = 0;
0195         commandQueueItem.params[6] = 0;
0196 
0197         m_commandQueue.append(commandQueueItem);
0198 
0199         if (m_commandQueue.count() == 2) {
0200             m_commandResendCount = 0;
0201             sendCommandInQueue();
0202         }
0203 
0204         break;
0205     }
0206     case Land: {
0207         if (!flying()) {
0208             return;
0209         }
0210 
0211         // Disarm vehicle
0212         CommandQueueItem commandQueueItem;
0213 
0214         commandQueueItem.command_int = false;
0215         commandQueueItem.target_system = m_configuration.sysid;
0216         commandQueueItem.target_component = m_configuration.compid;
0217         commandQueueItem.command = MAV_CMD_COMPONENT_ARM_DISARM;
0218         commandQueueItem.confirmation = 0;
0219         commandQueueItem.params[0] = 0;
0220         commandQueueItem.params[1] = 0;
0221         commandQueueItem.params[2] = 0;
0222         commandQueueItem.params[3] = 0;
0223         commandQueueItem.params[4] = 0;
0224         commandQueueItem.params[5] = 0;
0225         commandQueueItem.params[6] = 0;
0226 
0227         m_commandQueue.append(commandQueueItem);
0228 
0229         if (m_commandQueue.count() == 1) {
0230             m_commandResendCount = 0;
0231             sendCommandInQueue();
0232         }
0233 
0234         break;
0235     }
0236     default: {
0237     }
0238     }
0239 }
0240 
0241 void MAVLinkVehicle::sendCommandInQueue()
0242 {
0243     if (m_commandQueue.count() == 0) {
0244         return;
0245     }
0246 
0247     if (m_commandResendCount++ > 10) {
0248         m_commandQueue.removeFirst();
0249         m_commandResendCount = 0;
0250         sendCommandInQueue();
0251         return;
0252     }
0253 
0254     CommandQueueItem commandQueueItem = m_commandQueue[0];
0255     mavlink_command_long_t command_long;
0256 
0257     command_long.target_system = commandQueueItem.target_system;
0258     command_long.target_component = commandQueueItem.target_component;
0259     command_long.command = commandQueueItem.command;
0260     command_long.confirmation = commandQueueItem.confirmation;
0261     command_long.param1 = commandQueueItem.params[0];
0262     command_long.param2 = commandQueueItem.params[1];
0263     command_long.param3 = commandQueueItem.params[2];
0264     command_long.param4 = commandQueueItem.params[3];
0265     command_long.param5 = commandQueueItem.params[4];
0266     command_long.param6 = commandQueueItem.params[5];
0267     command_long.param7 = commandQueueItem.params[6];
0268 
0269     m_commandResendTimer.start();
0270     m_connection->sendMessage(command_long);
0271 }
0272 
0273 void MAVLinkVehicle::pilot(qint8 roll, qint8 pitch, qint8 yaw, qint8 gaz)
0274 {
0275     mavlink_manual_control_t manual_control;
0276 
0277     manual_control.target = 1;
0278     manual_control.x = pitch * 10; // [-1000,1000] range
0279     manual_control.y = roll * 10; // [-1000,1000] range
0280     manual_control.z = gaz * 10; // [-1000,1000] range
0281     manual_control.r = yaw * 10; // [-1000,1000] range
0282     manual_control.buttons = 0;
0283 
0284     m_connection->sendMessage(manual_control);
0285 }
0286 
0287 float MAVLinkVehicle::roll() const
0288 {
0289     return m_roll;
0290 }
0291 
0292 float MAVLinkVehicle::pitch() const
0293 {
0294     return m_pitch;
0295 }
0296 
0297 float MAVLinkVehicle::yaw() const
0298 {
0299     return m_yaw;
0300 }
0301 
0302 int MAVLinkVehicle::signalStrength() const
0303 {
0304     return m_signalStrength;
0305 }
0306 
0307 int MAVLinkVehicle::batteryLevel() const
0308 {
0309     return m_batteryLevel;
0310 }
0311 
0312 bool MAVLinkVehicle::gpsSupported() const
0313 {
0314     return true;
0315 }
0316 
0317 bool MAVLinkVehicle::gpsFix() const
0318 {
0319     return m_gpsFix;
0320 }
0321 
0322 QGeoCoordinate MAVLinkVehicle::gpsPosition() const
0323 {
0324     return m_gpsPosition;
0325 }
0326 
0327 float MAVLinkVehicle::altitude() const
0328 {
0329     return m_altitudeSource.altitude;
0330 }
0331 
0332 QString MAVLinkVehicle::videoSource() const
0333 {
0334     return QStringLiteral(
0335         "rtspsrc location=rtsp://192.168.99.1/media/stream2 latency=5 ! "
0336         "rtph264depay ! "
0337         "video/x-h264 ! "
0338         "queue ! "
0339         "h264parse ! "
0340         "decodebin ! ");
0341 }
0342 
0343 void MAVLinkVehicle::fetchParameters()
0344 {
0345     // We need to handle this in a different way to ensure that we are going to receive all parameters.
0346     static bool requested = false;
0347 
0348     if (connectionState() < Kirogi::AbstractVehicle::ConnectionState::Connected || requested) {
0349         return;
0350     }
0351 
0352     requested = true;
0353 
0354     mavlink_param_request_list_t param_request_list;
0355 
0356     param_request_list.target_system = 1;
0357     param_request_list.target_component = 1;
0358 
0359     m_connection->sendMessage(param_request_list);
0360 }
0361 
0362 void MAVLinkVehicle::handleHeartbeat(const mavlink_message_t &message)
0363 {
0364     mavlink_heartbeat_t heartbeat;
0365     mavlink_msg_heartbeat_decode(&message, &heartbeat);
0366 
0367     // TODO: Support mavlink state more precisely.
0368     switch (heartbeat.system_status) {
0369     case MAV_STATE_UNINIT:
0370         setFlyingState(Kirogi::AbstractVehicle::FlyingState::Unknown);
0371         break;
0372     case MAV_STATE_STANDBY:
0373         setFlyingState(Kirogi::AbstractVehicle::FlyingState::Landed);
0374         break;
0375     case MAV_STATE_ACTIVE:
0376         setFlyingState(Kirogi::AbstractVehicle::FlyingState::Flying);
0377         break;
0378     case MAV_STATE_BOOT:
0379     case MAV_STATE_CALIBRATING:
0380     case MAV_STATE_CRITICAL:
0381     case MAV_STATE_EMERGENCY:
0382     case MAV_STATE_POWEROFF:
0383     case MAV_STATE_FLIGHT_TERMINATION:
0384         break;
0385     default:
0386         qCDebug(KIROGI_VEHICLESUPPORT_MAVLINK_PLUGIN) << QString("Received heartbeat with state %1 which is not handled.").arg(heartbeat.system_status);
0387     }
0388 }
0389 
0390 void MAVLinkVehicle::handleCommandAck(const mavlink_message_t &message)
0391 {
0392     if (m_commandQueue.size() == 0) {
0393         qCDebug(KIROGI_VEHICLESUPPORT_MAVLINK_PLUGIN) << "Received command ack when there's no pending command in queue.";
0394         return;
0395     }
0396 
0397     mavlink_command_ack_t commandAck;
0398     mavlink_msg_command_ack_decode(&message, &commandAck);
0399 
0400     if (commandAck.command == m_commandQueue[0].command) {
0401         m_commandResendCount = 0;
0402         m_commandResendTimer.stop();
0403         m_commandQueue.removeFirst();
0404         // This recursive usage of function makes stack too deep. It would be better to use event loop.
0405         sendCommandInQueue();
0406     } else {
0407         qCDebug(KIROGI_VEHICLESUPPORT_MAVLINK_PLUGIN) << "Received unintended ack message.";
0408     }
0409 }
0410 
0411 void MAVLinkVehicle::handleRcChannelsScaled(const mavlink_message_t &message)
0412 {
0413     mavlink_rc_channels_scaled_t channels;
0414     mavlink_msg_rc_channels_scaled_decode(&message, &channels);
0415 
0416     m_signalStrength = channels.rssi >= 255 ? -1 : static_cast<float>(channels.rssi) / 2.54f;
0417 
0418     emit signalStrengthChanged();
0419 }
0420 
0421 void MAVLinkVehicle::handleRcChannelsRaw(const mavlink_message_t &message)
0422 {
0423     mavlink_rc_channels_raw_t channels;
0424     mavlink_msg_rc_channels_raw_decode(&message, &channels);
0425 
0426     m_signalStrength = channels.rssi >= 255 ? -1 : static_cast<float>(channels.rssi) / 2.54f;
0427 
0428     emit signalStrengthChanged();
0429 }
0430 
0431 void MAVLinkVehicle::handleRcChannels(const mavlink_message_t &message)
0432 {
0433     mavlink_rc_channels_t channels;
0434     mavlink_msg_rc_channels_decode(&message, &channels);
0435 
0436     m_signalStrength = channels.rssi >= 255 ? -1 : static_cast<float>(channels.rssi) / 2.54f;
0437 
0438     emit signalStrengthChanged();
0439 }
0440 
0441 void MAVLinkVehicle::handleAltitude(const mavlink_message_t &message)
0442 {
0443     mavlink_altitude_t altitude;
0444     mavlink_msg_altitude_decode(&message, &altitude);
0445 
0446     m_altitudeSource.altitudeMessage = true;
0447     m_altitudeSource.altitude = altitude.altitude_terrain;
0448 
0449     emit altitudeChanged();
0450 }
0451 
0452 void MAVLinkVehicle::handleAttitude(const mavlink_message_t &message)
0453 {
0454     mavlink_attitude_t attitude;
0455     mavlink_msg_attitude_decode(&message, &attitude);
0456 
0457     m_yaw = attitude.yaw;
0458     m_roll = attitude.roll;
0459     m_pitch = attitude.pitch;
0460 
0461     emit rollChanged();
0462     emit pitchChanged();
0463     emit yawChanged();
0464     emit attitudeChanged();
0465 }
0466 
0467 void MAVLinkVehicle::handleBatteryStatus(const mavlink_message_t &message)
0468 {
0469     mavlink_battery_status_t status;
0470     mavlink_msg_battery_status_decode(&message, &status);
0471 
0472     m_batteryLevel = status.battery_remaining;
0473 
0474     emit batteryLevelChanged();
0475 }
0476 
0477 void MAVLinkVehicle::handleGlobalPositionInt(const mavlink_message_t &message)
0478 {
0479     mavlink_global_position_int_t position;
0480     mavlink_msg_global_position_int_decode(&message, &position);
0481 
0482     m_gpsFix = !(qFuzzyCompare(position.lat, 0.0) && qFuzzyCompare(position.lon, 0.0));
0483     emit gpsFixChanged();
0484 
0485     if (m_gpsFix) {
0486         m_gpsPosition.setLatitude(position.lat / 1e7);
0487         m_gpsPosition.setLongitude(position.lon / 1e7);
0488     } else {
0489         // Invalidate position if there is no fix
0490         m_gpsPosition.setLatitude(qQNaN());
0491         m_gpsPosition.setLongitude(qQNaN());
0492     }
0493 
0494     m_gpsPosition.setAltitude(position.alt / 1e3);
0495     emit gpsPositionChanged();
0496 
0497     // Check if altitude message was received
0498     if (!m_altitudeSource.altitudeMessage) {
0499         m_altitudeSource.altitude = m_gpsPosition.altitude();
0500         emit altitudeChanged();
0501     }
0502 }
0503 
0504 void MAVLinkVehicle::handleParamValue(const mavlink_message_t &message)
0505 {
0506     mavlink_param_value_t paramValue;
0507     mavlink_msg_param_value_decode(&message, &paramValue);
0508 
0509     parameters()->update({
0510         {ParameterModel::Roles::Name, QString(paramValue.param_id)},
0511         {ParameterModel::Roles::Value, static_cast<float>(paramValue.param_value)},
0512         {ParameterModel::Roles::Index, static_cast<int>(paramValue.param_index)},
0513     });
0514 }