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

KDE's Doxygen guidelines are available online.