kopia lustrzana https://gitlab.com/eliggett/wfview
Split udpHandler into multiple class files
rodzic
82faf2c463
commit
772375fb9d
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
#ifndef UDPAUDIO_H
|
||||||
|
#define UDPAUDIO_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QNetworkDatagram>
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
// Allow easy endian-ness conversions
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
// Needed for audio
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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
|
|
@ -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<double>(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<double>(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:" <<txSeqBuf.size() << "MISS:" << rxMissing.size();
|
||||||
|
//purgeOldEntries(); // Delete entries older than PURGE_SECONDS seconds (currently 5)
|
||||||
|
sendSeq++;
|
||||||
|
|
||||||
|
udpMutex.lock();
|
||||||
|
udp->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Once a packet has reached PURGE_SECONDS old (currently 10) then it is not likely to be any use.
|
||||||
|
/// </summary>
|
||||||
|
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 -----";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
#ifndef UDPBASE_H
|
||||||
|
#define UDPBASE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QNetworkDatagram>
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
// Allow easy endian-ness conversions
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
// Needed for audio
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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<quint16, QTime> rxSeqBuf;
|
||||||
|
QMap<quint16, SEQBUFENTRY> txSeqBuf;
|
||||||
|
QMap<quint16, int> 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();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// passcode function used to generate secure (ish) code
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns>pointer to encoded username or password</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// returns a QByteArray of a null terminated string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c"></param>
|
||||||
|
/// <param name="s"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
|
@ -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<quint16>(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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Class for all (pseudo) serial communications
|
||||||
|
#ifndef UDPCIVDATA_H
|
||||||
|
#define UDPCIVDATA_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QNetworkDatagram>
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
// Allow easy endian-ness conversions
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
// Needed for audio
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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
|
1109
udphandler.cpp
1109
udphandler.cpp
Plik diff jest za duży
Load Diff
205
udphandler.h
205
udphandler.h
|
@ -1,6 +1,7 @@
|
||||||
#ifndef UDPHANDLER_H
|
#ifndef UDPHANDLER_H
|
||||||
#define UDPHANDLER_H
|
#define UDPHANDLER_H
|
||||||
|
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUdpSocket>
|
#include <QUdpSocket>
|
||||||
#include <QNetworkDatagram>
|
#include <QNetworkDatagram>
|
||||||
|
@ -22,207 +23,11 @@
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "audiohandler.h"
|
|
||||||
#include "packettypes.h"
|
#include "packettypes.h"
|
||||||
|
#include "audiohandler.h"
|
||||||
|
#include "udpbase.h"
|
||||||
struct udpPreferences {
|
#include "udpcivdata.h"
|
||||||
QString ipAddress;
|
#include "udpaudio.h"
|
||||||
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<quint16, QTime> rxSeqBuf;
|
|
||||||
QMap<quint16, SEQBUFENTRY> txSeqBuf;
|
|
||||||
QMap<quint16, int> 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;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,10 @@ SOURCES += main.cpp\
|
||||||
rigcommander.cpp \
|
rigcommander.cpp \
|
||||||
freqmemory.cpp \
|
freqmemory.cpp \
|
||||||
rigidentities.cpp \
|
rigidentities.cpp \
|
||||||
|
udpbase.cpp \
|
||||||
udphandler.cpp \
|
udphandler.cpp \
|
||||||
|
udpcivdata.cpp \
|
||||||
|
udpaudio.cpp \
|
||||||
logcategories.cpp \
|
logcategories.cpp \
|
||||||
audiohandler.cpp \
|
audiohandler.cpp \
|
||||||
audioconverter.cpp \
|
audioconverter.cpp \
|
||||||
|
@ -155,7 +158,10 @@ HEADERS += servermain.h \
|
||||||
rigcommander.h \
|
rigcommander.h \
|
||||||
freqmemory.h \
|
freqmemory.h \
|
||||||
rigidentities.h \
|
rigidentities.h \
|
||||||
|
udpbase.h \
|
||||||
udphandler.h \
|
udphandler.h \
|
||||||
|
udpcivdata.h \
|
||||||
|
udpaudio.h \
|
||||||
logcategories.h \
|
logcategories.h \
|
||||||
audiohandler.h \
|
audiohandler.h \
|
||||||
audioconverter.h \
|
audioconverter.h \
|
||||||
|
|
|
@ -162,7 +162,10 @@ SOURCES += main.cpp\
|
||||||
rigcommander.cpp \
|
rigcommander.cpp \
|
||||||
freqmemory.cpp \
|
freqmemory.cpp \
|
||||||
rigidentities.cpp \
|
rigidentities.cpp \
|
||||||
|
udpbase.cpp \
|
||||||
udphandler.cpp \
|
udphandler.cpp \
|
||||||
|
udpcivdata.cpp \
|
||||||
|
udpaudio.cpp \
|
||||||
logcategories.cpp \
|
logcategories.cpp \
|
||||||
audiohandler.cpp \
|
audiohandler.cpp \
|
||||||
audioconverter.cpp \
|
audioconverter.cpp \
|
||||||
|
@ -185,7 +188,10 @@ HEADERS += wfmain.h \
|
||||||
rigcommander.h \
|
rigcommander.h \
|
||||||
freqmemory.h \
|
freqmemory.h \
|
||||||
rigidentities.h \
|
rigidentities.h \
|
||||||
|
udpbase.h \
|
||||||
udphandler.h \
|
udphandler.h \
|
||||||
|
udpcivdata.h \
|
||||||
|
udpaudio.h \
|
||||||
logcategories.h \
|
logcategories.h \
|
||||||
audiohandler.h \
|
audiohandler.h \
|
||||||
audioconverter.h \
|
audioconverter.h \
|
||||||
|
|
|
@ -220,6 +220,9 @@
|
||||||
<ClCompile Include="selectradio.cpp" />
|
<ClCompile Include="selectradio.cpp" />
|
||||||
<ClCompile Include="tcpserver.cpp" />
|
<ClCompile Include="tcpserver.cpp" />
|
||||||
<ClCompile Include="transceiveradjustments.cpp" />
|
<ClCompile Include="transceiveradjustments.cpp" />
|
||||||
|
<ClCompile Include="udpaudio.cpp" />
|
||||||
|
<ClCompile Include="udpbase.cpp" />
|
||||||
|
<ClCompile Include="udpcivdata.cpp" />
|
||||||
<ClCompile Include="udphandler.cpp" />
|
<ClCompile Include="udphandler.cpp" />
|
||||||
<ClCompile Include="udpserver.cpp" />
|
<ClCompile Include="udpserver.cpp" />
|
||||||
<ClCompile Include="wfmain.cpp" />
|
<ClCompile Include="wfmain.cpp" />
|
||||||
|
@ -269,6 +272,9 @@
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
<QtMoc Include="udpserver.h">
|
<QtMoc Include="udpserver.h">
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
|
<QtMoc Include="udpaudio.h" />
|
||||||
|
<ClInclude Include="udpbase.h" />
|
||||||
|
<QtMoc Include="udpcivdata.h" />
|
||||||
<ClInclude Include="ulaw.h" />
|
<ClInclude Include="ulaw.h" />
|
||||||
<QtMoc Include="wfmain.h">
|
<QtMoc Include="wfmain.h">
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
|
|
|
@ -119,6 +119,15 @@
|
||||||
<ClCompile Include="audioconverter.cpp">
|
<ClCompile Include="audioconverter.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="udpbase.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="udpaudio.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="udpcivdata.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtMoc Include="aboutbox.h">
|
<QtMoc Include="aboutbox.h">
|
||||||
|
@ -208,6 +217,12 @@
|
||||||
<QtMoc Include="audioconverter.h">
|
<QtMoc Include="audioconverter.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
|
<QtMoc Include="udpcivdata.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</QtMoc>
|
||||||
|
<QtMoc Include="udpaudio.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</QtMoc>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
||||||
|
@ -374,4 +389,9 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include=".\wfview_resource.rc" />
|
<ResourceCompile Include=".\wfview_resource.rc" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="udpbase.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
Ładowanie…
Reference in New Issue