1 /*
2     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "guide.h"
8 
9 #include "guideadaptor.h"
10 #include "kstars.h"
11 #include "ksmessagebox.h"
12 #include "ksnotification.h"
13 #include "kstarsdata.h"
14 #include "opscalibration.h"
15 #include "opsguide.h"
16 #include "opsdither.h"
17 #include "opsgpg.h"
18 #include "Options.h"
19 #include "auxiliary/QProgressIndicator.h"
20 #include "ekos/manager.h"
21 #include "ekos/auxiliary/darklibrary.h"
22 #include "externalguide/linguider.h"
23 #include "externalguide/phd2.h"
24 #include "fitsviewer/fitsdata.h"
25 #include "fitsviewer/fitsview.h"
26 #include "fitsviewer/fitsviewer.h"
27 #include "internalguide/internalguider.h"
28 #include "guideview.h"
29 #include "guidegraph.h"
30 
31 #include <KConfigDialog>
32 
33 #include <basedevice.h>
34 #include <ekos_guide_debug.h>
35 
36 #include "ui_manualdither.h"
37 
38 #include <random>
39 
40 #define CAPTURE_TIMEOUT_THRESHOLD 30000
41 
42 namespace Ekos
43 {
Guide()44 Guide::Guide() : QWidget()
45 {
46     // #0 Prelude
47     internalGuider = new InternalGuider(); // Init Internal Guider always
48 
49     KConfigDialog *dialog = new KConfigDialog(this, "guidesettings", Options::self());
50 
51     opsGuide = new OpsGuide();  // Initialize sub dialog AFTER main dialog
52     KPageWidgetItem *page = dialog->addPage(opsGuide, i18n("Guide"));
53     page->setIcon(QIcon::fromTheme("kstars_guides"));
54     connect(opsGuide, &OpsGuide::settingsUpdated, [this]()
55     {
56         onThresholdChanged(Options::guideAlgorithm());
57         configurePHD2Camera();
58         configSEPMultistarOptions(); // due to changes in 'Guide Setting: Algorithm'
59     });
60 
61     opsCalibration = new OpsCalibration(internalGuider);
62     page = dialog->addPage(opsCalibration, i18n("Calibration"));
63     page->setIcon(QIcon::fromTheme("tool-measure"));
64 
65     opsDither = new OpsDither();
66     page = dialog->addPage(opsDither, i18n("Dither"));
67     page->setIcon(QIcon::fromTheme("transform-move"));
68 
69     opsGPG = new OpsGPG(internalGuider);
70     page = dialog->addPage(opsGPG, i18n("GPG RA Guider"));
71     page->setIcon(QIcon::fromTheme("pathshape"));
72 
73     // #1 Setup UI
74     setupUi(this);
75 
76     // #2 Register DBus
77     qRegisterMetaType<Ekos::GuideState>("Ekos::GuideState");
78     qDBusRegisterMetaType<Ekos::GuideState>();
79     new GuideAdaptor(this);
80     QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this);
81 
82     // #3 Init Plots
83     initPlots();
84 
85     // #4 Init View
86     initView();
87     internalGuider->setGuideView(guideView);
88 
89     // #5 Load all settings
90     loadSettings();
91 
92     // #6 Init Connections
93     initConnections();
94 
95     // Image Filters
96     for (auto &filter : FITSViewer::filterTypes)
97         filterCombo->addItem(filter);
98     filterCombo->setCurrentIndex(guideFilterIndex);
99 
100     // Progress Indicator
101     pi = new QProgressIndicator(this);
102     controlLayout->addWidget(pi, 1, 2, 1, 1);
103 
104     showFITSViewerB->setIcon(
105         QIcon::fromTheme("kstars_fitsviewer"));
106     connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Guide::showFITSViewer);
107     showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
108 
109     guideAutoScaleGraphB->setIcon(
110         QIcon::fromTheme("zoom-fit-best"));
111     connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs);
112     guideAutoScaleGraphB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
113 
114     guideSaveDataB->setIcon(
115         QIcon::fromTheme("document-save"));
116     connect(guideSaveDataB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::exportGuideData);
117     guideSaveDataB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
118 
119     guideDataClearB->setIcon(
120         QIcon::fromTheme("application-exit"));
121     connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs);
122     guideDataClearB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
123 
124     // These icons seem very hard to read for this button. Just went with +.
125     // guideZoomInXB->setIcon(QIcon::fromTheme("zoom-in"));
126     guideZoomInXB->setText("+");
127     connect(guideZoomInXB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::zoomInX);
128     guideZoomInXB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
129 
130     // These icons seem very hard to read for this button. Just went with -.
131     // guideZoomOutXB->setIcon(QIcon::fromTheme("zoom-out"));
132     guideZoomOutXB->setText("-");
133     connect(guideZoomOutXB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::zoomOutX);
134     guideZoomOutXB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
135 
136 
137     // Exposure
138     //Should we set the range for the spin box here?
139     QList<double> exposureValues;
140     exposureValues << 0.02 << 0.05 << 0.1 << 0.2 << 0.5 << 1 << 1.5 << 2 << 2.5 << 3 << 3.5 << 4 << 4.5 << 5 << 6 << 7 << 8 << 9
141                    << 10 << 15 << 30;
142     exposureIN->setRecommendedValues(exposureValues);
143     connect(exposureIN, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure);
144 
145     // Set current guide type
146     setGuiderType(-1);
147 
148     //This allows the current guideSubframe option to be loaded.
149     if(guiderType == GUIDE_PHD2)
150         setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled());
151 
152     // Initialize non guided dithering random generator.
153     resetNonGuidedDither();
154 
155     //Note:  This is to prevent a button from being called the default button
156     //and then executing when the user hits the enter key such as when on a Text Box
157     QList<QPushButton *> qButtons = findChildren<QPushButton *>();
158     for (auto &button : qButtons)
159         button->setAutoDefault(false);
160 
161     connect(KStars::Instance(), &KStars::colorSchemeChanged, driftGraph, &GuideDriftGraph::refreshColorScheme);
162 
163     m_DarkProcessor = new DarkProcessor(this);
164     connect(m_DarkProcessor, &DarkProcessor::newLog, this, &Ekos::Guide::appendLogText);
165     connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted, this, [this](bool completed)
166     {
167         if (completed != darkFrameCheck->isChecked())
168             setDarkFrameEnabled(completed);
169         if (completed)
170         {
171             guideView->rescale(ZOOM_KEEP_LEVEL);
172             guideView->updateFrame();
173         }
174         setCaptureComplete();
175     });
176 }
177 
~Guide()178 Guide::~Guide()
179 {
180     delete guider;
181 }
182 
handleHorizontalPlotSizeChange()183 void Guide::handleHorizontalPlotSizeChange()
184 {
185     targetPlot->handleHorizontalPlotSizeChange();
186     calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0);
187     calibrationPlot->replot();
188 }
189 
handleVerticalPlotSizeChange()190 void Guide::handleVerticalPlotSizeChange()
191 {
192     targetPlot->handleVerticalPlotSizeChange();
193     calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0);
194     calibrationPlot->replot();
195 }
196 
guideAfterMeridianFlip()197 void Guide::guideAfterMeridianFlip()
198 {
199     //This will clear the tracking box selection
200     //The selected guide star is no longer valid due to the flip
201     guideView->setTrackingBoxEnabled(false);
202     starCenter = QVector3D();
203 
204     if (Options::resetGuideCalibration())
205         clearCalibration();
206 
207     // GPG guide algorithm should be reset on any slew.
208     if (Options::gPGEnabled())
209         guider->resetGPG();
210 
211     guide();
212 }
213 
resizeEvent(QResizeEvent * event)214 void Guide::resizeEvent(QResizeEvent *event)
215 {
216     if (event->oldSize().width() != -1)
217     {
218         if (event->oldSize().width() != size().width())
219             handleHorizontalPlotSizeChange();
220         else if (event->oldSize().height() != size().height())
221             handleVerticalPlotSizeChange();
222     }
223     else
224     {
225         QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange);
226     }
227 }
228 
buildTarget()229 void Guide::buildTarget()
230 {
231     double accuracyRadius = accuracyRadiusSpin->value();
232     Options::setGuiderAccuracyThreshold(accuracyRadius);
233     targetPlot->buildTarget(accuracyRadius);
234 }
235 
clearGuideGraphs()236 void Guide::clearGuideGraphs()
237 {
238     driftGraph->clear();
239     targetPlot->clear();
240 }
241 
clearCalibrationGraphs()242 void Guide::clearCalibrationGraphs()
243 {
244     calibrationPlot->graph(GuideGraph::G_RA)->data()->clear(); //RA out
245     calibrationPlot->graph(GuideGraph::G_DEC)->data()->clear(); //RA back
246     calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->data()->clear(); //Backlash
247     calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->data()->clear(); //DEC out
248     calibrationPlot->graph(GuideGraph::G_RA_PULSE)->data()->clear(); //DEC back
249     calibrationPlot->replot();
250 }
251 
slotAutoScaleGraphs()252 void Guide::slotAutoScaleGraphs()
253 {
254     driftGraph->zoomX(defaultXZoomLevel);
255 
256     driftGraph->autoScaleGraphs();
257     targetPlot->autoScaleGraphs(accuracyRadiusSpin->value());
258 
259     calibrationPlot->rescaleAxes();
260     calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0);
261     calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0);
262     calibrationPlot->replot();
263 }
264 
guideHistory()265 void Guide::guideHistory()
266 {
267     int sliderValue = guideSlider->value();
268     latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum());
269     double ra = driftGraph->graph(GuideGraph::G_RA)->dataMainValue(sliderValue); //Get RA from RA data
270     double de = driftGraph->graph(GuideGraph::G_DEC)->dataMainValue(sliderValue); //Get DEC from DEC data
271     driftGraph->guideHistory(sliderValue, graphOnLatestPt);
272 
273     targetPlot->showPoint(ra, de);
274 }
275 
setLatestGuidePoint(bool isChecked)276 void Guide::setLatestGuidePoint(bool isChecked)
277 {
278     graphOnLatestPt = isChecked;
279     driftGraph->setLatestGuidePoint(isChecked);
280     targetPlot->setLatestGuidePoint(isChecked);
281 
282     if(isChecked)
283         guideSlider->setValue(guideSlider->maximum());
284 }
285 
setRecommendedExposureValues(QList<double> values)286 QString Guide::setRecommendedExposureValues(QList<double> values)
287 {
288     exposureIN->setRecommendedValues(values);
289     return exposureIN->getRecommendedValuesString();
290 }
291 
addCamera(ISD::GDInterface * newCCD)292 void Guide::addCamera(ISD::GDInterface *newCCD)
293 {
294     ISD::CCD *ccd = static_cast<ISD::CCD *>(newCCD);
295 
296     if (CCDs.contains(ccd))
297         return;
298     if(guiderType != GUIDE_INTERNAL)
299     {
300         connect(ccd, &ISD::CCD::newBLOBManager, [ccd, this](INDI::Property * prop)
301         {
302             if (prop->isNameMatch("CCD1") ||  prop->isNameMatch("CCD2"))
303             {
304                 ccd->setBLOBEnabled(false); //This will disable PHD2 external guide frames until it is properly connected.
305                 currentCCD = ccd;
306             }
307         });
308         guiderCombo->clear();
309         guiderCombo->setEnabled(false);
310         if (guiderType == GUIDE_PHD2)
311             guiderCombo->addItem("PHD2");
312         else
313             guiderCombo->addItem("LinGuider");
314     }
315     else
316     {
317         guiderCombo->setEnabled(true);
318         guiderCombo->addItem(ccd->getDeviceName());
319     }
320 
321     CCDs.append(ccd);
322     checkCCD();
323     configurePHD2Camera();
324 }
325 
configurePHD2Camera()326 void Guide::configurePHD2Camera()
327 {
328     //Maybe something like this can be done for Linguider?
329     //But for now, Linguider doesn't support INDI Cameras
330     if(guiderType != GUIDE_PHD2)
331         return;
332     //This prevents a crash if phd2guider is null
333     if(!phd2Guider)
334         return;
335     //This way it doesn't check if the equipment isn't connected yet.
336     //It will check again when the equipment is connected.
337     if(!phd2Guider->isConnected())
338         return;
339     //This way it doesn't check if the equipment List has not been received yet.
340     //It will ask for the list.  When the list is received it will check again.
341     if(phd2Guider->getCurrentCamera().isEmpty())
342     {
343         phd2Guider->requestCurrentEquipmentUpdate();
344         return;
345     }
346 
347     //this checks to see if a CCD in the list matches the name of PHD2's camera
348     ISD::CCD *ccdMatch = nullptr;
349     QString currentPHD2CameraName = "None";
350     foreach(ISD::CCD *ccd, CCDs)
351     {
352         if(phd2Guider->getCurrentCamera().contains(ccd->getDeviceName()))
353         {
354             ccdMatch = ccd;
355             currentPHD2CameraName = (phd2Guider->getCurrentCamera());
356             break;
357         }
358     }
359 
360     //If this method gives the same result as last time, no need to update the Camera info again.
361     //That way the user doesn't see a ton of messages printing about the PHD2 external camera.
362     //But lets make sure the blob is set correctly every time.
363     if(lastPHD2CameraName == currentPHD2CameraName)
364     {
365         setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled());
366         return;
367     }
368 
369     //This means that a Guide Camera was connected before but it changed.
370     if(currentCCD)
371         setExternalGuiderBLOBEnabled(false);
372 
373     //Updating the currentCCD
374     currentCCD = ccdMatch;
375 
376     //This updates the last camera name for the next time it is checked.
377     lastPHD2CameraName = currentPHD2CameraName;
378 
379     //This sets a boolean that allows you to tell if the PHD2 camera is in Ekos
380     phd2Guider->setCurrentCameraIsNotInEkos(currentCCD == nullptr);
381 
382     if(phd2Guider->isCurrentCameraNotInEkos())
383     {
384         appendLogText(
385             i18n("PHD2's current camera: %1, is NOT connected to Ekos.  The PHD2 Guide Star Image will be received, but the full external guide frames cannot.",
386                  phd2Guider->getCurrentCamera()));
387         subFrameCheck->setEnabled(false);
388         //We don't want to actually change the user's subFrame Setting for when a camera really is connected, just check the box to tell the user.
389         disconnect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled);
390         subFrameCheck->setChecked(true);
391         return;
392     }
393 
394     appendLogText(
395         i18n("PHD2's current camera: %1, IS connected to Ekos.  You can select whether to use the full external guide frames or just receive the PHD2 Guide Star Image using the SubFrame checkbox.",
396              phd2Guider->getCurrentCamera()));
397     subFrameCheck->setEnabled(true);
398     connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled);
399     subFrameCheck->setChecked(Options::guideSubframeEnabled());
400 }
401 
addGuideHead(ISD::GDInterface * newCCD)402 void Guide::addGuideHead(ISD::GDInterface *newCCD)
403 {
404     if (guiderType != GUIDE_INTERNAL)
405         return;
406 
407     ISD::CCD *ccd = static_cast<ISD::CCD *>(newCCD);
408 
409     CCDs.append(ccd);
410 
411     QString guiderName = ccd->getDeviceName() + QString(" Guider");
412 
413     if (guiderCombo->findText(guiderName) == -1)
414     {
415         guiderCombo->addItem(guiderName);
416         //CCDs.append(static_cast<ISD::CCD *> (newCCD));
417     }
418 
419     //checkCCD(CCDs.count()-1);
420     //guiderCombo->setCurrentIndex(CCDs.count()-1);
421 
422     //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2);
423 }
424 
setTelescope(ISD::GDInterface * newTelescope)425 void Guide::setTelescope(ISD::GDInterface *newTelescope)
426 {
427     currentTelescope = dynamic_cast<ISD::Telescope *>(newTelescope);
428 
429     syncTelescopeInfo();
430 }
431 
setCamera(const QString & device)432 bool Guide::setCamera(const QString &device)
433 {
434     if (guiderType != GUIDE_INTERNAL)
435         return true;
436 
437     for (int i = 0; i < guiderCombo->count(); i++)
438         if (device == guiderCombo->itemText(i))
439         {
440             guiderCombo->setCurrentIndex(i);
441             checkCCD(i);
442             // Set requested binning in INDIDRiver of the camera selected for guiding
443             updateCCDBin(guideBinIndex);
444             return true;
445         }
446     // If we choose new profile with a new guider camera it will not be found because the default
447     // camera in 'kstarscfg' is still the old one and will be updated only if we select the new one
448     // in the 'Guider'pulldown menu. So we cannnot set binning in INDIDriver. As the default binning
449     // of the new camera is mostly 1x1 binning is set to 1x1 to prevent false error report of
450     // binning support in 'processCCDNumber'.
451     guideBinIndex = 0;
452 
453     return false;
454 }
455 
camera()456 QString Guide::camera()
457 {
458     if (currentCCD)
459         return currentCCD->getDeviceName();
460 
461     return QString();
462 }
463 
checkCCD(int ccdNum)464 void Guide::checkCCD(int ccdNum)
465 {
466     // Do NOT perform checks when the camera is capturing as this may result
467     // in signals/slots getting disconnected.
468     if (guiderType != GUIDE_INTERNAL)
469         return;
470 
471     switch (state)
472     {
473         // Not busy, camera change is OK
474         case GUIDE_IDLE:
475         case GUIDE_ABORTED:
476         case GUIDE_CONNECTED:
477         case GUIDE_DISCONNECTED:
478         case GUIDE_CALIBRATION_ERROR:
479             break;
480 
481         // Busy, camera change is not OK
482         case GUIDE_CAPTURE:
483         case GUIDE_LOOPING:
484         case GUIDE_DARK:
485         case GUIDE_SUBFRAME:
486         case GUIDE_STAR_SELECT:
487         case GUIDE_CALIBRATING:
488         case GUIDE_CALIBRATION_SUCESS:
489         case GUIDE_GUIDING:
490         case GUIDE_SUSPENDED:
491         case GUIDE_REACQUIRE:
492         case GUIDE_DITHERING:
493         case GUIDE_MANUAL_DITHERING:
494         case GUIDE_DITHERING_ERROR:
495         case GUIDE_DITHERING_SUCCESS:
496         case GUIDE_DITHERING_SETTLE:
497             return;
498     }
499 
500     if (ccdNum == -1)
501     {
502         ccdNum = guiderCombo->currentIndex();
503 
504         if (ccdNum == -1)
505             return;
506     }
507 
508     if (ccdNum <= CCDs.count())
509     {
510         currentCCD = CCDs.at(ccdNum);
511 
512         if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider"))
513             useGuideHead = true;
514         else
515             useGuideHead = false;
516 
517         ISD::CCDChip *targetChip =
518             currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
519         if (targetChip && targetChip->isCapturing())
520             return;
521 
522         if (guiderType != GUIDE_INTERNAL)
523         {
524             syncCCDInfo();
525             return;
526         }
527 
528         // Make sure to disconnect all CCDs first from slots of Ekos::Guide
529         for (const auto &oneCamera : CCDs)
530             oneCamera->disconnect(this);
531 
532         connect(currentCCD, &ISD::CCD::numberUpdated, this, &Ekos::Guide::processCCDNumber, Qt::UniqueConnection);
533         connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection);
534 
535         targetChip->setImageView(guideView, FITS_GUIDE);
536 
537         syncCCDInfo();
538     }
539 }
540 
syncCCDInfo()541 void Guide::syncCCDInfo()
542 {
543     if (!currentCCD)
544         return;
545 
546     auto nvp = currentCCD->getBaseDevice()->getNumber(useGuideHead ? "GUIDER_INFO" : "CCD_INFO");
547 
548     if (nvp)
549     {
550         auto np = nvp->findWidgetByName("CCD_PIXEL_SIZE_X");
551         if (np)
552             ccdPixelSizeX = np->getValue();
553 
554         np = nvp->findWidgetByName( "CCD_PIXEL_SIZE_Y");
555         if (np)
556             ccdPixelSizeY = np->getValue();
557 
558         np = nvp->findWidgetByName("CCD_PIXEL_SIZE_Y");
559         if (np)
560             ccdPixelSizeY = np->getValue();
561     }
562 
563     updateGuideParams();
564 }
565 
setTelescopeInfo(double primaryFocalLength,double primaryAperture,double guideFocalLength,double guideAperture)566 void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength,
567                              double guideAperture)
568 {
569     if (primaryFocalLength > 0)
570         focal_length = primaryFocalLength;
571     if (primaryAperture > 0)
572         aperture = primaryAperture;
573     // If we have guide scope info, always prefer that over primary
574     if (guideFocalLength > 0)
575         focal_length = guideFocalLength;
576     if (guideAperture > 0)
577         aperture = guideAperture;
578 
579     updateGuideParams();
580 }
581 
syncTelescopeInfo()582 void Guide::syncTelescopeInfo()
583 {
584     if (currentTelescope == nullptr || currentTelescope->isConnected() == false)
585         return;
586 
587     auto nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO");
588 
589     if (nvp)
590     {
591         auto np = nvp->findWidgetByName("TELESCOPE_APERTURE");
592 
593         if (np && np->getValue() > 0)
594             primaryAperture = np->getValue();
595 
596         np = nvp->findWidgetByName("GUIDER_APERTURE");
597         if (np && np->getValue() > 0)
598             guideAperture = np->getValue();
599 
600         aperture = primaryAperture;
601 
602         //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE)
603         if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE)
604             aperture = guideAperture;
605 
606         np = nvp->findWidgetByName("TELESCOPE_FOCAL_LENGTH");
607         if (np && np->getValue() > 0)
608             primaryFL = np->getValue();
609 
610         np = nvp->findWidgetByName("GUIDER_FOCAL_LENGTH");
611         if (np && np->getValue() > 0)
612             guideFL = np->getValue();
613 
614         focal_length = primaryFL;
615 
616         //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE)
617         if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE)
618             focal_length = guideFL;
619     }
620 
621     updateGuideParams();
622 }
623 
updateGuideParams()624 void Guide::updateGuideParams()
625 {
626     if (currentCCD == nullptr)
627         return;
628 
629     if (currentCCD->hasGuideHead() == false)
630         useGuideHead = false;
631 
632     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
633 
634     if (targetChip == nullptr)
635     {
636         appendLogText(i18n("Connection to the guide CCD is lost."));
637         return;
638     }
639 
640     if (targetChip->getFrameType() != FRAME_LIGHT)
641         return;
642 
643     if(guiderType == GUIDE_INTERNAL)
644         binningCombo->setEnabled(targetChip->canBin());
645 
646     int subBinX = 1, subBinY = 1;
647     if (targetChip->canBin())
648     {
649         int maxBinX, maxBinY;
650         targetChip->getBinning(&subBinX, &subBinY);
651         targetChip->getMaxBin(&maxBinX, &maxBinY);
652 
653         binningCombo->blockSignals(true);
654 
655         binningCombo->clear();
656         for (int i = 1; i <= maxBinX; i++)
657             binningCombo->addItem(QString("%1x%2").arg(i).arg(i));
658         binningCombo->setCurrentIndex(subBinX - 1);
659 
660         binningCombo->blockSignals(false);
661     }
662 
663     // If frame setting does not exist, create a new one.
664     if (frameSettings.contains(targetChip) == false)
665     {
666         int x, y, w, h;
667         if (targetChip->getFrame(&x, &y, &w, &h))
668         {
669             if (w > 0 && h > 0)
670             {
671                 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
672                 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
673 
674                 QVariantMap settings;
675 
676                 settings["x"]    = Options::guideSubframeEnabled() ? x : minX;
677                 settings["y"]    = Options::guideSubframeEnabled() ? y : minY;
678                 settings["w"]    = Options::guideSubframeEnabled() ? w : maxW;
679                 settings["h"]    = Options::guideSubframeEnabled() ? h : maxH;
680                 settings["binx"] = subBinX;
681                 settings["biny"] = subBinY;
682 
683                 frameSettings[targetChip] = settings;
684             }
685         }
686     }
687     // Otherwise update existing map
688     else
689     {
690         QVariantMap settings = frameSettings[targetChip];
691         settings["binx"] = subBinX;
692         settings["biny"] = subBinY;
693         frameSettings[targetChip] = settings;
694     }
695 
696     if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1)
697     {
698         FOVScopeCombo->setItemData(
699             ISD::CCD::TELESCOPE_PRIMARY,
700             i18nc("F-Number, Focal length, Aperture",
701                   "<nobr>F<b>%1</b> Focal length: <b>%2</b> mm Aperture: <b>%3</b> mm<sup>2</sup></nobr>",
702                   QString::number(primaryFL / primaryAperture, 'f', 1), QString::number(primaryFL, 'f', 2),
703                   QString::number(primaryAperture, 'f', 2)),
704             Qt::ToolTipRole);
705         FOVScopeCombo->setItemData(
706             ISD::CCD::TELESCOPE_GUIDE,
707             i18nc("F-Number, Focal length, Aperture",
708                   "<nobr>F<b>%1</b> Focal length: <b>%2</b> mm Aperture: <b>%3</b> mm<sup>2</sup></nobr>",
709                   QString::number(guideFL / guideAperture, 'f', 1), QString::number(guideFL, 'f', 2),
710                   QString::number(guideAperture, 'f', 2)),
711             Qt::ToolTipRole);
712 
713         guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length);
714         emit guideChipUpdated(targetChip);
715 
716         int x, y, w, h;
717         if (targetChip->getFrame(&x, &y, &w, &h))
718         {
719             guider->setFrameParams(x, y, w, h, subBinX, subBinY);
720         }
721 
722         l_Focal->setText(QString::number(focal_length, 'f', 1));
723         l_Aperture->setText(QString::number(aperture, 'f', 1));
724         if (aperture == 0)
725         {
726             l_FbyD->setText("0");
727             // Pixel scale in arcsec/pixel
728             pixScaleX = 0;
729             pixScaleY = 0;
730         }
731         else
732         {
733             l_FbyD->setText(QString::number(focal_length / aperture, 'f', 1));
734             // Pixel scale in arcsec/pixel
735             pixScaleX = 206264.8062470963552 * ccdPixelSizeX / 1000.0 / focal_length;
736             pixScaleY = 206264.8062470963552 * ccdPixelSizeY / 1000.0 / focal_length;
737         }
738 
739         // FOV in arcmin
740         double fov_w = (w * pixScaleX) / 60.0;
741         double fov_h = (h * pixScaleY) / 60.0;
742 
743         l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1)));
744     }
745 }
746 
addST4(ISD::ST4 * newST4)747 void Guide::addST4(ISD::ST4 *newST4)
748 {
749     if (guiderType != GUIDE_INTERNAL)
750         return;
751 
752     for (auto &guidePort : ST4List)
753     {
754         if (guidePort->getDeviceName() == newST4->getDeviceName())
755             return;
756     }
757 
758     ST4List.append(newST4);
759 
760     ST4Combo->addItem(newST4->getDeviceName());
761 
762     setST4(0);
763 }
764 
setST4(const QString & device)765 bool Guide::setST4(const QString &device)
766 {
767     if (guiderType != GUIDE_INTERNAL)
768         return true;
769 
770     for (int i = 0; i < ST4List.count(); i++)
771         if (ST4List.at(i)->getDeviceName() == device)
772         {
773             ST4Combo->setCurrentIndex(i);
774             setST4(i);
775             return true;
776         }
777 
778     return false;
779 }
780 
st4()781 QString Guide::st4()
782 {
783     if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1)
784         return QString();
785 
786     return ST4Combo->currentText();
787 }
788 
setST4(int index)789 void Guide::setST4(int index)
790 {
791     if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL)
792         return;
793 
794     ST4Driver = ST4List.at(index);
795 
796     GuideDriver = ST4Driver;
797 }
798 
capture()799 bool Guide::capture()
800 {
801     buildOperationStack(GUIDE_CAPTURE);
802 
803     return executeOperationStack();
804 }
805 
captureOneFrame()806 bool Guide::captureOneFrame()
807 {
808     captureTimeout.stop();
809 
810     if (currentCCD == nullptr)
811         return false;
812 
813     if (currentCCD->isConnected() == false)
814     {
815         appendLogText(i18n("Error: lost connection to CCD."));
816         return false;
817     }
818 
819     // If CCD Telescope Type does not match desired scope type, change it
820     if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex())
821         currentCCD->setTelescopeType(static_cast<ISD::CCD::TelescopeType>(FOVScopeCombo->currentIndex()));
822 
823     double seqExpose = exposureIN->value();
824 
825     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
826 
827     prepareCapture(targetChip);
828 
829     guideView->setBaseSize(guideWidget->size());
830     setBusy(true);
831 
832     // Check if we have a valid frame setting
833     if (frameSettings.contains(targetChip))
834     {
835         QVariantMap settings = frameSettings[targetChip];
836         targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(),
837                              settings["h"].toInt());
838         targetChip->setBinning(settings["binx"].toInt(), settings["biny"].toInt());
839     }
840 
841     connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection);
842     qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame...";
843 
844     double finalExposure = seqExpose;
845 
846     // Increase exposure for calibration frame if we need auto-select a star
847     // To increase chances we detect one.
848     if (operationStack.contains(GUIDE_STAR_SELECT) && Options::guideAutoStarEnabled() &&
849             !((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled()))
850         finalExposure *= 3;
851 
852     // Timeout is exposure duration + timeout threshold in seconds
853     captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD);
854 
855     targetChip->capture(finalExposure);
856 
857     return true;
858 }
859 
prepareCapture(ISD::CCDChip * targetChip)860 void Guide::prepareCapture(ISD::CCDChip *targetChip)
861 {
862     targetChip->setBatchMode(false);
863     targetChip->setCaptureMode(FITS_GUIDE);
864     targetChip->setFrameType(FRAME_LIGHT);
865     if (darkFrameCheck->isChecked())
866         targetChip->setCaptureFilter(FITS_NONE);
867     else
868         targetChip->setCaptureFilter(static_cast<FITSScale>(filterCombo->currentIndex()));
869     currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS);
870 }
871 
abort()872 bool Guide::abort()
873 {
874     if (currentCCD && guiderType == GUIDE_INTERNAL)
875     {
876         captureTimeout.stop();
877         pulseTimer.stop();
878         ISD::CCDChip *targetChip =
879             currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
880         if (targetChip->isCapturing())
881             targetChip->abortExposure();
882     }
883 
884     manualDitherB->setEnabled(false);
885 
886     setBusy(false);
887 
888     switch (state)
889     {
890         case GUIDE_IDLE:
891         case GUIDE_CONNECTED:
892         case GUIDE_DISCONNECTED:
893             break;
894 
895         case GUIDE_CALIBRATING:
896         case GUIDE_DITHERING:
897         case GUIDE_STAR_SELECT:
898         case GUIDE_CAPTURE:
899         case GUIDE_GUIDING:
900         case GUIDE_LOOPING:
901             guider->abort();
902             break;
903 
904         default:
905             break;
906     }
907 
908     return true;
909 }
910 
setBusy(bool enable)911 void Guide::setBusy(bool enable)
912 {
913     if (enable && pi->isAnimated())
914         return;
915     else if (enable == false && pi->isAnimated() == false)
916         return;
917 
918     if (enable)
919     {
920         clearCalibrationB->setEnabled(false);
921         guideB->setEnabled(false);
922         captureB->setEnabled(false);
923         loopB->setEnabled(false);
924 
925         darkFrameCheck->setEnabled(false);
926         subFrameCheck->setEnabled(false);
927         autoStarCheck->setEnabled(false);
928 
929         stopB->setEnabled(true);
930 
931         pi->startAnimation();
932 
933         //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int)));
934     }
935     else
936     {
937         if(guiderType != GUIDE_LINGUIDER)
938         {
939             captureB->setEnabled(true);
940             loopB->setEnabled(true);
941             autoStarCheck->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions()
942             if(currentCCD)
943                 subFrameCheck->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions()
944         }
945         if (guiderType == GUIDE_INTERNAL)
946             darkFrameCheck->setEnabled(true);
947 
948         if (calibrationComplete ||
949                 ((guiderType == GUIDE_INTERNAL) &&
950                  Options::reuseGuideCalibration() &&
951                  !Options::serializedCalibration().isEmpty()))
952             clearCalibrationB->setEnabled(true);
953         guideB->setEnabled(true);
954         stopB->setEnabled(false);
955         pi->stopAnimation();
956 
957         connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection);
958     }
959 }
960 
processCaptureTimeout()961 void Guide::processCaptureTimeout()
962 {
963     auto restartExposure = [&]()
964     {
965         appendLogText(i18n("Exposure timeout. Restarting exposure..."));
966         currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS);
967         ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
968         targetChip->abortExposure();
969         prepareCapture(targetChip);
970         targetChip->capture(exposureIN->value());
971         captureTimeout.start(exposureIN->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD);
972     };
973 
974     m_CaptureTimeoutCounter++;
975 
976     if (m_DeviceRestartCounter >= 3)
977     {
978         m_CaptureTimeoutCounter = 0;
979         m_DeviceRestartCounter = 0;
980         if (state == GUIDE_GUIDING)
981             appendLogText(i18n("Exposure timeout. Aborting Autoguide."));
982         else if (state == GUIDE_DITHERING)
983             appendLogText(i18n("Exposure timeout. Aborting Dithering."));
984         else if (state == GUIDE_CALIBRATING)
985             appendLogText(i18n("Exposure timeout. Aborting Calibration."));
986 
987         captureTimeout.stop();
988         abort();
989         return;
990     }
991 
992     if (m_CaptureTimeoutCounter > 1)
993     {
994         QString camera = currentCCD->getDeviceName();
995         QString via = ST4Driver ? ST4Driver->getDeviceName() : "";
996         emit driverTimedout(camera);
997         QTimer::singleShot(5000, [ &, camera, via]()
998         {
999             m_DeviceRestartCounter++;
1000             reconnectDriver(camera, via);
1001         });
1002         return;
1003     }
1004 
1005     else
1006         restartExposure();
1007 }
1008 
reconnectDriver(const QString & camera,const QString & via)1009 void Guide::reconnectDriver(const QString &camera, const QString &via)
1010 {
1011     for (auto &oneCamera : CCDs)
1012     {
1013         if (oneCamera->getDeviceName() == camera)
1014         {
1015             // Set camera again to the one we restarted
1016             guiderCombo->setCurrentIndex(guiderCombo->findText(camera));
1017             ST4Combo->setCurrentIndex(ST4Combo->findText(via));
1018             checkCCD();
1019 
1020             if (guiderType == GUIDE_INTERNAL)
1021             {
1022                 // restart capture
1023                 m_CaptureTimeoutCounter = 0;
1024                 captureOneFrame();
1025             }
1026 
1027             return;
1028         }
1029     }
1030 
1031     QTimer::singleShot(5000, this, [ &, camera, via]()
1032     {
1033         reconnectDriver(camera, via);
1034     });
1035 }
1036 
processData(const QSharedPointer<FITSData> & data)1037 void Guide::processData(const QSharedPointer<FITSData> &data)
1038 {
1039     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
1040     if (targetChip->getCaptureMode() != FITS_GUIDE)
1041     {
1042         if (data)
1043         {
1044             QString blobInfo = QString("{Device: %1 Property: %2 Element: %3 Chip: %4}").arg(data->property("device").toString())
1045                                .arg(data->property("blobVector").toString())
1046                                .arg(data->property("blobElement").toString())
1047                                .arg(data->property("chip").toInt());
1048 
1049             qCWarning(KSTARS_EKOS_GUIDE) << blobInfo << "Ignoring Received FITS as it has the wrong capture mode" <<
1050                                          targetChip->getCaptureMode();
1051         }
1052 
1053         return;
1054     }
1055 
1056     if (data)
1057         m_ImageData = data;
1058     else
1059         m_ImageData.reset();
1060 
1061     if (guiderType == GUIDE_INTERNAL)
1062         internalGuider->setImageData(m_ImageData);
1063 
1064     captureTimeout.stop();
1065     m_CaptureTimeoutCounter = 0;
1066 
1067     disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Guide::processData);
1068 
1069     qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame.";
1070 
1071     int subBinX = 1, subBinY = 1;
1072     targetChip->getBinning(&subBinX, &subBinY);
1073 
1074     if (starCenter.x() == 0 && starCenter.y() == 0)
1075     {
1076         int x = 0, y = 0, w = 0, h = 0;
1077 
1078         if (frameSettings.contains(targetChip))
1079         {
1080             QVariantMap settings = frameSettings[targetChip];
1081             x                    = settings["x"].toInt();
1082             y                    = settings["y"].toInt();
1083             w                    = settings["w"].toInt();
1084             h                    = settings["h"].toInt();
1085         }
1086         else
1087             targetChip->getFrame(&x, &y, &w, &h);
1088 
1089         starCenter.setX(w / (2 * subBinX));
1090         starCenter.setY(h / (2 * subBinY));
1091         starCenter.setZ(subBinX);
1092     }
1093 
1094     syncTrackingBoxPosition();
1095 
1096     setCaptureComplete();
1097 }
1098 
setCaptureComplete()1099 void Guide::setCaptureComplete()
1100 {
1101     if (guideView != nullptr)
1102         guideView->clearNeighbors();
1103 
1104     DarkLibrary::Instance()->disconnect(this);
1105 
1106     if (operationStack.isEmpty() == false)
1107     {
1108         executeOperationStack();
1109         return;
1110     }
1111 
1112     switch (state)
1113     {
1114         case GUIDE_IDLE:
1115         case GUIDE_ABORTED:
1116         case GUIDE_CONNECTED:
1117         case GUIDE_DISCONNECTED:
1118         case GUIDE_CALIBRATION_SUCESS:
1119         case GUIDE_CALIBRATION_ERROR:
1120         case GUIDE_DITHERING_ERROR:
1121             setBusy(false);
1122             break;
1123 
1124         case GUIDE_CAPTURE:
1125             state = GUIDE_IDLE;
1126             emit newStatus(state);
1127             setBusy(false);
1128             break;
1129 
1130         case GUIDE_LOOPING:
1131             capture();
1132             break;
1133 
1134         case GUIDE_CALIBRATING:
1135             guider->calibrate();
1136             break;
1137 
1138         case GUIDE_GUIDING:
1139             guider->guide();
1140             break;
1141 
1142         case GUIDE_DITHERING:
1143             guider->dither(Options::ditherPixels());
1144             break;
1145 
1146         // Feature only of internal guider
1147         case GUIDE_MANUAL_DITHERING:
1148             dynamic_cast<InternalGuider*>(guider)->processManualDithering();
1149             break;
1150 
1151         case GUIDE_REACQUIRE:
1152             guider->reacquire();
1153             break;
1154 
1155         case GUIDE_DITHERING_SETTLE:
1156             if (Options::ditherNoGuiding())
1157                 return;
1158             capture();
1159             break;
1160 
1161         case GUIDE_SUSPENDED:
1162             if (Options::gPGEnabled())
1163                 guider->guide();
1164             break;
1165 
1166         default:
1167             break;
1168     }
1169 
1170     emit newImage(guideView);
1171     emit newStarPixmap(guideView->getTrackingBoxPixmap(10));
1172 }
1173 
appendLogText(const QString & text)1174 void Guide::appendLogText(const QString &text)
1175 {
1176     m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
1177                               KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
1178 
1179     qCInfo(KSTARS_EKOS_GUIDE) << text;
1180 
1181     emit newLog(text);
1182 }
1183 
clearLog()1184 void Guide::clearLog()
1185 {
1186     m_LogText.clear();
1187     emit newLog(QString());
1188 }
1189 
setDECSwap(bool enable)1190 void Guide::setDECSwap(bool enable)
1191 {
1192     if (ST4Driver == nullptr || guider == nullptr)
1193         return;
1194 
1195     if (guiderType == GUIDE_INTERNAL)
1196     {
1197         dynamic_cast<InternalGuider *>(guider)->setDECSwap(enable);
1198         ST4Driver->setDECSwap(enable);
1199     }
1200 }
1201 
sendPulse(GuideDirection ra_dir,int ra_msecs,GuideDirection dec_dir,int dec_msecs)1202 bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
1203 {
1204     if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR))
1205         return false;
1206 
1207     // If we're calibrating and we send a pulse, then schedule a subsequent capture.
1208     if (state == GUIDE_CALIBRATING)
1209         pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100);
1210 
1211     return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
1212 }
1213 
sendPulse(GuideDirection dir,int msecs)1214 bool Guide::sendPulse(GuideDirection dir, int msecs)
1215 {
1216     if (GuideDriver == nullptr || dir == NO_DIR)
1217         return false;
1218 
1219     if (state == GUIDE_CALIBRATING)
1220         pulseTimer.start(msecs + 100);
1221 
1222     return GuideDriver->doPulse(dir, msecs);
1223 }
1224 
getST4Devices()1225 QStringList Guide::getST4Devices()
1226 {
1227     QStringList devices;
1228 
1229     foreach (ISD::ST4 *driver, ST4List)
1230         devices << driver->getDeviceName();
1231 
1232     return devices;
1233 }
1234 
1235 
calibrate()1236 bool Guide::calibrate()
1237 {
1238     // Set status to idle and let the operations change it as they get executed
1239     state = GUIDE_IDLE;
1240     emit newStatus(state);
1241 
1242     if (guiderType == GUIDE_INTERNAL)
1243     {
1244         ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
1245 
1246         if (frameSettings.contains(targetChip))
1247         {
1248             targetChip->resetFrame();
1249             int x, y, w, h;
1250             targetChip->getFrame(&x, &y, &w, &h);
1251             QVariantMap settings      = frameSettings[targetChip];
1252             settings["x"]             = x;
1253             settings["y"]             = y;
1254             settings["w"]             = w;
1255             settings["h"]             = h;
1256             frameSettings[targetChip] = settings;
1257 
1258             subFramed = false;
1259         }
1260     }
1261 
1262     saveSettings();
1263 
1264     buildOperationStack(GUIDE_CALIBRATING);
1265 
1266     executeOperationStack();
1267 
1268     qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" <<
1269                                ST4Combo->currentText();
1270 
1271     return true;
1272 }
1273 
guide()1274 bool Guide::guide()
1275 {
1276     auto executeGuide = [this]()
1277     {
1278         if(guiderType != GUIDE_PHD2)
1279         {
1280             if (calibrationComplete == false)
1281             {
1282                 calibrate();
1283                 return;
1284             }
1285         }
1286 
1287         saveSettings();
1288         guider->guide();
1289 
1290         //If PHD2 gets a Guide command and it is looping, it will accept a lock position
1291         //but if it was not looping it will ignore the lock position and do an auto star automatically
1292         //This is not the default behavior in Ekos if auto star is not selected.
1293         //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide.
1294         if(!Options::guideAutoStarEnabled())
1295         {
1296             if(guiderType == GUIDE_PHD2 && guideView->isTrackingBoxEnabled())
1297             {
1298                 double x = starCenter.x();
1299                 double y = starCenter.y();
1300 
1301                 if(!m_ImageData.isNull())
1302                 {
1303                     if(m_ImageData->width() > 50)
1304                     {
1305                         guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState)
1306                         {
1307                             if(newState == GUIDE_GUIDING)
1308                             {
1309                                 phd2Guider->setLockPosition(x, y);
1310                                 disconnect(guideConnect);
1311                             }
1312                         });
1313                     }
1314                 }
1315             }
1316         }
1317     };
1318 
1319     if (Options::defaultCaptureCCD() == guiderCombo->currentText())
1320     {
1321         connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeGuide]()
1322         {
1323             //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
1324             KSMessageBox::Instance()->disconnect(this);
1325             executeGuide();
1326         });
1327 
1328         KSMessageBox::Instance()->questionYesNo(
1329             i18n("The guide camera is identical to the primary imaging camera. Are you sure you want to continue?"));
1330 
1331         return false;
1332     }
1333 
1334     if (m_MountStatus == ISD::Telescope::MOUNT_PARKED)
1335     {
1336         KSMessageBox::Instance()->sorry(i18n("The mount is parked. Unpark to start guiding."));
1337         return false;
1338     }
1339 
1340     executeGuide();
1341     return true;
1342 }
1343 
dither()1344 bool Guide::dither()
1345 {
1346     if (Options::ditherNoGuiding() && state == GUIDE_IDLE)
1347     {
1348         nonGuidedDither();
1349         return true;
1350     }
1351 
1352     if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE)
1353         return true;
1354 
1355     //This adds a dither text item to the graph where dithering occurred.
1356     double time = guideTimer.elapsed() / 1000.0;
1357     QCPItemText *ditherLabel = new QCPItemText(driftGraph);
1358     ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft);
1359     ditherLabel->position->setType(QCPItemPosition::ptPlotCoords);
1360     ditherLabel->position->setCoords(time, 1.5);
1361     ditherLabel->setColor(Qt::white);
1362     ditherLabel->setBrush(Qt::NoBrush);
1363     ditherLabel->setPen(Qt::NoPen);
1364     ditherLabel->setText("Dither");
1365     ditherLabel->setFont(QFont(font().family(), 10));
1366 
1367     if (guiderType == GUIDE_INTERNAL)
1368     {
1369         if (state != GUIDE_GUIDING)
1370             capture();
1371 
1372         setStatus(GUIDE_DITHERING);
1373 
1374         return true;
1375     }
1376     else
1377         return guider->dither(Options::ditherPixels());
1378 }
1379 
suspend()1380 bool Guide::suspend()
1381 {
1382     if (state == GUIDE_SUSPENDED)
1383         return true;
1384     else if (state >= GUIDE_CAPTURE)
1385         return guider->suspend();
1386     else
1387         return false;
1388 }
1389 
resume()1390 bool Guide::resume()
1391 {
1392     if (state == GUIDE_GUIDING)
1393         return true;
1394     else if (state == GUIDE_SUSPENDED)
1395         return guider->resume();
1396     else
1397         return false;
1398 }
1399 
setCaptureStatus(CaptureState newState)1400 void Guide::setCaptureStatus(CaptureState newState)
1401 {
1402     switch (newState)
1403     {
1404         case CAPTURE_DITHERING:
1405             dither();
1406             break;
1407         case CAPTURE_IDLE:
1408         case CAPTURE_ABORTED:
1409             // We need to reset the non guided dithering status every time a new capture task is started (and not for every single capture).
1410             // The non dithering logic is a bit convoluted and controlled by the Capture module,
1411             // which calls Guide::setCaptureStatus(CAPTURE_DITHERING) when it wants guide to dither.
1412             // It actually calls newStatus(CAPTURE_DITHERING) in Capture::checkDithering(), but manager.cpp in Manager::connectModules() connects that to Guide::setCaptureStatus()).
1413             // So the only way to reset the non guided dithering prior to a new capture task is to put it here, when the Capture status moves to IDLE or ABORTED state.
1414             resetNonGuidedDither();
1415             break;
1416         default:
1417             break;
1418     }
1419 }
1420 
setPierSide(ISD::Telescope::PierSide newSide)1421 void Guide::setPierSide(ISD::Telescope::PierSide newSide)
1422 {
1423     guider->setPierSide(newSide);
1424 
1425     // If pier side changes in internal guider
1426     // and calibration was already done
1427     // then let's swap
1428     if (guiderType == GUIDE_INTERNAL &&
1429             state != GUIDE_GUIDING &&
1430             state != GUIDE_CALIBRATING &&
1431             calibrationComplete)
1432     {
1433         // Couldn't restore an old calibration if we call clearCalibration().
1434         if (Options::reuseGuideCalibration())
1435             calibrationComplete = false;
1436         else
1437         {
1438             clearCalibration();
1439             appendLogText(i18n("Pier side change detected. Clearing calibration."));
1440         }
1441     }
1442 }
1443 
setMountStatus(ISD::Telescope::Status newState)1444 void Guide::setMountStatus(ISD::Telescope::Status newState)
1445 {
1446     m_MountStatus = newState;
1447 
1448     if (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING)
1449     {
1450         // reset the calibration if "Always reset calibration" is selected and the mount moves
1451         if (Options::resetGuideCalibration())
1452         {
1453             appendLogText(i18n("Mount is moving. Resetting calibration..."));
1454             clearCalibration();
1455         }
1456         else if (Options::reuseGuideCalibration() && (guiderType == GUIDE_INTERNAL))
1457         {
1458             // It will restore it with the reused one, and this way it reselects a guide star.
1459             calibrationComplete = false;
1460         }
1461         // GPG guide algorithm should be reset on any slew.
1462         if (Options::gPGEnabled())
1463             guider->resetGPG();
1464 
1465         // If we're guiding, and the mount either slews or parks, then we abort.
1466         if (state == GUIDE_GUIDING || state == GUIDE_DITHERING)
1467         {
1468             if (newState == ISD::Telescope::MOUNT_PARKING)
1469                 appendLogText(i18n("Mount is parking. Aborting guide..."));
1470             else
1471                 appendLogText(i18n("Mount is slewing. Aborting guide..."));
1472 
1473             abort();
1474         }
1475     }
1476 
1477     if (guiderType != GUIDE_INTERNAL)
1478         return;
1479 
1480     switch (newState)
1481     {
1482         case ISD::Telescope::MOUNT_SLEWING:
1483         case ISD::Telescope::MOUNT_PARKING:
1484         case ISD::Telescope::MOUNT_MOVING:
1485             captureB->setEnabled(false);
1486             loopB->setEnabled(false);
1487             clearCalibrationB->setEnabled(false);
1488             break;
1489 
1490         default:
1491             if (pi->isAnimated() == false)
1492             {
1493                 captureB->setEnabled(true);
1494                 loopB->setEnabled(true);
1495                 clearCalibrationB->setEnabled(true);
1496             }
1497     }
1498 }
1499 
setMountCoords(const SkyPoint & position,ISD::Telescope::PierSide pierSide,const dms & ha)1500 void Guide::setMountCoords(const SkyPoint &position, ISD::Telescope::PierSide pierSide, const dms &ha)
1501 {
1502     Q_UNUSED(ha);
1503     guider->setMountCoords(position, pierSide);
1504 }
1505 
setExposure(double value)1506 void Guide::setExposure(double value)
1507 {
1508     exposureIN->setValue(value);
1509 }
1510 
setImageFilter(const QString & value)1511 void Guide::setImageFilter(const QString &value)
1512 {
1513     for (int i = 0; i < filterCombo->count(); i++)
1514         if (filterCombo->itemText(i) == value)
1515         {
1516             filterCombo->setCurrentIndex(i);
1517             break;
1518         }
1519 }
1520 
setCalibrationTwoAxis(bool enable)1521 void Guide::setCalibrationTwoAxis(bool enable)
1522 {
1523     Options::setTwoAxisEnabled(enable);
1524 }
1525 
setCalibrationAutoStar(bool enable)1526 void Guide::setCalibrationAutoStar(bool enable)
1527 {
1528     autoStarCheck->setChecked(enable);
1529 }
1530 
setCalibrationAutoSquareSize(bool enable)1531 void Guide::setCalibrationAutoSquareSize(bool enable)
1532 {
1533     Options::setGuideAutoSquareSizeEnabled(enable);
1534 }
1535 
setCalibrationPulseDuration(int pulseDuration)1536 void Guide::setCalibrationPulseDuration(int pulseDuration)
1537 {
1538     Options::setCalibrationPulseDuration(pulseDuration);
1539 }
1540 
setGuideBoxSizeIndex(int index)1541 void Guide::setGuideBoxSizeIndex(int index)
1542 {
1543     Options::setGuideSquareSizeIndex(index);
1544 }
1545 
setGuideAlgorithmIndex(int index)1546 void Guide::setGuideAlgorithmIndex(int index)
1547 {
1548     Options::setGuideAlgorithm(index);
1549 }
1550 
setSubFrameEnabled(bool enable)1551 void Guide::setSubFrameEnabled(bool enable)
1552 {
1553     Options::setGuideSubframeEnabled(enable);
1554     if (subFrameCheck->isChecked() != enable)
1555         subFrameCheck->setChecked(enable);
1556     if(guiderType == GUIDE_PHD2)
1557         setExternalGuiderBLOBEnabled(!enable);
1558 }
1559 
1560 
setDitherSettings(bool enable,double value)1561 void Guide::setDitherSettings(bool enable, double value)
1562 {
1563     Options::setDitherEnabled(enable);
1564     Options::setDitherPixels(value);
1565 }
1566 
1567 
clearCalibration()1568 void Guide::clearCalibration()
1569 {
1570     calibrationComplete = false;
1571 
1572     guider->clearCalibration();
1573 
1574     appendLogText(i18n("Calibration is cleared."));
1575 }
1576 
setStatus(Ekos::GuideState newState)1577 void Guide::setStatus(Ekos::GuideState newState)
1578 {
1579     if (newState == state)
1580     {
1581         // pass through the aborted state
1582         if (newState == GUIDE_ABORTED)
1583             emit newStatus(state);
1584         return;
1585     }
1586 
1587     GuideState previousState = state;
1588 
1589     state = newState;
1590     emit newStatus(state);
1591 
1592     switch (state)
1593     {
1594         case GUIDE_CONNECTED:
1595             appendLogText(i18n("External guider connected."));
1596             externalConnectB->setEnabled(false);
1597             externalDisconnectB->setEnabled(true);
1598             clearCalibrationB->setEnabled(true);
1599             guideB->setEnabled(true);
1600 
1601             if(guiderType == GUIDE_PHD2)
1602             {
1603                 captureB->setEnabled(true);
1604                 loopB->setEnabled(true);
1605                 autoStarCheck->setEnabled(true);
1606                 configurePHD2Camera();
1607                 setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled());
1608                 boxSizeCombo->setEnabled(true);
1609             }
1610             break;
1611 
1612         case GUIDE_DISCONNECTED:
1613             appendLogText(i18n("External guider disconnected."));
1614             setBusy(false); //This needs to come before caputureB since it will set it to enabled again.
1615             externalConnectB->setEnabled(true);
1616             externalDisconnectB->setEnabled(false);
1617             clearCalibrationB->setEnabled(false);
1618             guideB->setEnabled(false);
1619             captureB->setEnabled(false);
1620             loopB->setEnabled(false);
1621             autoStarCheck->setEnabled(false);
1622             boxSizeCombo->setEnabled(false);
1623             //setExternalGuiderBLOBEnabled(true);
1624 #ifdef Q_OS_OSX
1625             repaint(); //This is a band-aid for a bug in QT 5.10.0
1626 #endif
1627             break;
1628 
1629         case GUIDE_CALIBRATION_SUCESS:
1630             appendLogText(i18n("Calibration completed."));
1631             calibrationComplete = true;
1632 
1633             if(guiderType !=
1634                     GUIDE_PHD2) //PHD2 will take care of this.  If this command is executed for PHD2, it might start guiding when it is first connected, if the calibration was completed already.
1635                 guide();
1636             break;
1637 
1638         case GUIDE_IDLE:
1639         case GUIDE_CALIBRATION_ERROR:
1640             setBusy(false);
1641             manualDitherB->setEnabled(false);
1642             break;
1643 
1644         case GUIDE_CALIBRATING:
1645             clearCalibrationGraphs();
1646             appendLogText(i18n("Calibration started."));
1647             setBusy(true);
1648             break;
1649 
1650         case GUIDE_GUIDING:
1651             if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS)
1652                 appendLogText(i18n("Guiding resumed."));
1653             else
1654             {
1655                 appendLogText(i18n("Autoguiding started."));
1656                 setBusy(true);
1657 
1658                 clearGuideGraphs();
1659                 guideTimer = QTime::currentTime();
1660                 driftGraph->resetTimer();
1661                 driftGraph->refreshColorScheme();
1662             }
1663             manualDitherB->setEnabled(true);
1664 
1665             break;
1666 
1667         case GUIDE_ABORTED:
1668             appendLogText(i18n("Autoguiding aborted."));
1669             setBusy(false);
1670             break;
1671 
1672         case GUIDE_SUSPENDED:
1673             appendLogText(i18n("Guiding suspended."));
1674             break;
1675 
1676         case GUIDE_REACQUIRE:
1677             if (guiderType == GUIDE_INTERNAL)
1678                 capture();
1679             break;
1680 
1681         case GUIDE_MANUAL_DITHERING:
1682             appendLogText(i18n("Manual dithering in progress."));
1683             break;
1684 
1685         case GUIDE_DITHERING:
1686             appendLogText(i18n("Dithering in progress."));
1687             break;
1688 
1689         case GUIDE_DITHERING_SETTLE:
1690             appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...",
1691                                 Options::ditherSettle()));
1692             break;
1693 
1694         case GUIDE_DITHERING_ERROR:
1695             appendLogText(i18n("Dithering failed."));
1696             // LinGuider guide continue after dithering failure
1697             if (guiderType != GUIDE_LINGUIDER)
1698             {
1699                 //state = GUIDE_IDLE;
1700                 state = GUIDE_ABORTED;
1701                 setBusy(false);
1702             }
1703             break;
1704 
1705         case GUIDE_DITHERING_SUCCESS:
1706             appendLogText(i18n("Dithering completed successfully."));
1707             // Go back to guiding state immediately if using regular guider
1708             if (Options::ditherNoGuiding() == false)
1709             {
1710                 setStatus(GUIDE_GUIDING);
1711                 // Only capture again if we are using internal guider
1712                 if (guiderType == GUIDE_INTERNAL)
1713                     capture();
1714             }
1715             break;
1716         default:
1717             break;
1718     }
1719 }
1720 
updateCCDBin(int index)1721 void Guide::updateCCDBin(int index)
1722 {
1723     if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL)
1724         return;
1725 
1726     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
1727 
1728     targetChip->setBinning(index + 1, index + 1);
1729     guideBinIndex = index;
1730 
1731     QVariantMap settings      = frameSettings[targetChip];
1732     settings["binx"]          = index + 1;
1733     settings["biny"]          = index + 1;
1734     frameSettings[targetChip] = settings;
1735 
1736     guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(),
1737                            settings["binx"].toInt(), settings["biny"].toInt());
1738 
1739     // saveSettings(); too early! Check first supported binning (see "processCCDNumber")
1740 }
1741 
processCCDNumber(INumberVectorProperty * nvp)1742 void Guide::processCCDNumber(INumberVectorProperty *nvp)
1743 {
1744     if (currentCCD == nullptr || (nvp->device != currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL)
1745         return;
1746 
1747     if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) ||
1748             (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead))
1749     {
1750         binningCombo->disconnect();
1751         if (guideBinIndex > (nvp->np[0].value - 1)) // INDI driver reports not supported binning
1752         {
1753             appendLogText(i18n("%1x%1 guide binning is not supported.", guideBinIndex + 1));
1754             guideBinIndex = nvp->np[0].value - 1;
1755         }
1756         binningCombo->setCurrentIndex(guideBinIndex);
1757         connect(binningCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin);
1758         saveSettings(); // Save binning (and more) immediately
1759     }
1760 }
1761 
checkExposureValue(ISD::CCDChip * targetChip,double exposure,IPState expState)1762 void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState)
1763 {
1764     // Ignore if not using internal guider, or chip belongs to a different camera.
1765     if (guiderType != GUIDE_INTERNAL || targetChip->getCCD() != currentCCD)
1766         return;
1767 
1768     INDI_UNUSED(exposure);
1769 
1770     if (expState == IPS_ALERT &&
1771             ((state == GUIDE_GUIDING) || (state == GUIDE_DITHERING) || (state == GUIDE_CALIBRATING)))
1772     {
1773         appendLogText(i18n("Exposure failed. Restarting exposure..."));
1774         currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS);
1775         targetChip->capture(exposureIN->value());
1776     }
1777 }
1778 
configSEPMultistarOptions()1779 void Guide::configSEPMultistarOptions()
1780 {
1781     // SEP MultiStar always uses an automated guide star & doesn't subframe.
1782     if (internalGuider->SEPMultiStarEnabled())
1783     {
1784         subFrameCheck->setChecked(false);
1785         subFrameCheck->setEnabled(false);
1786         autoStarCheck->setChecked(true);
1787         autoStarCheck->setEnabled(false);
1788     }
1789     else
1790     {
1791         subFrameCheck->setChecked(Options::guideSubframeEnabled());
1792         subFrameCheck->setEnabled(true);
1793         autoStarCheck->setChecked(Options::guideAutoStarEnabled());
1794         autoStarCheck->setEnabled(true);
1795     }
1796 }
1797 
setDarkFrameEnabled(bool enable)1798 void Guide::setDarkFrameEnabled(bool enable)
1799 {
1800     Options::setGuideDarkFrameEnabled(enable);
1801     if (darkFrameCheck->isChecked() != enable)
1802         darkFrameCheck->setChecked(enable);
1803 }
1804 
saveDefaultGuideExposure()1805 void Guide::saveDefaultGuideExposure()
1806 {
1807     Options::setGuideExposure(exposureIN->value());
1808     if(guiderType == GUIDE_PHD2)
1809         phd2Guider->requestSetExposureTime(exposureIN->value() * 1000);
1810 }
1811 
setStarPosition(const QVector3D & newCenter,bool updateNow)1812 void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow)
1813 {
1814     starCenter.setX(newCenter.x());
1815     starCenter.setY(newCenter.y());
1816     if (newCenter.z() > 0)
1817         starCenter.setZ(newCenter.z());
1818 
1819     if (updateNow)
1820         syncTrackingBoxPosition();
1821 }
1822 
syncTrackingBoxPosition()1823 void Guide::syncTrackingBoxPosition()
1824 {
1825     if(!currentCCD || guiderType == GUIDE_LINGUIDER)
1826         return;
1827 
1828     if(guiderType == GUIDE_PHD2)
1829     {
1830         //This way it won't set the tracking box on the Guide Star Image.
1831         if(!m_ImageData.isNull())
1832         {
1833             if(m_ImageData->width() < 50)
1834             {
1835                 guideView->setTrackingBoxEnabled(false);
1836                 return;
1837             }
1838         }
1839     }
1840 
1841     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
1842     Q_ASSERT(targetChip);
1843 
1844     int subBinX = 1, subBinY = 1;
1845     targetChip->getBinning(&subBinX, &subBinY);
1846 
1847     if (starCenter.isNull() == false)
1848     {
1849         double boxSize = boxSizeCombo->currentText().toInt();
1850         int x, y, w, h;
1851         targetChip->getFrame(&x, &y, &w, &h);
1852         // If box size is larger than image size, set it to lower index
1853         if (boxSize / subBinX >= w || boxSize / subBinY >= h)
1854         {
1855             int newIndex = boxSizeCombo->currentIndex() - 1;
1856             if (newIndex >= 0)
1857                 boxSizeCombo->setCurrentIndex(newIndex);
1858             return;
1859         }
1860 
1861         // If binning changed, update coords accordingly
1862         if (subBinX != starCenter.z())
1863         {
1864             if (starCenter.z() > 0)
1865             {
1866                 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX));
1867                 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY));
1868             }
1869 
1870             starCenter.setZ(subBinX);
1871         }
1872 
1873         QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY),
1874                                boxSize / subBinX, boxSize / subBinY);
1875         guideView->setTrackingBoxEnabled(true);
1876         guideView->setTrackingBox(starRect);
1877     }
1878 }
1879 
setGuiderType(int type)1880 bool Guide::setGuiderType(int type)
1881 {
1882     // Use default guider option
1883     if (type == -1)
1884         type = Options::guiderType();
1885     else if (type == guiderType)
1886         return true;
1887 
1888     if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING)
1889     {
1890         appendLogText(i18n("Cannot change guider type while active."));
1891         return false;
1892     }
1893 
1894     if (guider != nullptr)
1895     {
1896         // Disconnect from host
1897         if (guider->isConnected())
1898             guider->Disconnect();
1899 
1900         // Disconnect signals
1901         guider->disconnect();
1902     }
1903 
1904     guiderType = static_cast<GuiderType>(type);
1905 
1906     switch (type)
1907     {
1908         case GUIDE_INTERNAL:
1909         {
1910             connect(internalGuider, SIGNAL(newPulse(GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int)));
1911             connect(internalGuider, SIGNAL(newPulse(GuideDirection, int, GuideDirection, int)), this,
1912                     SLOT(sendPulse(GuideDirection, int, GuideDirection, int)));
1913             connect(internalGuider, SIGNAL(DESwapChanged(bool)), swapCheck, SLOT(setChecked(bool)));
1914             connect(internalGuider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &)));
1915 
1916             guider = internalGuider;
1917 
1918             internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex());
1919 
1920             clearCalibrationB->setEnabled(true);
1921             guideB->setEnabled(true);
1922             captureB->setEnabled(true);
1923             loopB->setEnabled(true);
1924 
1925             configSEPMultistarOptions();
1926             darkFrameCheck->setEnabled(true);
1927 
1928             guiderCombo->setEnabled(true);
1929             ST4Combo->setEnabled(true);
1930             exposureIN->setEnabled(true);
1931             binningCombo->setEnabled(true);
1932             boxSizeCombo->setEnabled(true);
1933             filterCombo->setEnabled(true);
1934 
1935             externalConnectB->setEnabled(false);
1936             externalDisconnectB->setEnabled(false);
1937 
1938             opsGuide->controlGroup->setEnabled(true);
1939             infoGroup->setEnabled(true);
1940             label_6->setEnabled(true);
1941             FOVScopeCombo->setEnabled(true);
1942             l_5->setEnabled(true);
1943             l_6->setEnabled(true);
1944             l_7->setEnabled(true);
1945             l_8->setEnabled(true);
1946             l_Aperture->setEnabled(true);
1947             l_FOV->setEnabled(true);
1948             l_FbyD->setEnabled(true);
1949             l_Focal->setEnabled(true);
1950             driftGraphicsGroup->setEnabled(true);
1951 
1952             guiderCombo->setToolTip(i18n("Select guide camera."));
1953 
1954             updateGuideParams();
1955         }
1956         break;
1957 
1958         case GUIDE_PHD2:
1959             if (phd2Guider.isNull())
1960                 phd2Guider = new PHD2();
1961 
1962             guider = phd2Guider;
1963             phd2Guider->setGuideView(guideView);
1964 
1965             connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &)));
1966 
1967             clearCalibrationB->setEnabled(true);
1968             captureB->setEnabled(false);
1969             loopB->setEnabled(false);
1970             darkFrameCheck->setEnabled(false);
1971             subFrameCheck->setEnabled(false);
1972             autoStarCheck->setEnabled(false);
1973             guideB->setEnabled(false); //This will be enabled later when equipment connects (or not)
1974             externalConnectB->setEnabled(false);
1975 
1976             checkBox_DirRA->setEnabled(false);
1977             eastControlCheck->setEnabled(false);
1978             westControlCheck->setEnabled(false);
1979             swapCheck->setEnabled(false);
1980 
1981 
1982             opsGuide->controlGroup->setEnabled(false);
1983             infoGroup->setEnabled(true);
1984             label_6->setEnabled(false);
1985             FOVScopeCombo->setEnabled(false);
1986             l_5->setEnabled(false);
1987             l_6->setEnabled(false);
1988             l_7->setEnabled(false);
1989             l_8->setEnabled(false);
1990             l_Aperture->setEnabled(false);
1991             l_FOV->setEnabled(false);
1992             l_FbyD->setEnabled(false);
1993             l_Focal->setEnabled(false);
1994             driftGraphicsGroup->setEnabled(true);
1995 
1996             ST4Combo->setEnabled(false);
1997             exposureIN->setEnabled(true);
1998             binningCombo->setEnabled(false);
1999             boxSizeCombo->setEnabled(false);
2000             filterCombo->setEnabled(false);
2001             guiderCombo->setEnabled(false);
2002 
2003             if (Options::resetGuideCalibration())
2004                 appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2."));
2005 
2006             updateGuideParams();
2007             break;
2008 
2009         case GUIDE_LINGUIDER:
2010             if (linGuider.isNull())
2011                 linGuider = new LinGuider();
2012 
2013             guider = linGuider;
2014 
2015             clearCalibrationB->setEnabled(true);
2016             captureB->setEnabled(false);
2017             loopB->setEnabled(false);
2018             darkFrameCheck->setEnabled(false);
2019             subFrameCheck->setEnabled(false);
2020             autoStarCheck->setEnabled(false);
2021             guideB->setEnabled(true);
2022             externalConnectB->setEnabled(true);
2023 
2024             opsGuide->controlGroup->setEnabled(false);
2025             infoGroup->setEnabled(false);
2026             driftGraphicsGroup->setEnabled(false);
2027 
2028             ST4Combo->setEnabled(false);
2029             exposureIN->setEnabled(false);
2030             binningCombo->setEnabled(false);
2031             boxSizeCombo->setEnabled(false);
2032             filterCombo->setEnabled(false);
2033 
2034             guiderCombo->setEnabled(false);
2035 
2036             updateGuideParams();
2037 
2038             break;
2039     }
2040 
2041     if (guider != nullptr)
2042     {
2043         connect(guider, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture);
2044         connect(guider, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText);
2045         connect(guider, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus);
2046         connect(guider, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition);
2047         connect(guider, &Ekos::GuideInterface::guideStats, this, &Ekos::Guide::guideStats);
2048 
2049         connect(guider, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta);
2050         connect(guider, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse);
2051         connect(guider, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma);
2052         connect(guider, &Ekos::GuideInterface::newSNR, this, &Ekos::Guide::setSNR);
2053 
2054         driftGraph->connectGuider(guider);
2055         targetPlot->connectGuider(guider);
2056 
2057         connect(guider, &Ekos::GuideInterface::calibrationUpdate, this, &Ekos::Guide::calibrationUpdate);
2058 
2059         connect(guider, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera);
2060     }
2061 
2062     externalConnectB->setEnabled(false);
2063     externalDisconnectB->setEnabled(false);
2064 
2065     if (guider != nullptr && guiderType != GUIDE_INTERNAL)
2066     {
2067         externalConnectB->setEnabled(!guider->isConnected());
2068         externalDisconnectB->setEnabled(guider->isConnected());
2069     }
2070 
2071     if (guider != nullptr)
2072         guider->Connect();
2073 
2074     return true;
2075 }
2076 
updateTrackingBoxSize(int currentIndex)2077 void Guide::updateTrackingBoxSize(int currentIndex)
2078 {
2079     if (currentIndex >= 0)
2080     {
2081         Options::setGuideSquareSizeIndex(currentIndex);
2082 
2083         if (guiderType == GUIDE_INTERNAL)
2084             dynamic_cast<InternalGuider *>(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt());
2085 
2086         syncTrackingBoxPosition();
2087     }
2088 }
2089 
onThresholdChanged(int index)2090 void Guide::onThresholdChanged(int index)
2091 {
2092     switch (guiderType)
2093     {
2094         case GUIDE_INTERNAL:
2095             dynamic_cast<InternalGuider *>(guider)->setSquareAlgorithm(index);
2096             break;
2097 
2098         default:
2099             break;
2100     }
2101 }
2102 
onEnableDirRA(bool enable)2103 void Guide::onEnableDirRA(bool enable)
2104 {
2105     // If RA guiding is enable or disabled, the GPG should be reset.
2106     if (Options::gPGEnabled())
2107         guider->resetGPG();
2108     Options::setRAGuideEnabled(enable);
2109 }
2110 
onEnableDirDEC(bool enable)2111 void Guide::onEnableDirDEC(bool enable)
2112 {
2113     Options::setDECGuideEnabled(enable);
2114     updatePHD2Directions();
2115 }
2116 
syncSettings()2117 void Guide::syncSettings()
2118 {
2119     QSpinBox *pSB = nullptr;
2120     QDoubleSpinBox *pDSB = nullptr;
2121     QCheckBox *pCB = nullptr;
2122 
2123     QObject *obj = sender();
2124 
2125     if ((pSB = qobject_cast<QSpinBox *>(obj)))
2126     {
2127         if (pSB == opsGuide->spinBox_MaxPulseArcSecRA)
2128             Options::setRAMaximumPulseArcSec(pSB->value());
2129         else if (pSB == opsGuide->spinBox_MaxPulseArcSecDEC)
2130             Options::setDECMaximumPulseArcSec(pSB->value());
2131     }
2132     else if ((pDSB = qobject_cast<QDoubleSpinBox *>(obj)))
2133     {
2134         if (pDSB == opsGuide->spinBox_PropGainRA)
2135             Options::setRAProportionalGain(pDSB->value());
2136         else if (pDSB == opsGuide->spinBox_PropGainDEC)
2137             Options::setDECProportionalGain(pDSB->value());
2138         else if (pDSB == opsGuide->spinBox_IntGainRA)
2139             Options::setRAIntegralGain(pDSB->value());
2140         else if (pDSB == opsGuide->spinBox_IntGainDEC)
2141             Options::setDECIntegralGain(pDSB->value());
2142         else if (pDSB == opsGuide->spinBox_MinPulseArcSecRA)
2143             Options::setRAMinimumPulseArcSec(pDSB->value());
2144         else if (pDSB == opsGuide->spinBox_MinPulseArcSecDEC)
2145             Options::setDECMinimumPulseArcSec(pDSB->value());
2146     }
2147     else if ((pCB = qobject_cast<QCheckBox*>(obj)))
2148     {
2149         if (pCB == autoStarCheck)
2150             Options::setGuideAutoStarEnabled(pCB->isChecked());
2151     }
2152 
2153     emit settingsUpdated(getSettings());
2154 }
2155 
onControlDirectionChanged(bool enable)2156 void Guide::onControlDirectionChanged(bool enable)
2157 {
2158     QObject *obj = sender();
2159 
2160     if (northControlCheck == dynamic_cast<QCheckBox *>(obj))
2161     {
2162         Options::setNorthDECGuideEnabled(enable);
2163         updatePHD2Directions();
2164     }
2165     else if (southControlCheck == dynamic_cast<QCheckBox *>(obj))
2166     {
2167         Options::setSouthDECGuideEnabled(enable);
2168         updatePHD2Directions();
2169     }
2170     else if (westControlCheck == dynamic_cast<QCheckBox *>(obj))
2171     {
2172         Options::setWestRAGuideEnabled(enable);
2173     }
2174     else if (eastControlCheck == dynamic_cast<QCheckBox *>(obj))
2175     {
2176         Options::setEastRAGuideEnabled(enable);
2177     }
2178 }
updatePHD2Directions()2179 void Guide::updatePHD2Directions()
2180 {
2181     if(guiderType == GUIDE_PHD2)
2182         phd2Guider -> requestSetDEGuideMode(checkBox_DirDEC->isChecked(), northControlCheck->isChecked(),
2183                                             southControlCheck->isChecked());
2184 }
updateDirectionsFromPHD2(const QString & mode)2185 void Guide::updateDirectionsFromPHD2(const QString &mode)
2186 {
2187     //disable connections
2188     disconnect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2189     disconnect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2190     disconnect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2191 
2192     if(mode == "Auto")
2193     {
2194         checkBox_DirDEC->setChecked(true);
2195         northControlCheck->setChecked(true);
2196         southControlCheck->setChecked(true);
2197     }
2198     else if(mode == "North")
2199     {
2200         checkBox_DirDEC->setChecked(true);
2201         northControlCheck->setChecked(true);
2202         southControlCheck->setChecked(false);
2203     }
2204     else if(mode == "South")
2205     {
2206         checkBox_DirDEC->setChecked(true);
2207         northControlCheck->setChecked(false);
2208         southControlCheck->setChecked(true);
2209     }
2210     else //Off
2211     {
2212         checkBox_DirDEC->setChecked(false);
2213         northControlCheck->setChecked(true);
2214         southControlCheck->setChecked(true);
2215     }
2216 
2217     //Re-enable connections
2218     connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2219     connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2220     connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2221 }
2222 
2223 
loadSettings()2224 void Guide::loadSettings()
2225 {
2226     // Exposure
2227     exposureIN->setValue(Options::guideExposure());
2228     // Bin Size
2229     guideBinIndex = Options::guideBinSizeIndex();
2230     // Box Size
2231     boxSizeCombo->setCurrentIndex(Options::guideSquareSizeIndex());
2232     // Effect filter
2233     guideFilterIndex = Options::guideFilterFITSIndex();
2234     // Dark frame?
2235     darkFrameCheck->setChecked(Options::guideDarkFrameEnabled());
2236     // Subframed?
2237     subFrameCheck->setChecked(Options::guideSubframeEnabled());
2238     // RA/DEC enabled?
2239     checkBox_DirRA->setChecked(Options::rAGuideEnabled());
2240     checkBox_DirDEC->setChecked(Options::dECGuideEnabled());
2241     // N/S enabled?
2242     northControlCheck->setChecked(Options::northDECGuideEnabled());
2243     southControlCheck->setChecked(Options::southDECGuideEnabled());
2244     // W/E enabled?
2245     westControlCheck->setChecked(Options::westRAGuideEnabled());
2246     eastControlCheck->setChecked(Options::eastRAGuideEnabled());
2247 
2248     // Transition code: if old values are stored in the proportional gains,
2249     // change them to a default value.
2250     if (Options::rAProportionalGain() > 1.0)
2251         Options::setRAProportionalGain(0.75);
2252     if (Options::dECProportionalGain() > 1.0)
2253         Options::setDECProportionalGain(0.75);
2254 
2255     // PID Control - Proportional Gain
2256     opsGuide->spinBox_PropGainRA->setValue(Options::rAProportionalGain());
2257     opsGuide->spinBox_PropGainDEC->setValue(Options::dECProportionalGain());
2258 
2259     // Transition code: if old values are stored in the integral gains,
2260     // change them to a default value.
2261     if (Options::rAIntegralGain() > 1.0)
2262         Options::setRAIntegralGain(0.75);
2263     if (Options::dECIntegralGain() > 1.0)
2264         Options::setDECIntegralGain(0.75);
2265 
2266     // PID Control - Integral Gain
2267     opsGuide->spinBox_IntGainRA->setValue(Options::rAIntegralGain());
2268     opsGuide->spinBox_IntGainDEC->setValue(Options::dECIntegralGain());
2269     // Max Pulse Duration (arcsec)
2270     opsGuide->spinBox_MaxPulseArcSecRA->setValue(Options::rAMaximumPulseArcSec());
2271     opsGuide->spinBox_MaxPulseArcSecDEC->setValue(Options::dECMaximumPulseArcSec());
2272     // Min Pulse Duration (arcsec)
2273     opsGuide->spinBox_MinPulseArcSecRA->setValue(Options::rAMinimumPulseArcSec());
2274     opsGuide->spinBox_MinPulseArcSecDEC->setValue(Options::dECMinimumPulseArcSec());
2275     // Autostar
2276     autoStarCheck->setChecked(Options::guideAutoStarEnabled());
2277 }
2278 
saveSettings()2279 void Guide::saveSettings()
2280 {
2281     // Exposure
2282     Options::setGuideExposure(exposureIN->value());
2283     // Bin Size
2284     Options::setGuideBinSizeIndex(binningCombo->currentIndex());
2285     // Box Size
2286     Options::setGuideSquareSizeIndex(boxSizeCombo->currentIndex());
2287     // Effect filter
2288     Options::setGuideFilterFITSIndex(filterCombo->currentIndex());
2289     // Dark frame?
2290     Options::setGuideDarkFrameEnabled(darkFrameCheck->isChecked());
2291     // Subframed?
2292     Options::setGuideSubframeEnabled(subFrameCheck->isChecked());
2293     // RA/DEC enabled?
2294     Options::setRAGuideEnabled(checkBox_DirRA->isChecked());
2295     Options::setDECGuideEnabled(checkBox_DirDEC->isChecked());
2296     // N/S enabled?
2297     Options::setNorthDECGuideEnabled(northControlCheck->isChecked());
2298     Options::setSouthDECGuideEnabled(southControlCheck->isChecked());
2299     // W/E enabled?
2300     Options::setWestRAGuideEnabled(westControlCheck->isChecked());
2301     Options::setEastRAGuideEnabled(eastControlCheck->isChecked());
2302     // PID Control - Proportional Gain
2303     Options::setRAProportionalGain(opsGuide->spinBox_PropGainRA->value());
2304     Options::setDECProportionalGain(opsGuide->spinBox_PropGainDEC->value());
2305     // PID Control - Integral Gain
2306     Options::setRAIntegralGain(opsGuide->spinBox_IntGainRA->value());
2307     Options::setDECIntegralGain(opsGuide->spinBox_IntGainDEC->value());
2308     // Max Pulse Duration (arcsec)
2309     Options::setRAMaximumPulseArcSec(opsGuide->spinBox_MaxPulseArcSecRA->value());
2310     Options::setDECMaximumPulseArcSec(opsGuide->spinBox_MaxPulseArcSecDEC->value());
2311     // Min Pulse Duration (arcsec)
2312     Options::setRAMinimumPulseArcSec(opsGuide->spinBox_MinPulseArcSecRA->value());
2313     Options::setDECMinimumPulseArcSec(opsGuide->spinBox_MinPulseArcSecDEC->value());
2314 }
2315 
setTrackingStar(int x,int y)2316 void Guide::setTrackingStar(int x, int y)
2317 {
2318     QVector3D newStarPosition(x, y, -1);
2319     setStarPosition(newStarPosition, true);
2320 
2321     if(guiderType == GUIDE_PHD2)
2322     {
2323         //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that.
2324         if(!m_ImageData.isNull())
2325         {
2326             if(m_ImageData->width() > 50)
2327                 phd2Guider->setLockPosition(starCenter.x(), starCenter.y());
2328         }
2329     }
2330 
2331     if (operationStack.isEmpty() == false)
2332         executeOperationStack();
2333 }
2334 
setAxisDelta(double ra,double de)2335 void Guide::setAxisDelta(double ra, double de)
2336 {
2337     //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding.
2338     //If guide pulses start coming in, it must be guiding.
2339     // 2020-04-10 sterne-jaeger: Will be resolved inside EKOS phd guiding.
2340     // if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING)
2341     //     setStatus(GUIDE_GUIDING);
2342 
2343     ra = -ra;  //The ra is backwards in sign from how it should be displayed on the graph.
2344 
2345     int currentNumPoints = driftGraph->graph(GuideGraph::G_RA)->dataCount();
2346     guideSlider->setMaximum(currentNumPoints);
2347     if(graphOnLatestPt)
2348     {
2349         guideSlider->setValue(currentNumPoints);
2350     }
2351     l_DeltaRA->setText(QString::number(ra, 'f', 2));
2352     l_DeltaDEC->setText(QString::number(de, 'f', 2));
2353 
2354     emit newAxisDelta(ra, de);
2355 }
2356 
calibrationUpdate(GuideInterface::CalibrationUpdateType type,const QString & message,double dx,double dy)2357 void Guide::calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString &message,
2358                               double dx, double dy)
2359 {
2360     switch (type)
2361     {
2362         case GuideInterface::RA_OUT:
2363             calibrationPlot->graph(GuideGraph::G_RA)->addData(dx, dy);
2364             break;
2365         case GuideInterface::RA_IN:
2366             calibrationPlot->graph(GuideGraph::G_DEC)->addData(dx, dy);
2367             break;
2368         case GuideInterface::BACKLASH:
2369             calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->addData(dx, dy);
2370             break;
2371         case GuideInterface::DEC_OUT:
2372             calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(dx, dy);
2373             break;
2374         case GuideInterface::DEC_IN:
2375             calibrationPlot->graph(GuideGraph::G_RA_PULSE)->addData(dx, dy);
2376             break;
2377         case GuideInterface::CALIBRATION_MESSAGE_ONLY:
2378             ;
2379     }
2380     calLabel->setText(message);
2381     calibrationPlot->replot();
2382 }
2383 
setAxisSigma(double ra,double de)2384 void Guide::setAxisSigma(double ra, double de)
2385 {
2386     l_ErrRA->setText(QString::number(ra, 'f', 2));
2387     l_ErrDEC->setText(QString::number(de, 'f', 2));
2388     const double total = std::hypot(ra, de);
2389     l_TotalRMS->setText(QString::number(total, 'f', 2));
2390 
2391     emit newAxisSigma(ra, de);
2392 }
2393 
axisDelta()2394 QList<double> Guide::axisDelta()
2395 {
2396     QList<double> delta;
2397 
2398     delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble();
2399 
2400     return delta;
2401 }
2402 
axisSigma()2403 QList<double> Guide::axisSigma()
2404 {
2405     QList<double> sigma;
2406 
2407     sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble();
2408 
2409     return sigma;
2410 }
2411 
setAxisPulse(double ra,double de)2412 void Guide::setAxisPulse(double ra, double de)
2413 {
2414     l_PulseRA->setText(QString::number(static_cast<int>(ra)));
2415     l_PulseDEC->setText(QString::number(static_cast<int>(de)));
2416 }
2417 
setSNR(double snr)2418 void Guide::setSNR(double snr)
2419 {
2420     l_SNR->setText(QString::number(snr, 'f', 1));
2421 }
2422 
buildOperationStack(GuideState operation)2423 void Guide::buildOperationStack(GuideState operation)
2424 {
2425     operationStack.clear();
2426 
2427     switch (operation)
2428     {
2429         case GUIDE_CAPTURE:
2430             if (Options::guideDarkFrameEnabled())
2431                 operationStack.push(GUIDE_DARK);
2432 
2433             operationStack.push(GUIDE_CAPTURE);
2434             operationStack.push(GUIDE_SUBFRAME);
2435             break;
2436 
2437         case GUIDE_CALIBRATING:
2438             operationStack.push(GUIDE_CALIBRATING);
2439             if (guiderType == GUIDE_INTERNAL)
2440             {
2441                 if (Options::guideDarkFrameEnabled())
2442                     operationStack.push(GUIDE_DARK);
2443 
2444                 // Auto Star Selected Path
2445                 if (Options::guideAutoStarEnabled() ||
2446                         // SEP MultiStar always uses an automated guide star.
2447                         internalGuider->SEPMultiStarEnabled())
2448                 {
2449                     // If subframe is enabled and we need to auto select a star, then we need to make the final capture
2450                     // of the subframed image. This is only done if we aren't already subframed.
2451                     if (subFramed == false && Options::guideSubframeEnabled())
2452                         operationStack.push(GUIDE_CAPTURE);
2453 
2454                     operationStack.push(GUIDE_SUBFRAME);
2455                     operationStack.push(GUIDE_STAR_SELECT);
2456 
2457 
2458                     operationStack.push(GUIDE_CAPTURE);
2459 
2460                     // If we are being ask to go full frame, let's do that first
2461                     if (subFramed == true && Options::guideSubframeEnabled() == false)
2462                         operationStack.push(GUIDE_SUBFRAME);
2463                 }
2464                 // Manual Star Selection Path
2465                 else
2466                 {
2467                     // Final capture before we start calibrating
2468                     if (subFramed == false && Options::guideSubframeEnabled())
2469                         operationStack.push(GUIDE_CAPTURE);
2470 
2471                     // Subframe if required
2472                     operationStack.push(GUIDE_SUBFRAME);
2473 
2474                     // First capture an image
2475                     operationStack.push(GUIDE_CAPTURE);
2476                 }
2477 
2478             }
2479             break;
2480 
2481         default:
2482             break;
2483     }
2484 }
2485 
executeOperationStack()2486 bool Guide::executeOperationStack()
2487 {
2488     if (operationStack.isEmpty())
2489         return false;
2490 
2491     GuideState nextOperation = operationStack.pop();
2492 
2493     bool actionRequired = false;
2494 
2495     switch (nextOperation)
2496     {
2497         case GUIDE_SUBFRAME:
2498             actionRequired = executeOneOperation(nextOperation);
2499             break;
2500 
2501         case GUIDE_DARK:
2502             actionRequired = executeOneOperation(nextOperation);
2503             break;
2504 
2505         case GUIDE_CAPTURE:
2506             actionRequired = captureOneFrame();
2507             break;
2508 
2509         case GUIDE_STAR_SELECT:
2510             actionRequired = executeOneOperation(nextOperation);
2511             break;
2512 
2513         case GUIDE_CALIBRATING:
2514             if (guiderType == GUIDE_INTERNAL)
2515             {
2516                 guider->setStarPosition(starCenter);
2517 
2518                 // Tracking must be engaged
2519                 if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false)
2520                     currentTelescope->setTrackEnabled(true);
2521             }
2522 
2523             if (guider->calibrate())
2524             {
2525                 if (guiderType == GUIDE_INTERNAL)
2526                     disconnect(guideView, SIGNAL(trackingStarSelected(int, int)), this,
2527                                SLOT(setTrackingStar(int, int)));
2528                 setBusy(true);
2529             }
2530             else
2531             {
2532                 emit newStatus(GUIDE_CALIBRATION_ERROR);
2533                 state = GUIDE_IDLE;
2534                 appendLogText(i18n("Calibration failed to start."));
2535                 setBusy(false);
2536             }
2537             break;
2538 
2539         default:
2540             break;
2541     }
2542 
2543     // If an additional action is required, return return and continue later
2544     if (actionRequired)
2545         return true;
2546     // Otherwise, continue processing the stack
2547     else
2548         return executeOperationStack();
2549 }
2550 
executeOneOperation(GuideState operation)2551 bool Guide::executeOneOperation(GuideState operation)
2552 {
2553     bool actionRequired = false;
2554 
2555     if (currentCCD == nullptr)
2556         return actionRequired;
2557 
2558     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
2559     if (targetChip == nullptr)
2560         return false;
2561 
2562     int subBinX, subBinY;
2563     targetChip->getBinning(&subBinX, &subBinY);
2564 
2565     switch (operation)
2566     {
2567         case GUIDE_SUBFRAME:
2568         {
2569             // SEP MultiStar doesn't subframe.
2570             if ((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled())
2571                 break;
2572             // Check if we need and can subframe
2573             if (subFramed == false && Options::guideSubframeEnabled() == true && targetChip->canSubframe())
2574             {
2575                 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2576                 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2577 
2578                 int offset = boxSizeCombo->currentText().toInt() / subBinX;
2579 
2580                 int x = starCenter.x();
2581                 int y = starCenter.y();
2582 
2583                 x     = (x - offset * 2) * subBinX;
2584                 y     = (y - offset * 2) * subBinY;
2585                 int w = offset * 4 * subBinX;
2586                 int h = offset * 4 * subBinY;
2587 
2588                 if (x < minX)
2589                     x = minX;
2590                 if (y < minY)
2591                     y = minY;
2592                 if ((x + w) > maxW)
2593                     w = maxW - x;
2594                 if ((y + h) > maxH)
2595                     h = maxH - y;
2596 
2597                 targetChip->setFrame(x, y, w, h);
2598 
2599                 subFramed            = true;
2600                 QVariantMap settings = frameSettings[targetChip];
2601                 settings["x"]        = x;
2602                 settings["y"]        = y;
2603                 settings["w"]        = w;
2604                 settings["h"]        = h;
2605                 settings["binx"]     = subBinX;
2606                 settings["biny"]     = subBinY;
2607 
2608                 frameSettings[targetChip] = settings;
2609 
2610                 starCenter.setX(w / (2 * subBinX));
2611                 starCenter.setY(h / (2 * subBinX));
2612             }
2613             // Otherwise check if we are already subframed
2614             // and we need to go back to full frame
2615             // or if we need to go back to full frame since we need
2616             // to reaquire a star
2617             else if (subFramed &&
2618                      (Options::guideSubframeEnabled() == false ||
2619                       state == GUIDE_REACQUIRE))
2620             {
2621                 targetChip->resetFrame();
2622 
2623                 int x, y, w, h;
2624                 targetChip->getFrame(&x, &y, &w, &h);
2625 
2626                 QVariantMap settings;
2627                 settings["x"]             = x;
2628                 settings["y"]             = y;
2629                 settings["w"]             = w;
2630                 settings["h"]             = h;
2631                 settings["binx"]          = subBinX;
2632                 settings["biny"]          = subBinY;
2633                 frameSettings[targetChip] = settings;
2634 
2635                 subFramed = false;
2636 
2637                 starCenter.setX(w / (2 * subBinX));
2638                 starCenter.setY(h / (2 * subBinX));
2639 
2640                 //starCenter.setX(0);
2641                 //starCenter.setY(0);
2642             }
2643         }
2644         break;
2645 
2646         case GUIDE_DARK:
2647         {
2648             // Do we need to take a dark frame?
2649             if (m_ImageData && Options::guideDarkFrameEnabled())
2650             {
2651                 QVariantMap settings = frameSettings[targetChip];
2652                 uint16_t offsetX = 0;
2653                 uint16_t offsetY = 0;
2654 
2655                 if (settings["x"].isValid() &&
2656                         settings["y"].isValid() &&
2657                         settings["binx"].isValid() &&
2658                         settings["biny"].isValid())
2659                 {
2660                     offsetX = settings["x"].toInt() / settings["binx"].toInt();
2661                     offsetY = settings["y"].toInt() / settings["biny"].toInt();
2662                 }
2663 
2664                 actionRequired = true;
2665                 targetChip->setCaptureFilter(static_cast<FITSScale>(filterCombo->currentIndex()));
2666                 m_DarkProcessor->denoise(targetChip, m_ImageData, exposureIN->value(), offsetX, offsetY);
2667             }
2668         }
2669         break;
2670 
2671         case GUIDE_STAR_SELECT:
2672         {
2673             state = GUIDE_STAR_SELECT;
2674             emit newStatus(state);
2675 
2676             if (Options::guideAutoStarEnabled() ||
2677                     // SEP MultiStar always uses an automated guide star.
2678                     ((guiderType == GUIDE_INTERNAL) &&
2679                      internalGuider->SEPMultiStarEnabled()))
2680             {
2681                 bool autoStarCaptured = internalGuider->selectAutoStar();
2682                 if (autoStarCaptured)
2683                 {
2684                     appendLogText(i18n("Auto star selected."));
2685                 }
2686                 else
2687                 {
2688                     appendLogText(i18n("Failed to select an auto star."));
2689                     actionRequired = true;
2690                     state = GUIDE_CALIBRATION_ERROR;
2691                     emit newStatus(state);
2692                     setBusy(false);
2693                 }
2694             }
2695             else
2696             {
2697                 appendLogText(i18n("Select a guide star to calibrate."));
2698                 actionRequired = true;
2699             }
2700         }
2701         break;
2702 
2703         default:
2704             break;
2705     }
2706 
2707     return actionRequired;
2708 }
2709 
processGuideOptions()2710 void Guide::processGuideOptions()
2711 {
2712     if (Options::guiderType() != guiderType)
2713     {
2714         guiderType = static_cast<GuiderType>(Options::guiderType());
2715         setGuiderType(Options::guiderType());
2716     }
2717 }
2718 
showFITSViewer()2719 void Guide::showFITSViewer()
2720 {
2721     static int lastFVTabID = -1;
2722     if (m_ImageData)
2723     {
2724         QUrl url = QUrl::fromLocalFile("guide.fits");
2725         if (fv.isNull())
2726         {
2727             fv = KStars::Instance()->createFITSViewer();
2728             fv->loadData(m_ImageData, url, &lastFVTabID);
2729         }
2730         else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false)
2731             fv->loadData(m_ImageData, url, &lastFVTabID);
2732 
2733         fv->show();
2734     }
2735 }
2736 
setExternalGuiderBLOBEnabled(bool enable)2737 void Guide::setExternalGuiderBLOBEnabled(bool enable)
2738 {
2739     // Nothing to do if guider is internal
2740     if (guiderType == GUIDE_INTERNAL)
2741         return;
2742 
2743     if(!currentCCD)
2744         return;
2745 
2746     currentCCD->setBLOBEnabled(enable);
2747 
2748     if(currentCCD->isBLOBEnabled())
2749     {
2750         if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider"))
2751             useGuideHead = true;
2752         else
2753             useGuideHead = false;
2754 
2755         ISD::CCDChip *targetChip =
2756             currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
2757         if (targetChip)
2758         {
2759             targetChip->setImageView(guideView, FITS_GUIDE);
2760             targetChip->setCaptureMode(FITS_GUIDE);
2761         }
2762         syncCCDInfo();
2763     }
2764 
2765 }
2766 
resetNonGuidedDither()2767 void Guide::resetNonGuidedDither()
2768 {
2769     // reset non guided dither total drift
2770     nonGuidedDitherRaOffsetMsec = 0;
2771     nonGuidedDitherDecOffsetMsec = 0;
2772     qCDebug(KSTARS_EKOS_GUIDE) << "Reset non guiding dithering position";
2773 
2774     // initialize random generator if not done before
2775     if (!isNonGuidedDitherInitialized)
2776     {
2777         auto seed = std::chrono::system_clock::now().time_since_epoch().count();
2778         nonGuidedPulseGenerator.seed(seed);
2779         isNonGuidedDitherInitialized = true;
2780         qCDebug(KSTARS_EKOS_GUIDE) << "Initialize non guiding dithering random generator";
2781     }
2782 }
2783 
nonGuidedDither()2784 void Guide::nonGuidedDither()
2785 {
2786     double ditherPulse = Options::ditherNoGuidingPulse();
2787 
2788     // Randomize dithering position up to +/-dithePulse distance from original
2789     std::uniform_int_distribution<int> newPos(-ditherPulse, +ditherPulse);
2790 
2791     // Calculate the pulse needed to move to the new position, then save the new position and apply the pulse
2792 
2793     // for ra
2794     const int newRaOffsetMsec = newPos(nonGuidedPulseGenerator);
2795     const int raPulse = nonGuidedDitherRaOffsetMsec - newRaOffsetMsec;
2796     nonGuidedDitherRaOffsetMsec = newRaOffsetMsec;
2797     const int raMsec  = std::abs(raPulse);
2798     const int raPolarity = (raPulse >= 0 ? 1 : -1);
2799 
2800     // and for dec
2801     const int newDecOffsetMsec = newPos(nonGuidedPulseGenerator);
2802     const int decPulse = nonGuidedDitherDecOffsetMsec - newDecOffsetMsec;
2803     nonGuidedDitherDecOffsetMsec = newDecOffsetMsec;
2804     const int decMsec  = std::abs(decPulse);
2805     const int decPolarity = (decPulse >= 0 ? 1 : -1);
2806 
2807     qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither...";
2808     qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << raMsec << "ra_polarity:" << raPolarity << "de_msec:" << decMsec <<
2809                                "de_polarity:" << decPolarity;
2810 
2811     bool rc = sendPulse(raPolarity > 0 ? RA_INC_DIR : RA_DEC_DIR, raMsec, decPolarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR,
2812                         decMsec);
2813 
2814     if (rc)
2815     {
2816         qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful.";
2817         QTimer::singleShot( (raMsec > decMsec ? raMsec : decMsec) + Options::ditherSettle() * 1000 + 100, [this]()
2818         {
2819             emit newStatus(GUIDE_DITHERING_SUCCESS);
2820             state = GUIDE_IDLE;
2821         });
2822     }
2823     else
2824     {
2825         qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed.";
2826         emit newStatus(GUIDE_DITHERING_ERROR);
2827         state = GUIDE_IDLE;
2828     }
2829 }
2830 
updateTelescopeType(int index)2831 void Guide::updateTelescopeType(int index)
2832 {
2833     if (currentCCD == nullptr)
2834         return;
2835 
2836     focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL;
2837     aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture;
2838 
2839     Options::setGuideScopeType(index);
2840 
2841     syncTelescopeInfo();
2842 }
2843 
setDefaultST4(const QString & driver)2844 void Guide::setDefaultST4(const QString &driver)
2845 {
2846     Options::setDefaultST4Driver(driver);
2847 }
2848 
setDefaultCCD(const QString & ccd)2849 void Guide::setDefaultCCD(const QString &ccd)
2850 {
2851     if (guiderType == GUIDE_INTERNAL)
2852         Options::setDefaultGuideCCD(ccd);
2853 }
2854 
handleManualDither()2855 void Guide::handleManualDither()
2856 {
2857     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
2858     if (targetChip == nullptr)
2859         return;
2860 
2861     Ui::ManualDither ditherDialog;
2862     QDialog container(this);
2863     ditherDialog.setupUi(&container);
2864 
2865     if (guiderType != GUIDE_INTERNAL)
2866     {
2867         ditherDialog.coordinatesR->setEnabled(false);
2868         ditherDialog.x->setEnabled(false);
2869         ditherDialog.y->setEnabled(false);
2870     }
2871 
2872     int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2873     targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2874 
2875     ditherDialog.x->setMinimum(minX);
2876     ditherDialog.x->setMaximum(maxX);
2877     ditherDialog.y->setMinimum(minY);
2878     ditherDialog.y->setMaximum(maxY);
2879 
2880     ditherDialog.x->setValue(starCenter.x());
2881     ditherDialog.y->setValue(starCenter.y());
2882 
2883     if (container.exec() == QDialog::Accepted)
2884     {
2885         if (ditherDialog.magnitudeR->isChecked())
2886             guider->dither(ditherDialog.magnitude->value());
2887         else
2888         {
2889             InternalGuider * const ig = dynamic_cast<InternalGuider *>(guider);
2890             if (ig)
2891                 ig->ditherXY(ditherDialog.x->value(), ditherDialog.y->value());
2892         }
2893     }
2894 }
2895 
connectGuider()2896 bool Guide::connectGuider()
2897 {
2898     return guider->Connect();
2899 }
2900 
disconnectGuider()2901 bool Guide::disconnectGuider()
2902 {
2903     return guider->Disconnect();
2904 }
2905 
initPlots()2906 void Guide::initPlots()
2907 {
2908     initDriftGraph();
2909     initCalibrationPlot();
2910 
2911     connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange);
2912     connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange);
2913 
2914     //This sets the values of all the Graph Options that are stored.
2915     accuracyRadiusSpin->setValue(Options::guiderAccuracyThreshold());
2916     showRAPlotCheck->setChecked(Options::rADisplayedOnGuideGraph());
2917     showDECPlotCheck->setChecked(Options::dEDisplayedOnGuideGraph());
2918     showRACorrectionsCheck->setChecked(Options::rACorrDisplayedOnGuideGraph());
2919     showDECorrectionsCheck->setChecked(Options::dECorrDisplayedOnGuideGraph());
2920     showSNRPlotCheck->setChecked(Options::sNRDisplayedOnGuideGraph());
2921     showRMSPlotCheck->setChecked(Options::rMSDisplayedOnGuideGraph());
2922 
2923     buildTarget();
2924 }
2925 
initDriftGraph()2926 void Guide::initDriftGraph()
2927 {
2928     //Dragging and zooming settings
2929     // make bottom axis transfer its range to the top axis if the graph gets zoomed:
2930     connect(driftGraph->xAxis,  static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2931             driftGraph->xAxis2, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::setRange));
2932     // update the second vertical axis properly if the graph gets zoomed.
2933     connect(driftGraph->yAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2934             [this]()
2935     {
2936         driftGraph->setCorrectionGraphScale(correctionSlider->value());
2937     });
2938 
2939     connect(driftGraph, &QCustomPlot::mouseMove, driftGraph, &GuideDriftGraph::mouseOverLine);
2940     connect(driftGraph, &QCustomPlot::mousePress, driftGraph, &GuideDriftGraph::mouseClicked);
2941 
2942     int scale =
2943         50;  //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg
2944     correctionSlider->setValue(scale);
2945 }
2946 
initCalibrationPlot()2947 void Guide::initCalibrationPlot()
2948 {
2949     calibrationPlot->setBackground(QBrush(Qt::black));
2950     calibrationPlot->setSelectionTolerance(10);
2951 
2952     calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1));
2953     calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1));
2954 
2955     calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1));
2956     calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1));
2957 
2958     calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1));
2959     calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1));
2960 
2961     calibrationPlot->xAxis->setTickLabelColor(Qt::white);
2962     calibrationPlot->yAxis->setTickLabelColor(Qt::white);
2963 
2964     calibrationPlot->xAxis->setLabelColor(Qt::white);
2965     calibrationPlot->yAxis->setLabelColor(Qt::white);
2966 
2967     calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10));
2968     calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10));
2969     calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9));
2970     calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9));
2971 
2972     calibrationPlot->xAxis->setLabelPadding(2);
2973     calibrationPlot->yAxis->setLabelPadding(2);
2974 
2975     calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2976     calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2977     calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2978     calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2979     calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2980     calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2981 
2982     calibrationPlot->xAxis->setLabel(i18n("x (pixels)"));
2983     calibrationPlot->yAxis->setLabel(i18n("y (pixels)"));
2984 
2985     calibrationPlot->xAxis->setRange(-20, 20);
2986     calibrationPlot->yAxis->setRange(-20, 20);
2987 
2988     calibrationPlot->setInteractions(QCP::iRangeZoom);
2989     calibrationPlot->setInteraction(QCP::iRangeDrag, true);
2990 
2991     calibrationPlot->addGraph();
2992     calibrationPlot->graph(GuideGraph::G_RA)->setLineStyle(QCPGraph::lsNone);
2993     calibrationPlot->graph(GuideGraph::G_RA)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
2994             QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 6));
2995     calibrationPlot->graph(GuideGraph::G_RA)->setName("RA out");
2996 
2997     calibrationPlot->addGraph();
2998     calibrationPlot->graph(GuideGraph::G_DEC)->setLineStyle(QCPGraph::lsNone);
2999     calibrationPlot->graph(GuideGraph::G_DEC)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::white, 2),
3000             QBrush(), 4));
3001     calibrationPlot->graph(GuideGraph::G_DEC)->setName("RA in");
3002 
3003     calibrationPlot->addGraph();
3004     calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
3005     calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlus, QPen(Qt::white,
3006             2),
3007             QBrush(), 6));
3008     calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setName("Backlash");
3009 
3010     calibrationPlot->addGraph();
3011     calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
3012     calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
3013             QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 6));
3014     calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setName("DEC out");
3015 
3016     calibrationPlot->addGraph();
3017     calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setLineStyle(QCPGraph::lsNone);
3018     calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::yellow,
3019             2),
3020             QBrush(), 4));
3021     calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setName("DEC in");
3022 
3023     calLabel = new QCPItemText(calibrationPlot);
3024     calLabel->setColor(QColor(255, 255, 255));
3025     calLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter);
3026     calLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
3027     calLabel->position->setCoords(0.5, 0);
3028     calLabel->setText("");
3029     calLabel->setFont(QFont(font().family(), 10));
3030     calLabel->setVisible(true);
3031 
3032     calibrationPlot->resize(190, 190);
3033     calibrationPlot->replot();
3034 }
3035 
initView()3036 void Guide::initView()
3037 {
3038     guideStateWidget = new GuideStateWidget();
3039     guideInfoLayout->insertWidget(0, guideStateWidget);
3040 
3041     guideView = new GuideView(guideWidget, FITS_GUIDE);
3042     guideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
3043     guideView->setBaseSize(guideWidget->size());
3044     guideView->createFloatingToolBar();
3045     QVBoxLayout *vlayout = new QVBoxLayout();
3046     vlayout->addWidget(guideView);
3047     guideWidget->setLayout(vlayout);
3048     connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar);
3049 }
3050 
initConnections()3051 void Guide::initConnections()
3052 {
3053     // Exposure Timeout
3054     captureTimeout.setSingleShot(true);
3055     connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout);
3056 
3057     // Guiding Box Size
3058     connect(boxSizeCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
3059             &Ekos::Guide::updateTrackingBoxSize);
3060 
3061     // Guider CCD Selection
3062     connect(guiderCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), this,
3063             &Ekos::Guide::setDefaultCCD);
3064     connect(guiderCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this,
3065             [&](int index)
3066     {
3067         if (guiderType == GUIDE_INTERNAL)
3068         {
3069             starCenter = QVector3D();
3070             checkCCD(index);
3071         }
3072     }
3073            );
3074 
3075     FOVScopeCombo->setCurrentIndex(Options::guideScopeType());
3076     connect(FOVScopeCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
3077             &Ekos::Guide::updateTelescopeType);
3078 
3079     // Dark Frame Check
3080     connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled);
3081     // Subframe check
3082     if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method
3083         connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled);
3084     // ST4 Selection
3085     connect(ST4Combo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), [&](const QString & text)
3086     {
3087         setDefaultST4(text);
3088         setST4(text);
3089     });
3090 
3091     // Binning Combo Selection
3092     connect(binningCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
3093             &Ekos::Guide::updateCCDBin);
3094 
3095     // RA/DEC Enable directions
3096     connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA);
3097     connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
3098 
3099     // N/W and W/E direction enable
3100     connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
3101     connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
3102     connect(westControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
3103     connect(eastControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
3104 
3105     // Auto star check
3106     connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3107 
3108     // Declination Swap
3109     connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap);
3110 
3111     // PID Control - Proportional Gain
3112     connect(opsGuide->spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3113     connect(opsGuide->spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3114 
3115     // PID Control - Integral Gain
3116     connect(opsGuide->spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3117     connect(opsGuide->spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3118 
3119     // Max Pulse Duration (ms)
3120     connect(opsGuide->spinBox_MaxPulseArcSecRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3121     connect(opsGuide->spinBox_MaxPulseArcSecDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3122 
3123     // Min Pulse Duration (ms)
3124     connect(opsGuide->spinBox_MinPulseArcSecRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3125     connect(opsGuide->spinBox_MinPulseArcSecDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3126 
3127     // Capture
3128     connect(captureB, &QPushButton::clicked, this, [this]()
3129     {
3130         state = GUIDE_CAPTURE;
3131         emit newStatus(state);
3132 
3133         if(guiderType == GUIDE_PHD2)
3134         {
3135             configurePHD2Camera();
3136             if(phd2Guider->isCurrentCameraNotInEkos())
3137                 appendLogText(
3138                     i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images.  But you will still see the Guide Star Image when you guide."));
3139             else if(Options::guideSubframeEnabled())
3140             {
3141                 appendLogText(
3142                     i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked.  Unchecking it now to enable your image captures.  You can re-enable it before Guiding"));
3143                 subFrameCheck->setChecked(false);
3144             }
3145             phd2Guider->captureSingleFrame();
3146         }
3147         else if (guiderType == GUIDE_INTERNAL)
3148             capture();
3149     });
3150 
3151     // Framing
3152     connect(loopB, &QPushButton::clicked, this, &Guide::loop);
3153 
3154     // Stop
3155     connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort);
3156 
3157     // Clear Calibrate
3158     //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate()));
3159     connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration);
3160 
3161     // Guide
3162     connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide);
3163 
3164     // Connect External Guide
3165     connect(externalConnectB, &QPushButton::clicked, this, [&]()
3166     {
3167         //setExternalGuiderBLOBEnabled(false);
3168         guider->Connect();
3169     });
3170     connect(externalDisconnectB, &QPushButton::clicked, this, [&]()
3171     {
3172         //setExternalGuiderBLOBEnabled(true);
3173         guider->Disconnect();
3174     });
3175 
3176     // Pulse Timer
3177     pulseTimer.setSingleShot(true);
3178     connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture);
3179 
3180     //This connects all the buttons and slider below the guide plots.
3181     connect(accuracyRadiusSpin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
3182             &Ekos::Guide::buildTarget);
3183     connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory);
3184     connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint);
3185     connect(showRAPlotCheck, &QCheckBox::toggled, [this](bool isChecked)
3186     {
3187         driftGraph->toggleShowPlot(GuideGraph::G_RA, isChecked);
3188     });
3189     connect(showDECPlotCheck, &QCheckBox::toggled, [this](bool isChecked)
3190     {
3191         driftGraph->toggleShowPlot(GuideGraph::G_DEC, isChecked);
3192     });
3193     connect(showRACorrectionsCheck, &QCheckBox::toggled, [this](bool isChecked)
3194     {
3195         driftGraph->toggleShowPlot(GuideGraph::G_RA_PULSE, isChecked);
3196     });
3197     connect(showDECorrectionsCheck, &QCheckBox::toggled, [this](bool isChecked)
3198     {
3199         driftGraph->toggleShowPlot(GuideGraph::G_DEC_PULSE, isChecked);
3200     });
3201     connect(showSNRPlotCheck, &QCheckBox::toggled, [this](bool isChecked)
3202     {
3203         driftGraph->toggleShowPlot(GuideGraph::G_SNR, isChecked);
3204     });
3205     connect(showRMSPlotCheck, &QCheckBox::toggled, [this](bool isChecked)
3206     {
3207         driftGraph->toggleShowPlot(GuideGraph::G_RMS, isChecked);
3208     });
3209     connect(correctionSlider, &QSlider::sliderMoved, driftGraph, &GuideDriftGraph::setCorrectionGraphScale);
3210 
3211     connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither);
3212 
3213     connect(this, &Ekos::Guide::newStatus, guideStateWidget, &Ekos::GuideStateWidget::updateGuideStatus);
3214 }
3215 
removeDevice(ISD::GDInterface * device)3216 void Guide::removeDevice(ISD::GDInterface *device)
3217 {
3218     device->disconnect(this);
3219     if (currentTelescope && (currentTelescope->getDeviceName() == device->getDeviceName()))
3220     {
3221         currentTelescope = nullptr;
3222     }
3223     else if (CCDs.contains(static_cast<ISD::CCD *>(device)))
3224     {
3225         CCDs.removeAll(static_cast<ISD::CCD *>(device));
3226         guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName()));
3227         guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName() + QString(" Guider")));
3228         if (CCDs.empty())
3229         {
3230             currentCCD = nullptr;
3231             guiderCombo->setCurrentIndex(-1);
3232         }
3233         else
3234         {
3235             currentCCD = CCDs[0];
3236             guiderCombo->setCurrentIndex(0);
3237         }
3238 
3239         QTimer::singleShot(1000, this, [this]()
3240         {
3241             checkCCD();
3242         });
3243     }
3244 
3245     auto st4 = std::find_if(ST4List.begin(), ST4List.end(), [device](ISD::ST4 * st)
3246     {
3247         return (st->getDeviceName() == device->getDeviceName());
3248     });
3249 
3250     if (st4 != ST4List.end())
3251     {
3252         ST4List.removeOne(*st4);
3253 
3254         ST4Combo->removeItem(ST4Combo->findText(device->getDeviceName()));
3255         if (ST4List.empty())
3256         {
3257             ST4Driver = GuideDriver = nullptr;
3258         }
3259         else
3260         {
3261             setST4(ST4Combo->currentText());
3262         }
3263     }
3264 
3265 }
3266 
getSettings() const3267 QJsonObject Guide::getSettings() const
3268 {
3269     QJsonObject settings;
3270 
3271     settings.insert("camera", guiderCombo->currentText());
3272     settings.insert("via", ST4Combo->currentText());
3273     settings.insert("exp", exposureIN->value());
3274     settings.insert("bin", qMax(1, binningCombo->currentIndex() + 1));
3275     settings.insert("dark", darkFrameCheck->isChecked());
3276     settings.insert("box", boxSizeCombo->currentText());
3277     settings.insert("ra_control", checkBox_DirRA->isChecked());
3278     settings.insert("de_control", checkBox_DirDEC->isChecked());
3279     settings.insert("east", eastControlCheck->isChecked());
3280     settings.insert("west", westControlCheck->isChecked());
3281     settings.insert("north", northControlCheck->isChecked());
3282     settings.insert("south", southControlCheck->isChecked());
3283     settings.insert("scope", qMax(0, FOVScopeCombo->currentIndex()));
3284     settings.insert("swap", swapCheck->isChecked());
3285     settings.insert("ra_gain", opsGuide->spinBox_PropGainRA->value());
3286     settings.insert("de_gain", opsGuide->spinBox_PropGainDEC->value());
3287     settings.insert("dither_enabled", Options::ditherEnabled());
3288     settings.insert("dither_pixels", Options::ditherPixels());
3289     settings.insert("dither_frequency", static_cast<int>(Options::ditherFrames()));
3290     settings.insert("gpGuideGraph::G_enabled", Options::gPGEnabled());
3291 
3292     return settings;
3293 }
3294 
setSettings(const QJsonObject & settings)3295 void Guide::setSettings(const QJsonObject &settings)
3296 {
3297     static bool init = false;
3298 
3299     auto syncControl = [settings](const QString & key, QWidget * widget)
3300     {
3301         QSpinBox *pSB = nullptr;
3302         QDoubleSpinBox *pDSB = nullptr;
3303         QCheckBox *pCB = nullptr;
3304         QComboBox *pComboBox = nullptr;
3305 
3306         if ((pSB = qobject_cast<QSpinBox *>(widget)))
3307         {
3308             const int value = settings[key].toInt(pSB->value());
3309             if (value != pSB->value())
3310             {
3311                 pSB->setValue(value);
3312                 return true;
3313             }
3314         }
3315         else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
3316         {
3317             const double value = settings[key].toDouble(pDSB->value());
3318             if (value != pDSB->value())
3319             {
3320                 pDSB->setValue(value);
3321                 return true;
3322             }
3323         }
3324         else if ((pCB = qobject_cast<QCheckBox *>(widget)))
3325         {
3326             const bool value = settings[key].toBool(pCB->isChecked());
3327             if (value != pCB->isChecked())
3328             {
3329                 pCB->setChecked(value);
3330                 return true;
3331             }
3332         }
3333         // ONLY FOR STRINGS, not INDEX
3334         else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
3335         {
3336             const QString value = settings[key].toString(pComboBox->currentText());
3337             if (value != pComboBox->currentText())
3338             {
3339                 pComboBox->setCurrentText(value);
3340                 return true;
3341             }
3342         }
3343 
3344         return false;
3345     };
3346 
3347     // Camera
3348     if (syncControl("camera", guiderCombo) || init == false)
3349         checkCCD();
3350     // Via
3351     syncControl("via", ST4Combo);
3352     // Exposure
3353     syncControl("exp", exposureIN);
3354     // Binning
3355     const int bin = settings["bin"].toInt(binningCombo->currentIndex() + 1) - 1;
3356     if (bin != binningCombo->currentIndex())
3357         binningCombo->setCurrentIndex(bin);
3358     // Dark
3359     syncControl("dark", darkFrameCheck);
3360     // Box
3361     syncControl("box", boxSizeCombo);
3362     // Swap
3363     syncControl("swap", swapCheck);
3364     // RA Control
3365     syncControl("ra_control", checkBox_DirRA);
3366     // DE Control
3367     syncControl("de_control", checkBox_DirDEC);
3368     // NSWE controls
3369     syncControl("east", eastControlCheck);
3370     syncControl("west", westControlCheck);
3371     syncControl("north", northControlCheck);
3372     syncControl("south", southControlCheck);
3373     // Scope
3374     const int scope = settings["scope"].toInt(FOVScopeCombo->currentIndex());
3375     if (scope >= 0 && scope != FOVScopeCombo->currentIndex())
3376         FOVScopeCombo->setCurrentIndex(scope);
3377     // RA Gain
3378     syncControl("ra_gain", opsGuide->spinBox_PropGainRA);
3379     // DE Gain
3380     syncControl("de_gain", opsGuide->spinBox_PropGainDEC);
3381     // Options
3382     const bool ditherEnabled = settings["dither_enabled"].toBool(Options::ditherEnabled());
3383     Options::setDitherEnabled(ditherEnabled);
3384     const double ditherPixels = settings["dither_pixels"].toDouble(Options::ditherPixels());
3385     Options::setDitherPixels(ditherPixels);
3386     const int ditherFrequency = settings["dither_frequency"].toInt(Options::ditherFrames());
3387     Options::setDitherFrames(ditherFrequency);
3388     const bool gpg = settings["gpGuideGraph::G_enabled"].toBool(Options::gPGEnabled());
3389     Options::setGPGEnabled(gpg);
3390 
3391     init = true;
3392 }
3393 
loop()3394 void Guide::loop()
3395 {
3396     state = GUIDE_LOOPING;
3397     emit newStatus(state);
3398 
3399     if(guiderType == GUIDE_PHD2)
3400     {
3401         configurePHD2Camera();
3402         if(phd2Guider->isCurrentCameraNotInEkos())
3403             appendLogText(
3404                 i18n("The PHD2 camera is not available to Ekos, so you cannot see the captured images.  But you will still see the Guide Star Image when you guide."));
3405         else if(Options::guideSubframeEnabled())
3406         {
3407             appendLogText(
3408                 i18n("To receive PHD2 images other than the Guide Star Image, SubFrame must be unchecked.  Unchecking it now to enable your image captures.  You can re-enable it before Guiding"));
3409             subFrameCheck->setChecked(false);
3410         }
3411         phd2Guider->loop();
3412         stopB->setEnabled(true);
3413     }
3414     else if (guiderType == GUIDE_INTERNAL)
3415         capture();
3416 }
3417 }
3418