Add frequency scanner channel plugin

pull/1852/head
srcejon 2023-09-28 16:45:35 +01:00
rodzic 88aded6e04
commit 37521224c3
30 zmienionych plików z 5395 dodań i 17 usunięć

Wyświetl plik

@ -92,6 +92,7 @@ option(ENABLE_CHANNELRX_DEMODRTTY "Enable channelrx demodrtty plugin" ON)
option(ENABLE_CHANNELRX_DEMODILS "Enable channelrx demodils plugin" ON)
option(ENABLE_CHANNELRX_DEMODDSC "Enable channelrx demoddsc plugin" ON)
option(ENABLE_CHANNELRX_HEATMAP "Enable channelrx heatmap plugin" ON)
option(ENABLE_CHANNELRX_FREQSCANNER "Enable channelrx freqscanner plugin" ON)
# Channel Tx enablers
option(ENABLE_CHANNELTX "Enable channeltx plugins" ON)

Wyświetl plik

@ -0,0 +1,66 @@
project(freqscanner)
set(freqscanner_SOURCES
freqscanner.cpp
freqscannersettings.cpp
freqscannerbaseband.cpp
freqscannersink.cpp
freqscannerplugin.cpp
freqscannerwebapiadapter.cpp
)
set(freqscanner_HEADERS
freqscanner.h
freqscannersettings.h
freqscannerbaseband.h
freqscannersink.h
freqscannerplugin.h
freqscannerwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(freqscanner_SOURCES
${freqscanner_SOURCES}
freqscannergui.cpp
freqscannergui.ui
freqscanneraddrangedialog.cpp
freqscanneraddrangedialog.ui
)
set(freqscanner_HEADERS
${freqscanner_HEADERS}
freqscannergui.h
freqscanneraddrangedialog.h
)
set(TARGET_NAME freqscanner)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME freqscannersrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${freqscanner_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -0,0 +1,435 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNER_H
#define INCLUDE_FREQSCANNER_H
#include <QNetworkRequest>
#include <QThread>
#include <QTimer>
#include <QDateTime>
#include <QDebug>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "freqscannerbaseband.h"
#include "freqscannersettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class FreqScanner : public BasebandSampleSink, public ChannelAPI {
public:
class MsgConfigureFreqScanner : public Message {
MESSAGE_CLASS_DECLARATION
public:
const FreqScannerSettings& getSettings() const { return m_settings; }
const QStringList& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
static MsgConfigureFreqScanner* create(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force)
{
return new MsgConfigureFreqScanner(settings, settingsKeys, force);
}
private:
FreqScannerSettings m_settings;
QStringList m_settingsKeys;
bool m_force;
MsgConfigureFreqScanner(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force)
{ }
};
class MsgReportChannels : public Message {
MESSAGE_CLASS_DECLARATION
public:
QList<FreqScannerSettings::AvailableChannel>& getChannels() { return m_channels; }
static MsgReportChannels* create() {
return new MsgReportChannels();
}
private:
QList<FreqScannerSettings::AvailableChannel> m_channels;
MsgReportChannels() :
Message()
{}
};
class MsgStartScan : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStartScan* create()
{
return new MsgStartScan();
}
private:
MsgStartScan() :
Message()
{
}
};
class MsgStopScan : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStopScan* create()
{
return new MsgStopScan();
}
private:
MsgStopScan() :
Message()
{
}
};
class MsgScanComplete : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgScanComplete* create()
{
return new MsgScanComplete();
}
private:
MsgScanComplete() :
Message()
{
}
};
class MsgScanResult : public Message {
MESSAGE_CLASS_DECLARATION
public:
struct ScanResult {
qint64 m_frequency;
float m_power;
};
const QDateTime& getFFTStartTime() { return m_fftStartTime; }
QList<ScanResult>& getScanResults() { return m_scanResults; }
static MsgScanResult* create(const QDateTime& fftStartTime) {
return new MsgScanResult(fftStartTime);
}
private:
QDateTime m_fftStartTime;
QList<ScanResult> m_scanResults;
MsgScanResult(const QDateTime& fftStartTime) :
Message(),
m_fftStartTime(fftStartTime)
{}
};
class MsgStatus : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getText() const { return m_text; }
static MsgStatus* create(const QString& text)
{
return new MsgStatus(text);
}
private:
QString m_text;
MsgStatus(const QString& text) :
Message(),
m_text(text)
{
}
};
class MsgReportActiveFrequency : public Message {
MESSAGE_CLASS_DECLARATION
public:
qint64 getCenterFrequency() const { return m_centerFrequency; }
static MsgReportActiveFrequency* create(qint64 centerFrequency)
{
return new MsgReportActiveFrequency(centerFrequency);
}
private:
qint64 m_centerFrequency;
MsgReportActiveFrequency(qint64 centerFrequency) :
Message(),
m_centerFrequency(centerFrequency)
{
}
};
class MsgReportActivePower : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getPower() const { return m_power; }
static MsgReportActivePower* create(float power)
{
return new MsgReportActivePower(power);
}
private:
Real m_power;
MsgReportActivePower(float power) :
Message(),
m_power(power)
{
}
};
class MsgReportScanning : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgReportScanning* create()
{
return new MsgReportScanning();
}
private:
MsgReportScanning() :
Message()
{
}
};
class MsgReportScanRange : public Message {
MESSAGE_CLASS_DECLARATION
public:
qint64 getCenterFrequency() const { return m_centerFrequency; }
int getTotalBandwidth() const { return m_totalBandwidth; }
int getFftSize() const { return m_fftSize; }
static MsgReportScanRange* create(qint64 centerFrequency, int totalBandwidth, int fftSize)
{
return new MsgReportScanRange(centerFrequency, totalBandwidth, fftSize);
}
private:
qint64 m_centerFrequency;
int m_totalBandwidth;
int m_fftSize;
MsgReportScanRange(qint64 centerFrequency, int totalBandwidth, int fftSize) :
Message(),
m_centerFrequency(centerFrequency),
m_totalBandwidth(totalBandwidth),
m_fftSize(fftSize)
{
}
};
FreqScanner(DeviceAPI *deviceAPI);
virtual ~FreqScanner();
virtual void destroy() { delete this; }
virtual void setDeviceAPI(DeviceAPI *deviceAPI);
virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSinkName() { return objectName(); }
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const FreqScannerSettings& settings);
static void webapiUpdateChannelSettings(
FreqScannerSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}
uint32_t getNumberOfDeviceStreams() const;
static void calcScannerSampleRate(int channelBW, int basebandSampleRate, int& scannerSampleRate, int& fftSize, int& binsPerChannel)
{
const int maxFFTSize = 2048;
const int maxBinsPerChannel = 32;
const int minBinsPerChannel = 8;
// Use multiple bins per channel, to account for FFT spectral leakage
binsPerChannel = maxFFTSize / (basebandSampleRate / channelBW);
binsPerChannel = std::min(binsPerChannel, maxBinsPerChannel);
binsPerChannel = std::max(binsPerChannel, minBinsPerChannel);
double binBW = channelBW / (double)binsPerChannel;
// Find next smallest power of 2
fftSize = pow(2.0, floor(log2(basebandSampleRate / binBW)));
fftSize = std::min(maxFFTSize, fftSize);
scannerSampleRate = binBW * fftSize;
qInfo() << "binsPerChannel:" << binsPerChannel << "fftSize:" << fftSize << "scannerSampleRate:" << scannerSampleRate;
}
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
FreqScannerBaseband* m_basebandSink;
bool m_running;
FreqScannerSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QHash<ChannelAPI*, FreqScannerSettings::AvailableChannel> m_availableChannels;
int m_scanDeviceSetIndex;
int m_scanChannelIndex;
qint64 m_activeFrequency;
QDateTime m_minFFTStartTime;
int m_scannerSampleRate;
bool m_stepping;
qint64 m_stepStartFrequency;
qint64 m_stepStopFrequency;
QList<MsgScanResult::ScanResult> m_scanResults;
enum State {
IDLE,
START_SCAN,
SCAN_FOR_MAX_POWER,
WAIT_FOR_END_TX,
WAIT_FOR_RETRANSMISSION
} m_state;
QTimer m_timeoutTimer;
virtual bool handleMessage(const Message& cmd);
void applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force = false);
void webapiReverseSendSettings(const QStringList& channelSettingsKeys, const FreqScannerSettings& settings, bool force);
void webapiFormatChannelSettings(
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const FreqScannerSettings& settings,
bool force
);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void scanAvailableChannels();
void notifyUpdateChannels();
void startScan();
void stopScan();
void initScan();
void processScanResults(const QDateTime& fftStartTime, const QList<MsgScanResult::ScanResult>& results);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleIndexInDeviceSetChanged(int index);
void handleChannelAdded(int deviceSetIndex, ChannelAPI* channel);
void handleChannelRemoved(int deviceSetIndex, ChannelAPI* channel);
void timeout();
};
#endif // INCLUDE_FREQSCANNER_H

Wyświetl plik

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "freqscanneraddrangedialog.h"
#include "ui_freqscanneraddrangedialog.h"
FreqScannerAddRangeDialog::FreqScannerAddRangeDialog(QWidget* parent) :
QDialog(parent),
ui(new Ui::FreqScannerAddRangeDialog)
{
ui->setupUi(this);
ui->start->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->start->setValueRange(false, 11, 0, 99999999999);
ui->stop->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->stop->setValueRange(false, 11, 0, 99999999999);
// Airband frequency range
ui->start->setValue(118000000);
ui->stop->setValue(137000000);
}
FreqScannerAddRangeDialog::~FreqScannerAddRangeDialog()
{
delete ui;
}
void FreqScannerAddRangeDialog::accept()
{
m_start = ui->start->getValue();
m_stop = ui->stop->getValue();
m_step = ui->step->currentText().toLongLong();
QDialog::accept();
}

Wyświetl plik

@ -0,0 +1,44 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERADDRANGEDIALOG_H
#define INCLUDE_FREQSCANNERADDRANGEDIALOG_H
#include <QDialog>
namespace Ui {
class FreqScannerAddRangeDialog;
}
class FreqScannerAddRangeDialog : public QDialog {
Q_OBJECT
public:
explicit FreqScannerAddRangeDialog(QWidget* parent = nullptr);
~FreqScannerAddRangeDialog();
qint64 m_start;
qint64 m_stop;
int m_step;
private slots:
void accept();
private:
Ui::FreqScannerAddRangeDialog *ui;
};
#endif // INCLUDE_FREQSCANNERADDRANGEDIALOG_H

Wyświetl plik

@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FreqScannerAddRangeDialog</class>
<widget class="QDialog" name="FreqScannerAddRangeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>162</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Add Range</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Add Frequency Range</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="ValueDialZ" name="stop" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Stop frequency in Hertz</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="stopLabel">
<property name="text">
<string>Stop Frequency</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="startLabel">
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Start Frequency</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="stopUnits">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="step">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>25000</string>
</property>
</item>
<item>
<property name="text">
<string>8333.3</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="ValueDialZ" name="start" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Start frequency in Hertz</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="stepUnits">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="startUnits">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="stepLabel">
<property name="text">
<string>Step</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="../demodapt/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>FreqScannerAddRangeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>FreqScannerAddRangeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Wyświetl plik

@ -0,0 +1,215 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "freqscannerbaseband.h"
#include "freqscanner.h"
MESSAGE_CLASS_DEFINITION(FreqScannerBaseband::MsgConfigureFreqScannerBaseband, Message)
FreqScannerBaseband::FreqScannerBaseband(FreqScanner *freqScanner) :
m_sink(freqScanner),
m_messageQueueToGUI(nullptr),
m_running(false)
{
qDebug("FreqScannerBaseband::FreqScannerBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
m_channelSampleRate = 0;
m_scannerSampleRate = 0;
}
FreqScannerBaseband::~FreqScannerBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void FreqScannerBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
m_channelSampleRate = 0;
}
void FreqScannerBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&FreqScannerBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void FreqScannerBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&FreqScannerBaseband::handleData
);
m_running = false;
}
void FreqScannerBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void FreqScannerBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void FreqScannerBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void FreqScannerBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool FreqScannerBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureFreqScannerBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureFreqScannerBaseband& cfg = (MsgConfigureFreqScannerBaseband&) cmd;
qDebug() << "FreqScannerBaseband::handleMessage: MsgConfigureFreqScannerBaseband";
applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "FreqScannerBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
if (m_channelSampleRate != m_channelizer->getChannelSampleRate()) {
m_channelSampleRate = m_channelizer->getChannelSampleRate();
}
m_sink.setCenterFrequency(notif.getCenterFrequency());
return true;
}
else
{
return false;
}
}
void FreqScannerBaseband::applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force)
{
if ((settings.m_channelBandwidth != m_settings.m_channelBandwidth) || (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
calcScannerSampleRate(m_channelizer->getBasebandSampleRate(), settings.m_channelBandwidth, settings.m_inputFrequencyOffset);
}
m_sink.applySettings(settings, settingsKeys, force);
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}
int FreqScannerBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void FreqScannerBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
calcScannerSampleRate(sampleRate, m_settings.m_channelBandwidth, m_settings.m_inputFrequencyOffset);
}
void FreqScannerBaseband::calcScannerSampleRate(int basebandSampleRate, float rfBandwidth, int inputFrequencyOffset)
{
int fftSize;
int binsPerChannel;
FreqScanner::calcScannerSampleRate(rfBandwidth, basebandSampleRate, m_scannerSampleRate, fftSize, binsPerChannel);
m_channelizer->setChannelization(m_scannerSampleRate, inputFrequencyOffset);
m_channelSampleRate = m_channelizer->getChannelSampleRate();
m_sink.applyChannelSettings(m_channelSampleRate, m_channelizer->getChannelFrequencyOffset(), m_scannerSampleRate, fftSize, binsPerChannel);
qInfo() << "FreqScannerBaseband::calcScannerSampleRate"
<< "basebandSampleRate:" << basebandSampleRate
<< "channelSampleRate:" << m_channelSampleRate
<< "scannerSampleRate:" << m_scannerSampleRate
<< "rfBandwidth:" << rfBandwidth
<< "fftSize:" << fftSize
<< "binsPerChannel:" << binsPerChannel;
if (getMessageQueueToGUI())
{
FreqScanner::MsgReportScanRange* msg = FreqScanner::MsgReportScanRange::create(inputFrequencyOffset, m_scannerSampleRate, fftSize);
getMessageQueueToGUI()->push(msg);
}
}

Wyświetl plik

@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERBASEBAND_H
#define INCLUDE_FREQSCANNERBASEBAND_H
#include <QObject>
#include <QRecursiveMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "freqscannersink.h"
class DownChannelizer;
class ChannelAPI;
class FreqScanner;
class FreqScannerBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureFreqScannerBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const FreqScannerSettings& getSettings() const { return m_settings; }
const QStringList& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
static MsgConfigureFreqScannerBaseband* create(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force)
{
return new MsgConfigureFreqScannerBaseband(settings, settingsKeys, force);
}
private:
FreqScannerSettings m_settings;
QStringList m_settingsKeys;
bool m_force;
MsgConfigureFreqScannerBaseband(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force)
{ }
};
FreqScannerBaseband(FreqScanner *packetDemod);
~FreqScannerBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setMessageQueueToGUI(MessageQueue* messageQueue) { m_messageQueueToGUI = messageQueue; }
void setBasebandSampleRate(int sampleRate);
int getChannelSampleRate() const;
void setChannel(ChannelAPI *channel);
bool isRunning() const { return m_running; }
void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
int m_channelSampleRate;
int m_scannerSampleRate;
FreqScannerSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
FreqScannerSettings m_settings;
bool m_running;
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force = false);
void calcScannerSampleRate(int basebandSampleRate, float rfBandwidth, int inputFrequencyOffset);
MessageQueue* getMessageQueueToGUI() { return m_messageQueueToGUI; }
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_FREQSCANNERBASEBAND_H

Wyświetl plik

@ -0,0 +1,911 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QAction>
#include <QClipboard>
#include <QMenu>
#include <QTableWidget>
#include <QTableWidgetItem>
#include "device/deviceset.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_freqscannergui.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "gui/basicchannelsettingsdialog.h"
#include "dsp/dspengine.h"
#include "dsp/glscopesettings.h"
#include "gui/tabletapandhold.h"
#include "gui/dialogpositioner.h"
#include "gui/decimaldelegate.h"
#include "gui/frequencydelegate.h"
#include "gui/glspectrum.h"
#include "channel/channelwebapiutils.h"
#include "freqscannergui.h"
#include "freqscanneraddrangedialog.h"
#include "freqscanner.h"
#include "freqscannersink.h"
FreqScannerGUI* FreqScannerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
FreqScannerGUI* gui = new FreqScannerGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void FreqScannerGUI::destroy()
{
delete this;
}
void FreqScannerGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applyAllSettings();
}
QByteArray FreqScannerGUI::serialize() const
{
return m_settings.serialize();
}
bool FreqScannerGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data))
{
displaySettings();
applyAllSettings();
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool FreqScannerGUI::handleMessage(const Message& message)
{
if (FreqScanner::MsgConfigureFreqScanner::match(message))
{
qDebug("FreqScannerGUI::handleMessage: FreqScanner::MsgConfigureFreqScanner");
const FreqScanner::MsgConfigureFreqScanner& cfg = (FreqScanner::MsgConfigureFreqScanner&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(true, 7, 0, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
ui->channelBandwidth->setValueRange(true, 7, 8, m_basebandSampleRate);
if (m_channelMarker.getBandwidth() == 0) {
m_channelMarker.setBandwidth(m_basebandSampleRate);
}
updateAbsoluteCenterFrequency();
return true;
}
else if (FreqScanner::MsgReportChannels::match(message))
{
FreqScanner::MsgReportChannels& report = (FreqScanner::MsgReportChannels&)message;
updateChannelsList(report.getChannels());
return true;
}
else if (FreqScanner::MsgStatus::match(message))
{
FreqScanner::MsgStatus& report = (FreqScanner::MsgStatus&)message;
ui->status->setText(report.getText());
return true;
}
else if (FreqScanner::MsgReportScanning::match(message))
{
ui->status->setText("Scanning");
ui->table->clearSelection();
ui->channelPower->setText("-");
return true;
}
else if (FreqScanner::MsgScanComplete::match(message))
{
ui->startStop->setChecked(false);
return true;
}
else if (FreqScanner::MsgReportActiveFrequency::match(message))
{
FreqScanner::MsgReportActiveFrequency& report = (FreqScanner::MsgReportActiveFrequency&)message;
qint64 f = report.getCenterFrequency();
QString frequency;
QString annotation;
QList<QTableWidgetItem*> items = ui->table->findItems(QString::number(f), Qt::MatchExactly);
if (items.size() > 0)
{
ui->table->selectRow(items[0]->row());
frequency = ui->table->item(items[0]->row(), COL_FREQUENCY)->text();
annotation = ui->table->item(items[0]->row(), COL_ANNOTATION)->text();
}
FrequencyDelegate freqDelegate("Auto", 3);
QString formattedFrequency = freqDelegate.displayText(frequency, QLocale::system());
ui->status->setText(QString("Active: %1 %2").arg(formattedFrequency).arg(annotation));
return true;
}
else if (FreqScanner::MsgReportActivePower::match(message))
{
FreqScanner::MsgReportActivePower& report = (FreqScanner::MsgReportActivePower&)message;
float power = report.getPower();
ui->channelPower->setText(QString::number(power, 'f', 1));
return true;
}
else if (FreqScanner::MsgReportScanRange::match(message))
{
FreqScanner::MsgReportScanRange& report = (FreqScanner::MsgReportScanRange&)message;
m_channelMarker.setCenterFrequency(report.getCenterFrequency());
m_channelMarker.setBandwidth(report.getTotalBandwidth());
return true;
}
else if (FreqScanner::MsgScanResult::match(message))
{
FreqScanner::MsgScanResult& report = (FreqScanner::MsgScanResult&)message;
QList<FreqScanner::MsgScanResult::ScanResult> results = report.getScanResults();
// Clear column
for (int i = 0; i < ui->table->rowCount(); i++)
{
QTableWidgetItem* item = ui->table->item(i, COL_POWER);
item->setText("");
item->setBackground(QBrush());
}
// Add results
for (int i = 0; i < results.size(); i++)
{
qint64 freq = results[i].m_frequency;
QList<QTableWidgetItem *> items = ui->table->findItems(QString::number(freq), Qt::MatchExactly);
for (auto item : items) {
int row = item->row();
QTableWidgetItem* item = ui->table->item(row, COL_POWER);
item->setData(Qt::DisplayRole, results[i].m_power);
bool active = results[i].m_power >= m_settings.m_threshold;
if (active)
{
item->setBackground(Qt::darkGreen);
QTableWidgetItem* activeCountItem = ui->table->item(row, COL_ACTIVE_COUNT);
activeCountItem->setData(Qt::DisplayRole, activeCountItem->data(Qt::DisplayRole).toInt() + 1);
}
}
}
return true;
}
return false;
}
void FreqScannerGUI::updateChannelsList(const QList<FreqScannerSettings::AvailableChannel>& channels)
{
ui->channels->blockSignals(true);
ui->channels->clear();
for (const auto& channel : channels)
{
// Add channels in this device set, other than ourself
if ((channel.m_deviceSetIndex == getDeviceSetIndex()) && (channel.m_channelIndex != getIndex()))
{
QString name = QString("R%1:%2").arg(channel.m_deviceSetIndex).arg(channel.m_channelIndex);
ui->channels->addItem(name);
}
}
// Channel can be created after this plugin, so select it
// if the chosen channel appears
int channelIndex = ui->channels->findText(m_settings.m_channel);
if (channelIndex >= 0) {
ui->channels->setCurrentIndex(channelIndex);
} else {
ui->channels->setCurrentIndex(-1); // return to nothing selected
}
ui->channels->blockSignals(false);
}
void FreqScannerGUI::on_channels_currentIndexChanged(int index)
{
if (index >= 0)
{
m_settings.m_channel = ui->channels->currentText();
applySetting("channel");
}
}
void FreqScannerGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message)) {
delete message;
}
}
}
void FreqScannerGUI::channelMarkerChangedByCursor()
{
}
void FreqScannerGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void FreqScannerGUI::on_deltaFrequency_changed(qint64 value)
{
m_settings.m_channelFrequencyOffset = value;
applySetting("channelFrequencyOffset");
}
void FreqScannerGUI::on_channelBandwidth_changed(qint64 value)
{
m_settings.m_channelBandwidth = value;
applySetting("channelBandwidth");
}
void FreqScannerGUI::on_scanTime_valueChanged(int value)
{
ui->scanTimeText->setText(QString("%1 s").arg(value / 10.0, 0, 'f', 1));
m_settings.m_scanTime = value / 10.0;
applySetting("scanTime");
}
void FreqScannerGUI::on_retransmitTime_valueChanged(int value)
{
ui->retransmitTimeText->setText(QString("%1 s").arg(value / 10.0, 0, 'f', 1));
m_settings.m_retransmitTime = value / 10.0;
applySetting("retransmitTime");
}
void FreqScannerGUI::on_tuneTime_valueChanged(int value)
{
ui->tuneTimeText->setText(QString("%1 ms").arg(value));
m_settings.m_tuneTime = value;
applySetting("tuneTime");
}
void FreqScannerGUI::on_thresh_valueChanged(int value)
{
ui->threshText->setText(QString("%1 dB").arg(value / 10.0, 0, 'f', 1));
m_settings.m_threshold = value / 10.0;
applySetting("threshold");
}
void FreqScannerGUI::on_priority_currentIndexChanged(int index)
{
m_settings.m_priority = (FreqScannerSettings::Priority)index;
applySetting("priority");
}
void FreqScannerGUI::on_measurement_currentIndexChanged(int index)
{
m_settings.m_measurement = (FreqScannerSettings::Measurement)index;
applySetting("measurement");
}
void FreqScannerGUI::on_mode_currentIndexChanged(int index)
{
m_settings.m_mode = (FreqScannerSettings::Mode)index;
applySetting("mode");
}
void FreqScannerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySetting("rollupState");
}
void FreqScannerGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.setDefaultTitle(m_displayedName);
if (m_deviceUISet->m_deviceMIMOEngine)
{
dialog.setNumberOfStreams(m_freqScanner->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
}
dialog.move(p);
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitle(m_channelMarker.getTitle());
setTitleColor(m_settings.m_rgbColor);
QList<QString> settingsKeys({
"m_rgbColor",
"title",
"useReverseAPI",
"reverseAPIAddress",
"reverseAPIPort",
"reverseAPIDeviceIndex",
"reverseAPIChannelIndex"
});
if (m_deviceUISet->m_deviceMIMOEngine)
{
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
updateIndexLabel();
}
applySettings(settingsKeys);
}
resetContextMenuType();
}
FreqScannerGUI::FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::FreqScannerGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_doApplySettings(true)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/freqscanner/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_freqScanner = reinterpret_cast<FreqScanner*>(rxChannel);
m_freqScanner->setMessageQueueToGUI(getInputMessageQueue());
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(true, 7, 0, 9999999);
ui->channelBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->channelBandwidth->setValueRange(true, 7, 16, 9999999);
m_channelMarker.setColor(Qt::yellow);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle("Frequency Scanner");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setRollupState(&m_rollupState);
m_deviceUISet->addChannelMarker(&m_channelMarker);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->table->horizontalHeader()->setSectionsMovable(true);
// Add context menu to allow hiding/showing of columns
m_menu = new QMenu(ui->table);
for (int i = 0; i < ui->table->horizontalHeader()->count(); i++)
{
QString text = ui->table->horizontalHeaderItem(i)->text();
m_menu->addAction(createCheckableItem(text, i, true));
}
ui->table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->table->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->table->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(table_sectionMoved(int, int, int)));
connect(ui->table->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(table_sectionResized(int, int, int)));
// Context menu
ui->table->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->table, &QTableWidget::customContextMenuRequested, this, &FreqScannerGUI::table_customContextMenuRequested);
TableTapAndHold* tableTapAndHold = new TableTapAndHold(ui->table);
connect(tableTapAndHold, &TableTapAndHold::tapAndHold, this, &FreqScannerGUI::table_customContextMenuRequested);
ui->startStop->setStyleSheet(QString("QToolButton{ background-color: blue; } QToolButton:checked{ background-color: green; }"));
displaySettings();
makeUIConnections();
applyAllSettings();
ui->table->setItemDelegateForColumn(COL_FREQUENCY, new FrequencyDelegate("Auto", 3));
ui->table->setItemDelegateForColumn(COL_POWER, new DecimalDelegate(1));
}
FreqScannerGUI::~FreqScannerGUI()
{
delete ui;
}
void FreqScannerGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void FreqScannerGUI::applySetting(const QString& settingsKey)
{
applySettings({settingsKey});
}
void FreqScannerGUI::applySettings(const QStringList& settingsKeys, bool force)
{
if (m_doApplySettings)
{
FreqScanner::MsgConfigureFreqScanner* message = FreqScanner::MsgConfigureFreqScanner::create(m_settings, settingsKeys, force);
m_freqScanner->getInputMessageQueue()->push(message);
}
}
void FreqScannerGUI::applyAllSettings()
{
applySettings(QStringList(), true);
}
void FreqScannerGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setBandwidth(m_basebandSampleRate);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
setTitle(m_channelMarker.getTitle());
blockApplySettings(true);
ui->deltaFrequency->setValue(m_settings.m_channelFrequencyOffset);
ui->channelBandwidth->setValue(m_settings.m_channelBandwidth);
ui->scanTime->setValue(m_settings.m_scanTime * 10.0);
ui->scanTimeText->setText(QString("%1 s").arg(m_settings.m_scanTime, 0, 'f', 1));
ui->retransmitTime->setValue(m_settings.m_retransmitTime * 10.0);
ui->retransmitTimeText->setText(QString("%1 s").arg(m_settings.m_retransmitTime, 0, 'f', 1));
ui->tuneTime->setValue(m_settings.m_tuneTime);
ui->tuneTimeText->setText(QString("%1 ms").arg(m_settings.m_tuneTime));
ui->thresh->setValue(m_settings.m_threshold * 10.0);
ui->threshText->setText(QString("%1 dB").arg(m_settings.m_threshold, 0, 'f', 1));
ui->priority->setCurrentIndex((int)m_settings.m_priority);
ui->measurement->setCurrentIndex((int)m_settings.m_measurement);
ui->mode->setCurrentIndex((int)m_settings.m_mode);
for (int i = 0; i < m_settings.m_frequencies.size(); i++) {
addRow(m_settings.m_frequencies[i], m_settings.m_enabled[i], m_settings.m_notes[i]);
}
// Order and size columns
QHeaderView* header = ui->table->horizontalHeader();
for (int i = 0; i < m_settings.m_columnSizes.size(); i++)
{
bool hidden = m_settings.m_columnSizes[i] == 0;
header->setSectionHidden(i, hidden);
m_menu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_columnSizes[i] > 0) {
ui->table->setColumnWidth(i, m_settings.m_columnSizes[i]);
}
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
}
updateIndexLabel();
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
}
void FreqScannerGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void FreqScannerGUI::enterEvent(EnterEventType* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void FreqScannerGUI::on_startStop_clicked(bool checked)
{
if (checked)
{
FreqScanner::MsgStartScan* message = FreqScanner::MsgStartScan::create();
m_freqScanner->getInputMessageQueue()->push(message);
}
else
{
FreqScanner::MsgStopScan* message = FreqScanner::MsgStopScan::create();
m_freqScanner->getInputMessageQueue()->push(message);
}
}
void FreqScannerGUI::addRow(qint64 frequency, bool enabled, const QString& notes)
{
int row = ui->table->rowCount();
ui->table->setRowCount(row + 1);
// Must create before frequency so updateAnnotation can work
QTableWidgetItem* annotationItem = new QTableWidgetItem();
annotationItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->table->setItem(row, COL_ANNOTATION, annotationItem);
ui->table->setItem(row, COL_FREQUENCY, new QTableWidgetItem(QString("%1").arg(frequency)));
QTableWidgetItem *enableItem = new QTableWidgetItem();
enableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
enableItem->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
ui->table->setItem(row, COL_ENABLE, enableItem);
QTableWidgetItem* powerItem = new QTableWidgetItem();
powerItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->table->setItem(row, COL_POWER, powerItem);
QTableWidgetItem *activeCountItem = new QTableWidgetItem();
activeCountItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->table->setItem(row, COL_ACTIVE_COUNT, activeCountItem);
activeCountItem->setData(Qt::DisplayRole, 0);
QTableWidgetItem* notesItem = new QTableWidgetItem(notes);
ui->table->setItem(row, COL_NOTES, notesItem);
}
void FreqScannerGUI::on_addSingle_clicked()
{
addRow(0, true);
}
void FreqScannerGUI::on_addRange_clicked()
{
FreqScannerAddRangeDialog dialog(this);
new DialogPositioner(&dialog, false);
if (dialog.exec())
{
qint64 start = dialog.m_start;
qint64 stop = dialog.m_stop;
int step = dialog.m_step;
if ((start <= stop) && (step > 0))
{
for (qint64 f = start; f <= stop; f += step) {
addRow(f, true);
}
}
}
}
void FreqScannerGUI::on_remove_clicked()
{
QList<QTableWidgetItem*> items = ui->table->selectedItems();
for (auto item : items)
{
int row = ui->table->row(item);
ui->table->removeRow(row);
m_settings.m_frequencies.removeAt(row); // table_cellChanged isn't called for removeRow
m_settings.m_enabled.removeAt(row);
}
}
static QList<QTableWidgetItem*> takeRow(QTableWidget* table, int row)
{
QList<QTableWidgetItem*> rowItems;
for (int col = 0; col < table->columnCount(); col++) {
rowItems.append(table->takeItem(row, col));
}
return rowItems;
}
static void setRow(QTableWidget* table, int row, const QList<QTableWidgetItem*>& rowItems)
{
for (int col = 0; col < rowItems.size(); col++) {
table->setItem(row, col, rowItems.at(col));
}
}
void FreqScannerGUI::on_up_clicked()
{
QList<QTableWidgetItem*> items = ui->table->selectedItems();
for (auto item : items)
{
int row = ui->table->row(item);
if (row > 0)
{
QList<QTableWidgetItem*> sourceItems = takeRow(ui->table, row);
QList<QTableWidgetItem*> destItems = takeRow(ui->table, row - 1);
setRow(ui->table, row - 1, sourceItems);
setRow(ui->table, row, destItems);
ui->table->setCurrentCell(row - 1, 0);
}
}
}
void FreqScannerGUI::on_down_clicked()
{
QList<QTableWidgetItem*> items = ui->table->selectedItems();
for (auto item : items)
{
int row = ui->table->row(item);
if (row < ui->table->rowCount() - 1)
{
QList<QTableWidgetItem*> sourceItems = takeRow(ui->table, row);
QList<QTableWidgetItem*> destItems = takeRow(ui->table, row + 1);
setRow(ui->table, row + 1, sourceItems);
setRow(ui->table, row, destItems);
ui->table->setCurrentCell(row + 1, 0);
}
}
}
void FreqScannerGUI::on_table_cellChanged(int row, int column)
{
QTableWidgetItem* item = ui->table->item(row, column);
if (item)
{
if (column == COL_FREQUENCY)
{
qint64 value = item->text().toLongLong();
while (m_settings.m_frequencies.size() <= row)
{
m_settings.m_frequencies.append(0);
m_settings.m_enabled.append(true);
m_settings.m_notes.append("");
}
m_settings.m_frequencies[row] = value;
updateAnnotation(row);
QList<QString> settingsKeys({
"frequencies",
"enabled",
"notes"
});
applySettings(settingsKeys);
}
else if (column == COL_ENABLE)
{
m_settings.m_enabled[row] = item->checkState() == Qt::Checked;
applySetting("enabled");
}
else if (column == COL_NOTES)
{
m_settings.m_notes[row] = item->text();
applySetting("notes");
}
}
}
void FreqScannerGUI::updateAnnotation(int row)
{
QTableWidgetItem* item = ui->table->item(row, COL_FREQUENCY);
QTableWidgetItem* annotationItem = ui->table->item(row, COL_ANNOTATION);
if (item && annotationItem)
{
qint64 frequency = item->text().toLongLong();
const QList<SpectrumAnnotationMarker>& markers = m_deviceUISet->m_spectrum->getAnnotationMarkers();
const SpectrumAnnotationMarker* closest = nullptr;
for (const auto& marker : markers)
{
if ((marker.m_startFrequency <= frequency) && (frequency < marker.m_startFrequency + marker.m_bandwidth))
{
if (marker.m_bandwidth == m_settings.m_channelBandwidth) {
// Exact match
annotationItem->setText(marker.m_text);
return;
} else if (!closest) {
closest = &marker;
} else {
if (marker.m_bandwidth < closest->m_bandwidth) {
closest = &marker;
}
}
}
}
if (closest) {
annotationItem->setText(closest->m_text);
}
}
}
void FreqScannerGUI::table_customContextMenuRequested(QPoint pos)
{
QTableWidgetItem* item = ui->table->itemAt(pos);
if (item)
{
int row = item->row();
QMenu* tableContextMenu = new QMenu(ui->table);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
QAction* copyAction = new QAction("Copy", tableContextMenu);
const QString text = item->text();
connect(copyAction, &QAction::triggered, this, [text]()->void {
QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
});
tableContextMenu->addAction(copyAction);
tableContextMenu->addSeparator();
// Tune to frequency
const QRegExp re("R([0-9]+):([0-9]+)");
if (re.indexIn(m_settings.m_channel) >= 0)
{
int scanDeviceSetIndex = re.capturedTexts()[1].toInt();
int scanChannelIndex = re.capturedTexts()[2].toInt();
qDebug() << "scanDeviceSetIndex" << scanDeviceSetIndex << "scanChannelIndex" << scanChannelIndex;
qint64 frequency = ui->table->item(row, COL_FREQUENCY)->text().toLongLong();
QAction* findChannelMapAction = new QAction(QString("Tune R%1:%2 to %3").arg(scanDeviceSetIndex).arg(scanChannelIndex).arg(frequency), tableContextMenu);
connect(findChannelMapAction, &QAction::triggered, this, [this, scanDeviceSetIndex, scanChannelIndex, frequency]()->void {
if ((frequency - m_settings.m_channelBandwidth / 2 < m_deviceCenterFrequency - m_basebandSampleRate / 2)
|| (frequency + m_settings.m_channelBandwidth / 2 >= m_deviceCenterFrequency + m_basebandSampleRate / 2))
{
qint64 centerFrequency = frequency;
int offset = 0;
while (frequency - centerFrequency < m_settings.m_channelFrequencyOffset)
{
centerFrequency -= m_settings.m_channelBandwidth;
offset += m_settings.m_channelBandwidth;
}
if (!ChannelWebAPIUtils::setCenterFrequency(getDeviceSetIndex(), centerFrequency)) {
qWarning() << "Scanner failed to set frequency" << centerFrequency;
}
ChannelWebAPIUtils::setFrequencyOffset(scanDeviceSetIndex, scanChannelIndex, offset);
}
else
{
int offset = frequency - m_deviceCenterFrequency;
ChannelWebAPIUtils::setFrequencyOffset(scanDeviceSetIndex, scanChannelIndex, offset);
}
});
tableContextMenu->addAction(findChannelMapAction);
}
else
{
qDebug() << "Failed to parse channel" << m_settings.m_channel;
}
tableContextMenu->popup(ui->table->viewport()->mapToGlobal(pos));
}
}
// Columns in table reordered
void FreqScannerGUI::table_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void)oldVisualIndex;
m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void FreqScannerGUI::table_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void)oldSize;
m_settings.m_columnSizes[logicalIndex] = newSize;
}
// Right click in ADSB table header - show column select menu
void FreqScannerGUI::columnSelectMenu(QPoint pos)
{
m_menu->popup(ui->table->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void FreqScannerGUI::columnSelectMenuChecked(bool checked)
{
(void)checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->table->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction* FreqScannerGUI::createCheckableItem(QString& text, int idx, bool checked)
{
QAction* action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
return action;
}
void FreqScannerGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = ui->table->rowCount();
ui->table->setRowCount(row + 1);
ui->table->setItem(row, COL_FREQUENCY, new QTableWidgetItem("999.000 MHz"));
ui->table->setItem(row, COL_ANNOTATION, new QTableWidgetItem("An annotation"));
ui->table->setItem(row, COL_ENABLE, new QTableWidgetItem("Enable"));
ui->table->setItem(row, COL_POWER, new QTableWidgetItem("-100.0"));
ui->table->setItem(row, COL_ACTIVE_COUNT, new QTableWidgetItem("10000"));
ui->table->setItem(row, COL_NOTES, new QTableWidgetItem("Enter some notes"));
ui->table->resizeColumnsToContents();
ui->table->setRowCount(row);
}
void FreqScannerGUI::makeUIConnections()
{
QObject::connect(ui->channels, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_channels_currentIndexChanged);
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &FreqScannerGUI::on_deltaFrequency_changed);
QObject::connect(ui->channelBandwidth, &ValueDialZ::changed, this, &FreqScannerGUI::on_channelBandwidth_changed);
QObject::connect(ui->scanTime, &QDial::valueChanged, this, &FreqScannerGUI::on_scanTime_valueChanged);
QObject::connect(ui->retransmitTime, &QDial::valueChanged, this, &FreqScannerGUI::on_retransmitTime_valueChanged);
QObject::connect(ui->tuneTime, &QDial::valueChanged, this, &FreqScannerGUI::on_tuneTime_valueChanged);
QObject::connect(ui->thresh, &QDial::valueChanged, this, &FreqScannerGUI::on_thresh_valueChanged);
QObject::connect(ui->priority, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_priority_currentIndexChanged);
QObject::connect(ui->measurement, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_measurement_currentIndexChanged);
QObject::connect(ui->mode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_mode_currentIndexChanged);
QObject::connect(ui->startStop, &ButtonSwitch::clicked, this, &FreqScannerGUI::on_startStop_clicked);
QObject::connect(ui->table, &QTableWidget::cellChanged, this, &FreqScannerGUI::on_table_cellChanged);
QObject::connect(ui->addSingle, &QToolButton::clicked, this, &FreqScannerGUI::on_addSingle_clicked);
QObject::connect(ui->addRange, &QToolButton::clicked, this, &FreqScannerGUI::on_addRange_clicked);
QObject::connect(ui->remove, &QToolButton::clicked, this, &FreqScannerGUI::on_remove_clicked);
QObject::connect(ui->up, &QToolButton::clicked, this, &FreqScannerGUI::on_up_clicked);
QObject::connect(ui->down, &QToolButton::clicked, this, &FreqScannerGUI::on_down_clicked);
}
void FreqScannerGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}

Wyświetl plik

@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERGUI_H
#define INCLUDE_FREQSCANNERGUI_H
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "freqscanner.h"
#include "freqscannersettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class FreqScanner;
class FreqScannerGUI;
class QMenu;
namespace Ui {
class FreqScannerGUI;
}
class FreqScannerGUI;
class FreqScannerGUI : public ChannelGUI {
Q_OBJECT
public:
static FreqScannerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; };
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; };
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; };
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; };
virtual QString getTitle() const { return m_settings.m_title; };
virtual QColor getTitleColor() const { return m_settings.m_rgbColor; };
virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; }
virtual bool getHidden() const { return m_settings.m_hidden; }
virtual ChannelMarker& getChannelMarker() { return m_channelMarker; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual void setStreamIndex(int streamIndex) { m_settings.m_streamIndex = streamIndex; }
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
Ui::FreqScannerGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
FreqScannerSettings m_settings;
qint64 m_deviceCenterFrequency;
bool m_doApplySettings;
FreqScanner* m_freqScanner;
int m_basebandSampleRate;
MessageQueue m_inputMessageQueue;
QMenu *m_menu;
explicit FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~FreqScannerGUI();
void blockApplySettings(bool block);
void applySetting(const QString& settingsKey);
void applySettings(const QStringList& settingsKeys, bool force = false);
void applyAllSettings();
void displaySettings();
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void addRow(qint64 frequency, bool enabled, const QString& notes = "");
void updateAnnotation(int row);
void updateChannelsList(const QList<FreqScannerSettings::AvailableChannel>& channels);
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
void resizeTable();
QAction* createCheckableItem(QString& text, int idx, bool checked);
enum Col {
COL_FREQUENCY,
COL_ANNOTATION,
COL_ENABLE,
COL_POWER,
COL_ACTIVE_COUNT,
COL_NOTES
};
private slots:
void on_channels_currentIndexChanged(int index);
void on_deltaFrequency_changed(qint64 value);
void on_channelBandwidth_changed(qint64 index);
void on_scanTime_valueChanged(int value);
void on_retransmitTime_valueChanged(int value);
void on_tuneTime_valueChanged(int value);
void on_thresh_valueChanged(int value);
void on_priority_currentIndexChanged(int index);
void on_measurement_currentIndexChanged(int index);
void on_mode_currentIndexChanged(int index);
void on_table_cellChanged(int row, int column);
void table_customContextMenuRequested(QPoint pos);
void table_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void table_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void on_startStop_clicked(bool checked = false);
void on_addSingle_clicked();
void on_addRange_clicked();
void on_remove_clicked();
void on_up_clicked();
void on_down_clicked();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
};
#endif // INCLUDE_FREQSCANNERGUI_H

Wyświetl plik

@ -0,0 +1,776 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FreqScannerGUI</class>
<widget class="RollupContents" name="FreqScannerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>419</width>
<height>431</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Frequency Scanner</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>411</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="channelsLabel">
<property name="text">
<string>Channel</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channels">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Channel to tune</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Minimum demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Active frequency power </string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="powerLayout">
<item>
<widget class="QLabel" name="threshLabel">
<property name="text">
<string>TH</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="thresh">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Power threshold in dB</string>
</property>
<property name="minimum">
<number>-1400</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="threshText">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>-100.0 dB</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tuneTimeLabel">
<property name="text">
<string>t&lt;sub&gt;Δf&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="tuneTime">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Time in milliseconds to wait before starting measurement after changing frequency</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tuneTimeText">
<property name="minimumSize">
<size>
<width>45</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100 ms</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scanTimeLabel">
<property name="text">
<string>t&lt;sub&gt;S&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="scanTime">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Scan power measurement time in seconds</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scanTimeText">
<property name="minimumSize">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0 s</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="retransmitTimeLabel">
<property name="text">
<string>t&lt;sub&gt;RTX&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="retransmitTime">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Time in seconds to wait for frequency to become active again, before restarting scan</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="retransmitTimeText">
<property name="minimumSize">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0 s</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="phySettingsLayout">
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>Ch BW</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="channelBandwidth" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Channel bandwidth</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWUnits">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="priorityLabel">
<property name="text">
<string>Pri</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="priority">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Prioritisation. Select frequency with highest power or first in table.</string>
</property>
<item>
<property name="text">
<string>Max Power</string>
</property>
</item>
<item>
<property name="text">
<string>Table Order</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="measurementLabel">
<property name="text">
<string>Meas</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="measurement">
<property name="toolTip">
<string>Whether the power measurement is the peak power within the channel or total channel power</string>
</property>
<item>
<property name="text">
<string>Peak</string>
</property>
</item>
<item>
<property name="text">
<string>Total</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="filterLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="toolbarLayout">
<item>
<widget class="QComboBox" name="mode">
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Run mode</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Single</string>
</property>
</item>
<item>
<property name="text">
<string>Continuous</string>
</property>
</item>
<item>
<property name="text">
<string>Scan-only</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>Start/stop frequency scanning</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableWidget" name="table">
<column>
<property name="text">
<string>Freq (Hz)</string>
</property>
<property name="toolTip">
<string>Center frequency in Hertz of the channel</string>
</property>
</column>
<column>
<property name="text">
<string>Annotation</string>
</property>
</column>
<column>
<property name="text">
<string>Enable</string>
</property>
<property name="toolTip">
<string>Whether the channel is enabled</string>
</property>
</column>
<column>
<property name="text">
<string>Power (dB)</string>
</property>
<property name="toolTip">
<string>Channel power in decibels during the previous scan</string>
</property>
</column>
<column>
<property name="text">
<string>Active Count</string>
</property>
<property name="toolTip">
<string>Count of the number of times the channel is active when scanned</string>
</property>
</column>
<column>
<property name="text">
<string>Notes</string>
</property>
<property name="toolTip">
<string>User notes about this frequency</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="addSingle">
<property name="toolTip">
<string>Add a single frequency</string>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addRange">
<property name="toolTip">
<string>Add a range of frequencies</string>
</property>
<property name="text">
<string>Add Range</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="toolTip">
<string>Remove selected items</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="up">
<property name="toolTip">
<string>Move selected rows up</string>
</property>
<property name="text">
<string>Up</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="down">
<property name="toolTip">
<string>Move selected rows down</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>deltaFrequency</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

Wyświetl plik

@ -0,0 +1,93 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "freqscannergui.h"
#endif
#include "freqscanner.h"
#include "freqscannerwebapiadapter.h"
#include "freqscannerplugin.h"
const PluginDescriptor FreqScannerPlugin::m_pluginDescriptor = {
FreqScanner::m_channelId,
QStringLiteral("Frequency Scanner"),
QStringLiteral("7.17.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
FreqScannerPlugin::FreqScannerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& FreqScannerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void FreqScannerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(FreqScanner::m_channelIdURI, FreqScanner::m_channelId, this);
}
void FreqScannerPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
FreqScanner *instance = new FreqScanner(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* FreqScannerPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* FreqScannerPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return FreqScannerGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* FreqScannerPlugin::createChannelWebAPIAdapter() const
{
return new FreqScannerWebAPIAdapter();
}

Wyświetl plik

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERPLUGIN_H
#define INCLUDE_FREQSCANNERPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class FreqScannerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.freqscanner")
public:
explicit FreqScannerPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FREQSCANNERPLUGIN_H

Wyświetl plik

@ -0,0 +1,366 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "freqscannersettings.h"
FreqScannerSettings::FreqScannerSettings() :
m_channelMarker(nullptr),
m_rollupState(nullptr)
{
for (int i = 0; i < FREQSCANNER_COLUMNS; i++)
{
m_columnIndexes.append(i);
m_columnSizes.append(-1);
}
resetToDefaults();
}
void FreqScannerSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_channelBandwidth = 25000;
m_channelFrequencyOffset = 25000;
m_threshold = -60.0f;
m_channel = "";
m_scanTime = 0.1f;
m_retransmitTime = 2.0f;
m_tuneTime = 100;
m_priority = MAX_POWER;
m_measurement = PEAK;
m_mode = CONTINUOUS;
for (int i = 0; i < FREQSCANNER_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
m_rgbColor = QColor(0, 205, 200).rgb();
m_title = "Frequency Scanner";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
}
QByteArray FreqScannerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_channelBandwidth);
s.writeS32(3, m_channelFrequencyOffset);
s.writeFloat(4, m_threshold);
s.writeList(5, m_notes);
s.writeList(6, m_enabled);
s.writeList(7, m_frequencies);
s.writeString(8, m_channel);
s.writeFloat(9, m_scanTime);
s.writeFloat(10, m_retransmitTime);
s.writeS32(11, m_tuneTime);
s.writeS32(12, (int)m_priority);
s.writeS32(13, (int)m_measurement);
s.writeS32(14, (int)m_mode);
s.writeList(20, m_columnIndexes);
s.writeList(21, m_columnSizes);
s.writeU32(40, m_rgbColor);
s.writeString(41, m_title);
if (m_channelMarker) {
s.writeBlob(42, m_channelMarker->serialize());
}
s.writeS32(44, m_streamIndex);
s.writeBool(45, m_useReverseAPI);
s.writeString(46, m_reverseAPIAddress);
s.writeU32(47, m_reverseAPIPort);
s.writeU32(48, m_reverseAPIDeviceIndex);
s.writeU32(49, m_reverseAPIChannelIndex);
if (m_rollupState) {
s.writeBlob(52, m_rollupState->serialize());
}
s.writeS32(53, m_workspaceIndex);
s.writeBlob(54, m_geometryBytes);
s.writeBool(55, m_hidden);
return s.final();
}
bool FreqScannerSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(2, &m_channelBandwidth, 25000);
d.readS32(3, &m_channelFrequencyOffset, 25000);
d.readFloat(4, &m_threshold, -60.0f);
d.readList(5, &m_notes);
d.readList(6, &m_enabled);
d.readList(7, &m_frequencies);
d.readString(8, &m_channel);
while (m_notes.size() < m_frequencies.size()) {
m_notes.append("");
}
while (m_enabled.size() < m_frequencies.size()) {
m_enabled.append(true);
}
d.readFloat(9, &m_scanTime, 0.1f);
d.readFloat(10, &m_retransmitTime, 2.0f);
d.readS32(11, &m_tuneTime, 100);
d.readS32(12, (int*)&m_priority, (int)MAX_POWER);
d.readS32(13, (int*)&m_measurement, (int)PEAK);
d.readS32(14, (int*)&m_mode, (int)CONTINUOUS);
d.readList(20, &m_columnIndexes);
d.readList(21, &m_columnSizes);
d.readU32(40, &m_rgbColor, QColor(0, 205, 200).rgb());
d.readString(41, &m_title, "Frequency Scanner");
if (m_channelMarker)
{
d.readBlob(42, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readS32(44, &m_streamIndex, 0);
d.readBool(45, &m_useReverseAPI, false);
d.readString(46, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(47, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(48, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(49, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(52, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(53, &m_workspaceIndex, 0);
d.readBlob(54, &m_geometryBytes);
d.readBool(55, &m_hidden, false);
return true;
}
else
{
resetToDefaults();
return false;
}
}
void FreqScannerSettings::applySettings(const QStringList& settingsKeys, const FreqScannerSettings& settings)
{
if (settingsKeys.contains("inputFrequencyOffset")) {
m_inputFrequencyOffset = settings.m_inputFrequencyOffset;
}
if (settingsKeys.contains("channelBandwidth")) {
m_channelBandwidth = settings.m_channelBandwidth;
}
if (settingsKeys.contains("channelFrequencyOffset")) {
m_channelFrequencyOffset = settings.m_channelFrequencyOffset;
}
if (settingsKeys.contains("threshold")) {
m_threshold = settings.m_threshold;
}
if (settingsKeys.contains("frequencies")) {
m_frequencies = settings.m_frequencies;
}
if (settingsKeys.contains("enabled")) {
m_enabled = settings.m_enabled;
}
if (settingsKeys.contains("notes")) {
m_notes = settings.m_notes;
}
if (settingsKeys.contains("channel")) {
m_channel = settings.m_channel;
}
if (settingsKeys.contains("scanTime")) {
m_scanTime = settings.m_scanTime;
}
if (settingsKeys.contains("retransmitTime")) {
m_retransmitTime = settings.m_retransmitTime;
}
if (settingsKeys.contains("tuneTime")) {
m_tuneTime = settings.m_tuneTime;
}
if (settingsKeys.contains("priority")) {
m_priority = settings.m_priority;
}
if (settingsKeys.contains("measurement")) {
m_measurement = settings.m_measurement;
}
if (settingsKeys.contains("mode")) {
m_mode = settings.m_mode;
}
if (settingsKeys.contains("columnIndexes")) {
m_columnIndexes = settings.m_columnIndexes;
}
if (settingsKeys.contains("columnSizes")) {
m_columnSizes = settings.m_columnSizes;
}
if (settingsKeys.contains("rgbColor")) {
m_rgbColor = settings.m_rgbColor;
}
if (settingsKeys.contains("title")) {
m_title = settings.m_title;
}
if (settingsKeys.contains("streamIndex")) {
m_streamIndex = settings.m_streamIndex;
}
if (settingsKeys.contains("useReverseAPI")) {
m_useReverseAPI = settings.m_useReverseAPI;
}
if (settingsKeys.contains("reverseAPIAddress")) {
m_reverseAPIAddress = settings.m_reverseAPIAddress;
}
if (settingsKeys.contains("reverseAPIPort")) {
m_reverseAPIPort = settings.m_reverseAPIPort;
}
if (settingsKeys.contains("reverseAPIDeviceIndex")) {
m_reverseAPIDeviceIndex = settings.m_reverseAPIDeviceIndex;
}
if (settingsKeys.contains("reverseAPIChannelIndex")) {
m_reverseAPIChannelIndex = settings.m_reverseAPIChannelIndex;
}
if (settingsKeys.contains("workspaceIndex")) {
m_workspaceIndex = settings.m_workspaceIndex;
}
if (settingsKeys.contains("hidden")) {
m_hidden = settings.m_hidden;
}
}
QString FreqScannerSettings::getDebugString(const QStringList& settingsKeys, bool force) const
{
std::ostringstream ostr;
if (settingsKeys.contains("inputFrequencyOffset") || force) {
ostr << " m_inputFrequencyOffset: " << m_inputFrequencyOffset;
}
if (settingsKeys.contains("channelBandwidth") || force) {
ostr << " m_channelBandwidth: " << m_channelBandwidth;
}
if (settingsKeys.contains("channelFrequencyOffset") || force) {
ostr << " m_channelFrequencyOffset: " << m_channelFrequencyOffset;
}
if (settingsKeys.contains("threshold") || force) {
ostr << " m_threshold: " << m_threshold;
}
if (settingsKeys.contains("frequencies") || force)
{
QStringList s;
for (auto f : m_frequencies) {
s.append(QString::number(f));
}
ostr << " m_frequencies: " << s.join(",").toStdString();
}
if (settingsKeys.contains("enabled") || force)
{
QStringList s;
for (auto e : m_enabled) {
s.append(e ? "true" : "false");
}
ostr << " m_enabled: " << s.join(",").toStdString();
}
if (settingsKeys.contains("notes") || force) {
ostr << " m_notes: " << m_notes.join(",").toStdString();
}
if (settingsKeys.contains("channel") || force) {
ostr << " m_channel: " << m_channel.toStdString();
}
if (settingsKeys.contains("scanTime") || force) {
ostr << " m_scanTime: " << m_scanTime;
}
if (settingsKeys.contains("retransmitTime") || force) {
ostr << " m_retransmitTime: " << m_retransmitTime;
}
if (settingsKeys.contains("tuneTime") || force) {
ostr << " m_tuneTime: " << m_tuneTime;
}
if (settingsKeys.contains("priority") || force) {
ostr << " m_priority: " << m_priority;
}
if (settingsKeys.contains("measurement") || force) {
ostr << " m_measurement: " << m_measurement;
}
if (settingsKeys.contains("mode") || force) {
ostr << " m_mode: " << m_mode;
}
if (settingsKeys.contains("columnIndexes") || force) {
// Don't display
}
if (settingsKeys.contains("columnSizes") || force) {
// Don't display
}
if (settingsKeys.contains("rgbColor") || force) {
ostr << " m_rgbColor: " << m_rgbColor;
}
if (settingsKeys.contains("title") || force) {
ostr << " m_title: " << m_title.toStdString();
}
if (settingsKeys.contains("streamIndex") || force) {
ostr << " m_streamIndex: " << m_streamIndex;
}
if (settingsKeys.contains("useReverseAPI") || force) {
ostr << " m_useReverseAPI: " << m_useReverseAPI;
}
if (settingsKeys.contains("reverseAPIAddress") || force) {
ostr << " m_reverseAPIAddress: " << m_reverseAPIAddress.toStdString();
}
if (settingsKeys.contains("reverseAPIPort") || force) {
ostr << " m_reverseAPIPort: " << m_reverseAPIPort;
}
if (settingsKeys.contains("reverseAPIDeviceIndex") || force) {
ostr << " m_reverseAPIDeviceIndex: " << m_reverseAPIDeviceIndex;
}
if (settingsKeys.contains("reverseAPIChannelIndex") || force) {
ostr << " m_reverseAPIChannelIndex: " << m_reverseAPIChannelIndex;
}
if (settingsKeys.contains("workspaceIndex") || force) {
ostr << " m_workspaceIndex: " << m_workspaceIndex;
}
if (settingsKeys.contains("hidden") || force) {
ostr << " m_hidden: " << m_hidden;
}
return QString(ostr.str().c_str());
}

Wyświetl plik

@ -0,0 +1,96 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERSETTINGS_H
#define INCLUDE_FREQSCANNERSETTINGS_H
#include <QByteArray>
class Serializable;
class ChannelAPI;
// Number of columns in the table
#define FREQSCANNER_COLUMNS 6
struct FreqScannerSettings
{
struct AvailableChannel
{
int m_deviceSetIndex;
int m_channelIndex;
ChannelAPI* m_channelAPI;
AvailableChannel() = default;
AvailableChannel(const AvailableChannel&) = default;
AvailableChannel& operator=(const AvailableChannel&) = default;
};
qint32 m_inputFrequencyOffset; //!< Not modifable in GUI
qint32 m_channelBandwidth; //!< Channel bandwidth
qint32 m_channelFrequencyOffset;//!< Minium DC offset of tuned channel
Real m_threshold; //!< Power threshold in dB
QList<qint64> m_frequencies; //!< Frequencies to scan
QList<bool> m_enabled; //!< Whether corresponding frequency is enabled
QList<QString> m_notes; //!< User editable notes about this frequency
QString m_channel; //!< Channel (E.g: R1:4) to tune to active frequency
float m_scanTime; //!< In seconds
float m_retransmitTime; //!< In seconds
int m_tuneTime; //!< In milliseconds
enum Priority {
MAX_POWER,
TABLE_ORDER
} m_priority; //!< Which frequency has priority when multiple frequencies are above threshold
enum Measurement {
PEAK,
TOTAL
} m_measurement; //!< How power is measured
enum Mode {
SINGLE,
CONTINUOUS,
SCAN_ONLY
} m_mode; //!< Whether to run a single or many scans
QList<int> m_columnIndexes;//!< How the columns are ordered in the table
QList<int> m_columnSizes; //!< Size of the coumns in the table
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
Serializable *m_rollupState;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
FreqScannerSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void applySettings(const QStringList& settingsKeys, const FreqScannerSettings& settings);
QString getDebugString(const QStringList& settingsKeys, bool force = false) const;
};
#endif /* INCLUDE_FREQSCANNERSETTINGS_H */

Wyświetl plik

@ -0,0 +1,253 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <complex.h>
#include "dsp/dspengine.h"
#include "util/db.h"
#include "freqscanner.h"
#include "freqscannersink.h"
FreqScannerSink::FreqScannerSink(FreqScanner *ilsDemod) :
m_freqScanner(ilsDemod),
m_channel(nullptr),
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_scannerSampleRate(33320),
m_centerFrequency(0),
m_messageQueueToChannel(nullptr),
m_fftSequence(-1),
m_fft(nullptr),
m_fftCounter(0)
{
applySettings(m_settings, QStringList(), true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, 16, 4, true);
}
FreqScannerSink::~FreqScannerSink()
{
}
void FreqScannerSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance == 1.0f) // Don't call decimate, as we don't want filter applied if possible
{
processOneSample(c);
}
else if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void FreqScannerSink::processOneSample(Complex &ci)
{
ci /= SDR_RX_SCALEF;
m_fft->in()[m_fftCounter] = ci;
m_fftCounter++;
if (m_fftCounter == m_fftSize)
{
// Apply windowing function
m_fftWindow.apply(m_fft->in());
// Perform FFT
m_fft->transform();
// Reorder (so negative frequencies are first) and average
int halfSize = m_fftSize / 2;
for (int i = 0; i < halfSize; i++) {
m_fftAverage.storeAndGetAvg(m_magSq[i], magSq(i + halfSize), i);
}
for (int i = 0; i < halfSize; i++) {
m_fftAverage.storeAndGetAvg(m_magSq[i + halfSize], magSq(i), i + halfSize);
}
if (m_fftAverage.nextAverage())
{
// Send results to channel
if (getMessageQueueToChannel())
{
FreqScanner::MsgScanResult* msg = FreqScanner::MsgScanResult::create(m_fftStartTime);
QList<FreqScanner::MsgScanResult::ScanResult>& results = msg->getScanResults();
for (int i = 0; i < m_settings.m_frequencies.size(); i++)
{
if (m_settings.m_enabled[i])
{
qint64 frequency = m_settings.m_frequencies[i];
qint64 startFrequency = m_centerFrequency - m_scannerSampleRate / 2;
qint64 diff = frequency - startFrequency;
int binBW = m_settings.m_channelBandwidth / m_binsPerChannel;
if ((diff < m_scannerSampleRate) && (diff >= 0))
{
int bin = diff / binBW;
// Calculate power at that frequency
Real power;
if (m_settings.m_measurement == FreqScannerSettings::PEAK) {
power = peakPower(bin);
} else {
power = totalPower(bin);
}
FreqScanner::MsgScanResult::ScanResult result = {frequency, power};
results.append(result);
}
}
}
getMessageQueueToChannel()->push(msg);
}
m_averageCount = 0;
m_fftStartTime = QDateTime::currentDateTime();
}
m_fftCounter = 0;
}
}
// Calculate total power in a channel containing the specified bin (i.e. sums adjacent bins in the same channel)
Real FreqScannerSink::totalPower(int bin) const
{
// Skip bin between halfway between channels
// Then skip first and last bins, to avoid spectral leakage (particularly at DC)
int startBin = bin - m_binsPerChannel / 2 + 1 + 1;
Real magSqSum = 0.0f;
for (int i = 0; i < m_binsPerChannel - 2 - 1; i++) {
int idx = startBin + i;
if ((idx < 0) || (idx >= m_fftSize)) {
continue;
}
magSqSum += m_magSq[idx];
}
Real db = CalcDb::dbPower(magSqSum);
return db;
}
// Calculate peak power in a channel containing the specified bin
Real FreqScannerSink::peakPower(int bin) const
{
// Skip bin between halfway between channels
// Then skip first and last bins, to avoid spectral leakage (particularly at DC)
int startBin = bin - m_binsPerChannel/2 + 1 + 1;
Real maxMagSq = m_magSq[startBin];
for (int i = 1; i < m_binsPerChannel - 2 - 1; i++) {
int idx = startBin + i;
if ((idx < 0) || (idx >= m_fftSize)) {
continue;
}
maxMagSq = std::max(maxMagSq, m_magSq[idx]);
}
Real db = CalcDb::dbPower(maxMagSq);
return db;
}
Real FreqScannerSink::magSq(int bin) const
{
Complex c = m_fft->out()[bin];
Real v = c.real() * c.real() + c.imag() * c.imag();
Real magsq = v / (m_fftSize * m_fftSize);
return magsq;
}
void FreqScannerSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, int scannerSampleRate, int fftSize, int binsPerChannel, bool force)
{
qDebug() << "FreqScannerSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset
<< " scannerSampleRate: " << scannerSampleRate
<< " fftSize: " << fftSize
<< " binsPerChannel: " << binsPerChannel;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || (m_scannerSampleRate != scannerSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, scannerSampleRate / 2.0); // Highest cutoff, so we don't attentuate first/last channel
m_interpolatorDistance = (Real) channelSampleRate / (Real)scannerSampleRate;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
if ((m_fftSize != fftSize) || force)
{
FFTFactory* fftFactory = DSPEngine::instance()->getFFTFactory();
if (m_fftSequence >= 0) {
fftFactory->releaseEngine(fftSize, false, m_fftSequence);
}
m_fftSequence = fftFactory->getEngine(fftSize, false, &m_fft);
m_fftCounter = 0;
m_fftStartTime = QDateTime::currentDateTime();
m_fftWindow.create(FFTWindow::Hanning, fftSize);
int averages = m_settings.m_scanTime * scannerSampleRate / 2 / fftSize;
m_fftAverage.resize(fftSize, averages);
m_magSq.resize(fftSize);
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_scannerSampleRate = scannerSampleRate;
m_fftSize = fftSize;
m_binsPerChannel = binsPerChannel;
}
void FreqScannerSink::applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force)
{
qDebug() << "FreqScannerSink::applySettings:"
<< settings.getDebugString(settingsKeys, force)
<< " force: " << force;
if (settingsKeys.contains("scanTime") || force)
{
int averages = settings.m_scanTime * m_scannerSampleRate / 2 / m_fftSize;
m_fftAverage.resize(m_fftSize, averages);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}

Wyświetl plik

@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNERSINK_H
#define INCLUDE_FREQSCANNERSINK_H
#include <QDateTime>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/fftfactory.h"
#include "dsp/fftengine.h"
#include "dsp/fftwindow.h"
#include "util/fixedaverage2d.h"
#include "util/messagequeue.h"
#include "freqscannersettings.h"
class ChannelAPI;
class FreqScanner;
class FreqScannerSink : public ChannelSampleSink {
public:
FreqScannerSink(FreqScanner *packetDemod);
~FreqScannerSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, int scannerSampleRate, int fftSize, int binsPerChannel, bool force = false);
void applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
void setCenterFrequency(qint64 centerFrequency) { m_centerFrequency = centerFrequency; }
private:
FreqScanner *m_freqScanner;
FreqScannerSettings m_settings;
ChannelAPI *m_channel;
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_scannerSampleRate; // Sample rate scanner runs at
qint64 m_centerFrequency;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
MessageQueue *m_messageQueueToChannel;
int m_fftSequence;
FFTEngine *m_fft;
int m_fftCounter;
FFTWindow m_fftWindow;
int m_fftSize;
int m_binsPerChannel;
QDateTime m_fftStartTime;
FixedAverage2D<Real> m_fftAverage; // magSq average
QVector<Real> m_magSq;
int m_averageCount;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
Real totalPower(int bin) const;
Real peakPower(int bin) const;
Real magSq(int bin) const;
};
#endif // INCLUDE_FREQSCANNERSINK_H

Wyświetl plik

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "freqscanner.h"
#include "freqscannerwebapiadapter.h"
FreqScannerWebAPIAdapter::FreqScannerWebAPIAdapter()
{}
FreqScannerWebAPIAdapter::~FreqScannerWebAPIAdapter()
{}
int FreqScannerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
/*response.ssetFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings());
response.gsetFreqScannerSettings()->init();
FreqScanner::webapiFormatChannelSettings(response, m_settings);*/
return 200;
}
int FreqScannerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
FreqScanner::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

Wyświetl plik

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FREQSCANNER_WEBAPIADAPTER_H
#define INCLUDE_FREQSCANNER_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "freqscannersettings.h"
/**
* Standalone API adapter only for the settings
*/
class FreqScannerWebAPIAdapter : public ChannelWebAPIAdapter {
public:
FreqScannerWebAPIAdapter();
virtual ~FreqScannerWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
FreqScannerSettings m_settings;
};
#endif // INCLUDE_FREQSCANNER_WEBAPIADAPTER_H

Wyświetl plik

@ -0,0 +1,121 @@
<h1>Frequency Scanner Plugin</h1>
<h2>Introduction</h2>
This plugin can be used to scan a range of frequencies looking for a transmission and then tune another channel (such as an AM or DSD Demod) to that frequency.
<h2>Interface</h2>
The top and bottom bars of the channel window are described [here](../../../sdrgui/channel/readme.md)
![Frequency Scanner plugin GUI](../../../doc/img/FreqScanner_plugin.png)
<h3>1: Channel</h3>
Specifies the channel (such as an AM, NFM or DSD Demod), by device set and channel index, that should be tuned to the active frequency.
<h3>2: Minimum frequency shift from center frequency of reception for channel</h3>
Use the wheels of keyboard to adjust the minimim frequency shift in Hz from the center frequency of reception for the channel (1). 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.
<h3>3: Active frequency power</h3>
Average power in dB relative to a +/- 1.0 amplitude signal received for the active frequency. This is set to '-' while scanning.
<h3>4: TH - Threshold</h3>
Power threshold in dB that determines whether a frequency is active or not.
<h3>5: t_delta_f - Tune time</h3>
Specifies the time in milliseconds that the Frequency Scanner should wait after adjusting the device center frequency, before starting a measurement.
This time should take in to account PLL settle time and the device to host transfer latency, so that the measurement only starts when IQ data
that corresponds to the set frequency is being recieved.
<h3>6: t_s - Scan time</h3>
Specifies the time in seconds that the Frequency Scanner will average its channel power measurement over.
<h3>7: t_rtx - Retransmission Time</h3>
Specifies the time in seconds that the Frequency Scanner will wait after the power on the active frequency falls below the threshold, before restarting
scanning. This allows for a temporary break in transmission.
<h3>8: Ch BW - Channel Bandwidth</h3>
This specifies the bandwidth of the channels to be scanned.
<h3>9: Pri - Priority</h3>
Specifies which frequency will be chosen as the active frequency, when multiple frequencies exceed the threshold (4):
- Max power: The frequency with the highest power will be chosen
- Table order: The frequency first in the frequency table (14) will be chosen.
<h3>10: Meas - Power Measurement</h3>
Specifies how power is measured. In both cases, a FFT is used, with the channel bandwidth being spread over 8 to 32 bins, with the first and last bins being excluded from the measurement (to reduce spectral leakage from adjacent channels):
- Peak: Power is the highest value in all of the bins, averaged over the scan time (6).
- Total: Power is the sum of power in all of the bins, averaged over the scan time (6).
Peak can be used when you wish to set the threshold roughly according to the level displayed in the Main Spectrum.
Total is potentially more useful for wideband signals, that are close to the noise floor.
<h3>11: Run Mode</h3>
Specifies the run mode:
- Single: All frequencies are scanned once. Channel (1) is tuned to the active frequency at the end of the scan. The scan does not repeat.
- Continuous: All frequencies scanned, with channel (1) being tuned to active frequency at the end of the scan. Scan repeats once the power on the active frequency falls below the threshold (4) for longer than the retransmission time (7).
- Scan only: All frequencies are scanned repeatedly. The channel will not be tuned. This mode is just for counting how often frequencies are active, which can be seen in the Active Count column in the frequency table (14).
<h3>12: Start/Stop Scanning</h3>
Press this button to start or stop scanning.
<h3>13: Status Text</h3>
Displays the current status of the Frequency Scanner.
- "Scanning": When scanning for active frequencies.
- Frequency and annotation for active frequency.
<h3>14: Frequency Table</h3>
The frequency table contains the list of frequencies to be scanned, along with results of a scan. The columns are:
- Freq (Hz): Specifies the channel center frequencies to be scanned. These should be spaced by integer multiples of the channel bandwidth (8). Values should be entered in Hertz.
- Annotation: An annotation (description) for the frequency, that is based on the closest matching [annotation marker](../../../sdrgui/gui/spectrummarkers.md) in the Main Spectrum.
- Enable: Determines whether the frequency will be scanned. This can be used to temporaily disable frequencies you aren't interested in.
- Power (dB): Displays the measured power in decibels during the previous scan. The cell will have a green background if the power was above the threshold (4).
- Active Count: Displays the number of scans in which the power for this frequency was above the threshold (4). This allows you to see which frequencies are commonly in use.
- Notes: Available for user-entry of notes/information about this frequency.
When an active frequency is found after a scan, the corresponding row in the table will be selected.
Right clicking on a cell will display a popup menu:
- Copy contents of cell to clipboard.
- Tune selected channel (1) to the frequency in the row clicked on.
<h3>15: Add</h3>
Press to add a single row to the frequency table (14).
<h3>16: Add Range</h3>
Press to add a range of frequencies to the frequency table (14). A dialog is displayed with start and stop frequencies, as well as a step value.
The step value should typically be an integer multiple of the channel bandwidth (8).
<h3>17: Remove</h3>
Removes the selected rows from the frequency table (14).
<h3>18: Up</h3>
Moves the selected rows up the frequency table (14).
<h3>19: Down</h3>
Moves the selected rows the the frequency table (14).

Wyświetl plik

@ -937,6 +937,47 @@ bool ChannelWebAPIUtils::setFrequencyOffset(unsigned int deviceIndex, int channe
return false;
}
bool ChannelWebAPIUtils::setAudioMute(unsigned int deviceIndex, int channelIndex, bool mute)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
QString errorResponse;
int httpRC;
QJsonObject* jsonObj;
ChannelAPI* channel = MainCore::instance()->getChannel(deviceIndex, channelIndex);
if (channel != nullptr)
{
httpRC = channel->webapiSettingsGet(channelSettingsResponse, errorResponse);
if (httpRC / 100 != 2)
{
qWarning("ChannelWebAPIUtils::setAudioMute: get channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
jsonObj = channelSettingsResponse.asJsonObject();
if (WebAPIUtils::setSubObjectInt(*jsonObj, "audioMute", (int)mute))
{
QStringList keys;
keys.append("audioMute");
channelSettingsResponse.init();
channelSettingsResponse.fromJsonObject(*jsonObj);
httpRC = channel->webapiSettingsPutPatch(false, keys, channelSettingsResponse, errorResponse);
if (httpRC / 100 != 2)
{
qWarning("ChannelWebAPIUtils::setAudioMute: patch channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
}
return false;
}
// Start or stop all file sinks in a given device set
bool ChannelWebAPIUtils::startStopFileSinks(unsigned int deviceIndex, bool start)
{

Wyświetl plik

@ -60,6 +60,7 @@ public:
static bool stop(unsigned int deviceIndex, int subsystemIndex=0);
static bool getFrequencyOffset(unsigned int deviceIndex, int channelIndex, int& offset);
static bool setFrequencyOffset(unsigned int deviceIndex, int channelIndex, int offset);
static bool setAudioMute(unsigned int deviceIndex, int channelIndex, bool mute);
static bool startStopFileSinks(unsigned int deviceIndex, bool start);
static bool satelliteAOS(const QString name, bool northToSouthPass, const QString &tle, QDateTime dateTime);
static bool satelliteLOS(const QString name);

Wyświetl plik

@ -4523,6 +4523,11 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setFreeDvModSettings(new SWGSDRangel::SWGFreeDVModSettings());
channelSettings->getFreeDvModSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "FreqScannerSettings")
{
//channelSettings->setFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings());
//channelSettings->getFreqScannerSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "FreqTrackerSettings")
{
channelSettings->setFreqTrackerSettings(new SWGSDRangel::SWGFreqTrackerSettings());
@ -5442,6 +5447,8 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings&
channelSettings.setDatvModSettings(nullptr);
channelSettings.setDabDemodSettings(nullptr);
channelSettings.setDsdDemodSettings(nullptr);
//channelSettings.setFreqScannerSettings(nullptr);
channelSettings.setFreqTrackerSettings(nullptr);
channelSettings.setHeatMapSettings(nullptr);
channelSettings.setIeee802154ModSettings(nullptr);
channelSettings.setIlsDemodSettings(nullptr);
@ -5484,6 +5491,8 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan
channelReport.setBfmDemodReport(nullptr);
channelReport.setDatvModReport(nullptr);
channelReport.setDsdDemodReport(nullptr);
//channelReport.setFreqScannerReport(nullptr);
channelReport.setFreqTrackerReport(nullptr);
channelReport.setHeatMapReport(nullptr);
channelReport.setIlsDemodReport(nullptr);
channelReport.setNavtexDemodReport(nullptr);

Wyświetl plik

@ -47,6 +47,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channeltx.filesource", "FileSourceSettings"},
{"sdrangel.channel.freedvdemod", "FreeDVDemodSettings"},
{"sdrangel.channeltx.freedvmod", "FreeDVModSettings"},
{"sdrangel.channel.freqscanner", "FreqScannerSettings"},
{"sdrangel.channel.freqtracker", "FreqTrackerSettings"},
{"sdrangel.channel.heatmap", "HeatMapSettings"},
{"sdrangel.channel.ilsdemod", "ILSDemodSettings"},
@ -168,6 +169,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"FileSource", "FileSourceSettings"},
{"FreeDVDemod", "FreeDVDemodSettings"},
{"FreeDVMod", "FreeDVModSettings"},
{"FreqScanner", "FreqScannerSettings"},
{"FreqTracker", "FreqTrackerSettings"},
{"HeatMap", "HeatMapSettings"},
{"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"},

Wyświetl plik

@ -15,9 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QLineEdit>
#include "frequencydelegate.h"
FrequencyDelegate::FrequencyDelegate(QString units, int precision, bool group) :
FrequencyDelegate::FrequencyDelegate(const QString& units, int precision, bool group) :
m_units(units),
m_precision(precision),
m_group(group)
@ -30,29 +32,107 @@ QString FrequencyDelegate::displayText(const QVariant &value, const QLocale &loc
qlonglong v = value.toLongLong(&ok);
if (ok)
{
double d;
if (m_units == "GHz") {
d = v / 1000000000.0;
} else if (m_units == "MHz") {
d = v / 1000000.0;
} else if (m_units == "kHz") {
d = v / 1000.0;
} else {
d = v;
}
QLocale l(locale);
if (m_group) {
l.setNumberOptions(l.numberOptions() & ~QLocale::OmitGroupSeparator);
} else {
}
else {
l.setNumberOptions(l.numberOptions() | QLocale::OmitGroupSeparator);
}
QString s = l.toString(d, 'f', m_precision);
return QString("%1 %2").arg(s).arg(m_units);
if (m_units == "Auto")
{
if (v == 0)
{
return "0 Hz";
}
else
{
QString s = QString::number(v);
int scale = 1;
while (s.endsWith("000"))
{
s.chop(3);
scale *= 1000;
}
v /= scale;
double d = v;
if ((abs(v) >= 1000) && (m_precision >= 3))
{
scale *= 1000;
d /= 1000.0;
}
QString units;
if (scale == 1) {
units = "Hz";
} else if (scale == 1000) {
units = "kHz";
} else if (scale == 1000000) {
units = "MHz";
} else if (scale == 1000000000) {
units = "GHz";
}
if (scale == 1) {
s = l.toString(d, 'f', 0);
} else {
s = l.toString(d, 'f', m_precision);
}
return QString("%1 %2").arg(s).arg(units);
}
}
else
{
double d;
if (m_units == "GHz") {
d = v / 1000000000.0;
}
else if (m_units == "MHz") {
d = v / 1000000.0;
}
else if (m_units == "kHz") {
d = v / 1000.0;
}
else {
d = v;
}
QString s = l.toString(d, 'f', m_precision);
return QString("%1 %2").arg(s).arg(m_units);
}
}
else
{
return value.toString();
}
}
QWidget* FrequencyDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QLineEdit* editor = new QLineEdit(parent);
QIntValidator* validator = new QIntValidator();
validator->setBottom(0);
editor->setValidator(validator);
return editor;
}
void FrequencyDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
QString value = index.model()->data(index, Qt::EditRole).toString();
QLineEdit* line = static_cast<QLineEdit*>(editor);
line->setText(value);
}
void FrequencyDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
QLineEdit* line = static_cast<QLineEdit*>(editor);
QString value = line->text();
model->setData(index, value);
}
void FrequencyDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}

Wyświetl plik

@ -26,8 +26,14 @@
class SDRGUI_API FrequencyDelegate : public QStyledItemDelegate {
public:
FrequencyDelegate(QString units = "kHz", int precision=1, bool group=true);
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
FrequencyDelegate(const QString& units = "kHz", int precision=1, bool group=true);
QString displayText(const QVariant &value, const QLocale &locale) const override;
protected:
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void setEditorData(QWidget* editor, const QModelIndex& index) const;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
QString m_units;

Wyświetl plik

@ -49,6 +49,8 @@ ChannelReport:
$ref: "http://swgserver:8081/api/swagger/include/FreeDVDemod.yaml#/FreeDVDemodReport"
FreeDVModReport:
$ref: "http://swgserver:8081/api/swagger/include/FreeDVMod.yaml#/FreeDVModReport"
FreqScannerReport:
$ref: "http://swgserver:8081/api/swagger/include/FreqScanner.yaml#/FreqScannerReport"
FreqTrackerReport:
$ref: "http://swgserver:8081/api/swagger/include/FreqTracker.yaml#/FreqTrackerReport"
FT8DemodReport:

Wyświetl plik

@ -63,6 +63,8 @@ ChannelSettings:
$ref: "http://swgserver:8081/api/swagger/include/FreeDVDemod.yaml#/FreeDVDemodSettings"
FreeDVModSettings:
$ref: "http://swgserver:8081/api/swagger/include/FreeDVMod.yaml#/FreeDVModSettings"
FreqScannerSettings:
$ref: "http://swgserver:8081/api/swagger/include/FreqScanner.yaml#/FreqScannerSettings"
FreqTrackerSettings:
$ref: "http://swgserver:8081/api/swagger/include/FreqTracker.yaml#/FreqTrackerSettings"
FT8DemodSettings:

Wyświetl plik

@ -0,0 +1,70 @@
FreqTrackerSettings:
description: FreqTracker
properties:
channelBandwidth:
description: channel RF bandwidth in Hz
type: integer
channelFrequencyOffset:
description: channel center frequency shift from baseband center in Hz
type: integer
threshold:
type: number
format: float
m_frequencies:
type: array
items:
type: integer
format: int64
m_enabled:
type: array
items:
type: integer
m_notes:
type: array
items:
type: string
channel:
type: string
scanTime:
type: number
format: float
retransmitTime:
type: number
format: float
tuneTime:
type: number
format: float
priority:
type: integer
measurement:
type: integer
mode:
type: integer
rgbColor:
type: integer
title:
type: string
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer
channelMarker:
$ref: "http://swgserver:8081/api/swagger/include/ChannelMarker.yaml#/ChannelMarker"
rollupState:
$ref: "http://swgserver:8081/api/swagger/include/RollupState.yaml#/RollupState"
FreqScannerReport:
description: FreqScanner
properties:
channelSampleRate:
type: integer