File indexing completed on 2024-04-28 15:09:52
0001 /* 0002 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "guide.h" 0008 0009 #include "guideadaptor.h" 0010 #include "kstars.h" 0011 #include "ksmessagebox.h" 0012 #include "kstarsdata.h" 0013 #include "opscalibration.h" 0014 #include "opsguide.h" 0015 #include "opsdither.h" 0016 #include "opsgpg.h" 0017 #include "Options.h" 0018 #include "indi/indiguider.h" 0019 #include "indi/indiadaptiveoptics.h" 0020 #include "auxiliary/QProgressIndicator.h" 0021 #include "ekos/auxiliary/opticaltrainmanager.h" 0022 #include "ekos/auxiliary/profilesettings.h" 0023 #include "ekos/auxiliary/opticaltrainsettings.h" 0024 #include "ekos/auxiliary/darklibrary.h" 0025 #include "externalguide/linguider.h" 0026 #include "externalguide/phd2.h" 0027 #include "fitsviewer/fitsdata.h" 0028 #include "fitsviewer/fitsview.h" 0029 #include "fitsviewer/fitsviewer.h" 0030 #include "internalguide/internalguider.h" 0031 #include "guideview.h" 0032 #include "guidegraph.h" 0033 #include "guidestatewidget.h" 0034 #include "manualpulse.h" 0035 #include "ekos/auxiliary/darkprocessor.h" 0036 0037 #include <KConfigDialog> 0038 0039 #include <basedevice.h> 0040 #include <ekos_guide_debug.h> 0041 0042 #include "ui_manualdither.h" 0043 0044 #include <random> 0045 0046 #define CAPTURE_TIMEOUT_THRESHOLD 30000 0047 0048 namespace Ekos 0049 { 0050 Guide::Guide() : QWidget() 0051 { 0052 // #0 Prelude 0053 internalGuider = new InternalGuider(); // Init Internal Guider always 0054 0055 KConfigDialog *dialog = new KConfigDialog(this, "guidesettings", Options::self()); 0056 0057 opsGuide = new OpsGuide(); // Initialize sub dialog AFTER main dialog 0058 KPageWidgetItem *page = dialog->addPage(opsGuide, i18n("Guide")); 0059 page->setIcon(QIcon::fromTheme("kstars_guides")); 0060 connect(opsGuide, &OpsGuide::settingsUpdated, this, [this]() 0061 { 0062 onThresholdChanged(Options::guideAlgorithm()); 0063 configurePHD2Camera(); 0064 configSEPMultistarOptions(); // due to changes in 'Guide Setting: Algorithm' 0065 checkUseGuideHead(); 0066 }); 0067 0068 opsCalibration = new OpsCalibration(internalGuider); 0069 page = dialog->addPage(opsCalibration, i18n("Calibration")); 0070 page->setIcon(QIcon::fromTheme("tool-measure")); 0071 0072 opsDither = new OpsDither(); 0073 page = dialog->addPage(opsDither, i18n("Dither")); 0074 page->setIcon(QIcon::fromTheme("transform-move")); 0075 0076 opsGPG = new OpsGPG(internalGuider); 0077 page = dialog->addPage(opsGPG, i18n("GPG RA Guider")); 0078 page->setIcon(QIcon::fromTheme("pathshape")); 0079 0080 // #1 Setup UI 0081 setupUi(this); 0082 0083 // #2 Register DBus 0084 qRegisterMetaType<Ekos::GuideState>("Ekos::GuideState"); 0085 qDBusRegisterMetaType<Ekos::GuideState>(); 0086 new GuideAdaptor(this); 0087 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this); 0088 0089 // #3 Init Plots 0090 initPlots(); 0091 0092 // #4 Init View 0093 initView(); 0094 internalGuider->setGuideView(m_GuideView); 0095 0096 // #5 Init Connections 0097 initConnections(); 0098 0099 // Progress Indicator 0100 pi = new QProgressIndicator(this); 0101 controlLayout->addWidget(pi, 1, 2, 1, 1); 0102 0103 showFITSViewerB->setIcon( 0104 QIcon::fromTheme("kstars_fitsviewer")); 0105 connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Guide::showFITSViewer); 0106 showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0107 0108 guideAutoScaleGraphB->setIcon( 0109 QIcon::fromTheme("zoom-fit-best")); 0110 connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs); 0111 guideAutoScaleGraphB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0112 0113 guideSaveDataB->setIcon( 0114 QIcon::fromTheme("document-save")); 0115 connect(guideSaveDataB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::exportGuideData); 0116 guideSaveDataB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0117 0118 guideDataClearB->setIcon( 0119 QIcon::fromTheme("application-exit")); 0120 connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs); 0121 guideDataClearB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0122 0123 // These icons seem very hard to read for this button. Just went with +. 0124 // guideZoomInXB->setIcon(QIcon::fromTheme("zoom-in")); 0125 guideZoomInXB->setText("+"); 0126 connect(guideZoomInXB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::zoomInX); 0127 guideZoomInXB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0128 0129 // These icons seem very hard to read for this button. Just went with -. 0130 // guideZoomOutXB->setIcon(QIcon::fromTheme("zoom-out")); 0131 guideZoomOutXB->setText("-"); 0132 connect(guideZoomOutXB, &QPushButton::clicked, driftGraph, &GuideDriftGraph::zoomOutX); 0133 guideZoomOutXB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0134 0135 // Icons 0136 focalLengthIcon->setPixmap(QIcon::fromTheme("gnumeric-formulaguru").pixmap(32, 32)); 0137 apertureIcon->setPixmap(QIcon::fromTheme("lensautofix").pixmap(32, 32)); 0138 reducerIcon->setPixmap(QIcon::fromTheme("format-align-vertical-bottom").pixmap(32, 32)); 0139 FOVIcon->setPixmap(QIcon::fromTheme("timeline-use-zone-on").pixmap(32, 32)); 0140 focalRatioIcon->setPixmap(QIcon::fromTheme("node-type-symmetric").pixmap(32, 32)); 0141 0142 // Exposure 0143 //Should we set the range for the spin box here? 0144 QList<double> exposureValues; 0145 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 0146 << 10 << 15 << 30; 0147 guideExposure->setRecommendedValues(exposureValues); 0148 connect(guideExposure, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure); 0149 0150 // Set current guide type 0151 setGuiderType(-1); 0152 0153 //This allows the current guideSubframe option to be loaded. 0154 if(guiderType == GUIDE_PHD2) 0155 { 0156 setExternalGuiderBLOBEnabled(!Options::guideSubframe()); 0157 } 0158 else 0159 { 0160 // These only apply to PHD2, so disabling them when using the internal guider. 0161 opsDither->kcfg_DitherTimeout->setEnabled(false); 0162 opsDither->kcfg_DitherThreshold->setEnabled(false); 0163 opsDither->kcfg_DitherMaxIterations->setEnabled(!Options::ditherWithOnePulse()); 0164 } 0165 0166 // Initialize non guided dithering random generator. 0167 resetNonGuidedDither(); 0168 0169 //Note: This is to prevent a button from being called the default button 0170 //and then executing when the user hits the enter key such as when on a Text Box 0171 QList<QPushButton *> qButtons = findChildren<QPushButton *>(); 0172 for (auto &button : qButtons) 0173 button->setAutoDefault(false); 0174 0175 connect(KStars::Instance(), &KStars::colorSchemeChanged, driftGraph, &GuideDriftGraph::refreshColorScheme); 0176 0177 m_DarkProcessor = new DarkProcessor(this); 0178 connect(m_DarkProcessor, &DarkProcessor::newLog, this, &Ekos::Guide::appendLogText); 0179 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted, this, [this](bool completed) 0180 { 0181 if (completed != guideDarkFrame->isChecked()) 0182 setDarkFrameEnabled(completed); 0183 m_GuideView->setProperty("suspended", false); 0184 if (completed) 0185 { 0186 m_GuideView->rescale(ZOOM_KEEP_LEVEL); 0187 m_GuideView->updateFrame(); 0188 } 0189 m_GuideView->updateFrame(); 0190 setCaptureComplete(); 0191 }); 0192 0193 m_ManaulPulse = new ManualPulse(this); 0194 connect(m_ManaulPulse, &ManualPulse::newSinglePulse, this, &Guide::sendSinglePulse); 0195 connect(manualPulseB, &QPushButton::clicked, this, [this]() 0196 { 0197 m_ManaulPulse->reset(); 0198 m_ManaulPulse->show(); 0199 }); 0200 0201 loadGlobalSettings(); 0202 connectSettings(); 0203 0204 setupOpticalTrainManager(); 0205 } 0206 0207 Guide::~Guide() 0208 { 0209 delete m_GuiderInstance; 0210 } 0211 0212 void Guide::handleHorizontalPlotSizeChange() 0213 { 0214 targetPlot->handleHorizontalPlotSizeChange(); 0215 calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); 0216 calibrationPlot->replot(); 0217 } 0218 0219 void Guide::handleVerticalPlotSizeChange() 0220 { 0221 targetPlot->handleVerticalPlotSizeChange(); 0222 calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); 0223 calibrationPlot->replot(); 0224 } 0225 0226 void Guide::guideAfterMeridianFlip() 0227 { 0228 //This will clear the tracking box selection 0229 //The selected guide star is no longer valid due to the flip 0230 m_GuideView->setTrackingBoxEnabled(false); 0231 starCenter = QVector3D(); 0232 0233 if (Options::resetGuideCalibration()) 0234 clearCalibration(); 0235 0236 // GPG guide algorithm should be reset on any slew. 0237 if (Options::gPGEnabled()) 0238 m_GuiderInstance->resetGPG(); 0239 0240 guide(); 0241 } 0242 0243 void Guide::resizeEvent(QResizeEvent * event) 0244 { 0245 if (event->oldSize().width() != -1) 0246 { 0247 if (event->oldSize().width() != size().width()) 0248 handleHorizontalPlotSizeChange(); 0249 else if (event->oldSize().height() != size().height()) 0250 handleVerticalPlotSizeChange(); 0251 } 0252 else 0253 { 0254 QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange); 0255 } 0256 } 0257 0258 void Guide::buildTarget() 0259 { 0260 targetPlot->buildTarget(guiderAccuracyThreshold->value()); 0261 } 0262 0263 void Guide::clearGuideGraphs() 0264 { 0265 driftGraph->clear(); 0266 targetPlot->clear(); 0267 } 0268 0269 void Guide::clearCalibrationGraphs() 0270 { 0271 calibrationPlot->graph(GuideGraph::G_RA)->data()->clear(); //RA out 0272 calibrationPlot->graph(GuideGraph::G_DEC)->data()->clear(); //RA back 0273 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->data()->clear(); //Backlash 0274 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->data()->clear(); //DEC out 0275 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->data()->clear(); //DEC back 0276 calibrationPlot->replot(); 0277 } 0278 0279 void Guide::slotAutoScaleGraphs() 0280 { 0281 driftGraph->zoomX(defaultXZoomLevel); 0282 0283 driftGraph->autoScaleGraphs(); 0284 targetPlot->autoScaleGraphs(guiderAccuracyThreshold->value()); 0285 0286 calibrationPlot->rescaleAxes(); 0287 calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); 0288 calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); 0289 calibrationPlot->replot(); 0290 } 0291 0292 void Guide::guideHistory() 0293 { 0294 int sliderValue = guideSlider->value(); 0295 latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); 0296 double ra = driftGraph->graph(GuideGraph::G_RA)->dataMainValue(sliderValue); //Get RA from RA data 0297 double de = driftGraph->graph(GuideGraph::G_DEC)->dataMainValue(sliderValue); //Get DEC from DEC data 0298 driftGraph->guideHistory(sliderValue, graphOnLatestPt); 0299 0300 targetPlot->showPoint(ra, de); 0301 } 0302 0303 void Guide::setLatestGuidePoint(bool isChecked) 0304 { 0305 graphOnLatestPt = isChecked; 0306 driftGraph->setLatestGuidePoint(isChecked); 0307 targetPlot->setLatestGuidePoint(isChecked); 0308 0309 if(isChecked) 0310 guideSlider->setValue(guideSlider->maximum()); 0311 } 0312 0313 QString Guide::setRecommendedExposureValues(QList<double> values) 0314 { 0315 guideExposure->setRecommendedValues(values); 0316 return guideExposure->getRecommendedValuesString(); 0317 } 0318 0319 bool Guide::setCamera(ISD::Camera * device) 0320 { 0321 if (m_Camera && device == m_Camera) 0322 { 0323 checkCamera(); 0324 return false; 0325 } 0326 0327 if (m_Camera) 0328 m_Camera->disconnect(this); 0329 0330 m_Camera = device; 0331 0332 if (m_Camera) 0333 { 0334 connect(m_Camera, &ISD::Camera::Connected, this, [this]() 0335 { 0336 controlGroupBox->setEnabled(true); 0337 }); 0338 connect(m_Camera, &ISD::ConcreteDevice::Disconnected, this, [this]() 0339 { 0340 controlGroupBox->setEnabled(false); 0341 }); 0342 } 0343 0344 controlGroupBox->setEnabled(m_Camera && m_Camera->isConnected()); 0345 0346 // If camera was reset, return now. 0347 if (!m_Camera) 0348 return false; 0349 0350 if(guiderType != GUIDE_INTERNAL) 0351 m_Camera->setBLOBEnabled(false); 0352 0353 checkCamera(); 0354 configurePHD2Camera(); 0355 0356 // In case we are recovering from a crash and capture is pending, process it immediately. 0357 if (captureTimeout.isActive() && m_State >= Ekos::GUIDE_CAPTURE) 0358 QTimer::singleShot(100, this, &Guide::processCaptureTimeout); 0359 0360 return true; 0361 } 0362 0363 void Guide::configurePHD2Camera() 0364 { 0365 //Maybe something like this can be done for Linguider? 0366 //But for now, Linguider doesn't support INDI Cameras 0367 if(guiderType != GUIDE_PHD2) 0368 return; 0369 //This prevents a crash if phd2guider is null 0370 if(!phd2Guider) 0371 return; 0372 //This way it doesn't check if the equipment isn't connected yet. 0373 //It will check again when the equipment is connected. 0374 if(!phd2Guider->isConnected()) 0375 return; 0376 //This way it doesn't check if the equipment List has not been received yet. 0377 //It will ask for the list. When the list is received it will check again. 0378 if(phd2Guider->getCurrentCamera().isEmpty()) 0379 { 0380 phd2Guider->requestCurrentEquipmentUpdate(); 0381 return; 0382 } 0383 0384 //this checks to see if a CCD in the list matches the name of PHD2's camera 0385 ISD::Camera *ccdMatch = nullptr; 0386 QString currentPHD2CameraName = "None"; 0387 if(m_Camera && phd2Guider->getCurrentCamera().contains(m_Camera->getDeviceName())) 0388 { 0389 ccdMatch = m_Camera; 0390 currentPHD2CameraName = (phd2Guider->getCurrentCamera()); 0391 } 0392 0393 //If this method gives the same result as last time, no need to update the Camera info again. 0394 //That way the user doesn't see a ton of messages printing about the PHD2 external camera. 0395 //But lets make sure the blob is set correctly every time. 0396 if(m_LastPHD2CameraName == currentPHD2CameraName) 0397 { 0398 setExternalGuiderBLOBEnabled(!guideSubframe->isChecked()); 0399 return; 0400 } 0401 0402 //This means that a Guide Camera was connected before but it changed. 0403 if(m_Camera) 0404 setExternalGuiderBLOBEnabled(false); 0405 0406 //Updating the currentCCD 0407 m_Camera = ccdMatch; 0408 0409 //This updates the last camera name for the next time it is checked. 0410 m_LastPHD2CameraName = currentPHD2CameraName; 0411 0412 m_LastPHD2MountName = phd2Guider->getCurrentMount(); 0413 0414 //This sets a boolean that allows you to tell if the PHD2 camera is in Ekos 0415 phd2Guider->setCurrentCameraIsNotInEkos(m_Camera == nullptr); 0416 0417 if(phd2Guider->isCurrentCameraNotInEkos()) 0418 { 0419 appendLogText( 0420 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.", 0421 phd2Guider->getCurrentCamera())); 0422 guideSubframe->setEnabled(false); 0423 //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. 0424 disconnect(guideSubframe, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); 0425 guideSubframe->setChecked(true); 0426 return; 0427 } 0428 0429 appendLogText( 0430 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.", 0431 phd2Guider->getCurrentCamera())); 0432 guideSubframe->setEnabled(true); 0433 connect(guideSubframe, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); 0434 guideSubframe->setChecked(guideSubframe->isChecked()); 0435 } 0436 0437 bool Guide::setMount(ISD::Mount * device) 0438 { 0439 if (m_Mount && m_Mount == device) 0440 { 0441 syncTelescopeInfo(); 0442 return false; 0443 } 0444 0445 if (m_Mount) 0446 m_Mount->disconnect(this); 0447 0448 m_Mount = device; 0449 syncTelescopeInfo(); 0450 return true; 0451 } 0452 0453 QString Guide::camera() 0454 { 0455 if (m_Camera) 0456 return m_Camera->getDeviceName(); 0457 0458 return QString(); 0459 } 0460 0461 void Guide::checkCamera() 0462 { 0463 // Do NOT perform checks when the camera is capturing as this may result 0464 // in signals/slots getting disconnected. 0465 if (!m_Camera || guiderType != GUIDE_INTERNAL) 0466 return; 0467 0468 switch (m_State) 0469 { 0470 // Not busy, camera change is OK 0471 case GUIDE_IDLE: 0472 case GUIDE_ABORTED: 0473 case GUIDE_CONNECTED: 0474 case GUIDE_DISCONNECTED: 0475 case GUIDE_CALIBRATION_ERROR: 0476 break; 0477 0478 // Busy, camera change is not OK 0479 case GUIDE_CAPTURE: 0480 case GUIDE_LOOPING: 0481 case GUIDE_DARK: 0482 case GUIDE_SUBFRAME: 0483 case GUIDE_STAR_SELECT: 0484 case GUIDE_CALIBRATING: 0485 case GUIDE_CALIBRATION_SUCCESS: 0486 case GUIDE_GUIDING: 0487 case GUIDE_SUSPENDED: 0488 case GUIDE_REACQUIRE: 0489 case GUIDE_DITHERING: 0490 case GUIDE_MANUAL_DITHERING: 0491 case GUIDE_DITHERING_ERROR: 0492 case GUIDE_DITHERING_SUCCESS: 0493 case GUIDE_DITHERING_SETTLE: 0494 return; 0495 } 0496 0497 checkUseGuideHead(); 0498 0499 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0500 if (!targetChip) 0501 { 0502 qCCritical(KSTARS_EKOS_GUIDE) << "Failed to retrieve active guide chip in camera"; 0503 return; 0504 } 0505 0506 if (targetChip->isCapturing()) 0507 return; 0508 0509 if (guiderType != GUIDE_INTERNAL) 0510 { 0511 syncCameraInfo(); 0512 return; 0513 } 0514 0515 connect(m_Camera, &ISD::Camera::propertyUpdated, this, &Ekos::Guide::updateProperty, Qt::UniqueConnection); 0516 connect(m_Camera, &ISD::Camera::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection); 0517 0518 syncCameraInfo(); 0519 } 0520 0521 void Ekos::Guide::checkUseGuideHead() 0522 { 0523 if (m_Camera == nullptr) 0524 return; 0525 0526 if (m_Camera->hasGuideHead() && Options::useGuideHead()) 0527 useGuideHead = true; 0528 else 0529 useGuideHead = false; 0530 // guiding option only enabled if camera has a dedicated guiding chip 0531 opsGuide->kcfg_UseGuideHead->setEnabled(m_Camera->hasGuideHead()); 0532 } 0533 0534 void Guide::syncCameraInfo() 0535 { 0536 if (!m_Camera) 0537 return; 0538 0539 auto nvp = m_Camera->getNumber(useGuideHead ? "GUIDER_INFO" : "CCD_INFO"); 0540 0541 if (nvp) 0542 { 0543 auto np = nvp->findWidgetByName("CCD_PIXEL_SIZE_X"); 0544 if (np) 0545 ccdPixelSizeX = np->getValue(); 0546 0547 np = nvp->findWidgetByName( "CCD_PIXEL_SIZE_Y"); 0548 if (np) 0549 ccdPixelSizeY = np->getValue(); 0550 0551 np = nvp->findWidgetByName("CCD_PIXEL_SIZE_Y"); 0552 if (np) 0553 ccdPixelSizeY = np->getValue(); 0554 } 0555 0556 updateGuideParams(); 0557 } 0558 0559 void Guide::syncTelescopeInfo() 0560 { 0561 if (m_Mount == nullptr || m_Mount->isConnected() == false) 0562 return; 0563 0564 updateGuideParams(); 0565 } 0566 0567 void Guide::updateGuideParams() 0568 { 0569 if (m_Camera == nullptr) 0570 return; 0571 0572 checkUseGuideHead(); 0573 0574 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0575 0576 if (targetChip == nullptr) 0577 { 0578 appendLogText(i18n("Connection to the guide CCD is lost.")); 0579 return; 0580 } 0581 0582 if (targetChip->getFrameType() != FRAME_LIGHT) 0583 return; 0584 0585 if(guiderType == GUIDE_INTERNAL) 0586 guideBinning->setEnabled(targetChip->canBin()); 0587 0588 int subBinX = 1, subBinY = 1; 0589 if (targetChip->canBin()) 0590 { 0591 int maxBinX, maxBinY; 0592 0593 targetChip->getBinning(&subBinX, &subBinY); 0594 targetChip->getMaxBin(&maxBinX, &maxBinY); 0595 0596 //override with stored guide bin index, if within the range of possible bin modes 0597 if( guideBinIndex >= 0 && guideBinIndex < maxBinX && guideBinIndex < maxBinY ) 0598 { 0599 subBinX = guideBinIndex + 1; 0600 subBinY = guideBinIndex + 1; 0601 } 0602 0603 guideBinIndex = subBinX - 1; 0604 0605 guideBinning->blockSignals(true); 0606 0607 guideBinning->clear(); 0608 for (int i = 1; i <= maxBinX; i++) 0609 guideBinning->addItem(QString("%1x%2").arg(i).arg(i)); 0610 0611 guideBinning->setCurrentIndex( guideBinIndex ); 0612 0613 guideBinning->blockSignals(false); 0614 } 0615 0616 // If frame setting does not exist, create a new one. 0617 if (frameSettings.contains(targetChip) == false) 0618 { 0619 int x, y, w, h; 0620 if (targetChip->getFrame(&x, &y, &w, &h)) 0621 { 0622 if (w > 0 && h > 0) 0623 { 0624 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; 0625 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 0626 auto subframed = guideSubframe->isChecked(); 0627 0628 QVariantMap settings; 0629 0630 settings["x"] = subframed ? x : minX; 0631 settings["y"] = subframed ? y : minY; 0632 settings["w"] = subframed ? w : maxW; 0633 settings["h"] = subframed ? h : maxH; 0634 settings["binx"] = subBinX; 0635 settings["biny"] = subBinY; 0636 0637 frameSettings[targetChip] = settings; 0638 } 0639 } 0640 } 0641 // Otherwise update existing map 0642 else 0643 { 0644 QVariantMap settings = frameSettings[targetChip]; 0645 settings["binx"] = subBinX; 0646 settings["biny"] = subBinY; 0647 frameSettings[targetChip] = settings; 0648 } 0649 0650 if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && m_FocalLength > 0) 0651 { 0652 auto effectiveFocaLength = m_Reducer * m_FocalLength; 0653 m_GuiderInstance->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, m_Aperture, effectiveFocaLength); 0654 emit guideChipUpdated(targetChip); 0655 0656 int x, y, w, h; 0657 if (targetChip->getFrame(&x, &y, &w, &h)) 0658 { 0659 m_GuiderInstance->setFrameParams(x, y, w, h, subBinX, subBinY); 0660 } 0661 0662 l_Focal->setText(QString("%1mm").arg(m_FocalLength, 0, 'f', 0)); 0663 if (m_Aperture > 0) 0664 l_Aperture->setText(QString("%1mm").arg(m_Aperture, 0, 'f', 0)); 0665 // FIXME is there a way to know aperture? 0666 else 0667 l_Aperture->setText("DSLR"); 0668 l_Reducer->setText(QString("%1x").arg(QString::number(m_Reducer, 'f', 2))); 0669 0670 if (m_FocalRatio > 0) 0671 l_FbyD->setText(QString("F/%1").arg(m_FocalRatio, 0, 'f', 1)); 0672 else if (m_Aperture > 0) 0673 l_FbyD->setText(QString("F/%1").arg(m_FocalLength / m_Aperture, 0, 'f', 1)); 0674 0675 // Pixel scale in arcsec/pixel 0676 pixScaleX = 206264.8062470963552 * ccdPixelSizeX / 1000.0 / effectiveFocaLength; 0677 pixScaleY = 206264.8062470963552 * ccdPixelSizeY / 1000.0 / effectiveFocaLength; 0678 0679 // FOV in arcmin 0680 double fov_w = (w * pixScaleX) / 60.0; 0681 double fov_h = (h * pixScaleY) / 60.0; 0682 0683 l_FOV->setText(QString("%1' x %2'").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); 0684 } 0685 } 0686 0687 bool Guide::setGuider(ISD::Guider * device) 0688 { 0689 if (guiderType != GUIDE_INTERNAL || (m_Guider && device == m_Guider)) 0690 return false; 0691 0692 if (m_Guider) 0693 m_Guider->disconnect(this); 0694 0695 m_Guider = device; 0696 0697 if (m_Guider) 0698 { 0699 connect(m_Guider, &ISD::ConcreteDevice::Connected, this, [this]() 0700 { 0701 guideB->setEnabled(true); 0702 }); 0703 connect(m_Guider, &ISD::ConcreteDevice::Disconnected, this, [this]() 0704 { 0705 guideB->setEnabled(false); 0706 }); 0707 } 0708 0709 guideB->setEnabled(m_Guider && m_Guider->isConnected()); 0710 return true; 0711 } 0712 0713 bool Guide::setAdaptiveOptics(ISD::AdaptiveOptics * device) 0714 { 0715 if (guiderType != GUIDE_INTERNAL || (m_AO && device == m_AO)) 0716 return false; 0717 0718 if (m_AO) 0719 m_AO->disconnect(this); 0720 0721 // FIXME AO are not yet utilized property in Guide module 0722 m_AO = device; 0723 return true; 0724 } 0725 0726 QString Guide::guider() 0727 { 0728 if (guiderType != GUIDE_INTERNAL || m_Guider == nullptr) 0729 return QString(); 0730 0731 return m_Guider->getDeviceName(); 0732 } 0733 0734 bool Guide::capture() 0735 { 0736 buildOperationStack(GUIDE_CAPTURE); 0737 0738 return executeOperationStack(); 0739 } 0740 0741 bool Guide::captureOneFrame() 0742 { 0743 captureTimeout.stop(); 0744 0745 if (m_Camera == nullptr) 0746 return false; 0747 0748 if (m_Camera->isConnected() == false) 0749 { 0750 appendLogText(i18n("Error: lost connection to CCD.")); 0751 return false; 0752 } 0753 0754 double seqExpose = guideExposure->value(); 0755 0756 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0757 0758 prepareCapture(targetChip); 0759 0760 m_GuideView->setBaseSize(guideWidget->size()); 0761 setBusy(true); 0762 0763 // Check if we have a valid frame setting 0764 if (frameSettings.contains(targetChip)) 0765 { 0766 QVariantMap settings = frameSettings[targetChip]; 0767 targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), 0768 settings["h"].toInt()); 0769 targetChip->setBinning(settings["binx"].toInt(), settings["biny"].toInt()); 0770 } 0771 0772 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection); 0773 qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; 0774 0775 double finalExposure = seqExpose; 0776 0777 // Increase exposure for calibration frame if we need auto-select a star 0778 // To increase chances we detect one. 0779 if (operationStack.contains(GUIDE_STAR_SELECT) && guideAutoStar->isChecked() && 0780 !((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled())) 0781 finalExposure *= 3; 0782 0783 // Prevent flicker when processing dark frame by suspending updates 0784 m_GuideView->setProperty("suspended", operationStack.contains(GUIDE_DARK)); 0785 0786 // Timeout is exposure duration + timeout threshold in seconds 0787 captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); 0788 0789 targetChip->capture(finalExposure); 0790 0791 return true; 0792 } 0793 0794 void Guide::prepareCapture(ISD::CameraChip * targetChip) 0795 { 0796 targetChip->setBatchMode(false); 0797 targetChip->setCaptureMode(FITS_GUIDE); 0798 targetChip->setFrameType(FRAME_LIGHT); 0799 targetChip->setCaptureFilter(FITS_NONE); 0800 m_Camera->setEncodingFormat("FITS"); 0801 } 0802 0803 void Guide::abortExposure() 0804 { 0805 if (m_Camera && guiderType == GUIDE_INTERNAL) 0806 { 0807 captureTimeout.stop(); 0808 m_PulseTimer.stop(); 0809 ISD::CameraChip *targetChip = 0810 m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0811 if (targetChip->isCapturing()) 0812 { 0813 qCDebug(KSTARS_EKOS_GUIDE) << "Aborting guide capture"; 0814 targetChip->abortExposure(); 0815 } 0816 } 0817 } 0818 0819 bool Guide::abort() 0820 { 0821 if (m_Camera && guiderType == GUIDE_INTERNAL) 0822 { 0823 captureTimeout.stop(); 0824 m_PulseTimer.stop(); 0825 ISD::CameraChip *targetChip = 0826 m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0827 if (targetChip->isCapturing()) 0828 targetChip->abortExposure(); 0829 } 0830 0831 manualDitherB->setEnabled(false); 0832 0833 setBusy(false); 0834 0835 switch (m_State) 0836 { 0837 case GUIDE_IDLE: 0838 case GUIDE_CONNECTED: 0839 case GUIDE_DISCONNECTED: 0840 break; 0841 0842 case GUIDE_CALIBRATING: 0843 case GUIDE_DITHERING: 0844 case GUIDE_STAR_SELECT: 0845 case GUIDE_CAPTURE: 0846 case GUIDE_GUIDING: 0847 case GUIDE_LOOPING: 0848 m_GuiderInstance->abort(); 0849 break; 0850 0851 default: 0852 break; 0853 } 0854 0855 return true; 0856 } 0857 0858 void Guide::setBusy(bool enable) 0859 { 0860 if (enable && pi->isAnimated()) 0861 return; 0862 else if (enable == false && pi->isAnimated() == false) 0863 return; 0864 0865 if (enable) 0866 { 0867 clearCalibrationB->setEnabled(false); 0868 guideB->setEnabled(false); 0869 captureB->setEnabled(false); 0870 loopB->setEnabled(false); 0871 guideDarkFrame->setEnabled(false); 0872 guideSubframe->setEnabled(false); 0873 guideAutoStar->setEnabled(false); 0874 stopB->setEnabled(true); 0875 // Optical Train 0876 opticalTrainCombo->setEnabled(false); 0877 trainB->setEnabled(false); 0878 0879 pi->startAnimation(); 0880 } 0881 else 0882 { 0883 if(guiderType != GUIDE_LINGUIDER) 0884 { 0885 captureB->setEnabled(true); 0886 loopB->setEnabled(true); 0887 guideAutoStar->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions() 0888 if(m_Camera) 0889 guideSubframe->setEnabled(!internalGuider->SEPMultiStarEnabled()); // cf. configSEPMultistarOptions() 0890 } 0891 if (guiderType == GUIDE_INTERNAL) 0892 guideDarkFrame->setEnabled(true); 0893 0894 if (calibrationComplete || 0895 ((guiderType == GUIDE_INTERNAL) && 0896 Options::reuseGuideCalibration() && 0897 !Options::serializedCalibration().isEmpty())) 0898 clearCalibrationB->setEnabled(true); 0899 guideB->setEnabled(true); 0900 stopB->setEnabled(false); 0901 pi->stopAnimation(); 0902 0903 // Optical Train 0904 opticalTrainCombo->setEnabled(true); 0905 trainB->setEnabled(true); 0906 0907 connect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); 0908 } 0909 } 0910 0911 void Guide::processCaptureTimeout() 0912 { 0913 auto restartExposure = [&]() 0914 { 0915 appendLogText(i18n("Exposure timeout. Restarting exposure...")); 0916 m_Camera->setEncodingFormat("FITS"); 0917 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0918 targetChip->abortExposure(); 0919 prepareCapture(targetChip); 0920 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection); 0921 connect(m_Camera, &ISD::Camera::propertyUpdated, this, &Ekos::Guide::updateProperty, Qt::UniqueConnection); 0922 connect(m_Camera, &ISD::Camera::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection); 0923 targetChip->capture(guideExposure->value()); 0924 captureTimeout.start(guideExposure->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); 0925 }; 0926 0927 m_CaptureTimeoutCounter++; 0928 0929 if (m_Camera == nullptr) 0930 return; 0931 0932 if (m_DeviceRestartCounter >= 3) 0933 { 0934 m_CaptureTimeoutCounter = 0; 0935 m_DeviceRestartCounter = 0; 0936 if (m_State == GUIDE_GUIDING) 0937 appendLogText(i18n("Exposure timeout. Aborting Autoguide.")); 0938 else if (m_State == GUIDE_DITHERING) 0939 appendLogText(i18n("Exposure timeout. Aborting Dithering.")); 0940 else if (m_State == GUIDE_CALIBRATING) 0941 appendLogText(i18n("Exposure timeout. Aborting Calibration.")); 0942 0943 captureTimeout.stop(); 0944 abort(); 0945 return; 0946 } 0947 0948 if (m_CaptureTimeoutCounter > 1) 0949 { 0950 QString camera = m_Camera->getDeviceName(); 0951 QString via = m_Guider ? m_Guider->getDeviceName() : ""; 0952 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0953 QVariantMap settings = frameSettings[targetChip]; 0954 emit driverTimedout(camera); 0955 QTimer::singleShot(5000, [ &, camera, settings]() 0956 { 0957 m_DeviceRestartCounter++; 0958 reconnectDriver(camera, settings); 0959 }); 0960 return; 0961 } 0962 else 0963 restartExposure(); 0964 } 0965 0966 void Guide::reconnectDriver(const QString &camera, QVariantMap settings) 0967 { 0968 if (m_Camera && m_Camera->getDeviceName() == camera) 0969 { 0970 // Set state to IDLE so that checkCamera is processed since it will not process GUIDE_GUIDING state. 0971 Ekos::GuideState currentState = m_State; 0972 m_State = GUIDE_IDLE; 0973 checkCamera(); 0974 // Restore state to last state. 0975 m_State = currentState; 0976 0977 if (guiderType == GUIDE_INTERNAL) 0978 { 0979 // Reset the frame settings to the restarted camera once again before capture. 0980 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0981 frameSettings[targetChip] = settings; 0982 // restart capture 0983 m_CaptureTimeoutCounter = 0; 0984 captureOneFrame(); 0985 } 0986 0987 return; 0988 } 0989 0990 QTimer::singleShot(5000, this, [ &, camera, settings]() 0991 { 0992 reconnectDriver(camera, settings); 0993 }); 0994 } 0995 0996 void Guide::processData(const QSharedPointer<FITSData> &data) 0997 { 0998 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 0999 if (targetChip->getCaptureMode() != FITS_GUIDE) 1000 { 1001 if (data) 1002 { 1003 QString blobInfo = QString("{Device: %1 Property: %2 Element: %3 Chip: %4}").arg(data->property("device").toString()) 1004 .arg(data->property("blobVector").toString()) 1005 .arg(data->property("blobElement").toString()) 1006 .arg(data->property("chip").toInt()); 1007 1008 qCWarning(KSTARS_EKOS_GUIDE) << blobInfo << "Ignoring Received FITS as it has the wrong capture mode" << 1009 targetChip->getCaptureMode(); 1010 } 1011 1012 return; 1013 } 1014 1015 if (data) 1016 { 1017 m_GuideView->loadData(data); 1018 m_ImageData = data; 1019 } 1020 else 1021 m_ImageData.reset(); 1022 1023 if (guiderType == GUIDE_INTERNAL) 1024 internalGuider->setImageData(m_ImageData); 1025 1026 captureTimeout.stop(); 1027 m_CaptureTimeoutCounter = 0; 1028 1029 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Guide::processData); 1030 1031 // qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; 1032 1033 int subBinX = 1, subBinY = 1; 1034 targetChip->getBinning(&subBinX, &subBinY); 1035 1036 if (starCenter.x() == 0 && starCenter.y() == 0) 1037 { 1038 int x = 0, y = 0, w = 0, h = 0; 1039 1040 if (frameSettings.contains(targetChip)) 1041 { 1042 QVariantMap settings = frameSettings[targetChip]; 1043 x = settings["x"].toInt(); 1044 y = settings["y"].toInt(); 1045 w = settings["w"].toInt(); 1046 h = settings["h"].toInt(); 1047 } 1048 else 1049 targetChip->getFrame(&x, &y, &w, &h); 1050 1051 starCenter.setX(w / (2 * subBinX)); 1052 starCenter.setY(h / (2 * subBinY)); 1053 starCenter.setZ(subBinX); 1054 } 1055 1056 syncTrackingBoxPosition(); 1057 // qCDebug(KSTARS_EKOS_GUIDE) << "Tracking box position synched."; 1058 1059 setCaptureComplete(); 1060 // qCDebug(KSTARS_EKOS_GUIDE) << "Capture complete."; 1061 1062 } 1063 1064 void Guide::setCaptureComplete() 1065 { 1066 if (!m_GuideView.isNull()) 1067 m_GuideView->clearNeighbors(); 1068 1069 DarkLibrary::Instance()->disconnect(this); 1070 1071 if (operationStack.isEmpty() == false) 1072 { 1073 executeOperationStack(); 1074 return; 1075 } 1076 1077 qCDebug(KSTARS_EKOS_GUIDE) << "Capture complete, state=" << getGuideStatusString(m_State); 1078 switch (m_State) 1079 { 1080 case GUIDE_IDLE: 1081 case GUIDE_ABORTED: 1082 case GUIDE_CONNECTED: 1083 case GUIDE_DISCONNECTED: 1084 case GUIDE_CALIBRATION_SUCCESS: 1085 case GUIDE_CALIBRATION_ERROR: 1086 case GUIDE_DITHERING_ERROR: 1087 setBusy(false); 1088 break; 1089 1090 case GUIDE_CAPTURE: 1091 qCDebug(KSTARS_EKOS_GUIDE) << "Guiding capture complete."; 1092 m_State = GUIDE_IDLE; 1093 emit newStatus(m_State); 1094 setBusy(false); 1095 break; 1096 1097 case GUIDE_LOOPING: 1098 capture(); 1099 break; 1100 1101 case GUIDE_CALIBRATING: 1102 m_GuiderInstance->calibrate(); 1103 break; 1104 1105 case GUIDE_GUIDING: 1106 m_GuiderInstance->guide(); 1107 break; 1108 1109 case GUIDE_DITHERING: 1110 m_GuiderInstance->dither(Options::ditherPixels()); 1111 break; 1112 1113 // Feature only of internal guider 1114 case GUIDE_MANUAL_DITHERING: 1115 dynamic_cast<InternalGuider*>(m_GuiderInstance)->processManualDithering(); 1116 break; 1117 1118 case GUIDE_REACQUIRE: 1119 m_GuiderInstance->reacquire(); 1120 break; 1121 1122 case GUIDE_DITHERING_SETTLE: 1123 if (Options::ditherNoGuiding()) 1124 return; 1125 capture(); 1126 break; 1127 1128 case GUIDE_SUSPENDED: 1129 if (Options::gPGEnabled()) 1130 m_GuiderInstance->guide(); 1131 break; 1132 1133 default: 1134 break; 1135 } 1136 1137 emit newImage(m_GuideView); 1138 emit newStarPixmap(m_GuideView->getTrackingBoxPixmap(10)); 1139 } 1140 1141 void Guide::appendLogText(const QString &text) 1142 { 1143 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", 1144 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); 1145 1146 qCInfo(KSTARS_EKOS_GUIDE) << text; 1147 1148 emit newLog(text); 1149 } 1150 1151 void Guide::clearLog() 1152 { 1153 m_LogText.clear(); 1154 emit newLog(QString()); 1155 } 1156 1157 void Guide::setDECSwap(bool enable) 1158 { 1159 if (m_Guider == nullptr || m_GuiderInstance == nullptr) 1160 return; 1161 1162 if (guiderType == GUIDE_INTERNAL) 1163 { 1164 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setDECSwap(enable); 1165 m_Guider->setDECSwap(enable); 1166 } 1167 } 1168 1169 bool Guide::sendMultiPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs, 1170 CaptureAfterPulses followWithCapture) 1171 { 1172 if (m_Guider == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) 1173 return false; 1174 1175 if (followWithCapture == StartCaptureAfterPulses) 1176 { 1177 // Delay next capture by user-configurable delay. 1178 // If user delay is zero, delay by the pulse length plus 100 milliseconds before next capture. 1179 auto ms = std::max(ra_msecs, dec_msecs) + 100; 1180 auto delay = std::max(static_cast<int>(guideDelay->value() * 1000), ms); 1181 1182 m_PulseTimer.start(delay); 1183 } 1184 return m_Guider->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); 1185 } 1186 1187 bool Guide::sendSinglePulse(GuideDirection dir, int msecs, CaptureAfterPulses followWithCapture) 1188 { 1189 if (m_Guider == nullptr || dir == NO_DIR) 1190 return false; 1191 1192 if (followWithCapture == StartCaptureAfterPulses) 1193 { 1194 // Delay next capture by user-configurable delay. 1195 // If user delay is zero, delay by the pulse length plus 100 milliseconds before next capture. 1196 auto ms = msecs + 100; 1197 auto delay = std::max(static_cast<int>(guideDelay->value() * 1000), ms); 1198 1199 m_PulseTimer.start(delay); 1200 } 1201 1202 return m_Guider->doPulse(dir, msecs); 1203 } 1204 1205 bool Guide::calibrate() 1206 { 1207 // Set status to idle and let the operations change it as they get executed 1208 m_State = GUIDE_IDLE; 1209 qCDebug(KSTARS_EKOS_GUIDE) << "Calibrating..."; 1210 emit newStatus(m_State); 1211 1212 if (guiderType == GUIDE_INTERNAL) 1213 { 1214 if (!m_Camera) 1215 { 1216 qCCritical(KSTARS_EKOS_GUIDE) << "No camera detected. Check optical trains."; 1217 return false; 1218 } 1219 1220 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 1221 1222 if (frameSettings.contains(targetChip)) 1223 { 1224 targetChip->resetFrame(); 1225 int x, y, w, h; 1226 targetChip->getFrame(&x, &y, &w, &h); 1227 QVariantMap settings = frameSettings[targetChip]; 1228 settings["x"] = x; 1229 settings["y"] = y; 1230 settings["w"] = w; 1231 settings["h"] = h; 1232 frameSettings[targetChip] = settings; 1233 1234 subFramed = false; 1235 } 1236 } 1237 1238 buildOperationStack(GUIDE_CALIBRATING); 1239 1240 executeOperationStack(); 1241 1242 if (m_Camera && m_Guider) 1243 { 1244 qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using camera:" << m_Camera->getDeviceName() << "via" << 1245 m_Guider->getDeviceName(); 1246 } 1247 1248 return true; 1249 } 1250 1251 bool Guide::guide() 1252 { 1253 auto executeGuide = [this]() 1254 { 1255 if(guiderType != GUIDE_PHD2) 1256 { 1257 if (calibrationComplete == false) 1258 { 1259 calibrate(); 1260 return; 1261 } 1262 } 1263 1264 m_GuiderInstance->guide(); 1265 1266 //If PHD2 gets a Guide command and it is looping, it will accept a lock position 1267 //but if it was not looping it will ignore the lock position and do an auto star automatically 1268 //This is not the default behavior in Ekos if auto star is not selected. 1269 //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide. 1270 if(!guideAutoStar->isChecked()) 1271 { 1272 if(guiderType == GUIDE_PHD2 && m_GuideView->isTrackingBoxEnabled()) 1273 { 1274 double x = starCenter.x(); 1275 double y = starCenter.y(); 1276 1277 if(!m_ImageData.isNull()) 1278 { 1279 if(m_ImageData->width() > 50) 1280 { 1281 guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState) 1282 { 1283 if(newState == GUIDE_GUIDING) 1284 { 1285 phd2Guider->setLockPosition(x, y); 1286 disconnect(guideConnect); 1287 } 1288 }); 1289 } 1290 } 1291 } 1292 } 1293 }; 1294 1295 if (m_MountStatus == ISD::Mount::MOUNT_PARKED) 1296 { 1297 KSMessageBox::Instance()->sorry(i18n("The mount is parked. Unpark to start guiding.")); 1298 return false; 1299 } 1300 1301 executeGuide(); 1302 return true; 1303 } 1304 1305 bool Guide::dither() 1306 { 1307 if (Options::ditherNoGuiding() && m_State == GUIDE_IDLE) 1308 { 1309 nonGuidedDither(); 1310 return true; 1311 } 1312 1313 if (m_State == GUIDE_DITHERING || m_State == GUIDE_DITHERING_SETTLE) 1314 return true; 1315 1316 //This adds a dither text item to the graph where dithering occurred. 1317 double time = guideTimer.elapsed() / 1000.0; 1318 QCPItemText *ditherLabel = new QCPItemText(driftGraph); 1319 ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft); 1320 ditherLabel->position->setType(QCPItemPosition::ptPlotCoords); 1321 ditherLabel->position->setCoords(time, 1.5); 1322 ditherLabel->setColor(Qt::white); 1323 ditherLabel->setBrush(Qt::NoBrush); 1324 ditherLabel->setPen(Qt::NoPen); 1325 ditherLabel->setText("Dither"); 1326 ditherLabel->setFont(QFont(font().family(), 10)); 1327 1328 if (guiderType == GUIDE_INTERNAL && !Options::ditherWithOnePulse()) 1329 { 1330 if (m_State != GUIDE_GUIDING) 1331 capture(); 1332 1333 setStatus(GUIDE_DITHERING); 1334 1335 return true; 1336 } 1337 else 1338 return m_GuiderInstance->dither(Options::ditherPixels()); 1339 } 1340 1341 bool Guide::suspend() 1342 { 1343 if (m_State == GUIDE_SUSPENDED) 1344 return true; 1345 else if (m_State >= GUIDE_CAPTURE) 1346 return m_GuiderInstance->suspend(); 1347 else 1348 return false; 1349 } 1350 1351 bool Guide::resume() 1352 { 1353 if (m_State == GUIDE_GUIDING) 1354 return true; 1355 else if (m_State == GUIDE_SUSPENDED) 1356 return m_GuiderInstance->resume(); 1357 else 1358 return false; 1359 } 1360 1361 void Guide::setCaptureStatus(CaptureState newState) 1362 { 1363 switch (newState) 1364 { 1365 case CAPTURE_DITHERING: 1366 dither(); 1367 break; 1368 case CAPTURE_IDLE: 1369 case CAPTURE_ABORTED: 1370 // We need to reset the non guided dithering status every time a new capture task is started (and not for every single capture). 1371 // The non dithering logic is a bit convoluted and controlled by the Capture module, 1372 // which calls Guide::setCaptureStatus(CAPTURE_DITHERING) when it wants guide to dither. 1373 // It actually calls newStatus(CAPTURE_DITHERING) in Capture::checkDithering(), but manager.cpp in Manager::connectModules() connects that to Guide::setCaptureStatus()). 1374 // So the only way to reset the non guided dithering prior to a new capture task is to put it here, when the Capture status moves to IDLE or ABORTED state. 1375 resetNonGuidedDither(); 1376 break; 1377 default: 1378 break; 1379 } 1380 } 1381 1382 void Guide::setPierSide(ISD::Mount::PierSide newSide) 1383 { 1384 m_GuiderInstance->setPierSide(newSide); 1385 1386 // If pier side changes in internal guider 1387 // and calibration was already done 1388 // then let's swap 1389 if (guiderType == GUIDE_INTERNAL && 1390 m_State != GUIDE_GUIDING && 1391 m_State != GUIDE_CALIBRATING && 1392 calibrationComplete) 1393 { 1394 // Couldn't restore an old calibration if we call clearCalibration(). 1395 if (Options::reuseGuideCalibration()) 1396 calibrationComplete = false; 1397 else 1398 { 1399 clearCalibration(); 1400 appendLogText(i18n("Pier side change detected. Clearing calibration.")); 1401 } 1402 } 1403 } 1404 1405 void Guide::setMountStatus(ISD::Mount::Status newState) 1406 { 1407 m_MountStatus = newState; 1408 1409 if (newState == ISD::Mount::MOUNT_PARKING || newState == ISD::Mount::MOUNT_SLEWING) 1410 { 1411 // reset the calibration if "Always reset calibration" is selected and the mount moves 1412 if (Options::resetGuideCalibration()) 1413 { 1414 appendLogText(i18n("Mount is moving. Resetting calibration...")); 1415 clearCalibration(); 1416 } 1417 else if (Options::reuseGuideCalibration() && (guiderType == GUIDE_INTERNAL)) 1418 { 1419 // It will restore it with the reused one, and this way it reselects a guide star. 1420 calibrationComplete = false; 1421 } 1422 // GPG guide algorithm should be reset on any slew. 1423 if (Options::gPGEnabled()) 1424 m_GuiderInstance->resetGPG(); 1425 1426 // If we're guiding, and the mount either slews or parks, then we abort. 1427 if (m_State == GUIDE_GUIDING || m_State == GUIDE_DITHERING) 1428 { 1429 if (newState == ISD::Mount::MOUNT_PARKING) 1430 appendLogText(i18n("Mount is parking. Aborting guide...")); 1431 else 1432 appendLogText(i18n("Mount is slewing. Aborting guide...")); 1433 1434 abort(); 1435 } 1436 } 1437 1438 if (guiderType != GUIDE_INTERNAL) 1439 return; 1440 1441 switch (newState) 1442 { 1443 case ISD::Mount::MOUNT_SLEWING: 1444 case ISD::Mount::MOUNT_PARKING: 1445 case ISD::Mount::MOUNT_MOVING: 1446 captureB->setEnabled(false); 1447 loopB->setEnabled(false); 1448 clearCalibrationB->setEnabled(false); 1449 manualPulseB->setEnabled(false); 1450 break; 1451 1452 default: 1453 if (pi->isAnimated() == false) 1454 { 1455 captureB->setEnabled(true); 1456 loopB->setEnabled(true); 1457 clearCalibrationB->setEnabled(true); 1458 manualPulseB->setEnabled(true); 1459 } 1460 } 1461 } 1462 1463 void Guide::setMountCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha) 1464 { 1465 Q_UNUSED(ha); 1466 m_GuiderInstance->setMountCoords(position, pierSide); 1467 m_ManaulPulse->setMountCoords(position); 1468 } 1469 1470 void Guide::setExposure(double value) 1471 { 1472 guideExposure->setValue(value); 1473 } 1474 1475 void Guide::setSubFrameEnabled(bool enable) 1476 { 1477 if (guideSubframe->isChecked() != enable) 1478 guideSubframe->setChecked(enable); 1479 if(guiderType == GUIDE_PHD2) 1480 setExternalGuiderBLOBEnabled(!enable); 1481 } 1482 1483 void Guide::setAutoStarEnabled(bool enable) 1484 { 1485 if(guiderType == GUIDE_INTERNAL) 1486 guideAutoStar->setChecked(enable); 1487 } 1488 1489 void Guide::clearCalibration() 1490 { 1491 calibrationComplete = false; 1492 1493 m_GuiderInstance->clearCalibration(); 1494 1495 appendLogText(i18n("Calibration is cleared.")); 1496 } 1497 1498 void Guide::setStatus(Ekos::GuideState newState) 1499 { 1500 if (newState == m_State) 1501 { 1502 // pass through the aborted state 1503 if (newState == GUIDE_ABORTED) 1504 emit newStatus(m_State); 1505 return; 1506 } 1507 1508 GuideState previousState = m_State; 1509 1510 m_State = newState; 1511 emit newStatus(m_State); 1512 1513 switch (m_State) 1514 { 1515 case GUIDE_CONNECTED: 1516 appendLogText(i18n("External guider connected.")); 1517 externalConnectB->setEnabled(false); 1518 externalDisconnectB->setEnabled(true); 1519 clearCalibrationB->setEnabled(true); 1520 guideB->setEnabled(true); 1521 1522 if(guiderType == GUIDE_PHD2) 1523 { 1524 captureB->setEnabled(true); 1525 loopB->setEnabled(true); 1526 guideAutoStar->setEnabled(true); 1527 configurePHD2Camera(); 1528 setExternalGuiderBLOBEnabled(!guideSubframe->isChecked()); 1529 guideSquareSize->setEnabled(true); 1530 } 1531 break; 1532 1533 case GUIDE_DISCONNECTED: 1534 appendLogText(i18n("External guider disconnected.")); 1535 setBusy(false); //This needs to come before caputureB since it will set it to enabled again. 1536 externalConnectB->setEnabled(true); 1537 externalDisconnectB->setEnabled(false); 1538 clearCalibrationB->setEnabled(false); 1539 guideB->setEnabled(false); 1540 captureB->setEnabled(false); 1541 loopB->setEnabled(false); 1542 guideAutoStar->setEnabled(false); 1543 guideSquareSize->setEnabled(false); 1544 //setExternalGuiderBLOBEnabled(true); 1545 #ifdef Q_OS_OSX 1546 repaint(); //This is a band-aid for a bug in QT 5.10.0 1547 #endif 1548 break; 1549 1550 case GUIDE_CALIBRATION_SUCCESS: 1551 appendLogText(i18n("Calibration completed.")); 1552 manualPulseB->setEnabled(true); 1553 calibrationComplete = true; 1554 1555 if(guiderType != 1556 GUIDE_PHD2) //PHD2 will take care of this. If this command is executed for PHD2, it might start guiding when it is first connected, if the calibration was completed already. 1557 guide(); 1558 break; 1559 1560 case GUIDE_IDLE: 1561 case GUIDE_CALIBRATION_ERROR: 1562 setBusy(false); 1563 manualDitherB->setEnabled(false); 1564 manualPulseB->setEnabled(true); 1565 break; 1566 1567 case GUIDE_CALIBRATING: 1568 clearCalibrationGraphs(); 1569 appendLogText(i18n("Calibration started.")); 1570 setBusy(true); 1571 manualPulseB->setEnabled(false); 1572 break; 1573 1574 case GUIDE_GUIDING: 1575 if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) 1576 appendLogText(i18n("Guiding resumed.")); 1577 else 1578 { 1579 appendLogText(i18n("Autoguiding started.")); 1580 setBusy(true); 1581 1582 clearGuideGraphs(); 1583 guideTimer.start(); 1584 driftGraph->resetTimer(); 1585 driftGraph->refreshColorScheme(); 1586 } 1587 manualDitherB->setEnabled(true); 1588 break; 1589 1590 case GUIDE_ABORTED: 1591 appendLogText(i18n("Autoguiding aborted.")); 1592 setBusy(false); 1593 break; 1594 1595 case GUIDE_SUSPENDED: 1596 appendLogText(i18n("Guiding suspended.")); 1597 break; 1598 1599 case GUIDE_REACQUIRE: 1600 if (guiderType == GUIDE_INTERNAL) 1601 capture(); 1602 break; 1603 1604 case GUIDE_MANUAL_DITHERING: 1605 appendLogText(i18n("Manual dithering in progress.")); 1606 break; 1607 1608 case GUIDE_DITHERING: 1609 appendLogText(i18n("Dithering in progress.")); 1610 break; 1611 1612 case GUIDE_DITHERING_SETTLE: 1613 appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...", 1614 Options::ditherSettle())); 1615 break; 1616 1617 case GUIDE_DITHERING_ERROR: 1618 appendLogText(i18n("Dithering failed.")); 1619 // LinGuider guide continue after dithering failure 1620 if (guiderType != GUIDE_LINGUIDER) 1621 { 1622 //state = GUIDE_IDLE; 1623 m_State = GUIDE_ABORTED; 1624 setBusy(false); 1625 } 1626 break; 1627 1628 case GUIDE_DITHERING_SUCCESS: 1629 appendLogText(i18n("Dithering completed successfully.")); 1630 // Go back to guiding state immediately if using regular guider 1631 if (Options::ditherNoGuiding() == false) 1632 { 1633 setStatus(GUIDE_GUIDING); 1634 // Only capture again if we are using internal guider 1635 if (guiderType == GUIDE_INTERNAL) 1636 capture(); 1637 } 1638 break; 1639 default: 1640 break; 1641 } 1642 } 1643 1644 void Guide::updateCCDBin(int index) 1645 { 1646 if (m_Camera == nullptr || guiderType != GUIDE_INTERNAL) 1647 return; 1648 1649 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 1650 1651 targetChip->setBinning(index + 1, index + 1); 1652 guideBinIndex = index; 1653 1654 QVariantMap settings = frameSettings[targetChip]; 1655 settings["binx"] = index + 1; 1656 settings["biny"] = index + 1; 1657 frameSettings[targetChip] = settings; 1658 1659 m_GuiderInstance->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), 1660 settings["binx"].toInt(), settings["biny"].toInt()); 1661 } 1662 1663 void Guide::updateProperty(INDI::Property prop) 1664 { 1665 if (m_Camera == nullptr || (prop.getDeviceName() != m_Camera->getDeviceName()) || guiderType != GUIDE_INTERNAL) 1666 return; 1667 1668 if ((prop.isNameMatch("CCD_BINNING") && useGuideHead == false) || 1669 (prop.isNameMatch("GUIDER_BINNING") && useGuideHead)) 1670 { 1671 auto nvp = prop.getNumber(); 1672 auto value = nvp->at(0)->getValue(); 1673 if (guideBinIndex > (value - 1)) // INDI driver reports not supported binning 1674 { 1675 appendLogText(i18n("%1x%1 guide binning is not supported.", guideBinIndex + 1)); 1676 guideBinning->setCurrentIndex( value - 1 ); 1677 updateSetting("guideBinning", guideBinning->currentText()); 1678 } 1679 else 1680 { 1681 guideBinning->setCurrentIndex(guideBinIndex); 1682 } 1683 } 1684 } 1685 1686 void Guide::checkExposureValue(ISD::CameraChip * targetChip, double exposure, IPState expState) 1687 { 1688 // Ignore if not using internal guider, or chip belongs to a different camera. 1689 if (guiderType != GUIDE_INTERNAL || targetChip->getCCD() != m_Camera) 1690 return; 1691 1692 INDI_UNUSED(exposure); 1693 1694 if (expState == IPS_ALERT && 1695 ((m_State == GUIDE_GUIDING) || (m_State == GUIDE_DITHERING) || (m_State == GUIDE_CALIBRATING))) 1696 { 1697 appendLogText(i18n("Exposure failed. Restarting exposure...")); 1698 m_Camera->setEncodingFormat("FITS"); 1699 targetChip->capture(guideExposure->value()); 1700 } 1701 } 1702 1703 void Guide::configSEPMultistarOptions() 1704 { 1705 // SEP MultiStar always uses an automated guide star & doesn't subframe. 1706 if (internalGuider->SEPMultiStarEnabled()) 1707 { 1708 guideSubframe->setChecked(false); 1709 guideSubframe->setEnabled(false); 1710 guideAutoStar->setChecked(true); 1711 guideAutoStar->setEnabled(false); 1712 } 1713 else 1714 { 1715 guideAutoStar->setEnabled(true); 1716 guideSubframe->setEnabled(true); 1717 1718 auto subframed = m_Settings["guideSubframe"]; 1719 if (subframed.isValid()) 1720 guideSubframe->setChecked(subframed.toBool()); 1721 1722 auto autostar = m_Settings["guideAutoStar"]; 1723 if (autostar.isValid()) 1724 guideAutoStar->setChecked(autostar.toBool()); 1725 } 1726 } 1727 1728 void Guide::setDarkFrameEnabled(bool enable) 1729 { 1730 if (guideDarkFrame->isChecked() != enable) 1731 guideDarkFrame->setChecked(enable); 1732 } 1733 1734 void Guide::saveDefaultGuideExposure() 1735 { 1736 if(guiderType == GUIDE_PHD2) 1737 1738 phd2Guider->requestSetExposureTime(guideExposure->value() * 1000); 1739 else if (guiderType == GUIDE_INTERNAL) 1740 { 1741 internalGuider->setExposureTime(); 1742 } 1743 } 1744 1745 void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) 1746 { 1747 starCenter.setX(newCenter.x()); 1748 starCenter.setY(newCenter.y()); 1749 if (newCenter.z() > 0) 1750 starCenter.setZ(newCenter.z()); 1751 1752 if (updateNow) 1753 syncTrackingBoxPosition(); 1754 } 1755 1756 void Guide::syncTrackingBoxPosition() 1757 { 1758 if(!m_Camera || guiderType == GUIDE_LINGUIDER) 1759 return; 1760 1761 if(guiderType == GUIDE_PHD2) 1762 { 1763 //This way it won't set the tracking box on the Guide Star Image. 1764 if(!m_ImageData.isNull()) 1765 { 1766 if(m_ImageData->width() < 50) 1767 { 1768 m_GuideView->setTrackingBoxEnabled(false); 1769 return; 1770 } 1771 } 1772 } 1773 1774 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 1775 Q_ASSERT(targetChip); 1776 1777 int subBinX = 1, subBinY = 1; 1778 targetChip->getBinning(&subBinX, &subBinY); 1779 1780 if (starCenter.isNull() == false) 1781 { 1782 double boxSize = guideSquareSize->currentText().toInt(); 1783 int x, y, w, h; 1784 targetChip->getFrame(&x, &y, &w, &h); 1785 // If box size is larger than image size, set it to lower index 1786 if (boxSize / subBinX >= w || boxSize / subBinY >= h) 1787 { 1788 int newIndex = guideSquareSize->currentIndex() - 1; 1789 if (newIndex >= 0) 1790 guideSquareSize->setCurrentIndex(newIndex); 1791 return; 1792 } 1793 1794 // If binning changed, update coords accordingly 1795 if (subBinX != starCenter.z()) 1796 { 1797 if (starCenter.z() > 0) 1798 { 1799 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); 1800 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); 1801 } 1802 1803 starCenter.setZ(subBinX); 1804 } 1805 1806 QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), 1807 boxSize / subBinX, boxSize / subBinY); 1808 m_GuideView->setTrackingBoxEnabled(true); 1809 m_GuideView->setTrackingBox(starRect); 1810 } 1811 } 1812 1813 bool Guide::setGuiderType(int type) 1814 { 1815 // Use default guider option 1816 if (type == -1) 1817 type = Options::guiderType(); 1818 else if (type == guiderType) 1819 return true; 1820 1821 if (m_State == GUIDE_CALIBRATING || m_State == GUIDE_GUIDING || m_State == GUIDE_DITHERING) 1822 { 1823 appendLogText(i18n("Cannot change guider type while active.")); 1824 return false; 1825 } 1826 1827 if (m_GuiderInstance != nullptr) 1828 { 1829 // Disconnect from host 1830 if (m_GuiderInstance->isConnected()) 1831 m_GuiderInstance->Disconnect(); 1832 1833 // Disconnect signals 1834 m_GuiderInstance->disconnect(); 1835 } 1836 1837 guiderType = static_cast<GuiderType>(type); 1838 1839 switch (type) 1840 { 1841 case GUIDE_INTERNAL: 1842 { 1843 connect(internalGuider, &InternalGuider::newMultiPulse, this, &Guide::sendMultiPulse); 1844 connect(internalGuider, &InternalGuider::newSinglePulse, this, &Guide::sendSinglePulse); 1845 connect(internalGuider, &InternalGuider::DESwapChanged, this, &Guide::setDECSwap); 1846 connect(internalGuider, &InternalGuider::newStarPixmap, this, &Guide::newStarPixmap); 1847 1848 m_GuiderInstance = internalGuider; 1849 1850 internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); 1851 1852 clearCalibrationB->setEnabled(true); 1853 guideB->setEnabled(true); 1854 captureB->setEnabled(true); 1855 loopB->setEnabled(true); 1856 1857 configSEPMultistarOptions(); 1858 guideDarkFrame->setEnabled(true); 1859 1860 guideExposure->setEnabled(true); 1861 guideBinning->setEnabled(true); 1862 guideSquareSize->setEnabled(true); 1863 1864 externalConnectB->setEnabled(false); 1865 externalDisconnectB->setEnabled(false); 1866 1867 opsGuide->controlGroup->setEnabled(true); 1868 infoGroup->setEnabled(true); 1869 l_Aperture->setEnabled(true); 1870 l_FOV->setEnabled(true); 1871 l_FbyD->setEnabled(true); 1872 l_Focal->setEnabled(true); 1873 driftGraphicsGroup->setEnabled(true); 1874 1875 updateGuideParams(); 1876 } 1877 break; 1878 1879 case GUIDE_PHD2: 1880 if (phd2Guider.isNull()) 1881 phd2Guider = new PHD2(); 1882 1883 m_GuiderInstance = phd2Guider; 1884 phd2Guider->setGuideView(m_GuideView); 1885 1886 connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); 1887 1888 clearCalibrationB->setEnabled(true); 1889 captureB->setEnabled(false); 1890 loopB->setEnabled(false); 1891 guideDarkFrame->setEnabled(false); 1892 guideSubframe->setEnabled(false); 1893 guideAutoStar->setEnabled(false); 1894 guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) 1895 externalConnectB->setEnabled(false); 1896 1897 rAGuideEnabled->setEnabled(false); 1898 eastRAGuideEnabled->setEnabled(false); 1899 westRAGuideEnabled->setEnabled(false); 1900 1901 opsGuide->controlGroup->setEnabled(false); 1902 infoGroup->setEnabled(true); 1903 l_Aperture->setEnabled(false); 1904 l_FOV->setEnabled(false); 1905 l_FbyD->setEnabled(false); 1906 l_Focal->setEnabled(false); 1907 driftGraphicsGroup->setEnabled(true); 1908 1909 guideExposure->setEnabled(true); 1910 guideBinning->setEnabled(false); 1911 guideSquareSize->setEnabled(false); 1912 1913 if (Options::resetGuideCalibration()) 1914 appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); 1915 1916 updateGuideParams(); 1917 break; 1918 1919 case GUIDE_LINGUIDER: 1920 if (linGuider.isNull()) 1921 linGuider = new LinGuider(); 1922 1923 m_GuiderInstance = linGuider; 1924 1925 clearCalibrationB->setEnabled(true); 1926 captureB->setEnabled(false); 1927 loopB->setEnabled(false); 1928 guideDarkFrame->setEnabled(false); 1929 guideSubframe->setEnabled(false); 1930 guideAutoStar->setEnabled(false); 1931 guideB->setEnabled(true); 1932 externalConnectB->setEnabled(true); 1933 1934 opsGuide->controlGroup->setEnabled(false); 1935 infoGroup->setEnabled(false); 1936 driftGraphicsGroup->setEnabled(false); 1937 1938 guideExposure->setEnabled(false); 1939 guideBinning->setEnabled(false); 1940 guideSquareSize->setEnabled(false); 1941 1942 updateGuideParams(); 1943 1944 break; 1945 } 1946 1947 if (m_GuiderInstance != nullptr) 1948 { 1949 connect(m_GuiderInstance, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture); 1950 connect(m_GuiderInstance, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText); 1951 connect(m_GuiderInstance, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus); 1952 connect(m_GuiderInstance, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition); 1953 connect(m_GuiderInstance, &Ekos::GuideInterface::guideStats, this, &Ekos::Guide::guideStats); 1954 1955 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta); 1956 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse); 1957 connect(m_GuiderInstance, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma); 1958 connect(m_GuiderInstance, &Ekos::GuideInterface::newSNR, this, &Ekos::Guide::setSNR); 1959 connect(m_GuiderInstance, &Ekos::GuideInterface::guideInfo, this, &Ekos::Guide::guideInfo); 1960 connect(m_GuiderInstance, &Ekos::GuideInterface::abortExposure, this, &Ekos::Guide::abortExposure); 1961 1962 driftGraph->connectGuider(m_GuiderInstance); 1963 targetPlot->connectGuider(m_GuiderInstance); 1964 1965 connect(m_GuiderInstance, &Ekos::GuideInterface::calibrationUpdate, this, &Ekos::Guide::calibrationUpdate); 1966 1967 connect(m_GuiderInstance, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera); 1968 } 1969 1970 externalConnectB->setEnabled(false); 1971 externalDisconnectB->setEnabled(false); 1972 1973 if (m_GuiderInstance != nullptr && guiderType != GUIDE_INTERNAL) 1974 { 1975 externalConnectB->setEnabled(!m_GuiderInstance->isConnected()); 1976 externalDisconnectB->setEnabled(m_GuiderInstance->isConnected()); 1977 } 1978 1979 if (m_GuiderInstance != nullptr) 1980 m_GuiderInstance->Connect(); 1981 1982 return true; 1983 } 1984 1985 void Guide::guideInfo(const QString &info) 1986 { 1987 if (info.size() == 0) 1988 { 1989 guideInfoLabel->setVisible(false); 1990 guideInfoText->setVisible(false); 1991 return; 1992 } 1993 guideInfoLabel->setVisible(true); 1994 guideInfoLabel->setText("Detections"); 1995 guideInfoText->setVisible(true); 1996 guideInfoText->setText(info); 1997 } 1998 1999 void Guide::updateTrackingBoxSize(int currentIndex) 2000 { 2001 if (currentIndex >= 0) 2002 { 2003 if (guiderType == GUIDE_INTERNAL) 2004 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setGuideBoxSize(guideSquareSize->currentText().toInt()); 2005 2006 syncTrackingBoxPosition(); 2007 } 2008 } 2009 2010 void Guide::onThresholdChanged(int index) 2011 { 2012 switch (guiderType) 2013 { 2014 case GUIDE_INTERNAL: 2015 dynamic_cast<InternalGuider *>(m_GuiderInstance)->setSquareAlgorithm(index); 2016 break; 2017 2018 default: 2019 break; 2020 } 2021 } 2022 2023 void Guide::onEnableDirRA() 2024 { 2025 // If RA guiding is enable or disabled, the GPG should be reset. 2026 if (Options::gPGEnabled()) 2027 m_GuiderInstance->resetGPG(); 2028 } 2029 2030 void Guide::onEnableDirDEC() 2031 { 2032 onControlDirectionChanged(); 2033 } 2034 2035 void Guide::onControlDirectionChanged() 2036 { 2037 if(guiderType == GUIDE_PHD2) 2038 phd2Guider -> requestSetDEGuideMode(dECGuideEnabled->isChecked(), northDECGuideEnabled->isChecked(), 2039 southDECGuideEnabled->isChecked()); 2040 } 2041 2042 void Guide::updateDirectionsFromPHD2(const QString &mode) 2043 { 2044 //disable connections 2045 disconnect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); 2046 disconnect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2047 disconnect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2048 2049 if(mode == "Auto") 2050 { 2051 dECGuideEnabled->setChecked(true); 2052 northDECGuideEnabled->setChecked(true); 2053 southDECGuideEnabled->setChecked(true); 2054 } 2055 else if(mode == "North") 2056 { 2057 dECGuideEnabled->setChecked(true); 2058 northDECGuideEnabled->setChecked(true); 2059 southDECGuideEnabled->setChecked(false); 2060 } 2061 else if(mode == "South") 2062 { 2063 dECGuideEnabled->setChecked(true); 2064 northDECGuideEnabled->setChecked(false); 2065 southDECGuideEnabled->setChecked(true); 2066 } 2067 else //Off 2068 { 2069 dECGuideEnabled->setChecked(false); 2070 northDECGuideEnabled->setChecked(true); 2071 southDECGuideEnabled->setChecked(true); 2072 } 2073 2074 //Re-enable connections 2075 connect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); 2076 connect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2077 connect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2078 } 2079 2080 void Guide::setTrackingStar(int x, int y) 2081 { 2082 QVector3D newStarPosition(x, y, -1); 2083 setStarPosition(newStarPosition, true); 2084 2085 if(guiderType == GUIDE_PHD2) 2086 { 2087 //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that. 2088 if(!m_ImageData.isNull()) 2089 { 2090 if(m_ImageData->width() > 50) 2091 phd2Guider->setLockPosition(starCenter.x(), starCenter.y()); 2092 } 2093 } 2094 2095 if (operationStack.isEmpty() == false) 2096 executeOperationStack(); 2097 } 2098 2099 void Guide::setAxisDelta(double ra, double de) 2100 { 2101 //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding. 2102 //If guide pulses start coming in, it must be guiding. 2103 // 2020-04-10 sterne-jaeger: Will be resolved inside EKOS phd guiding. 2104 // if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING) 2105 // setStatus(GUIDE_GUIDING); 2106 2107 ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. 2108 2109 int currentNumPoints = driftGraph->graph(GuideGraph::G_RA)->dataCount(); 2110 guideSlider->setMaximum(currentNumPoints); 2111 if(graphOnLatestPt) 2112 { 2113 guideSlider->setValue(currentNumPoints); 2114 } 2115 l_DeltaRA->setText(QString::number(ra, 'f', 2)); 2116 l_DeltaDEC->setText(QString::number(de, 'f', 2)); 2117 2118 emit newAxisDelta(ra, de); 2119 } 2120 2121 void Guide::calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString &message, 2122 double dx, double dy) 2123 { 2124 switch (type) 2125 { 2126 case GuideInterface::RA_OUT: 2127 calibrationPlot->graph(GuideGraph::G_RA)->addData(dx, dy); 2128 break; 2129 case GuideInterface::RA_IN: 2130 calibrationPlot->graph(GuideGraph::G_DEC)->addData(dx, dy); 2131 break; 2132 case GuideInterface::BACKLASH: 2133 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->addData(dx, dy); 2134 break; 2135 case GuideInterface::DEC_OUT: 2136 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(dx, dy); 2137 break; 2138 case GuideInterface::DEC_IN: 2139 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->addData(dx, dy); 2140 break; 2141 case GuideInterface::CALIBRATION_MESSAGE_ONLY: 2142 ; 2143 } 2144 calLabel->setText(message); 2145 calibrationPlot->replot(); 2146 } 2147 2148 void Guide::setAxisSigma(double ra, double de) 2149 { 2150 l_ErrRA->setText(QString::number(ra, 'f', 2)); 2151 l_ErrDEC->setText(QString::number(de, 'f', 2)); 2152 const double total = std::hypot(ra, de); 2153 l_TotalRMS->setText(QString::number(total, 'f', 2)); 2154 2155 emit newAxisSigma(ra, de); 2156 } 2157 2158 QList<double> Guide::axisDelta() 2159 { 2160 QList<double> delta; 2161 2162 delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); 2163 2164 return delta; 2165 } 2166 2167 QList<double> Guide::axisSigma() 2168 { 2169 QList<double> sigma; 2170 2171 sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); 2172 2173 return sigma; 2174 } 2175 2176 void Guide::setAxisPulse(double ra, double de) 2177 { 2178 l_PulseRA->setText(QString::number(static_cast<int>(ra))); 2179 l_PulseDEC->setText(QString::number(static_cast<int>(de))); 2180 } 2181 2182 void Guide::setSNR(double snr) 2183 { 2184 l_SNR->setText(QString::number(snr, 'f', 1)); 2185 } 2186 2187 void Guide::buildOperationStack(GuideState operation) 2188 { 2189 operationStack.clear(); 2190 2191 switch (operation) 2192 { 2193 case GUIDE_CAPTURE: 2194 if (guideDarkFrame->isChecked()) 2195 operationStack.push(GUIDE_DARK); 2196 2197 operationStack.push(GUIDE_CAPTURE); 2198 operationStack.push(GUIDE_SUBFRAME); 2199 break; 2200 2201 case GUIDE_CALIBRATING: 2202 operationStack.push(GUIDE_CALIBRATING); 2203 if (guiderType == GUIDE_INTERNAL) 2204 { 2205 if (guideDarkFrame->isChecked()) 2206 operationStack.push(GUIDE_DARK); 2207 2208 // Auto Star Selected Path 2209 if (guideAutoStar->isChecked() || 2210 // SEP MultiStar always uses an automated guide star. 2211 internalGuider->SEPMultiStarEnabled()) 2212 { 2213 // If subframe is enabled and we need to auto select a star, then we need to make the final capture 2214 // of the subframed image. This is only done if we aren't already subframed. 2215 if (subFramed == false && guideSubframe->isChecked()) 2216 operationStack.push(GUIDE_CAPTURE); 2217 2218 operationStack.push(GUIDE_SUBFRAME); 2219 operationStack.push(GUIDE_STAR_SELECT); 2220 2221 2222 operationStack.push(GUIDE_CAPTURE); 2223 2224 // If we are being ask to go full frame, let's do that first 2225 if (subFramed == true && guideSubframe->isChecked() == false) 2226 operationStack.push(GUIDE_SUBFRAME); 2227 } 2228 // Manual Star Selection Path 2229 else 2230 { 2231 // Final capture before we start calibrating 2232 if (subFramed == false && guideSubframe->isChecked()) 2233 operationStack.push(GUIDE_CAPTURE); 2234 2235 // Subframe if required 2236 operationStack.push(GUIDE_SUBFRAME); 2237 2238 // First capture an image 2239 operationStack.push(GUIDE_CAPTURE); 2240 } 2241 2242 } 2243 break; 2244 2245 default: 2246 break; 2247 } 2248 } 2249 2250 bool Guide::executeOperationStack() 2251 { 2252 if (operationStack.isEmpty()) 2253 return false; 2254 2255 GuideState nextOperation = operationStack.pop(); 2256 // qCDebug(KSTARS_EKOS_GUIDE) << "Executing operation " << getGuideStatusString(nextOperation); 2257 2258 bool actionRequired = false; 2259 2260 switch (nextOperation) 2261 { 2262 case GUIDE_SUBFRAME: 2263 actionRequired = executeOneOperation(nextOperation); 2264 break; 2265 2266 case GUIDE_DARK: 2267 actionRequired = executeOneOperation(nextOperation); 2268 break; 2269 2270 case GUIDE_CAPTURE: 2271 actionRequired = captureOneFrame(); 2272 break; 2273 2274 case GUIDE_STAR_SELECT: 2275 actionRequired = executeOneOperation(nextOperation); 2276 break; 2277 2278 case GUIDE_CALIBRATING: 2279 if (guiderType == GUIDE_INTERNAL) 2280 { 2281 m_GuiderInstance->setStarPosition(starCenter); 2282 2283 // Tracking must be engaged 2284 if (m_Mount && m_Mount->canControlTrack() && m_Mount->isTracking() == false) 2285 m_Mount->setTrackEnabled(true); 2286 } 2287 2288 if (m_GuiderInstance->calibrate()) 2289 { 2290 if (guiderType == GUIDE_INTERNAL) 2291 disconnect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Guide::setTrackingStar); 2292 setBusy(true); 2293 } 2294 else 2295 { 2296 emit newStatus(GUIDE_CALIBRATION_ERROR); 2297 m_State = GUIDE_IDLE; 2298 appendLogText(i18n("Calibration failed to start.")); 2299 setBusy(false); 2300 } 2301 break; 2302 2303 default: 2304 break; 2305 } 2306 2307 // If an additional action is required, return return and continue later 2308 if (actionRequired) 2309 return true; 2310 // Otherwise, continue processing the stack 2311 else 2312 return executeOperationStack(); 2313 } 2314 2315 bool Guide::executeOneOperation(GuideState operation) 2316 { 2317 bool actionRequired = false; 2318 2319 if (m_Camera == nullptr) 2320 return actionRequired; 2321 2322 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 2323 if (targetChip == nullptr) 2324 return false; 2325 2326 int subBinX, subBinY; 2327 targetChip->getBinning(&subBinX, &subBinY); 2328 2329 switch (operation) 2330 { 2331 case GUIDE_SUBFRAME: 2332 { 2333 // SEP MultiStar doesn't subframe. 2334 if ((guiderType == GUIDE_INTERNAL) && internalGuider->SEPMultiStarEnabled()) 2335 break; 2336 // Check if we need and can subframe 2337 if (subFramed == false && guideSubframe->isChecked() == true && targetChip->canSubframe()) 2338 { 2339 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; 2340 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 2341 2342 int offset = guideSquareSize->currentText().toInt() / subBinX; 2343 2344 int x = starCenter.x(); 2345 int y = starCenter.y(); 2346 2347 x = (x - offset * 2) * subBinX; 2348 y = (y - offset * 2) * subBinY; 2349 int w = offset * 4 * subBinX; 2350 int h = offset * 4 * subBinY; 2351 2352 if (x < minX) 2353 x = minX; 2354 if (y < minY) 2355 y = minY; 2356 if ((x + w) > maxW) 2357 w = maxW - x; 2358 if ((y + h) > maxH) 2359 h = maxH - y; 2360 2361 targetChip->setFrame(x, y, w, h); 2362 2363 subFramed = true; 2364 QVariantMap settings = frameSettings[targetChip]; 2365 settings["x"] = x; 2366 settings["y"] = y; 2367 settings["w"] = w; 2368 settings["h"] = h; 2369 settings["binx"] = subBinX; 2370 settings["biny"] = subBinY; 2371 2372 frameSettings[targetChip] = settings; 2373 2374 starCenter.setX(w / (2 * subBinX)); 2375 starCenter.setY(h / (2 * subBinX)); 2376 } 2377 // Otherwise check if we are already subframed 2378 // and we need to go back to full frame 2379 // or if we need to go back to full frame since we need 2380 // to reaquire a star 2381 else if (subFramed && 2382 (guideSubframe->isChecked() == false || 2383 m_State == GUIDE_REACQUIRE)) 2384 { 2385 targetChip->resetFrame(); 2386 2387 int x, y, w, h; 2388 targetChip->getFrame(&x, &y, &w, &h); 2389 2390 QVariantMap settings; 2391 settings["x"] = x; 2392 settings["y"] = y; 2393 settings["w"] = w; 2394 settings["h"] = h; 2395 settings["binx"] = subBinX; 2396 settings["biny"] = subBinY; 2397 frameSettings[targetChip] = settings; 2398 2399 subFramed = false; 2400 2401 starCenter.setX(w / (2 * subBinX)); 2402 starCenter.setY(h / (2 * subBinX)); 2403 2404 //starCenter.setX(0); 2405 //starCenter.setY(0); 2406 } 2407 } 2408 break; 2409 2410 case GUIDE_DARK: 2411 { 2412 // Do we need to take a dark frame? 2413 if (m_ImageData && guideDarkFrame->isChecked()) 2414 { 2415 QVariantMap settings = frameSettings[targetChip]; 2416 uint16_t offsetX = 0; 2417 uint16_t offsetY = 0; 2418 2419 if (settings["x"].isValid() && 2420 settings["y"].isValid() && 2421 settings["binx"].isValid() && 2422 settings["biny"].isValid()) 2423 { 2424 offsetX = settings["x"].toInt() / settings["binx"].toInt(); 2425 offsetY = settings["y"].toInt() / settings["biny"].toInt(); 2426 } 2427 2428 actionRequired = true; 2429 targetChip->setCaptureFilter(FITS_NONE); 2430 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()), 2431 targetChip, m_ImageData, guideExposure->value(), offsetX, offsetY); 2432 } 2433 } 2434 break; 2435 2436 case GUIDE_STAR_SELECT: 2437 { 2438 m_State = GUIDE_STAR_SELECT; 2439 emit newStatus(m_State); 2440 2441 if (guideAutoStar->isChecked() || 2442 // SEP MultiStar always uses an automated guide star. 2443 ((guiderType == GUIDE_INTERNAL) && 2444 internalGuider->SEPMultiStarEnabled())) 2445 { 2446 bool autoStarCaptured = internalGuider->selectAutoStar(); 2447 if (autoStarCaptured) 2448 { 2449 appendLogText(i18n("Auto star selected.")); 2450 } 2451 else 2452 { 2453 appendLogText(i18n("Failed to select an auto star.")); 2454 actionRequired = true; 2455 m_State = GUIDE_CALIBRATION_ERROR; 2456 emit newStatus(m_State); 2457 setBusy(false); 2458 } 2459 } 2460 else 2461 { 2462 appendLogText(i18n("Select a guide star to calibrate.")); 2463 actionRequired = true; 2464 } 2465 } 2466 break; 2467 2468 default: 2469 break; 2470 } 2471 2472 return actionRequired; 2473 } 2474 2475 void Guide::processGuideOptions() 2476 { 2477 if (Options::guiderType() != guiderType) 2478 { 2479 guiderType = static_cast<GuiderType>(Options::guiderType()); 2480 setGuiderType(Options::guiderType()); 2481 } 2482 } 2483 2484 void Guide::showFITSViewer() 2485 { 2486 static int lastFVTabID = -1; 2487 if (m_ImageData) 2488 { 2489 QUrl url = QUrl::fromLocalFile("guide.fits"); 2490 if (fv.isNull()) 2491 { 2492 fv = KStars::Instance()->createFITSViewer(); 2493 fv->loadData(m_ImageData, url, &lastFVTabID); 2494 connect(fv.get(), &FITSViewer::terminated, this, [this]() 2495 { 2496 fv.clear(); 2497 }); 2498 } 2499 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false) 2500 fv->loadData(m_ImageData, url, &lastFVTabID); 2501 2502 fv->show(); 2503 } 2504 } 2505 2506 void Guide::setExternalGuiderBLOBEnabled(bool enable) 2507 { 2508 // Nothing to do if guider is internal 2509 if (guiderType == GUIDE_INTERNAL) 2510 return; 2511 2512 if(!m_Camera) 2513 return; 2514 2515 m_Camera->setBLOBEnabled(enable); 2516 2517 if(m_Camera->isBLOBEnabled()) 2518 { 2519 checkUseGuideHead(); 2520 2521 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 2522 if (targetChip) 2523 targetChip->setCaptureMode(FITS_GUIDE); 2524 syncCameraInfo(); 2525 } 2526 2527 } 2528 2529 void Guide::resetNonGuidedDither() 2530 { 2531 // reset non guided dither total drift 2532 nonGuidedDitherRaOffsetMsec = 0; 2533 nonGuidedDitherDecOffsetMsec = 0; 2534 qCDebug(KSTARS_EKOS_GUIDE) << "Reset non guiding dithering position"; 2535 2536 // initialize random generator if not done before 2537 if (!isNonGuidedDitherInitialized) 2538 { 2539 auto seed = std::chrono::system_clock::now().time_since_epoch().count(); 2540 nonGuidedPulseGenerator.seed(seed); 2541 isNonGuidedDitherInitialized = true; 2542 qCDebug(KSTARS_EKOS_GUIDE) << "Initialize non guiding dithering random generator"; 2543 } 2544 } 2545 2546 void Guide::nonGuidedDither() 2547 { 2548 double ditherPulse = Options::ditherNoGuidingPulse(); 2549 2550 // Randomize dithering position up to +/-dithePulse distance from original 2551 std::uniform_int_distribution<int> newPos(-ditherPulse, +ditherPulse); 2552 2553 // Calculate the pulse needed to move to the new position, then save the new position and apply the pulse 2554 2555 // for ra 2556 const int newRaOffsetMsec = newPos(nonGuidedPulseGenerator); 2557 const int raPulse = nonGuidedDitherRaOffsetMsec - newRaOffsetMsec; 2558 nonGuidedDitherRaOffsetMsec = newRaOffsetMsec; 2559 const int raMsec = std::abs(raPulse); 2560 const int raPolarity = (raPulse >= 0 ? 1 : -1); 2561 2562 // and for dec 2563 const int newDecOffsetMsec = newPos(nonGuidedPulseGenerator); 2564 const int decPulse = nonGuidedDitherDecOffsetMsec - newDecOffsetMsec; 2565 nonGuidedDitherDecOffsetMsec = newDecOffsetMsec; 2566 const int decMsec = std::abs(decPulse); 2567 const int decPolarity = (decPulse >= 0 ? 1 : -1); 2568 2569 qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither..."; 2570 qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << raMsec << "ra_polarity:" << raPolarity << "de_msec:" << decMsec << 2571 "de_polarity:" << decPolarity; 2572 2573 bool rc = sendMultiPulse(raPolarity > 0 ? RA_INC_DIR : RA_DEC_DIR, raMsec, decPolarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR, 2574 decMsec, DontCaptureAfterPulses); 2575 2576 if (rc) 2577 { 2578 qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful."; 2579 QTimer::singleShot( (raMsec > decMsec ? raMsec : decMsec) + Options::ditherSettle() * 1000 + 100, this, [this]() 2580 { 2581 emit newStatus(GUIDE_DITHERING_SUCCESS); 2582 m_State = GUIDE_IDLE; 2583 }); 2584 } 2585 else 2586 { 2587 qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed."; 2588 emit newStatus(GUIDE_DITHERING_ERROR); 2589 m_State = GUIDE_IDLE; 2590 } 2591 } 2592 2593 void Guide::handleManualDither() 2594 { 2595 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD); 2596 if (targetChip == nullptr) 2597 return; 2598 2599 Ui::ManualDither ditherDialog; 2600 QDialog container(this); 2601 ditherDialog.setupUi(&container); 2602 2603 if (guiderType != GUIDE_INTERNAL) 2604 { 2605 ditherDialog.coordinatesR->setEnabled(false); 2606 ditherDialog.x->setEnabled(false); 2607 ditherDialog.y->setEnabled(false); 2608 } 2609 2610 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; 2611 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 2612 2613 ditherDialog.x->setMinimum(minX); 2614 ditherDialog.x->setMaximum(maxX); 2615 ditherDialog.y->setMinimum(minY); 2616 ditherDialog.y->setMaximum(maxY); 2617 2618 ditherDialog.x->setValue(starCenter.x()); 2619 ditherDialog.y->setValue(starCenter.y()); 2620 2621 if (container.exec() == QDialog::Accepted) 2622 { 2623 if (ditherDialog.magnitudeR->isChecked()) 2624 m_GuiderInstance->dither(ditherDialog.magnitude->value()); 2625 else 2626 { 2627 InternalGuider * const ig = dynamic_cast<InternalGuider *>(m_GuiderInstance); 2628 if (ig) 2629 ig->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); 2630 } 2631 } 2632 } 2633 2634 bool Guide::connectGuider() 2635 { 2636 setStatus(GUIDE_IDLE); 2637 return m_GuiderInstance->Connect(); 2638 } 2639 2640 bool Guide::disconnectGuider() 2641 { 2642 return m_GuiderInstance->Disconnect(); 2643 } 2644 2645 void Guide::initPlots() 2646 { 2647 initDriftGraph(); 2648 initCalibrationPlot(); 2649 2650 connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); 2651 connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); 2652 2653 buildTarget(); 2654 } 2655 2656 void Guide::initDriftGraph() 2657 { 2658 //Dragging and zooming settings 2659 // make bottom axis transfer its range to the top axis if the graph gets zoomed: 2660 connect(driftGraph->xAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged), 2661 driftGraph->xAxis2, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::setRange)); 2662 // update the second vertical axis properly if the graph gets zoomed. 2663 connect(driftGraph->yAxis, static_cast<void(QCPAxis::*)(const QCPRange &)>(&QCPAxis::rangeChanged), 2664 [this]() 2665 { 2666 driftGraph->setCorrectionGraphScale(correctionSlider->value()); 2667 }); 2668 2669 connect(driftGraph, &QCustomPlot::mouseMove, driftGraph, &GuideDriftGraph::mouseOverLine); 2670 connect(driftGraph, &QCustomPlot::mousePress, driftGraph, &GuideDriftGraph::mouseClicked); 2671 2672 int scale = 2673 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg 2674 correctionSlider->setValue(scale); 2675 } 2676 2677 void Guide::initCalibrationPlot() 2678 { 2679 calibrationPlot->setBackground(QBrush(Qt::black)); 2680 calibrationPlot->setSelectionTolerance(10); 2681 2682 calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1)); 2683 calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1)); 2684 2685 calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1)); 2686 calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1)); 2687 2688 calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); 2689 calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); 2690 2691 calibrationPlot->xAxis->setTickLabelColor(Qt::white); 2692 calibrationPlot->yAxis->setTickLabelColor(Qt::white); 2693 2694 calibrationPlot->xAxis->setLabelColor(Qt::white); 2695 calibrationPlot->yAxis->setLabelColor(Qt::white); 2696 2697 calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10)); 2698 calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10)); 2699 calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); 2700 calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); 2701 2702 calibrationPlot->xAxis->setLabelPadding(2); 2703 calibrationPlot->yAxis->setLabelPadding(2); 2704 2705 calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); 2706 calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); 2707 calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); 2708 calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); 2709 calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); 2710 calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); 2711 2712 calibrationPlot->xAxis->setLabel(i18n("x (pixels)")); 2713 calibrationPlot->yAxis->setLabel(i18n("y (pixels)")); 2714 2715 calibrationPlot->xAxis->setRange(-20, 20); 2716 calibrationPlot->yAxis->setRange(-20, 20); 2717 2718 calibrationPlot->setInteractions(QCP::iRangeZoom); 2719 calibrationPlot->setInteraction(QCP::iRangeDrag, true); 2720 2721 calibrationPlot->addGraph(); 2722 calibrationPlot->graph(GuideGraph::G_RA)->setLineStyle(QCPGraph::lsNone); 2723 calibrationPlot->graph(GuideGraph::G_RA)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 2724 QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 6)); 2725 calibrationPlot->graph(GuideGraph::G_RA)->setName("RA out"); 2726 2727 calibrationPlot->addGraph(); 2728 calibrationPlot->graph(GuideGraph::G_DEC)->setLineStyle(QCPGraph::lsNone); 2729 calibrationPlot->graph(GuideGraph::G_DEC)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::white, 2), 2730 QBrush(), 4)); 2731 calibrationPlot->graph(GuideGraph::G_DEC)->setName("RA in"); 2732 2733 calibrationPlot->addGraph(); 2734 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone); 2735 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlus, QPen(Qt::white, 2736 2), 2737 QBrush(), 6)); 2738 calibrationPlot->graph(GuideGraph::G_RA_HIGHLIGHT)->setName("Backlash"); 2739 2740 calibrationPlot->addGraph(); 2741 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone); 2742 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 2743 QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 6)); 2744 calibrationPlot->graph(GuideGraph::G_DEC_HIGHLIGHT)->setName("DEC out"); 2745 2746 calibrationPlot->addGraph(); 2747 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setLineStyle(QCPGraph::lsNone); 2748 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::yellow, 2749 2), 2750 QBrush(), 4)); 2751 calibrationPlot->graph(GuideGraph::G_RA_PULSE)->setName("DEC in"); 2752 2753 calLabel = new QCPItemText(calibrationPlot); 2754 calLabel->setColor(QColor(255, 255, 255)); 2755 calLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter); 2756 calLabel->position->setType(QCPItemPosition::ptAxisRectRatio); 2757 calLabel->position->setCoords(0.5, 0); 2758 calLabel->setText(""); 2759 calLabel->setFont(QFont(font().family(), 10)); 2760 calLabel->setVisible(true); 2761 2762 calibrationPlot->resize(190, 190); 2763 calibrationPlot->replot(); 2764 } 2765 2766 void Guide::initView() 2767 { 2768 guideStateWidget = new GuideStateWidget(); 2769 guideInfoLayout->insertWidget(-1, guideStateWidget); 2770 2771 m_GuideView.reset(new GuideView(guideWidget, FITS_GUIDE)); 2772 m_GuideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 2773 m_GuideView->setBaseSize(guideWidget->size()); 2774 m_GuideView->createFloatingToolBar(); 2775 QVBoxLayout *vlayout = new QVBoxLayout(); 2776 vlayout->addWidget(m_GuideView.get()); 2777 guideWidget->setLayout(vlayout); 2778 connect(m_GuideView.get(), &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar); 2779 guideInfoLabel->setVisible(false); 2780 guideInfoText->setVisible(false); 2781 } 2782 2783 void Guide::initConnections() 2784 { 2785 // Exposure Timeout 2786 captureTimeout.setSingleShot(true); 2787 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); 2788 2789 // Guiding Box Size 2790 connect(guideSquareSize, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 2791 &Ekos::Guide::updateTrackingBoxSize); 2792 2793 // Dark Frame Check 2794 connect(guideDarkFrame, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); 2795 // Subframe check 2796 if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method 2797 connect(guideSubframe, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); 2798 2799 // Binning Combo Selection 2800 connect(guideBinning, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 2801 &Ekos::Guide::updateCCDBin); 2802 2803 // RA/DEC Enable directions 2804 connect(rAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); 2805 connect(dECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); 2806 2807 // N/W and W/E direction enable 2808 connect(northDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2809 connect(southDECGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2810 connect(westRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2811 connect(eastRAGuideEnabled, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); 2812 2813 // Capture 2814 connect(captureB, &QPushButton::clicked, this, [this]() 2815 { 2816 m_State = GUIDE_CAPTURE; 2817 emit newStatus(m_State); 2818 2819 if(guiderType == GUIDE_PHD2) 2820 { 2821 configurePHD2Camera(); 2822 if(phd2Guider->isCurrentCameraNotInEkos()) 2823 appendLogText( 2824 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.")); 2825 else if(guideSubframe->isChecked()) 2826 { 2827 appendLogText( 2828 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")); 2829 guideSubframe->setChecked(false); 2830 } 2831 phd2Guider->captureSingleFrame(); 2832 } 2833 else if (guiderType == GUIDE_INTERNAL) 2834 capture(); 2835 }); 2836 2837 // Framing 2838 connect(loopB, &QPushButton::clicked, this, &Guide::loop); 2839 2840 // Stop 2841 connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); 2842 2843 // Clear Calibrate 2844 //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); 2845 connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); 2846 2847 // Guide 2848 connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); 2849 2850 // Connect External Guide 2851 connect(externalConnectB, &QPushButton::clicked, this, [&]() 2852 { 2853 //setExternalGuiderBLOBEnabled(false); 2854 m_GuiderInstance->Connect(); 2855 }); 2856 connect(externalDisconnectB, &QPushButton::clicked, this, [&]() 2857 { 2858 //setExternalGuiderBLOBEnabled(true); 2859 m_GuiderInstance->Disconnect(); 2860 }); 2861 2862 // Pulse Timer 2863 m_PulseTimer.setSingleShot(true); 2864 connect(&m_PulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); 2865 2866 //This connects all the buttons and slider below the guide plots. 2867 connect(guiderAccuracyThreshold, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, 2868 &Ekos::Guide::buildTarget); 2869 connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory); 2870 connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint); 2871 connect(rADisplayedOnGuideGraph, &QCheckBox::toggled, [this](bool isChecked) 2872 { 2873 driftGraph->toggleShowPlot(GuideGraph::G_RA, isChecked); 2874 }); 2875 connect(dEDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked) 2876 { 2877 driftGraph->toggleShowPlot(GuideGraph::G_DEC, isChecked); 2878 }); 2879 connect(rACorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked) 2880 { 2881 driftGraph->toggleShowPlot(GuideGraph::G_RA_PULSE, isChecked); 2882 }); 2883 connect(dECorrDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked) 2884 { 2885 driftGraph->toggleShowPlot(GuideGraph::G_DEC_PULSE, isChecked); 2886 }); 2887 connect(sNRDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked) 2888 { 2889 driftGraph->toggleShowPlot(GuideGraph::G_SNR, isChecked); 2890 }); 2891 connect(rMSDisplayedOnGuideGraph, &QCheckBox::toggled, this, [this](bool isChecked) 2892 { 2893 driftGraph->toggleShowPlot(GuideGraph::G_RMS, isChecked); 2894 }); 2895 connect(correctionSlider, &QSlider::sliderMoved, driftGraph, &GuideDriftGraph::setCorrectionGraphScale); 2896 2897 connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); 2898 2899 connect(this, &Ekos::Guide::newStatus, guideStateWidget, &Ekos::GuideStateWidget::updateGuideStatus); 2900 } 2901 2902 void Guide::removeDevice(const QSharedPointer<ISD::GenericDevice> &device) 2903 { 2904 auto name = device->getDeviceName(); 2905 2906 device->disconnect(this); 2907 2908 // Mounts 2909 if (m_Mount && m_Mount->getDeviceName() == name) 2910 { 2911 m_Mount->disconnect(this); 2912 m_Mount = nullptr; 2913 } 2914 2915 2916 // Cameras 2917 if (m_Camera && m_Camera->getDeviceName() == name) 2918 { 2919 m_Camera->disconnect(this); 2920 m_Camera = nullptr; 2921 } 2922 2923 2924 // Guiders 2925 if (m_Guider && m_Guider->getDeviceName() == name) 2926 { 2927 m_Guider->disconnect(this); 2928 m_Guider = nullptr; 2929 } 2930 2931 // Adaptive Optics 2932 // FIXME AO are not yet utilized property in Guide module 2933 if (m_AO && m_AO->getDeviceName() == name) 2934 { 2935 m_AO->disconnect(this); 2936 m_AO = nullptr; 2937 } 2938 } 2939 2940 void Guide::loop() 2941 { 2942 m_State = GUIDE_LOOPING; 2943 emit newStatus(m_State); 2944 2945 if(guiderType == GUIDE_PHD2) 2946 { 2947 configurePHD2Camera(); 2948 if(phd2Guider->isCurrentCameraNotInEkos()) 2949 appendLogText( 2950 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.")); 2951 else if(guideSubframe->isChecked()) 2952 { 2953 appendLogText( 2954 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")); 2955 guideSubframe->setChecked(false); 2956 } 2957 phd2Guider->loop(); 2958 stopB->setEnabled(true); 2959 } 2960 else if (guiderType == GUIDE_INTERNAL) 2961 capture(); 2962 } 2963 2964 /////////////////////////////////////////////////////////////////////////////////////////// 2965 /// 2966 /////////////////////////////////////////////////////////////////////////////////////////// 2967 QVariantMap Guide::getAllSettings() const 2968 { 2969 QVariantMap settings; 2970 2971 // All Combo Boxes 2972 for (auto &oneWidget : findChildren<QComboBox*>()) 2973 settings.insert(oneWidget->objectName(), oneWidget->currentText()); 2974 2975 // All Double Spin Boxes 2976 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 2977 settings.insert(oneWidget->objectName(), oneWidget->value()); 2978 2979 // All Spin Boxes 2980 for (auto &oneWidget : findChildren<QSpinBox*>()) 2981 settings.insert(oneWidget->objectName(), oneWidget->value()); 2982 2983 // All Checkboxes 2984 for (auto &oneWidget : findChildren<QCheckBox*>()) 2985 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 2986 2987 return settings; 2988 } 2989 2990 /////////////////////////////////////////////////////////////////////////////////////////// 2991 /// 2992 /////////////////////////////////////////////////////////////////////////////////////////// 2993 void Guide::setAllSettings(const QVariantMap &settings) 2994 { 2995 // Disconnect settings that we don't end up calling syncSettings while 2996 // performing the changes. 2997 disconnectSettings(); 2998 2999 for (auto &name : settings.keys()) 3000 { 3001 // Combo 3002 auto comboBox = findChild<QComboBox*>(name); 3003 if (comboBox) 3004 { 3005 syncControl(settings, name, comboBox); 3006 continue; 3007 } 3008 3009 // Double spinbox 3010 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name); 3011 if (doubleSpinBox) 3012 { 3013 syncControl(settings, name, doubleSpinBox); 3014 continue; 3015 } 3016 3017 // spinbox 3018 auto spinBox = findChild<QSpinBox*>(name); 3019 if (spinBox) 3020 { 3021 syncControl(settings, name, spinBox); 3022 continue; 3023 } 3024 3025 // checkbox 3026 auto checkbox = findChild<QCheckBox*>(name); 3027 if (checkbox) 3028 { 3029 syncControl(settings, name, checkbox); 3030 continue; 3031 } 3032 } 3033 3034 // Sync to options 3035 for (auto &key : settings.keys()) 3036 { 3037 auto value = settings[key]; 3038 // Save immediately 3039 Options::self()->setProperty(key.toLatin1(), value); 3040 3041 m_Settings[key] = value; 3042 m_GlobalSettings[key] = value; 3043 } 3044 3045 emit settingsUpdated(getAllSettings()); 3046 3047 // Save to optical train specific settings as well 3048 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 3049 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings); 3050 3051 // Restablish connections 3052 connectSettings(); 3053 } 3054 3055 /////////////////////////////////////////////////////////////////////////////////////////// 3056 /// 3057 /////////////////////////////////////////////////////////////////////////////////////////// 3058 bool Guide::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget) 3059 { 3060 QSpinBox *pSB = nullptr; 3061 QDoubleSpinBox *pDSB = nullptr; 3062 QCheckBox *pCB = nullptr; 3063 QComboBox *pComboBox = nullptr; 3064 bool ok = false; 3065 3066 if ((pSB = qobject_cast<QSpinBox *>(widget))) 3067 { 3068 const int value = settings[key].toInt(&ok); 3069 if (ok) 3070 { 3071 pSB->setValue(value); 3072 return true; 3073 } 3074 } 3075 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget))) 3076 { 3077 const double value = settings[key].toDouble(&ok); 3078 if (ok) 3079 { 3080 pDSB->setValue(value); 3081 return true; 3082 } 3083 } 3084 else if ((pCB = qobject_cast<QCheckBox *>(widget))) 3085 { 3086 const bool value = settings[key].toBool(); 3087 pCB->setChecked(value); 3088 return true; 3089 } 3090 // ONLY FOR STRINGS, not INDEX 3091 else if ((pComboBox = qobject_cast<QComboBox *>(widget))) 3092 { 3093 const QString value = settings[key].toString(); 3094 pComboBox->setCurrentText(value); 3095 return true; 3096 } 3097 3098 return false; 3099 }; 3100 3101 void Guide::setupOpticalTrainManager() 3102 { 3103 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Guide::refreshOpticalTrain); 3104 connect(trainB, &QPushButton::clicked, this, [this]() 3105 { 3106 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText()); 3107 }); 3108 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 3109 { 3110 if (guiderType == GUIDE_PHD2 && m_GuiderInstance->isConnected()) 3111 { 3112 appendLogText(i18n("Cannot change active optical train while PHD2 is connected")); 3113 return; 3114 } 3115 3116 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain, 3117 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index))); 3118 refreshOpticalTrain(); 3119 emit trainChanged(); 3120 }); 3121 } 3122 3123 void Guide::refreshOpticalTrain() 3124 { 3125 opticalTrainCombo->blockSignals(true); 3126 opticalTrainCombo->clear(); 3127 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames()); 3128 trainB->setEnabled(true); 3129 3130 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::GuideOpticalTrain); 3131 3132 if (trainID.isValid()) 3133 { 3134 auto id = trainID.toUInt(); 3135 3136 // If train not found, select the first one available. 3137 if (OpticalTrainManager::Instance()->exists(id) == false) 3138 { 3139 qCWarning(KSTARS_EKOS_GUIDE) << "Optical train doesn't exist for id" << id; 3140 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0)); 3141 } 3142 3143 auto name = OpticalTrainManager::Instance()->name(id); 3144 3145 opticalTrainCombo->setCurrentText(name); 3146 3147 auto scope = OpticalTrainManager::Instance()->getScope(name); 3148 m_FocalLength = scope["focal_length"].toDouble(-1); 3149 m_Aperture = scope["aperture"].toDouble(-1); 3150 m_FocalRatio = scope["focal_ratio"].toDouble(-1); 3151 m_Reducer = OpticalTrainManager::Instance()->getReducer(name); 3152 3153 // DSLR Lens Aperture 3154 if (m_Aperture < 0 && m_FocalRatio > 0) 3155 m_Aperture = m_FocalLength / m_FocalRatio; 3156 3157 auto mount = OpticalTrainManager::Instance()->getMount(name); 3158 setMount(mount); 3159 3160 auto camera = OpticalTrainManager::Instance()->getCamera(name); 3161 if (camera) 3162 { 3163 if (guiderType == GUIDE_INTERNAL) 3164 starCenter = QVector3D(); 3165 3166 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture); 3167 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString())); 3168 } 3169 setCamera(camera); 3170 3171 syncTelescopeInfo(); 3172 3173 auto guider = OpticalTrainManager::Instance()->getGuider(name); 3174 setGuider(guider); 3175 3176 auto ao = OpticalTrainManager::Instance()->getAdaptiveOptics(name); 3177 setAdaptiveOptics(ao); 3178 3179 // Load train settings 3180 OpticalTrainSettings::Instance()->setOpticalTrainID(id); 3181 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Guide); 3182 if (settings.isValid()) 3183 setAllSettings(settings.toJsonObject().toVariantMap()); 3184 else 3185 m_Settings = m_GlobalSettings; 3186 } 3187 3188 opticalTrainCombo->blockSignals(false); 3189 } 3190 3191 void Guide::loadGlobalSettings() 3192 { 3193 QString key; 3194 QVariant value; 3195 3196 QVariantMap settings; 3197 // All Combo Boxes 3198 for (auto &oneWidget : findChildren<QComboBox*>()) 3199 { 3200 if (oneWidget->objectName() == "opticalTrainCombo") 3201 continue; 3202 3203 key = oneWidget->objectName(); 3204 value = Options::self()->property(key.toLatin1()); 3205 if (value.isValid()) 3206 { 3207 oneWidget->setCurrentText(value.toString()); 3208 settings[key] = value; 3209 } 3210 } 3211 3212 // All Double Spin Boxes 3213 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 3214 { 3215 key = oneWidget->objectName(); 3216 value = Options::self()->property(key.toLatin1()); 3217 if (value.isValid()) 3218 { 3219 oneWidget->setValue(value.toDouble()); 3220 settings[key] = value; 3221 } 3222 } 3223 3224 // All Spin Boxes 3225 for (auto &oneWidget : findChildren<QSpinBox*>()) 3226 { 3227 key = oneWidget->objectName(); 3228 value = Options::self()->property(key.toLatin1()); 3229 if (value.isValid()) 3230 { 3231 oneWidget->setValue(value.toInt()); 3232 settings[key] = value; 3233 } 3234 } 3235 3236 // All Checkboxes 3237 for (auto &oneWidget : findChildren<QCheckBox*>()) 3238 { 3239 key = oneWidget->objectName(); 3240 value = Options::self()->property(key.toLatin1()); 3241 if (value.isValid()) 3242 { 3243 oneWidget->setChecked(value.toBool()); 3244 settings[key] = value; 3245 } 3246 } 3247 3248 m_GlobalSettings = m_Settings = settings; 3249 } 3250 3251 void Guide::connectSettings() 3252 { 3253 // All Combo Boxes 3254 for (auto &oneWidget : findChildren<QComboBox*>()) 3255 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings); 3256 3257 // All Double Spin Boxes 3258 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 3259 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); 3260 3261 // All Spin Boxes 3262 for (auto &oneWidget : findChildren<QSpinBox*>()) 3263 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); 3264 3265 // All Checkboxes 3266 for (auto &oneWidget : findChildren<QCheckBox*>()) 3267 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); 3268 3269 // Train combo box should NOT be synced. 3270 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings); 3271 } 3272 3273 void Guide::disconnectSettings() 3274 { 3275 // All Combo Boxes 3276 for (auto &oneWidget : findChildren<QComboBox*>()) 3277 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Guide::syncSettings); 3278 3279 // All Double Spin Boxes 3280 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 3281 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); 3282 3283 // All Spin Boxes 3284 for (auto &oneWidget : findChildren<QSpinBox*>()) 3285 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); 3286 3287 // All Checkboxes 3288 for (auto &oneWidget : findChildren<QCheckBox*>()) 3289 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); 3290 3291 } 3292 3293 void Guide::updateSetting(const QString &key, const QVariant &value) 3294 { 3295 // Save immediately 3296 Options::self()->setProperty(key.toLatin1(), value); 3297 Options::self()->save(); 3298 3299 m_Settings[key] = value; 3300 m_GlobalSettings[key] = value; 3301 3302 emit settingsUpdated(getAllSettings()); 3303 3304 // Save to optical train specific settings as well 3305 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 3306 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Guide, m_Settings); 3307 } 3308 3309 void Guide::syncSettings() 3310 { 3311 QDoubleSpinBox *dsb = nullptr; 3312 QSpinBox *sb = nullptr; 3313 QCheckBox *cb = nullptr; 3314 QComboBox *cbox = nullptr; 3315 3316 QString key; 3317 QVariant value; 3318 3319 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender()))) 3320 { 3321 key = dsb->objectName(); 3322 value = dsb->value(); 3323 3324 } 3325 else if ( (sb = qobject_cast<QSpinBox*>(sender()))) 3326 { 3327 key = sb->objectName(); 3328 value = sb->value(); 3329 } 3330 else if ( (cb = qobject_cast<QCheckBox*>(sender()))) 3331 { 3332 key = cb->objectName(); 3333 value = cb->isChecked(); 3334 } 3335 else if ( (cbox = qobject_cast<QComboBox*>(sender()))) 3336 { 3337 key = cbox->objectName(); 3338 value = cbox->currentText(); 3339 } 3340 3341 updateSetting(key, value); 3342 } 3343 3344 3345 }