1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-05-02 04:04:02 -04:00

Merge branch 'f4exb:master' into inmarsat

This commit is contained in:
srcejon 2026-01-04 11:44:41 +00:00 committed by GitHub
commit d4c187602f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1272 additions and 149 deletions

View File

@ -19,6 +19,6 @@
#include "devicehackrfshared.h"
MESSAGE_CLASS_DEFINITION(DeviceHackRFShared::MsgSynchronizeFrequency, Message)
MESSAGE_CLASS_DEFINITION(DeviceHackRFShared::MsgSynchronizeSampleRate, Message)
const unsigned int DeviceHackRFShared::m_sampleFifoMinRate = 48000; // 48kS/s knee

View File

@ -28,24 +28,24 @@
class DEVICES_API DeviceHackRFShared
{
public:
class DEVICES_API MsgSynchronizeFrequency : public Message
class DEVICES_API MsgSynchronizeSampleRate : public Message
{
MESSAGE_CLASS_DECLARATION
public:
uint64_t getFrequency() const { return m_frequency; }
uint64_t getDeviceSampleRate() const { return m_deviceSampleRate; }
static MsgSynchronizeFrequency *create(uint64_t frequency)
static MsgSynchronizeSampleRate *create(uint64_t deviceSampleRate)
{
return new MsgSynchronizeFrequency(frequency);
return new MsgSynchronizeSampleRate(deviceSampleRate);
}
private:
uint64_t m_frequency;
uint64_t m_deviceSampleRate;
MsgSynchronizeFrequency(uint64_t frequency) :
MsgSynchronizeSampleRate(uint64_t deviceSampleRate) :
Message(),
m_frequency(frequency)
m_deviceSampleRate(deviceSampleRate)
{ }
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

View File

@ -217,6 +217,7 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force)
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_afBandwidth: " << settings.m_afBandwidth
<< " m_deEmphasis: " << settings.getDeEmphasisTimeConstant()
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_audioStereo: " << settings.m_audioStereo
@ -236,6 +237,9 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force)
if ((settings.m_volume != m_settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
if ((settings.m_audioMute != m_settings.m_audioMute) || force) {
reverseAPIKeys.append("audioMute");
}
if ((settings.m_audioStereo != m_settings.m_audioStereo) || force) {
reverseAPIKeys.append("audioStereo");
}
@ -254,6 +258,9 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force)
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_deEmphasis != m_settings.m_deEmphasis) || force) {
reverseAPIKeys.append("deEmphasis");
}
if ((settings.m_squelch != m_settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
}
@ -383,12 +390,22 @@ void BFMDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("afBandwidth")) {
settings.m_afBandwidth = response.getBfmDemodSettings()->getAfBandwidth();
}
if (channelSettingsKeys.contains("deEmphasis"))
{
int deEmphasis = response.getBfmDemodSettings()->getDeEmphasis();
if (deEmphasis >= 0 && deEmphasis < 2) {
settings.m_deEmphasis = static_cast<BFMDemodSettings::DeEmphasis>(deEmphasis);
}
}
if (channelSettingsKeys.contains("volume")) {
settings.m_volume = response.getBfmDemodSettings()->getVolume();
}
if (channelSettingsKeys.contains("squelch")) {
settings.m_squelch = response.getBfmDemodSettings()->getSquelch();
}
if (channelSettingsKeys.contains("audioMute")) {
settings.m_audioMute = response.getBfmDemodSettings()->getAudioMute() != 0;
}
if (channelSettingsKeys.contains("audioStereo")) {
settings.m_audioStereo = response.getBfmDemodSettings()->getAudioStereo() != 0;
}
@ -455,8 +472,10 @@ void BFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getBfmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getBfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getBfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth);
response.getBfmDemodSettings()->setDeEmphasis(static_cast<int>(settings.m_deEmphasis));
response.getBfmDemodSettings()->setVolume(settings.m_volume);
response.getBfmDemodSettings()->setSquelch(settings.m_squelch);
response.getBfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getBfmDemodSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0);
response.getBfmDemodSettings()->setLsbStereo(settings.m_lsbStereo ? 1 : 0);
response.getBfmDemodSettings()->setShowPilot(settings.m_showPilot ? 1 : 0);
@ -665,12 +684,18 @@ void BFMDemod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("afBandwidth") || force) {
swgBFMDemodSettings->setAfBandwidth(settings.m_afBandwidth);
}
if (channelSettingsKeys.contains("deEmphasis") || force) {
swgBFMDemodSettings->setDeEmphasis(static_cast<int>(settings.m_deEmphasis));
}
if (channelSettingsKeys.contains("volume") || force) {
swgBFMDemodSettings->setVolume(settings.m_volume);
}
if (channelSettingsKeys.contains("squelch") || force) {
swgBFMDemodSettings->setSquelch(settings.m_squelch);
}
if (channelSettingsKeys.contains("audioMute") || force) {
swgBFMDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0);
}
if (channelSettingsKeys.contains("audioStereo") || force) {
swgBFMDemodSettings->setAudioStereo(settings.m_audioStereo ? 1 : 0);
}

View File

@ -179,6 +179,12 @@ void BFMDemodGUI::on_afBW_valueChanged(int value)
applySettings();
}
void BFMDemodGUI::on_deEmphasis_currentIndexChanged(int index)
{
m_settings.m_deEmphasis = static_cast<BFMDemodSettings::DeEmphasis>(index);
applySettings();
}
void BFMDemodGUI::on_volume_valueChanged(int value)
{
ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
@ -193,6 +199,12 @@ void BFMDemodGUI::on_squelch_valueChanged(int value)
applySettings();
}
void BFMDemodGUI::on_audioMute_toggled(bool mute)
{
m_settings.m_audioMute = mute;
applySettings();
}
void BFMDemodGUI::on_audioStereo_toggled(bool stereo)
{
if (!stereo)
@ -512,6 +524,7 @@ void BFMDemodGUI::displaySettings()
ui->squelch->setValue(m_settings.m_squelch);
ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch));
ui->audioMute->setChecked(m_settings.m_audioMute);
ui->audioStereo->setChecked(m_settings.m_audioStereo);
ui->lsbStereo->setChecked(m_settings.m_lsbStereo);
ui->showPilot->setChecked(m_settings.m_showPilot);
@ -879,8 +892,10 @@ void BFMDemodGUI::makeUIConnections()
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &BFMDemodGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &BFMDemodGUI::on_rfBW_valueChanged);
QObject::connect(ui->afBW, &QSlider::valueChanged, this, &BFMDemodGUI::on_afBW_valueChanged);
QObject::connect(ui->deEmphasis, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BFMDemodGUI::on_deEmphasis_currentIndexChanged);
QObject::connect(ui->volume, &QSlider::valueChanged, this, &BFMDemodGUI::on_volume_valueChanged);
QObject::connect(ui->squelch, &QSlider::valueChanged, this, &BFMDemodGUI::on_squelch_valueChanged);
QObject::connect(ui->audioMute, &QToolButton::toggled, this, &BFMDemodGUI::on_audioMute_toggled);
QObject::connect(ui->audioStereo, &QToolButton::toggled, this, &BFMDemodGUI::on_audioStereo_toggled);
QObject::connect(ui->lsbStereo, &ButtonSwitch::toggled, this, &BFMDemodGUI::on_lsbStereo_toggled);
QObject::connect(ui->showPilot, &ButtonSwitch::clicked, this, &BFMDemodGUI::on_showPilot_clicked);

View File

@ -108,8 +108,10 @@ private slots:
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int value);
void on_afBW_valueChanged(int value);
void on_deEmphasis_currentIndexChanged(int index);
void on_volume_valueChanged(int value);
void on_squelch_valueChanged(int value);
void on_audioMute_toggled(bool mute);
void on_audioStereo_toggled(bool stereo);
void on_lsbStereo_toggled(bool lsb);
void on_showPilot_clicked();

View File

@ -267,6 +267,24 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="audioMute">
<property name="toolTip">
<string>Mute/Unmute audio</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_on.png</normaloff>
<normalon>:/sound_off.png</normalon>:/sound_on.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -400,6 +418,37 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deEmphasisLabel">
<property name="text">
<string>De-emph</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="deEmphasis">
<property name="toolTip">
<string>De-emphasis time constant</string>
</property>
<item>
<property name="text">
<string>50us</string>
</property>
</item>
<item>
<property name="text">
<string>75us</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
@ -1883,11 +1932,6 @@
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
@ -1900,6 +1944,11 @@
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>

View File

@ -46,10 +46,12 @@ void BFMDemodSettings::resetToDefaults()
m_afBandwidth = 15000;
m_volume = 2.0;
m_squelch = -60.0;
m_audioMute = false;
m_audioStereo = false;
m_lsbStereo = false;
m_showPilot = false;
m_rdsActive = false;
m_deEmphasis = DeEmphasis50us;
m_rgbColor = QColor(80, 120, 228).rgb();
m_title = "Broadcast FM Demod";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
@ -101,6 +103,8 @@ QByteArray BFMDemodSettings::serialize() const
s.writeS32(22, m_workspaceIndex);
s.writeBlob(23, m_geometryBytes);
s.writeBool(24, m_hidden);
s.writeBool(25, m_audioMute);
s.writeS32(26, m_deEmphasis);
return s.final();
}
@ -178,6 +182,9 @@ bool BFMDemodSettings::deserialize(const QByteArray& data)
d.readS32(22, &m_workspaceIndex, 0);
d.readBlob(23, &m_geometryBytes);
d.readBool(24, &m_hidden, false);
d.readBool(25, &m_audioMute, false);
d.readS32(26, &tmp, DeEmphasis50us);
m_deEmphasis = static_cast<DeEmphasis>(tmp);
return true;
}

View File

@ -27,15 +27,23 @@ class Serializable;
struct BFMDemodSettings
{
enum DeEmphasis
{
DeEmphasis50us,
DeEmphasis75us,
};
qint64 m_inputFrequencyOffset;
Real m_rfBandwidth;
Real m_afBandwidth;
Real m_volume;
Real m_squelch;
bool m_audioMute;
bool m_audioStereo;
bool m_lsbStereo;
bool m_showPilot;
bool m_rdsActive;
DeEmphasis m_deEmphasis;
quint32 m_rgbColor;
QString m_title;
QString m_audioDeviceName;
@ -75,6 +83,19 @@ struct BFMDemodSettings
return (3*rfBW)/2;
}
}
double getDeEmphasisTimeConstant() const
{
switch (m_deEmphasis)
{
case DeEmphasis50us:
return 50e-6;
case DeEmphasis75us:
return 75e-6;
default:
return 50e-6;
}
}
};

View File

@ -30,7 +30,6 @@
#include "rdsparser.h"
#include "bfmdemodsink.h"
const Real BFMDemodSink::default_deemphasis = 50.0; // 50 us
const int BFMDemodSink::default_excursion = 750000; // +/- 75 kHz
BFMDemodSink::BFMDemodSink() :
@ -41,8 +40,8 @@ BFMDemodSink::BFMDemodSink() :
m_audioBufferFill(0),
m_audioFifo(48000),
m_pilotPLL(19000/384000, 50/384000, 0.01),
m_deemphasisFilterX(default_deemphasis * 48000 * 1.0e-6),
m_deemphasisFilterY(default_deemphasis * 48000 * 1.0e-6),
m_deemphasisFilterX(m_settings.getDeEmphasisTimeConstant() * 48000),
m_deemphasisFilterY(m_settings.getDeEmphasisTimeConstant() * 48000),
m_fmExcursion(default_excursion)
{
m_magsq = 0.0f;
@ -67,8 +66,8 @@ BFMDemodSink::BFMDemodSink() :
m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, filtFftLen);
m_deemphasisFilterX.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
m_deemphasisFilterY.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
m_deemphasisFilterX.configure(m_settings.getDeEmphasisTimeConstant() * m_audioSampleRate);
m_deemphasisFilterY.configure(m_settings.getDeEmphasisTimeConstant() * m_audioSampleRate);
m_phaseDiscri.setFMScaling(384000/m_fmExcursion);
m_audioBuffer.resize(1<<14);
@ -96,6 +95,10 @@ void BFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
m_sampleBuffer.clear();
if (m_settings.m_audioMute) {
return;
}
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
@ -283,8 +286,8 @@ void BFMDemodSink::applyAudioSampleRate(int sampleRate)
m_interpolatorStereoDistanceRemain = (Real) m_channelSampleRate / sampleRate;
m_interpolatorStereoDistance = (Real) m_channelSampleRate / (Real) sampleRate;
m_deemphasisFilterX.configure(default_deemphasis * sampleRate * 1.0e-6);
m_deemphasisFilterY.configure(default_deemphasis * sampleRate * 1.0e-6);
m_deemphasisFilterX.configure(m_settings.getDeEmphasisTimeConstant() * sampleRate);
m_deemphasisFilterY.configure(m_settings.getDeEmphasisTimeConstant() * sampleRate);
m_audioSampleRate = sampleRate;
}
@ -333,6 +336,7 @@ void BFMDemodSink::applySettings(const BFMDemodSettings& settings, bool force)
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_afBandwidth: " << settings.m_afBandwidth
<< " m_deEmphasis: " << settings.getDeEmphasisTimeConstant()
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_audioStereo: " << settings.m_audioStereo
@ -344,8 +348,16 @@ void BFMDemodSink::applySettings(const BFMDemodSettings& settings, bool force)
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " force: " << force;
if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force) {
if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force)
{
m_pilotPLL.configure(19000.0/m_channelSampleRate, 50.0/m_channelSampleRate, 0.01);
applyAudioSampleRate(m_audioSampleRate); // re-apply audio sample rate to reconfigure interpolators
}
if ((settings.getDeEmphasisTimeConstant() != m_settings.getDeEmphasisTimeConstant()) || force)
{
m_deemphasisFilterX.configure(settings.getDeEmphasisTimeConstant() * m_audioSampleRate);
m_deemphasisFilterY.configure(settings.getDeEmphasisTimeConstant() * m_audioSampleRate);
}
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)

View File

@ -155,7 +155,6 @@ private:
LowPassFilterRC m_deemphasisFilterX;
LowPassFilterRC m_deemphasisFilterY;
static const Real default_deemphasis;
Real m_fmExcursion;
static const int default_excursion;

View File

@ -48,25 +48,36 @@ This is the power of the reconstructed stereo pilot signal.
Use this button to activate or de-activate RDS decoding
<h3>A.8: Level meter in dB</h3>
<h3>A.8: Audio mute</h3>
Use this button to mute or unmute audio
<h3>A.9: 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>A.9: RF Bandwidth</h3>
<h3>A.10: RF Bandwidth</h3>
This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps: 80, 100, 120, 140, 160, 180, 200, 220 and 250 kHz. Inspect the baseband spectrum (B) to adjust for best quality.
<h3>A.10: AF bandwidth</h3>
<h3>A.11: AF bandwidth</h3>
This is the AF bandwidth in kHz. It can be varied continuously between 1 and 20 kHz in steps of 1 kHz.
<h3>A.11: AF volume</h3>
<h3>A.12: De-emphasis</h3>
Select the de-emphasis time constant:
- **50us**: for Europe, Australia...
- **75us**: for North America, Japan...
<h3>A.13: AF volume</h3>
This is the relative AF volume from 0 to 10.
<h3>A.12: Squelch</h3>
<h3>A.14: Squelch</h3>
Adjust squelch in dB.

View File

@ -9,6 +9,7 @@ set(demodft8_SOURCES
ft8plugin.cpp
ft8buffer.cpp
ft8demodworker.cpp
pskreporterworker.cpp
)
set(demodft8_HEADERS
@ -19,7 +20,8 @@ set(demodft8_HEADERS
ft8demodwebapiadapter.h
ft8plugin.h
ft8buffer.h
ft8demodworker.h;
ft8demodworker.h
pskreporterworker.h
)
include_directories(

View File

@ -329,6 +329,18 @@ void FT8Demod::applySettings(const FT8DemodSettings& settings, bool force)
if ((m_settings.m_verifyOSD != settings.m_verifyOSD) || force) {
reverseAPIKeys.append("verifyOSD");
}
if ((m_settings.m_enablePSKReporter != settings.m_enablePSKReporter) || force) {
reverseAPIKeys.append("enablePSKReporter");
}
if ((m_settings.m_pskReporterCallsign != settings.m_pskReporterCallsign) || force) {
reverseAPIKeys.append("pskReporterCallsign");
}
if ((m_settings.m_pskReporterLocator != settings.m_pskReporterLocator) || force) {
reverseAPIKeys.append("pskReporterLocator");
}
if ((m_settings.m_pskReporterSoftware != settings.m_pskReporterSoftware) || force) {
reverseAPIKeys.append("pskReporterSoftware");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
@ -525,6 +537,18 @@ void FT8Demod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("verifyOSD")) {
settings.m_verifyOSD = response.getFt8DemodSettings()->getVerifyOsd() != 0;
}
if (channelSettingsKeys.contains("enablePSKReporter")) {
settings.m_enablePSKReporter = response.getFt8DemodSettings()->getEnablePskReporter() != 0;
}
if (channelSettingsKeys.contains("pskReporterCallsign")) {
settings.m_pskReporterCallsign = *response.getFt8DemodSettings()->getPskReporterCallsign();
}
if (channelSettingsKeys.contains("pskReporterLocator")) {
settings.m_pskReporterLocator = *response.getFt8DemodSettings()->getPskReporterLocator();
}
if (channelSettingsKeys.contains("pskReporterSoftware")) {
settings.m_pskReporterSoftware = *response.getFt8DemodSettings()->getPskReporterSoftware();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getFt8DemodSettings()->getRgbColor();
}
@ -589,6 +613,10 @@ void FT8Demod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getFt8DemodSettings()->setOsdDepth(settings.m_osdDepth);
response.getFt8DemodSettings()->setOsdLdpcThreshold(settings.m_osdLDPCThreshold);
response.getFt8DemodSettings()->setUseOsd(settings.m_verifyOSD ? 1 : 0);
response.getFt8DemodSettings()->setEnablePskReporter(settings.m_enablePSKReporter ? 1 : 0);
response.getFt8DemodSettings()->setPskReporterCallsign(new QString(settings.m_pskReporterCallsign));
response.getFt8DemodSettings()->setPskReporterLocator(new QString(settings.m_pskReporterLocator));
response.getFt8DemodSettings()->setPskReporterSoftware(new QString(settings.m_pskReporterSoftware));
response.getFt8DemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getFt8DemodSettings()->getTitle()) {
@ -784,6 +812,18 @@ void FT8Demod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("verifyOSD") || force) {
swgFT8DemodSettings->setVerifyOsd(settings.m_verifyOSD ? 1 : 0);
}
if (channelSettingsKeys.contains("enablePSKReporter") || force) {
swgFT8DemodSettings->setEnablePskReporter(settings.m_enablePSKReporter ? 1 : 0);
}
if (channelSettingsKeys.contains("pskReporterCallsign") || force) {
swgFT8DemodSettings->setPskReporterCallsign(new QString(settings.m_pskReporterCallsign));
}
if (channelSettingsKeys.contains("pskReporterLocator") || force) {
swgFT8DemodSettings->setPskReporterLocator(new QString(settings.m_pskReporterLocator));
}
if (channelSettingsKeys.contains("pskReporterSoftware") || force) {
swgFT8DemodSettings->setPskReporterSoftware(new QString(settings.m_pskReporterSoftware));
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgFT8DemodSettings->setRgbColor(settings.m_rgbColor);
}

View File

@ -24,6 +24,7 @@
#include "maincore.h"
#include "ft8demodworker.h"
#include "pskreporterworker.h"
#include "ft8demodbaseband.h"
MESSAGE_CLASS_DEFINITION(FT8DemodBaseband::MsgConfigureFT8DemodBaseband, Message)
@ -48,12 +49,7 @@ FT8DemodBaseband::FT8DemodBaseband() :
m_ft8DemodWorker,
&QObject::deleteLater
);
QObject::connect(
m_workerThread,
&QThread::finished,
m_ft8DemodWorker,
&QThread::deleteLater
);
QObject::connect(
this,
&FT8DemodBaseband::bufferReady,
@ -64,6 +60,20 @@ FT8DemodBaseband::FT8DemodBaseband() :
m_workerThread->start();
m_pskReporterThread = new QThread();
m_pskReporterWorker = new PskReporterWorker();
m_ft8DemodWorker->setPSKReportingMessageQueue(m_pskReporterWorker->getInputMessageQueue());
m_pskReporterWorker->moveToThread(m_pskReporterThread);
QObject::connect(
m_pskReporterThread,
&QThread::finished,
m_pskReporterWorker,
&QObject::deleteLater
);
m_pskReporterThread->start();
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
@ -84,6 +94,8 @@ FT8DemodBaseband::~FT8DemodBaseband()
m_workerThread->exit();
m_workerThread->wait();
delete[] m_ft8WorkerBuffer;
m_pskReporterThread->exit();
m_pskReporterThread->wait();
}
void FT8DemodBaseband::reset()
@ -96,7 +108,7 @@ void FT8DemodBaseband::reset()
void FT8DemodBaseband::setMessageQueueToGUI(MessageQueue *messageQueue)
{
m_messageQueueToGUI = messageQueue;
m_ft8DemodWorker->setReportingMessageQueue(m_messageQueueToGUI);
m_ft8DemodWorker->setGUIReportingMessageQueue(m_messageQueueToGUI);
}
void FT8DemodBaseband::setChannel(ChannelAPI *channel)
@ -237,6 +249,22 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc
m_ft8DemodWorker->setLogMessages(settings.m_logMessages);
}
if ((settings.m_enablePSKReporter != m_settings.m_enablePSKReporter) || force) {
m_ft8DemodWorker->setEnablePskReporter(settings.m_enablePSKReporter);
}
if ((settings.m_pskReporterCallsign != m_settings.m_pskReporterCallsign) || force) {
m_pskReporterWorker->setMyCallsign(settings.m_pskReporterCallsign);
}
if ((settings.m_pskReporterLocator != m_settings.m_pskReporterLocator) || force) {
m_pskReporterWorker->setMyLocator(settings.m_pskReporterLocator);
}
if ((settings.m_pskReporterSoftware != m_settings.m_pskReporterSoftware) || force) {
m_pskReporterWorker->setDecoderInfo(settings.m_pskReporterSoftware);
}
if ((settings.m_nbDecoderThreads != m_settings.m_nbDecoderThreads) || force) {
m_ft8DemodWorker->setNbDecoderThreads(settings.m_nbDecoderThreads);
}

View File

@ -36,6 +36,7 @@ class ChannelAPI;
class SpectrumVis;
class QThread;
class FT8DemodWorker;
class PskReporterWorker;
class FT8DemodBaseband : public QObject
{
@ -102,6 +103,8 @@ private:
int m_tickCount;
QThread *m_workerThread;
FT8DemodWorker *m_ft8DemodWorker;
QThread *m_pskReporterThread;
PskReporterWorker *m_pskReporterWorker;
int16_t *m_ft8WorkerBuffer;
qint64 m_deviceCenterFrequency;
QRecursiveMutex m_mutex;

View File

@ -528,6 +528,30 @@ void FT8DemodGUI::on_settings_clicked()
changed = true;
}
if (settingsKeys.contains("enablePSKReporter"))
{
m_settings.m_enablePSKReporter = settings.m_enablePSKReporter;
changed = true;
}
if (settingsKeys.contains("pskReporterCallsign"))
{
m_settings.m_pskReporterCallsign = settings.m_pskReporterCallsign;
changed = true;
}
if (settingsKeys.contains("pskReporterLocator"))
{
m_settings.m_pskReporterLocator = settings.m_pskReporterLocator;
changed = true;
}
if (settingsKeys.contains("pskReporterSoftware"))
{
m_settings.m_pskReporterSoftware = settings.m_pskReporterSoftware;
changed = true;
}
if (settingsKeys.contains("bandPresets"))
{
m_settings.m_bandPresets = settings.m_bandPresets;

View File

@ -19,9 +19,12 @@
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QCoreApplication>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "maincore.h"
#include "util/maidenhead.h"
#include "ft8demodsettings.h"
const int FT8DemodSettings::m_ft8SampleRate = 12000;
@ -66,6 +69,10 @@ void FT8DemodSettings::resetToDefaults()
m_workspaceIndex = 0;
m_hidden = false;
m_filterIndex = 0;
m_enablePSKReporter = false;
m_pskReporterCallsign = getDefaultReporterCallsign();
m_pskReporterLocator = getDefaultReporterLocator();
m_pskReporterSoftware = "SDRangel FT8 Demod";
resetBandPresets();
}
@ -133,6 +140,10 @@ QByteArray FT8DemodSettings::serialize() const
s.writeBlob(26, m_geometryBytes);
s.writeBool(27, m_hidden);
s.writeU32(29, m_filterIndex);
s.writeBool(30, m_enablePSKReporter);
s.writeString(31, m_pskReporterCallsign);
s.writeString(32, m_pskReporterLocator);
s.writeString(33, m_pskReporterSoftware);
for (unsigned int i = 0; i < 10; i++)
{
@ -214,6 +225,10 @@ bool FT8DemodSettings::deserialize(const QByteArray& data)
d.readBool(27, &m_hidden, false);
d.readU32(29, &utmp, 0);
m_filterIndex = utmp < 10 ? utmp : 0;
d.readBool(30, &m_enablePSKReporter, false);
d.readString(31, &m_pskReporterCallsign, getDefaultReporterCallsign());
d.readString(32, &m_pskReporterLocator, getDefaultReporterLocator());
d.readString(33, &m_pskReporterSoftware, getDefaultReporterSoftware());
for (unsigned int i = 0; (i < 10); i++)
{
@ -252,3 +267,20 @@ QDataStream& operator>>(QDataStream& in, FT8DemodBandPreset& bandPreset)
in >> bandPreset.m_channelOffset;
return in;
}
QString FT8DemodSettings::getDefaultReporterCallsign() const
{
return MainCore::instance()->getSettings().getStationName();
}
QString FT8DemodSettings::getDefaultReporterLocator() const
{
float lat = MainCore::instance()->getSettings().getLatitude();
float lon = MainCore::instance()->getSettings().getLongitude();
return Maidenhead::toMaidenhead(lat, lon);
}
QString FT8DemodSettings::getDefaultReporterSoftware() const
{
return QCoreApplication::applicationName() + " " + QCoreApplication::applicationVersion();
}

View File

@ -97,6 +97,10 @@ public:
std::vector<FT8DemodFilterSettings> m_filterBank;
unsigned int m_filterIndex;
QList<FT8DemodBandPreset> m_bandPresets;
bool m_enablePSKReporter;
QString m_pskReporterCallsign;
QString m_pskReporterLocator;
QString m_pskReporterSoftware;
Serializable *m_channelMarker;
Serializable *m_spectrumGUI;
@ -110,6 +114,9 @@ public:
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void resetBandPresets();
QString getDefaultReporterCallsign() const;
QString getDefaultReporterLocator() const;
QString getDefaultReporterSoftware() const;
static const int m_ft8SampleRate;
static const int m_minPowerThresholdDB;

View File

@ -35,6 +35,10 @@ FT8DemodSettingsDialog::FT8DemodSettingsDialog(FT8DemodSettings& settings, QStri
ui->osdLDPCThreshold->setValue(m_settings.m_osdLDPCThreshold);
ui->osdLDPCThresholdText->setText(tr("%1").arg(m_settings.m_osdLDPCThreshold));
ui->verifyOSD->setChecked(m_settings.m_verifyOSD);
ui->enablePSKReporter->setChecked(m_settings.m_enablePSKReporter);
ui->reportingStation->setText(m_settings.m_pskReporterCallsign);
ui->reportingLocator->setText(m_settings.m_pskReporterLocator);
ui->reportingSoftware->setText(m_settings.m_pskReporterSoftware);
resizeBandsTable();
populateBandsTable();
connect(ui->bands, &QTableWidget::cellChanged, this, &FT8DemodSettingsDialog::textCellChanged);
@ -162,6 +166,65 @@ void FT8DemodSettingsDialog::on_verifyOSD_stateChanged(int state)
}
}
void FT8DemodSettingsDialog::on_enablePSKReporter_toggled(bool checked)
{
m_settings.m_enablePSKReporter = checked;
if (!m_settingsKeys.contains("enablePSKReporter")) {
m_settingsKeys.append("enablePSKReporter");
}
}
void FT8DemodSettingsDialog::on_pskReporterCallsign_editingFinished()
{
m_settings.m_pskReporterCallsign = ui->reportingStation->text();
if (!m_settingsKeys.contains("pskReporterCallsign")) {
m_settingsKeys.append("pskReporterCallsign");
}
}
void FT8DemodSettingsDialog::on_pskReporterLocator_editingFinished()
{
m_settings.m_pskReporterLocator = ui->reportingLocator->text();
if (!m_settingsKeys.contains("pskReporterLocator")) {
m_settingsKeys.append("pskReporterLocator");
}
}
void FT8DemodSettingsDialog::on_pskReporterSoftware_editingFinished()
{
m_settings.m_pskReporterSoftware = ui->reportingSoftware->text();
if (!m_settingsKeys.contains("pskReporterSoftware")) {
m_settingsKeys.append("pskReporterSoftware");
}
}
void FT8DemodSettingsDialog::on_reportingDefaults_clicked()
{
ui->reportingStation->setText(m_settings.getDefaultReporterCallsign());
ui->reportingLocator->setText(m_settings.getDefaultReporterLocator());
ui->reportingSoftware->setText(m_settings.getDefaultReporterSoftware());
m_settings.m_pskReporterCallsign = ui->reportingStation->text();
m_settings.m_pskReporterLocator = ui->reportingLocator->text();
m_settings.m_pskReporterSoftware = ui->reportingSoftware->text();
if (!m_settingsKeys.contains("pskReporterCallsign")) {
m_settingsKeys.append("pskReporterCallsign");
}
if (!m_settingsKeys.contains("pskReporterLocator")) {
m_settingsKeys.append("pskReporterLocator");
}
if (!m_settingsKeys.contains("pskReporterSoftware")) {
m_settingsKeys.append("pskReporterSoftware");
}
}
void FT8DemodSettingsDialog::on_addBand_clicked()
{
int currentRow = ui->bands->currentRow();

View File

@ -62,6 +62,11 @@ private slots:
void on_osdDepth_valueChanged(int value);
void on_osdLDPCThreshold_valueChanged(int value);
void on_verifyOSD_stateChanged(int state);
void on_enablePSKReporter_toggled(bool checked);
void on_pskReporterCallsign_editingFinished();
void on_pskReporterLocator_editingFinished();
void on_pskReporterSoftware_editingFinished();
void on_reportingDefaults_clicked();
void on_addBand_clicked();
void on_deleteBand_clicked();
void on_moveBandUp_clicked();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>349</width>
<height>333</height>
<width>343</width>
<height>423</height>
</rect>
</property>
<property name="font">
@ -229,6 +229,104 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="reporterGroup">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>PSK reporter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="enablePSKReporter">
<property name="toolTip">
<string>Enable sending reception reports to PSK reporter</string>
</property>
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="reportingStationLabel">
<property name="text">
<string>Sta</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="reportingStation">
<property name="toolTip">
<string>Reporting (your) station</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="reportingLocatorLabel">
<property name="text">
<string>Loc</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="reportingLocator">
<property name="toolTip">
<string>Reporting (your) locator</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="reportingSoftwareLabel">
<property name="text">
<string>Soft</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="reportingSoftware">
<property name="toolTip">
<string>Reporting (your) software</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reportingDefaults">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Reset reporter values to defaults</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="bandsGroup">
<property name="title">

View File

@ -141,6 +141,8 @@ QString FT8DemodWorker::FT8Callback::get_name()
FT8DemodWorker::FT8DemodWorker() :
m_recordSamples(false),
m_logMessages(false),
m_enablePskReporter(false),
m_nbDecoderThreads(6),
m_decoderTimeBudget(0.5),
m_useOSD(false),
@ -151,7 +153,8 @@ FT8DemodWorker::FT8DemodWorker() :
m_highFreq(3000),
m_invalidSequence(true),
m_baseFrequency(0),
m_reportingMessageQueue(nullptr),
m_guiReportingMessageQueue(nullptr),
m_pskReportingMessageQueue(nullptr),
m_channel(nullptr)
{
QString relPath = "ft8/save";
@ -237,8 +240,12 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
qDebug("FT8DemodWorker::processBuffer: done: at %6.3f %d messages",
m_baseFrequency / 1000000.0, (int)ft8Callback.getReportMessage()->getFT8Messages().size());
if (m_reportingMessageQueue) {
m_reportingMessageQueue->push(new MsgReportFT8Messages(*ft8Callback.getReportMessage()));
if (m_guiReportingMessageQueue) {
m_guiReportingMessageQueue->push(new MsgReportFT8Messages(*ft8Callback.getReportMessage()));
}
if (m_enablePskReporter && m_pskReportingMessageQueue) {
m_pskReportingMessageQueue->push(new MsgReportFT8Messages(*ft8Callback.getReportMessage()));
}
QList<ObjectPipe*> mapPipes;

View File

@ -41,6 +41,7 @@ public:
void processBuffer(int16_t *buffer, QDateTime periodTS);
void setRecordSamples(bool recordSamples) { m_recordSamples = recordSamples; }
void setLogMessages(bool logMessages) { m_logMessages = logMessages; }
void setEnablePskReporter(bool enablePskReporter) { m_enablePskReporter = enablePskReporter; }
void setNbDecoderThreads(int nbDecoderThreads) { m_nbDecoderThreads = nbDecoderThreads; }
void setDecoderTimeBudget(float decoderTimeBudget) { m_decoderTimeBudget = decoderTimeBudget; }
void setUseOSD(bool useOSD) { m_useOSD = useOSD; }
@ -49,7 +50,8 @@ public:
void setVerifyOSD(bool verifyOSD) { m_verifyOSD = verifyOSD; }
void setLowFrequency(int lowFreq) { m_lowFreq = lowFreq; }
void setHighFrequency(int highFreq) { m_highFreq = highFreq; }
void setReportingMessageQueue(MessageQueue *messageQueue) { m_reportingMessageQueue = messageQueue; }
void setGUIReportingMessageQueue(MessageQueue *messageQueue) { m_guiReportingMessageQueue = messageQueue; }
void setPSKReportingMessageQueue(MessageQueue *messageQueue) { m_pskReportingMessageQueue = messageQueue; }
void invalidateSequence() { m_invalidSequence = true; }
void setBaseFrequency(qint64 baseFrequency) { m_baseFrequency = baseFrequency; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
@ -92,6 +94,7 @@ private:
QString m_logsPath;
bool m_recordSamples;
bool m_logMessages;
bool m_enablePskReporter;
int m_nbDecoderThreads;
float m_decoderTimeBudget;
bool m_useOSD;
@ -104,7 +107,8 @@ private:
qint64 m_baseFrequency;
FT8::FT8Decoder m_ft8Decoder;
FT8::Packing m_packing;
MessageQueue *m_reportingMessageQueue;
MessageQueue *m_guiReportingMessageQueue;
MessageQueue *m_pskReportingMessageQueue;
ChannelAPI *m_channel;
QSet<QString> m_validCallsigns;
};

View File

@ -0,0 +1,287 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.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 //
// 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 <QHostInfo>
#include <QRandomGenerator>
#include "util/ft8message.h"
#include "pskreporterworker.h"
const char PskReporterWorker::hostname[] = "report.pskreporter.info";
const char PskReporterWorker::service[] = "4739";
const char PskReporterWorker::test_service[] = "14739";
const char PskReporterWorker::txMode[] = "FT8";
const unsigned char PskReporterWorker::rxDescriptor[] = {
0x00, 0x03, // Template Set ID
0x00, 0x24, // Length
0x99, 0x92, // Link ID
0x00, 0x03, // Field Count
0x00, 0x00, // Scope Field Count
0x80, 0x02, // Receiver Callsign ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x04, // Receiver Locator ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x08, // Receiver Decoder Software ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x00, 0x00 // Padding
}; // "PSKREPORTER_RX"
const unsigned char PskReporterWorker::txDescriptor[] = {
0x00, 0x02, // Template Set ID
0x00, 0x3C, // Length
0x99, 0x93, // Link ID
0x00, 0x07, // Field Count
0x80, 0x01, // Sender Callsign ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x05, // Sender Frequency ID
0x00, 0x04, // Fixed length (4)
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x06, // Sender SNR ID
0x00, 0x01, // Fixed length (1)
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x0A, // Sender Mode ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x03, // Sender Locator ID
0xFF, 0xFF, // Variable field length
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x80, 0x0B, // Information Source ID
0x00, 0x01, // Fixed length (1)
0x00, 0x00, 0x76, 0x8F, // Enterprise number
0x00, 0x96, // DateTimeSeconds ID
0x00, 0x04 // Field Length
}; // "PSKREPORTER_TX"
PskReporterWorker::PskReporterWorker() :
m_udpSocket(new QUdpSocket(this))
{
connect(&m_reportQueue, &MessageQueue::messageEnqueued,
this, &PskReporterWorker::handleInputMessages);
m_lastReportTime = QDateTime::currentDateTimeUtc();
m_identifier = QRandomGenerator::global()->generate(); // random number for the identifier for this session
}
void PskReporterWorker::processFT8Messages(const QList<FT8Message>& ft8Messages, qint64 baseFrequency)
{
m_ft8MessageQueue.append(ft8Messages); // Queue messages for processing
qDebug("PskReporterWorker::processFT8Messages: queued %d messages", ft8Messages.size());
// Avoid reporting too frequently - 5 minutes interval is the recommended value
if (m_lastReportTime.secsTo(QDateTime::currentDateTimeUtc()) < 5*60) {
return;
}
m_lastReportTime = QDateTime::currentDateTimeUtc();
uint32_t txPtr = 4;
std::fill(std::begin(txInfoData), std::end(txInfoData), 0);
while (!m_ft8MessageQueue.isEmpty())
{
FT8Message msg = m_ft8MessageQueue.dequeue();
if (m_reportedCalls.contains(msg.call2)) {
continue;
}
m_reportedCalls.insert(msg.call2);
if (txPtr > 1200)
{
sendMessageToPskReporter(txPtr);
txPtr = 4;
std::fill(std::begin(txInfoData), std::end(txInfoData), 0);
}
// Station callsign
*(uint8_t *)&txInfoData[txPtr] = (uint8_t) msg.call2.size();
txPtr += 1;
memcpy(&txInfoData[txPtr], msg.call2.toStdString().c_str(), msg.call2.size());
txPtr += msg.call2.size();
// Station frequency
uint32_t freq = static_cast<uint32_t>(baseFrequency + msg.df);
freq = SwapEndian32(freq);
memcpy(&txInfoData[txPtr], &freq, 4);
txPtr +=4;
// Station SNR
int8_t snr = static_cast<int8_t>(msg.snr);
memcpy(&txInfoData[txPtr], &snr, 1);
txPtr +=1;
// Station Mode
const size_t modeLen = strlen(txMode);
*(uint8_t *)&txInfoData[txPtr] = (uint8_t)modeLen;
txPtr += 1;
memcpy(&txInfoData[txPtr], txMode, modeLen);
txPtr += modeLen;
// Station locator
*(uint8_t *)&txInfoData[txPtr] = (uint8_t) msg.loc.size();
txPtr += 1;
memcpy(&txInfoData[txPtr], msg.loc.toStdString().c_str(), msg.loc.size());
txPtr += msg.loc.size();
/* Station Info -- Static length (1) */
*(uint8_t *)&txInfoData[txPtr] = (uint8_t)1;
txPtr += 1;
// Message timestamp -- Static length (4)
uint32_t timestamp = static_cast<uint32_t>(msg.ts.toSecsSinceEpoch());
timestamp = SwapEndian32(timestamp);
memcpy(&txInfoData[txPtr], &timestamp, 4);
txPtr +=4;
}
sendMessageToPskReporter(txPtr);
m_reportedCalls.clear();
m_reportSequenceNumber++;
}
void PskReporterWorker::sendMessageToPskReporter(uint32_t txPtr)
{
// Implement PSK Reporter message sending here
if (txInfoData[4] == 0) {
return; // No data to send
}
// Prepare and send the UDP message to PSK Reporter server
const uint32_t headerSize = 16;
char headerData[headerSize] = {0};
uint32_t hPtr = 0;
*(uint16_t *)&headerData[hPtr] = SwapEndian16(0x000A);
hPtr += 2;
hPtr += 2; // Skip the size block, adjust later
uint32_t timestamp = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
*(uint32_t *)&headerData[hPtr] = SwapEndian32(timestamp);
hPtr += 4;
*(uint32_t *)&headerData[hPtr] = SwapEndian32(m_reportSequenceNumber);
hPtr += 4;
*(uint32_t *)&headerData[hPtr] = SwapEndian32(m_identifier);
hPtr += 4;
char rxInfoData[256] = {0};
uint32_t rxPtr = 0;
*(uint16_t *)&rxInfoData[rxPtr] = SwapEndian16(0x9992);
rxPtr += 2;
rxPtr += 2; // Skip the size block, adjust later
// Receiver callsign
*(uint8_t *)&rxInfoData[rxPtr] = (uint8_t) m_myCallsign.size();
rxPtr += 1;
memcpy(&rxInfoData[rxPtr], m_myCallsign.toStdString().c_str(), m_myCallsign.size());
rxPtr += m_myCallsign.size();
// Receiver locator
*(uint8_t *)&rxInfoData[rxPtr] = (uint8_t) m_myLocator.size();
rxPtr += 1;
memcpy(&rxInfoData[rxPtr], m_myLocator.toStdString().c_str(), m_myLocator.size());
rxPtr += m_myLocator.size();
// Receiver decoder software
*(uint8_t *)&rxInfoData[rxPtr] = (uint8_t) m_decoderInfo.size();
rxPtr += 1;
memcpy(&rxInfoData[rxPtr], m_decoderInfo.toStdString().c_str(), m_decoderInfo.size());
rxPtr += m_decoderInfo.size();
// Padding to 4-byte boundary
if ((rxPtr % 4) > 0)
rxPtr += (4 - (rxPtr % 4));
// Padding to 4-byte boundary
if ((txPtr % 4) > 0)
txPtr += (4 - (txPtr % 4));
*(uint16_t *)&txInfoData[0] = SwapEndian16(0x9993);
/* Adjust the block sizes */
uint32_t fullBlockSize = headerSize + sizeof(rxDescriptor) + sizeof(txDescriptor) + rxPtr + txPtr;
*(uint16_t *)&rxInfoData[2] = SwapEndian16(rxPtr);
*(uint16_t *)&txInfoData[2] = SwapEndian16(txPtr);
*(uint16_t *)&headerData[2] = SwapEndian16(fullBlockSize);
/* Assemble the block to send over UDP */
char *fullBlockData = new char[fullBlockSize];
uint32_t ptrBlock = 0;
memcpy(&fullBlockData[ptrBlock], headerData, headerSize); ptrBlock += headerSize;
memcpy(&fullBlockData[ptrBlock], rxDescriptor, sizeof(rxDescriptor)); ptrBlock += sizeof(rxDescriptor);
memcpy(&fullBlockData[ptrBlock], txDescriptor, sizeof(txDescriptor)); ptrBlock += sizeof(txDescriptor);
memcpy(&fullBlockData[ptrBlock], rxInfoData, rxPtr); ptrBlock += rxPtr;
memcpy(&fullBlockData[ptrBlock], txInfoData, txPtr); ptrBlock += txPtr;
/* Send via UDP */
const char* servicePort = m_isTestMode ? test_service : service;
QHostAddress hostAddress;
// Resolve hostname
QHostInfo hostInfo = QHostInfo::fromName(hostname);
if (!hostInfo.addresses().isEmpty()) {
hostAddress = hostInfo.addresses().first();
// Send the datagram
qint64 bytesSent = m_udpSocket->writeDatagram(fullBlockData, fullBlockSize, hostAddress, QString(servicePort).toUInt());
if (bytesSent == -1) {
qWarning("PskReporterWorker::sendMessageToPskReporter: Failed to send UDP datagram: %s",
qPrintable(m_udpSocket->errorString()));
} else {
qDebug("PskReporterWorker::sendMessageToPskReporter: Sent %lld bytes to %s:%s",
bytesSent, hostname, servicePort);
}
} else {
qWarning("PskReporterWorker::sendMessageToPskReporter: Failed to resolve hostname: %s", hostname);
}
delete[] fullBlockData;
}
bool PskReporterWorker::handleMessage(const Message& message)
{
if (MsgReportFT8Messages::match(message))
{
const MsgReportFT8Messages& ft8Msg = static_cast<const MsgReportFT8Messages&>(message);
const QList<FT8Message>& ft8Messages = ft8Msg.getFT8Messages();
qint64 baseFrequency = ft8Msg.getBaseFrequency();
// Process FT8 messages for PSK Reporter here
processFT8Messages(ft8Messages, baseFrequency);
return true;
}
return false;
}
void PskReporterWorker::handleInputMessages()
{
Message* message = m_reportQueue.pop();
if (message == nullptr) {
return;
}
if (!handleMessage(*message)) {
delete message;
}
}

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.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 //
// 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_PSKREPORTERWORKER_H
#define INCLUDE_PSKREPORTERWORKER_H
#include <QObject>
#include <QList>
#include <QSet>
#include <QQueue>
#include <QUdpSocket>
#include "util/ft8message.h"
#include "util/message.h"
#include "util/messagequeue.h"
class PskReporterWorker : public QObject
{
Q_OBJECT
public:
PskReporterWorker();
~PskReporterWorker() = default;
MessageQueue* getInputMessageQueue() { return &m_reportQueue; }
void setMyCallsign(const QString& callsign) { m_myCallsign = callsign; }
void setMyLocator(const QString& locator) { m_myLocator = locator; }
void setDecoderInfo(const QString& decoderInfo) { m_decoderInfo = decoderInfo; }
void setTestMode(bool isTestMode) { m_isTestMode = isTestMode; }
private:
struct decoder_results {
char call[13];
char loc[7];
int32_t freq;
int32_t snr;
};
QString m_myCallsign;
QString m_myLocator;
QString m_decoderInfo;
bool m_isTestMode = false;
MessageQueue m_reportQueue;
QQueue<FT8Message> m_ft8MessageQueue;
QSet<QString> m_reportedCalls;
uint32_t m_reportSequenceNumber = 1;
QDateTime m_lastReportTime;
QUdpSocket* m_udpSocket;
uint32_t m_identifier;
char txInfoData[1500];
static const char hostname[];
static const char service[];
static const char test_service[];
static const char txMode[];
static const unsigned char rxDescriptor[];
static const unsigned char txDescriptor[];
void processFT8Messages(const QList<FT8Message>& ft8Messages, qint64 baseFrequency);
void sendMessageToPskReporter(uint32_t txPtr);
bool handleMessage(const Message& message);
inline uint16_t SwapEndian16(uint16_t val) {
return (val<<8) | (val>>8);
}
inline uint32_t SwapEndian32(uint32_t val) {
return (val<<24) | ((val<<8) & 0x00ff0000) | ((val>>8) & 0x0000ff00) | (val>>24);
}
private slots:
void handleInputMessages();
};
#endif // INCLUDE_PSKREPORTERWORKER_H

View File

@ -235,7 +235,27 @@ Sets the minimum number of correct LDPC bits (out of 83) necessary to trigger OS
OSD search may find invalid solutions as mentioned above. When checking this option the callsigns in messages not passed through OSD (thus very certainly valid) are stored for the life of the plugin. When OSD is engaged the second callsign field which is always a callsign is checked against this list and if the callsign is not found the message is rejected. This is quite efficient in removing false messages when OSD is engaged although some valid messages may be removed.
<h4>C.1.7: Band presets table</h4>
<h4>C.1.7: Report to PSK reporter</h4>
Sends received calls details to the PSK reporter (report.pskreporter.info port 4739) via UDP packets. Reports are sent every 5 minutes.
<h4>C.1.8: Reporting station callsign</h4>
This is the callsign of your station as it will be reported (may be an Amateur Radio callsign or something else). By default it takes the value for "Sation name" in the "Preferences > My Position..." in the main window.
<h4>C.1.9: Reporting station Maindenhead locator</h4>
This is the Maidenhead locator as it will be reported. By default it is calculated from the "Latitude" and "Longitude" values in the "Preferences > My Position..." in the main window.
<h4>C.1.10: Reporting station software</h4>
This is the decoding Software name and version as it will be reported in the "Using" field. By default it takes the value shown at the bottom right of the main SDRangel window.
<h4>C.1.11: Revert to default values</h4>
Use this push button to revert to default values for the reporting station details.
<h4>C.1.12: Band presets table</h4>
This table shows the band presets values that will appear in (C.5)
@ -245,23 +265,23 @@ This table shows the band presets values that will appear in (C.5)
You can edit these values by clicking on the cell in the table.
<h4>C.1.8: Add preset</h4>
<h4>C.1.13: Add preset</h4>
Use this button to create a new preset. It will take the values from the row of the selected cell in the table (if selected) and put the new preset at the bottom of the table
<h4>C.1.9: Delete preset</h4>
<h4>C.1.14: Delete preset</h4>
Delete the preset designated by the selected cell in the table.
<h4>C.1.10: Move up preset</h4>
<h4>C.1.15: Move up preset</h4>
Move up the preset designated by the selected cell in the table.
<h4>C.1.11: Move down preset</h4>
<h4>C.1.16: Move down preset</h4>
Move down the preset designated by the selected cell in the table.
<h4>C.1.12: Restore defaults</h4>
<h4>C.1.17: Restore defaults</h4>
This restores the default band preset values:
@ -283,10 +303,10 @@ This restores the default band preset values:
Channel offsets are all set to 0 kHz.
<h4>C.1.13 Commit changes</h4>
<h4>C.1.18 Commit changes</h4>
Click on the "OK" button to commit changes and close dialog.
<h4>C.1.14 Cancel changes</h4>
<h4>C.1.19 Cancel changes</h4>
Click on the "Cancel" button to close dialog without making changes.

View File

@ -17,8 +17,8 @@
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
@ -33,6 +33,8 @@ extern "C"
#include <opencv2/opencv.hpp> // Add OpenCV for text overlay
#include <QDateTime>
#include "tsgenerator.h"
const double TSGenerator::rate_qpsk[11][4] = {{1.0, 4.0, 12.0, 2}, {1.0, 3.0, 12.0, 2}, {2.0, 5.0, 12.0, 2}, {1.0, 2.0, 12.0, 2}, {3.0, 5.0, 12.0, 2}, {2.0, 3.0, 10.0, 2}, {3.0, 4.0, 12.0, 2}, {4.0, 5.0, 12.0, 2}, {5.0, 6.0, 10.0, 2}, {8.0, 9.0, 8.0, 2}, {9.0, 10.0, 8.0, 1}};
@ -174,19 +176,19 @@ AVFrame* TSGenerator::load_image_to_yuv_with_opencv(const char* filename, int wi
}
// 2. OPTIONAL: Overlay timestamp
if (overlay_timestamp) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
char time_str[32];
struct tm time_buffer; // Your own buffer
std::strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime_r(&time_t, &time_buffer));
if (overlay_timestamp)
{
QString time_str = QDateTime::currentDateTime().toString("HH:mm:ss");
char time_cstr[32];
strncpy(time_cstr, time_str.toStdString().c_str(), sizeof(time_cstr) - 1);
time_cstr[sizeof(time_cstr) - 1] = '\0';
// Draw black background box first
cv::rectangle(rgb_image, cv::Point(15, 28), cv::Point(170, 65),
cv::Scalar(0, 0, 0), -1);
// Draw white timestamp text
cv::putText(rgb_image, time_str, cv::Point(20, 55),
cv::putText(rgb_image, time_cstr, cv::Point(20, 55),
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255), 2);
}
@ -374,7 +376,11 @@ void TSGenerator::encode_frame_to_ts(AVFormatContext* oc, AVCodecContext* codec_
}
// Write callback - stores TS packets in memory
#if LIBAVFORMAT_VERSION_INT < ((61 << 16) | (0 << 8) | 0)
int TSGenerator::write_packet_cb(void* opaque, uint8_t* buf, int buf_size)
#else
int TSGenerator::write_packet_cb(void* opaque, const uint8_t* buf, int buf_size)
#endif
{
std::vector<uint8_t>* buffer = static_cast<std::vector<uint8_t>*>(opaque);
buffer->insert(buffer->end(), buf, buf + buf_size);

View File

@ -21,6 +21,7 @@
#include <vector>
#include <cstdint>
#include <string>
#include <libavformat/version.h>
#include "datvmodsettings.h"
@ -67,7 +68,11 @@ private:
int setup_ts_context(AVFormatContext** oc, AVCodecContext* codec_ctx);
void encode_frame_to_ts(AVFormatContext* oc, AVCodecContext* codec_ctx, AVFrame* frame, int stream_idx = 0);
std::pair<const AVCodec*, AVCodecContext*> create_codec_context(int fps, int bitrate, int width, int height, int duration_sec);
#if LIBAVFORMAT_VERSION_INT < ((61 << 16) | (0 << 8) | 0)
static int write_packet_cb(void* opaque, uint8_t* buf, int buf_size);
#else
static int write_packet_cb(void* opaque, const uint8_t* buf, int buf_size);
#endif
static int read_packet_cb(void* opaque, uint8_t* buf, int buf_size);
static int64_t seek_cb(void* opaque, int64_t offset, int whence);
static double get_dvbs2_rate(double symbol_rate, DATVModSettings::DATVModulation modulation, DATVModSettings::DATVCodeRate code_rate);

View File

@ -286,30 +286,23 @@ bool HackRFOutput::handleMessage(const Message& message)
return true;
}
else if (DeviceHackRFShared::MsgSynchronizeFrequency::match(message))
else if (DeviceHackRFShared::MsgSynchronizeSampleRate::match(message))
{
DeviceHackRFShared::MsgSynchronizeFrequency& freqMsg = (DeviceHackRFShared::MsgSynchronizeFrequency&) message;
qint64 centerFrequency = DeviceSampleSink::calculateCenterFrequency(
freqMsg.getFrequency(),
0,
m_settings.m_log2Interp,
(DeviceSampleSink::fcPos_t) m_settings.m_fcPos,
m_settings.m_devSampleRate);
qDebug("HackRFOutput::handleMessage: MsgSynchronizeFrequency: centerFrequency: %lld Hz", centerFrequency);
DeviceHackRFShared::MsgSynchronizeSampleRate& cmd = (DeviceHackRFShared::MsgSynchronizeSampleRate&) message;
qDebug() << "HackRFOutput::handleMessage: MsgSynchronizeSampleRate: " << cmd.getDeviceSampleRate();
HackRFOutputSettings settings = m_settings;
settings.m_centerFrequency = centerFrequency;
settings.m_devSampleRate = cmd.getDeviceSampleRate();
MsgConfigureHackRF* message = MsgConfigureHackRF::create(settings, QList<QString>{"devSampleRate"}, false);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureHackRF* messageToGUI = MsgConfigureHackRF::create(settings, QList<QString>{"centerFrequency"}, false);
MsgConfigureHackRF* messageToGUI = MsgConfigureHackRF::create(settings, QList<QString>{"devSampleRate"}, false);
m_guiMessageQueue->push(messageToGUI);
}
m_settings.m_centerFrequency = settings.m_centerFrequency;
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2Interp);
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
return true;
}
else
@ -394,6 +387,13 @@ bool HackRFOutput::applySettings(const HackRFOutputSettings& settings, const QLi
settings.m_devSampleRate);
}
}
if (m_deviceAPI->getSourceBuddies().size() > 0)
{
DeviceAPI *buddy = m_deviceAPI->getSourceBuddies()[0];
DeviceHackRFShared::MsgSynchronizeSampleRate *sampleRateMsg = DeviceHackRFShared::MsgSynchronizeSampleRate::create(settings.m_devSampleRate);
buddy->getSamplingDeviceInputMessageQueue()->push(sampleRateMsg);
}
}
if (settingsKeys.contains("log2Interp") || force)
@ -405,13 +405,13 @@ bool HackRFOutput::applySettings(const HackRFOutputSettings& settings, const QLi
}
}
if (settingsKeys.contains("centerFrequency") ||
if (m_running &&(settingsKeys.contains("centerFrequency") ||
settingsKeys.contains("devSampleRate") ||
settingsKeys.contains("log2Interp") ||
settingsKeys.contains("fcPos") ||
settingsKeys.contains("transverterMode") ||
settingsKeys.contains("transverterDeltaFrequency") ||
settingsKeys.contains("LOppmTenths") || force)
settingsKeys.contains("LOppmTenths") || force))
{
qint64 deviceCenterFrequency = DeviceSampleSink::calculateDeviceCenterFrequency(
settings.m_centerFrequency,
@ -422,13 +422,6 @@ bool HackRFOutput::applySettings(const HackRFOutputSettings& settings, const QLi
settings.m_transverterMode);
setDeviceCenterFrequency(deviceCenterFrequency, settings.m_LOppmTenths);
if (m_deviceAPI->getSourceBuddies().size() > 0)
{
DeviceAPI *buddy = m_deviceAPI->getSourceBuddies()[0];
DeviceHackRFShared::MsgSynchronizeFrequency *freqMsg = DeviceHackRFShared::MsgSynchronizeFrequency::create(deviceCenterFrequency);
buddy->getSamplingDeviceInputMessageQueue()->push(freqMsg);
}
forwardChange = true;
}

View File

@ -200,10 +200,10 @@
<string>Local Oscillator ppm correction</string>
</property>
<property name="minimum">
<number>-300</number>
<number>-1000</number>
</property>
<property name="maximum">
<number>300</number>
<number>1000</number>
</property>
<property name="pageStep">
<number>1</number>
@ -546,17 +546,17 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>ValueDial</class>
<extends>QWidget</extends>
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>TransverterButton</class>
<extends>QPushButton</extends>

View File

@ -72,27 +72,34 @@ void HackRFOutputThread::run()
m_running = true;
m_startWaiter.wakeAll();
if (hackrf_is_streaming(m_dev) == HACKRF_TRUE)
{
qDebug("HackRFInputThread::run: HackRF is streaming already");
}
else
{
qDebug("HackRFInputThread::run: HackRF is not streaming");
rc = (hackrf_error) hackrf_start_tx(m_dev, tx_callback, this);
qDebug("HackRFOutputThread::run: HackRF is streaming already");
rc = (hackrf_error) hackrf_stop_tx(m_dev);
if (rc == HACKRF_SUCCESS)
{
qDebug("HackRFOutputThread::run: started HackRF Tx");
qDebug("HackRFOutputThread::run: stopped HackRF Tx");
}
else
{
qDebug("HackRFOutputThread::run: failed to start HackRF Tx: %s", hackrf_error_name(rc));
qDebug("HackRFOutputThread::run: failed to stop HackRF Tx: %s", hackrf_error_name(rc));
}
}
usleep(200000);
rc = (hackrf_error) hackrf_start_tx(m_dev, tx_callback, this);
if (rc == HACKRF_SUCCESS)
{
qDebug("HackRFOutputThread::run: started HackRF Tx");
}
else
{
qDebug("HackRFOutputThread::run: failed to start HackRF Tx: %s", hackrf_error_name(rc));
}
while ((m_running) && (hackrf_is_streaming(m_dev) == HACKRF_TRUE))
{
usleep(200000);

View File

@ -38,7 +38,7 @@ This is the center frequency of transmission in kHz.
<h3>4: Local Oscillator correction</h3>
Use this slider to adjust LO correction in ppm. It can be varied from -10.0 to 10.0 in 0.1 steps and is applied in software.
Use this slider to adjust LO correction in ppm. It can be varied from -100.0 to 100.0 in 0.1 steps and is applied in software.
<h3>5: Rx filter bandwidth</h3>

View File

@ -152,9 +152,6 @@ bool HackRFInput::start()
return false;
}
// applySettings needs to called before thread is started,
// otherwise HackRF will not start correctly
applySettings(m_settings, QList<QString>(), true);
m_hackRFThread = new HackRFInputThread(m_dev, &m_sampleFifo);
@ -164,6 +161,7 @@ bool HackRFInput::start()
m_hackRFThread->setIQOrder(m_settings.m_iqOrder);
m_hackRFThread->startWork();
m_running = true;
applySettings(m_settings, QList<QString>(), true);
mutexLocker.unlock();
@ -301,32 +299,23 @@ bool HackRFInput::handleMessage(const Message& message)
return true;
}
else if (DeviceHackRFShared::MsgSynchronizeFrequency::match(message))
else if (DeviceHackRFShared::MsgSynchronizeSampleRate::match(message))
{
DeviceHackRFShared::MsgSynchronizeFrequency& freqMsg = (DeviceHackRFShared::MsgSynchronizeFrequency&) message;
qint64 centerFrequency = DeviceSampleSource::calculateCenterFrequency(
freqMsg.getFrequency(),
0,
m_settings.m_log2Decim,
(DeviceSampleSource::fcPos_t) m_settings.m_fcPos,
m_settings.m_devSampleRate,
DeviceSampleSource::FSHIFT_TXSYNC);
qDebug("HackRFInput::handleMessage: MsgSynchronizeFrequency: centerFrequency: %lld Hz", centerFrequency);
DeviceHackRFShared::MsgSynchronizeSampleRate& cmd = (DeviceHackRFShared::MsgSynchronizeSampleRate&) message;
qDebug() << "HackRFInput::handleMessage: MsgSynchronizeSampleRate: " << cmd.getDeviceSampleRate();
HackRFInputSettings settings = m_settings;
settings.m_centerFrequency = centerFrequency;
settings.m_devSampleRate = cmd.getDeviceSampleRate();
MsgConfigureHackRF* message = MsgConfigureHackRF::create(settings, QList<QString>{"devSampleRate"}, false);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
QList<QString> settingsKeys({"log2Decim", "fcPos", "devSampleRate", "centerFrequency"});
MsgConfigureHackRF* messageToGUI = MsgConfigureHackRF::create(settings, settingsKeys, false);
MsgConfigureHackRF* messageToGUI = MsgConfigureHackRF::create(settings, QList<QString>{"devSampleRate"}, false);
m_guiMessageQueue->push(messageToGUI);
}
m_settings.m_centerFrequency = settings.m_centerFrequency;
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2Decim);
DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, m_settings.m_centerFrequency);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
return true;
}
else
@ -388,6 +377,13 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, const QList
}
}
}
if (m_deviceAPI->getSourceBuddies().size() > 0)
{
DeviceAPI *buddy = m_deviceAPI->getSourceBuddies()[0];
DeviceHackRFShared::MsgSynchronizeSampleRate *sampleRateMsg = DeviceHackRFShared::MsgSynchronizeSampleRate::create(settings.m_devSampleRate);
buddy->getSamplingDeviceInputMessageQueue()->push(sampleRateMsg);
}
}
if (settingsKeys.contains("log2Decim") || force)
@ -522,7 +518,7 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, const QList
m_deviceAPI->configureCorrections(m_settings.m_dcBlock, m_settings.m_iqCorrection);
}
if (setFrequency)
if (m_running && setFrequency)
{
qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency(
m_settings.m_centerFrequency,
@ -535,13 +531,6 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, const QList
qDebug() << "HackRFInput::applySettings deviceCenterFrequency:" << deviceCenterFrequency;
setDeviceCenterFrequency(deviceCenterFrequency, m_settings.m_LOppmTenths);
if (m_deviceAPI->getSinkBuddies().size() > 0) // forward to buddy if necessary
{
DeviceAPI *buddy = m_deviceAPI->getSinkBuddies()[0];
DeviceHackRFShared::MsgSynchronizeFrequency *freqMsg = DeviceHackRFShared::MsgSynchronizeFrequency::create(deviceCenterFrequency);
buddy->getSamplingDeviceInputMessageQueue()->push(freqMsg);
}
forwardChange = true;
}

View File

@ -191,10 +191,10 @@
<string>Local Oscillator ppm correction</string>
</property>
<property name="minimum">
<number>-300</number>
<number>-1000</number>
</property>
<property name="maximum">
<number>300</number>
<number>1000</number>
</property>
<property name="pageStep">
<number>1</number>
@ -667,17 +667,17 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>ValueDial</class>
<extends>QWidget</extends>
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>TransverterButton</class>
<extends>QPushButton</extends>

View File

@ -83,23 +83,31 @@ void HackRFInputThread::run()
if (hackrf_is_streaming(m_dev) == HACKRF_TRUE)
{
qDebug("HackRFInputThread::run: HackRF is streaming already");
}
else
{
qDebug("HackRFInputThread::run: HackRF is not streaming");
rc = (hackrf_error) hackrf_start_rx(m_dev, rx_callback, this);
rc = (hackrf_error) hackrf_stop_rx(m_dev);
if (rc == HACKRF_SUCCESS)
{
qDebug("HackRFInputThread::run: started HackRF Rx");
qDebug("HackRFInputThread::run: stopped HackRF Rx");
}
else
{
qDebug("HackRFInputThread::run: failed to start HackRF Rx: %s", hackrf_error_name(rc));
qDebug("HackRFInputThread::run: failed to stop HackRF Rx: %s", hackrf_error_name(rc));
}
}
usleep(200000);
rc = (hackrf_error) hackrf_start_rx(m_dev, rx_callback, this);
if (rc == HACKRF_SUCCESS)
{
qDebug("HackRFInputThread::run: started HackRF Rx");
}
else
{
qDebug("HackRFInputThread::run: failed to start HackRF Rx: %s", hackrf_error_name(rc));
}
while ((m_running) && (hackrf_is_streaming(m_dev) == HACKRF_TRUE))
{
usleep(200000);

View File

@ -38,7 +38,7 @@ In baseband sample rate input mode (6A) this is the device to host sample rate i
<h3>2: Local Oscillator correction</h3>
Use this slider to adjust LO correction in ppm. It can be varied from -10.0 to 10.0 in 0.1 steps and is applied in software.
Use this slider to adjust LO correction in ppm. It can be varied from -100.0 to 100.0 in 0.1 steps and is applied in software.
<h3>3: Auto correction options</h3>

View File

@ -2948,6 +2948,10 @@ margin-bottom: 20px;
"type" : "number",
"format" : "float"
},
"deEmphasis" : {
"type" : "integer",
"description" : "De-emphasis time constant (us)\n * 0 - 50us (Europe, Australia)\n * 1 - 75us (US, Japan)\n"
},
"volume" : {
"type" : "number",
"format" : "float"
@ -2956,6 +2960,9 @@ margin-bottom: 20px;
"type" : "number",
"format" : "float"
},
"audioMute" : {
"type" : "integer"
},
"audioStereo" : {
"type" : "integer"
},
@ -6386,6 +6393,19 @@ margin-bottom: 20px;
"type" : "integer",
"description" : "Verify OSD decoded message against a list of validated callsigns\n * 0 - Disable\n * 1 - Enable\n"
},
"enablePSKReporter" : {
"type" : "integer",
"description" : "Enable reporting of decoded messages to PSK Reporter server\n * 0 - Disable\n * 1 - Enable\n"
},
"pskReporterCallsign" : {
"type" : "string"
},
"pskReporterLocator" : {
"type" : "string"
},
"pskReporterSoftware" : {
"type" : "string"
},
"rgbColor" : {
"type" : "integer"
},
@ -59734,7 +59754,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2025-12-31T21:12:50.336+01:00
Generated 2026-01-04T00:29:36.688+01:00
</div>
</div>
</div>

View File

@ -10,12 +10,20 @@ BFMDemodSettings:
afBandwidth:
type: number
format: float
deEmphasis:
type: integer
description: >
De-emphasis time constant (us)
* 0 - 50us (Europe, Australia)
* 1 - 75us (US, Japan)
volume:
type: number
format: float
squelch:
type: number
format: float
audioMute:
type: integer
audioStereo:
type: integer
lsbStereo:

View File

@ -70,6 +70,18 @@ FT8DemodSettings:
Verify OSD decoded message against a list of validated callsigns
* 0 - Disable
* 1 - Enable
enablePSKReporter:
type: integer
description: >
Enable reporting of decoded messages to PSK Reporter server
* 0 - Disable
* 1 - Enable
pskReporterCallsign:
type: string
pskReporterLocator:
type: string
pskReporterSoftware:
type: string
rgbColor:
type: integer
title:

View File

@ -44,7 +44,9 @@ struct SDRBASE_API FT8Message
class SDRBASE_API MsgReportFT8Messages : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QList<FT8Message>& getFT8Messages() const { return m_ft8Messages; }
QList<FT8Message>& getFT8Messages() { return m_ft8Messages; }
qint64 getBaseFrequency() const { return m_baseFrequency; }
void setBaseFrequency(qint64 baseFrequency) { m_baseFrequency = baseFrequency; }
static MsgReportFT8Messages* create() {

View File

@ -10,12 +10,20 @@ BFMDemodSettings:
afBandwidth:
type: number
format: float
deEmphasis:
type: integer
description: >
De-emphasis time constant (us)
* 0 - 50us (Europe, Australia)
* 1 - 75us (US, Japan)
volume:
type: number
format: float
squelch:
type: number
format: float
audioMute:
type: integer
audioStereo:
type: integer
lsbStereo:

View File

@ -70,6 +70,18 @@ FT8DemodSettings:
Verify OSD decoded message against a list of validated callsigns
* 0 - Disable
* 1 - Enable
enablePSKReporter:
type: integer
description: >
Enable reporting of decoded messages to PSK Reporter server
* 0 - Disable
* 1 - Enable
pskReporterCallsign:
type: string
pskReporterLocator:
type: string
pskReporterSoftware:
type: string
rgbColor:
type: integer
title:

View File

@ -2948,6 +2948,10 @@ margin-bottom: 20px;
"type" : "number",
"format" : "float"
},
"deEmphasis" : {
"type" : "integer",
"description" : "De-emphasis time constant (us)\n * 0 - 50us (Europe, Australia)\n * 1 - 75us (US, Japan)\n"
},
"volume" : {
"type" : "number",
"format" : "float"
@ -2956,6 +2960,9 @@ margin-bottom: 20px;
"type" : "number",
"format" : "float"
},
"audioMute" : {
"type" : "integer"
},
"audioStereo" : {
"type" : "integer"
},
@ -6386,6 +6393,19 @@ margin-bottom: 20px;
"type" : "integer",
"description" : "Verify OSD decoded message against a list of validated callsigns\n * 0 - Disable\n * 1 - Enable\n"
},
"enablePSKReporter" : {
"type" : "integer",
"description" : "Enable reporting of decoded messages to PSK Reporter server\n * 0 - Disable\n * 1 - Enable\n"
},
"pskReporterCallsign" : {
"type" : "string"
},
"pskReporterLocator" : {
"type" : "string"
},
"pskReporterSoftware" : {
"type" : "string"
},
"rgbColor" : {
"type" : "integer"
},
@ -59734,7 +59754,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2025-12-31T21:12:50.336+01:00
Generated 2026-01-04T00:29:36.688+01:00
</div>
</div>
</div>

View File

@ -34,10 +34,14 @@ SWGBFMDemodSettings::SWGBFMDemodSettings() {
m_rf_bandwidth_isSet = false;
af_bandwidth = 0.0f;
m_af_bandwidth_isSet = false;
de_emphasis = 0;
m_de_emphasis_isSet = false;
volume = 0.0f;
m_volume_isSet = false;
squelch = 0.0f;
m_squelch_isSet = false;
audio_mute = 0;
m_audio_mute_isSet = false;
audio_stereo = 0;
m_audio_stereo_isSet = false;
lsb_stereo = 0;
@ -84,10 +88,14 @@ SWGBFMDemodSettings::init() {
m_rf_bandwidth_isSet = false;
af_bandwidth = 0.0f;
m_af_bandwidth_isSet = false;
de_emphasis = 0;
m_de_emphasis_isSet = false;
volume = 0.0f;
m_volume_isSet = false;
squelch = 0.0f;
m_squelch_isSet = false;
audio_mute = 0;
m_audio_mute_isSet = false;
audio_stereo = 0;
m_audio_stereo_isSet = false;
lsb_stereo = 0;
@ -134,6 +142,8 @@ SWGBFMDemodSettings::cleanup() {
if(title != nullptr) {
delete title;
}
@ -176,10 +186,14 @@ SWGBFMDemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&af_bandwidth, pJson["afBandwidth"], "float", "");
::SWGSDRangel::setValue(&de_emphasis, pJson["deEmphasis"], "qint32", "");
::SWGSDRangel::setValue(&volume, pJson["volume"], "float", "");
::SWGSDRangel::setValue(&squelch, pJson["squelch"], "float", "");
::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", "");
::SWGSDRangel::setValue(&audio_stereo, pJson["audioStereo"], "qint32", "");
::SWGSDRangel::setValue(&lsb_stereo, pJson["lsbStereo"], "qint32", "");
@ -237,12 +251,18 @@ SWGBFMDemodSettings::asJsonObject() {
if(m_af_bandwidth_isSet){
obj->insert("afBandwidth", QJsonValue(af_bandwidth));
}
if(m_de_emphasis_isSet){
obj->insert("deEmphasis", QJsonValue(de_emphasis));
}
if(m_volume_isSet){
obj->insert("volume", QJsonValue(volume));
}
if(m_squelch_isSet){
obj->insert("squelch", QJsonValue(squelch));
}
if(m_audio_mute_isSet){
obj->insert("audioMute", QJsonValue(audio_mute));
}
if(m_audio_stereo_isSet){
obj->insert("audioStereo", QJsonValue(audio_stereo));
}
@ -325,6 +345,16 @@ SWGBFMDemodSettings::setAfBandwidth(float af_bandwidth) {
this->m_af_bandwidth_isSet = true;
}
qint32
SWGBFMDemodSettings::getDeEmphasis() {
return de_emphasis;
}
void
SWGBFMDemodSettings::setDeEmphasis(qint32 de_emphasis) {
this->de_emphasis = de_emphasis;
this->m_de_emphasis_isSet = true;
}
float
SWGBFMDemodSettings::getVolume() {
return volume;
@ -345,6 +375,16 @@ SWGBFMDemodSettings::setSquelch(float squelch) {
this->m_squelch_isSet = true;
}
qint32
SWGBFMDemodSettings::getAudioMute() {
return audio_mute;
}
void
SWGBFMDemodSettings::setAudioMute(qint32 audio_mute) {
this->audio_mute = audio_mute;
this->m_audio_mute_isSet = true;
}
qint32
SWGBFMDemodSettings::getAudioStereo() {
return audio_stereo;
@ -519,12 +559,18 @@ SWGBFMDemodSettings::isSet(){
if(m_af_bandwidth_isSet){
isObjectUpdated = true; break;
}
if(m_de_emphasis_isSet){
isObjectUpdated = true; break;
}
if(m_volume_isSet){
isObjectUpdated = true; break;
}
if(m_squelch_isSet){
isObjectUpdated = true; break;
}
if(m_audio_mute_isSet){
isObjectUpdated = true; break;
}
if(m_audio_stereo_isSet){
isObjectUpdated = true; break;
}

View File

@ -54,12 +54,18 @@ public:
float getAfBandwidth();
void setAfBandwidth(float af_bandwidth);
qint32 getDeEmphasis();
void setDeEmphasis(qint32 de_emphasis);
float getVolume();
void setVolume(float volume);
float getSquelch();
void setSquelch(float squelch);
qint32 getAudioMute();
void setAudioMute(qint32 audio_mute);
qint32 getAudioStereo();
void setAudioStereo(qint32 audio_stereo);
@ -121,12 +127,18 @@ private:
float af_bandwidth;
bool m_af_bandwidth_isSet;
qint32 de_emphasis;
bool m_de_emphasis_isSet;
float volume;
bool m_volume_isSet;
float squelch;
bool m_squelch_isSet;
qint32 audio_mute;
bool m_audio_mute_isSet;
qint32 audio_stereo;
bool m_audio_stereo_isSet;

View File

@ -60,6 +60,14 @@ SWGFT8DemodSettings::SWGFT8DemodSettings() {
m_osd_ldpc_threshold_isSet = false;
verify_osd = 0;
m_verify_osd_isSet = false;
enable_psk_reporter = 0;
m_enable_psk_reporter_isSet = false;
psk_reporter_callsign = nullptr;
m_psk_reporter_callsign_isSet = false;
psk_reporter_locator = nullptr;
m_psk_reporter_locator_isSet = false;
psk_reporter_software = nullptr;
m_psk_reporter_software_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
@ -122,6 +130,14 @@ SWGFT8DemodSettings::init() {
m_osd_ldpc_threshold_isSet = false;
verify_osd = 0;
m_verify_osd_isSet = false;
enable_psk_reporter = 0;
m_enable_psk_reporter_isSet = false;
psk_reporter_callsign = new QString("");
m_psk_reporter_callsign_isSet = false;
psk_reporter_locator = new QString("");
m_psk_reporter_locator_isSet = false;
psk_reporter_software = new QString("");
m_psk_reporter_software_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
@ -165,6 +181,16 @@ SWGFT8DemodSettings::cleanup() {
if(psk_reporter_callsign != nullptr) {
delete psk_reporter_callsign;
}
if(psk_reporter_locator != nullptr) {
delete psk_reporter_locator;
}
if(psk_reporter_software != nullptr) {
delete psk_reporter_software;
}
if(title != nullptr) {
delete title;
}
@ -230,6 +256,14 @@ SWGFT8DemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&verify_osd, pJson["verifyOSD"], "qint32", "");
::SWGSDRangel::setValue(&enable_psk_reporter, pJson["enablePSKReporter"], "qint32", "");
::SWGSDRangel::setValue(&psk_reporter_callsign, pJson["pskReporterCallsign"], "QString", "QString");
::SWGSDRangel::setValue(&psk_reporter_locator, pJson["pskReporterLocator"], "QString", "QString");
::SWGSDRangel::setValue(&psk_reporter_software, pJson["pskReporterSoftware"], "QString", "QString");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
@ -316,6 +350,18 @@ SWGFT8DemodSettings::asJsonObject() {
if(m_verify_osd_isSet){
obj->insert("verifyOSD", QJsonValue(verify_osd));
}
if(m_enable_psk_reporter_isSet){
obj->insert("enablePSKReporter", QJsonValue(enable_psk_reporter));
}
if(psk_reporter_callsign != nullptr && *psk_reporter_callsign != QString("")){
toJsonValue(QString("pskReporterCallsign"), psk_reporter_callsign, obj, QString("QString"));
}
if(psk_reporter_locator != nullptr && *psk_reporter_locator != QString("")){
toJsonValue(QString("pskReporterLocator"), psk_reporter_locator, obj, QString("QString"));
}
if(psk_reporter_software != nullptr && *psk_reporter_software != QString("")){
toJsonValue(QString("pskReporterSoftware"), psk_reporter_software, obj, QString("QString"));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
@ -513,6 +559,46 @@ SWGFT8DemodSettings::setVerifyOsd(qint32 verify_osd) {
this->m_verify_osd_isSet = true;
}
qint32
SWGFT8DemodSettings::getEnablePskReporter() {
return enable_psk_reporter;
}
void
SWGFT8DemodSettings::setEnablePskReporter(qint32 enable_psk_reporter) {
this->enable_psk_reporter = enable_psk_reporter;
this->m_enable_psk_reporter_isSet = true;
}
QString*
SWGFT8DemodSettings::getPskReporterCallsign() {
return psk_reporter_callsign;
}
void
SWGFT8DemodSettings::setPskReporterCallsign(QString* psk_reporter_callsign) {
this->psk_reporter_callsign = psk_reporter_callsign;
this->m_psk_reporter_callsign_isSet = true;
}
QString*
SWGFT8DemodSettings::getPskReporterLocator() {
return psk_reporter_locator;
}
void
SWGFT8DemodSettings::setPskReporterLocator(QString* psk_reporter_locator) {
this->psk_reporter_locator = psk_reporter_locator;
this->m_psk_reporter_locator_isSet = true;
}
QString*
SWGFT8DemodSettings::getPskReporterSoftware() {
return psk_reporter_software;
}
void
SWGFT8DemodSettings::setPskReporterSoftware(QString* psk_reporter_software) {
this->psk_reporter_software = psk_reporter_software;
this->m_psk_reporter_software_isSet = true;
}
qint32
SWGFT8DemodSettings::getRgbColor() {
return rgb_color;
@ -676,6 +762,18 @@ SWGFT8DemodSettings::isSet(){
if(m_verify_osd_isSet){
isObjectUpdated = true; break;
}
if(m_enable_psk_reporter_isSet){
isObjectUpdated = true; break;
}
if(psk_reporter_callsign && *psk_reporter_callsign != QString("")){
isObjectUpdated = true; break;
}
if(psk_reporter_locator && *psk_reporter_locator != QString("")){
isObjectUpdated = true; break;
}
if(psk_reporter_software && *psk_reporter_software != QString("")){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}

View File

@ -93,6 +93,18 @@ public:
qint32 getVerifyOsd();
void setVerifyOsd(qint32 verify_osd);
qint32 getEnablePskReporter();
void setEnablePskReporter(qint32 enable_psk_reporter);
QString* getPskReporterCallsign();
void setPskReporterCallsign(QString* psk_reporter_callsign);
QString* getPskReporterLocator();
void setPskReporterLocator(QString* psk_reporter_locator);
QString* getPskReporterSoftware();
void setPskReporterSoftware(QString* psk_reporter_software);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
@ -178,6 +190,18 @@ private:
qint32 verify_osd;
bool m_verify_osd_isSet;
qint32 enable_psk_reporter;
bool m_enable_psk_reporter_isSet;
QString* psk_reporter_callsign;
bool m_psk_reporter_callsign_isSet;
QString* psk_reporter_locator;
bool m_psk_reporter_locator_isSet;
QString* psk_reporter_software;
bool m_psk_reporter_software_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;