Added experimental auto-sync function for normal mode.

This commit is contained in:
Jordan Sherer 2020-05-13 21:15:21 -04:00
parent 672f0e4535
commit c44e75b20a
5 changed files with 241 additions and 24 deletions

View File

@ -810,6 +810,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(m_wideGraph.data(), &WideGraph::qsy, this, &MainWindow::qsy);
connect(m_wideGraph.data(), &WideGraph::drifted, this, &MainWindow::drifted);
decodeBusy(false);
QString t1[28]={"1 uW","2 uW","5 uW","10 uW","20 uW","50 uW","100 uW","200 uW","500 uW",
"1 mW","2 mW","5 mW","10 mW","20 mW","50 mW","100 mW","200 mW","500 mW",
@ -2268,6 +2270,7 @@ void MainWindow::writeSettings()
m_settings->setValue("SubModeHB", ui->actionModeJS8HB->isChecked());
m_settings->setValue("SubModeHBAck", ui->actionHeartbeatAcknowledgements->isChecked());
m_settings->setValue("SubModeMultiDecode", ui->actionModeMultiDecoder->isChecked());
m_settings->setValue("SubModeAutoSync", ui->actionModeAutoSync->isChecked());
m_settings->setValue("DTtol",m_DTtol);
m_settings->setValue("Ftol", ui->sbFtol->value ());
m_settings->setValue("MinSync",m_minSync);
@ -2424,6 +2427,7 @@ void MainWindow::readSettings()
ui->actionModeJS8HB->setChecked(m_settings->value("SubModeHB", false).toBool());
ui->actionHeartbeatAcknowledgements->setChecked(m_settings->value("SubModeHBAck", false).toBool());
ui->actionModeMultiDecoder->setChecked(m_settings->value("SubModeMultiDecode", true).toBool());
ui->actionModeAutoSync->setChecked(m_settings->value("SubModeAutoSync", false).toBool());
ui->sbFtol->setValue (m_settings->value("Ftol", 20).toInt());
m_minSync=m_settings->value("MinSync",0).toInt();
@ -2676,6 +2680,18 @@ int MainWindow::computeFramesPerCycleForDecode(int submode){
return computePeriodForSubmode(submode) * RX_SAMPLE_RATE;
}
int MainWindow::computePeriodStartDelayForDecode(int submode){
int delay = 0;
switch(submode){
case Varicode::JS8CallNormal: delay = JS8A_START_DELAY_MS; break;
case Varicode::JS8CallFast: delay = JS8B_START_DELAY_MS; break;
case Varicode::JS8CallTurbo: delay = JS8C_START_DELAY_MS; break;
case Varicode::JS8CallSlow: delay = JS8E_START_DELAY_MS; break;
case Varicode::JS8CallUltra: delay = JS8I_START_DELAY_MS; break;
}
return delay;
}
int MainWindow::computeFramesNeededForDecode(int submode){
int symbolSamples = 0;
float threshold = 0.0;
@ -2706,6 +2722,8 @@ void MainWindow::dataSink(qint64 frames)
k0 = k;
}
qDebug() << "k" << k << "k0" << k0 << "delta" << k-k0;
#if JS8_USE_REFSPEC
QString fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat"))};
QByteArray bafname = fname.toLatin1();
@ -4445,7 +4463,7 @@ bool MainWindow::decodeEnqueueReady(qint32 k, qint32 k0){
bool MainWindow::decodeEnqueueReadyExperiment(qint32 k, qint32 /*k0*/){
// TODO: make this non-static field of MainWindow?
// map of last decode positions for each submode
static QMap<qint32, qint32> lastDecodeStartMap;
// static QMap<qint32, qint32> m_lastDecodeStartMap;
// TODO: make this non-static field of MainWindow?
// map of submodes to decode + optional alternate decode positions
@ -4464,24 +4482,32 @@ bool MainWindow::decodeEnqueueReadyExperiment(qint32 k, qint32 /*k0*/){
// do we have a better way to check this?
bool multi = ui->actionModeMultiDecoder->isChecked();
// do we have a better way to check this?
bool everySecond = ui->actionModeAutoSync->isChecked();
foreach(auto submode, submodes.keys()){
// skip if multi is disabled and this mode is not the current submode
if(!multi && submode != m_nSubMode){
continue;
}
// check alternate decode positions
// check all alternate decode positions
foreach(auto alt, submodes.value(submode)){
// skip alts if we are decoding every second
if(everySecond && alt != 0){
continue;
}
qint32 cycle = computeAltCycleForDecode(submode, k, alt*oneSecondSamples);
qint32 cycleFrames = computeFramesPerCycleForDecode(submode);
qint32 cycleFramesNeeded = computeFramesNeededForDecode(submode) - oneSecondSamples;
qint32 cycleFramesReady = k - (cycle * cycleFrames);
if(!lastDecodeStartMap.contains(submode)){
lastDecodeStartMap[submode] = cycle * cycleFrames;
if(!m_lastDecodeStartMap.contains(submode)){
m_lastDecodeStartMap[submode] = cycle * cycleFrames;
}
qint32 lastDecodeStart = lastDecodeStartMap[submode];
qint32 lastDecodeStart = m_lastDecodeStartMap[submode];
qint32 incrementedBy = k - lastDecodeStart;
if(k < lastDecodeStart){
incrementedBy = maxSamples - lastDecodeStart + k;
@ -4489,7 +4515,21 @@ bool MainWindow::decodeEnqueueReadyExperiment(qint32 k, qint32 /*k0*/){
if(JS8_DEBUG_DECODE) qDebug() << submodeName(submode) << "alt" << alt << "cycle" << cycle << "cycle frames" << cycleFrames << "cycle start" << cycle*cycleFrames << "cycle end" << (cycle+1)*cycleFrames << "k" << k << "frames ready" << cycleFramesReady << "incremeted by" << incrementedBy;
if(incrementedBy >= oneSecondSamples && cycleFramesReady >= cycleFramesNeeded){
if(everySecond && incrementedBy >= oneSecondSamples){
DecodeParams d;
d.submode = submode;
d.sz = cycleFrames;
d.start = k - d.sz;
if(d.start < 0){
d.start += maxSamples;
}
m_decoderQueue.append(d);
decodes++;
// keep track of last decode position
m_lastDecodeStartMap[submode] = k;
}
else if(incrementedBy >= oneSecondSamples && cycleFramesReady >= cycleFramesNeeded){
DecodeParams d;
d.submode = submode;
d.start = cycle*cycleFrames;
@ -4497,8 +4537,8 @@ bool MainWindow::decodeEnqueueReadyExperiment(qint32 k, qint32 /*k0*/){
m_decoderQueue.append(d);
decodes++;
// keep track of last decode start position
lastDecodeStartMap[submode] = k;
// keep track of last decode position
m_lastDecodeStartMap[submode] = k;
}
}
}
@ -4809,7 +4849,7 @@ bool MainWindow::decodeProcessQueue(qint32 *pSubmode){
int period = computePeriodForSubmode(submode);
dec_data.params.syncStats = m_wideGraph->shouldDisplayDecodeAttempts();
dec_data.params.syncStats = (m_wideGraph->shouldDisplayDecodeAttempts() || ui->actionModeAutoSync->isChecked());
dec_data.params.npts8=(m_ihsym*m_nsps)/16;
dec_data.params.newdat=1;
dec_data.params.nagain=0;
@ -5166,10 +5206,19 @@ void MainWindow::processDecodedLine(QByteArray t){
bool bAvgMsg=false;
int navg=0;
#if JS8_TIME_DRIFT_EXPERIMENT
static bool hasNewDrift = false;
static int newDrift = 0;
#endif
static QList<int> driftQueue;
static qint32 syncStart = -1;
if(t.indexOf("<DecodeDebug> sync start") >= 0){
auto segs = QString(t.trimmed()).split(QRegExp("[\\s\\t]+"), QString::SkipEmptyParts);
if(segs.isEmpty()){
return;
}
auto spos = segs.at(3);
syncStart = spos.toInt();
return;
}
if(t.indexOf("<DecodeSyncStat>") >= 0) {
auto segs = QString(t.trimmed()).split(QRegExp("[\\s\\t]+"), QString::SkipEmptyParts);
@ -5177,7 +5226,8 @@ void MainWindow::processDecodedLine(QByteArray t){
return;
}
if(!m_wideGraph->shouldDisplayDecodeAttempts()){
// only continue if we should either display decode attempts or if we should try to auto sync
if(!m_wideGraph->shouldDisplayDecodeAttempts() && !ui->actionModeAutoSync->isChecked()){
return;
}
@ -5191,10 +5241,11 @@ void MainWindow::processDecodedLine(QByteArray t){
auto s = int(s1.toFloat());
auto xdt1 = QString(segs.at(8));
auto xdt = int(xdt1.toFloat());
auto xdt = xdt1.toFloat();
auto xdtMs = int(xdt*1000);
// draw candidates
if(abs(xdt) <= 2){
if(abs(xdtMs) <= 2000){
if(s < 10){
m_wideGraph->drawDecodeLine(QColor(Qt::darkCyan), f, f + computeBandwidthForSubmode(m));
} else if (s <= 15){
@ -5211,6 +5262,131 @@ void MainWindow::processDecodedLine(QByteArray t){
// draw decodes
m_wideGraph->drawDecodeLine(QColor(Qt::red), f, f + computeBandwidthForSubmode(m));
// compute time drift if needed
if(!ui->actionModeAutoSync->isChecked()){
return;
}
if(m != Varicode::JS8CallNormal){
return;
}
// if we're here at this point, we _should_ be operating a decode every second
//
// so we need to figure out where:
//
// 1) this current decode started
// 2) when that cycle _should_ have started
// 3) compute the delta
// 4) apply the drift
/// if(!m_lastDecodeStartMap.contains(m)){
/// return;
/// }
// this is where we started the decode
static qint32 oneSecondSamples = RX_SAMPLE_RATE;
static qint32 maxSamples = NTMAX * RX_SAMPLE_RATE;
int periodMs = 1000 * computePeriodForSubmode(m);
auto now = QDateTime::currentDateTimeUtc();
writeNoticeTextToUI(now, QString("Decode at %1 (kin: %2, lastDecoded: %3)").arg(syncStart).arg(dec_data.params.kin).arg(m_lastDecodeStartMap.value(m)));
float expectedStartDelay = computePeriodStartDelayForDecode(m)/1000.0;
float decodedSignalTime = (float)syncStart/(float)RX_SAMPLE_RATE;
writeNoticeTextToUI(now, QString("--> started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime));
writeNoticeTextToUI(now, QString("--> we add a time delta of %1 seconds into the start of the cycle").arg(xdt));
// adjust for expected start delay
decodedSignalTime -= expectedStartDelay;
// adjust for time delta
decodedSignalTime += xdt;
// ensure that we are within a 60 second minute
if(decodedSignalTime < 0){
decodedSignalTime += 60.0;
} else if(decodedSignalTime > 60){
decodedSignalTime -= 60.0;
}
writeNoticeTextToUI(now, QString("--> so signal adjusted started at %1 seconds into the start of my drifted minute").arg(decodedSignalTime));
int decodedSignalTimeMs = 1000 * decodedSignalTime;
int cycleStartTimeMs = (decodedSignalTimeMs / periodMs) * periodMs;
int driftMs = cycleStartTimeMs - decodedSignalTimeMs;
writeNoticeTextToUI(now, QString("--> which is a drift adjustment of %1 milliseconds").arg(driftMs));
// if we have a large negative offset (say -14000), use the positive inverse of +1000
if(driftMs + periodMs < qAbs(driftMs)){
driftMs += periodMs;
}
// if we have a large positive offset (say 14000, use the negative inverse of -1000)
else if(qAbs(driftMs - periodMs) < driftMs){
driftMs -= periodMs;
}
writeNoticeTextToUI(now, QString("--> which is a corrected drift adjustment of %1 milliseconds").arg(driftMs));
int newDrift = DriftingDateTime::drift() + driftMs;
if(newDrift < 0){
newDrift %= -periodMs;
} else {
newDrift %= periodMs;
}
writeNoticeTextToUI(now, QString("--> which is rounded to a total drift of %1 milliseconds for this period").arg(newDrift));
driftQueue.append(newDrift);
/// qint32 cycle = computeCycleForDecode(m, syncStart);
/// qint32 cycleFrames = computeFramesPerCycleForDecode(m);
/// qint32 cycleStart = cycle*cycleFrames;
///
/// writeNoticeTextToUI(now, QString("--> cycle started at %1 and is %2 frames long").arg(cycleStart).arg(cycleFrames));
/// writeNoticeTextToUI(now, QString("--> decode delta from cycle start is %1 frames and is %2 milliseconds after cycle start").arg(syncStart - cycleStart).arg(1000*float(syncStart-cycleStart)/RX_SAMPLE_RATE));
/// writeNoticeTextToUI(now, QString("--> decode time delta is %1 milliseconds").arg(xdtMs));
// TODO: we're assuming 1 second decoding at this point
/// qint32 cycleFrames = computeFramesPerCycleForDecode(m);
/// qint32 startK = syncStart;
/// // qint32 startK = m_lastDecodeStartMap.value(m) - cycleFrames;
/// // if(startK < 0){
/// // startK += maxSamples;
/// // }
/// qint32 cycle = computeCycleForDecode(m, startK);
/// qint32 framesIntoCycle = startK - cycle*cycleFrames;
///
/// // TODO: do we need to know the *lastDecode* drift setting or is it safe to use this?
/// // float currentDriftSeconds = m_wideGraph->drift()/1000.0;
///
/// // compute the relative seconds into the cycle
/// float secondsIntoCycle = (float)framesIntoCycle/(float)oneSecondSamples;
///
/// // if we have any drift applied currently, add that to the cycle seconds since the cycle is relative
/// // float adjustedSecondsIntoCycle = secondsIntoCycle - currentDriftSeconds;
///
/// // compute new drift adjustment
/// int periodMs = computePeriodForSubmode(m) * 1000;
/// int newDrift = (-secondsIntoCycle*1000)-xdtMs);
///
/// int pos = newDrift + periodMs;
/// int neg = newDrift - periodMs;
/// if(qAbs(neg) < qAbs(pos)){
/// newDrift = neg;
/// } else {
/// newDrift = pos;
/// }
///
/// writeNoticeTextToUI(QDateTime::currentDateTimeUtc(), QString("Decode at startK %4 and %1 seconds into cycle %5 with dt of %2 milliseconds should adjust drift by %3").arg(secondsIntoCycle).arg(xdtMs).arg(newDrift).arg(startK).arg(cycle));
/// setDrift(newDrift);
#if JS8_TIME_DRIFT_EXPERIMENT
// use normal decodes for auto drift if we haven't already defined a new drift for this period
if(/*!hasNewDrift && */ (m == Varicode::JS8CallSlow || m == Varicode::JS8CallNormal)){
@ -5261,16 +5437,22 @@ void MainWindow::processDecodedLine(QByteArray t){
int msec = m_decoderBusyStartTime.msecsTo(QDateTime::currentDateTimeUtc());
if(JS8_DEBUG_DECODE) qDebug() << "decode duration" << msec << "ms";
#if JS8_TIME_DRIFT_EXPERIMENT
if(hasNewDrift){
if(!driftQueue.isEmpty()){
static int driftN = 1;
newDrift = ((driftN-1)*DriftingDateTime::drift() + newDrift)/driftN;
setDrift(newDrift);
if(driftN < 60) driftN++; // cap it to 60 observations
writeNoticeTextToUI(QDateTime::currentDateTimeUtc(), QString("Drift: %1").arg(newDrift));
hasNewDrift = false;
static int driftAvg = DriftingDateTime::drift();
while(!driftQueue.isEmpty()){
int newDrift = driftQueue.first();
driftQueue.removeFirst();
driftAvg = ((driftN-1)*driftAvg + newDrift)/driftN;
if(driftN < 60) driftN++; // cap it to 60 observations
}
setDrift(driftAvg);
writeNoticeTextToUI(QDateTime::currentDateTimeUtc(), QString("Drift: %1").arg(driftAvg));
}
#endif
m_bDecoded = t.mid(16).trimmed().toInt() > 0;
int mswait=3*1000*m_TRperiod/4;
@ -10051,6 +10233,12 @@ void MainWindow::qsy(int hzDelta){
displayActivity(true);
}
void MainWindow::drifted(int /*prev*/, int /*cur*/){
// here we reset the buffer position without clearing the buffer
// this makes the detected emit the correct k when drifting time
m_detector->resetBufferPosition();
}
void MainWindow::setFreqOffsetForRestore(int freq, bool shouldRestore){
setFreq4(freq, freq);
if(shouldRestore){
@ -10541,6 +10729,7 @@ void MainWindow::updateModeButtonText(){
auto selectedCallsign = callsignSelected();
auto multi = ui->actionModeMultiDecoder->isChecked();
auto autosync = ui->actionModeAutoSync->isChecked();
auto autoreply = ui->actionModeAutoreply->isChecked();
auto heartbeat = ui->actionModeJS8HB->isEnabled() && ui->actionModeJS8HB->isChecked();
auto ack = autoreply && ui->actionHeartbeatAcknowledgements->isChecked() && (!m_config.heartbeat_qso_pause() || selectedCallsign.isEmpty());
@ -10550,6 +10739,10 @@ void MainWindow::updateModeButtonText(){
modeText += QString("+MULTI");
}
if(autosync){
modeText += QString("+SYNC");
}
if(autoreply){
if(m_config.autoreply_confirmation()){
modeText += QString("+AUTO+CONF");
@ -10565,6 +10758,7 @@ void MainWindow::updateModeButtonText(){
modeText += QString("+HB");
}
}
ui->modeButton->setText(modeText);
}

View File

@ -132,6 +132,7 @@ public slots:
void readFromStdout(QProcess * proc);
void setXIT(int n, Frequency base = 0u);
void qsy(int hzDelta);
void drifted(int prev, int cur);
void setFreqOffsetForRestore(int freq, bool shouldRestore);
bool tryRestoreFreqOffset();
void setFreq4(int rxFreq, int txFreq);
@ -615,6 +616,7 @@ private:
bool m_loopall;
bool m_decoderBusy;
QString m_decoderBusyBand;
QMap<qint32, qint32> m_lastDecodeStartMap;
Radio::Frequency m_decoderBusyFreq;
QDateTime m_decoderBusyStartTime;
bool m_auto;
@ -991,6 +993,7 @@ private:
int computeCycleForDecode(int submode, int k);
int computeAltCycleForDecode(int submode, int k, int offsetFrames);
int computeFramesPerCycleForDecode(int submode);
int computePeriodStartDelayForDecode(int submode);
int computeFramesNeededForDecode(int submode);
bool shortList(QString callsign);
void transmit (double snr = 99.);

View File

@ -4749,10 +4749,12 @@ list. The list can be maintained in Settings (F2).</string>
<addaction name="separator"/>
<addaction name="menu_Decode_Passes"/>
<addaction name="actionModeMultiDecoder"/>
<addaction name="actionModeAutoSync"/>
<addaction name="separator"/>
<addaction name="actionModeAutoreply"/>
<addaction name="actionModeJS8HB"/>
<addaction name="actionHeartbeatAcknowledgements"/>
<addaction name="separator"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuConfig"/>
@ -5762,6 +5764,14 @@ list. The list can be maintained in Settings (F2).</string>
<string>Enable Tuning Tone (T&amp;UNE)</string>
</property>
</action>
<action name="actionModeAutoSync">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Automatic Synchronization (S&amp;YNC)</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -921,6 +921,8 @@ void WideGraph::on_driftSyncResetButton_clicked(){
}
void WideGraph::setDrift(int n){
int prev = drift();
DriftingDateTime::setDrift(n);
qDebug() << qSetRealNumberPrecision(12) << "Drift milliseconds:" << n;
@ -930,6 +932,12 @@ void WideGraph::setDrift(int n){
if(ui->driftSpinBox->value() != n){
ui->driftSpinBox->setValue(n);
}
emit drifted(prev, n);
}
int WideGraph::drift(){
return DriftingDateTime::drift();
}
void WideGraph::setQSYEnabled(bool enabled){

View File

@ -77,6 +77,7 @@ signals:
void setXIT2(int n);
void setFreq3(int rxFreq, int txFreq);
void qsy(int hzDelta);
void drifted(int prev, int cur);
public slots:
void wideFreezeDecode(int n);
@ -85,6 +86,7 @@ public slots:
void setControlsVisible(bool visible);
bool controlsVisible();
void setDrift(int n);
int drift();
void setQSYEnabled(bool enabled);
void setPaused(bool paused){ m_paused = paused; }