Added background thread to compute message frames
This commit is contained in:
parent
862e702b2d
commit
2cb1665e2e
364
mainwindow.cpp
364
mainwindow.cpp
@ -263,28 +263,6 @@ namespace
|
|||||||
return roundDown + multiple;
|
return roundDown + multiple;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString rstrip(const QString& str) {
|
|
||||||
int n = str.size() - 1;
|
|
||||||
for (; n >= 0; --n) {
|
|
||||||
if (str.at(n).isSpace()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return str.left(n + 1);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString lstrip(const QString& str) {
|
|
||||||
int len = str.size();
|
|
||||||
for (int n = 0; n < len; n++) {
|
|
||||||
if(str.at(n).isSpace()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return str.mid(n);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTextEditFont(QTextEdit *edit, QFont font){
|
void setTextEditFont(QTextEdit *edit, QFont font){
|
||||||
edit->setFont(font);
|
edit->setFont(font);
|
||||||
edit->setFontFamily(font.family());
|
edit->setFontFamily(font.family());
|
||||||
@ -1417,6 +1395,10 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
m_txTextDirtyDebounce.setSingleShot(true);
|
||||||
|
m_txTextDirtyDebounce.setInterval(100);
|
||||||
|
connect(&m_txTextDirtyDebounce, &QTimer::timeout, this, &MainWindow::buildMessageFramesAndUpdateCountDisplay);
|
||||||
|
|
||||||
QTimer::singleShot(0, this, &MainWindow::initializeDummyData);
|
QTimer::singleShot(0, this, &MainWindow::initializeDummyData);
|
||||||
|
|
||||||
// this must be the last statement of constructor
|
// this must be the last statement of constructor
|
||||||
@ -6060,7 +6042,7 @@ void MainWindow::createMessage(QString const& text){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createMessageTransmitQueue(QString const& text){
|
void MainWindow::createMessageTransmitQueue(QString const& text){
|
||||||
auto frames = buildFT8MessageFrames(text);
|
auto frames = buildMessageFrames(text);
|
||||||
|
|
||||||
m_txFrameQueue.append(frames);
|
m_txFrameQueue.append(frames);
|
||||||
m_txFrameCount = frames.length();
|
m_txFrameCount = frames.length();
|
||||||
@ -6135,16 +6117,11 @@ int MainWindow::currentFreqOffset(){
|
|||||||
return ui->RxFreqSpinBox->value();
|
return ui->RxFreqSpinBox->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
int MainWindow::countFT8MessageFrames(QString const& text){
|
int MainWindow::countMessageFrames(QString const& text){
|
||||||
return buildFT8MessageFrames(text).length();
|
return buildMessageFrames(text).length();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MainWindow::buildFT8MessageFrames(QString const& text){
|
QStringList MainWindow::buildMessageFrames(const QString &text){
|
||||||
#define ALLOW_SEND_COMPOUND 1
|
|
||||||
#define AUTO_PREPEND_DIRECTED 1
|
|
||||||
|
|
||||||
QStringList frames;
|
|
||||||
|
|
||||||
// prepare selected callsign for directed message
|
// prepare selected callsign for directed message
|
||||||
QString selectedCall = callsignSelected();
|
QString selectedCall = callsignSelected();
|
||||||
|
|
||||||
@ -6157,226 +6134,10 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
|
|||||||
basecall = "<....>";
|
basecall = "<....>";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(QString line, text.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts)){
|
auto frames = Varicode::buildMessageFrames(mycall, basecall, mygrid, compound, selectedCall, text);
|
||||||
// once we find a directed call, data encode the rest of the line.
|
|
||||||
bool hasDirected = false;
|
|
||||||
|
|
||||||
// do the same for when we have sent data...
|
|
||||||
bool hasData = false;
|
|
||||||
|
|
||||||
// remove our callsign from the start of the line...
|
|
||||||
if(line.startsWith(mycall + ":") || line.startsWith(mycall + " ")){
|
|
||||||
line = lstrip(line.mid(mycall.length() + 1));
|
|
||||||
}
|
|
||||||
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...
|
|
||||||
// and if this isn't a raw message (starting with "<")... then...
|
|
||||||
if(!selectedCall.isEmpty() && !line.startsWith(selectedCall) && !line.startsWith("<")){
|
|
||||||
auto calls = Varicode::parseCallsigns(line);
|
|
||||||
|
|
||||||
bool lineStartsWithBaseCall = (
|
|
||||||
line.startsWith("ALLCALL") ||
|
|
||||||
line.startsWith("BEACON") ||
|
|
||||||
Varicode::startsWithCQ(line)
|
|
||||||
);
|
|
||||||
|
|
||||||
bool lineStartsWithStandardCall = !calls.isEmpty() && line.startsWith(calls.first());
|
|
||||||
|
|
||||||
if(lineStartsWithBaseCall || lineStartsWithStandardCall){
|
|
||||||
// pass
|
|
||||||
} else {
|
|
||||||
// if the message doesn't start with a base call
|
|
||||||
// and if there are no other callsigns in this message
|
|
||||||
// or if the first callsign in the message isn't at the beginning...
|
|
||||||
// then we should be auto-prefixing this line with the selected call
|
|
||||||
|
|
||||||
line = QString("%1 %2").arg(selectedCall).arg(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while(line.size() > 0){
|
|
||||||
QString frame;
|
|
||||||
|
|
||||||
bool useStd = false;
|
|
||||||
bool useBcn = false;
|
|
||||||
#if ALLOW_SEND_COMPOUND
|
|
||||||
bool useCmp = false;
|
|
||||||
#endif
|
|
||||||
bool useDir = false;
|
|
||||||
bool useDat = false;
|
|
||||||
bool isFree = false;
|
|
||||||
QString stdFrame = parseFT8Message(line, &isFree);
|
|
||||||
|
|
||||||
int l = 0;
|
|
||||||
QString bcnFrame = Varicode::packBeaconMessage(line, mycall, &l);
|
|
||||||
|
|
||||||
#if ALLOW_SEND_COMPOUND
|
|
||||||
int o = 0;
|
|
||||||
QString cmpFrame = Varicode::packCompoundMessage(line, &o);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int n = 0;
|
|
||||||
QString dirCmd;
|
|
||||||
QString dirTo;
|
|
||||||
QString dirNum;
|
|
||||||
QString dirFrame = Varicode::packDirectedMessage(line, basecall, &dirTo, &dirCmd, &dirNum, &n);
|
|
||||||
bool dirToCompound = dirTo.contains("/");
|
|
||||||
|
|
||||||
int m = 0;
|
|
||||||
QString datFrame = Varicode::packDataMessage(line.left(24) + "\x04", &m); // 66 / 3 + 2 = 22 (maximum number of 3bit chars we could possibly stuff in here plus 2 for good measure :P)
|
|
||||||
|
|
||||||
// if this parses to a standard FT8 free text message
|
|
||||||
// but it can be parsed as a directed message, then we
|
|
||||||
// should send the directed version. if we've already sent
|
|
||||||
// a directed message or a data frame, we will only follow it
|
|
||||||
// with more data frames.
|
|
||||||
|
|
||||||
if(isFree && !hasDirected && !hasData && l > 0){
|
|
||||||
useBcn = true;
|
|
||||||
hasDirected = false;
|
|
||||||
frame = bcnFrame;
|
|
||||||
}
|
|
||||||
#if ALLOW_SEND_COMPOUND
|
|
||||||
else if(isFree && !hasDirected && !hasData && o > 0){
|
|
||||||
useCmp = true;
|
|
||||||
hasDirected = false;
|
|
||||||
frame = cmpFrame;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else if(isFree && !hasDirected && !hasData && n > 0){
|
|
||||||
useDir = true;
|
|
||||||
hasDirected = true;
|
|
||||||
frame = dirFrame;
|
|
||||||
}
|
|
||||||
else if ((isFree || hasDirected) && m > 0) {
|
|
||||||
useDat = true;
|
|
||||||
hasData = true;
|
|
||||||
frame = datFrame;
|
|
||||||
} else {
|
|
||||||
useStd = true;
|
|
||||||
frame = stdFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(useStd){
|
|
||||||
if(frame.isEmpty()){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
frames.append(frame);
|
|
||||||
|
|
||||||
if(!line.startsWith(frame)){
|
|
||||||
line = (
|
|
||||||
line.left(frame.length()).replace(':', ' ') + // is this the only case where we could have a mismatch?
|
|
||||||
line.mid(frame.length())
|
|
||||||
).trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.mid(frame.length()).trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(useBcn){
|
|
||||||
frames.append(frame);
|
|
||||||
line = line.mid(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ALLOW_SEND_COMPOUND
|
|
||||||
if(useCmp){
|
|
||||||
frames.append(frame);
|
|
||||||
line = line.mid(o);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(useDir){
|
|
||||||
/**
|
|
||||||
* We have a few special cases when we are sending to a compound call, or our call is a compound call, or both.
|
|
||||||
* CASE 0: Non-compound: KN4CRD: J1Y ACK
|
|
||||||
* -> One standard directed message frame
|
|
||||||
*
|
|
||||||
* CASE 1: Compound From: KN4CRD/P: J1Y ACK
|
|
||||||
* -> One standard compound frame, followed by a standard directed message frame with placeholder
|
|
||||||
* -> The second standard directed frame _could_ be replaced with a compound directed frame
|
|
||||||
* -> <KN4CRD/P EM73> then <....>: J1Y ACK
|
|
||||||
* -> <KN4CRD/P EM73> then <J1Y ACK>
|
|
||||||
*
|
|
||||||
* CASE 2: Compound To: KN4CRD: J1Y/P ACK
|
|
||||||
* -> One standard compound frame, followed by a compound directed frame
|
|
||||||
* -> <KN4CRD EM73> then <J1Y/P ACK>
|
|
||||||
*
|
|
||||||
* CASE 3: Compound From & To: KN4CRD/P: J1Y/P ACK
|
|
||||||
* -> One standard compound frame, followed by a compound directed frame
|
|
||||||
* -> <KN4CRD/P EM73> then <J1Y/P ACK>
|
|
||||||
**/
|
|
||||||
bool shouldUseStandardFrame = true;
|
|
||||||
if(compound || dirToCompound){
|
|
||||||
// Cases 1, 2, 3 all send a standard compound frame first...
|
|
||||||
QString deCompoundMessage = QString("<%1 %2>").arg(mycall).arg(mygrid);
|
|
||||||
QString deCompoundFrame = Varicode::packCompoundMessage(deCompoundMessage, nullptr);
|
|
||||||
if(!deCompoundFrame.isEmpty()){
|
|
||||||
frames.append(deCompoundFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Followed, by a standard OR compound directed message...
|
|
||||||
QString dirCompoundMessage = QString("<%1%2%3>").arg(dirTo).arg(dirCmd).arg(dirNum);
|
|
||||||
QString dirCompoundFrame = Varicode::packCompoundMessage(dirCompoundMessage, nullptr);
|
|
||||||
if(!dirCompoundFrame.isEmpty()){
|
|
||||||
frames.append(dirCompoundFrame);
|
|
||||||
}
|
|
||||||
shouldUseStandardFrame = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(shouldUseStandardFrame) {
|
|
||||||
// otherwise, just send the standard directed frame
|
|
||||||
frames.append(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.mid(n);
|
|
||||||
|
|
||||||
// generate a checksum for buffered commands with line data
|
|
||||||
if(Varicode::isCommandBuffered(dirCmd) && !line.isEmpty()){
|
|
||||||
qDebug() << "generating checksum for line" << line << line.mid(1);
|
|
||||||
|
|
||||||
// strip leading whitespace after a buffered directed command
|
|
||||||
line = lstrip(line);
|
|
||||||
|
|
||||||
qDebug() << "before:" << line;
|
|
||||||
int checksumSize = Varicode::isCommandChecksumed(dirCmd);
|
|
||||||
|
|
||||||
if(checksumSize == 32){
|
|
||||||
line = line + " " + Varicode::checksum32(line);
|
|
||||||
} else if (checksumSize == 16) {
|
|
||||||
line = line + " " + Varicode::checksum16(line);
|
|
||||||
} else if (checksumSize == 0) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
qDebug() << "after:" << line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// APRS:
|
|
||||||
if(dirCmd.trimmed() == "APRS:" && !m_aprsClient->isPasscodeValid()){
|
|
||||||
MessageBox::warning_message(this, tr ("Please enter a valid APRS passcode in the settings to send an APRS packet."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(useDat){
|
|
||||||
frames.append(frame);
|
|
||||||
line = line.mid(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
qDebug() << "parsed frames:";
|
qDebug() << "frames:";
|
||||||
foreach(auto frame, frames){
|
foreach(auto frame, frames){
|
||||||
auto dt = DecodedText(frame);
|
auto dt = DecodedText(frame);
|
||||||
qDebug() << "->" << frame << dt.message() << Varicode::frameTypeString(dt.frameType());
|
qDebug() << "->" << frame << dt.message() << Varicode::frameTypeString(dt.frameType());
|
||||||
@ -6386,60 +6147,6 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
|
|||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString MainWindow::parseFT8Message(QString input, bool *isFree){
|
|
||||||
if(isFree) *isFree = true;
|
|
||||||
return input;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
char message[29];
|
|
||||||
char msgsent[29];
|
|
||||||
char volatile ft8msgbits[75 + 12];
|
|
||||||
int volatile itone[NUM_ISCAT_SYMBOLS];
|
|
||||||
|
|
||||||
QByteArray ba = input.toLocal8Bit();
|
|
||||||
ba2msg(ba,message);
|
|
||||||
|
|
||||||
qint32 i3bit = 0;
|
|
||||||
bool bcontest = false;
|
|
||||||
char MyGrid[6];
|
|
||||||
strncpy(MyGrid, (m_config.my_grid()+" ").toLatin1(),6);
|
|
||||||
genft8_(message, MyGrid, &bcontest, &i3bit, msgsent, const_cast<char *> (ft8msgbits),
|
|
||||||
const_cast<int *> (itone), 22, 6, 22);
|
|
||||||
msgsent[22]=0;
|
|
||||||
|
|
||||||
// decode the msg bits into 6-bit bytes so we can check to see if its a free text message or not...
|
|
||||||
// see extractmessage1764.f90 for the original implementation. we could technically add a boolean output
|
|
||||||
// from the fortran code, but this avoids having to modify that so we can easily apply updates to the
|
|
||||||
// signal functions in the future without incurring too much cognitive overhead of merge conflicts.
|
|
||||||
char msgbytes[12];
|
|
||||||
for(int ibyte = 1; ibyte <= 12; ibyte++){
|
|
||||||
int itmp = 0;
|
|
||||||
for(int ibit = 1; ibit <= 6; ibit++){
|
|
||||||
itmp = (itmp << 1) + (1 & (ft8msgbits[((ibyte-1) * 6 + ibit)-1]));
|
|
||||||
}
|
|
||||||
msgbytes[ibyte-1] = itmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
int a = msgbytes[9];
|
|
||||||
int b = msgbytes[10];
|
|
||||||
int c = msgbytes[11];
|
|
||||||
int d = ((a & 15) << 12) + (b << 6) + c;
|
|
||||||
|
|
||||||
QString output = QString::fromLatin1(msgsent);
|
|
||||||
|
|
||||||
if(isFree){
|
|
||||||
// TODO: jsherer - lets figure out a better way to detect this for the case:
|
|
||||||
// KN4CRD 16
|
|
||||||
// this gets encoded as a standard message, but doesn't seem to make sense as to why...
|
|
||||||
// see decodedtext.cpp for the alternate decoding side of this...
|
|
||||||
*isFree = (d >= 32768) || !QRegularExpression("^(CQ|DE|QRZ)\\s").match(output).hasMatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.trimmed();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MainWindow::prepareNextMessageFrame()
|
bool MainWindow::prepareNextMessageFrame()
|
||||||
{
|
{
|
||||||
m_i3bit = Varicode::FT8Call;
|
m_i3bit = Varicode::FT8Call;
|
||||||
@ -8997,10 +8704,50 @@ void MainWindow::updateButtonDisplay(){
|
|||||||
ui->startTxButton->setText(m_tune ? "Tuning" : QString("Sending (%1/%2)").arg(sent).arg(count));
|
ui->startTxButton->setText(m_tune ? "Tuning" : QString("Sending (%1/%2)").arg(sent).arg(count));
|
||||||
} else if(m_txTextDirty) {
|
} else if(m_txTextDirty) {
|
||||||
|
|
||||||
// TODO: only if text changed
|
// TODO: maybe add debounce?
|
||||||
|
if(m_txTextDirtyDebounce.isActive()){
|
||||||
|
m_txTextDirtyDebounce.stop();
|
||||||
|
}
|
||||||
|
m_txTextDirtyDebounce.start(100);
|
||||||
|
m_txTextDirty = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::buildMessageFramesAndUpdateCountDisplay(){
|
||||||
|
qDebug() << "buildMessageFramesAndUpdateCountDisplay";
|
||||||
|
|
||||||
auto text = ui->extFreeTextMsgEdit->toPlainText();
|
auto text = ui->extFreeTextMsgEdit->toPlainText();
|
||||||
int count = countFT8MessageFrames(text);
|
|
||||||
|
#if USE_SYNC_FRAME_COUNT
|
||||||
|
int count = countMessageFrames(text);
|
||||||
|
updateFrameCountDisplay(text, count);
|
||||||
|
#else
|
||||||
|
// prepare selected callsign for directed message
|
||||||
|
QString selectedCall = callsignSelected();
|
||||||
|
|
||||||
|
// prepare compound
|
||||||
|
bool compound = Radio::is_compound_callsign(m_config.my_callsign());
|
||||||
|
QString mygrid = m_config.my_grid().left(4);
|
||||||
|
QString mycall = m_config.my_callsign();
|
||||||
|
QString basecall = Radio::base_callsign(m_config.my_callsign());
|
||||||
|
if(basecall != mycall){
|
||||||
|
basecall = "<....>";
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildMessageFramesThread *t = new BuildMessageFramesThread(
|
||||||
|
mycall, basecall, mygrid, compound, selectedCall, text
|
||||||
|
);
|
||||||
|
|
||||||
|
connect(t, &BuildMessageFramesThread::finished, t, &QObject::deleteLater);
|
||||||
|
connect(t, &BuildMessageFramesThread::resultReady, this, [this, text](const QStringList frames){
|
||||||
|
updateFrameCountDisplay(text, frames.length());
|
||||||
|
});
|
||||||
|
t->start();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateFrameCountDisplay(QString text, int count){
|
||||||
if(count > 0){
|
if(count > 0){
|
||||||
ui->startTxButton->setText(QString("Send (%1)").arg(count));
|
ui->startTxButton->setText(QString("Send (%1)").arg(count));
|
||||||
ui->startTxButton->setEnabled(true);
|
ui->startTxButton->setEnabled(true);
|
||||||
@ -9009,14 +8756,13 @@ void MainWindow::updateButtonDisplay(){
|
|||||||
auto wpm = QString::number(words/(count/4.0), 'f', 1);
|
auto wpm = QString::number(words/(count/4.0), 'f', 1);
|
||||||
auto cpm = QString::number(text.length()/(count/4.0), 'f', 0);
|
auto cpm = QString::number(text.length()/(count/4.0), 'f', 0);
|
||||||
wpm_label.setText(QString("%1wpm / %2cpm").arg(wpm).arg(cpm));
|
wpm_label.setText(QString("%1wpm / %2cpm").arg(wpm).arg(cpm));
|
||||||
|
wpm_label.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
ui->startTxButton->setText("Send");
|
ui->startTxButton->setText("Send");
|
||||||
ui->startTxButton->setEnabled(false);
|
ui->startTxButton->setEnabled(false);
|
||||||
|
wpm_label.setVisible(false);
|
||||||
wpm_label.clear();
|
wpm_label.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_txTextDirty = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MainWindow::callsignSelected(){
|
QString MainWindow::callsignSelected(){
|
||||||
@ -9344,7 +9090,7 @@ void MainWindow::processBufferedActivity() {
|
|||||||
foreach(auto part, buffer.msgs) {
|
foreach(auto part, buffer.msgs) {
|
||||||
message.append(part.text);
|
message.append(part.text);
|
||||||
}
|
}
|
||||||
message = rstrip(message);
|
message = Varicode::rstrip(message);
|
||||||
|
|
||||||
QString checksum;
|
QString checksum;
|
||||||
|
|
||||||
|
12
mainwindow.h
12
mainwindow.h
@ -95,6 +95,7 @@ class DecodedText;
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
typedef std::function<void()> Callback;
|
typedef std::function<void()> Callback;
|
||||||
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
@ -294,9 +295,8 @@ private slots:
|
|||||||
void on_nextFreeTextMsg_currentTextChanged (QString const&);
|
void on_nextFreeTextMsg_currentTextChanged (QString const&);
|
||||||
void on_extFreeTextMsgEdit_currentTextChanged (QString const&);
|
void on_extFreeTextMsgEdit_currentTextChanged (QString const&);
|
||||||
int currentFreqOffset();
|
int currentFreqOffset();
|
||||||
int countFT8MessageFrames(QString const& text);
|
int countMessageFrames(QString const& text);
|
||||||
QStringList buildFT8MessageFrames(QString const& text);
|
QStringList buildMessageFrames(QString const& text);
|
||||||
QString parseFT8Message(QString input, bool *isFree);
|
|
||||||
bool prepareNextMessageFrame();
|
bool prepareNextMessageFrame();
|
||||||
bool isFreqOffsetFree(int f, int bw);
|
bool isFreqOffsetFree(int f, int bw);
|
||||||
int findFreeFreqOffset(int fmin, int fmax, int bw);
|
int findFreeFreqOffset(int fmin, int fmax, int bw);
|
||||||
@ -383,6 +383,7 @@ private slots:
|
|||||||
void expiry_warning_message ();
|
void expiry_warning_message ();
|
||||||
void not_GA_warning_message ();
|
void not_GA_warning_message ();
|
||||||
void clearCallsignSelected();
|
void clearCallsignSelected();
|
||||||
|
void buildMessageFramesAndUpdateCountDisplay();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,
|
Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,
|
||||||
@ -646,7 +647,6 @@ private:
|
|||||||
QTimer splashTimer;
|
QTimer splashTimer;
|
||||||
QTimer p1Timer;
|
QTimer p1Timer;
|
||||||
QTimer beaconTimer;
|
QTimer beaconTimer;
|
||||||
QTimer selectedCallTimer;
|
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
QString m_baseCall;
|
QString m_baseCall;
|
||||||
@ -728,6 +728,7 @@ private:
|
|||||||
bool m_rxDirty;
|
bool m_rxDirty;
|
||||||
bool m_rxDisplayDirty;
|
bool m_rxDisplayDirty;
|
||||||
int m_txFrameCount;
|
int m_txFrameCount;
|
||||||
|
QTimer m_txTextDirtyDebounce;
|
||||||
bool m_txTextDirty;
|
bool m_txTextDirty;
|
||||||
QString m_lastTxMessage;
|
QString m_lastTxMessage;
|
||||||
QDateTime m_lastTxTime;
|
QDateTime m_lastTxTime;
|
||||||
@ -877,9 +878,11 @@ private:
|
|||||||
void postDecode (bool is_new, QString const& message);
|
void postDecode (bool is_new, QString const& message);
|
||||||
void displayTransmit();
|
void displayTransmit();
|
||||||
void updateButtonDisplay();
|
void updateButtonDisplay();
|
||||||
|
void updateFrameCountDisplay(QString text, int count);
|
||||||
bool isMyCallIncluded(QString const &text);
|
bool isMyCallIncluded(QString const &text);
|
||||||
bool isAllCallIncluded(QString const &text);
|
bool isAllCallIncluded(QString const &text);
|
||||||
QString callsignSelected();
|
QString callsignSelected();
|
||||||
|
void clearCallsignSelected();
|
||||||
bool isRecentOffset(int offset);
|
bool isRecentOffset(int offset);
|
||||||
void markOffsetRecent(int offset);
|
void markOffsetRecent(int offset);
|
||||||
bool isDirectedOffset(int offset, bool *pIsAllCall);
|
bool isDirectedOffset(int offset, bool *pIsAllCall);
|
||||||
@ -936,7 +939,6 @@ private:
|
|||||||
void write_transmit_entry (QString const& file_name);
|
void write_transmit_entry (QString const& file_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
extern int killbyname(const char* progName);
|
extern int killbyname(const char* progName);
|
||||||
extern void getDev(int* numDevices,char hostAPI_DeviceName[][50],
|
extern void getDev(int* numDevices,char hostAPI_DeviceName[][50],
|
||||||
int minChan[], int maxChan[],
|
int minChan[], int maxChan[],
|
||||||
|
261
varicode.cpp
261
varicode.cpp
@ -438,6 +438,28 @@ int dbmTomwatts(int dbm){
|
|||||||
return iter.value();
|
return iter.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Varicode::rstrip(const QString& str) {
|
||||||
|
int n = str.size() - 1;
|
||||||
|
for (; n >= 0; --n) {
|
||||||
|
if (str.at(n).isSpace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return str.left(n + 1);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Varicode::lstrip(const QString& str) {
|
||||||
|
int len = str.size();
|
||||||
|
for (int n = 0; n < len; n++) {
|
||||||
|
if(str.at(n).isSpace()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return str.mid(n);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* VARICODE
|
* VARICODE
|
||||||
*/
|
*/
|
||||||
@ -1642,3 +1664,242 @@ QString Varicode::unpackDataMessage(const QString &text, quint8 *pType){
|
|||||||
|
|
||||||
return unpacked;
|
return unpacked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove the dependence on providing all this data?
|
||||||
|
QStringList Varicode::buildMessageFrames(
|
||||||
|
QString const& mycall,
|
||||||
|
QString const& basecall,
|
||||||
|
QString const& mygrid,
|
||||||
|
bool compound,
|
||||||
|
QString const& selectedCall,
|
||||||
|
QString const& text
|
||||||
|
){
|
||||||
|
#define ALLOW_SEND_COMPOUND 1
|
||||||
|
#define AUTO_PREPEND_DIRECTED 1
|
||||||
|
|
||||||
|
QStringList frames;
|
||||||
|
|
||||||
|
foreach(QString line, text.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts)){
|
||||||
|
// once we find a directed call, data encode the rest of the line.
|
||||||
|
bool hasDirected = false;
|
||||||
|
|
||||||
|
// do the same for when we have sent data...
|
||||||
|
bool hasData = false;
|
||||||
|
|
||||||
|
// remove our callsign from the start of the line...
|
||||||
|
if(line.startsWith(mycall + ":") || line.startsWith(mycall + " ")){
|
||||||
|
line = lstrip(line.mid(mycall.length() + 1));
|
||||||
|
}
|
||||||
|
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...
|
||||||
|
// and if this isn't a raw message (starting with "<")... then...
|
||||||
|
if(!selectedCall.isEmpty() && !line.startsWith(selectedCall) && !line.startsWith("<")){
|
||||||
|
auto calls = Varicode::parseCallsigns(line);
|
||||||
|
|
||||||
|
bool lineStartsWithBaseCall = (
|
||||||
|
line.startsWith("ALLCALL") ||
|
||||||
|
line.startsWith("BEACON") ||
|
||||||
|
Varicode::startsWithCQ(line)
|
||||||
|
);
|
||||||
|
|
||||||
|
bool lineStartsWithStandardCall = !calls.isEmpty() && line.startsWith(calls.first());
|
||||||
|
|
||||||
|
if(lineStartsWithBaseCall || lineStartsWithStandardCall){
|
||||||
|
// pass
|
||||||
|
} else {
|
||||||
|
// if the message doesn't start with a base call
|
||||||
|
// and if there are no other callsigns in this message
|
||||||
|
// or if the first callsign in the message isn't at the beginning...
|
||||||
|
// then we should be auto-prefixing this line with the selected call
|
||||||
|
|
||||||
|
line = QString("%1 %2").arg(selectedCall).arg(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while(line.size() > 0){
|
||||||
|
QString frame;
|
||||||
|
|
||||||
|
bool useBcn = false;
|
||||||
|
#if ALLOW_SEND_COMPOUND
|
||||||
|
bool useCmp = false;
|
||||||
|
#endif
|
||||||
|
bool useDir = false;
|
||||||
|
bool useDat = false;
|
||||||
|
|
||||||
|
int l = 0;
|
||||||
|
QString bcnFrame = Varicode::packBeaconMessage(line, mycall, &l);
|
||||||
|
|
||||||
|
#if ALLOW_SEND_COMPOUND
|
||||||
|
int o = 0;
|
||||||
|
QString cmpFrame = Varicode::packCompoundMessage(line, &o);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
QString dirCmd;
|
||||||
|
QString dirTo;
|
||||||
|
QString dirNum;
|
||||||
|
QString dirFrame = Varicode::packDirectedMessage(line, basecall, &dirTo, &dirCmd, &dirNum, &n);
|
||||||
|
bool dirToCompound = dirTo.contains("/");
|
||||||
|
|
||||||
|
int m = 0;
|
||||||
|
QString datFrame = Varicode::packDataMessage(line.left(24) + "\x04", &m); // 66 / 3 + 2 = 22 (maximum number of 3bit chars we could possibly stuff in here plus 2 for good measure :P)
|
||||||
|
|
||||||
|
// if this parses to a standard FT8 free text message
|
||||||
|
// but it can be parsed as a directed message, then we
|
||||||
|
// should send the directed version. if we've already sent
|
||||||
|
// a directed message or a data frame, we will only follow it
|
||||||
|
// with more data frames.
|
||||||
|
|
||||||
|
if(!hasDirected && !hasData && l > 0){
|
||||||
|
useBcn = true;
|
||||||
|
hasDirected = false;
|
||||||
|
frame = bcnFrame;
|
||||||
|
}
|
||||||
|
#if ALLOW_SEND_COMPOUND
|
||||||
|
else if(!hasDirected && !hasData && o > 0){
|
||||||
|
useCmp = true;
|
||||||
|
hasDirected = false;
|
||||||
|
frame = cmpFrame;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if(!hasDirected && !hasData && n > 0){
|
||||||
|
useDir = true;
|
||||||
|
hasDirected = true;
|
||||||
|
frame = dirFrame;
|
||||||
|
}
|
||||||
|
else if (m > 0) {
|
||||||
|
useDat = true;
|
||||||
|
hasData = true;
|
||||||
|
frame = datFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(useBcn){
|
||||||
|
frames.append(frame);
|
||||||
|
line = line.mid(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ALLOW_SEND_COMPOUND
|
||||||
|
if(useCmp){
|
||||||
|
frames.append(frame);
|
||||||
|
line = line.mid(o);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(useDir){
|
||||||
|
/**
|
||||||
|
* We have a few special cases when we are sending to a compound call, or our call is a compound call, or both.
|
||||||
|
* CASE 0: Non-compound: KN4CRD: J1Y ACK
|
||||||
|
* -> One standard directed message frame
|
||||||
|
*
|
||||||
|
* CASE 1: Compound From: KN4CRD/P: J1Y ACK
|
||||||
|
* -> One standard compound frame, followed by a standard directed message frame with placeholder
|
||||||
|
* -> The second standard directed frame _could_ be replaced with a compound directed frame
|
||||||
|
* -> <KN4CRD/P EM73> then <....>: J1Y ACK
|
||||||
|
* -> <KN4CRD/P EM73> then <J1Y ACK>
|
||||||
|
*
|
||||||
|
* CASE 2: Compound To: KN4CRD: J1Y/P ACK
|
||||||
|
* -> One standard compound frame, followed by a compound directed frame
|
||||||
|
* -> <KN4CRD EM73> then <J1Y/P ACK>
|
||||||
|
*
|
||||||
|
* CASE 3: Compound From & To: KN4CRD/P: J1Y/P ACK
|
||||||
|
* -> One standard compound frame, followed by a compound directed frame
|
||||||
|
* -> <KN4CRD/P EM73> then <J1Y/P ACK>
|
||||||
|
**/
|
||||||
|
bool shouldUseStandardFrame = true;
|
||||||
|
if(compound || dirToCompound){
|
||||||
|
// Cases 1, 2, 3 all send a standard compound frame first...
|
||||||
|
QString deCompoundMessage = QString("<%1 %2>").arg(mycall).arg(mygrid);
|
||||||
|
QString deCompoundFrame = Varicode::packCompoundMessage(deCompoundMessage, nullptr);
|
||||||
|
if(!deCompoundFrame.isEmpty()){
|
||||||
|
frames.append(deCompoundFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Followed, by a standard OR compound directed message...
|
||||||
|
QString dirCompoundMessage = QString("<%1%2%3>").arg(dirTo).arg(dirCmd).arg(dirNum);
|
||||||
|
QString dirCompoundFrame = Varicode::packCompoundMessage(dirCompoundMessage, nullptr);
|
||||||
|
if(!dirCompoundFrame.isEmpty()){
|
||||||
|
frames.append(dirCompoundFrame);
|
||||||
|
}
|
||||||
|
shouldUseStandardFrame = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(shouldUseStandardFrame) {
|
||||||
|
// otherwise, just send the standard directed frame
|
||||||
|
frames.append(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.mid(n);
|
||||||
|
|
||||||
|
// generate a checksum for buffered commands with line data
|
||||||
|
if(Varicode::isCommandBuffered(dirCmd) && !line.isEmpty()){
|
||||||
|
qDebug() << "generating checksum for line" << line << line.mid(1);
|
||||||
|
|
||||||
|
// strip leading whitespace after a buffered directed command
|
||||||
|
line = lstrip(line);
|
||||||
|
|
||||||
|
qDebug() << "before:" << line;
|
||||||
|
int checksumSize = Varicode::isCommandChecksumed(dirCmd);
|
||||||
|
|
||||||
|
if(checksumSize == 32){
|
||||||
|
line = line + " " + Varicode::checksum32(line);
|
||||||
|
} else if (checksumSize == 16) {
|
||||||
|
line = line + " " + Varicode::checksum16(line);
|
||||||
|
} else if (checksumSize == 0) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
qDebug() << "after:" << line;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// APRS:
|
||||||
|
if(dirCmd.trimmed() == "APRS:" && !m_aprsClient->isPasscodeValid()){
|
||||||
|
MessageBox::warning_message(this, tr ("Please enter a valid APRS passcode in the settings to send an APRS packet."));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(useDat){
|
||||||
|
frames.append(frame);
|
||||||
|
line = line.mid(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildMessageFramesThread::BuildMessageFramesThread(const QString &mycall, const QString &basecall, const QString &mygrid, bool compound, const QString &selectedCall, const QString &text, QObject *parent):
|
||||||
|
QThread(parent),
|
||||||
|
m_mycall{mycall},
|
||||||
|
m_basecall{basecall},
|
||||||
|
m_mygrid{mygrid},
|
||||||
|
m_compound{compound},
|
||||||
|
m_selectedCall{selectedCall},
|
||||||
|
m_text{text}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildMessageFramesThread::run(){
|
||||||
|
auto results = Varicode::buildMessageFrames(
|
||||||
|
m_mycall,
|
||||||
|
m_basecall,
|
||||||
|
m_mygrid,
|
||||||
|
m_compound,
|
||||||
|
m_selectedCall,
|
||||||
|
m_text
|
||||||
|
);
|
||||||
|
|
||||||
|
emit resultReady(results);
|
||||||
|
}
|
||||||
|
38
varicode.h
38
varicode.h
@ -10,6 +10,7 @@
|
|||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
class Varicode
|
class Varicode
|
||||||
{
|
{
|
||||||
@ -56,6 +57,9 @@ public:
|
|||||||
|
|
||||||
//Varicode();
|
//Varicode();
|
||||||
|
|
||||||
|
static QString rstrip(const QString& str);
|
||||||
|
static QString lstrip(const QString& str);
|
||||||
|
|
||||||
static QMap<QString, QString> defaultHuffTable();
|
static QMap<QString, QString> defaultHuffTable();
|
||||||
static QMap<QString, QString> defaultHuffTableEscaped();
|
static QMap<QString, QString> defaultHuffTableEscaped();
|
||||||
static QString cqString(int number);
|
static QString cqString(int number);
|
||||||
@ -142,6 +146,40 @@ public:
|
|||||||
|
|
||||||
static QString packDataMessage(QString const& text, int *n);
|
static QString packDataMessage(QString const& text, int *n);
|
||||||
static QString unpackDataMessage(QString const& text, quint8 *pType);
|
static QString unpackDataMessage(QString const& text, quint8 *pType);
|
||||||
|
|
||||||
|
static QStringList buildMessageFrames(
|
||||||
|
QString const& mycall,
|
||||||
|
QString const& basecall,
|
||||||
|
QString const& mygrid,
|
||||||
|
bool compound,
|
||||||
|
QString const& selectedCall,
|
||||||
|
QString const& text
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class BuildMessageFramesThread : public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BuildMessageFramesThread(QString const& mycall,
|
||||||
|
QString const& basecall,
|
||||||
|
QString const& mygrid,
|
||||||
|
bool compound,
|
||||||
|
QString const& selectedCall,
|
||||||
|
QString const& text,
|
||||||
|
QObject *parent=nullptr);
|
||||||
|
void run() override;
|
||||||
|
signals:
|
||||||
|
void resultReady(const QStringList s);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_mycall;
|
||||||
|
QString m_basecall;
|
||||||
|
QString m_mygrid;
|
||||||
|
bool m_compound;
|
||||||
|
QString m_selectedCall;
|
||||||
|
QString m_text;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VARICODE_H
|
#endif // VARICODE_H
|
||||||
|
Loading…
Reference in New Issue
Block a user