mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-04-08 20:48:45 -04:00
AFC plugin: brute force copy from Simple PTT plugin
This commit is contained in:
parent
3e004a257b
commit
0cd512ce4a
@ -1,4 +1,5 @@
|
||||
project(feature)
|
||||
|
||||
add_subdirectory(afc)
|
||||
add_subdirectory(rigctlserver)
|
||||
add_subdirectory(simpleptt)
|
||||
|
58
plugins/feature/afc/CMakeLists.txt
Normal file
58
plugins/feature/afc/CMakeLists.txt
Normal 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
427
plugins/feature/afc/afc.cpp
Normal 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
168
plugins/feature/afc/afc.h
Normal 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_
|
397
plugins/feature/afc/afcgui.cpp
Normal file
397
plugins/feature/afc/afcgui.cpp
Normal 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);
|
||||
}
|
||||
}
|
88
plugins/feature/afc/afcgui.h
Normal file
88
plugins/feature/afc/afcgui.h
Normal 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_
|
324
plugins/feature/afc/afcgui.ui
Normal file
324
plugins/feature/afc/afcgui.ui
Normal 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>
|
80
plugins/feature/afc/afcplugin.cpp
Normal file
80
plugins/feature/afc/afcplugin.cpp
Normal 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();
|
||||
}
|
48
plugins/feature/afc/afcplugin.h
Normal file
48
plugins/feature/afc/afcplugin.h
Normal 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
|
26
plugins/feature/afc/afcreport.cpp
Normal file
26
plugins/feature/afc/afcreport.cpp
Normal 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()
|
||||
{}
|
55
plugins/feature/afc/afcreport.h
Normal file
55
plugins/feature/afc/afcreport.h
Normal 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_
|
109
plugins/feature/afc/afcsettings.cpp
Normal file
109
plugins/feature/afc/afcsettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
46
plugins/feature/afc/afcsettings.h
Normal file
46
plugins/feature/afc/afcsettings.h
Normal 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_
|
50
plugins/feature/afc/afcwebapiadapter.cpp
Normal file
50
plugins/feature/afc/afcwebapiadapter.cpp
Normal 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;
|
||||
}
|
49
plugins/feature/afc/afcwebapiadapter.h
Normal file
49
plugins/feature/afc/afcwebapiadapter.h
Normal 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
|
210
plugins/feature/afc/afcworker.cpp
Normal file
210
plugins/feature/afc/afcworker.cpp
Normal 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;
|
||||
}
|
||||
}
|
106
plugins/feature/afc/afcworker.h
Normal file
106
plugins/feature/afc/afcworker.h
Normal 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_
|
45
plugins/feature/afc/readme.md
Normal file
45
plugins/feature/afc/readme.md
Normal 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>
|
||||
|
||||

|
||||
|
||||
<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
|
Loading…
Reference in New Issue
Block a user