1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

Merge pull request #2057 from srcejon/freq_scanner

Support VOR Localizer on Qt6. Add Add Channels dialog to SID. Add JJY to Radio Clock
This commit is contained in:
Edouard Griffiths 2024-04-07 20:12:41 +02:00 committed by GitHub
commit a6c1dc56c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1426 additions and 85 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -289,13 +289,26 @@ int ChannelPower::webapiSettingsPutPatch(
ChannelPowerSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureChannelPower *msg = MsgConfigureChannelPower::create(settings, channelSettingsKeys, force);
// Ensure inputFrequencyOffset and frequency are consistent
QStringList settingsKeys = channelSettingsKeys;
if (settingsKeys.contains("frequency") && !settingsKeys.contains("inputFrequencyOffset"))
{
settings.m_inputFrequencyOffset = settings.m_frequency - m_centerFrequency;
settingsKeys.append("inputFrequencyOffset");
}
else if (settingsKeys.contains("inputFrequencyOffset") && !settingsKeys.contains("frequency"))
{
settings.m_frequency = m_centerFrequency + settings.m_inputFrequencyOffset;
settingsKeys.append("frequency");
}
MsgConfigureChannelPower *msg = MsgConfigureChannelPower::create(settings, settingsKeys, force);
m_inputMessageQueue.push(msg);
qDebug("ChannelPower::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureChannelPower *msgToGUI = MsgConfigureChannelPower::create(settings, channelSettingsKeys, force);
MsgConfigureChannelPower *msgToGUI = MsgConfigureChannelPower::create(settings, settingsKeys, force);
m_guiMessageQueue->push(msgToGUI);
}
@ -312,6 +325,12 @@ void ChannelPower::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getChannelPowerSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("frequencyMode")) {
settings.m_frequencyMode = (ChannelPowerSettings::FrequencyMode) response.getChannelPowerSettings()->getFrequencyMode();
}
if (channelSettingsKeys.contains("frequency")) {
settings.m_frequency = response.getChannelPowerSettings()->getFrequency();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getChannelPowerSettings()->getRfBandwidth();
}
@ -356,6 +375,8 @@ void ChannelPower::webapiUpdateChannelSettings(
void ChannelPower::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ChannelPowerSettings& settings)
{
response.getChannelPowerSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getChannelPowerSettings()->setFrequencyMode(settings.m_frequencyMode);
response.getChannelPowerSettings()->setFrequency(settings.m_frequency);
response.getChannelPowerSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getChannelPowerSettings()->setPulseThreshold(settings.m_pulseThreshold);
response.getChannelPowerSettings()->setAveragePeriodUs(settings.m_averagePeriodUS);
@ -469,6 +490,12 @@ void ChannelPower::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgChannelPowerSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("frequencyMode") || force) {
swgChannelPowerSettings->setFrequencyMode(settings.m_frequencyMode);
}
if (channelSettingsKeys.contains("inputFrequency") || force) {
swgChannelPowerSettings->setFrequency(settings.m_frequency);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgChannelPowerSettings->setRfBandwidth(settings.m_rfBandwidth);
}

View File

@ -1,4 +1,4 @@
<h1>Channel Power Plugin</h1>
<h1>Channel Power Plugin</h1>
<h2>Introduction</h2>
@ -10,38 +10,48 @@ The top and bottom bars of the channel window are described [here](../../../sdrg
![Channel power plugin GUI](../../../doc/img/ChannelPower_plugin_settings.png)
<h3>1: Frequency shift from center frequency of reception</h3>
<h3>1: Channel frequency entry mode</h3>
Use the wheels to adjust the channel center frequency as a shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
Select from one of the following modes to determine how the channel center frequency is calculated:
<h3>2: BW - Channel Bandwidth</h3>
* Δf - Specify an offset in Hz from device center frequency.
* f - Specific a frequency in Hz.
<h3>2: Channel Frequency</h3>
Specifies channel center frequency according to frequency entry mode (1):
* Δf - Offset in Hz from device center frequency.
* f - Absolute frequency in Hz.
<h3>3: BW - Channel Bandwidth</h3>
Bandwidth in Hz of the channel for which power is to be measured.
<h3>3: Tavg - Average Time</h3>
<h3>4: Tavg - Average Time</h3>
Time period overwhich the channel power is averaged. Values range from 10us to 10s in powers of 10. The available values depend upon the sample rate.
<h3>4: THp - Pulse Threshold</h3>
<h3>5: THp - Pulse Threshold</h3>
The pulse threshold sets the power in dB for which the channel power needs to exceed, in order to be included in the pulse average power measurement.
<h3>5: Avg - Average Power</h3>
<h3>6: Avg - Average Power</h3>
Displays the most recent average power measurement in dB.
<h3>6: Max - Max Peak Power</h3>
<h3>7: Max - Max Peak Power</h3>
Displays the maximum instantaneous peak power measurement in dB.
<h3>7: Min - Min Peak Power</h3>
<h3>8: Min - Min Peak Power</h3>
Displays the minimum instantaneous peak power measurement in dB.
<h3>8: Pulse - Pulse Average Power</h3>
<h3>9: Pulse - Pulse Average Power</h3>
Displays the most recent pulse average power measurement in dB.
<h3>9: Clear Data</h3>
<h3>10: Clear Data</h3>
Clears current measurements (min and max values are reset).

View File

@ -431,7 +431,6 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
console.log("AIRPORT CLICKED ************************* ");
if (mouse.button === Qt.RightButton) {
showRangeItem.visible = !rangeGroup.groupVisible
hideRangeItem.visible = rangeGroup.groupVisible
@ -508,7 +507,6 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: (mouse) => {
console.log("AIRPORT 2 CLICKED ************************* ");
if (showFreq) {
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
if (freqIdx == 0) {

View File

@ -233,9 +233,15 @@ void RadioClock::applySettings(const RadioClockSettings& settings, bool force)
QList<QString> reverseAPIKeys;
if ((settings.m_frequencyMode != m_settings.m_frequencyMode) || force) {
reverseAPIKeys.append("frequencyMode");
}
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_frequency != m_settings.m_frequency) || force) {
reverseAPIKeys.append("frequency");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
@ -331,6 +337,13 @@ int RadioClock::webapiSettingsPutPatch(
RadioClockSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
// Ensure inputFrequencyOffset and frequency are consistent
if (channelSettingsKeys.contains("frequency") && !channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = settings.m_frequency - m_centerFrequency;
} else if (channelSettingsKeys.contains("inputFrequencyOffset") && !channelSettingsKeys.contains("frequency")) {
settings.m_frequency = m_centerFrequency + settings.m_inputFrequencyOffset;
}
MsgConfigureRadioClock *msg = MsgConfigureRadioClock::create(settings, force);
m_inputMessageQueue.push(msg);
@ -351,9 +364,15 @@ void RadioClock::webapiUpdateChannelSettings(
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("frequencyMode")) {
settings.m_frequencyMode = (RadioClockSettings::FrequencyMode) response.getRadioClockSettings()->getFrequencyMode();
}
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getRadioClockSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("frequency")) {
settings.m_frequency = response.getRadioClockSettings()->getFrequency();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getRadioClockSettings()->getRfBandwidth();
}
@ -403,7 +422,9 @@ void RadioClock::webapiUpdateChannelSettings(
void RadioClock::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const RadioClockSettings& settings)
{
response.getRadioClockSettings()->setFrequencyMode((int)settings.m_frequencyMode);
response.getRadioClockSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getRadioClockSettings()->setFrequency(settings.m_frequency);
response.getRadioClockSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getRadioClockSettings()->setThreshold(settings.m_threshold);
response.getRadioClockSettings()->setModulation((int)settings.m_modulation);
@ -536,9 +557,15 @@ void RadioClock::webapiFormatChannelSettings(
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("frequencyMode") || force) {
swgRadioClockSettings->setFrequencyMode(settings.m_frequencyMode);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgRadioClockSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("frequency") || force) {
swgRadioClockSettings->setFrequency(settings.m_frequency);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgRadioClockSettings->setRfBandwidth(settings.m_rfBandwidth);
}

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
@ -139,8 +139,7 @@ bool RadioClockGUI::handleMessage(const Message& message)
const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
calcOffset();
updateAbsoluteCenterFrequency();
return true;
}
@ -148,6 +147,23 @@ bool RadioClockGUI::handleMessage(const Message& message)
return false;
}
// Calculate input frequency offset, when device center frequency changes
void RadioClockGUI::calcOffset()
{
if (m_settings.m_frequencyMode == RadioClockSettings::Offset)
{
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
}
else
{
qint64 offset = m_settings.m_frequency - m_deviceCenterFrequency;
m_channelMarker.setCenterFrequency(offset);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySettings();
}
}
void RadioClockGUI::handleInputMessages()
{
Message* message;
@ -163,8 +179,22 @@ void RadioClockGUI::handleInputMessages()
void RadioClockGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
m_settings.m_frequency = m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset;
qint64 value = 0;
if (m_settings.m_frequencyMode == RadioClockSettings::Offset) {
value = m_settings.m_inputFrequencyOffset;
} else if (m_settings.m_frequencyMode == RadioClockSettings::Absolute) {
value = m_settings.m_frequency;
}
ui->deltaFrequency->blockSignals(true);
ui->deltaFrequency->setValue(value);
ui->deltaFrequency->blockSignals(false);
updateAbsoluteCenterFrequency();
applySettings();
}
@ -173,9 +203,46 @@ void RadioClockGUI::channelMarkerHighlightedByCursor()
setHighlighted(m_channelMarker.getHighlighted());
}
void RadioClockGUI::on_frequencyMode_currentIndexChanged(int index)
{
m_settings.m_frequencyMode = (RadioClockSettings::FrequencyMode) index;
ui->deltaFrequency->blockSignals(true);
if (m_settings.m_frequencyMode == RadioClockSettings::Offset)
{
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->deltaFrequency->setValue(m_settings.m_inputFrequencyOffset);
ui->deltaUnits->setText("Hz");
}
else if (m_settings.m_frequencyMode == RadioClockSettings::Absolute)
{
ui->deltaFrequency->setValueRange(true, 11, 0, 99999999999, 0);
ui->deltaFrequency->setValue(m_settings.m_frequency);
ui->deltaUnits->setText("Hz");
}
ui->deltaFrequency->blockSignals(false);
updateAbsoluteCenterFrequency();
applySettings();
}
void RadioClockGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
qint64 offset = 0;
if (m_settings.m_frequencyMode == RadioClockSettings::Offset)
{
offset = value;
m_settings.m_frequency = m_deviceCenterFrequency + offset;
}
else if (m_settings.m_frequencyMode == RadioClockSettings::Absolute)
{
m_settings.m_frequency = value;
offset = m_settings.m_frequency - m_deviceCenterFrequency;
}
m_channelMarker.setCenterFrequency(offset);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySettings();
@ -300,7 +367,6 @@ RadioClockGUI::RadioClockGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
ui->status->setText("Looking for minute marker");
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
@ -368,7 +434,8 @@ void RadioClockGUI::displaySettings()
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->frequencyMode->setCurrentIndex((int) m_settings.m_frequencyMode);
on_frequencyMode_currentIndexChanged((int) m_settings.m_frequencyMode);
ui->rfBWText->setText(QString("%1 Hz").arg((int)m_settings.m_rfBandwidth));
ui->rfBW->setValue(m_settings.m_rfBandwidth);
@ -420,6 +487,7 @@ void RadioClockGUI::tick()
void RadioClockGUI::makeUIConnections()
{
QObject::connect(ui->frequencyMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioClockGUI::on_frequencyMode_currentIndexChanged);
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &RadioClockGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &RadioClockGUI::on_rfBW_valueChanged);
QObject::connect(ui->threshold, &QDial::valueChanged, this, &RadioClockGUI::on_threshold_valueChanged);
@ -430,4 +498,11 @@ void RadioClockGUI::makeUIConnections()
void RadioClockGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
if ( (m_basebandSampleRate > 1)
&& ( (m_settings.m_inputFrequencyOffset >= m_basebandSampleRate / 2)
|| (m_settings.m_inputFrequencyOffset < -m_basebandSampleRate / 2))) {
setStatusText("Frequency out of band");
} else {
setStatusText("");
}
}

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020-2022 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
@ -93,6 +93,7 @@ private:
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void calcOffset();
void displayDateTime();
@ -100,6 +101,7 @@ private:
void enterEvent(EnterEventType*);
private slots:
void on_frequencyMode_currentIndexChanged(int index);
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int index);
void on_threshold_valueChanged(int value);

View File

@ -74,16 +74,32 @@
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<widget class="QComboBox" name="frequencyMode">
<property name="minimumSize">
<size>
<width>16</width>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Select frequency entry mode.</string>
</property>
<item>
<property name="text">
<string>Δf</string>
</property>
</item>
<item>
<property name="text">
<string>f</string>
</property>
</item>
</widget>
</item>
<item>
@ -370,6 +386,11 @@
<string>WWVB</string>
</property>
</item>
<item>
<property name="text">
<string>JJY</string>
</property>
</item>
</widget>
</item>
<item>

View File

@ -2,7 +2,7 @@
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
@ -35,7 +35,9 @@ RadioClockSettings::RadioClockSettings() :
void RadioClockSettings::resetToDefaults()
{
m_frequencyMode = Offset;
m_inputFrequencyOffset = 0;
m_frequency = 0;
m_rfBandwidth = 50.0f;
m_threshold = 5;
m_modulation = MSF;
@ -58,9 +60,11 @@ QByteArray RadioClockSettings::serialize() const
s.writeS32(1, m_inputFrequencyOffset);
s.writeFloat(2, m_rfBandwidth);
s.writeS64(3, m_frequency);
s.writeFloat(4, m_threshold);
s.writeS32(5, (int)m_modulation);
s.writeS32(6, (int)m_timezone);
s.writeS32(7, (int)m_frequencyMode);
s.writeU32(12, m_rgbColor);
s.writeString(13, m_title);
@ -108,9 +112,11 @@ bool RadioClockSettings::deserialize(const QByteArray& data)
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readFloat(2, &m_rfBandwidth, 50.0f);
d.readS64(3, &m_frequency, 0);
d.readFloat(4, &m_threshold, 30);
d.readS32(5, (int *)&m_modulation, DCF77);
d.readS32(6, (int *)&m_timezone, BROADCAST);
d.readS32(7, (int *)&m_frequencyMode, Offset);
d.readU32(12, &m_rgbColor, QColor(102, 0, 0).rgb());
d.readString(13, &m_title, "Radio Clock");

View File

@ -2,7 +2,7 @@
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
@ -30,14 +30,20 @@ class Serializable;
struct RadioClockSettings
{
enum FrequencyMode {
Offset,
Absolute
} m_frequencyMode;
qint32 m_inputFrequencyOffset;
qint64 m_frequency;
Real m_rfBandwidth;
Real m_threshold; //!< For MSF and DCF in dB
enum Modulation {
MSF,
DCF77,
TDF,
WWVB
WWVB,
JJY
} m_modulation;
enum DisplayTZ {
BROADCAST,

View File

@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
@ -794,6 +794,158 @@ void RadioClockSink::wwvb()
m_prevData = m_data;
}
// Japan JJY 40kHz
// https://en.wikipedia.org/wiki/JJY
void RadioClockSink::jjy()
{
// JJY reduces carrier by -10dB
// Full power, then reduced power, which is the opposite of WWVB
// 0.8s full power is is zero bit, 0.5s full power is one bit
// 0.2s full power 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 == 1) && (m_prevData == 0))
{
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::jjy - 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::jjy - 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_highCount = 0;
}
else if ((m_data == 0) && (m_prevData == 1))
{
m_lowCount = 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 == 1;
// If we see too many 0s instead of 1s, assume we've lost the signal
if ((m_second > 10) && (m_secondMarkers / m_second < 0.7))
{
qDebug() << "RadioClockSink::jjy - 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 == 650)
{
// 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::jjy - Missing marker at bit " << markerBits[i];
}
}
if (missingMarkers >= 3)
{
m_gotMinuteMarker = false;
qDebug() << "RadioClockSink::jjy - 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, 55, 56, 57, 58};
for (int i = 0; i < zeroBits.size(); i++)
{
if (m_timeCode[zeroBits[i]] != 0) {
qDebug() << "RadioClockSink::jjy - 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(41, 48);
// Japan doesn't have daylight savings
m_dst = RadioClockSettings::NOT_IN_EFFECT;
// 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);
}
}
else if (m_periodCount == 1000)
{
m_periodCount = 0;
}
}
m_prevData = m_data;
}
void RadioClockSink::processOneSample(Complex &ci)
{
// Calculate average and peak levels for level meter
@ -817,6 +969,8 @@ void RadioClockSink::processOneSample(Complex &ci)
tdf(ci);
} else if (m_settings.m_modulation == RadioClockSettings::WWVB) {
wwvb();
} else if (m_settings.m_modulation == RadioClockSettings::JJY) {
jjy();
} else {
msf60();
}

View File

@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020-2021 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
@ -160,6 +160,7 @@ private:
void tdf(Complex &ci);
void msf60();
void wwvb();
void jjy();
};
#endif // INCLUDE_RADIOCLOCKSINK_H

View File

@ -1,4 +1,4 @@
<h1>Radio clock plugin</h1>
<h1>Radio Clock Plugin</h1>
<h2>Introduction</h2>
@ -8,6 +8,7 @@ This plugin can be used to receive the time and date as broadcast on Low Frequen
* [DCF77](https://en.wikipedia.org/wiki/DCF77) - Germany - 77.5kHz
* [TDF](https://en.wikipedia.org/wiki/TDF_time_signal) - France - 162kHz
* [WWVB](https://en.wikipedia.org/wiki/WWVB) - USA - 60kHz
* [JJY](https://en.wikipedia.org/wiki/JJY) - Japan - 40kHz
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).
@ -21,29 +22,39 @@ The top and bottom bars of the channel window are described [here](../../../sdrg
![Radio clock plugin GUI](../../../doc/img/RadioClock_plugin.png)
<h3>1: Frequency shift from center frequency of reception</h3>
<h3>1: Channel frequency entry mode</h3>
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
Select from one of the following modes to determine how the channel center frequency is calculated:
<h3>2: Channel power</h3>
* Δf - Specify an offset in Hz from device center frequency.
* f - Specific a frequency in Hz.
<h3>2: Channel Frequency</h3>
Specifies channel center frequency according to frequency entry mode (1):
* Δf - Offset in Hz from device center frequency.
* f - Absolute frequency in Hz.
<h3>3: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Level meter in dB</h3>
<h3>4: Level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>4: BW - RF Bandwidth</h3>
<h3>5: BW - RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth.
<h3>5: TH - Threshold</h3>
<h3>6: TH - Threshold</h3>
For MSF, DCF77 and WWVB, specifies the threshold in dB below the average carrier power level that determines a binary 0 or 1.
For MSF, DCF77, WWVB and JJY, specifies the threshold in dB below the average carrier power level that determines a binary 0 or 1.
<h3>6: Modulation</h3>
<h3>7: Modulation</h3>
Specifies the modulation and timecode encoding used:
@ -51,8 +62,9 @@ Specifies the modulation and timecode encoding used:
* DCF77 - OOK (On-off keying)
* TDF - PM (Phase modulation)
* WWVB - OOK (On-off keying)
* JJY - OOK (On-off keying)
<h3>7: Display Time Zone</h3>
<h3>8: Display Time Zone</h3>
Specifies the time zone used to display the received time. This can be:
@ -60,15 +72,15 @@ Specifies the time zone used to display the received time. This can be:
* 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.
<h3>8: Date</h3>
<h3>9: Date</h3>
Displays the decoded date.
<h3>9: Time</h3>
<h3>10: Time</h3>
Displays the decoded time, adjusted for the time zone set by (7).
Displays the decoded time, adjusted for the time zone set by (8).
<h3>10: Status</h3>
<h3>11: Status</h3>
Displays the demodulator status. This can be:
@ -81,7 +93,7 @@ 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.
<h3>11: Daylight Savings</h3>
<h3>12: Daylight Savings</h3>
Displays the daylight savings state:
@ -90,7 +102,7 @@ Displays the daylight savings state:
* 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.
For MSF, DCF77 and TDF, starting/ending is indicated one hour before the change. For WWVB it is set for the whole day. Japan does not use daylight savings.
<h3>Waveforms</h3>

View File

@ -27,12 +27,15 @@ if(NOT SERVER_MODE)
sidgui.ui
sidsettingsdialog.cpp
sidsettingsdialog.ui
sidaddchannelsdialog.cpp
sidaddchannelsdialog.ui
icons.qrc
)
set(sid_HEADERS
${sid_HEADERS}
sidgui.h
sidsettingsdialog.h
sidaddchannelsdialog.h
)
set(TARGET_NAME featuresid)

View File

@ -119,7 +119,17 @@ When checked, all data is displayed on a single combined chart.
Check to display a legend on the chart. When unchecked the legend will be hidden. You can click on items in the legend to temporarily hide and then show the corresponding series on the chart.
The position of the legend can be set in the Settings Dialog.
<h3>16: Open Settings Dialog</h3>
<h3>16: Add Channels</h3>
Click to open the Add Channels Dialog. This allows you to easily add the Channel Power channels for each VLF transmitter on each RX device.
This dialog shows a table with one row for each VLF Transmitter, and a column for each RX device.
When OK is pressed, a corresponding Channel Power channel will be created for every cell that is checked.
For example, in the image below, Channel Power channels will be added for the GDQ transmitter on both devices R0 and R1, with one for FTA on device R0 only.
![SID Add Channels dialog](../../../doc/img/SID_plugin_addchannels.png)
<h3>17: Open Settings Dialog</h3>
Click to open the Settings Dialog. The settings dialog allows a user to:
@ -134,7 +144,7 @@ Click to open the Settings Dialog. The settings dialog allows a user to:
![SID settings dialog](../../../doc/img/SID_plugin_settings_dialog.png)
<h3>17: Display SDO/SOHO Imagery</h3>
<h3>18: Display SDO/SOHO Imagery</h3>
When checked, displays imagary from NASA's SDO (Solar Dynamic Observatory) and ESA/NASA's SOHO (Solar and Heliospheric Observatory) satellites.
@ -142,11 +152,11 @@ SDOs images the Sun in a variety of UV and EUV wavelengths. SOHO shows images of
Solar flares are particularly visible in the AIA 131 Å images.
<h3>18: Image or Video Selection</h3>
<h3>19: Image or Video Selection</h3>
Selects whether to display images (unchecked) or video (checked).
<h3>19: Image/Wavelength Selection</h3>
<h3>20: Image/Wavelength Selection</h3>
Selects which image / wavelength to view.
@ -172,63 +182,63 @@ Selects which image / wavelength to view.
* LASCO (Large Angle Spectrometric Coronagraph) shows solar corona. C2 shows corona up to 8.4Mkm. C3 shows corona up to 23Mkm.
<h3>20: Show GOES 16, 18 and SDO</h3>
<h3>21: Show GOES 16, 18 and SDO</h3>
When checked, opens a [Satellite Tracker](../../feature/satellitetracker/readme.md) feature and sets it to display data for the GOES 16, GOES 18 and SDO satellites.
The position and tracks of the satellites will then be visible on a [Map](../../feature/map/readme.md) feature.
<h3>21: Autoscale X</h3>
<h3>22: Autoscale X</h3>
When clicked, the chart X-axis is automatically scaled so that all power data is visible. When right-clicked, autoscaling of the X-axis will occur whenever new data is added to the chart.
<h3>22: Autoscale Y</h3>
<h3>23: Autoscale Y</h3>
When clicked, the chart Y-axis is automatically scaled so that all power data is visible. When right-clicked, autoscaling of the Y-axis will occur whenever new data is added to the chart.
<h3>23: Set X-axis to Today</h3>
<h3>24: Set X-axis to Today</h3>
When clicked, the X-axis is set to show today, from midnight to midnight.
When right-clicked, the X-axis is set to show sunrise to sunset. This uses latitude and longitude from Preferences > My position.
<h3>24: Set X-axis to -1 day</h3>
<h3>25: Set X-axis to -1 day</h3>
When clicked, the X-axis is set 1 day earlier than the current setting, at the same time.
<h3>25: Set X-axis to +1 day</h3>
<h3>26: Set X-axis to +1 day</h3>
When clicked, the X-axis is set 1 day later than the current setting, at the same time.
<h3>26: Start Time</h3>
<h3>27: Start Time</h3>
Displays/sets the current start time of the chart (X-axis minimum). It's possible to scroll through hours/days/months by clicking on the relevant segment and using the mouse scroll wheel.
<h3>27: End Time</h3>
<h3>28: End Time</h3>
Displays/sets the current end time of the chart (X-axis maximum). It's possible to scroll through hours/days/months by clicking on the relevant segment and using the mouse scroll wheel.
<h3>28: Min</h3>
<h3>29: Min</h3>
Displays/sets the minimum Y-axis value.
<h3>29: Max</h3>
<h3>30: Max</h3>
Displays/sets the maximum Y-axis value.
<h3>30: Now</h3>
<h3>31: Now</h3>
When checked, the latest SDO imagery is displayed. When unchecked, you can enter a date and time for which imagery should be displayed.
<h3>31: Date Time</h3>
<h3>32: Date Time</h3>
Specifies the date and time for which SDO imagery should be displayed. Images are updated every 15 minutes. The date and time can also be set by clicking on the chart.
<h3>32: Map</h3>
<h3>33: Map</h3>
Select a Map to link to the SID feature. When a time is selected on the SID charts, the [Map](../../feature/map/readme.md) feature will have it's time set accordingly.
This allows you, for example, to see the corresponding impact on MUF/foF2 displayed on the 3D map.
<h3>33: Show Paths on Map</h3>
<h3>34: Show Paths on Map</h3>
When clicked, shows the great circle paths between transmitters and receivers on a [Map](../../feature/map/readme.md).

View File

@ -0,0 +1,156 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QTableWidgetItem>
#include <QDebug>
#include "util/vlftransmitters.h"
#include "channel/channelwebapiutils.h"
#include "device/deviceapi.h"
#include "device/deviceset.h"
#include "dsp/dspdevicesourceengine.h"
#include "maincore.h"
#include "sidaddchannelsdialog.h"
SIDAddChannelsDialog::SIDAddChannelsDialog(SIDSettings *settings, QWidget* parent) :
QDialog(parent),
ui(new Ui::SIDAddChannelsDialog),
m_settings(settings)
{
ui->setupUi(this);
MainCore *mainCore = MainCore::instance();
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
ui->channels->setColumnCount(deviceSets.size() + 2);
// Create column header
ui->channels->setHorizontalHeaderItem(COL_TX_NAME, new QTableWidgetItem("Callsign"));
ui->channels->setHorizontalHeaderItem(COL_TX_FREQUENCY, new QTableWidgetItem("Frequency (Hz)"));
for (unsigned int i = 0; i < deviceSets.size(); i++)
{
if (deviceSets[i]->m_deviceSourceEngine || deviceSets[i]->m_deviceMIMOEngine)
{
QTableWidgetItem *item = new QTableWidgetItem(mainCore->getDeviceSetId(deviceSets[i]));
ui->channels->setHorizontalHeaderItem(COL_DEVICE + i, item);
}
}
// Add row for each transmitter, with checkbox for each device
for (int j = 0; j < VLFTransmitters::m_transmitters.size(); j++)
{
int row = ui->channels->rowCount();
ui->channels->setRowCount(row+1);
ui->channels->setItem(row, COL_TX_NAME, new QTableWidgetItem(VLFTransmitters::m_transmitters[j].m_callsign));
ui->channels->setItem(row, COL_TX_FREQUENCY, new QTableWidgetItem(QString::number(VLFTransmitters::m_transmitters[j].m_frequency)));
for (unsigned int i = 0; i < deviceSets.size(); i++)
{
if (deviceSets[i]->m_deviceSourceEngine || deviceSets[i]->m_deviceMIMOEngine)
{
QTableWidgetItem *enableItem = new QTableWidgetItem();
enableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
enableItem->setCheckState(Qt::Unchecked);
ui->channels->setItem(row, COL_DEVICE + i, enableItem);
}
}
}
ui->channels->resizeColumnsToContents();
}
SIDAddChannelsDialog::~SIDAddChannelsDialog()
{
delete ui;
}
void SIDAddChannelsDialog::accept()
{
if (ui->channels->columnCount() > 2)
{
MainCore *mainCore = MainCore::instance();
connect(mainCore, &MainCore::channelAdded, this, &SIDAddChannelsDialog::channelAdded);
m_count = m_settings->m_channelSettings.size();
m_row = 0;
m_col = COL_DEVICE;
addNextChannel();
}
else
{
QDialog::accept();
}
}
void SIDAddChannelsDialog::addNextChannel()
{
if (m_row < ui->channels->rowCount())
{
QString id = ui->channels->horizontalHeaderItem(m_col)->text();
unsigned int deviceSetIndex;
if (ui->channels->item(m_row, m_col)->checkState() == Qt::Checked)
{
MainCore::getDeviceSetIndexFromId(id, deviceSetIndex);
ChannelWebAPIUtils::addChannel(deviceSetIndex, "sdrangel.channel.channelpower", 0);
}
else
{
nextChannel(); // can recursively call this method
}
}
else
{
QDialog::accept();
}
}
void SIDAddChannelsDialog::nextChannel()
{
m_col++;
if (m_col >= ui->channels->columnCount())
{
m_col = COL_DEVICE;
m_row++;
}
addNextChannel();
}
void SIDAddChannelsDialog::channelAdded(int deviceSetIndex, ChannelAPI *channel)
{
(void) deviceSetIndex;
const VLFTransmitters::Transmitter *transmitter = VLFTransmitters::m_callsignHash.value(ui->channels->item(m_row, COL_TX_NAME)->text());
ChannelWebAPIUtils::patchChannelSetting(channel, "title", transmitter->m_callsign);
ChannelWebAPIUtils::patchChannelSetting(channel, "frequency", transmitter->m_frequency);
ChannelWebAPIUtils::patchChannelSetting(channel, "frequencyMode", 1);
ChannelWebAPIUtils::patchChannelSetting(channel, "rfBandwidth", 300);
ChannelWebAPIUtils::patchChannelSetting(channel, "averagePeriodUS", 10000000);
// Update setings if they are created by SIDGUI before this slot is called
if (m_count < m_settings->m_channelSettings.size()) {
m_settings->m_channelSettings[m_count].m_label = transmitter->m_callsign;
}
m_count++;
nextChannel();
}

View File

@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SIDADDCHANNELSDIALOG_H
#define INCLUDE_SIDADDCHANNELSDIALOG_H
#include "ui_sidaddchannelsdialog.h"
#include "sidsettings.h"
class SIDAddChannelsDialog : public QDialog {
Q_OBJECT
public:
explicit SIDAddChannelsDialog(SIDSettings *settings, QWidget* parent = 0);
~SIDAddChannelsDialog();
private:
private slots:
void accept();
void channelAdded(int deviceSetIndex, ChannelAPI *channel);
private:
Ui::SIDAddChannelsDialog* ui;
enum Column {
COL_TX_NAME,
COL_TX_FREQUENCY,
COL_DEVICE
};
SIDSettings *m_settings;
int m_row; // Row and column in table, when adding channels
int m_col;
int m_count; // How many channels we've added
void addNextChannel();
void nextChannel();
};
#endif // INCLUDE_SIDADDCHANNELSDIALOG_H

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SIDAddChannelsDialog</class>
<widget class="QDialog" name="SIDAddChannelsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>441</width>
<height>463</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Add channels</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Select devices and VLF transmitters to add channels for</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QTableWidget" name="channels">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SIDAddChannelsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>31</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SIDAddChannelsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>31</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -39,6 +39,7 @@
#include "sid.h"
#include "sidgui.h"
#include "sidsettingsdialog.h"
#include "sidaddchannelsdialog.h"
#include "SWGMapItem.h"
@ -1482,6 +1483,16 @@ void SIDGUI::on_deleteAll_clicked()
getData();
}
void SIDGUI::on_addChannels_clicked()
{
SIDAddChannelsDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
dialog.exec();
}
void SIDGUI::on_settings_clicked()
{
SIDSettingsDialog dialog(&m_settings);
@ -1493,6 +1504,8 @@ void SIDGUI::on_settings_clicked()
&SIDGUI::removeChannels
);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
setAutosaveTimer();
@ -1587,6 +1600,7 @@ void SIDGUI::makeUIConnections()
QObject::connect(ui->saveData, &QToolButton::clicked, this, &SIDGUI::on_saveData_clicked);
QObject::connect(ui->loadData, &QToolButton::clicked, this, &SIDGUI::on_loadData_clicked);
QObject::connect(ui->saveChartImage, &QToolButton::clicked, this, &SIDGUI::on_saveChartImage_clicked);
QObject::connect(ui->addChannels, &QToolButton::clicked, this, &SIDGUI::on_addChannels_clicked);
QObject::connect(ui->settings, &QToolButton::clicked, this, &SIDGUI::on_settings_clicked);
}

View File

@ -284,6 +284,7 @@ private slots:
void updateStatus();
void autosave();
void on_settings_clicked();
void on_addChannels_clicked();
void xRayDataUpdated(const QList<GOESXRay::XRayData>& data, bool primary);
void protonDataUpdated(const QList<GOESXRay::ProtonData>& data, bool primary);
void grbDataUpdated(const QList<GRB::Data>& data);

View File

@ -327,6 +327,20 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addChannels">
<property name="toolTip">
<string>Add channels</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/channels_add.png</normaloff>:/channels_add.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="settings">
<property name="toolTip">

View File

@ -1,7 +1,9 @@
<RCC>
<qresource prefix="/demodvor/">
<file>map/map.qml</file>
<file>map/map_6.qml</file>
<file>map/MapStation.qml</file>
<file>map/ModifiedMapView.qml</file>
<file>map/antenna.png</file>
<file>map/VOR.png</file>
<file>map/VOR-DME.png</file>

View File

@ -0,0 +1,178 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import QtQuick
import QtLocation as QL
import QtPositioning as QP
import Qt.labs.animation
/*!
\qmltype MapView
\inqmlmodule QtLocation
\brief An interactive map viewer component.
MapView wraps a Map and adds the typical interactive features:
changing the zoom level, panning and tilting the map.
The implementation is a QML assembly of smaller building blocks that are
available separately. In case you want to make changes in your own version
of this component, you can copy the QML, which is installed into the
\c qml/QtLocation module directory, and modify it as needed.
\sa Map
*/
Item {
/*!
\qmlproperty Map MapView::map
This property provides access to the underlying Map instance.
*/
property alias map: map
/*!
\qmlproperty real minimumZoomLevel
The minimum zoom level according to the size of the view.
\sa Map::minimumZoomLevel
*/
property real minimumZoomLevel: map.minimumZoomLevel
/*!
\qmlproperty real maximumZoomLevel
The maximum valid zoom level for the map.
\sa Map::maximumZoomLevel
*/
property real maximumZoomLevel: map.maximumZoomLevel
// --------------------------------
// implementation
id: root
Component.onCompleted: map.resetPinchMinMax()
QL.Map {
id: map
width: parent.width
height: parent.height
tilt: tiltHandler.persistentTranslation.y / -5
property bool pinchAdjustingZoom: false
BoundaryRule on zoomLevel {
id: br
minimum: map.minimumZoomLevel
maximum: map.maximumZoomLevel
}
onZoomLevelChanged: {
br.returnToBounds();
if (!pinchAdjustingZoom) resetPinchMinMax()
}
function resetPinchMinMax() {
pinch.persistentScale = 1
pinch.scaleAxis.minimum = Math.pow(2, root.minimumZoomLevel - map.zoomLevel + 1)
pinch.scaleAxis.maximum = Math.pow(2, root.maximumZoomLevel - map.zoomLevel - 1)
}
PinchHandler {
id: pinch
target: null
property real rawBearing: 0
property QP.geoCoordinate startCentroid
onActiveChanged: if (active) {
flickAnimation.stop()
pinch.startCentroid = map.toCoordinate(pinch.centroid.position, false)
} else {
flickAnimation.restart(centroid.velocity)
map.resetPinchMinMax()
}
onScaleChanged: (delta) => {
map.pinchAdjustingZoom = true
map.zoomLevel += Math.log2(delta)
map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
map.pinchAdjustingZoom = false
}
onRotationChanged: (delta) => {
pinch.rawBearing -= delta
// snap to 0° if we're close enough
map.bearing = (Math.abs(pinch.rawBearing) < 5) ? 0 : pinch.rawBearing
map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
}
grabPermissions: PointerHandler.TakeOverForbidden
}
WheelHandler {
id: wheel
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
? PointerDevice.Mouse | PointerDevice.TouchPad
: PointerDevice.Mouse
onWheel: (event) => {
const loc = map.toCoordinate(wheel.point.position)
switch (event.modifiers) {
case Qt.NoModifier:
// jonb - Changed to make more like Qt5
//map.zoomLevel += event.angleDelta.y / 120
map.zoomLevel += event.angleDelta.y / 1000
break
case Qt.ShiftModifier:
map.bearing += event.angleDelta.y / 15
break
case Qt.ControlModifier:
map.tilt += event.angleDelta.y / 15
break
}
map.alignCoordinateToPoint(loc, wheel.point.position)
}
}
DragHandler {
id: drag
signal flickStarted // for autotests only
signal flickEnded
target: null
onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
onActiveChanged: if (active) {
flickAnimation.stop()
} else {
flickAnimation.restart(centroid.velocity)
}
}
property vector3d animDest
onAnimDestChanged: if (flickAnimation.running) {
const delta = Qt.vector2d(animDest.x - flickAnimation.animDestLast.x, animDest.y - flickAnimation.animDestLast.y)
map.pan(-delta.x, -delta.y)
flickAnimation.animDestLast = animDest
}
Vector3dAnimation on animDest {
id: flickAnimation
property vector3d animDestLast
from: Qt.vector3d(0, 0, 0)
duration: 500
easing.type: Easing.OutQuad
onStarted: drag.flickStarted()
onStopped: drag.flickEnded()
function restart(vel) {
stop()
map.animDest = Qt.vector3d(0, 0, 0)
animDestLast = Qt.vector3d(0, 0, 0)
to = Qt.vector3d(vel.x / duration * 100, vel.y / duration * 100, 0)
start()
}
}
DragHandler {
id: tiltHandler
minimumPointCount: 2
maximumPointCount: 2
target: null
xAxis.enabled: false
grabPermissions: PointerHandler.TakeOverForbidden
onActiveChanged: if (active) flickAnimation.stop()
}
}
}

View File

@ -0,0 +1,156 @@
import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 6.5
import QtPositioning 6.5
Item {
id: qmlMap
property int vorZoomLevel: 11
property string mapProvider: "osm"
property variant mapPtr
property string requestedMapType
property variant guiPtr
function createMap(pluginParameters, requestedMap, gui) {
requestedMapType = requestedMap
guiPtr = gui
var paramString = ""
for (var prop in pluginParameters) {
var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
paramString = paramString + parameter
}
var pluginString = 'import QtLocation 6.5; Plugin{ name:"' + mapProvider + '"; ' + paramString + '}'
var plugin = Qt.createQmlObject (pluginString, qmlMap)
if (mapPtr) {
// Objects aren't destroyed immediately, so don't call findChild("map")
mapPtr.destroy()
mapPtr = null
}
mapPtr = actualMapComponent.createObject(page)
mapPtr.map.plugin = plugin
mapPtr.map.forceActiveFocus()
return mapPtr
}
Item {
id: page
anchors.fill: parent
}
Component {
id: actualMapComponent
ModifiedMapView {
id: mapView
objectName: "mapView"
anchors.fill: parent
map.center: QtPositioning.coordinate(51.5, 0.125) // London
map.zoomLevel: 10
map.objectName: "map"
// not in 6
//gesture.enabled: true
//gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture
MapItemView {
model: vorModel
delegate: vorRadialComponent
parent: mapView.map
}
MapStation {
id: station
objectName: "station"
stationName: "Home"
}
MapItemView {
model: vorModel
delegate: vorComponent
parent: mapView.map
}
map.onZoomLevelChanged: {
if (map.zoomLevel > 11) {
station.zoomLevel = map.zoomLevel
vorZoomLevel = map.zoomLevel
} else {
station.zoomLevel = 11
vorZoomLevel = 11
}
}
map.onSupportedMapTypesChanged : {
for (var i = 0; i < map.supportedMapTypes.length; i++) {
if (requestedMapType == map.supportedMapTypes[i].name) {
map.activeMapType = map.supportedMapTypes[i]
}
}
}
}
}
Component {
id: vorRadialComponent
MapPolyline {
line.width: 2
line.color: 'gray'
path: vorRadial
}
}
Component {
id: vorComponent
MapQuickItem {
id: vor
anchorPoint.x: image.width/2
anchorPoint.y: bubble.height/2
coordinate: position
zoomLevel: vorZoomLevel
sourceItem: Grid {
columns: 1
Grid {
horizontalItemAlignment: Grid.AlignHCenter
verticalItemAlignment: Grid.AlignVCenter
columnSpacing: 5
layer.enabled: true
layer.smooth: true
Image {
id: image
source: vorImage
MouseArea {
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: (mouse) => {
selected = !selected
}
}
}
Rectangle {
id: bubble
color: bubbleColour
border.width: 1
width: text.width + 5
height: text.height + 5
radius: 5
Text {
id: text
anchors.centerIn: parent
text: vorData
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: (mouse) => {
selected = !selected
}
}
}
}
}
}
}
}

View File

@ -1041,6 +1041,11 @@ void VORLocalizerGUI::applyMapSettings()
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
QQuickItem *item = ui->map->rootObject();
if (!item)
{
qCritical("VORLocalizerGUI::applyMapSettings: Map not found. Are all required Qt plugins installed?");
return;
}
QObject *object = item->findChild<QObject*>("map");
QGeoCoordinate coords;
@ -1146,7 +1151,11 @@ VORLocalizerGUI::VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
ui->map->rootContext()->setContextProperty("vorModel", &m_vorModel);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
ui->map->setSource(QUrl(QStringLiteral("qrc:/demodvor/map/map.qml")));
#else
ui->map->setSource(QUrl(QStringLiteral("qrc:/demodvor/map/map_6.qml")));
#endif
m_muteIcon.addPixmap(QPixmap("://sound_off.png"), QIcon::Normal, QIcon::On);
m_muteIcon.addPixmap(QPixmap("://sound_on.png"), QIcon::Normal, QIcon::Off);

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
@ -267,6 +267,23 @@ bool ChannelWebAPIUtils::getChannelSettings(unsigned int deviceIndex, unsigned i
return true;
}
bool ChannelWebAPIUtils::getChannelSettings(ChannelAPI *channel, SWGSDRangel::SWGChannelSettings &channelSettingsResponse)
{
QString errorResponse;
int httpRC;
httpRC = channel->webapiSettingsGet(channelSettingsResponse, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getChannelSettings: get channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
bool ChannelWebAPIUtils::getChannelReport(unsigned int deviceIndex, unsigned int channelIndex, SWGSDRangel::SWGChannelReport &channelReport)
{
QString errorResponse;
@ -1485,22 +1502,19 @@ bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsig
}
}
bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, double value)
bool ChannelWebAPIUtils::patchChannelSetting(ChannelAPI *channel, const QString &setting, const QVariant& value)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
QString errorResponse;
int httpRC;
ChannelAPI *channel;
if (getChannelSettings(deviceSetIndex, channelIndex, channelSettingsResponse, channel))
if (getChannelSettings(channel, channelSettingsResponse))
{
// Patch settings
QJsonObject *jsonObj = channelSettingsResponse.asJsonObject();
double oldValue;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, setting, oldValue))
if (WebAPIUtils::hasSubObject(*jsonObj, setting))
{
WebAPIUtils::setSubObjectDouble(*jsonObj, setting, value);
WebAPIUtils::setSubObject(*jsonObj, setting, value);
QStringList channelSettingsKeys;
channelSettingsKeys.append(setting);
channelSettingsResponse.init();
@ -1511,19 +1525,19 @@ bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsign
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %f OK", qPrintable(setting), value);
qDebug("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %s OK", qPrintable(setting), qPrintable(value.toString()));
return true;
}
else
{
qWarning("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %f error %d: %s",
qPrintable(setting), value, httpRC, qPrintable(*errorResponse2.getMessage()));
qWarning("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %s error %d: %s",
qPrintable(setting), qPrintable(value.toString()), httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qWarning("ChannelWebAPIUtils::patchChannelSetting: no key %s in feature settings", qPrintable(setting));
qWarning("ChannelWebAPIUtils::patchChannelSetting: no key %s in channel settings", qPrintable(setting));
return false;
}
}
@ -1533,6 +1547,39 @@ bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsign
}
}
bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, const QString& value)
{
ChannelAPI *channel = MainCore::instance()->getChannel(deviceSetIndex, channelIndex);
if (channel) {
return patchChannelSetting(channel, setting, value);
} else {
return false;
}
}
bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, int value)
{
ChannelAPI *channel = MainCore::instance()->getChannel(deviceSetIndex, channelIndex);
if (channel) {
return patchChannelSetting(channel, setting, value);
} else {
return false;
}
}
bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, double value)
{
ChannelAPI *channel = MainCore::instance()->getChannel(deviceSetIndex, channelIndex);
if (channel) {
return patchChannelSetting(channel, setting, value);
} else {
return false;
}
}
bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, const QJsonArray& value)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
@ -1835,3 +1882,30 @@ bool ChannelWebAPIUtils::getChannelReportValue(unsigned int deviceIndex, unsigne
return false;
}
bool ChannelWebAPIUtils::addChannel(unsigned int deviceSetIndex, const QString& uri, int direction)
{
MainCore *mainCore = MainCore::instance();
PluginAPI::ChannelRegistrations *channelRegistrations = mainCore->getPluginManager()->getRxChannelRegistrations();
int nbRegistrations = channelRegistrations->size();
int index = 0;
for (; index < nbRegistrations; index++)
{
if (channelRegistrations->at(index).m_channelIdURI == uri) {
break;
}
}
if (index < nbRegistrations)
{
MainCore::MsgAddChannel *msg = MainCore::MsgAddChannel::create(deviceSetIndex, index, direction);
mainCore->getMainMessageQueue()->push(msg);
return true;
}
else
{
qWarning() << "ChannelWebAPIUtils::addChannel:" << uri << "plugin not available";
return false;
}
}

View File

@ -2,7 +2,7 @@
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2020-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
@ -79,6 +79,9 @@ public:
static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value);
static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value);
static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QJsonArray& value);
static bool patchChannelSetting(ChannelAPI *channel, const QString &setting, const QVariant &value);
static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, const QString &value);
static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, int value);
static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, double value);
static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, const QJsonArray& value);
static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, int &value);
@ -98,7 +101,9 @@ public:
static bool getFeatureSettings(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureSettings &featureSettingsResponse, Feature *&feature);
static bool getFeatureReport(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureReport &featureReport);
static bool getChannelSettings(unsigned int deviceIndex, unsigned int channelIndex, SWGSDRangel::SWGChannelSettings &channelSettingsResponse, ChannelAPI *&channel);
static bool getChannelSettings(ChannelAPI *channel, SWGSDRangel::SWGChannelSettings &channelSettingsResponse);
static bool getChannelReport(unsigned int deviceIndex, unsigned int channelIndex, SWGSDRangel::SWGChannelReport &channelReport);
static bool addChannel(unsigned int deviceSetIndex, const QString& uri, int direction);
protected:
static QString getDeviceHardwareId(unsigned int deviceIndex);
};

View File

@ -3819,6 +3819,15 @@ margin-bottom: 20px;
"type" : "integer",
"format" : "int64"
},
"frequencyMode" : {
"type" : "integer",
"description" : "(0 for Offset, 1 for Absolute)"
},
"frequency" : {
"type" : "integer",
"format" : "int64",
"description" : "Channel center frequency"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float"
@ -12692,6 +12701,15 @@ margin-bottom: 20px;
"format" : "int64",
"description" : "channel center frequency shift from baseband center in Hz"
},
"frequencyMode" : {
"type" : "integer",
"description" : "(0 for Offset, 1 for Absolute)"
},
"frequency" : {
"type" : "integer",
"format" : "int64",
"description" : "Channel center frequency"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float",
@ -12703,7 +12721,7 @@ margin-bottom: 20px;
},
"modulation" : {
"type" : "integer",
"description" : "0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB"
"description" : "0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB, 4 - JJY"
},
"timezone" : {
"type" : "integer",
@ -58934,7 +58952,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2024-04-04T16:23:36.765+02:00
Generated 2024-04-07T17:52:28.367+02:00
</div>
</div>
</div>

View File

@ -4,6 +4,13 @@ ChannelPowerSettings:
inputFrequencyOffset:
type: integer
format: int64
frequencyMode:
description: (0 for Offset, 1 for Absolute)
type: integer
frequency:
description: Channel center frequency
type: integer
format: int64
rfBandwidth:
type: number
format: float

View File

@ -5,6 +5,13 @@ RadioClockSettings:
description: channel center frequency shift from baseband center in Hz
type: integer
format: int64
frequencyMode:
description: (0 for Offset, 1 for Absolute)
type: integer
frequency:
description: Channel center frequency
type: integer
format: int64
rfBandwidth:
description: channel RF bandwidth in Hz
type: number
@ -13,7 +20,7 @@ RadioClockSettings:
type: number
format: float
modulation:
description: 0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB
description: 0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB, 4 - JJY
type: integer
timezone:
description: 0 - Broadcast, 1 - Local, 2 - UTC

View File

@ -584,6 +584,54 @@ bool WebAPIUtils::getSubObjectIntList(const QJsonObject &json, const QString &ke
return false;
}
bool WebAPIUtils::hasSubObject(const QJsonObject &json, const QString &key)
{
for (QJsonObject::const_iterator it = json.begin(); it != json.end(); it++)
{
QJsonValue jsonValue = it.value();
if (jsonValue.isObject())
{
QJsonObject subObject = jsonValue.toObject();
if (subObject.contains(key)) {
return true;
}
}
}
return false;
}
// Set value withing nested JSON object
bool WebAPIUtils::setSubObject(QJsonObject &json, const QString &key, const QVariant &value)
{
for (QJsonObject::iterator it = json.begin(); it != json.end(); it++)
{
QJsonValue jsonValue = it.value();
if (jsonValue.isObject())
{
QJsonObject subObject = jsonValue.toObject();
if (subObject.contains(key))
{
if (subObject[key].isString()) {
subObject[key] = value.toString();
} else if (subObject[key].isDouble()) {
subObject[key] = value.toDouble();
} else {
qDebug() << "WebAPIUtils::setSubObject: Unsupported type";
}
it.value() = subObject;
return true;
}
}
}
return false;
}
// look for value in key=value
bool WebAPIUtils::extractValue(const QJsonObject &json, const QString &key, QJsonValue &value)
{

View File

@ -54,6 +54,8 @@ public:
static bool getSubObjectString(const QJsonObject &json, const QString &key, QString &value);
static bool setSubObjectString(QJsonObject &json, const QString &key, const QString &value);
static bool getSubObjectIntList(const QJsonObject &json, const QString &key, const QString &subKey, QList<int> &values);
static bool setSubObject(QJsonObject &json, const QString &key, const QVariant &value);
static bool hasSubObject(const QJsonObject &json, const QString &key);
static bool extractValue(const QJsonObject &json, const QString &key, QJsonValue &value);
static bool extractArray(const QJsonObject &json, const QString &key, QJsonArray &value);
static bool extractObject(const QJsonObject &json, const QString &key, QJsonObject &value);

View File

@ -4,6 +4,13 @@ ChannelPowerSettings:
inputFrequencyOffset:
type: integer
format: int64
frequencyMode:
description: (0 for Offset, 1 for Absolute)
type: integer
frequency:
description: Channel center frequency
type: integer
format: int64
rfBandwidth:
type: number
format: float

View File

@ -5,6 +5,13 @@ RadioClockSettings:
description: channel center frequency shift from baseband center in Hz
type: integer
format: int64
frequencyMode:
description: (0 for Offset, 1 for Absolute)
type: integer
frequency:
description: Channel center frequency
type: integer
format: int64
rfBandwidth:
description: channel RF bandwidth in Hz
type: number
@ -13,7 +20,7 @@ RadioClockSettings:
type: number
format: float
modulation:
description: 0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB
description: 0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB, 4 - JJY
type: integer
timezone:
description: 0 - Broadcast, 1 - Local, 2 - UTC

View File

@ -3819,6 +3819,15 @@ margin-bottom: 20px;
"type" : "integer",
"format" : "int64"
},
"frequencyMode" : {
"type" : "integer",
"description" : "(0 for Offset, 1 for Absolute)"
},
"frequency" : {
"type" : "integer",
"format" : "int64",
"description" : "Channel center frequency"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float"
@ -12692,6 +12701,15 @@ margin-bottom: 20px;
"format" : "int64",
"description" : "channel center frequency shift from baseband center in Hz"
},
"frequencyMode" : {
"type" : "integer",
"description" : "(0 for Offset, 1 for Absolute)"
},
"frequency" : {
"type" : "integer",
"format" : "int64",
"description" : "Channel center frequency"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float",
@ -12703,7 +12721,7 @@ margin-bottom: 20px;
},
"modulation" : {
"type" : "integer",
"description" : "0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB"
"description" : "0 - MSF, 1 - DCF77, 2 - TDF, 3 - WWVB, 4 - JJY"
},
"timezone" : {
"type" : "integer",
@ -58934,7 +58952,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2024-04-04T16:23:36.765+02:00
Generated 2024-04-07T17:52:28.367+02:00
</div>
</div>
</div>

View File

@ -30,6 +30,10 @@ SWGChannelPowerSettings::SWGChannelPowerSettings(QString* json) {
SWGChannelPowerSettings::SWGChannelPowerSettings() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
frequency_mode = 0;
m_frequency_mode_isSet = false;
frequency = 0L;
m_frequency_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
pulse_threshold = 0.0f;
@ -66,6 +70,10 @@ void
SWGChannelPowerSettings::init() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
frequency_mode = 0;
m_frequency_mode_isSet = false;
frequency = 0L;
m_frequency_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
pulse_threshold = 0.0f;
@ -101,6 +109,8 @@ SWGChannelPowerSettings::cleanup() {
if(title != nullptr) {
delete title;
}
@ -133,6 +143,10 @@ void
SWGChannelPowerSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", "");
::SWGSDRangel::setValue(&frequency_mode, pJson["frequencyMode"], "qint32", "");
::SWGSDRangel::setValue(&frequency, pJson["frequency"], "qint64", "");
::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", "");
::SWGSDRangel::setValue(&pulse_threshold, pJson["pulseThreshold"], "float", "");
@ -178,6 +192,12 @@ SWGChannelPowerSettings::asJsonObject() {
if(m_input_frequency_offset_isSet){
obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset));
}
if(m_frequency_mode_isSet){
obj->insert("frequencyMode", QJsonValue(frequency_mode));
}
if(m_frequency_isSet){
obj->insert("frequency", QJsonValue(frequency));
}
if(m_rf_bandwidth_isSet){
obj->insert("rfBandwidth", QJsonValue(rf_bandwidth));
}
@ -231,6 +251,26 @@ SWGChannelPowerSettings::setInputFrequencyOffset(qint64 input_frequency_offset)
this->m_input_frequency_offset_isSet = true;
}
qint32
SWGChannelPowerSettings::getFrequencyMode() {
return frequency_mode;
}
void
SWGChannelPowerSettings::setFrequencyMode(qint32 frequency_mode) {
this->frequency_mode = frequency_mode;
this->m_frequency_mode_isSet = true;
}
qint64
SWGChannelPowerSettings::getFrequency() {
return frequency;
}
void
SWGChannelPowerSettings::setFrequency(qint64 frequency) {
this->frequency = frequency;
this->m_frequency_isSet = true;
}
float
SWGChannelPowerSettings::getRfBandwidth() {
return rf_bandwidth;
@ -369,6 +409,12 @@ SWGChannelPowerSettings::isSet(){
if(m_input_frequency_offset_isSet){
isObjectUpdated = true; break;
}
if(m_frequency_mode_isSet){
isObjectUpdated = true; break;
}
if(m_frequency_isSet){
isObjectUpdated = true; break;
}
if(m_rf_bandwidth_isSet){
isObjectUpdated = true; break;
}

View File

@ -47,6 +47,12 @@ public:
qint64 getInputFrequencyOffset();
void setInputFrequencyOffset(qint64 input_frequency_offset);
qint32 getFrequencyMode();
void setFrequencyMode(qint32 frequency_mode);
qint64 getFrequency();
void setFrequency(qint64 frequency);
float getRfBandwidth();
void setRfBandwidth(float rf_bandwidth);
@ -93,6 +99,12 @@ private:
qint64 input_frequency_offset;
bool m_input_frequency_offset_isSet;
qint32 frequency_mode;
bool m_frequency_mode_isSet;
qint64 frequency;
bool m_frequency_isSet;
float rf_bandwidth;
bool m_rf_bandwidth_isSet;

View File

@ -30,6 +30,10 @@ SWGRadioClockSettings::SWGRadioClockSettings(QString* json) {
SWGRadioClockSettings::SWGRadioClockSettings() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
frequency_mode = 0;
m_frequency_mode_isSet = false;
frequency = 0L;
m_frequency_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
threshold = 0.0f;
@ -70,6 +74,10 @@ void
SWGRadioClockSettings::init() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
frequency_mode = 0;
m_frequency_mode_isSet = false;
frequency = 0L;
m_frequency_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
threshold = 0.0f;
@ -110,6 +118,8 @@ SWGRadioClockSettings::cleanup() {
if(title != nullptr) {
delete title;
}
@ -145,6 +155,10 @@ void
SWGRadioClockSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", "");
::SWGSDRangel::setValue(&frequency_mode, pJson["frequencyMode"], "qint32", "");
::SWGSDRangel::setValue(&frequency, pJson["frequency"], "qint64", "");
::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", "");
::SWGSDRangel::setValue(&threshold, pJson["threshold"], "float", "");
@ -194,6 +208,12 @@ SWGRadioClockSettings::asJsonObject() {
if(m_input_frequency_offset_isSet){
obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset));
}
if(m_frequency_mode_isSet){
obj->insert("frequencyMode", QJsonValue(frequency_mode));
}
if(m_frequency_isSet){
obj->insert("frequency", QJsonValue(frequency));
}
if(m_rf_bandwidth_isSet){
obj->insert("rfBandwidth", QJsonValue(rf_bandwidth));
}
@ -253,6 +273,26 @@ SWGRadioClockSettings::setInputFrequencyOffset(qint64 input_frequency_offset) {
this->m_input_frequency_offset_isSet = true;
}
qint32
SWGRadioClockSettings::getFrequencyMode() {
return frequency_mode;
}
void
SWGRadioClockSettings::setFrequencyMode(qint32 frequency_mode) {
this->frequency_mode = frequency_mode;
this->m_frequency_mode_isSet = true;
}
qint64
SWGRadioClockSettings::getFrequency() {
return frequency;
}
void
SWGRadioClockSettings::setFrequency(qint64 frequency) {
this->frequency = frequency;
this->m_frequency_isSet = true;
}
float
SWGRadioClockSettings::getRfBandwidth() {
return rf_bandwidth;
@ -411,6 +451,12 @@ SWGRadioClockSettings::isSet(){
if(m_input_frequency_offset_isSet){
isObjectUpdated = true; break;
}
if(m_frequency_mode_isSet){
isObjectUpdated = true; break;
}
if(m_frequency_isSet){
isObjectUpdated = true; break;
}
if(m_rf_bandwidth_isSet){
isObjectUpdated = true; break;
}

View File

@ -48,6 +48,12 @@ public:
qint64 getInputFrequencyOffset();
void setInputFrequencyOffset(qint64 input_frequency_offset);
qint32 getFrequencyMode();
void setFrequencyMode(qint32 frequency_mode);
qint64 getFrequency();
void setFrequency(qint64 frequency);
float getRfBandwidth();
void setRfBandwidth(float rf_bandwidth);
@ -100,6 +106,12 @@ private:
qint64 input_frequency_offset;
bool m_input_frequency_offset_isSet;
qint32 frequency_mode;
bool m_frequency_mode_isSet;
qint64 frequency;
bool m_frequency_isSet;
float rf_bandwidth;
bool m_rf_bandwidth_isSet;