diff --git a/udpaudio.cpp b/udpaudio.cpp new file mode 100644 index 0000000..e8d0f3a --- /dev/null +++ b/udpaudio.cpp @@ -0,0 +1,248 @@ +#include "udpaudio.h" +#include "logcategories.h" + +// Audio stream +udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 lport, audioSetup rxSetup, audioSetup txSetup) +{ + qInfo(logUdp()) << "Starting udpAudio"; + this->localIP = local; + this->port = audioPort; + this->radioIP = ip; + + if (txSetup.sampleRate == 0) { + enableTx = false; + } + + init(lport); // Perform connection + + QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::dataReceived); + + rxaudio = new audioHandler(); + rxAudioThread = new QThread(this); + rxAudioThread->setObjectName("rxAudio()"); + + rxaudio->moveToThread(rxAudioThread); + + rxAudioThread->start(QThread::TimeCriticalPriority); + + connect(this, SIGNAL(setupRxAudio(audioSetup)), rxaudio, SLOT(init(audioSetup))); + + // signal/slot not currently used. + connect(this, SIGNAL(haveAudioData(audioPacket)), rxaudio, SLOT(incomingAudio(audioPacket))); + connect(this, SIGNAL(haveChangeLatency(quint16)), rxaudio, SLOT(changeLatency(quint16))); + connect(this, SIGNAL(haveSetVolume(unsigned char)), rxaudio, SLOT(setVolume(unsigned char))); + connect(rxaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool)), this, SLOT(getRxLevels(quint16, quint16, quint16, bool))); + connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); + + + sendControl(false, 0x03, 0x00); // First connect packet + + pingTimer = new QTimer(); + connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); + pingTimer->start(PING_PERIOD); // send ping packets every 100ms + + if (enableTx) { + txaudio = new audioHandler(); + txAudioThread = new QThread(this); + rxAudioThread->setObjectName("txAudio()"); + + txaudio->moveToThread(txAudioThread); + + txAudioThread->start(QThread::TimeCriticalPriority); + + connect(this, SIGNAL(setupTxAudio(audioSetup)), txaudio, SLOT(init(audioSetup))); + connect(txaudio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); + connect(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool))); + + connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); + emit setupTxAudio(txSetup); + } + + emit setupRxAudio(rxSetup); + + watchdogTimer = new QTimer(); + connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); + watchdogTimer->start(WATCHDOG_PERIOD); + + areYouThereTimer = new QTimer(); + connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); + areYouThereTimer->start(AREYOUTHERE_PERIOD); +} + +udpAudio::~udpAudio() +{ + if (pingTimer != Q_NULLPTR) + { + qDebug(logUdp()) << "Stopping pingTimer"; + pingTimer->stop(); + delete pingTimer; + pingTimer = Q_NULLPTR; + } + + if (idleTimer != Q_NULLPTR) + { + qDebug(logUdp()) << "Stopping idleTimer"; + idleTimer->stop(); + delete idleTimer; + idleTimer = Q_NULLPTR; + } + + if (watchdogTimer != Q_NULLPTR) + { + qDebug(logUdp()) << "Stopping watchdogTimer"; + watchdogTimer->stop(); + delete watchdogTimer; + watchdogTimer = Q_NULLPTR; + } + + if (rxAudioThread != Q_NULLPTR) { + qDebug(logUdp()) << "Stopping rxaudio thread"; + rxAudioThread->quit(); + rxAudioThread->wait(); + } + + if (txAudioThread != Q_NULLPTR) { + qDebug(logUdp()) << "Stopping txaudio thread"; + txAudioThread->quit(); + txAudioThread->wait(); + } + qDebug(logUdp()) << "udpHandler successfully closed"; +} + +void udpAudio::watchdog() +{ + static bool alerted = false; + if (lastReceived.msecsTo(QTime::currentTime()) > 2000) + { + if (!alerted) { + /* Just log it at the moment, maybe try signalling the control channel that it needs to + try requesting civ/audio again? */ + + qInfo(logUdp()) << " Audio Watchdog: no audio data received for 2s, restart required?"; + alerted = true; + } + } + else + { + alerted = false; + } +} + + +void udpAudio::sendTxAudio() +{ + if (txaudio == Q_NULLPTR) { + return; + } + +} + +void udpAudio::receiveAudioData(audioPacket audio) { + // I really can't see how this could be possible but a quick sanity check! + if (txaudio == Q_NULLPTR) { + return; + } + if (audio.data.length() > 0) { + int counter = 1; + int len = 0; + + while (len < audio.data.length()) { + QByteArray partial = audio.data.mid(len, 1364); + audio_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p) + partial.length(); + p.sentid = myId; + p.rcvdid = remoteId; + if (partial.length() == 0xa0) { + p.ident = 0x9781; + } + else { + p.ident = 0x0080; // TX audio is always this? + } + p.datalen = (quint16)qToBigEndian((quint16)partial.length()); + p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN! + QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); + tx.append(partial); + len = len + partial.length(); + //qInfo(logUdp()) << "Sending audio packet length: " << tx.length(); + sendTrackedPacket(tx); + sendAudioSeq++; + counter++; + } + } +} + +void udpAudio::changeLatency(quint16 value) +{ + emit haveChangeLatency(value); +} + +void udpAudio::setVolume(unsigned char value) +{ + emit haveSetVolume(value); +} + +void udpAudio::getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under) { + + emit haveRxLevels(amplitude, latency, current, under); +} + +void udpAudio::getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under) { + emit haveTxLevels(amplitude, latency, current, under); +} + +void udpAudio::dataReceived() +{ + while (udp->hasPendingDatagrams()) { + QNetworkDatagram datagram = udp->receiveDatagram(); + //qInfo(logUdp()) << "Received: " << datagram.data().mid(0,10); + QByteArray r = datagram.data(); + + switch (r.length()) + { + case (16): // Response to control packet handled in udpBase + { + //control_packet_t in = (control_packet_t)r.constData(); + break; + } + default: + { + /* Audio packets start as follows: + PCM 16bit and PCM8/uLAW stereo: 0x44,0x02 for first packet and 0x6c,0x05 for second. + uLAW 8bit/PCM 8bit 0xd8,0x03 for all packets + PCM 16bit stereo 0x6c,0x05 first & second 0x70,0x04 third + + + */ + control_packet_t in = (control_packet_t)r.constData(); + + if (in->type != 0x01 && in->len >= 0x20) { + if (in->seq == 0) + { + // Seq number has rolled over. + seqPrefix++; + } + + // 0xac is the smallest possible audio packet. + lastReceived = QTime::currentTime(); + audioPacket tempAudio; + tempAudio.seq = (quint32)seqPrefix << 16 | in->seq; + tempAudio.time = lastReceived; + tempAudio.sent = 0; + tempAudio.data = r.mid(0x18); + // Prefer signal/slot to forward audio as it is thread/safe + // Need to do more testing but latency appears fine. + //rxaudio->incomingAudio(tempAudio); + emit haveAudioData(tempAudio); + } + break; + } + } + + udpBase::dataReceived(r); // Call parent function to process the rest. + r.clear(); + datagram.clear(); + } +} + + diff --git a/udpaudio.h b/udpaudio.h new file mode 100644 index 0000000..ec03b8d --- /dev/null +++ b/udpaudio.h @@ -0,0 +1,83 @@ +#ifndef UDPAUDIO_H +#define UDPAUDIO_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Allow easy endian-ness conversions +#include + +// Needed for audio +#include +#include + +#include + +#include "packettypes.h" + +#include "udpbase.h" + +#include "audiohandler.h" + + +// Class for all audio communications. +class udpAudio : public udpBase +{ + Q_OBJECT + +public: + udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 lport, audioSetup rxSetup, audioSetup txSetup); + ~udpAudio(); + + int audioLatency = 0; + +signals: + void haveAudioData(audioPacket data); + + void setupTxAudio(audioSetup setup); + void setupRxAudio(audioSetup setup); + + void haveChangeLatency(quint16 value); + void haveSetVolume(unsigned char value); + void haveRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); + void haveTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); + +public slots: + void changeLatency(quint16 value); + void setVolume(unsigned char value); + void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); + void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); + void receiveAudioData(audioPacket audio); + +private: + + void sendTxAudio(); + void dataReceived(); + void watchdog(); + + uint16_t sendAudioSeq = 0; + + audioHandler* rxaudio = Q_NULLPTR; + QThread* rxAudioThread = Q_NULLPTR; + + audioHandler* txaudio = Q_NULLPTR; + QThread* txAudioThread = Q_NULLPTR; + + QTimer* txAudioTimer = Q_NULLPTR; + bool enableTx = true; + + QMutex audioMutex; + +}; + +#endif \ No newline at end of file diff --git a/udpbase.cpp b/udpbase.cpp new file mode 100644 index 0000000..9381fd8 --- /dev/null +++ b/udpbase.cpp @@ -0,0 +1,545 @@ +#include "udpbase.h" +#include "logcategories.h" + +void udpBase::init(quint16 lport) +{ + //timeStarted.start(); + udp = new QUdpSocket(this); + udp->bind(lport); // Bind to random port. + localPort = udp->localPort(); + qInfo(logUdp()) << "UDP Stream bound to local port:" << localPort << " remote port:" << port; + uint32_t addr = localIP.toIPv4Address(); + myId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff); + + retransmitTimer = new QTimer(); + connect(retransmitTimer, &QTimer::timeout, this, &udpBase::sendRetransmitRequest); + retransmitTimer->start(RETRANSMIT_PERIOD); +} + +udpBase::~udpBase() +{ + qInfo(logUdp()) << "Closing UDP stream :" << radioIP.toString() << ":" << port; + if (udp != Q_NULLPTR) { + sendControl(false, 0x05, 0x00); // Send disconnect + udp->close(); + delete udp; + } + if (areYouThereTimer != Q_NULLPTR) + { + areYouThereTimer->stop(); + delete areYouThereTimer; + } + + if (pingTimer != Q_NULLPTR) + { + pingTimer->stop(); + delete pingTimer; + } + if (idleTimer != Q_NULLPTR) + { + idleTimer->stop(); + delete idleTimer; + } + if (retransmitTimer != Q_NULLPTR) + { + retransmitTimer->stop(); + delete retransmitTimer; + } + + pingTimer = Q_NULLPTR; + idleTimer = Q_NULLPTR; + areYouThereTimer = Q_NULLPTR; + retransmitTimer = Q_NULLPTR; + +} + +// Base class! + +void udpBase::dataReceived(QByteArray r) +{ + if (r.length() < 0x10) + { + return; // Packet too small do to anything with? + } + + switch (r.length()) + { + case (CONTROL_SIZE): // Empty response used for simple comms and retransmit requests. + { + control_packet_t in = (control_packet_t)r.constData(); + if (in->type == 0x01 && in->len == 0x10) + { + // Single packet request + packetsLost++; + congestion = static_cast(packetsSent) / packetsLost * 100; + txBufferMutex.lock(); + auto match = txSeqBuf.find(in->seq); + if (match != txSeqBuf.end()) { + // Found matching entry? + // Send "untracked" as it has already been sent once. + // Don't constantly retransmit the same packet, give-up eventually + qDebug(logUdp()) << this->metaObject()->className() << ": Sending (single packet) retransmit of " << QString("0x%1").arg(match->seqNum, 0, 16); + match->retransmitCount++; + udpMutex.lock(); + udp->writeDatagram(match->data, radioIP, port); + udpMutex.unlock(); + } + else { + qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" + << QString("0x%1").arg(in->seq, 0, 16) << + "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << + "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); + } + txBufferMutex.unlock(); + } + if (in->type == 0x04) { + qInfo(logUdp()) << this->metaObject()->className() << ": Received I am here "; + areYouThereCounter = 0; + // I don't think that we will ever receive an "I am here" other than in response to "Are you there?" + remoteId = in->sentid; + if (areYouThereTimer != Q_NULLPTR && areYouThereTimer->isActive()) { + // send ping packets every second + areYouThereTimer->stop(); + } + sendControl(false, 0x06, 0x01); // Send Are you ready - untracked. + } + else if (in->type == 0x06) + { + // Just get the seqnum and ignore the rest. + } + break; + } + case (PING_SIZE): // ping packet + { + ping_packet_t in = (ping_packet_t)r.constData(); + if (in->type == 0x07) + { + // It is a ping request/response + if (in->reply == 0x00) + { + ping_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p); + p.type = 0x07; + p.sentid = myId; + p.rcvdid = remoteId; + p.reply = 0x01; + p.seq = in->seq; + p.time = in->time; + udpMutex.lock(); + udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); + udpMutex.unlock(); + } + else if (in->reply == 0x01) { + if (in->seq == pingSendSeq) + { + // This is response to OUR request so increment counter + pingSendSeq++; + } + } + else { + qInfo(logUdp()) << this->metaObject()->className() << "Unhandled response to ping. I have never seen this! 0x10=" << r[16]; + } + + } + break; + } + default: + { + + // All packets "should" be added to the incoming buffer. + // First check that we haven't already received it. + + + } + break; + + } + + + // All packets except ping and retransmit requests should trigger this. + control_packet_t in = (control_packet_t)r.constData(); + + // This is a variable length retransmit request! + if (in->type == 0x01 && in->len != 0x10) + { + + for (quint16 i = 0x10; i < r.length(); i = i + 2) + { + quint16 seq = (quint8)r[i] | (quint8)r[i + 1] << 8; + auto match = txSeqBuf.find(seq); + if (match == txSeqBuf.end()) { + qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" + << QString("0x%1").arg(seq, 0, 16) << + "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << + "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); + // Just send idle packet. + sendControl(false, 0, seq); + } + else { + // Found matching entry? + // Send "untracked" as it has already been sent once. + qDebug(logUdp()) << this->metaObject()->className() << ": Sending (multiple packet) retransmit of " << QString("0x%1").arg(match->seqNum, 0, 16); + match->retransmitCount++; + udpMutex.lock(); + udp->writeDatagram(match->data, radioIP, port); + udpMutex.unlock(); + packetsLost++; + congestion = static_cast(packetsSent) / packetsLost * 100; + } + } + } + else if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) + { + rxBufferMutex.lock(); + if (rxSeqBuf.isEmpty()) { + rxSeqBuf.insert(in->seq, QTime::currentTime()); + } + else + { + if (in->seq < rxSeqBuf.firstKey() || in->seq - rxSeqBuf.lastKey() > MAX_MISSING) + { + qInfo(logUdp()) << this->metaObject()->className() << "Large seq number gap detected, previous highest: " << + QString("0x%1").arg(rxSeqBuf.lastKey(), 0, 16) << " current: " << QString("0x%1").arg(in->seq, 0, 16); + //seqPrefix++; + // Looks like it has rolled over so clear buffer and start again. + rxSeqBuf.clear(); + // Add this packet to the incoming buffer + rxSeqBuf.insert(in->seq, QTime::currentTime()); + rxBufferMutex.unlock(); + missingMutex.lock(); + rxMissing.clear(); + missingMutex.unlock(); + return; + } + + if (!rxSeqBuf.contains(in->seq)) + { + // Add incoming packet to the received buffer and if it is in the missing buffer, remove it. + + if (in->seq > rxSeqBuf.lastKey() + 1) { + qInfo(logUdp()) << this->metaObject()->className() << "1 or more missing packets detected, previous: " << + QString("0x%1").arg(rxSeqBuf.lastKey(), 0, 16) << " current: " << QString("0x%1").arg(in->seq, 0, 16); + // We are likely missing packets then! + missingMutex.lock(); + //int missCounter = 0; + // Sanity check! + for (quint16 f = rxSeqBuf.lastKey() + 1; f <= in->seq; f++) + { + if (rxSeqBuf.size() > BUFSIZE) + { + rxSeqBuf.erase(rxSeqBuf.begin()); + } + rxSeqBuf.insert(f, QTime::currentTime()); + if (f != in->seq && !rxMissing.contains(f)) + { + rxMissing.insert(f, 0); + } + } + missingMutex.unlock(); + } + else { + if (rxSeqBuf.size() > BUFSIZE) + { + rxSeqBuf.erase(rxSeqBuf.begin()); + } + rxSeqBuf.insert(in->seq, QTime::currentTime()); + + } + } + else { + // This is probably one of our missing packets! + missingMutex.lock(); + auto s = rxMissing.find(in->seq); + if (s != rxMissing.end()) + { + qInfo(logUdp()) << this->metaObject()->className() << ": Missing SEQ has been received! " << QString("0x%1").arg(in->seq, 0, 16); + + s = rxMissing.erase(s); + } + missingMutex.unlock(); + + } + + } + rxBufferMutex.unlock(); + + } +} + + +void udpBase::sendRetransmitRequest() +{ + // Find all gaps in received packets and then send requests for them. + // This will run every 100ms so out-of-sequence packets will not trigger a retransmit request. + if (rxMissing.isEmpty()) { + return; + } + else if (rxMissing.size() > MAX_MISSING) { + qInfo(logUdp()) << "Too many missing packets," << rxMissing.size() << "flushing all buffers"; + missingMutex.lock(); + rxMissing.clear(); + missingMutex.unlock(); + + rxBufferMutex.lock(); + rxSeqBuf.clear(); + rxBufferMutex.unlock(); + return; + } + + QByteArray missingSeqs; + + missingMutex.lock(); + auto it = rxMissing.begin(); + while (it != rxMissing.end()) + { + if (it.key() != 0x0) + { + if (it.value() < 4) + { + missingSeqs.append(it.key() & 0xff); + missingSeqs.append(it.key() >> 8 & 0xff); + missingSeqs.append(it.key() & 0xff); + missingSeqs.append(it.key() >> 8 & 0xff); + it.value()++; + it++; + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": No response for missing packet" << QString("0x%1").arg(it.key(), 0, 16) << "deleting"; + it = rxMissing.erase(it); + } + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": found empty key in missing buffer"; + it++; + } + } + missingMutex.unlock(); + + if (missingSeqs.length() != 0) + { + control_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p); + p.type = 0x01; + p.seq = 0x0000; + p.sentid = myId; + p.rcvdid = remoteId; + if (missingSeqs.length() == 4) // This is just a single missing packet so send using a control. + { + p.seq = (missingSeqs[0] & 0xff) | (quint16)(missingSeqs[1] << 8); + qInfo(logUdp()) << this->metaObject()->className() << ": sending request for missing packet : " << QString("0x%1").arg(p.seq, 0, 16); + udpMutex.lock(); + udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); + udpMutex.unlock(); + } + else + { + qInfo(logUdp()) << this->metaObject()->className() << ": sending request for multiple missing packets : " << missingSeqs.toHex(':'); + missingMutex.lock(); + p.len = sizeof(p) + missingSeqs.size(); + missingSeqs.insert(0, p.packet, sizeof(p)); + missingMutex.unlock(); + + udpMutex.lock(); + udp->writeDatagram(missingSeqs, radioIP, port); + udpMutex.unlock(); + } + } + +} + + + +// Used to send idle and other "control" style messages +void udpBase::sendControl(bool tracked = true, quint8 type = 0, quint16 seq = 0) +{ + control_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p); + p.type = type; + p.sentid = myId; + p.rcvdid = remoteId; + + if (!tracked) { + p.seq = seq; + udpMutex.lock(); + udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); + udpMutex.unlock(); + } + else { + sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); + } + return; +} + +// Send periodic ping packets +void udpBase::sendPing() +{ + ping_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p); + p.type = 0x07; + p.sentid = myId; + p.rcvdid = remoteId; + p.seq = pingSendSeq; + QTime now = QTime::currentTime(); + p.time = (quint32)now.msecsSinceStartOfDay(); + lastPingSentTime = QDateTime::currentDateTime(); + udpMutex.lock(); + udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); + udpMutex.unlock(); + return; +} + + +void udpBase::sendTrackedPacket(QByteArray d) +{ + // As the radio can request retransmission of these packets, store them in a buffer + d[6] = sendSeq & 0xff; + d[7] = (sendSeq >> 8) & 0xff; + SEQBUFENTRY s; + s.seqNum = sendSeq; + s.timeSent = QTime::currentTime(); + s.retransmitCount = 0; + s.data = d; + if (txBufferMutex.tryLock(100)) + { + + if (sendSeq == 0) { + // We are either the first ever sent packet or have rolled-over so clear the buffer. + txSeqBuf.clear(); + congestion = 0; + } + if (txSeqBuf.size() > BUFSIZE) + { + txSeqBuf.erase(txSeqBuf.begin()); + } + txSeqBuf.insert(sendSeq, s); + + txBufferMutex.unlock(); + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; + } + // Stop using purgeOldEntries() as it is likely slower than just removing the earliest packet. + //qInfo(logUdp()) << this->metaObject()->className() << "RX:" << rxSeqBuf.size() << "TX:" <writeDatagram(d, radioIP, port); + if (congestion > 10) { // Poor quality connection? + udp->writeDatagram(d, radioIP, port); + if (congestion > 20) // Even worse so send again. + udp->writeDatagram(d, radioIP, port); + } + if (idleTimer != Q_NULLPTR && idleTimer->isActive()) { + idleTimer->start(IDLE_PERIOD); // Reset idle counter if it's running + } + udpMutex.unlock(); + packetsSent++; + return; +} + + +/// +/// Once a packet has reached PURGE_SECONDS old (currently 10) then it is not likely to be any use. +/// +void udpBase::purgeOldEntries() +{ + // Erase old entries from the tx packet buffer + if (txBufferMutex.tryLock(100)) + { + if (!txSeqBuf.isEmpty()) + { + // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS + for (auto it = txSeqBuf.begin(); it != txSeqBuf.end();) { + if (it.value().timeSent.secsTo(QTime::currentTime()) > PURGE_SECONDS) { + txSeqBuf.erase(it++); + } + else { + break; + } + } + } + txBufferMutex.unlock(); + + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; + } + + + + if (rxBufferMutex.tryLock(100)) + { + if (!rxSeqBuf.isEmpty()) { + // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS + for (auto it = rxSeqBuf.begin(); it != rxSeqBuf.end();) { + if (it.value().secsTo(QTime::currentTime()) > PURGE_SECONDS) { + rxSeqBuf.erase(it++); + } + else { + break; + } + } + } + rxBufferMutex.unlock(); + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": rxBuffer mutex is locked"; + } + + if (missingMutex.tryLock(100)) + { + // Erase old entries from the missing packets buffer + if (!rxMissing.isEmpty() && rxMissing.size() > 50) { + for (size_t i = 0; i < 25; ++i) { + rxMissing.erase(rxMissing.begin()); + } + } + missingMutex.unlock(); + } + else { + qInfo(logUdp()) << this->metaObject()->className() << ": missingBuffer mutex is locked"; + } +} + +void udpBase::printHex(const QByteArray& pdata) +{ + printHex(pdata, false, true); +} + +void udpBase::printHex(const QByteArray& pdata, bool printVert, bool printHoriz) +{ + qDebug(logUdp()) << "---- Begin hex dump -----:"; + QString sdata("DATA: "); + QString index("INDEX: "); + QStringList strings; + + for (int i = 0; i < pdata.length(); i++) + { + strings << QString("[%1]: %2").arg(i, 8, 10, QChar('0')).arg((unsigned char)pdata[i], 2, 16, QChar('0')); + sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0'))); + index.append(QString("%1 ").arg(i, 2, 10, QChar('0'))); + } + + if (printVert) + { + for (int i = 0; i < strings.length(); i++) + { + //sdata = QString(strings.at(i)); + qDebug(logUdp()) << strings.at(i); + } + } + + if (printHoriz) + { + qDebug(logUdp()) << index; + qDebug(logUdp()) << sdata; + } + qDebug(logUdp()) << "----- End hex dump -----"; +} + + diff --git a/udpbase.h b/udpbase.h new file mode 100644 index 0000000..43f4c77 --- /dev/null +++ b/udpbase.h @@ -0,0 +1,205 @@ +#ifndef UDPBASE_H +#define UDPBASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Allow easy endian-ness conversions +#include + +// Needed for audio +#include +#include + +#include + +#include "packettypes.h" + + + +struct udpPreferences { + QString ipAddress; + quint16 controlLANPort; + quint16 serialLANPort; + quint16 audioLANPort; + QString username; + QString password; + QString clientName; + quint8 waterfallFormat; +}; + +struct networkStatus { + quint8 rxAudioBufferPercent; + quint8 txAudioBufferPercent; + quint8 rxAudioLevel; + quint8 txAudioLevel; + quint16 rxLatency; + quint16 txLatency; + bool rxUnderrun; + bool txUnderrun; + quint16 rxCurrentLatency; + quint16 txCurrentLatency; + quint32 packetsSent = 0; + quint32 packetsLost = 0; + quint16 rtt = 0; + quint32 networkLatency = 0; + QString message; +}; + + +// Parent class that contains all common items. +class udpBase : public QObject +{ + + +public: + ~udpBase(); + + void init(quint16 local); + + void reconnect(); + + void dataReceived(QByteArray r); + void sendPing(); + void sendRetransmitRange(quint16 first, quint16 second, quint16 third, quint16 fourth); + + void sendControl(bool tracked, quint8 id, quint16 seq); + + void printHex(const QByteArray& pdata); + void printHex(const QByteArray& pdata, bool printVert, bool printHoriz); + + + //QTime timeStarted; + + QUdpSocket* udp = Q_NULLPTR; + uint32_t myId = 0; + uint32_t remoteId = 0; + uint16_t authSeq = 0x30; + uint16_t sendSeqB = 0; + uint16_t sendSeq = 1; + uint16_t lastReceivedSeq = 1; + uint16_t pkt0SendSeq = 0; + uint16_t periodicSeq = 0; + quint64 latency = 0; + + QString username = ""; + QString password = ""; + QHostAddress radioIP; + QHostAddress localIP; + bool isAuthenticated = false; + quint16 localPort = 0; + quint16 port = 0; + bool periodicRunning = false; + bool sentPacketConnect2 = false; + QTime lastReceived = QTime::currentTime(); + QMutex udpMutex; + QMutex txBufferMutex; + QMutex rxBufferMutex; + QMutex missingMutex; + + struct SEQBUFENTRY { + QTime timeSent; + uint16_t seqNum; + QByteArray data; + quint8 retransmitCount; + }; + + QMap rxSeqBuf; + QMap txSeqBuf; + QMap rxMissing; + + void sendTrackedPacket(QByteArray d); + void purgeOldEntries(); + + QTimer* areYouThereTimer = Q_NULLPTR; // Send are-you-there packets every second until a response is received. + QTimer* pingTimer = Q_NULLPTR; // Start sending pings immediately. + QTimer* idleTimer = Q_NULLPTR; // Start watchdog once we are connected. + + QTimer* watchdogTimer = Q_NULLPTR; + QTimer* retransmitTimer = Q_NULLPTR; + + QDateTime lastPingSentTime; + uint16_t pingSendSeq = 0; + + quint16 areYouThereCounter = 0; + + quint32 packetsSent = 0; + quint32 packetsLost = 0; + + quint16 seqPrefix = 0; + QString connectionType = ""; + int congestion = 0; + + +private: + void sendRetransmitRequest(); + +}; + + +/// +/// passcode function used to generate secure (ish) code +/// +/// +/// pointer to encoded username or password +static inline void passcode(QString in, QByteArray& out) +{ + const quint8 sequence[] = + { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x47,0x5d,0x4c,0x42,0x66,0x20,0x23,0x46,0x4e,0x57,0x45,0x3d,0x67,0x76,0x60,0x41,0x62,0x39,0x59,0x2d,0x68,0x7e, + 0x7c,0x65,0x7d,0x49,0x29,0x72,0x73,0x78,0x21,0x6e,0x5a,0x5e,0x4a,0x3e,0x71,0x2c,0x2a,0x54,0x3c,0x3a,0x63,0x4f, + 0x43,0x75,0x27,0x79,0x5b,0x35,0x70,0x48,0x6b,0x56,0x6f,0x34,0x32,0x6c,0x30,0x61,0x6d,0x7b,0x2f,0x4b,0x64,0x38, + 0x2b,0x2e,0x50,0x40,0x3f,0x55,0x33,0x37,0x25,0x77,0x24,0x26,0x74,0x6a,0x28,0x53,0x4d,0x69,0x22,0x5c,0x44,0x31, + 0x36,0x58,0x3b,0x7a,0x51,0x5f,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + }; + + QByteArray ba = in.toLocal8Bit(); + uchar* ascii = (uchar*)ba.constData(); + for (int i = 0; i < in.length() && i < 16; i++) + { + int p = ascii[i] + i; + if (p > 126) + { + p = 32 + p % 127; + } + out.append(sequence[p]); + } + return; +} + +/// +/// returns a QByteArray of a null terminated string +/// +/// +/// +/// +static inline QByteArray parseNullTerminatedString(QByteArray c, int s) +{ + //QString res = ""; + QByteArray res; + for (int i = s; i < c.length(); i++) + { + if (c[i] != '\0') + { + res.append(c[i]); + } + else + { + break; + } + } + return res; +} + +#endif diff --git a/udpcivdata.cpp b/udpcivdata.cpp new file mode 100644 index 0000000..f6cd51d --- /dev/null +++ b/udpcivdata.cpp @@ -0,0 +1,268 @@ +#include "udpcivdata.h" +#include "logcategories.h" + +// Class that manages all Civ Data to/from the rig +udpCivData::udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort, bool splitWf, quint16 localPort = 0) +{ + qInfo(logUdp()) << "Starting udpCivData"; + localIP = local; + port = civPort; + radioIP = ip; + splitWaterfall = splitWf; + + udpBase::init(localPort); // Perform connection + + QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpCivData::dataReceived); + + sendControl(false, 0x03, 0x00); // First connect packet + + /* + Connect various timers + */ + pingTimer = new QTimer(); + idleTimer = new QTimer(); + areYouThereTimer = new QTimer(); + startCivDataTimer = new QTimer(); + watchdogTimer = new QTimer(); + + connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); + connect(watchdogTimer, &QTimer::timeout, this, &udpCivData::watchdog); + connect(idleTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, true, 0, 0)); + connect(startCivDataTimer, &QTimer::timeout, this, std::bind(&udpCivData::sendOpenClose, this, false)); + connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); + watchdogTimer->start(WATCHDOG_PERIOD); + // Start sending are you there packets - will be stopped once "I am here" received + // send ping packets every 100 ms (maybe change to less frequent?) + pingTimer->start(PING_PERIOD); + // Send idle packets every 100ms, this timer will be reset every time a non-idle packet is sent. + idleTimer->start(IDLE_PERIOD); + areYouThereTimer->start(AREYOUTHERE_PERIOD); +} + +udpCivData::~udpCivData() +{ + sendOpenClose(true); + if (startCivDataTimer != Q_NULLPTR) + { + startCivDataTimer->stop(); + delete startCivDataTimer; + startCivDataTimer = Q_NULLPTR; + } + if (pingTimer != Q_NULLPTR) + { + pingTimer->stop(); + delete pingTimer; + pingTimer = Q_NULLPTR; + } + if (idleTimer != Q_NULLPTR) + { + idleTimer->stop(); + delete idleTimer; + idleTimer = Q_NULLPTR; + } + if (watchdogTimer != Q_NULLPTR) + { + watchdogTimer->stop(); + delete watchdogTimer; + watchdogTimer = Q_NULLPTR; + } +} + +void udpCivData::watchdog() +{ + static bool alerted = false; + if (lastReceived.msecsTo(QTime::currentTime()) > 2000) + { + if (!alerted) { + qInfo(logUdp()) << " CIV Watchdog: no CIV data received for 2s, requesting data start."; + if (startCivDataTimer != Q_NULLPTR) + { + startCivDataTimer->start(100); + } + alerted = true; + } + } + else + { + alerted = false; + } +} + +void udpCivData::send(QByteArray d) +{ + //qInfo(logUdp()) << "Sending: (" << d.length() << ") " << d; + data_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p) + d.length(); + p.sentid = myId; + p.rcvdid = remoteId; + p.reply = (char)0xc1; + p.datalen = d.length(); + p.sendseq = qToBigEndian(sendSeqB); // THIS IS BIG ENDIAN! + + QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); + t.append(d); + sendTrackedPacket(t); + sendSeqB++; + return; +} + + +void udpCivData::sendOpenClose(bool close) +{ + uint8_t magic = 0x04; + + if (close) + { + magic = 0x00; + } + + openclose_packet p; + memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! + p.len = sizeof(p); + p.sentid = myId; + p.rcvdid = remoteId; + p.data = 0x01c0; // Not sure what other values are available: + p.sendseq = qToBigEndian(sendSeqB); + p.magic = magic; + + sendSeqB++; + + sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); + return; +} + + + +void udpCivData::dataReceived() +{ + while (udp->hasPendingDatagrams()) + { + QNetworkDatagram datagram = udp->receiveDatagram(); + //qInfo(logUdp()) << "Received: " << datagram.data(); + QByteArray r = datagram.data(); + + + switch (r.length()) + { + case (CONTROL_SIZE): // Control packet + { + control_packet_t in = (control_packet_t)r.constData(); + if (in->type == 0x04) + { + areYouThereTimer->stop(); + } + else if (in->type == 0x06) + { + // Update remoteId + remoteId = in->sentid; + // Manually send a CIV start request and start the timer if it isn't received. + // The timer will be stopped as soon as valid CIV data is received. + sendOpenClose(false); + if (startCivDataTimer != Q_NULLPTR) { + startCivDataTimer->start(100); + } + } + break; + } + default: + { + if (r.length() > 21) { + data_packet_t in = (data_packet_t)r.constData(); + if (in->type != 0x01) { + // Process this packet, any re-transmit requests will happen later. + //uint16_t gotSeq = qFromLittleEndian(r.mid(6, 2)); + // We have received some Civ data so stop sending Start packets! + if (startCivDataTimer != Q_NULLPTR) { + startCivDataTimer->stop(); + } + lastReceived = QTime::currentTime(); + if (quint16(in->datalen + 0x15) == (quint16)in->len) + { + //if (r.mid(0x15).length() != 157) + // Find data length + int pos = r.indexOf(QByteArrayLiteral("\x27\x00\x00")) + 2; + int len = r.mid(pos).indexOf(QByteArrayLiteral("\xfd")); + //splitWaterfall = false; + if (splitWaterfall && pos > 1 && len > 100) { + // We need to split waterfall data into its component parts + // There are only 2 types that we are currently aware of + int numDivisions = 0; + if (len == 490) // IC705, IC9700, IC7300(LAN) + { + numDivisions = 11; + } + else if (len == 704) // IC7610, IC7851, ICR8600 + { + numDivisions = 15; + } + else { + qInfo(logUdp()) << "Unknown spectrum size" << len; + break; + } + // (sequence #1) includes center/fixed mode at [05]. No pixels. + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 " + // "DATA: 27 00 00 01 11 01 00 00 00 14 00 00 00 35 14 00 00 fd " + // (sequences 2-10, 50 pixels) + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 " + // "DATA: 27 00 00 07 11 27 13 15 01 00 22 21 09 08 06 19 0e 20 23 25 2c 2d 17 27 29 16 14 1b 1b 21 27 1a 18 17 1e 21 1b 24 21 22 23 13 19 23 2f 2d 25 25 0a 0e 1e 20 1f 1a 0c fd " + // ^--^--(seq 7/11) + // ^-- start waveform data 0x00 to 0xA0, index 05 to 54 + // (sequence #11) + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 " + // "DATA: 27 00 00 11 11 0b 13 21 23 1a 1b 22 1e 1a 1d 13 21 1d 26 28 1f 19 1a 18 09 2c 2c 2c 1a 1b fd " + + int divSize = (len / numDivisions) + 6; + QByteArray wfPacket; + for (int i = 0; i < numDivisions; i++) { + + wfPacket = r.mid(pos - 6, 9); // First part of packet + + wfPacket = r.mid(pos - 6, 9); // First part of packet + char tens = ((i + 1) / 10); + char units = ((i + 1) - (10 * tens)); + wfPacket[7] = units | (tens << 4); + + tens = (numDivisions / 10); + units = (numDivisions - (10 * tens)); + wfPacket[8] = units | (tens << 4); + + if (i == 0) { + //Just send initial data, first BCD encode the max number: + wfPacket.append(r.mid(pos + 3, 12)); + } + else + { + wfPacket.append(r.mid((pos + 15) + ((i - 1) * divSize), divSize)); + } + if (i < numDivisions - 1) { + wfPacket.append('\xfd'); + } + //printHex(wfPacket, false, true); + + emit receive(wfPacket); + wfPacket.clear(); + + } + //qDebug(logUdp()) << "Waterfall packet len" << len << "Num Divisions" << numDivisions << "Division Size" << divSize; + } + else { + // Not waterfall data or split not enabled. + emit receive(r.mid(0x15)); + } + //qDebug(logUdp()) << "Got incoming CIV datagram" << r.mid(0x15).length(); + + } + + } + } + break; + } + } + udpBase::dataReceived(r); // Call parent function to process the rest. + + r.clear(); + datagram.clear(); + + } +} diff --git a/udpcivdata.h b/udpcivdata.h new file mode 100644 index 0000000..410f969 --- /dev/null +++ b/udpcivdata.h @@ -0,0 +1,56 @@ +// Class for all (pseudo) serial communications +#ifndef UDPCIVDATA_H +#define UDPCIVDATA_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Allow easy endian-ness conversions +#include + +// Needed for audio +#include +#include + +#include + +#include "packettypes.h" + +#include "udpbase.h" + +class udpCivData : public udpBase +{ + Q_OBJECT + +public: + udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort, bool splitWf, quint16 lport); + ~udpCivData(); + QMutex serialmutex; + +signals: + int receive(QByteArray); + +public slots: + void send(QByteArray d); + + +private: + void watchdog(); + void dataReceived(); + void sendOpenClose(bool close); + + QTimer* startCivDataTimer = Q_NULLPTR; + bool splitWaterfall = false; +}; + + +#endif \ No newline at end of file diff --git a/udphandler.cpp b/udphandler.cpp index 2413bc0..384c517 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -637,1112 +637,3 @@ void udpHandler::sendToken(uint8_t magic) return; } - -// Class that manages all Civ Data to/from the rig -udpCivData::udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort, bool splitWf, quint16 localPort=0 ) -{ - qInfo(logUdp()) << "Starting udpCivData"; - localIP = local; - port = civPort; - radioIP = ip; - splitWaterfall = splitWf; - - udpBase::init(localPort); // Perform connection - - QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpCivData::dataReceived); - - sendControl(false, 0x03, 0x00); // First connect packet - - /* - Connect various timers - */ - pingTimer = new QTimer(); - idleTimer = new QTimer(); - areYouThereTimer = new QTimer(); - startCivDataTimer = new QTimer(); - watchdogTimer = new QTimer(); - - connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); - connect(watchdogTimer, &QTimer::timeout, this, &udpCivData::watchdog); - connect(idleTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, true, 0, 0)); - connect(startCivDataTimer, &QTimer::timeout, this, std::bind(&udpCivData::sendOpenClose, this, false)); - connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); - watchdogTimer->start(WATCHDOG_PERIOD); - // Start sending are you there packets - will be stopped once "I am here" received - // send ping packets every 100 ms (maybe change to less frequent?) - pingTimer->start(PING_PERIOD); - // Send idle packets every 100ms, this timer will be reset every time a non-idle packet is sent. - idleTimer->start(IDLE_PERIOD); - areYouThereTimer->start(AREYOUTHERE_PERIOD); -} - -udpCivData::~udpCivData() -{ - sendOpenClose(true); - if (startCivDataTimer != Q_NULLPTR) - { - startCivDataTimer->stop(); - delete startCivDataTimer; - startCivDataTimer = Q_NULLPTR; - } - if (pingTimer != Q_NULLPTR) - { - pingTimer->stop(); - delete pingTimer; - pingTimer = Q_NULLPTR; - } - if (idleTimer != Q_NULLPTR) - { - idleTimer->stop(); - delete idleTimer; - idleTimer = Q_NULLPTR; - } - if (watchdogTimer != Q_NULLPTR) - { - watchdogTimer->stop(); - delete watchdogTimer; - watchdogTimer = Q_NULLPTR; - } -} - -void udpCivData::watchdog() -{ - static bool alerted = false; - if (lastReceived.msecsTo(QTime::currentTime()) > 2000) - { - if (!alerted) { - qInfo(logUdp()) << " CIV Watchdog: no CIV data received for 2s, requesting data start."; - if (startCivDataTimer != Q_NULLPTR) - { - startCivDataTimer->start(100); - } - alerted = true; - } - } - else - { - alerted = false; - } -} - -void udpCivData::send(QByteArray d) -{ - //qInfo(logUdp()) << "Sending: (" << d.length() << ") " << d; - data_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p)+d.length(); - p.sentid = myId; - p.rcvdid = remoteId; - p.reply = (char)0xc1; - p.datalen = d.length(); - p.sendseq = qToBigEndian(sendSeqB); // THIS IS BIG ENDIAN! - - QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); - t.append(d); - sendTrackedPacket(t); - sendSeqB++; - return; -} - - -void udpCivData::sendOpenClose(bool close) -{ - uint8_t magic = 0x04; - - if (close) - { - magic = 0x00; - } - - openclose_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p); - p.sentid = myId; - p.rcvdid = remoteId; - p.data = 0x01c0; // Not sure what other values are available: - p.sendseq = qToBigEndian(sendSeqB); - p.magic = magic; - - sendSeqB++; - - sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); - return; -} - - - -void udpCivData::dataReceived() -{ - while (udp->hasPendingDatagrams()) - { - QNetworkDatagram datagram = udp->receiveDatagram(); - //qInfo(logUdp()) << "Received: " << datagram.data(); - QByteArray r = datagram.data(); - - - switch (r.length()) - { - case (CONTROL_SIZE): // Control packet - { - control_packet_t in = (control_packet_t)r.constData(); - if (in->type == 0x04) - { - areYouThereTimer->stop(); - } - else if (in->type == 0x06) - { - // Update remoteId - remoteId = in->sentid; - // Manually send a CIV start request and start the timer if it isn't received. - // The timer will be stopped as soon as valid CIV data is received. - sendOpenClose(false); - if (startCivDataTimer != Q_NULLPTR) { - startCivDataTimer->start(100); - } - } - break; - } - default: - { - if (r.length() > 21) { - data_packet_t in = (data_packet_t)r.constData(); - if (in->type != 0x01) { - // Process this packet, any re-transmit requests will happen later. - //uint16_t gotSeq = qFromLittleEndian(r.mid(6, 2)); - // We have received some Civ data so stop sending Start packets! - if (startCivDataTimer != Q_NULLPTR) { - startCivDataTimer->stop(); - } - lastReceived = QTime::currentTime(); - if (quint16(in->datalen + 0x15) == (quint16)in->len) - { - //if (r.mid(0x15).length() != 157) - // Find data length - int pos = r.indexOf(QByteArrayLiteral("\x27\x00\x00"))+2; - int len = r.mid(pos).indexOf(QByteArrayLiteral("\xfd")); - //splitWaterfall = false; - if (splitWaterfall && pos > 1 && len > 100) { - // We need to split waterfall data into its component parts - // There are only 2 types that we are currently aware of - int numDivisions = 0; - if (len == 490) // IC705, IC9700, IC7300(LAN) - { - numDivisions = 11; - } - else if (len == 704) // IC7610, IC7851, ICR8600 - { - numDivisions = 15; - } - else { - qInfo(logUdp()) << "Unknown spectrum size" << len; - break; - } - // (sequence #1) includes center/fixed mode at [05]. No pixels. - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 " - // "DATA: 27 00 00 01 11 01 00 00 00 14 00 00 00 35 14 00 00 fd " - // (sequences 2-10, 50 pixels) - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 " - // "DATA: 27 00 00 07 11 27 13 15 01 00 22 21 09 08 06 19 0e 20 23 25 2c 2d 17 27 29 16 14 1b 1b 21 27 1a 18 17 1e 21 1b 24 21 22 23 13 19 23 2f 2d 25 25 0a 0e 1e 20 1f 1a 0c fd " - // ^--^--(seq 7/11) - // ^-- start waveform data 0x00 to 0xA0, index 05 to 54 - // (sequence #11) - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 " - // "DATA: 27 00 00 11 11 0b 13 21 23 1a 1b 22 1e 1a 1d 13 21 1d 26 28 1f 19 1a 18 09 2c 2c 2c 1a 1b fd " - - int divSize = (len / numDivisions)+6; - QByteArray wfPacket; - for (int i = 0; i < numDivisions; i++) { - - wfPacket = r.mid(pos - 6, 9); // First part of packet - - wfPacket = r.mid(pos - 6, 9); // First part of packet - char tens = ((i + 1) / 10); - char units = ((i + 1) - (10 * tens)); - wfPacket[7] = units | (tens << 4); - - tens = (numDivisions / 10); - units = (numDivisions - (10 * tens)); - wfPacket[8] = units | (tens << 4); - - if (i == 0) { - //Just send initial data, first BCD encode the max number: - wfPacket.append(r.mid(pos + 3, 12)); - } - else - { - wfPacket.append(r.mid((pos + 15) + ((i-1) * divSize),divSize)); - } - if (i < numDivisions-1) { - wfPacket.append('\xfd'); - } - //printHex(wfPacket, false, true); - - emit receive(wfPacket); - wfPacket.clear(); - - } - //qDebug(logUdp()) << "Waterfall packet len" << len << "Num Divisions" << numDivisions << "Division Size" << divSize; - } - else { - // Not waterfall data or split not enabled. - emit receive(r.mid(0x15)); - } - //qDebug(logUdp()) << "Got incoming CIV datagram" << r.mid(0x15).length(); - - } - - } - } - break; - } - } - udpBase::dataReceived(r); // Call parent function to process the rest. - - r.clear(); - datagram.clear(); - - } -} - - -// Audio stream -udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 lport, audioSetup rxSetup, audioSetup txSetup) -{ - qInfo(logUdp()) << "Starting udpAudio"; - this->localIP = local; - this->port = audioPort; - this->radioIP = ip; - - if (txSetup.sampleRate == 0) { - enableTx = false; - } - - init(lport); // Perform connection - - QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::dataReceived); - - rxaudio = new audioHandler(); - rxAudioThread = new QThread(this); - rxAudioThread->setObjectName("rxAudio()"); - - rxaudio->moveToThread(rxAudioThread); - - rxAudioThread->start(QThread::TimeCriticalPriority); - - connect(this, SIGNAL(setupRxAudio(audioSetup)), rxaudio, SLOT(init(audioSetup))); - - // signal/slot not currently used. - connect(this, SIGNAL(haveAudioData(audioPacket)), rxaudio, SLOT(incomingAudio(audioPacket))); - connect(this, SIGNAL(haveChangeLatency(quint16)), rxaudio, SLOT(changeLatency(quint16))); - connect(this, SIGNAL(haveSetVolume(unsigned char)), rxaudio, SLOT(setVolume(unsigned char))); - connect(rxaudio, SIGNAL(haveLevels(quint16, quint16, quint16,bool)), this, SLOT(getRxLevels(quint16, quint16, quint16,bool))); - connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); - - - sendControl(false, 0x03, 0x00); // First connect packet - - pingTimer = new QTimer(); - connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); - pingTimer->start(PING_PERIOD); // send ping packets every 100ms - - if (enableTx) { - txaudio = new audioHandler(); - txAudioThread = new QThread(this); - rxAudioThread->setObjectName("txAudio()"); - - txaudio->moveToThread(txAudioThread); - - txAudioThread->start(QThread::TimeCriticalPriority); - - connect(this, SIGNAL(setupTxAudio(audioSetup)), txaudio, SLOT(init(audioSetup))); - connect(txaudio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); - connect(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool))); - - connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); - emit setupTxAudio(txSetup); - } - - emit setupRxAudio(rxSetup); - - watchdogTimer = new QTimer(); - connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); - watchdogTimer->start(WATCHDOG_PERIOD); - - areYouThereTimer = new QTimer(); - connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); - areYouThereTimer->start(AREYOUTHERE_PERIOD); -} - -udpAudio::~udpAudio() -{ - if (pingTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping pingTimer"; - pingTimer->stop(); - delete pingTimer; - pingTimer = Q_NULLPTR; - } - - if (idleTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping idleTimer"; - idleTimer->stop(); - delete idleTimer; - idleTimer = Q_NULLPTR; - } - - if (watchdogTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping watchdogTimer"; - watchdogTimer->stop(); - delete watchdogTimer; - watchdogTimer = Q_NULLPTR; - } - - if (rxAudioThread != Q_NULLPTR) { - qDebug(logUdp()) << "Stopping rxaudio thread"; - rxAudioThread->quit(); - rxAudioThread->wait(); - } - - if (txAudioThread != Q_NULLPTR) { - qDebug(logUdp()) << "Stopping txaudio thread"; - txAudioThread->quit(); - txAudioThread->wait(); - } - qDebug(logUdp()) << "udpHandler successfully closed"; -} - -void udpAudio::watchdog() -{ - static bool alerted = false; - if (lastReceived.msecsTo(QTime::currentTime()) > 2000) - { - if (!alerted) { - /* Just log it at the moment, maybe try signalling the control channel that it needs to - try requesting civ/audio again? */ - - qInfo(logUdp()) << " Audio Watchdog: no audio data received for 2s, restart required?"; - alerted = true; - } - } - else - { - alerted = false; - } -} - - -void udpAudio::sendTxAudio() -{ - if (txaudio == Q_NULLPTR) { - return; - } - -} - -void udpAudio::receiveAudioData(audioPacket audio) { - // I really can't see how this could be possible but a quick sanity check! - if (txaudio == Q_NULLPTR) { - return; - } - if (audio.data.length() > 0) { - int counter = 1; - int len = 0; - - while (len < audio.data.length()) { - QByteArray partial = audio.data.mid(len, 1364); - audio_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p) + partial.length(); - p.sentid = myId; - p.rcvdid = remoteId; - if (partial.length() == 0xa0) { - p.ident = 0x9781; - } - else { - p.ident = 0x0080; // TX audio is always this? - } - p.datalen = (quint16)qToBigEndian((quint16)partial.length()); - p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN! - QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); - tx.append(partial); - len = len + partial.length(); - //qInfo(logUdp()) << "Sending audio packet length: " << tx.length(); - sendTrackedPacket(tx); - sendAudioSeq++; - counter++; - } - } -} - -void udpAudio::changeLatency(quint16 value) -{ - emit haveChangeLatency(value); -} - -void udpAudio::setVolume(unsigned char value) -{ - emit haveSetVolume(value); -} - -void udpAudio::getRxLevels(quint16 amplitude,quint16 latency, quint16 current, bool under) { - - emit haveRxLevels(amplitude,latency, current, under); -} - -void udpAudio::getTxLevels(quint16 amplitude,quint16 latency, quint16 current, bool under) { - emit haveTxLevels(amplitude,latency, current, under); -} - -void udpAudio::dataReceived() -{ - while (udp->hasPendingDatagrams()) { - QNetworkDatagram datagram = udp->receiveDatagram(); - //qInfo(logUdp()) << "Received: " << datagram.data().mid(0,10); - QByteArray r = datagram.data(); - - switch (r.length()) - { - case (16): // Response to control packet handled in udpBase - { - //control_packet_t in = (control_packet_t)r.constData(); - break; - } - default: - { - /* Audio packets start as follows: - PCM 16bit and PCM8/uLAW stereo: 0x44,0x02 for first packet and 0x6c,0x05 for second. - uLAW 8bit/PCM 8bit 0xd8,0x03 for all packets - PCM 16bit stereo 0x6c,0x05 first & second 0x70,0x04 third - - - */ - control_packet_t in = (control_packet_t)r.constData(); - - if (in->type != 0x01 && in->len >= 0x20) { - if (in->seq == 0) - { - // Seq number has rolled over. - seqPrefix++; - } - - // 0xac is the smallest possible audio packet. - lastReceived = QTime::currentTime(); - audioPacket tempAudio; - tempAudio.seq = (quint32)seqPrefix << 16 | in->seq; - tempAudio.time = lastReceived; - tempAudio.sent = 0; - tempAudio.data = r.mid(0x18); - // Prefer signal/slot to forward audio as it is thread/safe - // Need to do more testing but latency appears fine. - //rxaudio->incomingAudio(tempAudio); - emit haveAudioData(tempAudio); - } - break; - } - } - - udpBase::dataReceived(r); // Call parent function to process the rest. - r.clear(); - datagram.clear(); - } -} - - - -void udpBase::init(quint16 lport) -{ - //timeStarted.start(); - udp = new QUdpSocket(this); - udp->bind(lport); // Bind to random port. - localPort = udp->localPort(); - qInfo(logUdp()) << "UDP Stream bound to local port:" << localPort << " remote port:" << port; - uint32_t addr = localIP.toIPv4Address(); - myId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff); - - retransmitTimer = new QTimer(); - connect(retransmitTimer, &QTimer::timeout, this, &udpBase::sendRetransmitRequest); - retransmitTimer->start(RETRANSMIT_PERIOD); -} - -udpBase::~udpBase() -{ - qInfo(logUdp()) << "Closing UDP stream :" << radioIP.toString() << ":" << port; - if (udp != Q_NULLPTR) { - sendControl(false, 0x05, 0x00); // Send disconnect - udp->close(); - delete udp; - } - if (areYouThereTimer != Q_NULLPTR) - { - areYouThereTimer->stop(); - delete areYouThereTimer; - } - - if (pingTimer != Q_NULLPTR) - { - pingTimer->stop(); - delete pingTimer; - } - if (idleTimer != Q_NULLPTR) - { - idleTimer->stop(); - delete idleTimer; - } - if (retransmitTimer != Q_NULLPTR) - { - retransmitTimer->stop(); - delete retransmitTimer; - } - - pingTimer = Q_NULLPTR; - idleTimer = Q_NULLPTR; - areYouThereTimer = Q_NULLPTR; - retransmitTimer = Q_NULLPTR; - -} - -// Base class! - -void udpBase::dataReceived(QByteArray r) -{ - if (r.length() < 0x10) - { - return; // Packet too small do to anything with? - } - - switch (r.length()) - { - case (CONTROL_SIZE): // Empty response used for simple comms and retransmit requests. - { - control_packet_t in = (control_packet_t)r.constData(); - if (in->type == 0x01 && in->len == 0x10) - { - // Single packet request - packetsLost++; - congestion = static_cast(packetsSent) / packetsLost * 100; - txBufferMutex.lock(); - auto match = txSeqBuf.find(in->seq); - if (match != txSeqBuf.end()) { - // Found matching entry? - // Send "untracked" as it has already been sent once. - // Don't constantly retransmit the same packet, give-up eventually - qDebug(logUdp()) << this->metaObject()->className() << ": Sending (single packet) retransmit of " << QString("0x%1").arg(match->seqNum, 0, 16); - match->retransmitCount++; - udpMutex.lock(); - udp->writeDatagram(match->data, radioIP, port); - udpMutex.unlock(); - } - else { - qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" - << QString("0x%1").arg(in->seq, 0, 16) << - "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << - "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); - } - txBufferMutex.unlock(); - } - if (in->type == 0x04) { - qInfo(logUdp()) << this->metaObject()->className() << ": Received I am here "; - areYouThereCounter = 0; - // I don't think that we will ever receive an "I am here" other than in response to "Are you there?" - remoteId = in->sentid; - if (areYouThereTimer != Q_NULLPTR && areYouThereTimer->isActive()) { - // send ping packets every second - areYouThereTimer->stop(); - } - sendControl(false, 0x06, 0x01); // Send Are you ready - untracked. - } - else if (in->type == 0x06) - { - // Just get the seqnum and ignore the rest. - } - break; - } - case (PING_SIZE): // ping packet - { - ping_packet_t in = (ping_packet_t)r.constData(); - if (in->type == 0x07) - { - // It is a ping request/response - if (in->reply == 0x00) - { - ping_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p); - p.type = 0x07; - p.sentid = myId; - p.rcvdid = remoteId; - p.reply = 0x01; - p.seq = in->seq; - p.time = in->time; - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else if (in->reply == 0x01) { - if (in->seq == pingSendSeq) - { - // This is response to OUR request so increment counter - pingSendSeq++; - } - } - else { - qInfo(logUdp()) << this->metaObject()->className() << "Unhandled response to ping. I have never seen this! 0x10=" << r[16]; - } - - } - break; - } - default: - { - - // All packets "should" be added to the incoming buffer. - // First check that we haven't already received it. - - - } - break; - - } - - - // All packets except ping and retransmit requests should trigger this. - control_packet_t in = (control_packet_t)r.constData(); - - // This is a variable length retransmit request! - if (in->type == 0x01 && in->len != 0x10) - { - - for (quint16 i = 0x10; i < r.length(); i = i + 2) - { - quint16 seq = (quint8)r[i] | (quint8)r[i + 1] << 8; - auto match = txSeqBuf.find(seq); - if (match == txSeqBuf.end()) { - qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" - << QString("0x%1").arg(seq, 0, 16) << - "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << - "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); - // Just send idle packet. - sendControl(false, 0, seq); - } - else { - // Found matching entry? - // Send "untracked" as it has already been sent once. - qDebug(logUdp()) << this->metaObject()->className() << ": Sending (multiple packet) retransmit of " << QString("0x%1").arg(match->seqNum,0,16); - match->retransmitCount++; - udpMutex.lock(); - udp->writeDatagram(match->data, radioIP, port); - udpMutex.unlock(); - packetsLost++; - congestion = static_cast(packetsSent) / packetsLost * 100; - } - } - } - else if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) - { - rxBufferMutex.lock(); - if (rxSeqBuf.isEmpty()) { - rxSeqBuf.insert(in->seq, QTime::currentTime()); - } - else - { - if (in->seq < rxSeqBuf.firstKey() || in->seq - rxSeqBuf.lastKey() > MAX_MISSING) - { - qInfo(logUdp()) << this->metaObject()->className() << "Large seq number gap detected, previous highest: " << - QString("0x%1").arg(rxSeqBuf.lastKey(),0,16) << " current: " << QString("0x%1").arg(in->seq,0,16); - //seqPrefix++; - // Looks like it has rolled over so clear buffer and start again. - rxSeqBuf.clear(); - // Add this packet to the incoming buffer - rxSeqBuf.insert(in->seq, QTime::currentTime()); - rxBufferMutex.unlock(); - missingMutex.lock(); - rxMissing.clear(); - missingMutex.unlock(); - return; - } - - if (!rxSeqBuf.contains(in->seq)) - { - // Add incoming packet to the received buffer and if it is in the missing buffer, remove it. - - if (in->seq > rxSeqBuf.lastKey() + 1) { - qInfo(logUdp()) << this->metaObject()->className() << "1 or more missing packets detected, previous: " << - QString("0x%1").arg(rxSeqBuf.lastKey(),0,16) << " current: " << QString("0x%1").arg(in->seq,0,16); - // We are likely missing packets then! - missingMutex.lock(); - //int missCounter = 0; - // Sanity check! - for (quint16 f = rxSeqBuf.lastKey() + 1; f <= in->seq; f++) - { - if (rxSeqBuf.size() > BUFSIZE) - { - rxSeqBuf.erase(rxSeqBuf.begin()); - } - rxSeqBuf.insert(f, QTime::currentTime()); - if (f != in->seq && !rxMissing.contains(f)) - { - rxMissing.insert(f, 0); - } - } - missingMutex.unlock(); - } - else { - if (rxSeqBuf.size() > BUFSIZE) - { - rxSeqBuf.erase(rxSeqBuf.begin()); - } - rxSeqBuf.insert(in->seq, QTime::currentTime()); - - } - } - else { - // This is probably one of our missing packets! - missingMutex.lock(); - auto s = rxMissing.find(in->seq); - if (s != rxMissing.end()) - { - qInfo(logUdp()) << this->metaObject()->className() << ": Missing SEQ has been received! " << QString("0x%1").arg(in->seq,0,16); - - s = rxMissing.erase(s); - } - missingMutex.unlock(); - - } - - } - rxBufferMutex.unlock(); - - } -} - -bool missing(quint16 i, quint16 j) { - return (i + 1 != j); -} - -void udpBase::sendRetransmitRequest() -{ - // Find all gaps in received packets and then send requests for them. - // This will run every 100ms so out-of-sequence packets will not trigger a retransmit request. - if (rxMissing.isEmpty()) { - return; - } - else if (rxMissing.size() > MAX_MISSING) { - qInfo(logUdp()) << "Too many missing packets," << rxMissing.size() << "flushing all buffers"; - missingMutex.lock(); - rxMissing.clear(); - missingMutex.unlock(); - - rxBufferMutex.lock(); - rxSeqBuf.clear(); - rxBufferMutex.unlock(); - return; - } - - QByteArray missingSeqs; - - missingMutex.lock(); - auto it = rxMissing.begin(); - while (it != rxMissing.end()) - { - if (it.key() != 0x0) - { - if (it.value() < 4) - { - missingSeqs.append(it.key() & 0xff); - missingSeqs.append(it.key() >> 8 & 0xff); - missingSeqs.append(it.key() & 0xff); - missingSeqs.append(it.key() >> 8 & 0xff); - it.value()++; - it++; - } - else { - qInfo(logUdp()) << this->metaObject()->className() << ": No response for missing packet" << QString("0x%1").arg(it.key(), 0, 16) << "deleting"; - it = rxMissing.erase(it); - } - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": found empty key in missing buffer"; - it++; - } - } - missingMutex.unlock(); - - if (missingSeqs.length() != 0) - { - control_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p); - p.type = 0x01; - p.seq = 0x0000; - p.sentid = myId; - p.rcvdid = remoteId; - if (missingSeqs.length() == 4) // This is just a single missing packet so send using a control. - { - p.seq = (missingSeqs[0] & 0xff) | (quint16)(missingSeqs[1] << 8); - qInfo(logUdp()) << this->metaObject()->className() << ": sending request for missing packet : " << QString("0x%1").arg(p.seq,0,16); - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else - { - qInfo(logUdp()) << this->metaObject()->className() << ": sending request for multiple missing packets : " << missingSeqs.toHex(':'); - missingMutex.lock(); - p.len = sizeof(p)+missingSeqs.size(); - missingSeqs.insert(0, p.packet, sizeof(p)); - missingMutex.unlock(); - - udpMutex.lock(); - udp->writeDatagram(missingSeqs, radioIP, port); - udpMutex.unlock(); - } - } - -} - - - -// Used to send idle and other "control" style messages -void udpBase::sendControl(bool tracked = true, quint8 type = 0, quint16 seq = 0) -{ - control_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p); - p.type = type; - p.sentid = myId; - p.rcvdid = remoteId; - - if (!tracked) { - p.seq = seq; - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else { - sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); - } - return; -} - -// Send periodic ping packets -void udpBase::sendPing() -{ - ping_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p); - p.type = 0x07; - p.sentid = myId; - p.rcvdid = remoteId; - p.seq = pingSendSeq; - QTime now=QTime::currentTime(); - p.time = (quint32)now.msecsSinceStartOfDay(); - lastPingSentTime = QDateTime::currentDateTime(); - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - return; -} - - -void udpBase::sendTrackedPacket(QByteArray d) -{ - // As the radio can request retransmission of these packets, store them in a buffer - d[6] = sendSeq & 0xff; - d[7] = (sendSeq >> 8) & 0xff; - SEQBUFENTRY s; - s.seqNum = sendSeq; - s.timeSent = QTime::currentTime(); - s.retransmitCount = 0; - s.data = d; - if (txBufferMutex.tryLock(100)) - { - - if (sendSeq == 0) { - // We are either the first ever sent packet or have rolled-over so clear the buffer. - txSeqBuf.clear(); - congestion = 0; - } - if (txSeqBuf.size() > BUFSIZE) - { - txSeqBuf.erase(txSeqBuf.begin()); - } - txSeqBuf.insert(sendSeq, s); - - txBufferMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; - } - // Stop using purgeOldEntries() as it is likely slower than just removing the earliest packet. - //qInfo(logUdp()) << this->metaObject()->className() << "RX:" << rxSeqBuf.size() << "TX:" <writeDatagram(d, radioIP, port); - if (congestion>10) { // Poor quality connection? - udp->writeDatagram(d, radioIP, port); - if (congestion>20) // Even worse so send again. - udp->writeDatagram(d, radioIP, port); - } - if (idleTimer != Q_NULLPTR && idleTimer->isActive()) { - idleTimer->start(IDLE_PERIOD); // Reset idle counter if it's running - } - udpMutex.unlock(); - packetsSent++; - return; -} - - -/// -/// Once a packet has reached PURGE_SECONDS old (currently 10) then it is not likely to be any use. -/// -void udpBase::purgeOldEntries() -{ - // Erase old entries from the tx packet buffer - if (txBufferMutex.tryLock(100)) - { - if (!txSeqBuf.isEmpty()) - { - // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS - for (auto it = txSeqBuf.begin(); it != txSeqBuf.end();) { - if (it.value().timeSent.secsTo(QTime::currentTime()) > PURGE_SECONDS) { - txSeqBuf.erase(it++); - } - else { - break; - } - } - } - txBufferMutex.unlock(); - - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; - } - - - - if (rxBufferMutex.tryLock(100)) - { - if (!rxSeqBuf.isEmpty()) { - // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS - for (auto it = rxSeqBuf.begin(); it != rxSeqBuf.end();) { - if (it.value().secsTo(QTime::currentTime()) > PURGE_SECONDS) { - rxSeqBuf.erase(it++); - } - else { - break; - } - } - } - rxBufferMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": rxBuffer mutex is locked"; - } - - if (missingMutex.tryLock(100)) - { - // Erase old entries from the missing packets buffer - if (!rxMissing.isEmpty() && rxMissing.size() > 50) { - for (size_t i = 0; i < 25; ++i) { - rxMissing.erase(rxMissing.begin()); - } - } - missingMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": missingBuffer mutex is locked"; - } -} - -void udpBase::printHex(const QByteArray& pdata) -{ - printHex(pdata, false, true); -} - -void udpBase::printHex(const QByteArray& pdata, bool printVert, bool printHoriz) -{ - qDebug(logUdp()) << "---- Begin hex dump -----:"; - QString sdata("DATA: "); - QString index("INDEX: "); - QStringList strings; - - for (int i = 0; i < pdata.length(); i++) - { - strings << QString("[%1]: %2").arg(i, 8, 10, QChar('0')).arg((unsigned char)pdata[i], 2, 16, QChar('0')); - sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0'))); - index.append(QString("%1 ").arg(i, 2, 10, QChar('0'))); - } - - if (printVert) - { - for (int i = 0; i < strings.length(); i++) - { - //sdata = QString(strings.at(i)); - qDebug(logUdp()) << strings.at(i); - } - } - - if (printHoriz) - { - qDebug(logUdp()) << index; - qDebug(logUdp()) << sdata; - } - qDebug(logUdp()) << "----- End hex dump -----"; -} - -/// -/// passcode function used to generate secure (ish) code -/// -/// -/// pointer to encoded username or password -void passcode(QString in, QByteArray& out) -{ - const quint8 sequence[] = - { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0x47,0x5d,0x4c,0x42,0x66,0x20,0x23,0x46,0x4e,0x57,0x45,0x3d,0x67,0x76,0x60,0x41,0x62,0x39,0x59,0x2d,0x68,0x7e, - 0x7c,0x65,0x7d,0x49,0x29,0x72,0x73,0x78,0x21,0x6e,0x5a,0x5e,0x4a,0x3e,0x71,0x2c,0x2a,0x54,0x3c,0x3a,0x63,0x4f, - 0x43,0x75,0x27,0x79,0x5b,0x35,0x70,0x48,0x6b,0x56,0x6f,0x34,0x32,0x6c,0x30,0x61,0x6d,0x7b,0x2f,0x4b,0x64,0x38, - 0x2b,0x2e,0x50,0x40,0x3f,0x55,0x33,0x37,0x25,0x77,0x24,0x26,0x74,0x6a,0x28,0x53,0x4d,0x69,0x22,0x5c,0x44,0x31, - 0x36,0x58,0x3b,0x7a,0x51,0x5f,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - - }; - - QByteArray ba = in.toLocal8Bit(); - uchar* ascii = (uchar*)ba.constData(); - for (int i = 0; i < in.length() && i < 16; i++) - { - int p = ascii[i] + i; - if (p > 126) - { - p = 32 + p % 127; - } - out.append(sequence[p]); - } - return; -} - -/// -/// returns a QByteArray of a null terminated string -/// -/// -/// -/// -QByteArray parseNullTerminatedString(QByteArray c, int s) -{ - //QString res = ""; - QByteArray res; - for (int i = s; i < c.length(); i++) - { - if (c[i] != '\0') - { - res.append(c[i]); - } - else - { - break; - } - } - return res; -} - diff --git a/udphandler.h b/udphandler.h index 3e51ed9..0faf2eb 100644 --- a/udphandler.h +++ b/udphandler.h @@ -1,6 +1,7 @@ #ifndef UDPHANDLER_H #define UDPHANDLER_H + #include #include #include @@ -22,207 +23,11 @@ #include -#include "audiohandler.h" #include "packettypes.h" - - -struct udpPreferences { - QString ipAddress; - quint16 controlLANPort; - quint16 serialLANPort; - quint16 audioLANPort; - QString username; - QString password; - QString clientName; - quint8 waterfallFormat; -}; - -struct networkStatus { - quint8 rxAudioBufferPercent; - quint8 txAudioBufferPercent; - quint8 rxAudioLevel; - quint8 txAudioLevel; - quint16 rxLatency; - quint16 txLatency; - bool rxUnderrun; - bool txUnderrun; - quint16 rxCurrentLatency; - quint16 txCurrentLatency; - quint32 packetsSent=0; - quint32 packetsLost=0; - quint16 rtt=0; - quint32 networkLatency=0; - QString message; -}; - -void passcode(QString in, QByteArray& out); -QByteArray parseNullTerminatedString(QByteArray c, int s); - -// Parent class that contains all common items. -class udpBase : public QObject -{ - - -public: - ~udpBase(); - - void init(quint16 local); - - void reconnect(); - - void dataReceived(QByteArray r); - void sendPing(); - void sendRetransmitRange(quint16 first, quint16 second, quint16 third,quint16 fourth); - - void sendControl(bool tracked,quint8 id, quint16 seq); - - void printHex(const QByteArray& pdata); - void printHex(const QByteArray& pdata, bool printVert, bool printHoriz); - - - //QTime timeStarted; - - QUdpSocket* udp=Q_NULLPTR; - uint32_t myId = 0; - uint32_t remoteId = 0; - uint16_t authSeq = 0x30; - uint16_t sendSeqB = 0; - uint16_t sendSeq = 1; - uint16_t lastReceivedSeq = 1; - uint16_t pkt0SendSeq = 0; - uint16_t periodicSeq = 0; - quint64 latency = 0; - - QString username = ""; - QString password = ""; - QHostAddress radioIP; - QHostAddress localIP; - bool isAuthenticated = false; - quint16 localPort=0; - quint16 port=0; - bool periodicRunning = false; - bool sentPacketConnect2 = false; - QTime lastReceived =QTime::currentTime(); - QMutex udpMutex; - QMutex txBufferMutex; - QMutex rxBufferMutex; - QMutex missingMutex; - - struct SEQBUFENTRY { - QTime timeSent; - uint16_t seqNum; - QByteArray data; - quint8 retransmitCount; - }; - - QMap rxSeqBuf; - QMap txSeqBuf; - QMap rxMissing; - - void sendTrackedPacket(QByteArray d); - void purgeOldEntries(); - - QTimer* areYouThereTimer = Q_NULLPTR; // Send are-you-there packets every second until a response is received. - QTimer* pingTimer = Q_NULLPTR; // Start sending pings immediately. - QTimer* idleTimer = Q_NULLPTR; // Start watchdog once we are connected. - - QTimer* watchdogTimer = Q_NULLPTR; - QTimer* retransmitTimer = Q_NULLPTR; - - QDateTime lastPingSentTime; - uint16_t pingSendSeq = 0; - - quint16 areYouThereCounter=0; - - quint32 packetsSent=0; - quint32 packetsLost=0; - - quint16 seqPrefix = 0; - QString connectionType=""; - int congestion = 0; - - -private: - void sendRetransmitRequest(); - -}; - -// Class for all (pseudo) serial communications -class udpCivData : public udpBase -{ - Q_OBJECT - -public: - udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort, bool splitWf, quint16 lport); - ~udpCivData(); - QMutex serialmutex; - -signals: - int receive(QByteArray); - -public slots: - void send(QByteArray d); - - -private: - void watchdog(); - void dataReceived(); - void sendOpenClose(bool close); - - QTimer* startCivDataTimer = Q_NULLPTR; - bool splitWaterfall = false; -}; - - -// Class for all audio communications. -class udpAudio : public udpBase -{ - Q_OBJECT - -public: - udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 lport, audioSetup rxSetup, audioSetup txSetup); - ~udpAudio(); - - int audioLatency = 0; - -signals: - void haveAudioData(audioPacket data); - - void setupTxAudio(audioSetup setup); - void setupRxAudio(audioSetup setup); - - void haveChangeLatency(quint16 value); - void haveSetVolume(unsigned char value); - void haveRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); - void haveTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); - -public slots: - void changeLatency(quint16 value); - void setVolume(unsigned char value); - void getRxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); - void getTxLevels(quint16 amplitude, quint16 latency, quint16 current, bool under); - void receiveAudioData(audioPacket audio); - -private: - - void sendTxAudio(); - void dataReceived(); - void watchdog(); - - uint16_t sendAudioSeq = 0; - - audioHandler* rxaudio = Q_NULLPTR; - QThread* rxAudioThread = Q_NULLPTR; - - audioHandler* txaudio = Q_NULLPTR; - QThread* txAudioThread = Q_NULLPTR; - - QTimer* txAudioTimer = Q_NULLPTR; - bool enableTx = true; - - QMutex audioMutex; - -}; +#include "audiohandler.h" +#include "udpbase.h" +#include "udpcivdata.h" +#include "udpaudio.h" diff --git a/wfserver.pro b/wfserver.pro index 1154884..37dad25 100644 --- a/wfserver.pro +++ b/wfserver.pro @@ -139,7 +139,10 @@ SOURCES += main.cpp\ rigcommander.cpp \ freqmemory.cpp \ rigidentities.cpp \ + udpbase.cpp \ udphandler.cpp \ + udpcivdata.cpp \ + udpaudio.cpp \ logcategories.cpp \ audiohandler.cpp \ audioconverter.cpp \ @@ -155,7 +158,10 @@ HEADERS += servermain.h \ rigcommander.h \ freqmemory.h \ rigidentities.h \ + udpbase.h \ udphandler.h \ + udpcivdata.h \ + udpaudio.h \ logcategories.h \ audiohandler.h \ audioconverter.h \ diff --git a/wfview.pro b/wfview.pro index 203dc88..667ffff 100644 --- a/wfview.pro +++ b/wfview.pro @@ -162,7 +162,10 @@ SOURCES += main.cpp\ rigcommander.cpp \ freqmemory.cpp \ rigidentities.cpp \ + udpbase.cpp \ udphandler.cpp \ + udpcivdata.cpp \ + udpaudio.cpp \ logcategories.cpp \ audiohandler.cpp \ audioconverter.cpp \ @@ -185,7 +188,10 @@ HEADERS += wfmain.h \ rigcommander.h \ freqmemory.h \ rigidentities.h \ + udpbase.h \ udphandler.h \ + udpcivdata.h \ + udpaudio.h \ logcategories.h \ audiohandler.h \ audioconverter.h \ diff --git a/wfview.vcxproj b/wfview.vcxproj index 7a48226..5c147a6 100644 --- a/wfview.vcxproj +++ b/wfview.vcxproj @@ -220,6 +220,9 @@ + + + @@ -269,6 +272,9 @@ + + + diff --git a/wfview.vcxproj.filters b/wfview.vcxproj.filters index a12a594..40ce604 100644 --- a/wfview.vcxproj.filters +++ b/wfview.vcxproj.filters @@ -119,6 +119,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -208,6 +217,12 @@ Header Files + + Header Files + + + Header Files + @@ -374,4 +389,9 @@ + + + Header Files + + \ No newline at end of file