Added background thread to compute message frames
This commit is contained in:
		
							parent
							
								
									862e702b2d
								
							
						
					
					
						commit
						2cb1665e2e
					
				
							
								
								
									
										388
									
								
								mainwindow.cpp
									
									
									
									
									
								
							
							
						
						
									
										388
									
								
								mainwindow.cpp
									
									
									
									
									
								
							@ -263,28 +263,6 @@ namespace
 | 
			
		||||
   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){
 | 
			
		||||
    edit->setFont(font);
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
  // this must be the last statement of constructor
 | 
			
		||||
@ -6060,7 +6042,7 @@ void MainWindow::createMessage(QString const& text){
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWindow::createMessageTransmitQueue(QString const& text){
 | 
			
		||||
  auto frames = buildFT8MessageFrames(text);
 | 
			
		||||
  auto frames = buildMessageFrames(text);
 | 
			
		||||
 | 
			
		||||
  m_txFrameQueue.append(frames);
 | 
			
		||||
  m_txFrameCount = frames.length();
 | 
			
		||||
@ -6135,16 +6117,11 @@ int MainWindow::currentFreqOffset(){
 | 
			
		||||
    return ui->RxFreqSpinBox->value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MainWindow::countFT8MessageFrames(QString const& text){
 | 
			
		||||
    return buildFT8MessageFrames(text).length();
 | 
			
		||||
int MainWindow::countMessageFrames(QString const& text){
 | 
			
		||||
    return buildMessageFrames(text).length();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QStringList MainWindow::buildFT8MessageFrames(QString const& text){
 | 
			
		||||
    #define ALLOW_SEND_COMPOUND 1
 | 
			
		||||
    #define AUTO_PREPEND_DIRECTED 1
 | 
			
		||||
 | 
			
		||||
    QStringList frames;
 | 
			
		||||
 | 
			
		||||
QStringList MainWindow::buildMessageFrames(const QString &text){
 | 
			
		||||
    // prepare selected callsign for directed message
 | 
			
		||||
    QString selectedCall = callsignSelected();
 | 
			
		||||
 | 
			
		||||
@ -6157,226 +6134,10 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
 | 
			
		||||
        basecall = "<....>";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 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);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    auto frames = Varicode::buildMessageFrames(mycall, basecall, mygrid, compound, selectedCall, text);
 | 
			
		||||
 | 
			
		||||
#if 1
 | 
			
		||||
    qDebug() << "parsed frames:";
 | 
			
		||||
    qDebug() << "frames:";
 | 
			
		||||
    foreach(auto frame, frames){
 | 
			
		||||
        auto dt = DecodedText(frame);
 | 
			
		||||
        qDebug() << "->" << frame << dt.message() << Varicode::frameTypeString(dt.frameType());
 | 
			
		||||
@ -6386,60 +6147,6 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
 | 
			
		||||
    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()
 | 
			
		||||
{
 | 
			
		||||
  m_i3bit = Varicode::FT8Call;
 | 
			
		||||
@ -8997,25 +8704,64 @@ void MainWindow::updateButtonDisplay(){
 | 
			
		||||
        ui->startTxButton->setText(m_tune ? "Tuning" : QString("Sending (%1/%2)").arg(sent).arg(count));
 | 
			
		||||
    } else if(m_txTextDirty) {
 | 
			
		||||
 | 
			
		||||
        // TODO: only if text changed
 | 
			
		||||
 | 
			
		||||
        auto text = ui->extFreeTextMsgEdit->toPlainText();
 | 
			
		||||
        int count = countFT8MessageFrames(text);
 | 
			
		||||
        if(count > 0){
 | 
			
		||||
            ui->startTxButton->setText(QString("Send (%1)").arg(count));
 | 
			
		||||
            ui->startTxButton->setEnabled(true);
 | 
			
		||||
 | 
			
		||||
            auto words = text.split(" ", QString::SkipEmptyParts).length();
 | 
			
		||||
            auto wpm = QString::number(words/(count/4.0), 'f', 1);
 | 
			
		||||
            auto cpm = QString::number(text.length()/(count/4.0), 'f', 0);
 | 
			
		||||
            wpm_label.setText(QString("%1wpm / %2cpm").arg(wpm).arg(cpm));
 | 
			
		||||
        } else {
 | 
			
		||||
            ui->startTxButton->setText("Send");
 | 
			
		||||
            ui->startTxButton->setEnabled(false);
 | 
			
		||||
            wpm_label.clear();
 | 
			
		||||
        // 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();
 | 
			
		||||
 | 
			
		||||
#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){
 | 
			
		||||
        ui->startTxButton->setText(QString("Send (%1)").arg(count));
 | 
			
		||||
        ui->startTxButton->setEnabled(true);
 | 
			
		||||
 | 
			
		||||
        auto words = text.split(" ", QString::SkipEmptyParts).length();
 | 
			
		||||
        auto wpm = QString::number(words/(count/4.0), 'f', 1);
 | 
			
		||||
        auto cpm = QString::number(text.length()/(count/4.0), 'f', 0);
 | 
			
		||||
        wpm_label.setText(QString("%1wpm / %2cpm").arg(wpm).arg(cpm));
 | 
			
		||||
        wpm_label.setVisible(true);
 | 
			
		||||
    } else {
 | 
			
		||||
        ui->startTxButton->setText("Send");
 | 
			
		||||
        ui->startTxButton->setEnabled(false);
 | 
			
		||||
        wpm_label.setVisible(false);
 | 
			
		||||
        wpm_label.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9344,7 +9090,7 @@ void MainWindow::processBufferedActivity() {
 | 
			
		||||
        foreach(auto part, buffer.msgs) {
 | 
			
		||||
            message.append(part.text);
 | 
			
		||||
        }
 | 
			
		||||
        message = rstrip(message);
 | 
			
		||||
        message = Varicode::rstrip(message);
 | 
			
		||||
 | 
			
		||||
        QString checksum;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								mainwindow.h
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								mainwindow.h
									
									
									
									
									
								
							@ -95,6 +95,7 @@ class DecodedText;
 | 
			
		||||
using namespace std;
 | 
			
		||||
typedef std::function<void()> Callback;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow : public QMainWindow
 | 
			
		||||
{
 | 
			
		||||
  Q_OBJECT;
 | 
			
		||||
@ -294,9 +295,8 @@ private slots:
 | 
			
		||||
  void on_nextFreeTextMsg_currentTextChanged (QString const&);
 | 
			
		||||
  void on_extFreeTextMsgEdit_currentTextChanged (QString const&);
 | 
			
		||||
  int currentFreqOffset();
 | 
			
		||||
  int countFT8MessageFrames(QString const& text);
 | 
			
		||||
  QStringList buildFT8MessageFrames(QString const& text);
 | 
			
		||||
  QString parseFT8Message(QString input, bool *isFree);
 | 
			
		||||
  int countMessageFrames(QString const& text);
 | 
			
		||||
  QStringList buildMessageFrames(QString const& text);
 | 
			
		||||
  bool prepareNextMessageFrame();
 | 
			
		||||
  bool isFreqOffsetFree(int f, int bw);
 | 
			
		||||
  int findFreeFreqOffset(int fmin, int fmax, int bw);
 | 
			
		||||
@ -383,6 +383,7 @@ private slots:
 | 
			
		||||
  void expiry_warning_message ();
 | 
			
		||||
  void not_GA_warning_message ();
 | 
			
		||||
  void clearCallsignSelected();
 | 
			
		||||
  void buildMessageFramesAndUpdateCountDisplay();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,
 | 
			
		||||
@ -646,7 +647,6 @@ private:
 | 
			
		||||
  QTimer splashTimer;
 | 
			
		||||
  QTimer p1Timer;
 | 
			
		||||
  QTimer beaconTimer;
 | 
			
		||||
  QTimer selectedCallTimer;
 | 
			
		||||
 | 
			
		||||
  QString m_path;
 | 
			
		||||
  QString m_baseCall;
 | 
			
		||||
@ -728,6 +728,7 @@ private:
 | 
			
		||||
  bool m_rxDirty;
 | 
			
		||||
  bool m_rxDisplayDirty;
 | 
			
		||||
  int m_txFrameCount;
 | 
			
		||||
  QTimer m_txTextDirtyDebounce;
 | 
			
		||||
  bool m_txTextDirty;
 | 
			
		||||
  QString m_lastTxMessage;
 | 
			
		||||
  QDateTime m_lastTxTime;
 | 
			
		||||
@ -877,9 +878,11 @@ private:
 | 
			
		||||
  void postDecode (bool is_new, QString const& message);
 | 
			
		||||
  void displayTransmit();
 | 
			
		||||
  void updateButtonDisplay();
 | 
			
		||||
  void updateFrameCountDisplay(QString text, int count);
 | 
			
		||||
  bool isMyCallIncluded(QString const &text);
 | 
			
		||||
  bool isAllCallIncluded(QString const &text);
 | 
			
		||||
  QString callsignSelected();
 | 
			
		||||
  void clearCallsignSelected();
 | 
			
		||||
  bool isRecentOffset(int offset);
 | 
			
		||||
  void markOffsetRecent(int offset);
 | 
			
		||||
  bool isDirectedOffset(int offset, bool *pIsAllCall);
 | 
			
		||||
@ -936,7 +939,6 @@ private:
 | 
			
		||||
  void write_transmit_entry (QString const& file_name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern int killbyname(const char* progName);
 | 
			
		||||
extern void getDev(int* numDevices,char hostAPI_DeviceName[][50],
 | 
			
		||||
                   int minChan[], int maxChan[],
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										261
									
								
								varicode.cpp
									
									
									
									
									
								
							
							
						
						
									
										261
									
								
								varicode.cpp
									
									
									
									
									
								
							@ -438,6 +438,28 @@ int dbmTomwatts(int dbm){
 | 
			
		||||
    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
 | 
			
		||||
 */
 | 
			
		||||
@ -1642,3 +1664,242 @@ QString Varicode::unpackDataMessage(const QString &text, quint8 *pType){
 | 
			
		||||
 | 
			
		||||
    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 <QString>
 | 
			
		||||
#include <QVector>
 | 
			
		||||
#include <QThread>
 | 
			
		||||
 | 
			
		||||
class Varicode
 | 
			
		||||
{
 | 
			
		||||
@ -56,6 +57,9 @@ public:
 | 
			
		||||
 | 
			
		||||
    //Varicode();
 | 
			
		||||
 | 
			
		||||
    static QString rstrip(const QString& str);
 | 
			
		||||
    static QString lstrip(const QString& str);
 | 
			
		||||
 | 
			
		||||
    static QMap<QString, QString> defaultHuffTable();
 | 
			
		||||
    static QMap<QString, QString> defaultHuffTableEscaped();
 | 
			
		||||
    static QString cqString(int number);
 | 
			
		||||
@ -142,6 +146,40 @@ public:
 | 
			
		||||
 | 
			
		||||
    static QString packDataMessage(QString const& text, int *n);
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user