Add channel power channel.

This commit is contained in:
srcejon 2024-03-13 15:45:14 +00:00
parent 2fdd92c85c
commit 0a19722ece
18 changed files with 3046 additions and 1 deletions

View File

@ -0,0 +1,64 @@
project(channelpower)
set(channelpower_SOURCES
channelpower.cpp
channelpowersettings.cpp
channelpowerbaseband.cpp
channelpowersink.cpp
channelpowerplugin.cpp
channelpowerwebapiadapter.cpp
)
set(channelpower_HEADERS
channelpower.h
channelpowersettings.h
channelpowerbaseband.h
channelpowersink.h
channelpowerplugin.h
channelpowerwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(channelpower_SOURCES
${channelpower_SOURCES}
channelpowergui.cpp
channelpowergui.ui
)
set(channelpower_HEADERS
${channelpower_HEADERS}
channelpowergui.h
)
set(TARGET_NAME channelpower)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME channelpowersrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${channelpower_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()

View File

@ -0,0 +1,531 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "channelpower.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGChannelReport.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "settings/serializable.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(ChannelPower::MsgConfigureChannelPower, Message)
const char * const ChannelPower::m_channelIdURI = "sdrangel.channel.channelpower";
const char * const ChannelPower::m_channelId = "ChannelPower";
ChannelPower::ChannelPower(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new ChannelPowerBaseband(this);
m_basebandSink->setChannel(this);
m_basebandSink->moveToThread(&m_thread);
applySettings(m_settings, QStringList(), true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&ChannelPower::networkManagerFinished
);
QObject::connect(
this,
&ChannelAPI::indexInDeviceSetChanged,
this,
&ChannelPower::handleIndexInDeviceSetChanged
);
}
ChannelPower::~ChannelPower()
{
qDebug("ChannelPower::~ChannelPower");
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&ChannelPower::networkManagerFinished
);
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_basebandSink->isRunning()) {
stop();
}
delete m_basebandSink;
}
void ChannelPower::setDeviceAPI(DeviceAPI *deviceAPI)
{
if (deviceAPI != m_deviceAPI)
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
m_deviceAPI = deviceAPI;
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
}
}
uint32_t ChannelPower::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void ChannelPower::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void ChannelPower::start()
{
qDebug("ChannelPower::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
ChannelPowerBaseband::MsgConfigureChannelPowerBaseband *msg = ChannelPowerBaseband::MsgConfigureChannelPowerBaseband::create(m_settings, QStringList(), true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void ChannelPower::stop()
{
qDebug("ChannelPower::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool ChannelPower::handleMessage(const Message& cmd)
{
if (MsgConfigureChannelPower::match(cmd))
{
MsgConfigureChannelPower& cfg = (MsgConfigureChannelPower&) cmd;
qDebug() << "ChannelPower::handleMessage: MsgConfigureChannelPower";
applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "ChannelPower::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(new DSPSignalNotification(notif));
}
return true;
}
else
{
return false;
}
}
void ChannelPower::setCenterFrequency(qint64 frequency)
{
ChannelPowerSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, {"inputFrequencyOffset"}, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureChannelPower *msgToGUI = MsgConfigureChannelPower::create(settings, {"inputFrequencyOffset"}, false);
m_guiMessageQueue->push(msgToGUI);
}
}
void ChannelPower::applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force)
{
qDebug() << "ChannelPower::applySettings:"
<< settings.getDebugString(settingsKeys, force)
<< " force: " << force;
if (settingsKeys.contains("streamIndex"))
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
m_settings.m_streamIndex = settings.m_streamIndex; // make sure ChannelAPI::getStreamIndex() is consistent
emit streamIndexChanged(settings.m_streamIndex);
}
}
ChannelPowerBaseband::MsgConfigureChannelPowerBaseband *msg = ChannelPowerBaseband::MsgConfigureChannelPowerBaseband::create(settings, settingsKeys, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) ||
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIDeviceIndex") ||
settingsKeys.contains("reverseAPIChannelIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}
QByteArray ChannelPower::serialize() const
{
return m_settings.serialize();
}
bool ChannelPower::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureChannelPower *msg = MsgConfigureChannelPower::create(m_settings, QStringList(), true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureChannelPower *msg = MsgConfigureChannelPower::create(m_settings, QStringList(), true);
m_inputMessageQueue.push(msg);
return false;
}
}
int ChannelPower::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setChannelPowerReport(new SWGSDRangel::SWGChannelPowerReport());
response.getChannelPowerReport()->init();
webapiFormatChannelReport(response);
return 200;
}
int ChannelPower::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setChannelPowerSettings(new SWGSDRangel::SWGChannelPowerSettings());
response.getChannelPowerSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int ChannelPower::webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage)
{
(void) errorMessage;
response.setIndex(m_settings.m_workspaceIndex);
return 200;
}
int ChannelPower::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
ChannelPowerSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureChannelPower *msg = MsgConfigureChannelPower::create(settings, channelSettingsKeys, force);
m_inputMessageQueue.push(msg);
qDebug("ChannelPower::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureChannelPower *msgToGUI = MsgConfigureChannelPower::create(settings, channelSettingsKeys, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void ChannelPower::webapiUpdateChannelSettings(
ChannelPowerSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getChannelPowerSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getChannelPowerSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("pulseThreshold")) {
settings.m_pulseThreshold = response.getChannelPowerSettings()->getPulseThreshold();
}
if (channelSettingsKeys.contains("averagePeriodUS")) {
settings.m_averagePeriodUS = response.getChannelPowerSettings()->getAveragePeriodUs();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getChannelPowerSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getChannelPowerSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getChannelPowerSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getChannelPowerSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getChannelPowerSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getChannelPowerSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getChannelPowerSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getChannelPowerSettings()->getReverseApiChannelIndex();
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getChannelPowerSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getChannelPowerSettings()->getRollupState());
}
}
void ChannelPower::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ChannelPowerSettings& settings)
{
response.getChannelPowerSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getChannelPowerSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getChannelPowerSettings()->setPulseThreshold(settings.m_pulseThreshold);
response.getChannelPowerSettings()->setAveragePeriodUs(settings.m_averagePeriodUS);
response.getChannelPowerSettings()->setRgbColor(settings.m_rgbColor);
if (response.getChannelPowerSettings()->getTitle()) {
*response.getChannelPowerSettings()->getTitle() = settings.m_title;
} else {
response.getChannelPowerSettings()->setTitle(new QString(settings.m_title));
}
response.getChannelPowerSettings()->setStreamIndex(settings.m_streamIndex);
response.getChannelPowerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getChannelPowerSettings()->getReverseApiAddress()) {
*response.getChannelPowerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getChannelPowerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getChannelPowerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getChannelPowerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getChannelPowerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_channelMarker)
{
if (response.getChannelPowerSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getChannelPowerSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getChannelPowerSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getChannelPowerSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getChannelPowerSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getChannelPowerSettings()->setRollupState(swgRollupState);
}
}
}
void ChannelPower::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
//if (!m_running) {
// return;
//}
double magAvg, magPulseAvg, magMaxPeak, magMinPeak;
getMagLevels(magAvg, magPulseAvg, magMaxPeak, magMinPeak);
response.getChannelPowerReport()->setChannelPowerDb(CalcDb::dbPower(magAvg));
response.getChannelPowerReport()->setChannelPowerMaxDb(CalcDb::dbPower(magMaxPeak));
response.getChannelPowerReport()->setChannelPowerMinDb(CalcDb::dbPower(magMinPeak));
response.getChannelPowerReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
void ChannelPower::webapiReverseSendSettings(const QList<QString>& channelSettingsKeys, const ChannelPowerSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void ChannelPower::webapiFormatChannelSettings(
const QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const ChannelPowerSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("ChannelPower"));
swgChannelSettings->setChannelPowerSettings(new SWGSDRangel::SWGChannelPowerSettings());
SWGSDRangel::SWGChannelPowerSettings *swgChannelPowerSettings = swgChannelSettings->getChannelPowerSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgChannelPowerSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgChannelPowerSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("pulseThreshold") || force) {
swgChannelPowerSettings->setPulseThreshold(settings.m_pulseThreshold);
}
if (channelSettingsKeys.contains("averagePeriodUS") || force) {
swgChannelPowerSettings->setAveragePeriodUs(settings.m_averagePeriodUS);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgChannelPowerSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgChannelPowerSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgChannelPowerSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgChannelPowerSettings->setChannelMarker(swgChannelMarker);
}
}
void ChannelPower::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "ChannelPower::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("ChannelPower::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
void ChannelPower::handleIndexInDeviceSetChanged(int index)
{
if (index < 0) {
return;
}
QString fifoLabel = QString("%1 [%2:%3]")
.arg(m_channelId)
.arg(m_deviceAPI->getDeviceSetIndex())
.arg(index);
m_basebandSink->setFifoLabel(fifoLabel);
}

View File

@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWER_H
#define INCLUDE_CHANNELPOWER_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QDateTime>
#include <QFile>
#include <QTextStream>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "channelpowerbaseband.h"
#include "channelpowersettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class ChannelPower : public BasebandSampleSink, public ChannelAPI {
public:
class MsgConfigureChannelPower : public Message {
MESSAGE_CLASS_DECLARATION
public:
const ChannelPowerSettings& getSettings() const { return m_settings; }
const QStringList& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
static MsgConfigureChannelPower* create(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force)
{
return new MsgConfigureChannelPower(settings, settingsKeys, force);
}
private:
ChannelPowerSettings m_settings;
QStringList m_settingsKeys;
bool m_force;
MsgConfigureChannelPower(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force)
{ }
};
ChannelPower(DeviceAPI *deviceAPI);
virtual ~ChannelPower();
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 int getStreamIndex() const { return -1; }
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 ChannelPowerSettings& settings);
static void webapiUpdateChannelSettings(
ChannelPowerSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
void getMagLevels(double& avg, double& pulseAvg, double &maxPeak, double &minPeak)
{
m_basebandSink->getMagLevels(avg, pulseAvg, maxPeak, minPeak);
}
void resetMagLevels() {
m_basebandSink->resetMagLevels();
}
/* void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}*/
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
ChannelPowerBaseband* m_basebandSink;
ChannelPowerSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
virtual bool handleMessage(const Message& cmd);
void applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force = false);
void webapiReverseSendSettings(const QList<QString>& channelSettingsKeys, const ChannelPowerSettings& settings, bool force);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiFormatChannelSettings(
const QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const ChannelPowerSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleIndexInDeviceSetChanged(int index);
};
#endif // INCLUDE_CHANNELPOWER_H

View File

@ -0,0 +1,184 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "channelpowerbaseband.h"
MESSAGE_CLASS_DEFINITION(ChannelPowerBaseband::MsgConfigureChannelPowerBaseband, Message)
ChannelPowerBaseband::ChannelPowerBaseband(ChannelPower *channelPower) :
m_sink(channelPower),
m_running(false)
{
qDebug("ChannelPowerBaseband::ChannelPowerBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
ChannelPowerBaseband::~ChannelPowerBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void ChannelPowerBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void ChannelPowerBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&ChannelPowerBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void ChannelPowerBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&ChannelPowerBaseband::handleData
);
m_running = false;
}
void ChannelPowerBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void ChannelPowerBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void ChannelPowerBaseband::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 ChannelPowerBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool ChannelPowerBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureChannelPowerBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureChannelPowerBaseband& cfg = (MsgConfigureChannelPowerBaseband&) cmd;
qDebug() << "ChannelPowerBaseband::handleMessage: MsgConfigureChannelPowerBaseband";
applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "ChannelPowerBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else
{
return false;
}
}
void ChannelPowerBaseband::applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force)
{
if ((settingsKeys.contains("inputFrequencyOffset") && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset))
|| force)
{
m_channelizer->setChannelization(m_channelizer->getBasebandSampleRate(), settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, settingsKeys, force);
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}
void ChannelPowerBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
int ChannelPowerBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}

View File

@ -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_CHANNELPOWERBASEBAND_H
#define INCLUDE_CHANNELPOWERBASEBAND_H
#include <QObject>
#include <QRecursiveMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "channelpowersink.h"
class DownChannelizer;
class ChannelAPI;
class ChannelPower;
class ChannelPowerBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureChannelPowerBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const ChannelPowerSettings& getSettings() const { return m_settings; }
const QStringList& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
static MsgConfigureChannelPowerBaseband* create(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force)
{
return new MsgConfigureChannelPowerBaseband(settings, settingsKeys, force);
}
private:
ChannelPowerSettings m_settings;
QStringList m_settingsKeys;
bool m_force;
MsgConfigureChannelPowerBaseband(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force)
{ }
};
ChannelPowerBaseband(ChannelPower *channelPower);
~ChannelPowerBaseband();
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 getMagLevels(double& avg, double& pulseAvg, double &maxPeak, double &minPeak) {
m_sink.getMagLevels(avg, pulseAvg, maxPeak, minPeak);
}
void resetMagLevels() {
m_sink.resetMagLevels();
}
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;
ChannelPowerSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
ChannelPowerSettings m_settings;
bool m_running;
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_CHANNELPOWERBASEBAND_H

View File

@ -0,0 +1,446 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "channelpowergui.h"
#include "device/deviceuiset.h"
#include "device/deviceapi.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_channelpowergui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
#include "dsp/dspengine.h"
#include "maincore.h"
#include "channelpower.h"
ChannelPowerGUI* ChannelPowerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
ChannelPowerGUI* gui = new ChannelPowerGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void ChannelPowerGUI::destroy()
{
delete this;
}
void ChannelPowerGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applyAllSettings();
}
QByteArray ChannelPowerGUI::serialize() const
{
return m_settings.serialize();
}
bool ChannelPowerGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applyAllSettings();
return true;
} else {
resetToDefaults();
return false;
}
}
bool ChannelPowerGUI::handleMessage(const Message& message)
{
if (ChannelPower::MsgConfigureChannelPower::match(message))
{
qDebug("ChannelPowerGUI::handleMessage: ChannelPower::MsgConfigureChannelPower");
const ChannelPower::MsgConfigureChannelPower& cfg = (ChannelPower::MsgConfigureChannelPower&) 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(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
ui->rfBW->setValueRange(false, ceil(log10(m_basebandSampleRate)), 0, m_basebandSampleRate);
updateAbsoluteCenterFrequency();
return true;
}
return false;
}
void ChannelPowerGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void ChannelPowerGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySetting("inputFrequencyOffset");
}
void ChannelPowerGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void ChannelPowerGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySetting("inputFrequencyOffset");
}
void ChannelPowerGUI::on_rfBW_changed(qint64 value)
{
m_channelMarker.setBandwidth(value);
m_settings.m_rfBandwidth = value;
applySetting("rfBandwidth");
}
void ChannelPowerGUI::on_pulseTH_valueChanged(int value)
{
m_settings.m_pulseThreshold = (float)value;
ui->pulseTHText->setText(QString::number(value));
applySetting("pulseThreshold");
}
const QStringList ChannelPowerGUI::m_averagePeriodTexts = {
"10us", "100us", "1ms", "10ms", "100ms", "1s", "10s"
};
void ChannelPowerGUI::on_averagePeriod_valueChanged(int value)
{
m_settings.m_averagePeriodUS = (int)std::pow(10.0f, (float)value);
ui->averagePeriodText->setText(m_averagePeriodTexts[value-1]);
applySetting("averagePeriodUS");
}
void ChannelPowerGUI::on_clearChannelPower_clicked()
{
m_channelPower->resetMagLevels();
}
void ChannelPowerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySetting("rollupState");
}
void ChannelPowerGUI::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_channelPower->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
}
dialog.move(p);
new DialogPositioner(&dialog, true);
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);
QStringList settingsKeys({
"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();
}
ChannelPowerGUI::ChannelPowerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::ChannelPowerGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_tickCount(0)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/channelpower/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_channelPower = reinterpret_cast<ChannelPower*>(rxChannel);
m_channelPower->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->rfBW->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->rfBW->setValueRange(false, 7, -9999999, 9999999);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::yellow);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle("Channel Power");
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()));
displaySettings();
makeUIConnections();
applyAllSettings();
DialPopup::addPopupsToChildDials(this);
m_resizer.enableChildMouseTracking();
}
ChannelPowerGUI::~ChannelPowerGUI()
{
disconnect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
delete ui;
}
void ChannelPowerGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void ChannelPowerGUI::applySetting(const QString& settingsKey)
{
applySettings({settingsKey});
}
void ChannelPowerGUI::applySettings(const QStringList& settingsKeys, bool force)
{
m_settingsKeys.append(settingsKeys);
if (m_doApplySettings)
{
ChannelPower::MsgConfigureChannelPower* message = ChannelPower::MsgConfigureChannelPower::create(m_settings, m_settingsKeys, force);
m_channelPower->getInputMessageQueue()->push(message);
m_settingsKeys.clear();
}
}
void ChannelPowerGUI::applyAllSettings()
{
applySettings(QStringList(), true);
}
void ChannelPowerGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
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_channelMarker.getCenterFrequency());
ui->rfBW->setValue(m_settings.m_rfBandwidth);
ui->pulseTH->setValue(m_settings.m_pulseThreshold);
ui->pulseTHText->setText(QString::number((int)m_settings.m_pulseThreshold));
int value = (int)std::log10(m_settings.m_averagePeriodUS);
ui->averagePeriod->setValue(value);
ui->averagePeriodText->setText(m_averagePeriodTexts[value-1]);
ui->averagePeriod->setMinimum(std::max(1, static_cast<int> (m_averagePeriodTexts.size()) - value));
updateIndexLabel();
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
}
void ChannelPowerGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void ChannelPowerGUI::enterEvent(EnterEventType* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void ChannelPowerGUI::tick()
{
// Get power measurements
double magAvg, magPulseAvg, magMaxPeak, magMinPeak;
m_channelPower->getMagLevels(magAvg, magPulseAvg, magMaxPeak, magMinPeak);
double powDbAvg, powDbPulseAvg, powDbMaxPeak, powDbMinPeak, powDbPathLoss;
powDbAvg = std::numeric_limits<double>::quiet_NaN();
powDbPulseAvg = std::numeric_limits<double>::quiet_NaN();
powDbMaxPeak = std::numeric_limits<double>::quiet_NaN();
powDbMinPeak = std::numeric_limits<double>::quiet_NaN();
powDbPathLoss = std::numeric_limits<double>::quiet_NaN();
if (!std::isnan(magAvg))
{
powDbAvg = CalcDb::dbPower(magAvg * magAvg);
if (m_tickCount % 4 == 0) {
ui->average->setText(QString::number(powDbAvg, 'f', 1));
}
}
else
{
ui->average->setText("");
}
if (!std::isnan(magPulseAvg))
{
powDbPulseAvg = CalcDb::dbPower(magPulseAvg * magPulseAvg);
if (m_tickCount % 4 == 0) {
ui->pulseAverage->setText(QString::number(powDbPulseAvg, 'f', 1));
}
}
else
{
ui->pulseAverage->setText("");
}
if (magMaxPeak != -std::numeric_limits<double>::max())
{
powDbMaxPeak = CalcDb::dbPower(magMaxPeak * magMaxPeak);
if (m_tickCount % 4 == 0) {
ui->maxPeak->setText(QString::number(powDbMaxPeak, 'f', 1));
}
}
else
{
ui->maxPeak->setText("");
}
if (magMinPeak != std::numeric_limits<double>::max())
{
powDbMinPeak = CalcDb::dbPower(magMinPeak * magMinPeak);
if (m_tickCount % 4 == 0) {
ui->minPeak->setText(QString::number(powDbMinPeak, 'f', 1));
}
}
else
{
ui->minPeak->setText("");
}
m_tickCount++;
}
void ChannelPowerGUI::on_clearMeasurements_clicked()
{
m_channelPower->resetMagLevels();
}
void ChannelPowerGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ChannelPowerGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &ValueDialZ::changed, this, &ChannelPowerGUI::on_rfBW_changed);
QObject::connect(ui->pulseTH, QOverload<int>::of(&QDial::valueChanged), this, &ChannelPowerGUI::on_pulseTH_valueChanged);
QObject::connect(ui->averagePeriod, QOverload<int>::of(&QDial::valueChanged), this, &ChannelPowerGUI::on_averagePeriod_valueChanged);
QObject::connect(ui->clearChannelPower, &QPushButton::clicked, this, &ChannelPowerGUI::on_clearMeasurements_clicked);
}
void ChannelPowerGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}

View File

@ -0,0 +1,113 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWERGUI_H
#define INCLUDE_CHANNELPOWERGUI_H
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "channelpowersettings.h"
#include "channelpower.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class ChannelPower;
class ChannelPowerGUI;
namespace Ui {
class ChannelPowerGUI;
}
class ChannelPowerGUI : public ChannelGUI {
Q_OBJECT
public:
static ChannelPowerGUI* 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::ChannelPowerGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
ChannelPowerSettings m_settings;
QStringList m_settingsKeys;
qint64 m_deviceCenterFrequency;
int m_basebandSampleRate;
bool m_doApplySettings;
ChannelPower* m_channelPower;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
const static QStringList m_averagePeriodTexts;
explicit ChannelPowerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~ChannelPowerGUI();
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 on_clearMeasurements_clicked();
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_changed(qint64 value);
void on_clearChannelPower_clicked();
void on_pulseTH_valueChanged(int value);
void on_averagePeriod_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_CHANNELPOWERGUI_H

View File

@ -0,0 +1,607 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChannelPowerGUI</class>
<widget class="RollupContents" name="ChannelPowerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>395</width>
<height>116</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</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>Channel Power</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>61</height>
</rect>
</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="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>Channel center offset from device center frequency 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>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="rfBW" 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 in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWUnits">
<property name="text">
<string>Hz </string>
</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>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="timingSettingsLayout">
<item>
<widget class="QLabel" name="averagePeriodLabel">
<property name="text">
<string>T&lt;sub&gt;AVG&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="averagePeriod">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Time period to average measurement over</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>7</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="averagePeriodText">
<property name="minimumSize">
<size>
<width>44</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100ms</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="pulseTHLine">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pulseTHLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>TH&lt;sub&gt;P&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="pulseTH">
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Pulse threshold power in dB</string>
</property>
<property name="minimum">
<number>-120</number>
</property>
<property name="maximum">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pulseTHText">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>28</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>-120</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pulseTHUnits">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>dB</string>
</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>
</widget>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>70</y>
<width>391</width>
<height>41</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Power</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<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="powMeasurementsLayout">
<item>
<widget class="QLabel" name="averageLabel">
<property name="text">
<string>Avg</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="average">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Average power in dB</string>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="averageUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="maxLabel">
<property name="text">
<string>Max</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="maxPeak">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Maximum peak power in dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="maxPeakUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="minPeakLabel">
<property name="text">
<string>Min</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="minPeak">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Minimum peak power in dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="minPeakUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_12">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pulseAverageLabel">
<property name="text">
<string>Pulse</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="pulseAverage">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Pulse average power in dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pulseAverageUnits">
<property name="text">
<string>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="QPushButton" name="clearChannelPower">
<property name="toolTip">
<string>Clear data</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<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>
<tabstop>averagePeriod</tabstop>
<tabstop>pulseTH</tabstop>
<tabstop>average</tabstop>
<tabstop>maxPeak</tabstop>
<tabstop>minPeak</tabstop>
<tabstop>pulseAverage</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="../demodadsb/icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "channelpowergui.h"
#endif
#include "channelpower.h"
#include "channelpowerwebapiadapter.h"
#include "channelpowerplugin.h"
const PluginDescriptor ChannelPowerPlugin::m_pluginDescriptor = {
ChannelPower::m_channelId,
QStringLiteral("Channel Power"),
QStringLiteral("7.19.1"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
ChannelPowerPlugin::ChannelPowerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& ChannelPowerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void ChannelPowerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(ChannelPower::m_channelIdURI, ChannelPower::m_channelId, this);
}
void ChannelPowerPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
ChannelPower *instance = new ChannelPower(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* ChannelPowerPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* ChannelPowerPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return ChannelPowerGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* ChannelPowerPlugin::createChannelWebAPIAdapter() const
{
return new ChannelPowerWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWERPLUGIN_H
#define INCLUDE_CHANNELPOWERPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class ChannelPowerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.channelpower")
public:
explicit ChannelPowerPlugin(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_CHANNELPOWERPLUGIN_H

View File

@ -0,0 +1,208 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "channelpowersettings.h"
ChannelPowerSettings::ChannelPowerSettings() :
m_channelMarker(nullptr),
m_rollupState(nullptr)
{
resetToDefaults();
}
void ChannelPowerSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 16000.0f;
m_pulseThreshold= -50.0f;
m_averagePeriodUS = 100000;
m_rgbColor = QColor(102, 40, 220).rgb();
m_title = "Channel Power";
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 ChannelPowerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeFloat(2, m_rfBandwidth);
s.writeFloat(3, m_pulseThreshold);
s.writeS32(4, m_averagePeriodUS);
s.writeU32(21, m_rgbColor);
s.writeString(22, m_title);
if (m_channelMarker) {
s.writeBlob(23, m_channelMarker->serialize());
}
s.writeS32(24, m_streamIndex);
s.writeBool(25, m_useReverseAPI);
s.writeString(26, m_reverseAPIAddress);
s.writeU32(27, m_reverseAPIPort);
s.writeU32(28, m_reverseAPIDeviceIndex);
s.writeU32(29, m_reverseAPIChannelIndex);
if (m_rollupState) {
s.writeBlob(30, m_rollupState->serialize());
}
s.writeS32(32, m_workspaceIndex);
s.writeBlob(33, m_geometryBytes);
s.writeBool(34, m_hidden);
return s.final();
}
bool ChannelPowerSettings::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.readFloat(2, &m_rfBandwidth, 16000.0f);
d.readFloat(3, &m_pulseThreshold, 50.0f);
d.readS32(4, &m_averagePeriodUS, 100000);
d.readU32(21, &m_rgbColor, QColor(102, 40, 220).rgb());
d.readString(22, &m_title, "Channel Power");
if (m_channelMarker)
{
d.readBlob(23, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readS32(24, &m_streamIndex, 0);
d.readBool(25, &m_useReverseAPI, false);
d.readString(26, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(27, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(28, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(29, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(30, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(32, &m_workspaceIndex, 0);
d.readBlob(33, &m_geometryBytes);
d.readBool(34, &m_hidden, false);
return true;
}
else
{
resetToDefaults();
return false;
}
}
void ChannelPowerSettings::applySettings(const QStringList& settingsKeys, const ChannelPowerSettings& settings)
{
if (settingsKeys.contains("inputFrequencyOffset")) {
m_inputFrequencyOffset = settings.m_inputFrequencyOffset;
}
if (settingsKeys.contains("rfBandwidth")) {
m_rfBandwidth = settings.m_rfBandwidth;
}
if (settingsKeys.contains("pulseThreshold")) {
m_pulseThreshold = settings.m_pulseThreshold;
}
if (settingsKeys.contains("averagePeriodUS")) {
m_averagePeriodUS = settings.m_averagePeriodUS;
}
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;
}
}
QString ChannelPowerSettings::getDebugString(const QStringList& settingsKeys, bool force) const
{
std::ostringstream ostr;
if (settingsKeys.contains("inputFrequencyOffset") || force) {
ostr << " m_inputFrequencyOffset: " << m_inputFrequencyOffset;
}
if (settingsKeys.contains("rfBandwidth") || force) {
ostr << " m_rfBandwidth: " << m_rfBandwidth;
}
if (settingsKeys.contains("pulseThreshold") || force) {
ostr << " m_pulseThreshold: " << m_pulseThreshold;
}
if (settingsKeys.contains("averagePeriodUS") || force) {
ostr << " m_averagePeriodUS: " << m_averagePeriodUS;
}
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("everseAPIDeviceIndex") || force) {
ostr << " m_reverseAPIDeviceIndex: " << m_reverseAPIDeviceIndex;
}
return QString(ostr.str().c_str());
}

View File

@ -0,0 +1,62 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWERSETTINGS_H
#define INCLUDE_CHANNELPOWERSETTINGS_H
#include <QByteArray>
#include <QString>
#include "dsp/dsptypes.h"
class Serializable;
struct ChannelPowerSettings
{
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth;
float m_pulseThreshold;
int m_averagePeriodUS;
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_scopeGUI;
Serializable *m_rollupState;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
ChannelPowerSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void applySettings(const QStringList& settingsKeys, const ChannelPowerSettings& settings);
QString getDebugString(const QStringList& settingsKeys, bool force=false) const;
};
#endif /* INCLUDE_CHANNELPOWERSETTINGS_H */

View File

@ -0,0 +1,154 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "channelpowersink.h"
ChannelPowerSink::ChannelPowerSink(ChannelPower *channelPower) :
m_channelPower(channelPower),
m_channelSampleRate(10000),
m_channelFrequencyOffset(0),
m_lowpassFFT(nullptr),
m_lowpassBufferIdx(0)
{
resetMagLevels();
applySettings(m_settings, QStringList(), true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
ChannelPowerSink::~ChannelPowerSink()
{
}
void ChannelPowerSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
QMutexLocker mutexLocker(&m_mutex);
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
processOneSample(c);
}
}
void ChannelPowerSink::processOneSample(Complex &ci)
{
// Low pass filter to desired channel bandwidth
fftfilt::cmplx *filtered;
int nOut = m_lowpassFFT->runFilt(ci, &filtered);
if (nOut > 0)
{
memcpy(m_lowpassBuffer, filtered, nOut * sizeof(Complex));
m_lowpassBufferIdx = 0;
}
Complex c = m_lowpassBuffer[m_lowpassBufferIdx++];
// Calculate power
Real re = c.real() / SDR_RX_SCALEF;
Real im = c.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
// Although computationally more expensive to take the square root here,
// it possibly reduces problems of accumulating numbers
// that may differ significantly in magnitude, for long averages
double mag = sqrt((double)magsq);
m_magSum += mag;
if (mag > m_pulseThresholdLinear)
{
m_magPulseSum += mag;
m_magPulseCount++;
if (m_magPulseCount >= m_averageCnt)
{
m_magPulseAvg = m_magPulseSum / m_magPulseCount;
m_magPulseSum = 0.0;
m_magPulseCount = 0;
}
}
if (mag > m_magMaxPeak) {
m_magMaxPeak = mag;
}
if (mag < m_magMinPeak) {
m_magMinPeak = mag;
}
m_magCount++;
if (m_magCount >= m_averageCnt)
{
m_magAvg = m_magSum / m_magCount;
m_magSum = 0.0;
m_magCount = 0;
}
}
void ChannelPowerSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "ChannelPowerSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
delete m_lowpassFFT;
m_lowpassFFT = new fftfilt(0, m_settings.m_rfBandwidth / 2.0f / m_channelSampleRate, m_lowpassLen);
m_lowpassBufferIdx = 0;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_averageCnt = (int)((m_settings.m_averagePeriodUS * (qint64)m_channelSampleRate / 1e6));
}
void ChannelPowerSink::applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force)
{
qDebug() << "ChannelPowerSink::applySettings:" << " force: " << force << settings.getDebugString(settingsKeys, force);
if ((settingsKeys.contains("rfBandwidth") && (settings.m_rfBandwidth != m_settings.m_rfBandwidth)) || force)
{
delete m_lowpassFFT;
m_lowpassFFT = new fftfilt(0, settings.m_rfBandwidth / 2.0f / m_channelSampleRate, m_lowpassLen);
m_lowpassBufferIdx = 0;
}
if (settingsKeys.contains("averagePeriodUS") || force) {
m_averageCnt = (int)((settings.m_averagePeriodUS * (qint64)m_channelSampleRate / 1e6));
}
if (settingsKeys.contains("pulseThreshold") || force) {
m_pulseThresholdLinear = std::pow(10.0, settings.m_pulseThreshold / 20.0);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}

View File

@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWERSINK_H
#define INCLUDE_CHANNELPOWERSINK_H
#include <QMutex>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/fftfilt.h"
#include "channelpowersettings.h"
class ChannelAPI;
class ChannelPower;
class ChannelPowerSink : public ChannelSampleSink {
public:
ChannelPowerSink(ChannelPower *channelPower);
~ChannelPowerSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const ChannelPowerSettings& settings, const QStringList& settingsKeys, bool force = false);
void setChannel(ChannelAPI *channel) { m_channel = channel; }
void getMagLevels(double& avg, double& pulseAvg, double &maxPeak, double &minPeak)
{
QMutexLocker mutexLocker(&m_mutex);
avg = m_magAvg;
pulseAvg = m_magPulseAvg;
maxPeak = m_magMaxPeak;
minPeak = m_magMinPeak;
}
void resetMagLevels()
{
QMutexLocker mutexLocker(&m_mutex);
m_magSum = 0.0;
m_magCount = 0;
m_magAvg = std::numeric_limits<double>::quiet_NaN();
m_magPulseSum = 0.0;
m_magPulseCount = 0;
m_magPulseAvg = std::numeric_limits<double>::quiet_NaN();
m_magMinPeak = std::numeric_limits<double>::max();
m_magMaxPeak = -std::numeric_limits<double>::max();
}
private:
ChannelPower *m_channelPower;
ChannelPowerSettings m_settings;
ChannelAPI *m_channel;
int m_channelSampleRate;
int m_channelFrequencyOffset;
NCO m_nco;
fftfilt *m_lowpassFFT;
static constexpr int m_lowpassLen = 2048;
Complex m_lowpassBuffer[m_lowpassLen];
int m_lowpassBufferIdx;
double m_magSum;
double m_magCount;
double m_magAvg;
double m_magPulseSum;
double m_magPulseCount;
double m_magPulseAvg;
double m_magMaxPeak;
double m_magMinPeak;
int m_averageCnt;
double m_pulseThresholdLinear;
QMutex m_mutex;
void processOneSample(Complex &ci);
};
#endif // INCLUDE_CHANNELPOWERSINK_H

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "channelpower.h"
#include "channelpowerwebapiadapter.h"
ChannelPowerWebAPIAdapter::ChannelPowerWebAPIAdapter()
{}
ChannelPowerWebAPIAdapter::~ChannelPowerWebAPIAdapter()
{}
int ChannelPowerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setChannelPowerSettings(new SWGSDRangel::SWGChannelPowerSettings());
response.getChannelPowerSettings()->init();
ChannelPower::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int ChannelPowerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
ChannelPower::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_CHANNELPOWER_WEBAPIADAPTER_H
#define INCLUDE_CHANNELPOWER_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "channelpowersettings.h"
/**
* Standalone API adapter only for the settings
*/
class ChannelPowerWebAPIAdapter : public ChannelWebAPIAdapter {
public:
ChannelPowerWebAPIAdapter();
virtual ~ChannelPowerWebAPIAdapter();
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:
ChannelPowerSettings m_settings;
};
#endif // INCLUDE_CHANNELPOWER_WEBAPIADAPTER_H

View File

@ -0,0 +1,47 @@
<h1>Channel Power Plugin</h1>
<h2>Introduction</h2>
This plugin can be used to calculate channel power. Channel power is measured as average power, maximum peak power, minimum peak power as well as pulse average power (i.e. average power above a threshold) over a specified bandwidth.
<h2>Interface</h2>
The top and bottom bars of the channel window are described [here](../../../sdrgui/channel/readme.md)
![Channel power plugin GUI](../../../doc/img/ChannelPower_plugin_settings.png)
<h3>1: Frequency shift from center frequency of reception</h3>
Use the wheels to adjust the channel center frequency as a shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
<h3>2: BW - Channel Bandwidth</h3>
Bandwidth in Hz of the channel for which power is to be measured.
<h3>3: Tavg - Average Time</h3>
Time period overwhich the channel power is averaged. Values range from 10us to 10s in powers of 10. The available values depend upon the sample rate.
<h3>4: THp - Pulse Threshold</h3>
The pulse threshold sets the power in dB for which the channel power needs to exceed, in order to be included in the pulse average power measurement.
<h3>5: Avg - Average Power</h3>
Displays the most recent average power measurement in dB.
<h3>6: Max - Max Peak Power</h3>
Displays the maximum instantaneous peak power measurement in dB.
<h3>7: Min - Min Peak Power</h3>
Displays the minimum instantaneous peak power measurement in dB.
<h3>8: Pulse - Pulse Average Power</h3>
Displays the most recent pulse average power measurement in dB.
<h3>9: Clear Data</h3>
Clears current measurements (min and max values are reset).

View File

@ -1,5 +1,5 @@
ChannelPowerSettings:
description: ChannelDemod
description: ChannelPower
properties:
inputFrequencyOffset:
type: integer
@ -52,5 +52,9 @@ ChannelPowerReport:
description: min power received in channel (dB)
type: number
format: float
channelPowerPulseDB:
description: pulse power received in channel (dB)
type: number
format: float
channelSampleRate:
type: integer