File indexing completed on 2024-06-09 05:46:37
0001 /* 0002 * Copyright 2019 Eike Hein <hein@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 "ryzetellovehicle.h" 0022 #include "debug.h" 0023 #include "ryzetelloconnection.h" 0024 0025 #include <QDataStream> 0026 #include <QDateTime> 0027 #include <QStandardPaths> 0028 0029 RyzeTelloVehicle::RyzeTelloVehicle(QObject *parent) 0030 : Kirogi::AbstractVehicle(parent) 0031 , m_stateSeq(0) 0032 , m_motorOnTime(-1) 0033 , m_oldMotorOnTime(-1) 0034 , m_roll(0.0) 0035 , m_pitch(0.0) 0036 , m_yaw(0.0) 0037 , m_signalStrength(100) 0038 , m_batteryLevel(100) 0039 , m_distance(0.0) 0040 , m_altitude(0.0) 0041 , m_speed(0.0) 0042 , m_signalStrengthTimer(std::make_unique<QTimer>(this)) 0043 , m_disconnectTimer(std::make_unique<QTimer>(this)) 0044 , m_connection(nullptr) 0045 { 0046 m_connection = new RyzeTelloConnection(name()); 0047 0048 // Queued connections across thread boundaries. 0049 QObject::connect(m_connection, &RyzeTelloConnection::stateChanged, this, &RyzeTelloVehicle::setConnectionState, Qt::QueuedConnection); 0050 0051 QObject::connect( 0052 m_connection, 0053 &RyzeTelloConnection::stateChanged, 0054 this, 0055 [this](AbstractVehicle::ConnectionState state) { 0056 if (state == Connecting) { 0057 initVehicle(); 0058 } 0059 0060 if (state == Disconnected) { 0061 m_stateSeq = 0; 0062 m_motorOnTime = 0; 0063 m_oldMotorOnTime = 0; 0064 0065 m_signalStrengthTimer->stop(); 0066 0067 setFlyingState(Unknown); 0068 } 0069 }, 0070 Qt::QueuedConnection); 0071 0072 QObject::connect(m_connection, &RyzeTelloConnection::responseReceived, this, &RyzeTelloVehicle::processIncomingResponse, Qt::QueuedConnection); 0073 0074 QObject::connect(m_connection, &RyzeTelloConnection::stateReceived, this, &RyzeTelloVehicle::processIncomingState, Qt::QueuedConnection); 0075 0076 // Do the networking on a separate thread, so our fixed-tick work never gets 0077 // blocked by activity on the main thread. 0078 m_connection->moveToThread(&m_connectionThread); 0079 QObject::connect(&m_connectionThread, &QThread::finished, m_connection, &QObject::deleteLater); 0080 0081 m_connectionThread.setObjectName(QStringLiteral("RyzeTelloConnectionThread")); 0082 m_connectionThread.start(); 0083 } 0084 0085 RyzeTelloVehicle::~RyzeTelloVehicle() 0086 { 0087 m_connectionThread.quit(); 0088 m_connectionThread.wait(); 0089 } 0090 0091 QString RyzeTelloVehicle::name() const 0092 { 0093 return QStringLiteral("Tello"); 0094 } 0095 0096 QString RyzeTelloVehicle::iconName() const 0097 { 0098 return QStringLiteral("uav-quadcopter"); 0099 } 0100 0101 Kirogi::AbstractVehicle::VehicleType RyzeTelloVehicle::vehicleType() const 0102 { 0103 return Kirogi::AbstractVehicle::QuadCopter; 0104 } 0105 0106 QList<Kirogi::AbstractVehicle::VehicleAction> RyzeTelloVehicle::supportedActions() const 0107 { 0108 return {Kirogi::AbstractVehicle::TakeOff, 0109 Kirogi::AbstractVehicle::Land, 0110 Kirogi::AbstractVehicle::FlipForward, 0111 Kirogi::AbstractVehicle::FlipBackward, 0112 Kirogi::AbstractVehicle::FlipLeft, 0113 Kirogi::AbstractVehicle::FlipRight, 0114 Kirogi::AbstractVehicle::SwitchPerformanceMode}; 0115 } 0116 0117 void RyzeTelloVehicle::requestAction(Kirogi::AbstractVehicle::VehicleAction action) 0118 { 0119 if (!ready()) { 0120 qCWarning(KIROGI_VEHICLESUPPORT_RYZETELLO) << name() << "Requested an action while not ready, aborting. Current connection state:" << connectionState(); 0121 0122 return; 0123 } 0124 0125 switch (action) { 0126 case TakeOff: { 0127 if (flying()) { 0128 return; 0129 } 0130 0131 sendCommand(QStringLiteral("takeoff")); 0132 setFlyingState(TakingOff); // FIXME: We don't /really/ know that without looking at the response. 0133 break; 0134 } 0135 case Land: { 0136 if (!flying()) { 0137 return; 0138 } 0139 0140 sendCommand(QStringLiteral("land")); 0141 setFlyingState(Landing); // FIXME: We don't /really/ know that without looking at the response. 0142 break; 0143 } 0144 case FlipForward: { 0145 if (!flying()) { 0146 return; 0147 } 0148 0149 sendCommand(QStringLiteral("flip f")); 0150 break; 0151 } 0152 case FlipBackward: { 0153 if (!flying()) { 0154 return; 0155 } 0156 0157 sendCommand(QStringLiteral("flip b")); 0158 break; 0159 } 0160 case FlipLeft: { 0161 if (!flying()) { 0162 return; 0163 } 0164 0165 sendCommand(QStringLiteral("flip l")); 0166 break; 0167 } 0168 case FlipRight: { 0169 if (!flying()) { 0170 return; 0171 } 0172 0173 sendCommand(QStringLiteral("flip r")); 0174 break; 0175 } 0176 default: { 0177 } 0178 } 0179 } 0180 0181 void RyzeTelloVehicle::pilot(qint8 roll, qint8 pitch, qint8 yaw, qint8 gaz) 0182 { 0183 if (!flying()) { 0184 qCWarning(KIROGI_VEHICLESUPPORT_RYZETELLO) << name() << "Piloting request while not flying, aborting. Current flying state:" << flyingState(); 0185 0186 return; 0187 } 0188 0189 QMetaObject::invokeMethod(m_connection, "pilot", Qt::QueuedConnection, Q_ARG(qint8, roll), Q_ARG(qint8, pitch), Q_ARG(qint8, yaw), Q_ARG(qint8, gaz)); 0190 } 0191 0192 Kirogi::AbstractVehicle::PerformanceMode RyzeTelloVehicle::performanceMode() const 0193 { 0194 return CustomPerformance; // FIXME 0195 } 0196 0197 void RyzeTelloVehicle::requestPerformanceMode(Kirogi::AbstractVehicle::PerformanceMode mode) 0198 { 0199 if (performanceMode() != mode) { 0200 switch (mode) { 0201 case FilmPerformance: { 0202 sendCommand(QStringLiteral("speed 10")); 0203 0204 break; 0205 } 0206 case SportPerformance: { 0207 sendCommand(QStringLiteral("speed 100")); 0208 0209 break; 0210 } 0211 default: { 0212 } 0213 } 0214 } 0215 } 0216 0217 float RyzeTelloVehicle::roll() const 0218 { 0219 return m_roll; 0220 } 0221 0222 float RyzeTelloVehicle::pitch() const 0223 { 0224 return m_pitch; 0225 } 0226 0227 float RyzeTelloVehicle::yaw() const 0228 { 0229 return m_yaw; 0230 } 0231 0232 int RyzeTelloVehicle::signalStrength() const 0233 { 0234 return m_signalStrength; 0235 } 0236 0237 int RyzeTelloVehicle::batteryLevel() const 0238 { 0239 return m_batteryLevel; 0240 } 0241 0242 bool RyzeTelloVehicle::gpsSupported() const 0243 { 0244 return false; 0245 } 0246 0247 float RyzeTelloVehicle::distance() const 0248 { 0249 return m_distance; 0250 } 0251 0252 float RyzeTelloVehicle::altitude() const 0253 { 0254 return m_altitude; 0255 } 0256 0257 float RyzeTelloVehicle::speed() const 0258 { 0259 return m_speed; 0260 } 0261 0262 void RyzeTelloVehicle::connectToVehicle() 0263 { 0264 if (connectionState() > Disconnected) { 0265 if (connectionState() > Connecting) { 0266 qCDebug(KIROGI_VEHICLESUPPORT_RYZETELLO) << name() << "Asked to connect when not disconnected."; 0267 } 0268 0269 QMetaObject::invokeMethod(m_connection, "reset", Qt::BlockingQueuedConnection); 0270 } 0271 0272 QMetaObject::invokeMethod(m_connection, "handshake", Qt::QueuedConnection); 0273 0274 // Keep re-trying every 3 seconds until we're successfully connected. 0275 QTimer::singleShot(3000, this, [this]() { 0276 if (connectionState() != Ready) { 0277 qCWarning(KIROGI_VEHICLESUPPORT_RYZETELLO) << name() << "Unable to establish connection within 3 seconds. Starting over."; 0278 0279 connectToVehicle(); 0280 } 0281 }); 0282 } 0283 0284 void RyzeTelloVehicle::processIncomingResponse(const QString &response) 0285 { 0286 if (connectionState() < Connected) { 0287 setConnectionState(Connected); 0288 } 0289 0290 if (connectionState() != Ready && response == QStringLiteral("ok")) { 0291 setConnectionState(Ready); 0292 0293 return; 0294 } 0295 0296 bool ok = false; 0297 const int signalStrength = response.trimmed().toInt(&ok); 0298 0299 if (ok) { 0300 m_disconnectTimer->stop(); 0301 0302 m_signalStrength = signalStrength; 0303 emit signalStrengthChanged(); 0304 0305 m_signalStrengthTimer->singleShot(2000, this, &RyzeTelloVehicle::pollSignalStrength); 0306 } 0307 } 0308 0309 void RyzeTelloVehicle::processIncomingState(const QByteArray &state) 0310 { 0311 // From the SDK doc: 0312 // "pitch:%d;roll:%d;yaw:%d;vgx:%d;vgy%d;vgz:%d;templ:%d;temph:%d;tof:%d;h:%d;bat:%d;baro:%.2f;time:%d;agx:%.2f;agy:%.2f;agz:%.2f;\r\n" 0313 0314 ++m_stateSeq; 0315 m_stateSeq %= 20; 0316 0317 const QString &encoded = QString::fromUtf8(state); 0318 const QStringList &segments = encoded.split(QChar(';')); 0319 0320 float speedX = 0.0; 0321 float speedY = 0.0; 0322 float speedZ = 0.0; 0323 0324 for (const QString &segment : segments) { 0325 if (segment.startsWith(QStringLiteral("roll"))) { 0326 m_roll = segment.midRef(5).toFloat() / 100; 0327 } else if (segment.startsWith(QStringLiteral("pitch"))) { 0328 m_pitch = segment.midRef(6).toFloat() / 100; 0329 } else if (segment.startsWith(QStringLiteral("yaw"))) { 0330 m_yaw = segment.midRef(4).toFloat() / 100; 0331 } else if (segment.startsWith(QStringLiteral("bat"))) { 0332 m_batteryLevel = segment.midRef(4).toInt(); 0333 } else if (segment.startsWith(QStringLiteral("tof"))) { 0334 m_distance = segment.midRef(2).toInt() / 100; 0335 } else if (segment.startsWith(QStringLiteral("h"))) { 0336 m_altitude = segment.midRef(2).toInt() / 100; 0337 } else if (segment.startsWith(QStringLiteral("vgx"))) { 0338 speedX = segment.midRef(4).toInt() / 100; 0339 } else if (segment.startsWith(QStringLiteral("vgy"))) { 0340 speedY = segment.midRef(4).toInt() / 100; 0341 } else if (segment.startsWith(QStringLiteral("vgz"))) { 0342 speedZ = segment.midRef(4).toInt() / 100; 0343 } else if (segment.startsWith(QStringLiteral("time"))) { 0344 m_motorOnTime = segment.midRef(5).toInt(); 0345 0346 if (m_oldMotorOnTime == -1) { 0347 m_oldMotorOnTime = m_motorOnTime; 0348 } 0349 } 0350 } 0351 0352 m_speed = std::max({speedX, speedY, speedZ}); 0353 0354 if (m_stateSeq == 19) { 0355 if (m_motorOnTime != m_oldMotorOnTime) { 0356 setFlyingState(Flying); 0357 } else { 0358 setFlyingState(Landed); 0359 } 0360 0361 m_oldMotorOnTime = m_motorOnTime; 0362 } 0363 0364 emit attitudeChanged(); 0365 emit rollChanged(); 0366 emit pitchChanged(); 0367 emit yawChanged(); 0368 0369 emit batteryLevelChanged(); 0370 0371 emit distanceChanged(); 0372 emit altitudeChanged(); 0373 0374 emit speedChanged(); 0375 } 0376 0377 void RyzeTelloVehicle::pollSignalStrength() 0378 { 0379 sendCommand(QStringLiteral("wifi?")); 0380 0381 if (connectionState() == Ready) { 0382 m_disconnectTimer->singleShot(500, this, &RyzeTelloVehicle::connectToVehicle); 0383 } 0384 } 0385 0386 void RyzeTelloVehicle::initVehicle() 0387 { 0388 sendCommand(QStringLiteral("command"), /* retryForever */ true); 0389 0390 requestPerformanceMode(FilmPerformance); 0391 0392 pollSignalStrength(); 0393 } 0394 0395 void RyzeTelloVehicle::sendCommand(const QString &command, bool retryForever) 0396 { 0397 if (connectionState() < Connecting) { 0398 qCWarning(KIROGI_VEHICLESUPPORT_RYZETELLO) << name() << "Request to send command" << command 0399 << "rejected. Connection not ready. Current connection state:" << connectionState(); 0400 return; 0401 } 0402 0403 QMetaObject::invokeMethod(m_connection, "sendCommand", Qt::QueuedConnection, Q_ARG(QString, command), Q_ARG(bool, retryForever)); 0404 }