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

KDE's Doxygen guidelines are available online.