From 257b265ee87bbcc64e736518a732e9f485ca8c00 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 23 Nov 2021 12:13:24 +0000 Subject: [PATCH 1/3] 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. --- .../gs232controller/gs232controllergui.cpp | 46 +- .../gs232controller/gs232controllergui.h | 4 + .../gs232controller/gs232controllergui.ui | 407 ++++++++++-------- .../gs232controller/gs232controllerplugin.cpp | 4 +- .../gs232controllersettings.cpp | 19 +- .../gs232controller/gs232controllersettings.h | 6 +- .../gs232controller/gs232controllerworker.cpp | 207 +++++++-- .../gs232controller/gs232controllerworker.h | 11 +- plugins/feature/gs232controller/readme.md | 44 +- 9 files changed, 515 insertions(+), 233 deletions(-) diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index ee4d4d443..745bd1cb4 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -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; diff --git a/plugins/feature/gs232controller/gs232controllergui.h b/plugins/feature/gs232controller/gs232controllergui.h index b5ca162f5..d454bafd4 100644 --- a/plugins/feature/gs232controller/gs232controllergui.h +++ b/plugins/feature/gs232controller/gs232controllergui.h @@ -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); diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 415634483..1970ea9ea 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -7,7 +7,7 @@ 0 0 360 - 231 + 281 @@ -37,17 +37,17 @@ GS-232 Rotator Controller - + 10 10 341 - 211 + 81 - Settings + Controls @@ -184,34 +184,192 @@ - - - - Tolerance + + + + + 150 + 0 + + + + Target to track - + + + + Check to enable automatic tracking of azimuth and elevation from the specified channel + + + Track + + + + + + + Name of the target being tracked as indicated by the source channel / feature + + + true + + + + + + + + + + + 10 + 110 + 341 + 161 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + Azimuth offset + + + + Azimuth min + + + - - - Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment - - - -360 - + - 360 + 450 + + + + + + + Protocol + + + + + + + Elevation max + + + 450 + + + + + + + Connection + + + + + + + 180 + + + + + + + Port + + + + + + + Tolerance in degrees + + + 1 + + + + + + + Serial Port + + + + + + + The type of connection to use to the rotator + + + + Serial + + + + + TCP + + + + + + + + Tolerance + + + + + + + Elevation offset + + + + + + + Elevation min + + + + Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment @@ -227,7 +385,14 @@ - + + + + Host name / IP address of computer running rotctld + + + + Name of serial port to use to connect to the GS-232 controller @@ -237,7 +402,43 @@ - + + + + Command protocol + + + + GS-232 + + + + + SPID + + + + + rotctld + + + + + + + + Host + + + + + + + 180 + + + + Serial port baud rate for the GS-232 controller @@ -297,161 +498,40 @@ - - - - Serial Port - - - - - - Elevation offset - - - - - - - Elevation max - - - - - - - 180 - - - - - - - Command protocol - - - - GS-232 - - - - - SPID - - - - - - - - Azimuth min - - - - - - - 450 - - - - - - - Protocol - - - - - - - 180 - - - - - - - Elevation min - - - - - - - Baud rate - - - - - - - 450 - - - - Azimuth max - - + + - Check to enable automatic tracking of azimuth and elevation from the specified channel + Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment - - Track + + -360 - - - - - - Name of the target being tracked as indicated by the source channel / feature - - - true - - - - - - - Target - - - - - - - Source - - - - - - - - 150 - 0 - - - - Target to track + + 360 - + - Tolerance in degrees + TCP port number rotctld is listening on - - 1 + + 65535 + + + + + + + Baud rate @@ -479,17 +559,6 @@ elevation track sources - targetName - protocol - tolerance - serialPort - baudRate - azimuthOffset - elevationOffset - azimuthMin - azimuthMax - elevationMin - elevationMax diff --git a/plugins/feature/gs232controller/gs232controllerplugin.cpp b/plugins/feature/gs232controller/gs232controllerplugin.cpp index 1c2f82d6d..14c1cc18b 100644 --- a/plugins/feature/gs232controller/gs232controllerplugin.cpp +++ b/plugins/feature/gs232controller/gs232controllerplugin.cpp @@ -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, diff --git a/plugins/feature/gs232controller/gs232controllersettings.cpp b/plugins/feature/gs232controller/gs232controllersettings.cpp index 0b8afdb7f..ddb8ffe0f 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.cpp +++ b/plugins/feature/gs232controller/gs232controllersettings.cpp @@ -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; } diff --git a/plugins/feature/gs232controller/gs232controllersettings.h b/plugins/feature/gs232controller/gs232controllersettings.h index ca0e153d9..e79668438 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.h +++ b/plugins/feature/gs232controller/gs232controllersettings.h @@ -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; diff --git a/plugins/feature/gs232controller/gs232controllerworker.cpp b/plugins/feature/gs232controller/gs232controllerworker.cpp index 8f7d5e5b4..1de4676a9 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.cpp +++ b/plugins/feature/gs232controller/gs232controllerworker.cpp @@ -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); + } } } diff --git a/plugins/feature/gs232controller/gs232controllerworker.h b/plugins/feature/gs232controller/gs232controllerworker.h index 70e9b7cc4..479b0410b 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.h +++ b/plugins/feature/gs232controller/gs232controllerworker.h @@ -22,6 +22,7 @@ #include #include #include +#include #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(); }; diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 55cebc876..c7d9e5963 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -1,14 +1,14 @@ -

GS-232 Rotator Controller Feature Plugin

+

Rotator Controller Feature Plugin

Introduction

-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.

Interface

-![GS232 Rotator Controller feature plugin GUI](../../../doc/img/GS232Controller_plugin.png) +![Rotator Controller feature plugin GUI](../../../doc/img/GS232Controller_plugin.png)

1: Start/Stop plugin

@@ -42,15 +42,11 @@ For example, the ADS-B plugin will display the flight number of the target aircr

7: Protocol

-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. -

8: Tolerance

+

8: Connection

-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.

9: Serial Port

@@ -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. -

11: Azimuth Offset

+

11: Host

+ +Specifies the hostname / IP address of the computer running rotctld. + +

12: Port

+ +Specifies the TCP port number rotctld is listening on. + +

13: Azimuth Offset

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. -

12: Elevation Offset

+

14: Elevation Offset

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. -

13 and 14: Azimuth Min and Max

+

15 and 16: Azimuth Min and Max

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. -

15 and 16: Elevation Min and Max

+

17 and 18: Elevation Min and Max

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. +

19: Tolerance

+ +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. +

GS-232 Protocol Implementation

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. +

rotctld Protocol Implementation

+ +The controller uses the 'P' and 'p' commands to set and get azimuth and elevation. +

API

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: From 555888c7cfab2460a39320b42ccd0fa18aaf6f9d Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 23 Nov 2021 12:14:24 +0000 Subject: [PATCH 2/3] Update doc images --- doc/img/GS232Controller_plugin.png | Bin 13372 -> 21832 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/img/GS232Controller_plugin.png b/doc/img/GS232Controller_plugin.png index a7510cdd814a76770bfc6b1df3bcf9d081cdae53..b490fc3120c47c53c186df4efd1e123020b47c7d 100644 GIT binary patch literal 21832 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 G_x~UDj3ze# From fb394165a3a8e0f53a74bc67cd49397b282a505f Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 23 Nov 2021 12:28:06 +0000 Subject: [PATCH 3/3] Update API --- .../gs232controller/gs232controller.cpp | 22 +++++++++ .../gs232controller/gs232controllergui.ui | 8 ++-- plugins/feature/gs232controller/readme.md | 15 +++--- .../api/swagger/include/GS232Controller.yaml | 6 +++ swagger/sdrangel/code/html2/index.html | 10 +++- .../qt5/client/SWGGS232ControllerSettings.cpp | 48 +++++++++++++++++++ .../qt5/client/SWGGS232ControllerSettings.h | 12 +++++ 7 files changed, 110 insertions(+), 11 deletions(-) diff --git a/plugins/feature/gs232controller/gs232controller.cpp b/plugins/feature/gs232controller/gs232controller.cpp index 37a299469..eb7c0128e 100644 --- a/plugins/feature/gs232controller/gs232controller.cpp +++ b/plugins/feature/gs232controller/gs232controller.cpp @@ -244,6 +244,8 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo << " m_protocol: " << settings.m_protocol << " m_serialPort: " << settings.m_serialPort << " m_baudRate: " << settings.m_baudRate + << " m_host: " << settings.m_host + << " m_port: " << settings.m_port << " m_track: " << settings.m_track << " m_source: " << settings.m_source << " m_title: " << settings.m_title @@ -269,6 +271,12 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo if ((m_settings.m_baudRate != settings.m_baudRate) || force) { reverseAPIKeys.append("baudRate"); } + if ((m_settings.m_host != settings.m_host) || force) { + reverseAPIKeys.append("host"); + } + if ((m_settings.m_port != settings.m_port) || force) { + reverseAPIKeys.append("port"); + } if ((m_settings.m_track != settings.m_track) || force) { reverseAPIKeys.append("track"); } @@ -427,6 +435,8 @@ void GS232Controller::webapiFormatFeatureSettings( response.getGs232ControllerSettings()->setElevation(settings.m_elevation); response.getGs232ControllerSettings()->setSerialPort(new QString(settings.m_serialPort)); response.getGs232ControllerSettings()->setBaudRate(settings.m_baudRate); + response.getGs232ControllerSettings()->setHost(new QString(settings.m_host)); + response.getGs232ControllerSettings()->setPort(settings.m_port); response.getGs232ControllerSettings()->setTrack(settings.m_track); response.getGs232ControllerSettings()->setSource(new QString(settings.m_source)); response.getGs232ControllerSettings()->setAzimuthOffset(settings.m_azimuthOffset); @@ -475,6 +485,12 @@ void GS232Controller::webapiUpdateFeatureSettings( if (featureSettingsKeys.contains("baudRate")) { settings.m_baudRate = response.getGs232ControllerSettings()->getBaudRate(); } + if (featureSettingsKeys.contains("host")) { + settings.m_host = *response.getGs232ControllerSettings()->getHost(); + } + if (featureSettingsKeys.contains("port")) { + settings.m_port = response.getGs232ControllerSettings()->getPort(); + } if (featureSettingsKeys.contains("track")) { settings.m_track = response.getGs232ControllerSettings()->getTrack() != 0; } @@ -551,6 +567,12 @@ void GS232Controller::webapiReverseSendSettings(QList& featureSettingsK if (featureSettingsKeys.contains("baudRate") || force) { swgGS232ControllerSettings->setBaudRate(settings.m_baudRate); } + if (featureSettingsKeys.contains("host") || force) { + swgGS232ControllerSettings->setHost(new QString(settings.m_host)); + } + if (featureSettingsKeys.contains("port") || force) { + swgGS232ControllerSettings->setPort(settings.m_port); + } if (featureSettingsKeys.contains("track") || force) { swgGS232ControllerSettings->setTrack(settings.m_track); } diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 1970ea9ea..603068634 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -388,14 +388,14 @@ - Host name / IP address of computer running rotctld + Hostname / IP address of computer to connect to - Name of serial port to use to connect to the GS-232 controller + Name of serial port to use to connect to the rotator true @@ -441,7 +441,7 @@ - Serial port baud rate for the GS-232 controller + Serial port baud rate 3 @@ -521,7 +521,7 @@ - TCP port number rotctld is listening on + TCP port number to connect to 65535 diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index c7d9e5963..e719eaf89 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -2,7 +2,8 @@

Introduction

-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. +The Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators as well as hamlib's rotctld, via a serial or TCP connection. +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. @@ -58,11 +59,11 @@ Specifies the baud rate that will be used to send commands to the rotator. Typic

11: Host

-Specifies the hostname / IP address of the computer running rotctld. +Specifies the hostname / IP address of the computer to connect to.

12: Port

-Specifies the TCP port number rotctld is listening on. +Specifies the TCP port number to connect to.

13: Azimuth Offset

@@ -91,20 +92,22 @@ This can prevent some rotators that have a limited accuracy from making unbenefi 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. -

GS-232 Protocol Implementation

+

Protocol Implementations

+ +

GS-232 Protocol Implementation Notes

The controller uses the Waaa eee command when elevation needs to be set. When only azimuth needs to be set, the Maaa command is used. The C2 command is used to read current azimuth and elevation. A response of AZ=aaaEL=eee is expected. -

SPID rot2prog Protocol Implementation

+

SPID rot2prog Protocol Implementation

The controller uses the 0x2f set command with PH/PV=2 to set azimuth and elevation. 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. -

rotctld Protocol Implementation

+

rotctld Protocol Implementation

The controller uses the 'P' and 'p' commands to set and get azimuth and elevation. diff --git a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml index c6374262e..d23324ec4 100644 --- a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml +++ b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml @@ -15,6 +15,12 @@ GS232ControllerSettings: baudRate: description: The baud rate to use for the serial connection to the GS-232 controller type: integer + host: + description: Hostname / IP address of computer running rotctld. + type: string + port: + description: TCP port number rotctld is listening on. + type: integer track: description: Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no) type: integer diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 38b54fa11..b66c50ba1 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -6007,6 +6007,14 @@ margin-bottom: 20px; "type" : "integer", "description" : "The baud rate to use for the serial connection to the GS-232 controller" }, + "host" : { + "type" : "string", + "description" : "Hostname / IP address of computer running rotctld." + }, + "port" : { + "type" : "integer", + "description" : "TCP port number rotctld is listening on." + }, "track" : { "type" : "integer", "description" : "Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no)" @@ -51367,7 +51375,7 @@ except ApiException as e:
- Generated 2021-11-21T00:20:10.840+01:00 + Generated 2021-11-23T13:18:02.821+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp index 3f7ab4369..61b6c901f 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp @@ -36,6 +36,10 @@ SWGGS232ControllerSettings::SWGGS232ControllerSettings() { m_serial_port_isSet = false; baud_rate = 0; m_baud_rate_isSet = false; + host = nullptr; + m_host_isSet = false; + port = 0; + m_port_isSet = false; track = 0; m_track_isSet = false; source = nullptr; @@ -86,6 +90,10 @@ SWGGS232ControllerSettings::init() { m_serial_port_isSet = false; baud_rate = 0; m_baud_rate_isSet = false; + host = new QString(""); + m_host_isSet = false; + port = 0; + m_port_isSet = false; track = 0; m_track_isSet = false; source = new QString(""); @@ -130,6 +138,10 @@ SWGGS232ControllerSettings::cleanup() { delete serial_port; } + if(host != nullptr) { + delete host; + } + if(source != nullptr) { delete source; @@ -174,6 +186,10 @@ SWGGS232ControllerSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&baud_rate, pJson["baudRate"], "qint32", ""); + ::SWGSDRangel::setValue(&host, pJson["host"], "QString", "QString"); + + ::SWGSDRangel::setValue(&port, pJson["port"], "qint32", ""); + ::SWGSDRangel::setValue(&track, pJson["track"], "qint32", ""); ::SWGSDRangel::setValue(&source, pJson["source"], "QString", "QString"); @@ -236,6 +252,12 @@ SWGGS232ControllerSettings::asJsonObject() { if(m_baud_rate_isSet){ obj->insert("baudRate", QJsonValue(baud_rate)); } + if(host != nullptr && *host != QString("")){ + toJsonValue(QString("host"), host, obj, QString("QString")); + } + if(m_port_isSet){ + obj->insert("port", QJsonValue(port)); + } if(m_track_isSet){ obj->insert("track", QJsonValue(track)); } @@ -331,6 +353,26 @@ SWGGS232ControllerSettings::setBaudRate(qint32 baud_rate) { this->m_baud_rate_isSet = true; } +QString* +SWGGS232ControllerSettings::getHost() { + return host; +} +void +SWGGS232ControllerSettings::setHost(QString* host) { + this->host = host; + this->m_host_isSet = true; +} + +qint32 +SWGGS232ControllerSettings::getPort() { + return port; +} +void +SWGGS232ControllerSettings::setPort(qint32 port) { + this->port = port; + this->m_port_isSet = true; +} + qint32 SWGGS232ControllerSettings::getTrack() { return track; @@ -518,6 +560,12 @@ SWGGS232ControllerSettings::isSet(){ if(m_baud_rate_isSet){ isObjectUpdated = true; break; } + if(host && *host != QString("")){ + isObjectUpdated = true; break; + } + if(m_port_isSet){ + isObjectUpdated = true; break; + } if(m_track_isSet){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h index 77d4f68ad..4c5a47e66 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h @@ -54,6 +54,12 @@ public: qint32 getBaudRate(); void setBaudRate(qint32 baud_rate); + QString* getHost(); + void setHost(QString* host); + + qint32 getPort(); + void setPort(qint32 port); + qint32 getTrack(); void setTrack(qint32 track); @@ -121,6 +127,12 @@ private: qint32 baud_rate; bool m_baud_rate_isSet; + QString* host; + bool m_host_isSet; + + qint32 port; + bool m_port_isSet; + qint32 track; bool m_track_isSet;