From f51e8b40958e42dc76501eb47031e7664a5537c6 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 30 May 2021 12:38:07 +0100 Subject: [PATCH 1/4] Add SPID rot2prog protocol support. Add tolerance setting. --- .../gs232controller/gs232controller.cpp | 22 + .../gs232controller/gs232controllergui.cpp | 44 +- .../gs232controller/gs232controllergui.h | 8 +- .../gs232controller/gs232controllergui.ui | 387 +++++++++--------- .../gs232controller/gs232controllerplugin.cpp | 2 +- .../gs232controllersettings.cpp | 18 +- .../gs232controller/gs232controllersettings.h | 6 +- .../gs232controller/gs232controllerworker.cpp | 215 ++++++++-- .../gs232controller/gs232controllerworker.h | 12 +- plugins/feature/gs232controller/readme.md | 31 +- .../api/swagger/include/GS232Controller.yaml | 12 +- .../qt5/client/SWGGS232ControllerSettings.cpp | 66 ++- .../qt5/client/SWGGS232ControllerSettings.h | 24 +- 13 files changed, 572 insertions(+), 275 deletions(-) diff --git a/plugins/feature/gs232controller/gs232controller.cpp b/plugins/feature/gs232controller/gs232controller.cpp index 6e83a841e..d9cf785a1 100644 --- a/plugins/feature/gs232controller/gs232controller.cpp +++ b/plugins/feature/gs232controller/gs232controller.cpp @@ -216,6 +216,8 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo << " m_azimuthMax: " << settings.m_azimuthMax << " m_elevationMin: " << settings.m_elevationMin << " m_elevationMax: " << settings.m_elevationMax + << " m_tolerance: " << settings.m_tolerance + << " m_protocol: " << settings.m_protocol << " m_serialPort: " << settings.m_serialPort << " m_baudRate: " << settings.m_baudRate << " m_track: " << settings.m_track @@ -274,6 +276,12 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo if ((m_settings.m_elevationMin != settings.m_elevationMin) || force) { reverseAPIKeys.append("elevationMin"); } + if ((m_settings.m_tolerance != settings.m_tolerance) || force) { + reverseAPIKeys.append("tolerance"); + } + if ((m_settings.m_protocol != settings.m_protocol) || force) { + reverseAPIKeys.append("m_protocol"); + } if ((m_settings.m_title != settings.m_title) || force) { reverseAPIKeys.append("title"); } @@ -360,6 +368,8 @@ void GS232Controller::webapiFormatFeatureSettings( response.getGs232ControllerSettings()->setAzimuthMax(settings.m_azimuthMax); response.getGs232ControllerSettings()->setElevationMin(settings.m_elevationMin); response.getGs232ControllerSettings()->setElevationMax(settings.m_elevationMax); + response.getGs232ControllerSettings()->setTolerance(settings.m_tolerance); + response.getGs232ControllerSettings()->setProtocol(settings.m_protocol); if (response.getGs232ControllerSettings()->getTitle()) { *response.getGs232ControllerSettings()->getTitle() = settings.m_title; @@ -420,6 +430,12 @@ void GS232Controller::webapiUpdateFeatureSettings( if (featureSettingsKeys.contains("elevationMax")) { settings.m_elevationMax = response.getGs232ControllerSettings()->getElevationMax(); } + if (featureSettingsKeys.contains("tolerance")) { + settings.m_tolerance = response.getGs232ControllerSettings()->getTolerance(); + } + if (featureSettingsKeys.contains("protocol")) { + settings.m_protocol = (GS232ControllerSettings::Protocol)response.getGs232ControllerSettings()->getProtocol(); + } if (featureSettingsKeys.contains("title")) { settings.m_title = *response.getGs232ControllerSettings()->getTitle(); } @@ -484,6 +500,12 @@ void GS232Controller::webapiReverseSendSettings(QList& featureSettingsK if (featureSettingsKeys.contains("elevationMax") || force) { swgGS232ControllerSettings->setElevationMax(settings.m_elevationMax); } + if (featureSettingsKeys.contains("tolerance") || force) { + swgGS232ControllerSettings->setTolerance(settings.m_tolerance); + } + if (featureSettingsKeys.contains("protocol") || force) { + swgGS232ControllerSettings->setProtocol((int)settings.m_protocol); + } if (featureSettingsKeys.contains("title") || force) { swgGS232ControllerSettings->setTitle(new QString(settings.m_title)); } diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index f53445912..e0109d61c 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -94,8 +94,8 @@ bool GS232ControllerGUI::handleMessage(const Message& message) else if (GS232ControllerReport::MsgReportAzAl::match(message)) { GS232ControllerReport::MsgReportAzAl& azAl = (GS232ControllerReport::MsgReportAzAl&) message; - ui->azimuthCurrentText->setText(QString("%1").arg(round(azAl.getAzimuth()))); - ui->elevationCurrentText->setText(QString("%1").arg(round(azAl.getElevation()))); + ui->azimuthCurrentText->setText(QString("%1").arg(azAl.getAzimuth())); + ui->elevationCurrentText->setText(QString("%1").arg(azAl.getElevation())); return true; } else if (MainCore::MsgTargetAzimuthElevation::match(message)) @@ -153,6 +153,9 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(1000); + ui->azimuthCurrentText->setText("-"); + ui->elevationCurrentText->setText("-"); + updateSerialPortList(); displaySettings(); applySettings(true); @@ -175,6 +178,8 @@ void GS232ControllerGUI::displaySettings() blockApplySettings(true); ui->azimuth->setValue(m_settings.m_azimuth); ui->elevation->setValue(m_settings.m_elevation); + ui->protocol->setCurrentIndex((int)m_settings.m_protocol); + updateDecimals(m_settings.m_protocol); 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)); @@ -281,6 +286,27 @@ void GS232ControllerGUI::on_startStop_toggled(bool checked) } } +void GS232ControllerGUI::updateDecimals(GS232ControllerSettings::Protocol protocol) +{ + if (protocol == GS232ControllerSettings::GS232) + { + ui->azimuth->setDecimals(0); + ui->elevation->setDecimals(0); + } + else + { + ui->azimuth->setDecimals(1); + ui->elevation->setDecimals(1); + } +} + +void GS232ControllerGUI::on_protocol_currentIndexChanged(int index) +{ + m_settings.m_protocol = (GS232ControllerSettings::Protocol)index; + updateDecimals(m_settings.m_protocol); + applySettings(); +} + void GS232ControllerGUI::on_serialPort_currentIndexChanged(int index) { (void) index; @@ -295,16 +321,16 @@ void GS232ControllerGUI::on_baudRate_currentIndexChanged(int index) applySettings(); } -void GS232ControllerGUI::on_azimuth_valueChanged(int value) +void GS232ControllerGUI::on_azimuth_valueChanged(double value) { - m_settings.m_azimuth = value; + m_settings.m_azimuth = (float)value; ui->targetName->setText(""); applySettings(); } -void GS232ControllerGUI::on_elevation_valueChanged(int value) +void GS232ControllerGUI::on_elevation_valueChanged(double value) { - m_settings.m_elevation = value; + m_settings.m_elevation = (float)value; ui->targetName->setText(""); applySettings(); } @@ -345,6 +371,12 @@ void GS232ControllerGUI::on_elevationMax_valueChanged(int value) applySettings(); } +void GS232ControllerGUI::on_tolerance_valueChanged(int value) +{ + m_settings.m_tolerance = value; + applySettings(); +} + void GS232ControllerGUI::on_track_stateChanged(int state) { m_settings.m_track = state == Qt::Checked; diff --git a/plugins/feature/gs232controller/gs232controllergui.h b/plugins/feature/gs232controller/gs232controllergui.h index 8d46300e2..9fbfa8dda 100644 --- a/plugins/feature/gs232controller/gs232controllergui.h +++ b/plugins/feature/gs232controller/gs232controllergui.h @@ -64,6 +64,7 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); + void updateDecimals(GS232ControllerSettings::Protocol protocol); void updatePipeList(); void updateSerialPortList(); bool handleMessage(const Message& message); @@ -76,11 +77,12 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void handleInputMessages(); void on_startStop_toggled(bool checked); + void on_protocol_currentIndexChanged(int index); void on_serialPort_currentIndexChanged(int index); void on_baudRate_currentIndexChanged(int index); void on_track_stateChanged(int state); - void on_azimuth_valueChanged(int value); - void on_elevation_valueChanged(int value); + void on_azimuth_valueChanged(double value); + void on_elevation_valueChanged(double value); void on_targets_currentTextChanged(const QString& text); void on_azimuthOffset_valueChanged(int value); void on_elevationOffset_valueChanged(int value); @@ -88,8 +90,8 @@ private slots: void on_azimuthMax_valueChanged(int value); void on_elevationMin_valueChanged(int value); void on_elevationMax_valueChanged(int value); + void on_tolerance_valueChanged(int value); void updateStatus(); }; - #endif // INCLUDE_FEATURE_GS232CONTROLLERGUI_H_ diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 4e0c067dd..1ce95168a 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -6,8 +6,8 @@ 0 0 - 350 - 223 + 360 + 231 @@ -18,13 +18,13 @@ - 320 + 360 100 - 350 + 360 16777215 @@ -42,8 +42,8 @@ 10 10 - 331 - 191 + 341 + 211 @@ -70,7 +70,7 @@ - start/stop acquisition + Start/stop controller @@ -103,18 +103,18 @@ - + Target azimuth in degrees - - 0 + + 1 - 450 + 450.000000000000000 - 359 + 360.000000000000000 @@ -122,7 +122,7 @@ - 23 + 32 0 @@ -130,7 +130,7 @@ Current azimuth in degrees - - + 360.0 @@ -149,15 +149,18 @@ - + Target elevation in degrees + + 1 + - 180 + 180.000000000000000 - 180 + 180.000000000000000 @@ -165,7 +168,7 @@ - 22 + 32 0 @@ -173,75 +176,7 @@ Current elevation in degrees - - - - - - - - - - - - - Check to enable automatic tracking of azimuth and elevation from the specified channel - - - Track - - - - - - - Source - - - - - - - - 150 - 0 - - - - Target to track - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Target - - - - - - - Name of the target being tracked as indicated by the source channel / feature - - - true + 180.0 @@ -249,49 +184,60 @@ - - + + - Baud rate + Tolerance + + + + + + + Azimuth offset + + + + + + + Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment + + + -360 + + + 360 + + + + + + + Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment + + + -180 + + + 180 + + + 1 - - - 180 + + + Name of serial port to use to connect to the GS-232 controller - - - - - - Elevation min - - - - - - - Azimuth min - - - - - - - Elevation max + + true - - - 180 - - - - Serial port baud rate for the GS-232 controller @@ -351,84 +297,158 @@ - - - - 450 - - - - - - - 450 - - - - - - - Name of serial port to use to connect to the GS-232 controller - - - true - - - - + Serial Port - - - - Azimuth max - - - - - - - Azimuth offset - - - - - - - Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment - - - -360 - - - 360 - - - - + Elevation offset - - - - Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment - - - -180 + + + + Elevation max + + + + 180 - - 1 + + + + + + Command protocol + + + + GS-232 + + + + + SPID + + + + + + + + Azimuth min + + + + + + + 450 + + + + + + + Tolerance in degrees + + + + + + + Protocol + + + + + + + 180 + + + + + + + Elevation min + + + + + + + Baud rate + + + + + + + 450 + + + + + + + Azimuth max + + + + + + + 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 + + + + + + + Target + + + + + + + Source + + + + + + + + 150 + 0 + + + + Target to track @@ -454,9 +474,6 @@ startStop azimuth elevation - track - targets - targetName serialPort baudRate azimuthOffset diff --git a/plugins/feature/gs232controller/gs232controllerplugin.cpp b/plugins/feature/gs232controller/gs232controllerplugin.cpp index 3bd347824..b8974314c 100644 --- a/plugins/feature/gs232controller/gs232controllerplugin.cpp +++ b/plugins/feature/gs232controller/gs232controllerplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor GS232ControllerPlugin::m_pluginDescriptor = { GS232Controller::m_featureId, QStringLiteral("GS-232 Rotator Controller"), - QStringLiteral("6.13.0"), + QStringLiteral("6.13.1"), 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 298f296e0..12f01d643 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.cpp +++ b/plugins/feature/gs232controller/gs232controllersettings.cpp @@ -44,8 +44,8 @@ GS232ControllerSettings::GS232ControllerSettings() void GS232ControllerSettings::resetToDefaults() { - m_azimuth = 0; - m_elevation = 0; + m_azimuth = 0.0f; + m_elevation = 0.0f; m_serialPort = ""; m_baudRate = 9600; m_track = false; @@ -63,14 +63,16 @@ void GS232ControllerSettings::resetToDefaults() m_azimuthMax = 450; m_elevationMin = 0; m_elevationMax = 180; + m_tolerance = 0; + m_protocol = GS232; } QByteArray GS232ControllerSettings::serialize() const { SimpleSerializer s(1); - s.writeS32(1, m_azimuth); - s.writeS32(2, m_elevation); + s.writeFloat(1, m_azimuth); + s.writeFloat(2, m_elevation); s.writeString(3, m_serialPort); s.writeS32(4, m_baudRate); s.writeBool(5, m_track); @@ -88,6 +90,8 @@ QByteArray GS232ControllerSettings::serialize() const s.writeS32(18, m_azimuthMax); s.writeS32(19, m_elevationMin); s.writeS32(20, m_elevationMax); + s.writeS32(21, m_tolerance); + s.writeS32(22, (int)m_protocol); return s.final(); } @@ -108,8 +112,8 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data) uint32_t utmp; QString strtmp; - d.readS32(1, &m_azimuth, 0); - d.readS32(2, &m_elevation, 0); + d.readFloat(1, &m_azimuth, 0); + d.readFloat(2, &m_elevation, 0); d.readString(3, &m_serialPort, ""); d.readS32(4, &m_baudRate, 9600); d.readBool(5, &m_track, false); @@ -136,6 +140,8 @@ 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.readS32(21, &m_tolerance, 0); + d.readS32(22, (int*)&m_protocol, GS232); return true; } diff --git a/plugins/feature/gs232controller/gs232controllersettings.h b/plugins/feature/gs232controller/gs232controllersettings.h index 83dd8b787..eebb31b2b 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.h +++ b/plugins/feature/gs232controller/gs232controllersettings.h @@ -28,8 +28,8 @@ class Serializable; struct GS232ControllerSettings { - int m_azimuth; - int m_elevation; + float m_azimuth; + float m_elevation; QString m_serialPort; int m_baudRate; bool m_track; @@ -40,6 +40,8 @@ struct GS232ControllerSettings int m_azimuthMax; int m_elevationMin; int m_elevationMax; + int m_tolerance; + enum Protocol { GS232, SPID } m_protocol; QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; diff --git a/plugins/feature/gs232controller/gs232controllerworker.cpp b/plugins/feature/gs232controller/gs232controllerworker.cpp index 6b36fd002..1e5fe72b9 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.cpp +++ b/plugins/feature/gs232controller/gs232controllerworker.cpp @@ -38,8 +38,11 @@ GS232ControllerWorker::GS232ControllerWorker() : m_msgQueueToGUI(nullptr), m_running(false), m_mutex(QMutex::Recursive), - m_lastAzimuth(-1), - m_lastElevation(-1) + m_lastAzimuth(-1.0f), + m_lastElevation(-1.0f), + m_spidSetOutstanding(false), + m_spidSetSent(false), + m_spidStatusSent(false) { connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update())); m_pollTimer.start(1000); @@ -54,6 +57,11 @@ void GS232ControllerWorker::reset() { QMutexLocker mutexLocker(&m_mutex); m_inputMessageQueue.clear(); + m_lastAzimuth = -1.0f; + m_lastElevation = -1.0f; + m_spidSetOutstanding = false; + m_spidSetSent = false; + m_spidStatusSent = false; } bool GS232ControllerWorker::startWork() @@ -61,7 +69,9 @@ bool GS232ControllerWorker::startWork() QMutexLocker mutexLocker(&m_mutex); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect(&m_serialPort, &QSerialPort::readyRead, this, &GS232ControllerWorker::readSerialData); - openSerialPort(m_settings); + if (!m_settings.m_serialPort.isEmpty()) { + openSerialPort(m_settings); + } m_running = true; return m_running; } @@ -116,6 +126,8 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting << " m_azimuthMax: " << settings.m_azimuthMax << " m_elevationMin: " << settings.m_elevationMin << " m_elevationMax: " << settings.m_elevationMax + << " m_tolerance: " << settings.m_tolerance + << " m_protocol: " << settings.m_protocol << " m_serialPort: " << settings.m_serialPort << " m_baudRate: " << settings.m_baudRate << " force: " << force; @@ -131,21 +143,25 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting // Apply offset then clamp - int azimuth = settings.m_azimuth; + float azimuth = settings.m_azimuth; azimuth += settings.m_azimuthOffset; - azimuth = std::max(azimuth, settings.m_azimuthMin); - azimuth = std::min(azimuth, settings.m_azimuthMax); + azimuth = std::max(azimuth, (float)settings.m_azimuthMin); + azimuth = std::min(azimuth, (float)settings.m_azimuthMax); - int elevation = settings.m_elevation; + float elevation = settings.m_elevation; elevation += settings.m_elevationOffset; - elevation = std::max(elevation, settings.m_elevationMin); - elevation = std::min(elevation, settings.m_elevationMax); + elevation = std::max(elevation, (float)settings.m_elevationMin); + elevation = std::min(elevation, (float)settings.m_elevationMax); - if (((elevation != m_lastElevation) || force) && (settings.m_elevationMax != 0)) + // Don't set if within tolerance of last setting + float azDiff = std::abs(azimuth - m_lastAzimuth); + float elDiff = std::abs(elevation - m_lastElevation); + + if (((elDiff > settings.m_tolerance) || (m_lastElevation == -1) || force) && (settings.m_elevationMax != 0)) { setAzimuthElevation(azimuth, elevation); } - else if ((azimuth != m_lastAzimuth) || force) + else if ((azDiff > settings.m_tolerance) || (m_lastAzimuth == -1) || force) { setAzimuth(azimuth); } @@ -155,35 +171,81 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting void GS232ControllerWorker::openSerialPort(const GS232ControllerSettings& settings) { - if (m_serialPort.isOpen()) + 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)) { qCritical() << "GS232ControllerWorker::openSerialPort: Failed to open serial port " << settings.m_serialPort << ". Error: " << m_serialPort.error(); - if (m_msgQueueToFeature) + if (m_msgQueueToFeature) { m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Failed to open serial port %1: %2").arg(settings.m_serialPort).arg(m_serialPort.error()))); + } } m_lastAzimuth = -1; m_lastElevation = -1; } -void GS232ControllerWorker::setAzimuth(int azimuth) +void GS232ControllerWorker::setAzimuth(float azimuth) { - QString cmd = QString("M%1\r\n").arg(azimuth, 3, 10, QLatin1Char('0')); - QByteArray data = cmd.toLatin1(); - m_serialPort.write(data); - m_lastAzimuth = azimuth; + if (m_settings.m_protocol == GS232ControllerSettings::GS232) + { + QString cmd = QString("M%1\r\n").arg((int)std::round(azimuth), 3, 10, QLatin1Char('0')); + QByteArray data = cmd.toLatin1(); + m_serialPort.write(data); + } + else + { + setAzimuthElevation(azimuth, m_lastElevation); + } + m_lastAzimuth = azimuth; } -void GS232ControllerWorker::setAzimuthElevation(int azimuth, int elevation) +void GS232ControllerWorker::setAzimuthElevation(float azimuth, float elevation) { - QString cmd = QString("W%1 %2\r\n").arg(azimuth, 3, 10, QLatin1Char('0')).arg(elevation, 3, 10, QLatin1Char('0')); - QByteArray data = cmd.toLatin1(); - m_serialPort.write(data); - m_lastAzimuth = azimuth; - m_lastElevation = elevation; + if (m_settings.m_protocol == GS232ControllerSettings::GS232) + { + QString cmd = QString("W%1 %2\r\n").arg((int)std::round(azimuth), 3, 10, QLatin1Char('0')).arg((int)std::round(elevation), 3, 10, QLatin1Char('0')); + QByteArray data = cmd.toLatin1(); + m_serialPort.write(data); + } + else + { + qDebug() << "GS232ControllerWorker::setAzimuthElevation " << " AZ " << azimuth << " EL " << elevation; + + if (!m_spidSetSent && !m_spidStatusSent) + { + QByteArray cmd(13, (char)0); + + cmd[0] = 0x57; // Start + int h = std::round((azimuth + 360.0f) * 2.0f); + cmd[1] = 0x30 | (h / 1000); + cmd[2] = 0x30 | ((h % 1000) / 100); + cmd[3] = 0x30 | ((h % 100) / 10); + cmd[4] = 0x30 | (h % 10); + cmd[5] = 2; // 2 degree per impulse + int v = std::round((elevation + 360.0f) * 2.0f); + cmd[6] = 0x30 | (v / 1000); + cmd[7] = 0x30 | ((v % 1000) / 100); + cmd[8] = 0x30 | ((v % 100) / 10); + cmd[9] = 0x30 | (v % 10); + cmd[10] = 2; // 2 degree per impulse + cmd[11] = 0x2f; // Set cmd + cmd[12] = 0x20; // End + + m_serialPort.write(cmd); + + m_spidSetSent = true; + } + else + { + qDebug() << "GS232ControllerWorker::setAzimuthElevation: Not sent, waiting for status reply"; + m_spidSetOutstanding = true; + } + } + m_lastAzimuth = azimuth; + m_lastElevation = elevation; } void GS232ControllerWorker::readSerialData() @@ -191,32 +253,77 @@ void GS232ControllerWorker::readSerialData() char buf[1024]; qint64 len; - while (m_serialPort.canReadLine()) + if (m_settings.m_protocol == GS232ControllerSettings::GS232) { - len = m_serialPort.readLine(buf, sizeof(buf)); - if (len != -1) + while (m_serialPort.canReadLine()) { - QString response = QString::fromUtf8(buf, len); - // MD-02 can return AZ=-00 EL=-00 and other negative angles - QRegularExpression re("AZ=([-\\d]\\d\\d) *EL=([-\\d]\\d\\d)"); - QRegularExpressionMatch match = re.match(response); - if (match.hasMatch()) + len = m_serialPort.readLine(buf, sizeof(buf)); + if (len != -1) { - QString az = match.captured(1); - QString el = match.captured(2); - //qDebug() << "GS232ControllerWorker::readSerialData read az " << az << " el " << el; - if (getMessageQueueToGUI()) - getMessageQueueToGUI()->push( GS232ControllerReport::MsgReportAzAl::create(az.toFloat(), el.toFloat())); + QString response = QString::fromUtf8(buf, len); + // MD-02 can return AZ=-00 EL=-00 and other negative angles + QRegularExpression re("AZ=([-\\d]\\d\\d) *EL=([-\\d]\\d\\d)"); + QRegularExpressionMatch match = re.match(response); + if (match.hasMatch()) + { + QString az = match.captured(1); + QString el = match.captured(2); + //qDebug() << "GS232ControllerWorker::readSerialData read Az " << az << " El " << el; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push( GS232ControllerReport::MsgReportAzAl::create(az.toFloat(), el.toFloat())); + } + } + else if (response == "\r\n") + { + // Ignore + } + else + { + qDebug() << "GS232ControllerWorker::readSerialData - unexpected GS-232 response \"" << response << "\""; + if (m_msgQueueToFeature) { + m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected GS-232 response: %1").arg(response))); + } + } } - else if (response == "\r\n") + } + } + else + { + while (m_serialPort.bytesAvailable() >= 12) + { + len = m_serialPort.read(buf, 12); + if ((len == 12) && (buf[0] == 0x57)) { - // Ignore + 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; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push( GS232ControllerReport::MsgReportAzAl::create(az, el)); + } + if (m_spidStatusSent && m_spidSetSent) { + qDebug() << "GS232ControllerWorker::readSerialData - m_spidStatusSent and m_spidSetSent set simultaneously"; + } + if (m_spidStatusSent) { + m_spidStatusSent = false; + } + if (m_spidSetSent) { + m_spidSetSent = false; + } + if (m_spidSetOutstanding) + { + m_spidSetOutstanding = false; + setAzimuthElevation(m_lastAzimuth, m_lastElevation); + } } else { - qDebug() << "GS232ControllerWorker::readSerialData - unexpected response \"" << response << "\""; - if (m_msgQueueToFeature) - m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected GS-232 serial response: %1").arg(response))); + 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()))); + } } } } @@ -227,7 +334,27 @@ void GS232ControllerWorker::update() // Request current Az/El from GS-232 controller if (m_serialPort.isOpen()) { - QByteArray cmd("C2\r\n"); - m_serialPort.write(cmd); + if (m_settings.m_protocol == GS232ControllerSettings::GS232) + { + QByteArray cmd("C2\r\n"); + m_serialPort.write(cmd); + } + else + { + // Don't send a new status command, if waiting for a previous reply + if (!m_spidSetSent && !m_spidStatusSent) + { + // Status + QByteArray cmd; + cmd.append((char)0x57); // Start + for (int i = 0; i < 10; i++) { + cmd.append((char)0x0); + } + cmd.append((char)0x1f); // Status + cmd.append((char)0x20); // End + m_serialPort.write(cmd); + m_spidStatusSent = true; + } + } } } diff --git a/plugins/feature/gs232controller/gs232controllerworker.h b/plugins/feature/gs232controller/gs232controllerworker.h index 0bf22cace..3755cde20 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.h +++ b/plugins/feature/gs232controller/gs232controllerworker.h @@ -76,15 +76,19 @@ private: QSerialPort m_serialPort; QTimer m_pollTimer; - int m_lastAzimuth; - int m_lastElevation; + float m_lastAzimuth; + float m_lastElevation; + + bool m_spidSetOutstanding; + bool m_spidSetSent; + bool m_spidStatusSent; bool handleMessage(const Message& cmd); void applySettings(const GS232ControllerSettings& settings, bool force = false); MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; } void openSerialPort(const GS232ControllerSettings& settings); - void setAzimuth(int azimuth); - void setAzimuthElevation(int azimuth, int elevation); + void setAzimuth(float azimuth); + void setAzimuthElevation(float azimuth, float elevation); private slots: void handleInputMessages(); diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 854a336c9..4dfb24669 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -38,28 +38,40 @@ Specify the SDRangel Channel or Feature that that will control the target aziumt When tracking is enabled, this field will display a name for the target being tracked, as indicated by the selected Source plugin (5). For example, the ADS-B plugin will display the flight number of the target aircraft. The Star Tracker plugin will display Sun, Moon or Star. -

7: Serial Port

+

7: Protocol

+ +Selects which serial protocol to use. This can be GS-232 or SPID (rot2prog). + +

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

9: Serial Port

Specifies the serial port (E.g. COM3 on Windows or /dev/ttyS0 on Linux) that will be used to send commands to the GS-232 rotator. -

8: Baud rate

+

10: Baud rate

Specifies the baud rate that will be used to send commands to the GS-232 rotator. Typically this is 9600. -

9: Azimuth Offset

+

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

10: Elevation Offset

+

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

11 and 12: Azimuth Min and Max

+

13 and 14: 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. -

13 and 14: Elevation Min and Max

+

15 and 16: 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. @@ -71,6 +83,13 @@ 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

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

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: diff --git a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml index 347fa5239..e193bd01e 100644 --- a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml +++ b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml @@ -3,10 +3,12 @@ GS232ControllerSettings: properties: azimuth: description: Target azimuth in degrees (0-450) - type: integer + type: number + format: float elevation: description: Target elevation in degrees (0-180) - type: integer + type: number + format: float serialPort: description: The serial port the GS-232 controller is connected to type: string @@ -37,6 +39,12 @@ GS232ControllerSettings: elevationMax: description: Maximum elevation the controller will output type: integer + tolerance: + description: Tolerance in degrees + type: integer + protocol: + description: (0 GS-232, 1 SPID rot2prog) + type: integer title: type: string rgbColor: diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp index cab3b0ed5..d2b798065 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp @@ -28,9 +28,9 @@ SWGGS232ControllerSettings::SWGGS232ControllerSettings(QString* json) { } SWGGS232ControllerSettings::SWGGS232ControllerSettings() { - azimuth = 0; + azimuth = 0.0f; m_azimuth_isSet = false; - elevation = 0; + elevation = 0.0f; m_elevation_isSet = false; serial_port = nullptr; m_serial_port_isSet = false; @@ -52,6 +52,10 @@ SWGGS232ControllerSettings::SWGGS232ControllerSettings() { m_elevation_min_isSet = false; elevation_max = 0; m_elevation_max_isSet = false; + tolerance = 0; + m_tolerance_isSet = false; + protocol = 0; + m_protocol_isSet = false; title = nullptr; m_title_isSet = false; rgb_color = 0; @@ -74,9 +78,9 @@ SWGGS232ControllerSettings::~SWGGS232ControllerSettings() { void SWGGS232ControllerSettings::init() { - azimuth = 0; + azimuth = 0.0f; m_azimuth_isSet = false; - elevation = 0; + elevation = 0.0f; m_elevation_isSet = false; serial_port = new QString(""); m_serial_port_isSet = false; @@ -98,6 +102,10 @@ SWGGS232ControllerSettings::init() { m_elevation_min_isSet = false; elevation_max = 0; m_elevation_max_isSet = false; + tolerance = 0; + m_tolerance_isSet = false; + protocol = 0; + m_protocol_isSet = false; title = new QString(""); m_title_isSet = false; rgb_color = 0; @@ -132,6 +140,8 @@ SWGGS232ControllerSettings::cleanup() { + + if(title != nullptr) { delete title; } @@ -156,9 +166,9 @@ SWGGS232ControllerSettings::fromJson(QString &json) { void SWGGS232ControllerSettings::fromJsonObject(QJsonObject &pJson) { - ::SWGSDRangel::setValue(&azimuth, pJson["azimuth"], "qint32", ""); + ::SWGSDRangel::setValue(&azimuth, pJson["azimuth"], "float", ""); - ::SWGSDRangel::setValue(&elevation, pJson["elevation"], "qint32", ""); + ::SWGSDRangel::setValue(&elevation, pJson["elevation"], "float", ""); ::SWGSDRangel::setValue(&serial_port, pJson["serialPort"], "QString", "QString"); @@ -180,6 +190,10 @@ SWGGS232ControllerSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&elevation_max, pJson["elevationMax"], "qint32", ""); + ::SWGSDRangel::setValue(&tolerance, pJson["tolerance"], "qint32", ""); + + ::SWGSDRangel::setValue(&protocol, pJson["protocol"], "qint32", ""); + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); @@ -246,6 +260,12 @@ SWGGS232ControllerSettings::asJsonObject() { if(m_elevation_max_isSet){ obj->insert("elevationMax", QJsonValue(elevation_max)); } + if(m_tolerance_isSet){ + obj->insert("tolerance", QJsonValue(tolerance)); + } + if(m_protocol_isSet){ + obj->insert("protocol", QJsonValue(protocol)); + } if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } @@ -271,22 +291,22 @@ SWGGS232ControllerSettings::asJsonObject() { return obj; } -qint32 +float SWGGS232ControllerSettings::getAzimuth() { return azimuth; } void -SWGGS232ControllerSettings::setAzimuth(qint32 azimuth) { +SWGGS232ControllerSettings::setAzimuth(float azimuth) { this->azimuth = azimuth; this->m_azimuth_isSet = true; } -qint32 +float SWGGS232ControllerSettings::getElevation() { return elevation; } void -SWGGS232ControllerSettings::setElevation(qint32 elevation) { +SWGGS232ControllerSettings::setElevation(float elevation) { this->elevation = elevation; this->m_elevation_isSet = true; } @@ -391,6 +411,26 @@ SWGGS232ControllerSettings::setElevationMax(qint32 elevation_max) { this->m_elevation_max_isSet = true; } +qint32 +SWGGS232ControllerSettings::getTolerance() { + return tolerance; +} +void +SWGGS232ControllerSettings::setTolerance(qint32 tolerance) { + this->tolerance = tolerance; + this->m_tolerance_isSet = true; +} + +qint32 +SWGGS232ControllerSettings::getProtocol() { + return protocol; +} +void +SWGGS232ControllerSettings::setProtocol(qint32 protocol) { + this->protocol = protocol; + this->m_protocol_isSet = true; +} + QString* SWGGS232ControllerSettings::getTitle() { return title; @@ -502,6 +542,12 @@ SWGGS232ControllerSettings::isSet(){ if(m_elevation_max_isSet){ isObjectUpdated = true; break; } + if(m_tolerance_isSet){ + isObjectUpdated = true; break; + } + if(m_protocol_isSet){ + isObjectUpdated = true; break; + } if(title && *title != QString("")){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h index 689936b02..10d0adfdd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h @@ -42,11 +42,11 @@ public: virtual void fromJsonObject(QJsonObject &json) override; virtual SWGGS232ControllerSettings* fromJson(QString &jsonString) override; - qint32 getAzimuth(); - void setAzimuth(qint32 azimuth); + float getAzimuth(); + void setAzimuth(float azimuth); - qint32 getElevation(); - void setElevation(qint32 elevation); + float getElevation(); + void setElevation(float elevation); QString* getSerialPort(); void setSerialPort(QString* serial_port); @@ -78,6 +78,12 @@ public: qint32 getElevationMax(); void setElevationMax(qint32 elevation_max); + qint32 getTolerance(); + void setTolerance(qint32 tolerance); + + qint32 getProtocol(); + void setProtocol(qint32 protocol); + QString* getTitle(); void setTitle(QString* title); @@ -103,10 +109,10 @@ public: virtual bool isSet() override; private: - qint32 azimuth; + float azimuth; bool m_azimuth_isSet; - qint32 elevation; + float elevation; bool m_elevation_isSet; QString* serial_port; @@ -139,6 +145,12 @@ private: qint32 elevation_max; bool m_elevation_max_isSet; + qint32 tolerance; + bool m_tolerance_isSet; + + qint32 protocol; + bool m_protocol_isSet; + QString* title; bool m_title_isSet; From 395c04e4bdd4fd0d98d77eddb860b778a73e7a40 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 30 May 2021 12:45:11 +0100 Subject: [PATCH 2/4] Update GS232 Controller image --- doc/img/GS232Controller_plugin.png | Bin 16788 -> 13372 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/img/GS232Controller_plugin.png b/doc/img/GS232Controller_plugin.png index 9dcef6887ea036de9d829106f5d24722bf1173c6..a7510cdd814a76770bfc6b1df3bcf9d081cdae53 100644 GIT binary patch literal 13372 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 G_x~UDj3ze# literal 16788 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 From 8eb369b3bcdf1eeb41d9add40e1f7681abd1583d Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 30 May 2021 12:52:01 +0100 Subject: [PATCH 3/4] Add cmath to fix gcc compilation --- plugins/feature/gs232controller/gs232controllerworker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/feature/gs232controller/gs232controllerworker.cpp b/plugins/feature/gs232controller/gs232controllerworker.cpp index 1e5fe72b9..11c930594 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.cpp +++ b/plugins/feature/gs232controller/gs232controllerworker.cpp @@ -17,6 +17,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include #include From 51c240f42f3ccb3f9b9f768c6f32d3853870714c Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 30 May 2021 12:54:43 +0100 Subject: [PATCH 4/4] Remove unused variable --- plugins/feature/startracker/startrackergui.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/feature/startracker/startrackergui.cpp b/plugins/feature/startracker/startrackergui.cpp index 4cb1e23db..0f0be1689 100644 --- a/plugins/feature/startracker/startrackergui.cpp +++ b/plugins/feature/startracker/startrackergui.cpp @@ -740,7 +740,7 @@ QList StarTrackerGUI::createDriftScan(bool galactic) AzAlt aa; aa.alt = m_settings.m_el; aa.az = m_settings.m_az; - double prevX, prevY; + double prevX; // Plot every 30min over a day for (int i = 0; i <= 24*2; i++) { @@ -767,7 +767,6 @@ QList StarTrackerGUI::createDriftScan(bool galactic) series->append(x, y); } prevX = x; - prevY = y; } return list;