summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasem Mutlaq <mutlaqja@ikarustech.com>2016-09-24 20:16:34 (GMT)
committerJasem Mutlaq <mutlaqja@ikarustech.com>2016-09-24 20:16:34 (GMT)
commitabcbcd87736bcd2a76e20fe37f4982f0a775e7c9 (patch)
treefdf6f89259c1d56b45d5485b26db1a432db41b9f
parentaeb023954713b066e43f7e85d4c44cdf50255915 (diff)
Moving all guide module files to their own dedicated directly. Separating external vs internal guiding tools. Started on creating internal to unify all guiding commands so that they can be used for either internal or external guiding tools equally. Major redesign of guide module tab to make all information available in one place. Still lots of work in progress
-rw-r--r--kstars/ekos/guide/externalguide/phd2.cpp591
-rw-r--r--kstars/ekos/guide/externalguide/phd2.h104
-rw-r--r--kstars/ekos/guide/guide.cpp1279
-rw-r--r--kstars/ekos/guide/guide.h346
-rw-r--r--kstars/ekos/guide/guide.ui1000
-rw-r--r--kstars/ekos/guide/internalguide/common.cpp (renamed from kstars/ekos/guide/common.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/common.h (renamed from kstars/ekos/guide/common.h)0
-rw-r--r--kstars/ekos/guide/internalguide/gmath.cpp (renamed from kstars/ekos/guide/gmath.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/gmath.h (renamed from kstars/ekos/guide/gmath.h)0
-rw-r--r--kstars/ekos/guide/internalguide/guider.cpp (renamed from kstars/ekos/guide/guider.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/guider.h (renamed from kstars/ekos/guide/guider.h)0
-rw-r--r--kstars/ekos/guide/internalguide/guider.ui (renamed from kstars/ekos/guide/guider.ui)0
-rw-r--r--kstars/ekos/guide/internalguide/matr.cpp (renamed from kstars/ekos/guide/matr.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/matr.h (renamed from kstars/ekos/guide/matr.h)0
-rw-r--r--kstars/ekos/guide/internalguide/rcalibration.cpp (renamed from kstars/ekos/guide/rcalibration.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/rcalibration.h (renamed from kstars/ekos/guide/rcalibration.h)0
-rw-r--r--kstars/ekos/guide/internalguide/rcalibration.ui (renamed from kstars/ekos/guide/rcalibration.ui)0
-rw-r--r--kstars/ekos/guide/internalguide/scroll_graph.cpp (renamed from kstars/ekos/guide/scroll_graph.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/scroll_graph.h (renamed from kstars/ekos/guide/scroll_graph.h)0
-rw-r--r--kstars/ekos/guide/internalguide/vect.cpp (renamed from kstars/ekos/guide/vect.cpp)0
-rw-r--r--kstars/ekos/guide/internalguide/vect.h (renamed from kstars/ekos/guide/vect.h)0
21 files changed, 3320 insertions, 0 deletions
diff --git a/kstars/ekos/guide/externalguide/phd2.cpp b/kstars/ekos/guide/externalguide/phd2.cpp
new file mode 100644
index 0000000..b64db81
--- /dev/null
+++ b/kstars/ekos/guide/externalguide/phd2.cpp
@@ -0,0 +1,591 @@
+/* Ekos PHD2 Handler
+ Copyright (C) 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+*/
+
+#include <QtNetwork/QNetworkReply>
+#include <QtNetwork/QNetworkRequest>
+#include <QUrl>
+#include <QVariantMap>
+#include <QDebug>
+#include <QHttpMultiPart>
+#include <QFile>
+#include <QJsonObject>
+#include <QJsonDocument>
+
+#include <KMessageBox>
+#include <KLocalizedString>
+
+#include "phd2.h"
+#include "Options.h"
+
+
+namespace Ekos
+{
+
+PHD2::PHD2()
+{
+ tcpSocket = new QTcpSocket(this);
+
+ connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readPHD2()));
+ connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError)));
+
+ connect(tcpSocket, SIGNAL(connected()), this, SIGNAL(connected()));
+ connect(tcpSocket, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
+
+ methodID=1;
+ state = STOPPED;
+ connection = DISCONNECTED;
+ event = Alert;
+
+ events["Version"] = Version;
+ events["LockPositionSet"] = LockPositionSet;
+ events["CalibrationComplete"] = CalibrationComplete;
+ events["StarSelected"] = StarSelected;
+ events["StartGuiding"] = StartGuiding;
+ events["Paused"] = Paused;
+ events["StartCalibration"] = StartCalibration;
+ events["AppState"] = AppState;
+ events["CalibrationFailed"] = CalibrationFailed;
+ events["CalibrationDataFlipped"] = CalibrationDataFlipped;
+ events["LoopingExposures"] = LoopingExposures;
+ events["LoopingExposuresStopped"] = LoopingExposuresStopped;
+ events["Settling"] = Settling;
+ events["SettleDone"] = SettleDone;
+ events["StarLost"] = StarLost;
+ events["GuidingStopped"] = GuidingStopped;
+ events["Resumed"] = Resumed;
+ events["GuideStep"] = GuideStep;
+ events["GuidingDithered"] = GuidingDithered;
+ events["LockPositionLost"] = LockPositionLost;
+ events["Alert"] = Alert;
+
+}
+
+PHD2::~PHD2()
+{
+
+}
+
+void PHD2::connectPHD2()
+{
+ if (connection == DISCONNECTED)
+ {
+ connection = CONNECTING;
+ tcpSocket->connectToHost(Options::pHD2Host(), Options::pHD2Port());
+ }
+ // Already connected, let's connect equipment
+ else
+ setEquipmentConnected(true);
+}
+
+void PHD2::disconnectPHD2()
+{
+ if (connection == EQUIPMENT_CONNECTED)
+ setEquipmentConnected(false);
+
+ connection = DISCONNECTED;
+ tcpSocket->disconnectFromHost();
+}
+
+void PHD2::displayError(QAbstractSocket::SocketError socketError)
+{
+ switch (socketError)
+ {
+ case QAbstractSocket::RemoteHostClosedError:
+ break;
+ case QAbstractSocket::HostNotFoundError:
+ emit newLog(i18n("The host was not found. Please check the host name and port settings in Ekos options."));
+ break;
+ case QAbstractSocket::ConnectionRefusedError:
+ emit newLog(i18n("The connection was refused by the peer. Make sure the PHD2 is running, and check that the host name and port settings are correct."));
+ break;
+ default:
+ emit newLog(i18n("The following error occurred: %1.", tcpSocket->errorString()));
+ }
+
+ connection = DISCONNECTED;
+
+}
+
+void PHD2::readPHD2()
+{
+ QTextStream stream(tcpSocket);
+
+ QJsonParseError qjsonError;
+
+ while (stream.atEnd() == false)
+ {
+ QString rawString = stream.readLine();
+
+ if (rawString.isEmpty())
+ continue;
+
+ QJsonDocument jdoc = QJsonDocument::fromJson(rawString.toLatin1(), &qjsonError);
+
+ if (qjsonError.error != QJsonParseError::NoError)
+ {
+ emit newLog(rawString);
+ emit newLog(qjsonError.errorString());
+ continue;
+ }
+
+ emit newLog(rawString);
+
+ processJSON(jdoc.object());
+ }
+
+}
+
+void PHD2::processJSON(const QJsonObject &jsonObj)
+{
+ PHD2MessageType messageType = PHD2_UNKNOWN;
+ bool result = false;
+
+ if (jsonObj.contains("Event"))
+ {
+ messageType = PHD2_EVENT;
+ processPHD2Event(jsonObj);
+
+ if (event == Alert)
+ return;
+ }
+ else if (jsonObj.contains("error"))
+ {
+ messageType = PHD2_ERROR;
+ result = false;
+ processPHD2Error(jsonObj);
+ }
+ else if (jsonObj.contains("result"))
+ {
+ messageType = PHD2_RESULT;
+ result = true;
+ }
+
+ switch (connection)
+ {
+ case CONNECTING:
+ if (event == Version)
+ connection = CONNECTED;
+ return;
+
+ case CONNECTED:
+ // If initial state is STOPPED, let us connect equipment
+ if (state == STOPPED)
+ setEquipmentConnected(true);
+ else if (state == GUIDING)
+ {
+ connection = EQUIPMENT_CONNECTED;
+ emit connected();
+ }
+ return;
+
+ case DISCONNECTED:
+ break;
+
+ case EQUIPMENT_CONNECTING:
+ if (messageType == PHD2_RESULT)
+ {
+ if (result)
+ {
+ connection = EQUIPMENT_CONNECTED;
+ emit connected();
+ }
+ else
+ {
+ connection = EQUIPMENT_DISCONNECTED;
+ emit disconnected();
+ }
+ }
+ return;
+
+ case EQUIPMENT_CONNECTED:
+ case EQUIPMENT_DISCONNECTED:
+ break;
+
+ case EQUIPMENT_DISCONNECTING:
+ connection = EQUIPMENT_DISCONNECTED;
+ //emit disconnected();
+ return;
+ }
+
+ switch (state)
+ {
+ case GUIDING:
+ break;
+
+ case PAUSED:
+ break;
+
+ case STOPPED:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void PHD2::processPHD2Event(const QJsonObject &jsonEvent)
+{
+ QString eventName = jsonEvent["Event"].toString();
+
+ if (events.contains(eventName) == false)
+ {
+ emit newLog(i18n("Unknown PHD2 event: %1", eventName));
+ return;
+ }
+
+ event = events.value(eventName);
+
+ switch (event)
+ {
+ case Version:
+ emit newLog(i18n("PHD2: Version %1", jsonEvent["PHDVersion"].toString()));
+ break;
+
+ case CalibrationComplete:
+ //state = CALIBRATION_SUCCESSFUL;
+ // It goes immediately to guiding until PHD implements a calibration-only method
+ state = GUIDING;
+ emit newLog(i18n("PHD2: Calibration Complete."));
+ //emit guideReady();
+ emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS);
+ break;
+
+ case StartGuiding:
+ state = GUIDING;
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ connection = EQUIPMENT_CONNECTED;
+ emit connected();
+ }
+ emit newLog(i18n("PHD2: Guiding Started."));
+ //emit autoGuidingToggled(true);
+ emit newStatus(Ekos::GUIDE_GUIDING);
+ break;
+
+ case Paused:
+ state = PAUSED;
+ emit newLog(i18n("PHD2: Guiding Paused."));
+ emit newStatus(Ekos::GUIDE_SUSPENDED);
+ break;
+
+ case StartCalibration:
+ state = CALIBRATING;
+ emit newLog(i18n("PHD2: Calibration Started."));
+ emit newStatus(Ekos::GUIDE_CALIBRATING);
+ break;
+
+ case AppState:
+ processPHD2State(jsonEvent["State"].toString());
+ break;
+
+ case CalibrationFailed:
+ state = CALIBRATION_FAILED;
+ emit newLog(i18n("PHD2: Calibration Failed (%1).", jsonEvent["Reason"].toString()));
+ emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
+ break;
+
+ case CalibrationDataFlipped:
+ emit newLog(i18n("Calibration Data Flipped."));
+ break;
+
+ case LoopingExposures:
+ //emit newLog(i18n("PHD2: Looping Exposures."));
+ break;
+
+ case LoopingExposuresStopped:
+ emit newLog(i18n("PHD2: Looping Exposures Stopped."));
+ break;
+
+ case Settling:
+ break;
+
+ case SettleDone:
+ {
+ bool error=false;
+
+ if (jsonEvent["Status"].toInt() != 0)
+ {
+ error = true;
+ emit newLog(i18n("PHD2: Settling failed (%1).", jsonEvent["Error"].toString()));
+ }
+
+ if (state == GUIDING)
+ {
+ if (error)
+ state = STOPPED;
+ }
+ else if (state == DITHERING)
+ {
+ if (error)
+ {
+ state = DITHER_FAILED;
+ //emit ditherFailed();
+ emit newStatus(GUIDE_DITHERING_ERROR);
+ }
+ else
+ {
+ state = DITHER_SUCCESSFUL;
+ emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
+ }
+ }
+ }
+ break;
+
+ case StarSelected:
+ emit newLog(i18n("PHD2: Star Selected."));
+ break;
+
+ case StarLost:
+ emit newLog(i18n("PHD2: Star Lost."));
+ emit newStatus(Ekos::GUIDE_ABORTED);
+ break;
+
+ case GuidingStopped:
+ emit newLog(i18n("PHD2: Guiding Stopped."));
+ //emit autoGuidingToggled(false);
+ emit newStatus(Ekos::GUIDE_IDLE);
+ break;
+
+ case Resumed:
+ emit newLog(i18n("PHD2: Guiding Resumed."));
+ emit newStatus(Ekos::GUIDE_GUIDING);
+ state = GUIDING;
+ break;
+
+ case GuideStep:
+ {
+ double diff_ra_pixels, diff_de_pixels, diff_ra_arcsecs, diff_de_arcsecs;
+ diff_ra_pixels = jsonEvent["RADistanceRaw"].toDouble();
+ diff_de_pixels = jsonEvent["DecDistanceRaw"].toDouble();
+
+ diff_ra_arcsecs = 206264.8062470963552 * diff_ra_pixels * ccd_pixel_width / focal;
+ diff_de_arcsecs = 206264.8062470963552 * diff_de_pixels * ccd_pixel_height / focal;
+
+ emit newAxisDelta(diff_ra_arcsecs, diff_de_arcsecs);
+ }
+ break;
+
+ case GuidingDithered:
+ emit newLog(i18n("PHD2: Guide Dithering."));
+ emit newStatus(Ekos::GUIDE_DITHERING);
+ break;
+
+ case LockPositionSet:
+ emit newLog(i18n("PHD2: Lock Position Set."));
+ break;
+
+ case LockPositionLost:
+ emit newLog(i18n("PHD2: Lock Position Lost."));
+ emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
+ break;
+
+ case Alert:
+ emit newLog(i18n("PHD2 %1: %2", jsonEvent["Type"].toString(), jsonEvent["Msg"].toString()));
+ break;
+
+ }
+}
+
+void PHD2::processPHD2State(const QString &phd2State)
+{
+ if (phd2State == "Stopped")
+ state = STOPPED;
+ else if (phd2State == "Selected")
+ state = SELECTED;
+ else if (phd2State == "Calibrating")
+ state = CALIBRATING;
+ else if (phd2State == "GUIDING")
+ state = GUIDING;
+ else if (phd2State == "LostLock")
+ state = LOSTLOCK;
+ else if (phd2State == "Paused")
+ state = PAUSED;
+ else if (phd2State == "Looping")
+ state = LOOPING;
+}
+
+void PHD2::processPHD2Error(const QJsonObject &jsonError)
+{
+ QJsonObject jsonErrorObject = jsonError["error"].toObject();
+
+ emit newLog(i18n("PHD2 Error: %1", jsonErrorObject["message"].toString()));
+
+ /* switch (connection)
+ {
+ case CONNECTING:
+ case CONNECTED:
+ emit disconnected();
+ break;
+
+ default:
+ break;
+ }*/
+}
+
+void PHD2::sendJSONRPCRequest(const QString & method, const QJsonArray args)
+{
+
+ QJsonObject jsonRPC;
+
+ jsonRPC.insert("jsonrpc", "2.0");
+ jsonRPC.insert("method", method);
+
+ if (args.empty() == false)
+ jsonRPC.insert("params", args);
+
+ jsonRPC.insert("id", methodID++);
+
+ QJsonDocument json_doc(jsonRPC);
+
+ emit newLog(json_doc.toJson(QJsonDocument::Compact));
+
+ tcpSocket->write(json_doc.toJson(QJsonDocument::Compact));
+ tcpSocket->write("\r\n");
+}
+
+void PHD2::setEquipmentConnected(bool enable)
+{
+ if ( (connection == EQUIPMENT_CONNECTED && enable == true) || (connection == EQUIPMENT_DISCONNECTED && enable == false) )
+ return;
+
+ if (enable)
+ connection = EQUIPMENT_CONNECTING;
+ else
+ connection = EQUIPMENT_DISCONNECTING;
+
+ QJsonArray args;
+
+ // connected = enable
+ args << enable;
+
+ sendJSONRPCRequest("set_connected", args);
+}
+
+bool PHD2::startGuiding()
+{
+
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ emit newLog(i18n("PHD2 Error: Equipment not connected."));
+ return false;
+ }
+
+ QJsonArray args;
+ QJsonObject settle;
+
+ settle.insert("pixels", 1.5);
+ settle.insert("time", 8);
+ settle.insert("timeout", 45);
+
+ // Settle param
+ args << settle;
+ // Recalibrate param
+ args << false;
+
+ sendJSONRPCRequest("guide", args);
+
+ return true;
+}
+
+bool PHD2::stopGuiding()
+{
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ emit newLog(i18n("PHD2 Error: Equipment not connected."));
+ return false;
+ }
+
+ sendJSONRPCRequest("stop_capture");
+ return true;
+}
+
+bool PHD2::pauseGuiding()
+{
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ emit newLog(i18n("PHD2 Error: Equipment not connected."));
+ return false;
+ }
+
+ QJsonArray args;
+
+ // Paused param
+ args << true;
+ // FULL param
+ args << "full";
+
+ sendJSONRPCRequest("set_paused", args);
+
+ return true;
+
+}
+
+bool PHD2::resumeGuiding()
+{
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ emit newLog(i18n("PHD2 Error: Equipment not connected."));
+ return false;
+ }
+
+ QJsonArray args;
+
+ // Paused param
+ args << false;
+
+ sendJSONRPCRequest("set_paused", args);
+
+ return true;
+
+}
+
+bool PHD2::dither(double pixels)
+{
+ if (connection != EQUIPMENT_CONNECTED)
+ {
+ emit newLog(i18n("PHD2 Error: Equipment not connected."));
+ return false;
+ }
+
+ QJsonArray args;
+ QJsonObject settle;
+
+ settle.insert("pixels", 1.5);
+ settle.insert("time", 8);
+ settle.insert("timeout", 45);
+
+ // Pixels
+ args << pixels;
+ // RA Only?
+ args << false;
+ // Settle
+ args << settle;
+
+ state = DITHERING;
+
+ sendJSONRPCRequest("dither", args);
+
+ return true;
+}
+
+bool PHD2::isConnected()
+{
+ return (connection >= CONNECTED);
+}
+
+void PHD2::setCCDMountParams(double ccd_pix_w, double ccd_pix_h, double mount_focal)
+{
+ ccd_pixel_width = ccd_pix_w/1000.0;
+ ccd_pixel_height= ccd_pix_h/1000.0;
+ focal = mount_focal;
+}
+
+}
+
+
diff --git a/kstars/ekos/guide/externalguide/phd2.h b/kstars/ekos/guide/externalguide/phd2.h
new file mode 100644
index 0000000..23fb523
--- /dev/null
+++ b/kstars/ekos/guide/externalguide/phd2.h
@@ -0,0 +1,104 @@
+/* Ekos PHD2 Handler
+ Copyright (C) 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+*/
+
+#ifndef PHD2_H
+#define PHD2_H
+
+#include <QAbstractSocket>
+#include <QJsonArray>
+
+#include "guide.h"
+
+class QTcpSocket;
+
+namespace Ekos
+{
+
+/**
+ * @class PHD2
+ * Uses external PHD2 for guiding.
+ *
+ * @authro Jasem Mutlaq
+ */
+class PHD2 : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ typedef enum { Version, LockPositionSet, CalibrationComplete, StarSelected, StartGuiding, Paused, StartCalibration, AppState, CalibrationFailed, CalibrationDataFlipped, LoopingExposures,
+ LoopingExposuresStopped, Settling, SettleDone, StarLost, GuidingStopped, Resumed, GuideStep, GuidingDithered, LockPositionLost, Alert } PHD2Event;
+ typedef enum { STOPPED, SELECTED, LOSTLOCK, PAUSED, LOOPING, CALIBRATING, CALIBRATION_FAILED, CALIBRATION_SUCCESSFUL, GUIDING, DITHERING, DITHER_FAILED, DITHER_SUCCESSFUL } PHD2State;
+ typedef enum { DISCONNECTED, CONNECTING, CONNECTED, EQUIPMENT_DISCONNECTING, EQUIPMENT_DISCONNECTED, EQUIPMENT_CONNECTING, EQUIPMENT_CONNECTED } PHD2Connection;
+ typedef enum { PHD2_UNKNOWN, PHD2_RESULT, PHD2_EVENT, PHD2_ERROR } PHD2MessageType;
+
+ PHD2();
+ ~PHD2();
+
+ void connectPHD2();
+ void disconnectPHD2();
+
+ bool isConnected();
+ bool isCalibrating() { return state == CALIBRATING; }
+ bool isCalibrationComplete() { return state > CALIBRATING; }
+ bool isCalibrationSuccessful() { return state >= CALIBRATION_SUCCESSFUL; }
+ bool isGuiding() { return state == GUIDING; }
+ bool isDithering() { return state == DITHERING; }
+
+ void setCCDMountParams(double ccd_pix_w, double ccd_pix_h, double mount_focal);
+
+ void setEquipmentConnected(bool enable);
+
+ bool startGuiding();
+ bool stopGuiding();
+ bool pauseGuiding();
+ bool resumeGuiding();
+ bool dither(double pixels);
+
+private slots:
+
+ void readPHD2();
+ void displayError(QAbstractSocket::SocketError socketError);
+
+signals:
+
+ void newLog(const QString &);
+ void connected();
+ void disconnected();
+ //void ditherComplete();
+ //void ditherFailed();
+ void newAxisDelta(double delta_ra, double delta_dec);
+ //void autoGuidingToggled(bool);
+ //void guideReady();
+ void newStatus(Ekos::GuideState);
+
+private:
+
+ void sendJSONRPCRequest(const QString & method, const QJsonArray args = QJsonArray());
+ void processJSON(const QJsonObject &jsonObj);
+
+ void processPHD2Event(const QJsonObject &jsonEvent);
+ void processPHD2State(const QString &phd2State);
+ void processPHD2Error(const QJsonObject &jsonError);
+
+ QTcpSocket *tcpSocket;
+ qint64 methodID;
+
+ QHash<QString, PHD2Event> events;
+
+ PHD2State state;
+ PHD2Connection connection;
+ PHD2Event event;
+
+ double ccd_pixel_width, ccd_pixel_height, focal;
+};
+
+}
+
+#endif // PHD2_H
diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp
new file mode 100644
index 0000000..f5e99ec
--- /dev/null
+++ b/kstars/ekos/guide/guide.cpp
@@ -0,0 +1,1279 @@
+/* Ekos
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#include "guide.h"
+
+#include <QDateTime>
+
+#include <KMessageBox>
+#include <KLed>
+#include <KLocalizedString>
+
+#include <basedevice.h>
+
+#include "Options.h"
+
+#include "guide/gmath.h"
+#include "guide/guider.h"
+#include "phd2.h"
+
+#include "darklibrary.h"
+#include "indi/driverinfo.h"
+#include "indi/clientmanager.h"
+
+#include "fitsviewer/fitsviewer.h"
+#include "fitsviewer/fitsview.h"
+
+#include "guide/rcalibration.h"
+#include "guideadaptor.h"
+#include "kspaths.h"
+
+#define MAX_GUIDE_STARS 10
+
+namespace Ekos
+{
+
+Guide::Guide() : QWidget()
+{
+ setupUi(this);
+
+ new GuideAdaptor(this);
+ QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this);
+
+ currentCCD = NULL;
+ currentTelescope = NULL;
+ ccd_hor_pixel = ccd_ver_pixel = focal_length = aperture = -1;
+ useGuideHead = false;
+ rapidGuideReticleSet = false;
+ isSuspended = false;
+ AODriver= NULL;
+ GuideDriver=NULL;
+ calibration=NULL;
+ guider=NULL;
+
+ state = GUIDE_IDLE;
+
+ guideDeviationRA = guideDeviationDEC = 0;
+
+ exposureIN->setValue(Options::guideExposure());
+ connect(exposureIN, SIGNAL(editingFinished()), this, SLOT(saveDefaultGuideExposure()));
+
+ boxSizeCombo->setCurrentIndex(Options::guideSquareSizeIndex());
+ connect(boxSizeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTrackingBoxSize(int)));
+
+ // TODO must develop a parent abstract GuideProcess class that is then inherited by both the internal guider and PHD2 and any additional future guiders
+ // It should provide the interface to hook its actions and results here instead of this mess.
+ pmath = new cgmath();
+
+ connect(pmath, SIGNAL(newAxisDelta(double,double)), this, SIGNAL(newAxisDelta(double,double)));
+ connect(pmath, SIGNAL(newAxisDelta(double,double)), this, SLOT(updateGuideDriver(double,double)));
+ connect(pmath, SIGNAL(newStarPosition(QVector3D,bool)), this, SLOT(setStarPosition(QVector3D,bool)));
+
+ calibration = new internalCalibration(pmath, this);
+
+ connect(calibration, SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(setStatus(Ekos::GuideState)));
+
+ guider = new internalGuider(pmath, this);
+
+ connect(guider, SIGNAL(ditherToggled(bool)), this, SIGNAL(ditherToggled(bool)));
+ //connect(guider, SIGNAL(autoGuidingToggled(bool)), this, SIGNAL(autoGuidingToggled(bool)));
+ //connect(guider, SIGNAL(ditherComplete()), this, SIGNAL(ditherComplete()));
+ connect(guider, SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(setStatus(Ekos::GuideState)));
+ connect(guider, SIGNAL(newProfilePixmap(QPixmap &)), this, SIGNAL(newProfilePixmap(QPixmap &)));
+ connect(guider, SIGNAL(newStarPosition(QVector3D,bool)), this, SLOT(setStarPosition(QVector3D,bool)));
+
+ tabWidget->addTab(calibration, calibration->windowTitle());
+ tabWidget->addTab(guider, guider->windowTitle());
+ tabWidget->setTabEnabled(1, false);
+
+ connect(ST4Combo, SIGNAL(currentIndexChanged(int)), this, SLOT(newST4(int)));
+ connect(ST4Combo, SIGNAL(activated(QString)), this, SLOT(setDefaultST4(QString)));
+
+ connect(guiderCombo, SIGNAL(activated(QString)), this, SLOT(setDefaultCCD(QString)));
+ connect(guiderCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(checkCCD(int)));
+
+ connect(binningCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateCCDBin(int)));
+
+ foreach(QString filter, FITSViewer::filterTypes)
+ filterCombo->addItem(filter);
+
+ darkFrameCheck->setChecked(Options::useGuideDarkFrame());
+ connect(darkFrameCheck, SIGNAL(toggled(bool)), this, SLOT(setDarkFrameEnabled(bool)));
+
+ phd2 = new PHD2();
+
+ connect(phd2, SIGNAL(newLog(QString)), this, SLOT(appendLogText(QString)));
+ connect(phd2, SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(setStatus(Ekos::GuideState)));
+ connect(phd2, SIGNAL(newStatus(Ekos::GuideState)), guider, SLOT(toggleExternalGuideStateGUI(Ekos::GuideState)));
+
+ connect(phd2, SIGNAL(newAxisDelta(double,double)), this, SIGNAL(newAxisDelta(double,double)));
+ //connect(phd2, SIGNAL(guideReady()), this, SIGNAL(guideReady()));
+ //connect(phd2, SIGNAL(autoGuidingToggled(bool)), this, SIGNAL(autoGuidingToggled(bool)));
+ //connect(phd2, SIGNAL(autoGuidingToggled(bool)), guider, SLOT(setGuideState(bool)));
+ //connect(phd2, SIGNAL(ditherComplete()), this, SIGNAL(ditherComplete()));
+
+ if (Options::usePHD2Guider())
+ phd2->connectPHD2();
+}
+
+Guide::~Guide()
+{
+ delete guider;
+ delete calibration;
+ delete pmath;
+ delete phd2;
+}
+
+void Guide::setDefaultCCD(QString ccd)
+{
+ Options::setDefaultGuideCCD(ccd);
+}
+
+void Guide::addCCD(ISD::GDInterface *newCCD)
+{
+ ISD::CCD *ccd = static_cast<ISD::CCD*>(newCCD);
+
+ if (CCDs.contains(ccd))
+ return;
+
+ CCDs.append(ccd);
+
+ guiderCombo->addItem(ccd->getDeviceName());
+
+ //checkCCD(CCDs.count()-1);
+ //guiderCombo->setCurrentIndex(CCDs.count()-1);
+
+ setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2);
+}
+
+void Guide::addGuideHead(ISD::GDInterface *newCCD)
+{
+ ISD::CCD *ccd = static_cast<ISD::CCD *> (newCCD);
+
+ CCDs.append(ccd);
+
+ QString guiderName = ccd->getDeviceName() + QString(" Guider");
+
+ if (guiderCombo->findText(guiderName) == -1)
+ {
+ guiderCombo->addItem(guiderName);
+ //CCDs.append(static_cast<ISD::CCD *> (newCCD));
+ }
+
+ //checkCCD(CCDs.count()-1);
+ //guiderCombo->setCurrentIndex(CCDs.count()-1);
+
+ setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2);
+
+}
+
+void Guide::setTelescope(ISD::GDInterface *newTelescope)
+{
+ currentTelescope = (ISD::Telescope*) newTelescope;
+
+ syncTelescopeInfo();
+
+}
+
+bool Guide::setCCD(QString device)
+{
+ for (int i=0; i < guiderCombo->count(); i++)
+ if (device == guiderCombo->itemText(i))
+ {
+ guiderCombo->setCurrentIndex(i);
+ return true;
+ }
+
+ return false;
+}
+
+void Guide::checkCCD(int ccdNum)
+{
+ if (ccdNum == -1)
+ {
+ ccdNum = guiderCombo->currentIndex();
+
+ if (ccdNum == -1)
+ return;
+ }
+
+ if (ccdNum <= CCDs.count())
+ {
+ currentCCD = CCDs.at(ccdNum);
+
+ //connect(currentCCD, SIGNAL(FITSViewerClosed()), this, SLOT(viewerClosed()), Qt::UniqueConnection);
+ connect(currentCCD, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(processCCDNumber(INumberVectorProperty*)), Qt::UniqueConnection);
+ connect(currentCCD, SIGNAL(newExposureValue(ISD::CCDChip*,double,IPState)), this, SLOT(checkExposureValue(ISD::CCDChip*,double,IPState)), Qt::UniqueConnection);
+
+ if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider"))
+ useGuideHead=true;
+ else
+ useGuideHead=false;
+
+ syncCCDInfo();
+ }
+}
+
+void Guide::setGuiderProcess(int guiderProcess)
+{
+ // Don't do anything unless we have a CCD and it is online
+ if (currentCCD == NULL || currentCCD->isConnected() == false)
+ return;
+
+ if (guiderProcess == GUIDE_PHD2)
+ {
+ // Disable calibration tab
+ tabWidget->setTabEnabled(0, false);
+ // Enable guide tab
+ tabWidget->setTabEnabled(1, true);
+ // Set current tab to guide
+ tabWidget->setCurrentIndex(1);
+
+ guider->setPHD2(phd2);
+
+ // Do not receive BLOBs from the driver
+ currentCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, currentCCD->getDeviceName(), useGuideHead ? "CCD2" : "CCD1");
+ }
+ else
+ {
+ // Enable calibration tab
+ tabWidget->setTabEnabled(0, true);
+ // Disable guide tab?
+ // TODO: Check if calibration is already complete, then no need to disable guiding tab
+ tabWidget->setTabEnabled(1, false);
+ // Set current tab to calibration
+ tabWidget->setCurrentIndex(0);
+
+ guider->setPHD2(NULL);
+
+ // Receive BLOBs from the driver
+ currentCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, currentCCD->getDeviceName(), useGuideHead ? "CCD2" : "CCD1");
+ }
+}
+
+void Guide::syncCCDInfo()
+{
+ INumberVectorProperty * nvp = NULL;
+
+ if (currentCCD == NULL)
+ return;
+
+ if (useGuideHead)
+ nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO");
+ else
+ nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO");
+
+ if (nvp)
+ {
+ INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X");
+ if (np)
+ ccd_hor_pixel = np->value;
+
+ np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
+ if (np)
+ ccd_ver_pixel = np->value;
+
+ np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
+ if (np)
+ ccd_ver_pixel = np->value;
+ }
+
+ updateGuideParams();
+}
+
+void Guide::syncTelescopeInfo()
+{
+ if (currentTelescope == NULL)
+ return;
+
+ INumberVectorProperty * nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO");
+
+ if (nvp)
+ {
+ INumber *np = IUFindNumber(nvp, "GUIDER_APERTURE");
+
+ if (np && np->value != 0)
+ aperture = np->value;
+ else
+ {
+ np = IUFindNumber(nvp, "TELESCOPE_APERTURE");
+ if (np)
+ aperture = np->value;
+ }
+
+ np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH");
+ if (np && np->value != 0)
+ focal_length = np->value;
+ else
+ {
+ np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH");
+ if (np)
+ focal_length = np->value;
+ }
+ }
+
+ updateGuideParams();
+
+}
+
+void Guide::updateGuideParams()
+{
+ if (currentCCD == NULL)
+ return;
+
+ if (currentCCD->hasGuideHead() == false)
+ useGuideHead = false;
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ if (targetChip == NULL)
+ {
+ appendLogText(i18n("Connection to the guide CCD is lost."));
+ return;
+ }
+
+ binningCombo->setEnabled(targetChip->canBin());
+ if (targetChip->canBin())
+ {
+ int binX,binY, maxBinX, maxBinY;
+ targetChip->getBinning(&binX, &binY);
+ targetChip->getMaxBin(&maxBinX, &maxBinY);
+
+ binningCombo->disconnect();
+
+ binningCombo->clear();
+
+ for (int i=1; i <= maxBinX; i++)
+ binningCombo->addItem(QString("%1x%2").arg(i).arg(i));
+
+ binningCombo->setCurrentIndex(binX-1);
+
+ connect(binningCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateCCDBin(int)));
+ }
+
+ if (ccd_hor_pixel != -1 && ccd_ver_pixel != -1 && focal_length != -1 && aperture != -1)
+ {
+ pmath->setGuiderParameters(ccd_hor_pixel, ccd_ver_pixel, aperture, focal_length);
+ phd2->setCCDMountParams(ccd_hor_pixel, ccd_ver_pixel, focal_length);
+
+ int x,y,w,h;
+
+ emit guideChipUpdated(targetChip);
+
+ guider->setTargetChip(targetChip);
+
+ if (targetChip->getFrame(&x,&y,&w,&h))
+ pmath->setVideoParameters(w, h);
+
+ guider->setInterface();
+
+ }
+}
+
+void Guide::addST4(ISD::ST4 *newST4)
+{
+ foreach(ISD::ST4 *guidePort, ST4List)
+ {
+ if (!strcmp(guidePort->getDeviceName(),newST4->getDeviceName()))
+ return;
+ }
+
+ ST4List.append(newST4);
+
+ ST4Combo->addItem(newST4->getDeviceName());
+}
+
+bool Guide::setST4(QString device)
+{
+ for (int i=0; i < ST4List.count(); i++)
+ if (ST4List.at(i)->getDeviceName() == device)
+ {
+ ST4Combo->setCurrentIndex(i);
+ return true;
+ }
+
+ return false;
+}
+
+void Guide::setDefaultST4(QString st4)
+{
+ Options::setDefaultST4Driver(st4);
+}
+
+void Guide::newST4(int index)
+{
+ if (ST4List.empty() || index >= ST4List.count())
+ return;
+
+ ST4Driver = ST4List.at(index);
+
+ GuideDriver = ST4Driver;
+}
+
+void Guide::setAO(ISD::ST4 *newAO)
+{
+ AODriver = newAO;
+ guider->setAO(true);
+}
+
+bool Guide::capture()
+{
+ if (currentCCD == NULL)
+ return false;
+
+ double seqExpose = exposureIN->value();
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ if (currentCCD->isConnected() == false)
+ {
+ appendLogText(i18n("Error: Lost connection to CCD."));
+ return false;
+ }
+
+ //If calibrating, reset frame
+ if (calibration->getCalibrationStage() == internalCalibration::CAL_CAPTURE_IMAGE)
+ {
+ targetChip->resetFrame();
+ guider->setSubFramed(false);
+ }
+
+ targetChip->setCaptureMode(FITS_GUIDE);
+ targetChip->setFrameType(FRAME_LIGHT);
+
+ if (Options::useGuideDarkFrame())
+ targetChip->setCaptureFilter(FITS_NONE);
+ else
+ targetChip->setCaptureFilter((FITSScale) filterCombo->currentIndex());
+
+ if (guider->isGuiding())
+ {
+ if (guider->isRapidGuide() == false)
+ connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
+
+ targetChip->capture(seqExpose);
+ return true;
+ }
+
+ connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
+ targetChip->capture(seqExpose);
+
+ return true;
+
+}
+
+void Guide::newFITS(IBLOB *bp)
+{
+ INDI_UNUSED(bp);
+
+ //FITSViewer *fv = currentCCD->getViewer();
+
+ disconnect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ // Do we need to take a dark frame?
+ if (Options::useGuideDarkFrame())
+ {
+ int x,y,w,h;
+ int binx,biny;
+
+ targetChip->getFrame(&x,&y,&w,&h);
+ targetChip->getBinning(&binx,&biny);
+
+ FITSView *currentImage = targetChip->getImage(FITS_GUIDE);
+ FITSData *darkData = NULL;
+ uint16_t offsetX = x / binx;
+ uint16_t offsetY = y / biny;
+
+ darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value());
+
+ connect(DarkLibrary::Instance(), SIGNAL(darkFrameCompleted(bool)), this, SLOT(setCaptureComplete()));
+ connect(DarkLibrary::Instance(), SIGNAL(newLog(QString)), this, SLOT(appendLogText(QString)));
+
+ if (darkData)
+ DarkLibrary::Instance()->subtract(darkData, currentImage, targetChip->getCaptureFilter(), offsetX, offsetY);
+ else
+ {
+ //if (calibration->useAutoStar() == false)
+ //KMessageBox::information(NULL, i18n("If the guide camera is not equipped with a shutter, cover the telescope or camera in order to take a dark exposure."), i18n("Dark Exposure"), "dark_exposure_dialog_notification");
+
+ DarkLibrary::Instance()->captureAndSubtract(targetChip, currentImage, exposureIN->value(), offsetX, offsetY);
+ }
+ return;
+ }
+
+ setCaptureComplete();
+}
+
+void Guide::setCaptureComplete()
+{
+
+ DarkLibrary::Instance()->disconnect(this);
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
+
+ if (targetImage == NULL)
+ {
+ if (Options::guideLogging())
+ qDebug() << "Guide: guide frame is missing! Capturing again...";
+
+ capture();
+ return;
+ }
+
+ if (Options::guideLogging())
+ qDebug() << "Guide: received guide frame.";
+
+ FITSData *image_data = targetImage->getImageData();
+ Q_ASSERT(image_data);
+
+ pmath->setImageView(targetImage);
+ guider->setImageView(targetImage);
+
+ int subBinX=1, subBinY=1;
+ targetChip->getBinning(&subBinX, &subBinY);
+
+ // It should be false in case we do not need to process the image for motion
+ // which happens when we take an image for auto star selection.
+ if (calibration->setImageView(targetImage) == false)
+ return;
+
+ if (starCenter.x() == 0 && starCenter.y() == 0)
+ {
+ int x,y,w,h;
+ targetChip->getFrame(&x,&y,&w,&h);
+
+ starCenter.setX(w/(2*subBinX));
+ starCenter.setY(h/(2*subBinY));
+ starCenter.setZ(subBinX);
+ }
+
+ syncTrackingBoxPosition();
+
+ if (isSuspended)
+ {
+ if (Options::guideLogging())
+ qDebug() << "Guide: Guider is suspended.";
+
+ return;
+ }
+
+ if (guider->isDithering())
+ {
+ pmath->performProcessing();
+ if (guider->dither() == false)
+ {
+ appendLogText(i18n("Dithering failed. Autoguiding aborted."));
+ emit newStatus(GUIDE_DITHERING_ERROR);
+ guider->abort();
+ //emit ditherFailed();
+ }
+ }
+ else if (guider->isGuiding())
+ {
+ guider->guide();
+
+ if (guider->isGuiding())
+ capture();
+ }
+ else if (calibration->isCalibrating())
+ {
+ GuideDriver = ST4Driver;
+ pmath->performProcessing();
+ calibration->processCalibration();
+
+ if (calibration->isCalibrationComplete())
+ {
+ guider->setReady(true);
+ tabWidget->setTabEnabled(1, true);
+ //emit guideReady();
+ }
+ }
+
+ emit newStarPixmap(targetImage->getTrackingBoxPixmap());
+}
+
+void Guide::appendLogText(const QString &text)
+{
+
+ logText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text));
+
+ if (Options::guideLogging())
+ qDebug() << "Guide: " << text;
+
+ emit newLog();
+}
+
+void Guide::clearLog()
+{
+ logText.clear();
+ emit newLog();
+}
+
+void Guide::setDECSwap(bool enable)
+{
+ if (ST4Driver == NULL || guider == NULL)
+ return;
+
+ guider->setDECSwap(enable);
+ ST4Driver->setDECSwap(enable);
+
+}
+
+bool Guide::sendPulse( GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs )
+{
+ if (GuideDriver == NULL || (ra_dir == NO_DIR && dec_dir == NO_DIR))
+ return false;
+
+ if (calibration->isCalibrating())
+ QTimer::singleShot( (ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100, this, SLOT(capture()));
+
+ return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
+}
+
+bool Guide::sendPulse( GuideDirection dir, int msecs )
+{
+ if (GuideDriver == NULL || dir==NO_DIR)
+ return false;
+
+ if (calibration->isCalibrating())
+ QTimer::singleShot(msecs+100, this, SLOT(capture()));
+
+ return GuideDriver->doPulse(dir, msecs);
+
+}
+
+QStringList Guide::getST4Devices()
+{
+ QStringList devices;
+
+ foreach(ISD::ST4* driver, ST4List)
+ devices << driver->getDeviceName();
+
+ return devices;
+}
+
+double Guide::getReticleAngle()
+{
+ return calibration->getReticleAngle();
+}
+
+/*void Guide::viewerClosed()
+{
+ pmath->set_image(NULL);
+ guider->setImage(NULL);
+ calibration->setImage(NULL);
+}*/
+
+void Guide::processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit)
+{
+ // Check if guide star is lost
+ if (dx == -1 && dy == -1 && fit == -1)
+ {
+ KMessageBox::error(NULL, i18n("Lost track of the guide star. Rapid guide aborted."));
+ guider->abort();
+ return;
+ }
+
+ FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
+
+ if (targetImage == NULL)
+ {
+ pmath->setImageView(NULL);
+ guider->setImageView(NULL);
+ calibration->setImageView(NULL);
+ }
+
+ if (rapidGuideReticleSet == false)
+ {
+ // Let's set reticle parameter on first capture to those of the star, then we check if there
+ // is any set
+ double x,y,angle;
+ pmath->getReticleParameters(&x, &y, &angle);
+ pmath->setReticleParameters(dx, dy, angle);
+ rapidGuideReticleSet = true;
+ }
+
+ pmath->setRapidStarData(dx, dy);
+
+ if (guider->isDithering())
+ {
+ pmath->performProcessing();
+ if (guider->dither() == false)
+ {
+ appendLogText(i18n("Dithering failed. Autoguiding aborted."));
+ emit newStatus(GUIDE_DITHERING_ERROR);
+ guider->abort();
+ //emit ditherFailed();
+ }
+ }
+ else
+ {
+ guider->guide();
+ capture();
+ }
+
+}
+
+void Guide::startRapidGuide()
+{
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ if (currentCCD->setRapidGuide(targetChip, true) == false)
+ {
+ appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting..."));
+ guider->abort();
+ return;
+ }
+
+ rapidGuideReticleSet = false;
+
+ pmath->setRapidGuide(true);
+ currentCCD->configureRapidGuide(targetChip, true);
+ connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double)), this, SLOT(processRapidStarData(ISD::CCDChip*,double,double,double)));
+
+}
+
+void Guide::stopRapidGuide()
+{
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ pmath->setRapidGuide(false);
+
+ rapidGuideReticleSet = false;
+
+ currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double)));
+
+ currentCCD->configureRapidGuide(targetChip, false, false, false);
+
+ currentCCD->setRapidGuide(targetChip, false);
+
+}
+
+
+bool Guide::isDithering()
+{
+ return (Options::useEkosGuider() ? guider->isDithering() : phd2->isDithering());
+}
+
+void Guide::dither()
+{
+ if (Options::useEkosGuider())
+ {
+ if (isDithering() == false)
+ guider->dither();
+ }
+ else
+ {
+ if (isDithering() == false)
+ phd2->dither(guider->getDitherPixels());
+ }
+
+}
+
+void Guide::updateGuideDriver(double delta_ra, double delta_dec)
+{
+ guideDeviationRA = delta_ra;
+ guideDeviationDEC = delta_dec;
+
+ // If using PHD2 or not guiding, no need to go further on
+ if (Options::usePHD2Guider() || isGuiding() == false)
+ return;
+
+ if (isDithering())
+ {
+ GuideDriver = ST4Driver;
+ return;
+ }
+
+ // Guide via AO only if guiding deviation is below AO limit
+ if (AODriver != NULL && (fabs(delta_ra) < guider->getAOLimit()) && (fabs(delta_dec) < guider->getAOLimit()))
+ {
+ if (AODriver != GuideDriver)
+ appendLogText(i18n("Using %1 to correct for guiding errors.", AODriver->getDeviceName()));
+ GuideDriver = AODriver;
+ return;
+ }
+
+ if (GuideDriver != ST4Driver)
+ appendLogText(i18n("Using %1 to correct for guiding errors.", ST4Driver->getDeviceName()));
+
+ GuideDriver = ST4Driver;
+}
+
+bool Guide::isCalibrationComplete()
+{
+ if (Options::useEkosGuider())
+ return calibration->isCalibrationComplete();
+ else
+ return phd2->isCalibrationComplete();
+
+}
+
+bool Guide::isCalibrationSuccessful()
+{
+ if (Options::useEkosGuider())
+ return calibration->isCalibrationSuccessful();
+ else
+ return phd2->isCalibrationSuccessful();
+}
+
+bool Guide::startCalibration()
+{
+ if (Options::useEkosGuider())
+ return calibration->startCalibration();
+ else
+ return phd2->startGuiding();
+}
+
+bool Guide::stopCalibration()
+{
+ if (Options::useEkosGuider())
+ return calibration->stopCalibration();
+ else
+ return phd2->stopGuiding();
+}
+
+bool Guide::isCalibrating()
+{
+ if (Options::useEkosGuider())
+ return calibration->isCalibrating();
+ else
+ return phd2->isCalibrating();
+}
+
+bool Guide::isGuiding()
+{
+ if (Options::useEkosGuider())
+ return guider->isGuiding();
+ else
+ return phd2->isGuiding();
+}
+
+bool Guide::startGuiding()
+{
+ // This will handle both internal and external guiders
+ return guider->start();
+
+ /*if (Options::useEkosGuider())
+ return guider->start();
+ else
+ return phd2->startGuiding();*/
+}
+
+bool Guide::stopGuiding()
+{
+ isSuspended=false;
+
+ if (Options::useEkosGuider())
+ return guider->abort(true);
+ else
+ // guider stop will call phd2->stopGuide() and change GUI elements accordingly
+ return guider->stop();
+}
+
+void Guide::setSuspended(bool enable)
+{
+ if (enable == isSuspended || (enable && isGuiding() == false))
+ return;
+
+ isSuspended = enable;
+
+ if (isSuspended)
+ {
+ if (Options::usePHD2Guider())
+ phd2->pauseGuiding();
+ }
+ else
+ {
+ if (Options::useEkosGuider())
+ capture();
+ else
+ phd2->resumeGuiding();
+ //phd2->startGuiding();
+ }
+
+ if (isSuspended)
+ {
+ appendLogText(i18n("Guiding suspended."));
+ }
+ else
+ {
+ appendLogText(i18n("Guiding resumed."));
+ }
+}
+
+void Guide::setExposure(double value)
+{
+ exposureIN->setValue(value);
+}
+
+
+void Guide::setImageFilter(const QString & value)
+{
+ for (int i=0; i < filterCombo->count(); i++)
+ if (filterCombo->itemText(i) == value)
+ {
+ filterCombo->setCurrentIndex(i);
+ break;
+ }
+}
+
+void Guide::setCalibrationTwoAxis(bool enable)
+{
+ calibration->setCalibrationTwoAxis(enable);
+}
+
+void Guide::setCalibrationAutoStar(bool enable)
+{
+ calibration->setCalibrationAutoStar(enable);
+}
+
+void Guide::setCalibrationAutoSquareSize(bool enable)
+{
+ calibration->setCalibrationAutoSquareSize(enable);
+}
+
+void Guide::setCalibrationPulseDuration(int pulseDuration)
+{
+ calibration->setCalibrationPulseDuration(pulseDuration);
+}
+
+void Guide::setGuideBoxSizeIndex(int boxSize)
+{
+ boxSizeCombo->setCurrentIndex(boxSize);
+}
+
+void Guide::setGuideAlgorithm(const QString & algorithm)
+{
+ guider->setGuideOptions(algorithm, guider->useSubFrame(), guider->useRapidGuide());
+}
+
+void Guide::setSubFrameEnabled(bool enable)
+{
+ guider->setGuideOptions(guider->getAlgorithm(), enable , guider->useRapidGuide());
+}
+
+void Guide::setGuideRapid(bool enable)
+{
+ guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable);
+}
+
+void Guide::setDither(bool enable, double value)
+{
+ guider->setDither(enable, value);
+}
+
+QList<double> Guide::getGuidingDeviation()
+{
+ QList<double> deviation;
+
+ deviation << guideDeviationRA << guideDeviationDEC;
+
+ return deviation;
+}
+
+void Guide::startAutoCalibrateGuiding()
+{
+ if (Options::useEkosGuider())
+ connect(calibration, SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(checkAutoCalibrateGuiding(Ekos::GuideState)));
+ else
+ connect(phd2, SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(checkAutoCalibrateGuiding(Ekos::GuideState)));
+
+ startCalibration();
+}
+
+void Guide::checkAutoCalibrateGuiding(Ekos::GuideState state)
+{
+ if (state < GUIDE_CALIBRATION_SUCESS || state > GUIDE_CALIBRATION_ERROR)
+ return;
+
+ if (Options::useEkosGuider())
+ disconnect(calibration, SIGNAL(newStatus(GuideState)), this, SLOT(checkAutoCalibrateGuiding(GuideState)));
+ else
+ disconnect(phd2, SIGNAL(newStatus(GuideState)), this, SLOT(checkAutoCalibrateGuiding(GuideState)));
+
+ if (state == GUIDE_CALIBRATION_SUCESS)
+ {
+ appendLogText(i18n("Auto calibration successful. Starting guiding..."));
+ startGuiding();
+ }
+ else
+ {
+ appendLogText(i18n("Auto calibration failed."));
+ }
+}
+
+void Guide::setStatus(Ekos::GuideState newState)
+{
+ if (newState == state)
+ return;
+
+ state = newState;
+
+ emit newStatus(newState);
+}
+
+QString Guide::getStatusString(Ekos::GuideState state)
+{
+
+ switch (state)
+ {
+ case GUIDE_IDLE:
+ return i18n("Idle");
+ break;
+
+ case GUIDE_CALIBRATING:
+ return i18n("Calibrating");
+ break;
+
+ case GUIDE_CALIBRATION_SUCESS:
+ return i18n("Calibration successful");
+ break;
+
+ case GUIDE_CALIBRATION_ERROR:
+ return i18n("Calibration error");
+ break;
+
+ case GUIDE_GUIDING:
+ return i18n("Guiding");
+ break;
+
+ case GUIDE_ABORTED:
+ return i18n("Aborted");
+ break;
+
+
+ case GUIDE_SUSPENDED:
+ return i18n("Suspended");
+ break;
+
+ case GUIDE_DITHERING:
+ return i18n("Dithering");
+ break;
+
+ case GUIDE_DITHERING_SUCCESS:
+ return i18n("Dithering successful");
+ break;
+
+ case GUIDE_DITHERING_ERROR:
+ return i18n("Dithering error");
+ break;
+
+ }
+
+ return i18n("Unknown");
+}
+
+void Guide::updateCCDBin(int index)
+{
+ if (currentCCD == NULL && Options::usePHD2Guider())
+ return;
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+
+ targetChip->setBinning(index+1, index+1);
+
+}
+
+void Guide::processCCDNumber(INumberVectorProperty *nvp)
+{
+ if (currentCCD == NULL || strcmp(nvp->device, currentCCD->getDeviceName()))
+ return;
+
+ if ( (!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead) )
+ {
+ binningCombo->disconnect();
+ binningCombo->setCurrentIndex(nvp->np[0].value-1);
+ connect(binningCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateCCDBin(int)));
+ }
+}
+
+void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState state)
+{
+ INDI_UNUSED(exposure);
+
+ if (state == IPS_ALERT && (guider->isGuiding() || guider->isDithering() || calibration->isCalibrating()))
+ {
+ appendLogText(i18n("Exposure failed. Restarting exposure..."));
+ targetChip->capture(exposureIN->value());
+ }
+}
+
+void Guide::setDarkFrameEnabled(bool enable)
+{
+ Options::setUseGuideDarkFrame(enable);
+
+ /*if (enable && calibration && calibration->useAutoStar())
+ appendLogText(i18n("Warning: In auto mode, you will not be asked to cover cameras unequipped with shutters in order to capture a dark frame. The dark frame capture will proceed without warning."
+ " You can capture dark frames with auto mode off and they shall be saved in the dark library for use when ever needed."));*/
+}
+
+void Guide::saveDefaultGuideExposure()
+{
+ Options::setGuideExposure(exposureIN->value());
+}
+
+void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow)
+{
+ starCenter.setX(newCenter.x());
+ starCenter.setY(newCenter.y());
+ if (newCenter.z() > 0)
+ starCenter.setZ(newCenter.z());
+
+ if (updateNow)
+ syncTrackingBoxPosition();
+
+}
+
+void Guide::syncTrackingBoxPosition()
+{
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+ Q_ASSERT(targetChip);
+
+ int subBinX=1, subBinY=1;
+ targetChip->getBinning(&subBinX, &subBinY);
+
+ FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
+
+ if (targetImage && starCenter.isNull() == false)
+ {
+ double boxSize = boxSizeCombo->currentText().toInt();
+ int x,y,w,h;
+ targetChip->getFrame(&x,&y,&w,&h);
+ // If box size is larger than image size, set it to lower index
+ if (boxSize/subBinX >= w || boxSize/subBinY >= h)
+ {
+ boxSizeCombo->setCurrentIndex(boxSizeCombo->currentIndex()-1);
+ return;
+ }
+
+ // If binning changed, update coords accordingly
+ if (subBinX != starCenter.z())
+ {
+ if (starCenter.z() > 0)
+ {
+ starCenter.setX(starCenter.x() * (starCenter.z()/subBinX));
+ starCenter.setY(starCenter.y() * (starCenter.z()/subBinY));
+ }
+
+ starCenter.setZ(subBinX);
+ }
+
+ QRect starRect = QRect( starCenter.x()-boxSize/(2*subBinX), starCenter.y()-boxSize/(2*subBinY), boxSize/subBinX, boxSize/subBinY);
+ targetImage->setTrackingBoxEnabled(true);
+ targetImage->setTrackingBox(starRect);
+ }
+}
+
+void Guide::updateTrackingBoxSize(int currentIndex)
+{
+ Options::setGuideSquareSizeIndex(currentIndex);
+
+ syncTrackingBoxPosition();
+}
+
+bool Guide::selectAutoStar()
+{
+ if (currentCCD == NULL)
+ return false;
+
+ ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
+ if (targetChip == NULL)
+ return false;
+
+ FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
+ if (targetImage == NULL)
+ return false;
+
+ FITSData *imageData = targetImage->getImageData();
+ if (imageData == NULL)
+ return false;
+
+ imageData->findStars();
+
+ QList<Edge*> starCenters = imageData->getStarCenters();
+
+ if (starCenters.empty())
+ return false;
+
+ qSort(starCenters.begin(), starCenters.end(), [](const Edge *a, const Edge *b){return a->width > b->width;});
+
+ int maxX = imageData->getWidth();
+ int maxY = imageData->getHeight();
+
+ int scores[MAX_GUIDE_STARS];
+
+ int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count();
+
+ for (int i=0; i < maxIndex; i++)
+ {
+ int score=100;
+
+ Edge *center = starCenters.at(i);
+
+ //qDebug() << "#" << i << " X: " << center->x << " Y: " << center->y << " HFR: " << center->HFR << " Width" << center->width;
+
+ // Severely reject stars close to edges
+ if (center->x < (center->width*5) || center->y < (center->width*5) || center->x > (maxX-center->width*5) || center->y > (maxY-center->width*5))
+ score-=50;
+
+ // Moderately favor brighter stars
+ score += center->width*center->width;
+
+ // Moderately reject stars close to other stars
+ foreach(Edge *edge, starCenters)
+ {
+ if (edge == center)
+ continue;
+
+ if (abs(center->x - edge->x) < center->width*2 && abs(center->y - edge->y) < center->width*2)
+ {
+ score -= 15;
+ break;
+ }
+ }
+
+ scores[i] = score;
+ }
+
+ int maxScore=0;
+ int maxScoreIndex=0;
+ for (int i=0; i < maxIndex; i++)
+ {
+ if (scores[i] > maxScore)
+ {
+ maxScore = scores[i];
+ maxScoreIndex = i;
+ }
+ }
+
+ /*if (ui.autoSquareSizeCheck->isEnabled() && ui.autoSquareSizeCheck->isChecked())
+ {
+ // Select appropriate square size
+ int idealSize = ceil(starCenters[maxScoreIndex]->width * 1.5);
+
+ if (Options::guideLogging())
+ qDebug() << "Guide: Ideal calibration box size for star width: " << starCenters[maxScoreIndex]->width << " is " << idealSize << " pixels";
+
+ // TODO Set square size in GuideModule
+ }*/
+
+ QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0);
+ setStarPosition(newStarCenter, false);
+
+ return true;
+}
+
+}
+
+
diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h
new file mode 100644
index 0000000..709a8e6
--- /dev/null
+++ b/kstars/ekos/guide/guide.h
@@ -0,0 +1,346 @@
+/* Ekos guide tool
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef guide_H
+#define guide_H
+
+#include <QTimer>
+#include <QtDBus/QtDBus>
+
+#include "ekos.h"
+#include "guide/common.h"
+#include "guide.h"
+#include "fitsviewer/fitscommon.h"
+#include "indi/indistd.h"
+#include "indi/inditelescope.h"
+#include "indi/indiccd.h"
+#include "ui_guide.h"
+
+class QTabWidget;
+class cgmath;
+class internalCalibration;
+class internalGuider;
+class FITSData;
+
+namespace Ekos
+{
+
+class PHD2;
+
+/**
+ *@class Guide
+ *@short Performs calibration and autoguiding using an ST4 port or directly via the INDI driver. Can be used with the following external guiding applications:
+ * PHD2
+ *@author Jasem Mutlaq
+ *@version 1.2
+ */
+class Guide : public QWidget, public Ui::Guide
+{
+
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Guide")
+
+public:
+ Guide();
+ ~Guide();
+
+ enum GuiderStage { CALIBRATION_STAGE, GUIDE_STAGE };
+ enum GuiderProcess { GUIDE_INTERNAL, GUIDE_PHD2 };
+
+ /** @defgroup GuideDBusInterface Ekos DBus Interface - Capture Module
+ * Ekos::Guide interface provides advanced scripting capabilities to calibrate and guide a mount via a CCD camera.
+ */
+
+ /*@{*/
+
+ /** DBUS interface function.
+ * select the CCD device from the available CCD drivers.
+ * @param device The CCD device name
+ * @return Returns true if CCD device is found and set, false otherwise.
+ */
+ Q_SCRIPTABLE bool setCCD(QString device);
+
+ /** DBUS interface function.
+ * select the ST4 device from the available ST4 drivers.
+ * @param device The ST4 device name
+ * @return Returns true if ST4 device is found and set, false otherwise.
+ */
+ Q_SCRIPTABLE bool setST4(QString device);
+
+ /** DBUS interface function.
+ * @return Returns List of registered ST4 devices.
+ */
+ Q_SCRIPTABLE QStringList getST4Devices();
+
+ /** DBUS interface function.
+ * @return Returns true if calibraiton is in progress.
+ */
+ Q_SCRIPTABLE bool isCalibrating();
+
+ /** DBUS interface function.
+ * @return Returns true if calibration procedure is complete.
+ */
+ Q_SCRIPTABLE bool isCalibrationComplete();
+
+ /** DBUS interface function.
+ * @return Returns true if calibration procedure is successful.
+ */
+ Q_SCRIPTABLE bool isCalibrationSuccessful();
+
+ /** DBUS interface function.
+ * @return Returns true if autoguiding is in progress.
+ */
+ Q_SCRIPTABLE bool isGuiding();
+
+ /** DBUS interface function.
+ * @return Returns guiding deviation from guide star in arcsecs. First elemenet is RA guiding deviation, second element is DEC guiding deviation.
+ */
+ Q_SCRIPTABLE QList<double> getGuidingDeviation();
+
+ /** DBUS interface function.
+ * Set CCD exposure value
+ * @value exposure value in seconds.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setExposure(double value);
+
+ /** DBUS interface function.
+ * Set image filter to apply to the image after capture.
+ * @param value Image filter (Auto Stretch, High Contrast, Equalize, High Pass)
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setImageFilter(const QString & value);
+
+ /** DBUS interface function.
+ * Set calibration Use Two Axis option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, calibration will be performed in both RA and DEC axis. Otherwise, only RA axis will be calibrated.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setCalibrationTwoAxis(bool enable);
+
+ /** DBUS interface function.
+ * Set auto star calibration option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, Ekos will attempt to automatically select the best guide star and proceed with the calibration procedure.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoStar(bool enable);
+
+ /** DBUS interface function.
+ * In case of automatic star selection, calculate the appropriate square size given the selected star width. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, Ekos will attempt to automatically select the best square size for calibration and guiding phases.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoSquareSize(bool enable);
+
+ /** DBUS interface function.
+ * Set calibration dark frame option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, a dark frame will be captured to subtract from the light frame.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setDarkFrameEnabled(bool enable);
+
+ /** DBUS interface function.
+ * Set calibration parameters.
+ * @param pulseDuration Pulse duration in milliseconds to use in the calibration steps.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setCalibrationPulseDuration(int pulseDuration);
+
+ /** DBUS interface function.
+ * Set guiding box size. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used.
+ * @param boxSizeIndex box size index (0 to 4) for box size from 8 to 128 pixels. The box size should be suitable for the size of the guide star selected. The boxSize is also used to select the subframe size around the guide star. Default is 16 pixels
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setGuideBoxSizeIndex(int boxSizeIndex);
+
+ /** DBUS interface function.
+ * Set guiding algorithm. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used.
+ * @param algorithm Select the algorithm used to calculate the centroid of the guide star (Smart, Fast, Auto, No thresh).
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setGuideAlgorithm(const QString & algorithm);
+
+ /** DBUS interface function.
+ * Set guiding options. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, it will select a subframe around the guide star depending on the boxSize size.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setSubFrameEnabled(bool enable);
+
+ /** DBUS interface function.
+ * Set rapid guiding option. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used.
+ * @param enable if true, it will activate RapidGuide in the CCD driver. When Rapid Guide is used, no frames are sent to Ekos for analysis and the centeroid calculations are done in the CCD driver.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setGuideRapid(bool enable);
+
+ /** DBUS interface function.
+ * Enable or disables dithering
+ * @param enable if true, dithering is enabled and is performed after each exposure is complete. Otheriese, dithering is disabled.
+ * @param value dithering range in pixels. Ekos will move the guide star in a random direction for the specified dithering value in pixels.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setDither(bool enable, double value);
+
+
+ /** DBUS interface function.
+ * Selects which guiding process to utilize for calibration & guiding.
+ * @param guideProcess Either use Ekos internal guider or external PHD2 process.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void setGuiderProcess(int guiderProcess);
+
+ /** @}*/
+
+ void addCCD(ISD::GDInterface *newCCD);
+ void setTelescope(ISD::GDInterface *newTelescope);
+ void addST4(ISD::ST4 *newST4);
+ void setAO(ISD::ST4 *newAO);
+
+ bool isDithering();
+
+ void addGuideHead(ISD::GDInterface *newCCD);
+ void syncTelescopeInfo();
+ void syncCCDInfo();
+
+ void clearLog();
+
+ void setDECSwap(bool enable);
+ bool sendPulse( GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs );
+ bool sendPulse( GuideDirection dir, int msecs );
+
+ QString getLogText() { return logText.join("\n"); }
+
+ QVector3D getStarPosition() { return starCenter; }
+
+ // Tracking Box
+ void setTrackingBoxSize(int index) { boxSizeCombo->setCurrentIndex(index); }
+ int getTrackingBoxSize() { return boxSizeCombo->currentText().toInt(); }
+
+ double getReticleAngle();
+
+ void startRapidGuide();
+ void stopRapidGuide();
+
+ static QString getStatusString(Ekos::GuideState state);
+
+public slots:
+
+ /** DBUS interface function.
+ * Start the autoguiding operation.
+ * @return Returns true if guiding started successfully, false otherwise.
+ */
+ Q_SCRIPTABLE bool startGuiding();
+
+ /** DBUS interface function.
+ * Stop the autoguiding operation.
+ * @return Returns true if guiding stopped successfully, false otherwise.
+ */
+ Q_SCRIPTABLE bool stopGuiding();
+
+ /** DBUS interface function.
+ * Start the calibration operation.
+ * @return Returns true if calibration started successfully, false otherwise.
+ */
+ Q_SCRIPTABLE bool startCalibration();
+
+ /** DBUS interface function.
+ * Stop the calibration operation.
+ * @return Returns true if calibration stopped successfully, false otherwise.
+ */
+ Q_SCRIPTABLE bool stopCalibration();
+
+ /** DBUS interface function.
+ * Capture a guide frame
+ * @return Returns true if capture command is sent successfully to INDI server.
+ */
+ Q_SCRIPTABLE bool capture();
+
+ /** DBUS interface function.
+ * Attempts to automatically select a star from the current guide frame
+ * @return Returns true if a star is selected successfully, false otherwise
+ */
+ Q_SCRIPTABLE bool selectAutoStar();
+
+ void checkCCD(int ccdNum=-1);
+ void checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState state);
+ void newFITS(IBLOB*);
+ void newST4(int index);
+ void processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit);
+ void updateGuideDriver(double delta_ra, double delta_dec);
+
+ // Auto Calibration Guiding (Cablirate first then start guiding immediately)
+ void startAutoCalibrateGuiding();
+ void checkAutoCalibrateGuiding(Ekos::GuideState state);
+
+ void dither();
+ void setSuspended(bool enable);
+
+ void appendLogText(const QString &);
+
+ void setStatus(Ekos::GuideState newState);
+
+ // Star Position
+ void setStarPosition(const QVector3D &newCenter, bool updateNow);
+
+ // Capture
+ void setCaptureComplete();
+
+protected slots:
+ void updateCCDBin(int index);
+
+ /**
+ * @brief processCCDNumber Process number properties arriving from CCD. Currently, binning changes are processed.
+ * @param nvp pointer to number property.
+ */
+ void processCCDNumber(INumberVectorProperty *nvp);
+
+ void saveDefaultGuideExposure();
+
+ void setDefaultCCD(QString ccd);
+ void setDefaultST4(QString st4);
+
+ void updateTrackingBoxSize(int currentIndex);
+
+signals:
+ void newLog();
+ void newStatus(Ekos::GuideState status);
+
+ void newStarPixmap(QPixmap &);
+ void newProfilePixmap(QPixmap &);
+
+ //void guideReady();
+ void newAxisDelta(double delta_ra, double delta_dec);
+ //void autoGuidingToggled(bool);
+ //void ditherComplete();
+ //void ditherFailed();
+ void ditherToggled(bool);
+ void guideChipUpdated(ISD::CCDChip*);
+
+private:
+ void updateGuideParams();
+ void syncTrackingBoxPosition();
+
+ ISD::CCD *currentCCD;
+ ISD::Telescope *currentTelescope;
+ ISD::ST4* ST4Driver;
+ ISD::ST4* AODriver;
+ ISD::ST4* GuideDriver;
+
+ QList<ISD::ST4*> ST4List;
+ QList<ISD::CCD *> CCDs;
+
+ cgmath *pmath;
+ internalCalibration *calibration;
+ internalGuider *guider;
+ PHD2 *phd2;
+
+ bool useGuideHead;
+ bool isSuspended;
+
+ QVector3D starCenter;
+
+ QStringList logText;
+
+ double ccd_hor_pixel, ccd_ver_pixel, focal_length, aperture, guideDeviationRA, guideDeviationDEC;
+ bool rapidGuideReticleSet;
+
+ GuideState state;
+};
+
+}
+
+#endif // guide_H
diff --git a/kstars/ekos/guide/guide.ui b/kstars/ekos/guide/guide.ui
new file mode 100644
index 0000000..a546d1a
--- /dev/null
+++ b/kstars/ekos/guide/guide.ui
@@ -0,0 +1,1000 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Guide</class>
+ <widget class="QWidget" name="Guide">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>683</width>
+ <height>457</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="mainLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" name="leftLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Control</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="captureB">
+ <property name="text">
+ <string>Capture</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="darkFrameCheck">
+ <property name="toolTip">
+ <string>Subtract dark frame. If no dark frame is available, a new dark frame shall be captured and saved for future use.</string>
+ </property>
+ <property name="text">
+ <string>Dark</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="calibrationB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Calibrate</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="calibrationOptionsB">
+ <property name="text">
+ <string>Options</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QPushButton" name="guideB">
+ <property name="text">
+ <string>Guide</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="pushButton_2">
+ <property name="text">
+ <string>Options</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Guider:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="guiderCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="toolTip">
+ <string>Select which device receives the guiding correction commands.</string>
+ </property>
+ <property name="text">
+ <string>Via:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QComboBox" name="ST4Combo"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="toolTip">
+ <string>Exposure time in seconds</string>
+ </property>
+ <property name="text">
+ <string>Exp:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QDoubleSpinBox" name="exposureIN">
+ <property name="maximum">
+ <double>60.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_4">
+ <property name="toolTip">
+ <string>Guide camera binning. It is recommended to set binning to 2x2 or higher.</string>
+ </property>
+ <property name="text">
+ <string>Bin:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QComboBox" name="binningCombo"/>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="toolTip">
+ <string>Guide star tracking box size. Box size must be set in accordance to the selected star size.</string>
+ </property>
+ <property name="text">
+ <string>Box:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="boxSizeCombo">
+ <item>
+ <property name="text">
+ <string>8</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>16</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>32</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>64</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>128</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QLabel" name="label_8">
+ <property name="toolTip">
+ <string>Apply filter to image after capture to enhance it</string>
+ </property>
+ <property name="text">
+ <string>Filters:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QComboBox" name="filterCombo">
+ <item>
+ <property name="text">
+ <string>--</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="infoGroup">
+ <property name="title">
+ <string>Guide Info</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="l_3">
+ <property name="toolTip">
+ <string>Mount guiding rate</string>
+ </property>
+ <property name="text">
+ <string>Guiding rate,x15&quot;/sec</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="spinBox_GuideRate">
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>0.100000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>2.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_RecommendedGain">
+ <property name="toolTip">
+ <string>Recommended proportional rate</string>
+ </property>
+ <property name="text">
+ <string notr="true">P=xxx</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="l_5">
+ <property name="text">
+ <string>Focal,mm</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="l_Focal">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" colspan="2">
+ <widget class="QLabel" name="l_6">
+ <property name="text">
+ <string>Aperture,mm</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="l_Aperture">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="l_7">
+ <property name="text">
+ <string>F/D</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="l_FbyD">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="l_8">
+ <property name="text">
+ <string>FOV,'</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="2">
+ <widget class="QLabel" name="l_FOV">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string>YYxYY</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="l_16">
+ <property name="text">
+ <string>Delta ,&quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="l_DeltaRA">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_DeltaDEC">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="l_17">
+ <property name="text">
+ <string>Pulse duration, ms</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="l_PulseRA">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_PulseDEC">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="l_20">
+ <property name="text">
+ <string>Sig(RA)&quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_ErrRA">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="l_21">
+ <property name="text">
+ <string>Sig(DEC)&quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_ErrDEC">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="text">
+ <string notr="true">xxx</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="rightLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="guideFrame">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>200</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QDockWidget" name="dockWidget">
+ <property name="minimumSize">
+ <size>
+ <width>225</width>
+ <height>247</height>
+ </size>
+ </property>
+ <property name="features">
+ <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+ </property>
+ <property name="windowTitle">
+ <string>Drift Graphics</string>
+ </property>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame_Graph">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>200</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>500</width>
+ <height>500</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>300</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLabel" name="l_1">
+ <property name="text">
+ <string>X scale(frm.)</string>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_XScale">
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="l_2">
+ <property name="text">
+ <string>Y scale(&quot;)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_YScale">
+ <property name="minimum">
+ <number>5</number>
+ </property>
+ <property name="maximum">
+ <number>30</number>
+ </property>
+ <property name="singleStep">
+ <number>5</number>
+ </property>
+ <property name="value">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>13</width>
+ <height>17</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="controlGroup">
+ <property name="title">
+ <string>Control Parameters</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="swapCheck">
+ <property name="toolTip">
+ <string>Swap DEC direction pulses. This value is determined automatically from the calibration procedure, only override if necessary.</string>
+ </property>
+ <property name="text">
+ <string>Swap</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QCheckBox" name="checkBox_DirDEC">
+ <property name="text">
+ <string>DEC</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="l_9">
+ <property name="text">
+ <string>Enable directions</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="checkBox_DirRA">
+ <property name="text">
+ <string>RA</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="l_11">
+ <property name="text">
+ <string>Proportional gain</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="spinBox_PropGainRA">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QDoubleSpinBox" name="spinBox_PropGainDEC">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="l_12">
+ <property name="text">
+ <string>Integral gain</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="spinBox_IntGainRA">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QDoubleSpinBox" name="spinBox_IntGainDEC">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="l_13">
+ <property name="text">
+ <string>Derivative gain</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QDoubleSpinBox" name="spinBox_DerGainRA">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QDoubleSpinBox" name="spinBox_DerGainDEC">
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>1000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="l_14">
+ <property name="text">
+ <string>Maximum pulse</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QSpinBox" name="spinBox_MaxPulseRA">
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="spinBox_MaxPulseDEC">
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="l_15">
+ <property name="text">
+ <string>Minimum pulse</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QSpinBox" name="spinBox_MinPulseRA">
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QSpinBox" name="spinBox_MinPulseDEC">
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="l_23">
+ <property name="text">
+ <string>AO Limits</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QDoubleSpinBox" name="spinBox_AOLimit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Maximum deviation to correct for using Adaptive Optics unit. If the guiding deviation exceeds this value, Ekos will guide the mount mechanically</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <double>30.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.500000000000000</double>
+ </property>
+ <property name="value">
+ <double>2.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QLabel" name="l_24">
+ <property name="text">
+ <string>arcsecs</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="mainVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ <zorder>layoutWidget</zorder>
+ <zorder>guideFrame</zorder>
+ <zorder>dockWidget</zorder>
+ <zorder>groupBox</zorder>
+ <zorder>mainVerticalSpacer</zorder>
+ <zorder>frame_Graph</zorder>
+ <zorder></zorder>
+ <zorder>frame_Graph</zorder>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/kstars/ekos/guide/common.cpp b/kstars/ekos/guide/internalguide/common.cpp
index cf56f61..cf56f61 100644
--- a/kstars/ekos/guide/common.cpp
+++ b/kstars/ekos/guide/internalguide/common.cpp
diff --git a/kstars/ekos/guide/common.h b/kstars/ekos/guide/internalguide/common.h
index ead1a10..ead1a10 100644
--- a/kstars/ekos/guide/common.h
+++ b/kstars/ekos/guide/internalguide/common.h
diff --git a/kstars/ekos/guide/gmath.cpp b/kstars/ekos/guide/internalguide/gmath.cpp
index bdbfac9..bdbfac9 100644
--- a/kstars/ekos/guide/gmath.cpp
+++ b/kstars/ekos/guide/internalguide/gmath.cpp
diff --git a/kstars/ekos/guide/gmath.h b/kstars/ekos/guide/internalguide/gmath.h
index 59730bf..59730bf 100644
--- a/kstars/ekos/guide/gmath.h
+++ b/kstars/ekos/guide/internalguide/gmath.h
diff --git a/kstars/ekos/guide/guider.cpp b/kstars/ekos/guide/internalguide/guider.cpp
index 9ebd7ef..9ebd7ef 100644
--- a/kstars/ekos/guide/guider.cpp
+++ b/kstars/ekos/guide/internalguide/guider.cpp
diff --git a/kstars/ekos/guide/guider.h b/kstars/ekos/guide/internalguide/guider.h
index 5e1edc2..5e1edc2 100644
--- a/kstars/ekos/guide/guider.h
+++ b/kstars/ekos/guide/internalguide/guider.h
diff --git a/kstars/ekos/guide/guider.ui b/kstars/ekos/guide/internalguide/guider.ui
index 970971b..970971b 100644
--- a/kstars/ekos/guide/guider.ui
+++ b/kstars/ekos/guide/internalguide/guider.ui
diff --git a/kstars/ekos/guide/matr.cpp b/kstars/ekos/guide/internalguide/matr.cpp
index 17da12f..17da12f 100644
--- a/kstars/ekos/guide/matr.cpp
+++ b/kstars/ekos/guide/internalguide/matr.cpp
diff --git a/kstars/ekos/guide/matr.h b/kstars/ekos/guide/internalguide/matr.h
index a46971b..a46971b 100644
--- a/kstars/ekos/guide/matr.h
+++ b/kstars/ekos/guide/internalguide/matr.h
diff --git a/kstars/ekos/guide/rcalibration.cpp b/kstars/ekos/guide/internalguide/rcalibration.cpp
index 7fbe1b9..7fbe1b9 100644
--- a/kstars/ekos/guide/rcalibration.cpp
+++ b/kstars/ekos/guide/internalguide/rcalibration.cpp
diff --git a/kstars/ekos/guide/rcalibration.h b/kstars/ekos/guide/internalguide/rcalibration.h
index 01d2c15..01d2c15 100644
--- a/kstars/ekos/guide/rcalibration.h
+++ b/kstars/ekos/guide/internalguide/rcalibration.h
diff --git a/kstars/ekos/guide/rcalibration.ui b/kstars/ekos/guide/internalguide/rcalibration.ui
index 3a9da84..3a9da84 100644
--- a/kstars/ekos/guide/rcalibration.ui
+++ b/kstars/ekos/guide/internalguide/rcalibration.ui
diff --git a/kstars/ekos/guide/scroll_graph.cpp b/kstars/ekos/guide/internalguide/scroll_graph.cpp
index de8e998..de8e998 100644
--- a/kstars/ekos/guide/scroll_graph.cpp
+++ b/kstars/ekos/guide/internalguide/scroll_graph.cpp
diff --git a/kstars/ekos/guide/scroll_graph.h b/kstars/ekos/guide/internalguide/scroll_graph.h
index 3a73f62..3a73f62 100644
--- a/kstars/ekos/guide/scroll_graph.h
+++ b/kstars/ekos/guide/internalguide/scroll_graph.h
diff --git a/kstars/ekos/guide/vect.cpp b/kstars/ekos/guide/internalguide/vect.cpp
index 4c82da0..4c82da0 100644
--- a/kstars/ekos/guide/vect.cpp
+++ b/kstars/ekos/guide/internalguide/vect.cpp
diff --git a/kstars/ekos/guide/vect.h b/kstars/ekos/guide/internalguide/vect.h
index 8fc5910..8fc5910 100644
--- a/kstars/ekos/guide/vect.h
+++ b/kstars/ekos/guide/internalguide/vect.h