Working through QUERY MSG [id] and MSG commands to make it easier to work with the inbox

This commit is contained in:
Jordan Sherer 2019-02-01 01:46:01 -05:00
parent e0838a2164
commit da41991171
3 changed files with 188 additions and 120 deletions

View File

@ -5728,7 +5728,7 @@ void MainWindow::addMessageText(QString text, bool clear, bool selectFirstPlaceh
c.insertText(text); c.insertText(text);
if(selectFirstPlaceholder){ if(selectFirstPlaceholder){
auto match = QRegularExpression("(\\[.+\\])").match(ui->extFreeTextMsgEdit->toPlainText()); auto match = QRegularExpression("(\\[[^\\]]+\\])").match(ui->extFreeTextMsgEdit->toPlainText());
if(match.hasMatch()){ if(match.hasMatch()){
c.setPosition(match.capturedStart()); c.setPosition(match.capturedStart());
c.setPosition(match.capturedEnd(), QTextCursor::KeepAnchor); c.setPosition(match.capturedEnd(), QTextCursor::KeepAnchor);
@ -6949,8 +6949,8 @@ void MainWindow::sendHeartbeat(){
processTxQueue(); processTxQueue();
} }
void MainWindow::sendHeartbeatAck(QString to, int snr){ void MainWindow::sendHeartbeatAck(QString to, int snr, QString extra){
auto message = QString("%1 ACK %2").arg(to).arg(Varicode::formatSNR(snr)); 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); auto f = m_config.heartbeat_anywhere() ? -1 : findFreeFreqOffset(500, 1000, 50);
@ -7357,7 +7357,7 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
}); });
#endif #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); alertAction->setDisabled(isAllCall);
connect(alertAction, &QAction::triggered, this, [this](){ connect(alertAction, &QAction::triggered, this, [this](){
@ -7509,19 +7509,6 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
if(m_config.transmit_directed()) toggleTx(true); 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()); auto sevenThreeAction = menu->addAction(QString("%1 73 - I send my best regards").arg(call).trimmed());
connect(sevenThreeAction, &QAction::triggered, this, [this](){ connect(sevenThreeAction, &QAction::triggered, this, [this](){
@ -9696,10 +9683,13 @@ void MainWindow::processCommandActivity() {
c.movePosition(QTextCursor::StartOfBlock); c.movePosition(QTextCursor::StartOfBlock);
c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
qDebug() << "should display directed message, erasing last rx activity line..." << c.selectedText(); qDebug() << "should display directed message, erasing last rx activity line..." << c.selectedText();
c.removeSelectedText();
c.deletePreviousChar(); c.deletePreviousChar();
c.deletePreviousChar(); c.deletePreviousChar();
/*
c.deleteChar(); c.deleteChar();
c.deleteChar(); c.deleteChar();
*/
} }
// log it to the display! // 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 // 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")) { } else if(!d.text.startsWith("ACK")) {
QStringList calls;
QString callDePattern = {R"(\sDE\s(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"}; // parse out the callsign path
QRegularExpression re(callDePattern); auto calls = parseRelayPathCallsigns(d.from, d.text);
auto iter = re.globalMatch(text);
while(iter.hasNext()){
auto match = iter.next();
calls.prepend(match.captured("callsign"));
}
// put these third party calls in the heard list // put these third party calls in the heard list
foreach(auto call, calls){ foreach(auto call, calls){
@ -9862,7 +9847,6 @@ void MainWindow::processCommandActivity() {
logCallActivity(cd, false); logCallActivity(cd, false);
} }
calls.prepend(d.from);
d.relayPath = calls.join('>'); d.relayPath = calls.join('>');
reply = QString("%1 ACK").arg(d.relayPath); reply = QString("%1 ACK").arg(d.relayPath);
@ -9883,7 +9867,7 @@ void MainWindow::processCommandActivity() {
rd.bits = d.bits; rd.bits = d.bits;
rd.cmd = first; rd.cmd = first;
rd.freq = d.freq; rd.freq = d.freq;
rd.from = d.relayPath; rd.from = d.relayPath; // is this correct?
rd.text = d.text; rd.text = d.text;
rd.to = d.to; rd.to = d.to;
rd.utcTimestamp = d.utcTimestamp; 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 // if we make it here, this is a message
addCommandToMyInbox(d); addCommandToMyInbox(d);
#endif
#if ALERT_ON_NEW_MSG
QTimer::singleShot(500, this, [this, d](){ 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)); 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 // PROCESS ACTIVE HEARTBEAT
// if we have auto reply enabled and we are heartbeating and selcall is not enabled // 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){ 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){ if(isAllCall){
// since all pings are technically @ALLCALL, let's bump the allcall cache here... // since all pings are technically @ALLCALL, let's bump the allcall cache here...
m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 5); 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 // make sure this is explicit
continue; continue;
} }
// PROCESS BUFFERED CMD // PROCESS BUFFERED CMD
else if (d.cmd == " CMD" && ui->autoReplyButton->isChecked()){ else if (d.cmd == " CMD"){
qDebug() << "skipping incoming command" << d.text; qDebug() << "skipping incoming command" << d.text;
// make sure this is explicit // make sure this is explicit
@ -9963,7 +10001,7 @@ void MainWindow::processCommandActivity() {
} }
// PROCESS BUFFERED QUERY // PROCESS BUFFERED QUERY
else if (d.cmd == " QUERY" && ui->autoReplyButton->isChecked()){ else if (d.cmd == " QUERY"){
auto who = d.from; auto who = d.from;
QStringList segs = d.text.split(" "); QStringList segs = d.text.split(" ");
@ -10002,7 +10040,12 @@ void MainWindow::processCommandActivity() {
continue; 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(who);
reply = reply.arg(mid); reply = reply.arg(mid);
reply = reply.arg(text); reply = reply.arg(text);
@ -10014,21 +10057,11 @@ void MainWindow::processCommandActivity() {
else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){ else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){
auto who = d.from; 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. // 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 // we reply yes if the user would be able to retreive a stored message
auto v = inbox.values("STORE", "$.params.TO", who, 0, 10); auto mid = getNextMessageIdForCallsign(who);
foreach(auto pair, v){ if(mid != -1){
auto params = pair.second.params(); reply = QString("%1 YES MSG ID %2").arg(who).arg(mid);
auto text = params.value("TEXT").toString().trimmed();
if(!text.isEmpty()){
reply = QString("%1 YES MSG %2").arg(who).arg(pair.first);
break;
}
} }
// if this is not an allcall and we have no messages, reply no. // 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 // local cache for inbox count
m_rxInboxCountCache[d.from] = m_rxInboxCountCache.value(d.from, 0) + 1; m_rxInboxCountCache[d.from] = m_rxInboxCountCache.value(d.from, 0) + 1;
// add it to my unread inbox // 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: // inbox:
auto inbox = Inbox(inboxPath()); auto inbox = Inbox(inboxPath());
if(inbox.open()){ if(!inbox.open()){
auto df = dialFrequency(); return -1;
QMap<QString, QVariant> 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);
} }
auto df = dialFrequency();
QMap<QString, QVariant> 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(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[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){ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QString cmd){

View File

@ -284,7 +284,7 @@ private slots:
void buildCQMenu(QMenu *menu); void buildCQMenu(QMenu *menu);
void buildRepeatMenu(QMenu *menu, QPushButton * button, int * interval); void buildRepeatMenu(QMenu *menu, QPushButton * button, int * interval);
void sendHeartbeat(); 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_toggled(bool checked);
void on_hbMacroButton_clicked(); void on_hbMacroButton_clicked();
void sendCQ(bool repeat=false); void sendCQ(bool repeat=false);
@ -952,8 +952,10 @@ private:
void processCommandActivity(); void processCommandActivity();
QString inboxPath(); QString inboxPath();
void refreshInboxCounts(); void refreshInboxCounts();
void addCommandToMyInbox(CommandDetail d); int addCommandToMyInbox(CommandDetail d);
void addCommandToInboxStorage(QString type, 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 processAlertReplyForCommand(CommandDetail d, QString from, QString cmd);
void processSpots(); void processSpots();
void processTxQueue(); void processTxQueue();

View File

@ -45,73 +45,71 @@ QString alphanumeric = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ /@"}; // callsign
QMap<QString, int> directed_cmds = { QMap<QString, int> directed_cmds = {
// any changes here need to be made also in the directed regular xpression for parsing // 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 {" SNR?", 0 }, // query snr
{"?", 0 }, // compat {"?", 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 {" SNR", 25 }, // seen a station at the provided snr
{" HW CPY?", 19 }, // how do you copy? {" NO", 26 }, // negative confirm
{" SK", 20 }, // end of contact {" YES", 27 }, // confirm
{" RR", 21 }, // roger roger {" 73", 28 }, // best regards, end of contact
{" QSL?", 22 }, // do you copy? {" ACK", 29 }, // acknowledge
{" QSL", 23 }, // i copy {" AGN?", 30 }, // repeat message
{" ", 31 }, // send freetext (weird artifact)
{" CMD", 24 }, // open ended command {" ", 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 // commands allowed to be processed
QSet<int> 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<int> 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) // commands that result in an autoreply (which can be relayed)
QSet<int> autoreply_cmds = {0, 1, 3, 4, 6, 10, 11, 12, 13, 30}; QSet<int> autoreply_cmds = {0, 3, 4, 6, 9, 11, 12, 13, 16, 30};
// commands that should be buffered // commands that should be buffered
QSet<int> buffered_cmds = {3, 5, /*6,*/ /*7,*/ 10, 11, 12, 13, 14, 15, 24}; QSet<int> buffered_cmds = {5, /*6,*/ /*7,*/ 9, 10, 11, 12, 13, 14, 15, 24};
// commands that may include an SNR value // commands that may include an SNR value
QSet<int> snr_cmds = {25, 29}; QSet<int> snr_cmds = {25, 29};
@ -119,6 +117,7 @@ QSet<int> snr_cmds = {25, 29};
// commands that are checksummed and their crc size // commands that are checksummed and their crc size
QMap<int, int> checksum_cmds = { QMap<int, int> checksum_cmds = {
{ 5, 16 }, { 5, 16 },
{ 9, 16 },
{ 10, 16 }, { 10, 16 },
{ 11, 16 }, { 11, 16 },
{ 12, 16 }, { 12, 16 },
@ -129,7 +128,7 @@ QMap<int, int> checksum_cmds = {
}; };
QString callsign_pattern = QString("(?<callsign>[@]?[A-Z0-9/]+)"); QString callsign_pattern = QString("(?<callsign>[@]?[A-Z0-9/]+)");
QString optional_cmd_pattern = QString("(?<cmd>\\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("(?<cmd>\\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("(?<grid>\\s?[A-R]{2}[0-9]{2})?"); QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?"); QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
QString optional_num_pattern = QString("(?<num>(?<=SNR|ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?"); QString optional_num_pattern = QString("(?<num>(?<=SNR|ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");