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 }