mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-02 22:14:45 -04:00
New Jogdial Controller feature plugin. Implements #1088
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
project(jogdialcontroller)
|
||||
|
||||
set(jogdialcontroller_SOURCES
|
||||
jogdialcontroller.cpp
|
||||
jogdialcontrollersettings.cpp
|
||||
jogdialcontrollerplugin.cpp
|
||||
jogdialcontrollerwebapiadapter.cpp
|
||||
)
|
||||
|
||||
set(jogdialcontroller_HEADERS
|
||||
jogdialcontroller.h
|
||||
jogdialcontrollersettings.h
|
||||
jogdialcontrollerplugin.h
|
||||
jogdialcontrollerwebapiadapter.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
set(jogdialcontroller_SOURCES
|
||||
${jogdialcontroller_SOURCES}
|
||||
jogdialcontrollergui.cpp
|
||||
jogdialcontrollergui.ui
|
||||
)
|
||||
set(jogdialcontroller_HEADERS
|
||||
${jogdialcontroller_HEADERS}
|
||||
jogdialcontrollergui.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME jogdialcontroller)
|
||||
set(TARGET_LIB "Qt5::Widgets")
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
|
||||
else()
|
||||
set(TARGET_NAME jogdialcontrollersrv)
|
||||
set(TARGET_LIB "")
|
||||
set(TARGET_LIB_GUI "")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
|
||||
endif()
|
||||
|
||||
add_library(${TARGET_NAME} SHARED
|
||||
${jogdialcontroller_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
||||
@@ -0,0 +1,648 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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 "SWGFeatureActions.h"
|
||||
#include "SWGDeviceState.h"
|
||||
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspdevicesourceengine.h"
|
||||
#include "dsp/dspdevicesinkengine.h"
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/devicesamplesink.h"
|
||||
#include "device/deviceset.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "commands/commandkeyreceiver.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "jogdialcontroller.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgConfigureJogdialController, Message)
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgRefreshChannels, Message)
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgReportChannels, Message)
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgReportControl, Message)
|
||||
MESSAGE_CLASS_DEFINITION(JogdialController::MsgSelectChannel, Message)
|
||||
|
||||
const char* const JogdialController::m_featureIdURI = "sdrangel.feature.jogdialcontroller";
|
||||
const char* const JogdialController::m_featureId = "JogdialController";
|
||||
|
||||
JogdialController::JogdialController(WebAPIAdapterInterface *webAPIAdapterInterface) :
|
||||
Feature(m_featureIdURI, webAPIAdapterInterface),
|
||||
m_selectedDevice(nullptr),
|
||||
m_selectedChannel(nullptr),
|
||||
m_selectedIndex(-1),
|
||||
m_deviceElseChannelControl(true),
|
||||
m_multiplier(1)
|
||||
{
|
||||
qDebug("JogdialController::JogdialController: webAPIAdapterInterface: %p", webAPIAdapterInterface);
|
||||
setObjectName(m_featureId);
|
||||
m_state = StIdle;
|
||||
m_errorMessage = "JogdialController error";
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
connect(&m_repeatTimer, SIGNAL(timeout()), this, SLOT(handleRepeat()));
|
||||
}
|
||||
|
||||
JogdialController::~JogdialController()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
void JogdialController::start()
|
||||
{
|
||||
qDebug("JogdialController::start");
|
||||
m_state = StRunning;
|
||||
}
|
||||
|
||||
void JogdialController::stop()
|
||||
{
|
||||
qDebug("JogdialController::stop");
|
||||
m_state = StIdle;
|
||||
}
|
||||
|
||||
bool JogdialController::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureJogdialController::match(cmd))
|
||||
{
|
||||
MsgConfigureJogdialController& cfg = (MsgConfigureJogdialController&) cmd;
|
||||
qDebug() << "JogdialController::handleMessage: MsgConfigureJogdialController";
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgStartStop::match(cmd))
|
||||
{
|
||||
MsgStartStop& cfg = (MsgStartStop&) cmd;
|
||||
qDebug() << "JogdialController::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
|
||||
|
||||
if (cfg.getStartStop()) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgRefreshChannels::match(cmd))
|
||||
{
|
||||
qDebug() << "JogdialController::handleMessage: MsgRefreshChannels";
|
||||
updateChannels();
|
||||
return true;
|
||||
}
|
||||
else if (MsgSelectChannel::match(cmd))
|
||||
{
|
||||
MsgSelectChannel& cfg = (MsgSelectChannel&) cmd;
|
||||
int index = cfg.getIndex();
|
||||
|
||||
if ((index >= 0) && (index < m_availableChannels.size()))
|
||||
{
|
||||
DeviceAPI *selectedDevice = m_availableChannels[cfg.getIndex()].m_deviceAPI;
|
||||
ChannelAPI *selectedChannel = m_availableChannels[cfg.getIndex()].m_channelAPI;
|
||||
QString channelId;
|
||||
selectedChannel->getIdentifier(channelId);
|
||||
qDebug() << "JogdialController::handleMessage: MsgSelectChannel"
|
||||
<< "device:" << selectedDevice->getHardwareId()
|
||||
<< "channel:" << channelId;
|
||||
m_selectedDevice = selectedDevice;
|
||||
m_selectedChannel = selectedChannel;
|
||||
m_selectedIndex = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning("JogdialController::handleMessage: MsgSelectChannel: index out of range: %d", index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray JogdialController::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool JogdialController::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
MsgConfigureJogdialController *msg = MsgConfigureJogdialController::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
MsgConfigureJogdialController *msg = MsgConfigureJogdialController::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::applySettings(const JogdialControllerSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "JogdialController::applySettings:"
|
||||
<< " 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_title != settings.m_title) || force) {
|
||||
reverseAPIKeys.append("title");
|
||||
}
|
||||
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
|
||||
reverseAPIKeys.append("rgbColor");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void JogdialController::updateChannels()
|
||||
{
|
||||
MainCore *mainCore = MainCore::instance();
|
||||
// MessagePipes& messagePipes = mainCore->getMessagePipes();
|
||||
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
|
||||
std::vector<DeviceSet*>::const_iterator it = deviceSets.begin();
|
||||
m_availableChannels.clear();
|
||||
|
||||
int deviceIndex = 0;
|
||||
|
||||
for (; it != deviceSets.end(); ++it, deviceIndex++)
|
||||
{
|
||||
DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine;
|
||||
DSPDeviceSinkEngine *deviceSinkEngine = (*it)->m_deviceSinkEngine;
|
||||
DeviceAPI *device = (*it)->m_deviceAPI;
|
||||
device->getHardwareId();
|
||||
|
||||
if (deviceSourceEngine || deviceSinkEngine)
|
||||
{
|
||||
// DeviceSampleSource *deviceSource = deviceSourceEngine->getSource();
|
||||
// quint64 deviceCenterFrequency = deviceSource->getCenterFrequency();
|
||||
// int basebandSampleRate = deviceSource->getSampleRate();
|
||||
|
||||
for (int chi = 0; chi < (*it)->getNumberOfChannels(); chi++)
|
||||
{
|
||||
ChannelAPI *channel = (*it)->getChannelAt(chi);
|
||||
QString channelId;
|
||||
channel->getIdentifier(channelId);
|
||||
JogdialControllerSettings::AvailableChannel availableChannel =
|
||||
JogdialControllerSettings::AvailableChannel{
|
||||
deviceSinkEngine != nullptr,
|
||||
deviceIndex,
|
||||
chi,
|
||||
device,
|
||||
channel,
|
||||
device->getHardwareId(),
|
||||
channelId
|
||||
};
|
||||
m_availableChannels.push_back(availableChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportChannels *msgToGUI = MsgReportChannels::create();
|
||||
QList<JogdialControllerSettings::AvailableChannel>& msgAvailableChannels = msgToGUI->getAvailableChannels();
|
||||
msgAvailableChannels = m_availableChannels;
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::channelUp()
|
||||
{
|
||||
if ((m_selectedIndex < 0) || (m_availableChannels.size() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_selectedIndex++;
|
||||
|
||||
if (m_selectedIndex >= m_availableChannels.size()) {
|
||||
m_selectedIndex = 0;
|
||||
}
|
||||
|
||||
m_selectedDevice = m_availableChannels.at(m_selectedIndex).m_deviceAPI;
|
||||
m_selectedChannel = m_availableChannels.at(m_selectedIndex).m_channelAPI;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgSelectChannel *msgToGUI = MsgSelectChannel::create(m_selectedIndex);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::channelDown()
|
||||
{
|
||||
if ((m_selectedIndex < 0) || (m_availableChannels.size() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_selectedIndex--;
|
||||
|
||||
if (m_selectedIndex < 0) {
|
||||
m_selectedIndex = m_availableChannels.size() - 1;
|
||||
}
|
||||
|
||||
m_selectedDevice = m_availableChannels.at(m_selectedIndex).m_deviceAPI;
|
||||
m_selectedChannel = m_availableChannels.at(m_selectedIndex).m_channelAPI;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgSelectChannel *msgToGUI = MsgSelectChannel::create(m_selectedIndex);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
|
||||
int JogdialController::webapiRun(bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
getFeatureStateStr(*response.getState());
|
||||
MsgStartStop *msg = MsgStartStop::create(run);
|
||||
getInputMessageQueue()->push(msg);
|
||||
return 202;
|
||||
}
|
||||
|
||||
int JogdialController::webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setJogdialControllerSettings(new SWGSDRangel::SWGJogdialControllerSettings());
|
||||
response.getJogdialControllerSettings()->init();
|
||||
webapiFormatFeatureSettings(response, m_settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
int JogdialController::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
JogdialControllerSettings settings = m_settings;
|
||||
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
|
||||
|
||||
MsgConfigureJogdialController *msg = MsgConfigureJogdialController::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
qDebug("JogdialController::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgConfigureJogdialController *msgToGUI = MsgConfigureJogdialController::create(settings, force);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
webapiFormatFeatureSettings(response, settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
void JogdialController::webapiFormatFeatureSettings(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
const JogdialControllerSettings& settings)
|
||||
{
|
||||
if (response.getJogdialControllerSettings()->getTitle()) {
|
||||
*response.getJogdialControllerSettings()->getTitle() = settings.m_title;
|
||||
} else {
|
||||
response.getJogdialControllerSettings()->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
|
||||
response.getJogdialControllerSettings()->setRgbColor(settings.m_rgbColor);
|
||||
response.getJogdialControllerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
|
||||
|
||||
if (response.getJogdialControllerSettings()->getReverseApiAddress()) {
|
||||
*response.getJogdialControllerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
|
||||
} else {
|
||||
response.getJogdialControllerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
|
||||
}
|
||||
|
||||
response.getJogdialControllerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
|
||||
response.getJogdialControllerSettings()->setReverseApiFeatureSetIndex(settings.m_reverseAPIFeatureSetIndex);
|
||||
response.getJogdialControllerSettings()->setReverseApiFeatureIndex(settings.m_reverseAPIFeatureIndex);
|
||||
}
|
||||
|
||||
void JogdialController::webapiUpdateFeatureSettings(
|
||||
JogdialControllerSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response)
|
||||
{
|
||||
if (featureSettingsKeys.contains("title")) {
|
||||
settings.m_title = *response.getJogdialControllerSettings()->getTitle();
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor")) {
|
||||
settings.m_rgbColor = response.getJogdialControllerSettings()->getRgbColor();
|
||||
}
|
||||
if (featureSettingsKeys.contains("useReverseAPI")) {
|
||||
settings.m_useReverseAPI = response.getJogdialControllerSettings()->getUseReverseApi() != 0;
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIAddress")) {
|
||||
settings.m_reverseAPIAddress = *response.getJogdialControllerSettings()->getReverseApiAddress();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIPort")) {
|
||||
settings.m_reverseAPIPort = response.getJogdialControllerSettings()->getReverseApiPort();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIFeatureSetIndex")) {
|
||||
settings.m_reverseAPIFeatureSetIndex = response.getJogdialControllerSettings()->getReverseApiFeatureSetIndex();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIFeatureIndex")) {
|
||||
settings.m_reverseAPIFeatureIndex = response.getJogdialControllerSettings()->getReverseApiFeatureIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const JogdialControllerSettings& settings, bool force)
|
||||
{
|
||||
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
|
||||
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
|
||||
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
|
||||
swgFeatureSettings->setFeatureType(new QString("JogdialAnalyzer"));
|
||||
swgFeatureSettings->setJogdialControllerSettings(new SWGSDRangel::SWGJogdialControllerSettings());
|
||||
SWGSDRangel::SWGJogdialControllerSettings *swgJogdialControllerSettings = swgFeatureSettings->getJogdialControllerSettings();
|
||||
|
||||
// transfer data that has been modified. When force is on transfer all data except reverse API data
|
||||
|
||||
if (featureSettingsKeys.contains("title") || force) {
|
||||
swgJogdialControllerSettings->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor") || force) {
|
||||
swgJogdialControllerSettings->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 JogdialController::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
QNetworkReply::NetworkError replyError = reply->error();
|
||||
|
||||
if (replyError)
|
||||
{
|
||||
qWarning() << "JogdialController::networkManagerFinished:"
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("JogdialController::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void JogdialController::handleChannelMessageQueue(MessageQueue* messageQueue)
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = messageQueue->pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::commandKeyPressed(Qt::Key key, Qt::KeyboardModifiers keyModifiers, bool release)
|
||||
{
|
||||
(void) release;
|
||||
|
||||
if (key == Qt::Key_C)
|
||||
{
|
||||
m_deviceElseChannelControl = false;
|
||||
|
||||
if (m_guiMessageQueue) {
|
||||
m_guiMessageQueue->push(MsgReportControl::create(false));
|
||||
}
|
||||
}
|
||||
else if (key == Qt::Key_D)
|
||||
{
|
||||
m_deviceElseChannelControl = true;
|
||||
|
||||
if (m_guiMessageQueue) {
|
||||
m_guiMessageQueue->push(MsgReportControl::create(true));
|
||||
}
|
||||
}
|
||||
else if (key == Qt::Key_Left)
|
||||
{
|
||||
channelDown();
|
||||
}
|
||||
else if (key == Qt::Key_Right)
|
||||
{
|
||||
channelUp();
|
||||
}
|
||||
else if (key == Qt::Key_Up)
|
||||
{
|
||||
m_repeatTimer.stop();
|
||||
|
||||
if (keyModifiers == Qt::NoModifier) {
|
||||
stepFrequency(1);
|
||||
} else if (keyModifiers == Qt::ControlModifier) {
|
||||
stepFrequency(10);
|
||||
} else if (keyModifiers == Qt::ShiftModifier) {
|
||||
stepFrequency(100);
|
||||
} else if (keyModifiers == (Qt::ControlModifier | Qt::ShiftModifier)) {
|
||||
stepFrequency(1000);
|
||||
}
|
||||
}
|
||||
else if (key == Qt::Key_Down)
|
||||
{
|
||||
m_repeatTimer.stop();
|
||||
|
||||
if (keyModifiers == Qt::NoModifier) {
|
||||
stepFrequency(-1);
|
||||
} else if (keyModifiers == Qt::ControlModifier) {
|
||||
stepFrequency(-10);
|
||||
} else if (keyModifiers == Qt::ShiftModifier) {
|
||||
stepFrequency(-100);
|
||||
} else if (keyModifiers == (Qt::ControlModifier | Qt::ShiftModifier)) {
|
||||
stepFrequency(-1000);
|
||||
}
|
||||
}
|
||||
else if (key == Qt::Key_Home)
|
||||
{
|
||||
resetChannelFrequency();
|
||||
}
|
||||
else if (key == Qt::Key_0)
|
||||
{
|
||||
m_repeatTimer.stop();
|
||||
}
|
||||
else if (key == Qt::Key_1)
|
||||
{
|
||||
m_multiplier = 1;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_2)
|
||||
{
|
||||
m_multiplier = 10;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_3)
|
||||
{
|
||||
m_multiplier = 100;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_4)
|
||||
{
|
||||
m_multiplier = 1000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_5)
|
||||
{
|
||||
m_multiplier = 10000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_6)
|
||||
{
|
||||
m_multiplier = 100000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_7)
|
||||
{
|
||||
m_multiplier = 1000000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_Exclam)
|
||||
{
|
||||
m_multiplier = -1;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_At)
|
||||
{
|
||||
m_multiplier = -10;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_NumberSign)
|
||||
{
|
||||
m_multiplier = -100;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_Dollar)
|
||||
{
|
||||
m_multiplier = -1000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_Percent)
|
||||
{
|
||||
m_multiplier = -10000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if ((key == Qt::Key_Dead_Circumflex) || (key == Qt::Key_AsciiCircum))
|
||||
{
|
||||
m_multiplier = -100000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
else if (key == Qt::Key_Ampersand)
|
||||
{
|
||||
m_multiplier = -1000000;
|
||||
m_repeatTimer.start(m_repeatms);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::resetChannelFrequency()
|
||||
{
|
||||
if (m_selectedChannel) {
|
||||
m_selectedChannel->setCenterFrequency(0);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::stepFrequency(int step)
|
||||
{
|
||||
qDebug("JogdialController::stepFrequency: step: %d", step);
|
||||
if (m_deviceElseChannelControl)
|
||||
{
|
||||
if (m_selectedDevice)
|
||||
{
|
||||
DSPDeviceSourceEngine *sourceEngine = m_selectedDevice->getDeviceSourceEngine();
|
||||
DSPDeviceSinkEngine *sinkEngine = m_selectedDevice->getDeviceSinkEngine();
|
||||
|
||||
if (sourceEngine)
|
||||
{
|
||||
quint64 frequency = sourceEngine->getSource()->getCenterFrequency();
|
||||
qDebug("JogdialController::stepFrequency: frequency: %llu", frequency);
|
||||
sourceEngine->getSource()->setCenterFrequency(frequency + step*1000LL);
|
||||
}
|
||||
|
||||
if (sinkEngine)
|
||||
{
|
||||
quint64 frequency = sinkEngine->getSink()->getCenterFrequency();
|
||||
sinkEngine->getSink()->setCenterFrequency(frequency + step*1000LL);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_selectedChannel)
|
||||
{
|
||||
qint64 frequency = m_selectedChannel->getCenterFrequency();
|
||||
m_selectedChannel->setCenterFrequency(frequency + step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialController::handleRepeat()
|
||||
{
|
||||
stepFrequency(m_multiplier);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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_JOGDIALCONTROLLER_H_
|
||||
#define INCLUDE_FEATURE_JOGDIALCONTROLLER_H_
|
||||
|
||||
#include <QList>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
|
||||
#include "feature/feature.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "jogdialcontrollersettings.h"
|
||||
|
||||
class WebAPIAdapterInterface;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace SWGSDRangel {
|
||||
class SWGDeviceState;
|
||||
}
|
||||
|
||||
class JogdialController : public Feature
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureJogdialController : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const JogdialControllerSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureJogdialController* create(const JogdialControllerSettings& settings, bool force) {
|
||||
return new MsgConfigureJogdialController(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
JogdialControllerSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureJogdialController(const JogdialControllerSettings& 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)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgRefreshChannels : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
static MsgRefreshChannels* create() {
|
||||
return new MsgRefreshChannels();
|
||||
}
|
||||
|
||||
protected:
|
||||
MsgRefreshChannels() :
|
||||
Message()
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportChannels : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QList<JogdialControllerSettings::AvailableChannel>& getAvailableChannels() { return m_availableChannels; }
|
||||
|
||||
static MsgReportChannels* create() {
|
||||
return new MsgReportChannels();
|
||||
}
|
||||
|
||||
private:
|
||||
QList<JogdialControllerSettings::AvailableChannel> m_availableChannels;
|
||||
|
||||
MsgReportChannels() :
|
||||
Message()
|
||||
{}
|
||||
};
|
||||
|
||||
class MsgSelectChannel : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getIndex() const { return m_index; }
|
||||
static MsgSelectChannel* create(int index) {
|
||||
return new MsgSelectChannel(index);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_index;
|
||||
|
||||
MsgSelectChannel(int index) :
|
||||
Message(),
|
||||
m_index(index)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportControl : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getDeviceElseChannel() const { return m_deviceElseChannel; }
|
||||
|
||||
static MsgReportControl* create(bool deviceElseChannel) {
|
||||
return new MsgReportControl(deviceElseChannel);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_deviceElseChannel;
|
||||
|
||||
MsgReportControl(bool deviceElseChannel) :
|
||||
Message(),
|
||||
m_deviceElseChannel(deviceElseChannel)
|
||||
{ }
|
||||
};
|
||||
|
||||
JogdialController(WebAPIAdapterInterface *webAPIAdapterInterface);
|
||||
virtual ~JogdialController();
|
||||
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);
|
||||
|
||||
void resetChannelFrequency();
|
||||
void stepFrequency(int step);
|
||||
|
||||
static void webapiFormatFeatureSettings(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
const JogdialControllerSettings& settings);
|
||||
|
||||
static void webapiUpdateFeatureSettings(
|
||||
JogdialControllerSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response);
|
||||
|
||||
static const char* const m_featureIdURI;
|
||||
static const char* const m_featureId;
|
||||
|
||||
public slots:
|
||||
void commandKeyPressed(Qt::Key key, Qt::KeyboardModifiers keyModifiers, bool release);
|
||||
|
||||
private:
|
||||
JogdialControllerSettings m_settings;
|
||||
QList<JogdialControllerSettings::AvailableChannel> m_availableChannels;
|
||||
DeviceAPI *m_selectedDevice;
|
||||
ChannelAPI *m_selectedChannel;
|
||||
int m_selectedIndex;
|
||||
bool m_deviceElseChannelControl;
|
||||
int m_multiplier;
|
||||
QTimer m_repeatTimer;
|
||||
static const int m_repeatms = 100;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void applySettings(const JogdialControllerSettings& settings, bool force = false);
|
||||
void updateChannels();
|
||||
void channelUp();
|
||||
void channelDown();
|
||||
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const JogdialControllerSettings& settings, bool force);
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
void handleChannelMessageQueue(MessageQueue *messageQueues);
|
||||
void handleRepeat();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_DEMODANALYZER_H_
|
||||
@@ -0,0 +1,362 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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 <QMouseEvent>
|
||||
|
||||
#include "feature/featureuiset.h"
|
||||
#include "gui/basicfeaturesettingsdialog.h"
|
||||
#include "device/deviceset.h"
|
||||
#include "util/db.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "ui_jogdialcontrollergui.h"
|
||||
#include "jogdialcontroller.h"
|
||||
#include "jogdialcontrollergui.h"
|
||||
|
||||
JogdialControllerGUI* JogdialControllerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
|
||||
{
|
||||
JogdialControllerGUI* gui = new JogdialControllerGUI(pluginAPI, featureUISet, feature);
|
||||
return gui;
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::resetToDefaults()
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
QByteArray JogdialControllerGUI::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool JogdialControllerGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool JogdialControllerGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (JogdialController::MsgConfigureJogdialController::match(message))
|
||||
{
|
||||
qDebug("JogdialControllerGUI::handleMessage: JogdialController::MsgConfigureJogdialController");
|
||||
const JogdialController::MsgConfigureJogdialController& cfg = (JogdialController::MsgConfigureJogdialController&) message;
|
||||
m_settings = cfg.getSettings();
|
||||
blockApplySettings(true);
|
||||
displaySettings();
|
||||
blockApplySettings(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (JogdialController::MsgReportChannels::match(message))
|
||||
{
|
||||
qDebug("JogdialControllerGUI::handleMessage: JogdialController::MsgReportChannels");
|
||||
JogdialController::MsgReportChannels& report = (JogdialController::MsgReportChannels&) message;
|
||||
m_availableChannels = report.getAvailableChannels();
|
||||
updateChannelList();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (JogdialController::MsgReportControl::match(message))
|
||||
{
|
||||
qDebug("JogdialControllerGUI::handleMessage: JogdialController::MsgReportControl");
|
||||
JogdialController::MsgReportControl& report = (JogdialController::MsgReportControl&) message;
|
||||
ui->controlLabel->setText(report.getDeviceElseChannel() ? "D" : "C");
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (JogdialController::MsgSelectChannel::match(message))
|
||||
{
|
||||
qDebug("JogdialControllerGUI::handleMessage: JogdialController::MsgSelectChannel");
|
||||
JogdialController::MsgSelectChannel& report = (JogdialController::MsgSelectChannel&) message;
|
||||
int index = report.getIndex();
|
||||
|
||||
if ((index >= 0) && (index < m_availableChannels.size()))
|
||||
{
|
||||
ui->channels->blockSignals(true);
|
||||
ui->channels->setCurrentIndex(index);
|
||||
ui->channels->blockSignals(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = getInputMessageQueue()->pop()))
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||
{
|
||||
(void) widget;
|
||||
(void) rollDown;
|
||||
|
||||
m_settings.m_rollupState = saveState();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
JogdialControllerGUI::JogdialControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
|
||||
FeatureGUI(parent),
|
||||
ui(new Ui::JogdialControllerGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_featureUISet(featureUISet),
|
||||
m_doApplySettings(true),
|
||||
m_lastFeatureState(0),
|
||||
m_selectedChannel(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
m_helpURL = "plugins/feature/jogdialcontroller/readme.md";
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setChannelWidget(false);
|
||||
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
||||
|
||||
m_jogdialController = reinterpret_cast<JogdialController*>(feature);
|
||||
m_jogdialController->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);
|
||||
|
||||
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
|
||||
this->installEventFilter(&m_commandKeyReceiver);
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
JogdialControllerGUI::~JogdialControllerGUI()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::blockApplySettings(bool block)
|
||||
{
|
||||
m_doApplySettings = !block;
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::displaySettings()
|
||||
{
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
setWindowTitle(m_settings.m_title);
|
||||
blockApplySettings(true);
|
||||
restoreState(m_settings.m_rollupState);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::updateChannelList()
|
||||
{
|
||||
ui->channels->blockSignals(true);
|
||||
ui->channels->clear();
|
||||
|
||||
QList<JogdialControllerSettings::AvailableChannel>::const_iterator it = m_availableChannels.begin();
|
||||
int selectedItem = -1;
|
||||
|
||||
for (int i = 0; it != m_availableChannels.end(); ++it, i++)
|
||||
{
|
||||
ui->channels->addItem(tr("%1%2:%3 %4")
|
||||
.arg(it->m_tx ? "T" : "R")
|
||||
.arg(it->m_deviceSetIndex)
|
||||
.arg(it->m_channelIndex)
|
||||
.arg(it->m_channelId)
|
||||
);
|
||||
|
||||
if (it->m_channelAPI == m_selectedChannel) {
|
||||
selectedItem = i;
|
||||
}
|
||||
}
|
||||
|
||||
int currentSelectedChannelIndex = ui->channels->currentIndex();
|
||||
ui->channels->blockSignals(false);
|
||||
|
||||
if (m_availableChannels.size() > 0)
|
||||
{
|
||||
if (selectedItem >= 0) {
|
||||
ui->channels->setCurrentIndex(selectedItem);
|
||||
} else {
|
||||
ui->channels->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSelectedChannelIndex == ui->channels->currentIndex()) {
|
||||
on_channels_currentIndexChanged(ui->channels->currentIndex()); // force sending
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::leaveEvent(QEvent*)
|
||||
{
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::enterEvent(QEvent*)
|
||||
{
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::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 JogdialControllerGUI::on_startStop_toggled(bool checked)
|
||||
{
|
||||
if (checked)
|
||||
{
|
||||
setFocus();
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
connect(&m_commandKeyReceiver, SIGNAL(capturedKey(Qt::Key, Qt::KeyboardModifiers, bool)),
|
||||
m_jogdialController, SLOT(commandKeyPressed(Qt::Key, Qt::KeyboardModifiers, bool)));
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect(&m_commandKeyReceiver, SIGNAL(capturedKey(Qt::Key, Qt::KeyboardModifiers, bool)),
|
||||
m_jogdialController, SLOT(commandKeyPressed(Qt::Key, Qt::KeyboardModifiers, bool)));
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
clearFocus();
|
||||
}
|
||||
|
||||
JogdialController::MsgStartStop *message = JogdialController::MsgStartStop::create(checked);
|
||||
m_jogdialController->getInputMessageQueue()->push(message);
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::on_devicesRefresh_clicked()
|
||||
{
|
||||
JogdialController::MsgRefreshChannels *msg = JogdialController::MsgRefreshChannels::create();
|
||||
m_jogdialController->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::on_channels_currentIndexChanged(int index)
|
||||
{
|
||||
if ((index >= 0) && (index < m_availableChannels.size()))
|
||||
{
|
||||
m_selectedChannel = m_availableChannels[index].m_channelAPI;
|
||||
JogdialController::MsgSelectChannel *msg = JogdialController::MsgSelectChannel::create(index);
|
||||
m_jogdialController->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::tick()
|
||||
{
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::updateStatus()
|
||||
{
|
||||
int state = m_jogdialController->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_jogdialController->getErrorMessage());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_lastFeatureState = state;
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
JogdialController::MsgConfigureJogdialController* message = JogdialController::MsgConfigureJogdialController::create( m_settings, force);
|
||||
m_jogdialController->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::focusInEvent(QFocusEvent*)
|
||||
{
|
||||
qDebug("JogdialControllerGUI::focusInEvent");
|
||||
ui->focusIndicator->setStyleSheet("QLabel { background-color: rgb(85, 232, 85); border-radius: 8px; }"); // green
|
||||
ui->focusIndicator->setToolTip("Active");
|
||||
}
|
||||
|
||||
void JogdialControllerGUI::focusOutEvent(QFocusEvent*)
|
||||
{
|
||||
qDebug("JogdialControllerGUI::focusOutEvent");
|
||||
ui->focusIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }"); // gray
|
||||
ui->focusIndicator->setToolTip("Idle");
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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_JOGDIALCONTROLLERGUI_H_
|
||||
#define INCLUDE_FEATURE_JOGDIALCONTROLLERGUI_H_
|
||||
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
|
||||
#include "feature/featuregui.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "commands/commandkeyreceiver.h"
|
||||
#include "jogdialcontrollersettings.h"
|
||||
|
||||
class PluginAPI;
|
||||
class FeatureUISet;
|
||||
class JogdialController;
|
||||
class Feature;
|
||||
|
||||
namespace Ui {
|
||||
class JogdialControllerGUI;
|
||||
}
|
||||
|
||||
class JogdialControllerGUI : public FeatureGUI {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static JogdialControllerGUI* 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; }
|
||||
|
||||
protected:
|
||||
void focusInEvent(QFocusEvent* e);
|
||||
void focusOutEvent(QFocusEvent *e);
|
||||
|
||||
private:
|
||||
Ui::JogdialControllerGUI* ui;
|
||||
PluginAPI* m_pluginAPI;
|
||||
FeatureUISet* m_featureUISet;
|
||||
JogdialControllerSettings m_settings;
|
||||
bool m_doApplySettings;
|
||||
|
||||
JogdialController* m_jogdialController;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
QTimer m_statusTimer;
|
||||
int m_lastFeatureState;
|
||||
QList<JogdialControllerSettings::AvailableChannel> m_availableChannels;
|
||||
ChannelAPI *m_selectedChannel;
|
||||
CommandKeyReceiver m_commandKeyReceiver;
|
||||
|
||||
explicit JogdialControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
|
||||
virtual ~JogdialControllerGUI();
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void displaySettings();
|
||||
void updateChannelList();
|
||||
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_device_currentIndexChanged(int index);
|
||||
void on_channels_currentIndexChanged(int index);
|
||||
void updateStatus();
|
||||
void tick();
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_FEATURE_JOGDIALCONTROLLERGUI_H_
|
||||
@@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>JogdialControllerGUI</class>
|
||||
<widget class="RollupWidget" name="JogdialControllerGUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>365</width>
|
||||
<height>105</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>340</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Jogdial Controller</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="settingsContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="mainLayout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="startStop">
|
||||
<property name="toolTip">
|
||||
<string>start/stop plugin</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/play.png</normaloff>
|
||||
<normalon>:/stop.png</normalon>:/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="focusIndicator">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Idle</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QLabel { background-color: gray; border-radius: 8px; }</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="channelLabel">
|
||||
<property name="text">
|
||||
<string>Chan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="channels">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel to control</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="controlLabel">
|
||||
<property name="toolTip">
|
||||
<string>Device (D) or channel (C) control</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>D</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="targetLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<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>
|
||||
@@ -0,0 +1,80 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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 "jogdialcontrollergui.h"
|
||||
#endif
|
||||
#include "jogdialcontroller.h"
|
||||
#include "jogdialcontrollerplugin.h"
|
||||
#include "jogdialcontrollerwebapiadapter.h"
|
||||
|
||||
const PluginDescriptor JogdialControllerPlugin::m_pluginDescriptor = {
|
||||
JogdialController::m_featureId,
|
||||
QStringLiteral("Jogdial Controller"),
|
||||
QStringLiteral("6.18.0"),
|
||||
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel")
|
||||
};
|
||||
|
||||
JogdialControllerPlugin::JogdialControllerPlugin(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_pluginAPI(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
const PluginDescriptor& JogdialControllerPlugin::getPluginDescriptor() const
|
||||
{
|
||||
return m_pluginDescriptor;
|
||||
}
|
||||
|
||||
void JogdialControllerPlugin::initPlugin(PluginAPI* pluginAPI)
|
||||
{
|
||||
m_pluginAPI = pluginAPI;
|
||||
|
||||
// register RigCtl Server feature
|
||||
m_pluginAPI->registerFeature(JogdialController::m_featureIdURI, JogdialController::m_featureId, this);
|
||||
}
|
||||
|
||||
#ifdef SERVER_MODE
|
||||
FeatureGUI* JogdialControllerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
|
||||
{
|
||||
(void) featureUISet;
|
||||
(void) feature;
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
FeatureGUI* JogdialControllerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
|
||||
{
|
||||
return JogdialControllerGUI::create(m_pluginAPI, featureUISet, feature);
|
||||
}
|
||||
#endif
|
||||
|
||||
Feature* JogdialControllerPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
|
||||
{
|
||||
return new JogdialController(webAPIAdapterInterface);
|
||||
}
|
||||
|
||||
FeatureWebAPIAdapter* JogdialControllerPlugin::createFeatureWebAPIAdapter() const
|
||||
{
|
||||
return new JogdialControllerWebAPIAdapter();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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_JOGDIALCONTROLLERPLUGIN_H
|
||||
#define INCLUDE_FEATURE_JOGDIALCONTROLLERPLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include "plugin/plugininterface.h"
|
||||
|
||||
class FeatureGUI;
|
||||
class WebAPIAdapterInterface;
|
||||
|
||||
class JogdialControllerPlugin : public QObject, PluginInterface {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(PluginInterface)
|
||||
Q_PLUGIN_METADATA(IID "sdrangel.feature.jogdialcontroller")
|
||||
|
||||
public:
|
||||
explicit JogdialControllerPlugin(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_JOGDIALCONTROLLERPLUGIN_H
|
||||
@@ -0,0 +1,132 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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 "jogdialcontrollersettings.h"
|
||||
|
||||
const QStringList JogdialControllerSettings::m_channelTypes = {
|
||||
QStringLiteral("AISDemod"),
|
||||
QStringLiteral("AISMod"),
|
||||
QStringLiteral("AMDemod"),
|
||||
QStringLiteral("AMMod"),
|
||||
QStringLiteral("DABDemod"),
|
||||
QStringLiteral("DSDDemod"),
|
||||
QStringLiteral("NFMDemod"),
|
||||
QStringLiteral("NFMMod"),
|
||||
QStringLiteral("PacketDemod"),
|
||||
QStringLiteral("PacketMod"),
|
||||
QStringLiteral("SSBDemod"),
|
||||
QStringLiteral("SSBMod"),
|
||||
QStringLiteral("WFMDemod"),
|
||||
QStringLiteral("WFMMod"),
|
||||
};
|
||||
|
||||
const QStringList JogdialControllerSettings::m_channelURIs = {
|
||||
QStringLiteral("sdrangel.channel.aisdemod"),
|
||||
QStringLiteral("sdrangel.channel.modais"),
|
||||
QStringLiteral("sdrangel.channel.amdemod"),
|
||||
QStringLiteral("sdrangel.channeltx.modam"),
|
||||
QStringLiteral("sdrangel.channel.dabdemod"),
|
||||
QStringLiteral("sdrangel.channel.dsddemod"),
|
||||
QStringLiteral("sdrangel.channel.nfmdemod"),
|
||||
QStringLiteral("sdrangel.channeltx.modnfm"),
|
||||
QStringLiteral("sdrangel.channel.packetdemod"),
|
||||
QStringLiteral("sdrangel.channeltx.modpacket"),
|
||||
QStringLiteral("sdrangel.channel.ssbdemod"),
|
||||
QStringLiteral("sdrangel.channeltx.modssb"),
|
||||
QStringLiteral("sdrangel.channel.wfmdemod"),
|
||||
QStringLiteral("sdrangel.channeltx.modwfm"),
|
||||
};
|
||||
|
||||
JogdialControllerSettings::JogdialControllerSettings()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void JogdialControllerSettings::resetToDefaults()
|
||||
{
|
||||
m_title = "Jogdial Controller";
|
||||
m_rgbColor = QColor(3, 198, 252).rgb();
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIFeatureSetIndex = 0;
|
||||
m_reverseAPIFeatureIndex = 0;
|
||||
}
|
||||
|
||||
QByteArray JogdialControllerSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
|
||||
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);
|
||||
s.writeBlob(12, m_rollupState);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool JogdialControllerSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if (!d.isValid())
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d.getVersion() == 1)
|
||||
{
|
||||
QByteArray bytetmp;
|
||||
uint32_t utmp;
|
||||
QString strtmp;
|
||||
|
||||
d.readString(5, &m_title, "Jogdial Controller");
|
||||
d.readU32(6, &m_rgbColor, QColor(3, 198, 252).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;
|
||||
d.readBlob(12, &m_rollupState);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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_JOGDIALCONTROLLERSETTINGS_H_
|
||||
#define INCLUDE_FEATURE_JOGDIALCONTROLLERSETTINGS_H_
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "util/message.h"
|
||||
|
||||
class Serializable;
|
||||
class ChannelAPI;
|
||||
class DeviceAPI;
|
||||
|
||||
struct JogdialControllerSettings
|
||||
{
|
||||
struct AvailableChannel
|
||||
{
|
||||
bool m_tx;
|
||||
int m_deviceSetIndex;
|
||||
int m_channelIndex;
|
||||
DeviceAPI *m_deviceAPI;
|
||||
ChannelAPI *m_channelAPI;
|
||||
QString m_deviceId;
|
||||
QString m_channelId;
|
||||
|
||||
AvailableChannel() = default;
|
||||
AvailableChannel(const AvailableChannel&) = default;
|
||||
AvailableChannel& operator=(const AvailableChannel&) = default;
|
||||
};
|
||||
|
||||
QString m_title;
|
||||
quint32 m_rgbColor;
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
uint16_t m_reverseAPIFeatureSetIndex;
|
||||
uint16_t m_reverseAPIFeatureIndex;
|
||||
QByteArray m_rollupState;
|
||||
|
||||
JogdialControllerSettings();
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
|
||||
static const QStringList m_channelTypes;
|
||||
static const QStringList m_channelURIs;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_JOGDIALCONTROLLERSETTINGS_H_
|
||||
@@ -0,0 +1,51 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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 "jogdialcontroller.h"
|
||||
#include "jogdialcontrollerwebapiadapter.h"
|
||||
|
||||
JogdialControllerWebAPIAdapter::JogdialControllerWebAPIAdapter()
|
||||
{}
|
||||
|
||||
JogdialControllerWebAPIAdapter::~JogdialControllerWebAPIAdapter()
|
||||
{}
|
||||
|
||||
int JogdialControllerWebAPIAdapter::webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setDemodAnalyzerSettings(new SWGSDRangel::SWGDemodAnalyzerSettings());
|
||||
response.getDemodAnalyzerSettings()->init();
|
||||
JogdialController::webapiFormatFeatureSettings(response, m_settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int JogdialControllerWebAPIAdapter::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) force; // no action
|
||||
(void) errorMessage;
|
||||
JogdialController::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
|
||||
|
||||
return 200;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 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_JOGDIALCONTROLLER_WEBAPIADAPTER_H
|
||||
#define INCLUDE_JOGDIALCONTROLLER_WEBAPIADAPTER_H
|
||||
|
||||
#include "feature/featurewebapiadapter.h"
|
||||
#include "jogdialcontrollersettings.h"
|
||||
|
||||
/**
|
||||
* Standalone API adapter only for the settings
|
||||
*/
|
||||
class JogdialControllerWebAPIAdapter : public FeatureWebAPIAdapter {
|
||||
public:
|
||||
JogdialControllerWebAPIAdapter();
|
||||
virtual ~JogdialControllerWebAPIAdapter();
|
||||
|
||||
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:
|
||||
JogdialControllerSettings m_settings;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DEMODANALYZER_WEBAPIADAPTER_H
|
||||
@@ -0,0 +1,297 @@
|
||||
<h1>Jogdial Controller Feature Plugin</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
This plugin aims at supporting frequency control via a "jog dial". A jog dial, jog wheel, shuttle dial, or shuttle wheel is a type of knob, ring, wheel, or dial which allows the user to shuttle or jog through audio or video media.
|
||||
|
||||
It is designed to support the Contour ShuttleXpress and ShuttlePro products but as it is keyboard keys based any device capable of reproducing the same key sequences (including a standard keyboard - see next) can interact with this plugin.
|
||||
|
||||
In the last section you will find details about the key sequences and placement of supported knobs on the ShuttleXpress and ShuttlePro.
|
||||
|
||||
Note that it is based on Qt keyboard events. These events are supported only for GUI applications therefore this plugin is not built in the server variant.
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||

|
||||
|
||||
<h3>1: Start/Stop</h3>
|
||||
|
||||
Use this toggle to activate or deactivate the plugin. Note that for the control to be effective the plugin should also have the focus. You can check the focus state with the focus indicator (2)
|
||||
|
||||
<h3>2: Focus indicator</h3>
|
||||
|
||||
This indicator turns green if the plugin has focus. To set the focus on the plugin just click anywhere on it. The controls are effective only if the plugin has focus.
|
||||
|
||||
<h3>3: Refresh channels list</h3>
|
||||
|
||||
Use this button to refresh the list of available channels. It will scan through all the device sets presents in the instance to list their channels in the combo box next (4).
|
||||
|
||||
Note that on first start the list in (4) is empty therefore you must press this button right after the plugin is started with (1)
|
||||
|
||||
<h3>4: Select channel</h3>
|
||||
|
||||
Use this combo box to select which channel to control. The list item is formatted this way:
|
||||
|
||||
- R ot T for a source (Rx) or sink (Tx) device set
|
||||
- The sequence number of the device set
|
||||
- The sequence number of the channel after the semicolon separator
|
||||
- The type of channel
|
||||
|
||||
The frequency of the device of the device set the channel belongs to can also be controlled when device control is selected. The type of control is displayed in (5)
|
||||
|
||||
To select the type of control:
|
||||
|
||||
- Press K5 on the Contour device or ¨D" (shift+D) on the keyboard for device control
|
||||
- Press K9 on the Contour device or ¨C" (shift+C) on the keyboard for channel control
|
||||
|
||||
<h3>5: Control type indicator</h3>
|
||||
|
||||
It displays "D" when in device control or "C" when in channel control mode.
|
||||
|
||||
<h2>Contour products and keyboard control</h2>
|
||||
|
||||
<h3>Contour devices</h3>
|
||||
|
||||
The Controur devices generally have a central "jog" wheel inside a spring loaded ring called the "shuttle" wheel plus a series of buttons.
|
||||
|
||||
<b>ShuttleXpress layout</b>
|
||||
|
||||

|
||||
|
||||
<b>ShuttlePRO layout</b>
|
||||
|
||||

|
||||
|
||||
The **jog wheel** is used to go up and down in discrete frequency units. You can use the Ctl and Shift keys on the keyboard simultaneously to select the frequency step:
|
||||
|
||||
- No key: ± 1 Hz for channels ± 1 kHz for devices
|
||||
- Ctl key: ± 10 Hz for channels ± 10 kHz for devices
|
||||
- Shift key: ± 100 Hz for channels ± 100 kHz for devices
|
||||
- Ctl+Shift key: ± 1 kHz for channels ± 1 MHz for devices
|
||||
|
||||
|
||||
The **shuttle wheel** has a central rest position and 7 positions on the left and 7 on the right. The left positions are used to go down in frequency and the right positions to go up. The frequency increments or decrements are sent every 100 ms as long as the shuttle position is maintained. It stops at rest (central) position. The further you go from the center the larger the frequency increment or decrement at each step. The amount is multiplied by 10 from one position to the next as you move away from the center. Thus to summarize:
|
||||
|
||||
- Center: rest position stops moving
|
||||
- ± 1 step: moves ± 1 unit (1 Hz for channels, 1 kHz for devices)
|
||||
- ± 2 steps: moves ± 10 units
|
||||
- ± 3 steps: moves ± 100 units
|
||||
- ± 4 steps: moves ± 1000 units (1 kHz for channels, 1 MHz for devices)
|
||||
- ± 5 steps: moves ± 10000 units
|
||||
- ± 6 steps: moves ± 100000 units
|
||||
- ± 7 steps: moves ± 1000000 units (1 MHz for channels, 1 GHz for devices)
|
||||
|
||||
The **keys** are mapped as follows:
|
||||
|
||||
- K5: Select device control
|
||||
- K6: Move down through the list of available channels
|
||||
- K7: Center the channel (set its frequency to 0) - useful when you loose the channel out of the baseband window
|
||||
- K8: Move up through the list of available channels
|
||||
- K9: Select channel control
|
||||
|
||||
<h3>Mapping to keyboard and keyboard control</h3>
|
||||
|
||||
The contour devices proceed by mapping their events to keyboard events and this makes them very adaptable. The Jogdial Controller feature is keyboard event based so you may as well use your keyboard for control. In that case for better visual mapping it is recommended to use a US or US International keyboard.
|
||||
|
||||
Contour provides software to perform the mapping on Windows. So when running on Windows please refer to Contour documentation to implement the keyboard sequence mapping that is described next.
|
||||
|
||||
When running on Linux ou may use [ShuttlePRO](https://github.com/nanosyzygy/ShuttlePRO) for keyboard mapping. It will work also for the ShuttleXpress with minor changes. You will have to identify the path of the Shuttle device and run the program against it in a terminal. See the last section for details.
|
||||
|
||||
<h4>Keyboard mapping</h4>
|
||||
|
||||
The shuttle devices controls are mapped according to the following table. K5 to K9 represent the shuttle keys. S-7 to S7 represent the shuttle (outer ring) positions S0 being the rest middle position. JL is a jog step to the left and JR a jog step to the right:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Shuttle</th>
|
||||
<th>Keyboard</th>
|
||||
<th>Key sequence (US)</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>K5</td>
|
||||
<td>D</td>
|
||||
<td>Shift+D</td>
|
||||
<td>Device control</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>K6</td>
|
||||
<td>←</td>
|
||||
<td>←</td>
|
||||
<td>Previous channel</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>K7</td>
|
||||
<td>Home</td>
|
||||
<td>Home</td>
|
||||
<td>Center channel</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>K8</td>
|
||||
<td>→</td>
|
||||
<td>→</td>
|
||||
<td>Next channel</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>K9</td>
|
||||
<td>C</td>
|
||||
<td>Shift+C</td>
|
||||
<td>Channel control</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-7</td>
|
||||
<td>&</td>
|
||||
<td>Shift+7</td>
|
||||
<td>Continuous -1 MHz / -1 GHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-6</td>
|
||||
<td>^</td>
|
||||
<td>Shift+6</td>
|
||||
<td>Continuous -100 kHz / -100 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-5</td>
|
||||
<td>%</td>
|
||||
<td>Shift+5</td>
|
||||
<td>Continuous -10 kHz / -10 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-4</td>
|
||||
<td>$</td>
|
||||
<td>Shift+4</td>
|
||||
<td>Continuous -1 kHz / -1 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-3</td>
|
||||
<td>#</td>
|
||||
<td>Shift+3</td>
|
||||
<td>Continuous -100 Hz / -100 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-2</td>
|
||||
<td>@</td>
|
||||
<td>Shift+2</td>
|
||||
<td>Continuous -10 Hz / -10 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S-1</td>
|
||||
<td>!</td>
|
||||
<td>Shift+1</td>
|
||||
<td>Continuous -1 Hz / -1 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>Stop continuous move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S1</td>
|
||||
<td>1</td>
|
||||
<td>1</td>
|
||||
<td>Continuous +1 Hz / +1 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S2</td>
|
||||
<td>2</td>
|
||||
<td>2</td>
|
||||
<td>Continuous +10 Hz / +10 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S3</td>
|
||||
<td>3</td>
|
||||
<td>3</td>
|
||||
<td>Continuous +100 Hz / +100 kHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S4</td>
|
||||
<td>4</td>
|
||||
<td>4</td>
|
||||
<td>Continuous +1 kHz / +1 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S5</td>
|
||||
<td>5</td>
|
||||
<td>5</td>
|
||||
<td>Continuous +10 kHz / +10 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S6</td>
|
||||
<td>6</td>
|
||||
<td>6</td>
|
||||
<td>Continuous +100 kHz / +100 MHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>S7</td>
|
||||
<td>7</td>
|
||||
<td>7</td>
|
||||
<td>Continuous +1 MHz / +1 GHz move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>JL</td>
|
||||
<td>↓</td>
|
||||
<td>↓</td>
|
||||
<td>Single -1/-10/-100/-1000 (Hz/kHz) move</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>JR</td>
|
||||
<td>↑</td>
|
||||
<td>↑</td>
|
||||
<td>Single +1/+10/+100/+1000 (Hz/kHz) move</td>
|
||||
<tr>
|
||||
</table>
|
||||
|
||||
<h4>US keyboard mapping</h4>
|
||||
|
||||

|
||||
|
||||
<h3>Running the Contour devices in Linux</h3>
|
||||
|
||||
As briefly introduced earlier you may use [ShuttlePRO](https://github.com/nanosyzygy/ShuttlePRO) for keyboard mapping.
|
||||
|
||||
The program will normally work as-is for ShuttlePRO. It will also work for ShuttleXpress with a few adaptations detailed next.
|
||||
|
||||
Firstly you need to add the proper udev rules that are slightly different. In fact you just need to change the "ATTR" line by:
|
||||
```
|
||||
ATTRS{name}=="Contour Design ShuttleXpress" MODE="0644"
|
||||
```
|
||||
Secondly the device path is different for ShuttleXPress. You have to look for your device in the path starting with `/dev/input/by-id/usb-Contour_Design_` In most cases this will be: `/dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00`
|
||||
|
||||
You will then use this path as argument to the `shuttlepro` program:
|
||||
|
||||
`shuttlepro /dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00`
|
||||
|
||||
You may change the argument in the `shuttle` script for convenience:
|
||||
|
||||
`exec shuttlepro /dev/input/by-id/usb-Contour_Design_ShuttleXpress-event-if00`
|
||||
|
||||
In any case you need to specify the mapping in the `~/.shuttlerc` file by adding a section for SDRangel like this:
|
||||
```
|
||||
[SDRangel]
|
||||
K5 "D"
|
||||
K6 XK_Left
|
||||
K7 XK_Home
|
||||
K8 XK_Right
|
||||
K9 "C"
|
||||
|
||||
S-7 XK_Shift_L/D "7"
|
||||
S-6 XK_Shift_L/D "6"
|
||||
S-5 XK_Shift_L/D "5"
|
||||
S-4 XK_Shift_L/D "4"
|
||||
S-3 XK_Shift_L/D "3"
|
||||
S-2 XK_Shift_L/D "2"
|
||||
S-1 XK_Shift_L/D "1"
|
||||
S0 "0"
|
||||
S1 "1"
|
||||
S2 "2"
|
||||
S3 "3"
|
||||
S4 "4"
|
||||
S5 "5"
|
||||
S6 "6"
|
||||
S7 "7"
|
||||
|
||||
JL XK_Down
|
||||
JR XK_Up
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user