diff --git a/Configuration.cpp b/Configuration.cpp
index 301f42a..d220f82 100644
--- a/Configuration.cpp
+++ b/Configuration.cpp
@@ -769,7 +769,7 @@ bool Configuration::clear_DX () const {return m_->clear_DX_;}
bool Configuration::miles () const {return m_->miles_;}
bool Configuration::quick_call () const {return m_->quick_call_;}
bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;}
-int Configuration::beacon () const {return m_->beacon_;}
+int Configuration::beacon () const {return qMax(10, qMin(m_->beacon_, 1440));}
int Configuration::watchdog () const {return m_->watchdog_;}
bool Configuration::TX_messages () const {return m_->TX_messages_;}
bool Configuration::enable_VHF_features () const {return m_->enable_VHF_features_;}
diff --git a/Configuration.ui b/Configuration.ui
index 884eb97..e6ce461 100644
--- a/Configuration.ui
+++ b/Configuration.ui
@@ -530,10 +530,10 @@
- 15
+ 10
- 60
+ 1440
1
diff --git a/mainwindow.cpp b/mainwindow.cpp
index 1e2cda8..04fcc35 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -904,7 +904,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(&TxAgainTimer, SIGNAL(timeout()), this, SLOT(TxAgain()));
beaconTimer.setSingleShot(false);
- connect(&beaconTimer, &QTimer::timeout, this, &MainWindow::checkBacon);
+ connect(&beaconTimer, &QTimer::timeout, this, &MainWindow::checkBeacon);
connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this,
SLOT(setFreq4(int,int)));
@@ -3719,7 +3719,18 @@ void MainWindow::readFromStdout() //readFromStdout
cd.bits = decodedtext.bits();
if(decodedtext.isBeacon()){
- logCallActivity(cd, true);
+ // convert BEACON to a directed command and process...
+ CommandDetail d = {};
+ d.from = cd.call;
+ d.to = "ALLCALL";
+ d.cmd = " BEACON";
+ d.snr = cd.snr;
+ d.bits = cd.bits;
+ d.extra = cd.grid;
+ d.freq = cd.freq;
+ d.utcTimestamp = cd.utcTimestamp;
+ m_rxCommandQueue.append(d);
+
} else {
qDebug() << "buffering compound call" << cd.call << cd.bits;
m_messageBuffer[cd.freq/10*10].compound.append(cd);
@@ -6200,6 +6211,11 @@ void MainWindow::enqueueMessage(int priority, QString message, int freq, Callbac
);
}
+void MainWindow::enqueueBeacon(QString message){
+ m_txBeaconQueue.enqueue(message);
+ scheduleBeacon(true);
+}
+
void MainWindow::resetMessage(){
resetMessageUI();
resetMessageTransmitQueue();
@@ -6356,13 +6372,19 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
bool hasData = false;
// remove our callsign from the start of the line...
- if(line.startsWith(mycall + ":")){
+ if(line.startsWith(mycall + ":") || line.startsWith(mycall + " ")){
line = lstrip(line.mid(mycall.length() + 1));
}
- if(line.startsWith(basecall + ":")){
+ if(line.startsWith(basecall + ":") || line.startsWith(basecall + " ")){
line = lstrip(line.mid(basecall.length() + 1));
}
+ // remove trailing whitespace as long as there are characters left afterwards
+ auto rline = rstrip(line);
+ if(!rline.isEmpty()){
+ line = rline;
+ }
+
#if AUTO_PREPEND_DIRECTED
// see if we need to prepend the directed call to the line...
// if we have a selected call and the text doesn't start with that call...
@@ -6650,7 +6672,7 @@ bool MainWindow::prepareNextMessageFrame()
if(ui->beaconButton->isChecked()){
// bump beacon
- scheduleBacon(false);
+ scheduleBeacon(false);
}
return true;
@@ -6711,7 +6733,7 @@ int MainWindow::findFreeFreqOffset(int fmin, int fmax, int bw){
}
// scheduleBeacon
-void MainWindow::scheduleBacon(bool first){
+void MainWindow::scheduleBeacon(bool first){
auto timestamp = DriftingDateTime::currentDateTimeUtc();
auto orig = timestamp;
@@ -6742,7 +6764,7 @@ void MainWindow::scheduleBacon(bool first){
}
// pauseBeacon
-void MainWindow::pauseBacon(){
+void MainWindow::pauseBeacon(){
ui->beaconButton->setChecked(false);
m_nextBeaconPaused = true;
@@ -6752,40 +6774,52 @@ void MainWindow::pauseBacon(){
}
// checkBeacon
-void MainWindow::checkBacon(){
+void MainWindow::checkBeacon(){
if(!ui->beaconButton->isChecked()){
return;
}
- if(DriftingDateTime::currentDateTimeUtc().secsTo(m_nextBeacon) > 5){
+ auto secondsUntilBeacon = DriftingDateTime::currentDateTimeUtc().secsTo(m_nextBeacon);
+ if(secondsUntilBeacon > 5 && m_txBeaconQueue.isEmpty()){
return;
}
if(m_nextBeaconQueued){
return;
}
- prepareBacon();
+ prepareBeacon();
}
// prepareBeacon
-void MainWindow::prepareBacon(){
+void MainWindow::prepareBeacon(){
QStringList lines;
QString mycall = m_config.my_callsign();
QString mygrid = m_config.my_grid().left(4);
// FT8Call Style
- lines.append(QString("%1: BEACON %2").arg(mycall).arg(mygrid));
-
- bool shouldTransmitTwoBeacons = true;
- if(shouldTransmitTwoBeacons){
+ if(m_txBeaconQueue.isEmpty()){
lines.append(QString("%1: BEACON %2").arg(mycall).arg(mygrid));
+
+ bool shouldTransmitTwoBeacons = true;
+ if(shouldTransmitTwoBeacons){
+ lines.append(QString("%1: BEACON %2").arg(mycall).arg(mygrid));
+ }
+ } else {
+ while(!m_txBeaconQueue.isEmpty() && lines.length() < 2){
+ lines.append(m_txBeaconQueue.dequeue());
+ }
}
// Choose a beacon frequency
auto f = findFreeFreqOffset(500, 1000, 50);
+ auto text = lines.join(QChar('\n'));
+ if(text.isEmpty()){
+ return;
+ }
+
// Queue the beacon
- enqueueMessage(PriorityLow, lines.join(QChar('\n')), f, [this](){
+ enqueueMessage(PriorityLow, text, f, [this](){
m_nextBeaconQueued = false;
});
@@ -7616,6 +7650,8 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
// for now, we're going to omit displaying the call...delete this if we want the other functionality
call = "";
+ auto grid = m_config.my_grid();
+
auto callAction = menu->addAction(QString("Send a directed message to selected callsign"));
connect(callAction, &QAction::triggered, this, [this](){
@@ -7729,17 +7765,6 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
if(m_config.transmit_directed()) toggleTx(true);
});
- auto qsoQueryAction = menu->addAction(QString("%1 QSO [CALLSIGN]? - Can you communicate directly with [CALLSIGN]?").arg(call).trimmed());
- connect(qsoQueryAction, &QAction::triggered, this, [this](){
-
- QString selectedCall = callsignSelected();
- if(selectedCall.isEmpty()){
- return;
- }
-
- addMessageText(QString("%1 QSO [CALLSIGN]?").arg(selectedCall), true, true);
- });
-
auto hashAction = menu->addAction(QString("%1#[MESSAGE] - Please ACK if you receive this message in its entirety").arg(call).trimmed());
hashAction->setDisabled(isAllCall);
connect(hashAction, &QAction::triggered, this, [this](){
@@ -7778,10 +7803,22 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
addMessageText(QString("%1>[MESSAGE]").arg(selectedCall), true, true);
});
+ auto qsoQueryAction = menu->addAction(QString("%1 BEACON REQ [CALLSIGN]? - Please acknowledge you can communicate directly with [CALLSIGN]").arg(call).trimmed());
+ connect(qsoQueryAction, &QAction::triggered, this, [this](){
+
+ QString selectedCall = callsignSelected();
+ if(selectedCall.isEmpty()){
+ return;
+ }
+
+ addMessageText(QString("%1 BEACON REQ [CALLSIGN]?").arg(selectedCall), true, true);
+ });
+
menu->addSeparator();
bool emptyQTC = m_config.my_station().isEmpty();
- bool emptyQTH = m_config.my_qth().isEmpty() && m_config.my_grid().isEmpty();
+ bool emptyQTH = m_config.my_qth().isEmpty();
+ bool emptyGrid = m_config.my_grid().isEmpty();
auto qtcAction = menu->addAction(QString("%1 QTC - Send my station message").arg(call).trimmed());
qtcAction->setDisabled(emptyQTC);
@@ -7811,8 +7848,9 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
if(m_config.transmit_directed()) toggleTx(true);
});
- auto grid = m_config.my_grid();
+
auto gridAction = menu->addAction(QString("%1 GRID %2 - Send my current station Maidenhead grid locator").arg(call).arg(grid).trimmed());
+ gridAction->setDisabled(emptyGrid);
connect(gridAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
@@ -8294,9 +8332,9 @@ void MainWindow::on_pbT2R_clicked()
void MainWindow::on_beaconButton_clicked()
{
if(ui->beaconButton->isChecked()){
- scheduleBacon(true);
+ scheduleBeacon(true);
} else {
- pauseBacon();
+ pauseBeacon();
}
}
@@ -9653,17 +9691,11 @@ void MainWindow::processCommandActivity() {
// construct a reply, if needed
QString reply;
+ int priority = PriorityNormal;
+ int freq = -1;
// QUERIED SNR
- if (d.cmd == "?") {
- // do not respond to allcall ? if:
- // 1. we recently responded to one
- // 2. or, we are in a directed qso...(i.e., we have a callsign selected that isn't ALLCALL)
- auto selectedCall = callsignSelected();
- if(isAllCall && !selectedCall.isEmpty() && selectedCall != "ALLCALL" && selectedCall != d.from){
- continue;
- }
-
+ if (d.cmd == "?" && !isAllCall) {
reply = QString("%1 SNR %2").arg(d.from).arg(Varicode::formatSNR(d.snr));
}
@@ -9810,8 +9842,22 @@ void MainWindow::processCommandActivity() {
reply = m_lastTxMessage;
}
- // PROCESS BUFFERED QSO QUERY
- else if (d.cmd == " QSO"){
+ // PROCESS BEACON
+ else if (d.cmd == " BEACON" && ui->beaconButton->isChecked()){
+ reply = QString("%1 BEACON ACK %2").arg(d.from).arg(Varicode::formatSNR(d.snr));
+
+ enqueueBeacon(reply);
+
+ if(isAllCall){
+ // since all beacons are technically ALLCALL, let's bump the allcall cache here...
+ m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 25);
+ }
+
+ continue;
+ }
+
+ // PROCESS BUFFERED BEACON REQ QUERY
+ else if (d.cmd == " BEACON REQ" && ui->beaconButton->isChecked()){
auto who = d.text;
if(who.isEmpty()){
continue;
@@ -9831,11 +9877,22 @@ void MainWindow::processCommandActivity() {
}
if(baseCall == cd.call || baseCall == Radio::base_callsign(cd.call)){
- auto r = QString("%1 ACK %2 %3 (%4)").arg(d.from).arg(cd.call).arg(Varicode::formatSNR(cd.snr)).arg(since(cd.utcTimestamp));
+ auto r = QString("%1 BEACON ACK %2").arg(cd.call).arg(Varicode::formatSNR(cd.snr));
replies.append(r);
}
}
reply = replies.join("\n");
+
+ if(!reply.isEmpty()){
+ enqueueBeacon(reply);
+
+ if(isAllCall){
+ // since all beacons are technically ALLCALL, let's bump the allcall cache here...
+ m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 25);
+ }
+ }
+
+ continue;
}
// PROCESS BUFFERED APRS:
@@ -9882,8 +9939,8 @@ void MainWindow::processCommandActivity() {
continue;
}
- // do not queue ALLCALL replies if auto-reply is not checked
- if(!ui->autoReplyButton->isChecked() && isAllCall){
+ // do not queue ALLCALL replies if auto-reply is not checked or it's a beacon reply
+ if(!ui->autoReplyButton->isChecked() && isAllCall && !d.cmd.contains("BEACON")){
continue;
}
@@ -9896,7 +9953,7 @@ void MainWindow::processCommandActivity() {
// unless, this is an allcall, to which we should be responding on a clear frequency offset
// we always want to make sure that the directed cache has been updated at this point so we have the
// most information available to make a frequency selection.
- enqueueMessage(PriorityNormal, reply, -1, nullptr);
+ enqueueMessage(priority, reply, freq, nullptr);
}
}
diff --git a/mainwindow.h b/mainwindow.h
index f91b3cd..e934761 100644
--- a/mainwindow.h
+++ b/mainwindow.h
@@ -145,6 +145,7 @@ public slots:
void prependMessageText(QString text);
void addMessageText(QString text, bool clear=false, bool selectFirstPlaceholder=false);
void enqueueMessage(int priority, QString message, int freq, Callback c);
+ void enqueueBeacon(QString message);
void resetMessage();
void resetMessageUI();
void restoreMessage();
@@ -297,10 +298,10 @@ private slots:
bool prepareNextMessageFrame();
bool isFreqOffsetFree(int f, int bw);
int findFreeFreqOffset(int fmin, int fmax, int bw);
- void scheduleBacon(bool first=false);
- void pauseBacon();
- void checkBacon();
- void prepareBacon();
+ void scheduleBeacon(bool first=false);
+ void pauseBeacon();
+ void checkBeacon();
+ void prepareBeacon();
QString calculateDistance(QString const& grid, int *pDistance=nullptr);
void on_driftSpinBox_valueChanged(int n);
void on_driftSyncButton_clicked();
@@ -772,6 +773,7 @@ private:
QMap> m_bandActivity; // freq -> [(text, last timestamp), ...]
QMap m_messageBuffer; // freq -> (cmd, [frames, ...])
QMap m_callActivity; // call -> (last freq, last timestamp)
+ QQueue m_txBeaconQueue; // beacon frames to be sent
QMap> m_callActivityCache; // band -> call activity
QMap>> m_bandActivityCache; // band -> band activity
diff --git a/varicode.cpp b/varicode.cpp
index 537b64a..782db8a 100644
--- a/varicode.cpp
+++ b/varicode.cpp
@@ -52,16 +52,18 @@ QMap directed_cmds = {
// {"=", 9 }, // unused
// {"/", 10 }, // unused
- // {"/", 11 }, // unused
- // {"/", 12 }, // unused
- // {"/", 13 }, // unused
// directed responses
- {" QSO", 13 }, // can you communicate with? i can communicate with
+ {" BEACON", -1 }, // this is my beacon (unused except for faux processing of beacons as directed commands)
+ {" BEACON ACK", 12 }, // i received your beacon at this snr
+ {" BEACON REQ", 13 }, // can you transmit a beacon to callsign?
+
{" APRS:", 14 }, // send an aprs packet
+
{" GRID", 15 }, // this is my current grid locator
{" QTC", 16 }, // this is my qtc message
{" QTH", 17 }, // this is my qth message
+
{" FB", 18 }, // fine business
{" HW CPY?", 19 }, // how do you copy?
{" HEARING", 20 }, // i am hearing the following stations
@@ -78,14 +80,14 @@ QMap directed_cmds = {
{" ", 31 }, // send freetext
};
-QSet allowed_cmds = {0, 1, 2, 3, 4, 5, /*6,*/ /*7,*/ 8, /*...*/ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, /*24,*/ 25, 26, 27, 28, 29, 30, 31};
+QSet allowed_cmds = {-1, 0, 1, 2, 3, 4, 5, /*6,*/ /*7,*/ 8, /*...*/ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, /*24,*/ 25, 26, 27, 28, 29, 30, 31};
-QSet buffered_cmds = {5, /*6,*/ /*7,*/ 8, 13, 14, 15};
+QSet buffered_cmds = {3, 5, /*6,*/ /*7,*/ 8, 13, 14, 15};
+
+QSet snr_cmds = {12, 25};
QMap checksum_cmds = {
{ 5, 16 },
- /*{ 6, 16 },*/
- /*{ 7, 16 },*/
{ 8, 32 },
{ 13, 16 },
{ 14, 16 },
@@ -93,10 +95,10 @@ QMap checksum_cmds = {
};
QString callsign_pattern = QString("(?[A-Z0-9/]+)");
-QString optional_cmd_pattern = QString("(?\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|APRS[:]|QSO|[?@&$%#^> ]))?");
+QString optional_cmd_pattern = QString("(?\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|APRS[:]|BEACON (ACK|REQ)|[?@&$%#^> ]))?");
QString optional_grid_pattern = QString("(?\\s?[A-R]{2}[0-9]{2})?");
QString optional_extended_grid_pattern = QString("^(?\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
-QString optional_num_pattern = QString("(?(?<=SNR|HEARING)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
+QString optional_num_pattern = QString("(?(?<=SNR|HEARING|BEACON ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
QRegularExpression directed_re("^" +
callsign_pattern +
@@ -1084,11 +1086,13 @@ quint8 Varicode::packCmd(quint8 cmd, quint8 num, bool *pPackedNum){
// if cmd == snr
quint8 value = 0;
- if(cmd == directed_cmds[" SNR"]){
+ auto cmdStr = directed_cmds.key(cmd);
+ if(Varicode::isSNRCommand(cmdStr)){
// 8 bits - 1 bit flag + 1 bit type + 6 bit number
// [1][X][6]
// X = 0 == SNR
- value = (1 << 1) << 6;
+ // X = 1 == BEACON ACK
+ value = ((1 << 1) | (int)(cmdStr == " BEACON ACK")) << 6;
value = value + (num & ((1<<6)-1));
if(pPackedNum) *pPackedNum = true;
} else {
@@ -1103,13 +1107,22 @@ quint8 Varicode::unpackCmd(quint8 value, quint8 *pNum){
// if the first bit is 1, this is an SNR with a number encoded in the lower 6 bits
if(value & (1<<7)){
if(pNum) *pNum = value & ((1<<6)-1);
- return directed_cmds[" SNR"];
+
+ auto cmd = directed_cmds[" SNR"];
+ if(value & (1<<6)){
+ cmd = directed_cmds[" BEACON ACK"];
+ }
+ return cmd;
} else {
if(pNum) *pNum = 0;
return value & ((1<<7)-1);
}
}
+bool Varicode::isSNRCommand(const QString &cmd){
+ return directed_cmds.contains(cmd) && snr_cmds.contains(directed_cmds[cmd]);
+}
+
bool Varicode::isCommandAllowed(const QString &cmd){
return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
}
@@ -1288,10 +1301,11 @@ QStringList Varicode::unpackCompoundMessage(const QString &text, quint8 *pType,
} else if (nusergrid <= extra && extra < nmaxgrid) {
quint8 num = 0;
auto cmd = Varicode::unpackCmd(extra - nusergrid, &num);
+ auto cmdStr = directed_cmds.key(cmd);
- unpacked.append(directed_cmds.key(cmd));
+ unpacked.append(cmdStr);
- if(cmd == directed_cmds[" SNR"]){
+ if(Varicode::isSNRCommand(cmdStr)){
unpacked.append(Varicode::formatSNR(num - 31));
}
}
@@ -1498,8 +1512,7 @@ QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
unpacked.append(cmd);
if(extra != 0){
- // TODO: jsherer - should we decide which format to use on the command, or something else?
- if(packed_cmd == directed_cmds[" SNR"]) {
+ if(Varicode::isSNRCommand(cmd)){
unpacked.append(Varicode::formatSNR((int)extra-31));
} else {
unpacked.append(QString("%1").arg(extra-31));
diff --git a/varicode.h b/varicode.h
index ffa4605..8ad340f 100644
--- a/varicode.h
+++ b/varicode.h
@@ -123,6 +123,7 @@ public:
static quint8 packCmd(quint8 cmd, quint8 num, bool *pPackedNum);
static quint8 unpackCmd(quint8 value, quint8 *pNum);
+ static bool isSNRCommand(const QString &cmd);
static bool isCommandAllowed(const QString &cmd);
static bool isCommandBuffered(const QString &cmd);
static int isCommandChecksumed(const QString &cmd);