File indexing completed on 2024-05-26 05:54:05

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 "parrotconnection.h"
0022 #include "debug.h"
0023 
0024 #include <QDataStream>
0025 #include <QHostInfo>
0026 #include <QJsonDocument>
0027 #include <QNetworkDatagram>
0028 
0029 ParrotConnection::ParrotConnection(ParrotVehicle::Type type, const QString &vehicleName, const QString &hostName, int port, QObject *parent)
0030     : QObject(parent)
0031     , m_type(type)
0032     , m_vehicleName(vehicleName)
0033     , m_port(port)
0034     , m_c2dport(-1)
0035     , m_d2cPort(54321)
0036     , m_roll(0)
0037     , m_pitch(0)
0038     , m_yaw(0)
0039     , m_gaz(0)
0040 {
0041 #ifndef Q_OS_ANDROID
0042     const QHostInfo &hostInfo = QHostInfo::fromName(hostName);
0043 
0044     if (!hostInfo.addresses().isEmpty()) {
0045         m_hostAddress = hostInfo.addresses().at(0);
0046         qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Resolved host name to:" << m_hostAddress;
0047     } else {
0048         qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << "Fatal: Unable to resolve host name to address.";
0049     }
0050 #else
0051     Q_UNUSED(hostName)
0052 
0053     // FIXME TODO: QHostInfo::fromName() doesn't seem to work on Android.
0054     m_hostAddress = QHostAddress("192.168.42.1");
0055 #endif
0056 }
0057 
0058 ParrotConnection::~ParrotConnection() = default;
0059 
0060 void ParrotConnection::handshake(const QString &productSerial)
0061 {
0062     // No point shaking hands if we don't have what we need to communicate later.
0063     if (m_hostAddress.isNull()) {
0064         qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Fatal: Cannot handshake without host address.";
0065 
0066         return;
0067     }
0068 
0069     emit stateChanged(Kirogi::AbstractVehicle::Connecting);
0070 
0071     m_handshakeSocket = new QTcpSocket(this);
0072 
0073     QObject::connect(m_handshakeSocket, &QTcpSocket::connected, this, [this, productSerial]() {
0074         QJsonObject obj;
0075         obj.insert(QStringLiteral("d2c_port"), m_d2cPort); // FIXME TODO: Don't hardcode the receive port. Current choice taken from Parrot example docs.
0076         obj.insert(QStringLiteral("controller_type"), QStringLiteral("desktop")); // FIXME TODO: Send different type based on host platform.
0077         obj.insert(QStringLiteral("controller_name"), QStringLiteral("KirogiParrotPlugin"));
0078         obj.insert(QStringLiteral("arstream2_client_stream_port"), 55004);
0079         obj.insert(QStringLiteral("arstream2_client_control_port"), 55005);
0080         obj.insert(QStringLiteral("arstream2_supported_metadata_version"), 1);
0081 
0082         if (!productSerial.isEmpty()) {
0083             obj.insert(QStringLiteral("device_id"), productSerial);
0084         }
0085 
0086         qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Sending handshake data:" << obj;
0087 
0088         QJsonDocument handshakeDoc;
0089         handshakeDoc.setObject(obj);
0090 
0091         m_handshakeSocket->write(handshakeDoc.toJson(QJsonDocument::Compact));
0092     });
0093 
0094     QObject::connect(m_handshakeSocket, &QTcpSocket::disconnected, this, [this]() {
0095         m_handshakeSocket->deleteLater();
0096     });
0097 
0098     QObject::connect(m_handshakeSocket, &QTcpSocket::readyRead, this, [this]() {
0099         const QByteArray &responseData = m_handshakeSocket->readAll();
0100 
0101         QJsonParseError error;
0102 
0103         // For Bebop 2 chopping by 1 byte, response data is \x00-terminated and the parser considers
0104         // this a fatal "garbage at end" error.
0105         const QJsonDocument &handshakeResponse = QJsonDocument::fromJson(m_type == ParrotVehicle::Bebop2 ? responseData.chopped(1) : responseData, &error);
0106 
0107         qDebug() << handshakeResponse;
0108 
0109         if (!handshakeResponse.isNull() && handshakeResponse.isObject()) {
0110             m_handshakeResponse = handshakeResponse.object();
0111         } else {
0112             qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Unable to decode handshake response.";
0113         }
0114 
0115         const int vehicleStatus = m_handshakeResponse.value(QStringLiteral("status")).toInt();
0116 
0117         if (vehicleStatus != 0) {
0118             qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Handshake unsuccessful. Vehicle status:";
0119             emit stateChanged(Kirogi::AbstractVehicle::Disconnected);
0120         } else {
0121             qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Handshake response:" << m_handshakeResponse;
0122 
0123             m_c2dport = m_handshakeResponse.value(QStringLiteral("c2d_port")).toInt();
0124             qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Got Controller-to-Device (c2d) port:" << m_c2dport;
0125 
0126             qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Handshake successful.";
0127 
0128             initSockets();
0129         }
0130 
0131         m_handshakeSocket->disconnectFromHost();
0132     });
0133 
0134     QObject::connect(m_handshakeSocket, &QAbstractSocket::errorOccurred, this, [this]() {
0135         qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Handshake failed with socket error:" << m_handshakeSocket->errorString();
0136         emit stateChanged(Kirogi::AbstractVehicle::Disconnected);
0137         m_handshakeSocket->abort();
0138     });
0139 
0140     qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Performing handshake ...";
0141 
0142     m_handshakeSocket->connectToHost(m_hostAddress, m_port);
0143 }
0144 
0145 void ParrotConnection::reset()
0146 {
0147     qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Resetting connection.";
0148 
0149     if (m_handshakeSocket) {
0150         m_handshakeSocket->abort();
0151         m_handshakeSocket->deleteLater();
0152     }
0153 
0154     m_c2dAckTimer.reset();
0155 
0156     m_c2dAckQueue.clear();
0157 
0158     m_d2cSocket->abort();
0159     delete m_d2cSocket;
0160     m_c2dSocket->abort();
0161     delete m_c2dSocket;
0162 
0163     emit stateChanged(Kirogi::AbstractVehicle::Disconnected);
0164 }
0165 
0166 void ParrotConnection::sendCommand(Parrot::Command _command, const QVariantList &arguments, bool retryForever)
0167 {
0168     ParrotCommand command(_command, arguments);
0169 
0170     // FIXME Generi-fy.
0171     int bufferId = Parrot::BUFFER_C2D_NON_ACK_DATA;
0172 
0173     if (_command != Parrot::Ardrone3PilotingPCMD) {
0174         bufferId = Parrot::BUFFER_C2D_ACK_DATA;
0175     }
0176 
0177     ParrotFrame frame(Parrot::DATA_WITH_ACK, bufferId, makeSeq(bufferId), command.pack());
0178 
0179     if (retryForever) {
0180         frame.retry = -1;
0181     }
0182 
0183     sendFrame(frame);
0184 }
0185 
0186 void ParrotConnection::pilot(qint8 roll, qint8 pitch, qint8 yaw, qint8 gaz)
0187 {
0188     m_roll = roll;
0189     m_pitch = pitch;
0190     m_yaw = yaw;
0191     m_gaz = gaz;
0192 
0193     if (roll != 0 || pitch != 0 || yaw != 0 || gaz != 0) {
0194         if (!m_pilotingTimer) {
0195             m_pilotingTimer = std::make_unique<QTimer>(this);
0196             m_pilotingTimer->setInterval(40);
0197             QObject::connect(m_pilotingTimer.get(), &QTimer::timeout, this, &ParrotConnection::sendPilotingCommand);
0198         }
0199 
0200         if (!m_pilotingTimer->isActive()) {
0201             sendPilotingCommand();
0202         }
0203 
0204         m_pilotingTimer->start();
0205     } else if (m_pilotingTimer) {
0206         m_pilotingTimer->stop();
0207         sendPilotingCommand();
0208     }
0209 }
0210 
0211 void ParrotConnection::receiveData()
0212 {
0213     while (m_d2cSocket->hasPendingDatagrams()) {
0214         const QNetworkDatagram &datagram = m_d2cSocket->receiveDatagram();
0215 
0216         if (datagram.isValid()) {
0217             const QByteArray &data = datagram.data();
0218 
0219             int bytesRead = 0;
0220 
0221             // There may be more than one frame in a datagram.
0222             while ((data.size() - bytesRead) >= Parrot::HEADER_FRAME) {
0223                 ParrotFrame frame(data, bytesRead);
0224                 bytesRead += frame.size;
0225                 processIncomingFrame(frame);
0226             }
0227         }
0228     }
0229 }
0230 
0231 void ParrotConnection::pumpC2dAckQueue()
0232 {
0233     // We do this here instead of in the constructor so the timer is created on the
0234     // current thread. It's not allowed to start/stop timers across thread boundaries.
0235     if (!m_c2dAckTimer) {
0236         m_c2dAckTimer = std::make_unique<QTimer>(this);
0237         QObject::connect(m_c2dAckTimer.get(), &QTimer::timeout, this, &ParrotConnection::pumpC2dAckQueue, Qt::QueuedConnection);
0238     }
0239 
0240     m_c2dAckTimer->stop();
0241 
0242     if (m_c2dAckQueue.isEmpty()) {
0243         return;
0244     }
0245 
0246     ParrotFrame &frame = m_c2dAckQueue.head();
0247 
0248     // Skip over frames that have been re-sent n times and went unacknowledged. They
0249     // are considered lost.
0250     // NOTE: According to Parrot docs, the number of retries is per-buffer. There's
0251     // one buffer for controller-to-device ACK data however, and it's documented with
0252     // a retry attempt value of 5.
0253     if (frame.retry != -1) {
0254         if (frame.retry >= 5) {
0255             qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Frame lost!";
0256 
0257             m_c2dAckQueue.dequeue();
0258 
0259             if (m_c2dAckQueue.isEmpty()) {
0260                 return;
0261             }
0262 
0263             frame = m_c2dAckQueue.head();
0264         } else {
0265             ++frame.retry;
0266         }
0267     }
0268 
0269     sendData(frame.pack(), frame.size);
0270 
0271     // NOTE: According to Parrot docs, the ACK timeout is theoretically per-buffer;
0272     // however, they currently use 150ms for all buffers in practice.
0273     m_c2dAckTimer->start(150);
0274 }
0275 
0276 void ParrotConnection::sendPilotingCommand()
0277 {
0278     const quint8 flag = 1;
0279     const quint32 timestampAndSeqNum = 0;
0280     sendCommand(Parrot::Ardrone3PilotingPCMD, {flag, m_roll, m_pitch, m_yaw, m_gaz, timestampAndSeqNum});
0281 }
0282 
0283 void ParrotConnection::initSockets()
0284 {
0285     qCDebug(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Initializing UDP socket ...";
0286 
0287     m_d2cSocket = new QUdpSocket(this);
0288     QObject::connect(m_d2cSocket, &QUdpSocket::readyRead, this, &ParrotConnection::receiveData);
0289 
0290     m_c2dSocket = new QUdpSocket(this);
0291 
0292     m_d2cSocket->bind(QHostAddress::AnyIPv4, m_d2cPort);
0293 
0294     emit stateChanged(Kirogi::AbstractVehicle::Connected);
0295 }
0296 
0297 void ParrotConnection::processIncomingFrame(const ParrotFrame &frame)
0298 {
0299     // Respond to PING with PONG.
0300     if (frame.bufferId == Parrot::BUFFER_PING) {
0301         sendFrame(ParrotFrame(Parrot::DATA, Parrot::BUFFER_PONG, makeSeq(Parrot::BUFFER_PONG), frame.data));
0302 
0303         return;
0304     }
0305 
0306     // Check if this is an ACK for a frame in our outbound queue;
0307     // if it is, pump the queue.
0308     if (frame.bufferId == Parrot::BUFFER_D2C_ACK_FOR_C2D_ACK_DATA) {
0309         if (m_c2dAckQueue.isEmpty()) {
0310             return;
0311         }
0312 
0313         QDataStream s(frame.data);
0314         s.setByteOrder(QDataStream::LittleEndian);
0315 
0316         quint8 seq;
0317 
0318         s >> seq;
0319 
0320         if (seq == m_c2dAckQueue.head().seq) {
0321             m_c2dAckQueue.dequeue();
0322             pumpC2dAckQueue();
0323         }
0324 
0325         return;
0326     }
0327 
0328     // Respond with ACK if requested.
0329     if (frame.dataType == Parrot::DATA_WITH_ACK) {
0330         sendAck(frame);
0331     }
0332 
0333     if (frame.size - Parrot::HEADER_FRAME >= Parrot::HEADER_COMMAND) {
0334         emit commandReceived(frame.command());
0335     }
0336 }
0337 
0338 void ParrotConnection::sendAck(const ParrotFrame &frame)
0339 {
0340     quint8 targetBuffer = (frame.bufferId + 128) % 256;
0341 
0342     QByteArray data;
0343     data.reserve(1);
0344 
0345     QDataStream s(&data, QIODevice::WriteOnly);
0346     s.setByteOrder(QDataStream::LittleEndian);
0347 
0348     s << frame.seq;
0349 
0350     sendFrame(ParrotFrame(Parrot::ACK, targetBuffer, makeSeq(targetBuffer), data));
0351 }
0352 
0353 void ParrotConnection::sendFrame(const ParrotFrame &frame)
0354 {
0355     switch (frame.bufferId) {
0356     case Parrot::BUFFER_C2D_ACK_DATA: {
0357         m_c2dAckQueue.enqueue(frame);
0358 
0359         if (!m_c2dAckTimer || !m_c2dAckTimer->isActive()) {
0360             pumpC2dAckQueue();
0361         }
0362 
0363         break;
0364     }
0365     default: {
0366         sendData(frame.pack(), frame.size);
0367     }
0368     }
0369 }
0370 
0371 void ParrotConnection::sendData(const QByteArray &data, quint32 size)
0372 {
0373     if (!m_c2dSocket) {
0374         qCWarning(KIROGI_VEHICLESUPPORT_PARROT) << m_vehicleName << "Trying to send data without socket.";
0375 
0376         return;
0377     }
0378 
0379     m_c2dSocket->writeDatagram(data.constData(), size, m_hostAddress, m_c2dport);
0380 }
0381 
0382 quint8 ParrotConnection::makeSeq(quint8 bufferId)
0383 {
0384     if (!m_seq.contains(bufferId)) {
0385         m_seq[bufferId] = 0;
0386     } else {
0387         m_seq[bufferId] += 1;
0388         m_seq[bufferId] %= 256;
0389     }
0390 
0391     return m_seq[bufferId];
0392 }