Added support for various spectrum sizes and rig capability support.

wfview has been tested with the following Icom radios over USB port:
IC-7300, IC-7610, IC-7851 (and IC-7850), and IC-9700. It likely works
fine with the IC-705 as well. At this time, the rig's CIV address must
be changed in the preference file to indicate the rig you are using, and
this must be in integer, not hex.
merge-requests/2/merge
roeland jansen 2021-02-10 17:32:56 +00:00
rodzic d7967e0356
commit 53fd008f3a
19 zmienionych plików z 2957 dodań i 153 usunięć

Wyświetl plik

@ -1,5 +1,19 @@
# wfview
[wfview](https://gitlab.com/eliggett/wfview) is an open-source front-end application for the [Icom IC-7300](https://www.icomamerica.com/en/products/amateur/hf/7300/default.aspx) HF SDR Amateur Radio. wfview supports viewing the spectrum display waterfall and most normal radio controls. Using wfview, the radio can be operated using the mouse, or just the keyboard (great for those with visual impairments), or even a touch screen display. The gorgous waterfall spectrum can be displayed on a monitor of any size, and can even projected onto a wall for a presentation. Even a VNC session can make use of wfview for interesting remote rig posibilities. wfview runs on humble hardware, ranging from the $35 Raspberry Pi, to laptops, to desktops. wfview is designed to run on GNU Linux, but can probably be adapted to run on other operating systems.
[wfview](https://gitlab.com/eliggett/wfview) is an open-source front-end application for the
- [Icom IC-7300](https://www.icomamerica.com/en/products/amateur/hf/7300/default.aspx) HF SDR Amateur Radio
- [Icom IC-7610](https://www.icomamerica.com/en/products/amateur/hf/7610/default.aspx) HF SDR Amateur Radio
- [Icom IC-7850](https://www.icomamerica.com/en/products/amateur/hf/7850/default.aspx) HF Hybrid SDR Amateur Radio
- [Icom IC-7851](https://www.icomamerica.com/en/products/amateur/hf/7851/default.aspx) HF Hybrid SDR Amateur Radio
- [Icom IC-9700](https://www.icomamerica.com/en/products/amateur/hf/9700/default.aspx) VHF/UHF SDR Amateur Radio
Other models to be tested/added (including the IC-705)..
wfview supports viewing the spectrum display waterfall and most normal radio controls. Using wfview, the radio can be operated using the mouse, or just the keyboard (great for those with visual impairments), or even a touch screen display. The gorgous waterfall spectrum can be displayed on a monitor of any size, and can even projected onto a wall for a presentation. Even a VNC session can make use of wfview for interesting remote rig posibilities. wfview runs on humble hardware, ranging from the $35 Raspberry Pi, to laptops, to desktops. wfview is designed to run on GNU Linux, but can probably be adapted to run on other operating systems. In fact we do have working example in windows as well.
wfview is unique in the radio control ecosystem in that it is free and open-source software and can take advantage of modern radio features (such as the waterfall). wfview also does not "eat the serial port", and can allow a second program, such as fldigi, access to the radio via a pseudo-terminal device.
@ -16,6 +30,7 @@ wfview is copyright 2017-2020 Elliott H. Liggett. All rights reserved. wfview so
6. 100 user memories stored in plain text on the computer
7. Stylable GUI using CSS
8. pseudo-terminal device, which allows for secondary program to control the radio while wfview is running
9. works for radios that support the ethernet interface with compareable waterfall speeds as on the radio itself.
### Build Requirements:
1. gcc / g++ / make
@ -61,3 +76,10 @@ sudo chown `whoami` /dev/ttyUSB*
6. Better settings panel (select serial port, CI-V address, more obvious exit button)
7. Add support for festival or other text-to-speech method using the computer (as apposed to the radio's speech module)
see also the wiki:
- [bugs](https://gitlab.com/eliggett/wfview/-/wikis/Bugs)
- [feature requests](https://gitlab.com/eliggett/wfview/-/wikis/Feature-requests)
- [raspberry pi server](https://gitlab.com/eliggett/wfview/-/wikis/raspi-server-functionality-for-7300,7100-etc)

Wyświetl plik

@ -10,7 +10,6 @@ commHandler::commHandler()
// grab baud rate and other comm port details
// if they need to be changed later, please
// destroy this and create a new one.
port = new QSerialPort();
@ -89,6 +88,9 @@ void commHandler::openPtPort()
success = pseudoterm->open(QIODevice::ReadWrite);
if(success)
{
#ifndef Q_OS_WIN
qDebug() << "Opened pt device, attempting to grant pt status";
ptfd = pseudoterm->handle();
qDebug() << "ptfd: " << ptfd;
@ -116,6 +118,7 @@ void commHandler::openPtPort()
{
qDebug() << "Received error from pseudo-terminal symlink command: code: [" << sysResult << "]" << " command: [" << ptLinkCmd << "]";
}
#endif
} else {
ptfd = 0;
@ -131,6 +134,7 @@ commHandler::~commHandler()
void commHandler::setupComm()
{
serialError = false;
port->setPortName(portName);
port->setBaudRate(baudrate);
port->setStopBits(QSerialPort::OneStop);// OneStop is other option
@ -264,6 +268,8 @@ void commHandler::openPort()
// debug?
qDebug() << "Could not open serial port " << portName << " , please restart.";
isConnected = false;
serialError = true;
emit haveSerialPortError(portName, "Could not open port. Please restart.");
return;
}

Wyświetl plik

@ -17,6 +17,7 @@ class commHandler : public QObject
public:
commHandler();
commHandler(QString portName, quint32 baudRate);
bool serialError;
~commHandler();
@ -30,6 +31,8 @@ signals:
void haveTextMessage(QString message); // status, debug only
void sendDataOutToPort(const QByteArray &writeData); // not used
void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander
void haveSerialPortError(const QString port, const QString error);
void haveStatusUpdate(const QString text);
private:
void setupComm();

Wyświetl plik

@ -0,0 +1,6 @@
#include "logcategories.h"
Q_LOGGING_CATEGORY(logDebug, "Debug")
Q_LOGGING_CATEGORY(logInfo, "Info")
Q_LOGGING_CATEGORY(logWarning, "Warning")
Q_LOGGING_CATEGORY(logCritical, "Critical")

11
logcategories.h 100644
Wyświetl plik

@ -0,0 +1,11 @@
#ifndef LOGCATEGORIES_H
#define LOGCATEGORIES_H
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(logDebug)
Q_DECLARE_LOGGING_CATEGORY(logInfo)
Q_DECLARE_LOGGING_CATEGORY(logWarning)
Q_DECLARE_LOGGING_CATEGORY(logCritical)
#endif // LOGCATEGORIES_H

115
main.cpp
Wyświetl plik

@ -1,22 +1,129 @@
#include "wfmain.h"
#include <QApplication>
#include <iostream>
#include "wfmain.h"
// Copytight 2017-2020 Elliott H. Liggett
// Copytight 2017-2021 Elliott H. Liggett
// Smart pointer to log file
QScopedPointer<QFile> m_logFile;
QMutex logMutex;
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//a.setStyle( "Fusion" );
a.setOrganizationName("eliggett");
a.setOrganizationDomain("nodomain");
a.setApplicationName("wfview");
QString serialPortCL;
QString hostCL;
QString civCL;
QString logFilename= QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/wfview.log";
QString currentArg;
const QString helpText = QString("Usage: -p --port /dev/port, -h --host remotehostname, -c --civ 0xAddr, -l --logfile filename.log"); // TODO...
for(int c=1; c<argc; c++)
{
//qDebug() << "Argc: " << c << " argument: " << argv[c];
currentArg = QString(argv[c]);
if((currentArg == "-p") || currentArg == "--port")
{
if(argc > c)
{
serialPortCL = argv[c+1];
c+=1;
}
} else if ((currentArg == "-h") || (currentArg == "--host"))
{
if(argc > c)
{
hostCL = argv[c+1];
c+=1;
}
}
else if ((currentArg == "-c") || (currentArg == "--civ"))
{
if (argc > c)
{
civCL = argv[c + 1];
c += 1;
}
}
else if ((currentArg == "-l") || (currentArg == "--logfile"))
{
if (argc > c)
{
logFilename = argv[c + 1];
c += 1;
}
} else if ((currentArg == "--help"))
{
std::cout << helpText.toStdString();
return 0;
} else {
std::cout << "Unrecognized option: " << currentArg.toStdString();
std::cout << helpText.toStdString();
return -1;
}
}
// Set the logging file before doing anything else.
m_logFile.reset(new QFile(logFilename));
// Open the file logging
m_logFile.data()->open(QFile::Append | QFile::Text);
// Set handler
qInstallMessageHandler(messageHandler);
qDebug(logInfo()) << "Starting wfview";
#ifdef QT_DEBUG
qDebug(logDebug()) << "SerialPortCL as set by parser: " << serialPortCL;
qDebug(logDebug()) << "remote host as set by parser: " << hostCL;
qDebug(logDebug()) << "CIV as set by parser: " << civCL;
#endif
a.setWheelScrollLines(1); // one line per wheel click
wfmain w;
wfmain w( serialPortCL, hostCL);
w.show();
return a.exec();
qDebug(logInfo()) << "wfview is finished";
}
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
// Open stream file writes
QMutexLocker locker(&logMutex);
QTextStream out(m_logFile.data());
// Write the date of recording
out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz ");
// By type determine to what level belongs message
switch (type)
{
case QtInfoMsg: out << "INF "; break;
case QtDebugMsg: out << "DBG "; break;
case QtWarningMsg: out << "WRN "; break;
case QtCriticalMsg: out << "CRT "; break;
case QtFatalMsg: out << "FTL "; break;
}
// Write to the output category of the message and the message itself
out << context.category << ": " << msg << "\n";
out.flush(); // Clear the buffered data
}

Wyświetl plik

@ -8,13 +8,6 @@
// This file parses data from the radio and also forms commands to the radio.
// The radio physical interface is handled by the commHandler() instance "comm"
// TODO:
// + Allow parameters to pass to the commHandler indicating which serial port to use
// + Impliment additional commands (of course)
// + Impliment external serial port "pass through"
// + Impliment XML RPC server?
// + Grab initial state of band scope and adjust UI accordingly.
//
// See here for a wonderful CI-V overview:
// http://www.plicht.de/ekki/civ/civ-p0a.html
@ -26,8 +19,18 @@
// Note: When sending \x00, must use QByteArray.setRawData()
rigCommander::rigCommander()
{
rigCommander::rigCommander(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate)
}
rigCommander::~rigCommander()
{
closeComm();
}
void rigCommander::commSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate)
{
// construct
// TODO: Bring this parameter and the comm port from the UI.
@ -35,23 +38,15 @@ rigCommander::rigCommander(unsigned char rigCivAddr, QString rigSerialPort, quin
// civAddr = 0x94; // address of the radio. Decimal is 148.
civAddr = rigCivAddr; // address of the radio. Decimal is 148.
usingNativeLAN = false;
setCIVAddr(civAddr);
//compCivAddr = 0xE1;
//payloadPrefix = QByteArray("\xFE\xFE\x94\xE0");
payloadPrefix = QByteArray("\xFE\xFE");
payloadPrefix.append(civAddr);
payloadPrefix.append(compCivAddr);
// ---
setup();
// ---
// payloadPrefix.append("\xE0");
this->rigSerialPort = rigSerialPort;
this->rigBaudRate = rigBaudRate;
payloadSuffix = QByteArray("\xFD");
// TODO: list full contents of /dev/serial, grep for IC-7300
// /dev/serial/by-path$ ls
// total 0
// lrwxrwxrwx 1 root root 13 Nov 24 21:43 pci-0000:00:12.0-usb-0:2.1:1.0-port0 -> ../../ttyUSB0
// comm = new commHandler("/dev/ttyUSB0");
comm = new commHandler(rigSerialPort, rigBaudRate);
// data from the comm port to the program:
@ -60,18 +55,131 @@ rigCommander::rigCommander(unsigned char rigCivAddr, QString rigSerialPort, quin
// data from the program to the comm port:
connect(this, SIGNAL(dataForComm(QByteArray)), comm, SLOT(receiveDataFromUserToRig(QByteArray)));
connect(comm, SIGNAL(haveSerialPortError(QString, QString)), this, SLOT(handleSerialPortError(QString, QString)));
connect(this, SIGNAL(getMoreDebug()), comm, SLOT(debugThis()));
pttAllowed = true; // This is for developing, set to false for "safe" debugging. Set to true for deployment.
emit commReady();
}
rigCommander::~rigCommander()
void rigCommander::commSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport,
QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec)
{
delete comm;
// construct
// TODO: Bring this parameter and the comm port from the UI.
// Keep in hex in the UI as is done with other CIV apps.
// civAddr = 0x94; // address of the radio. Decimal is 148.
civAddr = rigCivAddr; // address of the radio. Decimal is 148.
usingNativeLAN = true;
// ---
setup();
// ---
/* is this used for anything now???
this->ip = ip;
this->cport = cport;
this->sport = sport;
this->aport = aport;
this->username = username;
this->password = password;
*/
if (udp == Q_NULLPTR) {
udp = new udpHandler(ip, cport, sport, aport, username, password,buffer,rxsample,rxcodec,txsample,txcodec);
connect(udp, SIGNAL(haveDataFromPort(QByteArray)), this, SLOT(handleNewData(QByteArray)));
// data from the program to the comm port:
connect(this, SIGNAL(dataForComm(QByteArray)), udp, SLOT(receiveDataFromUserToRig(QByteArray)));
connect(this, SIGNAL(haveChangeBufferSize(quint16)), udp, SLOT(changeBufferSize(quint16)));
// Connect for errors/alerts
connect(udp, SIGNAL(haveNetworkError(QString, QString)), this, SLOT(handleSerialPortError(QString, QString)));
connect(udp, SIGNAL(haveNetworkStatus(QString)), this, SLOT(handleStatusUpdate(QString)));
}
// data from the comm port to the program:
emit commReady();
pttAllowed = true; // This is for developing, set to false for "safe" debugging. Set to true for deployment.
}
void rigCommander::closeComm()
{
if (comm != Q_NULLPTR) {
delete comm;
}
comm = Q_NULLPTR;
if (udp != Q_NULLPTR) {
delete udp;
}
udp = Q_NULLPTR;
}
void rigCommander::setup()
{
// common elements between the two constructors go here:
setCIVAddr(civAddr);
spectSeqMax = 0; // this is now set after rig ID determined
payloadPrefix = QByteArray("\xFE\xFE");
payloadPrefix.append(civAddr);
payloadPrefix.append(compCivAddr);
payloadSuffix = QByteArray("\xFD");
lookingForRig = false;
foundRig = false;
oldScopeMode = 3;
pttAllowed = true; // This is for developing, set to false for "safe" debugging. Set to true for deployment.
}
void rigCommander::process()
{
// new thread enters here. Do nothing.
// new thread enters here. Do nothing but do check for errors.
if(comm!=Q_NULLPTR && comm->serialError)
{
emit haveSerialPortError(rigSerialPort, QString("Error from commhandler. Check serial port."));
}
}
void rigCommander::handleSerialPortError(const QString port, const QString errorText)
{
qDebug() << "Error using port " << port << " message: " << errorText;
emit haveSerialPortError(port, errorText);
}
void rigCommander::handleStatusUpdate(const QString text)
{
emit haveStatusUpdate(text);
}
void rigCommander::findRigs()
{
// This function sends data to 0x00 ("broadcast") to look for any connected rig.
lookingForRig = true;
foundRig = false;
QByteArray data;
QByteArray data2;
//data.setRawData("\xFE\xFE\xa2", 3);
data.setRawData("\xFE\xFE\x00", 3);
data.append(compCivAddr); // wfview's address, 0xE1
data2.setRawData("\x19\x00", 2); // get rig ID
data.append(data2);
data.append(payloadSuffix);
//check this:
#ifdef QT_DEBUG
qDebug() << "About to request list of radios connected, using this command: ";
printHex(data, false, true);
#endif
emit dataForComm(data);
return;
}
void rigCommander::prepDataAndSend(QByteArray data)
@ -114,9 +222,87 @@ void rigCommander::disableSpectrumDisplay()
prepDataAndSend(payload);
}
void rigCommander::setSpectrumBounds()
void rigCommander::setSpectrumBounds(double startFreq, double endFreq, unsigned char edgeNumber)
{
if((edgeNumber > 3) || (!edgeNumber))
{
return;
}
unsigned char freqRange = 1; // 1 = VHF, 2 = UHF, 3 = L-Band
switch(rigCaps.model)
{
case model9700:
if(startFreq > 148)
{
freqRange++;
if(startFreq > 450)
{
freqRange++;
}
}
break;
case model705:
case model7300:
case model7610:
case model7850:
// Some rigs do not go past 60 MHz, but we will not encounter
// requests for those ranges since they are derived from the rig's existing scope range.
// start value of freqRange is 1.
if(startFreq > 1.6)
freqRange++;
if(startFreq > 2.0)
freqRange++;
if(startFreq > 6.0)
freqRange++;
if(startFreq > 8.0)
freqRange++;
if(startFreq > 11.0)
freqRange++;
if(startFreq > 15.0)
freqRange++;
if(startFreq > 20.0)
freqRange++;
if(startFreq > 22.0)
freqRange++;
if(startFreq > 26.0)
freqRange++;
if(startFreq > 30.0)
freqRange++;
if(startFreq > 45.0)
freqRange++;
if(startFreq > 60.0)
freqRange++;
if(startFreq > 74.8)
freqRange++;
if(startFreq > 108.0)
freqRange++;
if(startFreq > 137.0)
freqRange++;
if(startFreq > 400.0)
freqRange++;
break;
default:
return;
break;
}
QByteArray lowerEdge = makeFreqPayload(startFreq);
QByteArray higherEdge = makeFreqPayload(endFreq);
QByteArray payload;
payload.setRawData("\x27\x1E", 2);
payload.append(freqRange);
payload.append(edgeNumber);
payload.append(lowerEdge);
payload.append(higherEdge);
prepDataAndSend(payload);
}
void rigCommander::getScopeMode()
@ -439,6 +625,8 @@ void rigCommander::parseData(QByteArray dataInput)
//return;
}
incomingCIVAddr = data[03]; // track the CIV of the sender.
switch(data[02])
{
// case civAddr: // can't have a variable here :-(
@ -463,8 +651,18 @@ void rigCommander::parseData(QByteArray dataInput)
case '\x00':
// data send initiated by the rig due to user control
// extract the payload out and parse.
payloadIn = data.right(data.length() - 4);
parseCommand();
if((unsigned char)data[03]==compCivAddr)
{
// This is an echo of our own broadcast request.
// The data are "to 00" and "from E1"
// Don't use it!
#ifdef QT_DEBUG
qDebug() << "Caught it! Found the echo'd broadcast request from us!";
#endif
} else {
payloadIn = data.right(data.length() - 4);
parseCommand();
}
break;
default:
// could be for other equipment on the CIV network.
@ -532,7 +730,11 @@ void rigCommander::parseCommand()
case '\x19':
// qDebug() << "Have rig ID: " << (unsigned int)payloadIn[2];
// printHex(payloadIn, false, true);
model = determineRadioModel(payloadIn[2]);
model = determineRadioModel(payloadIn[2]); // verify this is the model not the CIV
determineRigCaps();
qDebug() << "Have rig ID: decimal: " << (unsigned int)model;
break;
case '\x26':
if((int)payloadIn[1] == 0)
@ -794,7 +996,6 @@ void rigCommander::parseDetailedRegisters1A05()
void rigCommander::parseWFData()
{
float freqSpan = 0.0;
switch(payloadIn[1])
{
case 0:
@ -831,6 +1032,11 @@ void rigCommander::parseWFData()
// [1] 0x16
// [2] 0x01, 0x02, 0x03: Edge 1,2,3
break;
case 0x17:
// Hold status (only 9700?)
qDebug() << "Received 0x17 hold status - need to deal with this!";
printHex(payloadIn, false, true);
break;
case 0x19:
// scope reference level
// [1] 0x19
@ -846,8 +1052,114 @@ void rigCommander::parseWFData()
}
}
void rigCommander::determineRigCaps()
{
//TODO: Add if(usingNativeLAN) condition
//TODO: Determine available bands (low priority, rig will reject out of band requests anyway)
rigCaps.model = model;
rigCaps.modelID = model; // may delete later
rigCaps.civ = incomingCIVAddr;
switch(model){
case model7300:
rigCaps.modelName = QString("IC-7300");
rigCaps.hasSpectrum = true;
rigCaps.spectSeqMax = 11;
rigCaps.spectAmpMax = 160;
rigCaps.spectLenMax = 475;
rigCaps.hasLan = false;
rigCaps.hasEthernet = false;
rigCaps.hasWiFi = false;
break;
case model9700:
rigCaps.modelName = QString("IC-9700");
rigCaps.hasSpectrum = true;
rigCaps.spectSeqMax = 11;
rigCaps.spectAmpMax = 160;
rigCaps.spectLenMax = 475;
rigCaps.hasLan = true;
rigCaps.hasEthernet = true;
rigCaps.hasWiFi = false;
break;
case model7610:
rigCaps.modelName = QString("IC-7610");
rigCaps.hasSpectrum = true;
rigCaps.spectSeqMax = 15;
rigCaps.spectAmpMax = 200;
rigCaps.spectLenMax = 689;
rigCaps.hasLan = true;
rigCaps.hasEthernet = true;
rigCaps.hasWiFi = false;
break;
case model7850:
rigCaps.modelName = QString("IC-785x");
rigCaps.hasSpectrum = true;
rigCaps.spectSeqMax = 15;
rigCaps.spectAmpMax = 136;
rigCaps.spectLenMax = 689;
rigCaps.hasLan = true;
rigCaps.hasEthernet = true;
rigCaps.hasWiFi = false;
break;
case model705:
rigCaps.modelName = QString("IC-705");
rigCaps.hasSpectrum = true;
rigCaps.spectSeqMax = 11;
rigCaps.spectAmpMax = 160;
rigCaps.spectLenMax = 475;
rigCaps.hasLan = true;
rigCaps.hasEthernet = false;
rigCaps.hasWiFi = true;
break;
default:
rigCaps.modelName = QString("IC-unknown");
rigCaps.hasSpectrum = false;
rigCaps.spectSeqMax = 0;
rigCaps.spectAmpMax = 0;
rigCaps.spectLenMax = 0;
rigCaps.hasLan = false;
rigCaps.hasEthernet = false;
rigCaps.hasWiFi = false;
break;
}
haveRigCaps = true;
if(lookingForRig)
{
lookingForRig = false;
foundRig = true;
#ifdef QT_DEBUG
qDebug() << "---Rig FOUND from broadcast query:";
#endif
this->civAddr = incomingCIVAddr; // Override and use immediately.
payloadPrefix = QByteArray("\xFE\xFE");
payloadPrefix.append(civAddr);
payloadPrefix.append(compCivAddr);
// if there is a compile-time error, remove the following line, the "hex" part is the issue:
qDebug() << "Using incomingCIVAddr: (int): " << this->civAddr << " hex: " << hex << this->civAddr;
emit discoveredRigID(rigCaps);
} else {
emit haveRigID(rigCaps);
}
}
void rigCommander::parseSpectrum()
{
if(!haveRigCaps)
{
#ifdef QT_DEBUG
qDebug() << "Spectrum received in rigCommander, but rigID is incomplete.";
#endif
return;
}
if(rigCaps.spectSeqMax == 0)
{
// there is a chance this will happen with rigs that support spectrum. Once our RigID query returns, we will parse correctly.
qDebug() << "Warning: Spectrum sequence max was zero, yet spectrum was received.";
return;
}
// Here is what to expect:
// payloadIn[00] = '\x27';
// payloadIn[01] = '\x00';
@ -882,7 +1194,8 @@ void rigCommander::parseSpectrum()
unsigned char sequence = bcdHexToDecimal(payloadIn[03]);
//unsigned char sequenceMax = bcdHexToDecimal(payloadIn[04]);
unsigned char scopeMode = bcdHexToDecimal(payloadIn[05]);
// unsigned char waveInfo = payloadIn[06]; // really just one byte?
//qDebug() << "Spectrum Data received: " << sequence << "/" << sequenceMax << " mode: " << scopeMode << " waveInfo: " << waveInfo << " length: " << payloadIn.length();
@ -893,8 +1206,19 @@ void rigCommander::parseSpectrum()
// It looks like the data length may be variable, so we need to detect it each time.
// start at payloadIn.length()-1 (to override the FD). Never mind, index -1 bad.
// chop off FD.
if(sequence == 1)
if ((sequence == 1) && (sequence < rigCaps.spectSeqMax))
{
unsigned char scopeMode = bcdHexToDecimal(payloadIn[05]); // 0=center, 1=fixed
if(scopeMode != oldScopeMode)
{
//TODO: Figure out if this is the first spectrum, and if so, always emit.
emit haveSpectrumFixedMode(scopeMode==1);
oldScopeMode = scopeMode;
}
// wave information
spectrumLine.clear();
// parseFrequency(endPosition); // overload does not emit! Return? Where? how...
@ -906,16 +1230,23 @@ void rigCommander::parseSpectrum()
spectrumStartFreq -= spectrumEndFreq;
spectrumEndFreq = spectrumStartFreq + 2*(spectrumEndFreq);
}
} else if ((sequence > 1) && (sequence < 11))
if (payloadIn.length() > 400) // Must be a LAN packet.
{
payloadIn.chop(1);
//spectrumLine.append(payloadIn.mid(17,475)); // write over the FD, last one doesn't, oh well.
spectrumLine.append(payloadIn.right(payloadIn.length()-17)); // write over the FD, last one doesn't, oh well.
emit haveSpectrumData(spectrumLine, spectrumStartFreq, spectrumEndFreq);
}
} else if ((sequence > 1) && (sequence < rigCaps.spectSeqMax))
{
// spectrum from index 05 to index 54, length is 55 per segment. Length is 56 total. Pixel data is 50 pixels.
// sequence numbers 2 through 10, 50 pixels each. Total after sequence 10 is 450 pixels.
payloadIn.chop(1);
spectrumLine.insert(spectrumLine.length(), payloadIn.right(payloadIn.length() - 5)); // write over the FD, last one doesn't, oh well.
//qDebug() << "sequence: " << sequence << "spec index: " << (sequence-2)*55 << " payloadPosition: " << payloadIn.length() - 5 << " payload length: " << payloadIn.length();
} else if (sequence == 11)
} else if (sequence == rigCaps.spectSeqMax)
{
// last spectrum, a little bit different (last 25 pixels). Total at end is 475 pixels.
// last spectrum, a little bit different (last 25 pixels). Total at end is 475 pixels (7300).
payloadIn.chop(1);
spectrumLine.insert(spectrumLine.length(), payloadIn.right(payloadIn.length() - 5));
//qDebug() << "sequence: " << sequence << " spec index: " << (sequence-2)*55 << " payloadPosition: " << payloadIn.length() - 5 << " payload length: " << payloadIn.length();
@ -949,7 +1280,16 @@ void rigCommander::parseFrequency()
// payloadIn[01] = ; // . XX KHz
// printHex(payloadIn, false, true);
frequencyMhz = payloadIn[04] & 0x0f;
frequencyMhz = 0.0;
if(payloadIn.length() == 7)
{
// 7300 has these digits too, as zeros.
// IC-705 or IC-9700 with higher frequency data available.
frequencyMhz += 100*(payloadIn[05] & 0x0f);
frequencyMhz += (1000*((payloadIn[05] & 0xf0) >> 4));
}
frequencyMhz += payloadIn[04] & 0x0f;
frequencyMhz += 10*((payloadIn[04] & 0xf0) >> 4);
frequencyMhz += ((payloadIn[03] & 0xf0) >>4)/10.0 ;
@ -977,7 +1317,10 @@ float rigCommander::parseFrequency(QByteArray data, unsigned char lastPosition)
float freq = 0.0;
freq = data[lastPosition] & 0x0f;
freq += 100*(data[lastPosition+1] & 0x0f);
freq += (1000*((data[lastPosition+1] & 0xf0) >> 4));
freq += data[lastPosition] & 0x0f;
freq += 10*((data[lastPosition] & 0xf0) >> 4);
freq += ((data[lastPosition-1] & 0xf0) >>4)/10.0 ;
@ -1003,6 +1346,9 @@ void rigCommander::parseMode()
// USB:
//"INDEX: 00 01 02 03 "
//"DATA: 01 01 02 fd "
//TODO: D-Star DV and DD modes.
switch(payloadIn[01])
{
case '\x00':
@ -1072,6 +1418,11 @@ void rigCommander::getRigID()
prepDataAndSend(payload);
}
void rigCommander::changeBufferSize(const quint16 value)
{
emit haveChangeBufferSize(value);
}
void rigCommander::sayAll()
{
QByteArray payload;

Wyświetl plik

@ -2,8 +2,10 @@
#define RIGCOMMANDER_H
#include <QObject>
#include <QDebug>
#include "commhandler.h"
#include "udphandler.h"
#include "rigidentities.h"
// This file figures out what to send to the comm and also
@ -19,17 +21,21 @@ class rigCommander : public QObject
Q_OBJECT
public:
rigCommander(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate);
rigCommander();
~rigCommander();
public slots:
void process();
void commSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate);
void commSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport,
QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec,quint16 txsample, quint8 txcodec);
void closeComm();
void enableSpectOutput();
void disableSpectOutput();
void enableSpectrumDisplay();
void disableSpectrumDisplay();
void setSpectrumBounds();
void setSpectrumBounds(double startFreq, double endFreq, unsigned char edgeNumber);
void setSpectrumCenteredMode(bool centerEnable); // centered or band-wise
void getSpectrumCenterMode();
void setScopeSpan(char span);
@ -55,15 +61,24 @@ public slots:
void setATU(bool enabled);
void getATUStatus();
void getRigID();
void findRigs();
void setCIVAddr(unsigned char civAddr);
void handleNewData(const QByteArray &data);
void handleSerialPortError(const QString port, const QString errorText);
void handleStatusUpdate(const QString text);
void changeBufferSize(const quint16 value);
void sayFrequency();
void sayMode();
void sayAll();
void getDebug();
signals:
void commReady();
void haveSpectrumData(QByteArray spectrum, double startFreq, double endFreq); // pass along data to UI
void haveRigID(rigCapabilities rigCaps);
void discoveredRigID(rigCapabilities rigCaps);
void haveSerialPortError(const QString port, const QString errorText);
void haveStatusUpdate(const QString text);
void haveFrequency(double frequencyMhz);
void haveMode(QString mode);
void haveDataMode(bool dataModeEnabled);
@ -81,9 +96,11 @@ signals:
void finished();
void havePTTStatus(bool pttOn);
void haveATUStatus(unsigned char status);
void haveChangeBufferSize(quint16 value);
private:
void setup();
QByteArray stripData(const QByteArray &data, unsigned char cutPosition);
void parseData(QByteArray data); // new data come here
void parseCommand();
@ -106,7 +123,9 @@ private:
void prepDataAndSend(QByteArray data);
void debugMe();
void printHex(const QByteArray &pdata, bool printVert, bool printHoriz);
commHandler * comm;
commHandler * comm=Q_NULLPTR;
udpHandler* udp=Q_NULLPTR;
void determineRigCaps();
QByteArray payloadIn;
QByteArray echoPerfix;
QByteArray replyPrefix;
@ -121,13 +140,35 @@ private:
double spectrumStartFreq;
double spectrumEndFreq;
struct rigCapabilities rigCaps;
bool haveRigCaps;
model_kind model;
quint8 spectSeqMax;
quint16 spectAmpMax;
quint16 spectLenMax;
unsigned char oldScopeMode;
bool usingNativeLAN; // indicates using OEM LAN connection (705,7610,9700,7850)
bool lookingForRig;
bool foundRig;
double frequencyMhz;
unsigned char civAddr; // 0x94 is default = 148decimal
unsigned char civAddr; // IC-7300: 0x94 is default = 148decimal
unsigned char incomingCIVAddr; // place to store the incoming CIV.
//const unsigned char compCivAddr = 0xE1; // 0xE1 is new default, 0xE0 was before.
bool pttAllowed;
QString rigSerialPort;
quint32 rigBaudRate;
QString ip;
int cport;
int sport;
int aport;
QString username;
QString password;
QString serialPortError;
};

Wyświetl plik

@ -1,5 +1,6 @@
#include "rigidentities.h"
// Copytight 2017-2020 Elliott H. Liggett
// Copytight 2017-2021 Elliott H. Liggett
model_kind determineRadioModel(unsigned char rigID)
{
@ -32,6 +33,12 @@ model_kind determineRadioModel(unsigned char rigID)
case model7850:
rig = model7850;
break;
case model9700:
rig = model9700;
break;
case model705:
rig = model705;
break;
default:
rig = modelUnknown;
break;
@ -39,3 +46,8 @@ model_kind determineRadioModel(unsigned char rigID)
return rig;
}

Wyświetl plik

@ -1,6 +1,9 @@
#ifndef RIGIDENTITIES_H
#define RIGIDENTITIES_H
#include <QtNumeric>
#include <QString>
// Credit:
// http://www.docksideradio.com/Icom%20Radio%20Hex%20Addresses.htm
@ -15,12 +18,32 @@ enum model_kind {
model7700 = 0x74,
model7800 = 0x6A,
model7850 = 0x8E,
model9700 = 0xA2,
model705 = 0xA4,
modelUnknown = 0xFF
};
model_kind determineRadioModel(unsigned char rigID);
struct rigCapabilities {
model_kind model;
quint8 civ;
quint8 modelID;
QString modelName;
bool hasLan; // OEM ethernet or wifi connection
bool hasEthernet;
bool hasWiFi;
bool hasSpectrum;
quint8 spectSeqMax;
quint16 spectAmpMax;
quint16 spectLenMax;
};
#endif // RIGIDENTITIES_H

104
rxaudiohandler.cpp 100644
Wyświetl plik

@ -0,0 +1,104 @@
#include "rxaudiohandler.h"
rxAudioHandler::rxAudioHandler()
{
}
rxAudioHandler::~rxAudioHandler()
{
audio->stop();
delete audio;
}
void rxAudioHandler::process()
{
qDebug() << "rxAudio Handler created.";
}
void rxAudioHandler::setup(const QAudioFormat format, const quint16 bufferSize, const bool isUlaw)
{
this->format = format;
this->bufferSize = bufferSize;
this->isUlaw = isUlaw;
audio = new QAudioOutput(format);
audio->setBufferSize(bufferSize);
device = audio->start();
}
void rxAudioHandler::incomingAudio(const QByteArray data)
{
QMutexLocker locker(&mutex);
if (isUlaw) {
device->write(uLawDecode(data));
}
else {
device->write(data, data.length());
}
}
void rxAudioHandler::changeBufferSize(const quint16 newSize)
{
QMutexLocker locker(&mutex);
qDebug() << "Changing buffer size to: " << newSize << " from " << audio->bufferSize();
audio->stop();
audio->setBufferSize(newSize);
device = audio->start();
}
void rxAudioHandler::getBufferSize()
{
emit sendBufferSize(audio->bufferSize());
}
QByteArray rxAudioHandler::uLawDecode(const QByteArray in)
{
static const qint16 ulaw_decode[256] = {
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0 };
QByteArray out;
foreach(qint8 const temp, in)
{
qint16 decoded = ulaw_decode[0];
if (temp != 0) {
decoded = ulaw_decode[static_cast<quint8>(temp)];
}
out.append(static_cast<qint8>(decoded & 0xff));
out.append(static_cast<qint8>(decoded >> 8 & 0xff));
}
return out;
}

47
rxaudiohandler.h 100644
Wyświetl plik

@ -0,0 +1,47 @@
#ifndef RXAUDIOHANDLER_H
#define RXAUDIOHANDLER_H
#include <QObject>
#include <QtMultimedia/QAudioOutput>
#include <QMutexLocker>
#include <QIODevice>
#include <QDebug>
class rxAudioHandler : public QObject
{
Q_OBJECT
public:
rxAudioHandler();
~rxAudioHandler();
public slots:
void process();
void setup(const QAudioFormat format, const quint16 bufferSize, const bool isulaw);
void incomingAudio(const QByteArray data);
void changeBufferSize(const quint16 newSize);
void getBufferSize();
signals:
void audioMessage(QString message);
void sendBufferSize(quint16 newSize);
private:
QByteArray uLawDecode(const QByteArray in);
QAudioOutput* audio;
QAudioFormat format;
QIODevice* device;
int bufferSize;
QMutex mutex;
bool isUlaw;
};
#endif // RXAUDIOHANDLER_H

997
udphandler.cpp 100644
Wyświetl plik

@ -0,0 +1,997 @@
// Copyright 2021 Phil Taylor M0VSE
// This code is heavily based on "Kappanhang" by HA2NON, ES1AKOS and W6EL!
#include "udphandler.h"
udpHandler::udpHandler(QString ip, quint16 cport, quint16 sport, quint16 aport, QString username, QString password,
quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec)
{
qDebug() << "Starting udpHandler user:" << username << " buffer:" << buffer << " rx sample rate: " << rxsample <<
" rx codec: " << rxcodec << " tx sample rate: " << txsample << " tx codec: " << txcodec;
// Lookup IP address
this->port = cport;
this->aport = aport;
this->sport = sport;
this->username = username;
this->password = password;
this->rxBufferSize = buffer;
this->rxSampleRate = rxsample;
this->txSampleRate = txsample;
this->rxCodec = rxcodec;
this->txCodec = txcodec;
/*
0x01 uLaw 1ch 8bit
0x02 PCM 1ch 8bit
0x04 PCM 1ch 16bit
0x08 PCM 2ch 8bit
0x10 PCM 2ch 16bit
0x20 uLaw 2ch 8bit
*/
this->rxCodec = rxcodec;
this->txCodec = txcodec;
// Try to set the IP address, if it is a hostname then perform a DNS lookup.
if (!radioIP.setAddress(ip))
{
QHostInfo remote = QHostInfo::fromName(ip);
foreach(QHostAddress addr, remote.addresses())
{
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
radioIP = addr;
qDebug() << "Got IP Address :" << ip << ": " << addr.toString();
break;
}
}
if (radioIP.isNull())
{
qDebug() << "Error obtaining IP Address for :" << ip << ": " << remote.errorString();
return;
}
}
// Convoluted way to find the external IP address, there must be a better way????
QString localhostname = QHostInfo::localHostName();
QList<QHostAddress> hostList = QHostInfo::fromName(localhostname).addresses();
foreach(const QHostAddress & address, hostList)
{
if (address.protocol() == QAbstractSocket::IPv4Protocol && address.isLoopback() == false)
{
localIP = QHostAddress(address.toString());
}
}
init(); // Perform connection
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpHandler::DataReceived);
connect(&reauthTimer, &QTimer::timeout, this, QOverload<>::of(&udpHandler::ReAuth));
udpBase::SendPacketConnect(); // First connect packet
compName = QString("wfview").toUtf8();
}
udpHandler::~udpHandler()
{
if (isAuthenticated)
{
if (audio != Q_NULLPTR)
{
delete audio;
}
if (serial != Q_NULLPTR)
{
delete serial;
}
qDebug() << "Sending De-Auth packet to radio";
SendPacketAuth(0x01);
}
}
void udpHandler::changeBufferSize(quint16 value)
{
emit haveChangeBufferSize(value);
}
void udpHandler::ReAuth()
{
qDebug() << "Performing ReAuth";
SendPacketAuth(0x05);
}
void udpHandler::receiveFromSerialStream(QByteArray data)
{
emit haveDataFromPort(data);
}
void udpHandler::receiveDataFromUserToRig(QByteArray data)
{
if (serial != Q_NULLPTR)
{
serial->Send(data);
}
}
void udpHandler::DataReceived()
{
while (udp->hasPendingDatagrams()) {
lastReceived = time(0);
QNetworkDatagram datagram = udp->receiveDatagram();
//qDebug() << "Received: " << datagram.data();
QByteArray r = datagram.data();
switch (r.length())
{
case (16): // Response to pkt0
if (r.mid(0, 8) == QByteArrayLiteral("\x10\x00\x00\x00\x06\x00\x01\x00"))
{
// Update remoteSID
if (!sentPacketLogin) {
remoteSID = qFromBigEndian<quint32>(r.mid(8, 4));
SendPacketLogin(); // second login packet
sentPacketLogin = true;
}
}
break;
case (21): // pkt7,
if (r.mid(1, 5) == QByteArrayLiteral("\x00\x00\x00\x07\x00") && r[16] == (char)0x01 && serialAndAudioOpened)
{
//qDebug("Got response!");
// This is a response to our pkt7 request so measure latency (only once fully connected though.
latency += lastPacket7Sent.msecsTo(QDateTime::currentDateTime());
latency /= 2;
emit haveNetworkStatus(" rtt: " + QString::number(latency) + " ms");
}
break;
case (64): // Response to Auth packet?
if (r.mid(0, 6) == QByteArrayLiteral("\x40\x00\x00\x00\x00\x00"))
{
if (r[21] == (char)0x05)
{
// Request serial and audio!
gotAuthOK = true;
if (!serialAndAudioOpened)
{
SendRequestSerialAndAudio();
}
}
}
break;
case (80): // Status packet
if (r.mid(0, 6) == QByteArrayLiteral("\x50\x00\x00\x00\x00\x00"))
{
if (r.mid(48, 3) == QByteArrayLiteral("\xff\xff\xff"))
{
if (!serialAndAudioOpened)
{
emit haveNetworkError(radioIP.toString(), "Auth failed, try rebooting the radio.");
qDebug() << "Auth failed, try rebooting the radio.";
}
}
if (r.mid(48, 3) == QByteArrayLiteral("\x00\x00\x00") && r[64] == (char)0x01)
{
emit haveNetworkError(radioIP.toString(), "Got radio disconnected.");
qDebug() << "Got radio disconnected.";
}
}
break;
case(96): // Response to Login packet.
if (r.mid(0, 6) == QByteArrayLiteral("\x60\x00\x00\x00\x00\x00"))
{
if (r.mid(48, 4) == QByteArrayLiteral("\xff\xff\xff\xfe"))
{
emit haveNetworkError(radioIP.toString(), "Invalid Username/Password");
qDebug() << "Invalid Username/Password";
}
else if (!isAuthenticated)
{
emit haveNetworkError(radioIP.toString(), "Radio Login OK!");
qDebug() << "Login OK!";
authID[0] = r[26];
authID[1] = r[27];
authID[2] = r[28];
authID[3] = r[29];
authID[4] = r[30];
authID[5] = r[31];
pkt7Timer = new QTimer(this);
connect(pkt7Timer, &QTimer::timeout, this, &udpBase::SendPkt7Idle);
pkt7Timer->start(3000); // send pkt7 idle packets every 3 seconds
SendPacketAuth(0x02);
pkt0Timer = new QTimer(this);
connect(pkt0Timer, &QTimer::timeout, this, std::bind(&udpBase::SendPkt0Idle, this, true, 0));
pkt0Timer->start(100);
SendPacketAuth(0x05);
reauthTimer.start(reauthInterval);
isAuthenticated = true;
}
}
break;
case (144):
if (!serialAndAudioOpened && r.mid(0, 6) == QByteArrayLiteral("\x90\x00\x00\x00\x00\x00") && r[0x60] == (char)0x01)
{
devName = parseNullTerminatedString(r, 0x40);
QHostAddress ip = QHostAddress(qFromBigEndian<quint32>(r.mid(0x84, 4)));
if (parseNullTerminatedString(r, 0x64) != compName) // || ip != localIP ) // TODO: More testing of IP address detection code!
{
emit haveNetworkStatus("Radio in use by: " + QString::fromUtf8(parseNullTerminatedString(r, 0x64))+" ("+ip.toString()+")");
}
else
{
serial = new udpSerial(localIP, radioIP, sport);
audio = new udpAudio(localIP, radioIP, aport,rxBufferSize,rxSampleRate, rxCodec,txSampleRate,txCodec);
QObject::connect(serial, SIGNAL(Receive(QByteArray)), this, SLOT(receiveFromSerialStream(QByteArray)));
QObject::connect(this, SIGNAL(haveChangeBufferSize(quint16)), audio, SLOT(changeBufferSize(quint16)));
serialAndAudioOpened = true;
emit haveNetworkStatus(QString::fromUtf8(devName));
qDebug() << "Got serial and audio request success, device name: " << QString::fromUtf8(devName);
// Stuff can change in the meantime because of a previous login...
remoteSID = qFromBigEndian<quint32>(r.mid(8, 4));
localSID = qFromBigEndian<quint32>(r.mid(12, 4));
authID[0] = r[26];
authID[1] = r[27];
authID[2] = r[28];
authID[3] = r[29];
authID[4] = r[30];
authID[5] = r[31];
}
// Is there already somebody connected to the radio?
}
break;
case (168):
if (r.mid(0, 6) == QByteArrayLiteral("\xa8\x00\x00\x00\x00\x00"))
{
a8replyID[0] = r[66];
a8replyID[1] = r[67];
a8replyID[2] = r[68];
a8replyID[3] = r[69];
a8replyID[4] = r[70];
a8replyID[5] = r[71];
a8replyID[6] = r[72];
a8replyID[7] = r[73];
a8replyID[8] = r[74];
a8replyID[9] = r[75];
a8replyID[10] = r[76];
a8replyID[11] = r[77];
a8replyID[12] = r[78];
a8replyID[13] = r[79];
a8replyID[14] = r[80];
a8replyID[15] = r[81];
gotA8ReplyID = true;
}
break;
}
udpBase::DataReceived(r); // Call parent function to process the rest.
r.clear();
datagram.clear();
}
return;
}
qint64 udpHandler::SendRequestSerialAndAudio()
{
/*
0x72 is RX audio codec
0x73 is TX audio codec (only single channel options)
0x01 uLaw 1ch 8bit
0x02 PCM 1ch 8bit
0x04 PCM 1ch 16bit
0x08 PCM 2ch 8bit
0x10 PCM 2ch 16bit
0x20 uLaw 2ch 8bit
*/
quint8* usernameEncoded = Passcode(username);
int txSeqBufLengthMs = 200;
const quint8 p[] = {
0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0x00, 0x00, 0x00, 0x80, 0x01, 0x03, 0x00, static_cast<quint8>(authInnerSendSeq & 0xff), static_cast<quint8>(authInnerSendSeq >> 8 & 0xff),
0x00, static_cast<quint8>(authID[0]), static_cast<quint8>(authID[1]), static_cast<quint8>(authID[2]),
static_cast<quint8>(authID[3]), static_cast<quint8>(authID[4]), static_cast<quint8>(authID[5]),
static_cast<quint8>(a8replyID[0]), static_cast<quint8>(a8replyID[1]), static_cast<quint8>(a8replyID[2]), static_cast<quint8>(a8replyID[3]),
static_cast<quint8>(a8replyID[4]), static_cast<quint8>(a8replyID[5]), static_cast<quint8>(a8replyID[6]), static_cast<quint8>(a8replyID[7]),
static_cast<quint8>(a8replyID[8]), static_cast<quint8>(a8replyID[9]), static_cast<quint8>(a8replyID[10]), static_cast<quint8>(a8replyID[11]),
static_cast<quint8>(a8replyID[12]), static_cast<quint8>(a8replyID[13]), static_cast<quint8>(a8replyID[14]), static_cast<quint8>(a8replyID[15]),
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x49, 0x43, 0x2d, 0x37, 0x38, 0x35, 0x31, 0x00, // IC-7851 in plain text
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
usernameEncoded[0], usernameEncoded[1], usernameEncoded[2], usernameEncoded[3],
usernameEncoded[4], usernameEncoded[5], usernameEncoded[6], usernameEncoded[7],
usernameEncoded[8], usernameEncoded[9], usernameEncoded[10], usernameEncoded[11],
usernameEncoded[12], usernameEncoded[13], usernameEncoded[14], usernameEncoded[15],
0x01, 0x01, rxCodec, txCodec, 0x00, 0x00, static_cast<quint8>(rxSampleRate >> 8 & 0xff), static_cast<quint8>(rxSampleRate & 0xff),
0x00, 0x00, static_cast<quint8>(txSampleRate >> 8 & 0xff), static_cast<quint8>(txSampleRate & 0xff),
0x00, 0x00, static_cast<quint8>(sport >> 8 & 0xff), static_cast<quint8>(sport & 0xff),
0x00, 0x00, static_cast<quint8>(aport >> 8 & 0xff), static_cast<quint8>(aport & 0xff), 0x00, 0x00,
static_cast<quint8>(txSeqBufLengthMs >> 8 & 0xff), static_cast<quint8>(txSeqBufLengthMs & 0xff), 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
authInnerSendSeq++;
delete[] usernameEncoded;
return SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
qint64 udpHandler::SendPacketLogin() // Only used on control stream.
{
uint16_t authStartID = rand() | rand() << 8;
quint8* usernameEncoded = Passcode(username);
quint8* passwordEncoded = Passcode(password);
quint8 p[] = {
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, static_cast<quint8>(authInnerSendSeq & 0xff), static_cast<quint8>(authInnerSendSeq >> 8 & 0xff),
0x00, static_cast<quint8>(authStartID & 0xff), static_cast<quint8>(authStartID >> 8 & 0xff), 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
usernameEncoded[0], usernameEncoded[1], usernameEncoded[2], usernameEncoded[3],
usernameEncoded[4], usernameEncoded[5], usernameEncoded[6], usernameEncoded[7],
usernameEncoded[8], usernameEncoded[9], usernameEncoded[10], usernameEncoded[11],
usernameEncoded[12], usernameEncoded[13], usernameEncoded[14], usernameEncoded[15],
passwordEncoded[0], passwordEncoded[1], passwordEncoded[2], passwordEncoded[3],
passwordEncoded[4], passwordEncoded[5], passwordEncoded[6], passwordEncoded[7],
passwordEncoded[8], passwordEncoded[9], passwordEncoded[10], passwordEncoded[11],
passwordEncoded[12], passwordEncoded[13], passwordEncoded[14], passwordEncoded[15],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
memcpy(p + 0x60, compName.constData(), compName.length());
delete[] usernameEncoded;
delete[] passwordEncoded;
authInnerSendSeq++;
return SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
qint64 udpHandler::SendPacketAuth(uint8_t magic)
{
const quint8 p[] = {
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0x00, 0x00, 0x00, 0x30, 0x01, static_cast<quint8>(magic), 0x00, static_cast<quint8>(authInnerSendSeq & 0xff), static_cast<quint8>((authInnerSendSeq) >> 8 & 0xff), 0x00,
static_cast<quint8>(authID[0]), static_cast<quint8>(authID[1]), static_cast<quint8>(authID[2]),
static_cast<quint8>(authID[3]), static_cast<quint8>(authID[4]), static_cast<quint8>(authID[5]),
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
authInnerSendSeq++;
return SendTrackedPacket(QByteArray::fromRawData((const char *)p, sizeof(p)));
}
// (pseudo) serial class
udpSerial::udpSerial(QHostAddress local, QHostAddress ip, quint16 sport)
{
qDebug() << "Starting udpSerial";
localIP = local;
port = sport;
radioIP = ip;
init(); // Perform connection
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpSerial::DataReceived);
SendPacketConnect(); // First connect packet
}
int udpSerial::Send(QByteArray d)
{
// qDebug() << "Sending: (" << d.length() << ") " << d;
uint16_t l = d.length();
const quint8 p[] = { static_cast<quint8>(0x15 + l), 0x00, 0x00, 0x00, 0x00, 0x00,0x00,0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0xc1, static_cast<quint8>(l), 0x00, static_cast<quint8>(sendSeqB >> 8 & 0xff),static_cast<quint8>(sendSeqB & 0xff)
};
QByteArray t = QByteArray::fromRawData((const char*)p, sizeof(p));
t.append(d);
SendTrackedPacket(t);
sendSeqB++;
return 1;
}
void udpSerial::SendIdle()
{
const quint8 p[] = { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
void udpSerial::SendPeriodic()
{
const quint8 p[] = { 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
qint64 udpSerial::SendPacketOpenClose(bool close)
{
uint8_t magic = 0x05;
if (close)
{
magic = 0x00;
}
const quint8 p[] = {
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0xc0, 0x01, 0x00, static_cast<const quint8>(sendSeqB >> 8 & 0xff), static_cast<const quint8>(sendSeqB & 0xff),static_cast<quint8>(magic)
};
sendSeqB++;
return SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
void udpSerial::DataReceived()
{
while (udp->hasPendingDatagrams()) {
QNetworkDatagram datagram = udp->receiveDatagram();
//qDebug() << "Received: " << datagram.data();
QByteArray r = datagram.data();
switch (r.length())
{
case (16): // Response to pkt0
if (r.mid(0, 8) == QByteArrayLiteral("\x10\x00\x00\x00\x06\x00\x01\x00"))
{
// Update remoteSID
remoteSID = qFromBigEndian<quint32>(r.mid(8, 4));
if (!periodicRunning) {
SendPacketOpenClose(false); // First connect packet
pkt7Timer = new QTimer(this);
connect(pkt7Timer, &QTimer::timeout, this, &udpBase::SendPkt7Idle);
pkt7Timer->start(3000); // send pkt7 idle packets every 3 seconds
pkt0Timer = new QTimer(this);
connect(pkt0Timer, &QTimer::timeout, this, std::bind(&udpBase::SendPkt0Idle,this,true,0));
pkt0Timer->start(100);
periodicRunning = true;
}
}
break;
default:
if (r.length() > 21) {
// First check if we are missing any packets?
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
if (lastReceivedSeq == 0 || lastReceivedSeq > gotSeq) {
lastReceivedSeq = gotSeq;
}
for (uint16_t f = lastReceivedSeq + 1; f < gotSeq; f++) {
// Do we need to request a retransmit?
qDebug() << this->metaObject()->className() << ": Missing Sequence: (" << r.length() << ") " << f;
}
lastReceivedSeq = gotSeq;
quint8 temp = r[0] - 0x15;
if ((quint8)r[16] == 0xc1 && (quint8)r[17] == temp)
{
emit Receive(r.mid(21));
}
}
break;
}
udpBase::DataReceived(r); // Call parent function to process the rest.
r.clear();
datagram.clear();
}
}
// Audio stream
udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec)
{
qDebug() << "Starting udpAudio";
this->localIP = local;
this->port = aport;
this->radioIP = ip;
this->bufferSize = buffer;
this->rxSampleRate = rxsample;
this->txSampleRate = txsample;
this->rxCodec = rxcodec;
this->txCodec = txcodec;
init(); // Perform connection
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::DataReceived);
if (rxCodec == 0x01 || rxCodec == 0x20)
rxIsUlawCodec = true;
if (rxCodec == 0x08 || rxCodec == 0x10 || rxCodec == 0x20)
rxChannelCount = 2;
if (rxCodec == 0x02 || rxCodec == 0x8)
rxNumSamples = 8; // uLaw is actually 16bit.
// Init audio
format.setSampleRate(rxSampleRate);
format.setChannelCount(rxChannelCount);
format.setSampleSize(rxNumSamples);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (info.isFormatSupported(format))
{
qDebug() << "Audio format supported";
}
else
{
qDebug() << "Audio format not supported!";
if (info.isNull())
{
qDebug() << "No device was found. You probably need to install libqt5multimedia-plugins.";
}
else {
qDebug() << "Audio Devices found: ";
const auto deviceInfos = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
for (const QAudioDeviceInfo& deviceInfo : deviceInfos)
{
qDebug() << "Device name: " << deviceInfo.deviceName();
qDebug() << "is null (probably not good):" << deviceInfo.isNull();
qDebug() << "channel count:" << deviceInfo.supportedChannelCounts();
qDebug() << "byte order:" << deviceInfo.supportedByteOrders();
qDebug() << "supported codecs:" << deviceInfo.supportedCodecs();
qDebug() << "sample rates:" << deviceInfo.supportedSampleRates();
qDebug() << "sample sizes:" << deviceInfo.supportedSampleSizes();
qDebug() << "sample types:" << deviceInfo.supportedSampleTypes();
}
qDebug() << "----- done with audio info -----";
}
}
rxaudio = new rxAudioHandler();
rxAudioThread = new QThread(this);
rxaudio->moveToThread(rxAudioThread);
connect(this,SIGNAL(setupAudio(QAudioFormat,quint16,bool)), rxaudio, SLOT(setup(QAudioFormat,quint16,bool)));
connect(this, SIGNAL(haveAudioData(QByteArray)), rxaudio, SLOT(incomingAudio(QByteArray)));
connect(this, SIGNAL(haveChangeBufferSize(quint16)), rxaudio, SLOT(changeBufferSize(quint16)));
connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater()));
rxAudioThread->start();
emit setupAudio(format, bufferSize,rxIsUlawCodec);
SendPacketConnect(); // First connect packet, audio should start very soon after.
}
udpAudio::~udpAudio()
{
if (rxAudioThread) {
rxAudioThread->quit();
rxAudioThread->wait();
}
}
void udpAudio::changeBufferSize(quint16 value)
{
emit haveChangeBufferSize(value);
}
void udpAudio::DataReceived()
{
while (udp->hasPendingDatagrams()) {
QNetworkDatagram datagram = udp->receiveDatagram();
//qDebug() << "Received: " << datagram.data();
QByteArray r = datagram.data();
switch (r.length())
{
case (16): // Response to pkt0
if (r.mid(0, 8) == QByteArrayLiteral("\x10\x00\x00\x00\x06\x00\x01\x00"))
{
// Update remoteSID in case it has changed.
remoteSID = qFromBigEndian<quint32>(r.mid(8, 4));
if (!periodicRunning) {
periodicRunning = true;
pkt7Timer = new QTimer(this);
connect(pkt7Timer, &QTimer::timeout, this, &udpBase::SendPkt7Idle);
pkt7Timer->start(3000); // send pkt7 idle packets every 3 seconds
}
}
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
*/
if (r.mid(0, 2) == QByteArrayLiteral("\x6c\x05") ||
r.mid(0, 2) == QByteArrayLiteral("\x44\x02") ||
r.mid(0, 2) == QByteArrayLiteral("\xd8\x03") ||
r.mid(0, 2) == QByteArrayLiteral("\x70\x04"))
{
// First check if we are missing any packets
// Audio stream does not send periodic pkt0 so seq "should" be sequential.
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
if (lastReceivedSeq == 0 || lastReceivedSeq > gotSeq) {
lastReceivedSeq = gotSeq;
}
for (uint16_t f = lastReceivedSeq+1 ; f < gotSeq; f++) {
// Do we need to request a retransmit?
qDebug() << this->metaObject()->className() << ": Missing Sequence: (" << r.length() << ") " << f;
}
lastReceivedSeq = gotSeq;
emit haveAudioData(r.mid(24));
}
break;
}
udpBase::DataReceived(r); // Call parent function to process the rest.
r.clear();
datagram.clear();
}
}
void udpBase::init()
{
udp = new QUdpSocket(this);
udp->bind(); // Bind to random port.
localPort = udp->localPort();
qDebug() << "UDP Stream bound to local port:" << localPort << " remote port:" << port;
uint32_t addr = localIP.toIPv4Address();
localSID = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff);
}
udpBase::~udpBase()
{
qDebug() << "Closing UDP stream :" << radioIP.toString() << ":" << port;
if (udp != Q_NULLPTR) {
SendPacketDisconnect();
udp->close();
delete udp;
}
if (pkt0Timer != Q_NULLPTR)
{
pkt0Timer->stop();
delete pkt0Timer;
}
if (pkt7Timer != Q_NULLPTR)
{
pkt7Timer->stop();
delete pkt7Timer;
}
}
// Base class!
void udpBase::DataReceived(QByteArray r)
{
switch (r.length())
{
case (16): // Response to pkt0
if (r.mid(0, 8) == QByteArrayLiteral("\x10\x00\x00\x00\x04\x00\x00\x00"))
{
if (!sentPacketConnect2)
{
remoteSID = qFromBigEndian<quint32>(r.mid(8, 4));
SendPacketConnect2(); // second connect packet
sentPacketConnect2 = true;
}
}
else if (r.mid(0, 6) == QByteArrayLiteral("\x10\x00\x00\x00\x00\x00"))
{ // pkt0
// Just get the seqnum and ignore the rest.
lastReceivedSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
}
else if (r.mid(0, 6) == QByteArrayLiteral("\x10\x00\x00\x00\x01\x00"))
{ // retransmit request
// Send an idle with the requested seqnum if not found.
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
//qDebug() << "Retransmit request for "<< this->metaObject()->className() <<" : " << gotSeq;
bool found=false;
for (int f = txSeqBuf.length() - 1; f >= 0; f--)
{
if (txSeqBuf[f].seqNum == gotSeq) {
qDebug() << this->metaObject()->className() << ": retransmitting packet :" << gotSeq << " (len=" << txSeqBuf[f].data.length() << ")";
udp->writeDatagram(txSeqBuf[f].data, radioIP, port);
udp->writeDatagram(txSeqBuf[f].data, radioIP, port);
found = true;
break;
}
}
if (!found)
{
// Packet was not found in buffer
qDebug() << this->metaObject()->className() << ": Could not find requested packet " << gotSeq << ", sending idle.";
SendPkt0Idle(false, gotSeq);
}
}
else if (r.mid(0, 6) == QByteArrayLiteral("\x18\x00\x00\x00\x01\x00"))
{ // retransmit range request, can contain multiple ranges.
for (int f = 16; f < r.length() - 4; f = f + 4)
{
quint16 start = qFromLittleEndian<quint16>(r.mid(f, 2));
quint16 end = qFromLittleEndian<quint16>(r.mid(f + 2, 2));
qDebug() << this->metaObject()->className() << ": Retransmit range request for:" << start << " to " << end;
for (quint16 gotSeq = start; gotSeq <= end; gotSeq++)
{
bool found=false;
for (int h = txSeqBuf.length() - 1; h >= 0; h--)
if (txSeqBuf[h].seqNum == gotSeq) {
qDebug() << this->metaObject()->className() << ": retransmitting packet :" << gotSeq << " (len=" << txSeqBuf[f].data.length() << ")";
udp->writeDatagram(txSeqBuf[h].data, radioIP, port);
udp->writeDatagram(txSeqBuf[h].data, radioIP, port);
found = true;
break;
}
if (!found)
{
qDebug() << this->metaObject()->className() << ": Could not find requested packet " << gotSeq << ", sending idle.";
SendPkt0Idle(false, gotSeq);
}
}
}
}
break;
case (21): // pkt7, send response if request.
if (r.mid(1, 5) == QByteArrayLiteral("\x00\x00\x00\x07\x00"))
{
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
if (r[16] == (char)0x00)
{
QMutexLocker locker(&mutex);
const quint8 p[] = { 0x15, 0x00, 0x00, 0x00, 0x07, 0x00,static_cast<quint8>(gotSeq & 0xff),static_cast<quint8>((gotSeq >> 8) & 0xff),
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0x01,static_cast<quint8>(r[17]),static_cast<quint8>(r[18]),static_cast<quint8>(r[19]),static_cast<quint8>(r[20])
};
udp->writeDatagram(QByteArray::fromRawData((const char *)p, sizeof(p)), radioIP, port);
}
}
break;
default:
break;
}
}
// Send periodic idle packets (every 100ms)
void udpBase::SendPkt0Idle(bool tracked=true,quint16 seq=0)
{
quint8 p[] = { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
lastPacket0Sent = QDateTime::currentDateTime(); // Is this used?
if (!tracked) {
p[6] = seq & 0xff;
p[7] = (seq >> 8) & 0xff;
udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
}
else {
SendTrackedPacket(QByteArray::fromRawData((const char*)p, sizeof(p)));
}
return;
}
// Send periodic idle packets (every 3000ms)
void udpBase::SendPkt7Idle()
{
QMutexLocker locker(&mutex);
//qDebug() << this->metaObject()->className() << " tx buffer size:" << txSeqBuf.length();
const quint8 p[] = { 0x15, 0x00, 0x00, 0x00, 0x07, 0x00, static_cast<quint8>(pkt7SendSeq & 0xff),static_cast<quint8>(pkt7SendSeq >> 8 & 0xff),
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff),
0x00, static_cast<quint8>(rand()),static_cast<quint8>(innerSendSeq & 0xff),static_cast<quint8>(innerSendSeq >> 8 & 0xff), 0x06
};
//qDebug() << this->metaObject()->className() << ": Send pkt7: " << QByteArray::fromRawData((const char*)p, sizeof(p));
lastPacket7Sent = QDateTime::currentDateTime();
udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
pkt7SendSeq++;
innerSendSeq++;
return;
}
qint64 udpBase::SendTrackedPacket(QByteArray d)
{
QMutexLocker locker(&mutex);
// As the radio can request retransmission of these packets, store them in a buffer (eventually!)
d[6] = sendSeq & 0xff;
d[7] = (sendSeq >> 8) & 0xff;
SEQBUFENTRY s;
s.seqNum = sendSeq;
s.timeSent = time(NULL);
s.data = (d);
txSeqBuf.append(s);
PurgeOldEntries();
sendSeq++;
return udp->writeDatagram(d, radioIP, port);
}
void udpBase::PurgeOldEntries()
{
for (int f = txSeqBuf.length() - 1; f >= 0; f--)
{
// Delete any entries older than 1 second.
if (difftime(time(NULL), txSeqBuf[f].timeSent) > 60) // Delete anything more than 60 seconds old.
{
txSeqBuf.removeAt(f);
}
}
}
qint64 udpBase::SendPacketConnect()
{
qDebug() << this->metaObject()->className() << ": Sending Connect";
QMutexLocker locker(&mutex);
const quint8 p[] = { 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
return udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
}
qint64 udpBase::SendPacketConnect2()
{
qDebug() << this->metaObject()->className() << ": Sending Connect2";
QMutexLocker locker(&mutex);
const quint8 p[] = { 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
return udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
}
qint64 udpBase::SendPacketDisconnect() // Unmanaged packet
{
QMutexLocker locker(&mutex);
//qDebug() << "Sending Stream Disconnect";
const quint8 p[] = { 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
static_cast<quint8>(localSID >> 24 & 0xff), static_cast<quint8>(localSID >> 16 & 0xff), static_cast<quint8>(localSID >> 8 & 0xff), static_cast<quint8>(localSID & 0xff),
static_cast<quint8>(remoteSID >> 24 & 0xff), static_cast<quint8>(remoteSID >> 16 & 0xff), static_cast<quint8>(remoteSID >> 8 & 0xff), static_cast<quint8>(remoteSID & 0xff)
};
udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
return udp->writeDatagram(QByteArray::fromRawData((const char*)p, sizeof(p)), radioIP, port);
}
quint8* udpBase::Passcode(QString str)
{
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
};
quint8* res = new quint8[16];
memset(res, 0, 16); // Make sure res buffer is empty!
QByteArray ba = str.toLocal8Bit();
uchar* ascii = (uchar*)ba.constData();
for (int i = 0; i < str.length() && i < 16; i++)
{
int p = ascii[i] + i;
if (p > 126)
{
p = 32 + p % 127;
}
res[i] = sequence[p];
}
return res;
}
QByteArray udpBase::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;
}

221
udphandler.h 100644
Wyświetl plik

@ -0,0 +1,221 @@
#ifndef UDPHANDLER_H
#define UDPHANDLER_H
#include <QObject>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QHostInfo>
#include <QTimer>
#include <QMutex>
#include <QDateTime>
// Allow easy endian-ness conversions
#include <QtEndian>
// Needed for audio
#include <QtMultimedia/QAudioOutput>
#include <QBuffer>
#include <QThread>
#include <QDebug>
#include "rxaudiohandler.h"
// Parent class that contains all common items.
class udpBase : public QObject
{
public:
~udpBase();
void init();
qint64 SendTrackedPacket(QByteArray d);
qint64 SendPacketConnect();
qint64 SendPacketConnect2();
qint64 SendPacketDisconnect();
void SendPkt0Idle(bool tracked, quint16 seq);
void SendPkt7Idle();
void PurgeOldEntries();
void DataReceived(QByteArray r);
unsigned char* Passcode(QString str);
QByteArray parseNullTerminatedString(QByteArray c, int s);
QUdpSocket* udp=Q_NULLPTR;
uint32_t localSID = 0;
uint32_t remoteSID = 0;
char authID[6] = { 0, 0, 0, 0, 0, 0 };
char a8replyID[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t authInnerSendSeq = 0;
uint16_t innerSendSeq = 0x8304; // Not sure why?
uint16_t sendSeqB = 0;
uint16_t sendSeq = 1;
uint16_t lastReceivedSeq = 0;
uint16_t pkt0SendSeq = 0;
uint16_t pkt7SendSeq = 0;
uint16_t periodicSeq = 0;
QDateTime lastPacket0Sent;
QDateTime lastPacket7Sent;
quint64 latency = 0;
QString username = "";
QString password = "";
QHostAddress radioIP;
QHostAddress localIP;
bool isAuthenticated = false;
quint16 localPort=0;
quint16 port=0;
QTimer *pkt7Timer=Q_NULLPTR; // Send pkt7 packets every 3 seconds
QTimer *pkt0Timer=Q_NULLPTR; // Send pkt0 packets every 1000ms.
QTimer *periodic=Q_NULLPTR; // Send pkt0 packets every 1000ms.
bool periodicRunning = false;
bool sentPacketConnect2 = false;
time_t lastReceived = time(0);
QMutex mutex;
struct SEQBUFENTRY {
time_t timeSent;
uint16_t seqNum;
QByteArray data;
};
QList <SEQBUFENTRY> txSeqBuf = QList<SEQBUFENTRY>();
QList <SEQBUFENTRY> seqBuf = QList<SEQBUFENTRY>();
};
// Class for all (pseudo) serial communications
class udpSerial : public udpBase
{
Q_OBJECT
public:
udpSerial(QHostAddress local, QHostAddress ip, quint16 sport);
QMutex serialmutex;
signals:
//void ReceiveSerial(QByteArray);
int Receive(QByteArray);
public slots:
int Send(QByteArray d);
private:
void DataReceived();
void SendIdle();
void SendPeriodic();
qint64 SendPacketOpenClose(bool close);
};
// Class for all audio communications.
class udpAudio : public udpBase
{
Q_OBJECT
public:
udpAudio(QHostAddress local, QHostAddress ip, quint16 aport, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec);
~udpAudio();
QAudioOutput* audio;
signals:
void haveAudioData(QByteArray data);
void setupAudio(const QAudioFormat format, const quint16 bufferSize, const bool isulaw);
void haveChangeBufferSize(quint16 value);
public slots:
void changeBufferSize(quint16 value);
private:
void DataReceived();
QAudioFormat format;
quint16 bufferSize;
quint16 rxSampleRate;
quint16 txSampleRate;
quint8 rxCodec;
quint8 txCodec;
quint8 rxChannelCount = 1;
bool rxIsUlawCodec = false;
quint8 rxNumSamples = 16;
quint8 txChannelCount = 1;
bool txIsUlawCodec = false;
quint8 txNumSamples = 16;
bool sentPacketConnect2 = false;
uint16_t sendAudioSeq = 0;
rxAudioHandler* rxaudio;
QThread* rxAudioThread;
};
// Class to handle the connection/disconnection of the radio.
class udpHandler: public udpBase
{
Q_OBJECT
public:
udpHandler(QString ip, quint16 cport, quint16 sport, quint16 aport, QString username, QString password,
quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec);
~udpHandler();
udpSerial *serial=Q_NULLPTR;
udpAudio *audio=Q_NULLPTR;
bool serialAndAudioOpened = false;
public slots:
void receiveDataFromUserToRig(QByteArray); // This slot will send data on to
void receiveFromSerialStream(QByteArray);
void changeBufferSize(quint16 value);
signals:
void RigConnected(const QString&);
void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander
void haveNetworkError(QString, QString);
void haveNetworkStatus(QString);
void haveChangeBufferSize(quint16 value);
private:
qint64 SendRequestSerialAndAudio();
qint64 SendPacketLogin();
qint64 SendPacketAuth(uint8_t magic);
void ReAuth();
void DataReceived();
bool gotA8ReplyID = false;
bool gotAuthOK = false;
bool sentPacketLogin = false;
bool sentPacketConnect = false;
bool sentPacketConnect2 = false;
bool radioInUse = false;
quint16 aport;
quint16 sport;
quint16 rxSampleRate;
quint16 txSampleRate;
quint16 rxBufferSize;
quint8 rxCodec;
quint8 txCodec;
quint16 reauthInterval = 60000;
QTimer reauthTimer;
QByteArray devName;
QByteArray compName;
};
#endif

Wyświetl plik

@ -7,7 +7,7 @@
// This code is copyright 2017-2020 Elliott H. Liggett
// All rights reserved
wfmain::wfmain(QWidget *parent) :
wfmain::wfmain(const QString serialPortCL, const QString hostCL, QWidget *parent ) :
QMainWindow(parent),
ui(new Ui::wfmain)
{
@ -20,6 +20,11 @@ wfmain::wfmain(QWidget *parent) :
setWindowTitle(QString("wfview"));
this->serialPortCL = serialPortCL;
this->hostCL = hostCL;
haveRigCaps = false;
ui->bandStkLastUsedBtn->setVisible(false);
ui->bandStkVoiceBtn->setVisible(false);
ui->bandStkDataBtn->setVisible(false);
@ -141,32 +146,41 @@ wfmain::wfmain(QWidget *parent) :
keyM->setKey(Qt::Key_M);
connect(keyM, SIGNAL(activated()), this, SLOT(shortcutM()));
// Enumerate audio devices, need to do before settings are loaded.
const auto audioOutputs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
for (const QAudioDeviceInfo& deviceInfo : audioOutputs) {
ui->audioOutputCombo->addItem(deviceInfo.deviceName());
}
const auto audioInputs = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
for (const QAudioDeviceInfo& deviceInfo : audioInputs) {
ui->audioInputCombo->addItem(deviceInfo.deviceName());
}
setDefaultColors(); // set of UI colors with defaults populated
setDefPrefs(); // other default options
loadSettings(); // Look for saved preferences
// if setting for serial port is "auto" then...
if(prefs.serialPortRadio == QString("auto"))
{
// Find the ICOM IC-7300.
qDebug() << "Searching for serial port...";
QDirIterator it("/dev/serial", QStringList() << "*IC-7300*", QDir::Files, QDirIterator::Subdirectories);
// if(prefs.serialPortRadio == QString("auto"))
// {
// // Find the ICOM IC-7300.
// qDebug() << "Searching for serial port...";
// QDirIterator it("/dev/serial", QStringList() << "*IC-7300*", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext())
qDebug() << it.next();
// if (it.isEmpty()) // fail or default to ttyUSB0 if present
// iterator might not make sense
serialPortRig = it.filePath(); // first? last?
if(serialPortRig.isEmpty())
{
qDebug() << "Cannot find IC-7300 serial port. Trying /dev/ttyUSB0";
serialPortRig = QString("/dev/ttyUSB0");
}
// end finding the 7300 code
} else {
serialPortRig = prefs.serialPortRadio;
}
// while (it.hasNext())
// qDebug() << it.next();
// // if (it.isEmpty()) // fail or default to ttyUSB0 if present
// // iterator might not make sense
// serialPortRig = it.filePath(); // first? last?
// if(serialPortRig.isEmpty())
// {
// qDebug() << "Cannot find IC-7300 serial port. Trying /dev/ttyUSB0";
// serialPortRig = QString("/dev/ttyUSB0");
// }
// // end finding the 7300 code
// } else {
// serialPortRig = prefs.serialPortRadio;
// }
plot = ui->plot; // rename it waterfall.
@ -180,17 +194,17 @@ wfmain::wfmain(QWidget *parent) :
tracer->setBrush(Qt::green);
tracer->setSize(30);
spectWidth = 475; // fixed for now
wfLength = 160; // fixed for now
// spectWidth = 475; // fixed for now
// wfLength = 160; // fixed for now, time-length of waterfall
// Initialize before use!
// // Initialize before use!
QByteArray empty((int)spectWidth, '\x01');
spectrumPeaks = QByteArray( (int)spectWidth, '\x01' );
for(quint16 i=0; i<wfLength; i++)
{
wfimage.append(empty);
}
// QByteArray empty((int)spectWidth, '\x01');
// spectrumPeaks = QByteArray( (int)spectWidth, '\x01' );
// for(quint16 i=0; i<wfLength; i++)
// {
// wfimage.append(empty);
// }
// 0 1 2 3 4
modes << "LSB" << "USB" << "AM" << "CW" << "RTTY";
@ -213,18 +227,18 @@ wfmain::wfmain(QWidget *parent) :
ui->afGainSlider->setSingleStep(100);
ui->afGainSlider->setSingleStep(100);
rigStatus = new QLabel(this);
ui->statusBar->addPermanentWidget(rigStatus);
ui->statusBar->showMessage("Connecting to rig...", 1000);
ui->statusBar->showMessage("Ready", 2000);
delayedCommand = new QTimer(this);
delayedCommand->setInterval(250); // 250ms until we find rig civ and id, then 100ms.
delayedCommand->setSingleShot(true);
connect(delayedCommand, SIGNAL(timeout()), this, SLOT(runDelayedCommand()));
rig = new rigCommander(prefs.radioCIVAddr, serialPortRig, prefs.serialPortBaud);
rigThread = new QThread(this);
rig->moveToThread(rigThread);
connect(rigThread, SIGNAL(started()), rig, SLOT(process()));
connect(rig, SIGNAL(finished()), rigThread, SLOT(quit()));
rigThread->start();
openRig();
qRegisterMetaType<rigCapabilities>();
connect(rig, SIGNAL(haveFrequency(double)), this, SLOT(receiveFreq(double)));
connect(this, SIGNAL(getFrequency()), rig, SLOT(getFrequency()));
@ -244,6 +258,7 @@ wfmain::wfmain(QWidget *parent) :
connect(rig, SIGNAL(haveMode(QString)), this, SLOT(receiveMode(QString)));
connect(rig, SIGNAL(haveDataMode(bool)), this, SLOT(receiveDataModeStatus(bool)));
connect(rig, SIGNAL(haveSpectrumData(QByteArray, double, double)), this, SLOT(receiveSpectrumData(QByteArray, double, double)));
connect(rig, SIGNAL(haveSpectrumFixedMode(bool)), this, SLOT(receiveSpectrumFixedMode(bool)));
connect(this, SIGNAL(setFrequency(double)), rig, SLOT(setFrequency(double)));
connect(this, SIGNAL(setScopeCenterMode(bool)), rig, SLOT(setSpectrumCenteredMode(bool)));
connect(this, SIGNAL(setScopeEdge(char)), rig, SLOT(setScopeEdge(char)));
@ -251,6 +266,7 @@ wfmain::wfmain(QWidget *parent) :
connect(this, SIGNAL(getScopeMode()), rig, SLOT(getScopeMode()));
connect(this, SIGNAL(getScopeEdge()), rig, SLOT(getScopeEdge()));
connect(this, SIGNAL(getScopeSpan()), rig, SLOT(getScopeSpan()));
connect(this, SIGNAL(setScopeFixedEdge(double,double,unsigned char)), rig, SLOT(setSpectrumBounds(double,double,unsigned char)));
connect(this, SIGNAL(setMode(char)), rig, SLOT(setMode(char)));
connect(this, SIGNAL(getRfGain()), rig, SLOT(getRfGain()));
@ -266,8 +282,9 @@ wfmain::wfmain(QWidget *parent) :
connect(this, SIGNAL(getATUStatus()), rig, SLOT(getATUStatus()));
connect(this, SIGNAL(getRigID()), rig, SLOT(getRigID()));
connect(rig, SIGNAL(haveATUStatus(unsigned char)), this, SLOT(receiveATUStatus(unsigned char)));
connect(rig, SIGNAL(haveRigID(rigCapabilities)), this, SLOT(receiveRigID(rigCapabilities)));
// Speech (emitted from IC-7300 speaker)
// Speech (emitted from rig speaker)
connect(this, SIGNAL(sayAll()), rig, SLOT(sayAll()));
connect(this, SIGNAL(sayFrequency()), rig, SLOT(sayFrequency()));
connect(this, SIGNAL(sayMode()), rig, SLOT(sayMode()));
@ -296,15 +313,6 @@ wfmain::wfmain(QWidget *parent) :
#endif
colorScale = new QCPColorScale(wf);
colorMap->data()->setValueRange(QCPRange(0, wfLength-1));
colorMap->data()->setKeyRange(QCPRange(0, spectWidth-1));
colorMap->setDataRange(QCPRange(0, 160));
colorMap->setGradient(QCPColorGradient::gpJet); // TODO: Add preference
colorMapData = new QCPColorMapData(spectWidth, wfLength, QCPRange(0, spectWidth-1), QCPRange(0, wfLength-1));
colorMap->setData(colorMapData);
spectRowCurrent = 0;
wf->yAxis->setRangeReversed(true);
wf->xAxis->setVisible(false);
ui->tabWidget->setCurrentIndex(0);
@ -317,10 +325,6 @@ wfmain::wfmain(QWidget *parent) :
ui->freqMhzLineEdit->setValidator( new QDoubleValidator(0, 100, 6, this));
delayedCommand = new QTimer(this);
delayedCommand->setInterval(100); // ms. 250 was fine. TODO: Find practical maximum with margin on pi
delayedCommand->setSingleShot(true);
connect(delayedCommand, SIGNAL(timeout()), this, SLOT(runDelayedCommand()));
pttTimer = new QTimer(this);
pttTimer->setInterval(180*1000); // 3 minute max transmit time in ms
@ -355,27 +359,234 @@ wfmain::wfmain(QWidget *parent) :
on_drawPeakChk_clicked(prefs.drawPeaks);
drawPeaks = prefs.drawPeaks;
getInitialRigState();
//getInitialRigState();
oldFreqDialVal = ui->freqDial->value();
}
wfmain::~wfmain()
{
// rigThread->quit();
rigThread->quit();
rigThread->wait();
delete ui;
}
void wfmain::openRig()
{
// This function is intended to handle opening a connection to the rig.
// the connection can be either serial or network,
// and this function is also responsible for initiating the search for a rig model and capabilities.
// Any errors, such as unable to open connection or unable to open port, are to be reported to the user.
//TODO: if(hasRunPreviously)
//TODO: if(useNetwork){...
// } else {
// if (prefs.fileWasNotFound) {
// showRigSettings(); // rig setting dialog box for network/serial, CIV, hostname, port, baud rate, serial device, etc
// TODO: How do we know if the setting was loaded?
// TODO: Use these if they are found
#ifdef QT_DEBUG
if(!serialPortCL.isEmpty())
{
qDebug() << "Serial port specified by user: " << serialPortCL;
} else {
qDebug() << "Serial port not specified. ";
}
if(!hostCL.isEmpty())
{
qDebug() << "Remote host name specified by user: " << hostCL;
}
#endif
if (rigThread == Q_NULLPTR)
{
rig = new rigCommander();
rigThread = new QThread(this);
rig->moveToThread(rigThread);
connect(rigThread, SIGNAL(started()), rig, SLOT(process()));
connect(rigThread, SIGNAL(finished()), rig, SLOT(deleteLater()));
rigThread->start();
connect(rig, SIGNAL(haveSerialPortError(QString, QString)), this, SLOT(receiveSerialPortError(QString, QString)));
connect(rig, SIGNAL(haveStatusUpdate(QString)), this, SLOT(receiveStatusUpdate(QString)));
connect(this, SIGNAL(sendCommSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint8,quint16,quint8)), rig, SLOT(commSetup(unsigned char, QString, quint16, quint16, quint16, QString, QString,quint16,quint16,quint8,quint16,quint8)));
connect(this, SIGNAL(sendCommSetup(unsigned char, QString, quint32)), rig, SLOT(commSetup(unsigned char, QString, quint32)));
connect(this, SIGNAL(sendCloseComm()), rig, SLOT(closeComm()));
connect(this, SIGNAL(sendChangeBufferSize(quint16)), rig, SLOT(changeBufferSize(quint16)));
connect(this, SIGNAL(getRigCIV()), rig, SLOT(findRigs()));
connect(rig, SIGNAL(discoveredRigID(rigCapabilities)), this, SLOT(receiveFoundRigID(rigCapabilities)));
connect(rig, SIGNAL(commReady()), this, SLOT(receiveCommReady()));
}
if (prefs.enableLAN)
{
emit sendCommSetup(prefs.radioCIVAddr, prefs.ipAddress, prefs.controlLANPort,
prefs.serialLANPort, prefs.audioLANPort, prefs.username, prefs.password,prefs.audioRXBufferSize,prefs.audioRXSampleRate,prefs.audioRXCodec,prefs.audioTXSampleRate,prefs.audioTXCodec);
} else {
if( (prefs.serialPortRadio == QString("auto")) && (serialPortCL.isEmpty()))
{
// Find the ICOM
// qDebug() << "Searching for serial port...";
QDirIterator it73("/dev/serial", QStringList() << "*IC-7300*", QDir::Files, QDirIterator::Subdirectories);
QDirIterator it97("/dev/serial", QStringList() << "*IC-9700*A*", QDir::Files, QDirIterator::Subdirectories);
QDirIterator it785x("/dev/serial", QStringList() << "*IC-785*A*", QDir::Files, QDirIterator::Subdirectories);
QDirIterator it705("/dev/serial", QStringList() << "*IC-705*A", QDir::Files, QDirIterator::Subdirectories);
if(!it73.filePath().isEmpty())
{
// use
serialPortRig = it73.filePath(); // first
} else if(!it97.filePath().isEmpty())
{
// IC-9700 port
serialPortRig = it97.filePath();
} else if(!it785x.filePath().isEmpty())
{
// IC-785x port
serialPortRig = it785x.filePath();
} else if(!it705.filePath().isEmpty())
{
// IC-705
serialPortRig = it705.filePath();
} else {
//fall back:
qDebug() << "Could not find Icom serial port. Falling back to OS default. Use --port to specify, or modify preferences.";
#ifdef Q_OS_MAC
serialPortRig = QString("/dev/tty.SLAB_USBtoUART");
#endif
#ifdef Q_OS_LINUX
serialPortRig = QString("/dev/ttyUSB0");
#endif
#ifdef Q_OS_WIN
serialPortRig = QString("COM1");
#endif
}
} else {
if(serialPortCL.isEmpty())
{
serialPortRig = prefs.serialPortRadio;
} else {
serialPortRig = serialPortCL;
}
}
// Here, the radioCIVAddr is being set from a default preference, which is for the 7300.
// However, we will not use it initially. OTOH, if it is set explicitedly to a value in the prefs,
// then we skip auto detection.
emit sendCommSetup(prefs.radioCIVAddr, serialPortRig, prefs.serialPortBaud);
}
ui->statusBar->showMessage(QString("Connecting to rig using serial port ").append(serialPortRig), 1000);
/*
if(prefs.radioCIVAddr == 0)
{
// tell rigCommander to broadcast a request for all rig IDs.
// qDebug() << "Beginning search from wfview for rigCIV (auto-detection broadcast)";
ui->statusBar->showMessage(QString("Searching CIV bus for connected radios."), 1000);
emit getRigCIV();
cmdOutQue.append(cmdGetRigCIV);
delayedCommand->start();
} else {
// don't bother, they told us the CIV they want, stick with it.
// We still query the rigID to find the model, but at least we know the CIV.
qDebug() << "Skipping automatic CIV, using user-supplied value of " << prefs.radioCIVAddr;
getInitialRigState();
}
*/
}
void wfmain::receiveCommReady()
{
qDebug() << "Received CommReady!! ";
// taken from above:
if(prefs.radioCIVAddr == 0)
{
// tell rigCommander to broadcast a request for all rig IDs.
// qDebug() << "Beginning search from wfview for rigCIV (auto-detection broadcast)";
ui->statusBar->showMessage(QString("Searching CIV bus for connected radios."), 1000);
emit getRigCIV();
cmdOutQue.append(cmdGetRigCIV);
delayedCommand->start();
} else {
// don't bother, they told us the CIV they want, stick with it.
// We still query the rigID to find the model, but at least we know the CIV.
qDebug() << "Skipping automatic CIV, using user-supplied value of " << prefs.radioCIVAddr;
getInitialRigState();
}
}
void wfmain::receiveFoundRigID(rigCapabilities rigCaps)
{
// Entry point for unknown rig being identified at the start of the program.
//now we know what the rig ID is:
//qDebug() << "In wfview, we now have a reply to our request for rig identity sent to CIV BROADCAST.";
delayedCommand->setInterval(100); // faster polling is ok now.
receiveRigID(rigCaps);
getInitialRigState();
QString message = QString("Found model: ").append(rigCaps.modelName);
ui->statusBar->showMessage(message, 1500);
return;
}
void wfmain::receiveSerialPortError(QString port, QString errorText)
{
qDebug() << "wfmain: received serial port error for port: " << port << " with message: " << errorText;
ui->statusBar->showMessage(QString("ERROR: using port ").append(port).append(": ").append(errorText), 10000);
// TODO: Dialog box, exit, etc
}
void wfmain::receiveStatusUpdate(QString text)
{
this->rigStatus->setText(text);
}
void wfmain::setDefPrefs()
{
defPrefs.useFullScreen = true;
defPrefs.useFullScreen = false;
defPrefs.useDarkMode = true;
defPrefs.drawPeaks = true;
defPrefs.stylesheetPath = QString("qdarkstyle/style.qss");
defPrefs.radioCIVAddr = 0x94;
defPrefs.radioCIVAddr = 0x00; // previously was 0x94 for 7300.
defPrefs.serialPortRadio = QString("auto");
defPrefs.serialPortBaud = 115200;
defPrefs.enablePTT = false;
defPrefs.niceTS = true;
defPrefs.enableLAN = false;
defPrefs.ipAddress = QString("");
defPrefs.controlLANPort = 50001;
defPrefs.serialLANPort = 50002;
defPrefs.audioLANPort = 50003;
defPrefs.username = QString("");
defPrefs.password = QString("");
defPrefs.audioOutput = QAudioDeviceInfo::defaultOutputDevice().deviceName();
defPrefs.audioInput = QAudioDeviceInfo::defaultInputDevice().deviceName();
defPrefs.audioRXBufferSize = 12000;
defPrefs.audioRXSampleRate = 48000;
defPrefs.audioRXCodec = 4;
defPrefs.audioTXSampleRate = 48000;
defPrefs.audioTXCodec = 4;
}
void wfmain::loadSettings()
@ -410,6 +621,84 @@ void wfmain::loadSettings()
prefs.niceTS = settings.value("NiceTS", defPrefs.niceTS).toBool();
settings.endGroup();
settings.beginGroup("LAN");
prefs.enableLAN = settings.value("EnableLAN", defPrefs.enableLAN).toBool();
ui->lanEnableChk->setChecked(prefs.enableLAN);
prefs.ipAddress = settings.value("IPAddress", defPrefs.ipAddress).toString();
ui->ipAddressTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->ipAddressTxt->setText(prefs.ipAddress);
prefs.controlLANPort = settings.value("ControlLANPort", defPrefs.controlLANPort).toInt();
ui->controlPortTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->controlPortTxt->setText(QString("%1").arg(prefs.controlLANPort));
prefs.serialLANPort = settings.value("SerialLANPort", defPrefs.serialLANPort).toInt();
ui->serialPortTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->serialPortTxt->setText(QString("%1").arg(prefs.serialLANPort));
prefs.audioLANPort = settings.value("AudioLANPort", defPrefs.audioLANPort).toInt();
ui->audioPortTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->audioPortTxt->setText(QString("%1").arg(prefs.audioLANPort));
prefs.username = settings.value("Username", defPrefs.username).toString();
ui->usernameTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->usernameTxt->setText(QString("%1").arg(prefs.username));
prefs.password = settings.value("Password", defPrefs.password).toString();
ui->passwordTxt->setEnabled(ui->lanEnableChk->isChecked());
ui->passwordTxt->setText(QString("%1").arg(prefs.password));
prefs.audioRXBufferSize = settings.value("AudioRXBufferSize", defPrefs.audioRXBufferSize).toInt();
ui->audioBufferSizeSlider->setEnabled(ui->lanEnableChk->isChecked());
ui->audioBufferSizeSlider->setValue(prefs.audioRXBufferSize);
ui->audioBufferSizeSlider->setTracking(false); // Stop it sending value on every change.
prefs.audioRXSampleRate = settings.value("AudioRXSampleRate", defPrefs.audioRXSampleRate).toInt();
prefs.audioTXSampleRate = settings.value("AudioTXSampleRate", defPrefs.audioTXSampleRate).toInt();
ui->audioSampleRateCombo->setEnabled(ui->lanEnableChk->isChecked());
int audioSampleRateIndex = ui->audioSampleRateCombo->findText(QString::number(prefs.audioRXSampleRate));
if (audioSampleRateIndex != -1) {
ui->audioOutputCombo->setCurrentIndex(audioSampleRateIndex);
}
// Add codec combobox items here so that we can add userdata!
ui->audioRXCodecCombo->addItem("LPCM 1ch 16bit", 4);
ui->audioRXCodecCombo->addItem("LPCM 1ch 8bit", 1);
ui->audioRXCodecCombo->addItem("uLaw 1ch 8bit", 2);
ui->audioRXCodecCombo->addItem("LPCM 2ch 16bit", 16);
ui->audioRXCodecCombo->addItem("uLaw 2ch 8bit", 32);
ui->audioRXCodecCombo->addItem("PCM 2ch 8bit", 8);
prefs.audioRXCodec = settings.value("AudioRXCodec", defPrefs.audioRXCodec).toInt();
ui->audioRXCodecCombo->setEnabled(ui->lanEnableChk->isChecked());
for (int f = 0; f < ui->audioRXCodecCombo->count(); f++)
if (ui->audioRXCodecCombo->itemData(f).toInt() == prefs.audioRXCodec)
ui->audioRXCodecCombo->setCurrentIndex(f);
ui->audioTXCodecCombo->addItem("LPCM 1ch 16bit", 4);
ui->audioTXCodecCombo->addItem("LPCM 1ch 8bit", 1);
ui->audioTXCodecCombo->addItem("uLaw 1ch 8bit", 2);
prefs.audioTXCodec = settings.value("AudioTXCodec", defPrefs.audioTXCodec).toInt();
ui->audioTXCodecCombo->setEnabled(ui->lanEnableChk->isChecked());
for (int f = 0; f < ui->audioTXCodecCombo->count(); f++)
if (ui->audioTXCodecCombo->itemData(f).toInt() == prefs.audioTXCodec)
ui->audioTXCodecCombo->setCurrentIndex(f);
prefs.audioOutput = settings.value("AudioOutput", defPrefs.audioOutput).toString();
ui->audioOutputCombo->setEnabled(ui->lanEnableChk->isChecked());
int audioOutputIndex = ui->audioOutputCombo->findText(prefs.audioOutput);
if (audioOutputIndex != -1)
ui->audioOutputCombo->setCurrentIndex(audioOutputIndex);
prefs.audioInput = settings.value("AudioInput", defPrefs.audioInput).toString();
ui->audioInputCombo->setEnabled(ui->lanEnableChk->isChecked());
int audioInputIndex = ui->audioInputCombo->findText(prefs.audioInput);
if (audioInputIndex != - 1)
ui->audioOutputCombo->setCurrentIndex(audioInputIndex);
settings.endGroup();
// Memory channels
settings.beginGroup("Memory");
@ -476,6 +765,24 @@ void wfmain::saveSettings()
settings.setValue("NiceTS", prefs.niceTS);
settings.endGroup();
settings.beginGroup("LAN");
settings.setValue("EnableLAN", prefs.enableLAN);
settings.setValue("IPAddress", prefs.ipAddress);
settings.setValue("ControlLANPort", prefs.controlLANPort);
settings.setValue("SerialLANPort", prefs.serialLANPort);
settings.setValue("AudioLANPort", prefs.audioLANPort);
settings.setValue("Username", prefs.username);
settings.setValue("Password", prefs.password);
settings.setValue("AudioRXBufferSize", prefs.audioRXBufferSize);
settings.setValue("AudioRXSampleRate", prefs.audioRXSampleRate);
settings.setValue("AudioRXCodec", prefs.audioRXCodec);
settings.setValue("AudioTXBufferSize", prefs.audioRXBufferSize);
settings.setValue("AudioTXSampleRate", prefs.audioRXSampleRate);
settings.setValue("AudioTXCodec", prefs.audioTXCodec);
settings.setValue("AudioOutput", prefs.audioOutput);
settings.setValue("AudioInput", prefs.audioInput);
settings.endGroup();
// Memory channels
settings.beginGroup("Memory");
settings.beginWriteArray("Channel", (int)mem.getNumPresets());
@ -547,6 +854,43 @@ void wfmain::saveSettings()
settings.sync(); // Automatic, not needed (supposedly)
}
void wfmain::prepareWf()
{
// All this code gets moved in from the constructor of wfmain.
if(haveRigCaps)
{
// do things
spectWidth = rigCaps.spectLenMax; // was fixed at 475
wfLength = 160; // fixed for now, time-length of waterfall
// Initialize before use!
QByteArray empty((int)spectWidth, '\x01');
spectrumPeaks = QByteArray( (int)spectWidth, '\x01' );
for(quint16 i=0; i<wfLength; i++)
{
wfimage.append(empty);
}
// from line 305-313:
colorMap->data()->setValueRange(QCPRange(0, wfLength-1));
colorMap->data()->setKeyRange(QCPRange(0, spectWidth-1));
colorMap->setDataRange(QCPRange(0, rigCaps.spectAmpMax));
colorMap->setGradient(QCPColorGradient::gpJet); // TODO: Add preference
colorMapData = new QCPColorMapData(spectWidth, wfLength, QCPRange(0, spectWidth-1), QCPRange(0, wfLength-1));
colorMap->setData(colorMapData);
spectRowCurrent = 0;
wf->yAxis->setRangeReversed(true);
wf->xAxis->setVisible(false);
} else {
qDebug() << "Cannot prepare WF view without rigCaps. Waiting on this.";
return;
}
}
// Key shortcuts (hotkeys)
@ -560,6 +904,7 @@ void wfmain::shortcutF11()
this->showFullScreen();
onFullscreen = true;
}
ui->fullScreenChk->setChecked(onFullscreen);
}
void wfmain::shortcutF1()
@ -734,7 +1079,7 @@ void wfmain::shortcutM()
}
void wfmain::getInitialRigState()
void wfmain:: getInitialRigState()
{
// Initial list of queries to the radio.
// These are made when the program starts up
@ -742,7 +1087,7 @@ void wfmain::getInitialRigState()
// the polling interval is set at 100ms. Faster is possible but slower
// computers will glitch occassionally.
cmdOutQue.append(cmdGetRigID); // This may be used in the future.
//cmdOutQue.append(cmdGetRigID);
cmdOutQue.append(cmdGetFreq);
cmdOutQue.append(cmdGetMode);
@ -759,6 +1104,11 @@ void wfmain::getInitialRigState()
// get TX level
// get Scope reference Level
//cmdOutQue.append(cmdNone);
//cmdOutQue.append(cmdGetRigID);
//cmdOutQue.append(cmdNone);
//cmdOutQue.append(cmdGetRigID);
cmdOutQue.append(cmdDispEnable);
cmdOutQue.append(cmdSpecOn);
@ -907,6 +1257,14 @@ void wfmain::runDelayedCommand()
case cmdGetRigID:
emit getRigID();
break;
case cmdGetRigCIV:
// if(!know rig civ already)
if(!haveRigCaps)
{
emit getRigCIV();
cmdOutQue.append(cmdGetRigCIV); // This way, we stay here until we get an answer.
}
break;
case cmdGetFreq:
emit getFrequency();
break;
@ -947,6 +1305,12 @@ void wfmain::runDelayedCommand()
case cmdGetATUStatus:
emit getATUStatus();
break;
case cmdScopeCenterMode:
emit setScopeCenterMode(true);
break;
case cmdScopeFixedMode:
emit setScopeCenterMode(false);
break;
default:
break;
}
@ -962,9 +1326,39 @@ void wfmain::runDelayedCommand()
}
}
void wfmain::receiveRigID(rigCapabilities rigCaps)
{
// Note: We intentionally request rigID several times
// because without rigID, we can't do anything with the waterfall.
if(haveRigCaps)
{
return;
} else {
#ifdef QT_DEBUG
qDebug() << "Rig name: " << rigCaps.modelName;
qDebug() << "Has LAN capabilities: " << rigCaps.hasLan;
qDebug() << "Rig ID received into wfmain: spectLenMax: " << rigCaps.spectLenMax;
qDebug() << "Rig ID received into wfmain: spectAmpMax: " << rigCaps.spectAmpMax;
qDebug() << "Rig ID received into wfmain: spectSeqMax: " << rigCaps.spectSeqMax;
qDebug() << "Rig ID received into wfmain: hasSpectrum: " << rigCaps.hasSpectrum;
#endif
this->rigCaps = rigCaps;
this->spectWidth = rigCaps.spectLenMax; // used once haveRigCaps is true.
haveRigCaps = true;
ui->connectBtn->setText("Disconnect"); // We must be connected now.
prepareWf();
// Adding these here because clearly at this point we have valid
// rig comms. In the future, we should establish comms and then
// do all the initial grabs. For now, this hack of adding them here and there:
cmdOutQue.append(cmdGetFreq);
cmdOutQue.append(cmdGetMode);
}
}
void wfmain::receiveFreq(double freqMhz)
{
//qDebug() << "Frequency: " << freqMhz;
//qDebug() << "HEY WE GOT A Frequency: " << freqMhz;
ui->freqLabel->setText(QString("%1").arg(freqMhz, 0, 'f'));
this->freqMhz = freqMhz;
this->knobFreqMhz = freqMhz;
@ -979,6 +1373,14 @@ void wfmain::receivePTTstatus(bool pttOn)
void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double endFreq)
{
if(!haveRigCaps)
{
#ifdef QT_DEBUG
qDebug() << "Spectrum received, but RigID incomplete.";
#endif
return;
}
if((startFreq != oldLowerFreq) || (endFreq != oldUpperFreq))
{
// If the frequency changed and we were drawing peaks, now is the time to clearn them
@ -996,9 +1398,16 @@ void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double e
//qDebug() << "start: " << startFreq << " end: " << endFreq;
quint16 specLen = spectrum.length();
//qDebug() << "Spectrum data received at UI! Length: " << specLen;
if(specLen != 475)
//if( (specLen != 475) || (specLen!=689) )
if( specLen != rigCaps.spectLenMax )
{
//qDebug () << "Unusual spectrum: length: " << specLen;
#ifdef QT_DEBUG
qDebug() << "-------------------------------------------";
qDebug() << "------ Unusual spectrum received, length: " << specLen;
qDebug() << "------ Expected spectrum length: " << rigCaps.spectLenMax;
qDebug() << "------ This should happen once at most. ";
#endif
return; // safe. Using these unusual length things is a problem.
}
@ -1071,6 +1480,13 @@ void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double e
}
}
void wfmain::receiveSpectrumFixedMode(bool isFixed)
{
ui->scopeCenterModeChk->blockSignals(true);
ui->scopeCenterModeChk->setChecked(!isFixed);
ui->scopeCenterModeChk->blockSignals(false);
}
void wfmain::handlePlotDoubleClick(QMouseEvent *me)
{
double x;
@ -1116,12 +1532,15 @@ void wfmain::handleWFScroll(QWheelEvent *we)
// .y() and is +/- 120.
// We will click the dial once for every 120 received.
//QPoint delta = we->angleDelta();
// TODO: Use other method, knob has too few positions to be useful for large steps.
int steps = we->angleDelta().y() / 120;
Qt::KeyboardModifiers key= we->modifiers();
if (key == Qt::ShiftModifier)
{
// TODO: Zoom
steps *=20;
} else if (key == Qt::ControlModifier)
{
steps *=10;
@ -1146,6 +1565,16 @@ void wfmain::handlePlotScroll(QWheelEvent *we)
ui->freqDial->setValue( ui->freqDial->value() + (steps)*ui->freqDial->singleStep() );
}
void wfmain::on_scopeEnableWFBtn_clicked(bool checked)
{
if(checked)
{
emit spectOutputEnable();
} else {
emit spectOutputDisable();
}
}
void wfmain::on_startBtn_clicked()
{
emit spectOutputEnable();
@ -1208,7 +1637,11 @@ void wfmain::receiveDataModeStatus(bool dataEnabled)
void wfmain::on_clearPeakBtn_clicked()
{
spectrumPeaks = QByteArray( (int)spectWidth, '\x01' );
if(haveRigCaps)
{
spectrumPeaks = QByteArray( (int)spectWidth, '\x01' );
}
return;
}
void wfmain::on_drawPeakChk_clicked(bool checked)
@ -1832,11 +2265,120 @@ void wfmain::on_pttEnableChk_clicked(bool checked)
prefs.enablePTT = checked;
}
void wfmain::on_lanEnableChk_clicked(bool checked)
{
prefs.enableLAN = checked;
ui->ipAddressTxt->setEnabled(checked);
ui->controlPortTxt->setEnabled(checked);
ui->serialPortTxt->setEnabled(checked);
ui->audioPortTxt->setEnabled(checked);
ui->usernameTxt->setEnabled(checked);
ui->passwordTxt->setEnabled(checked);
if(checked)
{
showStatusBarText("After filling in values, press Save Settings and re-start wfview.");
}
}
void wfmain::on_ipAddressTxt_textChanged(QString text)
{
prefs.ipAddress = text;
}
void wfmain::on_controlPortTxt_textChanged(QString text)
{
prefs.controlLANPort = text.toUInt();
}
void wfmain::on_serialPortTxt_textChanged(QString text)
{
prefs.serialLANPort = text.toUInt();
}
void wfmain::on_audioPortTxt_textChanged(QString text)
{
prefs.audioLANPort = text.toUInt();
}
void wfmain::on_usernameTxt_textChanged(QString text)
{
prefs.username = text;
}
void wfmain::on_passwordTxt_textChanged(QString text)
{
prefs.password = text;
}
void wfmain::on_audioOutputCombo_currentIndexChanged(QString text)
{
prefs.audioOutput = text;
}
void wfmain::on_audioInputCombo_currentIndexChanged(QString text)
{
prefs.audioInput = text;
}
void wfmain::on_audioSampleRateCombo_currentIndexChanged(QString text)
{
prefs.audioRXSampleRate = text.toInt();
prefs.audioTXSampleRate = text.toInt();
}
void wfmain::on_audioRXCodecCombo_currentIndexChanged(int value)
{
prefs.audioRXCodec = ui->audioRXCodecCombo->itemData(value).toInt();
}
void wfmain::on_audioTXCodecCombo_currentIndexChanged(int value)
{
prefs.audioTXCodec = ui->audioTXCodecCombo->itemData(value).toInt();
}
void wfmain::on_audioBufferSizeSlider_valueChanged(int value)
{
prefs.audioRXBufferSize = value;
ui->bufferValue->setText(QString::number(value));
emit sendChangeBufferSize(value);
}
void wfmain::on_toFixedBtn_clicked()
{
emit setScopeFixedEdge(oldLowerFreq, oldUpperFreq, ui->scopeEdgeCombo->currentIndex()+1);
emit setScopeEdge(ui->scopeEdgeCombo->currentIndex()+1);
cmdOutQue.append(cmdScopeFixedMode);
delayedCommand->start();
}
void wfmain::on_connectBtn_clicked()
{
this->rigStatus->setText(""); // Clear status
if (haveRigCaps) {
emit sendCloseComm();
ui->connectBtn->setText("Connect");
haveRigCaps = false;
}
else
{
emit sendCloseComm(); // Just in case there is a failed connection open.
openRig();
}
}
// --- DEBUG FUNCTION ---
void wfmain::on_debugBtn_clicked()
{
qDebug() << "Debug button pressed.";
// TODO: Why don't these commands work?!
//emit getScopeMode();
//emit getScopeEdge(); // 1,2,3 only in "fixed" mode
//emit getScopeSpan(); // in khz, only in "center" mode
//qDebug() << "Debug: finding rigs attached. Let's see if this works. ";
//rig->findRigs();
}

Wyświetl plik

@ -8,11 +8,14 @@
#include <QTimer>
#include <QSettings>
#include <QShortcut>
#include <QMetaType>
#include "logcategories.h"
#include "commhandler.h"
#include "rigcommander.h"
#include "freqmemory.h"
#include "rigidentities.h"
#include <qcustomplot.h>
#include <qserialportinfo.h>
@ -25,7 +28,9 @@ class wfmain : public QMainWindow
Q_OBJECT
public:
explicit wfmain(QWidget *parent = 0);
explicit wfmain(const QString serialPortCL, const QString hostCL, QWidget *parent = 0);
QString serialPortCL;
QString hostCL;
~wfmain();
signals:
@ -47,7 +52,8 @@ signals:
void startATU();
void setATU(bool atuEnabled);
void getATUStatus();
void getRigID();
void getRigID(); // this is the model of the rig
void getRigCIV(); // get the rig's CIV addr
void spectOutputEnable();
void spectOutputDisable();
void scopeDisplayEnable();
@ -55,13 +61,18 @@ signals:
void setScopeCenterMode(bool centerEnable);
void setScopeSpan(char span);
void setScopeEdge(char edge);
void setScopeFixedEdge(double startFreq, double endFreq, unsigned char edgeNumber);
void getScopeMode();
void getScopeEdge();
void getScopeSpan();
void sayFrequency();
void sayMode();
void sayAll();
void sendCommSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate);
void sendCommSetup(unsigned char rigCivAddr, QString ip, quint16 cport, quint16 sport, quint16 aport,
QString username, QString password, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec);
void sendCloseComm();
void sendChangeBufferSize(quint16 value);
private slots:
void shortcutF1();
@ -101,9 +112,11 @@ private slots:
void handlePttLimit(); // hit at 3 min transmit length
void on_startBtn_clicked();
void receiveCommReady();
void receiveFreq(double);
void receiveMode(QString);
void receiveSpectrumData(QByteArray spectrum, double startFreq, double endFreq);
void receiveSpectrumFixedMode(bool isFixed);
void receivePTTstatus(bool pttOn);
void receiveDataModeStatus(bool dataOn);
void receiveBandStackReg(float freq, char mode, bool dataOn); // freq, mode, (filter,) datamode
@ -111,7 +124,10 @@ private slots:
void receiveAfGain(unsigned char level);
void receiveSql(unsigned char level);
void receiveATUStatus(unsigned char atustatus);
void receiveRigID(rigCapabilities rigCaps);
void receiveFoundRigID(rigCapabilities rigCaps);
void receiveSerialPortError(QString port, QString errorText);
void receiveStatusUpdate(QString errorText);
void handlePlotClick(QMouseEvent *);
void handlePlotDoubleClick(QMouseEvent *);
void handleWFClick(QMouseEvent *);
@ -229,6 +245,38 @@ private slots:
void on_pttEnableChk_clicked(bool checked);
void on_lanEnableChk_clicked(bool checked);
void on_ipAddressTxt_textChanged(QString text);
void on_controlPortTxt_textChanged(QString text);
void on_serialPortTxt_textChanged(QString text);
void on_audioPortTxt_textChanged(QString text);
void on_usernameTxt_textChanged(QString text);
void on_passwordTxt_textChanged(QString text);
void on_audioOutputCombo_currentIndexChanged(QString text);
void on_audioInputCombo_currentIndexChanged(QString text);
void on_toFixedBtn_clicked();
void on_connectBtn_clicked();
void on_audioBufferSizeSlider_valueChanged(int value);
void on_audioRXCodecCombo_currentIndexChanged(int value);
void on_audioTXCodecCombo_currentIndexChanged(int value);
void on_audioSampleRateCombo_currentIndexChanged(QString text);
void on_scopeEnableWFBtn_clicked(bool checked);
private:
Ui::wfmain *ui;
QSettings settings;
@ -240,7 +288,9 @@ private:
//commHandler *comm;
void setAppTheme(bool isDark);
void setPlotTheme(QCustomPlot *plot, bool isDark);
void prepareWf();
void getInitialRigState();
void openRig();
QWidget * theParent;
QStringList portList;
QString serialPortRig;
@ -282,8 +332,8 @@ private:
QShortcut *keyM;
rigCommander * rig;
QThread * rigThread;
rigCommander * rig=Q_NULLPTR;
QThread * rigThread=Q_NULLPTR;
QCPColorMap * colorMap;
QCPColorMapData * colorMapData;
QCPColorScale * colorScale;
@ -295,6 +345,7 @@ private:
QStringList spans;
QStringList edges;
QStringList commPorts;
QLabel* rigStatus;
quint16 spectWidth;
quint16 wfLength;
@ -314,9 +365,9 @@ private:
double oldUpperFreq;
double freqMhz;
double knobFreqMhz;
enum cmds {cmdNone, cmdGetRigID, cmdGetFreq, cmdGetMode, cmdGetDataMode, cmdSetDataModeOn, cmdSetDataModeOff,
enum cmds {cmdNone, cmdGetRigID, cmdGetRigCIV, cmdGetFreq, cmdGetMode, cmdGetDataMode, cmdSetDataModeOn, cmdSetDataModeOff,
cmdSpecOn, cmdSpecOff, cmdDispEnable, cmdDispDisable, cmdGetRxGain, cmdGetAfGain,
cmdGetSql, cmdGetATUStatus};
cmdGetSql, cmdGetATUStatus, cmdScopeCenterMode, cmdScopeFixedMode};
cmds cmdOut;
QVector <cmds> cmdOutQue;
freqMemory mem;
@ -354,7 +405,20 @@ private:
quint32 serialPortBaud;
bool enablePTT;
bool niceTS;
bool enableLAN;
QString ipAddress;
quint16 controlLANPort;
quint16 serialLANPort;
quint16 audioLANPort;
QString username;
QString password;
QString audioOutput;
QString audioInput;
quint16 audioRXBufferSize;
quint16 audioRXSampleRate;
quint8 audioRXCodec;
quint16 audioTXSampleRate;
quint8 audioTXCodec;
} prefs;
preferences defPrefs;
@ -366,10 +430,16 @@ private:
int oldFreqDialVal;
rigCapabilities rigCaps;
bool haveRigCaps;
void bandStackBtnClick();
bool waitingForBandStackRtn;
char bandStkBand;
char bandStkRegCode;
};
Q_DECLARE_METATYPE(struct rigCapabilities) ;
#endif // WFMAIN_H

262
wfmain.ui
Wyświetl plik

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>703</width>
<width>810</width>
<height>582</height>
</rect>
</property>
@ -18,7 +18,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>3</number>
<number>0</number>
</property>
<widget class="QWidget" name="mainTab">
<attribute name="title">
@ -75,6 +75,16 @@
<item>
<widget class="QComboBox" name="scopeEdgeCombo"/>
</item>
<item>
<widget class="QPushButton" name="toFixedBtn">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Press button to convert center mode spectrum to fixed mode, preserving the range. This allows you to tune without the spectrum moving, in the same currently-visible range that you see now. &lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;The currently-selected edge slot will be overriden.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>ToFixed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearPeakBtn">
<property name="text">
@ -83,16 +93,12 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="startBtn">
<widget class="QCheckBox" name="scopeEnableWFBtn">
<property name="text">
<string>Start WF</string>
<string>Enable WF</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopBtn">
<property name="text">
<string>Stop WF</string>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@ -123,7 +129,7 @@
</property>
<property name="minimumSize">
<size>
<width>175</width>
<width>190</width>
<height>0</height>
</size>
</property>
@ -140,7 +146,7 @@
</font>
</property>
<property name="text">
<string>00.000000</string>
<string>0000.000000</string>
</property>
</widget>
</item>
@ -1150,16 +1156,16 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="aboutQtBtn">
<widget class="QPushButton" name="saveSettingsBtn">
<property name="text">
<string>About Qt</string>
<string>Save Settings</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveSettingsBtn">
<widget class="QPushButton" name="connectBtn">
<property name="text">
<string>Save Settings</string>
<string>Connect</string>
</property>
</widget>
</item>
@ -1219,6 +1225,226 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QCheckBox" name="lanEnableChk">
<property name="text">
<string>Enable LAN</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Radio IP Address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ipAddressTxt"/>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Radio Control Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="controlPortTxt">
<property name="placeholderText">
<string>50001</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Radio Serial Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="serialPortTxt">
<property name="placeholderText">
<string>50002</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Radio Audio Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="audioPortTxt">
<property name="placeholderText">
<string>50003</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="usernameTxt"/>
</item>
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordTxt">
<property name="inputMethodHints">
<set>Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>RX Audio Buffer Size</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="audioBufferSizeSlider">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="bufferValue">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_19">
<property name="text">
<string>RX Codec</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioRXCodecCombo"/>
</item>
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>TX Codec</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioTXCodecCombo"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Sample Rate</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioSampleRateCombo">
<item>
<property name="text">
<string>48000</string>
</property>
</item>
<item>
<property name="text">
<string>24000</string>
</property>
</item>
<item>
<property name="text">
<string>16000</string>
</property>
</item>
<item>
<property name="text">
<string>8000</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Audio Output </string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioOutputCombo"/>
</item>
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>Audio Input</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioInputCombo"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
@ -1276,7 +1502,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>703</width>
<width>810</width>
<height>22</height>
</rect>
</property>
@ -1287,8 +1513,6 @@
<customwidgets>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<!-- <header>qcustomplot.h</header> -->
<container>1</container>
</customwidget>
</customwidgets>

Wyświetl plik

@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
]
}

Wyświetl plik

@ -4,7 +4,7 @@
#
#-------------------------------------------------
QT += core gui serialport
QT += core gui serialport network multimedia
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
@ -31,9 +31,14 @@ QMAKE_LFLAGS += -O2 -march=native -s
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += QCUSTOMPLOT_COMPILE_LIBRARY
DEFINES += HOST=\\\"`hostname`\\\" UNAME=\\\"`whoami`\\\"
linux:DEFINES += HOST=\\\"`hostname`\\\" UNAME=\\\"`whoami`\\\"
DEFINES += GITSHORT="\\\"$(shell git -C $$PWD rev-parse --short HEAD)\\\""
linux:DEFINES += GITSHORT="\\\"$(shell git -C $$PWD rev-parse --short HEAD)\\\""
win32:INCLUDEPATH += c:/qcustomplot
win32:DEFINES += HOST=1
win32:DEFINES += UNAME=1
win32:DEFINES += GITSHORT=1
RESOURCES += qdarkstyle/style.qrc \
@ -44,18 +49,17 @@ DISTFILES += resources/wfview.png \
resources/install.sh
DISTFILES += resources/wfview.desktop
QMAKE_POST_LINK += cp ../wfview/resources/wfview.png .;
QMAKE_POST_LINK += cp ../wfview/resources/wfview.desktop .;
QMAKE_POST_LINK += cp ../wfview/resources/install.sh .;
QMAKE_POST_LINK += cp -r ../wfview/qdarkstyle .;
QMAKE_POST_LINK += chmod 755 install.sh;
QMAKE_POST_LINK += echo; echo; echo "Run install.sh as root from the build directory to install."; echo; echo;
linux:QMAKE_POST_LINK += cp ../wfview/resources/wfview.png .;
linux:QMAKE_POST_LINK += cp ../wfview/resources/wfview.desktop .;
linux:QMAKE_POST_LINK += cp ../wfview/resources/install.sh .;
linux:QMAKE_POST_LINK += cp -r ../wfview/qdarkstyle .;
linux:QMAKE_POST_LINK += chmod 755 install.sh;
linux:QMAKE_POST_LINK += echo; echo; echo "Run install.sh as root from the build directory to install."; echo; echo;
# Do not do this, it will hang on start:
# CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT
CONFIG(debug, release|debug) {
win32:QCPLIB = qcustomplotd1
else: QCPLIB = qcustomplotd
@ -74,13 +78,19 @@ SOURCES += main.cpp\
commhandler.cpp \
rigcommander.cpp \
freqmemory.cpp \
rigidentities.cpp
rigidentities.cpp \
udphandler.cpp \
logcategories.cpp \
rxaudiohandler.cpp
HEADERS += wfmain.h \
commhandler.h \
rigcommander.h \
freqmemory.h \
rigidentities.h
rigidentities.h \
udphandler.h \
logcategories.h \
rxaudiohandler.h
FORMS += wfmain.ui