File indexing completed on 2024-12-22 05:29:18

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 "ryzetelloconnection.h"
0022 #include "debug.h"
0023 
0024 #include <QNetworkDatagram>
0025 
0026 RyzeTelloConnection::RyzeTelloConnection(const QString &vehicleName, QObject *parent)
0027     : QObject(parent)
0028     , m_vehicleName(vehicleName)
0029     , m_address(QHostAddress("192.168.10.1"))
0030     , m_roll(0)
0031     , m_pitch(0)
0032     , m_yaw(0)
0033     , m_gaz(0)
0034 {
0035 }
0036 
0037 RyzeTelloConnection::~RyzeTelloConnection()
0038 {
0039 }
0040 
0041 void RyzeTelloConnection::handshake()
0042 {
0043     initSockets();
0044 }
0045 
0046 void RyzeTelloConnection::reset()
0047 {
0048     m_commandQueueTimer.reset();
0049 
0050     m_commandQueue.clear();
0051 
0052     m_controlSocket->abort();
0053     delete m_controlSocket;
0054 
0055     m_stateSocket->abort();
0056     delete m_stateSocket;
0057 
0058     emit stateChanged(Kirogi::AbstractVehicle::Disconnected);
0059 }
0060 
0061 void RyzeTelloConnection::sendCommand(const QString &_command, bool retryForever)
0062 {
0063     RyzeTelloCommand command;
0064     command.text = _command;
0065 
0066     if (retryForever) {
0067         command.retry = -1;
0068     }
0069 
0070     m_commandQueue.enqueue(command);
0071 
0072     if (!m_commandQueueTimer || !m_commandQueueTimer->isActive()) {
0073         pumpCommandQueue();
0074     }
0075 }
0076 
0077 void RyzeTelloConnection::pilot(qint8 roll, qint8 pitch, qint8 yaw, qint8 gaz)
0078 {
0079     m_roll = roll;
0080     m_pitch = pitch;
0081     m_yaw = yaw;
0082     m_gaz = gaz;
0083 
0084     if (roll != 0 || pitch != 0 || yaw != 0 || gaz != 0) {
0085         if (!m_pilotingTimer) {
0086             m_pilotingTimer = std::make_unique<QTimer>(this);
0087             m_pilotingTimer->setInterval(40);
0088             QObject::connect(m_pilotingTimer.get(), &QTimer::timeout, this, &RyzeTelloConnection::sendPilotingCommand);
0089         }
0090 
0091         if (!m_pilotingTimer->isActive()) {
0092             sendPilotingCommand();
0093         }
0094 
0095         m_pilotingTimer->start();
0096     } else if (m_pilotingTimer) {
0097         m_pilotingTimer->stop();
0098         sendPilotingCommand();
0099     }
0100 }
0101 
0102 void RyzeTelloConnection::receiveData()
0103 {
0104     while (m_controlSocket->hasPendingDatagrams()) {
0105         const QNetworkDatagram &datagram = m_controlSocket->receiveDatagram();
0106 
0107         if (datagram.isValid()) {
0108             const QByteArray &data = datagram.data();
0109             const QString &encoded = QString::fromUtf8(data);
0110 
0111             if (encoded != QStringLiteral("error")) {
0112                 emit responseReceived(encoded);
0113 
0114                 if (m_commandQueue.size() > 1) {
0115                     m_commandQueue.dequeue();
0116                     pumpCommandQueue();
0117                 } else {
0118                     m_commandQueue.clear();
0119                 }
0120             }
0121         }
0122     }
0123 }
0124 
0125 void RyzeTelloConnection::receiveState()
0126 {
0127     while (m_stateSocket->hasPendingDatagrams()) {
0128         const QNetworkDatagram &datagram = m_stateSocket->receiveDatagram();
0129 
0130         if (datagram.isValid()) {
0131             emit stateReceived(datagram.data());
0132         }
0133     }
0134 }
0135 
0136 void RyzeTelloConnection::pumpCommandQueue()
0137 {
0138     // We do this here instead of in the constructor so the timer is created on the
0139     // current thread. It's not allowed to start/stop timers across thread boundaries.
0140     if (!m_commandQueueTimer) {
0141         m_commandQueueTimer = std::make_unique<QTimer>(this);
0142         QObject::connect(m_commandQueueTimer.get(), &QTimer::timeout, this, &RyzeTelloConnection::pumpCommandQueue, Qt::QueuedConnection);
0143     }
0144 
0145     m_commandQueueTimer->stop();
0146 
0147     if (m_commandQueue.isEmpty()) {
0148         return;
0149     }
0150 
0151     RyzeTelloCommand &command = m_commandQueue.head();
0152 
0153     if (command.retry > -1) {
0154         if (command.retry >= 5) {
0155             m_commandQueue.dequeue();
0156 
0157             if (m_commandQueue.isEmpty()) {
0158                 return;
0159             }
0160         } else {
0161             ++command.retry;
0162         }
0163     }
0164 
0165     m_controlSocket->writeDatagram(m_commandQueue.head().text.toUtf8(), m_address, 8889);
0166 
0167     // 300ms inspired by defaults in https://github.com/dji-sdk/Tello-Python
0168     m_commandQueueTimer->start(300);
0169 }
0170 
0171 void RyzeTelloConnection::sendPilotingCommand()
0172 {
0173     const QByteArray &decoded =
0174         QString("rc %1 %2 %3 %4").arg(QString::number(m_roll), QString::number(m_pitch), QString::number(m_gaz), QString::number(m_yaw)).toUtf8();
0175     m_controlSocket->writeDatagram(decoded, m_address, 8889);
0176 }
0177 
0178 void RyzeTelloConnection::initSockets()
0179 {
0180     m_controlSocket = new QUdpSocket(this);
0181     QObject::connect(m_controlSocket, &QUdpSocket::readyRead, this, &RyzeTelloConnection::receiveData);
0182     m_controlSocket->bind(QHostAddress::AnyIPv4, 8889);
0183 
0184     m_stateSocket = new QUdpSocket(this);
0185     QObject::connect(m_stateSocket, &QUdpSocket::readyRead, this, &RyzeTelloConnection::receiveState);
0186     m_stateSocket->bind(QHostAddress::AnyIPv4, 8890);
0187 
0188     emit stateChanged(Kirogi::AbstractVehicle::Connecting);
0189 }