Support compound callsigns
This commit is contained in:
parent
9bee00c5dd
commit
01249bd115
@ -63,6 +63,9 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
|
||||
if(!is_standard_){
|
||||
bool unpacked = false;
|
||||
|
||||
if(!unpacked){
|
||||
unpacked = tryUnpackCompound();
|
||||
}
|
||||
if(!unpacked){
|
||||
unpacked = tryUnpackDirected();
|
||||
}
|
||||
@ -72,6 +75,25 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodedText::tryUnpackCompound(){
|
||||
QString m = message().trimmed();
|
||||
|
||||
// directed calls will always be 12+ chars and contain no spaces.
|
||||
if(m.length() < 12 || m.contains(' ')){
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList parts = Varicode::unpackCompoundMessage(m);
|
||||
|
||||
if(parts.isEmpty() || parts.length() < 2){
|
||||
return false;
|
||||
}
|
||||
|
||||
compound_ = QString("%1/%2").arg(parts.at(0), parts.at(1));
|
||||
message_ = QString("%1:").arg(compound_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodedText::tryUnpackDirected(){
|
||||
QString m = message().trimmed();
|
||||
|
||||
@ -94,7 +116,7 @@ bool DecodedText::tryUnpackDirected(){
|
||||
message_ = QString("%1: %2%3 %4").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3));
|
||||
} else {
|
||||
// replace it with the correct unpacked (freetext)
|
||||
message_ = QString(parts.join(QChar()));
|
||||
message_ = QString(parts.join(""));
|
||||
}
|
||||
|
||||
directed_ = parts;
|
||||
|
@ -32,11 +32,15 @@ class DecodedText
|
||||
public:
|
||||
explicit DecodedText (QString const& message, bool, QString const& my_grid);
|
||||
|
||||
bool tryUnpackCompound();
|
||||
bool tryUnpackDirected();
|
||||
bool tryUnpackData();
|
||||
|
||||
QString compoundCall() const { return compound_; }
|
||||
bool isCompoundMessage() const { return !compound_.isEmpty(); }
|
||||
|
||||
QStringList directedMessage() const { return directed_; }
|
||||
bool isDirectedMessage() const { return !directed_.isEmpty(); }
|
||||
bool isDirectedMessage() const { return !directed_.isEmpty() && directed_.length() > 2; }
|
||||
|
||||
QString string() const { return string_; }
|
||||
QString message() const { return message_; }
|
||||
@ -85,6 +89,7 @@ private:
|
||||
column_mode = 19,
|
||||
column_qsoText = 22 };
|
||||
|
||||
QString compound_;
|
||||
QStringList directed_;
|
||||
QString string_;
|
||||
int padding_;
|
||||
|
165
mainwindow.cpp
165
mainwindow.cpp
@ -1046,10 +1046,23 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
|
||||
auto clearAction4 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->tableWidgetCalls);
|
||||
connect(clearAction4, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->tableWidgetCalls); });
|
||||
ui->tableWidgetCalls->setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||
ui->tableWidgetCalls->addAction(clearAction4);
|
||||
ui->tableWidgetCalls->addAction(clearActionSep);
|
||||
ui->tableWidgetCalls->addAction(clearActionAll);
|
||||
|
||||
ui->tableWidgetCalls->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->tableWidgetCalls, &QTableWidget::customContextMenuRequested, this, [this, clearAction4, clearActionAll](QPoint const &point){
|
||||
QMenu * menu = new QMenu(ui->tableWidgetCalls);
|
||||
|
||||
auto directedMenu = menu->addMenu("Directed");
|
||||
directedMenu->setDisabled(callsignSelected().isEmpty());
|
||||
buildQueryMenu(directedMenu);
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addAction(clearAction4);
|
||||
menu->addSeparator();
|
||||
menu->addAction(clearActionAll);
|
||||
|
||||
menu->popup(ui->tableWidgetCalls->mapToGlobal(point));
|
||||
});
|
||||
|
||||
|
||||
m_lastTxTime = QDateTime::currentDateTimeUtc();
|
||||
|
||||
@ -1091,6 +1104,35 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
frames++;
|
||||
qDebug() << "HuffFrames" << frames;
|
||||
qDebug() << Varicode::packCallsignPrefixSuffix("VE3") << Varicode::unpackCallsignPrefixSuffix(Varicode::packCallsignPrefixSuffix("VE3");
|
||||
|
||||
auto calls = Varicode::parseCallsigns("VE3/KN4CRD 9E/KN4CRD KN4CRD/7 KN4CRD/P KN4CRD");
|
||||
foreach(auto call, calls){
|
||||
qDebug() << call << Radio::base_callsign(call) << QString(call).replace(Radio::base_callsign(call), "");
|
||||
|
||||
auto base = Radio::base_callsign(call);
|
||||
auto fix = call.replace(base, "").replace("/", "");
|
||||
qDebug() << fix;
|
||||
|
||||
auto packedCall = ((quint64)Varicode::packCallsign(base) << 32) | Varicode::packCallsignPrefixSuffix(fix);
|
||||
|
||||
auto packed = Varicode::pack64bits(packedCall);
|
||||
|
||||
qDebug() << call << packedCall << packed;
|
||||
}
|
||||
|
||||
auto call = QString(m_config.my_callsign());
|
||||
qDebug() << call;
|
||||
auto basecall = Radio::base_callsign(call);
|
||||
qDebug() << call << basecall;
|
||||
auto fix = QString(call).replace(basecall, "");
|
||||
qDebug() << call << basecall << fix;
|
||||
auto prefix = !fix.startsWith("/");
|
||||
fix = fix.replace("/", "");
|
||||
|
||||
auto packed = Varicode::packCompoundMessage(basecall, fix, prefix, 99);
|
||||
qDebug() << packed << Varicode::unpackCompoundMessage(packed);
|
||||
|
||||
m_valid = false;
|
||||
#endif
|
||||
|
||||
// this must be the last statement of constructor
|
||||
@ -3257,7 +3299,7 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
auto parts = decodedtext.directedMessage();
|
||||
|
||||
CommandDetail d;
|
||||
d.from = parts.at(0);
|
||||
d.from = lookupCallInCompoundCache(parts.at(0));
|
||||
d.to = parts.at(1);
|
||||
d.cmd = parts.at(2);
|
||||
d.freq = decodedtext.frequencyOffset();
|
||||
@ -3268,8 +3310,8 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
CallDetail cd;
|
||||
cd.call = d.from;
|
||||
cd.grid = "";
|
||||
cd.snr = decodedtext.snr();
|
||||
cd.freq = decodedtext.frequencyOffset();
|
||||
cd.snr = d.snr;
|
||||
cd.freq = d.freq;
|
||||
cd.utcTimestamp = d.utcTimestamp;
|
||||
m_callActivity[Radio::base_callsign(cd.call)] = cd;
|
||||
|
||||
@ -3280,6 +3322,13 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
pskLogReport("FT8Call", d.freq, d.snr, d.from, "");
|
||||
}
|
||||
}
|
||||
|
||||
// Process compound callsign commands (put them in cache)"
|
||||
bool shouldProcessCompound = true;
|
||||
if(shouldProcessCompound && decodedtext.isCompoundMessage()){
|
||||
QString compoundCall = decodedtext.compoundCall();
|
||||
m_compoundCallCache[Radio::base_callsign(compoundCall)] = compoundCall;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3416,6 +3465,10 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
// See MainWindow::postDecode for displaying the latest decodes
|
||||
}
|
||||
|
||||
QString MainWindow::lookupCallInCompoundCache(QString const &call){
|
||||
return m_compoundCallCache.value(call, call);
|
||||
}
|
||||
|
||||
//
|
||||
// start_tolerance - only respond to "DE ..." and free text 73
|
||||
// messages within +/- this value
|
||||
@ -5483,7 +5536,13 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
|
||||
QStringList frames;
|
||||
QStringList lines;
|
||||
|
||||
QString mycall = Radio::base_callsign(m_config.my_callsign());
|
||||
// prepare compound
|
||||
bool compound = Radio::is_compound_callsign(m_config.my_callsign());
|
||||
QString mycall = m_config.my_callsign();
|
||||
QString basecall = Radio::base_callsign(m_config.my_callsign());
|
||||
QString fix = QString(m_config.my_callsign()).replace(basecall, "");
|
||||
bool prefix = !fix.startsWith("/");
|
||||
fix = fix.replace("/", "");
|
||||
|
||||
foreach(QString line, text.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts)){
|
||||
|
||||
@ -5509,7 +5568,7 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
|
||||
QString stdFrame = parseFT8Message(line, &isFree);
|
||||
|
||||
int n = 0;
|
||||
QString dirFrame = Varicode::packDirectedMessage(line, mycall, &n);
|
||||
QString dirFrame = Varicode::packDirectedMessage(line, basecall, &n);
|
||||
|
||||
int m = 0;
|
||||
QString datFrame = Varicode::packDataMessage(line.left(21) + "\x04", &m); // 63 / 3 = 21 (maximum number of 3bit chars we could possibly stuff in here)
|
||||
@ -5546,11 +5605,21 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
|
||||
}
|
||||
|
||||
if(useDir){
|
||||
frames.append(frame);
|
||||
// TODO: jsherer - would be nice to clean this up and have an object that can just decode the actual transmitted frames instead.
|
||||
if(!line.startsWith(mycall)){
|
||||
lines.append(QString("%1: ").arg(mycall));
|
||||
if(compound){
|
||||
QString compoundFrame = Varicode::packCompoundMessage(basecall, fix, prefix, 0);
|
||||
if(!compoundFrame.isEmpty()){
|
||||
frames.append(compoundFrame);
|
||||
lines.append(QString("%1: ").arg(mycall));
|
||||
}
|
||||
}
|
||||
|
||||
frames.append(frame);
|
||||
|
||||
// TODO: jsherer - would be nice to clean this up and have an object that can just decode the actual transmitted frames instead.
|
||||
if(!line.startsWith(basecall) && !compound){
|
||||
lines.append(QString("%1: ").arg(basecall));
|
||||
}
|
||||
|
||||
lines.append(line.left(n));
|
||||
line = line.mid(n);
|
||||
}
|
||||
@ -5566,7 +5635,7 @@ QPair<QStringList, QStringList> MainWindow::buildFT8MessageFrames(QString const&
|
||||
#if 1
|
||||
qDebug() << "parsed frames:";
|
||||
foreach(auto frame, frames){
|
||||
qDebug() << "->" << frame << Varicode::unpackDataMessage(frame) << Varicode::unpackDirectedMessage(frame);
|
||||
qDebug() << "->" << frame << Varicode::unpackDataMessage(frame) << Varicode::unpackDirectedMessage(frame) << Varicode::unpackCompoundMessage(frame);
|
||||
}
|
||||
|
||||
qDebug() << "lines:";
|
||||
@ -6910,14 +6979,6 @@ void MainWindow::on_qtcMacroButton_clicked(){
|
||||
addMessageText(qtc);
|
||||
}
|
||||
|
||||
void MainWindow::on_replyMacroButton_clicked(){
|
||||
QString selectedCall = callsignSelected();
|
||||
if(selectedCall.isEmpty()){
|
||||
return;
|
||||
}
|
||||
addMessageText(selectedCall);
|
||||
}
|
||||
|
||||
void MainWindow::on_qthMacroButton_clicked(){
|
||||
QString qth = m_config.my_qth();
|
||||
if(qth.isEmpty()){
|
||||
@ -6929,20 +6990,6 @@ void MainWindow::on_qthMacroButton_clicked(){
|
||||
addMessageText(qth);
|
||||
}
|
||||
|
||||
void MainWindow::on_snrMacroButton_clicked(){
|
||||
QString selectedCall = callsignSelected();
|
||||
if(selectedCall.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!m_callActivity.contains(selectedCall)){
|
||||
return;
|
||||
}
|
||||
|
||||
auto d = m_callActivity[selectedCall];
|
||||
addMessageText(QString("%1 SNR %2").arg(selectedCall).arg(Varicode::formatSNR(d.snr)));
|
||||
}
|
||||
|
||||
void MainWindow::buildQueryMenu(QMenu * menu){
|
||||
QString call = callsignSelected();
|
||||
if(call.isEmpty()){
|
||||
@ -6951,6 +6998,38 @@ void MainWindow::buildQueryMenu(QMenu * menu){
|
||||
|
||||
bool isAllCall = call == "ALLCALL";
|
||||
|
||||
auto sendReplyAction = menu->addAction("Reply to Callsign");
|
||||
|
||||
connect(sendReplyAction, &QAction::triggered, this, [this](){
|
||||
|
||||
QString selectedCall = callsignSelected();
|
||||
if(selectedCall.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
addMessageText(QString("%1").arg(selectedCall), true);
|
||||
});
|
||||
|
||||
|
||||
auto sendSNRAction = menu->addAction("Send Signal Report");
|
||||
sendSNRAction->setEnabled(m_callActivity.contains(call));
|
||||
connect(sendSNRAction, &QAction::triggered, this, [this](){
|
||||
|
||||
QString selectedCall = callsignSelected();
|
||||
if(selectedCall.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!m_callActivity.contains(selectedCall)){
|
||||
return;
|
||||
}
|
||||
|
||||
auto d = m_callActivity[selectedCall];
|
||||
addMessageText(QString("%1 SNR %2").arg(selectedCall).arg(Varicode::formatSNR(d.snr)), true);
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
auto snrAction = menu->addAction("? - What is my signal report?");
|
||||
|
||||
connect(snrAction, &QAction::triggered, this, [this](){
|
||||
@ -8080,12 +8159,10 @@ void MainWindow::updateButtonDisplay(){
|
||||
bool emptyQTH = m_config.my_qth().isEmpty() && m_config.my_grid().isEmpty();
|
||||
|
||||
ui->cqMacroButton->setDisabled(isTransmitting);
|
||||
ui->replyMacroButton->setDisabled(isTransmitting || emptyCallsign);
|
||||
ui->qtcMacroButton->setDisabled(isTransmitting || emptyQTC);
|
||||
ui->qthMacroButton->setDisabled(isTransmitting || emptyQTH);
|
||||
ui->snrMacroButton->setDisabled(isTransmitting || emptyCallsign);
|
||||
ui->queryButton->setDisabled(isTransmitting || emptyCallsign);
|
||||
ui->macrosMacroButton->setDisabled(isTransmitting);
|
||||
ui->queryButton->setDisabled(isTransmitting || emptyCallsign);
|
||||
}
|
||||
|
||||
QString MainWindow::callsignSelected(){
|
||||
@ -8356,7 +8433,7 @@ void MainWindow::displayActivity(bool force){
|
||||
}
|
||||
|
||||
// TODO: jsherer - check to make sure we haven't replied to their allcall recently (in the past beacon interval)
|
||||
if(isAllCall && m_txAllcallCommandCache.contains(d.from) && m_txAllcallCommandCache[d.from]->secsTo(QDateTime::currentDateTimeUtc())/60 < m_config.beacon()){
|
||||
if(isAllCall && m_txAllcallCommandCache.contains(Radio::base_callsign(d.from)) && m_txAllcallCommandCache[Radio::base_callsign(d.from)]->secsTo(QDateTime::currentDateTimeUtc())/60 < m_config.beacon()){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -8365,7 +8442,7 @@ void MainWindow::displayActivity(bool force){
|
||||
|
||||
// SNR
|
||||
if(d.cmd == "?"){
|
||||
reply = QString("%1 SNR %2").arg(d.from).arg(Varicode::formatSNR(d.snr));
|
||||
reply = QString("%1 SNR %2").arg(Radio::base_callsign(d.from)).arg(Varicode::formatSNR(d.snr));
|
||||
}
|
||||
// QTH
|
||||
else if(d.cmd == "@" && !isAllCall){
|
||||
@ -8378,11 +8455,11 @@ void MainWindow::displayActivity(bool force){
|
||||
qth = grid;
|
||||
}
|
||||
|
||||
reply = QString("%1 %2").arg(d.from).arg(qth);
|
||||
reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(qth);
|
||||
}
|
||||
// STATION MESSAGE
|
||||
else if(d.cmd == "&" && !isAllCall){
|
||||
reply = QString("%1 %2").arg(d.from).arg(m_config.my_station());
|
||||
reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(m_config.my_station());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@ -8395,7 +8472,7 @@ void MainWindow::displayActivity(bool force){
|
||||
// if we're responding via allcall, pick a different frequency and mark it in the cache.
|
||||
if(d.to == "ALLCALL"){
|
||||
f = findFreeFreqOffset(qMax(0, f-100), qMin(f+100, 2500), 50);
|
||||
m_txAllcallCommandCache.insert(d.from, new QDateTime(QDateTime::currentDateTimeUtc()), 25);
|
||||
m_txAllcallCommandCache.insert(Radio::base_callsign(d.from), new QDateTime(QDateTime::currentDateTimeUtc()), 25);
|
||||
}
|
||||
|
||||
processed = true;
|
||||
|
@ -121,6 +121,7 @@ public slots:
|
||||
void msgAvgDecode2();
|
||||
void fastPick(int x0, int x1, int y);
|
||||
|
||||
QString lookupCallInCompoundCache(QString const &call);
|
||||
void clearActivity();
|
||||
int logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block=-1);
|
||||
void addMessageText(QString text, bool clear=false);
|
||||
@ -238,9 +239,7 @@ private slots:
|
||||
void on_clearAction_triggered(QObject * sender);
|
||||
void on_cqMacroButton_clicked();
|
||||
void on_qtcMacroButton_clicked();
|
||||
void on_replyMacroButton_clicked();
|
||||
void on_qthMacroButton_clicked();
|
||||
void on_snrMacroButton_clicked();
|
||||
void buildQueryMenu(QMenu *);
|
||||
void on_queryButton_pressed();
|
||||
void on_macrosMacroButton_pressed();
|
||||
@ -684,6 +683,7 @@ private:
|
||||
QQueue<QString> m_txFrameQueue;
|
||||
QQueue<RXDetail> m_rxFrameQueue;
|
||||
QQueue<CommandDetail> m_rxCommandQueue;
|
||||
QMap<QString, QString> m_compoundCallCache; // base callsign -> compound callsign
|
||||
QCache<QString, QDateTime> m_txAllcallCommandCache; // callsign -> last tx
|
||||
QCache<int, QDateTime> m_rxRecentCache; // freq -> last rx
|
||||
QCache<int, QDateTime> m_rxDirectedCache; // freq -> last directed rx
|
||||
|
Loading…
Reference in New Issue
Block a user