diff --git a/doc/img/SID_plugin_addchannels.png b/doc/img/SID_plugin_addchannels.png new file mode 100644 index 000000000..09ff7878f Binary files /dev/null and b/doc/img/SID_plugin_addchannels.png differ diff --git a/doc/img/SID_plugin_settings.png b/doc/img/SID_plugin_settings.png index fa30c699b..986ab271a 100644 Binary files a/doc/img/SID_plugin_settings.png and b/doc/img/SID_plugin_settings.png differ diff --git a/plugins/channelrx/channelpower/channelpower.cpp b/plugins/channelrx/channelpower/channelpower.cpp index b57c70efb..42bca59e3 100644 --- a/plugins/channelrx/channelpower/channelpower.cpp +++ b/plugins/channelrx/channelpower/channelpower.cpp @@ -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); } diff --git a/plugins/channelrx/channelpower/readme.md b/plugins/channelrx/channelpower/readme.md index a176b3d43..cc8375ab8 100644 --- a/plugins/channelrx/channelpower/readme.md +++ b/plugins/channelrx/channelpower/readme.md @@ -1,4 +1,4 @@ -

Channel Power Plugin

+

Channel Power Plugin

Introduction

@@ -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) -

1: Frequency shift from center frequency of reception

+

1: Channel frequency entry mode

-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: -

2: BW - Channel Bandwidth

+* Δf - Specify an offset in Hz from device center frequency. +* f - Specific a frequency in Hz. + +

2: Channel Frequency

+ +Specifies channel center frequency according to frequency entry mode (1): + +* Δf - Offset in Hz from device center frequency. +* f - Absolute frequency in Hz. + +

3: BW - Channel Bandwidth

Bandwidth in Hz of the channel for which power is to be measured. -

3: Tavg - Average Time

+

4: Tavg - Average Time

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. -

4: THp - Pulse Threshold

+

5: THp - Pulse Threshold

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. -

5: Avg - Average Power

+

6: Avg - Average Power

Displays the most recent average power measurement in dB. -

6: Max - Max Peak Power

+

7: Max - Max Peak Power

Displays the maximum instantaneous peak power measurement in dB. -

7: Min - Min Peak Power

+

8: Min - Min Peak Power

Displays the minimum instantaneous peak power measurement in dB. -

8: Pulse - Pulse Average Power

+

9: Pulse - Pulse Average Power

Displays the most recent pulse average power measurement in dB. -

9: Clear Data

+

10: Clear Data

Clears current measurements (min and max values are reset). diff --git a/plugins/channelrx/demodadsb/map/map_6.qml b/plugins/channelrx/demodadsb/map/map_6.qml index 259eeb05b..86b48538f 100644 --- a/plugins/channelrx/demodadsb/map/map_6.qml +++ b/plugins/channelrx/demodadsb/map/map_6.qml @@ -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) { diff --git a/plugins/channelrx/radioclock/radioclock.cpp b/plugins/channelrx/radioclock/radioclock.cpp index aa42cde5f..8b6a2291e 100644 --- a/plugins/channelrx/radioclock/radioclock.cpp +++ b/plugins/channelrx/radioclock/radioclock.cpp @@ -233,9 +233,15 @@ void RadioClock::applySettings(const RadioClockSettings& settings, bool force) QList 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); } diff --git a/plugins/channelrx/radioclock/radioclockgui.cpp b/plugins/channelrx/radioclock/radioclockgui.cpp index 1b07ec698..8860c2233 100644 --- a/plugins/channelrx/radioclock/radioclockgui.cpp +++ b/plugins/channelrx/radioclock/radioclockgui.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// Copyright (C) 2020-2024 Jon Beniston, M7RCE // // Copyright (C) 2020-2022 Edouard Griffiths, F4EXB // // // // 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::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(""); + } } diff --git a/plugins/channelrx/radioclock/radioclockgui.h b/plugins/channelrx/radioclock/radioclockgui.h index ec4ed36c3..a17d01ff7 100644 --- a/plugins/channelrx/radioclock/radioclockgui.h +++ b/plugins/channelrx/radioclock/radioclockgui.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2020-2022 Jon Beniston, M7RCE // +// Copyright (C) 2020-2024 Jon Beniston, M7RCE // // Copyright (C) 2020, 2022 Edouard Griffiths, F4EXB // // // // 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); diff --git a/plugins/channelrx/radioclock/radioclockgui.ui b/plugins/channelrx/radioclock/radioclockgui.ui index ab7bf35ba..c9129783f 100644 --- a/plugins/channelrx/radioclock/radioclockgui.ui +++ b/plugins/channelrx/radioclock/radioclockgui.ui @@ -74,16 +74,32 @@ 2 - + - 16 + 40 0 - - Df + + + 40 + 16777215 + + + Select frequency entry mode. + + + + Δf + + + + + f + + @@ -370,6 +386,11 @@ WWVB + + + JJY + + diff --git a/plugins/channelrx/radioclock/radioclocksettings.cpp b/plugins/channelrx/radioclock/radioclocksettings.cpp index 6b7efe2d2..2a0825c48 100644 --- a/plugins/channelrx/radioclock/radioclocksettings.cpp +++ b/plugins/channelrx/radioclock/radioclocksettings.cpp @@ -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 // -// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2021-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 // @@ -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"); diff --git a/plugins/channelrx/radioclock/radioclocksettings.h b/plugins/channelrx/radioclock/radioclocksettings.h index 5a518fc9a..aa944aa53 100644 --- a/plugins/channelrx/radioclock/radioclocksettings.h +++ b/plugins/channelrx/radioclock/radioclocksettings.h @@ -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 // -// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2021-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 // @@ -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, diff --git a/plugins/channelrx/radioclock/radioclocksink.cpp b/plugins/channelrx/radioclock/radioclocksink.cpp index 4f3853c3e..21e00b6a4 100644 --- a/plugins/channelrx/radioclock/radioclocksink.cpp +++ b/plugins/channelrx/radioclock/radioclocksink.cpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2021-2022 Edouard Griffiths, F4EXB // -// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2021-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 // @@ -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 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 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(); } diff --git a/plugins/channelrx/radioclock/radioclocksink.h b/plugins/channelrx/radioclock/radioclocksink.h index e8a9a3ebc..b79a6956a 100644 --- a/plugins/channelrx/radioclock/radioclocksink.h +++ b/plugins/channelrx/radioclock/radioclocksink.h @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2019-2021 Edouard Griffiths, F4EXB // -// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// Copyright (C) 2020-2024 Jon Beniston, M7RCE // // Copyright (C) 2020 Kacper Michajłow // // // // 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 diff --git a/plugins/channelrx/radioclock/readme.md b/plugins/channelrx/radioclock/readme.md index ca1ab245e..716a560c2 100644 --- a/plugins/channelrx/radioclock/readme.md +++ b/plugins/channelrx/radioclock/readme.md @@ -1,4 +1,4 @@ -

Radio clock plugin

+

Radio Clock Plugin

Introduction

@@ -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) -

1: Frequency shift from center frequency of reception

+

1: Channel frequency entry mode

-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: -

2: Channel power

+* Δf - Specify an offset in Hz from device center frequency. +* f - Specific a frequency in Hz. + +

2: Channel Frequency

+ +Specifies channel center frequency according to frequency entry mode (1): + +* Δf - Offset in Hz from device center frequency. +* f - Absolute frequency in Hz. + +

3: Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

3: Level meter in dB

+

4: Level meter in dB

- top bar (green): average value - bottom bar (blue green): instantaneous peak value - tip vertical bar (bright green): peak hold value -

4: BW - RF Bandwidth

+

5: BW - RF Bandwidth

This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. -

5: TH - Threshold

+

6: TH - Threshold

-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. -

6: Modulation

+

7: Modulation

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) -

7: Display Time Zone

+

8: Display Time Zone

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. -

8: Date

+

9: Date

Displays the decoded date. -

9: Time

+

10: Time

-Displays the decoded time, adjusted for the time zone set by (7). +Displays the decoded time, adjusted for the time zone set by (8). -

10: Status

+

11: Status

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. -

11: Daylight Savings

+

12: Daylight Savings

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.

Waveforms

diff --git a/plugins/feature/sid/CMakeLists.txt b/plugins/feature/sid/CMakeLists.txt index e24703036..6e0d57f95 100644 --- a/plugins/feature/sid/CMakeLists.txt +++ b/plugins/feature/sid/CMakeLists.txt @@ -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) diff --git a/plugins/feature/sid/readme.md b/plugins/feature/sid/readme.md index 1e7efe154..a7433502d 100644 --- a/plugins/feature/sid/readme.md +++ b/plugins/feature/sid/readme.md @@ -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. -

16: Open Settings Dialog

+

16: Add Channels

+ +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) + +

17: Open Settings Dialog

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) -

17: Display SDO/SOHO Imagery

+

18: Display SDO/SOHO Imagery

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. -

18: Image or Video Selection

+

19: Image or Video Selection

Selects whether to display images (unchecked) or video (checked). -

19: Image/Wavelength Selection

+

20: Image/Wavelength Selection

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. -

20: Show GOES 16, 18 and SDO

+

21: Show GOES 16, 18 and SDO

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. -

21: Autoscale X

+

22: Autoscale X

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. -

22: Autoscale Y

+

23: Autoscale Y

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. -

23: Set X-axis to Today

+

24: Set X-axis to Today

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. -

24: Set X-axis to -1 day

+

25: Set X-axis to -1 day

When clicked, the X-axis is set 1 day earlier than the current setting, at the same time. -

25: Set X-axis to +1 day

+

26: Set X-axis to +1 day

When clicked, the X-axis is set 1 day later than the current setting, at the same time. -

26: Start Time

+

27: Start Time

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. -

27: End Time

+

28: End Time

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. -

28: Min

+

29: Min

Displays/sets the minimum Y-axis value. -

29: Max

+

30: Max

Displays/sets the maximum Y-axis value. -

30: Now

+

31: Now

When checked, the latest SDO imagery is displayed. When unchecked, you can enter a date and time for which imagery should be displayed. -

31: Date Time

+

32: Date Time

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. -

32: Map

+

33: Map

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. -

33: Show Paths on Map

+

34: Show Paths on Map

When clicked, shows the great circle paths between transmitters and receivers on a [Map](../../feature/map/readme.md). diff --git a/plugins/feature/sid/sidaddchannelsdialog.cpp b/plugins/feature/sid/sidaddchannelsdialog.cpp new file mode 100644 index 000000000..cad94cf91 --- /dev/null +++ b/plugins/feature/sid/sidaddchannelsdialog.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#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& 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(); +} diff --git a/plugins/feature/sid/sidaddchannelsdialog.h b/plugins/feature/sid/sidaddchannelsdialog.h new file mode 100644 index 000000000..f19592ea7 --- /dev/null +++ b/plugins/feature/sid/sidaddchannelsdialog.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#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 diff --git a/plugins/feature/sid/sidaddchannelsdialog.ui b/plugins/feature/sid/sidaddchannelsdialog.ui new file mode 100644 index 000000000..a5b0468c6 --- /dev/null +++ b/plugins/feature/sid/sidaddchannelsdialog.ui @@ -0,0 +1,92 @@ + + + SIDAddChannelsDialog + + + + 0 + 0 + 441 + 463 + + + + + Liberation Sans + 9 + + + + Add channels + + + + + + Select devices and VLF transmitters to add channels for + + + + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + true + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SIDAddChannelsDialog + accept() + + + 257 + 31 + + + 157 + 274 + + + + + buttonBox + rejected() + SIDAddChannelsDialog + reject() + + + 325 + 31 + + + 286 + 274 + + + + + diff --git a/plugins/feature/sid/sidgui.cpp b/plugins/feature/sid/sidgui.cpp index a8a5a0102..dce08bab2 100644 --- a/plugins/feature/sid/sidgui.cpp +++ b/plugins/feature/sid/sidgui.cpp @@ -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); } diff --git a/plugins/feature/sid/sidgui.h b/plugins/feature/sid/sidgui.h index 7807450d9..ae1ab9c3f 100644 --- a/plugins/feature/sid/sidgui.h +++ b/plugins/feature/sid/sidgui.h @@ -284,6 +284,7 @@ private slots: void updateStatus(); void autosave(); void on_settings_clicked(); + void on_addChannels_clicked(); void xRayDataUpdated(const QList& data, bool primary); void protonDataUpdated(const QList& data, bool primary); void grbDataUpdated(const QList& data); diff --git a/plugins/feature/sid/sidgui.ui b/plugins/feature/sid/sidgui.ui index 71a17c413..59b7d4b05 100644 --- a/plugins/feature/sid/sidgui.ui +++ b/plugins/feature/sid/sidgui.ui @@ -327,6 +327,20 @@
+ + + + Add channels + + + + + + + :/channels_add.png:/channels_add.png + + + diff --git a/plugins/feature/vorlocalizer/map.qrc b/plugins/feature/vorlocalizer/map.qrc index f5985ce53..1fec0ef98 100644 --- a/plugins/feature/vorlocalizer/map.qrc +++ b/plugins/feature/vorlocalizer/map.qrc @@ -1,7 +1,9 @@ map/map.qml + map/map_6.qml map/MapStation.qml + map/ModifiedMapView.qml map/antenna.png map/VOR.png map/VOR-DME.png diff --git a/plugins/feature/vorlocalizer/map/ModifiedMapView.qml b/plugins/feature/vorlocalizer/map/ModifiedMapView.qml new file mode 100644 index 000000000..2aa266940 --- /dev/null +++ b/plugins/feature/vorlocalizer/map/ModifiedMapView.qml @@ -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() + } + } +} diff --git a/plugins/feature/vorlocalizer/map/map_6.qml b/plugins/feature/vorlocalizer/map/map_6.qml new file mode 100644 index 000000000..a0e60095e --- /dev/null +++ b/plugins/feature/vorlocalizer/map/map_6.qml @@ -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 + } + } + } + } + } + } + } + +} diff --git a/plugins/feature/vorlocalizer/vorlocalizergui.cpp b/plugins/feature/vorlocalizer/vorlocalizergui.cpp index bae749c47..45aa3a76b 100644 --- a/plugins/feature/vorlocalizer/vorlocalizergui.cpp +++ b/plugins/feature/vorlocalizer/vorlocalizergui.cpp @@ -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("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); diff --git a/sdrbase/channel/channelwebapiutils.cpp b/sdrbase/channel/channelwebapiutils.cpp index 42292d54c..141964aa6 100644 --- a/sdrbase/channel/channelwebapiutils.cpp +++ b/sdrbase/channel/channelwebapiutils.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// Copyright (C) 2020-2024 Jon Beniston, M7RCE // // Copyright (C) 2020, 2022 Edouard Griffiths, F4EXB // // // // 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; + } +} diff --git a/sdrbase/channel/channelwebapiutils.h b/sdrbase/channel/channelwebapiutils.h index 8a9d25c4c..6e65cfcef 100644 --- a/sdrbase/channel/channelwebapiutils.h +++ b/sdrbase/channel/channelwebapiutils.h @@ -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 // -// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// Copyright (C) 2020-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 // @@ -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); }; diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 038c51178..304040e07 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -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:
- Generated 2024-04-04T16:23:36.765+02:00 + Generated 2024-04-07T17:52:28.367+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChannelPower.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChannelPower.yaml index 068d62151..ce8c6cdde 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChannelPower.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChannelPower.yaml @@ -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 diff --git a/sdrbase/resources/webapi/doc/swagger/include/RadioClock.yaml b/sdrbase/resources/webapi/doc/swagger/include/RadioClock.yaml index 8acd20869..600ee71bb 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/RadioClock.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/RadioClock.yaml @@ -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 diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index 3ceb398b4..5ed235b3d 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -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) { diff --git a/sdrbase/webapi/webapiutils.h b/sdrbase/webapi/webapiutils.h index 752996217..243053362 100644 --- a/sdrbase/webapi/webapiutils.h +++ b/sdrbase/webapi/webapiutils.h @@ -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 &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); diff --git a/swagger/sdrangel/api/swagger/include/ChannelPower.yaml b/swagger/sdrangel/api/swagger/include/ChannelPower.yaml index 303276da3..6289f76c1 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelPower.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelPower.yaml @@ -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 diff --git a/swagger/sdrangel/api/swagger/include/RadioClock.yaml b/swagger/sdrangel/api/swagger/include/RadioClock.yaml index f7502ae5c..50b771452 100644 --- a/swagger/sdrangel/api/swagger/include/RadioClock.yaml +++ b/swagger/sdrangel/api/swagger/include/RadioClock.yaml @@ -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 diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 038c51178..304040e07 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -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:
- Generated 2024-04-04T16:23:36.765+02:00 + Generated 2024-04-07T17:52:28.367+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.cpp index e2d31e2aa..5a1d0b1f3 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.cpp @@ -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; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.h index 7db5edcc5..4f01a5dc5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelPowerSettings.h @@ -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; diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.cpp index 5f85a1499..3d6a1e14c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.cpp @@ -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; } diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.h b/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.h index 2f651135a..11983fad6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGRadioClockSettings.h @@ -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;