2026-01-18 12:53:55 -06:00
|
|
|
|
2026-01-04 12:54:34 -06:00
|
|
|
|
2026-01-05 08:23:33 -06:00
|
|
|
/** \file
|
2026-01-17 15:24:43 -06:00
|
|
|
* @brief member function of the UI_Constructor class
|
2026-01-05 08:23:33 -06:00
|
|
|
* process decoded text
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include "JS8_UI/mainwindow.h"
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
void UI_Constructor::processDecodeEvent(JS8::Event::Variant const &event) {
|
2026-01-04 12:54:34 -06:00
|
|
|
static QList<qint32> driftQueue;
|
|
|
|
|
static qint32 syncStart = -1;
|
|
|
|
|
|
|
|
|
|
std::visit(
|
|
|
|
|
[this](auto &&e) {
|
|
|
|
|
using T = std::decay_t<decltype(e)>;
|
|
|
|
|
|
|
|
|
|
if constexpr (std::is_same_v<T, JS8::Event::DecodeStarted>) {
|
|
|
|
|
if (m_wideGraph->shouldDisplayDecodeAttempts()) {
|
|
|
|
|
m_wideGraph->drawHorizontalLine(QColor(Qt::yellow), 0, 5);
|
|
|
|
|
}
|
|
|
|
|
} else if constexpr (std::is_same_v<T, JS8::Event::SyncStart>) {
|
|
|
|
|
syncStart = e.position;
|
|
|
|
|
} else if constexpr (std::is_same_v<T, JS8::Event::SyncState>) {
|
|
|
|
|
if (m_wideGraph->shouldDisplayDecodeAttempts()) {
|
|
|
|
|
auto const drawDecodeLine =
|
|
|
|
|
[this, freq = static_cast<int>(e.frequency),
|
|
|
|
|
mode = e.mode](QColor const &color) {
|
|
|
|
|
m_wideGraph->drawDecodeLine(
|
|
|
|
|
color, freq,
|
|
|
|
|
freq + JS8::Submode::bandwidth(mode));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (e.type == JS8::Event::SyncState::Type::DECODED) {
|
|
|
|
|
drawDecodeLine(Qt::red);
|
|
|
|
|
} else if (auto const xdtMs = static_cast<int>(e.dt * 1000);
|
|
|
|
|
std::abs(xdtMs) <= 2000) {
|
|
|
|
|
if (e.sync.candidate < 10)
|
|
|
|
|
drawDecodeLine(Qt::darkCyan);
|
|
|
|
|
else if (e.sync.candidate <= 15)
|
|
|
|
|
drawDecodeLine(Qt::cyan);
|
|
|
|
|
else if (e.sync.candidate <= 21)
|
|
|
|
|
drawDecodeLine(Qt::white);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if constexpr (std::is_same_v<T,
|
|
|
|
|
JS8::Event::DecodeFinished>) {
|
|
|
|
|
qCDebug(decoder_js8) << "decode duration"
|
|
|
|
|
<< m_decoderBusyStartTime.msecsTo(
|
|
|
|
|
QDateTime::currentDateTimeUtc())
|
|
|
|
|
<< "ms";
|
|
|
|
|
|
|
|
|
|
// TODO: move this into a function
|
|
|
|
|
if (!driftQueue.isEmpty()) {
|
|
|
|
|
if (m_driftMsMMA_N == 0) {
|
|
|
|
|
m_driftMsMMA_N = 1;
|
|
|
|
|
m_driftMsMMA = DriftingDateTime::drift();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// let the widegraph know for timing control
|
|
|
|
|
m_wideGraph->notifyDriftedSignalsDecoded(
|
|
|
|
|
driftQueue.count());
|
|
|
|
|
|
|
|
|
|
while (!driftQueue.isEmpty()) {
|
|
|
|
|
qint32 newDrift = driftQueue.first();
|
|
|
|
|
driftQueue.removeFirst();
|
|
|
|
|
|
|
|
|
|
m_driftMsMMA =
|
|
|
|
|
(((m_driftMsMMA_N - 1) * m_driftMsMMA) + newDrift) /
|
|
|
|
|
m_driftMsMMA_N;
|
|
|
|
|
if (m_driftMsMMA_N < 60)
|
|
|
|
|
m_driftMsMMA_N++; // cap it to 60 observations
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// XXX The following lines do nothing; it's a completely dead
|
|
|
|
|
// store. For
|
|
|
|
|
// now, just #ifdefing them out, but they were in
|
|
|
|
|
// the 2.2.1-devel code, and presumably they were important;
|
|
|
|
|
// need to see what the intent was here.
|
|
|
|
|
#if 0
|
|
|
|
|
qint32 driftLimitMs = JS8::Submode::period(Varicode::JS8CallNormal) * 1000;
|
|
|
|
|
qint32 newDriftMs = m_driftMsMMA;
|
|
|
|
|
if(newDriftMs < 0){
|
|
|
|
|
newDriftMs = -((-newDriftMs) % driftLimitMs);
|
|
|
|
|
} else {
|
|
|
|
|
newDriftMs = ((newDriftMs) % driftLimitMs);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
setDrift(m_driftMsMMA);
|
|
|
|
|
// writeNoticeTextToUI(QDateTime::currentDateTimeUtc(),
|
|
|
|
|
// QString("Automatic Drift: %1").arg(driftAvg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_bDecoded = e.decoded > 0;
|
|
|
|
|
decodeDone();
|
|
|
|
|
} else if constexpr (std::is_same_v<T, JS8::Event::Decoded>) {
|
|
|
|
|
// A frame is valid if we haven't seen the same frame in the
|
|
|
|
|
// past 1/2 decode period.
|
|
|
|
|
//
|
|
|
|
|
// Note: Success here depends on decodes ordered such that
|
|
|
|
|
// frequencies
|
|
|
|
|
// near `dec_data.params.nfqso` arrive here first, so it's
|
|
|
|
|
// key to process the decode candidates in an ordered
|
|
|
|
|
// manner, likely by sorting the raw take from the initial
|
|
|
|
|
// selection pass.
|
|
|
|
|
|
|
|
|
|
DecodedText decodedtext(e);
|
|
|
|
|
FrameCacheKey dedupeKey(decodedtext.submode(),
|
|
|
|
|
decodedtext.frame());
|
|
|
|
|
|
|
|
|
|
if (auto const it = m_messageDupeCache.find(dedupeKey);
|
|
|
|
|
it != m_messageDupeCache.end()) {
|
|
|
|
|
if (it->second.secsTo(QDateTime::currentDateTimeUtc()) <
|
|
|
|
|
0.5 * JS8::Submode::period(decodedtext.submode())) {
|
|
|
|
|
qCDebug(mainwindow_js8)
|
|
|
|
|
<< "duplicate frame at" << it->second << "using key"
|
|
|
|
|
<< QString("%1:%2")
|
|
|
|
|
.arg(dedupeKey.submode)
|
|
|
|
|
.arg(dedupeKey.frame);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#if 0
|
|
|
|
|
// frames are valid if they meet our minimum rx threshold for the submode
|
|
|
|
|
bool bValidFrame = decodedtext.snr() >= JS8::Submode::rxSNRThreshold(decodedtext.submode());
|
|
|
|
|
|
|
|
|
|
qCDebug(mainwindow_js8) << "valid" << bValidFrame << JS8::Submode::name(decodedtext.submode()) << "decoded text" << decodedtext.message();
|
|
|
|
|
|
|
|
|
|
// skip if invalid
|
|
|
|
|
if(!bValidFrame) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
qCDebug(mainwindow_js8)
|
|
|
|
|
<< JS8::Submode::name(decodedtext.submode())
|
|
|
|
|
<< "decoded text" << decodedtext.message();
|
|
|
|
|
#endif
|
|
|
|
|
// TODO: move this into a function
|
|
|
|
|
// compute time drift for non-dupe messages
|
|
|
|
|
if (m_wideGraph->shouldAutoSyncSubmode(decodedtext.submode())) {
|
|
|
|
|
int m = decodedtext.submode();
|
|
|
|
|
float xdt = decodedtext.dt();
|
|
|
|
|
|
|
|
|
|
// if we're here at this point, we _should_ be operating a
|
|
|
|
|
// decode every second
|
|
|
|
|
//
|
|
|
|
|
// so we need to figure out where:
|
|
|
|
|
//
|
|
|
|
|
// 1) this current decode started
|
|
|
|
|
// 2) when that cycle _should_ have started
|
|
|
|
|
// 3) compute the delta
|
|
|
|
|
// 4) apply the drift
|
|
|
|
|
|
|
|
|
|
qint32 periodMs = 1000 * JS8::Submode::period(m);
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("Decode at %1 (kin: %2,
|
|
|
|
|
// lastDecoded:
|
|
|
|
|
// %3)").arg(syncStart).arg(dec_data.params.kin).arg(m_lastDecodeStartMap.value(m)));
|
|
|
|
|
|
|
|
|
|
float expectedStartDelay =
|
|
|
|
|
JS8::Submode::startDelayMS(m) / 1000.0;
|
|
|
|
|
|
|
|
|
|
float decodedSignalTime =
|
|
|
|
|
(float)syncStart / (float)JS8_RX_SAMPLE_RATE;
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> started at %1
|
|
|
|
|
// seconds into the start of my drifted
|
|
|
|
|
// minute").arg(decodedSignalTime));
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> we add a time delta
|
|
|
|
|
// of %1 seconds into the start of the cycle").arg(xdt));
|
|
|
|
|
|
|
|
|
|
// adjust for expected start delay
|
|
|
|
|
decodedSignalTime -= expectedStartDelay;
|
|
|
|
|
|
|
|
|
|
// adjust for time delta
|
|
|
|
|
decodedSignalTime += xdt;
|
|
|
|
|
|
|
|
|
|
// ensure that we are within a 60 second minute
|
|
|
|
|
if (decodedSignalTime < 0) {
|
|
|
|
|
decodedSignalTime += 60.0f;
|
|
|
|
|
} else if (decodedSignalTime > 60) {
|
|
|
|
|
decodedSignalTime -= 60.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> so signal adjusted
|
|
|
|
|
// started at %1 seconds into the start of my drifted
|
|
|
|
|
// minute").arg(decodedSignalTime));
|
|
|
|
|
|
|
|
|
|
qint32 decodedSignalTimeMs = 1000 * decodedSignalTime;
|
|
|
|
|
qint32 cycleStartTimeMs =
|
|
|
|
|
(decodedSignalTimeMs / periodMs) * periodMs;
|
|
|
|
|
qint32 driftMs = cycleStartTimeMs - decodedSignalTimeMs;
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> which is a drift
|
|
|
|
|
// adjustment of %1 milliseconds").arg(driftMs));
|
|
|
|
|
|
|
|
|
|
// if we have a large negative offset (say -14000), use the
|
|
|
|
|
// positive inverse of +1000
|
|
|
|
|
if (driftMs + periodMs < qAbs(driftMs)) {
|
|
|
|
|
driftMs += periodMs;
|
|
|
|
|
}
|
|
|
|
|
// if we have a large positive offset (say 14000, use the
|
|
|
|
|
// negative inverse of -1000)
|
|
|
|
|
else if (qAbs(driftMs - periodMs) < driftMs) {
|
|
|
|
|
driftMs -= periodMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> which is a
|
|
|
|
|
// corrected drift adjustment of %1
|
|
|
|
|
// milliseconds").arg(driftMs));
|
|
|
|
|
|
|
|
|
|
qint32 newDrift = DriftingDateTime::drift() + driftMs;
|
|
|
|
|
if (newDrift < 0) {
|
|
|
|
|
newDrift %= -periodMs;
|
|
|
|
|
} else {
|
|
|
|
|
newDrift %= periodMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// writeNoticeTextToUI(now, QString("--> which is rounded to
|
|
|
|
|
// a total drift of %1 milliseconds for this
|
|
|
|
|
// period").arg(newDrift));
|
|
|
|
|
|
|
|
|
|
driftQueue.append(newDrift);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the frame is valid, cache it!
|
|
|
|
|
m_messageDupeCache.insert_or_assign(
|
|
|
|
|
dedupeKey, QDateTime::currentDateTimeUtc());
|
|
|
|
|
|
|
|
|
|
// log valid frames to ALL.txt (and correct their timestamp
|
|
|
|
|
// format)
|
|
|
|
|
auto freq = dialFrequency();
|
|
|
|
|
|
|
|
|
|
// if we changed frequencies, use the old frequency that we
|
|
|
|
|
// started the decode with
|
|
|
|
|
if (m_decoderBusyFreq != freq) {
|
|
|
|
|
freq = m_decoderBusyFreq;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto date = DriftingDateTime::currentDateTimeUtc().toString(
|
|
|
|
|
"yyyy-MM-dd");
|
|
|
|
|
writeAllTxt(date + " " + decodedtext.string() + " " +
|
|
|
|
|
decodedtext.message());
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Send decode to WSJT-X protocol
|
|
|
|
|
*
|
|
|
|
|
* Converts JS8Call decode events to WSJT-X Decode messages and
|
|
|
|
|
* sends them to WSJT-X protocol clients. For HeartBeat
|
|
|
|
|
* messages, ensures the message text includes callsign and grid
|
|
|
|
|
* so clients can properly associate them with grid plots.
|
|
|
|
|
*/
|
|
|
|
|
if (m_wsjtxMessageMapper && m_config.wsjtx_protocol_enabled()) {
|
|
|
|
|
// Convert decode time from JS8Call format to QTime
|
|
|
|
|
auto const hms = decode_time(decodedtext.time());
|
|
|
|
|
QTime decode_time = QTime(hms.hour, hms.minute, hms.second);
|
|
|
|
|
|
|
|
|
|
// Send decode message
|
|
|
|
|
// Use "JS8" as the mode string (WSJT-X expects mode names
|
|
|
|
|
// like "FT8", "FT4", "JT9", etc.)
|
|
|
|
|
m_wsjtxMessageMapper->sendDecode(
|
|
|
|
|
true, // is_new - always true for new decodes
|
|
|
|
|
decode_time, decodedtext.snr(), decodedtext.dt(),
|
|
|
|
|
static_cast<quint32>(decodedtext.frequencyOffset()),
|
|
|
|
|
"JS8", // mode string
|
|
|
|
|
decodedtext.message(), decodedtext.isLowConfidence());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 const range =
|
|
|
|
|
JS8::Submode::rxThreshold(decodedtext.submode());
|
|
|
|
|
|
|
|
|
|
QList<int> 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.isCompound = decodedtext.isCompound();
|
|
|
|
|
d.isDirected = decodedtext.isDirectedMessage();
|
|
|
|
|
d.bits = decodedtext.bits();
|
|
|
|
|
d.dial = freq;
|
|
|
|
|
d.offset = offset;
|
|
|
|
|
d.text = decodedtext.message();
|
|
|
|
|
d.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
|
|
|
|
|
d.snr = decodedtext.snr();
|
|
|
|
|
d.isBuffered = false;
|
|
|
|
|
d.submode = decodedtext.submode();
|
|
|
|
|
d.tdrift = m_wideGraph->shouldAutoSyncSubmode(d.submode)
|
|
|
|
|
? DriftingDateTime::drift() / 1000.0
|
|
|
|
|
: decodedtext.dt();
|
|
|
|
|
|
|
|
|
|
// 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.offset, true,
|
|
|
|
|
&prevBufferOffset)) {
|
|
|
|
|
qCDebug(mainwindow_js8) << "first message encountered, "
|
|
|
|
|
"clearing existing buffer"
|
|
|
|
|
<< prevBufferOffset;
|
|
|
|
|
m_messageBuffer.remove(d.offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have a data frame, and a message buffer has been
|
|
|
|
|
// established, buffer it...
|
|
|
|
|
if (hasExistingMessageBuffer(decodedtext.submode(),
|
|
|
|
|
d.offset, true,
|
|
|
|
|
&prevBufferOffset) &&
|
|
|
|
|
!decodedtext.isCompound() &&
|
|
|
|
|
!decodedtext.isDirectedMessage()) {
|
|
|
|
|
qCDebug(mainwindow_js8)
|
|
|
|
|
<< "buffering data" << d.dial << d.offset << d.text;
|
|
|
|
|
d.isBuffered = true;
|
|
|
|
|
m_messageBuffer[d.offset].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
|
|
|
|
|
qCDebug(mainwindow_js8) << "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.dial = freq;
|
|
|
|
|
cd.offset = decodedtext.frequencyOffset();
|
|
|
|
|
cd.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
|
|
|
|
|
cd.bits = decodedtext.bits();
|
|
|
|
|
cd.submode = decodedtext.submode();
|
|
|
|
|
cd.tdrift = m_wideGraph->shouldAutoSyncSubmode(d.submode)
|
|
|
|
|
? DriftingDateTime::drift() / 1000.0
|
|
|
|
|
: decodedtext.dt();
|
|
|
|
|
|
|
|
|
|
// 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: @ALLCALL CQ CQ CQ"
|
|
|
|
|
cd.cqTimestamp =
|
|
|
|
|
DriftingDateTime::currentDateTimeUtc();
|
|
|
|
|
|
|
|
|
|
// convert CQ to a directed command and process...
|
|
|
|
|
cmd.from = cd.call;
|
|
|
|
|
cmd.to = "@ALLCALL";
|
|
|
|
|
cmd.cmd = " CQ";
|
|
|
|
|
cmd.snr = cd.snr;
|
|
|
|
|
cmd.bits = cd.bits;
|
|
|
|
|
cmd.grid = cd.grid;
|
|
|
|
|
cmd.dial = cd.dial;
|
|
|
|
|
cmd.offset = cd.offset;
|
|
|
|
|
cmd.utcTimestamp = cd.utcTimestamp;
|
|
|
|
|
cmd.tdrift = cd.tdrift;
|
|
|
|
|
cmd.submode = cd.submode;
|
|
|
|
|
cmd.text = decodedtext.message();
|
|
|
|
|
|
|
|
|
|
// TODO: check bits so we only auto respond to
|
|
|
|
|
// "finished" cqs
|
|
|
|
|
m_rxCommandQueue.append(cmd);
|
|
|
|
|
|
|
|
|
|
// since this is no longer processed here we omit
|
|
|
|
|
// logging it here. if we change this behavior, we'd
|
|
|
|
|
// change this back to logging here.
|
|
|
|
|
// logCallActivity(cd, true);
|
|
|
|
|
|
|
|
|
|
// notification for cq
|
|
|
|
|
tryNotify("cq");
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// convert HEARTBEAT to a directed command and
|
|
|
|
|
// process...
|
|
|
|
|
cmd.from = cd.call;
|
|
|
|
|
cmd.to = "@HB";
|
|
|
|
|
cmd.cmd = " HEARTBEAT";
|
|
|
|
|
cmd.snr = cd.snr;
|
|
|
|
|
cmd.bits = cd.bits;
|
|
|
|
|
cmd.grid = cd.grid;
|
|
|
|
|
cmd.dial = cd.dial;
|
|
|
|
|
cmd.offset = cd.offset;
|
|
|
|
|
cmd.utcTimestamp = cd.utcTimestamp;
|
|
|
|
|
cmd.tdrift = cd.tdrift;
|
|
|
|
|
cmd.submode = cd.submode;
|
|
|
|
|
|
|
|
|
|
// TODO: check bits so we only auto respond to
|
|
|
|
|
// "finished" heartbeats
|
|
|
|
|
m_rxCommandQueue.append(cmd);
|
|
|
|
|
|
|
|
|
|
// notification for hb
|
|
|
|
|
tryNotify("hb");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(mainwindow_js8)
|
|
|
|
|
<< "buffering compound call" << cd.offset << cd.call
|
|
|
|
|
<< cd.bits;
|
|
|
|
|
|
|
|
|
|
hasExistingMessageBuffer(cd.submode, cd.offset, true,
|
|
|
|
|
nullptr);
|
|
|
|
|
m_messageBuffer[cd.offset].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.dial = freq;
|
|
|
|
|
cmd.offset = decodedtext.frequencyOffset();
|
|
|
|
|
cmd.snr = decodedtext.snr();
|
|
|
|
|
cmd.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
|
|
|
|
|
cmd.bits = decodedtext.bits();
|
|
|
|
|
cmd.extra =
|
|
|
|
|
parts.length() > 2 ? parts.mid(3).join(" ") : "";
|
|
|
|
|
cmd.submode = decodedtext.submode();
|
|
|
|
|
cmd.tdrift = m_wideGraph->shouldAutoSyncSubmode(cmd.submode)
|
|
|
|
|
? DriftingDateTime::drift() / 1000.0
|
|
|
|
|
: decodedtext.dt();
|
|
|
|
|
|
|
|
|
|
// 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 == "<....>") {
|
|
|
|
|
qCDebug(mainwindow_js8)
|
|
|
|
|
<< "buffering cmd" << cmd.dial << cmd.offset
|
|
|
|
|
<< 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.dial = cmd.dial;
|
|
|
|
|
cmdcd.offset = cmd.offset;
|
|
|
|
|
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.offset, true,
|
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
|
|
if (cmd.to == m_config.my_callsign()) {
|
|
|
|
|
d.shouldDisplay = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_messageBuffer[cmd.offset].cmd = cmd;
|
|
|
|
|
m_messageBuffer[cmd.offset].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.dial = cmd.dial;
|
|
|
|
|
td.offset = cmd.offset;
|
|
|
|
|
td.utcTimestamp = cmd.utcTimestamp;
|
|
|
|
|
td.tdrift = cmd.tdrift;
|
|
|
|
|
td.submode = cmd.submode;
|
|
|
|
|
logCallActivity(td, true);
|
|
|
|
|
logHeardGraph(cmd.from, cmd.to);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
event);
|
|
|
|
|
}
|