mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-10-01 17:26:40 -04:00
Merge pull request #968 from srcejon/radioclock_wwvb
Add support for WWVB to Radio Clock plugin
This commit is contained in:
commit
c7b485504d
@ -68,19 +68,23 @@ public:
|
|||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QDateTime getDateTime() const { return m_dateTime; }
|
|
||||||
|
|
||||||
static MsgDateTime* create(QDateTime dateTime)
|
QDateTime getDateTime() const { return m_dateTime; }
|
||||||
|
RadioClockSettings::DST getDST() const { return m_dst; }
|
||||||
|
|
||||||
|
static MsgDateTime* create(QDateTime dateTime, RadioClockSettings::DST dst = RadioClockSettings::DST::UNKNOWN)
|
||||||
{
|
{
|
||||||
return new MsgDateTime(dateTime);
|
return new MsgDateTime(dateTime, dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDateTime m_dateTime;
|
QDateTime m_dateTime;
|
||||||
|
RadioClockSettings::DST m_dst;
|
||||||
|
|
||||||
MsgDateTime(QDateTime dateTime) :
|
MsgDateTime(QDateTime dateTime, RadioClockSettings::DST dst) :
|
||||||
Message(),
|
Message(),
|
||||||
m_dateTime(dateTime)
|
m_dateTime(dateTime),
|
||||||
|
m_dst(dst)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -104,6 +104,24 @@ bool RadioClockGUI::handleMessage(const Message& message)
|
|||||||
RadioClock::MsgDateTime& report = (RadioClock::MsgDateTime&) message;
|
RadioClock::MsgDateTime& report = (RadioClock::MsgDateTime&) message;
|
||||||
m_dateTime = report.getDateTime();
|
m_dateTime = report.getDateTime();
|
||||||
displayDateTime();
|
displayDateTime();
|
||||||
|
switch (report.getDST())
|
||||||
|
{
|
||||||
|
case RadioClockSettings::UNKNOWN:
|
||||||
|
ui->dst->setText("");
|
||||||
|
break;
|
||||||
|
case RadioClockSettings::NOT_IN_EFFECT:
|
||||||
|
ui->dst->setText("Not in effect");
|
||||||
|
break;
|
||||||
|
case RadioClockSettings::IN_EFFECT:
|
||||||
|
ui->dst->setText("In effect");
|
||||||
|
break;
|
||||||
|
case RadioClockSettings::ENDING:
|
||||||
|
ui->dst->setText("Ending");
|
||||||
|
break;
|
||||||
|
case RadioClockSettings::STARTING:
|
||||||
|
ui->dst->setText("Starting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (RadioClock::MsgStatus::match(message))
|
else if (RadioClock::MsgStatus::match(message))
|
||||||
@ -257,11 +275,11 @@ RadioClockGUI::RadioClockGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
|
|||||||
|
|
||||||
m_scopeVis = m_radioClock->getScopeSink();
|
m_scopeVis = m_radioClock->getScopeSink();
|
||||||
m_scopeVis->setGLScope(ui->glScope);
|
m_scopeVis->setGLScope(ui->glScope);
|
||||||
m_scopeVis->setNbStreams(7);
|
m_scopeVis->setNbStreams(RadioClockSettings::m_scopeStreams);
|
||||||
m_scopeVis->setLiveRate(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE);
|
m_scopeVis->setLiveRate(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE);
|
||||||
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
|
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
|
||||||
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
|
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
|
||||||
ui->scopeGUI->setStreams(QStringList({"IQ", "MagSq", "TH", "FM", "Data", "Samp", "GotMM"}));
|
ui->scopeGUI->setStreams(QStringList({"IQ", "MagSq", "TH", "FM", "Data", "Samp", "GotMM", "GotM"}));
|
||||||
ui->scopeGUI->setSampleRate(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE);
|
ui->scopeGUI->setSampleRate(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE);
|
||||||
|
|
||||||
ui->status->setText("Looking for minute marker");
|
ui->status->setText("Looking for minute marker");
|
||||||
|
@ -368,6 +368,11 @@
|
|||||||
<string>TDF</string>
|
<string>TDF</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>WWVB</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -554,6 +559,32 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="dstLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Daylight savings</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="dst">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Demodulator status</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -34,7 +34,8 @@ struct RadioClockSettings
|
|||||||
enum Modulation {
|
enum Modulation {
|
||||||
MSF,
|
MSF,
|
||||||
DCF77,
|
DCF77,
|
||||||
TDF
|
TDF,
|
||||||
|
WWVB
|
||||||
} m_modulation;
|
} m_modulation;
|
||||||
enum DisplayTZ {
|
enum DisplayTZ {
|
||||||
BROADCAST,
|
BROADCAST,
|
||||||
@ -53,6 +54,7 @@ struct RadioClockSettings
|
|||||||
uint16_t m_reverseAPIChannelIndex;
|
uint16_t m_reverseAPIChannelIndex;
|
||||||
Serializable *m_scopeGUI;
|
Serializable *m_scopeGUI;
|
||||||
static const int RADIOCLOCK_CHANNEL_SAMPLE_RATE = 1000;
|
static const int RADIOCLOCK_CHANNEL_SAMPLE_RATE = 1000;
|
||||||
|
static const int m_scopeStreams = 8;
|
||||||
|
|
||||||
RadioClockSettings();
|
RadioClockSettings();
|
||||||
void resetToDefaults();
|
void resetToDefaults();
|
||||||
@ -60,6 +62,14 @@ struct RadioClockSettings
|
|||||||
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
|
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
|
||||||
QByteArray serialize() const;
|
QByteArray serialize() const;
|
||||||
bool deserialize(const QByteArray& data);
|
bool deserialize(const QByteArray& data);
|
||||||
|
|
||||||
|
enum DST {
|
||||||
|
UNKNOWN,
|
||||||
|
IN_EFFECT,
|
||||||
|
NOT_IN_EFFECT,
|
||||||
|
STARTING,
|
||||||
|
ENDING
|
||||||
|
}; // Daylight savings status
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDE_RADIOCLOCKSETTINGS_H */
|
#endif /* INCLUDE_RADIOCLOCKSETTINGS_H */
|
||||||
|
@ -46,14 +46,16 @@ RadioClockSink::RadioClockSink(RadioClock *radioClock) :
|
|||||||
m_periodCount(0),
|
m_periodCount(0),
|
||||||
m_gotMinuteMarker(false),
|
m_gotMinuteMarker(false),
|
||||||
m_second(0),
|
m_second(0),
|
||||||
|
m_dst(RadioClockSettings::UNKNOWN),
|
||||||
m_zeroCount(0),
|
m_zeroCount(0),
|
||||||
m_sampleBufferIndex(0)
|
m_sampleBufferIndex(0),
|
||||||
|
m_gotMarker(false)
|
||||||
{
|
{
|
||||||
m_phaseDiscri.setFMScaling(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE / (2.0f * 20.0/M_PI));
|
m_phaseDiscri.setFMScaling(RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE / (2.0f * 20.0/M_PI));
|
||||||
applySettings(m_settings, true);
|
applySettings(m_settings, true);
|
||||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < RadioClockSettings::m_scopeStreams; i++) {
|
||||||
m_sampleBuffer[i].resize(m_sampleBufferSize);
|
m_sampleBuffer[i].resize(m_sampleBufferSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,13 +80,14 @@ void RadioClockSink::sampleToScope(Complex sample)
|
|||||||
m_sampleBuffer[4][m_sampleBufferIndex] = Complex(m_data, 0.0f);
|
m_sampleBuffer[4][m_sampleBufferIndex] = Complex(m_data, 0.0f);
|
||||||
m_sampleBuffer[5][m_sampleBufferIndex] = Complex(m_sample, 0.0f);
|
m_sampleBuffer[5][m_sampleBufferIndex] = Complex(m_sample, 0.0f);
|
||||||
m_sampleBuffer[6][m_sampleBufferIndex] = Complex(m_gotMinuteMarker, 0.0f);
|
m_sampleBuffer[6][m_sampleBufferIndex] = Complex(m_gotMinuteMarker, 0.0f);
|
||||||
|
m_sampleBuffer[7][m_sampleBufferIndex] = Complex(m_gotMarker, 0.0f);
|
||||||
m_sampleBufferIndex++;
|
m_sampleBufferIndex++;
|
||||||
|
|
||||||
if (m_sampleBufferIndex == m_sampleBufferSize)
|
if (m_sampleBufferIndex == m_sampleBufferSize)
|
||||||
{
|
{
|
||||||
std::vector<ComplexVector::const_iterator> vbegin;
|
std::vector<ComplexVector::const_iterator> vbegin;
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < RadioClockSettings::m_scopeStreams; i++) {
|
||||||
vbegin.push_back(m_sampleBuffer[i].begin());
|
vbegin.push_back(m_sampleBuffer[i].begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,18 +142,20 @@ int RadioClockSink::bcd(int firstBit, int lastBit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract binary-coded decimal from time code - MSB first
|
// Extract binary-coded decimal from time code - MSB first
|
||||||
int RadioClockSink::bcdMSB(int firstBit, int lastBit)
|
int RadioClockSink::bcdMSB(int firstBit, int lastBit, int skipBit1, int skipBit2)
|
||||||
{
|
{
|
||||||
const int vals[] = {1, 2, 4, 8, 10, 20, 40, 80};
|
const int vals[] = {1, 2, 4, 8, 10, 20, 40, 80, 100, 200};
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
int val = 0;
|
int val = 0;
|
||||||
for (int i = lastBit; i >= firstBit; i--)
|
for (int i = lastBit; i >= firstBit; i--)
|
||||||
{
|
{
|
||||||
|
if ((i != skipBit1) && (i != skipBit2)) {
|
||||||
if (m_timeCode[i]) {
|
if (m_timeCode[i]) {
|
||||||
val += vals[idx];
|
val += vals[idx];
|
||||||
}
|
}
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +269,19 @@ void RadioClockSink::dcf77()
|
|||||||
parityError= "Data parity error";
|
parityError= "Data parity error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Daylight savings
|
||||||
|
if (m_timeCode[17] && m_timeCode[16]) {
|
||||||
|
m_dst = RadioClockSettings::ENDING;
|
||||||
|
} else if (m_timeCode[17]) {
|
||||||
|
m_dst = RadioClockSettings::IN_EFFECT;
|
||||||
|
} else if (m_timeCode[18] && m_timeCode[16]) {
|
||||||
|
m_dst = RadioClockSettings::STARTING;
|
||||||
|
} else if (m_timeCode[18]) {
|
||||||
|
m_dst = RadioClockSettings::NOT_IN_EFFECT;
|
||||||
|
} else {
|
||||||
|
m_dst = RadioClockSettings::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
if (parityError.isEmpty())
|
if (parityError.isEmpty())
|
||||||
{
|
{
|
||||||
// Bit 17 indicates CEST rather than CET
|
// Bit 17 indicates CEST rather than CET
|
||||||
@ -289,7 +307,7 @@ void RadioClockSink::dcf77()
|
|||||||
|
|
||||||
if (getMessageQueueToChannel())
|
if (getMessageQueueToChannel())
|
||||||
{
|
{
|
||||||
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime);
|
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime, m_dst);
|
||||||
getMessageQueueToChannel()->push(msg);
|
getMessageQueueToChannel()->push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -406,6 +424,19 @@ void RadioClockSink::tdf(Complex &ci)
|
|||||||
int month = bcd(45, 49);
|
int month = bcd(45, 49);
|
||||||
int year = 2000 + bcd(50, 57);
|
int year = 2000 + bcd(50, 57);
|
||||||
|
|
||||||
|
// Daylight savings
|
||||||
|
if (m_timeCode[17] && m_timeCode[16]) {
|
||||||
|
m_dst = RadioClockSettings::ENDING;
|
||||||
|
} else if (m_timeCode[17]) {
|
||||||
|
m_dst = RadioClockSettings::IN_EFFECT;
|
||||||
|
} else if (m_timeCode[18] && m_timeCode[16]) {
|
||||||
|
m_dst = RadioClockSettings::STARTING;
|
||||||
|
} else if (m_timeCode[18]) {
|
||||||
|
m_dst = RadioClockSettings::NOT_IN_EFFECT;
|
||||||
|
} else {
|
||||||
|
m_dst = RadioClockSettings::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
QString parityError;
|
QString parityError;
|
||||||
if (!evenParity(21, 27, m_timeCode[28])) {
|
if (!evenParity(21, 27, m_timeCode[28])) {
|
||||||
parityError = "Minute parity error";
|
parityError = "Minute parity error";
|
||||||
@ -442,7 +473,7 @@ void RadioClockSink::tdf(Complex &ci)
|
|||||||
|
|
||||||
if (getMessageQueueToChannel())
|
if (getMessageQueueToChannel())
|
||||||
{
|
{
|
||||||
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime);
|
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime, m_dst);
|
||||||
getMessageQueueToChannel()->push(msg);
|
getMessageQueueToChannel()->push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -537,6 +568,17 @@ void RadioClockSink::msf60()
|
|||||||
int month = bcdMSB(25, 29);
|
int month = bcdMSB(25, 29);
|
||||||
int year = 2000 + bcdMSB(17, 24);
|
int year = 2000 + bcdMSB(17, 24);
|
||||||
|
|
||||||
|
// Daylight savings
|
||||||
|
if (m_timeCodeB[58] && m_timeCodeB[53]) {
|
||||||
|
m_dst = RadioClockSettings::ENDING;
|
||||||
|
} else if (m_timeCodeB[58]) {
|
||||||
|
m_dst = RadioClockSettings::IN_EFFECT;
|
||||||
|
} else if (m_timeCodeB[53]) {
|
||||||
|
m_dst = RadioClockSettings::STARTING;
|
||||||
|
} else {
|
||||||
|
m_dst = RadioClockSettings::NOT_IN_EFFECT;
|
||||||
|
}
|
||||||
|
|
||||||
QString parityError;
|
QString parityError;
|
||||||
if (!oddParity(39, 51, m_timeCodeB[57])) {
|
if (!oddParity(39, 51, m_timeCodeB[57])) {
|
||||||
parityError = "Hour/minute parity error";
|
parityError = "Hour/minute parity error";
|
||||||
@ -573,7 +615,173 @@ void RadioClockSink::msf60()
|
|||||||
|
|
||||||
if (getMessageQueueToChannel())
|
if (getMessageQueueToChannel())
|
||||||
{
|
{
|
||||||
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime);
|
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime, m_dst);
|
||||||
|
getMessageQueueToChannel()->push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_periodCount == 1000)
|
||||||
|
{
|
||||||
|
m_periodCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_prevData = m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// USA WWVB 60kHz
|
||||||
|
// https://en.wikipedia.org/wiki/WWVB
|
||||||
|
void RadioClockSink::wwvb()
|
||||||
|
{
|
||||||
|
// WWVB reduces carrier by -17dB
|
||||||
|
// 0.2s reduction is zero bit, 0.5s reduction is one bit
|
||||||
|
// 0.8s reduction is a marker. Seven markers per minute (0, 9, 19, 29, 39, 49, and 59s) and for leap second
|
||||||
|
m_threshold = m_thresholdMovingAverage.asDouble() * m_linearThreshold; // xdB below average
|
||||||
|
m_data = m_magsq > m_threshold;
|
||||||
|
|
||||||
|
// Look for minute marker - two consequtive markers
|
||||||
|
if ((m_data == 0) && (m_prevData == 1))
|
||||||
|
{
|
||||||
|
if ( (m_highCount <= RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE * 0.3)
|
||||||
|
&& (m_lowCount >= RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE * 0.7)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (m_gotMarker && !m_gotMinuteMarker)
|
||||||
|
{
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Minute marker: (low " << m_lowCount << " high " << m_highCount << ") prev period " << m_periodCount;
|
||||||
|
m_gotMinuteMarker = true;
|
||||||
|
m_second = 1;
|
||||||
|
m_secondMarkers = 1;
|
||||||
|
if (getMessageQueueToChannel()) {
|
||||||
|
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("Got minute marker"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Marker: (low " << m_lowCount << " high " << m_highCount << ") prev period " << m_periodCount << " second " << m_second;
|
||||||
|
}
|
||||||
|
m_gotMarker = true;
|
||||||
|
m_periodCount = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_gotMarker = false;
|
||||||
|
}
|
||||||
|
m_lowCount = 0;
|
||||||
|
}
|
||||||
|
else if ((m_data == 1) && (m_prevData == 0))
|
||||||
|
{
|
||||||
|
m_highCount = 0;
|
||||||
|
}
|
||||||
|
else if (m_data == 1)
|
||||||
|
{
|
||||||
|
m_highCount++;
|
||||||
|
}
|
||||||
|
else if (m_data == 0)
|
||||||
|
{
|
||||||
|
m_lowCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sample = false;
|
||||||
|
if (m_gotMinuteMarker)
|
||||||
|
{
|
||||||
|
m_periodCount++;
|
||||||
|
if (m_periodCount == 100)
|
||||||
|
{
|
||||||
|
// Check we get second marker
|
||||||
|
m_secondMarkers += m_data == 0;
|
||||||
|
// If we see too many 1s instead of 0s, assume we've lost the signal
|
||||||
|
if ((m_second > 10) && (m_secondMarkers / m_second < 0.7))
|
||||||
|
{
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Lost lock: " << m_secondMarkers << m_second;
|
||||||
|
m_gotMinuteMarker = false;
|
||||||
|
if (getMessageQueueToChannel()) {
|
||||||
|
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("Looking for minute marker"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_sample = true;
|
||||||
|
}
|
||||||
|
else if (m_periodCount == 350)
|
||||||
|
{
|
||||||
|
// Get data bit A for timecode
|
||||||
|
m_timeCode[m_second] = !m_data; // No carrier = 1, carrier = 0
|
||||||
|
m_sample = true;
|
||||||
|
}
|
||||||
|
else if (m_periodCount == 950)
|
||||||
|
{
|
||||||
|
if (m_second == 59)
|
||||||
|
{
|
||||||
|
// Check markers are decoded as 1s
|
||||||
|
const QList<int> markerBits = {9, 19, 29, 39, 49, 59};
|
||||||
|
int missingMarkers = 0;
|
||||||
|
for (int i = 0; i < markerBits.size(); i++)
|
||||||
|
{
|
||||||
|
if (m_timeCode[markerBits[i]] != 1)
|
||||||
|
{
|
||||||
|
missingMarkers++;
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Missing marker at bit " << markerBits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missingMarkers >= 3)
|
||||||
|
{
|
||||||
|
m_gotMinuteMarker = false;
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Lost lock: Missing markers: " << missingMarkers;
|
||||||
|
if (getMessageQueueToChannel()) {
|
||||||
|
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("Looking for minute marker"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 0s where expected
|
||||||
|
const QList<int> zeroBits = {4, 10, 11, 14, 20, 21, 24, 34, 35, 44, 54};
|
||||||
|
for (int i = 0; i < zeroBits.size(); i++)
|
||||||
|
{
|
||||||
|
if (m_timeCode[zeroBits[i]] != 0) {
|
||||||
|
qDebug() << "RadioClockSink::wwvb - Unexpected 1 at bit " << zeroBits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode timecode to time and date
|
||||||
|
int minute = bcdMSB(1, 8, 4);
|
||||||
|
int hour = bcdMSB(12, 18, 14);
|
||||||
|
int dayOfYear = bcdMSB(22, 33, 24, 29);
|
||||||
|
int year = 2000 + bcdMSB(45, 53, 49);
|
||||||
|
|
||||||
|
// Daylight savings
|
||||||
|
int dst = (m_timeCode[57] << 1) | m_timeCode[58];
|
||||||
|
switch (dst)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
m_dst = RadioClockSettings::NOT_IN_EFFECT;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
m_dst = RadioClockSettings::ENDING;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
m_dst = RadioClockSettings::STARTING;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
m_dst = RadioClockSettings::IN_EFFECT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time is UTC
|
||||||
|
QDate date(year, 1, 1);
|
||||||
|
date = date.addDays(dayOfYear - 1);
|
||||||
|
m_dateTime = QDateTime(date, QTime(hour, minute), Qt::OffsetFromUTC, 0);
|
||||||
|
if (getMessageQueueToChannel()) {
|
||||||
|
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("OK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_second = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_second++;
|
||||||
|
m_dateTime = m_dateTime.addSecs(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getMessageQueueToChannel())
|
||||||
|
{
|
||||||
|
RadioClock::MsgDateTime *msg = RadioClock::MsgDateTime::create(m_dateTime, m_dst);
|
||||||
getMessageQueueToChannel()->push(msg);
|
getMessageQueueToChannel()->push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -609,6 +817,8 @@ void RadioClockSink::processOneSample(Complex &ci)
|
|||||||
dcf77();
|
dcf77();
|
||||||
} else if (m_settings.m_modulation == RadioClockSettings::TDF) {
|
} else if (m_settings.m_modulation == RadioClockSettings::TDF) {
|
||||||
tdf(ci);
|
tdf(ci);
|
||||||
|
} else if (m_settings.m_modulation == RadioClockSettings::WWVB) {
|
||||||
|
wwvb();
|
||||||
} else {
|
} else {
|
||||||
msf60();
|
msf60();
|
||||||
}
|
}
|
||||||
@ -667,6 +877,7 @@ void RadioClockSink::applySettings(const RadioClockSettings& settings, bool forc
|
|||||||
m_highCount = 0;
|
m_highCount = 0;
|
||||||
m_zeroCount = 0;
|
m_zeroCount = 0;
|
||||||
m_second = 0;
|
m_second = 0;
|
||||||
|
m_dst = RadioClockSettings::UNKNOWN;
|
||||||
if (getMessageQueueToChannel()) {
|
if (getMessageQueueToChannel()) {
|
||||||
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("Looking for minute marker"));
|
getMessageQueueToChannel()->push(RadioClock::MsgStatus::create("Looking for minute marker"));
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,9 @@ private:
|
|||||||
|
|
||||||
MessageQueue *m_messageQueueToChannel;
|
MessageQueue *m_messageQueueToChannel;
|
||||||
|
|
||||||
MovingAverageUtil<Real, double, 40> m_movingAverage; //!< Moving average has sharpest step response of LPFs
|
MovingAverageUtil<Real, double, 80> m_movingAverage; //!< Moving average has sharpest step response of LPFs
|
||||||
|
|
||||||
MovingAverageUtil<Real, double, RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE> m_thresholdMovingAverage; // Average over 1 second
|
MovingAverageUtil<Real, double, 10*RadioClockSettings::RADIOCLOCK_CHANNEL_SAMPLE_RATE> m_thresholdMovingAverage; // Average over 10 seconds (because VVWB markers are 80% off)
|
||||||
|
|
||||||
int m_data; //!< Demod data before clocking
|
int m_data; //!< Demod data before clocking
|
||||||
int m_prevData; //!< Previous value of m_data
|
int m_prevData; //!< Previous value of m_data
|
||||||
@ -129,6 +129,7 @@ private:
|
|||||||
|
|
||||||
Real m_threshold; //!< Current threshold for display on scope
|
Real m_threshold; //!< Current threshold for display on scope
|
||||||
Real m_linearThreshold; //!< settings.m_threshold as a linear value rather than dB
|
Real m_linearThreshold; //!< settings.m_threshold as a linear value rather than dB
|
||||||
|
RadioClockSettings::DST m_dst; //!< Daylight savings time status
|
||||||
|
|
||||||
// MSF demod state
|
// MSF demod state
|
||||||
int m_timeCodeB[61];
|
int m_timeCodeB[61];
|
||||||
@ -138,15 +139,18 @@ private:
|
|||||||
int m_zeroCount;
|
int m_zeroCount;
|
||||||
MovingAverageUtil<Real, double, 10> m_fmDemodMovingAverage;
|
MovingAverageUtil<Real, double, 10> m_fmDemodMovingAverage;
|
||||||
int m_bits[4];
|
int m_bits[4];
|
||||||
ComplexVector m_sampleBuffer[7];
|
ComplexVector m_sampleBuffer[RadioClockSettings::m_scopeStreams];
|
||||||
static const int m_sampleBufferSize = 60;
|
static const int m_sampleBufferSize = 60;
|
||||||
int m_sampleBufferIndex;
|
int m_sampleBufferIndex;
|
||||||
|
|
||||||
|
// WWVB state
|
||||||
|
bool m_gotMarker; //!< Marker in previous second
|
||||||
|
|
||||||
void processOneSample(Complex &ci);
|
void processOneSample(Complex &ci);
|
||||||
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
|
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
|
||||||
void sampleToScope(Complex sample);
|
void sampleToScope(Complex sample);
|
||||||
int bcd(int firstBit, int lastBit);
|
int bcd(int firstBit, int lastBit);
|
||||||
int bcdMSB(int firstBit, int lastBit);
|
int bcdMSB(int firstBit, int lastBit, int skipBit1=0, int skipBit2=0);
|
||||||
int xorBits(int firstBit, int lastBit);
|
int xorBits(int firstBit, int lastBit);
|
||||||
bool evenParity(int firstBit, int lastBit, int parityBit);
|
bool evenParity(int firstBit, int lastBit, int parityBit);
|
||||||
bool oddParity(int firstBit, int lastBit, int parityBit);
|
bool oddParity(int firstBit, int lastBit, int parityBit);
|
||||||
@ -154,6 +158,7 @@ private:
|
|||||||
void dcf77();
|
void dcf77();
|
||||||
void tdf(Complex &ci);
|
void tdf(Complex &ci);
|
||||||
void msf60();
|
void msf60();
|
||||||
|
void wwvb();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INCLUDE_RADIOCLOCKSINK_H
|
#endif // INCLUDE_RADIOCLOCKSINK_H
|
||||||
|
@ -7,8 +7,9 @@ This plugin can be used to receive the time and date as broadcast on Low Frequen
|
|||||||
* [MSF](https://en.wikipedia.org/wiki/Time_from_NPL_(MSF)) - UK - 60kHz
|
* [MSF](https://en.wikipedia.org/wiki/Time_from_NPL_(MSF)) - UK - 60kHz
|
||||||
* [DCF77](https://en.wikipedia.org/wiki/DCF77) - Germany - 77.5kHz
|
* [DCF77](https://en.wikipedia.org/wiki/DCF77) - Germany - 77.5kHz
|
||||||
* [TDF](https://en.wikipedia.org/wiki/TDF_time_signal) - France - 162kHz
|
* [TDF](https://en.wikipedia.org/wiki/TDF_time_signal) - France - 162kHz
|
||||||
|
* [WWVB](https://en.wikipedia.org/wiki/WWVB) - USA - 60kHz
|
||||||
|
|
||||||
If you'd like other transmitters to be supported (such as WWVB), please upload a .sdriq file to SDRangel's [github issue tracker](https://github.com/f4exb/sdrangel/issues).
|
If you'd like other transmitters to be supported, please upload a .sdriq file to SDRangel's [github issue tracker](https://github.com/f4exb/sdrangel/issues).
|
||||||
|
|
||||||
Typically, it will take two minutes before the time is able to be displayed (up to one minute to find the minute marker, then another minute to receive the timecode).
|
Typically, it will take two minutes before the time is able to be displayed (up to one minute to find the minute marker, then another minute to receive the timecode).
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ This specifies the bandwidth of a LPF that is applied to the input signal to lim
|
|||||||
|
|
||||||
<h3>5: TH - Threshold</h3>
|
<h3>5: TH - Threshold</h3>
|
||||||
|
|
||||||
For MSF and DCF77, specifies the threshold in dB below the average carrier power level that determines a binary 0 or 1.
|
For MSF, DCF77 and WWVB, specifies the threshold in dB below the average carrier power level that determines a binary 0 or 1.
|
||||||
|
|
||||||
<h3>6: Modulation</h3>
|
<h3>6: Modulation</h3>
|
||||||
|
|
||||||
@ -47,12 +48,13 @@ Specifies the modulation and timecode encoding used:
|
|||||||
* MSF - OOK (On-off keying)
|
* MSF - OOK (On-off keying)
|
||||||
* DCF77 - OOK (On-off keying)
|
* DCF77 - OOK (On-off keying)
|
||||||
* TDF - PM (Phase modulation)
|
* TDF - PM (Phase modulation)
|
||||||
|
* WWVB - OOK (On-off keying)
|
||||||
|
|
||||||
<h3>7: Display Time Zone</h3>
|
<h3>7: Display Time Zone</h3>
|
||||||
|
|
||||||
Specifies the time zone used to display the received time. This can be:
|
Specifies the time zone used to display the received time. This can be:
|
||||||
|
|
||||||
* Broadcast - the time is displayed as broadcast (which is typically the time zone of the country the signal is broadcast from, adjusted for summer time).
|
* Broadcast - the time is displayed as broadcast (which is typically the time zone of the country the signal is broadcast from, adjusted for summer time. WWVB broadcasts UTC).
|
||||||
* Local - the time is converted to the local time (as determined by your operating system's time zone).
|
* Local - the time is converted to the local time (as determined by your operating system's time zone).
|
||||||
* UTC - the time is converted to Coordinated Universal Time.
|
* UTC - the time is converted to Coordinated Universal Time.
|
||||||
|
|
||||||
@ -77,6 +79,17 @@ The date and time fields are only valid when the status indicates OK.
|
|||||||
|
|
||||||
If while in the OK state several second markers are not detected, the status will return to Looking for minute marker.
|
If while in the OK state several second markers are not detected, the status will return to Looking for minute marker.
|
||||||
|
|
||||||
|
<h3>11: Daylight Savings</h3>
|
||||||
|
|
||||||
|
Displays the daylight savings state:
|
||||||
|
|
||||||
|
* In effect
|
||||||
|
* Not in effect
|
||||||
|
* Starting
|
||||||
|
* Ending
|
||||||
|
|
||||||
|
For MSF, DCF77 and TDF, starting/ending is indicated one hour before the change. For WWVB it is set for the whole day.
|
||||||
|
|
||||||
<h3>Waveforms</h3>
|
<h3>Waveforms</h3>
|
||||||
|
|
||||||
The scope shows how various variables within the demodulator vary with time. These can be used to help debug operation of the demodulator.
|
The scope shows how various variables within the demodulator vary with time. These can be used to help debug operation of the demodulator.
|
||||||
@ -90,6 +103,7 @@ The signals available include:
|
|||||||
- Data - Demodulated data. For MSF/DCF77, this data=MagSq>TH.
|
- Data - Demodulated data. For MSF/DCF77, this data=MagSq>TH.
|
||||||
- Samp - Indicates when data is sampled (either for the second marker or for a timecode data bit).
|
- Samp - Indicates when data is sampled (either for the second marker or for a timecode data bit).
|
||||||
- GotMM - Indicates whether the minute marker has been received. Cleared when synchronization to second marker is lost.
|
- GotMM - Indicates whether the minute marker has been received. Cleared when synchronization to second marker is lost.
|
||||||
|
- GotM - Indicates when a marker is detected. For WWVB only.
|
||||||
|
|
||||||
As an example of how this can be used, we can plot the MagSq as X and the calculated TH as Y, which can help to set the value of the
|
As an example of how this can be used, we can plot the MagSq as X and the calculated TH as Y, which can help to set the value of the
|
||||||
TH setting to an approproate level.
|
TH setting to an approproate level.
|
||||||
|
Loading…
Reference in New Issue
Block a user