1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-22 17:45:48 -05:00

RigCtl Server: added plugin

This commit is contained in:
f4exb 2020-09-28 00:31:08 +02:00
parent 882e580d44
commit 586d07bcb7
18 changed files with 2860 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

View File

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

View File

@ -0,0 +1,56 @@
project(rigctlserver)
set(rigctlserver_SOURCES
rigctlserver.cpp
rigctlserversettings.cpp
rigctlserverplugin.cpp
rigctlserverworker.cpp
rigctlserverwebapiadapter.cpp
)
set(rigctlserver_HEADERS
rigctlserver.h
rigctlserversettings.h
rigctlserverplugin.h
rigctlserverworker.h
rigctlserverwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(rigctlserver_SOURCES
${rigctlserver_SOURCES}
rigctlservergui.cpp
rigctlservergui.ui
)
set(rigctlserver_HEADERS
${rigctlserver_HEADERS}
rigctlservergui.h
)
set(TARGET_NAME featurerigctlserver)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featurerigctlserversrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${rigctlserver_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,66 @@
<h1>RigCtl server plugin</h1>
<h2>Introduction</h2>
The rigctl server plugin allows SDRangel to be controlled via [Hamlib](http://hamlib.sourceforge.net/manuals/hamlib.html)'s rigctld protocol. This allows other software that implements the rigctld protocol, such at the satellite tracking software GPredict, to control SDRangel, to adjust for doppler or to automatically switch between different satellite frequencies and modes.
<h2>Interface</h2>
![File source channel plugin GUI](../../../doc/img/RigCtlServer_plugin.png)
<h3>1: Start/Stop plugin</h3>
This button starts or stops the plugin
<h3>2: Enable rigctrl server</h3>
Checking this option will enable the rigctrl server in SDRangel. The default is disabled.
<h3>3: Refresh list of devices and channels</h3>
Use this button to refresh the list of devices (4) and channels (5)
<h3>4: Select device set</h3>
Specify the SDRangel device set that will be controlled by received rigctl commands. Defaults to R0.
<h3>5: Select channel</h3>
The channel index specifies the SDRangel channel that will be controlled by received rigctl commands. Defaults to 0.
<h3>6: Port</h3>
The rigctl server plugin opens a TCP port to receive commands from a rigctl client. Please specify a free TCP port number. The default rigctld port is 4532.
<h3>7: Max Frequency Offset in Hz</h3>
The maximum frequency offset controls whether the center frequency or frequency offset is adjusted when a new frequency is received by a rigctl command.
If the difference between the new frequency and the current center frequency is less than this value, the input offset (in the demodulator) will be adjusted.
If the difference is greater than this value, the center frequency will be set to the received frequency.
To only ever set the center frequency, set this value to 0. The default value is 10000.
<h2>Supported rigctrl Commands</h2>
The following rigctrl commands are supported:
<ul>
<li>F / set_freq
<li>f / get_freq
<li>M / set_mode
<li>m / get_mode
<li>get_powerstat
<li>set_powerstat
</ul>
<h2>Example rigctrl Session</h2>
Run SDRangel and from the Preferences menu select rigctrl. Check "Enable rigctrl server" and press OK.
In a terminal window, run:
<pre>
telnet localhost 4532
set_mode AM 1000
set_freq 100000000
set_powerstat 1
</pre>

View File

@ -0,0 +1,402 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 "SWGSimplePTTReport.h"
#include "SWGDeviceState.h"
#include "dsp/dspengine.h"
#include "rigctlserverworker.h"
#include "rigctlserver.h"
MESSAGE_CLASS_DEFINITION(RigCtlServer::MsgConfigureRigCtlServer, Message)
MESSAGE_CLASS_DEFINITION(RigCtlServer::MsgStartStop, Message)
const QString RigCtlServer::m_featureIdURI = "sdrangel.feature.rigctlserver";
const QString RigCtlServer::m_featureId = "RigCtlServer";
RigCtlServer::RigCtlServer(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface),
m_ptt(false)
{
qDebug("RigCtlServer::RigCtlServer: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_worker = new RigCtlServerWorker(webAPIAdapterInterface);
m_state = StIdle;
m_errorMessage = "RigCtlServer error";
}
RigCtlServer::~RigCtlServer()
{
if (m_worker->isRunning()) {
stop();
}
delete m_worker;
}
void RigCtlServer::start()
{
qDebug("RigCtlServer::start");
m_worker->reset();
m_worker->setMessageQueueToFeature(getInputMessageQueue());
bool ok = m_worker->startWork();
m_state = ok ? StRunning : StError;
m_thread.start();
RigCtlServerWorker::MsgConfigureRigCtlServerWorker *msg = RigCtlServerWorker::MsgConfigureRigCtlServerWorker::create(m_settings, true);
m_worker->getInputMessageQueue()->push(msg);
}
void RigCtlServer::stop()
{
qDebug("RigCtlServer::stop");
m_worker->stopWork();
m_state = StIdle;
m_thread.quit();
m_thread.wait();
}
bool RigCtlServer::handleMessage(const Message& cmd)
{
if (MsgConfigureRigCtlServer::match(cmd))
{
MsgConfigureRigCtlServer& cfg = (MsgConfigureRigCtlServer&) cmd;
qDebug() << "RigCtlServer::handleMessage: MsgConfigureRigCtlServer";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
qDebug() << "RigCtlServer::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
if (cfg.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else if (RigCtlServerSettings::MsgChannelIndexChange::match(cmd))
{
RigCtlServerSettings::MsgChannelIndexChange& cfg = (RigCtlServerSettings::MsgChannelIndexChange&) cmd;
int newChannelIndex = cfg.getIndex();
qDebug() << "RigCtlServer::handleMessage: MsgChannelIndexChange: " << newChannelIndex;
RigCtlServerSettings settings = m_settings;
settings.m_channelIndex = newChannelIndex;
applySettings(settings, false);
if (getMessageQueueToGUI())
{
RigCtlServerSettings::MsgChannelIndexChange *msg = new RigCtlServerSettings::MsgChannelIndexChange(cfg);
getMessageQueueToGUI()->push(msg);
}
return true;
}
else
{
return false;
}
}
QByteArray RigCtlServer::serialize() const
{
return m_settings.serialize();
}
bool RigCtlServer::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureRigCtlServer *msg = MsgConfigureRigCtlServer::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureRigCtlServer *msg = MsgConfigureRigCtlServer::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void RigCtlServer::applySettings(const RigCtlServerSettings& settings, bool force)
{
qDebug() << "RigCtlServer::applySettings:"
<< " m_enabled: " << settings.m_enabled
<< " m_deviceIndex: " << settings.m_deviceIndex
<< " m_channelIndex: " << settings.m_channelIndex
<< " m_rigCtlPort: " << settings.m_rigCtlPort
<< " m_maxFrequencyOffset: " << settings.m_maxFrequencyOffset
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex
<< " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_enabled != settings.m_enabled) || force) {
reverseAPIKeys.append("enabled");
}
if ((m_settings.m_deviceIndex != settings.m_deviceIndex) || force) {
reverseAPIKeys.append("deviceIndex");
}
if ((m_settings.m_channelIndex != settings.m_channelIndex) || force) {
reverseAPIKeys.append("channelIndex");
}
if ((m_settings.m_rigCtlPort != settings.m_rigCtlPort) || force) {
reverseAPIKeys.append("rigCtlPort");
}
if ((m_settings.m_maxFrequencyOffset != settings.m_maxFrequencyOffset) || force) {
reverseAPIKeys.append("maxFrequencyOffset");
}
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
RigCtlServerWorker::MsgConfigureRigCtlServerWorker *msg = RigCtlServerWorker::MsgConfigureRigCtlServerWorker::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 RigCtlServer::webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
getFeatureStateStr(*response.getState());
MsgStartStop *msg = MsgStartStop::create(run);
getInputMessageQueue()->push(msg);
return 202;
}
int RigCtlServer::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setRigCtlServerSettings(new SWGSDRangel::SWGRigCtlServerSettings());
response.getRigCtlServerSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int RigCtlServer::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
RigCtlServerSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureRigCtlServer *msg = MsgConfigureRigCtlServer::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("RigCtlServer::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureRigCtlServer *msgToGUI = MsgConfigureRigCtlServer::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
void RigCtlServer::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const RigCtlServerSettings& settings)
{
response.getRigCtlServerSettings()->setEnabled(settings.m_enabled ? 1 : 0);
response.getRigCtlServerSettings()->setDeviceIndex(settings.m_deviceIndex);
response.getRigCtlServerSettings()->setChannelIndex(settings.m_channelIndex);
response.getRigCtlServerSettings()->setRigCtlPort(settings.m_rigCtlPort);
response.getRigCtlServerSettings()->setMaxFrequencyOffset(settings.m_maxFrequencyOffset);
if (response.getRigCtlServerSettings()->getTitle()) {
*response.getRigCtlServerSettings()->getTitle() = settings.m_title;
} else {
response.getRigCtlServerSettings()->setTitle(new QString(settings.m_title));
}
response.getRigCtlServerSettings()->setRgbColor(settings.m_rgbColor);
response.getRigCtlServerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getRigCtlServerSettings()->getReverseApiAddress()) {
*response.getRigCtlServerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getRigCtlServerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getRigCtlServerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getRigCtlServerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
response.getRigCtlServerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
}
void RigCtlServer::webapiUpdateFeatureSettings(
RigCtlServerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("enabled")) {
settings.m_enabled = response.getRigCtlServerSettings()->getEnabled();
}
if (featureSettingsKeys.contains("deviceIndex")) {
settings.m_deviceIndex = response.getRigCtlServerSettings()->getDeviceIndex();
}
if (featureSettingsKeys.contains("channelIndex")) {
settings.m_channelIndex = response.getRigCtlServerSettings()->getChannelIndex();
}
if (featureSettingsKeys.contains("rigCtlPort")) {
settings.m_rigCtlPort = response.getRigCtlServerSettings()->getRigCtlPort();
}
if (featureSettingsKeys.contains("maxFrequencyOffset")) {
settings.m_maxFrequencyOffset = response.getRigCtlServerSettings()->getMaxFrequencyOffset();
}
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getRigCtlServerSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getRigCtlServerSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getRigCtlServerSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getRigCtlServerSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getRigCtlServerSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getRigCtlServerSettings()->getReverseApiDeviceIndex();
}
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIFeatureIndex = response.getRigCtlServerSettings()->getReverseApiChannelIndex();
}
}
void RigCtlServer::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const RigCtlServerSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("RigCtlServer"));
swgFeatureSettings->setRigCtlServerSettings(new SWGSDRangel::SWGRigCtlServerSettings());
SWGSDRangel::SWGRigCtlServerSettings *swgRigCtlServerSettings = swgFeatureSettings->getRigCtlServerSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("enabled") || force) {
swgRigCtlServerSettings->setEnabled(settings.m_enabled ? 1 : 0);
}
if (featureSettingsKeys.contains("deviceIndex") || force) {
swgRigCtlServerSettings->setDeviceIndex(settings.m_deviceIndex);
}
if (featureSettingsKeys.contains("channelIndex") || force) {
swgRigCtlServerSettings->setChannelIndex(settings.m_channelIndex);
}
if (featureSettingsKeys.contains("rigCtlPort") || force) {
swgRigCtlServerSettings->setRigCtlPort(settings.m_rigCtlPort);
}
if (featureSettingsKeys.contains("maxFrequencyOffset") || force) {
swgRigCtlServerSettings->setMaxFrequencyOffset(settings.m_maxFrequencyOffset);
}
if (featureSettingsKeys.contains("title") || force) {
swgRigCtlServerSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgRigCtlServerSettings->setRgbColor(settings.m_rgbColor);
}
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 RigCtlServer::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "RigCtlServer::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("RigCtlServer::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,139 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVER_H_
#define INCLUDE_FEATURE_RIGCTLSERVER_H_
#include <QThread>
#include <QNetworkRequest>
#include "feature/feature.h"
#include "util/message.h"
#include "rigctlserversettings.h"
class WebAPIAdapterInterface;
class RigCtlServerWorker;
class QNetworkAccessManager;
class QNetworkReply;
namespace SWGSDRangel {
class SWGDeviceState;
}
class RigCtlServer : public Feature
{
Q_OBJECT
public:
class MsgConfigureRigCtlServer : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RigCtlServerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRigCtlServer* create(const RigCtlServerSettings& settings, bool force) {
return new MsgConfigureRigCtlServer(settings, force);
}
private:
RigCtlServerSettings m_settings;
bool m_force;
MsgConfigureRigCtlServer(const RigCtlServerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
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)
{ }
};
RigCtlServer(WebAPIAdapterInterface *webAPIAdapterInterface);
~RigCtlServer();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) const { id = objectName(); }
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);
static void webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const RigCtlServerSettings& settings);
static void webapiUpdateFeatureSettings(
RigCtlServerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
static const QString m_featureIdURI;
static const QString m_featureId;
private:
QThread m_thread;
RigCtlServerWorker *m_worker;
RigCtlServerSettings m_settings;
bool m_ptt;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void start();
void stop();
void applySettings(const RigCtlServerSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const RigCtlServerSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_FEATURE_RIGCTLSERVER_H_

View File

@ -0,0 +1,409 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 "mainwindow.h"
#include "device/deviceuiset.h"
#include "ui_rigctlservergui.h"
#include "rigctlserver.h"
#include "rigctlservergui.h"
RigCtlServerGUI* RigCtlServerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
RigCtlServerGUI* gui = new RigCtlServerGUI(pluginAPI, featureUISet, feature);
return gui;
}
void RigCtlServerGUI::destroy()
{
delete this;
}
void RigCtlServerGUI::setName(const QString& name)
{
setObjectName(name);
}
QString RigCtlServerGUI::getName() const
{
return objectName();
}
void RigCtlServerGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray RigCtlServerGUI::serialize() const
{
qDebug("RigCtlServerGUI::serialize: %d", m_settings.m_channelIndex);
return m_settings.serialize();
}
bool RigCtlServerGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
qDebug("RigCtlServerGUI::deserialize: %d", m_settings.m_channelIndex);
updateDeviceSetList();
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool RigCtlServerGUI::handleMessage(const Message& message)
{
if (RigCtlServer::MsgConfigureRigCtlServer::match(message))
{
qDebug("RigCtlServerGUI::handleMessage: RigCtlServer::MsgConfigureRigCtlServer");
const RigCtlServer::MsgConfigureRigCtlServer& cfg = (RigCtlServer::MsgConfigureRigCtlServer&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (RigCtlServerSettings::MsgChannelIndexChange::match(message))
{
const RigCtlServerSettings::MsgChannelIndexChange& cfg = (RigCtlServerSettings::MsgChannelIndexChange&) message;
int newChannelIndex = cfg.getIndex();
qDebug("RigCtlServerGUI::handleMessage: RigCtlServerSettings::MsgChannelIndexChange: %d", newChannelIndex);
ui->channel->blockSignals(true);
ui->channel->setCurrentIndex(newChannelIndex);
m_settings.m_channelIndex = newChannelIndex;
ui->channel->blockSignals(false);
return true;
}
return false;
}
void RigCtlServerGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void RigCtlServerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
RigCtlServerGUI::RigCtlServerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
RollupWidget(parent),
ui(new Ui::RigCtlServerGUI),
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_rigCtlServer = reinterpret_cast<RigCtlServer*>(feature);
m_rigCtlServer->setMessageQueueToGUI(&m_inputMessageQueue);
m_featureUISet->registerFeatureInstance(RigCtlServer::m_featureIdURI, this, m_rigCtlServer);
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);
updateDeviceSetList();
displaySettings();
applySettings(true);
}
RigCtlServerGUI::~RigCtlServerGUI()
{
m_featureUISet->removeFeatureInstance(this);
delete m_rigCtlServer; // When the GUI closes it has to delete the demodulator because it can be done with (x)
delete ui;
}
void RigCtlServerGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void RigCtlServerGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
ui->rigCtrlPort->setValue(m_settings.m_rigCtlPort);
ui->maxFrequencyOffset->setValue(m_settings.m_maxFrequencyOffset);
blockApplySettings(false);
}
void RigCtlServerGUI::updateDeviceSetList()
{
MainWindow *mainWindow = MainWindow::getInstance();
std::vector<DeviceUISet*>& deviceUISets = mainWindow->getDeviceUISets();
std::vector<DeviceUISet*>::const_iterator it = deviceUISets.begin();
ui->device->blockSignals(true);
ui->device->clear();
unsigned int deviceIndex = 0;
for (; it != deviceUISets.end(); ++it, deviceIndex++)
{
DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine;
DSPDeviceSinkEngine *deviceSinkEngine = (*it)->m_deviceSinkEngine;
DSPDeviceMIMOEngine *deviceMIMOEngine = (*it)->m_deviceMIMOEngine;
if (deviceSourceEngine) {
ui->device->addItem(QString("R%1").arg(deviceIndex), deviceIndex);
}
}
int newDeviceIndex;
if (it != deviceUISets.begin())
{
if (m_settings.m_deviceIndex < 0) {
ui->device->setCurrentIndex(0);
} else {
ui->device->setCurrentIndex(m_settings.m_deviceIndex);
}
newDeviceIndex = ui->device->currentData().toInt();
}
else
{
newDeviceIndex = -1;
}
if (newDeviceIndex != m_settings.m_deviceIndex)
{
qDebug("RigCtlServerGUI::updateDeviceSetLists: device index changed: %d", newDeviceIndex);
m_settings.m_deviceIndex = newDeviceIndex;
}
updateChannelList();
ui->device->blockSignals(false);
}
bool RigCtlServerGUI::updateChannelList()
{
int newChannelIndex;
ui->channel->blockSignals(true);
ui->channel->clear();
if (m_settings.m_deviceIndex < 0)
{
newChannelIndex = -1;
}
else
{
MainWindow *mainWindow = MainWindow::getInstance();
std::vector<DeviceUISet*>& deviceUISets = mainWindow->getDeviceUISets();
DeviceUISet *deviceUISet = deviceUISets[m_settings.m_deviceIndex];
int nbChannels = deviceUISet->getNumberOfChannels();
for (int ch = 0; ch < nbChannels; ch++) {
ui->channel->addItem(QString("%1").arg(ch), ch);
}
if (nbChannels > 0)
{
if (m_settings.m_channelIndex < 0) {
ui->channel->setCurrentIndex(0);
} else {
ui->channel->setCurrentIndex(m_settings.m_channelIndex);
}
newChannelIndex = ui->channel->currentIndex();
}
else
{
newChannelIndex = -1;
}
}
ui->channel->blockSignals(false);
if (newChannelIndex != m_settings.m_channelIndex)
{
qDebug("RigCtlServerGUI::updateChannelList: channel index changed: %d", newChannelIndex);
m_settings.m_channelIndex = newChannelIndex;
return true;
}
return false;
}
void RigCtlServerGUI::leaveEvent(QEvent*)
{
}
void RigCtlServerGUI::enterEvent(QEvent*)
{
}
void RigCtlServerGUI::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 RigCtlServerGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
RigCtlServer::MsgStartStop *message = RigCtlServer::MsgStartStop::create(checked);
m_rigCtlServer->getInputMessageQueue()->push(message);
}
}
void RigCtlServerGUI::on_enable_toggled(bool checked)
{
m_settings.m_enabled = checked;
applySettings();
}
void RigCtlServerGUI::on_devicesRefresh_clicked()
{
updateDeviceSetList();
displaySettings();
applySettings();
}
void RigCtlServerGUI::on_device_currentIndexChanged(int index)
{
if (index >= 0)
{
m_settings.m_deviceIndex = ui->device->currentData().toInt();
updateChannelList();
applySettings();
}
}
void RigCtlServerGUI::on_channel_currentIndexChanged(int index)
{
if (index >= 0)
{
m_settings.m_channelIndex = index;
applySettings();
}
}
void RigCtlServerGUI::on_rigCtrlPort_valueChanged(int value)
{
m_settings.m_rigCtlPort = value;
applySettings();
}
void RigCtlServerGUI::on_maxFrequencyOffset_valueChanged(int value)
{
m_settings.m_maxFrequencyOffset = value;
applySettings();
}
void RigCtlServerGUI::updateStatus()
{
int state = m_rigCtlServer->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_rigCtlServer->getErrorMessage());
break;
default:
break;
}
m_lastFeatureState = state;
}
}
void RigCtlServerGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
RigCtlServer::MsgConfigureRigCtlServer* message = RigCtlServer::MsgConfigureRigCtlServer::create( m_settings, force);
m_rigCtlServer->getInputMessageQueue()->push(message);
}
}

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVERGUI_H_
#define INCLUDE_FEATURE_RIGCTLSERVERGUI_H_
#include <QTimer>
#include "plugin/plugininstancegui.h"
#include "gui/rollupwidget.h"
#include "util/messagequeue.h"
#include "rigctlserversettings.h"
class PluginAPI;
class FeatureUISet;
class RigCtlServer;
namespace Ui {
class RigCtlServerGUI;
}
class RigCtlServerGUI : public RollupWidget, public PluginInstanceGUI {
Q_OBJECT
public:
static RigCtlServerGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
void setName(const QString& name);
QString getName() const;
virtual qint64 getCenterFrequency() const { return 0; }
virtual void setCenterFrequency(qint64 centerFrequency) {}
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool handleMessage(const Message& message);
private:
Ui::RigCtlServerGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
RigCtlServerSettings m_settings;
bool m_doApplySettings;
RigCtlServer* m_rigCtlServer;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
int m_lastFeatureState;
explicit RigCtlServerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~RigCtlServerGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateDeviceSetList();
bool updateChannelList(); //!< true if channel index has changed
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_enable_toggled(bool checked);
void on_devicesRefresh_clicked();
void on_device_currentIndexChanged(int index);
void on_channel_currentIndexChanged(int index);
void on_rigCtrlPort_valueChanged(int value);
void on_maxFrequencyOffset_valueChanged(int value);
void updateStatus();
};
#endif // INCLUDE_FEATURE_RIGCTLSERVERGUI_H_

View File

@ -0,0 +1,282 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RigCtlServerGUI</class>
<widget class="RollupWidget" name="RigCtlServerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>189</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>RigCtl Server</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<height>171</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="controlLayout">
<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>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="enable">
<property name="toolTip">
<string>Select to enable rigctrl server.</string>
</property>
<property name="text">
<string>Enable rigctl server</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="deviceLabel">
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="device">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Receiver deviceset index</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelLabel">
<property name="text">
<string>Channel</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channel">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel 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>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="serverLayout">
<item>
<widget class="QLabel" name="rigCtrlPortLabel">
<property name="text">
<string>RigCtl Port</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rigCtrlPort">
<property name="toolTip">
<string>TCP port to listen for rigctrl commands on.
Default is 4532.</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65536</number>
</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>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="channelLayout">
<item>
<widget class="QLabel" name="maxFrequencyOffsetLabel">
<property name="text">
<string>Max Frequency Offset</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="maxFrequencyOffset">
<property name="toolTip">
<string>Controls whether the center frequency or frequency offset is adjusted when a new frequency is received via a rigctrl command.
If the difference between the new frequency and the current center frequency is less than this value, the offset will be adjusted. If it is greater than this value, the center frequency will be set to the new frequency.
To only ever set the center frequency, set this value to 0.
Default is 10000.</string>
</property>
<property name="maximum">
<number>9999999</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<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,81 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 "rigctlservergui.h"
#endif
#include "rigctlserver.h"
#include "rigctlserverplugin.h"
#include "rigctlserverwebapiadapter.h"
const PluginDescriptor RigCtlServerPlugin::m_pluginDescriptor = {
RigCtlServer::m_featureId,
QString("RigCtl Server"),
QString("5.12.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
RigCtlServerPlugin::RigCtlServerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& RigCtlServerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void RigCtlServerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register RigCtl Server feature
m_pluginAPI->registerFeature(RigCtlServer::m_featureIdURI, RigCtlServer::m_featureId, this);
}
#ifdef SERVER_MODE
PluginInstanceGUI* RigCtlServerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
PluginInstanceGUI* RigCtlServerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return RigCtlServerGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* RigCtlServerPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new RigCtlServer(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* RigCtlServerPlugin::createFeatureWebAPIAdapter() const
{
return new RigCtlServerWebAPIAdapter();
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVERPLUGIN_H
#define INCLUDE_FEATURE_RIGCTLSERVERPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class WebAPIAdapterInterface;
class RigCtlServerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.rigctlserver")
public:
explicit RigCtlServerPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual PluginInstanceGUI* 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_RIGCTLSERVERPLUGIN_H

View File

@ -0,0 +1,119 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 "rigctlserversettings.h"
MESSAGE_CLASS_DEFINITION(RigCtlServerSettings::MsgChannelIndexChange, Message)
RigCtlServerSettings::RigCtlServerSettings()
{
resetToDefaults();
}
void RigCtlServerSettings::resetToDefaults()
{
m_rigCtlPort = 4532;
m_maxFrequencyOffset = 10000;
m_deviceIndex = -1;
m_channelIndex = -1;
m_title = "RigCtl Server";
m_rgbColor = QColor(225, 25, 99).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
}
QByteArray RigCtlServerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeU32(1, m_rigCtlPort);
s.writeS32(2, m_maxFrequencyOffset);
s.writeS32(3, m_deviceIndex);
s.writeS32(4, m_channelIndex);
s.writeString(5, m_title);
s.writeU32(6, m_rgbColor);
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 RigCtlServerSettings::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.readU32(2, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_rigCtlPort = utmp;
} else {
m_rigCtlPort = 4532;
}
d.readS32(2, &m_maxFrequencyOffset, 10000);
d.readS32(3, &m_deviceIndex, -1);
d.readS32(4, &m_channelIndex, -1);
d.readString(5, &m_title, "RigCtl Server");
d.readU32(6, &m_rgbColor, QColor(225, 25, 99).rgb());
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,69 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVERSETTINGS_H_
#define INCLUDE_FEATURE_RIGCTLSERVERSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "util/message.h"
class Serializable;
struct RigCtlServerSettings
{
class MsgChannelIndexChange : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getIndex() const { return m_index; }
static MsgChannelIndexChange* create(int index) {
return new MsgChannelIndexChange(index);
}
protected:
int m_index;
MsgChannelIndexChange(int index) :
Message(),
m_index(index)
{ }
};
bool m_enabled;
quint32 m_rigCtlPort;
int m_maxFrequencyOffset;
int m_deviceIndex;
int m_channelIndex;
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
RigCtlServerSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif // INCLUDE_FEATURE_RIGCTLSERVERSETTINGS_H_

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 "rigctlserver.h"
#include "rigctlserverwebapiadapter.h"
RigCtlServerWebAPIAdapter::RigCtlServerWebAPIAdapter()
{}
RigCtlServerWebAPIAdapter::~RigCtlServerWebAPIAdapter()
{}
int RigCtlServerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings());
response.getSimplePttSettings()->init();
RigCtlServer::webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int RigCtlServerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
RigCtlServer::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVER_WEBAPIADAPTER_H
#define INCLUDE_RIGCTLSERVER_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "rigctlserversettings.h"
/**
* Standalone API adapter only for the settings
*/
class RigCtlServerWebAPIAdapter : public FeatureWebAPIAdapter {
public:
RigCtlServerWebAPIAdapter();
virtual ~RigCtlServerWebAPIAdapter();
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:
RigCtlServerSettings m_settings;
};
#endif // INCLUDE_RIGCTLSERVER_WEBAPIADAPTER_H

View File

@ -0,0 +1,850 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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 <QTcpServer>
#include <QTcpSocket>
#include <QEventLoop>
#include <QTimer>
#include "SWGDeviceState.h"
#include "SWGSuccessResponse.h"
#include "SWGErrorResponse.h"
#include "SWGDeviceSettings.h"
#include "SWGChannelSettings.h"
#include "SWGDeviceSet.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
#include "rigctlserverworker.h"
MESSAGE_CLASS_DEFINITION(RigCtlServerWorker::MsgConfigureRigCtlServerWorker, Message)
const unsigned int RigCtlServerWorker::m_CmdLength = 1024;
const unsigned int RigCtlServerWorker::m_ResponseLength = 1024;
const struct RigCtlServerWorker::ModeDemod RigCtlServerWorker::m_modeMap[] = {
{"FM", "NFMDemod"},
{"WFM", "WFMDemod"},
{"AM", "AMDemod"},
{"LSB", "SSBDemod"},
{"USB", "SSBDemod"},
{nullptr, nullptr}
};
RigCtlServerWorker::RigCtlServerWorker(WebAPIAdapterInterface *webAPIAdapterInterface) :
m_state(idle),
m_tcpServer(nullptr),
m_clientConnection(nullptr),
m_webAPIAdapterInterface(webAPIAdapterInterface),
m_msgQueueToFeature(nullptr),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("RigCtlServerWorker::RigCtlServerWorker");
}
RigCtlServerWorker::~RigCtlServerWorker()
{
m_inputMessageQueue.clear();
}
void RigCtlServerWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
bool RigCtlServerWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
return m_running;
}
void RigCtlServerWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
restartServer(false, 0);
m_running = false;
}
void RigCtlServerWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool RigCtlServerWorker::handleMessage(const Message& cmd)
{
if (MsgConfigureRigCtlServerWorker::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureRigCtlServerWorker& cfg = (MsgConfigureRigCtlServerWorker&) cmd;
qDebug() << "RigCtlServerWorker::handleMessage: MsgConfigureRigCtlServerWorker";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else
{
return false;
}
}
void RigCtlServerWorker::applySettings(const RigCtlServerSettings& settings, bool force)
{
qDebug() << "RigCtlServerWorker::applySettings:"
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_enabled: " << settings.m_enabled
<< " m_deviceIndex: " << settings.m_deviceIndex
<< " m_channelIndex: " << settings.m_channelIndex
<< " m_rigCtlPort: " << settings.m_rigCtlPort
<< " m_maxFrequencyOffset: " << settings.m_maxFrequencyOffset
<< " force: " << force;
if ((settings.m_rigCtlPort != m_settings.m_rigCtlPort) ||
(settings.m_enabled != m_settings.m_enabled) || force)
{
restartServer(settings.m_enabled, settings.m_rigCtlPort);
}
m_settings = settings;
}
void RigCtlServerWorker::restartServer(bool enabled, uint32_t port)
{
if (m_tcpServer)
{
if (m_clientConnection)
{
m_clientConnection->close();
delete m_clientConnection;
m_clientConnection = nullptr;
}
disconnect(m_tcpServer, &QTcpServer::newConnection, this, &RigCtlServerWorker::acceptConnection);
m_tcpServer->close();
delete m_tcpServer;
m_tcpServer = nullptr;
}
if (enabled)
{
qDebug() << "RigCtlServerWorker::restartServer: server enabled on port " << port;
m_tcpServer = new QTcpServer(this);
if (!m_tcpServer->listen(QHostAddress::Any, port)) {
qWarning("RigCtrl failed to listen on port %u. Check it is not already in use.", port);
} else {
connect(m_tcpServer, &QTcpServer::newConnection, this, &RigCtlServerWorker::acceptConnection);
}
}
else
{
qDebug() << "RigCtlServerWorker::restartServer: server disabled";
}
}
void RigCtlServerWorker::acceptConnection()
{
QMutexLocker mutexLocker(&m_mutex);
m_clientConnection = m_tcpServer->nextPendingConnection();
if (!m_clientConnection) {
return;
}
connect(m_clientConnection, &QIODevice::readyRead, this, &RigCtlServerWorker::getCommand);
connect(m_clientConnection, &QAbstractSocket::disconnected, m_clientConnection, &QObject::deleteLater);
}
// Get rigctl command and start processing it
void RigCtlServerWorker::getCommand()
{
QMutexLocker mutexLocker(&m_mutex);
char cmd[m_CmdLength];
qint64 len;
rig_errcode_e rigCtlRC;
char response[m_ResponseLength];
std::fill(response, response + m_ResponseLength, '\0');
// Get rigctld command from client
len = m_clientConnection->readLine(cmd, sizeof(cmd));
if (len != -1)
{
//qDebug() << "RigCtrl::getCommand - " << cmd;
if (!strncmp(cmd, "F ", 2) || !strncmp(cmd, "set_freq ", 9))
{
// Set frequency
double targetFrequency = atof(cmd[0] == 'F' ? &cmd[2] : &cmd[9]);
setFrequency(targetFrequency, rigCtlRC);
sprintf(response, "RPRT %d\n", rigCtlRC);
}
else if (!strncmp(cmd, "f", 1) || !strncmp(cmd, "get_freq", 8))
{
// Get frequency - need to add centerFrequency and inputFrequencyOffset
double frequency;
if (getFrequency(frequency, rigCtlRC)) {
sprintf(response, "%u\n", (unsigned int) frequency);
} else {
sprintf(response, "RPRT %d\n", rigCtlRC);
}
}
else if (!strncmp(cmd, "M ?", 3) || !(strncmp(cmd, "set_mode ?", 10)))
{
// Return list of modes supported
char *p = response;
for (int i = 0; m_modeMap[i].mode != nullptr; i++) {
p += sprintf(p, "%s ", m_modeMap[i].mode);
}
p += sprintf(p, "\n");
}
else if (!strncmp(cmd, "M ", 2) || !(strncmp(cmd, "set_mode ", 9)))
{
// Set mode
// Map rigctrl mode name to SDRangel modem name
const char *targetModem = nullptr;
const char *targetMode = nullptr;
int targetBW = -1;
char *p = (cmd[0] == 'M' ? &cmd[2] : &cmd[9]);
int i, l;
for (i = 0; m_modeMap[i].mode != nullptr; i++)
{
l = strlen(m_modeMap[i].mode);
if (!strncmp(p, m_modeMap[i].mode, l))
{
targetMode = m_modeMap[i].mode;
targetModem = m_modeMap[i].modem;
p += l;
break;
}
}
// Save bandwidth, if given
while(isspace(*p)) {
p++;
}
if (isdigit(*p))
{
targetBW = atoi(p);
}
if (m_modeMap[i].modem != nullptr)
{
changeModem(targetMode, targetModem, targetBW, rigCtlRC);
sprintf(response, "RPRT %d\n", rigCtlRC);
}
else
{
sprintf(response, "RPRT %d\n", RIG_EINVAL);
m_clientConnection->write(response, strlen(response));
}
}
else if (!strncmp(cmd, "m", 1) || !(strncmp(cmd, "get_mode", 8)))
{
// Get mode
const char *mode;
double passband;
if (getMode(&mode, passband, rigCtlRC))
{
if (passband < 0) {
sprintf(response, "%s\n", mode);
} else {
sprintf(response, "%s %u\n", mode, (unsigned int) passband);
}
}
else
{
sprintf(response, "RPRT %d\n", rigCtlRC);
}
}
else if (!strncmp(cmd, "set_powerstat 0", 15))
{
// Power off radio
setPowerOff(rigCtlRC);
sprintf(response, "RPRT %d\n", rigCtlRC);
}
else if (!strncmp(cmd, "set_powerstat 1", 15))
{
// Power on radio
setPowerOn(rigCtlRC);
sprintf(response, "RPRT %d\n", rigCtlRC);
}
else if (!strncmp(cmd, "get_powerstat", 13))
{
// Return if powered on or off
bool power;
if (getPower(power, rigCtlRC))
{
if (power) {
sprintf(response, "1\n");
} else {
sprintf(response, "0\n");
}
}
else
{
sprintf(response, "RPRT %d\n", rigCtlRC);
}
}
else
{
// Unimplemented command
sprintf(response, "RPRT %d\n", RIG_ENIMPL);
m_clientConnection->write(response, strlen(response));
}
}
m_clientConnection->write(response, strlen(response));
}
bool RigCtlServerWorker::setFrequency(double targetFrequency, rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC;
// Get current device center frequency
httpRC = m_webAPIAdapterInterface->devicesetDeviceSettingsGet(
m_settings.m_deviceIndex,
deviceSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::setFrequency: get device frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
double freq;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", freq))
{
bool outOfRange = std::abs(freq - targetFrequency) > m_settings.m_maxFrequencyOffset;
if (outOfRange)
{
// Update centerFrequency
WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", targetFrequency);
QStringList deviceSettingsKeys;
deviceSettingsKeys.append("centerFrequency");
deviceSettingsResponse.init();
deviceSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
httpRC = m_webAPIAdapterInterface->devicesetDeviceSettingsPutPatch(
m_settings.m_deviceIndex,
false, // PATCH
deviceSettingsKeys,
deviceSettingsResponse,
errorResponse2
);
if (httpRC/100 == 2)
{
qDebug("RigCtlServerWorker::setFrequency: set device frequency %f OK", targetFrequency);
}
else
{
qWarning("RigCtlServerWorker::setFrequency: set device frequency error %d: %s",
httpRC, qPrintable(*errorResponse2.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
}
// Update inputFrequencyOffset (offet if in range else zero)
float targetOffset = outOfRange ? 0 : targetFrequency - freq;
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
// Get channel settings containing inputFrequencyOffset, so we can patch it
httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsGet(
m_settings.m_deviceIndex,
m_settings.m_channelIndex,
channelSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::setFrequency: get channel offset frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
jsonObj = channelSettingsResponse.asJsonObject();
if (WebAPIUtils::setSubObjectDouble(*jsonObj, "inputFrequencyOffset", targetOffset))
{
QStringList channelSettingsKeys;
channelSettingsKeys.append("inputFrequencyOffset");
channelSettingsResponse.init();
channelSettingsResponse.fromJsonObject(*jsonObj);
httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsPutPatch(
m_settings.m_deviceIndex,
m_settings.m_channelIndex,
false, // PATCH
channelSettingsKeys,
channelSettingsResponse,
errorResponse
);
if (httpRC/100 == 2)
{
qDebug("RigCtlServerWorker::setFrequency: set channel offset frequency %f OK", targetOffset);
}
else
{
qWarning("RigCtlServerWorker::setFrequency: set channel frequency offset error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
}
else
{
qWarning("RigCtlServerWorker::setFrequency: No inputFrequencyOffset key in channel settings");
rigCtlRC = RIG_ENIMPL;
return false;
}
}
else
{
qWarning("RigCtlServerWorker::setFrequency: no centerFrequency key in device settings");
rigCtlRC = RIG_ENIMPL;
return false;
}
rigCtlRC = RIG_OK; // return OK
return true;
}
bool RigCtlServerWorker::getFrequency(double& frequency, rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC;
// Get current device center frequency
httpRC = m_webAPIAdapterInterface->devicesetDeviceSettingsGet(
m_settings.m_deviceIndex,
deviceSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::getFrequency: get device frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
double deviceFreq;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", deviceFreq))
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
// Get channel settings containg inputFrequencyOffset, so we can patch them
httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsGet(
m_settings.m_deviceIndex,
m_settings.m_channelIndex,
channelSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::setFrequency: get channel offset frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
QJsonObject *jsonObj2 = channelSettingsResponse.asJsonObject();
double channelOffset;
if (WebAPIUtils::getSubObjectDouble(*jsonObj2, "inputFrequencyOffset", channelOffset))
{
frequency = deviceFreq + channelOffset;
}
else
{
qWarning("RigCtlServerWorker::setFrequency: No inputFrequencyOffset key in channel settings");
rigCtlRC = RIG_ENIMPL;
return false;
}
}
else
{
qWarning("RigCtlServerWorker::setFrequency: no centerFrequency key in device settings");
rigCtlRC = RIG_ENIMPL;
return false;
}
rigCtlRC = RIG_OK;
return true;
}
bool RigCtlServerWorker::changeModem(const char *newMode, const char *newModemId, int newModemBw, rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceSet deviceSetResponse;
SWGSDRangel::SWGSuccessResponse successResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC;
int nbChannels;
int currentOffset;
// get deviceset details to get the number of channel modems
httpRC = m_webAPIAdapterInterface->devicesetGet(
m_settings.m_deviceIndex,
deviceSetResponse,
errorResponse
);
if (httpRC/100 != 2) // error
{
qWarning("RigCtlServerWorker::changeModem: deevice set get information error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
if (!WebAPIUtils::getObjectInt(*deviceSetResponse.asJsonObject(), "channelcount", nbChannels))
{
qWarning("RigCtlServerWorker::changeModem: no channelcount key in device set information");
rigCtlRC = RIG_ENIMPL;
return false;
}
QList<QJsonObject> channelObjects;
if (!WebAPIUtils::getObjectObjects(*deviceSetResponse.asJsonObject(), "channels", channelObjects))
{
qWarning("RigCtlServerWorker::changeModem: no channels key in device set information");
rigCtlRC = RIG_ENIMPL;
return false;
}
if (m_settings.m_channelIndex < channelObjects.size())
{
const QJsonObject& channelInfo = channelObjects.at(m_settings.m_channelIndex);
if (!WebAPIUtils::getObjectInt(channelInfo, "deltaFrequency", currentOffset))
{
qWarning("RigCtlServerWorker::changeModem: no deltaFrequency key in device set channel information");
rigCtlRC = RIG_ENIMPL;
return false;
}
}
else
{
qWarning("RigCtlServerWorker::changeModem: channel not found in device set channels information");
rigCtlRC = RIG_ENIMPL;
return false;
}
// delete current modem
httpRC = m_webAPIAdapterInterface->devicesetChannelDelete(
m_settings.m_deviceIndex,
m_settings.m_channelIndex,
successResponse,
errorResponse
);
if (httpRC/100 != 2) // error
{
qWarning("RigCtlServerWorker::changeModem: delete channel error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
// create new modem
SWGSDRangel::SWGChannelSettings query;
QString newModemIdStr(newModemId);
bool lsb = !strncmp(newMode, "LSB", 3);
query.init();
query.setChannelType(new QString(newModemIdStr));
query.setDirection(0);
httpRC = m_webAPIAdapterInterface->devicesetChannelPost(
m_settings.m_deviceIndex,
query,
successResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::changeModem: create channel error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
// wait for channel switchover
QEventLoop loop;
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer->start(200);
loop.exec();
delete timer;
// when a new channel is created it is put last in the list
qDebug("RigCtlServerWorker::changeModem: created %s at %d", newModemId, nbChannels-1);
if (m_msgQueueToFeature)
{
RigCtlServerSettings::MsgChannelIndexChange *msg = RigCtlServerSettings::MsgChannelIndexChange::create(nbChannels-1);
m_msgQueueToFeature->push(msg);
}
SWGSDRangel::SWGChannelSettings swgChannelSettings;
QStringList channelSettingsKeys;
channelSettingsKeys.append("inputFrequencyOffset");
QString jsonSettingsStr = tr("\"inputFrequencyOffset\":%1").arg(currentOffset);
if (!strncmp(newMode, "LSB", 3))
{
int bw = newModemBw < 0 ? -3000 : -newModemBw;
}
if (lsb || (newModemBw >= 0))
{
int bw = lsb ? (newModemBw < 0 ? -3000 : -newModemBw) : newModemBw;
channelSettingsKeys.append("rfBandwidth");
jsonSettingsStr.append(tr(",\"rfBandwidth\":%2").arg(bw));
}
QString jsonStr = tr("{ \"channelType\": \"%1\", \"%2Settings\": {%3}}")
.arg(QString(newModemId))
.arg(QString(newModemId))
.arg(jsonSettingsStr);
swgChannelSettings.fromJson(jsonStr);
httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsPutPatch(
m_settings.m_deviceIndex,
nbChannels-1, // new index
false, // PATCH
channelSettingsKeys,
swgChannelSettings,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::changeModem: set channel settings error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
rigCtlRC = RIG_OK;
return true;
}
bool RigCtlServerWorker::getMode(const char **mode, double& passband, rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC;
// Get channel settings containg inputFrequencyOffset, so we can patch them
httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsGet(
m_settings.m_deviceIndex,
m_settings.m_channelIndex,
channelSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::getModem: get channel settings error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
QJsonObject *jsonObj = channelSettingsResponse.asJsonObject();
QString channelType;
int i;
if (!WebAPIUtils::getObjectString(*jsonObj, "channelType", channelType))
{
qWarning("RigCtlServerWorker::getModem: no channelType key in channel settings");
rigCtlRC = RIG_ENIMPL;
return false;
}
for (i = 0; m_modeMap[i].mode != nullptr; i++)
{
if (!channelType.compare(m_modeMap[i].modem))
{
*mode = m_modeMap[i].mode;
break;
}
}
if (!m_modeMap[i].mode)
{
qWarning("RigCtlServerWorker::getModem: channel type not implemented: %s", qPrintable(channelType));
rigCtlRC = RIG_ENIMPL;
return false;
}
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "rfBandwidth", passband))
{
if (!channelType.compare("SSBDemod"))
{
if (passband < 0) { // LSB
passband = -passband;
} else { // USB
*mode = m_modeMap[i+1].mode;
}
}
}
else
{
passband = -1;
}
rigCtlRC = RIG_OK;
return true;
}
bool RigCtlServerWorker::setPowerOn(rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceState response;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC = m_webAPIAdapterInterface->devicesetDeviceRunPost(
m_settings.m_deviceIndex,
response,
errorResponse
);
if (httpRC/100 == 2)
{
qDebug("RigCtlServerWorker::setPowerOn: set device start OK");
rigCtlRC = RIG_OK;
return true;
}
else
{
qWarning("RigCtlServerWorker::setPowerOn: set device start error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
}
bool RigCtlServerWorker::setPowerOff(rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceState response;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC = m_webAPIAdapterInterface->devicesetDeviceRunDelete(
m_settings.m_deviceIndex,
response,
errorResponse
);
if (httpRC/100 == 2)
{
qDebug("RigCtlServerWorker::setPowerOff: set device stop OK");
rigCtlRC = RIG_OK;
return true;
}
else
{
qWarning("RigCtlServerWorker::setPowerOff: set device stop error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
}
bool RigCtlServerWorker::getPower(bool& power, rig_errcode_e& rigCtlRC)
{
SWGSDRangel::SWGDeviceState response;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC = m_webAPIAdapterInterface->devicesetDeviceRunGet(
m_settings.m_deviceIndex,
response,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("RigCtlServerWorker::getPower: get device run state error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
QJsonObject *jsonObj = response.asJsonObject();
QString state;
if (WebAPIUtils::getObjectString(*jsonObj, "state", state))
{
qDebug("RigCtlServerWorker::getPower: run state is %s", qPrintable(state));
if (state.compare("running")) {
power = false; // not equal
} else {
power = true; // equal
}
}
else
{
qWarning("RigCtlServerWorker::getPower: get device run state error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
rigCtlRC = RIG_EINVAL;
return false;
}
return true;
}

View File

@ -0,0 +1,145 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// 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_RIGCTLSERVERWORKER_H_
#define INCLUDE_FEATURE_RIGCTLSERVERWORKER_H_
#include <QObject>
#include <QTimer>
#include "util/message.h"
#include "util/messagequeue.h"
#include "rigctlserversettings.h"
class WebAPIAdapterInterface;
class QTcpServer;
class QTcpSocket;
class RigCtlServerWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureRigCtlServerWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RigCtlServerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRigCtlServerWorker* create(const RigCtlServerSettings& settings, bool force)
{
return new MsgConfigureRigCtlServerWorker(settings, force);
}
private:
RigCtlServerSettings m_settings;
bool m_force;
MsgConfigureRigCtlServerWorker(const RigCtlServerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
struct ModeDemod {
const char *mode;
const char *modem;
};
RigCtlServerWorker(WebAPIAdapterInterface *webAPIAdapterInterface);
~RigCtlServerWorker();
void reset();
bool startWork();
void stopWork();
bool isRunning() const { return m_running; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToFeature(MessageQueue *messageQueue) { m_msgQueueToFeature = messageQueue; }
private:
enum RigCtrlState
{
idle,
set_freq, set_freq_no_offset, set_freq_center, set_freq_center_no_offset, set_freq_set_offset, set_freq_offset,
get_freq_center, get_freq_offset,
set_mode_mod, set_mode_settings, set_mode_reply,
get_power,
set_power_on, set_power_off
};
// Hamlib rigctrl error codes
enum rig_errcode_e {
RIG_OK = 0, /*!< No error, operation completed successfully */
RIG_EINVAL = -1, /*!< invalid parameter */
RIG_ECONF = -2, /*!< invalid configuration (serial,..) */
RIG_ENOMEM = -3, /*!< memory shortage */
RIG_ENIMPL = -4, /*!< function not implemented, but will be */
RIG_ETIMEOUT = -5, /*!< communication timed out */
RIG_EIO = -6, /*!< IO error, including open failed */
RIG_EINTERNAL = -7, /*!< Internal Hamlib error, huh! */
RIG_EPROTO = -8, /*!< Protocol error */
RIG_ERJCTED = -9, /*!< Command rejected by the rig */
RIG_ETRUNC = -10, /*!< Command performed, but arg truncated */
RIG_ENAVAIL = -11, /*!< function not available */
RIG_ENTARGET = -12, /*!< VFO not targetable */
RIG_BUSERROR = -13, /*!< Error talking on the bus */
RIG_BUSBUSY = -14, /*!< Collision on the bus */
RIG_EARG = -15, /*!< NULL RIG handle or any invalid pointer parameter in get arg */
RIG_EVFO = -16, /*!< Invalid VFO */
RIG_EDOM = -17 /*!< Argument out of domain of func */
};
RigCtrlState m_state;
double m_targetFrequency;
double m_targetOffset;
QString m_targetModem;
int m_targetBW;
QTcpServer *m_tcpServer;
QTcpSocket *m_clientConnection;
WebAPIAdapterInterface *m_webAPIAdapterInterface;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object
RigCtlServerSettings m_settings;
bool m_running;
QMutex m_mutex;
static const unsigned int m_CmdLength;
static const unsigned int m_ResponseLength;
static const ModeDemod m_modeMap[];
bool handleMessage(const Message& cmd);
void applySettings(const RigCtlServerSettings& settings, bool force = false);
void restartServer(bool enabled, uint32_t port);
bool setFrequency(double frequency, rig_errcode_e& rigCtlRC);
bool getFrequency(double& frequency, rig_errcode_e& rigCtlRC);
bool changeModem(const char *newMode, const char *newModemId, int newModemBw, rig_errcode_e& rigCtlRC);
bool getMode(const char **mode, double& frequency, rig_errcode_e& rigCtlRC);
bool setPowerOn(rig_errcode_e& rigCtlRC);
bool setPowerOff(rig_errcode_e& rigCtlRC);
bool getPower(bool& power, rig_errcode_e& rigCtlRC);
private slots:
void handleInputMessages();
void acceptConnection();
void getCommand();
};
#endif // INCLUDE_FEATURE_RIGCTLSERVERWORKER_H_