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

KDE's Doxygen guidelines are available online.