diff --git a/mainwindow.cpp b/mainwindow.cpp index dea1b7d..510c2a7 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -5728,7 +5728,7 @@ void MainWindow::addMessageText(QString text, bool clear, bool selectFirstPlaceh c.insertText(text); if(selectFirstPlaceholder){ - auto match = QRegularExpression("(\\[.+\\])").match(ui->extFreeTextMsgEdit->toPlainText()); + auto match = QRegularExpression("(\\[[^\\]]+\\])").match(ui->extFreeTextMsgEdit->toPlainText()); if(match.hasMatch()){ c.setPosition(match.capturedStart()); c.setPosition(match.capturedEnd(), QTextCursor::KeepAnchor); @@ -6949,8 +6949,8 @@ void MainWindow::sendHeartbeat(){ processTxQueue(); } -void MainWindow::sendHeartbeatAck(QString to, int snr){ - auto message = QString("%1 ACK %2").arg(to).arg(Varicode::formatSNR(snr)); +void MainWindow::sendHeartbeatAck(QString to, int snr, QString extra){ + auto message = QString("%1 ACK %2 %3").arg(to).arg(Varicode::formatSNR(snr)).arg(extra).trimmed(); auto f = m_config.heartbeat_anywhere() ? -1 : findFreeFreqOffset(500, 1000, 50); @@ -7357,7 +7357,7 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){ }); #endif - auto alertAction = menu->addAction(QString("%1>[MESSAGE] - Please save this message or relay it to its destination").arg(call).trimmed()); + auto alertAction = menu->addAction(QString("%1>[MESSAGE] - Please relay this message to its destination").arg(call).trimmed()); alertAction->setDisabled(isAllCall); connect(alertAction, &QAction::triggered, this, [this](){ @@ -7509,19 +7509,6 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){ if(m_config.transmit_directed()) toggleTx(true); }); - auto tuAction = menu->addAction(QString("%1 TU - Thank You").arg(call).trimmed()); - connect(tuAction, &QAction::triggered, this, [this](){ - - QString selectedCall = callsignSelected(); - if(selectedCall.isEmpty()){ - return; - } - - addMessageText(QString("%1 TU").arg(selectedCall), true); - - if(m_config.transmit_directed()) toggleTx(true); - }); - auto sevenThreeAction = menu->addAction(QString("%1 73 - I send my best regards").arg(call).trimmed()); connect(sevenThreeAction, &QAction::triggered, this, [this](){ @@ -9696,10 +9683,13 @@ void MainWindow::processCommandActivity() { c.movePosition(QTextCursor::StartOfBlock); c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); qDebug() << "should display directed message, erasing last rx activity line..." << c.selectedText(); + c.removeSelectedText(); c.deletePreviousChar(); c.deletePreviousChar(); + /* c.deleteChar(); c.deleteChar(); + */ } // log it to the display! @@ -9841,14 +9831,9 @@ void MainWindow::processCommandActivity() { // otherwise, as long as we're not an ACK...alert the user and either send an ACK or Message } else if(!d.text.startsWith("ACK")) { - QStringList calls; - QString callDePattern = {R"(\sDE\s(?\b(?[A-Z0-9]{1,4}\/)?(?([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?\/[A-Z0-9]{1,4})?)\b)"}; - QRegularExpression re(callDePattern); - auto iter = re.globalMatch(text); - while(iter.hasNext()){ - auto match = iter.next(); - calls.prepend(match.captured("callsign")); - } + + // parse out the callsign path + auto calls = parseRelayPathCallsigns(d.from, d.text); // put these third party calls in the heard list foreach(auto call, calls){ @@ -9862,7 +9847,6 @@ void MainWindow::processCommandActivity() { logCallActivity(cd, false); } - calls.prepend(d.from); d.relayPath = calls.join('>'); reply = QString("%1 ACK").arg(d.relayPath); @@ -9883,7 +9867,7 @@ void MainWindow::processCommandActivity() { rd.bits = d.bits; rd.cmd = first; rd.freq = d.freq; - rd.from = d.relayPath; + rd.from = d.relayPath; // is this correct? rd.text = d.text; rd.to = d.to; rd.utcTimestamp = d.utcTimestamp; @@ -9893,12 +9877,16 @@ void MainWindow::processCommandActivity() { } } +#if STORE_RELAY_MSGS_TO_INBOX // if we make it here, this is a message addCommandToMyInbox(d); +#endif +#if ALERT_ON_NEW_MSG QTimer::singleShot(500, this, [this, d](){ MessageBox::information_message(this, QString("A new message was received at %1 UTC from %2").arg(d.utcTimestamp.time().toString()).arg(d.from)); }); +#endif } } @@ -9943,19 +9931,69 @@ void MainWindow::processCommandActivity() { // PROCESS ACTIVE HEARTBEAT // if we have auto reply enabled and we are heartbeating and selcall is not enabled else if (d.cmd == " HB" && ui->autoReplyButton->isChecked() && ui->hbMacroButton->isChecked() && m_hbInterval > 0){ - sendHeartbeatAck(d.from, d.snr); + + // check to see if we have a message for a station who is heartbeating + QString extra; + auto mid = getNextMessageIdForCallsign(d.from); + if(mid != -1){ + extra = QString("MSG ID %1").arg(mid); + } + + sendHeartbeatAck(d.from, d.snr, extra); if(isAllCall){ // since all pings are technically @ALLCALL, let's bump the allcall cache here... m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 5); } + continue; + } + + // PROCESS MSG + else if (d.cmd == " MSG"){ + + auto segs = d.text.split(" "); + if(segs.isEmpty()){ + continue; + } + + bool ok = false; + auto mid = segs.first().toInt(&ok); + if(!ok){ + continue; + } + + segs.removeFirst(); + if(segs.isEmpty()){ + continue; + } + + auto text = segs.join(" "); + + qDebug() << "adding message" << mid << "to inbox" << text; + + auto calls = parseRelayPathCallsigns(d.from, text); + + d.cmd = " MSG "; + d.relayPath = calls.join(">"); + d.text = text; + + addCommandToMyInbox(d); + + // make sure this is explicit + continue; + } + + // PROCESS ACKS + else if (d.cmd == " ACK"){ + qDebug() << "skipping incoming ack" << d.text; + // make sure this is explicit continue; } // PROCESS BUFFERED CMD - else if (d.cmd == " CMD" && ui->autoReplyButton->isChecked()){ + else if (d.cmd == " CMD"){ qDebug() << "skipping incoming command" << d.text; // make sure this is explicit @@ -9963,7 +10001,7 @@ void MainWindow::processCommandActivity() { } // PROCESS BUFFERED QUERY - else if (d.cmd == " QUERY" && ui->autoReplyButton->isChecked()){ + else if (d.cmd == " QUERY"){ auto who = d.from; QStringList segs = d.text.split(" "); @@ -10002,7 +10040,12 @@ void MainWindow::processCommandActivity() { continue; } - reply = QString("%1>MSG %2 %3 DE %4"); + // mark as delivered (so subsequent HBs and QUERY MSGS don't receive this message) + msg.setType("DELIVERED"); + inbox.set(mid, msg); + + // and reply + reply = QString("%1 MSG %2 %3 DE %4"); reply = reply.arg(who); reply = reply.arg(mid); reply = reply.arg(text); @@ -10014,21 +10057,11 @@ void MainWindow::processCommandActivity() { else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){ auto who = d.from; - auto inbox = Inbox(inboxPath()); - if(!inbox.open()){ - continue; - } - // if this is an allcall or a directed call, check to see if we have a stored message for user. // we reply yes if the user would be able to retreive a stored message - auto v = inbox.values("STORE", "$.params.TO", who, 0, 10); - foreach(auto pair, v){ - auto params = pair.second.params(); - auto text = params.value("TEXT").toString().trimmed(); - if(!text.isEmpty()){ - reply = QString("%1 YES MSG %2").arg(who).arg(pair.first); - break; - } + auto mid = getNextMessageIdForCallsign(who); + if(mid != -1){ + reply = QString("%1 YES MSG ID %2").arg(who).arg(mid); } // if this is not an allcall and we have no messages, reply no. @@ -10193,47 +10226,81 @@ void MainWindow::refreshInboxCounts(){ } } -void MainWindow::addCommandToMyInbox(CommandDetail d){ +int MainWindow::addCommandToMyInbox(CommandDetail d){ // local cache for inbox count m_rxInboxCountCache[d.from] = m_rxInboxCountCache.value(d.from, 0) + 1; // add it to my unread inbox - addCommandToInboxStorage("UNREAD", d); + return addCommandToInboxStorage("UNREAD", d); } -void MainWindow::addCommandToInboxStorage(QString type, CommandDetail d){ +int MainWindow::addCommandToInboxStorage(QString type, CommandDetail d){ // inbox: auto inbox = Inbox(inboxPath()); - if(inbox.open()){ - auto df = dialFrequency(); - - QMap v = { - {"UTC", QVariant(d.utcTimestamp.toString("yyyy-MM-dd hh:mm:ss"))}, - {"TO", QVariant(d.to)}, - {"FROM", QVariant(d.from)}, - {"PATH", QVariant(d.relayPath)}, - {"DIAL", QVariant((quint64)df)}, - {"TDRIFT", QVariant(d.tdrift)}, - {"OFFSET", QVariant(d.freq)}, - {"CMD", QVariant(d.cmd)}, - {"SNR", QVariant(d.snr)}, - }; - - if(!d.grid.isEmpty()){ - v["GRID"] = QVariant(d.grid); - } - - if(!d.extra.isEmpty()){ - v["EXTRA"] = QVariant(d.extra); - } - - if(!d.text.isEmpty()){ - v["TEXT"] = QVariant(d.text); - } - - auto m = Message(type, "", v); - inbox.append(m); + if(!inbox.open()){ + return -1; } + + auto df = dialFrequency(); + + QMap v = { + {"UTC", QVariant(d.utcTimestamp.toString("yyyy-MM-dd hh:mm:ss"))}, + {"TO", QVariant(d.to)}, + {"FROM", QVariant(d.from)}, + {"PATH", QVariant(d.relayPath)}, + {"DIAL", QVariant((quint64)df)}, + {"TDRIFT", QVariant(d.tdrift)}, + {"OFFSET", QVariant(d.freq)}, + {"CMD", QVariant(d.cmd)}, + {"SNR", QVariant(d.snr)}, + }; + + if(!d.grid.isEmpty()){ + v["GRID"] = QVariant(d.grid); + } + + if(!d.extra.isEmpty()){ + v["EXTRA"] = QVariant(d.extra); + } + + if(!d.text.isEmpty()){ + v["TEXT"] = QVariant(d.text); + } + + auto m = Message(type, "", v); + + return inbox.append(m); +} + +int MainWindow::getNextMessageIdForCallsign(QString callsign){ + auto inbox = Inbox(inboxPath()); + if(!inbox.open()){ + return -1; + } + + auto v = inbox.values("STORE", "$.params.TO", callsign, 0, 10); + foreach(auto pair, v){ + auto params = pair.second.params(); + auto text = params.value("TEXT").toString().trimmed(); + if(!text.isEmpty()){ + return pair.first; + } + } + + return -1; +} + +QStringList MainWindow::parseRelayPathCallsigns(QString from, QString text){ + QStringList calls; + QString callDePattern = {R"(\sDE\s(?\b(?[A-Z0-9]{1,4}\/)?(?([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?\/[A-Z0-9]{1,4})?)\b)"}; + QRegularExpression re(callDePattern); + auto iter = re.globalMatch(text); + while(iter.hasNext()){ + auto match = iter.next(); + calls.prepend(match.captured("callsign")); + } + calls.prepend(from); + return calls; } void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QString cmd){ diff --git a/mainwindow.h b/mainwindow.h index 65b9309..1e69ed3 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -284,7 +284,7 @@ private slots: void buildCQMenu(QMenu *menu); void buildRepeatMenu(QMenu *menu, QPushButton * button, int * interval); void sendHeartbeat(); - void sendHeartbeatAck(QString to, int snr); + void sendHeartbeatAck(QString to, int snr, QString extra); void on_hbMacroButton_toggled(bool checked); void on_hbMacroButton_clicked(); void sendCQ(bool repeat=false); @@ -952,8 +952,10 @@ private: void processCommandActivity(); QString inboxPath(); void refreshInboxCounts(); - void addCommandToMyInbox(CommandDetail d); - void addCommandToInboxStorage(QString type, CommandDetail d); + int addCommandToMyInbox(CommandDetail d); + int addCommandToInboxStorage(QString type, CommandDetail d); + int getNextMessageIdForCallsign(QString callsign); + QStringList parseRelayPathCallsigns(QString from, QString text); void processAlertReplyForCommand(CommandDetail d, QString from, QString cmd); void processSpots(); void processTxQueue(); diff --git a/varicode.cpp b/varicode.cpp index f45285b..34f4e6e 100644 --- a/varicode.cpp +++ b/varicode.cpp @@ -45,73 +45,71 @@ QString alphanumeric = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ /@"}; // callsign QMap directed_cmds = { // any changes here need to be made also in the directed regular xpression for parsing // ?*^&@ + {" HB", -1 }, // this is my heartbeat (unused except for faux processing of HBs as directed commands) - {" SNR?", 0 }, // query snr - {"?", 0 }, // compat + {" SNR?", 0 }, // query snr + {"?", 0 }, // compat - {" QTH?", 1 }, // query qth + //{" ", 1 }, // unused + //{" ", 2 }, // unused - //{" ", 2 }, // unused + {" HEARING?", 3 }, // query station calls heard - {" HEARING?", 3 }, // query station calls heard + {" GRID?", 4 }, // query grid - {" GRID?", 4 }, // query grid + {">", 5 }, // relay message - {">", 5 }, // relay message + {" STATUS?", 6 }, // query idle message - {" STATUS?", 6 }, // query idle message + {" STATUS", 7 }, // this is my status - {" STATUS", 7 }, // this is my status + {" HEARING", 8 }, // these are the stations i'm hearing - {" HEARING", 8 }, // these are the stations i'm hearing + {" MSG", 9 }, // this is a complete message - {" TU", 9 }, // thank you + {" MSG TO:", 10 }, // store message at a station - {" HB", -1 }, // this is my heartbeat (unused except for faux processing of HBs as directed commands) + {" QUERY", 11 }, // generic query - {" MSG TO:", 10 }, // store message at a station + {" QUERY MSGS", 12 }, // do you have any stored messages? - {" QUERY", 11 }, // generic query + {" QUERY CALL", 13 }, // can you transmit a ping to callsign? - {" QUERY MSGS", 12 }, // do you have any stored messages? + {" APRS:", 14 }, // send an aprs packet - {" QUERY CALL", 13 }, // can you transmit a ping to callsign? + {" GRID", 15 }, // this is my current grid locator - {" APRS:", 14 }, // send an aprs packet + {" QTH?", 16 }, // what is your qth message? + {" QTH", 17 }, // this is my qth message - {" GRID", 15 }, // this is my current grid locator + {" FB", 18 }, // fine business + {" HW CPY?", 19 }, // how do you copy? + {" SK", 20 }, // end of contact + {" RR", 21 }, // roger roger - //{" ", 16 }, // unused + {" QSL?", 22 }, // do you copy? + {" QSL", 23 }, // i copy - {" QTH", 17 }, // this is my qth message + {" CMD", 24 }, // command - {" FB", 18 }, // fine business - {" HW CPY?", 19 }, // how do you copy? - {" SK", 20 }, // end of contact - {" RR", 21 }, // roger roger - {" QSL?", 22 }, // do you copy? - {" QSL", 23 }, // i copy - - {" CMD", 24 }, // open ended command - - {" SNR", 25 }, // seen a station at the provided snr - {" NO", 26 }, // negative confirm - {" YES", 27 }, // confirm - {" 73", 28 }, // best regards, end of contact - {" ACK", 29 }, // acknowledge - {" AGN?", 30 }, // repeat message - {" ", 31 }, // send freetext (weird artifact) - {" ", 31 }, // send freetext + {" SNR", 25 }, // seen a station at the provided snr + {" NO", 26 }, // negative confirm + {" YES", 27 }, // confirm + {" 73", 28 }, // best regards, end of contact + {" ACK", 29 }, // acknowledge + {" AGN?", 30 }, // repeat message + {" ", 31 }, // send freetext (weird artifact) + {" ", 31 }, // send freetext }; // commands allowed to be processed -QSet allowed_cmds = {-1, 0, 1, /*2,*/ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 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, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; // commands that result in an autoreply (which can be relayed) -QSet autoreply_cmds = {0, 1, 3, 4, 6, 10, 11, 12, 13, 30}; +QSet autoreply_cmds = {0, 3, 4, 6, 9, 11, 12, 13, 16, 30}; // commands that should be buffered -QSet buffered_cmds = {3, 5, /*6,*/ /*7,*/ 10, 11, 12, 13, 14, 15, 24}; +QSet buffered_cmds = {5, /*6,*/ /*7,*/ 9, 10, 11, 12, 13, 14, 15, 24}; // commands that may include an SNR value QSet snr_cmds = {25, 29}; @@ -119,6 +117,7 @@ QSet snr_cmds = {25, 29}; // commands that are checksummed and their crc size QMap checksum_cmds = { { 5, 16 }, + { 9, 16 }, { 10, 16 }, { 11, 16 }, { 12, 16 }, @@ -129,7 +128,7 @@ QMap checksum_cmds = { }; QString callsign_pattern = QString("(?[@]?[A-Z0-9/]+)"); -QString optional_cmd_pattern = QString("(?\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|QTH[?]|GRID[?]|STATUS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|QTH|GRID|TU)(?=[ ]|$))|[?> ]))?"); +QString optional_cmd_pattern = QString("(?\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|QTH[?]|GRID[?]|STATUS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|QTH|GRID)(?=[ ]|$))|[?> ]))?"); 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|ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");