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, ¶mValue); 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 }