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");
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);
107
108 guideAutoScaleGraphB->setIcon(
109 QIcon::fromTheme("zoom-fit-best"));
110 connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs);
112
113 guideSaveDataB->setIcon(
114 QIcon::fromTheme("document-save"));
115 connect(guideSaveDataB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::exportGuideData);
117
118 guideDataClearB->setIcon(
119 QIcon::fromTheme("application-exit"));
120 connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs);
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);
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);
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?
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
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);
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;
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.
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
687bool Guide::setGuider(ISD::Guider * device)
688{
689 if (guiderType != GUIDE_INTERNAL || (m_Guider && device == m_Guider))
690 return false;
691
692 if (m_Guider)
693 m_Guider->disconnect(this);
694
695 m_Guider = device;
696
697 if (m_Guider)
698 {
699 connect(m_Guider, &ISD::ConcreteDevice::Connected, this, [this]()
700 {
701 guideB->setEnabled(true);
702 });
703 connect(m_Guider, &ISD::ConcreteDevice::Disconnected, this, [this]()
704 {
705 guideB->setEnabled(false);
706 });
707 }
708
709 guideB->setEnabled(m_Guider && m_Guider->isConnected());
710 return true;
711}
712
714{
715 if (guiderType != GUIDE_INTERNAL || (m_AO && device == m_AO))
716 return false;
717
718 if (m_AO)
719 m_AO->disconnect(this);
720
721 // FIXME AO are not yet utilized property in Guide module
722 m_AO = device;
723 return true;
724}
725
726QString Guide::guider()
727{
728 if (guiderType != GUIDE_INTERNAL || m_Guider == nullptr)
729 return QString();
730
731 return m_Guider->getDeviceName();
732}
733
735{
736 buildOperationStack(GUIDE_CAPTURE);
737
738 return executeOperationStack();
739}
740
741bool Guide::captureOneFrame()
742{
743 captureTimeout.stop();
744
745 if (m_Camera == nullptr)
746 return false;
747
748 if (m_Camera->isConnected() == false)
749 {
750 appendLogText(i18n("Error: lost connection to CCD."));
751 return false;
752 }
753
754 double seqExpose = guideExposure->value();
755
756 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
757
758 prepareCapture(targetChip);
759
760 m_GuideView->setBaseSize(guideWidget->size());
761 setBusy(true);
762
763 // Check if we have a valid frame setting
764 if (frameSettings.contains(targetChip))
765 {
766 QVariantMap settings = frameSettings[targetChip];
767 targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(),
768 settings["h"].toInt());
769 targetChip->setBinning(settings["binx"].toInt(), settings["biny"].toInt());
770 }
771
772 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection);
773 qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame...";
774
775 double finalExposure = seqExpose;
776
777 // Increase exposure for calibration frame if we need auto-select a star
778 // To increase chances we detect one.
779 if (operationStack.contains(GUIDE_STAR_SELECT) && guideAutoStar->isChecked() &&
780 !((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled()))
781 finalExposure *= 3;
782
783 // Prevent flicker when processing dark frame by suspending updates
784 m_GuideView->setProperty("suspended", operationStack.contains(GUIDE_DARK));
785
786 // Timeout is exposure duration + timeout threshold in seconds
787 captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD);
788
789 targetChip->capture(finalExposure);
790
791 return true;
792}
793
794void Guide::prepareCapture(ISD::CameraChip * targetChip)
795{
796 targetChip->setBatchMode(false);
797 targetChip->setCaptureMode(FITS_GUIDE);
798 targetChip->setFrameType(FRAME_LIGHT);
799 targetChip->setCaptureFilter(FITS_NONE);
800 m_Camera->setEncodingFormat("FITS");
801}
802
803void Guide::abortExposure()
804{
805 if (m_Camera && guiderType == GUIDE_INTERNAL)
806 {
807 captureTimeout.stop();
808 m_PulseTimer.stop();
809 ISD::CameraChip *targetChip =
810 m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
811 if (targetChip->isCapturing())
812 {
813 qCDebug(KSTARS_EKOS_GUIDE) << "Aborting guide capture";
814 targetChip->abortExposure();
815 }
816 }
817}
818
820{
821 if (m_Camera && guiderType == GUIDE_INTERNAL)
822 {
823 captureTimeout.stop();
824 m_PulseTimer.stop();
825 ISD::CameraChip *targetChip =
826 m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
827 if (targetChip->isCapturing())
828 targetChip->abortExposure();
829 }
830
831 manualDitherB->setEnabled(false);
832
833 setBusy(false);
834
835 switch (m_State)
836 {
837 case GUIDE_IDLE:
838 case GUIDE_CONNECTED:
839 case GUIDE_DISCONNECTED:
840 break;
841
842 case GUIDE_CALIBRATING:
843 case GUIDE_DITHERING:
844 case GUIDE_STAR_SELECT:
845 case GUIDE_CAPTURE:
846 case GUIDE_GUIDING:
847 case GUIDE_LOOPING:
848 m_GuiderInstance->abort();
849 break;
850
851 default:
852 break;
853 }
854
855 return true;
856}
857
858void Guide::setBusy(bool enable)
859{
860 if (enable && pi->isAnimated())
861 return;
862 else if (enable == false && pi->isAnimated() == false)
863 return;
864
865 if (enable)
866 {
867 clearCalibrationB->setEnabled(false);
868 guideB->setEnabled(false);
869 captureB->setEnabled(false);
870 loopB->setEnabled(false);
871 guideDarkFrame->setEnabled(false);
872 guideSubframe->setEnabled(false);
873 guideAutoStar->setEnabled(false);
874 stopB->setEnabled(true);
875 // Optical Train
876 opticalTrainCombo->setEnabled(false);
877 trainB->setEnabled(false);
878
879 pi->startAnimation();
880 }
881 else
882 {
883 if(guiderType != GUIDE_LINGUIDER)
884 {
885 captureB->setEnabled(true);
886 loopB->setEnabled(true);
887 guideAutoStar->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions()
888 if(m_Camera)
889 guideSubframe->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions()
890 }
891 if (guiderType == GUIDE_INTERNAL)
892 guideDarkFrame->setEnabled(true);
893
894 if (calibrationComplete ||
895 ((guiderType == GUIDE_INTERNAL) &&
896 Options::reuseGuideCalibration() &&
897 !Options::serializedCalibration().isEmpty()))
898 clearCalibrationB->setEnabled(true);
899 guideB->setEnabled(true);
900 stopB->setEnabled(false);
901 pi->stopAnimation();
902
903 // Optical Train
904 opticalTrainCombo->setEnabled(true);
905 trainB->setEnabled(true);
906
907 connect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection);
908 }
909}
910
911void Guide::processCaptureTimeout()
912{
913 auto restartExposure = [&]()
914 {
915 appendLogText(i18n("Exposure timeout. Restarting exposure..."));
916 m_Camera->setEncodingFormat("FITS");
917 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
918 targetChip->abortExposure();
919 prepareCapture(targetChip);
920 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection);
921 connect(m_Camera, &ISD::Camera::propertyUpdated, this, &Ekos::Guide::updateProperty, Qt::UniqueConnection);
922 connect(m_Camera, &ISD::Camera::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection);
923 targetChip->capture(guideExposure->value());
924 captureTimeout.start(guideExposure->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD);
925 };
926
927 m_CaptureTimeoutCounter++;
928
929 if (m_Camera == nullptr)
930 return;
931
932 if (m_DeviceRestartCounter >= 3)
933 {
934 m_CaptureTimeoutCounter = 0;
935 m_DeviceRestartCounter = 0;
936 if (m_State == GUIDE_GUIDING)
937 appendLogText(i18n("Exposure timeout. Aborting Autoguide."));
938 else if (m_State == GUIDE_DITHERING)
939 appendLogText(i18n("Exposure timeout. Aborting Dithering."));
940 else if (m_State == GUIDE_CALIBRATING)
941 appendLogText(i18n("Exposure timeout. Aborting Calibration."));
942
943 captureTimeout.stop();
944 abort();
945 return;
946 }
947
948 if (m_CaptureTimeoutCounter > 1)
949 {
950 QString camera = m_Camera->getDeviceName();
951 QString via = m_Guider ? m_Guider->getDeviceName() : "";
952 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
953 QVariantMap settings = frameSettings[targetChip];
954 emit driverTimedout(camera);
955 QTimer::singleShot(5000, [ &, camera, settings]()
956 {
957 m_DeviceRestartCounter++;
958 reconnectDriver(camera, settings);
959 });
960 return;
961 }
962 else
964}
965
966void Guide::reconnectDriver(const QString &camera, QVariantMap settings)
967{
968 if (m_Camera && m_Camera->getDeviceName() == camera)
969 {
970 // Set state to IDLE so that checkCamera is processed since it will not process GUIDE_GUIDING state.
971 Ekos::GuideState currentState = m_State;
972 m_State = GUIDE_IDLE;
973 checkCamera();
974 // Restore state to last state.
975 m_State = currentState;
976
977 if (guiderType == GUIDE_INTERNAL)
978 {
979 // Reset the frame settings to the restarted camera once again before capture.
980 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
981 frameSettings[targetChip] = settings;
982 // restart capture
983 m_CaptureTimeoutCounter = 0;
984 captureOneFrame();
985 }
986
987 return;
988 }
989
990 QTimer::singleShot(5000, this, [ &, camera, settings]()
991 {
992 reconnectDriver(camera, settings);
993 });
994}
995
997{
998 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
999 if (targetChip->getCaptureMode() != FITS_GUIDE)
1000 {
1001 if (data)
1002 {
1003 QString blobInfo = QString("{Device: %1 Property: %2 Element: %3 Chip: %4}").arg(data->property("device").toString())
1004 .arg(data->property("blobVector").toString())
1005 .arg(data->property("blobElement").toString())
1006 .arg(data->property("chip").toInt());
1007
1008 qCWarning(KSTARS_EKOS_GUIDE) << blobInfo << "Ignoring Received FITS as it has the wrong capture mode" <<
1009 targetChip->getCaptureMode();
1010 }
1011
1012 return;
1013 }
1014
1015 if (data)
1016 {
1017 m_GuideView->loadData(data);
1018 m_ImageData = data;
1019 }
1020 else
1021 m_ImageData.reset();
1022
1023 if (guiderType == GUIDE_INTERNAL)
1024 internalGuider->setImageData(m_ImageData);
1025
1026 captureTimeout.stop();
1027 m_CaptureTimeoutCounter = 0;
1028
1029 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData);
1030
1031 // qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame.";
1032
1033 int subBinX = 1, subBinY = 1;
1034 targetChip->getBinning(&subBinX, &subBinY);
1035
1036 if (starCenter.x() == 0 && starCenter.y() == 0)
1037 {
1038 int x = 0, y = 0, w = 0, h = 0;
1039
1040 if (frameSettings.contains(targetChip))
1041 {
1042 QVariantMap settings = frameSettings[targetChip];
1043 x = settings["x"].toInt();
1044 y = settings["y"].toInt();
1045 w = settings["w"].toInt();
1046 h = settings["h"].toInt();
1047 }
1048 else
1049 targetChip->getFrame(&x, &y, &w, &h);
1050
1051 starCenter.setX(w / (2 * subBinX));
1052 starCenter.setY(h / (2 * subBinY));
1053 starCenter.setZ(subBinX);
1054 }
1055
1056 syncTrackingBoxPosition();
1057 // qCDebug(KSTARS_EKOS_GUIDE) << "Tracking box position synched.";
1058
1059 setCaptureComplete();
1060 // qCDebug(KSTARS_EKOS_GUIDE) << "Capture complete.";
1061
1062}
1063
1064void Guide::setCaptureComplete()
1065{
1066 if (!m_GuideView.isNull())
1067 m_GuideView->clearNeighbors();
1068
1069 DarkLibrary::Instance()->disconnect(this);
1070
1071 if (operationStack.isEmpty() == false)
1072 {
1073 executeOperationStack();
1074 return;
1075 }
1076
1077 qCDebug(KSTARS_EKOS_GUIDE) << "Capture complete, state=" << getGuideStatusString(m_State);
1078 switch (m_State)
1079 {
1080 case GUIDE_IDLE:
1081 case GUIDE_ABORTED:
1082 case GUIDE_CONNECTED:
1083 case GUIDE_DISCONNECTED:
1084 case GUIDE_CALIBRATION_SUCCESS:
1085 case GUIDE_CALIBRATION_ERROR:
1086 case GUIDE_DITHERING_ERROR:
1087 setBusy(false);
1088 break;
1089
1090 case GUIDE_CAPTURE:
1091 qCDebug(KSTARS_EKOS_GUIDE) << "Guiding capture complete.";
1092 m_State = GUIDE_IDLE;
1093 emit newStatus(m_State);
1094 setBusy(false);
1095 break;
1096
1097 case GUIDE_LOOPING:
1098 capture();
1099 break;
1100
1101 case GUIDE_CALIBRATING:
1102 m_GuiderInstance->calibrate();
1103 break;
1104
1105 case GUIDE_GUIDING:
1106 m_GuiderInstance->guide();
1107 break;
1108
1109 case GUIDE_DITHERING:
1110 m_GuiderInstance->dither(Options::ditherPixels());
1111 break;
1112
1113 // Feature only of internal guider
1114 case GUIDE_MANUAL_DITHERING:
1115 dynamic_cast<InternalGuider*>(m_GuiderInstance)->processManualDithering();
1116 break;
1117
1118 case GUIDE_REACQUIRE:
1119 m_GuiderInstance->reacquire();
1120 break;
1121
1122 case GUIDE_DITHERING_SETTLE:
1123 if (Options::ditherNoGuiding())
1124 return;
1125 capture();
1126 break;
1127
1128 case GUIDE_SUSPENDED:
1129 if (Options::gPGEnabled())
1130 m_GuiderInstance->guide();
1131 break;
1132
1133 default:
1134 break;
1135 }
1136
1137 emit newImage(m_GuideView);
1138 emit newStarPixmap(m_GuideView->getTrackingBoxPixmap(10));
1139}
1140
1141void Guide::appendLogText(const QString &text)
1142{
1143 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
1144 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
1145
1146 qCInfo(KSTARS_EKOS_GUIDE) << text;
1147
1148 emit newLog(text);
1149}
1150
1152{
1153 m_LogText.clear();
1154 emit newLog(QString());
1155}
1156
1157void Guide::setDECSwap(bool enable)
1158{
1159 if (m_Guider == nullptr || m_GuiderInstance == nullptr)
1160 return;
1161
1162 if (guiderType == GUIDE_INTERNAL)
1163 {
1164 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setDECSwap(enable);
1165 m_Guider->setDECSwap(enable);
1166 }
1167}
1168
1169bool Guide::sendMultiPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs,
1170 CaptureAfterPulses followWithCapture)
1171{
1172 if (m_Guider == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR))
1173 return false;
1174
1175 if (followWithCapture == StartCaptureAfterPulses)
1176 {
1177 // Delay next capture by user-configurable delay.
1178 // If user delay is zero, delay by the pulse length plus 100 milliseconds before next capture.
1179 auto ms = std::max(ra_msecs, dec_msecs) + 100;
1180 auto delay = std::max(static_cast<int>(guideDelay->value() * 1000), ms);
1181
1182 m_PulseTimer.start(delay);
1183 }
1184 return m_Guider->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
1185}
1186
1187bool Guide::sendSinglePulse(GuideDirection dir, int msecs, CaptureAfterPulses followWithCapture)
1188{
1189 if (m_Guider == nullptr || dir == NO_DIR)
1190 return false;
1191
1192 if (followWithCapture == StartCaptureAfterPulses)
1193 {
1194 // Delay next capture by user-configurable delay.
1195 // If user delay is zero, delay by the pulse length plus 100 milliseconds before next capture.
1196 auto ms = msecs + 100;
1197 auto delay = std::max(static_cast<int>(guideDelay->value() * 1000), ms);
1198
1199 m_PulseTimer.start(delay);
1200 }
1201
1202 return m_Guider->doPulse(dir, msecs);
1203}
1204
1206{
1207 // Set status to idle and let the operations change it as they get executed
1208 m_State = GUIDE_IDLE;
1209 qCDebug(KSTARS_EKOS_GUIDE) << "Calibrating...";
1210 emit newStatus(m_State);
1211
1212 if (guiderType == GUIDE_INTERNAL)
1213 {
1214 if (!m_Camera)
1215 {
1216 qCCritical(KSTARS_EKOS_GUIDE) << "No camera detected. Check optical trains.";
1217 return false;
1218 }
1219
1220 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1221
1222 if (frameSettings.contains(targetChip))
1223 {
1224 targetChip->resetFrame();
1225 int x, y, w, h;
1226 targetChip->getFrame(&x, &y, &w, &h);
1227 QVariantMap settings = frameSettings[targetChip];
1228 settings["x"] = x;
1229 settings["y"] = y;
1230 settings["w"] = w;
1231 settings["h"] = h;
1232 frameSettings[targetChip] = settings;
1233
1234 subFramed = false;
1235 }
1236 }
1237
1238 buildOperationStack(GUIDE_CALIBRATING);
1239
1240 executeOperationStack();
1241
1242 if (m_Camera && m_Guider)
1243 {
1244 qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using camera:" << m_Camera->getDeviceName() << "via" <<
1245 m_Guider->getDeviceName();
1246 }
1247
1248 return true;
1249}
1250
1252{
1253 auto executeGuide = [this]()
1254 {
1255 if(guiderType != GUIDE_PHD2)
1256 {
1257 if (calibrationComplete == false)
1258 {
1259 calibrate();
1260 return;
1261 }
1262 }
1263
1264 m_GuiderInstance->guide();
1265
1266 //If PHD2 gets a Guide command and it is looping, it will accept a lock position
1267 //but if it was not looping it will ignore the lock position and do an auto star automatically
1268 //This is not the default behavior in Ekos if auto star is not selected.
1269 //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide.
1270 if(!guideAutoStar->isChecked())
1271 {
1272 if(guiderType == GUIDE_PHD2 && m_GuideView->isTrackingBoxEnabled())
1273 {
1274 double x = starCenter.x();
1275 double y = starCenter.y();
1276
1277 if(!m_ImageData.isNull())
1278 {
1279 if(m_ImageData->width() > 50)
1280 {
1281 guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState)
1282 {
1283 if(newState == GUIDE_GUIDING)
1284 {
1285 phd2Guider->setLockPosition(x, y);
1286 disconnect(guideConnect);
1287 }
1288 });
1289 }
1290 }
1291 }
1292 }
1293 };
1294
1295 if (m_MountStatus == ISD::Mount::MOUNT_PARKED)
1296 {
1297 KSMessageBox::Instance()->sorry(i18n("The mount is parked. Unpark to start guiding."));
1298 return false;
1299 }
1300
1301 executeGuide();
1302 return true;
1303}
1304
1306{
1307 if (Options::ditherNoGuiding() && m_State == GUIDE_IDLE)
1308 {
1309 nonGuidedDither();
1310 return true;
1311 }
1312
1313 if (m_State == GUIDE_DITHERING || m_State == GUIDE_DITHERING_SETTLE)
1314 return true;
1315
1316 //This adds a dither text item to the graph where dithering occurred.
1317 double time = guideTimer.elapsed() / 1000.0;
1319 ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft);
1320 ditherLabel->position->setType(QCPItemPosition::ptPlotCoords);
1321 ditherLabel->position->setCoords(time, 1.5);
1322 ditherLabel->setColor(Qt::white);
1323 ditherLabel->setBrush(Qt::NoBrush);
1324 ditherLabel->setPen(Qt::NoPen);
1325 ditherLabel->setText("Dither");
1326 ditherLabel->setFont(QFont(font().family(), 10));
1327
1328 if (guiderType == GUIDE_INTERNAL && !Options::ditherWithOnePulse())
1329 {
1330 if (m_State != GUIDE_GUIDING)
1331 capture();
1332
1333 setStatus(GUIDE_DITHERING);
1334
1335 return true;
1336 }
1337 else
1338 return m_GuiderInstance->dither(Options::ditherPixels());
1339}
1340
1342{
1343 if (m_State == GUIDE_SUSPENDED)
1344 return true;
1345 else if (m_State >= GUIDE_CAPTURE)
1346 return m_GuiderInstance->suspend();
1347 else
1348 return false;
1349}
1350
1352{
1353 if (m_State == GUIDE_GUIDING)
1354 return true;
1355 else if (m_State == GUIDE_SUSPENDED)
1356 return m_GuiderInstance->resume();
1357 else
1358 return false;
1359}
1360
1361void Guide::setCaptureStatus(CaptureState newState)
1362{
1363 switch (newState)
1364 {
1365 case CAPTURE_DITHERING:
1366 dither();
1367 break;
1368 case CAPTURE_IDLE:
1369 case CAPTURE_ABORTED:
1370 // We need to reset the non guided dithering status every time a new capture task is started (and not for every single capture).
1371 // The non dithering logic is a bit convoluted and controlled by the Capture module,
1372 // which calls Guide::setCaptureStatus(CAPTURE_DITHERING) when it wants guide to dither.
1373 // It actually calls newStatus(CAPTURE_DITHERING) in Capture::checkDithering(), but manager.cpp in Manager::connectModules() connects that to Guide::setCaptureStatus()).
1374 // 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.
1375 resetNonGuidedDither();
1376 break;
1377 default:
1378 break;
1379 }
1380}
1381
1382void Guide::setPierSide(ISD::Mount::PierSide newSide)
1383{
1384 m_GuiderInstance->setPierSide(newSide);
1385
1386 // If pier side changes in internal guider
1387 // and calibration was already done
1388 // then let's swap
1389 if (guiderType == GUIDE_INTERNAL &&
1390 m_State != GUIDE_GUIDING &&
1391 m_State != GUIDE_CALIBRATING &&
1392 calibrationComplete)
1393 {
1394 // Couldn't restore an old calibration if we call clearCalibration().
1395 if (Options::reuseGuideCalibration())
1396 calibrationComplete = false;
1397 else
1398 {
1400 appendLogText(i18n("Pier side change detected. Clearing calibration."));
1401 }
1402 }
1403}
1404
1405void Guide::setMountStatus(ISD::Mount::Status newState)
1406{
1407 m_MountStatus = newState;
1408
1409 if (newState == ISD::Mount::MOUNT_PARKING || newState == ISD::Mount::MOUNT_SLEWING)
1410 {
1411 // reset the calibration if "Always reset calibration" is selected and the mount moves
1412 if (Options::resetGuideCalibration())
1413 {
1414 appendLogText(i18n("Mount is moving. Resetting calibration..."));
1416 }
1417 else if (Options::reuseGuideCalibration() && (guiderType == GUIDE_INTERNAL))
1418 {
1419 // It will restore it with the reused one, and this way it reselects a guide star.
1420 calibrationComplete = false;
1421 }
1422 // GPG guide algorithm should be reset on any slew.
1423 if (Options::gPGEnabled())
1424 m_GuiderInstance->resetGPG();
1425
1426 // If we're guiding, and the mount either slews or parks, then we abort.
1427 if (m_State == GUIDE_GUIDING || m_State == GUIDE_DITHERING)
1428 {
1429 if (newState == ISD::Mount::MOUNT_PARKING)
1430 appendLogText(i18n("Mount is parking. Aborting guide..."));
1431 else
1432 appendLogText(i18n("Mount is slewing. Aborting guide..."));
1433
1434 abort();
1435 }
1436 }
1437
1438 if (guiderType != GUIDE_INTERNAL)
1439 return;
1440
1441 switch (newState)
1442 {
1443 case ISD::Mount::MOUNT_SLEWING:
1444 case ISD::Mount::MOUNT_PARKING:
1445 case ISD::Mount::MOUNT_MOVING:
1446 captureB->setEnabled(false);
1447 loopB->setEnabled(false);
1448 clearCalibrationB->setEnabled(false);
1449 manualPulseB->setEnabled(false);
1450 break;
1451
1452 default:
1453 if (pi->isAnimated() == false)
1454 {
1455 captureB->setEnabled(true);
1456 loopB->setEnabled(true);
1457 clearCalibrationB->setEnabled(true);
1458 manualPulseB->setEnabled(true);
1459 }
1460 }
1461}
1462
1463void Guide::setMountCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
1464{
1465 Q_UNUSED(ha);
1466 m_GuiderInstance->setMountCoords(position, pierSide);
1467 m_ManaulPulse->setMountCoords(position);
1468}
1469
1470void Guide::setExposure(double value)
1471{
1472 guideExposure->setValue(value);
1473}
1474
1476{
1477 if (guideSubframe->isChecked() != enable)
1478 guideSubframe->setChecked(enable);
1479 if(guiderType == GUIDE_PHD2)
1480 setExternalGuiderBLOBEnabled(!enable);
1481}
1482
1484{
1485 if(guiderType == GUIDE_INTERNAL)
1486 guideAutoStar->setChecked(enable);
1487}
1488
1490{
1491 calibrationComplete = false;
1492
1493 m_GuiderInstance->clearCalibration();
1494
1495 appendLogText(i18n("Calibration is cleared."));
1496}
1497
1498void Guide::setStatus(Ekos::GuideState newState)
1499{
1500 if (newState == m_State)
1501 {
1502 // pass through the aborted state
1503 if (newState == GUIDE_ABORTED)
1504 emit newStatus(m_State);
1505 return;
1506 }
1507
1508 GuideState previousState = m_State;
1509
1510 m_State = newState;
1511 emit newStatus(m_State);
1512
1513 switch (m_State)
1514 {
1515 case GUIDE_CONNECTED:
1516 appendLogText(i18n("External guider connected."));
1517 externalConnectB->setEnabled(false);
1518 externalDisconnectB->setEnabled(true);
1519 clearCalibrationB->setEnabled(true);
1520 guideB->setEnabled(true);
1521
1522 if(guiderType == GUIDE_PHD2)
1523 {
1524 captureB->setEnabled(true);
1525 loopB->setEnabled(true);
1526 guideAutoStar->setEnabled(true);
1527 configurePHD2Camera();
1528 setExternalGuiderBLOBEnabled(!guideSubframe->isChecked());
1529 guideSquareSize->setEnabled(true);
1530 }
1531 break;
1532
1533 case GUIDE_DISCONNECTED:
1534 appendLogText(i18n("External guider disconnected."));
1535 setBusy(false); //This needs to come before caputureB since it will set it to enabled again.
1536 externalConnectB->setEnabled(true);
1537 externalDisconnectB->setEnabled(false);
1538 clearCalibrationB->setEnabled(false);
1539 guideB->setEnabled(false);
1540 captureB->setEnabled(false);
1541 loopB->setEnabled(false);
1542 guideAutoStar->setEnabled(false);
1543 guideSquareSize->setEnabled(false);
1544 //setExternalGuiderBLOBEnabled(true);
1545#ifdef Q_OS_OSX
1546 repaint(); //This is a band-aid for a bug in QT 5.10.0
1547#endif
1548 break;
1549
1550 case GUIDE_CALIBRATION_SUCCESS:
1551 appendLogText(i18n("Calibration completed."));
1552 manualPulseB->setEnabled(true);
1553 calibrationComplete = true;
1554
1555 if(guiderType !=
1556 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.
1557 guide();
1558 break;
1559
1560 case GUIDE_IDLE:
1561 case GUIDE_CALIBRATION_ERROR:
1562 setBusy(false);
1563 manualDitherB->setEnabled(false);
1564 manualPulseB->setEnabled(true);
1565 break;
1566
1567 case GUIDE_CALIBRATING:
1568 clearCalibrationGraphs();
1569 appendLogText(i18n("Calibration started."));
1570 setBusy(true);
1571 manualPulseB->setEnabled(false);
1572 break;
1573
1574 case GUIDE_GUIDING:
1575 if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS)
1576 appendLogText(i18n("Guiding resumed."));
1577 else
1578 {
1579 appendLogText(i18n("Autoguiding started."));
1580 setBusy(true);
1581
1582 clearGuideGraphs();
1583 guideTimer.start();
1584 driftGraph->resetTimer();
1585 driftGraph->refreshColorScheme();
1586 }
1587 manualDitherB->setEnabled(true);
1588 break;
1589
1590 case GUIDE_ABORTED:
1591 appendLogText(i18n("Autoguiding aborted."));
1592 setBusy(false);
1593 break;
1594
1595 case GUIDE_SUSPENDED:
1596 appendLogText(i18n("Guiding suspended."));
1597 break;
1598
1599 case GUIDE_REACQUIRE:
1600 if (guiderType == GUIDE_INTERNAL)
1601 capture();
1602 break;
1603
1604 case GUIDE_MANUAL_DITHERING:
1605 appendLogText(i18n("Manual dithering in progress."));
1606 break;
1607
1608 case GUIDE_DITHERING:
1609 appendLogText(i18n("Dithering in progress."));
1610 break;
1611
1612 case GUIDE_DITHERING_SETTLE:
1613 appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...",
1614 Options::ditherSettle()));
1615 break;
1616
1617 case GUIDE_DITHERING_ERROR:
1618 appendLogText(i18n("Dithering failed."));
1619 // LinGuider guide continue after dithering failure
1620 if (guiderType != GUIDE_LINGUIDER)
1621 {
1622 //state = GUIDE_IDLE;
1623 m_State = GUIDE_ABORTED;
1624 setBusy(false);
1625 }
1626 break;
1627
1628 case GUIDE_DITHERING_SUCCESS:
1629 appendLogText(i18n("Dithering completed successfully."));
1630 // Go back to guiding state immediately if using regular guider
1631 if (Options::ditherNoGuiding() == false)
1632 {
1633 setStatus(GUIDE_GUIDING);
1634 // Only capture again if we are using internal guider
1635 if (guiderType == GUIDE_INTERNAL)
1636 capture();
1637 }
1638 break;
1639 default:
1640 break;
1641 }
1642}
1643
1644void Guide::updateCCDBin(int index)
1645{
1646 if (m_Camera == nullptr || guiderType != GUIDE_INTERNAL)
1647 return;
1648
1649 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1650
1651 targetChip->setBinning(index + 1, index + 1);
1652 guideBinIndex = index;
1653
1654 QVariantMap settings = frameSettings[targetChip];
1655 settings["binx"] = index + 1;
1656 settings["biny"] = index + 1;
1657 frameSettings[targetChip] = settings;
1658
1659 m_GuiderInstance->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(),
1660 settings["binx"].toInt(), settings["biny"].toInt());
1661}
1662
1663void Guide::updateProperty(INDI::Property prop)
1664{
1665 if (m_Camera == nullptr || (prop.getDeviceName() != m_Camera->getDeviceName()) || guiderType != GUIDE_INTERNAL)
1666 return;
1667
1668 if ((prop.isNameMatch("CCD_BINNING") && useGuideHead == false) ||
1669 (prop.isNameMatch("GUIDER_BINNING") && useGuideHead))
1670 {
1671 auto nvp = prop.getNumber();
1672 auto value = nvp->at(0)->getValue();
1673 if (guideBinIndex > (value - 1)) // INDI driver reports not supported binning
1674 {
1675 appendLogText(i18n("%1x%1 guide binning is not supported.", guideBinIndex + 1));
1676 guideBinning->setCurrentIndex( value - 1 );
1677 updateSetting("guideBinning", guideBinning->currentText());
1678 }
1679 else
1680 {
1681 guideBinning->setCurrentIndex(guideBinIndex);
1682 }
1683 }
1684}
1685
1686void Guide::checkExposureValue(ISD::CameraChip * targetChip, double exposure, IPState expState)
1687{
1688 // Ignore if not using internal guider, or chip belongs to a different camera.
1689 if (guiderType != GUIDE_INTERNAL || targetChip->getCCD() != m_Camera)
1690 return;
1691
1692 INDI_UNUSED(exposure);
1693
1694 if (expState == IPS_ALERT &&
1695 ((m_State == GUIDE_GUIDING) || (m_State == GUIDE_DITHERING) || (m_State == GUIDE_CALIBRATING)))
1696 {
1697 appendLogText(i18n("Exposure failed. Restarting exposure..."));
1698 m_Camera->setEncodingFormat("FITS");
1699 targetChip->capture(guideExposure->value());
1700 }
1701}
1702
1703void Guide::configSEPMultistarOptions()
1704{
1705 // SEP MultiStar always uses an automated guide star & doesn't subframe.
1706 if (internalGuider->SEPMultiStarEnabled())
1707 {
1708 guideSubframe->setChecked(false);
1709 guideSubframe->setEnabled(false);
1710 guideAutoStar->setChecked(true);
1711 guideAutoStar->setEnabled(false);
1712 }
1713 else
1714 {
1715 guideAutoStar->setEnabled(true);
1716 guideSubframe->setEnabled(true);
1717
1718 auto subframed = m_Settings["guideSubframe"];
1719 if (subframed.isValid())
1720 guideSubframe->setChecked(subframed.toBool());
1721
1722 auto autostar = m_Settings["guideAutoStar"];
1723 if (autostar.isValid())
1724 guideAutoStar->setChecked(autostar.toBool());
1725 }
1726}
1727
1729{
1730 if (guideDarkFrame->isChecked() != enable)
1731 guideDarkFrame->setChecked(enable);
1732}
1733
1734void Guide::saveDefaultGuideExposure()
1735{
1736 if(guiderType == GUIDE_PHD2)
1737
1738 phd2Guider->requestSetExposureTime(guideExposure->value() * 1000);
1739 else if (guiderType == GUIDE_INTERNAL)
1740 {
1741 internalGuider->setExposureTime();
1742 }
1743}
1744
1745void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow)
1746{
1747 starCenter.setX(newCenter.x());
1748 starCenter.setY(newCenter.y());
1749 if (newCenter.z() > 0)
1750 starCenter.setZ(newCenter.z());
1751
1752 if (updateNow)
1753 syncTrackingBoxPosition();
1754}
1755
1756void Guide::syncTrackingBoxPosition()
1757{
1758 if(!m_Camera || guiderType == GUIDE_LINGUIDER)
1759 return;
1760
1761 if(guiderType == GUIDE_PHD2)
1762 {
1763 //This way it won't set the tracking box on the Guide Star Image.
1764 if(!m_ImageData.isNull())
1765 {
1766 if(m_ImageData->width() < 50)
1767 {
1768 m_GuideView->setTrackingBoxEnabled(false);
1769 return;
1770 }
1771 }
1772 }
1773
1774 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1775 Q_ASSERT(targetChip);
1776
1777 int subBinX = 1, subBinY = 1;
1778 targetChip->getBinning(&subBinX, &subBinY);
1779
1780 if (starCenter.isNull() == false)
1781 {
1782 double boxSize = guideSquareSize->currentText().toInt();
1783 int x, y, w, h;
1784 targetChip->getFrame(&x, &y, &w, &h);
1785 // If box size is larger than image size, set it to lower index
1786 if (boxSize / subBinX >= w || boxSize / subBinY >= h)
1787 {
1788 int newIndex = guideSquareSize->currentIndex() - 1;
1789 if (newIndex >= 0)
1790 guideSquareSize->setCurrentIndex(newIndex);
1791 return;
1792 }
1793
1794 // If binning changed, update coords accordingly
1795 if (subBinX != starCenter.z())
1796 {
1797 if (starCenter.z() > 0)
1798 {
1799 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX));
1800 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY));
1801 }
1802
1803 starCenter.setZ(subBinX);
1804 }
1805
1806 QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY),
1807 boxSize / subBinX, boxSize / subBinY);
1808 m_GuideView->setTrackingBoxEnabled(true);
1809 m_GuideView->setTrackingBox(starRect);
1810 }
1811}
1812
1814{
1815 // Use default guider option
1816 if (type == -1)
1817 type = Options::guiderType();
1818 else if (type == guiderType)
1819 return true;
1820
1821 if (m_State == GUIDE_CALIBRATING || m_State == GUIDE_GUIDING || m_State == GUIDE_DITHERING)
1822 {
1823 appendLogText(i18n("Cannot change guider type while active."));
1824 return false;
1825 }
1826
1827 if (m_GuiderInstance != nullptr)
1828 {
1829 // Disconnect from host
1830 if (m_GuiderInstance->isConnected())
1831 m_GuiderInstance->Disconnect();
1832
1833 // Disconnect signals
1834 m_GuiderInstance->disconnect();
1835 }
1836
1837 guiderType = static_cast<GuiderType>(type);
1838
1839 switch (type)
1840 {
1841 case GUIDE_INTERNAL:
1842 {
1843 connect(internalGuider, &InternalGuider::newMultiPulse, this, &Guide::sendMultiPulse);
1844 connect(internalGuider, &InternalGuider::newSinglePulse, this, &Guide::sendSinglePulse);
1845 connect(internalGuider, &InternalGuider::DESwapChanged, this, &Guide::setDECSwap);
1846 connect(internalGuider, &InternalGuider::newStarPixmap, this, &Guide::newStarPixmap);
1847
1848 m_GuiderInstance = internalGuider;
1849
1850 internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex());
1851
1852 clearCalibrationB->setEnabled(true);
1853 guideB->setEnabled(true);
1854 captureB->setEnabled(true);
1855 loopB->setEnabled(true);
1856
1857 configSEPMultistarOptions();
1858 guideDarkFrame->setEnabled(true);
1859
1860 guideExposure->setEnabled(true);
1861 guideBinning->setEnabled(true);
1862 guideSquareSize->setEnabled(true);
1863
1864 externalConnectB->setEnabled(false);
1865 externalDisconnectB->setEnabled(false);
1866
1867 opsGuide->controlGroup->setEnabled(true);
1868 infoGroup->setEnabled(true);
1869 l_Aperture->setEnabled(true);
1870 l_FOV->setEnabled(true);
1871 l_FbyD->setEnabled(true);
1872 l_Focal->setEnabled(true);
1873 driftGraphicsGroup->setEnabled(true);
1874
1875 updateGuideParams();
1876 }
1877 break;
1878
1879 case GUIDE_PHD2:
1880 if (phd2Guider.isNull())
1881 phd2Guider = new PHD2();
1882
1883 m_GuiderInstance = phd2Guider;
1884 phd2Guider->setGuideView(m_GuideView);
1885
1886 connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &)));
1887
1888 clearCalibrationB->setEnabled(true);
1889 captureB->setEnabled(false);
1890 loopB->setEnabled(false);
1891 guideDarkFrame->setEnabled(false);
1892 guideSubframe->setEnabled(false);
1893 guideAutoStar->setEnabled(false);
1894 guideB->setEnabled(false); //This will be enabled later when equipment connects (or not)
1895 externalConnectB->setEnabled(false);
1896
1897 rAGuideEnabled->setEnabled(false);
1898 eastRAGuideEnabled->setEnabled(false);
1899 westRAGuideEnabled->setEnabled(false);
1900
1901 opsGuide->controlGroup->setEnabled(false);
1902 infoGroup->setEnabled(true);
1903 l_Aperture->setEnabled(false);
1904 l_FOV->setEnabled(false);
1905 l_FbyD->setEnabled(false);
1906 l_Focal->setEnabled(false);
1907 driftGraphicsGroup->setEnabled(true);
1908
1909 guideExposure->setEnabled(true);
1910 guideBinning->setEnabled(false);
1911 guideSquareSize->setEnabled(false);
1912
1913 if (Options::resetGuideCalibration())
1914 appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2."));
1915
1916 updateGuideParams();
1917 break;
1918
1919 case GUIDE_LINGUIDER:
1920 if (linGuider.isNull())
1921 linGuider = new LinGuider();
1922
1923 m_GuiderInstance = linGuider;
1924
1925 clearCalibrationB->setEnabled(true);
1926 captureB->setEnabled(false);
1927 loopB->setEnabled(false);
1928 guideDarkFrame->setEnabled(false);
1929 guideSubframe->setEnabled(false);
1930 guideAutoStar->setEnabled(false);
1931 guideB->setEnabled(true);
1932 externalConnectB->setEnabled(true);
1933
1934 opsGuide->controlGroup->setEnabled(false);
1935 infoGroup->setEnabled(false);
1936 driftGraphicsGroup->setEnabled(false);
1937
1938 guideExposure->setEnabled(false);
1939 guideBinning->setEnabled(false);
1940 guideSquareSize->setEnabled(false);
1941
1942 updateGuideParams();
1943
1944 break;
1945 }
1946
1947 if (m_GuiderInstance != nullptr)
1948 {
1949 connect(m_GuiderInstance, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture);
1950 connect(m_GuiderInstance, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText);
1951 connect(m_GuiderInstance, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus);
1952 connect(m_GuiderInstance, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition);
1953 connect(m_GuiderInstance, &Ekos::GuideInterface::guideStats, this, &Ekos::Guide::guideStats);
1954
1955 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta);
1956 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse);
1957 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma);
1958 connect(m_GuiderInstance, &Ekos::GuideInterface::newSNR, this, &Ekos::Guide::setSNR);
1959 connect(m_GuiderInstance, &Ekos::GuideInterface::guideInfo, this, &Ekos::Guide::guideInfo);
1960 connect(m_GuiderInstance, &Ekos::GuideInterface::abortExposure, this, &Ekos::Guide::abortExposure);
1961
1962 driftGraph->connectGuider(m_GuiderInstance);
1963 targetPlot->connectGuider(m_GuiderInstance);
1964
1965 connect(m_GuiderInstance, &Ekos::GuideInterface::calibrationUpdate, this, &Ekos::Guide::calibrationUpdate);
1966
1967 connect(m_GuiderInstance, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera);
1968 }
1969
1970 externalConnectB->setEnabled(false);
1971 externalDisconnectB->setEnabled(false);
1972
1973 if (m_GuiderInstance != nullptr && guiderType != GUIDE_INTERNAL)
1974 {
1975 externalConnectB->setEnabled(!m_GuiderInstance->isConnected());
1976 externalDisconnectB->setEnabled(m_GuiderInstance->isConnected());
1977 }
1978
1979 if (m_GuiderInstance != nullptr)
1980 m_GuiderInstance->Connect();
1981
1982 return true;
1983}
1984
1985void Guide::guideInfo(const QString &info)
1986{
1987 if (info.size() == 0)
1988 {
1989 guideInfoLabel->setVisible(false);
1990 guideInfoText->setVisible(false);
1991 return;
1992 }
1993 guideInfoLabel->setVisible(true);
1994 guideInfoLabel->setText("Detections");
1995 guideInfoText->setVisible(true);
1996 guideInfoText->setText(info);
1997}
1998
1999void Guide::updateTrackingBoxSize(int currentIndex)
2000{
2001 if (currentIndex >= 0)
2002 {
2003 if (guiderType == GUIDE_INTERNAL)
2004 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setGuideBoxSize(guideSquareSize->currentText().toInt());
2005
2006 syncTrackingBoxPosition();
2007 }
2008}
2009
2010void Guide::onThresholdChanged(int index)
2011{
2012 switch (guiderType)
2013 {
2014 case GUIDE_INTERNAL:
2015 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setSquareAlgorithm(index);
2016 break;
2017
2018 default:
2019 break;
2020 }
2021}
2022
2023void Guide::onEnableDirRA()
2024{
2025 // If RA guiding is enable or disabled, the GPG should be reset.
2026 if (Options::gPGEnabled())
2027 m_GuiderInstance->resetGPG();
2028}
2029
2030void Guide::onEnableDirDEC()
2031{
2032 onControlDirectionChanged();
2033}
2034
2035void Guide::onControlDirectionChanged()
2036{
2037 if(guiderType == GUIDE_PHD2)
2038 phd2Guider -> requestSetDEGuideMode(dECGuideEnabled->isChecked(), northDECGuideEnabled->isChecked(),
2039 southDECGuideEnabled->isChecked());
2040}
2041
2042void Guide::updateDirectionsFromPHD2(const QString &mode)
2043{
2044 //disable connections
2045 disconnect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2046 disconnect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2047 disconnect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2048
2049 if(mode == "Auto")
2050 {
2051 dECGuideEnabled->setChecked(true);
2052 northDECGuideEnabled->setChecked(true);
2053 southDECGuideEnabled->setChecked(true);
2054 }
2055 else if(mode == "North")
2056 {
2057 dECGuideEnabled->setChecked(true);
2058 northDECGuideEnabled->setChecked(true);
2059 southDECGuideEnabled->setChecked(false);
2060 }
2061 else if(mode == "South")
2062 {
2063 dECGuideEnabled->setChecked(true);
2064 northDECGuideEnabled->setChecked(false);
2065 southDECGuideEnabled->setChecked(true);
2066 }
2067 else //Off
2068 {
2069 dECGuideEnabled->setChecked(false);
2070 northDECGuideEnabled->setChecked(true);
2071 southDECGuideEnabled->setChecked(true);
2072 }
2073
2074 //Re-enable connections
2075 connect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2076 connect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2077 connect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2078}
2079
2080void Guide::setTrackingStar(int x, int y)
2081{
2082 QVector3D newStarPosition(x, y, -1);
2083 setStarPosition(newStarPosition, true);
2084
2085 if(guiderType == GUIDE_PHD2)
2086 {
2087 //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that.
2088 if(!m_ImageData.isNull())
2089 {
2090 if(m_ImageData->width() > 50)
2091 phd2Guider->setLockPosition(starCenter.x(), starCenter.y());
2092 }
2093 }
2094
2095 if (operationStack.isEmpty() == false)
2096 executeOperationStack();
2097}
2098
2099void Guide::setAxisDelta(double ra, double de)
2100{
2101 //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding.
2102 //If guide pulses start coming in, it must be guiding.
2103 // 2020-04-10 sterne-jaeger: Will be resolved inside EKOS phd guiding.
2104 // if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING)
2105 // setStatus(GUIDE_GUIDING);
2106
2107 ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph.
2108
2109 int currentNumPoints = driftGraph->graph(GuideGraph::G_RA)->dataCount();
2110 guideSlider->setMaximum(currentNumPoints);
2111 if(graphOnLatestPt)
2112 {
2113 guideSlider->setValue(currentNumPoints);
2114 }
2115 l_DeltaRA->setText(QString::number(ra, 'f', 2));
2116 l_DeltaDEC->setText(QString::number(de, 'f', 2));
2117
2118 emit newAxisDelta(ra, de);
2119}
2120
2121void Guide::calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString &message,
2122 double dx, double dy)
2123{
2124 switch (type)
2125 {
2126 case GuideInterface::RA_OUT:
2127 calibrationPlot->graph(GuideGraph::G_RA)->addData(dx, dy);
2128 break;
2129 case GuideInterface::RA_IN:
2130 calibrationPlot->graph(GuideGraph::G_DEC)->addData(dx, dy);
2131 break;
2132 case GuideInterface::BACKLASH:
2133 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->addData(dx, dy);
2134 break;
2135 case GuideInterface::DEC_OUT:
2136 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(dx, dy);
2137 break;
2138 case GuideInterface::DEC_IN:
2139 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->addData(dx, dy);
2140 break;
2141 case GuideInterface::CALIBRATION_MESSAGE_ONLY:
2142 ;
2143 }
2144 calLabel->setText(message);
2145 calibrationPlot->replot();
2146}
2147
2148void Guide::setAxisSigma(double ra, double de)
2149{
2150 l_ErrRA->setText(QString::number(ra, 'f', 2));
2151 l_ErrDEC->setText(QString::number(de, 'f', 2));
2152 const double total = std::hypot(ra, de);
2153 l_TotalRMS->setText(QString::number(total, 'f', 2));
2154
2155 emit newAxisSigma(ra, de);
2156}
2157
2158QList<double> Guide::axisDelta()
2159{
2160 QList<double> delta;
2161
2162 delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble();
2163
2164 return delta;
2165}
2166
2167QList<double> Guide::axisSigma()
2168{
2169 QList<double> sigma;
2170
2171 sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble();
2172
2173 return sigma;
2174}
2175
2176void Guide::setAxisPulse(double ra, double de)
2177{
2178 l_PulseRA->setText(QString::number(static_cast<int>(ra)));
2179 l_PulseDEC->setText(QString::number(static_cast<int>(de)));
2180}
2181
2182void Guide::setSNR(double snr)
2183{
2184 l_SNR->setText(QString::number(snr, 'f', 1));
2185}
2186
2187void Guide::buildOperationStack(GuideState operation)
2188{
2189 operationStack.clear();
2190
2191 switch (operation)
2192 {
2193 case GUIDE_CAPTURE:
2194 if (guideDarkFrame->isChecked())
2195 operationStack.push(GUIDE_DARK);
2196
2197 operationStack.push(GUIDE_CAPTURE);
2198 operationStack.push(GUIDE_SUBFRAME);
2199 break;
2200
2201 case GUIDE_CALIBRATING:
2202 operationStack.push(GUIDE_CALIBRATING);
2203 if (guiderType == GUIDE_INTERNAL)
2204 {
2205 if (guideDarkFrame->isChecked())
2206 operationStack.push(GUIDE_DARK);
2207
2208 // Auto Star Selected Path
2209 if (guideAutoStar->isChecked() ||
2210 // SEP MultiStar always uses an automated guide star.
2211 internalGuider->SEPMultiStarEnabled())
2212 {
2213 // If subframe is enabled and we need to auto select a star, then we need to make the final capture
2214 // of the subframed image. This is only done if we aren't already subframed.
2215 if (subFramed == false && guideSubframe->isChecked())
2216 operationStack.push(GUIDE_CAPTURE);
2217
2218 operationStack.push(GUIDE_SUBFRAME);
2219 operationStack.push(GUIDE_STAR_SELECT);
2220
2221
2222 operationStack.push(GUIDE_CAPTURE);
2223
2224 // If we are being ask to go full frame, let's do that first
2225 if (subFramed == true && guideSubframe->isChecked() == false)
2226 operationStack.push(GUIDE_SUBFRAME);
2227 }
2228 // Manual Star Selection Path
2229 else
2230 {
2231 // Final capture before we start calibrating
2232 if (subFramed == false && guideSubframe->isChecked())
2233 operationStack.push(GUIDE_CAPTURE);
2234
2235 // Subframe if required
2236 operationStack.push(GUIDE_SUBFRAME);
2237
2238 // First capture an image
2239 operationStack.push(GUIDE_CAPTURE);
2240 }
2241
2242 }
2243 break;
2244
2245 default:
2246 break;
2247 }
2248}
2249
2250bool Guide::executeOperationStack()
2251{
2252 if (operationStack.isEmpty())
2253 return false;
2254
2255 GuideState nextOperation = operationStack.pop();
2256 // qCDebug(KSTARS_EKOS_GUIDE) << "Executing operation " << getGuideStatusString(nextOperation);
2257
2258 bool actionRequired = false;
2259
2260 switch (nextOperation)
2261 {
2262 case GUIDE_SUBFRAME:
2263 actionRequired = executeOneOperation(nextOperation);
2264 break;
2265
2266 case GUIDE_DARK:
2267 actionRequired = executeOneOperation(nextOperation);
2268 break;
2269
2270 case GUIDE_CAPTURE:
2271 actionRequired = captureOneFrame();
2272 break;
2273
2274 case GUIDE_STAR_SELECT:
2275 actionRequired = executeOneOperation(nextOperation);
2276 break;
2277
2278 case GUIDE_CALIBRATING:
2279 if (guiderType == GUIDE_INTERNAL)
2280 {
2281 m_GuiderInstance->setStarPosition(starCenter);
2282
2283 // Tracking must be engaged
2284 if (m_Mount && m_Mount->canControlTrack() && m_Mount->isTracking() == false)
2285 m_Mount->setTrackEnabled(true);
2286 }
2287
2288 if (m_GuiderInstance->calibrate())
2289 {
2290 if (guiderType == GUIDE_INTERNAL)
2291 disconnect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Guide::setTrackingStar);
2292 setBusy(true);
2293 }
2294 else
2295 {
2296 emit newStatus(GUIDE_CALIBRATION_ERROR);
2297 m_State = GUIDE_IDLE;
2298 appendLogText(i18n("Calibration failed to start."));
2299 setBusy(false);
2300 }
2301 break;
2302
2303 default:
2304 break;
2305 }
2306
2307 // If an additional action is required, return return and continue later
2308 if (actionRequired)
2309 return true;
2310 // Otherwise, continue processing the stack
2311 else
2312 return executeOperationStack();
2313}
2314
2315bool Guide::executeOneOperation(GuideState operation)
2316{
2317 bool actionRequired = false;
2318
2319 if (m_Camera == nullptr)
2320 return actionRequired;
2321
2322 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2323 if (targetChip == nullptr)
2324 return false;
2325
2326 int subBinX, subBinY;
2327 targetChip->getBinning(&subBinX, &subBinY);
2328
2329 switch (operation)
2330 {
2331 case GUIDE_SUBFRAME:
2332 {
2333 // SEP MultiStar doesn't subframe.
2334 if ((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled())
2335 break;
2336 // Check if we need and can subframe
2337 if (subFramed == false && guideSubframe->isChecked() == true && targetChip->canSubframe())
2338 {
2339 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2340 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2341
2342 int offset = guideSquareSize->currentText().toInt() / subBinX;
2343
2344 int x = starCenter.x();
2345 int y = starCenter.y();
2346
2347 x = (x - offset * 2) * subBinX;
2348 y = (y - offset * 2) * subBinY;
2349 int w = offset * 4 * subBinX;
2350 int h = offset * 4 * subBinY;
2351
2352 if (x < minX)
2353 x = minX;
2354 if (y < minY)
2355 y = minY;
2356 if ((x + w) > maxW)
2357 w = maxW - x;
2358 if ((y + h) > maxH)
2359 h = maxH - y;
2360
2361 targetChip->setFrame(x, y, w, h);
2362
2363 subFramed = true;
2364 QVariantMap settings = frameSettings[targetChip];
2365 settings["x"] = x;
2366 settings["y"] = y;
2367 settings["w"] = w;
2368 settings["h"] = h;
2369 settings["binx"] = subBinX;
2370 settings["biny"] = subBinY;
2371
2372 frameSettings[targetChip] = settings;
2373
2374 starCenter.setX(w / (2 * subBinX));
2375 starCenter.setY(h / (2 * subBinX));
2376 }
2377 // Otherwise check if we are already subframed
2378 // and we need to go back to full frame
2379 // or if we need to go back to full frame since we need
2380 // to reaquire a star
2381 else if (subFramed &&
2382 (guideSubframe->isChecked() == false ||
2383 m_State == GUIDE_REACQUIRE))
2384 {
2385 targetChip->resetFrame();
2386
2387 int x, y, w, h;
2388 targetChip->getFrame(&x, &y, &w, &h);
2389
2390 QVariantMap settings;
2391 settings["x"] = x;
2392 settings["y"] = y;
2393 settings["w"] = w;
2394 settings["h"] = h;
2395 settings["binx"] = subBinX;
2396 settings["biny"] = subBinY;
2397 frameSettings[targetChip] = settings;
2398
2399 subFramed = false;
2400
2401 starCenter.setX(w / (2 * subBinX));
2402 starCenter.setY(h / (2 * subBinX));
2403
2404 //starCenter.setX(0);
2405 //starCenter.setY(0);
2406 }
2407 }
2408 break;
2409
2410 case GUIDE_DARK:
2411 {
2412 // Do we need to take a dark frame?
2413 if (m_ImageData && guideDarkFrame->isChecked())
2414 {
2415 QVariantMap settings = frameSettings[targetChip];
2416 uint16_t offsetX = 0;
2417 uint16_t offsetY = 0;
2418
2419 if (settings["x"].isValid() &&
2420 settings["y"].isValid() &&
2421 settings["binx"].isValid() &&
2422 settings["biny"].isValid())
2423 {
2424 offsetX = settings["x"].toInt() / settings["binx"].toInt();
2425 offsetY = settings["y"].toInt() / settings["biny"].toInt();
2426 }
2427
2428 actionRequired = true;
2429 targetChip->setCaptureFilter(FITS_NONE);
2430 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()),
2431 targetChip, m_ImageData, guideExposure->value(), offsetX, offsetY);
2432 }
2433 }
2434 break;
2435
2436 case GUIDE_STAR_SELECT:
2437 {
2438 m_State = GUIDE_STAR_SELECT;
2439 emit newStatus(m_State);
2440
2441 if (guideAutoStar->isChecked() ||
2442 // SEP MultiStar always uses an automated guide star.
2443 ((guiderType == GUIDE_INTERNAL) &&
2444 internalGuider->SEPMultiStarEnabled()))
2445 {
2446 bool autoStarCaptured = internalGuider->selectAutoStar();
2447 if (autoStarCaptured)
2448 {
2449 appendLogText(i18n("Auto star selected."));
2450 }
2451 else
2452 {
2453 appendLogText(i18n("Failed to select an auto star."));
2454 actionRequired = true;
2455 m_State = GUIDE_CALIBRATION_ERROR;
2456 emit newStatus(m_State);
2457 setBusy(false);
2458 }
2459 }
2460 else
2461 {
2462 appendLogText(i18n("Select a guide star to calibrate."));
2463 actionRequired = true;
2464 }
2465 }
2466 break;
2467
2468 default:
2469 break;
2470 }
2471
2472 return actionRequired;
2473}
2474
2475void Guide::processGuideOptions()
2476{
2477 if (Options::guiderType() != guiderType)
2478 {
2479 guiderType = static_cast<GuiderType>(Options::guiderType());
2480 setGuiderType(Options::guiderType());
2481 }
2482}
2483
2484void Guide::showFITSViewer()
2485{
2486 static int lastFVTabID = -1;
2487 if (m_ImageData)
2488 {
2489 QUrl url = QUrl::fromLocalFile("guide.fits");
2490 if (fv.isNull())
2491 {
2492 fv = KStars::Instance()->createFITSViewer();
2493 fv->loadData(m_ImageData, url, &lastFVTabID);
2494 connect(fv.get(), &FITSViewer::terminated, this, [this]()
2495 {
2496 fv.clear();
2497 });
2498 }
2499 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false)
2500 fv->loadData(m_ImageData, url, &lastFVTabID);
2501
2502 fv->show();
2503 }
2504}
2505
2506void Guide::setExternalGuiderBLOBEnabled(bool enable)
2507{
2508 // Nothing to do if guider is internal
2509 if (guiderType == GUIDE_INTERNAL)
2510 return;
2511
2512 if(!m_Camera)
2513 return;
2514
2515 m_Camera->setBLOBEnabled(enable);
2516
2517 if(m_Camera->isBLOBEnabled())
2518 {
2519 checkUseGuideHead();
2520
2521 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2522 if (targetChip)
2523 targetChip->setCaptureMode(FITS_GUIDE);
2524 syncCameraInfo();
2525 }
2526
2527}
2528
2529void Guide::resetNonGuidedDither()
2530{
2531 // reset non guided dither total drift
2532 nonGuidedDitherRaOffsetMsec = 0;
2533 nonGuidedDitherDecOffsetMsec = 0;
2534 qCDebug(KSTARS_EKOS_GUIDE) << "Reset non guiding dithering position";
2535
2536 // initialize random generator if not done before
2537 if (!isNonGuidedDitherInitialized)
2538 {
2539 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
2540 nonGuidedPulseGenerator.seed(seed);
2541 isNonGuidedDitherInitialized = true;
2542 qCDebug(KSTARS_EKOS_GUIDE) << "Initialize non guiding dithering random generator";
2543 }
2544}
2545
2546void Guide::nonGuidedDither()
2547{
2548 double ditherPulse = Options::ditherNoGuidingPulse();
2549
2550 // Randomize dithering position up to +/-dithePulse distance from original
2551 std::uniform_int_distribution<int> newPos(-ditherPulse, +ditherPulse);
2552
2553 // Calculate the pulse needed to move to the new position, then save the new position and apply the pulse
2554
2555 // for ra
2556 const int newRaOffsetMsec = newPos(nonGuidedPulseGenerator);
2557 const int raPulse = nonGuidedDitherRaOffsetMsec - newRaOffsetMsec;
2558 nonGuidedDitherRaOffsetMsec = newRaOffsetMsec;
2559 const int raMsec = std::abs(raPulse);
2560 const int raPolarity = (raPulse >= 0 ? 1 : -1);
2561
2562 // and for dec
2563 const int newDecOffsetMsec = newPos(nonGuidedPulseGenerator);
2564 const int decPulse = nonGuidedDitherDecOffsetMsec - newDecOffsetMsec;
2565 nonGuidedDitherDecOffsetMsec = newDecOffsetMsec;
2566 const int decMsec = std::abs(decPulse);
2567 const int decPolarity = (decPulse >= 0 ? 1 : -1);
2568
2569 qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither...";
2570 qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << raMsec << "ra_polarity:" << raPolarity << "de_msec:" << decMsec <<
2571 "de_polarity:" << decPolarity;
2572
2573 bool rc = sendMultiPulse(raPolarity > 0 ? RA_INC_DIR : RA_DEC_DIR, raMsec, decPolarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR,
2574 decMsec, DontCaptureAfterPulses);
2575
2576 if (rc)
2577 {
2578 qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful.";
2579 QTimer::singleShot( (raMsec > decMsec ? raMsec : decMsec) + Options::ditherSettle() * 1000 + 100, this, [this]()
2580 {
2581 emit newStatus(GUIDE_DITHERING_SUCCESS);
2582 m_State = GUIDE_IDLE;
2583 });
2584 }
2585 else
2586 {
2587 qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed.";
2588 emit newStatus(GUIDE_DITHERING_ERROR);
2589 m_State = GUIDE_IDLE;
2590 }
2591}
2592
2593void Guide::handleManualDither()
2594{
2595 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2596 if (targetChip == nullptr)
2597 return;
2598
2599 Ui::ManualDither ditherDialog;
2600 QDialog container(this);
2601 ditherDialog.setupUi(&container);
2602
2603 if (guiderType != GUIDE_INTERNAL)
2604 {
2605 ditherDialog.coordinatesR->setEnabled(false);
2606 ditherDialog.x->setEnabled(false);
2607 ditherDialog.y->setEnabled(false);
2608 }
2609
2610 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2611 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2612
2613 ditherDialog.x->setMinimum(minX);
2614 ditherDialog.x->setMaximum(maxX);
2615 ditherDialog.y->setMinimum(minY);
2616 ditherDialog.y->setMaximum(maxY);
2617
2618 ditherDialog.x->setValue(starCenter.x());
2619 ditherDialog.y->setValue(starCenter.y());
2620
2621 if (container.exec() == QDialog::Accepted)
2622 {
2623 if (ditherDialog.magnitudeR->isChecked())
2624 m_GuiderInstance->dither(ditherDialog.magnitude->value());
2625 else
2626 {
2627 InternalGuider * const ig = dynamic_cast<InternalGuider *>(m_GuiderInstance);
2628 if (ig)
2629 ig->ditherXY(ditherDialog.x->value(), ditherDialog.y->value());
2630 }
2631 }
2632}
2633
2634bool Guide::connectGuider()
2635{
2636 setStatus(GUIDE_IDLE);
2637 return m_GuiderInstance->Connect();
2638}
2639
2640bool Guide::disconnectGuider()
2641{
2642 return m_GuiderInstance->Disconnect();
2643}
2644
2645void Guide::initPlots()
2646{
2647 initDriftGraph();
2648 initCalibrationPlot();
2649
2650 connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange);
2651 connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange);
2652
2653 buildTarget();
2654}
2655
2656void Guide::initDriftGraph()
2657{
2658 //Dragging and zooming settings
2659 // make bottom axis transfer its range to the top axis if the graph gets zoomed:
2660 connect(driftGraph->xAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2661 driftGraph->xAxis2, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::setRange));
2662 // update the second vertical axis properly if the graph gets zoomed.
2663 connect(driftGraph->yAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged),
2664 [this]()
2665 {
2666 driftGraph->setCorrectionGraphScale(correctionSlider->value());
2667 });
2668
2669 connect(driftGraph, &QCustomPlot::mouseMove, driftGraph, &GuideDriftGraph::mouseOverLine);
2670 connect(driftGraph, &QCustomPlot::mousePress, driftGraph, &GuideDriftGraph::mouseClicked);
2671
2672 int scale =
2673 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg
2674 correctionSlider->setValue(scale);
2675}
2676
2677void Guide::initCalibrationPlot()
2678{
2679 calibrationPlot->setBackground(QBrush(Qt::black));
2680 calibrationPlot->setSelectionTolerance(10);
2681
2682 calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1));
2683 calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1));
2684
2685 calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1));
2686 calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1));
2687
2688 calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1));
2689 calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1));
2690
2691 calibrationPlot->xAxis->setTickLabelColor(Qt::white);
2692 calibrationPlot->yAxis->setTickLabelColor(Qt::white);
2693
2694 calibrationPlot->xAxis->setLabelColor(Qt::white);
2695 calibrationPlot->yAxis->setLabelColor(Qt::white);
2696
2697 calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10));
2698 calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10));
2699 calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9));
2700 calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9));
2701
2702 calibrationPlot->xAxis->setLabelPadding(2);
2703 calibrationPlot->yAxis->setLabelPadding(2);
2704
2705 calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2706 calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
2707 calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2708 calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
2709 calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2710 calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray));
2711
2712 calibrationPlot->xAxis->setLabel(i18n("x (pixels)"));
2713 calibrationPlot->yAxis->setLabel(i18n("y (pixels)"));
2714
2715 calibrationPlot->xAxis->setRange(-20, 20);
2716 calibrationPlot->yAxis->setRange(-20, 20);
2717
2718 calibrationPlot->setInteractions(QCP::iRangeZoom);
2719 calibrationPlot->setInteraction(QCP::iRangeDrag, true);
2720
2721 calibrationPlot->addGraph();
2722 calibrationPlot->graph(GuideGraph::G_RA)->setLineStyle(QCPGraph::lsNone);
2723 calibrationPlot->graph(GuideGraph::G_RA)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
2724 QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 6));
2725 calibrationPlot->graph(GuideGraph::G_RA)->setName("RA out");
2726
2727 calibrationPlot->addGraph();
2728 calibrationPlot->graph(GuideGraph::G_DEC)->setLineStyle(QCPGraph::lsNone);
2729 calibrationPlot->graph(GuideGraph::G_DEC)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::white, 2),
2730 QBrush(), 4));
2731 calibrationPlot->graph(GuideGraph::G_DEC)->setName("RA in");
2732
2733 calibrationPlot->addGraph();
2734 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
2735 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlus, QPen(Qt::white,
2736 2),
2737 QBrush(), 6));
2738 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setName("Backlash");
2739
2740 calibrationPlot->addGraph();
2741 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
2742 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,
2743 QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 6));
2744 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setName("DEC out");
2745
2746 calibrationPlot->addGraph();
2747 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setLineStyle(QCPGraph::lsNone);
2748 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::yellow,
2749 2),
2750 QBrush(), 4));
2751 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setName("DEC in");
2752
2753 calLabel = new QCPItemText(calibrationPlot);
2754 calLabel->setColor(QColor(255, 255, 255));
2755 calLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter);
2756 calLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
2757 calLabel->position->setCoords(0.5, 0);
2758 calLabel->setText("");
2759 calLabel->setFont(QFont(font().family(), 10));
2760 calLabel->setVisible(true);
2761
2762 calibrationPlot->resize(190, 190);
2763 calibrationPlot->replot();
2764}
2765
2766void Guide::initView()
2767{
2768 guideStateWidget = new GuideStateWidget();
2769 guideInfoLayout->insertWidget(-1, guideStateWidget);
2770
2771 m_GuideView.reset(new GuideView(guideWidget, FITS_GUIDE));
2772 m_GuideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
2773 m_GuideView->setBaseSize(guideWidget->size());
2774 m_GuideView->createFloatingToolBar();
2776 vlayout->addWidget(m_GuideView.get());
2777 guideWidget->setLayout(vlayout);
2778 connect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar);
2779 guideInfoLabel->setVisible(false);
2780 guideInfoText->setVisible(false);
2781}
2782
2783void Guide::initConnections()
2784{
2785 // Exposure Timeout
2786 captureTimeout.setSingleShot(true);
2787 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout);
2788
2789 // Setup Debounce timer to limit over-activation of settings changes
2790 m_DebounceTimer.setInterval(500);
2791 m_DebounceTimer.setSingleShot(true);
2792 connect(&m_DebounceTimer, &QTimer::timeout, this, &Guide::settleSettings);
2793
2794 // Guiding Box Size
2795 connect(guideSquareSize, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
2796 &Ekos::Guide::updateTrackingBoxSize);
2797
2798 // Dark Frame Check
2800 // Subframe check
2801 if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method
2803
2804 // Binning Combo Selection
2805 connect(guideBinning, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
2806 &Ekos::Guide::updateCCDBin);
2807
2808 // RA/DEC Enable directions
2809 connect(rAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA);
2810 connect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC);
2811
2812 // N/W and W/E direction enable
2813 connect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2814 connect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2815 connect(westRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2816 connect(eastRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged);
2817
2818 // Capture
2819 connect(captureB, &QPushButton::clicked, this, [this]()
2820 {
2821 m_State = GUIDE_CAPTURE;
2822 emit newStatus(m_State);
2823
2824 if(guiderType == GUIDE_PHD2)
2825 {
2826 configurePHD2Camera();
2827 if(phd2Guider->isCurrentCameraNotInEkos())
2828 appendLogText(
2829 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."));
2830 else if(guideSubframe->isChecked())
2831 {
2832 appendLogText(
2833 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"));
2834 guideSubframe->setChecked(false);
2835 }
2836 phd2Guider->captureSingleFrame();
2837 }
2838 else if (guiderType == GUIDE_INTERNAL)
2839 capture();
2840 });
2841
2842 // Framing
2843 connect(loopB, &QPushButton::clicked, this, &Guide::loop);
2844
2845 // Stop
2847
2848 // Clear Calibrate
2849 //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate()));
2851
2852 // Guide
2854
2855 // Connect External Guide
2857 {
2858 //setExternalGuiderBLOBEnabled(false);
2859 m_GuiderInstance->Connect();
2860 });
2862 {
2863 //setExternalGuiderBLOBEnabled(true);
2864 m_GuiderInstance->Disconnect();
2865 });
2866
2867 // Pulse Timer
2868 m_PulseTimer.setSingleShot(true);
2869 connect(&m_PulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture);
2870
2871 //This connects all the buttons and slider below the guide plots.
2872 connect(guiderAccuracyThreshold, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
2873 &Ekos::Guide::buildTarget);
2874 connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory);
2875 connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint);
2876 connect(rADisplayedOnGuideGraph, &QCheckBox::toggled, [this](bool isChecked)
2877 {
2878 driftGraph->toggleShowPlot(GuideGraph::G_RA, isChecked);
2879 });
2880 connect(dEDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
2881 {
2882 driftGraph->toggleShowPlot(GuideGraph::G_DEC, isChecked);
2883 });
2884 connect(rACorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
2885 {
2886 driftGraph->toggleShowPlot(GuideGraph::G_RA_PULSE, isChecked);
2887 });
2888 connect(dECorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
2889 {
2890 driftGraph->toggleShowPlot(GuideGraph::G_DEC_PULSE, isChecked);
2891 });
2892 connect(sNRDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
2893 {
2894 driftGraph->toggleShowPlot(GuideGraph::G_SNR, isChecked);
2895 });
2896 connect(rMSDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked)
2897 {
2898 driftGraph->toggleShowPlot(GuideGraph::G_RMS, isChecked);
2899 });
2900 connect(correctionSlider, &QSlider::sliderMoved, driftGraph, &GuideDriftGraph::setCorrectionGraphScale);
2901
2902 connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither);
2903
2904 connect(this, &Ekos::Guide::newStatus, guideStateWidget, &Ekos::GuideStateWidget::updateGuideStatus);
2905}
2906
2907void Guide::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
2908{
2909 auto name = device->getDeviceName();
2910
2911 device->disconnect(this);
2912
2913 // Mounts
2914 if (m_Mount && m_Mount->getDeviceName() == name)
2915 {
2916 m_Mount->disconnect(this);
2917 m_Mount = nullptr;
2918 }
2919
2920
2921 // Cameras
2922 if (m_Camera && m_Camera->getDeviceName() == name)
2923 {
2924 m_Camera->disconnect(this);
2925 m_Camera = nullptr;
2926 }
2927
2928
2929 // Guiders
2930 if (m_Guider && m_Guider->getDeviceName() == name)
2931 {
2932 m_Guider->disconnect(this);
2933 m_Guider = nullptr;
2934 }
2935
2936 // Adaptive Optics
2937 // FIXME AO are not yet utilized property in Guide module
2938 if (m_AO && m_AO->getDeviceName() == name)
2939 {
2940 m_AO->disconnect(this);
2941 m_AO = nullptr;
2942 }
2943}
2944
2945void Guide::loop()
2946{
2947 m_State = GUIDE_LOOPING;
2948 emit newStatus(m_State);
2949
2950 if(guiderType == GUIDE_PHD2)
2951 {
2952 configurePHD2Camera();
2953 if(phd2Guider->isCurrentCameraNotInEkos())
2954 appendLogText(
2955 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."));
2956 else if(guideSubframe->isChecked())
2957 {
2958 appendLogText(
2959 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"));
2960 guideSubframe->setChecked(false);
2961 }
2962 phd2Guider->loop();
2963 stopB->setEnabled(true);
2964 }
2965 else if (guiderType == GUIDE_INTERNAL)
2966 capture();
2967}
2968
2969///////////////////////////////////////////////////////////////////////////////////////////
2970///
2971///////////////////////////////////////////////////////////////////////////////////////////
2972QVariantMap Guide::getAllSettings() const
2973{
2974 QVariantMap settings;
2975
2976 // All Combo Boxes
2977 for (auto &oneWidget : findChildren<QComboBox*>())
2978 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2979
2980 // All Double Spin Boxes
2981 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
2982 settings.insert(oneWidget->objectName(), oneWidget->value());
2983
2984 // All Spin Boxes
2985 for (auto &oneWidget : findChildren<QSpinBox*>())
2986 settings.insert(oneWidget->objectName(), oneWidget->value());
2987
2988 // All Checkboxes
2989 for (auto &oneWidget : findChildren<QCheckBox*>())
2990 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2991
2992 return settings;
2993}
2994
2995///////////////////////////////////////////////////////////////////////////////////////////
2996///
2997///////////////////////////////////////////////////////////////////////////////////////////
2998void Guide::setAllSettings(const QVariantMap &settings)
2999{
3000 // Disconnect settings that we don't end up calling syncSettings while
3001 // performing the changes.
3002 disconnectSettings();
3003
3004 for (auto &name : settings.keys())
3005 {
3006 // Combo
3007 auto comboBox = findChild<QComboBox*>(name);
3008 if (comboBox)
3009 {
3010 syncControl(settings, name, comboBox);
3011 continue;
3012 }
3013
3014 // Double spinbox
3015 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
3016 if (doubleSpinBox)
3017 {
3018 syncControl(settings, name, doubleSpinBox);
3019 continue;
3020 }
3021
3022 // spinbox
3023 auto spinBox = findChild<QSpinBox*>(name);
3024 if (spinBox)
3025 {
3026 syncControl(settings, name, spinBox);
3027 continue;
3028 }
3029
3030 // checkbox
3031 auto checkbox = findChild<QCheckBox*>(name);
3032 if (checkbox)
3033 {
3034 syncControl(settings, name, checkbox);
3035 continue;
3036 }
3037 }
3038
3039 // Sync to options
3040 for (auto &key : settings.keys())
3041 {
3042 auto value = settings[key];
3043 // Save immediately
3044 Options::self()->setProperty(key.toLatin1(), value);
3045
3046 m_Settings[key] = value;
3047 m_GlobalSettings[key] = value;
3048 }
3049
3050 emit settingsUpdated(getAllSettings());
3051
3052 // Save to optical train specific settings as well
3053 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
3054 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings);
3055
3056 // Restablish connections
3057 connectSettings();
3058}
3059
3060///////////////////////////////////////////////////////////////////////////////////////////
3061///
3062///////////////////////////////////////////////////////////////////////////////////////////
3063bool Guide::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
3064{
3065 QSpinBox *pSB = nullptr;
3066 QDoubleSpinBox *pDSB = nullptr;
3067 QCheckBox *pCB = nullptr;
3068 QComboBox *pComboBox = nullptr;
3069 QRadioButton *pRadioButton = nullptr;
3070 bool ok = false;
3071
3072 if ((pSB = qobject_cast<QSpinBox *>(widget)))
3073 {
3074 const int value = settings[key].toInt(&ok);
3075 if (ok)
3076 {
3077 pSB->setValue(value);
3078 return true;
3079 }
3080 }
3081 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
3082 {
3083 const double value = settings[key].toDouble(&ok);
3084 if (ok)
3085 {
3086 pDSB->setValue(value);
3087 return true;
3088 }
3089 }
3090 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
3091 {
3092 const bool value = settings[key].toBool();
3093 if (value != pCB->isChecked())
3094 pCB->click();
3095 return true;
3096 }
3097 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
3098 {
3099 const bool value = settings[key].toBool();
3100 if (value)
3101 pRadioButton->click();
3102 return true;
3103 }
3104 // ONLY FOR STRINGS, not INDEX
3105 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
3106 {
3107 const QString value = settings[key].toString();
3108 pComboBox->setCurrentText(value);
3109 return true;
3110 }
3111
3112 return false;
3113};
3114
3115void Guide::setupOpticalTrainManager()
3116{
3117 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Guide::refreshOpticalTrain);
3118 connect(trainB, &QPushButton::clicked, this, [this]()
3119 {
3120 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
3121 });
3123 {
3124 if (guiderType == GUIDE_PHD2 && m_GuiderInstance->isConnected())
3125 {
3126 appendLogText(i18n("Cannot change active optical train while PHD2 is connected"));
3127 return;
3128 }
3129
3130 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain,
3131 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
3132 refreshOpticalTrain();
3133 emit trainChanged();
3134 });
3135}
3136
3137void Guide::refreshOpticalTrain()
3138{
3139 opticalTrainCombo->blockSignals(true);
3140 opticalTrainCombo->clear();
3141 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
3142 trainB->setEnabled(true);
3143
3144 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::GuideOpticalTrain);
3145
3146 if (trainID.isValid())
3147 {
3148 auto id = trainID.toUInt();
3149
3150 // If train not found, select the first one available.
3151 if (OpticalTrainManager::Instance()->exists(id) == false)
3152 {
3153 qCWarning(KSTARS_EKOS_GUIDE) << "Optical train doesn't exist for id" << id;
3154 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
3155 }
3156
3157 auto name = OpticalTrainManager::Instance()->name(id);
3158
3159 opticalTrainCombo->setCurrentText(name);
3160
3161 auto scope = OpticalTrainManager::Instance()->getScope(name);
3162 m_FocalLength = scope["focal_length"].toDouble(-1);
3163 m_Aperture = scope["aperture"].toDouble(-1);
3164 m_FocalRatio = scope["focal_ratio"].toDouble(-1);
3165 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
3166
3167 // DSLR Lens Aperture
3169 m_Aperture = m_FocalLength / m_FocalRatio;
3170
3171 auto mount = OpticalTrainManager::Instance()->getMount(name);
3172 setMount(mount);
3173
3174 auto camera = OpticalTrainManager::Instance()->getCamera(name);
3175 if (camera)
3176 {
3177 if (guiderType == GUIDE_INTERNAL)
3178 starCenter = QVector3D();
3179
3180 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
3181 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString()));
3182 }
3183 setCamera(camera);
3184
3185 syncTelescopeInfo();
3186
3187 auto guider = OpticalTrainManager::Instance()->getGuider(name);
3188 setGuider(guider);
3189
3190 auto ao = OpticalTrainManager::Instance()->getAdaptiveOptics(name);
3191 setAdaptiveOptics(ao);
3192
3193 // Load train settings
3194 OpticalTrainSettings::Instance()->setOpticalTrainID(id);
3195 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Guide);
3196 if (settings.isValid())
3197 {
3198 auto map = settings.toJsonObject().toVariantMap();
3199 if (map != m_Settings)
3200 setAllSettings(map);
3201 }
3202 else
3203 m_Settings = m_GlobalSettings;
3204 }
3205
3206 opticalTrainCombo->blockSignals(false);
3207}
3208
3209void Guide::loadGlobalSettings()
3210{
3211 QString key;
3212 QVariant value;
3213
3214 QVariantMap settings;
3215 // All Combo Boxes
3216 for (auto &oneWidget : findChildren<QComboBox*>())
3217 {
3218 if (oneWidget->objectName() == "opticalTrainCombo")
3219 continue;
3220
3221 key = oneWidget->objectName();
3222 value = Options::self()->property(key.toLatin1());
3223 if (value.isValid())
3224 {
3225 oneWidget->setCurrentText(value.toString());
3226 settings[key] = value;
3227 }
3228 }
3229
3230 // All Double Spin Boxes
3231 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3232 {
3233 key = oneWidget->objectName();
3234 value = Options::self()->property(key.toLatin1());
3235 if (value.isValid())
3236 {
3237 oneWidget->setValue(value.toDouble());
3238 settings[key] = value;
3239 }
3240 }
3241
3242 // All Spin Boxes
3243 for (auto &oneWidget : findChildren<QSpinBox*>())
3244 {
3245 key = oneWidget->objectName();
3246 value = Options::self()->property(key.toLatin1());
3247 if (value.isValid())
3248 {
3249 oneWidget->setValue(value.toInt());
3250 settings[key] = value;
3251 }
3252 }
3253
3254 // All Checkboxes
3255 for (auto &oneWidget : findChildren<QCheckBox*>())
3256 {
3257 key = oneWidget->objectName();
3258 value = Options::self()->property(key.toLatin1());
3259 if (value.isValid())
3260 {
3261 oneWidget->setChecked(value.toBool());
3262 settings[key] = value;
3263 }
3264 }
3265
3266 m_GlobalSettings = m_Settings = settings;
3267}
3268
3269void Guide::connectSettings()
3270{
3271 // All Combo Boxes
3272 for (auto &oneWidget : findChildren<QComboBox*>())
3273 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3274
3275 // All Double Spin Boxes
3276 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3277 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3278
3279 // All Spin Boxes
3280 for (auto &oneWidget : findChildren<QSpinBox*>())
3281 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3282
3283 // All Checkboxes
3284 for (auto &oneWidget : findChildren<QCheckBox*>())
3285 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3286
3287 // All Radio buttons
3288 for (auto &oneWidget : findChildren<QRadioButton*>())
3289 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3290
3291 // Train combo box should NOT be synced.
3292 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3293}
3294
3295void Guide::disconnectSettings()
3296{
3297 // All Combo Boxes
3298 for (auto &oneWidget : findChildren<QComboBox*>())
3299 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings);
3300
3301 // All Double Spin Boxes
3302 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
3303 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3304
3305 // All Spin Boxes
3306 for (auto &oneWidget : findChildren<QSpinBox*>())
3307 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings);
3308
3309 // All Checkboxes
3310 for (auto &oneWidget : findChildren<QCheckBox*>())
3311 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3312
3313 // All Radio buttons
3314 for (auto &oneWidget : findChildren<QRadioButton*>())
3315 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings);
3316
3317}
3318
3319void Guide::updateSetting(const QString &key, const QVariant &value)
3320{
3321 // Save immediately
3322 Options::self()->setProperty(key.toLatin1(), value);
3323 m_Settings[key] = value;
3324 m_GlobalSettings[key] = value;
3325
3326 m_DebounceTimer.start();
3327}
3328
3329///////////////////////////////////////////////////////////////////////////////////////////
3330///
3331///////////////////////////////////////////////////////////////////////////////////////////
3332void Guide::settleSettings()
3333{
3334 Options::self()->save();
3335 emit settingsUpdated(getAllSettings());
3336 // Save to optical train specific settings as well
3337 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
3338 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings);
3339}
3340
3341void Guide::syncSettings()
3342{
3343 QDoubleSpinBox *dsb = nullptr;
3344 QSpinBox *sb = nullptr;
3345 QCheckBox *cb = nullptr;
3346 QComboBox *cbox = nullptr;
3347 QRadioButton *rb = nullptr;
3348
3349 QString key;
3350 QVariant value;
3351
3352 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
3353 {
3354 key = dsb->objectName();
3355 value = dsb->value();
3356
3357 }
3358 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
3359 {
3360 key = sb->objectName();
3361 value = sb->value();
3362 }
3363 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
3364 {
3365 key = cb->objectName();
3366 value = cb->isChecked();
3367 }
3368 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
3369 {
3370 key = cbox->objectName();
3371 value = cbox->currentText();
3372 }
3373 else if ( (rb = qobject_cast<QRadioButton*>(sender())))
3374 {
3375 key = rb->objectName();
3376 value = true;
3377 }
3378
3379 updateSetting(key, value);
3380}
3381
3382
3383}
Q_SCRIPTABLE bool calibrate()
DBUS interface function.
Definition guide.cpp:1205
void updateProperty(INDI::Property prop)
processCCDNumber Process number properties arriving from CCD.
Definition guide.cpp:1663
Q_SCRIPTABLE Q_NOREPLY void setAutoStarEnabled(bool enable)
DBUS interface function.
Definition guide.cpp:1483
Q_SCRIPTABLE Q_NOREPLY void setDarkFrameEnabled(bool enable)
DBUS interface function.
Definition guide.cpp:1728
void processData(const QSharedPointer< FITSData > &data)
newFITS is called by the INDI framework whenever there is a new BLOB arriving
Definition guide.cpp:996
Q_SCRIPTABLE bool resume()
DBUS interface function.
Definition guide.cpp:1351
void setTrackingStar(int x, int y)
setTrackingStar Gets called when the user select a star in the guide frame
Definition guide.cpp:2080
Q_SCRIPTABLE bool setGuiderType(int type)
DBUS interface function.
Definition guide.cpp:1813
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:1686
void updateSetting(const QString &key, const QVariant &value)
updateSetting Update per-train and global setting
Definition guide.cpp:3319
bool setGuider(ISD::Guider *device)
Add new Guider.
Definition guide.cpp:687
Q_SCRIPTABLE bool suspend()
DBUS interface function.
Definition guide.cpp:1341
Q_SCRIPTABLE bool dither()
DBUS interface function.
Definition guide.cpp:1305
Q_SCRIPTABLE Q_NOREPLY void setExposure(double value)
DBUS interface function.
Definition guide.cpp:1470
Q_SCRIPTABLE Q_NOREPLY void setSubFrameEnabled(bool enable)
DBUS interface function.
Definition guide.cpp:1475
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:734
Q_SCRIPTABLE Q_NOREPLY void clearCalibration()
DBUS interface function.
Definition guide.cpp:1489
Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition guide.cpp:819
bool setAdaptiveOptics(ISD::AdaptiveOptics *device)
Add new Adaptive Optics.
Definition guide.cpp:713
void setDECSwap(bool enable)
setDECSwap Change ST4 declination pulse direction.
Definition guide.cpp:1157
void clearLog()
clearLog As the name suggests
Definition guide.cpp:1151
Q_SCRIPTABLE bool guide()
DBUS interface function.
Definition guide.cpp:1251
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:47
device handle controlling Mounts.
Definition indimount.h:27
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.
@ 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 setText(const QString &text)
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:78
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.
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
bool isValid(QStringView ifopt)
QString name(StandardShortcut id)
@ iRangeDrag
0x001 Axis ranges are draggable (see QCPAxisRect::setRangeDrag, QCPAxisRect::setRangeDragAxes)
@ iRangeZoom
0x002 Axis ranges are zoomable with the mouse wheel (see QCPAxisRect::setRangeZoom,...
bool isChecked() const const
void clicked(bool checked)
void toggled(bool checked)
void sliderMoved(int value)
void editingFinished()
void activated(int index)
void currentIndexChanged(int index)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void valueChanged(double d)
qint64 elapsed() const const
Int toInt() 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 splitterMoved(int pos, int index)
void push(const T &t)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
AlignVCenter
UniqueConnection
WA_LayoutUsesWidgetRect
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void stop()
void timeout()
QUrl fromLocalFile(const QString &localFile)
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const
uint toUInt(bool *ok) const const
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 Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.