AFC plugin: brute force copy from Simple PTT plugin

This commit is contained in:
f4exb 2020-10-16 19:20:55 +02:00
parent 3e004a257b
commit 0cd512ce4a
18 changed files with 2287 additions and 0 deletions

View File

@ -1,4 +1,5 @@
project(feature)
add_subdirectory(afc)
add_subdirectory(rigctlserver)
add_subdirectory(simpleptt)

View File

@ -0,0 +1,58 @@
project(afc)
set(afc_SOURCES
afc.cpp
afcsettings.cpp
afcplugin.cpp
afcworker.cpp
afcreport.cpp
afcwebapiadapter.cpp
)
set(afc_HEADERS
afc.h
afcsettings.h
afcplugin.h
afcworker.h
afcreport.h
afcwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(afc_SOURCES
${afc_SOURCES}
afcgui.cpp
afcgui.ui
)
set(afc_HEADERS
${afc_HEADERS}
afcgui.h
)
set(TARGET_NAME featureafc)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featureafcsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${afc_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

427
plugins/feature/afc/afc.cpp Normal file
View File

@ -0,0 +1,427 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "SWGAFCReport.h"
#include "SWGDeviceState.h"
#include "dsp/dspengine.h"
#include "afcworker.h"
#include "afc.h"
MESSAGE_CLASS_DEFINITION(AFC::MsgConfigureAFC, Message)
MESSAGE_CLASS_DEFINITION(AFC::MsgPTT, Message)
MESSAGE_CLASS_DEFINITION(AFC::MsgStartStop, Message)
const QString AFC::m_featureIdURI = "sdrangel.feature.afc";
const QString AFC::m_featureId = "AFC";
AFC::AFC(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface),
m_ptt(false)
{
setObjectName(m_featureId);
m_worker = new AFCWorker(webAPIAdapterInterface);
m_state = StIdle;
m_errorMessage = "AFC error";
}
AFC::~AFC()
{
if (m_worker->isRunning()) {
stop();
}
delete m_worker;
}
void AFC::start()
{
qDebug("AFC::start");
m_worker->reset();
m_worker->setMessageQueueToGUI(getMessageQueueToGUI());
bool ok = m_worker->startWork();
m_state = ok ? StRunning : StError;
m_thread.start();
AFCWorker::MsgConfigureAFCWorker *msg = AFCWorker::MsgConfigureAFCWorker::create(m_settings, true);
m_worker->getInputMessageQueue()->push(msg);
}
void AFC::stop()
{
qDebug("AFC::stop");
m_worker->stopWork();
m_state = StIdle;
m_thread.quit();
m_thread.wait();
}
bool AFC::handleMessage(const Message& cmd)
{
if (MsgConfigureAFC::match(cmd))
{
MsgConfigureAFC& cfg = (MsgConfigureAFC&) cmd;
qDebug() << "AFC::handleMessage: MsgConfigureAFC";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgPTT::match(cmd))
{
MsgPTT& cfg = (MsgPTT&) cmd;
m_ptt = cfg.getTx();
qDebug() << "AFC::handleMessage: MsgPTT: tx:" << m_ptt;
AFCWorker::MsgPTT *msg = AFCWorker::MsgPTT::create(m_ptt);
m_worker->getInputMessageQueue()->push(msg);
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
qDebug() << "AFC::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
if (cfg.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else
{
return false;
}
}
QByteArray AFC::serialize() const
{
return m_settings.serialize();
}
bool AFC::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureAFC *msg = MsgConfigureAFC::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureAFC *msg = MsgConfigureAFC::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void AFC::applySettings(const AFCSettings& settings, bool force)
{
qDebug() << "AFC::applySettings:"
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_rxDeviceSetIndex: " << settings.m_rxDeviceSetIndex
<< " m_txDeviceSetIndex: " << settings.m_txDeviceSetIndex
<< " m_rx2TxDelayMs: " << settings.m_rx2TxDelayMs
<< " m_tx2RxDelayMs: " << settings.m_tx2RxDelayMs
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
if ((m_settings.m_rxDeviceSetIndex != settings.m_rxDeviceSetIndex) || force) {
reverseAPIKeys.append("rxDeviceSetIndex");
}
if ((m_settings.m_txDeviceSetIndex != settings.m_txDeviceSetIndex) || force) {
reverseAPIKeys.append("txDeviceSetIndex");
}
if ((m_settings.m_rx2TxDelayMs != settings.m_rx2TxDelayMs) || force) {
reverseAPIKeys.append("rx2TxDelayMs");
}
if ((m_settings.m_tx2RxDelayMs != settings.m_tx2RxDelayMs) || force) {
reverseAPIKeys.append("tx2RxDelayMs");
}
AFCWorker::MsgConfigureAFCWorker *msg = AFCWorker::MsgConfigureAFCWorker::create(
settings, force
);
m_worker->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) ||
(m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
}
int AFC::webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
getFeatureStateStr(*response.getState());
MsgStartStop *msg = MsgStartStop::create(run);
getInputMessageQueue()->push(msg);
return 202;
}
int AFC::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAfcSettings(new SWGSDRangel::SWGAFCSettings());
response.getAfcSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int AFC::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
AFCSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureAFC *msg = MsgConfigureAFC::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("AFC::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAFC *msgToGUI = MsgConfigureAFC::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
int AFC::webapiReportGet(
SWGSDRangel::SWGFeatureReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAfcReport(new SWGSDRangel::SWGAFCReport());
response.getAfcReport()->init();
webapiFormatFeatureReport(response);
return 200;
}
int AFC::webapiActionsPost(
const QStringList& featureActionsKeys,
SWGSDRangel::SWGFeatureActions& query,
QString& errorMessage)
{
SWGSDRangel::SWGAFCActions *swgAFCActions = query.getAfcActions();
if (swgAFCActions)
{
if (featureActionsKeys.contains("ptt"))
{
bool ptt = swgAFCActions->getPtt() != 0;
MsgPTT *msg = MsgPTT::create(ptt);
getInputMessageQueue()->push(msg);
if (getMessageQueueToGUI())
{
MsgPTT *msgToGUI = MsgPTT::create(ptt);
getMessageQueueToGUI()->push(msgToGUI);
}
}
return 202;
}
else
{
errorMessage = "Missing AFCActions in query";
return 400;
}
}
void AFC::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const AFCSettings& settings)
{
if (response.getAfcSettings()->getTitle()) {
*response.getAfcSettings()->getTitle() = settings.m_title;
} else {
response.getAfcSettings()->setTitle(new QString(settings.m_title));
}
response.getAfcSettings()->setRgbColor(settings.m_rgbColor);
response.getAfcSettings()->setRxDeviceSetIndex(settings.m_rxDeviceSetIndex);
response.getAfcSettings()->setTxDeviceSetIndex(settings.m_txDeviceSetIndex);
response.getAfcSettings()->setRx2TxDelayMs(settings.m_rx2TxDelayMs);
response.getAfcSettings()->setTx2RxDelayMs(settings.m_tx2RxDelayMs);
response.getAfcSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAfcSettings()->getReverseApiAddress()) {
*response.getAfcSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAfcSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAfcSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getAfcSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
response.getAfcSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
}
void AFC::webapiUpdateFeatureSettings(
AFCSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getAfcSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAfcSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("rxDeviceSetIndex")) {
settings.m_rxDeviceSetIndex = response.getAfcSettings()->getRxDeviceSetIndex();
}
if (featureSettingsKeys.contains("txDeviceSetIndex")) {
settings.m_txDeviceSetIndex = response.getAfcSettings()->getTxDeviceSetIndex();
}
if (featureSettingsKeys.contains("rx2TxDelayMs")) {
settings.m_rx2TxDelayMs = response.getAfcSettings()->getRx2TxDelayMs();
}
if (featureSettingsKeys.contains("tx2RxDelayMs")) {
settings.m_tx2RxDelayMs = response.getAfcSettings()->getTx2RxDelayMs();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAfcSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAfcSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAfcSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getAfcSettings()->getReverseApiDeviceIndex();
}
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIFeatureIndex = response.getAfcSettings()->getReverseApiChannelIndex();
}
}
void AFC::webapiFormatFeatureReport(SWGSDRangel::SWGFeatureReport& response)
{
response.getAfcReport()->setPtt(m_ptt ? 1 : 0);
}
void AFC::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AFCSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("AFC"));
swgFeatureSettings->setAfcSettings(new SWGSDRangel::SWGAFCSettings());
SWGSDRangel::SWGAFCSettings *swgAFCSettings = swgFeatureSettings->getAfcSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("title") || force) {
swgAFCSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAFCSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("rxDeviceSetIndex") || force) {
swgAFCSettings->setRxDeviceSetIndex(settings.m_rxDeviceSetIndex);
}
if (channelSettingsKeys.contains("txDeviceSetIndex") || force) {
swgAFCSettings->setTxDeviceSetIndex(settings.m_txDeviceSetIndex);
}
if (channelSettingsKeys.contains("rx2TxDelayMs") || force) {
swgAFCSettings->setRx2TxDelayMs(settings.m_rx2TxDelayMs);
}
if (channelSettingsKeys.contains("tx2RxDelayMs") || force) {
swgAFCSettings->setTx2RxDelayMs(settings.m_tx2RxDelayMs);
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIFeatureSetIndex)
.arg(settings.m_reverseAPIFeatureIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgFeatureSettings->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 swgFeatureSettings;
}
void AFC::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AFC::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AFC::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

168
plugins/feature/afc/afc.h Normal file
View File

@ -0,0 +1,168 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFC_H_
#define INCLUDE_FEATURE_AFC_H_
#include <QThread>
#include <QNetworkRequest>
#include "feature/feature.h"
#include "util/message.h"
#include "afcsettings.h"
class WebAPIAdapterInterface;
class AFCWorker;
class QNetworkAccessManager;
class QNetworkReply;
namespace SWGSDRangel {
class SWGDeviceState;
}
class AFC : public Feature
{
Q_OBJECT
public:
class MsgConfigureAFC : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AFCSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAFC* create(const AFCSettings& settings, bool force) {
return new MsgConfigureAFC(settings, force);
}
private:
AFCSettings m_settings;
bool m_force;
MsgConfigureAFC(const AFCSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgPTT : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getTx() const { return m_tx; }
static MsgPTT* create(bool tx) {
return new MsgPTT(tx);
}
private:
bool m_tx;
MsgPTT(bool tx) :
Message(),
m_tx(tx)
{ }
};
class MsgStartStop : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getStartStop() const { return m_startStop; }
static MsgStartStop* create(bool startStop) {
return new MsgStartStop(startStop);
}
protected:
bool m_startStop;
MsgStartStop(bool startStop) :
Message(),
m_startStop(startStop)
{ }
};
AFC(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~AFC();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual const QString& getURI() const { return m_featureIdURI; }
virtual void getIdentifier(QString& id) const { id = m_featureId; }
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGFeatureReport& response,
QString& errorMessage);
virtual int webapiActionsPost(
const QStringList& featureActionsKeys,
SWGSDRangel::SWGFeatureActions& query,
QString& errorMessage);
static void webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const AFCSettings& settings);
static void webapiUpdateFeatureSettings(
AFCSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
static const QString m_featureIdURI;
static const QString m_featureId;
private:
QThread m_thread;
AFCWorker *m_worker;
AFCSettings m_settings;
bool m_ptt;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void start();
void stop();
void applySettings(const AFCSettings& settings, bool force = false);
void webapiFormatFeatureReport(SWGSDRangel::SWGFeatureReport& response);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const AFCSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_FEATURE_AFC_H_

View File

@ -0,0 +1,397 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 <QMessageBox>
#include "feature/featureuiset.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "device/deviceset.h"
#include "maincore.h"
#include "ui_afcgui.h"
#include "afcreport.h"
#include "afc.h"
#include "afcgui.h"
AFCGUI* AFCGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
AFCGUI* gui = new AFCGUI(pluginAPI, featureUISet, feature);
return gui;
}
void AFCGUI::destroy()
{
delete this;
}
void AFCGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray AFCGUI::serialize() const
{
return m_settings.serialize();
}
bool AFCGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool AFCGUI::handleMessage(const Message& message)
{
if (AFC::MsgConfigureAFC::match(message))
{
qDebug("AFCGUI::handleMessage: AFC::MsgConfigureAFC");
const AFC::MsgConfigureAFC& cfg = (AFC::MsgConfigureAFC&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (AFCReport::MsgRadioState::match(message))
{
qDebug("AFCGUI::handleMessage: AFCReport::MsgRadioState");
const AFCReport::MsgRadioState& cfg = (AFCReport::MsgRadioState&) message;
AFCReport::RadioState state = cfg.getState();
ui->statusIndicator->setStyleSheet("QLabel { background-color: " +
m_statusColors[(int) state] + "; border-radius: 12px; }");
ui->statusIndicator->setToolTip(m_statusTooltips[(int) state]);
return true;
}
else if (AFC::MsgPTT::match(message))
{
qDebug("AFCGUI::handleMessage: AFC::MsgPTT");
const AFC::MsgPTT& cfg = (AFC::MsgPTT&) message;
bool ptt = cfg.getTx();
blockApplySettings(true);
ui->ptt->setChecked(ptt);
blockApplySettings(false);
return true;
}
return false;
}
void AFCGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void AFCGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
AFCGUI::AFCGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::AFCGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_lastFeatureState(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
setChannelWidget(false);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_simplePTT = reinterpret_cast<AFC*>(feature);
m_simplePTT->setMessageQueueToGUI(&m_inputMessageQueue);
m_featureUISet->addRollupWidget(this);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(1000);
m_statusTooltips.push_back("Idle"); // 0 - all off
m_statusTooltips.push_back("Rx on"); // 1 - Rx on
m_statusTooltips.push_back("Tx on"); // 2 - Tx on
m_statusColors.push_back("gray"); // All off
m_statusColors.push_back("rgb(85, 232, 85)"); // Rx on (green)
m_statusColors.push_back("rgb(232, 85, 85)"); // Tx on (red)
updateDeviceSetLists();
displaySettings();
applySettings(true);
}
AFCGUI::~AFCGUI()
{
delete ui;
}
void AFCGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void AFCGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
ui->rxtxDelay->setValue(m_settings.m_rx2TxDelayMs);
ui->txrxDelay->setValue(m_settings.m_tx2RxDelayMs);
blockApplySettings(false);
}
void AFCGUI::updateDeviceSetLists()
{
MainCore *mainCore = MainCore::instance();
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
std::vector<DeviceSet*>::const_iterator it = deviceSets.begin();
ui->rxDevice->blockSignals(true);
ui->txDevice->blockSignals(true);
ui->rxDevice->clear();
ui->txDevice->clear();
unsigned int deviceIndex = 0;
unsigned int rxIndex = 0;
unsigned int txIndex = 0;
for (; it != deviceSets.end(); ++it, deviceIndex++)
{
DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine;
DSPDeviceSinkEngine *deviceSinkEngine = (*it)->m_deviceSinkEngine;
if (deviceSourceEngine)
{
ui->rxDevice->addItem(QString("R%1").arg(deviceIndex), deviceIndex);
rxIndex++;
}
else if (deviceSinkEngine)
{
ui->txDevice->addItem(QString("T%1").arg(deviceIndex), deviceIndex);
txIndex++;
}
}
int rxDeviceIndex;
int txDeviceIndex;
if (rxIndex > 0)
{
if (m_settings.m_rxDeviceSetIndex < 0) {
ui->rxDevice->setCurrentIndex(0);
} else {
ui->rxDevice->setCurrentIndex(m_settings.m_rxDeviceSetIndex);
}
rxDeviceIndex = ui->rxDevice->currentData().toInt();
}
else
{
rxDeviceIndex = -1;
}
if (txIndex > 0)
{
if (m_settings.m_txDeviceSetIndex < 0) {
ui->txDevice->setCurrentIndex(0);
} else {
ui->txDevice->setCurrentIndex(m_settings.m_txDeviceSetIndex);
}
txDeviceIndex = ui->txDevice->currentData().toInt();
}
else
{
txDeviceIndex = -1;
}
if ((rxDeviceIndex != m_settings.m_rxDeviceSetIndex) ||
(txDeviceIndex != m_settings.m_txDeviceSetIndex))
{
qDebug("AFCGUI::updateDeviceSetLists: device index changed: %d:%d", rxDeviceIndex, txDeviceIndex);
m_settings.m_rxDeviceSetIndex = rxDeviceIndex;
m_settings.m_txDeviceSetIndex = txDeviceIndex;
applySettings();
}
ui->rxDevice->blockSignals(false);
ui->txDevice->blockSignals(false);
}
void AFCGUI::leaveEvent(QEvent*)
{
}
void AFCGUI::enterEvent(QEvent*)
{
}
void AFCGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setColor(m_settings.m_rgbColor);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = dialog.getColor().rgb();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void AFCGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
AFC::MsgStartStop *message = AFC::MsgStartStop::create(checked);
m_simplePTT->getInputMessageQueue()->push(message);
}
}
void AFCGUI::on_devicesRefresh_clicked()
{
updateDeviceSetLists();
displaySettings();
}
void AFCGUI::on_rxDevice_currentIndexChanged(int index)
{
if (index >= 0)
{
m_settings.m_rxDeviceSetIndex = index;
applySettings();
}
}
void AFCGUI::on_txDevice_currentIndexChanged(int index)
{
if (index >= 0)
{
m_settings.m_txDeviceSetIndex = index;
applySettings();
}
}
void AFCGUI::on_rxtxDelay_valueChanged(int value)
{
m_settings.m_rx2TxDelayMs = value;
applySettings();
}
void AFCGUI::on_txrxDelay_valueChanged(int value)
{
m_settings.m_tx2RxDelayMs = value;
applySettings();
}
void AFCGUI::on_ptt_toggled(bool checked)
{
applyPTT(checked);
}
void AFCGUI::updateStatus()
{
int state = m_simplePTT->getState();
if (m_lastFeatureState != state)
{
switch (state)
{
case Feature::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case Feature::StIdle:
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case Feature::StRunning:
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case Feature::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_simplePTT->getErrorMessage());
break;
default:
break;
}
m_lastFeatureState = state;
}
}
void AFCGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AFC::MsgConfigureAFC* message = AFC::MsgConfigureAFC::create( m_settings, force);
m_simplePTT->getInputMessageQueue()->push(message);
}
}
void AFCGUI::applyPTT(bool tx)
{
if (m_doApplySettings)
{
AFC::MsgPTT* message = AFC::MsgPTT::create(tx);
m_simplePTT->getInputMessageQueue()->push(message);
}
}

View File

@ -0,0 +1,88 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFCGUI_H_
#define INCLUDE_FEATURE_AFCGUI_H_
#include <QTimer>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "afcsettings.h"
class PluginAPI;
class FeatureUISet;
class AFC;
namespace Ui {
class AFCGUI;
}
class AFCGUI : public FeatureGUI {
Q_OBJECT
public:
static AFCGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
private:
Ui::AFCGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
AFCSettings m_settings;
bool m_doApplySettings;
AFC* m_simplePTT;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
int m_lastFeatureState;
std::vector<QString> m_statusColors;
std::vector<QString> m_statusTooltips;
explicit AFCGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~AFCGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void applyPTT(bool tx);
void displaySettings();
void updateDeviceSetLists();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_devicesRefresh_clicked();
void on_rxDevice_currentIndexChanged(int index);
void on_txDevice_currentIndexChanged(int index);
void on_rxtxDelay_valueChanged(int value);
void on_txrxDelay_valueChanged(int value);
void on_ptt_toggled(bool checked);
void updateStatus();
};
#endif // INCLUDE_FEATURE_AFCGUI_H_

View File

@ -0,0 +1,324 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AFCGUI</class>
<widget class="RollupWidget" name="AFCGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>181</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>AFC</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<height>151</height>
</rect>
</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="pttLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</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="ButtonSwitch" name="ptt">
<property name="minimumSize">
<size>
<width>200</width>
<height>50</height>
</size>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
<string>Push To Talk</string>
</property>
<property name="text">
<string>PTT</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusIndicator">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Idle</string>
</property>
<property name="styleSheet">
<string notr="true">QLabel { background-color: gray; border-radius: 12px; }</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="localDeviceLayout">
<item>
<widget class="QPushButton" name="devicesRefresh">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Refresh indexes of available device sets</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rxDeviceLabel">
<property name="text">
<string>Rx dev</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="rxDevice">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Receiver deviceset index</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>
<item>
<widget class="QLabel" name="txDeviceLabel">
<property name="text">
<string>Tx dev</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="txDevice">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Transmitter deviceset index</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="DelaysLayout">
<item>
<widget class="QLabel" name="rxtxDelayLabel">
<property name="text">
<string>Rx-Tx </string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rxtxDelay">
<property name="toolTip">
<string>Rx to Tx transition delay (ms)</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rxtxDelayUnits">
<property name="text">
<string>ms</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="QLabel" name="txrxDelayLabel">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Tx-Rx</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="txrxDelay">
<property name="toolTip">
<string>Tx to Rx transition delay (ms)</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="txrxDelayUnits">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 "afcgui.h"
#endif
#include "afc.h"
#include "afcplugin.h"
#include "afcwebapiadapter.h"
const PluginDescriptor AFCPlugin::m_pluginDescriptor = {
AFC::m_featureId,
QString("AFC"),
QString("4.21.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
AFCPlugin::AFCPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& AFCPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AFCPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register AFC feature
m_pluginAPI->registerFeature(AFC::m_featureIdURI, AFC::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* AFCPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* AFCPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return AFCGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* AFCPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new AFC(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* AFCPlugin::createFeatureWebAPIAdapter() const
{
return new AFCWebAPIAdapter();
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFCPLUGIN_H
#define INCLUDE_FEATURE_AFCPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class AFCPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.afc")
public:
explicit AFCPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_AFCPLUGIN_H

View File

@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 "afcreport.h"
MESSAGE_CLASS_DEFINITION(AFCReport::MsgRadioState, Message)
AFCReport::AFCReport()
{}
AFCReport::~AFCReport()
{}

View File

@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFCREPORT_H_
#define INCLUDE_FEATURE_AFCREPORT_H_
#include "util/message.h"
class AFCReport
{
public:
enum RadioState {
RadioIdle,
RadioRx,
RadioTx
};
class MsgRadioState : public Message {
MESSAGE_CLASS_DECLARATION
public:
RadioState getState() const { return m_state; }
static MsgRadioState* create(RadioState state)
{
return new MsgRadioState(state);
}
private:
RadioState m_state;
MsgRadioState(RadioState state) :
Message(),
m_state(state)
{ }
};
AFCReport();
~AFCReport();
};
#endif // INCLUDE_FEATURE_AFCREPORT_H_

View File

@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 "afcsettings.h"
AFCSettings::AFCSettings()
{
resetToDefaults();
}
void AFCSettings::resetToDefaults()
{
m_title = "AFC";
m_rgbColor = QColor(255, 255, 0).rgb();
m_rxDeviceSetIndex = -1;
m_txDeviceSetIndex = -1;
m_rx2TxDelayMs = 100;
m_tx2RxDelayMs = 100;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
}
QByteArray AFCSettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_title);
s.writeU32(2, m_rgbColor);
s.writeS32(3, m_rxDeviceSetIndex);
s.writeS32(4, m_txDeviceSetIndex);
s.writeU32(5, m_rx2TxDelayMs);
s.writeU32(6, m_tx2RxDelayMs);
s.writeBool(7, m_useReverseAPI);
s.writeString(8, m_reverseAPIAddress);
s.writeU32(9, m_reverseAPIPort);
s.writeU32(10, m_reverseAPIFeatureSetIndex);
s.writeU32(11, m_reverseAPIFeatureIndex);
return s.final();
}
bool AFCSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
QString strtmp;
d.readString(1, &m_title, "AFC");
d.readU32(2, &m_rgbColor, QColor(255, 255, 0).rgb());
d.readS32(3, &m_rxDeviceSetIndex, -1);
d.readS32(4, &m_txDeviceSetIndex, -1);
d.readU32(5, &m_rx2TxDelayMs, 100);
d.readU32(6, &m_tx2RxDelayMs, 100);
d.readBool(7, &m_useReverseAPI, false);
d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(9, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(10, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(11, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFCSETTINGS_H_
#define INCLUDE_FEATURE_AFCSETTINGS_H_
#include <QByteArray>
#include <QString>
class Serializable;
struct AFCSettings
{
QString m_title;
quint32 m_rgbColor;
int m_rxDeviceSetIndex;
int m_txDeviceSetIndex;
unsigned int m_rx2TxDelayMs;
unsigned int m_tx2RxDelayMs;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
AFCSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif // INCLUDE_FEATURE_AFCSETTINGS_H_

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// 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 "SWGFeatureSettings.h"
#include "afc.h"
#include "afcwebapiadapter.h"
AFCWebAPIAdapter::AFCWebAPIAdapter()
{}
AFCWebAPIAdapter::~AFCWebAPIAdapter()
{}
int AFCWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAfcSettings(new SWGSDRangel::SWGAFCSettings());
response.getAfcSettings()->init();
AFC::webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int AFCWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
AFC::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// 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_AFC_WEBAPIADAPTER_H
#define INCLUDE_AFC_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "afcsettings.h"
/**
* Standalone API adapter only for the settings
*/
class AFCWebAPIAdapter : public FeatureWebAPIAdapter {
public:
AFCWebAPIAdapter();
virtual ~AFCWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
private:
AFCSettings m_settings;
};
#endif // INCLUDE_AFC_WEBAPIADAPTER_H

View File

@ -0,0 +1,210 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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 "SWGDeviceState.h"
#include "SWGSuccessResponse.h"
#include "SWGErrorResponse.h"
#include "webapi/webapiadapterinterface.h"
#include "afcreport.h"
#include "afcworker.h"
MESSAGE_CLASS_DEFINITION(AFCWorker::MsgConfigureAFCWorker, Message)
MESSAGE_CLASS_DEFINITION(AFCWorker::MsgPTT, Message)
AFCWorker::AFCWorker(WebAPIAdapterInterface *webAPIAdapterInterface) :
m_webAPIAdapterInterface(webAPIAdapterInterface),
m_msgQueueToGUI(nullptr),
m_running(false),
m_tx(false),
m_mutex(QMutex::Recursive)
{
qDebug("AFCWorker::AFCWorker");
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
}
AFCWorker::~AFCWorker()
{
m_inputMessageQueue.clear();
}
void AFCWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
bool AFCWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
return m_running;
}
void AFCWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = false;
}
void AFCWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool AFCWorker::handleMessage(const Message& cmd)
{
if (MsgConfigureAFCWorker::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureAFCWorker& cfg = (MsgConfigureAFCWorker&) cmd;
qDebug() << "AFCWorker::handleMessage: MsgConfigureAFCWorker";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgPTT::match(cmd))
{
MsgPTT& cfg = (MsgPTT&) cmd;
qDebug() << "AFCWorker::handleMessage: MsgPTT";
sendPTT(cfg.getTx());
return true;
}
else
{
return false;
}
}
void AFCWorker::applySettings(const AFCSettings& settings, bool force)
{
qDebug() << "AFCWorker::applySettings:"
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_rxDeviceSetIndex: " << settings.m_rxDeviceSetIndex
<< " m_txDeviceSetIndex: " << settings.m_txDeviceSetIndex
<< " m_rx2TxDelayMs: " << settings.m_rx2TxDelayMs
<< " m_tx2RxDelayMs: " << settings.m_tx2RxDelayMs
<< " force: " << force;
m_settings = settings;
}
void AFCWorker::sendPTT(bool tx)
{
if (!m_updateTimer.isActive())
{
bool switchedOff = false;
m_mutex.lock();
if (tx)
{
if (m_settings.m_rxDeviceSetIndex >= 0)
{
m_tx = false;
switchedOff = turnDevice(false);
}
if (m_settings.m_txDeviceSetIndex >= 0)
{
m_tx = true;
m_updateTimer.start(m_settings.m_rx2TxDelayMs);
}
}
else
{
if (m_settings.m_txDeviceSetIndex >= 0)
{
m_tx = true;
switchedOff = turnDevice(false);
}
if (m_settings.m_rxDeviceSetIndex >= 0)
{
m_tx = false;
m_updateTimer.start(m_settings.m_tx2RxDelayMs);
}
}
if (switchedOff && (m_msgQueueToGUI))
{
AFCReport::MsgRadioState *msg = AFCReport::MsgRadioState::create(AFCReport::RadioIdle);
m_msgQueueToGUI->push(msg);
}
}
}
void AFCWorker::updateHardware()
{
SWGSDRangel::SWGSuccessResponse response;
SWGSDRangel::SWGErrorResponse error;
m_updateTimer.stop();
m_mutex.unlock();
if (turnDevice(true))
{
m_webAPIAdapterInterface->devicesetFocusPatch(
m_tx ? m_settings.m_txDeviceSetIndex : m_settings.m_rxDeviceSetIndex, response, error);
if (m_msgQueueToGUI)
{
AFCReport::MsgRadioState *msg = AFCReport::MsgRadioState::create(
m_tx ? AFCReport::RadioTx : AFCReport::RadioRx
);
m_msgQueueToGUI->push(msg);
}
}
}
bool AFCWorker::turnDevice(bool on)
{
SWGSDRangel::SWGDeviceState response;
SWGSDRangel::SWGErrorResponse error;
int httpCode;
if (on) {
httpCode = m_webAPIAdapterInterface->devicesetDeviceRunPost(
m_tx ? m_settings.m_txDeviceSetIndex : m_settings.m_rxDeviceSetIndex, response, error);
} else {
httpCode = m_webAPIAdapterInterface->devicesetDeviceRunDelete(
m_tx ? m_settings.m_txDeviceSetIndex : m_settings.m_rxDeviceSetIndex, response, error);
}
if (httpCode/100 == 2)
{
return true;
}
else
{
qWarning("AFCWorker::turnDevice: error: %s", qPrintable(*error.getMessage()));
return false;
}
}

View File

@ -0,0 +1,106 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// 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_FEATURE_AFCWORKER_H_
#define INCLUDE_FEATURE_AFCWORKER_H_
#include <QObject>
#include <QTimer>
#include "util/message.h"
#include "util/messagequeue.h"
#include "afcsettings.h"
class WebAPIAdapterInterface;
class AFCWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureAFCWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AFCSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAFCWorker* create(const AFCSettings& settings, bool force)
{
return new MsgConfigureAFCWorker(settings, force);
}
private:
AFCSettings m_settings;
bool m_force;
MsgConfigureAFCWorker(const AFCSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgPTT : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getTx() const { return m_tx; }
static MsgPTT* create(bool tx) {
return new MsgPTT(tx);
}
private:
bool m_tx;
MsgPTT(bool tx) :
Message(),
m_tx(tx)
{ }
};
AFCWorker(WebAPIAdapterInterface *webAPIAdapterInterface);
~AFCWorker();
void reset();
bool startWork();
void stopWork();
bool isRunning() const { return m_running; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; }
private:
WebAPIAdapterInterface *m_webAPIAdapterInterface;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_msgQueueToGUI; //!< Queue to report state to GUI
AFCSettings m_settings;
bool m_running;
bool m_tx;
QTimer m_updateTimer;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const AFCSettings& settings, bool force = false);
void sendPTT(bool tx);
bool turnDevice(bool on);
private slots:
void handleInputMessages();
void updateHardware();
};
#endif // INCLUDE_FEATURE_SIMPLEPTTWORKER_H_

View File

@ -0,0 +1,45 @@
<h1>AFC plugin</h1>
<h2>Introduction</h2>
This plugin controls switchover between a Rx (Device source) and Tx (Device sink). It has no other controls than an adjustable delay from Rx to Tx and back to Rx. Because of its simplicity it can also serve as a model to build other feature plugins.
<h2>Interface</h2>
![File source channel plugin GUI](../../../doc/img/AFC_plugin.png)
<h3>1: Start/Stop plugin</h3>
This button starts or stops the plugin
<h3>2: PTT button</h3>
Click to switch from Rx to Tx and again to switch back to Rx. When in Tx mode the button is lit.
<h3>3: Status indicator</h3>
This LED type display shows the current PTT status:
- **Green**: Rx runs
- **Red**: Tx runs
- **Grey**: None active (transient)
<h3>4: Refresh list of devices</h3>
Use this button to refresh the list of devices (5) and (6)
<h3>5: Select Rx device set</h3>
Use this combo to select which Rx device is controlled
<h3>6: Select Tx device set</h3>
Use this combo to select which Tx device is controlled
<h3>7: Transistion delay from Rx to Tx</h3>
Value in milliseconds between Rx stop and Tx start
<h3>8: Transistion delay from Tx to Rx</h3>
Value in milliseconds between Tx stop and Rx start