Kstars

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

KDE's Doxygen guidelines are available online.