commit 4d86ab11cccdb44bd9300cc6cc0b0b1a3da047ea Author: Elliott Liggett Date: Tue Jun 19 12:58:52 2018 -0700 Initial commit. diff --git a/commhandler.cpp b/commhandler.cpp new file mode 100644 index 0000000..6a55ba8 --- /dev/null +++ b/commhandler.cpp @@ -0,0 +1,103 @@ +#include "commhandler.h" + +#include + +commHandler::commHandler() +{ + //constructor + // 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(); + + // The following should become arguments and/or functions + baudrate = 115200; + stopbits = 1; + portName = "/dev/ttyUSB0"; + + setupComm(); // basic parameters + openPort(); + + connect(port, SIGNAL(readyRead()), this, SLOT(receiveDataIn())); + +} + +commHandler::~commHandler() +{ + this->closePort(); +} + + +void commHandler::setupComm() +{ + port->setPortName(portName); + port->setBaudRate(baudrate); + port->setStopBits(QSerialPort::OneStop);// OneStop is other option +} + +void commHandler::receiveDataFromUserToRig(const QByteArray &data) +{ + sendDataOut(data); +} + +void commHandler::sendDataOut(const QByteArray &writeData) +{ + + mutex.lock(); + quint64 bytesWritten; + + // sned out data + //port.send() or whatever + bytesWritten = port->write(writeData); + + //qDebug() << "bytesWritten: " << bytesWritten << " length of byte array: " << writeData.length() << " size of byte array: " << writeData.size(); + + mutex.unlock(); +} + +void commHandler::receiveDataIn() +{ + // connected to comm port data signal + // inPortData.append(port->readAll()); + + inPortData = port->readAll(); + + // qDebug() << "Data: " << inPortData; + emit haveDataFromPort(inPortData); +} + +void commHandler::openPort() +{ + bool success; + // port->open(); + success = port->open(QIODevice::ReadWrite); + if(success) + { + isConnected = true; + qDebug() << "Opened port!"; + return; + } else { + // debug? + qDebug() << "Could not open serial port."; + isConnected = false; + return; + } + + +} + +void commHandler::closePort() +{ + port->close(); + isConnected = false; +} + +void commHandler::debugThis() +{ + qDebug() << "comm debug called."; + + inPortData = port->readAll(); + emit haveDataFromPort(inPortData); +} + diff --git a/commhandler.h b/commhandler.h new file mode 100644 index 0000000..3125783 --- /dev/null +++ b/commhandler.h @@ -0,0 +1,59 @@ +#ifndef COMMHANDLER_H +#define COMMHANDLER_H + +#include + +#include +#include +#include + +// This class abstracts the comm port in a useful way and connects to +// the command creator and command parser. + +class commHandler : public QObject +{ + Q_OBJECT + +public: + commHandler(); + ~commHandler(); + +private slots: + void receiveDataIn(); + void receiveDataFromUserToRig(const QByteArray &data); + void debugThis(); + +signals: + void haveTextMessage(QString message); // status, debug + void sendDataOutToPort(const QByteArray &writeData); + void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander + +private: + void setupComm(); + void openPort(); + void closePort(); + + void sendDataOut(const QByteArray &writeData); + void debugMe(); + void hexPrint(); + + //QDataStream stream; + QByteArray outPortData; + QByteArray inPortData; + + //QDataStream outStream; + //QDataStream inStream; + + unsigned char buffer[256]; + + QString portName; + QSerialPort *port; + qint32 baudrate; + unsigned char stopbits; + + + bool isConnected; // port opened + mutable QMutex mutex; +}; + +#endif // COMMHANDLER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..df26f3a --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include "wfmain.h" +#include + + + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + wfmain w; + w.show(); + + return a.exec(); +} diff --git a/rigcommander.cpp b/rigcommander.cpp new file mode 100644 index 0000000..ff21848 --- /dev/null +++ b/rigcommander.cpp @@ -0,0 +1,625 @@ +#include "rigcommander.h" +#include + + +// +// See here for a wonderful CI-V overview: +// http://www.plicht.de/ekki/civ/civ-p0a.html +// +// The IC-7300 "full" manual also contains a command reference. + +// How to make spectrum display stop using rigctl: +// echo "w \0xFE\0xFE\0x094\0xE0\0x27\0x11\0x00\0xFD" | rigctl -m 373 -r /dev/ttyUSB0 -s 115200 -vvvvv + +// Note: When sending \x00, must use QByteArray.setRawData() + + +rigCommander::rigCommander() +{ + // construct + civAddr = 0x94; // address of the radio. Decimal is 148. + setCIVAddr(civAddr); + //payloadPrefix = QByteArray("\xFE\xFE\x94\xE0"); + payloadPrefix = QByteArray("\xFE\xFE"); + payloadPrefix.append(civAddr); + payloadPrefix.append("\xE0"); + + payloadSuffix = QByteArray("\xFD"); + comm = new commHandler(); + + // data from the comm port to the program: + connect(comm, SIGNAL(haveDataFromPort(QByteArray)), this, SLOT(handleNewData(QByteArray))); + + // data from the program to the comm port: + connect(this, SIGNAL(dataForComm(QByteArray)), comm, SLOT(receiveDataFromUserToRig(QByteArray))); + + connect(this, SIGNAL(getMoreDebug()), comm, SLOT(debugThis())); +} + +rigCommander::~rigCommander() +{ + delete comm; +} + +void rigCommander::process() +{ + // new thread enters here. Do nothing. +} + +void rigCommander::prepDataAndSend(QByteArray data) +{ + data.prepend(payloadPrefix); + //printHex(data, false, true); + data.append(payloadSuffix); + //qDebug() << "Final payload in rig commander to be sent to rig: "; + //printHex(data, false, true); + emit dataForComm(data); +} + +void rigCommander::enableSpectOutput() +{ + QByteArray payload("\x27\x11\x01"); + prepDataAndSend(payload); +} + +void rigCommander::disableSpectOutput() +{ + QByteArray payload; + payload.setRawData("\x27\x11\x00", 3); + prepDataAndSend(payload); +} + +void rigCommander::enableSpectrumDisplay() +{ + // 27 10 01 + QByteArray payload("\x27\x10\x01"); + prepDataAndSend(payload); +} + +void rigCommander::disableSpectrumDisplay() +{ + // 27 10 00 + QByteArray payload; + payload.setRawData("\x27\x10\x00", 3); + prepDataAndSend(payload); +} + +void rigCommander::setSpectrumBounds() +{ + +} + +void rigCommander::setScopeEdge(char edge) +{ + // 1 2 or 3 + // 27 16 00 0X + if((edge <1) || (edge >3)) + return; + QByteArray payload; + payload.setRawData("\x27\x16\x00", 3); + payload.append(edge); + prepDataAndSend(payload); +} + +void rigCommander::setScopeSpan(char span) +{ + // See ICD, page 165, "19-12". + // 2.5k = 0 + // 5k = 2, etc. + if((span <1 ) || (span >7)) + return; + + QByteArray payload; + double freq; // MHz + payload.setRawData("\x27\x15\x00", 3); + // next 6 bytes are the frequency + switch(span) + { + case 0: + // 2.5k + freq = 2.5E-3; + break; + case 1: + // 5k + freq = 5.0E-3; + break; + case 2: + freq = 10.0E-3; + break; + case 3: + freq = 25.0E-3; + break; + case 4: + freq = 50.0E-3; + break; + case 5: + freq = 100.0E-3; + break; + case 6: + freq = 250.0E-3; + break; + case 7: + freq = 500.0E-3; + break; + default: + return; + break; + } + + payload.append( makeFreqPayload(freq)); + payload.append("\x00"); + printHex(payload, false, true); + prepDataAndSend(payload); +} + +void rigCommander::setSpectrumCenteredMode(bool centerEnable) +{ + QByteArray specModePayload; + if(centerEnable) + { + specModePayload.setRawData("\x27\x14\x00\x00", 4); + } else { + specModePayload.setRawData("\x27\x14\x00\x01", 4); + } + prepDataAndSend(specModePayload); +} + +void rigCommander::setFrequency(double freq) +{ + QByteArray freqPayload = makeFreqPayload(freq); + QByteArray cmdPayload; + + cmdPayload.append(freqPayload); + cmdPayload.prepend('\x00'); + + printHex(cmdPayload, false, true); + prepDataAndSend(cmdPayload); +} + +QByteArray rigCommander::makeFreqPayload(double freq) +{ + quint64 freqInt = (quint64) (freq * 1E6); + + QByteArray result; + unsigned char a; + int numchars = 5; + for (int i = 0; i < numchars; i++) { + a = 0; + a |= (freqInt) % 10; + freqInt /= 10; + a |= ((freqInt) % 10)<<4; + + freqInt /= 10; + + result.append(a); + //printHex(result, false, true); + } + //qDebug() << "encoded frequency for " << freq << " as int " << freqInt; + //printHex(result, false, true); + return result; + +} + +void rigCommander::setMode(char mode) +{ + QByteArray payload; + if((mode >=0) && (mode < 10)) + { + // valid + payload.setRawData("\x06", 1); // cmd 06 will apply the default filter, no need to specify. + payload.append(mode); + prepDataAndSend(payload); + } +} + +void rigCommander::getFrequency() +{ + // figure out frequency and then respond with haveFrequency(); + // send request to radio + // 1. make the data + QByteArray payload("\x03"); + prepDataAndSend(payload); +} + +void rigCommander::getMode() +{ + QByteArray payload("\x04"); + prepDataAndSend(payload); +} + +void rigCommander::getDataMode() +{ + QByteArray payload("\x1A\x06"); + prepDataAndSend(payload); +} + +void rigCommander::setCIVAddr(unsigned char civAddr) +{ + this->civAddr = civAddr; +} + +void rigCommander::handleNewData(const QByteArray &data) +{ + parseData(data); +} + +void rigCommander::parseData(QByteArray data) +{ + + // Data echo'd back from the rig start with this: + // fe fe 94 e0 ...... fd + + // Data from the rig that is not an echo start with this: + // fe fe e0 94 ...... fd (for example, a reply to a query) + + // Data from the rig that was not asked for is sent to controller 0x00: + // fe fe 00 94 ...... fd (for example, user rotates the tune control or changes the mode) + + //qDebug() << "Data received: "; + //printHex(data, false, true); + if(data.length() < 4) + { + if(data.length()) + { + qDebug() << "Data length too short: " << data.length() << " bytes. Data:"; + printHex(data, false, true); + } + return; + } + + if(!data.startsWith("\xFE\xFE")) + { + qDebug() << "Warning: Invalid data received, did not start with FE FE."; + // find 94 e0 and shift over, + // or look inside for a second FE FE + // Often a local echo will miss a few bytes at the beginning. + if(data.startsWith('\xFE')) + { + data.prepend('\xFE'); + qDebug() << "Warning: Working with prepended data stream."; + parseData(payloadIn); + return; + } else { + qDebug() << "Error: Could not reconstruct corrupted data: "; + printHex(data, false, true); + // data.right(data.length() - data.find('\xFE\xFE')); + // if found do not return and keep going. + return; + } + } + + if((unsigned char)data[02] == civAddr) + { + // data is or begins with an echoback from what we sent + // find the first 'fd' and cut it. Then continue. + payloadIn = data.right(data.length() - data.indexOf('\xfd')-1); + //qDebug() << "Trimmed off echo:"; + //printHex(payloadIn, false, true); + parseData(payloadIn); + return; + } + + switch(data[02]) + { +// case civAddr: // can't have a variable here :-( +// // data is or begins with an echoback from what we sent +// // find the first 'fd' and cut it. Then continue. +// payloadIn = data.right(data.length() - data.indexOf('\xfd')-1); +// //qDebug() << "Trimmed off echo:"; +// //printHex(payloadIn, false, true); +// parseData(payloadIn); +// break; + case '\xE0': + // data is a reply to some query we sent + // extract the payload out and parse. + // payload = getpayload(data); // or something + // parse (payload); // recursive ok? + payloadIn = data.right(data.length() - 4); + parseCommand(); + break; + 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(); + break; + default: + // could be for other equipment on the CIV network. + // just drop for now. + break; + } +} + +void rigCommander::parseCommand() +{ + // note: data already is trimmed of the beginning FE FE E0 94 stuff. + + // printHex(data, false, true); + //payloadIn = data; + switch(payloadIn[00]) + { + + case 00: + // frequency data + parseFrequency(); + break; + case 03: + parseFrequency(); + break; + case '\x01': + //qDebug() << "Have mode data"; + this->parseMode(); + break; + case '\x04': + //qDebug() << "Have mode data"; + this->parseMode(); + break; + case '\x27': + // scope data + //qDebug() << "Have scope data"; + //printHex(payloadIn, false, true); + parseSpectrum(); + break; + case '\xFB': + // Fine Business, ACK from rig. + break; + case '\xFA': + // error + qDebug() << "Error (FA) received from rig."; + printHex(payloadIn, false ,true); + break; + default: + qDebug() << "Have other data with cmd: " << std::hex << payloadIn[00]; + printHex(payloadIn, false, true); + break; + } +} + +void rigCommander::parseSpectrum() +{ + // Here is what to expect: + // payloadIn[00] = '\x27'; + // payloadIn[01] = '\x00'; + // payloadIn[02] = '\x00'; + // + // Example long: (sequences 2-10, 50 pixels) + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 " + // "DATA: 27 00 00 07 11 27 13 15 01 00 22 21 09 08 06 19 0e 20 23 25 2c 2d 17 27 29 16 14 1b 1b 21 27 1a 18 17 1e 21 1b 24 21 22 23 13 19 23 2f 2d 25 25 0a 0e 1e 20 1f 1a 0c fd " + // ^--^--(seq 7/11) + // ^-- start waveform data 0x00 to 0xA0, index 05 to 54 + // + + // Example medium: (sequence #11) + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 " + // "DATA: 27 00 00 11 11 0b 13 21 23 1a 1b 22 1e 1a 1d 13 21 1d 26 28 1f 19 1a 18 09 2c 2c 2c 1a 1b fd " + + // Example short: (sequence #1) includes center/fixed mode at [05]. No pixels. + // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 " + // "DATA: 27 00 00 01 11 01 00 00 00 14 00 00 00 35 14 00 00 fd " + // ^-- mode 00 (center) or 01 (fixed) + // ^--14.00 MHz lower edge + // ^-- 14.350 MHz upper edge + // ^-- possibly 00=in range 01 = out of range + + // Note, the index used here, -1, matches the ICD in the owner's manual. + // Owner's manual + 1 = our index. + + // divs: Mode: Waveinfo: Len: Comment: + // 2-10 var var 56 Minimum wave information w/waveform data + // 11 10 26 31 Minimum wave information w/waveform data + // 1 1 0 18 Only Wave Information without waveform data + + 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(); + + // Sequnce 2, index 05 is the start of data + // Sequence 11. index 05, is the last chunk + // Sequence 11, index 29, is the actual last pixel (it seems) + + // 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) + { + // wave information + spectrumLine.clear(); + // parseFrequency(endPosition); // overload does not emit! Return? Where? how... + spectrumStartFreq = parseFrequency(payloadIn, 9); + spectrumEndFreq = parseFrequency(payloadIn, 14); + if(scopeMode == 0) + { + // "center" mode, start is actuall center, end is bandwidth. + spectrumStartFreq -= spectrumEndFreq; + spectrumEndFreq = spectrumStartFreq + 2*(spectrumEndFreq); + } + } else if ((sequence > 1) && (sequence < 11)) + { + // 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) + { + // last spectrum, a little bit different (last 25 pixels). Total at end is 475 pixels. + 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(); + emit haveSpectrumData(spectrumLine, spectrumStartFreq, spectrumEndFreq); + } + + /* + if(spectrumLine.length() != 475) + { + qDebug() << "Unusual length spectrum: " << spectrumLine.length(); + printHex(spectrumLine, false, true); + } + */ +} + +unsigned char rigCommander::bcdHexToDecimal(unsigned char in) +{ + unsigned char out = 0; + out = in & 0x0f; + out += ((in & 0xf0) >> 4)*10; + return out; +} + +void rigCommander::parseFrequency() +{ + // process payloadIn, which is stripped. + // float frequencyMhz + // payloadIn[04] = ; // XX MHz + // payloadIn[03] = ; // XX0 KHz + // payloadIn[02] = ; // X.X KHz + // payloadIn[01] = ; // . XX KHz + + // printHex(payloadIn, false, true); + frequencyMhz = payloadIn[04] & 0x0f; + frequencyMhz += 10*((payloadIn[04] & 0xf0) >> 4); + + frequencyMhz += ((payloadIn[03] & 0xf0) >>4)/10.0 ; + frequencyMhz += (payloadIn[03] & 0x0f) / 100.0; + + frequencyMhz += ((payloadIn[02] & 0xf0) >> 4) / 1000.0; + frequencyMhz += (payloadIn[02] & 0x0f) / 10000.0; + + frequencyMhz += ((payloadIn[01] & 0xf0) >> 4) / 100000.0; + frequencyMhz += (payloadIn[01] & 0x0f) / 1000000.0; + + emit haveFrequency(frequencyMhz); +} + +float rigCommander::parseFrequency(QByteArray data, unsigned char lastPosition) +{ + // process payloadIn, which is stripped. + // float frequencyMhz + // payloadIn[04] = ; // XX MHz + // payloadIn[03] = ; // XX0 KHz + // payloadIn[02] = ; // X.X KHz + // payloadIn[01] = ; // . XX KHz + + //printHex(data, false, true); + + float freq = 0.0; + + freq = data[lastPosition] & 0x0f; + freq += 10*((data[lastPosition] & 0xf0) >> 4); + + freq += ((data[lastPosition-1] & 0xf0) >>4)/10.0 ; + freq += (data[lastPosition-1] & 0x0f) / 100.0; + + freq += ((data[lastPosition-2] & 0xf0) >> 4) / 1000.0; + freq += (data[lastPosition-2] & 0x0f) / 10000.0; + + freq += ((data[lastPosition-3] & 0xf0) >> 4) / 100000.0; + freq += (data[lastPosition-3] & 0x0f) / 1000000.0; + + return freq; +} + + +void rigCommander::parseMode() +{ + QString mode; + // LSB: + //"INDEX: 00 01 02 03 " + //"DATA: 01 00 02 fd " + + // USB: + //"INDEX: 00 01 02 03 " + //"DATA: 01 01 02 fd " + switch(payloadIn[01]) + { + case '\x00': + mode = "LSB"; + break; + case '\x01': + mode = "USB"; + break; + case '\x02': + mode = "AM"; + break; + case '\x03': + mode = "CW"; + break; + case '\x04': + mode = "RTTY"; + break; + case '\x05': + mode = "FM"; + break; + case '\x07': + mode = "CW-R"; + break; + case '\x08': + mode = "RTTY-R"; + break; + default: + qDebug() << "Mode: Unknown: " << payloadIn[01]; + mode = QString("Unk: %1").arg(payloadIn[01]); + } + + emit haveMode(mode); +} + + +QByteArray rigCommander::stripData(const QByteArray &data, unsigned char cutPosition) +{ + QByteArray rtndata; + if(data.length() < cutPosition) + { + return rtndata; + } + + rtndata = data.right(cutPosition); + return rtndata; +} + +void rigCommander::getDebug() +{ + // generic debug function for development. + emit getMoreDebug(); +} + +void rigCommander::printHex(const QByteArray &pdata, bool printVert, bool printHoriz) +{ + qDebug() << "---- Begin hex dump -----:"; + QString sdata("DATA: "); + QString index("INDEX: "); + QStringList strings; + + for(int i=0; i < pdata.length(); i++) + { + strings << QString("[%1]: %2").arg(i,8,10,QChar('0')).arg((unsigned char)pdata[i], 2, 16, QChar('0')); + sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0')) ); + index.append(QString("%1 ").arg(i, 2, 10, QChar('0'))); + } + + if(printVert) + { + for(int i=0; i < strings.length(); i++) + { + //sdata = QString(strings.at(i)); + qDebug() << strings.at(i); + } + } + + if(printHoriz) + { + qDebug() << index; + qDebug() << sdata; + } + qDebug() << "----- End hex dump -----"; +} + + + + + + + + diff --git a/rigcommander.h b/rigcommander.h new file mode 100644 index 0000000..fe2631a --- /dev/null +++ b/rigcommander.h @@ -0,0 +1,86 @@ +#ifndef RIGCOMMANDER_H +#define RIGCOMMANDER_H + +#include + +#include "commhandler.h" + +// This file figures out what to send to the comm and also +// parses returns into useful things. + +class rigCommander : public QObject +{ + Q_OBJECT + +public: + rigCommander(); + ~rigCommander(); + +public slots: + void process(); + + void enableSpectOutput(); + void disableSpectOutput(); + void enableSpectrumDisplay(); + void disableSpectrumDisplay(); + void setSpectrumBounds(); + void setSpectrumCenteredMode(bool centerEnable); // centered or band-wise + void setScopeSpan(char span); + void setScopeEdge(char edge); + void setFrequency(double freq); + void setMode(char mode); + void getFrequency(); + void getMode(); + void getDataMode(); + void setCIVAddr(unsigned char civAddr); + void handleNewData(const QByteArray &data); + void getDebug(); + +signals: + void haveSpectrumData(QByteArray spectrum, double startFreq, double endFreq); // pass along data to UI + void haveFrequency(double frequencyMhz); + void haveMode(QString mode); + void haveSpectrumBounds(); + void dataForComm(const QByteArray &outData); + void getMoreDebug(); + void finished(); + + +private: + QByteArray stripData(const QByteArray &data, unsigned char cutPosition); + void parseData(QByteArray data); // new data come here + void parseCommand(); + unsigned char bcdHexToDecimal(unsigned char in); + void parseFrequency(); + float parseFrequency(QByteArray data, unsigned char lastPosition); // supply index where Mhz is found + QByteArray makeFreqPayload(double frequency); + void parseMode(); + void parseSpectrum(); + void sendDataOut(); + void prepDataAndSend(QByteArray data); + void debugMe(); + void printHex(const QByteArray &pdata, bool printVert, bool printHoriz); + commHandler * comm; + QByteArray payloadIn; + QByteArray echoPerfix; + QByteArray replyPrefix; + QByteArray genericReplyPrefix; + + QByteArray payloadPrefix; + QByteArray payloadSuffix; + + QByteArray rigData; + + QByteArray spectrumLine; + double spectrumStartFreq; + double spectrumEndFreq; + + + double frequencyMhz; + unsigned char civAddr; // 0x94 is default = 148decimal + + + +}; + +#endif // RIGCOMMANDER_H diff --git a/wfmain.cpp b/wfmain.cpp new file mode 100644 index 0000000..fe8787f --- /dev/null +++ b/wfmain.cpp @@ -0,0 +1,487 @@ +#include "wfmain.h" +#include "ui_wfmain.h" + +#include "commhandler.h" + +wfmain::wfmain(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::wfmain) +{ + ui->setupUi(this); + plot = ui->plot; // rename it waterfall. + wf = ui->waterfall; + + spectWidth = 475; // fixed for now + wfLength = 160; // fixed for now + + // Initialize before use! + + QByteArray empty((int)spectWidth, '\x01'); + spectrumPeaks = QByteArray( (int)spectWidth, '\x01' ); + for(quint16 i=0; imodeSelectCombo->insertItems(0, modes); + + spans << "2.5k" << "5.0k" << "10k" << "25k"; + spans << "50k" << "100k" << "250k" << "500k"; + ui->scopeBWCombo->insertItems(0, spans); + + edges << "1" << "2" << "3"; // yep + ui->scopeEdgeCombo->insertItems(0,edges); + + // comm = new commHandler(); + rig = new rigCommander(); + rigThread = new QThread(this); + + rig->moveToThread(rigThread); + connect(rigThread, SIGNAL(started()), rig, SLOT(process())); + connect(rig, SIGNAL(finished()), rigThread, SLOT(quit())); + rigThread->start(); + + + connect(rig, SIGNAL(haveFrequency(double)), this, SLOT(receiveFreq(double))); + connect(this, SIGNAL(getFrequency()), rig, SLOT(getFrequency())); + connect(this, SIGNAL(getMode()), rig, SLOT(getMode())); + connect(this, SIGNAL(getDebug()), rig, SLOT(getDebug())); + connect(this, SIGNAL(spectOutputDisable()), rig, SLOT(disableSpectOutput())); + connect(this, SIGNAL(spectOutputEnable()), rig, SLOT(enableSpectOutput())); + connect(this, SIGNAL(scopeDisplayDisable()), rig, SLOT(disableSpectrumDisplay())); + connect(this, SIGNAL(scopeDisplayEnable()), rig, SLOT(enableSpectrumDisplay())); + connect(rig, SIGNAL(haveMode(QString)), this, SLOT(receiveMode(QString))); + connect(rig, SIGNAL(haveSpectrumData(QByteArray, double, double)), this, SLOT(receiveSpectrumData(QByteArray, double, double))); + 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))); + connect(this, SIGNAL(setScopeSpan(char)), rig, SLOT(setScopeSpan(char))); + connect(this, SIGNAL(setMode(char)), rig, SLOT(setMode(char))); + + + // Plot user interaction + connect(plot, SIGNAL(mouseDoubleClick(QMouseEvent*)), this, SLOT(handlePlotDoubleClick(QMouseEvent*))); + connect(wf, SIGNAL(mouseDoubleClick(QMouseEvent*)), this, SLOT(handleWFDoubleClick(QMouseEvent*))); + + + ui->plot->addGraph(); // primary + ui->plot->addGraph(0, 0); // secondary, peaks, same axis as first? + ui->waterfall->addGraph(); + + + + colorMap = new QCPColorMap(wf->xAxis, wf->yAxis); + colorMapData = NULL; + wf->addPlottable(colorMap); + 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); + colorMapData = new QCPColorMapData(spectWidth, wfLength, QCPRange(0, spectWidth-1), QCPRange(0, wfLength-1)); + colorMap->setData(colorMapData); + spectRowCurrent = 0; + wf->yAxis->setRangeReversed(true); + + ui->tabWidget->setCurrentIndex(0); + + QColor color(20+200/4.0*1,70*(1.6-1/4.0), 150, 150); + plot->graph(1)->setLineStyle(QCPGraph::lsLine); + plot->graph(1)->setPen(QPen(color.lighter(200))); + plot->graph(1)->setBrush(QBrush(color)); + + drawPeaks = false; + ui->drawPeakChk->setChecked(false); + + ui->freqMhzLineEdit->setValidator( new QDoubleValidator(0, 100, 6, this)); + + delayedCommand = new QTimer(this); + delayedCommand->setInterval(250); + delayedCommand->setSingleShot(true); + connect(delayedCommand, SIGNAL(timeout()), this, SLOT(runDelayedCommand())); + +} + +wfmain::~wfmain() +{ + // rigThread->quit(); + delete ui; +} + +void wfmain::runDelayedCommand() +{ + // switch case on enum + switch (cmdOut) + { + case cmdGetFreq: + emit getFrequency(); + break; + case cmdGetMode: + emit getMode(); + break; + default: + break; + } +} + +void wfmain::receiveFreq(double freqMhz) +{ + //qDebug() << "Frequency: " << freqMhz; + ui->freqLabel->setText(QString("%1").arg(freqMhz, 0, 'f')); +} + +void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double endFreq) +{ + if((startFreq != oldLowerFreq) || (endFreq != oldUpperFreq)) + { + if(drawPeaks) + on_clearPeakBtn_clicked(); + } + + oldLowerFreq = startFreq; + oldUpperFreq = endFreq; + + //qDebug() << "start: " << startFreq << " end: " << endFreq; + quint16 specLen = spectrum.length(); + //qDebug() << "Spectrum data received at UI! Length: " << specLen; + if(specLen != 475) + { + //qDebug () << "Unusual spectrum: length: " << specLen; + if(specLen > 475) + { + specLen = 475; + } else { + // as-is + } + return; // safe. Using these unusual length things is a problem. + } + + QVector x(spectWidth), y(spectWidth), y2(spectWidth); + + for(int i=0; i < spectWidth; i++) + { + x[i] = (i * (endFreq-startFreq)/spectWidth) + startFreq; + + } + + for(int i=0; i spectrumPeaks.at(i)) + { + spectrumPeaks[i] = spectrum.at(i); + } + y2[i] = spectrumPeaks.at(i); + } + + } + + //ui->qcp->addGraph(); + plot->graph(0)->setData(x,y); + if(drawPeaks) + { + plot->graph(1)->setData(x,y2); // peaks + } + plot->yAxis->setRange(0, 160); + plot->xAxis->setRange(startFreq, endFreq); + plot->replot(); + + if(specLen == spectWidth) + { + wfimage.prepend(spectrum); + if(wfimage.length() > wfLength) + { + wfimage.remove(wfLength); + } + + // Waterfall: + for(int row = 0; row < wfLength; row++) + { + for(int col = 0; col < spectWidth; col++) + { + //colorMap->data()->cellToCoord(xIndex, yIndex, &x, &y) + // Very fast but doesn't roll downward: + //colorMap->data()->setCell( col, spectRowCurrent, spectrum.at(col) ); + // Slow but rolls: + colorMap->data()->setCell( col, row, wfimage.at(row).at(col)); + } + } + + //colorMap->data()->setRange(QCPRange(startFreq, endFreq), QCPRange(0,wfLength-1)); + wf->yAxis->setRange(0,wfLength - 1); + wf->xAxis->setRange(0, spectWidth-1); + wf->replot(); + spectRowCurrent = (spectRowCurrent + 1) % wfLength; + //qDebug() << "updating spectrum, new row is: " << spectRowCurrent; + + } +} + +void wfmain::handlePlotDoubleClick(QMouseEvent *me) +{ + double x; + double y; + //double px; + x = plot->xAxis->pixelToCoord(me->pos().x()); + y = plot->yAxis->pixelToCoord(me->pos().y()); + emit setFrequency(x); + cmdOut = cmdGetFreq; + delayedCommand->start(); + + qDebug() << "PLOT double click: " << x << ", " << y; +} + +void wfmain::handleWFDoubleClick(QMouseEvent *me) +{ + double x; + //double y; + //x = wf->xAxis->pixelToCoord(me->pos().x()); + //y = wf->yAxis->pixelToCoord(me->pos().y()); + // cheap trick until I figure out how the axis works on the WF: + x = plot->xAxis->pixelToCoord(me->pos().x()); + emit setFrequency(x); + cmdOut = cmdGetFreq; + delayedCommand->start(); + + //qDebug() << "WF double click: " << x << ", " << y; + +} + +void wfmain::handlePlotClick(QMouseEvent *me) +{ + +} + +void wfmain::handleWFClick(QMouseEvent *me) +{ + +} + + +void wfmain::on_startBtn_clicked() +{ + //emit scopeDisplayEnable(); // TODO: need a little delay between these two + emit spectOutputEnable(); +} + +void wfmain::on_getFreqBtn_clicked() +{ + emit getFrequency(); +} + +void wfmain::on_getModeBtn_clicked() +{ + emit getMode(); +} + +void wfmain::on_debugBtn_clicked() +{ + emit getDebug(); +} + +void wfmain::on_stopBtn_clicked() +{ + emit spectOutputDisable(); + //emit scopeDisplayDisable(); +} + +void wfmain::receiveMode(QString mode) +{ + ui->modeLabel->setText(mode); + int index; + //bool ok; + index = modes.indexOf(QRegExp(mode)); + if( currentModeIndex == index) + { + // do nothing, no need to change the selected mode and fire more events off. + return; + } + if((index >= 0) && (index < 9)) + { + ui->modeSelectCombo->setCurrentIndex(index); + currentModeIndex = index; + } + // Note: we need to know if the DATA mode is active to reach mode-D + // some kind of queued query: + cmdOut = cmdGetDataMode; + //delayedCommand->start(); +} + +void wfmain::on_clearPeakBtn_clicked() +{ + spectrumPeaks = QByteArray( (int)spectWidth, '\x01' ); +} + +void wfmain::on_drawPeakChk_clicked(bool checked) +{ + if(checked) + { + on_clearPeakBtn_clicked(); // clear + drawPeaks = true; + + } else { + drawPeaks = false; + plot->graph(1)->clearData(); + + } +} + + +void wfmain::on_fullScreenChk_clicked(bool checked) +{ + if(checked) + this->showFullScreen(); + else + this->showNormal(); + + +} + +void wfmain::on_goFreqBtn_clicked() +{ + bool ok = false; + double freq = ui->freqMhzLineEdit->text().toDouble(&ok); + if(ok) + { + emit setFrequency(freq); + cmdOut = cmdGetFreq; + delayedCommand->start(); + } + ui->freqMhzLineEdit->selectAll(); + freqTextSelected = true; + +} + +void wfmain::checkFreqSel() +{ + if(freqTextSelected) + { + freqTextSelected = false; + ui->freqMhzLineEdit->clear(); + } +} + +void wfmain::on_f0btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("0")); +} +void wfmain::on_f1btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("1")); +} + +void wfmain::on_f2btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("2")); + +} +void wfmain::on_f3btn_clicked() +{ + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("3")); + +} +void wfmain::on_f4btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("4")); + +} +void wfmain::on_f5btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("5")); + +} +void wfmain::on_f6btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("6")); + +} +void wfmain::on_f7btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("7")); + +} +void wfmain::on_f8btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("8")); + +} +void wfmain::on_f9btn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append("9")); + +} +void wfmain::on_fDotbtn_clicked() +{ + checkFreqSel(); + ui->freqMhzLineEdit->setText(ui->freqMhzLineEdit->text().append(".")); + +} + + +void wfmain::on_fBackbtn_clicked() +{ + QString currentFreq = ui->freqMhzLineEdit->text(); + currentFreq.chop(1); + ui->freqMhzLineEdit->setText(currentFreq); +} + +void wfmain::on_fCEbtn_clicked() +{ + ui->freqMhzLineEdit->clear(); + freqTextSelected = false; +} + + + +void wfmain::on_scopeCenterModeChk_clicked(bool checked) +{ + emit setScopeCenterMode(checked); +} + +void wfmain::on_fEnterBtn_clicked() +{ + on_goFreqBtn_clicked(); +} + +void wfmain::on_scopeBWCombo_currentIndexChanged(int index) +{ + emit setScopeSpan((char)index); +} + +void wfmain::on_scopeEdgeCombo_currentIndexChanged(int index) +{ + emit setScopeEdge((char)index+1); +} + +void wfmain::on_modeSelectCombo_currentIndexChanged(int index) +{ + if(index < 10) + { + qDebug() << "Mode selection changed. index: " << index; + emit setMode(index); + + if(index > 7) + { + // set data mode on + } else { + // set data mode off + } + } +} diff --git a/wfmain.h b/wfmain.h new file mode 100644 index 0000000..d80a30f --- /dev/null +++ b/wfmain.h @@ -0,0 +1,135 @@ +#ifndef WFMAIN_H +#define WFMAIN_H + +#include +#include +#include +#include +#include + + +#include "commhandler.h" +#include "rigcommander.h" +#include + +namespace Ui { +class wfmain; +} + +class wfmain : public QMainWindow +{ + Q_OBJECT + +public: + explicit wfmain(QWidget *parent = 0); + ~wfmain(); + +signals: + void getFrequency(); + void setFrequency(double freq); + void getMode(); + void setMode(char modeIndex); + void getDebug(); + void spectOutputEnable(); + void spectOutputDisable(); + void scopeDisplayEnable(); + void scopeDisplayDisable(); + void setScopeCenterMode(bool centerEnable); + void setScopeSpan(char span); + void setScopeEdge(char edge); + +private slots: + void on_startBtn_clicked(); + void receiveFreq(double); + void receiveMode(QString); + void receiveSpectrumData(QByteArray spectrum, double startFreq, double endFreq); + void handlePlotClick(QMouseEvent *); + void handlePlotDoubleClick(QMouseEvent *); + void handleWFClick(QMouseEvent *); + void handleWFDoubleClick(QMouseEvent *); + void runDelayedCommand(); + + void on_getFreqBtn_clicked(); + + void on_getModeBtn_clicked(); + + void on_debugBtn_clicked(); + + void on_stopBtn_clicked(); + + void on_clearPeakBtn_clicked(); + + void on_drawPeakChk_clicked(bool checked); + + void on_fullScreenChk_clicked(bool checked); + + void on_goFreqBtn_clicked(); + + void on_f0btn_clicked(); + void on_f1btn_clicked(); + void on_f2btn_clicked(); + void on_f3btn_clicked(); + void on_f4btn_clicked(); + void on_f5btn_clicked(); + void on_f6btn_clicked(); + void on_f7btn_clicked(); + void on_f8btn_clicked(); + void on_f9btn_clicked(); + void on_fDotbtn_clicked(); + + + + void on_fBackbtn_clicked(); + + void on_fCEbtn_clicked(); + + + void on_scopeCenterModeChk_clicked(bool checked); + + void on_fEnterBtn_clicked(); + + void on_scopeBWCombo_currentIndexChanged(int index); + + void on_scopeEdgeCombo_currentIndexChanged(int index); + + void on_modeSelectCombo_currentIndexChanged(int index); + +private: + Ui::wfmain *ui; + QCustomPlot *plot; // line plot + QCustomPlot *wf; // waterfall image + //commHandler *comm; + rigCommander * rig; + QThread * rigThread; + QCPColorMap * colorMap; + QCPColorMapData * colorMapData; + QCPColorScale * colorScale; + QTimer * delayedCommand; + + QStringList modes; + int currentModeIndex; + QStringList spans; + QStringList edges; + QStringList commPorts; + + quint16 spectWidth; + quint16 wfLength; + + quint16 spectRowCurrent; + + QByteArray spectrumPeaks; + + QVector wfimage; + + bool drawPeaks; + bool freqTextSelected; + void checkFreqSel(); + + double oldLowerFreq; + double oldUpperFreq; + enum cmds {cmdGetFreq, cmdGetMode, cmdGetDataMode}; + cmds cmdOut; + +}; + +#endif // WFMAIN_H diff --git a/wfmain.ui b/wfmain.ui new file mode 100644 index 0000000..dd4adf2 --- /dev/null +++ b/wfmain.ui @@ -0,0 +1,1019 @@ + + + wfmain + + + + 0 + 0 + 583 + 368 + + + + wfmain + + + + + + + 2 + + + + View + + + + + + Spectrum + + + + + + Qt::Vertical + + + + + + + + + + + + 0 + + + + + Center Mode + + + + + + + Span: + + + + + + + + + + Edge + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + 145 + 30 + + + + + DejaVu Sans + 20 + + + + 07.225000 + + + + + + + + 16777215 + 30 + + + + + DejaVu Sans + 20 + + + + MHz + + + + + + + + 16777215 + 30 + + + + Mode: + + + + + + + + 16777215 + 30 + + + + unknown + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Start WF + + + + + + + Stop WF + + + + + + + Clear Peaks + + + + + + + Get Frequency + + + + + + + GetMode + + + + + + + Debug + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Band + + + + + + Band + + + + + + + + + 0 + 0 + + + + 6M + + + + + + + + 0 + 0 + + + + 10M + + + + + + + + 0 + 0 + + + + 12M + + + + + + + + + + + + 0 + 0 + + + + 15M + + + + + + + + 0 + 0 + + + + 17M + + + + + + + + 0 + 0 + + + + 20M + + + + + + + + + + + + 0 + 0 + + + + 30M + + + + + + + + 0 + 0 + + + + 40M + + + + + + + + 0 + 0 + + + + 60M + + + + + + + + + + + + 0 + 0 + + + + 80M + + + + + + + + 0 + 0 + + + + 160M + + + + + + + + 0 + 0 + + + + Broadcast + + + + + + + + + + + + + 0 + 0 + + + + Segment + + + + + + &Last Used + + + + 16 + 16 + + + + true + + + + + + + Voice + + + + + + + Data + + + + + + + &CW + + + + + + + + + + + Frequency + + + + + + + + Frequency: + + + + + + + + DejaVu Sans Mono + 14 + 75 + true + + + + + + + + Go + + + Return + + + true + + + + + + + + + Entry + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 5 + + + 5 + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + &RCL + + + R + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 6 + + + 6 + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 3 + + + 3 + + + false + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + &CE + + + C + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 4 + + + 4 + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + &STO + + + S + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 9 + + + 9 + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + Back + + + Backspace + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + Enter + + + Enter + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + . + + + . + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 1 + + + 1 + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 2 + + + 2 + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 7 + + + 7 + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + 8 + + + 8 + + + + + + + + + + + Settings + + + + + + + + Draw Peaks + + + + + + + Show full screen + + + + + + + Use Dark Theme + + + + + + + + + + + CIV Address (decimal) + + + + + + + 255 + + + 148 + + + + + + + + + + + Port Baudrate + + + + + + + + + + + + + + Comm Port + + + + + + + + + + Initialize Comm + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + 0 + 0 + 583 + 20 + + + + + + + + + QCustomPlot + QWidget +
qcustomplot.h
+ 1 +
+
+ + +
diff --git a/wfview.pro b/wfview.pro new file mode 100644 index 0000000..8251ccf --- /dev/null +++ b/wfview.pro @@ -0,0 +1,46 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-26T16:57:32 +# +#------------------------------------------------- + +QT += core gui serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport + +TARGET = wfview +TEMPLATE = app + +QMAKE_CXXFLAGS += -march=native + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QCUSTOMPLOT_COMPILE_LIBRARY + +#CONFIG(debug, release|debug) { +# win32:QCPLIB = qcustomplotd1 +# else: QCPLIB = qcustomplotd +#} else { +# win32:QCPLIB = qcustomplot1 +# else: QCPLIB = qcustomplot +#} + +QCPLIB = qcustomplot + +LIBS += -L./ -l$$QCPLIB + + +SOURCES += main.cpp\ + wfmain.cpp \ + commhandler.cpp \ + rigcommander.cpp + +HEADERS += wfmain.h \ + ../../../../../usr/include/qcustomplot.h \ + commhandler.h \ + rigcommander.h + +FORMS += wfmain.ui