Kstars

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

KDE's Doxygen guidelines are available online.