From 1a03619a2f31f1d50fa8d720db4e55adb1306767 Mon Sep 17 00:00:00 2001 From: Jordan Sherer Date: Tue, 19 Nov 2019 10:39:27 -0500 Subject: [PATCH] Move decoder process handling to separate thread to avoid exhaustuion --- commons.h | 1 + lib/decoder.f90 | 2 +- mainwindow.cpp | 717 +++++++++++++++++++++++++----------------------- mainwindow.h | 2 + 4 files changed, 376 insertions(+), 346 deletions(-) diff --git a/commons.h b/commons.h index 75ec770..d8a8d01 100644 --- a/commons.h +++ b/commons.h @@ -10,6 +10,7 @@ #define JS8_USE_IHSYM 0 // compute ihsym manually instead of from symspec #define JS8_RING_BUFFER 1 // use a ring buffer instead of clearing the decode frames #define JS8_SINGLE_DECODE 0 // single submode decode per instantiation of the decoder +#define JS8_DECODE_THREAD 1 // use a separate thread for decode process handling #ifdef QT_DEBUG #define JS8_DEBUG_DECODE 1 // emit debug statements for the decode pipeline diff --git a/lib/decoder.f90 b/lib/decoder.f90 index 9bba25d..7555ed3 100644 --- a/lib/decoder.f90 +++ b/lib/decoder.f90 @@ -170,7 +170,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample) call flush(6) ndecoded = my_js8a%decoded + my_js8b%decoded + my_js8c%decoded + my_js8e%decoded - call sleep_msec(3000) + !call sleep_msec(3000) write(*,1010) ndecoded 1010 format('',i4) call flush(6) diff --git a/mainwindow.cpp b/mainwindow.cpp index d7140ed..c8d370a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -536,6 +536,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, , &m_WSPR_band_hopping, &WSPRBandHopping::set_tx_percent); #endif + // decoder queue handler + connect(this, &MainWindow::decodedLineReady, this, &MainWindow::processDecodedLine); + on_EraseButton_clicked (); QActionGroup* modeGroup = new QActionGroup(this); @@ -1603,11 +1606,21 @@ void MainWindow::initDecoderSubprocess(){ if(JS8_DEBUG_DECODE) qDebug() << "decoder subprocess starting..."; - auto proc = new QProcess(this); +#if JS8_DECODE_THREAD + auto thread = new QThread(nullptr); +#endif + + auto proc = new QProcess(nullptr); connect(proc, &QProcess::readyReadStandardOutput, this, [this, proc](){ +#if JS8_DECODE_THREAD + while(proc->canReadLine()){ + emit decodedLineReady(proc->readLine()); + } +#else readFromStdout(proc); +#endif }); connect(proc, static_cast (&QProcess::error), @@ -1617,8 +1630,10 @@ void MainWindow::initDecoderSubprocess(){ connect(proc, static_cast (&QProcess::finished), [this, proc] (int exitCode, QProcess::ExitStatus status) { +#if JS8_DECODE_THREAD proc->deleteLater(); proc->thread()->quit(); +#endif subProcessFailed (proc, exitCode, status); }); @@ -1626,6 +1641,15 @@ void MainWindow::initDecoderSubprocess(){ proc->start(QDir::toNativeSeparators (m_appDir) + QDir::separator () + "js8", js8_args, QIODevice::ReadWrite | QIODevice::Unbuffered); +#if JS8_DECODE_THREAD + if(JS8_DEBUG_DECODE) qDebug() << "decoder subprocess moving to new thread..."; + // move process handling into its own thread + proc->moveToThread(thread); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->moveToThread(qApp->thread()); + thread->start(QThread::HighPriority); +#endif + // create a process watcher looking for stdout read... // seems like we're starving the event loop or something? // auto watcher = new QTimer(proc); @@ -4681,355 +4705,358 @@ void MainWindow::readFromStdout(QProcess * proc) //r } while(proc->canReadLine()) { - QByteArray t = proc->readLine(); - qDebug() << "JS8: " << QString(t); - - bool bAvgMsg=false; - int navg=0; - if(t.indexOf("") >= 0) { - if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); - return; - } - - if(t.indexOf("") >= 0) { - if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); - return; - } - - if(t.indexOf("") >= 0) { - if(m_mode=="QRA64") m_wideGraph->drawRed(0,0); - m_bDecoded = t.mid(16).trimmed().toInt() > 0; - int mswait=3*1000*m_TRperiod/4; - if(!m_diskData) killFileTimer.start(mswait); //Kill in 3/4 period - decodeDone(); - m_startAnother=m_loopall; - if(m_bNoMoreFiles) { - MessageBox::information_message(this, tr("No more files to open.")); - m_bNoMoreFiles=false; - } - return; - } - - if(m_mode=="JT4" or m_mode=="JT65" or m_mode=="QRA64" or m_mode=="FT8") { - int n=t.indexOf("f"); - if(n<0) n=t.indexOf("d"); - if(n>0) { - QString tt=t.mid(n+1,1); - navg=tt.toInt(); - if(navg==0) { - char c = tt.data()->toLatin1(); - if(int(c)>=65 and int(c)<=90) navg=int(c)-54; - } - if(navg>1 or t.indexOf("f*")>0) bAvgMsg=true; - } - } - - QFile f {m_config.writeable_data_dir ().absoluteFilePath ("ALL.TXT")}; - if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { - QTextStream out(&f); - if(m_RxLog==1) { - out << DriftingDateTime::currentDateTimeUtc().toString("yyyy-MM-dd hh:mm:ss") - << " " << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6) << " MHz " - << "JS8" << endl; - m_RxLog=0; - } - int n=t.length(); - auto logText = t.mid(0, n-2); - auto dt = DecodedText(logText, false, m_config.my_grid()); - out << logText << " " << dt.message() << endl; - f.close(); - } else { - MessageBox::warning_message (this, tr ("File Open Error") - , tr ("Cannot open \"%1\" for append: %2") - .arg (f.fileName ()).arg (f.errorString ())); - } - - DecodedText decodedtext {QString::fromUtf8 (t.constData ()).remove (QRegularExpression {"\r|\n"}), "FT8" == m_mode && - ui->cbVHFcontest->isChecked(), m_config.my_grid ()}; - - bool bValidFrame = decodedtext.snr() >= rxSnrThreshold(decodedtext.submode()); - - // dupe check - auto frame = decodedtext.message(); - auto frameOffset = decodedtext.frequencyOffset(); - if(m_messageDupeCache.contains(frame)){ - // check to see if the frequency is near our previous frame - auto cachedFreq = m_messageDupeCache.value(frame, 0); - if(qAbs(cachedFreq - frameOffset) <= rxThreshold(decodedtext.submode())){ - qDebug() << "duplicate frame from" << cachedFreq << "and" << frameOffset; - bValidFrame = false; - } - } else { - // cache for this decode cycle - m_messageDupeCache[frame] = frameOffset; - } - - qDebug() << "valid" << bValidFrame << submodeName(decodedtext.submode()) << "decoded text" << decodedtext.message(); - - // skip if invalid - if(!bValidFrame) { - continue; - } - - ActivityDetail d = {}; - CallDetail cd = {}; - CommandDetail cmd = {}; - CallDetail td = {}; - - // Parse General Activity -#if 1 - bool shouldParseGeneralActivity = true; - if(shouldParseGeneralActivity && !decodedtext.messageWords().isEmpty()){ - int offset = decodedtext.frequencyOffset(); - - if(!m_bandActivity.contains(offset)){ - int range = rxThreshold(decodedtext.submode()); - - QList offsets = generateOffsets(offset-range, offset+range); - - foreach(int prevOffset, offsets){ - if(!m_bandActivity.contains(prevOffset)){ continue; } - m_bandActivity[offset] = m_bandActivity[prevOffset]; - m_bandActivity.remove(prevOffset); - break; - } - } - - //ActivityDetail d = {}; - d.isLowConfidence = decodedtext.isLowConfidence(); - d.isFree = !decodedtext.isStandardMessage(); - d.isCompound = decodedtext.isCompound(); - d.isDirected = decodedtext.isDirectedMessage(); - d.bits = decodedtext.bits(); - d.freq = offset; - d.text = decodedtext.message(); - d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); - d.snr = decodedtext.snr(); - d.isBuffered = false; - d.tdrift = decodedtext.dt(); - d.submode = decodedtext.submode(); - - // if we have any "first" frame, and a buffer is already established, clear it... - int prevBufferOffset = -1; - if(((d.bits & Varicode::JS8CallFirst) == Varicode::JS8CallFirst) && hasExistingMessageBuffer(decodedtext.submode(), d.freq, true, &prevBufferOffset)){ - qDebug() << "first message encountered, clearing existing buffer" << prevBufferOffset; - m_messageBuffer.remove(d.freq); - } - - // if we have a data frame, and a message buffer has been established, buffer it... - if(hasExistingMessageBuffer(decodedtext.submode(), d.freq, true, &prevBufferOffset) && !decodedtext.isCompound() && !decodedtext.isDirectedMessage()){ - qDebug() << "buffering data" << d.freq << d.text; - d.isBuffered = true; - m_messageBuffer[d.freq].msgs.append(d); - // TODO: incremental display if it's "to" me. - } - - m_rxActivityQueue.append(d); - m_bandActivity[offset].append(d); - while(m_bandActivity[offset].count() > 10){ - m_bandActivity[offset].removeFirst(); - } - } -#endif - - // Process compound callsign commands (put them in cache)" -#if 1 - qDebug() << "decoded" << decodedtext.frameType() << decodedtext.isCompound() << decodedtext.isDirectedMessage() << decodedtext.isHeartbeat(); - bool shouldProcessCompound = true; - if(shouldProcessCompound && decodedtext.isCompound() && !decodedtext.isDirectedMessage()){ - cd.call = decodedtext.compoundCall(); - cd.grid = decodedtext.extra(); // compound calls via pings may contain grid... - cd.snr = decodedtext.snr(); - cd.freq = decodedtext.frequencyOffset(); - cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); - cd.bits = decodedtext.bits(); - cd.tdrift = decodedtext.dt(); - cd.submode = decodedtext.submode(); - - // Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings - if(decodedtext.isHeartbeat()){ - if(decodedtext.isAlt()){ - // this is a cq with a standard or compound call, ala "KN4CRD/P: CQCQCQ" - cd.cqTimestamp = DriftingDateTime::currentDateTimeUtc(); - - // it is not processed elsewhere, so we need to just log it here. - logCallActivity(cd, true); - - // notification for cq - tryNotify("cq"); - - } else { - // convert HEARTBEAT to a directed command and process... - cmd.from = cd.call; - cmd.to = "@ALLCALL"; - cmd.cmd = " HB"; - cmd.snr = cd.snr; - cmd.bits = cd.bits; - cmd.grid = cd.grid; - cmd.freq = cd.freq; - cmd.utcTimestamp = cd.utcTimestamp; - cmd.tdrift = cd.tdrift; - cmd.submode = cd.submode; - m_rxCommandQueue.append(cmd); - - // notification for hb - tryNotify("hb"); - } - - } else { - qDebug() << "buffering compound call" << cd.freq << cd.call << cd.bits; - - hasExistingMessageBuffer(cd.submode, cd.freq, true, nullptr); - m_messageBuffer[cd.freq].compound.append(cd); - } - } -#endif - - // Parse commands - // KN4CRD K1JT ? -#if 1 - bool shouldProcessDirected = true; - if(shouldProcessDirected && decodedtext.isDirectedMessage()){ - auto parts = decodedtext.directedMessage(); - - cmd.from = parts.at(0); - cmd.to = parts.at(1); - cmd.cmd = parts.at(2); - cmd.freq = decodedtext.frequencyOffset(); - cmd.snr = decodedtext.snr(); - cmd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); - cmd.bits = decodedtext.bits(); - cmd.extra = parts.length() > 2 ? parts.mid(3).join(" ") : ""; - cmd.tdrift = decodedtext.dt(); - cmd.submode = decodedtext.submode(); - - // if the command is a buffered command and its not the last frame OR we have from or to in a separate message (compound call) - if((Varicode::isCommandBuffered(cmd.cmd) && (cmd.bits & Varicode::JS8CallLast) != Varicode::JS8CallLast) || cmd.from == "<....>" || cmd.to == "<....>"){ - qDebug() << "buffering cmd" << cmd.freq << cmd.cmd << cmd.from << cmd.to; - - // log complete buffered callsigns immediately - if(cmd.from != "<....>" && cmd.to != "<....>"){ - CallDetail cmdcd = {}; - cmdcd.call = cmd.from; - cmdcd.bits = cmd.bits; - cmdcd.snr = cmd.snr; - cmdcd.freq = cmd.freq; - cmdcd.utcTimestamp = cmd.utcTimestamp; - cmdcd.ackTimestamp = cmd.to == m_config.my_callsign() ? cmd.utcTimestamp : QDateTime{}; - cmdcd.tdrift = cmd.tdrift; - cmdcd.submode = cmd.submode; - logCallActivity(cmdcd, false); - logHeardGraph(cmd.from, cmd.to); - } - - // merge any existing buffer to this frequency - hasExistingMessageBuffer(cmd.submode, cmd.freq, true, nullptr); - - if(cmd.to == m_config.my_callsign()){ - d.shouldDisplay = true; - } - - m_messageBuffer[cmd.freq].cmd = cmd; - m_messageBuffer[cmd.freq].msgs.clear(); - } else { - m_rxCommandQueue.append(cmd); - } - - // check to see if this is a station we've heard 3rd party - bool shouldCaptureThirdPartyCallsigns = false; - if(shouldCaptureThirdPartyCallsigns && Radio::base_callsign(cmd.to) != Radio::base_callsign(m_config.my_callsign())){ - QString relayCall = QString("%1|%2").arg(Radio::base_callsign(cmd.from)).arg(Radio::base_callsign(cmd.to)); - int snr = -100; - if(parts.length() == 4){ - snr = QString(parts.at(3)).toInt(); - } - - //CallDetail td = {}; - td.through = cmd.from; - td.call = cmd.to; - td.grid = ""; - td.snr = snr; - td.freq = cmd.freq; - td.utcTimestamp = cmd.utcTimestamp; - td.tdrift = cmd.tdrift; - td.submode = cmd.submode; - logCallActivity(td, true); - logHeardGraph(cmd.from, cmd.to); - } - } -#endif - - - - - // Parse CQs -#if 0 - bool shouldParseCQs = true; - if(shouldParseCQs && decodedtext.isStandardMessage()){ - QString theircall; - QString theirgrid; - decodedtext.deCallAndGrid(theircall, theirgrid); - - QStringList calls = Varicode::parseCallsigns(theircall); - if(!calls.isEmpty() && !calls.first().isEmpty()){ - theircall = calls.first(); - - CallDetail d = {}; - d.bits = decodedtext.bits(); - d.call = theircall; - d.grid = theirgrid; - d.snr = decodedtext.snr(); - d.freq = decodedtext.frequencyOffset(); - d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); - m_callActivity[d.call] = d; - } - } -#endif - - // Parse standard message callsigns - // K1JT KN4CRD EM73 - // KN4CRD K1JT -21 - // K1JT KN4CRD R-12 - // DE KN4CRD - // KN4CRD -#if 0 - bool shouldParseCallsigns = false; - if(shouldParseCallsigns){ - QStringList callsigns = Varicode::parseCallsigns(decodedtext.message()); - if(!callsigns.isEmpty()){ - // one callsign - // de [from] - // cq [from] - - // two callsigns - // [from]: [to] ... - // [to] [from] [grid|signal] - - QStringList grids = Varicode::parseGrids(decodedtext.message()); - - // one callsigns are handled above... so we only need to handle two callsigns if it's a standard message - if(decodedtext.isStandardMessage()){ - if(callsigns.length() == 2){ - auto de_callsign = callsigns.last(); - - // TODO: jsherer - put this in a function to record a callsign... - CallDetail d; - d.call = de_callsign; - d.grid = !grids.empty() ? grids.first() : ""; - d.snr = decodedtext.snr(); - d.freq = decodedtext.frequencyOffset(); - d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); - m_callActivity[Radio::base_callsign(de_callsign)] = d; - } - } - } - } -#endif + processDecodedLine(proc->readLine()); } // See MainWindow::postDecode for displaying the latest decodes } +void MainWindow::processDecodedLine(QByteArray t){ + qDebug() << "JS8: " << QString(t); + + bool bAvgMsg=false; + int navg=0; + if(t.indexOf("") >= 0) { + if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); + return; + } + + if(t.indexOf("") >= 0) { + if(JS8_DEBUG_DECODE) qDebug() << "--> busy?" << m_decoderBusy << "lock exists?" << ( QFile{m_config.temp_dir ().absoluteFilePath (".lock")}.exists()); + return; + } + + if(t.indexOf("") >= 0) { + if(m_mode=="QRA64") m_wideGraph->drawRed(0,0); + m_bDecoded = t.mid(16).trimmed().toInt() > 0; + int mswait=3*1000*m_TRperiod/4; + if(!m_diskData) killFileTimer.start(mswait); //Kill in 3/4 period + decodeDone(); + m_startAnother=m_loopall; + if(m_bNoMoreFiles) { + MessageBox::information_message(this, tr("No more files to open.")); + m_bNoMoreFiles=false; + } + return; + } + + if(m_mode=="JT4" or m_mode=="JT65" or m_mode=="QRA64" or m_mode=="FT8") { + int n=t.indexOf("f"); + if(n<0) n=t.indexOf("d"); + if(n>0) { + QString tt=t.mid(n+1,1); + navg=tt.toInt(); + if(navg==0) { + char c = tt.data()->toLatin1(); + if(int(c)>=65 and int(c)<=90) navg=int(c)-54; + } + if(navg>1 or t.indexOf("f*")>0) bAvgMsg=true; + } + } + + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("ALL.TXT")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + if(m_RxLog==1) { + out << DriftingDateTime::currentDateTimeUtc().toString("yyyy-MM-dd hh:mm:ss") + << " " << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6) << " MHz " + << "JS8" << endl; + m_RxLog=0; + } + int n=t.length(); + auto logText = t.mid(0, n-2); + auto dt = DecodedText(logText, false, m_config.my_grid()); + out << logText << " " << dt.message() << endl; + f.close(); + } else { + MessageBox::warning_message (this, tr ("File Open Error") + , tr ("Cannot open \"%1\" for append: %2") + .arg (f.fileName ()).arg (f.errorString ())); + } + + DecodedText decodedtext {QString::fromUtf8 (t.constData ()).remove (QRegularExpression {"\r|\n"}), "FT8" == m_mode && + ui->cbVHFcontest->isChecked(), m_config.my_grid ()}; + + bool bValidFrame = decodedtext.snr() >= rxSnrThreshold(decodedtext.submode()); + + // dupe check + auto frame = decodedtext.message(); + auto frameOffset = decodedtext.frequencyOffset(); + if(m_messageDupeCache.contains(frame)){ + // check to see if the frequency is near our previous frame + auto cachedFreq = m_messageDupeCache.value(frame, 0); + if(qAbs(cachedFreq - frameOffset) <= rxThreshold(decodedtext.submode())){ + qDebug() << "duplicate frame from" << cachedFreq << "and" << frameOffset; + bValidFrame = false; + } + } else { + // cache for this decode cycle + m_messageDupeCache[frame] = frameOffset; + } + + qDebug() << "valid" << bValidFrame << submodeName(decodedtext.submode()) << "decoded text" << decodedtext.message(); + + // skip if invalid + if(!bValidFrame) { + return; + } + + ActivityDetail d = {}; + CallDetail cd = {}; + CommandDetail cmd = {}; + CallDetail td = {}; + + // Parse General Activity +#if 1 + bool shouldParseGeneralActivity = true; + if(shouldParseGeneralActivity && !decodedtext.messageWords().isEmpty()){ + int offset = decodedtext.frequencyOffset(); + + if(!m_bandActivity.contains(offset)){ + int range = rxThreshold(decodedtext.submode()); + + QList offsets = generateOffsets(offset-range, offset+range); + + foreach(int prevOffset, offsets){ + if(!m_bandActivity.contains(prevOffset)){ continue; } + m_bandActivity[offset] = m_bandActivity[prevOffset]; + m_bandActivity.remove(prevOffset); + break; + } + } + + //ActivityDetail d = {}; + d.isLowConfidence = decodedtext.isLowConfidence(); + d.isFree = !decodedtext.isStandardMessage(); + d.isCompound = decodedtext.isCompound(); + d.isDirected = decodedtext.isDirectedMessage(); + d.bits = decodedtext.bits(); + d.freq = offset; + d.text = decodedtext.message(); + d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); + d.snr = decodedtext.snr(); + d.isBuffered = false; + d.tdrift = decodedtext.dt(); + d.submode = decodedtext.submode(); + + // if we have any "first" frame, and a buffer is already established, clear it... + int prevBufferOffset = -1; + if(((d.bits & Varicode::JS8CallFirst) == Varicode::JS8CallFirst) && hasExistingMessageBuffer(decodedtext.submode(), d.freq, true, &prevBufferOffset)){ + qDebug() << "first message encountered, clearing existing buffer" << prevBufferOffset; + m_messageBuffer.remove(d.freq); + } + + // if we have a data frame, and a message buffer has been established, buffer it... + if(hasExistingMessageBuffer(decodedtext.submode(), d.freq, true, &prevBufferOffset) && !decodedtext.isCompound() && !decodedtext.isDirectedMessage()){ + qDebug() << "buffering data" << d.freq << d.text; + d.isBuffered = true; + m_messageBuffer[d.freq].msgs.append(d); + // TODO: incremental display if it's "to" me. + } + + m_rxActivityQueue.append(d); + m_bandActivity[offset].append(d); + while(m_bandActivity[offset].count() > 10){ + m_bandActivity[offset].removeFirst(); + } + } +#endif + + // Process compound callsign commands (put them in cache)" +#if 1 + qDebug() << "decoded" << decodedtext.frameType() << decodedtext.isCompound() << decodedtext.isDirectedMessage() << decodedtext.isHeartbeat(); + bool shouldProcessCompound = true; + if(shouldProcessCompound && decodedtext.isCompound() && !decodedtext.isDirectedMessage()){ + cd.call = decodedtext.compoundCall(); + cd.grid = decodedtext.extra(); // compound calls via pings may contain grid... + cd.snr = decodedtext.snr(); + cd.freq = decodedtext.frequencyOffset(); + cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); + cd.bits = decodedtext.bits(); + cd.tdrift = decodedtext.dt(); + cd.submode = decodedtext.submode(); + + // Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings + if(decodedtext.isHeartbeat()){ + if(decodedtext.isAlt()){ + // this is a cq with a standard or compound call, ala "KN4CRD/P: CQCQCQ" + cd.cqTimestamp = DriftingDateTime::currentDateTimeUtc(); + + // it is not processed elsewhere, so we need to just log it here. + logCallActivity(cd, true); + + // notification for cq + tryNotify("cq"); + + } else { + // convert HEARTBEAT to a directed command and process... + cmd.from = cd.call; + cmd.to = "@ALLCALL"; + cmd.cmd = " HB"; + cmd.snr = cd.snr; + cmd.bits = cd.bits; + cmd.grid = cd.grid; + cmd.freq = cd.freq; + cmd.utcTimestamp = cd.utcTimestamp; + cmd.tdrift = cd.tdrift; + cmd.submode = cd.submode; + m_rxCommandQueue.append(cmd); + + // notification for hb + tryNotify("hb"); + } + + } else { + qDebug() << "buffering compound call" << cd.freq << cd.call << cd.bits; + + hasExistingMessageBuffer(cd.submode, cd.freq, true, nullptr); + m_messageBuffer[cd.freq].compound.append(cd); + } + } +#endif + + // Parse commands + // KN4CRD K1JT ? +#if 1 + bool shouldProcessDirected = true; + if(shouldProcessDirected && decodedtext.isDirectedMessage()){ + auto parts = decodedtext.directedMessage(); + + cmd.from = parts.at(0); + cmd.to = parts.at(1); + cmd.cmd = parts.at(2); + cmd.freq = decodedtext.frequencyOffset(); + cmd.snr = decodedtext.snr(); + cmd.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); + cmd.bits = decodedtext.bits(); + cmd.extra = parts.length() > 2 ? parts.mid(3).join(" ") : ""; + cmd.tdrift = decodedtext.dt(); + cmd.submode = decodedtext.submode(); + + // if the command is a buffered command and its not the last frame OR we have from or to in a separate message (compound call) + if((Varicode::isCommandBuffered(cmd.cmd) && (cmd.bits & Varicode::JS8CallLast) != Varicode::JS8CallLast) || cmd.from == "<....>" || cmd.to == "<....>"){ + qDebug() << "buffering cmd" << cmd.freq << cmd.cmd << cmd.from << cmd.to; + + // log complete buffered callsigns immediately + if(cmd.from != "<....>" && cmd.to != "<....>"){ + CallDetail cmdcd = {}; + cmdcd.call = cmd.from; + cmdcd.bits = cmd.bits; + cmdcd.snr = cmd.snr; + cmdcd.freq = cmd.freq; + cmdcd.utcTimestamp = cmd.utcTimestamp; + cmdcd.ackTimestamp = cmd.to == m_config.my_callsign() ? cmd.utcTimestamp : QDateTime{}; + cmdcd.tdrift = cmd.tdrift; + cmdcd.submode = cmd.submode; + logCallActivity(cmdcd, false); + logHeardGraph(cmd.from, cmd.to); + } + + // merge any existing buffer to this frequency + hasExistingMessageBuffer(cmd.submode, cmd.freq, true, nullptr); + + if(cmd.to == m_config.my_callsign()){ + d.shouldDisplay = true; + } + + m_messageBuffer[cmd.freq].cmd = cmd; + m_messageBuffer[cmd.freq].msgs.clear(); + } else { + m_rxCommandQueue.append(cmd); + } + + // check to see if this is a station we've heard 3rd party + bool shouldCaptureThirdPartyCallsigns = false; + if(shouldCaptureThirdPartyCallsigns && Radio::base_callsign(cmd.to) != Radio::base_callsign(m_config.my_callsign())){ + QString relayCall = QString("%1|%2").arg(Radio::base_callsign(cmd.from)).arg(Radio::base_callsign(cmd.to)); + int snr = -100; + if(parts.length() == 4){ + snr = QString(parts.at(3)).toInt(); + } + + //CallDetail td = {}; + td.through = cmd.from; + td.call = cmd.to; + td.grid = ""; + td.snr = snr; + td.freq = cmd.freq; + td.utcTimestamp = cmd.utcTimestamp; + td.tdrift = cmd.tdrift; + td.submode = cmd.submode; + logCallActivity(td, true); + logHeardGraph(cmd.from, cmd.to); + } + } +#endif + + + + + // Parse CQs +#if 0 + bool shouldParseCQs = true; + if(shouldParseCQs && decodedtext.isStandardMessage()){ + QString theircall; + QString theirgrid; + decodedtext.deCallAndGrid(theircall, theirgrid); + + QStringList calls = Varicode::parseCallsigns(theircall); + if(!calls.isEmpty() && !calls.first().isEmpty()){ + theircall = calls.first(); + + CallDetail d = {}; + d.bits = decodedtext.bits(); + d.call = theircall; + d.grid = theirgrid; + d.snr = decodedtext.snr(); + d.freq = decodedtext.frequencyOffset(); + d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); + m_callActivity[d.call] = d; + } + } +#endif + + // Parse standard message callsigns + // K1JT KN4CRD EM73 + // KN4CRD K1JT -21 + // K1JT KN4CRD R-12 + // DE KN4CRD + // KN4CRD +#if 0 + bool shouldParseCallsigns = false; + if(shouldParseCallsigns){ + QStringList callsigns = Varicode::parseCallsigns(decodedtext.message()); + if(!callsigns.isEmpty()){ + // one callsign + // de [from] + // cq [from] + + // two callsigns + // [from]: [to] ... + // [to] [from] [grid|signal] + + QStringList grids = Varicode::parseGrids(decodedtext.message()); + + // one callsigns are handled above... so we only need to handle two callsigns if it's a standard message + if(decodedtext.isStandardMessage()){ + if(callsigns.length() == 2){ + auto de_callsign = callsigns.last(); + + // TODO: jsherer - put this in a function to record a callsign... + CallDetail d; + d.call = de_callsign; + d.grid = !grids.empty() ? grids.first() : ""; + d.snr = decodedtext.snr(); + d.freq = decodedtext.frequencyOffset(); + d.utcTimestamp = DriftingDateTime::currentDateTimeUtc(); + m_callActivity[Radio::base_callsign(de_callsign)] = d; + } + } + } + } +#endif +} + bool MainWindow::hasExistingMessageBufferToMe(int *pOffset){ foreach(auto offset, m_messageBuffer.keys()){ auto buffer = m_messageBuffer[offset]; diff --git a/mainwindow.h b/mainwindow.h index 2c59960..43612bb 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -164,6 +164,7 @@ public slots: void tryNotify(const QString &key); int rxThreshold(int submode); int rxSnrThreshold(int submode); + void processDecodedLine(QByteArray t); protected: void keyPressEvent (QKeyEvent *) override; @@ -430,6 +431,7 @@ private slots: void refreshTextDisplay(); private: + Q_SIGNAL void decodedLineReady(QByteArray t); Q_SIGNAL void playNotification(const QString &name); Q_SIGNAL void initializeNotificationAudioOutputStream(const QAudioDeviceInfo &, unsigned, unsigned) const; Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,