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 break;
2225 case GuideInterface::BACKLASH:
2226 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->addData(dx, dy);
2227 break;
2228 case GuideInterface::DEC_OUT:
2229 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(dx, dy);
2230 break;
2231 case GuideInterface::DEC_OUT_OK:
2232 drawRADECAxis(calDECLabel, calDECArrow, dx, dy);
2233 break;
2234 case GuideInterface::DEC_IN:
2235 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->addData(dx, dy);
2236 break;
2237 case GuideInterface::CALIBRATION_MESSAGE_ONLY:
2238 ;
2239 }
2240 calLabel->setText(message);
2241 calibrationPlot->replot();
2242}
2243
2244void Guide::drawRADECAxis(QCPItemText *Label, QCPItemLine *Arrow, const double xEnd, const double yEnd)
2245{
2246
2247 Arrow->start->setCoords(0, 0);
2248 Arrow->end->setCoords(xEnd, yEnd);
2250 Label->position->setCoords(xEnd, yEnd);
2251 Label->setColor(Qt::white);
2252 yEnd > 0 ? Label->setPositionAlignment(Qt::AlignHCenter | Qt::AlignBottom) :
2253 Label->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop);
2254 Arrow->setVisible(true);
2255 Label->setVisible(true);
2256}
2257
2258void Guide::setAxisSigma(double ra, double de)
2259{
2260 l_ErrRA->setText(QString::number(ra, 'f', 2));
2261 l_ErrDEC->setText(QString::number(de, 'f', 2));
2262 const double total = std::hypot(ra, de);
2263 l_TotalRMS->setText(QString::number(total, 'f', 2));
2264
2265 emit newAxisSigma(ra, de);
2266}
2267
2268QList<double> Guide::axisDelta()
2269{
2270 QList<double> delta;
2271
2272 delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble();
2273
2274 return delta;
2275}
2276
2277QList<double> Guide::axisSigma()
2278{
2279 QList<double> sigma;
2280
2281 sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble();
2282
2283 return sigma;
2284}
2285
2286void Guide::setAxisPulse(double ra, double de)
2287{
2288 l_PulseRA->setText(QString::number(static_cast<int>(ra)));
2289 l_PulseDEC->setText(QString::number(static_cast<int>(de)));
2290}
2291
2292void Guide::setSNR(double snr)
2293{
2294 l_SNR->setText(QString::number(snr, 'f', 1));
2295}
2296
2297void Guide::buildOperationStack(GuideState operation)
2298{
2299 operationStack.clear();
2300
2301 switch (operation)
2302 {
2303 case GUIDE_CAPTURE:
2304 if (guideDarkFrame->isChecked())
2305 operationStack.push(GUIDE_DARK);
2306
2307 operationStack.push(GUIDE_CAPTURE);
2308 operationStack.push(GUIDE_SUBFRAME);
2309 break;
2310
2311 case GUIDE_CALIBRATING:
2312 operationStack.push(GUIDE_CALIBRATING);
2313 if (guiderType == GUIDE_INTERNAL)
2314 {
2315 if (guideDarkFrame->isChecked())
2316 operationStack.push(GUIDE_DARK);
2317
2318 // Auto Star Selected Path
2319 if (guideAutoStar->isChecked() ||
2320 // SEP MultiStar always uses an automated guide star.
2321 internalGuider->SEPMultiStarEnabled())
2322 {
2323 // If subframe is enabled and we need to auto select a star, then we need to make the final capture
2324 // of the subframed image. This is only done if we aren't already subframed.
2325 if (subFramed == false && guideSubframe->isChecked())
2326 operationStack.push(GUIDE_CAPTURE);
2327
2328 operationStack.push(GUIDE_SUBFRAME);
2329 operationStack.push(GUIDE_STAR_SELECT);
2330
2331
2332 operationStack.push(GUIDE_CAPTURE);
2333
2334 // If we are being ask to go full frame, let's do that first
2335 if (subFramed == true && guideSubframe->isChecked() == false)
2336 operationStack.push(GUIDE_SUBFRAME);
2337 }
2338 // Manual Star Selection Path
2339 else
2340 {
2341 // Final capture before we start calibrating
2342 if (subFramed == false && guideSubframe->isChecked())
2343 operationStack.push(GUIDE_CAPTURE);
2344
2345 // Subframe if required
2346 operationStack.push(GUIDE_SUBFRAME);
2347
2348 // First capture an image
2349 operationStack.push(GUIDE_CAPTURE);
2350 }
2351
2352 }
2353 break;
2354
2355 default:
2356 break;
2357 }
2358}
2359
2360bool Guide::executeOperationStack()
2361{
2362 if (operationStack.isEmpty())
2363 return false;
2364
2365 GuideState nextOperation = operationStack.pop();
2366 // qCDebug(KSTARS_EKOS_GUIDE) << "Executing operation " << getGuideStatusString(nextOperation);
2367
2368 bool actionRequired = false;
2369
2370 switch (nextOperation)
2371 {
2372 case GUIDE_SUBFRAME:
2373 actionRequired = executeOneOperation(nextOperation);
2374 break;
2375
2376 case GUIDE_DARK:
2377 actionRequired = executeOneOperation(nextOperation);
2378 break;
2379
2380 case GUIDE_CAPTURE:
2381 actionRequired = captureOneFrame();
2382 break;
2383
2384 case GUIDE_STAR_SELECT:
2385 actionRequired = executeOneOperation(nextOperation);
2386 break;
2387
2388 case GUIDE_CALIBRATING:
2389 if (guiderType == GUIDE_INTERNAL)
2390 {
2391 m_GuiderInstance->setStarPosition(starCenter);
2392
2393 // Tracking must be engaged
2394 if (m_Mount && m_Mount->canControlTrack() && m_Mount->isTracking() == false)
2395 m_Mount->setTrackEnabled(true);
2396 }
2397
2398 if (m_GuiderInstance->calibrate())
2399 {
2400 if (guiderType == GUIDE_INTERNAL)
2401 disconnect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Guide::setTrackingStar);
2402 setBusy(true);
2403 }
2404 else
2405 {
2406 emit newStatus(GUIDE_CALIBRATION_ERROR);
2407 m_State = GUIDE_IDLE;
2408 appendLogText(i18n("Calibration failed to start."));
2409 setBusy(false);
2410 }
2411 break;
2412
2413 default:
2414 break;
2415 }
2416
2417 // If an additional action is required, return return and continue later
2418 if (actionRequired)
2419 return true;
2420 // Otherwise, continue processing the stack
2421 else
2422 return executeOperationStack();
2423}
2424
2425bool Guide::executeOneOperation(GuideState operation)
2426{
2427 bool actionRequired = false;
2428
2429 if (m_Camera == nullptr)
2430 return actionRequired;
2431
2432 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2433 if (targetChip == nullptr)
2434 return false;
2435
2436 int subBinX, subBinY;
2437 targetChip->getBinning(&subBinX, &subBinY);
2438
2439 switch (operation)
2440 {
2441 case GUIDE_SUBFRAME:
2442 {
2443 // SEP MultiStar doesn't subframe.
2444 if ((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled())
2445 break;
2446 // Check if we need and can subframe
2447 if (subFramed == false && guideSubframe->isChecked() == true && targetChip->canSubframe())
2448 {
2449 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2450 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2451
2452 int offset = guideSquareSize->currentText().toInt() / subBinX;
2453
2454 int x = starCenter.x();
2455 int y = starCenter.y();
2456
2457 x = (x - offset * 2) * subBinX;
2458 y = (y - offset * 2) * subBinY;
2459 int w = offset * 4 * subBinX;
2460 int h = offset * 4 * subBinY;
2461
2462 if (x < minX)
2463 x = minX;
2464 if (y < minY)
2465 y = minY;
2466 if ((x + w) > maxW)
2467 w = maxW - x;
2468 if ((y + h) > maxH)
2469 h = maxH - y;
2470
2471 targetChip->setFrame(x, y, w, h);
2472
2473 subFramed = true;
2474 QVariantMap settings = frameSettings[targetChip];
2475 settings["x"] = x;
2476 settings["y"] = y;
2477 settings["w"] = w;
2478 settings["h"] = h;
2479 settings["binx"] = subBinX;
2480 settings["biny"] = subBinY;
2481
2482 frameSettings[targetChip] = settings;
2483
2484 starCenter.setX(w / (2 * subBinX));
2485 starCenter.setY(h / (2 * subBinX));
2486 }
2487 // Otherwise check if we are already subframed
2488 // and we need to go back to full frame
2489 // or if we need to go back to full frame since we need
2490 // to reaquire a star
2491 else if (subFramed &&
2492 (guideSubframe->isChecked() == false ||
2493 m_State == GUIDE_REACQUIRE))
2494 {
2495 targetChip->resetFrame();
2496
2497 int x, y, w, h;
2498 targetChip->getFrame(&x, &y, &w, &h);
2499
2500 QVariantMap settings;
2501 settings["x"] = x;
2502 settings["y"] = y;
2503 settings["w"] = w;
2504 settings["h"] = h;
2505 settings["binx"] = subBinX;
2506 settings["biny"] = subBinY;
2507 frameSettings[targetChip] = settings;
2508
2509 subFramed = false;
2510
2511 starCenter.setX(w / (2 * subBinX));
2512 starCenter.setY(h / (2 * subBinX));
2513
2514 //starCenter.setX(0);
2515 //starCenter.setY(0);
2516 }
2517 }
2518 break;
2519
2520 case GUIDE_DARK:
2521 {
2522 // Do we need to take a dark frame?
2523 if (m_ImageData && guideDarkFrame->isChecked())
2524 {
2525 QVariantMap settings = frameSettings[targetChip];
2526 uint16_t offsetX = 0;
2527 uint16_t offsetY = 0;
2528
2529 if (settings["x"].isValid() &&
2530 settings["y"].isValid() &&
2531 settings["binx"].isValid() &&
2532 settings["biny"].isValid())
2533 {
2534 offsetX = settings["x"].toInt() / settings["binx"].toInt();
2535 offsetY = settings["y"].toInt() / settings["biny"].toInt();
2536 }
2537
2538 actionRequired = true;
2539 targetChip->setCaptureFilter(FITS_NONE);
2540 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()),
2541 targetChip, m_ImageData, guideExposure->value(), offsetX, offsetY);
2542 }
2543 }
2544 break;
2545
2546 case GUIDE_STAR_SELECT:
2547 {
2548 m_State = GUIDE_STAR_SELECT;
2549 emit newStatus(m_State);
2550
2551 if (guideAutoStar->isChecked() ||
2552 // SEP MultiStar always uses an automated guide star.
2553 ((guiderType == GUIDE_INTERNAL) &&
2554 internalGuider->SEPMultiStarEnabled()))
2555 {
2556 bool autoStarCaptured = internalGuider->selectAutoStar();
2557 if (autoStarCaptured)
2558 {
2559 appendLogText(i18n("Auto star selected."));
2560 }
2561 else
2562 {
2563 appendLogText(i18n("Failed to select an auto star."));
2564 actionRequired = true;
2565 m_State = GUIDE_CALIBRATION_ERROR;
2566 emit newStatus(m_State);
2567 setBusy(false);
2568 }
2569 }
2570 else
2571 {
2572 appendLogText(i18n("Select a guide star to calibrate."));
2573 actionRequired = true;
2574 }
2575 }
2576 break;
2577
2578 default:
2579 break;
2580 }
2581
2582 return actionRequired;
2583}
2584
2585void Guide::processGuideOptions()
2586{
2587 if (Options::guiderType() != guiderType)
2588 {
2589 guiderType = static_cast<GuiderType>(Options::guiderType());
2590 setGuiderType(Options::guiderType());
2591 }
2592}
2593
2594void Guide::showFITSViewer()
2595{
2596 static int lastFVTabID = -1;
2597 if (m_ImageData)
2598 {
2599 QUrl url = QUrl::fromLocalFile("guide.fits");
2600 if (fv.isNull())
2601 {
2602 fv = KStars::Instance()->createFITSViewer();
2603 fv->loadData(m_ImageData, url, &lastFVTabID);
2604 connect(fv.get(), &FITSViewer::terminated, this, [this]()
2605 {
2606 fv.clear();
2607 });
2608 }
2609 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false)
2610 fv->loadData(m_ImageData, url, &lastFVTabID);
2611
2612 fv->show();
2613 }
2614}
2615
2616void Guide::setExternalGuiderBLOBEnabled(bool enable)
2617{
2618 // Nothing to do if guider is internal
2619 if (guiderType == GUIDE_INTERNAL)
2620 return;
2621
2622 if(!m_Camera)
2623 return;
2624
2625 m_Camera->setBLOBEnabled(enable);
2626
2627 if(m_Camera->isBLOBEnabled())
2628 {
2629 checkUseGuideHead();
2630
2631 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2632 if (targetChip)
2633 targetChip->setCaptureMode(FITS_GUIDE);
2634 syncCameraInfo();
2635 }
2636
2637}
2638
2640{
2641 // reset non guided dither total drift
2642 nonGuidedDitherRaOffsetMsec = 0;
2643 nonGuidedDitherDecOffsetMsec = 0;
2644 qCDebug(KSTARS_EKOS_GUIDE) << "Reset non guiding dithering position";
2645
2646 // initialize random generator if not done before
2647 if (!isNonGuidedDitherInitialized)
2648 {
2649 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
2650 nonGuidedPulseGenerator.seed(seed);
2651 isNonGuidedDitherInitialized = true;
2652 qCDebug(KSTARS_EKOS_GUIDE) << "Initialize non guiding dithering random generator";
2653 }
2654}
2655
2656void Guide::nonGuidedDither()
2657{
2658 double ditherPulse = Options::ditherNoGuidingPulse();
2659
2660 // Randomize dithering position up to +/-dithePulse distance from original
2661 std::uniform_int_distribution<int> newPos(-ditherPulse, +ditherPulse);
2662
2663 // Calculate the pulse needed to move to the new position, then save the new position and apply the pulse
2664
2665 // for ra
2666 const int newRaOffsetMsec = newPos(nonGuidedPulseGenerator);
2667 const int raPulse = nonGuidedDitherRaOffsetMsec - newRaOffsetMsec;
2668 nonGuidedDitherRaOffsetMsec = newRaOffsetMsec;
2669 const int raMsec = std::abs(raPulse);
2670 const int raPolarity = (raPulse >= 0 ? 1 : -1);
2671
2672 // and for dec
2673 const int newDecOffsetMsec = newPos(nonGuidedPulseGenerator);
2674 const int decPulse = nonGuidedDitherDecOffsetMsec - newDecOffsetMsec;
2675 nonGuidedDitherDecOffsetMsec = newDecOffsetMsec;
2676 const int decMsec = std::abs(decPulse);
2677 const int decPolarity = (decPulse >= 0 ? 1 : -1);
2678
2679 qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither...";
2680 qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << raMsec << "ra_polarity:" << raPolarity << "de_msec:" << decMsec <<
2681 "de_polarity:" << decPolarity;
2682
2683 bool rc = sendMultiPulse(raPolarity > 0 ? RA_INC_DIR : RA_DEC_DIR, raMsec, decPolarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR,
2684 decMsec, DontCaptureAfterPulses);
2685
2686 if (rc)
2687 {
2688 qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful.";
2689 QTimer::singleShot( (raMsec > decMsec ? raMsec : decMsec) + Options::ditherSettle() * 1000 + 100, this, [this]()
2690 {
2691 emit newStatus(GUIDE_DITHERING_SUCCESS);
2692 m_State = GUIDE_IDLE;
2693 });
2694 }
2695 else
2696 {
2697 qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed.";
2698 emit newStatus(GUIDE_DITHERING_ERROR);
2699 m_State = GUIDE_IDLE;
2700 }
2701}
2702
2703void Guide::handleManualDither()
2704{
2705 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2706 if (targetChip == nullptr)
2707 return;
2708
2709 Ui::ManualDither ditherDialog;
2710 QDialog container(this);
2711 ditherDialog.setupUi(&container);
2712
2713 if (guiderType != GUIDE_INTERNAL)
2714 {
2715 ditherDialog.coordinatesR->setEnabled(false);
2716 ditherDialog.x->setEnabled(false);
2717 ditherDialog.y->setEnabled(false);
2718 }
2719
2720 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2721 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2722
2723 ditherDialog.x->setMinimum(minX);
2724 ditherDialog.x->setMaximum(maxX);
2725 ditherDialog.y->setMinimum(minY);
2726 ditherDialog.y->setMaximum(maxY);
2727
2728 ditherDialog.x->setValue(starCenter.x());
2729 ditherDialog.y->setValue(starCenter.y());
2730
2731 if (container.exec() == QDialog::Accepted)
2732 {
2733 if (ditherDialog.magnitudeR->isChecked())
2734 m_GuiderInstance->dither(ditherDialog.magnitude->value());
2735 else
2736 {
2737 InternalGuider * const ig = dynamic_cast<InternalGuider *>(m_GuiderInstance);
2738 if (ig)
2739 ig->ditherXY(ditherDialog.x->value(), ditherDialog.y->value());
2740 }
2741 }
2742}
2743
2745{
2746 setStatus(GUIDE_IDLE);
2747 return m_GuiderInstance->Connect();
2748}
2749
2751{
2752 return m_GuiderInstance->Disconnect();
2753}
2754
2755void Guide::initPlots()
2756{
2757 initDriftGraph();
2758 initCalibrationPlot();
2759
2760 connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange);
2761 connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange);
2762
2763 buildTarget();
2764}
2765
2766void Guide::initDriftGraph()
2767{
2768 //Dragging and zooming settings
2769 // make bottom axis transfer its range to the top axis if the graph gets zoomed:
2770 connect(driftGraph->xAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2771 driftGraph->xAxis2, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::setRange));
2772 // update the second vertical axis properly if the graph gets zoomed.
2773 connect(driftGraph->yAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2774 [this]()
2775 {
2776 driftGraph->setCorrectionGraphScale(correctionSlider->value());
2777 });
2778
2779 connect(driftGraph, &QCustomPlot::mouseMove, driftGraph, &GuideDriftGraph::mouseOverLine);
2780 connect(driftGraph, &QCustomPlot::mousePress, driftGraph, &GuideDriftGraph::mouseClicked);
2781
2782 int scale =
2783 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg
2784 correctionSlider->setValue(scale);
2785}
2786
2787void Guide::initCalibrationPlot()
2788{
2789 calibrationPlot->setBackground(QBrush(Qt::black));
2790 calibrationPlot->setSelectionTolerance(10);
2791
2792 calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1));
2793 calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1));
2794
2795 calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1));
2796 calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1));
2797
2798 calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1));
2799 calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1));
2800
2801 calibrationPlot->xAxis->setTickLabelColor(Qt::white);
2802 calibrationPlot->yAxis->setTickLabelColor(Qt::white);
2803
2804 calibrationPlot->xAxis->setLabelColor(Qt::white);
2805 calibrationPlot->yAxis->setLabelColor(Qt::white);
2806
2807 calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10));
2808 calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10));
2809 calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9));
2810 calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9));
2811
2812 calibrationPlot->xAxis->setLabelPadding(2);
2813 calibrationPlot->yAxis->setLabelPadding(2);
2814
2815 calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2816 calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2817 calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2818 calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2819 calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2820 calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2821
2822 calibrationPlot->xAxis->setLabel(i18n("dx (pixels)"));
2823 calibrationPlot->yAxis->setLabel(i18n("dy (pixels)"));
2824
2825 calibrationPlot->xAxis->setRange(-20, 20);
2826 calibrationPlot->yAxis->setRange(-20, 20);
2827
2828 calibrationPlot->setInteractions(QCP::iRangeZoom);
2829 calibrationPlot->setInteraction(QCP::iRangeDrag, true);
2830
2831 calibrationPlot->addGraph();
2832 calibrationPlot->graph(GuideGraph::G_RA)->setLineStyle(QCPGraph::lsNone);
2833 calibrationPlot->graph(GuideGraph::G_RA)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
2834 QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2),
2835 QBrush(), 6));
2836 calibrationPlot->graph(GuideGraph::G_RA)->setName("RA+");
2837
2838 calibrationPlot->addGraph();
2839 calibrationPlot->graph(GuideGraph::G_DEC)->setLineStyle(QCPGraph::lsNone);
2840 calibrationPlot->graph(GuideGraph::G_DEC)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle,
2841 QPen(Qt::white, 2),
2842 QBrush(), 4));
2843 calibrationPlot->graph(GuideGraph::G_DEC)->setName("RA-");
2844
2845 calibrationPlot->addGraph();
2846 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
2847 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlus,
2848 QPen(Qt::white, 2),
2849 QBrush(), 6));
2850 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setName("Backlash");
2851
2852 calibrationPlot->addGraph();
2853 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
2854 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
2855 QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2),
2856 QBrush(), 6));
2857 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setName("DEC+");
2858
2859 calibrationPlot->addGraph();
2860 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setLineStyle(QCPGraph::lsNone);
2861 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle,
2862 QPen(Qt::yellow, 2),
2863 QBrush(), 4));
2864 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setName("DEC-");
2865
2866 calLabel = new QCPItemText(calibrationPlot);
2867 calLabel->setColor(QColor(255, 255, 255));
2868 calLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter);
2869 calLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
2870 calLabel->position->setCoords(0.5, 0);
2871 calLabel->setText("");
2872 calLabel->setFont(QFont(font().family(), 10));
2873 calLabel->setVisible(true);
2874
2875 calRALabel = new QCPItemText(calibrationPlot);
2876 calRALabel->setText("RA");
2877 calRALabel->setColor(Qt::white);
2878 calRALabel->setPen(QPen(Qt::white, 1)); // Draw frame
2879 calRALabel->setVisible(false);
2880 calRAArrow = new QCPItemLine(calibrationPlot);
2881 calRAArrow->setPen(QPen(Qt::white, 1));
2882 calRAArrow->setHead(QCPLineEnding::esSpikeArrow);
2883 calRAArrow->setVisible(false);
2884
2885 calDECLabel = new QCPItemText(calibrationPlot);
2886 calDECLabel->setText("DEC");
2887 calDECLabel->setColor(Qt::white); // Draw frame
2888 calDECLabel->setPen(QPen(Qt::white, 1));
2889 calDECLabel->setVisible(false);
2890 calDECArrow = new QCPItemLine(calibrationPlot);
2891 calDECArrow->setPen(QPen(Qt::white, 1));
2892 calDECArrow->setHead(QCPLineEnding::esSpikeArrow);
2893 calDECArrow->setVisible(false);
2894
2895 calibrationPlot->resize(190, 190);
2896 calibrationPlot->replot();
2897}
2898
2899void Guide::initView()
2900{
2901 guideStateWidget = new GuideStateWidget();
2902 guideInfoLayout->insertWidget(-1, guideStateWidget);
2903
2904 m_GuideView.reset(new GuideView(guideWidget, FITS_GUIDE));
2905 m_GuideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
2906 m_GuideView->setBaseSize(guideWidget->size());
2907 m_GuideView->createFloatingToolBar();
2908 QVBoxLayout *vlayout = new QVBoxLayout();
2909 vlayout->addWidget(m_GuideView.get());
2910 guideWidget->setLayout(vlayout);
2911 connect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar);
2912 guideInfoLabel->setVisible(false);
2913 guideInfoText->setVisible(false);
2914}
2915
2916void Guide::initConnections()
2917{
2918 // Exposure Timeout
2919 captureTimeout.setSingleShot(true);
2920 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout);
2921
2922 // Setup Debounce timer to limit over-activation of settings changes
2923 m_DebounceTimer.setInterval(500);
2924 m_DebounceTimer.setSingleShot(true);
2925 connect(&m_DebounceTimer, &QTimer::timeout, this, &Guide::settleSettings);
2926
2927 // Guiding Box Size
2928 connect(guideSquareSize, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
2929 &Ekos::Guide::updateTrackingBoxSize);
2930
2931 // Dark Frame Check
2933 // Subframe check
2934 if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method
2936
2937 // Binning Combo Selection
2938 connect(guideBinning, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
2939 &Ekos::Guide::updateCCDBin);
2940
2941 // RA/DEC Enable directions
2942 connect(rAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA);
2943 connect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2944
2945 // N/W and W/E direction enable
2946 connect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2947 connect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2948 connect(westRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2949 connect(eastRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2950
2951 // Capture
2952 connect(captureB, &QPushButton::clicked, this, [this]()
2953 {
2954 m_State = GUIDE_CAPTURE;
2955 emit newStatus(m_State);
2956
2957 if(guiderType == GUIDE_PHD2)
2958 {
2959 configurePHD2Camera();
2960 if(phd2Guider->isCurrentCameraNotInEkos())
2961 appendLogText(
2962 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."));
2963 else if(guideSubframe->isChecked())
2964 {
2965 appendLogText(
2966 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"));
2967 guideSubframe->setChecked(false);
2968 }
2969 phd2Guider->captureSingleFrame();
2970 }
2971 else if (guiderType == GUIDE_INTERNAL)
2972 capture();
2973 });
2974
2975 // Framing
2976 connect(loopB, &QPushButton::clicked, this, &Guide::loop);
2977
2978 // Stop
2980
2981 // Clear Calibrate
2982 //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate()));
2983 connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration);
2984
2985 // Guide
2987
2988 // Connect External Guide
2989 connect(externalConnectB, &QPushButton::clicked, this, [&]()
2990 {
2991 //setExternalGuiderBLOBEnabled(false);
2992 m_GuiderInstance->Connect();
2993 });
2994 connect(externalDisconnectB, &QPushButton::clicked, this, [&]()
2995 {
2996 //setExternalGuiderBLOBEnabled(true);
2997 m_GuiderInstance->Disconnect();
2998 });
2999
3000 // Pulse Timer
3001 m_PulseTimer.setSingleShot(true);
3002 connect(&m_PulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture);
3003
3004 //This connects all the buttons and slider below the guide plots.
3005 connect(guiderAccuracyThreshold, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
3006 &Ekos::Guide::buildTarget);
3007 connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory);
3008 connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint);
3009 connect(rADisplayedOnGuideGraph, &QCheckBox::toggled, [this](bool isChecked)
3010 {
3011 driftGraph->toggleShowPlot(GuideGraph::G_RA, isChecked);
3012 });
3013 connect(dEDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
3014 {
3015 driftGraph->toggleShowPlot(GuideGraph::G_DEC, isChecked);
3016 });
3017 connect(rACorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
3018 {
3019 driftGraph->toggleShowPlot(GuideGraph::G_RA_PULSE, isChecked);
3020 });
3021 connect(dECorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
3022 {
3023 driftGraph->toggleShowPlot(GuideGraph::G_DEC_PULSE, isChecked);
3024 });
3025 connect(sNRDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
3026 {
3027 driftGraph->toggleShowPlot(GuideGraph::G_SNR, isChecked);
3028 });
3029 connect(rMSDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
3030 {
3031 driftGraph->toggleShowPlot(GuideGraph::G_RMS, isChecked);
3032 });
3033 connect(correctionSlider, &QSlider::sliderMoved, driftGraph, &GuideDriftGraph::setCorrectionGraphScale);
3034
3035 connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither);
3036
3037 connect(this, &Ekos::Guide::newStatus, guideStateWidget, &Ekos::GuideStateWidget::updateGuideStatus);
3038}
3039
3040void Guide::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
3041{
3042 auto name = device->getDeviceName();
3043
3044 device->disconnect(this);
3045
3046 // Mounts
3047 if (m_Mount && m_Mount->getDeviceName() == name)
3048 {
3049 m_Mount->disconnect(this);
3050 m_Mount = nullptr;
3051 }
3052
3053
3054 // Cameras
3055 if (m_Camera && m_Camera->getDeviceName() == name)
3056 {
3057 m_Camera->disconnect(this);
3058 m_Camera = nullptr;
3059 }
3060
3061
3062 // Guiders
3063 if (m_Guider && m_Guider->getDeviceName() == name)
3064 {
3065 m_Guider->disconnect(this);
3066 m_Guider = nullptr;
3067 }
3068
3069 // Adaptive Optics
3070 // FIXME AO are not yet utilized property in Guide module
3071 if (m_AO && m_AO->getDeviceName() == name)
3072 {
3073 m_AO->disconnect(this);
3074 m_AO = nullptr;
3075 }
3076}
3077
3079{
3080 m_State = GUIDE_LOOPING;
3081 emit newStatus(m_State);
3082
3083 if(guiderType == GUIDE_PHD2)
3084 {
3085 configurePHD2Camera();
3086 if(phd2Guider->isCurrentCameraNotInEkos())
3087 appendLogText(
3088 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."));
3089 else if(guideSubframe->isChecked())
3090 {
3091 appendLogText(
3092 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"));
3093 guideSubframe->setChecked(false);
3094 }
3095 phd2Guider->loop();
3096 stopB->setEnabled(true);
3097 }
3098 else if (guiderType == GUIDE_INTERNAL)
3099 capture();
3100}
3101
3102///////////////////////////////////////////////////////////////////////////////////////////
3103///
3104///////////////////////////////////////////////////////////////////////////////////////////
3105QVariantMap Guide::getAllSettings() const
3106{
3107 QVariantMap settings;
3108
3109 // All Combo Boxes
3110 for (auto &oneWidget : findChildren<QComboBox*>())
3111 settings.insert(oneWidget->objectName(), oneWidget->currentText());
3112
3113 // All Double Spin Boxes
3114 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3115 settings.insert(oneWidget->objectName(), oneWidget->value());
3116
3117 // All Spin Boxes
3118 for (auto &oneWidget : findChildren<QSpinBox*>())
3119 settings.insert(oneWidget->objectName(), oneWidget->value());
3120
3121 // All Checkboxes
3122 for (auto &oneWidget : findChildren<QCheckBox*>())
3123 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
3124
3125 return settings;
3126}
3127
3128///////////////////////////////////////////////////////////////////////////////////////////
3129///
3130///////////////////////////////////////////////////////////////////////////////////////////
3131void Guide::setAllSettings(const QVariantMap &settings)
3132{
3133 // Disconnect settings that we don't end up calling syncSettings while
3134 // performing the changes.
3135 disconnectSettings();
3136
3137 for (auto &name : settings.keys())
3138 {
3139 // Combo
3140 auto comboBox = findChild<QComboBox*>(name);
3141 if (comboBox)
3142 {
3143 syncControl(settings, name, comboBox);
3144 continue;
3145 }
3146
3147 // Double spinbox
3148 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
3149 if (doubleSpinBox)
3150 {
3151 syncControl(settings, name, doubleSpinBox);
3152 continue;
3153 }
3154
3155 // spinbox
3156 auto spinBox = findChild<QSpinBox*>(name);
3157 if (spinBox)
3158 {
3159 syncControl(settings, name, spinBox);
3160 continue;
3161 }
3162
3163 // checkbox
3164 auto checkbox = findChild<QCheckBox*>(name);
3165 if (checkbox)
3166 {
3167 syncControl(settings, name, checkbox);
3168 continue;
3169 }
3170 }
3171
3172 // Sync to options
3173 for (auto &key : settings.keys())
3174 {
3175 auto value = settings[key];
3176 // Save immediately
3177 Options::self()->setProperty(key.toLatin1(), value);
3178
3179 m_Settings[key] = value;
3180 m_GlobalSettings[key] = value;
3181 }
3182
3183 emit settingsUpdated(getAllSettings());
3184
3185 // Save to optical train specific settings as well
3186 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
3187 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings);
3188
3189 // Restablish connections
3190 connectSettings();
3191}
3192
3193///////////////////////////////////////////////////////////////////////////////////////////
3194///
3195///////////////////////////////////////////////////////////////////////////////////////////
3196bool Guide::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
3197{
3198 QSpinBox *pSB = nullptr;
3199 QDoubleSpinBox *pDSB = nullptr;
3200 QCheckBox *pCB = nullptr;
3201 QComboBox *pComboBox = nullptr;
3202 QRadioButton *pRadioButton = nullptr;
3203 bool ok = false;
3204
3205 if ((pSB = qobject_cast<QSpinBox *>(widget)))
3206 {
3207 const int value = settings[key].toInt(&ok);
3208 if (ok)
3209 {
3210 pSB->setValue(value);
3211 return true;
3212 }
3213 }
3214 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
3215 {
3216 const double value = settings[key].toDouble(&ok);
3217 if (ok)
3218 {
3219 pDSB->setValue(value);
3220 return true;
3221 }
3222 }
3223 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
3224 {
3225 const bool value = settings[key].toBool();
3226 if (value != pCB->isChecked())
3227 pCB->setChecked(value);
3228 return true;
3229 }
3230 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
3231 {
3232 const bool value = settings[key].toBool();
3233 if (value)
3234 pRadioButton->setChecked(true);
3235 return true;
3236 }
3237 // ONLY FOR STRINGS, not INDEX
3238 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
3239 {
3240 const QString value = settings[key].toString();
3241 pComboBox->setCurrentText(value);
3242 return true;
3243 }
3244
3245 return false;
3246};
3247
3248void Guide::setupOpticalTrainManager()
3249{
3250 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Guide::refreshOpticalTrain);
3251 connect(trainB, &QPushButton::clicked, this, [this]()
3252 {
3253 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
3254 });
3255 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
3256 {
3257 if (guiderType == GUIDE_PHD2 && m_GuiderInstance->isConnected())
3258 {
3259 appendLogText(i18n("Cannot change active optical train while PHD2 is connected"));
3260 return;
3261 }
3262
3263 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain,
3264 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
3265 refreshOpticalTrain();
3266 emit trainChanged();
3267 });
3268}
3269
3270void Guide::refreshOpticalTrain()
3271{
3272 opticalTrainCombo->blockSignals(true);
3273 opticalTrainCombo->clear();
3274 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
3275 trainB->setEnabled(true);
3276
3277 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::GuideOpticalTrain);
3278
3279 if (trainID.isValid())
3280 {
3281 auto id = trainID.toUInt();
3282
3283 // If train not found, select the first one available.
3284 if (OpticalTrainManager::Instance()->exists(id) == false)
3285 {
3286 qCWarning(KSTARS_EKOS_GUIDE) << "Optical train doesn't exist for id" << id;
3287 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
3288 }
3289
3290 auto name = OpticalTrainManager::Instance()->name(id);
3291
3292 opticalTrainCombo->setCurrentText(name);
3293
3294 auto scope = OpticalTrainManager::Instance()->getScope(name);
3295 m_FocalLength = scope["focal_length"].toDouble(-1);
3296 m_Aperture = scope["aperture"].toDouble(-1);
3297 m_FocalRatio = scope["focal_ratio"].toDouble(-1);
3298 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
3299
3300 // DSLR Lens Aperture
3301 if (m_Aperture < 0 && m_FocalRatio > 0)
3302 m_Aperture = m_FocalLength / m_FocalRatio;
3303
3304 auto mount = OpticalTrainManager::Instance()->getMount(name);
3305 setMount(mount);
3306
3307 auto camera = OpticalTrainManager::Instance()->getCamera(name);
3308 if (camera)
3309 {
3310 if (guiderType == GUIDE_INTERNAL)
3311 starCenter = QVector3D();
3312
3313 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
3314 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString()));
3315 }
3316 setCamera(camera);
3317
3318 syncTelescopeInfo();
3319
3320 auto guider = OpticalTrainManager::Instance()->getGuider(name);
3321 setGuider(guider);
3322
3323 auto ao = OpticalTrainManager::Instance()->getAdaptiveOptics(name);
3325
3326 // Load train settings
3327 OpticalTrainSettings::Instance()->setOpticalTrainID(id);
3328 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Guide);
3329 if (settings.isValid())
3330 {
3331 auto map = settings.toJsonObject().toVariantMap();
3332 if (map != m_Settings)
3333 {
3334 m_Settings.clear();
3335 setAllSettings(map);
3336 }
3337 }
3338 else
3339 m_Settings = m_GlobalSettings;
3340 }
3341
3342 opticalTrainCombo->blockSignals(false);
3343}
3344
3345void Guide::loadGlobalSettings()
3346{
3347 QString key;
3348 QVariant value;
3349
3350 QVariantMap settings;
3351 // All Combo Boxes
3352 for (auto &oneWidget : findChildren<QComboBox*>())
3353 {
3354 if (oneWidget->objectName() == "opticalTrainCombo")
3355 continue;
3356
3357 key = oneWidget->objectName();
3358 value = Options::self()->property(key.toLatin1());
3359 if (value.isValid() && oneWidget->count() > 0)
3360 {
3361 oneWidget->setCurrentText(value.toString());
3362 settings[key] = value;
3363 }
3364 }
3365
3366 // All Double Spin Boxes
3367 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3368 {
3369 key = oneWidget->objectName();
3370 value = Options::self()->property(key.toLatin1());
3371 if (value.isValid())
3372 {
3373 oneWidget->setValue(value.toDouble());
3374 settings[key] = value;
3375 }
3376 }
3377
3378 // All Spin Boxes
3379 for (auto &oneWidget : findChildren<QSpinBox*>())
3380 {
3381 key = oneWidget->objectName();
3382 value = Options::self()->property(key.toLatin1());
3383 if (value.isValid())
3384 {
3385 oneWidget->setValue(value.toInt());
3386 settings[key] = value;
3387 }
3388 }
3389
3390 // All Checkboxes
3391 for (auto &oneWidget : findChildren<QCheckBox*>())
3392 {
3393 key = oneWidget->objectName();
3394 value = Options::self()->property(key.toLatin1());
3395 if (value.isValid())
3396 {
3397 oneWidget->setChecked(value.toBool());
3398 settings[key] = value;
3399 }
3400 }
3401
3402 m_GlobalSettings = m_Settings = settings;
3403}
3404
3405void Guide::connectSettings()
3406{
3407 // All Combo Boxes
3408 for (auto &oneWidget : findChildren<QComboBox*>())
3409 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3410
3411 // All Double Spin Boxes
3412 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3413 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3414
3415 // All Spin Boxes
3416 for (auto &oneWidget : findChildren<QSpinBox*>())
3417 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3418
3419 // All Checkboxes
3420 for (auto &oneWidget : findChildren<QCheckBox*>())
3421 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3422
3423 // All Radio buttons
3424 for (auto &oneWidget : findChildren<QRadioButton*>())
3425 connect(oneWidget, &QRadioButton::toggled, this, &Ekos::Guide::syncSettings);
3426
3427 // Train combo box should NOT be synced.
3428 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3429}
3430
3431void Guide::disconnectSettings()
3432{
3433 // All Combo Boxes
3434 for (auto &oneWidget : findChildren<QComboBox*>())
3435 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3436
3437 // All Double Spin Boxes
3438 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3439 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3440
3441 // All Spin Boxes
3442 for (auto &oneWidget : findChildren<QSpinBox*>())
3443 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3444
3445 // All Checkboxes
3446 for (auto &oneWidget : findChildren<QCheckBox*>())
3447 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3448
3449 // All Radio buttons
3450 for (auto &oneWidget : findChildren<QRadioButton*>())
3451 disconnect(oneWidget, &QRadioButton::toggled, this, &Ekos::Guide::syncSettings);
3452
3453}
3454
3455void Guide::updateSetting(const QString &key, const QVariant &value)
3456{
3457 // Save immediately
3458 Options::self()->setProperty(key.toLatin1(), value);
3459 m_Settings[key] = value;
3460 m_GlobalSettings[key] = value;
3461
3462 m_DebounceTimer.start();
3463}
3464
3465///////////////////////////////////////////////////////////////////////////////////////////
3466///
3467///////////////////////////////////////////////////////////////////////////////////////////
3468void Guide::settleSettings()
3469{
3470 Options::self()->save();
3471 emit settingsUpdated(getAllSettings());
3472 // Save to optical train specific settings as well
3473 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
3474 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings);
3475}
3476
3477void Guide::syncSettings()
3478{
3479 QDoubleSpinBox *dsb = nullptr;
3480 QSpinBox *sb = nullptr;
3481 QCheckBox *cb = nullptr;
3482 QComboBox *cbox = nullptr;
3483 QRadioButton *rb = nullptr;
3484
3485 QString key;
3486 QVariant value;
3487
3488 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
3489 {
3490 key = dsb->objectName();
3491 value = dsb->value();
3492
3493 }
3494 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
3495 {
3496 key = sb->objectName();
3497 value = sb->value();
3498 }
3499 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
3500 {
3501 key = cb->objectName();
3502 value = cb->isChecked();
3503 }
3504 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
3505 {
3506 key = cbox->objectName();
3507 value = cbox->currentText();
3508 }
3509 else if ( (rb = qobject_cast<QRadioButton*>(sender())))
3510 {
3511 key = rb->objectName();
3512 if (rb->isChecked() == false)
3513 {
3514 m_Settings.remove(key);
3515 return;
3516 }
3517 value = true;
3518 }
3519
3520 updateSetting(key, value);
3521}
3522
3523
3524}
Q_SCRIPTABLE bool connectGuider()
DBUS interface function.
Definition guide.cpp:2744
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:3455
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:2639
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:2750
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:3078
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:121
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)
bool isValid(QStringView ifopt)
QString name(StandardAction id)
@ 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 Jan 24 2025 11:53:01 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.