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