1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

Add Remote Control feature

This commit is contained in:
Jon Beniston 2022-09-16 10:20:16 +01:00
parent a4cd8af538
commit 65364eeb2c
27 changed files with 5991 additions and 0 deletions

View File

@ -124,6 +124,7 @@ option(ENABLE_FEATURE_STARTRACKER "Enable feature startracker plugin" ON)
option(ENABLE_FEATURE_RIGCTLSERVER "Enable feature rigctlserver plugin" ON)
option(ENABLE_FEATURE_PERTESTER "Enable feature pertester plugin" ON)
option(ENABLE_FEATURE_GS232CONTROLLER "Enable feature gs232controller plugin" ON)
option(ENABLE_FEATURE_REMOTECONTROL "Enable feature remote control plugin" ON)
# on windows always build external libraries
if(WIN32)

View File

@ -73,3 +73,7 @@ endif()
if (ENABLE_FEATURE_AMBE AND LIBSERIALDV_FOUND)
add_subdirectory(ambe)
endif()
if (ENABLE_FEATURE_REMOTECONTROL)
add_subdirectory(remotecontrol)
endif()

View File

@ -0,0 +1,71 @@
project(emotecontrol)
set(remotecontrol_SOURCES
remotecontrol.cpp
remotecontrolsettings.cpp
remotecontrolplugin.cpp
remotecontrolworker.cpp
)
set(remotecontrol_HEADERS
remotecontrol.h
remotecontrolsettings.h
remotecontrolplugin.h
remotecontrolworker.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(remotecontrol_SOURCES
${remotecontrol_SOURCES}
remotecontrolgui.cpp
remotecontrolgui.ui
remotecontrolsettingsdialog.cpp
remotecontrolsettingsdialog.ui
remotecontroldevicedialog.cpp
remotecontroldevicedialog.ui
remotecontrolvisasensordialog.cpp
remotecontrolvisasensordialog.ui
remotecontrolvisacontroldialog.cpp
remotecontrolvisacontroldialog.ui
)
set(remotecontrol_HEADERS
${remotecontrol_HEADERS}
remotecontrolgui.h
remotecontrolsettingsdialog.h
remotecontroldevicedialog.h
remotecontrolvisasensordialog.h
remotecontrolvisacontroldialog.h
)
set(TARGET_NAME featureremotecontrol)
set(TARGET_LIB Qt5::Widgets Qt5::Charts)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featureremotecontrolsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${remotecontrol_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,153 @@
<h1>Remote Control Feature Plugin</h1>
<h2>Introduction</h2>
The Remote Control Feature can be used to control and view the status of Smart Home / IoT devices,
such as plugs and switches, and test equipment that supports the VISA API, such as benchtop power
supplies, multimeters and spectrum analyzers.
In a SDR context, this can be useful for remotely controlling and monitoring power to SDRs, power
amplifiers and rotator controllers. Or it can be used to make and display signal power measurements
from a spectrum analyzer, in SDRangel.
The Remote Control feature can interface to devices via the following APIs:
* Home Assistant (https://www.home-assistant.io/)
* TP-Link's Kasa (https://www.tp-link.com/uk/home-networking/smart-plug/)
* VISA (https://www.ivifoundation.org/specifications/default.aspx)
A user-configurable GUI is supported, that allows customization of which controls and sensors are displayed for each device.
Sensor values can be plotted versus time on charts.
![Remote Control feature plugin GUI](../../../doc/img/RemoteControl_plugin.png)
<h2>Interface</h2>
![Remote Control feature settings](../../../doc/img/RemoteControl_plugin_settings.png)
<h3>1: Start/Stop</h3>
Starts or stops periodic reading of the state of all devices. The update period can be set in the Settings dialog (3).
<h3>2: Update State</h3>
Press to manually read the state of all devices. This can be used regardless of whether the plugin is started or stopped (1).
<h3>3: Display Settings Dialog</h3>
Pressing this button opens the Settings dialog.
<h3>4: Clear Chart Data</h3>
Pressing this button will clear all data from all charts.
<h3>5: Device GUIs</h3>
GUIs for the enabled controls and sensors within a device will be displayed below the settings.
If a device is not available, it will be greyed out. If an error occurs when getting the state for a specific
control or sensor, or an out of range value is received, the background of the corresponding widget will turn red.
<h2>Settings Dialog</h2>
<h3>Devices Tab</h3>
The Devices tab displays a list of devices that have been added to this Remote Control.
![Remote Control devices tab](../../../doc/img/RemoteControl_plugin_devices_tab.png)
* Press Add... to add a new device.
* Press Remove to remove the selected device.
* Press Edit... to edit settings for the selected device.
* The up and down arrows move the selected device up or down in the list.
The order of devices in the list determines the display order of the device's controls and senors in the Remote Control's GUI.
<h3>Settings Tab</h3>
![Remote Control settings tab](../../../doc/img/RemoteControl_plugin_settings_tab.png)
<h4>TP-Link Settings</h4>
The TP-Link fields must be completed in order to discover TP-Link Kasa Smart Plugs using TP-Link's protocol.
Enter the e-mail address and password used for the TPLink Kasa Smart Home app.
<h4>Home Assistant Settings</h4>
The Home Assistant fields must be completed in order to discover devices connected to Home Assistant.
* Access token - Access token required to use the Home Assistant API. Access tokens can be created on the user profile page, typically at: http://homeassistant.local:8123/profile
* Host - The hostname or IP address and port number of the computer running the Home Assistant server. This is typically http://homeassistant.local:8123
<h4>VISA Settings</h4>
* Resource filter - A regular expression of VISA resources not to attempt to open. This can be used to speed up VISA device discovery. As an example, devices using TCP and serial connections can be filted with: ^(TCPIP|ASRL). Leave the field empty to try to connect to all VISA devices.
* Log I/O - Check to log VISA commands and responses to the SDRangel log file.
<h4>Devices Settings</h4>
* Update period - Period in seconds between automatic updates of device control and sensor state.
<h4>Chart Settings</h4>
* Height - Specifies whether charts are a fixed height (Fixed), or can be expanded vertically (Expanding). This setting also determines where the 'Stack sub windows' button will place the GUI.
* Height (pixels) - When 'Height' is 'Fixed', this specifies the height in pixels.
<h2>Device Dialog</h2>
The Device Dialog allows selecting devices to add to the Remote Control, as well as customing what controls and sensors are displayed for the device in the GUI.
![Device dialog](../../../doc/img/RemoteControl_plugin_device.png)
When the dialog first appears when adding a new device, all fields will be disabled except for Protocol. You should first select a protocol in order to discover all devices that are currently
available via the selected protocol. The available devices will be added to the Device field. Select from this field the device you wish to add.
The device name is the name assigned by the selected protocol. If you wish to use a different label for the device in the GUI, this can be entered in the Label field.
The Controls and Sensors tables allow you to customize which are contols and sensors are visible in the GUI, via the checkbox in the Enable column.
The Left Label and Right Label fields hold the text that will be displayed either side of the control or sensor in the GUI.
The Left Label is initialised with the device name and the Right Label is initialised withthe units.
These fields can be changed by double clicking in the cell.
Checking the Plot column will result in a chart being drawn that plots sensor data verses time.
All enabled sensors for a device will be plotted on the same chart.
The Y Axis field below the table determines whether each series will have it's own Y axis (Per-sensor) or whether a single Y axis will be used for all series (Common).
The Layout fields control how the Controls and Sensors will be laid-out in the GUI. This can be set to be either Horizontally or Vertically.
When the Protocol is set to VISA, additional buttons will be displayed under the tables that allow controls and sensors to be added or removed, as unlike when selecting
TP-Link and Home Assitant devices, these are not automatically defined.
<h3>VISA Control Dialog</h3>
The VISA Control Dialog allows the specification of a control for a VISA device. Both the GUI element and the SCPI commands to set and get the state must be specified:
![Device dialog](../../../doc/img/RemoteControl_plugin_visa_control.png)
* Name - A name for the control. E.g. Voltage for a voltage control on a power supply. This field is used as the default value for the Left Label in the GUI.
* ID - A unique identifier for the control. This must be unique between all controls and sensors in a device.
* Type - The data type of the state being controlled. This can be:
* Boolean - For on/off controls. A toggle button is used in the GUI.
* Integer - For integers. Minimum and maximum limits can be specified. A spin box is used in the GUI.
* Float - For real numbers. Minimum and maximum limits can be specified, as well as the precision (number of decimals). The GUI can use either a spin box, dial or slider. The Scale field specifies a scale factor that is applied to the value from the GUI that is sent to the device. E.g. If you wish to have a value displayed in MHz, but the value in the SCPI command should be in Hz, then the Scale field should be set to 1000000.
* String - For a text string.
* List - For a list of text strings, selectable from a ComboBox in the GUI.
* Button - For a button that executes a specific command, but does not have any state to be displayed. E.g. for a Reset button that executes *RST.
* Units - The units of the control, if applicable. E.g V or Volts for a voltage control. This field is used as the default value for the Right Label in the GUI.
* Set state - SCPI commands that set the state of the setting in the device. The value of the control in the GUI can be substituted in to the command by using %d for boolean and integer, %f for float and %s for strings.
* Get state - SCPI commands that get the state of the setting in the device. This is used to update the control in the GUI.
<h3>VISA Sensor Dialog</h3>
The VISA Sensor Dialog allows the specification of a sensor for a VISA device. Both the GUI element and the SCPI commands to get the state must be specified:
![Device dialog](../../../doc/img/RemoteControl_plugin_visa_sensor.png)
* Name - A name for the sensor. E.g. Current for a current measurement from a power supply. This field is used as the default value for the Left Label in the GUI.
* ID - A unique identifier for the sensor. This must be unique between all controls and sensors in a device.
* Type - The data type of the sensor. This can be:
* Boolean - For on/off, true/false and 1/0 values.
* Float - For real numbers.
* String - For text strings.
* Units - The units of the sensor, if applicable. E.g A or Amps for a current sensor. This field is used as the default value for the Right Label in the GUI and also for the Chart Y-axis label.
* Get state - SCPI commands that get the state of the sensor from the device.

View File

@ -0,0 +1,150 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "feature/featureset.h"
#include "settings/serializable.h"
#include "remotecontrol.h"
#include "remotecontrolworker.h"
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgConfigureRemoteControl, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgDeviceGetState, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgDeviceSetState, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgDeviceStatus, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgDeviceError, Message)
MESSAGE_CLASS_DEFINITION(RemoteControl::MsgDeviceUnavailable, Message)
const char* const RemoteControl::m_featureIdURI = "sdrangel.feature.remotecontrol";
const char* const RemoteControl::m_featureId = "RemoteControl";
RemoteControl::RemoteControl(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface)
{
qDebug("RemoteControl::RemoteControl: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "RemoteControl error";
start();
}
RemoteControl::~RemoteControl()
{
stop();
}
void RemoteControl::start()
{
qDebug() << "RemoteControl::start";
m_thread = new QThread();
m_worker = new RemoteControlWorker();
m_worker->moveToThread(m_thread);
QObject::connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
QObject::connect(m_thread, &QThread::finished, m_thread, &QObject::deleteLater);
m_worker->setMessageQueueToFeature(getInputMessageQueue());
m_state = StRunning;
m_thread->start();
}
void RemoteControl::stop()
{
qDebug() << "RemoteControl::stop";
m_state = StIdle;
m_thread->quit();
m_thread->wait();
}
bool RemoteControl::handleMessage(const Message& cmd)
{
if (MsgConfigureRemoteControl::match(cmd))
{
MsgConfigureRemoteControl& cfg = (MsgConfigureRemoteControl&) cmd;
applySettings(cfg.getSettings(), cfg.getForce());
// Ensure GUI message queue is set. setMessageQueueToGUI() isn't virtual, so can't hook in there.
m_worker->setMessageQueueToGUI(getMessageQueueToGUI());
// Forward to worker
MsgConfigureRemoteControl *msgToWorker = new MsgConfigureRemoteControl(cfg);
m_worker->getInputMessageQueue()->push(msgToWorker);
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
// Unlike most other plugins, our worker is always running.
// Start/stop is used just to control automatic updating of device state
// Forward to worker
MsgStartStop *msgToWorker = new MsgStartStop(cfg);
m_worker->getInputMessageQueue()->push(msgToWorker);
return true;
}
else if (MsgDeviceGetState::match(cmd))
{
MsgDeviceGetState& get = (MsgDeviceGetState&)cmd;
// Forward to worker
MsgDeviceGetState *msgToWorker = new MsgDeviceGetState(get);
m_worker->getInputMessageQueue()->push(msgToWorker);
return true;
}
else if (MsgDeviceSetState::match(cmd))
{
MsgDeviceSetState& set = (MsgDeviceSetState&)cmd;
// Forward to worker
MsgDeviceSetState *msgToWorker = new MsgDeviceSetState(set);
m_worker->getInputMessageQueue()->push(msgToWorker);
return true;
}
else
{
return false;
}
}
QByteArray RemoteControl::serialize() const
{
return m_settings.serialize();
}
bool RemoteControl::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureRemoteControl *msg = MsgConfigureRemoteControl::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureRemoteControl *msg = MsgConfigureRemoteControl::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void RemoteControl::applySettings(const RemoteControlSettings& settings, bool force)
{
qDebug() << "RemoteControl::applySettings:"
<< " force: " << force;
m_settings = settings;
}

View File

@ -0,0 +1,215 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_REMOTECONTROL_H_
#define INCLUDE_FEATURE_REMOTECONTROL_H_
#include "feature/feature.h"
#include "util/message.h"
#include "remotecontrolsettings.h"
class WebAPIAdapterInterface;
class RemoteControlWorker;
namespace SWGSDRangel {
class SWGDeviceState;
}
class RemoteControl : public Feature
{
Q_OBJECT
public:
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 MsgConfigureRemoteControl : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RemoteControlSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRemoteControl* create(const RemoteControlSettings& settings, bool force) {
return new MsgConfigureRemoteControl(settings, force);
}
private:
RemoteControlSettings m_settings;
bool m_force;
MsgConfigureRemoteControl(const RemoteControlSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgDeviceGetState : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgDeviceGetState* create() {
return new MsgDeviceGetState();
}
protected:
MsgDeviceGetState() :
Message()
{ }
};
class MsgDeviceSetState : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getProtocol() const { return m_protocol; }
QString getDeviceId() const { return m_deviceId; }
QString getId() const { return m_id; }
QVariant getValue() const { return m_value; }
static MsgDeviceSetState* create(const QString &protocol, const QString &deviceId, const QString &id, QVariant value) {
return new MsgDeviceSetState(protocol, deviceId, id, value);
}
protected:
QString m_protocol;
QString m_deviceId;
QString m_id;
QVariant m_value;
MsgDeviceSetState(const QString &protocol, const QString &deviceId, const QString &id, QVariant value) :
Message(),
m_protocol(protocol),
m_deviceId(deviceId),
m_id(id),
m_value(value)
{ }
};
class MsgDeviceStatus : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getProtocol() const { return m_protocol; }
QString getDeviceId() const { return m_deviceId; }
QHash<QString, QVariant> getStatus() const { return m_status; }
static MsgDeviceStatus* create(const QString &protocol, const QString &deviceId, const QHash<QString, QVariant> status) {
return new MsgDeviceStatus(protocol, deviceId, status);
}
protected:
QString m_protocol;
QString m_deviceId;
QHash<QString, QVariant> m_status;
MsgDeviceStatus(const QString &protocol, const QString &deviceId, const QHash<QString, QVariant> status) :
Message(),
m_protocol(protocol),
m_deviceId(deviceId),
m_status(status)
{ }
};
class MsgDeviceError : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getErrorMessage() const { return m_errorMessage; }
static MsgDeviceError* create(const QString &errorMessage) {
return new MsgDeviceError(errorMessage);
}
protected:
QString m_errorMessage;
MsgDeviceError(const QString &errorMessage) :
Message(),
m_errorMessage(errorMessage)
{ }
};
class MsgDeviceUnavailable : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getProtocol() const { return m_protocol; }
QString getDeviceId() const { return m_deviceId; }
static MsgDeviceUnavailable* create(const QString &protocol, const QString &deviceId) {
return new MsgDeviceUnavailable(protocol, deviceId);
}
protected:
QString m_protocol;
QString m_deviceId;
MsgDeviceUnavailable(const QString &protocol, const QString &deviceId) :
Message(),
m_protocol(protocol),
m_deviceId(deviceId)
{ }
};
RemoteControl(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~RemoteControl();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) const { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
static const char* const m_featureIdURI;
static const char* const m_featureId;
private:
QThread *m_thread;
RemoteControlWorker *m_worker;
RemoteControlSettings m_settings;
void start();
void stop();
void applySettings(const RemoteControlSettings& settings, bool force = false);
};
#endif // INCLUDE_FEATURE_REMOTECONTROL_H_

View File

@ -0,0 +1,620 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "remotecontroldevicedialog.h"
#include "remotecontrolvisasensordialog.h"
#include "remotecontrolvisacontroldialog.h"
#include <QDebug>
#include <QUrl>
#include <QMessageBox>
RemoteControlDeviceDialog::RemoteControlDeviceDialog(RemoteControlSettings *settings, RemoteControlDevice *rcDevice, QWidget* parent) :
QDialog(parent),
ui(new Ui::RemoteControlDeviceDialog),
m_settings(settings),
m_rcDevice(rcDevice),
m_discoverer(nullptr),
m_setDeviceWhenAvailable(false)
{
ui->setupUi(this);
connect(ui->controls->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RemoteControlDeviceDialog::controlSelectionChanged);
connect(ui->sensors->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RemoteControlDeviceDialog::sensorSelectionChanged);
enableWidgets();
resizeTables();
if (!m_rcDevice->m_info.m_id.isEmpty())
{
ui->controlsLayout->setCurrentIndex((int)m_rcDevice->m_verticalControls);
ui->sensorsLayout->setCurrentIndex((int)m_rcDevice->m_verticalSensors);
ui->yAxis->setCurrentIndex((int)m_rcDevice->m_commonYAxis);
m_setDeviceWhenAvailable = true;
// Set protocol last, as that triggers discovery
ui->protocol->setCurrentText(m_rcDevice->m_protocol);
}
}
RemoteControlDeviceDialog::~RemoteControlDeviceDialog()
{
delete ui;
delete m_discoverer;
}
void RemoteControlDeviceDialog::resizeTables()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = ui->controls->rowCount();
ui->controls->setRowCount(row + 1);
ui->controls->setItem(row, COL_ENABLE, new QTableWidgetItem("C"));
ui->controls->setItem(row, COL_UNITS, new QTableWidgetItem("Units"));
ui->controls->setItem(row, COL_NAME, new QTableWidgetItem("A reasonably long control name"));
ui->controls->setItem(row, COL_ID, new QTableWidgetItem("An identifier"));
ui->controls->setItem(row, COL_LABEL_LEFT, new QTableWidgetItem("A reasonably long control name"));
ui->controls->setItem(row, COL_LABEL_RIGHT, new QTableWidgetItem("Units"));
ui->controls->resizeColumnsToContents();
ui->controls->removeRow(row);
row = ui->sensors->rowCount();
ui->sensors->setRowCount(row + 1);
ui->sensors->setItem(row, COL_ENABLE, new QTableWidgetItem("C"));
ui->sensors->setItem(row, COL_NAME, new QTableWidgetItem("A reasonably long sensor name"));
ui->sensors->setItem(row, COL_UNITS, new QTableWidgetItem("Units"));
ui->sensors->setItem(row, COL_ID, new QTableWidgetItem("An identifier"));
ui->sensors->setItem(row, COL_LABEL_LEFT, new QTableWidgetItem("A reasonably long sensor name"));
ui->sensors->setItem(row, COL_LABEL_RIGHT, new QTableWidgetItem("Units"));
ui->sensors->setItem(row, COL_FORMAT, new QTableWidgetItem("Format"));
ui->sensors->setItem(row, COL_PLOT, new QTableWidgetItem("C"));
ui->sensors->resizeColumnsToContents();
ui->sensors->removeRow(row);
}
void RemoteControlDeviceDialog::accept()
{
QDialog::accept();
if ((ui->protocol->currentIndex() > 0) && (!ui->device->currentText().isEmpty()))
{
int deviceIndex = ui->device->currentIndex();
m_rcDevice->m_protocol = ui->protocol->currentText();
m_rcDevice->m_label = ui->label->text();
m_rcDevice->m_verticalControls = ui->controlsLayout->currentIndex() == 1;
m_rcDevice->m_verticalSensors = ui->sensorsLayout->currentIndex() == 1;
m_rcDevice->m_commonYAxis = ui->yAxis->currentIndex() == 1;
m_rcDevice->m_info = m_deviceInfo[deviceIndex];
m_rcDevice->m_controls.clear();
for (int row = 0; row < ui->controls->rowCount(); row++)
{
if (ui->controls->item(row, COL_ENABLE)->checkState() == Qt::Checked)
{
RemoteControlControl control;
control.m_id = ui->controls->item(row, COL_ID)->text();
control.m_labelLeft = ui->controls->item(row, COL_LABEL_LEFT)->text();
control.m_labelRight = ui->controls->item(row, COL_LABEL_RIGHT)->text();
m_rcDevice->m_controls.append(control);
}
}
m_rcDevice->m_sensors.clear();
for (int row = 0; row < ui->sensors->rowCount(); row++)
{
if (ui->sensors->item(row, COL_ENABLE)->checkState() == Qt::Checked)
{
RemoteControlSensor sensor;
sensor.m_id = ui->sensors->item(row, COL_ID)->text();
sensor.m_labelLeft = ui->sensors->item(row, COL_LABEL_LEFT)->text();
sensor.m_labelRight = ui->sensors->item(row, COL_LABEL_RIGHT)->text();
sensor.m_format = ui->sensors->item(row, COL_FORMAT)->text();
sensor.m_plot = ui->sensors->item(row, COL_PLOT)->checkState() == Qt::Checked;
m_rcDevice->m_sensors.append(sensor);
}
}
}
}
void RemoteControlDeviceDialog::enableWidgets()
{
bool allEnabled = false;
bool visible = false;
bool editControlsEnabled = false;
bool editSensorsEnabled = false;
QString protocol = ui->protocol->currentText();
if (protocol != "Select a protocol...") {
allEnabled = true;
}
if (protocol == "VISA")
{
visible = true;
editControlsEnabled = ui->controls->selectedItems().size() > 0;
editSensorsEnabled = ui->sensors->selectedItems().size() > 0;
}
ui->device->setEnabled(allEnabled);
ui->deviceLabel->setEnabled(allEnabled);
ui->label->setEnabled(allEnabled);
ui->labelLabel->setEnabled(allEnabled);
ui->model->setEnabled(allEnabled);
ui->modelLabel->setEnabled(allEnabled);
ui->controlsGroup->setEnabled(allEnabled);
ui->sensorsGroup->setEnabled(allEnabled);
ui->controlAdd->setVisible(visible);
ui->controlRemove->setVisible(visible);
ui->controlEdit->setVisible(visible);
ui->controlRemove->setEnabled(editControlsEnabled);
ui->controlEdit->setEnabled(editControlsEnabled);
ui->sensorAdd->setVisible(visible);
ui->sensorRemove->setVisible(visible);
ui->sensorEdit->setVisible(visible);
ui->sensorRemove->setEnabled(editSensorsEnabled);
ui->sensorEdit->setEnabled(editSensorsEnabled);
}
void RemoteControlDeviceDialog::controlSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
bool arrowsEnabled = (selected.indexes().size() > 0);
bool editEnabled = arrowsEnabled && (ui->protocol->currentText() == "VISA");
ui->controlRemove->setEnabled(editEnabled);
ui->controlEdit->setEnabled(editEnabled);
ui->controlUp->setEnabled(arrowsEnabled);
ui->controlDown->setEnabled(arrowsEnabled);
}
void RemoteControlDeviceDialog::sensorSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
bool arrowsEnabled = (selected.indexes().size() > 0);
bool editEnabled = arrowsEnabled && (ui->protocol->currentText() == "VISA");
ui->sensorRemove->setEnabled(editEnabled);
ui->sensorEdit->setEnabled(editEnabled);
ui->sensorUp->setEnabled(arrowsEnabled);
ui->sensorDown->setEnabled(arrowsEnabled);
}
void RemoteControlDeviceDialog::on_protocol_currentTextChanged(const QString &protocol)
{
QHash<QString, QVariant> settings;
// Clear current values in all widgets
ui->device->setCurrentIndex(-1);
if (protocol != "Select a protocol...")
{
if (protocol == "TPLink")
{
settings.insert("username", m_settings->m_tpLinkUsername);
settings.insert("password", m_settings->m_tpLinkPassword);
}
else if (protocol == "HomeAssistant")
{
settings.insert("apiKey", m_settings->m_homeAssistantToken);
settings.insert("url", m_settings->m_homeAssistantHost);
}
else if (protocol == "VISA")
{
settings.insert("resourceFilter", m_settings->m_visaResourceFilter);
}
delete m_discoverer;
m_discoverer = DeviceDiscoverer::getDiscoverer(settings, protocol);
if (m_discoverer)
{
connect(m_discoverer, &DeviceDiscoverer::deviceList, this, &RemoteControlDeviceDialog::deviceList);
connect(m_discoverer, &DeviceDiscoverer::error, this, &RemoteControlDeviceDialog::deviceError);
m_discoverer->getDevices();
}
else
{
QMessageBox::critical(this, "Remote Control Error", QString("Failed to discover %1 devices").arg(protocol));
}
}
enableWidgets();
}
int RemoteControlDeviceDialog::addControlRow(const QString &name, const QString &id, const QString &units)
{
QTableWidgetItem *item;
int row = ui->controls->rowCount();
ui->controls->setRowCount(row + 1);
item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(Qt::Checked);
ui->controls->setItem(row, COL_ENABLE, item);
item = new QTableWidgetItem(name);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->controls->setItem(row, COL_NAME, item);
item = new QTableWidgetItem(units);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->controls->setItem(row, COL_UNITS, item);
item = new QTableWidgetItem(id);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->controls->setItem(row, COL_ID, item);
item = new QTableWidgetItem(name);
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->controls->setItem(row, COL_LABEL_LEFT, item);
item = new QTableWidgetItem(units);
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->controls->setItem(row, COL_LABEL_RIGHT, item);
return row;
}
int RemoteControlDeviceDialog::addSensorRow(const QString &name, const QString &id, const QString &units)
{
QTableWidgetItem *item;
int row = ui->sensors->rowCount();
ui->sensors->setRowCount(row + 1);
item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(Qt::Checked);
ui->sensors->setItem(row, COL_ENABLE, item);
item = new QTableWidgetItem(name);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_NAME, item);
item = new QTableWidgetItem(units);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_UNITS, item);
item = new QTableWidgetItem(id);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_ID, item);
item = new QTableWidgetItem(name);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_LABEL_LEFT, item);
item = new QTableWidgetItem(units);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_LABEL_RIGHT, item);
item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
ui->sensors->setItem(row, COL_FORMAT, item);
item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(Qt::Unchecked);
ui->sensors->setItem(row, COL_PLOT, item);
return row;
}
void RemoteControlDeviceDialog::on_device_currentIndexChanged(int index)
{
ui->model->setText("");
ui->label->setText("");
ui->controls->setRowCount(0);
ui->sensors->setRowCount(0);
if ((index < m_deviceInfo.size()) && (index >= 0))
{
DeviceDiscoverer::DeviceInfo *deviceInfo = &m_deviceInfo[index];
ui->model->setText(deviceInfo->m_model);
if (m_rcDevice->m_info.m_id == deviceInfo->m_id) {
ui->label->setText(m_rcDevice->m_label);
} else {
ui->label->setText(deviceInfo->m_name);
}
for (auto c : deviceInfo->m_controls) {
addControlRow(c->m_name, c->m_id, c->m_units);
}
for (auto s : deviceInfo->m_sensors) {
addSensorRow(s->m_name, s->m_id, s->m_units);
}
}
}
void RemoteControlDeviceDialog::updateTable()
{
for (int row = 0; row < ui->controls->rowCount(); row++)
{
QString controlId = ui->controls->item(row, COL_ID)->text();
RemoteControlControl *control = m_rcDevice->getControl(controlId);
if (control != nullptr)
{
ui->controls->item(row, COL_ENABLE)->setCheckState(Qt::Checked);
ui->controls->item(row, COL_LABEL_LEFT)->setText(control->m_labelLeft);
ui->controls->item(row, COL_LABEL_RIGHT)->setText(control->m_labelRight);
}
else
{
ui->controls->item(row, COL_ENABLE)->setCheckState(Qt::Unchecked);
}
}
for (int row = 0; row < ui->sensors->rowCount(); row++)
{
QString sensorId = ui->sensors->item(row, COL_ID)->text();
RemoteControlSensor *sensor = m_rcDevice->getSensor(sensorId);
if (sensor != nullptr)
{
ui->sensors->item(row, COL_ENABLE)->setCheckState(Qt::Checked);
ui->sensors->item(row, COL_LABEL_LEFT)->setText(sensor->m_labelLeft);
ui->sensors->item(row, COL_LABEL_RIGHT)->setText(sensor->m_labelRight);
ui->sensors->item(row, COL_FORMAT)->setText(sensor->m_format);
ui->sensors->item(row, COL_PLOT)->setCheckState(sensor->m_plot ? Qt::Checked : Qt::Unchecked);
}
else
{
ui->sensors->item(row, COL_ENABLE)->setCheckState(Qt::Unchecked);
}
}
}
void RemoteControlDeviceDialog::deviceList(const QList<DeviceDiscoverer::DeviceInfo> &devices)
{
ui->device->clear();
m_deviceInfo = devices; // Take a deep copy
int i = 0;
for (auto const &device : m_deviceInfo)
{
// Update default device info, with info for device we are editing
if (m_setDeviceWhenAvailable && (device.m_id == m_rcDevice->m_info.m_id)) {
m_deviceInfo[i] = m_rcDevice->m_info;
}
// Add device to list
ui->device->addItem(device.m_name);
i++;
}
if (m_setDeviceWhenAvailable)
{
ui->device->setCurrentText(m_rcDevice->m_info.m_name);
m_setDeviceWhenAvailable = false;
updateTable();
}
}
void RemoteControlDeviceDialog::deviceError(const QString &error)
{
QMessageBox::critical(this, "Remote Control Error", error);
}
void RemoteControlDeviceDialog::on_controlAdd_clicked()
{
VISADevice::VISAControl *control = new VISADevice::VISAControl();
RemoteControlVISAControlDialog dialog(m_settings, m_rcDevice, control, true);
if (dialog.exec() == QDialog::Accepted)
{
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_controls.append(reinterpret_cast<DeviceDiscoverer::ControlInfo *>(control));
addControlRow(control->m_name, control->m_id, control->m_units);
}
else
{
delete control;
}
}
void RemoteControlDeviceDialog::on_controlEdit_clicked()
{
QList<QTableWidgetItem *> items = ui->controls->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
QString id = ui->controls->item(row, COL_ID)->text();
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
VISADevice::VISAControl *control = reinterpret_cast<VISADevice::VISAControl *>(info->getControl(id));
RemoteControlVISAControlDialog dialog(m_settings, m_rcDevice, control, false);
if (dialog.exec() == QDialog::Accepted)
{
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
ui->controls->item(row, COL_NAME)->setText(control->m_name);
ui->controls->item(row, COL_UNITS)->setText(control->m_units);
ui->controls->item(row, COL_ID)->setText(control->m_id);
}
}
}
void RemoteControlDeviceDialog::on_controls_cellDoubleClicked(int row, int column)
{
if ((ui->protocol->currentText() == "VISA") && (column <= COL_ID)) {
on_controlEdit_clicked();
}
}
void RemoteControlDeviceDialog::on_controlRemove_clicked()
{
QList<QTableWidgetItem *> items = ui->controls->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
QString id = ui->controls->item(row, COL_ID)->text();
ui->controls->removeRow(row);
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->deleteControl(id);
}
}
void RemoteControlDeviceDialog::on_controlUp_clicked()
{
QList<QTableWidgetItem *> items = ui->controls->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row > 0)
{
// Swap rows in table
QTableWidgetItem *item1 = ui->controls->takeItem(row, col);
QTableWidgetItem *item2 = ui->controls->takeItem(row - 1, col);
ui->controls->setItem(row - 1, col, item1);
ui->controls->setItem(row, col, item2);
}
if (i == items.size() - 1)
{
ui->controls->setCurrentItem(items[i]);
if (row > 0)
{
// Swap device info
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_controls.swapItemsAt(row, row - 1);
}
}
}
}
void RemoteControlDeviceDialog::on_controlDown_clicked()
{
QList<QTableWidgetItem *> items = ui->controls->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row < ui->controls->rowCount() - 1)
{
// Swap rows in table
QTableWidgetItem *item1 = ui->controls->takeItem(row, col);
QTableWidgetItem *item2 = ui->controls->takeItem(row + 1, col);
ui->controls->setItem(row + 1, col, item1);
ui->controls->setItem(row, col, item2);
}
if (i == items.size() - 1)
{
ui->controls->setCurrentItem(items[i]);
if (row < ui->controls->rowCount() - 1)
{
// Swap device info
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_controls.swapItemsAt(row, row + 1);
}
}
}
}
void RemoteControlDeviceDialog::on_sensorAdd_clicked()
{
VISADevice::VISASensor *sensor = new VISADevice::VISASensor();
RemoteControlVISASensorDialog dialog(m_settings, m_rcDevice, sensor, true);
if (dialog.exec() == QDialog::Accepted)
{
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_sensors.append(reinterpret_cast<DeviceDiscoverer::SensorInfo *>(sensor));
addSensorRow(sensor->m_name, sensor->m_id, sensor->m_units);
}
else
{
delete sensor;
}
}
void RemoteControlDeviceDialog::on_sensorRemove_clicked()
{
QList<QTableWidgetItem *> items = ui->sensors->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
QString id = ui->sensors->item(row, COL_ID)->text();
ui->sensors->removeRow(row);
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->deleteSensor(id);
}
}
void RemoteControlDeviceDialog::on_sensorEdit_clicked()
{
QList<QTableWidgetItem *> items = ui->sensors->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
QString id = ui->sensors->item(row, COL_ID)->text();
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
VISADevice::VISASensor *sensor = reinterpret_cast<VISADevice::VISASensor *>(info->getSensor(id));
RemoteControlVISASensorDialog dialog(m_settings, m_rcDevice, sensor, false);
if (dialog.exec() == QDialog::Accepted)
{
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
ui->sensors->item(row, COL_NAME)->setText(sensor->m_name);
ui->sensors->item(row, COL_ID)->setText(sensor->m_id);
ui->sensors->item(row, COL_UNITS)->setText(sensor->m_units);
}
}
}
void RemoteControlDeviceDialog::on_sensors_cellDoubleClicked(int row, int column)
{
if ((ui->protocol->currentText() == "VISA") && (column <= COL_ID)) {
on_sensorEdit_clicked();
}
}
void RemoteControlDeviceDialog::on_sensorUp_clicked()
{
QList<QTableWidgetItem *> items = ui->sensors->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row > 0)
{
// Swap rows in table
QTableWidgetItem *item1 = ui->sensors->takeItem(row, col);
QTableWidgetItem *item2 = ui->sensors->takeItem(row - 1, col);
ui->sensors->setItem(row - 1, col, item1);
ui->sensors->setItem(row, col, item2);
}
if (i == items.size() - 1)
{
ui->sensors->setCurrentItem(items[i]);
if (row > 0)
{
// Swap device info
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_sensors.swapItemsAt(row, row - 1);
}
}
}
}
void RemoteControlDeviceDialog::on_sensorDown_clicked()
{
QList<QTableWidgetItem *> items = ui->sensors->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row < ui->sensors->rowCount() - 1)
{
// Swap rows in table
QTableWidgetItem *item1 = ui->sensors->takeItem(row, col);
QTableWidgetItem *item2 = ui->sensors->takeItem(row + 1, col);
ui->sensors->setItem(row + 1, col, item1);
ui->sensors->setItem(row, col, item2);
}
if (i == items.size() - 1)
{
ui->sensors->setCurrentItem(items[i]);
if (row < ui->sensors->rowCount() - 1)
{
// Swap device info
DeviceDiscoverer::DeviceInfo *info = &m_deviceInfo[ui->device->currentIndex()];
info->m_sensors.swapItemsAt(row, row + 1);
}
}
}
}

View File

@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_REMOTECONTROLDEVICEDIALOG_H
#define INCLUDE_FEATURE_REMOTECONTROLDEVICEDIALOG_H
#include "ui_remotecontroldevicedialog.h"
#include "remotecontrolsettings.h"
#include "util/iot/device.h"
class RemoteControlDeviceDialog : public QDialog {
Q_OBJECT
public:
explicit RemoteControlDeviceDialog(RemoteControlSettings *settings, RemoteControlDevice *device, QWidget* parent = 0);
~RemoteControlDeviceDialog();
private slots:
void accept();
void on_protocol_currentTextChanged(const QString &protocol);
void on_device_currentIndexChanged(int index);
void deviceList(const QList<DeviceDiscoverer::DeviceInfo> &devices);
void deviceError(const QString &error);
void on_controlAdd_clicked();
void on_controlRemove_clicked();
void on_controlEdit_clicked();
void on_controlUp_clicked();
void on_controlDown_clicked();
void on_controls_cellDoubleClicked(int row, int column);
void on_sensorAdd_clicked();
void on_sensorRemove_clicked();
void on_sensorEdit_clicked();
void on_sensorUp_clicked();
void on_sensorDown_clicked();
void on_sensors_cellDoubleClicked(int row, int column);
void controlSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void sensorSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
void enableWidgets();
void resizeTables();
void updateTable();
int addControlRow(const QString &name, const QString &id, const QString &units);
int addSensorRow(const QString &name, const QString &id, const QString &units);
Ui::RemoteControlDeviceDialog* ui;
RemoteControlSettings *m_settings;
RemoteControlDevice *m_rcDevice;
DeviceDiscoverer *m_discoverer;
QList<DeviceDiscoverer::DeviceInfo> m_deviceInfo;
bool m_setDeviceWhenAvailable;
enum SensorCol {
COL_ENABLE,
COL_NAME,
COL_UNITS,
COL_ID,
COL_LABEL_LEFT,
COL_LABEL_RIGHT,
COL_FORMAT,
COL_PLOT
};
};
#endif // INCLUDE_FEATURE_REMOTECONTROLDEVICEDIALOG_H

View File

@ -0,0 +1,592 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteControlDeviceDialog</class>
<widget class="QDialog" name="RemoteControlDeviceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>700</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Remote Control Device</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="flat">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="serviceLayout"/>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="protocolLabel">
<property name="toolTip">
<string>Protocol to connect to the device</string>
</property>
<property name="text">
<string>Protocol:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="protocol">
<item>
<property name="text">
<string>Select a protocol...</string>
</property>
</item>
<item>
<property name="text">
<string>TPLink</string>
</property>
</item>
<item>
<property name="text">
<string>HomeAssistant</string>
</property>
</item>
<item>
<property name="text">
<string>VISA</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="deviceLabel">
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="modelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="device"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="model">
<property name="toolTip">
<string>Device model name</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelLabel">
<property name="text">
<string>Label:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="label">
<property name="toolTip">
<string>Label to display for this device in the UI</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="controlsGroup">
<property name="title">
<string>Controls</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="controlsHorizontalLayout">
<item>
<widget class="QTableWidget" name="controls">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Enable</string>
</property>
<property name="toolTip">
<string>Enable display of control in GUI</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Name of the control</string>
</property>
</column>
<column>
<property name="text">
<string>Units</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>Left Label</string>
</property>
<property name="toolTip">
<string>Label to display to the left of this control in the UI</string>
</property>
</column>
<column>
<property name="text">
<string>Right Label</string>
</property>
<property name="toolTip">
<string>Label to display to the right of this control in the UI</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="controlAdd">
<property name="toolTip">
<string>Add a new control</string>
</property>
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="controlRemove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Remove selected control</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="controlEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Edit selected control</string>
</property>
<property name="text">
<string>Edit...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="controlDown">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move down</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="controlUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move up</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.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>
</layout>
</item>
<item>
<layout class="QFormLayout" name="controlsForm">
<item row="0" column="0">
<widget class="QLabel" name="controlsLayoutLabel">
<property name="text">
<string>Layout:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="controlsLayout">
<property name="toolTip">
<string>How controls are laid out in the UI</string>
</property>
<item>
<property name="text">
<string>Horizontal</string>
</property>
</item>
<item>
<property name="text">
<string>Vertical</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="sensorsGroup">
<property name="title">
<string>Sensors</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="sensorsHorizontalLayout">
<item>
<widget class="QTableWidget" name="sensors">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Enable</string>
</property>
<property name="toolTip">
<string>Enable display of this sensor's value</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Sensor name</string>
</property>
</column>
<column>
<property name="text">
<string>Units</string>
</property>
<property name="toolTip">
<string>Units to display for this sensor in the UI</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>Left Label</string>
</property>
<property name="toolTip">
<string>Label to display to the left of this sensor in the UI</string>
</property>
</column>
<column>
<property name="text">
<string>Right Label</string>
</property>
<property name="toolTip">
<string>Label to display to the right of this sensor in the UI</string>
</property>
</column>
<column>
<property name="text">
<string>Format</string>
</property>
<property name="toolTip">
<string>printf format string for formatting the sensor value as a decimal floating point value (E.g. %f %.1f %.3e) or as a string (%s)</string>
</property>
</column>
<column>
<property name="text">
<string>Plot</string>
</property>
<property name="toolTip">
<string>Plot sensor data on chart</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="sensorAdd">
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sensorRemove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sensorEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Edit...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sensorDown">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move down</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sensorUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move up</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
</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="QFormLayout" name="sensorsForm">
<item row="1" column="0">
<widget class="QLabel" name="sensorsLayoutLabel">
<property name="text">
<string>Layout</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="sensorsLayout">
<property name="toolTip">
<string>How sensors are laid out in the UI</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Horizontal</string>
</property>
</item>
<item>
<property name="text">
<string>Vertical</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="yAxisLabel">
<property name="text">
<string>Y Axis</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="yAxis">
<property name="toolTip">
<string>Set whether each series of sensor data is plotted on a common Y axis or with individual axes</string>
</property>
<item>
<property name="text">
<string>Per-sensor</string>
</property>
</item>
<item>
<property name="text">
<string>Common</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>protocol</tabstop>
<tabstop>device</tabstop>
<tabstop>label</tabstop>
<tabstop>controls</tabstop>
<tabstop>controlAdd</tabstop>
<tabstop>controlRemove</tabstop>
<tabstop>controlEdit</tabstop>
<tabstop>controlDown</tabstop>
<tabstop>controlUp</tabstop>
<tabstop>controlsLayout</tabstop>
<tabstop>sensors</tabstop>
<tabstop>sensorAdd</tabstop>
<tabstop>sensorRemove</tabstop>
<tabstop>sensorEdit</tabstop>
<tabstop>sensorDown</tabstop>
<tabstop>sensorUp</tabstop>
<tabstop>sensorsLayout</tabstop>
<tabstop>yAxis</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RemoteControlDeviceDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RemoteControlDeviceDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
</connections>
</ui>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_REMOTECONTROLGUI_H_
#define INCLUDE_FEATURE_REMOTECONTROLGUI_H_
#include <QTimer>
#include <QHash>
#include <QIcon>
#include <QtCharts>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "util/iot/device.h"
#include "gui/buttonswitch.h"
#include "settings/rollupstate.h"
#include "remotecontrolsettings.h"
class PluginAPI;
class FeatureUISet;
class RemoteControl;
class QGroupBox;
class QLabel;
class FlowLayout;
namespace Ui {
class RemoteControlGUI;
}
using namespace QtCharts;
class RemoteControlGUI : public FeatureGUI {
Q_OBJECT
struct RemoteControlDeviceGUI {
RemoteControlDevice *m_rcDevice;
QWidget *m_container;
QHash<QString, QList<QWidget *>> m_controls;
QHash<QString, QLabel *> m_sensorValueLabels;
QHash<QString, QTableWidgetItem *> m_sensorValueItems;
QChartView *m_chartView;
QChart *m_chart;
QHash<QString, QLineSeries *> m_series;
QHash<QString, QLineSeries *> m_onePointSeries; // Workaround for charts not drawing series with only one point properly
RemoteControlDeviceGUI(RemoteControlDevice *rcDevice) :
m_rcDevice(rcDevice),
m_container(nullptr),
m_chartView(nullptr),
m_chart(nullptr)
{
}
~RemoteControlDeviceGUI()
{
}
};
public:
static RemoteControlGUI* 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; }
virtual void setWorkspaceIndex(int index);
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; }
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; }
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; }
private:
Ui::RemoteControlGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
RemoteControlSettings m_settings;
RollupState m_rollupState;
bool m_doApplySettings;
RemoteControl* m_remoteControl;
MessageQueue m_inputMessageQueue;
QList<RemoteControlDeviceGUI *> m_deviceGUIs;
QIcon m_startStopIcon;
enum SensorCol {
COL_LABEL,
COL_VALUE,
COL_UNITS
};
explicit RemoteControlGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~RemoteControlGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
bool handleMessage(const Message& message);
void makeUIConnections();
void createControls(RemoteControlDeviceGUI *gui, QBoxLayout *vBox, FlowLayout *flow, int &widgetCnt);
void createChart(RemoteControlDeviceGUI *gui, QVBoxLayout *vBox, const QString &id, const QString &units);
void createSensors(RemoteControlDeviceGUI *gui, QVBoxLayout *vBox, FlowLayout *flow, int &widgetCnt, bool &hasCharts);
RemoteControlDeviceGUI *createDeviceGUI(RemoteControlDevice *device);
void createGUI();
void updateControl(QWidget *widget, const DeviceDiscoverer::ControlInfo *controlInfo, const QString &key, const QVariant &value);
void updateChart(RemoteControlDeviceGUI *deviceGUI, const QString &key, const QVariant &value);
void deviceUpdated(const QString &protocol, const QString &deviceId, const QHash<QString, QVariant> &status);
void deviceUnavailable(const QString &protocol, const QString &deviceId);
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_update_clicked();
void on_settings_clicked();
void on_clearData_clicked();
};
#endif // INCLUDE_FEATURE_REMOTECONTROLGUI_H_

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteControlGUI</class>
<widget class="RollupContents" name="RemoteControlGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>60</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Packet Error Rate Tester</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>2</x>
<y>2</y>
<width>351</width>
<height>51</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</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 periodic updating of device state</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="QToolButton" name="update">
<property name="toolTip">
<string>Update state of all devices</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="QToolButton" name="settings">
<property name="toolTip">
<string>Open settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="clearData">
<property name="toolTip">
<string>Clear data in charts</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<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>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>startStop</tabstop>
<tabstop>update</tabstop>
<tabstop>settings</tabstop>
<tabstop>clearData</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,74 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "remotecontrolgui.h"
#endif
#include "remotecontrol.h"
#include "remotecontrolplugin.h"
const PluginDescriptor RemoteControlPlugin::m_pluginDescriptor = {
RemoteControl::m_featureId,
QStringLiteral("Remote Control"),
QStringLiteral("7.7.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
RemoteControlPlugin::RemoteControlPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& RemoteControlPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void RemoteControlPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(RemoteControl::m_featureIdURI, RemoteControl::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* RemoteControlPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* RemoteControlPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return RemoteControlGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* RemoteControlPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new RemoteControl(webAPIAdapterInterface);
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_REMOTECONTROLPLUGIN_H
#define INCLUDE_FEATURE_REMOTECONTROLPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class RemoteControlPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.remotecontrol")
public:
explicit RemoteControlPlugin(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;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_REMOTECONTROLPLUGIN_H

View File

@ -0,0 +1,360 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 <QDebug>
#include <QDataStream>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "remotecontrolsettings.h"
RemoteControlSettings::RemoteControlSettings() :
m_rollupState(nullptr)
{
resetToDefaults();
}
void RemoteControlSettings::resetToDefaults()
{
m_updatePeriod = 1.0f;
m_tpLinkUsername = "";
m_tpLinkPassword = "";
m_homeAssistantToken = "";
m_homeAssistantHost = "http://homeassistant.local:8123";
m_visaResourceFilter = "";
m_visaLogIO = false;
m_chartHeightFixed = false;
m_chartHeightPixels = 130;
m_title = "Remote Control";
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;
m_workspaceIndex = 0;
}
QByteArray RemoteControlSettings::serialize() const
{
SimpleSerializer s(1);
s.writeFloat(1, m_updatePeriod);
s.writeString(2, m_tpLinkUsername);
s.writeString(3, m_tpLinkPassword);
s.writeString(4, m_homeAssistantToken);
s.writeString(5, m_homeAssistantHost);
s.writeString(6, m_visaResourceFilter);
s.writeBool(7, m_visaLogIO);
s.writeBool(10, m_chartHeightFixed);
s.writeS32(11, m_chartHeightPixels);
s.writeBlob(19, serializeDeviceList(m_devices));
s.writeString(20, m_title);
s.writeU32(21, m_rgbColor);
s.writeBool(22, m_useReverseAPI);
s.writeString(23, m_reverseAPIAddress);
s.writeU32(24, m_reverseAPIPort);
s.writeU32(25, m_reverseAPIFeatureSetIndex);
s.writeU32(26, m_reverseAPIFeatureIndex);
if (m_rollupState) {
s.writeBlob(27, m_rollupState->serialize());
}
s.writeS32(28, m_workspaceIndex);
return s.final();
}
bool RemoteControlSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
QByteArray blob;
d.readFloat(1, &m_updatePeriod, 1.0f);
d.readString(2, &m_tpLinkUsername, "");
d.readString(3, &m_tpLinkPassword, "");
d.readString(4, &m_homeAssistantToken, "");
d.readString(5, &m_homeAssistantHost, "http://homeassistant.local:8123");
d.readString(6, &m_visaResourceFilter, "");
d.readBool(7, &m_visaLogIO, false);
d.readBool(10, &m_chartHeightFixed, false);
d.readS32(11, &m_chartHeightPixels, 130);
d.readBlob(19, &blob);
deserializeDeviceList(blob, m_devices);
d.readString(20, &m_title, "Remote Control");
d.readU32(21, &m_rgbColor, QColor(225, 25, 99).rgb());
d.readBool(22, &m_useReverseAPI, false);
d.readString(23, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(24, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(25, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(26, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(27, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(28, &m_workspaceIndex, 0);
d.readBlob(29, &m_geometryBytes);
return true;
}
else
{
resetToDefaults();
return false;
}
}
QByteArray RemoteControlControl::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_id);
s.writeString(2, m_labelLeft);
s.writeString(3, m_labelRight);
return s.final();
}
bool RemoteControlControl::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid()) {
return false;
}
if (d.getVersion() == 1)
{
d.readString(1, &m_id);
d.readString(2, &m_labelLeft);
d.readString(3, &m_labelRight);
return true;
}
else
{
return false;
}
}
QByteArray RemoteControlSensor::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_id);
s.writeString(2, m_labelLeft);
s.writeString(3, m_labelRight);
s.writeString(4, m_format);
s.writeBool(5, m_plot);
return s.final();
}
bool RemoteControlSensor::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid()) {
return false;
}
if (d.getVersion() == 1)
{
d.readString(1, &m_id);
d.readString(2, &m_labelLeft);
d.readString(3, &m_labelRight);
d.readString(4, &m_format);
d.readBool(5, &m_plot);
return true;
}
else
{
return false;
}
}
QByteArray RemoteControlDevice::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_protocol);
s.writeString(2, m_label);
s.writeBlob(3, serializeControlList());
s.writeBlob(4, serializeSensorList());
s.writeBool(5, m_verticalControls);
s.writeBool(6, m_verticalSensors);
s.writeBool(7, m_commonYAxis);
s.writeBlob(8, m_info.serialize());
return s.final();
}
bool RemoteControlDevice::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid()) {
return false;
}
if (d.getVersion() == 1)
{
QByteArray blob;
d.readString(1, &m_protocol);
d.readString(2, &m_label);
d.readBlob(3, &blob);
deserializeControlList(blob);
d.readBlob(4, &blob);
deserializeSensorList(blob);
d.readBool(5, &m_verticalControls, false);
d.readBool(6, &m_verticalSensors, true);
d.readBool(7, &m_commonYAxis);
d.readBlob(8, &blob);
m_info.deserialize(blob);
return true;
}
else
{
return false;
}
}
QDataStream& operator<<(QDataStream& out, const RemoteControlControl& control)
{
out << control.serialize();
return out;
}
QDataStream& operator>>(QDataStream& in, RemoteControlControl& control)
{
QByteArray data;
in >> data;
control.deserialize(data);
return in;
}
QDataStream& operator<<(QDataStream& out, const RemoteControlSensor& sensor)
{
out << sensor.serialize();
return out;
}
QDataStream& operator>>(QDataStream& in, RemoteControlSensor& sensor)
{
QByteArray data;
in >> data;
sensor.deserialize(data);
return in;
}
QDataStream& operator<<(QDataStream& out, const RemoteControlDevice* device)
{
out << device->serialize();
return out;
}
QDataStream& operator>>(QDataStream& in, RemoteControlDevice*& device)
{
device = new RemoteControlDevice();
QByteArray data;
in >> data;
device->deserialize(data);
return in;
}
QByteArray RemoteControlDevice::serializeControlList() const
{
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << m_controls;
delete stream;
return data;
}
void RemoteControlDevice::deserializeControlList(const QByteArray& data)
{
QDataStream *stream = new QDataStream(data);
(*stream) >> m_controls;
delete stream;
}
QByteArray RemoteControlDevice::serializeSensorList() const
{
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << m_sensors;
delete stream;
return data;
}
void RemoteControlDevice::deserializeSensorList(const QByteArray& data)
{
QDataStream *stream = new QDataStream(data);
(*stream) >> m_sensors;
delete stream;
}
QByteArray RemoteControlSettings::serializeDeviceList(const QList<RemoteControlDevice *>& devices) const
{
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << devices;
delete stream;
return data;
}
void RemoteControlSettings::deserializeDeviceList(const QByteArray& data, QList<RemoteControlDevice *>& devices)
{
QDataStream *stream = new QDataStream(data);
(*stream) >> devices;
delete stream;
}

View File

@ -0,0 +1,129 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_REMOTECONTROLSETTINGS_H_
#define INCLUDE_FEATURE_REMOTECONTROLSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "util/message.h"
#include "util/iot/device.h"
class Serializable;
struct RemoteControlControl {
QString m_id;
QString m_labelLeft;
QString m_labelRight;
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
struct RemoteControlSensor {
QString m_id;
QString m_labelLeft;
QString m_labelRight;
QString m_format;
bool m_plot;
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
struct RemoteControlDevice {
QString m_protocol; // TPLink, HomeAssistant, VISA
QString m_label; // Label to display as device name
QList<RemoteControlControl> m_controls; // Enabled controls
QList<RemoteControlSensor> m_sensors; // Enabled sensors
bool m_verticalControls;
bool m_verticalSensors;
bool m_commonYAxis; // Whether multiple series on chart should share same axis
DeviceDiscoverer::DeviceInfo m_info;
RemoteControlDevice() :
m_verticalControls(false),
m_verticalSensors(true),
m_commonYAxis(false)
{
}
RemoteControlControl *getControl(const QString &id)
{
for (int i = 0; i < m_controls.size(); i++)
{
if (m_controls[i].m_id == id) {
return &m_controls[i];
}
}
return nullptr;
}
RemoteControlSensor *getSensor(const QString &id)
{
for (int i = 0; i < m_sensors.size(); i++)
{
if (m_sensors[i].m_id == id) {
return &m_sensors[i];
}
}
return nullptr;
}
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
protected:
QByteArray serializeControlList() const;
void deserializeControlList(const QByteArray& data);
QByteArray serializeSensorList() const;
void deserializeSensorList(const QByteArray& data);
};
struct RemoteControlSettings
{
float m_updatePeriod; //!< Period between device state updates
QString m_tpLinkUsername;
QString m_tpLinkPassword;
QString m_homeAssistantToken;
QString m_homeAssistantHost;
QString m_visaResourceFilter;
bool m_visaLogIO;
bool m_chartHeightFixed; //!< Whether chart heights should be fixed (to m_chartHeightPixels) or allowed to expand to use available space
int m_chartHeightPixels; //!< Chart height in pixels when fixed
QList<RemoteControlDevice *> m_devices;
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
Serializable *m_rollupState;
int m_workspaceIndex;
QByteArray m_geometryBytes;
RemoteControlSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
QByteArray serializeDeviceList(const QList<RemoteControlDevice *>& devices) const;
void deserializeDeviceList(const QByteArray& data, QList<RemoteControlDevice *>& devices);
};
#endif // INCLUDE_FEATURE_REMOTECONTROLSETTINGS_H_

View File

@ -0,0 +1,237 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "remotecontrolsettingsdialog.h"
#include "channel/channelwebapiutils.h"
RemoteControlSettingsDialog::RemoteControlSettingsDialog(RemoteControlSettings *settings, QWidget* parent) :
QDialog(parent),
m_settings(settings),
ui(new Ui::RemoteControlSettingsDialog)
{
ui->setupUi(this);
resizeTable();
ui->tpLinkUsername->setText(m_settings->m_tpLinkUsername);
ui->tpLinkPassword->setText(m_settings->m_tpLinkPassword);
ui->homeAssistantToken->setText(m_settings->m_homeAssistantToken);
ui->homeAssistantHost->setText(m_settings->m_homeAssistantHost);
ui->visaResourceFilter->setText(m_settings->m_visaResourceFilter);
ui->visaLogIO->setChecked(m_settings->m_visaLogIO);
ui->updatePeriod->setValue(m_settings->m_updatePeriod);
ui->chartVerticalPolicy->setCurrentIndex((int)m_settings->m_chartHeightFixed);
ui->chartHeight->setValue(m_settings->m_chartHeightPixels);
connect(ui->devices->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RemoteControlSettingsDialog::devicesSelectionChanged);
updateTable();
for (auto device : m_settings->m_devices) {
m_devices.append(new RemoteControlDevice(*device));
}
}
RemoteControlSettingsDialog::~RemoteControlSettingsDialog()
{
qDeleteAll(m_devices);
m_devices.clear();
delete ui;
}
void RemoteControlSettingsDialog::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = ui->devices->rowCount();
ui->devices->setRowCount(row + 1);
ui->devices->setItem(row, COL_LABEL, new QTableWidgetItem("A short label"));
ui->devices->setItem(row, COL_NAME, new QTableWidgetItem("A reasonably long name"));
ui->devices->setItem(row, COL_MODEL, new QTableWidgetItem("A long model name to display"));
ui->devices->setItem(row, COL_SERVICE, new QTableWidgetItem("Home Assistant"));
ui->devices->resizeColumnsToContents();
ui->devices->removeRow(row);
}
void RemoteControlSettingsDialog::addToTable(int row, RemoteControlDevice *device)
{
QTableWidgetItem *item;
item = new QTableWidgetItem(device->m_label);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->devices->setItem(row, COL_LABEL, item);
item = new QTableWidgetItem(device->m_info.m_name);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->devices->setItem(row, COL_NAME, item);
item = new QTableWidgetItem(device->m_info.m_model);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->devices->setItem(row, COL_MODEL, item);
item = new QTableWidgetItem(device->m_protocol);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->devices->setItem(row, COL_SERVICE, item);
}
void RemoteControlSettingsDialog::updateTable()
{
int row = 0;
ui->devices->setSortingEnabled(false);
ui->devices->setRowCount(m_settings->m_devices.size());
for (auto device : m_settings->m_devices)
{
addToTable(row, device);
row++;
}
ui->devices->setSortingEnabled(true);
}
void RemoteControlSettingsDialog::accept()
{
QDialog::accept();
m_settings->m_tpLinkUsername = ui->tpLinkUsername->text();
m_settings->m_tpLinkPassword = ui->tpLinkPassword->text();
m_settings->m_homeAssistantToken = ui->homeAssistantToken->text();
m_settings->m_homeAssistantHost = ui->homeAssistantHost->text();
m_settings->m_visaResourceFilter = ui->visaResourceFilter->text();
m_settings->m_visaLogIO = ui->visaLogIO->isChecked();
m_settings->m_updatePeriod = ui->updatePeriod->value();
m_settings->m_chartHeightFixed = ui->chartVerticalPolicy->currentIndex() == 1;
m_settings->m_chartHeightPixels = ui->chartHeight->value();
qDeleteAll(m_settings->m_devices);
m_settings->m_devices.clear();
m_settings->m_devices = m_devices;
m_devices.clear(); // So destructor doesn't delete them
}
void RemoteControlSettingsDialog::on_devices_cellDoubleClicked(int row, int column)
{
on_edit_clicked();
}
void RemoteControlSettingsDialog::devicesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
bool enabled = selected.indexes().size() > 0;
ui->remove->setEnabled(enabled);
ui->edit->setEnabled(enabled);
ui->deviceUp->setEnabled(enabled);
ui->deviceDown->setEnabled(enabled);
}
void RemoteControlSettingsDialog::on_add_clicked()
{
RemoteControlDevice *device = new RemoteControlDevice();
RemoteControlDeviceDialog dialog(m_settings, device);
if (dialog.exec() == QDialog::Accepted)
{
int row = ui->devices->rowCount();
ui->devices->setRowCount(row + 1);
addToTable(row, device);
m_devices.append(device);
}
else
{
delete device;
}
}
void RemoteControlSettingsDialog::on_remove_clicked()
{
QList<QTableWidgetItem *> items = ui->devices->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
if (row >= 0)
{
ui->devices->removeRow(row);
delete m_devices.takeAt(row);
}
}
}
void RemoteControlSettingsDialog::on_edit_clicked()
{
QList<QTableWidgetItem *> items = ui->devices->selectedItems();
if (items.size() > 0)
{
int row = items[0]->row();
if (row >= 0)
{
RemoteControlDevice *device = m_devices[row];
RemoteControlDeviceDialog dialog(m_settings, device);
if (dialog.exec() == QDialog::Accepted)
{
ui->devices->item(row, COL_LABEL)->setText(device->m_label);
ui->devices->item(row, COL_NAME)->setText(device->m_info.m_name);
ui->devices->item(row, COL_MODEL)->setText(device->m_info.m_model);
ui->devices->item(row, COL_SERVICE)->setText(device->m_protocol);
}
}
}
}
void RemoteControlSettingsDialog::on_deviceUp_clicked()
{
QList<QTableWidgetItem *> items = ui->devices->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row > 0)
{
QTableWidgetItem *item1 = ui->devices->takeItem(row, col);
QTableWidgetItem *item2 = ui->devices->takeItem(row - 1, col);
ui->devices->setItem(row - 1, col, item1);
ui->devices->setItem(row, col, item2);
if (i == items.size() - 1)
{
ui->devices->setCurrentItem(items[i]);
m_devices.swapItemsAt(row, row - 1);
}
}
}
}
void RemoteControlSettingsDialog::on_deviceDown_clicked()
{
QList<QTableWidgetItem *> items = ui->devices->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = items[i]->row();
int col = items[i]->column();
if (row < ui->devices->rowCount() - 1)
{
QTableWidgetItem *item1 = ui->devices->takeItem(row, col);
QTableWidgetItem *item2 = ui->devices->takeItem(row + 1, col);
ui->devices->setItem(row + 1, col, item1);
ui->devices->setItem(row, col, item2);
if (i == items.size() - 1)
{
ui->devices->setCurrentItem(items[i]);
m_devices.swapItemsAt(row, row + 1);
}
}
}
}
void RemoteControlSettingsDialog::on_chartVerticalPolicy_currentIndexChanged(int index)
{
bool enabled = index == 1;
ui->chartHeightLabel->setEnabled(enabled);
ui->chartHeight->setEnabled(enabled);
}

View File

@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_REMOTECONTROLSETTINGSDIALOG_H
#define INCLUDE_FEATURE_REMOTECONTROLSETTINGSDIALOG_H
#include "ui_remotecontrolsettingsdialog.h"
#include "remotecontrolsettings.h"
#include "remotecontroldevicedialog.h"
class RemoteControlSettingsDialog : public QDialog {
Q_OBJECT
public:
explicit RemoteControlSettingsDialog(RemoteControlSettings *settings, QWidget* parent = 0);
~RemoteControlSettingsDialog();
private slots:
void accept();
void on_devices_cellDoubleClicked(int row, int column);
void devicesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void on_add_clicked();
void on_remove_clicked();
void on_edit_clicked();
void on_deviceUp_clicked();
void on_deviceDown_clicked();
void on_chartVerticalPolicy_currentIndexChanged(int index);
private:
void resizeTable();
void addToTable(int row, RemoteControlDevice *device);
void updateTable();
Ui::RemoteControlSettingsDialog* ui;
RemoteControlSettings *m_settings;
QList<RemoteControlDevice *> m_devices;
enum DeviceCol {
COL_LABEL,
COL_NAME,
COL_MODEL,
COL_SERVICE,
};
};
#endif // INCLUDE_FEATURE_REMOTECONTROLSETTINGSDIALOG_H

View File

@ -0,0 +1,474 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteControlSettingsDialog</class>
<widget class="QDialog" name="RemoteControlSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>592</width>
<height>640</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Remote Control Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="devicesTab">
<attribute name="title">
<string>Devices</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableWidget" name="devices">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Label</string>
</property>
<property name="toolTip">
<string>Label used for the device in the GUI</string>
</property>
</column>
<column>
<property name="text">
<string>Device</string>
</property>
<property name="toolTip">
<string>Device name</string>
</property>
</column>
<column>
<property name="text">
<string>Model</string>
</property>
<property name="toolTip">
<string>Device model</string>
</property>
</column>
<column>
<property name="text">
<string>Protocol</string>
</property>
<property name="toolTip">
<string>Protocol used to communicate with the device</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="add">
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Edit...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deviceUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move up</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deviceDown">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Move down</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</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>
</layout>
</widget>
<widget class="QWidget" name="settingsTab">
<attribute name="title">
<string>Settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="tpLinkGroup">
<property name="title">
<string>TP-Link</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="tpLinkUsernameLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="tpLinkUsername">
<property name="toolTip">
<string>Username. Typically your email address</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="tpLinkPasswordLabel">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="tpLinkPassword">
<property name="toolTip">
<string>Password</string>
</property>
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="homeAssistantGroup">
<property name="title">
<string>Home Assistant</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="homeAssistantTokenLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Access Token</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="homeAssistantHostLabel">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="homeAssistantToken">
<property name="toolTip">
<string>API access token. Can be generated on your profile page: http://homeassistant.local:8123/profile</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="homeAssistantHost">
<property name="toolTip">
<string>Hostname of computer running Home Assistant. Typically http://homeassistant.local:8123/profile</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="visaGroup">
<property name="title">
<string>VISA</string>
</property>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="visaResourceFilterLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Resource filter</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="visaLogIOLabel">
<property name="text">
<string>Log IO</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="visaResourceFilter">
<property name="toolTip">
<string>Regular expression of VISA resources not to connect to</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="visaLogIO">
<property name="toolTip">
<string>When checked, VISA input and output is written to SDRangel log</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="deviceGroup">
<property name="title">
<string>Devices</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Update period (s)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Period in seconds between requests to update device state</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>1000000.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="chartsGroup">
<property name="title">
<string>Charts</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="chartVerticalPolicyLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="chartVerticalPolicy">
<item>
<property name="text">
<string>Expanding</string>
</property>
</item>
<item>
<property name="text">
<string>Fixed</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="chartHeightLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Height (pixels)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="chartHeight">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>50</number>
</property>
<property name="maximum">
<number>2000</number>
</property>
<property name="value">
<number>130</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RemoteControlSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RemoteControlSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,193 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "remotecontrolvisacontroldialog.h"
#include <QDebug>
#include <QPushButton>
RemoteControlVISAControlDialog::RemoteControlVISAControlDialog(RemoteControlSettings *settings, RemoteControlDevice *device, VISADevice::VISAControl *control, bool add, QWidget* parent) :
QDialog(parent),
ui(new Ui::RemoteControlVISAControlDialog),
m_settings(settings),
m_device(device),
m_control(control),
m_add(add),
m_userHasEditedId(false)
{
ui->setupUi(this);
ui->name->setText(m_control->m_name);
ui->id->setText(m_control->m_id);
DeviceDiscoverer::Type type = m_control->m_type == DeviceDiscoverer::AUTO ? DeviceDiscoverer::BOOL : m_control->m_type;
ui->type->setCurrentText(DeviceDiscoverer::m_typeStrings[(int)type]);
ui->widgetType->setCurrentText(DeviceDiscoverer::m_widgetTypeStrings[(int)m_control->m_widgetType]);
ui->min->setValue(m_control->m_min);
ui->max->setValue(m_control->m_max);
ui->scale->setValue(m_control->m_scale);
ui->precision->setValue(m_control->m_precision);
ui->values->insertItems(0, m_control->m_values);
if (m_control->m_values.size() > 0) {
ui->label->setText(m_control->m_values[0]);
}
ui->units->setText(m_control->m_units);
ui->setState->setPlainText(m_control->m_setState);
ui->getState->setPlainText(m_control->m_getState);
on_type_currentIndexChanged(ui->type->currentIndex());
validate();
}
RemoteControlVISAControlDialog::~RemoteControlVISAControlDialog()
{
delete ui;
}
void RemoteControlVISAControlDialog::accept()
{
QDialog::accept();
m_control->m_name = ui->name->text();
m_control->m_id = ui->id->text();
m_control->m_type = (DeviceDiscoverer::Type)DeviceDiscoverer::m_typeStrings.indexOf(ui->type->currentText());
m_control->m_widgetType = (DeviceDiscoverer::WidgetType)DeviceDiscoverer::m_widgetTypeStrings.indexOf(ui->widgetType->currentText());
m_control->m_min = ui->min->value();
m_control->m_max = ui->max->value();
m_control->m_scale = ui->scale->value();
m_control->m_precision = ui->precision->value();
m_control->m_values.clear();
if (m_control->m_type == DeviceDiscoverer::BUTTON)
{
m_control->m_values.append(ui->label->text());
}
else
{
for (int i = 0; i < ui->values->count(); i++) {
m_control->m_values.append(ui->values->itemText(i));
}
}
m_control->m_units = ui->units->text();
m_control->m_setState = ui->setState->toPlainText();
m_control->m_getState = ui->getState->toPlainText();
}
void RemoteControlVISAControlDialog::on_type_currentIndexChanged(int index)
{
DeviceDiscoverer::Type type;
if (index < 0) {
type = DeviceDiscoverer::BOOL;
} else {
type = (DeviceDiscoverer::Type)DeviceDiscoverer::m_typeStrings.indexOf(ui->type->currentText());
}
bool minMaxVisible = true; // Default to FLOAT
bool precisionVisible = true;
bool listVisible = false;
bool labelVisible = false;
int decimals = 3;
if (type == DeviceDiscoverer::BOOL)
{
minMaxVisible = false;
precisionVisible = false;
}
else if (type == DeviceDiscoverer::INT)
{
decimals = 0;
precisionVisible = false;
}
else if (type == DeviceDiscoverer::STRING)
{
minMaxVisible = false;
precisionVisible = false;
}
else if (type == DeviceDiscoverer::LIST)
{
minMaxVisible = false;
precisionVisible = false;
listVisible = true;
}
else if (type == DeviceDiscoverer::BUTTON)
{
minMaxVisible = false;
precisionVisible = false;
labelVisible = true;
}
ui->widgetType->setVisible(precisionVisible);
ui->minLabel->setVisible(minMaxVisible);
ui->min->setVisible(minMaxVisible);
ui->min->setDecimals(decimals);
ui->maxLabel->setVisible(minMaxVisible);
ui->max->setVisible(minMaxVisible);
ui->max->setDecimals(decimals);
ui->scaleLabel->setVisible(precisionVisible);
ui->scale->setVisible(precisionVisible);
ui->precisionLabel->setVisible(precisionVisible);
ui->precision->setVisible(precisionVisible);
ui->values->setVisible(listVisible);
ui->remove->setVisible(listVisible);
ui->labelLabel->setVisible(labelVisible);
ui->label->setVisible(labelVisible);
bool getStateEnabled = type != DeviceDiscoverer::BUTTON;
ui->getStateLabel->setEnabled(getStateEnabled);
ui->getState->setEnabled(getStateEnabled);
}
void RemoteControlVISAControlDialog::on_name_textChanged(const QString &text)
{
if (m_add && !m_userHasEditedId)
{
// Set Id to lower case version of name
ui->id->setText(text.trimmed().toLower().replace(" ", ""));
}
}
void RemoteControlVISAControlDialog::on_id_textChanged(const QString &text)
{
validate();
}
void RemoteControlVISAControlDialog::on_id_textEdited(const QString &text)
{
m_userHasEditedId = true;
}
void RemoteControlVISAControlDialog::on_setState_textChanged()
{
validate();
}
void RemoteControlVISAControlDialog::on_remove_clicked()
{
ui->values->removeItem(ui->values->currentIndex());
}
void RemoteControlVISAControlDialog::validate()
{
bool valid = true;
QString id = ui->id->text().trimmed();
if (id.isEmpty()) {
valid = false;
} else if (m_add) {
if (m_device->getControl(id)) {
valid = false;
}
}
if (ui->setState->toPlainText().trimmed().isEmpty()) {
valid = false;
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
}

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_REMOTECONTROLVISACONTROLDIALOG_H
#define INCLUDE_FEATURE_REMOTECONTROLVISACONTROLDIALOG_H
#include "ui_remotecontrolvisacontroldialog.h"
#include "remotecontrolsettings.h"
#include "util/iot/visa.h"
class RemoteControlVISAControlDialog : public QDialog {
Q_OBJECT
public:
explicit RemoteControlVISAControlDialog(RemoteControlSettings *settings, RemoteControlDevice *device, VISADevice::VISAControl *control, bool add, QWidget* parent = 0);
~RemoteControlVISAControlDialog();
private slots:
void accept();
void on_name_textChanged(const QString &text);
void on_type_currentIndexChanged(int index);
void on_id_textChanged(const QString &text);
void on_id_textEdited(const QString &text);
void on_setState_textChanged();
void on_remove_clicked();
private:
Ui::RemoteControlVISAControlDialog* ui;
RemoteControlSettings *m_settings;
RemoteControlDevice *m_device;
VISADevice::VISAControl *m_control;
bool m_add;
bool m_userHasEditedId;
void validate();
};
#endif // INCLUDE_FEATURE_REMOTECONTROLVISACONTROLDIALOG_H

View File

@ -0,0 +1,374 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteControlVISAControlDialog</class>
<widget class="QDialog" name="RemoteControlVISAControlDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>540</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>VISA Control</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="controlsGroup">
<property name="title">
<string>Controls</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="name">
<property name="toolTip">
<string>Name for this control</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="idLabel">
<property name="text">
<string>ID</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="id">
<property name="toolTip">
<string>Unique identifier for this control</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="typeLayout">
<item>
<widget class="QComboBox" name="type">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>Boolean</string>
</property>
</item>
<item>
<property name="text">
<string>Integer</string>
</property>
</item>
<item>
<property name="text">
<string>Float</string>
</property>
</item>
<item>
<property name="text">
<string>String</string>
</property>
</item>
<item>
<property name="text">
<string>List</string>
</property>
</item>
<item>
<property name="text">
<string>Button</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="widgetType">
<property name="toolTip">
<string>Type of widget to display value</string>
</property>
<item>
<property name="text">
<string>Spin box</string>
</property>
</item>
<item>
<property name="text">
<string>Dial</string>
</property>
</item>
<item>
<property name="text">
<string>Slider</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="minLabel">
<property name="text">
<string>Min</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="min">
<property name="toolTip">
<string>Minimum value</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-9999999999999999932209486743616279764617084419440640.000000000000000</double>
</property>
<property name="maximum">
<double>10000000000000000735758738477112498397576062152177456799245857901351759143802190202050679656153088.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="maxLabel">
<property name="text">
<string>Max</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="max">
<property name="toolTip">
<string>Maximum value</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-999999999999999983222784.000000000000000</double>
</property>
<property name="maximum">
<double>100000000000000007629769841091887003294964970946560.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scaleLabel">
<property name="text">
<string>Scale</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="scale">
<property name="toolTip">
<string>Scale factor applied to value before sending to instrument</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>1000000000000000.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="precisionLabel">
<property name="text">
<string>Precision</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="precision">
<property name="toolTip">
<string>Precision (number of decimals)</string>
</property>
<property name="maximum">
<number>323</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelLabel">
<property name="text">
<string>Label</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="label">
<property name="toolTip">
<string>Button label</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="values">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>List of allowable values</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="toolTip">
<string>Remove current entry from list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
<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 row="4" column="0">
<widget class="QLabel" name="unitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="units">
<property name="toolTip">
<string>Units</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="setStateLabel">
<property name="text">
<string>Set state</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QTextEdit" name="setState"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="getStateLabel">
<property name="text">
<string>Get state</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QTextEdit" name="getState"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>name</tabstop>
<tabstop>id</tabstop>
<tabstop>type</tabstop>
<tabstop>min</tabstop>
<tabstop>max</tabstop>
<tabstop>precision</tabstop>
<tabstop>values</tabstop>
<tabstop>units</tabstop>
<tabstop>setState</tabstop>
<tabstop>getState</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RemoteControlVISAControlDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RemoteControlVISAControlDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>619</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>319</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "remotecontrolvisasensordialog.h"
#include <QDebug>
#include <QPushButton>
RemoteControlVISASensorDialog::RemoteControlVISASensorDialog(RemoteControlSettings *settings, RemoteControlDevice *device, VISADevice::VISASensor *sensor, bool add, QWidget* parent) :
QDialog(parent),
ui(new Ui::RemoteControlVISASensorDialog),
m_settings(settings),
m_device(device),
m_sensor(sensor),
m_add(add),
m_userHasEditedId(false)
{
ui->setupUi(this);
ui->name->setText(m_sensor->m_name);
ui->id->setText(m_sensor->m_id);
ui->type->setCurrentText(DeviceDiscoverer::m_typeStrings[(int)m_sensor->m_type]);
ui->units->setText(m_sensor->m_units);
ui->getState->setPlainText(m_sensor->m_getState);
validate();
}
RemoteControlVISASensorDialog::~RemoteControlVISASensorDialog()
{
delete ui;
}
void RemoteControlVISASensorDialog::accept()
{
QDialog::accept();
m_sensor->m_name = ui->name->text();
m_sensor->m_id = ui->id->text();
m_sensor->m_type = (DeviceDiscoverer::Type)DeviceDiscoverer::m_typeStrings.indexOf(ui->type->currentText());
m_sensor->m_units = ui->units->text();
m_sensor->m_getState = ui->getState->toPlainText();
}
void RemoteControlVISASensorDialog::on_name_textChanged(const QString &text)
{
if (m_add && !m_userHasEditedId)
{
// Set Id to lower case version of name
ui->id->setText(text.trimmed().toLower().replace(" ", ""));
}
}
void RemoteControlVISASensorDialog::on_id_textChanged(const QString &text)
{
validate();
}
void RemoteControlVISASensorDialog::on_id_textEdited(const QString &text)
{
m_userHasEditedId = true;
}
void RemoteControlVISASensorDialog::on_getState_textChanged()
{
validate();
}
void RemoteControlVISASensorDialog::validate()
{
bool valid = true;
QString id = ui->id->text().trimmed();
if (id.isEmpty()) {
valid = false;
} else if (m_add) {
if (m_device->getSensor(id)) {
valid = false;
}
}
if (ui->getState->toPlainText().trimmed().isEmpty()) {
valid = false;
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
}

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_REMOTECONTROLVISASENSORDIALOG_H
#define INCLUDE_FEATURE_REMOTECONTROLVISASENSORDIALOG_H
#include "ui_remotecontrolvisasensordialog.h"
#include "remotecontrolsettings.h"
#include "util/iot/visa.h"
class RemoteControlVISASensorDialog : public QDialog {
Q_OBJECT
public:
explicit RemoteControlVISASensorDialog(RemoteControlSettings *settings, RemoteControlDevice *device, VISADevice::VISASensor *sensor, bool add, QWidget* parent = 0);
~RemoteControlVISASensorDialog();
private slots:
void accept();
void on_name_textChanged(const QString &text);
void on_id_textChanged(const QString &text);
void on_id_textEdited(const QString &text);
void on_getState_textChanged();
private:
Ui::RemoteControlVISASensorDialog* ui;
RemoteControlSettings *m_settings;
RemoteControlDevice *m_device;
VISADevice::VISASensor *m_sensor;
bool m_add;
bool m_userHasEditedId;
void validate();
};
#endif // INCLUDE_FEATURE_REMOTECONTROLVISASENSORDIALOG_H

View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteControlVISASensorDialog</class>
<widget class="QDialog" name="RemoteControlVISASensorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>350</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>VISA Sensor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="flat">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="topMargin">
<number>0</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="getStateLabel">
<property name="text">
<string>Get state</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QTextEdit" name="getState">
<property name="toolTip">
<string>VISA/SCPI command to get the state for this sensor</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name">
<property name="toolTip">
<string>Name of this sensor</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="type">
<item>
<property name="text">
<string>Boolean</string>
</property>
</item>
<item>
<property name="text">
<string>Float</string>
</property>
</item>
<item>
<property name="text">
<string>String</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="idLabel">
<property name="text">
<string>ID</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="id">
<property name="toolTip">
<string>Unique (per device) identifier for this sensor</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="units">
<property name="toolTip">
<string>The units of the sensor state</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>name</tabstop>
<tabstop>id</tabstop>
<tabstop>type</tabstop>
<tabstop>units</tabstop>
<tabstop>getState</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RemoteControlVISASensorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>356</x>
<y>330</y>
</hint>
<hint type="destinationlabel">
<x>356</x>
<y>175</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RemoteControlVISASensorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>356</x>
<y>330</y>
</hint>
<hint type="destinationlabel">
<x>356</x>
<y>175</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,234 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "util/iot/device.h"
#include "remotecontrol.h"
#include "remotecontrolworker.h"
RemoteControlWorker::RemoteControlWorker() :
m_msgQueueToFeature(nullptr),
m_msgQueueToGUI(nullptr),
m_timer(this)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_timer, SIGNAL(timeout()), this, SLOT(update()));
}
RemoteControlWorker::~RemoteControlWorker()
{
m_timer.stop();
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_inputMessageQueue.clear();
qDeleteAll(m_devices);
m_devices.clear();
}
void RemoteControlWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
Device *RemoteControlWorker::getDevice(const QString &protocol, const QString deviceId) const
{
for (auto device : m_devices)
{
if ((device->getProtocol() == protocol) && (device->getDeviceId() == deviceId)) {
return device;
}
}
return nullptr;
}
bool RemoteControlWorker::handleMessage(const Message& cmd)
{
if (RemoteControl::MsgConfigureRemoteControl::match(cmd))
{
RemoteControl::MsgConfigureRemoteControl& cfg = (RemoteControl::MsgConfigureRemoteControl&) cmd;
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (RemoteControl::MsgStartStop::match(cmd))
{
RemoteControl::MsgStartStop& cfg = (RemoteControl::MsgStartStop&) cmd;
// Start/stop automatic state updates
if (cfg.getStartStop()) {
m_timer.start(m_settings.m_updatePeriod * 1000.0);
} else {
m_timer.stop();
}
return true;
}
else if (RemoteControl::MsgDeviceGetState::match(cmd))
{
// Get state for all devices
update();
return true;
}
else if (RemoteControl::MsgDeviceSetState::match(cmd))
{
RemoteControl::MsgDeviceSetState& msg = (RemoteControl::MsgDeviceSetState&) cmd;
QString protocol = msg.getProtocol();
QString deviceId = msg.getDeviceId();
Device *device = getDevice(protocol, deviceId);
if (device)
{
QString id = msg.getId();
QVariant variant = msg.getValue();
if (variant.type() == QMetaType::Bool)
{
bool b = variant.toBool();
device->setState(id, b);
}
else if (variant.type() == QMetaType::Int)
{
int i = variant.toInt();
device->setState(id, i);
}
else if (variant.type() == QMetaType::Float)
{
float f = variant.toFloat();
device->setState(id, f);
}
else if (variant.type() == QMetaType::QString)
{
QString s = variant.toString();
device->setState(id, s);
}
else
{
qDebug() << "RemoteControlWorker::handleMessage: Unsupported type: " << variant.typeName();
}
}
else
{
qDebug() << "RemoteControlWorker::handleMessage: Device not found: " << protocol << " " << deviceId;
}
return true;
}
else
{
return false;
}
}
void RemoteControlWorker::applySettings(const RemoteControlSettings& settings, bool force)
{
qDebug() << "RemoteControlWorker::applySettings:"
<< " m_updatePeriod: " << settings.m_updatePeriod
<< " m_visaLogIO: " << settings.m_visaLogIO
<< " force: " << force;
if ((settings.m_updatePeriod != m_settings.m_updatePeriod) || force) {
m_timer.setInterval(settings.m_updatePeriod * 1000.0);
}
// Always recreate all devices, as DeviceInfo may have changed
qDeleteAll(m_devices);
m_devices.clear();
for (auto rcDevice : settings.m_devices)
{
QHash<QString, QVariant> devSettings;
if (rcDevice->m_protocol == "TPLink")
{
devSettings.insert("username", settings.m_tpLinkUsername);
devSettings.insert("password", settings.m_tpLinkPassword);
}
else if (rcDevice->m_protocol == "HomeAssistant")
{
devSettings.insert("apiKey", settings.m_homeAssistantToken);
devSettings.insert("url", settings.m_homeAssistantHost);
}
else if (rcDevice->m_protocol == "VISA")
{
devSettings.insert("logIO", settings.m_visaLogIO);
}
devSettings.insert("deviceId", rcDevice->m_info.m_id);
QStringList controlIDs;
for (auto control : rcDevice->m_controls) {
controlIDs.append(control.m_id);
}
QStringList sensorIDs;
for (auto sensor : rcDevice->m_sensors) {
sensorIDs.append(sensor.m_id);
}
devSettings.insert("controlIds", controlIDs);
devSettings.insert("sensorIds", sensorIDs);
Device *device = Device::create(devSettings, rcDevice->m_protocol, &rcDevice->m_info);
m_devices.append(device);
connect(device, &Device::deviceUpdated, this, &RemoteControlWorker::deviceUpdated);
connect(device, &Device::deviceUnavailable, this, &RemoteControlWorker::deviceUnavailable);
connect(device, &Device::error, this, &RemoteControlWorker::deviceError);
}
m_settings = settings;
}
void RemoteControlWorker::update()
{
for (auto device : m_devices) {
device->getState();
}
}
void RemoteControlWorker::deviceUpdated(QHash<QString, QVariant> status)
{
QObject *device = this->sender();
for (int i = 0; i < m_devices.size(); i++)
{
if (device == m_devices[i])
{
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(RemoteControl::MsgDeviceStatus::create(m_devices[i]->getProtocol(),
m_devices[i]->getDeviceId(),
status));
}
}
}
}
void RemoteControlWorker::deviceUnavailable()
{
if (getMessageQueueToGUI())
{
Device *device = qobject_cast<Device *>(this->sender());
getMessageQueueToGUI()->push(RemoteControl::MsgDeviceUnavailable::create(device->getProtocol(), device->getDeviceId()));
}
}
void RemoteControlWorker::deviceError(const QString &error)
{
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(RemoteControl::MsgDeviceError::create(error));
}
}

View File

@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_PERTESTERWORKER_H_
#define INCLUDE_FEATURE_REMOTECONTROLWORKER_H_
#include <QObject>
#include <QTimer>
#include <QUdpSocket>
#include "util/message.h"
#include "util/messagequeue.h"
#include "remotecontrolsettings.h"
class RemoteControlWorker : public QObject
{
Q_OBJECT
public:
RemoteControlWorker();
~RemoteControlWorker();
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToFeature(MessageQueue *messageQueue) { m_msgQueueToFeature = messageQueue; }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; }
private:
MessageQueue m_inputMessageQueue;
MessageQueue *m_msgQueueToFeature;
MessageQueue *m_msgQueueToGUI;
RemoteControlSettings m_settings;
bool m_running;
QTimer m_timer;
QList<Device *> m_devices;
bool handleMessage(const Message& cmd);
void applySettings(const RemoteControlSettings& settings, bool force = false);
MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; }
Device *getDevice(const QString &protocol, const QString deviceId) const;
private slots:
void handleInputMessages();
void update();
void deviceUpdated(QHash<QString, QVariant> status);
void deviceUnavailable();
void deviceError(const QString &error);
};
#endif // INCLUDE_FEATURE_REMOTECONTROLWORKER_H_