1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-22 08:04:49 -05:00

GS232 Rotator Controller updates

Add support for hamlib/rotctld protocol.
Add support for TCP connections.
Name plugin Rotator Controller, rather than GS-232 Rotator Controller, as it now supports 3 different protocols.
This commit is contained in:
Jon Beniston 2021-11-23 12:13:24 +00:00
parent a41d0319dc
commit 257b265ee8
9 changed files with 515 additions and 233 deletions

View File

@ -127,6 +127,8 @@ void GS232ControllerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
m_settings.m_rollupState = saveState();
applySettings();
}
GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
@ -179,10 +181,14 @@ void GS232ControllerGUI::displaySettings()
ui->azimuth->setValue(m_settings.m_azimuth);
ui->elevation->setValue(m_settings.m_elevation);
ui->protocol->setCurrentIndex((int)m_settings.m_protocol);
ui->connection->setCurrentIndex((int)m_settings.m_connection);
updateDecimals(m_settings.m_protocol);
if (m_settings.m_serialPort.length() > 0)
if (m_settings.m_serialPort.length() > 0) {
ui->serialPort->lineEdit()->setText(m_settings.m_serialPort);
}
ui->baudRate->setCurrentText(QString("%1").arg(m_settings.m_baudRate));
ui->host->setText(m_settings.m_host);
ui->port->setValue(m_settings.m_port);
ui->track->setChecked(m_settings.m_track);
ui->sources->setCurrentIndex(ui->sources->findText(m_settings.m_source));
ui->azimuthOffset->setValue(m_settings.m_azimuthOffset);
@ -192,9 +198,24 @@ void GS232ControllerGUI::displaySettings()
ui->elevationMin->setValue(m_settings.m_elevationMin);
ui->elevationMax->setValue(m_settings.m_elevationMax);
ui->tolerance->setValue(m_settings.m_tolerance);
restoreState(m_settings.m_rollupState);
updateConnectionWidgets();
blockApplySettings(false);
}
void GS232ControllerGUI::updateConnectionWidgets()
{
bool serial = m_settings.m_connection == GS232ControllerSettings::SERIAL;
ui->serialPortLabel->setVisible(serial);
ui->serialPort->setVisible(serial);
ui->baudRateLabel->setVisible(serial);
ui->baudRate->setVisible(serial);
ui->hostLabel->setVisible(!serial);
ui->host->setVisible(!serial);
ui->portLabel->setVisible(!serial);
ui->port->setVisible(!serial);
}
void GS232ControllerGUI::updateSerialPortList()
{
ui->serialPort->clear();
@ -315,6 +336,13 @@ void GS232ControllerGUI::on_protocol_currentIndexChanged(int index)
applySettings();
}
void GS232ControllerGUI::on_connection_currentIndexChanged(int index)
{
m_settings.m_connection = (GS232ControllerSettings::Connection)index;
applySettings();
updateConnectionWidgets();
}
void GS232ControllerGUI::on_serialPort_currentIndexChanged(int index)
{
(void) index;
@ -329,6 +357,18 @@ void GS232ControllerGUI::on_baudRate_currentIndexChanged(int index)
applySettings();
}
void GS232ControllerGUI::on_host_editingFinished()
{
m_settings.m_host = ui->host->text();
applySettings();
}
void GS232ControllerGUI::on_port_valueChanged(int value)
{
m_settings.m_port = value;
applySettings();
}
void GS232ControllerGUI::on_azimuth_valueChanged(double value)
{
m_settings.m_azimuth = (float)value;
@ -388,7 +428,7 @@ void GS232ControllerGUI::on_tolerance_valueChanged(double value)
void GS232ControllerGUI::on_track_stateChanged(int state)
{
m_settings.m_track = state == Qt::Checked;
ui->targetsLabel->setEnabled(m_settings.m_track);
ui->targetName->setEnabled(m_settings.m_track);
ui->sources->setEnabled(m_settings.m_track);
if (!m_settings.m_track) {
@ -439,7 +479,7 @@ void GS232ControllerGUI::updateStatus()
break;
case Feature::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_gs232Controller->getErrorMessage());
QMessageBox::critical(this, m_settings.m_title, m_gs232Controller->getErrorMessage());
break;
default:
break;

View File

@ -65,6 +65,7 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateConnectionWidgets();
void updateDecimals(GS232ControllerSettings::Protocol protocol);
void updatePipeList();
void updateSerialPortList();
@ -79,7 +80,10 @@ private slots:
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_protocol_currentIndexChanged(int index);
void on_connection_currentIndexChanged(int index);
void on_serialPort_currentIndexChanged(int index);
void on_host_editingFinished();
void on_port_valueChanged(int value);
void on_baudRate_currentIndexChanged(int index);
void on_track_stateChanged(int state);
void on_azimuth_valueChanged(double value);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>360</width>
<height>231</height>
<height>281</height>
</rect>
</property>
<property name="sizePolicy">
@ -37,17 +37,17 @@
<property name="windowTitle">
<string>GS-232 Rotator Controller</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<widget class="QWidget" name="controlsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>341</width>
<height>211</height>
<height>81</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
<string>Controls</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
@ -184,34 +184,192 @@
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<widget class="QLabel" name="toleranceLabel">
<property name="text">
<string>Tolerance</string>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="sources">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Target to track</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="0" column="0">
<widget class="QCheckBox" name="track">
<property name="toolTip">
<string>Check to enable automatic tracking of azimuth and elevation from the specified channel</string>
</property>
<property name="text">
<string>Track</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="targetName">
<property name="toolTip">
<string>Name of the target being tracked as indicated by the source channel / feature</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>341</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<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="QGridLayout" name="settingsGridLayout">
<item row="3" column="0">
<widget class="QLabel" name="azimuthOffsetLabel">
<property name="text">
<string>Azimuth offset</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="azimuthMinLabel">
<property name="text">
<string>Azimuth min</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="azimuthOffset">
<property name="toolTip">
<string>Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment</string>
</property>
<property name="minimum">
<number>-360</number>
</property>
<widget class="QSpinBox" name="azimuthMin">
<property name="maximum">
<number>360</number>
<number>450</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="protocolLabel">
<property name="text">
<string>Protocol</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="elevationMaxLabel">
<property name="text">
<string>Elevation max</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QSpinBox" name="azimuthMax">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="connectionLabel">
<property name="text">
<string>Connection</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="elevationMin">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="portLabel">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="tolerance">
<property name="toolTip">
<string>Tolerance in degrees</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="serialPortLabel">
<property name="text">
<string>Serial Port</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="connection">
<property name="toolTip">
<string>The type of connection to use to the rotator</string>
</property>
<item>
<property name="text">
<string>Serial</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="toleranceLabel">
<property name="text">
<string>Tolerance</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="elevationOffsetLabel">
<property name="text">
<string>Elevation offset</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="elevationMinLabel">
<property name="text">
<string>Elevation min</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QSpinBox" name="elevationOffset">
<property name="toolTip">
<string>Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment</string>
@ -227,7 +385,14 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="2" column="1">
<widget class="QLineEdit" name="host">
<property name="toolTip">
<string>Host name / IP address of computer running rotctld</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="serialPort">
<property name="toolTip">
<string>Name of serial port to use to connect to the GS-232 controller</string>
@ -237,7 +402,43 @@
</property>
</widget>
</item>
<item row="3" column="3">
<item row="0" column="1">
<widget class="QComboBox" name="protocol">
<property name="toolTip">
<string>Command protocol</string>
</property>
<item>
<property name="text">
<string>GS-232</string>
</property>
</item>
<item>
<property name="text">
<string>SPID</string>
</property>
</item>
<item>
<property name="text">
<string>rotctld</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="elevationMax">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="baudRate">
<property name="toolTip">
<string>Serial port baud rate for the GS-232 controller</string>
@ -297,161 +498,40 @@
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="serialLabel">
<property name="text">
<string>Serial Port</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="elevationOffsetLabel">
<property name="text">
<string>Elevation offset</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="elevationMaxLabel">
<property name="text">
<string>Elevation max</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="elevationMin">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="protocol">
<property name="toolTip">
<string>Command protocol</string>
</property>
<item>
<property name="text">
<string>GS-232</string>
</property>
</item>
<item>
<property name="text">
<string>SPID</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="azimuthMinLabel">
<property name="text">
<string>Azimuth min</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="azimuthMax">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="protocolLabel">
<property name="text">
<string>Protocol</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QSpinBox" name="elevationMax">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="elevationMinLabel">
<property name="text">
<string>Elevation min</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="baudRateLabel">
<property name="text">
<string>Baud rate</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="azimuthMin">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="azimuthMaxLabel">
<property name="text">
<string>Azimuth max</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="track">
<item row="3" column="1">
<widget class="QSpinBox" name="azimuthOffset">
<property name="toolTip">
<string>Check to enable automatic tracking of azimuth and elevation from the specified channel</string>
<string>Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment</string>
</property>
<property name="text">
<string>Track</string>
<property name="minimum">
<number>-360</number>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QLineEdit" name="targetName">
<property name="toolTip">
<string>Name of the target being tracked as indicated by the source channel / feature</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="targetNameLabel">
<property name="text">
<string>Target</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="targetsLabel">
<property name="text">
<string>Source</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QComboBox" name="sources">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Target to track</string>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="tolerance">
<widget class="QSpinBox" name="port">
<property name="toolTip">
<string>Tolerance in degrees</string>
<string>TCP port number rotctld is listening on</string>
</property>
<property name="decimals">
<number>1</number>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="baudRateLabel">
<property name="text">
<string>Baud rate</string>
</property>
</widget>
</item>
@ -479,17 +559,6 @@
<tabstop>elevation</tabstop>
<tabstop>track</tabstop>
<tabstop>sources</tabstop>
<tabstop>targetName</tabstop>
<tabstop>protocol</tabstop>
<tabstop>tolerance</tabstop>
<tabstop>serialPort</tabstop>
<tabstop>baudRate</tabstop>
<tabstop>azimuthOffset</tabstop>
<tabstop>elevationOffset</tabstop>
<tabstop>azimuthMin</tabstop>
<tabstop>azimuthMax</tabstop>
<tabstop>elevationMin</tabstop>
<tabstop>elevationMax</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>

View File

@ -29,8 +29,8 @@
const PluginDescriptor GS232ControllerPlugin::m_pluginDescriptor = {
GS232Controller::m_featureId,
QStringLiteral("GS-232 Rotator Controller"),
QStringLiteral("6.17.2"),
QStringLiteral("Rotator Controller"),
QStringLiteral("6.17.4"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,

View File

@ -50,7 +50,7 @@ void GS232ControllerSettings::resetToDefaults()
m_baudRate = 9600;
m_track = false;
m_source = "";
m_title = "GS-232 Rotator Controller";
m_title = "Rotator Controller";
m_rgbColor = QColor(225, 25, 99).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
@ -63,8 +63,11 @@ void GS232ControllerSettings::resetToDefaults()
m_azimuthMax = 450;
m_elevationMin = 0;
m_elevationMax = 180;
m_tolerance = 0.0f;
m_tolerance = 1.0f;
m_protocol = GS232;
m_connection = SERIAL;
m_host = "127.0.0.1";
m_port = 4533;
}
QByteArray GS232ControllerSettings::serialize() const
@ -92,6 +95,10 @@ QByteArray GS232ControllerSettings::serialize() const
s.writeS32(20, m_elevationMax);
s.writeFloat(21, m_tolerance);
s.writeS32(22, (int)m_protocol);
s.writeS32(23, (int)m_connection);
s.writeString(24, m_host);
s.writeS32(25, m_port);
s.writeBlob(26, m_rollupState);
return s.final();
}
@ -118,7 +125,7 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data)
d.readS32(4, &m_baudRate, 9600);
d.readBool(5, &m_track, false);
d.readString(6, &m_source, "");
d.readString(8, &m_title, "GS-232 Rotator Controller");
d.readString(8, &m_title, "Rotator Controller");
d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb());
d.readBool(10, &m_useReverseAPI, false);
d.readString(11, &m_reverseAPIAddress, "127.0.0.1");
@ -140,8 +147,12 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data)
d.readS32(18, &m_azimuthMax, 450);
d.readS32(19, &m_elevationMin, 0);
d.readS32(20, &m_elevationMax, 180);
d.readFloat(21, &m_tolerance, 0.0f);
d.readFloat(21, &m_tolerance, 1.0f);
d.readS32(22, (int*)&m_protocol, GS232);
d.readS32(23, (int*)&m_connection, SERIAL);
d.readString(24, &m_host, "127.0.0.1");
d.readS32(25, &m_port, 4533);
d.readBlob(26, &m_rollupState);
return true;
}

View File

@ -32,6 +32,8 @@ struct GS232ControllerSettings
float m_elevation;
QString m_serialPort;
int m_baudRate;
QString m_host;
int m_port;
bool m_track;
QString m_source; // Plugin to get az/el from. E.g: "R0:0 ADSBDemod". Use a string, so can be set via WebAPI
int m_azimuthOffset;
@ -41,7 +43,9 @@ struct GS232ControllerSettings
int m_elevationMin;
int m_elevationMax;
float m_tolerance;
enum Protocol { GS232, SPID } m_protocol;
enum Protocol { GS232, SPID, ROTCTLD } m_protocol;
enum Connection { SERIAL, TCP } m_connection;
QByteArray m_rollupState;
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;

View File

@ -35,11 +35,13 @@ GS232ControllerWorker::GS232ControllerWorker() :
m_msgQueueToFeature(nullptr),
m_running(false),
m_mutex(QMutex::Recursive),
m_device(nullptr),
m_lastAzimuth(-1.0f),
m_lastElevation(-1.0f),
m_spidSetOutstanding(false),
m_spidSetSent(false),
m_spidStatusSent(false)
m_spidStatusSent(false),
m_rotCtlDReadAz(false)
{
connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update()));
m_pollTimer.start(1000);
@ -65,9 +67,12 @@ bool GS232ControllerWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_serialPort, &QSerialPort::readyRead, this, &GS232ControllerWorker::readSerialData);
if (!m_settings.m_serialPort.isEmpty()) {
openSerialPort(m_settings);
connect(&m_serialPort, &QSerialPort::readyRead, this, &GS232ControllerWorker::readData);
connect(&m_socket, &QTcpSocket::readyRead, this, &GS232ControllerWorker::readData);
if (m_settings.m_connection == GS232ControllerSettings::TCP) {
m_device = openSocket(m_settings);
} else {
m_device = openSerialPort(m_settings);
}
m_running = true;
return m_running;
@ -77,10 +82,12 @@ void GS232ControllerWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
// Close serial port as USB/controller activity can create RFI
if (m_serialPort.isOpen())
m_serialPort.close();
if (m_device && m_device->isOpen()) {
m_device->close();
}
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
disconnect(&m_serialPort, &QSerialPort::readyRead, this, &GS232ControllerWorker::readSerialData);
disconnect(&m_serialPort, &QSerialPort::readyRead, this, &GS232ControllerWorker::readData);
disconnect(&m_socket, &QTcpSocket::readyRead, this, &GS232ControllerWorker::readData);
m_running = false;
}
@ -125,17 +132,33 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting
<< " m_elevationMax: " << settings.m_elevationMax
<< " m_tolerance: " << settings.m_tolerance
<< " m_protocol: " << settings.m_protocol
<< " m_connection: " << settings.m_connection
<< " m_serialPort: " << settings.m_serialPort
<< " m_baudRate: " << settings.m_baudRate
<< " m_host: " << settings.m_host
<< " m_port: " << settings.m_port
<< " force: " << force;
if ((settings.m_serialPort != m_settings.m_serialPort) || force)
if (settings.m_connection != m_settings.m_connection)
{
openSerialPort(settings);
if (m_device && m_device->isOpen()) {
m_device->close();
}
}
else if ((settings.m_baudRate != m_settings.m_baudRate) || force)
if (settings.m_connection == GS232ControllerSettings::TCP)
{
m_serialPort.setBaudRate(settings.m_baudRate);
if ((settings.m_host != m_settings.m_host) || (settings.m_port != m_settings.m_port) || force) {
m_device = openSocket(settings);
}
}
else
{
if ((settings.m_serialPort != m_settings.m_serialPort) || force) {
m_device = openSerialPort(settings);
} else if ((settings.m_baudRate != m_settings.m_baudRate) || force) {
m_serialPort.setBaudRate(settings.m_baudRate);
}
}
// Apply offset then clamp
@ -159,22 +182,54 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting
m_settings = settings;
}
void GS232ControllerWorker::openSerialPort(const GS232ControllerSettings& settings)
QIODevice *GS232ControllerWorker::openSerialPort(const GS232ControllerSettings& settings)
{
qDebug() << "GS232ControllerWorker::openSerialPort: " << settings.m_serialPort;
if (m_serialPort.isOpen()) {
m_serialPort.close();
}
m_serialPort.setPortName(settings.m_serialPort);
m_serialPort.setBaudRate(settings.m_baudRate);
if (!m_serialPort.open(QIODevice::ReadWrite))
m_lastAzimuth = -1;
m_lastElevation = -1;
if (!settings.m_serialPort.isEmpty())
{
qCritical() << "GS232ControllerWorker::openSerialPort: Failed to open serial port " << settings.m_serialPort << ". Error: " << m_serialPort.error();
if (m_msgQueueToFeature) {
m_serialPort.setPortName(settings.m_serialPort);
m_serialPort.setBaudRate(settings.m_baudRate);
if (!m_serialPort.open(QIODevice::ReadWrite))
{
qCritical() << "GS232ControllerWorker::openSerialPort: Failed to open serial port " << settings.m_serialPort << ". Error: " << m_serialPort.error();
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Failed to open serial port %1: %2").arg(settings.m_serialPort).arg(m_serialPort.error())));
return nullptr;
}
else
{
return &m_serialPort;
}
}
else
{
return nullptr;
}
}
QIODevice *GS232ControllerWorker::openSocket(const GS232ControllerSettings& settings)
{
qDebug() << "GS232ControllerWorker::openSocket: " << settings.m_host << settings.m_port;
if (m_socket.isOpen()) {
m_socket.close();
}
m_lastAzimuth = -1;
m_lastElevation = -1;
m_socket.connectToHost(settings.m_host, settings.m_port);
if (m_socket.waitForConnected(3000))
{
return &m_socket;
}
else
{
qCritical() << "GS232ControllerWorker::openSocket: Failed to connect to " << settings.m_host << settings.m_port;
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Failed to connect to %1:%2").arg(settings.m_host).arg(settings.m_port)));
return nullptr;
}
}
void GS232ControllerWorker::setAzimuth(float azimuth)
@ -200,10 +255,8 @@ void GS232ControllerWorker::setAzimuthElevation(float azimuth, float elevation)
QByteArray data = cmd.toLatin1();
m_serialPort.write(data);
}
else
else if (m_settings.m_protocol == GS232ControllerSettings::SPID)
{
qDebug() << "GS232ControllerWorker::setAzimuthElevation " << " AZ " << azimuth << " EL " << elevation;
if (!m_spidSetSent && !m_spidStatusSent)
{
QByteArray cmd(13, (char)0);
@ -233,21 +286,25 @@ void GS232ControllerWorker::setAzimuthElevation(float azimuth, float elevation)
qDebug() << "GS232ControllerWorker::setAzimuthElevation: Not sent, waiting for status reply";
m_spidSetOutstanding = true;
}
} else {
QString cmd = QString("P %1 %2\n").arg(azimuth).arg(elevation);
QByteArray data = cmd.toLatin1();
m_socket.write(data);
}
m_lastAzimuth = azimuth;
m_lastElevation = elevation;
}
void GS232ControllerWorker::readSerialData()
void GS232ControllerWorker::readData()
{
char buf[1024];
qint64 len;
if (m_settings.m_protocol == GS232ControllerSettings::GS232)
{
while (m_serialPort.canReadLine())
while (m_device->canReadLine())
{
len = m_serialPort.readLine(buf, sizeof(buf));
len = m_device->readLine(buf, sizeof(buf));
if (len != -1)
{
QString response = QString::fromUtf8(buf, len);
@ -258,7 +315,7 @@ void GS232ControllerWorker::readSerialData()
{
QString az = match.captured(1);
QString el = match.captured(2);
//qDebug() << "GS232ControllerWorker::readSerialData read Az " << az << " El " << el;
//qDebug() << "GS232ControllerWorker::readData read Az " << az << " El " << el;
m_msgQueueToFeature->push(GS232ControllerReport::MsgReportAzAl::create(az.toFloat(), el.toFloat()));
}
else if (response == "\r\n")
@ -267,27 +324,27 @@ void GS232ControllerWorker::readSerialData()
}
else
{
qDebug() << "GS232ControllerWorker::readSerialData - unexpected GS-232 response \"" << response << "\"";
qWarning() << "GS232ControllerWorker::readData - unexpected GS-232 response \"" << response << "\"";
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected GS-232 response: %1").arg(response)));
}
}
}
}
else
else if (m_settings.m_protocol == GS232ControllerSettings::SPID)
{
while (m_serialPort.bytesAvailable() >= 12)
while (m_device->bytesAvailable() >= 12)
{
len = m_serialPort.read(buf, 12);
len = m_device->read(buf, 12);
if ((len == 12) && (buf[0] == 0x57))
{
double az;
double el;
az = buf[1] * 100.0 + buf[2] * 10.0 + buf[3] + buf[4] / 10.0 - 360.0;
el = buf[6] * 100.0 + buf[7] * 10.0 + buf[8] + buf[9] / 10.0 - 360.0;
//qDebug() << "GS232ControllerWorker::readSerialData read Az " << az << " El " << el;
//qDebug() << "GS232ControllerWorker::readData read Az " << az << " El " << el;
m_msgQueueToFeature->push(GS232ControllerReport::MsgReportAzAl::create(az, el));
if (m_spidStatusSent && m_spidSetSent) {
qDebug() << "GS232ControllerWorker::readSerialData - m_spidStatusSent and m_spidSetSent set simultaneously";
qDebug() << "GS232ControllerWorker::readData - m_spidStatusSent and m_spidSetSent set simultaneously";
}
if (m_spidStatusSent) {
m_spidStatusSent = false;
@ -304,9 +361,78 @@ void GS232ControllerWorker::readSerialData()
else
{
QByteArray bytes(buf, (int)len);
qDebug() << "GS232ControllerWorker::readSerialData - unexpected SPID rot2prog response \"" << bytes.toHex() << "\"";
if (m_msgQueueToFeature) {
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected SPID rot2prog response: %1").arg(bytes.toHex().data())));
qWarning() << "GS232ControllerWorker::readData - unexpected SPID rot2prog response \"" << bytes.toHex() << "\"";
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected SPID rot2prog response: %1").arg(bytes.toHex().data())));
}
}
}
else
{
while (m_device->canReadLine())
{
len = m_device->readLine(buf, sizeof(buf));
if (len != -1)
{
QString response = QString::fromUtf8(buf, len).trimmed();
QRegularExpression rprt("RPRT (-?\\d+)");
QRegularExpressionMatch matchRprt = rprt.match(response);
QRegularExpression decimal("(-?\\d+.\\d+)");
QRegularExpressionMatch matchDecimal = decimal.match(response);
if (matchRprt.hasMatch())
{
// See rig_errcode_e in hamlib rig.h
const QStringList errors = {
"OK",
"Invalid parameter",
"Invalid configuration",
"No memory",
"Not implemented",
"Timeout",
"IO error",
"Internal error",
"Protocol error",
"Command rejected",
"Arg truncated",
"Not available",
"VFO not targetable",
"Bus error",
"Collision on bus",
"NULL rig handled or invalid pointer parameter",
"Invalid VFO",
"Argument out of domain of function"
};
int rprt = matchRprt.captured(1).toInt();
if (rprt != 0)
{
qWarning() << "GS232ControllerWorker::readData - rotctld error: " << errors[-rprt];
// Seem to get a lot of EPROTO errors from rotctld due to extra 00 char in response to GS232 C2 command
// E.g: ./rotctld.exe -m 603 -r com7 -vvvvv
// read_string(): RX 16 characters
// 0000 00 41 5a 3d 31 37 35 20 20 45 4c 3d 30 33 38 0d .AZ=175 EL=038.
// So don't pass these to GUI for now
if (rprt != -8) {
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("rotctld error: %1").arg(errors[-rprt])));
}
}
m_rotCtlDReadAz = false;
}
else if (matchDecimal.hasMatch() && !m_rotCtlDReadAz)
{
m_rotCtlDAz = response;
m_rotCtlDReadAz = true;
}
else if (matchDecimal.hasMatch() && m_rotCtlDReadAz)
{
QString az = m_rotCtlDAz;
QString el = response;
m_rotCtlDReadAz = false;
//qDebug() << "GS232ControllerWorker::readData read Az " << az << " El " << el;
m_msgQueueToFeature->push(GS232ControllerReport::MsgReportAzAl::create(az.toFloat(), el.toFloat()));
}
else
{
qWarning() << "GS232ControllerWorker::readData - Unexpected rotctld response \"" << response << "\"";
m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected rotctld response: %1").arg(response)));
}
}
}
@ -315,15 +441,15 @@ void GS232ControllerWorker::readSerialData()
void GS232ControllerWorker::update()
{
// Request current Az/El from GS-232 controller
if (m_serialPort.isOpen())
// Request current Az/El from controller
if (m_device && m_device->isOpen())
{
if (m_settings.m_protocol == GS232ControllerSettings::GS232)
{
QByteArray cmd("C2\r\n");
m_serialPort.write(cmd);
m_device->write(cmd);
}
else
else if (m_settings.m_protocol == GS232ControllerSettings::SPID)
{
// Don't send a new status command, if waiting for a previous reply
if (!m_spidSetSent && !m_spidStatusSent)
@ -336,9 +462,14 @@ void GS232ControllerWorker::update()
}
cmd.append((char)0x1f); // Status
cmd.append((char)0x20); // End
m_serialPort.write(cmd);
m_device->write(cmd);
m_spidStatusSent = true;
}
}
else
{
QByteArray cmd("p\n");
m_device->write(cmd);
}
}
}

View File

@ -22,6 +22,7 @@
#include <QObject>
#include <QTimer>
#include <QSerialPort>
#include <QTcpSocket>
#include "util/message.h"
#include "util/messagequeue.h"
@ -71,7 +72,9 @@ private:
GS232ControllerSettings m_settings;
bool m_running;
QMutex m_mutex;
QIODevice *m_device;
QSerialPort m_serialPort;
QTcpSocket m_socket;
QTimer m_pollTimer;
float m_lastAzimuth;
@ -81,15 +84,19 @@ private:
bool m_spidSetSent;
bool m_spidStatusSent;
bool m_rotCtlDReadAz; //!< rotctrld returns 'p' responses over two lines
QString m_rotCtlDAz;
bool handleMessage(const Message& cmd);
void applySettings(const GS232ControllerSettings& settings, bool force = false);
void openSerialPort(const GS232ControllerSettings& settings);
QIODevice *openSerialPort(const GS232ControllerSettings& settings);
QIODevice *openSocket(const GS232ControllerSettings& settings);
void setAzimuth(float azimuth);
void setAzimuthElevation(float azimuth, float elevation);
private slots:
void handleInputMessages();
void readSerialData();
void readData();
void update();
};

View File

@ -1,14 +1,14 @@
<h1>GS-232 Rotator Controller Feature Plugin</h1>
<h1>Rotator Controller Feature Plugin</h1>
<h2>Introduction</h2>
The GS-232 Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation.
The Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators as well as hamlib's rotctld, via serial or TCP. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation.
Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker.
<h2>Interface</h2>
![GS232 Rotator Controller feature plugin GUI](../../../doc/img/GS232Controller_plugin.png)
![Rotator Controller feature plugin GUI](../../../doc/img/GS232Controller_plugin.png)
<h3>1: Start/Stop plugin</h3>
@ -42,15 +42,11 @@ For example, the ADS-B plugin will display the flight number of the target aircr
<h3>7: Protocol</h3>
Selects which serial protocol to use. This can be GS-232 or SPID (rot2prog).
Selects which protocol to use. This can be GS-232, SPID (rot2prog) or rotctld.
<h3>8: Tolerance</h3>
<h3>8: Connection</h3>
Specifies a tolerance in degrees, below which, changes in target azimuth or elevation will not be sent to the rotator.
This can prevent some rotators that have a limited accuracy from making unbeneficial movements.
If this set to 0, every target azimuth and elevation received by the controller will be send to the rotator.
If it is set to 2, then a change in azimuth of +-1 degree from the previous azimuth, would not be sent to the rotator.
Selects whether to use a serial connection or TCP.
<h3>9: Serial Port</h3>
@ -60,25 +56,41 @@ Specifies the serial port (E.g. COM3 on Windows or /dev/ttyS0 on Linux) that wil
Specifies the baud rate that will be used to send commands to the rotator. Typically this is 9600 for GS-232.
<h3>11: Azimuth Offset</h3>
<h3>11: Host</h3>
Specifies the hostname / IP address of the computer running rotctld.
<h3>12: Port</h3>
Specifies the TCP port number rotctld is listening on.
<h3>13: Azimuth Offset</h3>
The azimuth offset specifies an angle in degrees that is added to the target azimuth before sending to the controller. This allows for a misalignment of the rotator to be corrected.
<h3>12: Elevation Offset</h3>
<h3>14: Elevation Offset</h3>
The elevation offset specifies an angle in degrees that is added to the target elevation before sending to the controller. This allows for a misalignment of the rotator to be corrected.
<h3>13 and 14: Azimuth Min and Max</h3>
<h3>15 and 16: Azimuth Min and Max</h3>
The azimuth min and max values specify the minimum and maximum azimuth values (after offset has been applied), that will be sent to the rotator.
These values can be used to prevent the rotator from rotating an antenna in to an obstable.
<h3>15 and 16: Elevation Min and Max</h3>
<h3>17 and 18: Elevation Min and Max</h3>
The elevation min and max values specify the minimum and maximum elevation values (after offset has been applied), that will be sent to the rotator.
These values can be used to prevent the rotator from rotating an antenna in to an obstable.
If the maximum elevation is set to 0, the controller will only use the M GS-232 command, rather than M and W.
<h3>19: Tolerance</h3>
Specifies a tolerance in degrees, below which, changes in target azimuth or elevation will not be sent to the rotator.
This can prevent some rotators that have a limited accuracy from making unbeneficial movements.
If this set to 0, every target azimuth and elevation received by the controller will be send to the rotator.
If it is set to 2, then a change in azimuth of +-1 degree from the previous azimuth, would not be sent to the rotator.
<h2>GS-232 Protocol Implementation</h2>
The controller uses the Waaa eee command when elevation needs to be set.
@ -92,6 +104,10 @@ The 0x1f status command is used to read current azimuth and elevation.
A 12 byte response is expected for set and status commands.
All frames start with 0x57 and end with 0x20.
<h2>rotctld Protocol Implementation</h2>
The controller uses the 'P' and 'p' commands to set and get azimuth and elevation.
<h2>API</h2>
Full details of the API can be found in the Swagger documentation. Here is a quick example of how to set the azimuth and elevation from the command line: