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 }