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

KDE's Doxygen guidelines are available online.