diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp index 5b0285b04..6dabeb04d 100644 --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -1,3627 +1,3762 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "guide.h" #include "guideadaptor.h" #include "kstars.h" #include "ksmessagebox.h" #include "ksnotification.h" #include "kstarsdata.h" #include "opscalibration.h" #include "opsguide.h" #include "Options.h" #include "auxiliary/QProgressIndicator.h" #include "ekos/auxiliary/darklibrary.h" #include "externalguide/linguider.h" #include "externalguide/phd2.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsviewer.h" #include "internalguide/internalguider.h" #include #include #include #include "ui_manualdither.h" #define CAPTURE_TIMEOUT_THRESHOLD 30000 namespace Ekos { Guide::Guide() : QWidget() { // #1 Setup UI setupUi(this); // #2 Register DBus qRegisterMetaType("Ekos::GuideState"); qDBusRegisterMetaType(); new GuideAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Guide", this); // #3 Init Plots initPlots(); // #4 Init View initView(); // #5 Load all settings loadSettings(); // #6 Init Connections initConnections(); // Image Filters for (auto &filter : FITSViewer::filterTypes) filterCombo->addItem(filter); // Progress Indicator pi = new QProgressIndicator(this); controlLayout->addWidget(pi, 1, 2, 1, 1); showFITSViewerB->setIcon( QIcon::fromTheme("kstars_fitsviewer")); connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Guide::showFITSViewer); showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideAutoScaleGraphB->setIcon( QIcon::fromTheme("zoom-fit-best")); connect(guideAutoScaleGraphB, &QPushButton::clicked, this, &Ekos::Guide::slotAutoScaleGraphs); guideAutoScaleGraphB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideSaveDataB->setIcon( QIcon::fromTheme("document-save")); connect(guideSaveDataB, &QPushButton::clicked, this, &Ekos::Guide::exportGuideData); guideSaveDataB->setAttribute(Qt::WA_LayoutUsesWidgetRect); guideDataClearB->setIcon( QIcon::fromTheme("application-exit")); connect(guideDataClearB, &QPushButton::clicked, this, &Ekos::Guide::clearGuideGraphs); guideDataClearB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Exposure //Should we set the range for the spin box here? QList exposureValues; 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 << 10 << 15 << 30; exposureIN->setRecommendedValues(exposureValues); connect(exposureIN, &NonLinearDoubleSpinBox::editingFinished, this, &Ekos::Guide::saveDefaultGuideExposure); // Init Internal Guider always internalGuider = new InternalGuider(); KConfigDialog *dialog = new KConfigDialog(this, "guidesettings", Options::self()); opsCalibration = new OpsCalibration(internalGuider); KPageWidgetItem *page = dialog->addPage(opsCalibration, i18n("Calibration")); page->setIcon(QIcon::fromTheme("tool-measure")); opsGuide = new OpsGuide(); connect(opsGuide, &OpsGuide::settingsUpdated, [this]() { onThresholdChanged(Options::guideAlgorithm()); configurePHD2Camera(); }); page = dialog->addPage(opsGuide, i18n("Guide")); page->setIcon(QIcon::fromTheme("kstars_guides")); internalGuider->setGuideView(guideView); // Set current guide type setGuiderType(-1); //This allows the current guideSubframe option to be loaded. if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); } Guide::~Guide() { delete guider; } void Guide::handleHorizontalPlotSizeChange() { driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); + calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); + calibrationPlot->replot(); } void Guide::handleVerticalPlotSizeChange() { driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->replot(); + calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); + calibrationPlot->replot(); } void Guide::guideAfterMeridianFlip() { //This will clear the tracking box selection //The selected guide star is no longer valid due to the flip guideView->setTrackingBoxEnabled(false); starCenter = QVector3D(); if (Options::resetGuideCalibration()) clearCalibration(); guide(); } void Guide::resizeEvent(QResizeEvent *event) { if (event->oldSize().width() != -1) { if (event->oldSize().width() != size().width()) handleHorizontalPlotSizeChange(); else if (event->oldSize().height() != size().height()) handleVerticalPlotSizeChange(); } else { QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange); } } void Guide::buildTarget() { double accuracyRadius = accuracyRadiusSpin->value(); Options::setGuiderAccuracyThreshold(accuracyRadius); if (centralTarget) { concentricRings->data()->clear(); redTarget->data()->clear(); yellowTarget->data()->clear(); centralTarget->data()->clear(); } else { concentricRings = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); redTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); yellowTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); centralTarget = new QCPCurve(driftPlot->xAxis, driftPlot->yAxis); } const int pointCount = 200; QVector circleRings( pointCount * (5)); //Have to multiply by the number of rings, Rings at : 25%, 50%, 75%, 125%, 175% QVector circleCentral(pointCount); QVector circleYellow(pointCount); QVector circleRed(pointCount); int circleRingPt = 0; for (int i = 0; i < pointCount; i++) { double theta = i / static_cast(pointCount) * 2 * M_PI; for (double ring = 1; ring < 8; ring++) { if (ring != 4 && ring != 6) { if (i % (9 - static_cast(ring)) == 0) //This causes fewer points to draw on the inner circles. { circleRings[circleRingPt] = QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta), accuracyRadius * ring * 0.25 * qSin(theta)); circleRingPt++; } } } circleCentral[i] = QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta)); circleYellow[i] = QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta)); circleRed[i] = QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta)); } concentricRings->setLineStyle(QCPCurve::lsNone); concentricRings->setScatterSkip(0); concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); concentricRings->data()->set(circleRings, true); redTarget->data()->set(circleRed, true); yellowTarget->data()->set(circleYellow, true); centralTarget->data()->set(circleCentral, true); concentricRings->setPen(QPen(Qt::white)); redTarget->setPen(QPen(Qt::red)); yellowTarget->setPen(QPen(Qt::yellow)); centralTarget->setPen(QPen(Qt::green)); concentricRings->setBrush(Qt::NoBrush); redTarget->setBrush(QBrush(QColor(255, 0, 0, 50))); yellowTarget->setBrush( QBrush(QColor(0, 255, 0, 50))); //Note this is actually yellow. It is green on top of red with equal opacity. centralTarget->setBrush(QBrush(QColor(0, 255, 0, 50))); if (driftPlot->size().width() > 0) driftPlot->replot(); } void Guide::clearGuideGraphs() { driftGraph->graph(0)->data()->clear(); //RA data driftGraph->graph(1)->data()->clear(); //DEC data driftGraph->graph(2)->data()->clear(); //RA highlighted point driftGraph->graph(3)->data()->clear(); //DEC highlighted point driftGraph->graph(4)->data()->clear(); //RA Pulses driftGraph->graph(5)->data()->clear(); //DEC Pulses driftPlot->graph(0)->data()->clear(); //Guide data driftPlot->graph(1)->data()->clear(); //Guide highlighted point driftGraph->clearItems(); //Clears dither text items from the graph driftGraph->replot(); driftPlot->replot(); //Since the labels got cleared with clearItems above. setupNSEWLabels(); } +void Guide::clearCalibrationGraphs() +{ + calibrationPlot->graph(0)->data()->clear(); //RA out + calibrationPlot->graph(1)->data()->clear(); //RA back + calibrationPlot->graph(2)->data()->clear(); //DEC out + calibrationPlot->graph(3)->data()->clear(); //DEC back + calibrationPlot->replot(); +} + void Guide::setupNSEWLabels() { //Labels for N/S/E/W QColor raLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); QColor deLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); //DriftGraph { QCPItemText *northLabel = new QCPItemText(driftGraph); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North", "N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.6, 0.1); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftGraph); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South", "S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.6, 0.8); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftGraph); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West", "W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8, 0.1); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftGraph); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East", "E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.8, 0.8); eastLabel->setVisible(true); } //DriftPlot { QCPItemText *northLabel = new QCPItemText(driftPlot); northLabel->setColor(deLabelColor); northLabel->setText(i18nc("North", "N")); northLabel->position->setType(QCPItemPosition::ptViewportRatio); northLabel->position->setCoords(0.25, 0.2); northLabel->setVisible(true); QCPItemText *southLabel = new QCPItemText(driftPlot); southLabel->setColor(deLabelColor); southLabel->setText(i18nc("South", "S")); southLabel->position->setType(QCPItemPosition::ptViewportRatio); southLabel->position->setCoords(0.25, 0.7); southLabel->setVisible(true); QCPItemText *westLabel = new QCPItemText(driftPlot); westLabel->setColor(raLabelColor); westLabel->setText(i18nc("West", "W")); westLabel->position->setType(QCPItemPosition::ptViewportRatio); westLabel->position->setCoords(0.8, 0.75); westLabel->setVisible(true); QCPItemText *eastLabel = new QCPItemText(driftPlot); eastLabel->setColor(raLabelColor); eastLabel->setText(i18nc("East", "E")); eastLabel->position->setType(QCPItemPosition::ptViewportRatio); eastLabel->position->setCoords(0.3, 0.75); eastLabel->setVisible(true); } } void Guide::slotAutoScaleGraphs() { double accuracyRadius = accuracyRadiusSpin->value(); double key = guideTimer.elapsed() / 1000.0; driftGraph->xAxis->setRange(key - 60, key); driftGraph->yAxis->setRange(-3, 3); driftGraph->graph(0)->rescaleValueAxis(true); driftGraph->replot(); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->graph(0)->rescaleAxes(true); driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); driftPlot->replot(); + + calibrationPlot->xAxis->setRange(-10, 10); + calibrationPlot->yAxis->setRange(-10, 10); + calibrationPlot->graph(0)->rescaleAxes(true); + + calibrationPlot->yAxis->setScaleRatio(calibrationPlot->xAxis, 1.0); + calibrationPlot->xAxis->setScaleRatio(calibrationPlot->yAxis, 1.0); + + calibrationPlot->replot(); } void Guide::guideHistory() { int sliderValue = guideSlider->value(); latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); driftGraph->graph(2)->data()->clear(); //Clear RA highlighted point driftGraph->graph(3)->data()->clear(); //Clear DEC highlighted point driftPlot->graph(1)->data()->clear(); //Clear Guide highlighted point double t = driftGraph->graph(0)->dataMainKey(sliderValue); //Get time from RA data double ra = driftGraph->graph(0)->dataMainValue(sliderValue); //Get RA from RA data double de = driftGraph->graph(1)->dataMainValue(sliderValue); //Get DEC from DEC data double raPulse = driftGraph->graph(4)->dataMainValue(sliderValue); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(sliderValue); //Get DEC Pulse from DEC pulse data driftGraph->graph(2)->addData(t, ra); //Set RA highlighted point driftGraph->graph(3)->addData(t, de); //Set DEC highlighted point //This will allow the graph to scroll left and right along with the guide slider if (driftGraph->xAxis->range().contains(t) == false) { if(t < driftGraph->xAxis->range().lower) { driftGraph->xAxis->setRange(t, t + driftGraph->xAxis->range().size()); } if(t > driftGraph->xAxis->range().upper) { driftGraph->xAxis->setRange(t - driftGraph->xAxis->range().size(), t); } } driftGraph->replot(); driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point driftPlot->replot(); if(!graphOnLatestPt) { QTime localTime = guideTimer; localTime = localTime.addSecs(t); QPoint localTooltipCoordinates = driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); QPoint globalTooltipCoordinates = driftGraph->mapToGlobal(localTooltipCoordinates); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2))); } else { QToolTip::showText( globalTooltipCoordinates, i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(ra, 'f', 2), QString::number(de, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } } void Guide::setLatestGuidePoint(bool isChecked) { graphOnLatestPt = isChecked; if(isChecked) guideSlider->setValue(guideSlider->maximum()); } void Guide::toggleShowRAPlot(bool isChecked) { Options::setRADisplayedOnGuideGraph(isChecked); driftGraph->graph(0)->setVisible(isChecked); driftGraph->graph(2)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleShowDEPlot(bool isChecked) { Options::setDEDisplayedOnGuideGraph(isChecked); driftGraph->graph(1)->setVisible(isChecked); driftGraph->graph(3)->setVisible(isChecked); driftGraph->replot(); } void Guide::toggleRACorrectionsPlot(bool isChecked) { Options::setRACorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(4)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::toggleDECorrectionsPlot(bool isChecked) { Options::setDECorrDisplayedOnGuideGraph(isChecked); driftGraph->graph(5)->setVisible(isChecked); updateCorrectionsScaleVisibility(); } void Guide::updateCorrectionsScaleVisibility() { bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); driftGraph->yAxis2->setVisible(isVisible); correctionSlider->setVisible(isVisible); driftGraph->replot(); } void Guide::setCorrectionGraphScale() { driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); driftGraph->replot(); } void Guide::exportGuideData() { int numPoints = driftGraph->graph(0)->dataCount(); if (numPoints == 0) return; QUrl exportFile = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export Guide Data"), guideURLPath, "CSV File (*.csv)"); if (exportFile.isEmpty()) // if user presses cancel return; if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false) exportFile.setPath(exportFile.toLocalFile() + ".csv"); QString path = exportFile.toLocalFile(); if (QFile::exists(path)) { int r = KMessageBox::warningContinueCancel(nullptr, i18n("A file named \"%1\" already exists. " "Overwrite it?", exportFile.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } if (!exportFile.isValid()) { QString message = i18n("Invalid URL: %1", exportFile.url()); KSNotification::sorry(message, i18n("Invalid URL")); return; } QFile file; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { QString message = i18n("Unable to write to file %1", path); KSNotification::sorry(message, i18n("Could Not Open File")); return; } QTextStream outstream(&file); outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; for (int i = 0; i < numPoints; i++) { double t = driftGraph->graph(0)->dataMainKey(i); double ra = driftGraph->graph(0)->dataMainValue(i); double de = driftGraph->graph(1)->dataMainValue(i); double raPulse = driftGraph->graph(4)->dataMainValue(i); double dePulse = driftGraph->graph(5)->dataMainValue(i); QTime localTime = guideTimer; localTime = localTime.addSecs(t); outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; } appendLogText(i18n("Guide Data Saved as: %1", path)); file.close(); } QString Guide::setRecommendedExposureValues(QList values) { exposureIN->setRecommendedValues(values); return exposureIN->getRecommendedValuesString(); } void Guide::addCamera(ISD::GDInterface *newCCD) { ISD::CCD *ccd = static_cast(newCCD); if (CCDs.contains(ccd)) return; if(guiderType != GUIDE_INTERNAL) { connect(ccd, &ISD::CCD::newBLOBManager, [ccd, this](INDI::Property * prop) { if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) { ccd->setBLOBEnabled(false); //This will disable PHD2 external guide frames until it is properly connected. currentCCD = ccd; } }); guiderCombo->clear(); guiderCombo->setEnabled(false); if (guiderType == GUIDE_PHD2) guiderCombo->addItem("PHD2"); else guiderCombo->addItem("LinGuider"); } else { guiderCombo->setEnabled(true); guiderCombo->addItem(ccd->getDeviceName()); } CCDs.append(ccd); checkCCD(); configurePHD2Camera(); } void Guide::configurePHD2Camera() { //Maybe something like this can be done for Linguider? //But for now, Linguider doesn't support INDI Cameras if(guiderType != GUIDE_PHD2) return; //This prevents a crash if phd2guider is null if(!phd2Guider) return; //This way it doesn't check if the equipment isn't connected yet. //It will check again when the equipment is connected. if(!phd2Guider->isConnected()) return; //This way it doesn't check if the equipment List has not been received yet. //It will ask for the list. When the list is received it will check again. if(phd2Guider->getCurrentCamera().isEmpty()) { phd2Guider->requestCurrentEquipmentUpdate(); return; } //this checks to see if a CCD in the list matches the name of PHD2's camera ISD::CCD *ccdMatch = nullptr; QString currentPHD2CameraName = "None"; foreach(ISD::CCD *ccd, CCDs) { if(phd2Guider->getCurrentCamera().contains(ccd->getDeviceName())) { ccdMatch = ccd; currentPHD2CameraName = (phd2Guider->getCurrentCamera()); break; } } //If this method gives the same result as last time, no need to update the Camera info again. //That way the user doesn't see a ton of messages printing about the PHD2 external camera. //But lets make sure the blob is set correctly every time. if(lastPHD2CameraName == currentPHD2CameraName) { setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); return; } //This means that a Guide Camera was connected before but it changed. if(currentCCD) setExternalGuiderBLOBEnabled(false); //Updating the currentCCD currentCCD = ccdMatch; //This updates the last camera name for the next time it is checked. lastPHD2CameraName = currentPHD2CameraName; //This sets a boolean that allows you to tell if the PHD2 camera is in Ekos phd2Guider->setCurrentCameraIsNotInEkos(currentCCD == nullptr); if(phd2Guider->isCurrentCameraNotInEkos()) { appendLogText( 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.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(false); //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. disconnect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(true); return; } appendLogText( 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.", phd2Guider->getCurrentCamera())); subFrameCheck->setEnabled(true); connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); subFrameCheck->setChecked(Options::guideSubframeEnabled()); } void Guide::addGuideHead(ISD::GDInterface *newCCD) { if (guiderType != GUIDE_INTERNAL) return; ISD::CCD *ccd = static_cast(newCCD); CCDs.append(ccd); QString guiderName = ccd->getDeviceName() + QString(" Guider"); if (guiderCombo->findText(guiderName) == -1) { guiderCombo->addItem(guiderName); //CCDs.append(static_cast (newCCD)); } //checkCCD(CCDs.count()-1); //guiderCombo->setCurrentIndex(CCDs.count()-1); //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); } void Guide::setTelescope(ISD::GDInterface *newTelescope) { currentTelescope = dynamic_cast(newTelescope); syncTelescopeInfo(); } bool Guide::setCamera(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < guiderCombo->count(); i++) if (device == guiderCombo->itemText(i)) { guiderCombo->setCurrentIndex(i); checkCCD(i); return true; } return false; } QString Guide::camera() { if (currentCCD) return currentCCD->getDeviceName(); return QString(); } void Guide::checkCCD(int ccdNum) { if (guiderType != GUIDE_INTERNAL) return; if (ccdNum == -1) { ccdNum = guiderCombo->currentIndex(); if (ccdNum == -1) return; } if (ccdNum <= CCDs.count()) { currentCCD = CCDs.at(ccdNum); if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip && targetChip->isCapturing()) return; if (guiderType != GUIDE_INTERNAL) { syncCCDInfo(); return; } //connect(currentCCD, SIGNAL(FITSViewerClosed()), this, &Ekos::Guide::viewerClosed()), Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::numberUpdated, this, &Ekos::Guide::processCCDNumber, Qt::UniqueConnection); connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Guide::checkExposureValue, Qt::UniqueConnection); targetChip->setImageView(guideView, FITS_GUIDE); syncCCDInfo(); } } void Guide::syncCCDInfo() { INumberVectorProperty *nvp = nullptr; if (currentCCD == nullptr) return; if (useGuideHead) nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); else nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); if (np) ccdPixelSizeX = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); if (np) ccdPixelSizeY = np->value; } updateGuideParams(); } void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture) { if (primaryFocalLength > 0) focal_length = primaryFocalLength; if (primaryAperture > 0) aperture = primaryAperture; // If we have guide scope info, always prefer that over primary if (guideFocalLength > 0) focal_length = guideFocalLength; if (guideAperture > 0) aperture = guideAperture; updateGuideParams(); } void Guide::syncTelescopeInfo() { if (currentTelescope == nullptr || currentTelescope->isConnected() == false) return; INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); if (nvp) { INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); if (np && np->value > 0) primaryAperture = np->value; np = IUFindNumber(nvp, "GUIDER_APERTURE"); if (np && np->value > 0) guideAperture = np->value; aperture = primaryAperture; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) aperture = guideAperture; np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); if (np && np->value > 0) primaryFL = np->value; np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); if (np && np->value > 0) guideFL = np->value; focal_length = primaryFL; //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) focal_length = guideFL; } updateGuideParams(); } void Guide::updateGuideParams() { if (currentCCD == nullptr) return; if (currentCCD->hasGuideHead() == false) useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) { appendLogText(i18n("Connection to the guide CCD is lost.")); return; } if (targetChip->getFrameType() != FRAME_LIGHT) return; if(guiderType == GUIDE_INTERNAL) binningCombo->setEnabled(targetChip->canBin()); int subBinX = 1, subBinY = 1; if (targetChip->canBin()) { int maxBinX, maxBinY; targetChip->getBinning(&subBinX, &subBinY); targetChip->getMaxBin(&maxBinX, &maxBinY); binningCombo->blockSignals(true); binningCombo->clear(); for (int i = 1; i <= maxBinX; i++) binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); binningCombo->setCurrentIndex(subBinX - 1); binningCombo->blockSignals(false); } if (frameSettings.contains(targetChip) == false) { int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { if (w > 0 && h > 0) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); QVariantMap settings; settings["x"] = Options::guideSubframeEnabled() ? x : minX; settings["y"] = Options::guideSubframeEnabled() ? y : minY; settings["w"] = Options::guideSubframeEnabled() ? w : maxW; settings["h"] = Options::guideSubframeEnabled() ? h : maxH; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; } } } if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) { FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_PRIMARY, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(primaryFL / primaryAperture, 'f', 1), QString::number(primaryFL, 'f', 2), QString::number(primaryAperture, 'f', 2)), Qt::ToolTipRole); FOVScopeCombo->setItemData( ISD::CCD::TELESCOPE_GUIDE, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(guideFL / guideAperture, 'f', 1), QString::number(guideFL, 'f', 2), QString::number(guideAperture, 'f', 2)), Qt::ToolTipRole); guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length); emit guideChipUpdated(targetChip); int x, y, w, h; if (targetChip->getFrame(&x, &y, &w, &h)) { guider->setFrameParams(x, y, w, h, subBinX, subBinY); } l_Focal->setText(QString::number(focal_length, 'f', 1)); l_Aperture->setText(QString::number(aperture, 'f', 1)); if (aperture == 0) { l_FbyD->setText("0"); // Pixel scale in arcsec/pixel pixScaleX = 0; pixScaleY = 0; } else { l_FbyD->setText(QString::number(focal_length / aperture, 'f', 1)); // Pixel scale in arcsec/pixel pixScaleX = 206264.8062470963552 * ccdPixelSizeX / 1000.0 / focal_length; pixScaleY = 206264.8062470963552 * ccdPixelSizeY / 1000.0 / focal_length; } // FOV in arcmin double fov_w = (w * pixScaleX) / 60.0; double fov_h = (h * pixScaleY) / 60.0; l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); } } void Guide::addST4(ISD::ST4 *newST4) { if (guiderType != GUIDE_INTERNAL) return; for (auto &guidePort : ST4List) { if (guidePort->getDeviceName() == newST4->getDeviceName()) return; } ST4List.append(newST4); ST4Combo->addItem(newST4->getDeviceName()); setST4(0); } bool Guide::setST4(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; for (int i = 0; i < ST4List.count(); i++) if (ST4List.at(i)->getDeviceName() == device) { ST4Combo->setCurrentIndex(i); setST4(i); return true; } return false; } QString Guide::st4() { if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) return QString(); return ST4Combo->currentText(); } void Guide::setST4(int index) { if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) return; ST4Driver = ST4List.at(index); GuideDriver = ST4Driver; } void Guide::setAO(ISD::ST4 *newAO) { AODriver = newAO; //guider->setAO(true); } bool Guide::capture() { buildOperationStack(GUIDE_CAPTURE); return executeOperationStack(); } bool Guide::captureOneFrame() { captureTimeout.stop(); if (currentCCD == nullptr) return false; if (currentCCD->isConnected() == false) { appendLogText(i18n("Error: lost connection to CCD.")); return false; } // If CCD Telescope Type does not match desired scope type, change it if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); double seqExpose = exposureIN->value(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setCaptureMode(FITS_GUIDE); targetChip->setFrameType(FRAME_LIGHT); if (darkFrameCheck->isChecked()) targetChip->setCaptureFilter(FITS_NONE); else targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); guideView->setBaseSize(guideWidget->size()); setBusy(true); if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt()); } currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; double finalExposure = seqExpose; // Increase exposure for calibration frame if we need auto-select a star // To increase chances we detect one. if (operationStack.contains(GUIDE_STAR_SELECT) && Options::guideAutoStarEnabled()) finalExposure *= 3; // Timeout is exposure duration + timeout threshold in seconds captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); targetChip->capture(finalExposure); return true; } bool Guide::abort() { if (currentCCD && guiderType == GUIDE_INTERNAL) { captureTimeout.stop(); pulseTimer.stop(); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip->isCapturing()) targetChip->abortExposure(); } manualDitherB->setEnabled(false); setBusy(false); switch (state) { case GUIDE_IDLE: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: break; case GUIDE_CALIBRATING: case GUIDE_DITHERING: case GUIDE_STAR_SELECT: case GUIDE_CAPTURE: case GUIDE_GUIDING: case GUIDE_LOOPING: guider->abort(); break; default: break; } return true; } void Guide::setBusy(bool enable) { if (enable && pi->isAnimated()) return; else if (enable == false && pi->isAnimated() == false) return; if (enable) { clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); stopB->setEnabled(true); pi->startAnimation(); //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); } else { if(guiderType != GUIDE_LINGUIDER) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); if(currentCCD) subFrameCheck->setEnabled(true); } if (guiderType == GUIDE_INTERNAL) darkFrameCheck->setEnabled(true); if (calibrationComplete) clearCalibrationB->setEnabled(true); guideB->setEnabled(true); stopB->setEnabled(false); pi->stopAnimation(); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); } } void Guide::processCaptureTimeout() { auto restartExposure = [&]() { appendLogText(i18n("Exposure timeout. Restarting exposure...")); currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->abortExposure(); targetChip->capture(exposureIN->value()); captureTimeout.start(exposureIN->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); }; m_CaptureTimeoutCounter++; if (m_DeviceRestartCounter >= 3) { m_CaptureTimeoutCounter = 0; m_DeviceRestartCounter = 0; if (state == GUIDE_GUIDING) appendLogText(i18n("Exposure timeout. Aborting Autoguide.")); else if (state == GUIDE_DITHERING) appendLogText(i18n("Exposure timeout. Aborting Dithering.")); else if (state == GUIDE_CALIBRATING) appendLogText(i18n("Exposure timeout. Aborting Calibration.")); abort(); return; } if (m_CaptureTimeoutCounter > 1) { QString camera = currentCCD->getDeviceName(); QString via = ST4Driver ? ST4Driver->getDeviceName() : ""; emit driverTimedout(camera); QTimer::singleShot(5000, [ &, camera, via]() { m_DeviceRestartCounter++; reconnectDriver(camera, via); }); return; } else restartExposure(); } void Guide::reconnectDriver(const QString &camera, const QString &via) { for (auto &oneCamera : CCDs) { if (oneCamera->getDeviceName() == camera) { // Set camera again to the one we restarted guiderCombo->setCurrentIndex(guiderCombo->findText(camera)); ST4Combo->setCurrentIndex(ST4Combo->findText(via)); checkCCD(); // restart capture m_CaptureTimeoutCounter = 0; captureOneFrame(); return; } } QTimer::singleShot(5000, this, [ &, camera, via]() { reconnectDriver(camera, via); }); } void Guide::newFITS(IBLOB *bp) { INDI_UNUSED(bp); captureTimeout.stop(); m_CaptureTimeoutCounter = 0; disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.x() == 0 && starCenter.y() == 0) { int x = 0, y = 0, w = 0, h = 0; if (frameSettings.contains(targetChip)) { QVariantMap settings = frameSettings[targetChip]; x = settings["x"].toInt(); y = settings["y"].toInt(); w = settings["w"].toInt(); h = settings["h"].toInt(); } else targetChip->getFrame(&x, &y, &w, &h); starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinY)); starCenter.setZ(subBinX); } syncTrackingBoxPosition(); setCaptureComplete(); } void Guide::setCaptureComplete() { DarkLibrary::Instance()->disconnect(this); if (operationStack.isEmpty() == false) { executeOperationStack(); return; } switch (state) { case GUIDE_IDLE: case GUIDE_ABORTED: case GUIDE_CONNECTED: case GUIDE_DISCONNECTED: case GUIDE_CALIBRATION_SUCESS: case GUIDE_CALIBRATION_ERROR: case GUIDE_DITHERING_ERROR: setBusy(false); break; case GUIDE_CAPTURE: state = GUIDE_IDLE; emit newStatus(state); setBusy(false); break; case GUIDE_LOOPING: capture(); break; case GUIDE_CALIBRATING: guider->calibrate(); break; case GUIDE_GUIDING: guider->guide(); break; case GUIDE_DITHERING: guider->dither(Options::ditherPixels()); break; // Feature only of internal guider case GUIDE_MANUAL_DITHERING: dynamic_cast(guider)->processManualDithering(); break; case GUIDE_REACQUIRE: guider->reacquire(); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherNoGuiding()) return; capture(); break; default: break; } emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } void Guide::appendLogText(const QString &text) { m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS_GUIDE) << text; emit newLog(text); } void Guide::clearLog() { m_LogText.clear(); emit newLog(QString()); } void Guide::setDECSwap(bool enable) { if (ST4Driver == nullptr || guider == nullptr) return; if (guiderType == GUIDE_INTERNAL) { dynamic_cast(guider)->setDECSwap(enable); ST4Driver->setDECSwap(enable); } } bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); } bool Guide::sendPulse(GuideDirection dir, int msecs) { if (GuideDriver == nullptr || dir == NO_DIR) return false; if (state == GUIDE_CALIBRATING) pulseTimer.start(msecs + 100); return GuideDriver->doPulse(dir, msecs); } QStringList Guide::getST4Devices() { QStringList devices; foreach (ISD::ST4 *driver, ST4List) devices << driver->getDeviceName(); return devices; } bool Guide::calibrate() { // Set status to idle and let the operations change it as they get executed state = GUIDE_IDLE; emit newStatus(state); if (guiderType == GUIDE_INTERNAL) { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (frameSettings.contains(targetChip)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; frameSettings[targetChip] = settings; subFramed = false; } } saveSettings(); buildOperationStack(GUIDE_CALIBRATING); executeOperationStack(); qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); return true; } bool Guide::guide() { auto executeGuide = [this]() { if(guiderType != GUIDE_PHD2) { if (calibrationComplete == false) { calibrate(); return; } } saveSettings(); guider->guide(); //If PHD2 gets a Guide command and it is looping, it will accept a lock position //but if it was not looping it will ignore the lock position and do an auto star automatically //This is not the default behavior in Ekos if auto star is not selected. //This gets around that by noting the position of the tracking box, and enforcing it after the state switches to guide. if(!Options::guideAutoStarEnabled()) { if(guiderType == GUIDE_PHD2 && guideView->isTrackingBoxEnabled()) { double x = starCenter.x(); double y = starCenter.y(); if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) { guideConnect = connect(this, &Guide::newStatus, this, [this, x, y](Ekos::GuideState newState) { if(newState == GUIDE_GUIDING) { phd2Guider->setLockPosition(x, y); disconnect(guideConnect); } }); } } } } }; if (Options::defaultCaptureCCD() == guiderCombo->currentText()) { connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeGuide]() { //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); KSMessageBox::Instance()->disconnect(this); executeGuide(); }); KSMessageBox::Instance()->questionYesNo( i18n("The guide camera is identical to the primary imaging camera. Are you sure you want to continue?")); return false; } if (m_MountStatus == ISD::Telescope::MOUNT_PARKED) { KSMessageBox::Instance()->sorry(i18n("The mount is parked. Unpark to start guiding.")); return false; } executeGuide(); return true; } bool Guide::dither() { if (Options::ditherNoGuiding() && state == GUIDE_IDLE) { ditherDirectly(); return true; } if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) return true; //This adds a dither text item to the graph where dithering occurred. double time = guideTimer.elapsed() / 1000.0; QCPItemText *ditherLabel = new QCPItemText(driftGraph); ditherLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft); ditherLabel->position->setType(QCPItemPosition::ptPlotCoords); ditherLabel->position->setCoords(time, 1.5); ditherLabel->setColor(Qt::white); ditherLabel->setBrush(Qt::NoBrush); ditherLabel->setPen(Qt::NoPen); ditherLabel->setText("Dither"); ditherLabel->setFont(QFont(font().family(), 10)); if (guiderType == GUIDE_INTERNAL) { if (state != GUIDE_GUIDING) capture(); setStatus(GUIDE_DITHERING); return true; } else return guider->dither(Options::ditherPixels()); } bool Guide::suspend() { if (state == GUIDE_SUSPENDED) return true; else if (state >= GUIDE_CAPTURE) return guider->suspend(); else return false; } bool Guide::resume() { if (state == GUIDE_GUIDING) return true; else if (state == GUIDE_SUSPENDED) return guider->resume(); else return false; } void Guide::setCaptureStatus(CaptureState newState) { switch (newState) { case CAPTURE_DITHERING: dither(); break; default: break; } } void Guide::setPierSide(ISD::Telescope::PierSide newSide) { Q_UNUSED(newSide); // If pier side changes in internal guider // and calibration was already done // then let's swap if (guiderType == GUIDE_INTERNAL && state != GUIDE_GUIDING && state != GUIDE_CALIBRATING && calibrationComplete) { clearCalibration(); appendLogText(i18n("Pier side change detected. Clearing calibration.")); } } void Guide::setMountStatus(ISD::Telescope::Status newState) { m_MountStatus = newState; if (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING) { // reset the calibration if "Always reset calibration" is selected and the mount moves if (Options::resetGuideCalibration()) { appendLogText(i18n("Mount is moving. Resetting calibration...")); clearCalibration(); } // If we're guiding, and the mount either slews or parks, then we abort. if (state == GUIDE_GUIDING || state == GUIDE_DITHERING) { if (newState == ISD::Telescope::MOUNT_PARKING) appendLogText(i18n("Mount is parking. Aborting guide...")); else appendLogText(i18n("Mount is slewing. Aborting guide...")); abort(); } } if (guiderType != GUIDE_INTERNAL) return; switch (newState) { case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_MOVING: captureB->setEnabled(false); loopB->setEnabled(false); clearCalibrationB->setEnabled(false); break; default: if (pi->isAnimated() == false) { captureB->setEnabled(true); loopB->setEnabled(true); clearCalibrationB->setEnabled(true); } } } void Guide::setExposure(double value) { exposureIN->setValue(value); } void Guide::setImageFilter(const QString &value) { for (int i = 0; i < filterCombo->count(); i++) if (filterCombo->itemText(i) == value) { filterCombo->setCurrentIndex(i); break; } } void Guide::setCalibrationTwoAxis(bool enable) { Options::setTwoAxisEnabled(enable); } void Guide::setCalibrationAutoStar(bool enable) { autoStarCheck->setChecked(enable); } void Guide::setCalibrationAutoSquareSize(bool enable) { Options::setGuideAutoSquareSizeEnabled(enable); } void Guide::setCalibrationPulseDuration(int pulseDuration) { Options::setCalibrationPulseDuration(pulseDuration); } void Guide::setGuideBoxSizeIndex(int index) { Options::setGuideSquareSizeIndex(index); } void Guide::setGuideAlgorithmIndex(int index) { Options::setGuideAlgorithm(index); } void Guide::setSubFrameEnabled(bool enable) { Options::setGuideSubframeEnabled(enable); if (subFrameCheck->isChecked() != enable) subFrameCheck->setChecked(enable); if(guiderType == GUIDE_PHD2) setExternalGuiderBLOBEnabled(!enable); } void Guide::setDitherSettings(bool enable, double value) { Options::setDitherEnabled(enable); Options::setDitherPixels(value); } void Guide::clearCalibration() { calibrationComplete = false; guider->clearCalibration(); appendLogText(i18n("Calibration is cleared.")); } void Guide::setStatus(Ekos::GuideState newState) { if (newState == state) { // pass through the aborted state if (newState == GUIDE_ABORTED) emit newStatus(state); return; } GuideState previousState = state; state = newState; emit newStatus(state); switch (state) { case GUIDE_CONNECTED: appendLogText(i18n("External guider connected.")); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(true); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); if(guiderType == GUIDE_PHD2) { captureB->setEnabled(true); loopB->setEnabled(true); autoStarCheck->setEnabled(true); configurePHD2Camera(); setExternalGuiderBLOBEnabled(!Options::guideSubframeEnabled()); boxSizeCombo->setEnabled(true); } break; case GUIDE_DISCONNECTED: appendLogText(i18n("External guider disconnected.")); setBusy(false); //This needs to come before caputureB since it will set it to enabled again. externalConnectB->setEnabled(true); externalDisconnectB->setEnabled(false); clearCalibrationB->setEnabled(false); guideB->setEnabled(false); captureB->setEnabled(false); loopB->setEnabled(false); autoStarCheck->setEnabled(false); boxSizeCombo->setEnabled(false); //setExternalGuiderBLOBEnabled(true); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif break; case GUIDE_CALIBRATION_SUCESS: appendLogText(i18n("Calibration completed.")); calibrationComplete = true; /*if (autoCalibrateGuide) { autoCalibrateGuide = false; guide(); } else setBusy(false);*/ if(guiderType != 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. guide(); break; case GUIDE_IDLE: case GUIDE_CALIBRATION_ERROR: setBusy(false); manualDitherB->setEnabled(false); break; case GUIDE_CALIBRATING: + clearCalibrationGraphs(); appendLogText(i18n("Calibration started.")); setBusy(true); break; case GUIDE_GUIDING: if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) appendLogText(i18n("Guiding resumed.")); else { appendLogText(i18n("Autoguiding started.")); setBusy(true); clearGuideGraphs(); guideTimer = QTime::currentTime(); refreshColorScheme(); } manualDitherB->setEnabled(true); break; case GUIDE_ABORTED: appendLogText(i18n("Autoguiding aborted.")); setBusy(false); break; case GUIDE_SUSPENDED: appendLogText(i18n("Guiding suspended.")); break; case GUIDE_REACQUIRE: capture(); break; case GUIDE_MANUAL_DITHERING: appendLogText(i18n("Manual dithering in progress.")); break; case GUIDE_DITHERING: appendLogText(i18n("Dithering in progress.")); break; case GUIDE_DITHERING_SETTLE: if (Options::ditherSettle() > 0) appendLogText(i18np("Post-dither settling for %1 second...", "Post-dither settling for %1 seconds...", Options::ditherSettle())); capture(); break; case GUIDE_DITHERING_ERROR: appendLogText(i18n("Dithering failed.")); // LinGuider guide continue after dithering failure if (guiderType != GUIDE_LINGUIDER) { //state = GUIDE_IDLE; state = GUIDE_ABORTED; setBusy(false); } break; case GUIDE_DITHERING_SUCCESS: appendLogText(i18n("Dithering completed successfully.")); // Go back to guiding state immediately if using regular guider if (Options::ditherNoGuiding() == false) { setStatus(GUIDE_GUIDING); // Only capture again if we are using internal guider if (guiderType == GUIDE_INTERNAL) capture(); } break; default: break; } } void Guide::updateCCDBin(int index) { if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) return; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); targetChip->setBinning(index + 1, index + 1); QVariantMap settings = frameSettings[targetChip]; settings["binx"] = index + 1; settings["biny"] = index + 1; frameSettings[targetChip] = settings; guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), settings["binx"].toInt(), settings["biny"].toInt()); } void Guide::processCCDNumber(INumberVectorProperty *nvp) { if (currentCCD == nullptr || (nvp->device != currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) return; if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) { binningCombo->disconnect(); binningCombo->setCurrentIndex(nvp->np[0].value - 1); connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); } } void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) { if (guiderType != GUIDE_INTERNAL) return; INDI_UNUSED(exposure); if (expState == IPS_ALERT && ((state == GUIDE_GUIDING) || (state == GUIDE_DITHERING) || (state == GUIDE_CALIBRATING))) { appendLogText(i18n("Exposure failed. Restarting exposure...")); currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); targetChip->capture(exposureIN->value()); } } void Guide::setDarkFrameEnabled(bool enable) { Options::setGuideDarkFrameEnabled(enable); if (darkFrameCheck->isChecked() != enable) darkFrameCheck->setChecked(enable); } void Guide::saveDefaultGuideExposure() { Options::setGuideExposure(exposureIN->value()); if(guiderType == GUIDE_PHD2) phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); } void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) { starCenter.setX(newCenter.x()); starCenter.setY(newCenter.y()); if (newCenter.z() > 0) starCenter.setZ(newCenter.z()); if (updateNow) syncTrackingBoxPosition(); } void Guide::syncTrackingBoxPosition() { if(!currentCCD || guiderType == GUIDE_LINGUIDER) return; if(guiderType == GUIDE_PHD2) { //This way it won't set the tracking box on the Guide Star Image. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() < 50) { guideView->setTrackingBoxEnabled(false); return; } } } ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); Q_ASSERT(targetChip); int subBinX = 1, subBinY = 1; targetChip->getBinning(&subBinX, &subBinY); if (starCenter.isNull() == false) { double boxSize = boxSizeCombo->currentText().toInt(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); // If box size is larger than image size, set it to lower index if (boxSize / subBinX >= w || boxSize / subBinY >= h) { int newIndex = boxSizeCombo->currentIndex() - 1; if (newIndex >= 0) boxSizeCombo->setCurrentIndex(newIndex); return; } // If binning changed, update coords accordingly if (subBinX != starCenter.z()) { if (starCenter.z() > 0) { starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); } starCenter.setZ(subBinX); } QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), boxSize / subBinX, boxSize / subBinY); guideView->setTrackingBoxEnabled(true); guideView->setTrackingBox(starRect); } } bool Guide::setGuiderType(int type) { // Use default guider option if (type == -1) type = Options::guiderType(); else if (type == guiderType) return true; if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) { appendLogText(i18n("Cannot change guider type while active.")); return false; } if (guider != nullptr) { // Disconnect from host if (guider->isConnected()) guider->Disconnect(); // Disconnect signals guider->disconnect(); } guiderType = static_cast(type); switch (type) { case GUIDE_INTERNAL: { connect(internalGuider, SIGNAL(newPulse(GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int))); connect(internalGuider, SIGNAL(newPulse(GuideDirection, int, GuideDirection, int)), this, SLOT(sendPulse(GuideDirection, int, GuideDirection, int))); connect(internalGuider, SIGNAL(DESwapChanged(bool)), swapCheck, SLOT(setChecked(bool))); connect(internalGuider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); guider = internalGuider; internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); clearCalibrationB->setEnabled(true); guideB->setEnabled(true); captureB->setEnabled(true); loopB->setEnabled(true); darkFrameCheck->setEnabled(true); subFrameCheck->setEnabled(true); autoStarCheck->setEnabled(true); guiderCombo->setEnabled(true); ST4Combo->setEnabled(true); exposureIN->setEnabled(true); binningCombo->setEnabled(true); boxSizeCombo->setEnabled(true); filterCombo->setEnabled(true); externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); controlGroup->setEnabled(true); infoGroup->setEnabled(true); label_6->setEnabled(true); FOVScopeCombo->setEnabled(true); l_3->setEnabled(true); spinBox_GuideRate->setEnabled(true); l_RecommendedGain->setEnabled(true); l_5->setEnabled(true); l_6->setEnabled(true); l_7->setEnabled(true); l_8->setEnabled(true); l_Aperture->setEnabled(true); l_FOV->setEnabled(true); l_FbyD->setEnabled(true); l_Focal->setEnabled(true); driftGraphicsGroup->setEnabled(true); guiderCombo->setToolTip(i18n("Select guide camera.")); updateGuideParams(); } break; case GUIDE_PHD2: if (phd2Guider.isNull()) phd2Guider = new PHD2(); guider = phd2Guider; phd2Guider->setGuideView(guideView); connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) externalConnectB->setEnabled(false); checkBox_DirRA->setEnabled(false); eastControlCheck->setEnabled(false); westControlCheck->setEnabled(false); swapCheck->setEnabled(false); controlGroup->setEnabled(false); infoGroup->setEnabled(true); label_6->setEnabled(false); FOVScopeCombo->setEnabled(false); l_3->setEnabled(false); spinBox_GuideRate->setEnabled(false); l_RecommendedGain->setEnabled(false); l_5->setEnabled(false); l_6->setEnabled(false); l_7->setEnabled(false); l_8->setEnabled(false); l_Aperture->setEnabled(false); l_FOV->setEnabled(false); l_FbyD->setEnabled(false); l_Focal->setEnabled(false); driftGraphicsGroup->setEnabled(true); ST4Combo->setEnabled(false); exposureIN->setEnabled(true); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); if (Options::resetGuideCalibration()) appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); updateGuideParams(); break; case GUIDE_LINGUIDER: if (linGuider.isNull()) linGuider = new LinGuider(); guider = linGuider; clearCalibrationB->setEnabled(true); captureB->setEnabled(false); loopB->setEnabled(false); darkFrameCheck->setEnabled(false); subFrameCheck->setEnabled(false); autoStarCheck->setEnabled(false); guideB->setEnabled(true); externalConnectB->setEnabled(true); controlGroup->setEnabled(false); infoGroup->setEnabled(false); driftGraphicsGroup->setEnabled(false); ST4Combo->setEnabled(false); exposureIN->setEnabled(false); binningCombo->setEnabled(false); boxSizeCombo->setEnabled(false); filterCombo->setEnabled(false); guiderCombo->setEnabled(false); updateGuideParams(); break; } if (guider != nullptr) { connect(guider, &Ekos::GuideInterface::frameCaptureRequested, this, &Ekos::Guide::capture); connect(guider, &Ekos::GuideInterface::newLog, this, &Ekos::Guide::appendLogText); connect(guider, &Ekos::GuideInterface::newStatus, this, &Ekos::Guide::setStatus); connect(guider, &Ekos::GuideInterface::newStarPosition, this, &Ekos::Guide::setStarPosition); connect(guider, &Ekos::GuideInterface::newAxisDelta, this, &Ekos::Guide::setAxisDelta); connect(guider, &Ekos::GuideInterface::newAxisPulse, this, &Ekos::Guide::setAxisPulse); connect(guider, &Ekos::GuideInterface::newAxisSigma, this, &Ekos::Guide::setAxisSigma); + connect(guider, &Ekos::GuideInterface::calibrationUpdate, this, &Ekos::Guide::calibrationUpdate); connect(guider, &Ekos::GuideInterface::guideEquipmentUpdated, this, &Ekos::Guide::configurePHD2Camera); } externalConnectB->setEnabled(false); externalDisconnectB->setEnabled(false); if (guider != nullptr && guiderType != GUIDE_INTERNAL) { externalConnectB->setEnabled(!guider->isConnected()); externalDisconnectB->setEnabled(guider->isConnected()); } if (guider != nullptr) guider->Connect(); return true; } void Guide::updateTrackingBoxSize(int currentIndex) { if (currentIndex >= 0) { Options::setGuideSquareSizeIndex(currentIndex); if (guiderType == GUIDE_INTERNAL) dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); syncTrackingBoxPosition(); } } /* void Guide::onXscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); driftGraphics->update(); } void Guide::onYscaleChanged( int i ) { int rx, ry; driftGraphics->getVisibleRanges( &rx, &ry ); driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); driftGraphics->update(); } */ void Guide::onThresholdChanged(int index) { switch (guiderType) { case GUIDE_INTERNAL: dynamic_cast(guider)->setSquareAlgorithm(index); break; default: break; } } void Guide::onInfoRateChanged(double val) { Options::setGuidingRate(val); double gain = 0; if (val > 0.01) gain = 1000.0 / (val * 15.0); l_RecommendedGain->setText(i18n("P: %1", QString().setNum(gain, 'f', 2))); } void Guide::onEnableDirRA(bool enable) { Options::setRAGuideEnabled(enable); } void Guide::onEnableDirDEC(bool enable) { Options::setDECGuideEnabled(enable); updatePHD2Directions(); } void Guide::syncSettings() { QSpinBox *pSB = nullptr; QDoubleSpinBox *pDSB = nullptr; QCheckBox *pCB = nullptr; QObject *obj = sender(); if ((pSB = qobject_cast(obj))) { if (pSB == spinBox_MaxPulseRA) Options::setRAMaximumPulse(pSB->value()); else if (pSB == spinBox_MaxPulseDEC) Options::setDECMaximumPulse(pSB->value()); else if (pSB == spinBox_MinPulseRA) Options::setRAMinimumPulse(pSB->value()); else if (pSB == spinBox_MinPulseDEC) Options::setDECMinimumPulse(pSB->value()); } else if ((pDSB = qobject_cast(obj))) { if (pDSB == spinBox_PropGainRA) Options::setRAProportionalGain(pDSB->value()); else if (pDSB == spinBox_PropGainDEC) Options::setDECProportionalGain(pDSB->value()); else if (pDSB == spinBox_IntGainRA) Options::setRAIntegralGain(pDSB->value()); else if (pDSB == spinBox_IntGainDEC) Options::setDECIntegralGain(pDSB->value()); else if (pDSB == spinBox_DerGainRA) Options::setRADerivativeGain(pDSB->value()); else if (pDSB == spinBox_DerGainDEC) Options::setDECDerivativeGain(pDSB->value()); } else if ((pCB = qobject_cast(obj))) { if (pCB == autoStarCheck) Options::setGuideAutoStarEnabled(pCB->isChecked()); } } void Guide::onControlDirectionChanged(bool enable) { QObject *obj = sender(); if (northControlCheck == dynamic_cast(obj)) { Options::setNorthDECGuideEnabled(enable); updatePHD2Directions(); } else if (southControlCheck == dynamic_cast(obj)) { Options::setSouthDECGuideEnabled(enable); updatePHD2Directions(); } else if (westControlCheck == dynamic_cast(obj)) { Options::setWestRAGuideEnabled(enable); } else if (eastControlCheck == dynamic_cast(obj)) { Options::setEastRAGuideEnabled(enable); } } void Guide::updatePHD2Directions() { if(guiderType == GUIDE_PHD2) phd2Guider -> requestSetDEGuideMode(checkBox_DirDEC->isChecked(), northControlCheck->isChecked(), southControlCheck->isChecked()); } void Guide::updateDirectionsFromPHD2(QString mode) { //disable connections disconnect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); disconnect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); disconnect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); if(mode == "Auto") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } else if(mode == "North") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(true); southControlCheck->setChecked(false); } else if(mode == "South") { checkBox_DirDEC->setChecked(true); northControlCheck->setChecked(false); southControlCheck->setChecked(true); } else //Off { checkBox_DirDEC->setChecked(false); northControlCheck->setChecked(true); southControlCheck->setChecked(true); } //Re-enable connections connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); } void Guide::loadSettings() { // Exposure exposureIN->setValue(Options::guideExposure()); // Box Size boxSizeCombo->setCurrentIndex(Options::guideSquareSizeIndex()); // Dark frame? darkFrameCheck->setChecked(Options::guideDarkFrameEnabled()); // Subframed? subFrameCheck->setChecked(Options::guideSubframeEnabled()); // Guiding Rate spinBox_GuideRate->setValue(Options::guidingRate()); // RA/DEC enabled? checkBox_DirRA->setChecked(Options::rAGuideEnabled()); checkBox_DirDEC->setChecked(Options::dECGuideEnabled()); // N/S enabled? northControlCheck->setChecked(Options::northDECGuideEnabled()); southControlCheck->setChecked(Options::southDECGuideEnabled()); // W/E enabled? westControlCheck->setChecked(Options::westRAGuideEnabled()); eastControlCheck->setChecked(Options::eastRAGuideEnabled()); // PID Control - Proportional Gain spinBox_PropGainRA->setValue(Options::rAProportionalGain()); spinBox_PropGainDEC->setValue(Options::dECProportionalGain()); // PID Control - Integral Gain spinBox_IntGainRA->setValue(Options::rAIntegralGain()); spinBox_IntGainDEC->setValue(Options::dECIntegralGain()); // PID Control - Derivative Gain spinBox_DerGainRA->setValue(Options::rADerivativeGain()); spinBox_DerGainDEC->setValue(Options::dECDerivativeGain()); // Max Pulse Duration (ms) spinBox_MaxPulseRA->setValue(Options::rAMaximumPulse()); spinBox_MaxPulseDEC->setValue(Options::dECMaximumPulse()); // Min Pulse Duration (ms) spinBox_MinPulseRA->setValue(Options::rAMinimumPulse()); spinBox_MinPulseDEC->setValue(Options::dECMinimumPulse()); // Autostar autoStarCheck->setChecked(Options::guideAutoStarEnabled()); } void Guide::saveSettings() { // Exposure Options::setGuideExposure(exposureIN->value()); // Box Size Options::setGuideSquareSizeIndex(boxSizeCombo->currentIndex()); // Dark frame? Options::setGuideDarkFrameEnabled(darkFrameCheck->isChecked()); // Subframed? Options::setGuideSubframeEnabled(subFrameCheck->isChecked()); // Guiding Rate? Options::setGuidingRate(spinBox_GuideRate->value()); // RA/DEC enabled? Options::setRAGuideEnabled(checkBox_DirRA->isChecked()); Options::setDECGuideEnabled(checkBox_DirDEC->isChecked()); // N/S enabled? Options::setNorthDECGuideEnabled(northControlCheck->isChecked()); Options::setSouthDECGuideEnabled(southControlCheck->isChecked()); // W/E enabled? Options::setWestRAGuideEnabled(westControlCheck->isChecked()); Options::setEastRAGuideEnabled(eastControlCheck->isChecked()); // PID Control - Proportional Gain Options::setRAProportionalGain(spinBox_PropGainRA->value()); Options::setDECProportionalGain(spinBox_PropGainDEC->value()); // PID Control - Integral Gain Options::setRAIntegralGain(spinBox_IntGainRA->value()); Options::setDECIntegralGain(spinBox_IntGainDEC->value()); // PID Control - Derivative Gain Options::setRADerivativeGain(spinBox_DerGainRA->value()); Options::setDECDerivativeGain(spinBox_DerGainDEC->value()); // Max Pulse Duration (ms) Options::setRAMaximumPulse(spinBox_MaxPulseRA->value()); Options::setDECMaximumPulse(spinBox_MaxPulseDEC->value()); // Min Pulse Duration (ms) Options::setRAMinimumPulse(spinBox_MinPulseRA->value()); Options::setDECMinimumPulse(spinBox_MinPulseDEC->value()); } void Guide::setTrackingStar(int x, int y) { QVector3D newStarPosition(x, y, -1); setStarPosition(newStarPosition, true); if(guiderType == GUIDE_PHD2) { //The Guide Star Image is 32 pixels across or less, so this guarantees it isn't that. if(guideView->getImageData() != nullptr) { if(guideView->getImageData()->width() > 50) phd2Guider->setLockPosition(starCenter.x(), starCenter.y()); } } /*if (state == GUIDE_STAR_SELECT) { guider->setStarPosition(newStarPosition); guider->calibrate(); }*/ if (operationStack.isEmpty() == false) executeOperationStack(); } void Guide::setAxisDelta(double ra, double de) { //If PHD2 starts guiding because somebody pusted the button remotely, we want to set the state to guiding. //If guide pulses start coming in, it must be guiding. // 2020-04-10 sterne-jaeger: Will be resolved inside EKOS phd guiding. // if(guiderType == GUIDE_PHD2 && state != GUIDE_GUIDING) // setStatus(GUIDE_GUIDING); // Time since timer started. double key = guideTimer.elapsed() / 1000.0; ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. driftGraph->graph(0)->addData(key, ra); driftGraph->graph(1)->addData(key, de); int currentNumPoints = driftGraph->graph(0)->dataCount(); guideSlider->setMaximum(currentNumPoints); if(graphOnLatestPt) guideSlider->setValue(currentNumPoints); // Expand range if it doesn't fit already if (driftGraph->yAxis->range().contains(ra) == false) driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); if (driftGraph->yAxis->range().contains(de) == false) driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); // Show last 120 seconds //driftGraph->xAxis->setRange(key, 120, Qt::AlignRight); if(graphOnLatestPt) { driftGraph->xAxis->setRange(key, driftGraph->xAxis->range().size(), Qt::AlignRight); driftGraph->graph(2)->data()->clear(); //Clear highlighted RA point driftGraph->graph(3)->data()->clear(); //Clear highlighted DEC point driftGraph->graph(2)->addData(key, ra); //Set highlighted RA point to latest point driftGraph->graph(3)->addData(key, de); //Set highlighted DEC point to latest point } driftGraph->replot(); //Add to Drift Plot driftPlot->graph(0)->addData(ra, de); if(graphOnLatestPt) { driftPlot->graph(1)->data()->clear(); //Clear highlighted point driftPlot->graph(1)->addData(ra, de); //Set highlighted point to latest point } if (driftPlot->xAxis->range().contains(ra) == false || driftPlot->yAxis->range().contains(de) == false) { driftPlot->setBackground(QBrush(Qt::gray)); QTimer::singleShot(300, this, [ = ]() { driftPlot->setBackground(QBrush(Qt::black)); driftPlot->replot(); }); } driftPlot->replot(); l_DeltaRA->setText(QString::number(ra, 'f', 2)); l_DeltaDEC->setText(QString::number(de, 'f', 2)); emit newAxisDelta(ra, de); profilePixmap = driftGraph->grab(); emit newProfilePixmap(profilePixmap); } +void Guide::calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString& message, + double dx, double dy) +{ + switch (type) + { + case GuideInterface::RA_IN: + calibrationPlot->graph(0)->addData(dx, dy); + break; + case GuideInterface::RA_OUT: + calibrationPlot->graph(1)->addData(dx, dy); + break; + case GuideInterface::DEC_IN: + calibrationPlot->graph(2)->addData(dx, dy); + break; + case GuideInterface::DEC_OUT: + calibrationPlot->graph(3)->addData(dx, dy); + break; + case GuideInterface::CALIBRATION_MESSAGE_ONLY: + ; + } + calLabel->setText(message); + calibrationPlot->replot(); +} + void Guide::setAxisSigma(double ra, double de) { l_ErrRA->setText(QString::number(ra, 'f', 2)); l_ErrDEC->setText(QString::number(de, 'f', 2)); l_TotalRMS->setText(QString::number(std::hypot(ra, de), 'f', 2)); emit newAxisSigma(ra, de); } QList Guide::axisDelta() { QList delta; delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); return delta; } QList Guide::axisSigma() { QList sigma; sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); return sigma; } void Guide::setAxisPulse(double ra, double de) { l_PulseRA->setText(QString::number(static_cast(ra))); l_PulseDEC->setText(QString::number(static_cast(de))); double key = guideTimer.elapsed() / 1000.0; driftGraph->graph(4)->addData(key, ra); driftGraph->graph(5)->addData(key, de); } void Guide::refreshColorScheme() { // Drift color legend if (driftGraph) { if (driftGraph->graph(0) && driftGraph->graph(1) && driftGraph->graph(2) && driftGraph->graph(3) && driftGraph->graph(4) && driftGraph->graph(5)) { driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); } } } void Guide::driftMouseClicked(QMouseEvent *event) { if (event->buttons() & Qt::RightButton) { driftGraph->yAxis->setRange(-3, 3); } } void Guide::driftMouseOverLine(QMouseEvent *event) { double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); if (driftGraph->xAxis->range().contains(key)) { QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); if (graph) { int raIndex = driftGraph->graph(0)->findBegin(key); int deIndex = driftGraph->graph(1)->findBegin(key); double raDelta = driftGraph->graph(0)->dataMainValue(raIndex); double deDelta = driftGraph->graph(1)->dataMainValue(deIndex); double raPulse = driftGraph->graph(4)->dataMainValue(raIndex); //Get RA Pulse from RA pulse data double dePulse = driftGraph->graph(5)->dataMainValue(deIndex); //Get DEC Pulse from DEC pulse data // Compute time value: QTime localTime = guideTimer; localTime = localTime.addSecs(key); QToolTip::hideText(); if(raPulse == 0 && dePulse == 0) { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds;", "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2))); } else { QToolTip::showText( event->globalPos(), i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is RA Pulse in ms; %5 is DE Pulse in ms", "" "" "" "" "" "" "
LT: %1
RA: %2 \"
DE: %3 \"
RA Pulse: %4 ms
DE Pulse: %5 ms
", localTime.toString("hh:mm:ss AP"), QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2), QString::number(raPulse, 'f', 2), QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph. } } else QToolTip::hideText(); driftGraph->replot(); } } void Guide::buildOperationStack(GuideState operation) { operationStack.clear(); switch (operation) { case GUIDE_CAPTURE: if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); operationStack.push(GUIDE_CAPTURE); operationStack.push(GUIDE_SUBFRAME); break; case GUIDE_CALIBRATING: operationStack.push(GUIDE_CALIBRATING); if (guiderType == GUIDE_INTERNAL) { if (Options::guideDarkFrameEnabled()) operationStack.push(GUIDE_DARK); // Auto Star Selected Path if (Options::guideAutoStarEnabled()) { // If subframe is enabled and we need to auto select a star, then we need to make the final capture // of the subframed image. This is only done if we aren't already subframed. if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Do not subframe and auto-select star on Image Guiding mode if (Options::imageGuidingEnabled() == false) { operationStack.push(GUIDE_SUBFRAME); operationStack.push(GUIDE_STAR_SELECT); } operationStack.push(GUIDE_CAPTURE); // If we are being ask to go full frame, let's do that first if (subFramed == true && Options::guideSubframeEnabled() == false) operationStack.push(GUIDE_SUBFRAME); } // Manual Star Selection Path else { // In Image Guiding, we never need to subframe if (Options::imageGuidingEnabled() == false) { // Final capture before we start calibrating if (subFramed == false && Options::guideSubframeEnabled()) operationStack.push(GUIDE_CAPTURE); // Subframe if required operationStack.push(GUIDE_SUBFRAME); } // First capture an image operationStack.push(GUIDE_CAPTURE); } } break; default: break; } } bool Guide::executeOperationStack() { if (operationStack.isEmpty()) return false; GuideState nextOperation = operationStack.pop(); bool actionRequired = false; switch (nextOperation) { case GUIDE_SUBFRAME: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_DARK: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CAPTURE: actionRequired = captureOneFrame(); break; case GUIDE_STAR_SELECT: actionRequired = executeOneOperation(nextOperation); break; case GUIDE_CALIBRATING: if (guiderType == GUIDE_INTERNAL) { guider->setStarPosition(starCenter); dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); // No need to calibrate if (Options::imageGuidingEnabled()) { setStatus(GUIDE_CALIBRATION_SUCESS); break; } // Tracking must be engaged if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) currentTelescope->setTrackEnabled(true); } if (guider->calibrate()) { if (guiderType == GUIDE_INTERNAL) disconnect(guideView, SIGNAL(trackingStarSelected(int, int)), this, SLOT(setTrackingStar(int, int))); setBusy(true); } else { emit newStatus(GUIDE_CALIBRATION_ERROR); state = GUIDE_IDLE; appendLogText(i18n("Calibration failed to start.")); setBusy(false); } break; default: break; } // If an additional action is required, return return and continue later if (actionRequired) return true; // Otherwise, continue processing the stack else return executeOperationStack(); } bool Guide::executeOneOperation(GuideState operation) { bool actionRequired = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); int subBinX, subBinY; targetChip->getBinning(&subBinX, &subBinY); switch (operation) { case GUIDE_SUBFRAME: { // Check if we need and can subframe if (subFramed == false && Options::guideSubframeEnabled() == true && targetChip->canSubframe()) { int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); int offset = boxSizeCombo->currentText().toInt() / subBinX; int x = starCenter.x(); int y = starCenter.y(); x = (x - offset * 2) * subBinX; y = (y - offset * 2) * subBinY; int w = offset * 4 * subBinX; int h = offset * 4 * subBinY; if (x < minX) x = minX; if (y < minY) y = minY; if ((x + w) > maxW) w = maxW - x; if ((y + h) > maxH) h = maxH - y; targetChip->setFrame(x, y, w, h); subFramed = true; QVariantMap settings = frameSettings[targetChip]; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = subBinX; settings["biny"] = subBinY; frameSettings[targetChip] = settings; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); } // Otherwise check if we are already subframed // and we need to go back to full frame // or if we need to go back to full frame since we need // to reaquire a star else if (subFramed && (Options::guideSubframeEnabled() == false || state == GUIDE_REACQUIRE)) { targetChip->resetFrame(); int x, y, w, h; targetChip->getFrame(&x, &y, &w, &h); QVariantMap settings; settings["x"] = x; settings["y"] = y; settings["w"] = w; settings["h"] = h; settings["binx"] = 1; settings["biny"] = 1; frameSettings[targetChip] = settings; subFramed = false; starCenter.setX(w / (2 * subBinX)); starCenter.setY(h / (2 * subBinX)); //starCenter.setX(0); //starCenter.setY(0); } } break; case GUIDE_DARK: { // Do we need to take a dark frame? if (Options::guideDarkFrameEnabled()) { QVariantMap settings = frameSettings[targetChip]; uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed) { DarkLibrary::Instance()->disconnect(this); if (completed != darkFrameCheck->isChecked()) setDarkFrameEnabled(completed); if (completed) setCaptureComplete(); else abort(); }); connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); actionRequired = true; targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); if (darkData) DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, offsetY); else { DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), offsetX, offsetY); } } } break; case GUIDE_STAR_SELECT: { state = GUIDE_STAR_SELECT; emit newStatus(state); if (Options::guideAutoStarEnabled()) { bool autoStarCaptured = internalGuider->selectAutoStar(); if (autoStarCaptured) { appendLogText(i18n("Auto star selected.")); } else { appendLogText(i18n("Failed to select an auto star.")); actionRequired = true; state = GUIDE_CALIBRATION_ERROR; emit newStatus(state); setBusy(false); } } else { appendLogText(i18n("Select a guide star to calibrate.")); actionRequired = true; } } break; default: break; } return actionRequired; } void Guide::processGuideOptions() { if (Options::guiderType() != guiderType) { guiderType = static_cast(Options::guiderType()); setGuiderType(Options::guiderType()); } } void Guide::showFITSViewer() { FITSData *data = guideView->getImageData(); if (data) { QUrl url = QUrl::fromLocalFile(data->filename()); if (fv.isNull()) { if (Options::singleWindowCapturedFITS()) fv = KStars::Instance()->genericFITSViewer(); else { fv = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); KStars::Instance()->addFITSViewer(fv); } fv->addFITS(url); FITSView *currentView = fv->getCurrentView(); if (currentView) currentView->getImageData()->setAutoRemoveTemporaryFITS(false); } else fv->updateFITS(url, 0); fv->show(); } } void Guide::setExternalGuiderBLOBEnabled(bool enable) { // Nothing to do if guider is internal if (guiderType == GUIDE_INTERNAL) return; if(!currentCCD) return; currentCCD->setBLOBEnabled(enable); if(currentCCD->isBLOBEnabled()) { if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) useGuideHead = true; else useGuideHead = false; ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip) { targetChip->setImageView(guideView, FITS_GUIDE); targetChip->setCaptureMode(FITS_GUIDE); } syncCCDInfo(); } } void Guide::ditherDirectly() { double ditherPulse = Options::ditherNoGuidingPulse(); // Randomize pulse length. It is equal to 50% of pulse length + random value up to 50% // e.g. if ditherPulse is 500ms then final pulse is = 250 + rand(0 to 250) int ra_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int ra_polarity = (rand() % 2 == 0) ? 1 : -1; int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); int de_polarity = (rand() % 2 == 0) ? 1 : -1; qCInfo(KSTARS_EKOS_GUIDE) << "Starting non-guiding dither..."; qCDebug(KSTARS_EKOS_GUIDE) << "dither ra_msec:" << ra_msec << "ra_polarity:" << ra_polarity << "de_msec:" << de_msec << "de_polarity:" << de_polarity; bool rc = sendPulse(ra_polarity > 0 ? RA_INC_DIR : RA_DEC_DIR, ra_msec, de_polarity > 0 ? DEC_INC_DIR : DEC_DEC_DIR, de_msec); if (rc) { qCInfo(KSTARS_EKOS_GUIDE) << "Non-guiding dither successful."; QTimer::singleShot( (ra_msec > de_msec ? ra_msec : de_msec) + Options::ditherSettle() * 1000 + 100, [this]() { emit newStatus(GUIDE_DITHERING_SUCCESS); state = GUIDE_IDLE; }); } else { qCWarning(KSTARS_EKOS_GUIDE) << "Non-guiding dither failed."; emit newStatus(GUIDE_DITHERING_ERROR); state = GUIDE_IDLE; } } void Guide::updateTelescopeType(int index) { if (currentCCD == nullptr) return; focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; Options::setGuideScopeType(index); syncTelescopeInfo(); } void Guide::setDefaultST4(const QString &driver) { Options::setDefaultST4Driver(driver); } void Guide::setDefaultCCD(const QString &ccd) { if (guiderType == GUIDE_INTERNAL) Options::setDefaultGuideCCD(ccd); } void Guide::handleManualDither() { ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); if (targetChip == nullptr) return; Ui::ManualDither ditherDialog; QDialog container(this); ditherDialog.setupUi(&container); if (guiderType != GUIDE_INTERNAL) { ditherDialog.coordinatesR->setEnabled(false); ditherDialog.x->setEnabled(false); ditherDialog.y->setEnabled(false); } int minX, maxX, minY, maxY, minW, maxW, minH, maxH; targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); ditherDialog.x->setMinimum(minX); ditherDialog.x->setMaximum(maxX); ditherDialog.y->setMinimum(minY); ditherDialog.y->setMaximum(maxY); ditherDialog.x->setValue(starCenter.x()); ditherDialog.y->setValue(starCenter.y()); if (container.exec() == QDialog::Accepted) { if (ditherDialog.magnitudeR->isChecked()) guider->dither(ditherDialog.magnitude->value()); else { dynamic_cast(guider)->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); } } } bool Guide::connectGuider() { return guider->Connect(); } bool Guide::disconnectGuider() { return guider->Disconnect(); } void Guide::initPlots() +{ + initDriftGraph(); + initDriftPlot(); + initCalibrationPlot(); + + connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); + connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); + + //This sets the values of all the Graph Options that are stored. + accuracyRadiusSpin->setValue(Options::guiderAccuracyThreshold()); + showRAPlotCheck->setChecked(Options::rADisplayedOnGuideGraph()); + showDECPlotCheck->setChecked(Options::dEDisplayedOnGuideGraph()); + showRACorrectionsCheck->setChecked(Options::rACorrDisplayedOnGuideGraph()); + showDECorrectionsCheck->setChecked(Options::dECorrDisplayedOnGuideGraph()); + + buildTarget(); +} + +void Guide::initDriftGraph() { // Drift Graph Color Settings driftGraph->setBackground(QBrush(Qt::black)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftGraph->xAxis->grid()->setZeroLinePen(Qt::NoPen); driftGraph->yAxis->grid()->setZeroLinePen(QPen(Qt::white, 1)); driftGraph->xAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis->setBasePen(QPen(Qt::white, 1)); driftGraph->yAxis2->setBasePen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftGraph->yAxis2->setSubTickPen(QPen(Qt::white, 1)); driftGraph->xAxis->setTickLabelColor(Qt::white); driftGraph->yAxis->setTickLabelColor(Qt::white); driftGraph->yAxis2->setTickLabelColor(Qt::white); driftGraph->xAxis->setLabelColor(Qt::white); driftGraph->yAxis->setLabelColor(Qt::white); driftGraph->yAxis2->setLabelColor(Qt::white); //Horizontal Axis Time Ticker Settings QSharedPointer timeTicker(new QCPAxisTickerTime); timeTicker->setTimeFormat("%m:%s"); driftGraph->xAxis->setTicker(timeTicker); //Vertical Axis Labels Settings driftGraph->yAxis2->setVisible(true); driftGraph->yAxis2->setTickLabels(true); driftGraph->yAxis->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis2->setLabelFont(QFont(font().family(), 10)); driftGraph->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis2->setTickLabelFont(QFont(font().family(), 9)); driftGraph->yAxis->setLabelPadding(1); driftGraph->yAxis2->setLabelPadding(1); driftGraph->yAxis->setLabel(i18n("drift (arcsec)")); driftGraph->yAxis2->setLabel(i18n("pulse (ms)")); setupNSEWLabels(); //Sets the default ranges driftGraph->xAxis->setRange(0, 60, Qt::AlignRight); driftGraph->yAxis->setRange(-3, 3); int scale = 50; //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg correctionSlider->setValue(scale); driftGraph->yAxis2->setRange(-3 * scale, 3 * scale); //This sets up the legend driftGraph->legend->setVisible(true); driftGraph->legend->setFont(QFont("Helvetica", 9)); driftGraph->legend->setTextColor(Qt::white); driftGraph->legend->setBrush(QBrush(Qt::black)); driftGraph->legend->setFillOrder(QCPLegend::foColumnsFirst); driftGraph->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft | Qt::AlignBottom); // RA Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(0)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(0)->setName("RA"); driftGraph->graph(0)->setLineStyle(QCPGraph::lsStepLeft); // DE Curve driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(1)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(1)->setName("DE"); driftGraph->graph(1)->setLineStyle(QCPGraph::lsStepLeft); // RA highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(2)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(2)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"))); driftGraph->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10)); // DE highlighted Point driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis); driftGraph->graph(3)->setLineStyle(QCPGraph::lsNone); driftGraph->graph(3)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"))); driftGraph->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10)); // RA Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); raPulseColor.setAlpha(75); driftGraph->graph(4)->setPen(QPen(raPulseColor)); driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); driftGraph->graph(4)->setName("RA Pulse"); driftGraph->graph(4)->setLineStyle(QCPGraph::lsStepLeft); // DEC Pulse driftGraph->addGraph(driftGraph->xAxis, driftGraph->yAxis2); QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); dePulseColor.setAlpha(75); driftGraph->graph(5)->setPen(QPen(dePulseColor)); driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); driftGraph->graph(5)->setName("DEC Pulse"); driftGraph->graph(5)->setLineStyle(QCPGraph::lsStepLeft); //This will prevent the highlighted points and Pulses from showing up in the legend. driftGraph->legend->removeItem(5); driftGraph->legend->removeItem(4); driftGraph->legend->removeItem(3); driftGraph->legend->removeItem(2); //Dragging and zooming settings // make bottom axis transfer its range to the top axis if the graph gets zoomed: connect(driftGraph->xAxis, static_cast(&QCPAxis::rangeChanged), driftGraph->xAxis2, static_cast(&QCPAxis::setRange)); // update the second vertical axis properly if the graph gets zoomed. connect(driftGraph->yAxis, static_cast(&QCPAxis::rangeChanged), this, &Ekos::Guide::setCorrectionGraphScale); driftGraph->setInteractions(QCP::iRangeZoom); driftGraph->setInteraction(QCP::iRangeDrag, true); connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); + //This sets the visibility of graph components to the stored values. + driftGraph->graph(0)->setVisible(Options::rADisplayedOnGuideGraph()); //RA data + driftGraph->graph(1)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC data + driftGraph->graph(2)->setVisible(Options::rADisplayedOnGuideGraph()); //RA highlighted point + driftGraph->graph(3)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC highlighted point + driftGraph->graph(4)->setVisible(Options::rACorrDisplayedOnGuideGraph()); //RA Pulses + driftGraph->graph(5)->setVisible(Options::dECorrDisplayedOnGuideGraph()); //DEC Pulses + updateCorrectionsScaleVisibility(); +} + +void Guide::initDriftPlot() +{ //drift plot double accuracyRadius = 2; driftPlot->setBackground(QBrush(Qt::black)); driftPlot->setSelectionTolerance(10); driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); driftPlot->xAxis->setTickLabelColor(Qt::white); driftPlot->yAxis->setTickLabelColor(Qt::white); driftPlot->xAxis->setLabelColor(Qt::white); driftPlot->yAxis->setLabelColor(Qt::white); driftPlot->xAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->yAxis->setLabelFont(QFont(font().family(), 10)); driftPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); driftPlot->xAxis->setLabelPadding(2); driftPlot->yAxis->setLabelPadding(2); driftPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); driftPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); driftPlot->setInteractions(QCP::iRangeZoom); driftPlot->setInteraction(QCP::iRangeDrag, true); driftPlot->addGraph(); driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); driftPlot->addGraph(); driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); - connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); - connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); + driftPlot->resize(190, 190); + driftPlot->replot(); +} - //This sets the values of all the Graph Options that are stored. - accuracyRadiusSpin->setValue(Options::guiderAccuracyThreshold()); - showRAPlotCheck->setChecked(Options::rADisplayedOnGuideGraph()); - showDECPlotCheck->setChecked(Options::dEDisplayedOnGuideGraph()); - showRACorrectionsCheck->setChecked(Options::rACorrDisplayedOnGuideGraph()); - showDECorrectionsCheck->setChecked(Options::dECorrDisplayedOnGuideGraph()); +void Guide::initCalibrationPlot() +{ + calibrationPlot->setBackground(QBrush(Qt::black)); + calibrationPlot->setSelectionTolerance(10); - //This sets the visibility of graph components to the stored values. - driftGraph->graph(0)->setVisible(Options::rADisplayedOnGuideGraph()); //RA data - driftGraph->graph(1)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC data - driftGraph->graph(2)->setVisible(Options::rADisplayedOnGuideGraph()); //RA highlighted point - driftGraph->graph(3)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC highlighted point - driftGraph->graph(4)->setVisible(Options::rACorrDisplayedOnGuideGraph()); //RA Pulses - driftGraph->graph(5)->setVisible(Options::dECorrDisplayedOnGuideGraph()); //DEC Pulses - updateCorrectionsScaleVisibility(); + calibrationPlot->xAxis->setBasePen(QPen(Qt::white, 1)); + calibrationPlot->yAxis->setBasePen(QPen(Qt::white, 1)); - driftPlot->resize(190, 190); - driftPlot->replot(); + calibrationPlot->xAxis->setTickPen(QPen(Qt::white, 1)); + calibrationPlot->yAxis->setTickPen(QPen(Qt::white, 1)); - buildTarget(); + calibrationPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); + calibrationPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); + + calibrationPlot->xAxis->setTickLabelColor(Qt::white); + calibrationPlot->yAxis->setTickLabelColor(Qt::white); + + calibrationPlot->xAxis->setLabelColor(Qt::white); + calibrationPlot->yAxis->setLabelColor(Qt::white); + + calibrationPlot->xAxis->setLabelFont(QFont(font().family(), 10)); + calibrationPlot->yAxis->setLabelFont(QFont(font().family(), 10)); + calibrationPlot->xAxis->setTickLabelFont(QFont(font().family(), 9)); + calibrationPlot->yAxis->setTickLabelFont(QFont(font().family(), 9)); + + calibrationPlot->xAxis->setLabelPadding(2); + calibrationPlot->yAxis->setLabelPadding(2); + + calibrationPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + calibrationPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + calibrationPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + calibrationPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + calibrationPlot->xAxis->grid()->setZeroLinePen(QPen(Qt::gray)); + calibrationPlot->yAxis->grid()->setZeroLinePen(QPen(Qt::gray)); + + calibrationPlot->xAxis->setLabel(i18n("x (pixels)")); + calibrationPlot->yAxis->setLabel(i18n("y (pixels)")); + + calibrationPlot->xAxis->setRange(-10, 10); + calibrationPlot->yAxis->setRange(-10, 10); + + calibrationPlot->setInteractions(QCP::iRangeZoom); + calibrationPlot->setInteraction(QCP::iRangeDrag, true); + + calibrationPlot->addGraph(); + calibrationPlot->graph(0)->setLineStyle(QCPGraph::lsNone); + calibrationPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 6)); + calibrationPlot->graph(0)->setName("RA out"); + + calibrationPlot->addGraph(); + calibrationPlot->graph(1)->setLineStyle(QCPGraph::lsNone); + calibrationPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::white, 2), QBrush(), 4)); + calibrationPlot->graph(1)->setName("RA in"); + + calibrationPlot->addGraph(); + calibrationPlot->graph(2)->setLineStyle(QCPGraph::lsNone); + calibrationPlot->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 6)); + calibrationPlot->graph(2)->setName("DEC out"); + + calibrationPlot->addGraph(); + calibrationPlot->graph(3)->setLineStyle(QCPGraph::lsNone); + calibrationPlot->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::yellow, 2), QBrush(), 4)); + calibrationPlot->graph(3)->setName("DEC in"); + + calLabel = new QCPItemText(calibrationPlot); + calLabel->setColor(QColor(255,255,255)); + calLabel->setPositionAlignment(Qt::AlignTop|Qt::AlignHCenter); + calLabel->position->setType(QCPItemPosition::ptAxisRectRatio); + calLabel->position->setCoords(0.5, 0); + calLabel->setText(""); + calLabel->setFont(QFont(font().family(), 10)); + calLabel->setVisible(true); + + calibrationPlot->resize(190, 190); + calibrationPlot->replot(); } void Guide::initView() { guideView = new FITSView(guideWidget, FITS_GUIDE); guideView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); guideView->setBaseSize(guideWidget->size()); guideView->createFloatingToolBar(); QVBoxLayout *vlayout = new QVBoxLayout(); vlayout->addWidget(guideView); guideWidget->setLayout(vlayout); connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar); } void Guide::initConnections() { // Exposure Timeout captureTimeout.setSingleShot(true); connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); // Guiding Box Size connect(boxSizeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTrackingBoxSize); // Guider CCD Selection connect(guiderCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::setDefaultCCD); connect(guiderCombo, static_cast(&QComboBox::activated), this, [&](int index) { if (guiderType == GUIDE_INTERNAL) { starCenter = QVector3D(); checkCCD(index); } } ); FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); // Dark Frame Check connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); // Subframe check if(guiderType != GUIDE_PHD2) //For PHD2, this is handled in the configurePHD2Camera method connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); // ST4 Selection connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) { setDefaultST4(text); setST4(text); }); // Binning Combo Selection connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); // RA/DEC Enable directions connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); // N/W and W/E direction enable connect(northControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(southControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(westControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); connect(eastControlCheck, &QCheckBox::toggled, this, &Ekos::Guide::onControlDirectionChanged); // Auto star check connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); // Declination Swap connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); // PID Control - Proportional Gain connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Integral Gain connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // PID Control - Derivative Gain connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Max Pulse Duration (ms) connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Min Pulse Duration (ms) connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); // Capture connect(captureB, &QPushButton::clicked, this, [this]() { state = GUIDE_CAPTURE; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText( 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.")); else if(Options::guideSubframeEnabled()) { appendLogText( 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")); subFrameCheck->setChecked(false); } phd2Guider->captureSingleFrame(); } else capture(); }); connect(loopB, &QPushButton::clicked, this, [this]() { state = GUIDE_LOOPING; emit newStatus(state); if(guiderType == GUIDE_PHD2) { configurePHD2Camera(); if(phd2Guider->isCurrentCameraNotInEkos()) appendLogText( 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.")); else if(Options::guideSubframeEnabled()) { appendLogText( 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")); subFrameCheck->setChecked(false); } phd2Guider->loop(); stopB->setEnabled(true); } else capture(); }); // Stop connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); // Clear Calibrate //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); // Guide connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); // Connect External Guide connect(externalConnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(false); guider->Connect(); }); connect(externalDisconnectB, &QPushButton::clicked, this, [&]() { //setExternalGuiderBLOBEnabled(true); guider->Disconnect(); }); // Pulse Timer pulseTimer.setSingleShot(true); connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); //This connects all the buttons and slider below the guide plots. connect(accuracyRadiusSpin, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::buildTarget); connect(guideSlider, &QSlider::sliderMoved, this, &Ekos::Guide::guideHistory); connect(latestCheck, &QCheckBox::toggled, this, &Ekos::Guide::setLatestGuidePoint); connect(showRAPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowRAPlot); connect(showDECPlotCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleShowDEPlot); connect(showRACorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleRACorrectionsPlot); connect(showDECorrectionsCheck, &QCheckBox::toggled, this, &Ekos::Guide::toggleDECorrectionsPlot); connect(correctionSlider, &QSlider::sliderMoved, this, &Ekos::Guide::setCorrectionGraphScale); connect(showGuideRateToolTipB, &QPushButton::clicked, [this]() { QToolTip::showText(showGuideRateToolTipB->mapToGlobal(QPoint(10, 10)), showGuideRateToolTipB->toolTip(), showGuideRateToolTipB); }); connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); // Guiding Rate - Advisory only onInfoRateChanged(spinBox_GuideRate->value()); connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); } void Guide::removeDevice(ISD::GDInterface *device) { device->disconnect(this); if (currentTelescope && (currentTelescope->getDeviceName() == device->getDeviceName())) { currentTelescope = nullptr; } else if (CCDs.contains(static_cast(device))) { CCDs.removeAll(static_cast(device)); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName())); guiderCombo->removeItem(guiderCombo->findText(device->getDeviceName() + QString(" Guider"))); if (CCDs.empty()) { currentCCD = nullptr; guiderCombo->setCurrentIndex(-1); } else guiderCombo->setCurrentIndex(0); checkCCD(); } auto st4 = std::find_if(ST4List.begin(), ST4List.end(), [device](ISD::ST4 * st) { return (st->getDeviceName() == device->getDeviceName()); }); if (st4 != ST4List.end()) { ST4List.removeOne(*st4); if (AODriver && (device->getDeviceName() == AODriver->getDeviceName())) AODriver = nullptr; ST4Combo->removeItem(ST4Combo->findText(device->getDeviceName())); if (ST4List.empty()) { ST4Driver = GuideDriver = nullptr; } else { setST4(ST4Combo->currentText()); } } } } diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h index ad1795ffb..7b594217e 100644 --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -1,648 +1,655 @@ /* Ekos guide tool Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ui_guide.h" +#include "guideinterface.h" #include "ekos/ekos.h" #include "indi/indiccd.h" #include "indi/inditelescope.h" #include #include #include class QProgressIndicator; class QTabWidget; class FITSView; class FITSViewer; class ScrollGraph; namespace Ekos { -class GuideInterface; class OpsCalibration; class OpsGuide; class InternalGuider; class PHD2; class LinGuider; /** * @class Guide * @short Performs calibration and autoguiding using an ST4 port or directly via the INDI driver. Can be used with the following external guiding applications: * PHD2 * LinGuider * * @author Jasem Mutlaq * @version 1.4 */ class Guide : public QWidget, public Ui::Guide { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Guide") Q_PROPERTY(Ekos::GuideState status READ status NOTIFY newStatus) Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) Q_PROPERTY(QString camera READ camera WRITE setCamera) Q_PROPERTY(QString st4 READ st4 WRITE setST4) Q_PROPERTY(double exposure READ exposure WRITE setExposure) Q_PROPERTY(QList axisDelta READ axisDelta NOTIFY newAxisDelta) Q_PROPERTY(QList axisSigma READ axisSigma NOTIFY newAxisSigma) public: Guide(); ~Guide(); enum GuiderStage { CALIBRATION_STAGE, GUIDE_STAGE }; enum GuiderType { GUIDE_INTERNAL, GUIDE_PHD2, GUIDE_LINGUIDER }; /** @defgroup GuideDBusInterface Ekos DBus Interface - Capture Module * Ekos::Guide interface provides advanced scripting capabilities to calibrate and guide a mount via a CCD camera. */ /*@{*/ /** DBUS interface function. * select the CCD device from the available CCD drivers. * @param device The CCD device name * @return Returns true if CCD device is found and set, false otherwise. */ Q_SCRIPTABLE bool setCamera(const QString &device); Q_SCRIPTABLE QString camera(); /** DBUS interface function. * select the ST4 device from the available ST4 drivers. * @param device The ST4 device name * @return Returns true if ST4 device is found and set, false otherwise. */ Q_SCRIPTABLE bool setST4(const QString &device); Q_SCRIPTABLE QString st4(); /** DBUS interface function. * @return Returns List of registered ST4 devices. */ Q_SCRIPTABLE QStringList getST4Devices(); /** DBUS interface function. * @brief connectGuider Establish connection to guider application. For internal guider, this always returns true. * @return True if successfully connected, false otherwise. */ Q_SCRIPTABLE bool connectGuider(); /** DBUS interface function. * @brief disconnectGuider Disconnect from guider application. For internal guider, this always returns true. * @return True if successfully disconnected, false otherwise. */ Q_SCRIPTABLE bool disconnectGuider(); /** * @brief getStatus Return guide module status * @return state of guide module from Ekos::GuideState */ Q_SCRIPTABLE Ekos::GuideState status() { return state; } /** DBUS interface function. * Set CCD exposure value * @param value exposure value in seconds. */ Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); double exposure() { return exposureIN->value(); } /** DBUS interface function. * Set image filter to apply to the image after capture. * @param value Image filter (Auto Stretch, High Contrast, Equalize, High Pass) */ Q_SCRIPTABLE Q_NOREPLY void setImageFilter(const QString &value); /** DBUS interface function. * Set calibration Use Two Axis option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, calibration will be performed in both RA and DEC axis. Otherwise, only RA axis will be calibrated. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationTwoAxis(bool enable); /** DBUS interface function. * Set auto star calibration option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best guide star and proceed with the calibration procedure. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoStar(bool enable); /** DBUS interface function. * In case of automatic star selection, calculate the appropriate square size given the selected star width. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will attempt to automatically select the best square size for calibration and guiding phases. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationAutoSquareSize(bool enable); /** DBUS interface function. * Set calibration dark frame option. The options must be set before starting the calibration operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, a dark frame will be captured to subtract from the light frame. */ Q_SCRIPTABLE Q_NOREPLY void setDarkFrameEnabled(bool enable); /** DBUS interface function. * Set calibration parameters. * @param pulseDuration Pulse duration in milliseconds to use in the calibration steps. */ Q_SCRIPTABLE Q_NOREPLY void setCalibrationPulseDuration(int pulseDuration); /** DBUS interface function. * Set guiding box size. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index box size index (0 to 4) for box size from 8 to 128 pixels. The box size should be suitable for the size of the guide star selected. The boxSize is also used to select the subframe size around the guide star. Default is 16 pixels */ Q_SCRIPTABLE Q_NOREPLY void setGuideBoxSizeIndex(int index); /** DBUS interface function. * Set guiding algorithm. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param index Select the algorithm used to calculate the centroid of the guide star (0 --> Smart, 1 --> Fast, 2 --> Auto, 3 --> No thresh). */ Q_SCRIPTABLE Q_NOREPLY void setGuideAlgorithmIndex(int index); /** DBUS interface function. * Set rapid guiding option. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will activate RapidGuide in the CCD driver. When Rapid Guide is used, no frames are sent to Ekos for analysis and the centeroid calculations are done in the CCD driver. */ //Q_SCRIPTABLE Q_NOREPLY void setGuideRapidEnabled(bool enable); /** DBUS interface function. * Enable or disables dithering. Set dither range * @param enable if true, dithering is enabled and is performed after each exposure is complete. Otherwise, dithering is disabled. * @param value dithering range in pixels. Ekos will move the guide star in a random direction for the specified dithering value in pixels. */ Q_SCRIPTABLE Q_NOREPLY void setDitherSettings(bool enable, double value); /** @}*/ void addCamera(ISD::GDInterface *newCCD); void configurePHD2Camera(); void setTelescope(ISD::GDInterface *newTelescope); void addST4(ISD::ST4 *setST4); void setAO(ISD::ST4 *newAO); void removeDevice(ISD::GDInterface *device); bool isDithering(); void addGuideHead(ISD::GDInterface *newCCD); void syncTelescopeInfo(); void syncCCDInfo(); /** * @brief clearLog As the name suggests */ void clearLog(); QStringList logText() { return m_LogText; } /** * @return Return current log text of guide module */ QString getLogText() { return m_LogText.join("\n"); } /** * @brief getStarPosition Return star center as selected by the user or auto-detected by KStars * @return QVector3D of starCenter. The 3rd parameter is used to store current bin settings and in unrelated to the star position. */ QVector3D getStarPosition() { return starCenter; } // Tracking Box int getTrackingBoxSize() { return boxSizeCombo->currentText().toInt(); } //void startRapidGuide(); //void stopRapidGuide(); GuideInterface *getGuider() { return guider; } public slots: /** DBUS interface function. * Start the autoguiding operation. * @return Returns true if guiding started successfully, false otherwise. */ Q_SCRIPTABLE bool guide(); /** DBUS interface function. * Stop any active calibration, guiding, or dithering operation * @return Returns true if operation is stopped successfully, false otherwise. */ Q_SCRIPTABLE bool abort(); /** DBUS interface function. * Start the calibration operation. Note that this will not start guiding automatically. * @return Returns true if calibration started successfully, false otherwise. */ Q_SCRIPTABLE bool calibrate(); /** DBUS interface function. * Clear calibration data. Next time any guide operation is performed, a calibration is first started. */ Q_SCRIPTABLE Q_NOREPLY void clearCalibration(); /** DBUS interface function. * @brief dither Starts dithering process in a random direction restricted by the number of pixels specified in dither options * @return True if dither started successfully, false otherwise. */ Q_SCRIPTABLE bool dither(); /** DBUS interface function. * @brief suspend Suspend autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool suspend(); /** DBUS interface function. * @brief resume Resume autoguiding * @return True if successful, false otherwise. */ Q_SCRIPTABLE bool resume(); /** DBUS interface function. * Capture a guide frame * @return Returns true if capture command is sent successfully to INDI server. */ Q_SCRIPTABLE bool capture(); /** DBUS interface function. * Set guiding options. The options must be set before starting the guiding operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, it will select a subframe around the guide star depending on the boxSize size. */ Q_SCRIPTABLE Q_NOREPLY void setSubFrameEnabled(bool enable); /** DBUS interface function. * Selects which guiding process to utilize for calibration & guiding. * @param type Type of guider process to use. 0 for internal guider, 1 for external PHD2, 2 for external lin_guider. Pass -1 to select default guider in options. * @return True if guiding is switched to the new requested type. False otherwise. */ Q_SCRIPTABLE bool setGuiderType(int type); /** DBUS interface function. * @brief axisDelta returns the last immediate axis delta deviation in arcseconds. This is the deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisDelta(); /** DBUS interface function. * @brief axisSigma return axis sigma deviation in arcseconds RMS. This is the RMS deviation of locked star position when guiding started. * @return List of doubles. First member is RA deviation. Second member is DE deviation. */ Q_SCRIPTABLE QList axisSigma(); /** * @brief checkCCD Check all CCD parameters and ensure all variables are updated to reflect the selected CCD * @param ccdNum CCD index number in the CCD selection combo box */ void checkCCD(int ccdNum = -1); /** * @brief checkExposureValue This function is called by the INDI framework whenever there is a new exposure value. We use it to know if there is a problem with the exposure * @param targetChip Chip for which the exposure is undergoing * @param exposure numbers of seconds left in the exposure * @param expState State of the exposure property */ void checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState); /** * @brief newFITS is called by the INDI framework whenever there is a new BLOB arriving */ void newFITS(IBLOB *); /** * @brief setST4 Sets a new ST4 device from the combobox index * @param index Index of selected ST4 in the combobox */ void setST4(int index); /* * @brief processRapidStarData is called by INDI framework when we receive new Rapid Guide data * @param targetChip target Chip for which rapid guide is enabled * @param dx Deviation in X * @param dy Deviation in Y * @param fit fitting score */ //void processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit); /** * @brief Set telescope and guide scope info. All measurements is in millimeters. * @param primaryFocalLength Primary Telescope Focal Length. Set to 0 to skip setting this value. * @param primaryAperture Primary Telescope Aperture. Set to 0 to skip setting this value. * @param guideFocalLength Guide Telescope Focal Length. Set to 0 to skip setting this value. * @param guideAperture Guide Telescope Aperture. Set to 0 to skip setting this value. */ void setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture); // This Function will allow PHD2 to update the exposure values to the recommended ones. QString setRecommendedExposureValues(QList values); // Append Log entry void appendLogText(const QString &); // Update Guide module status void setStatus(Ekos::GuideState newState); // Update Capture Module status void setCaptureStatus(Ekos::CaptureState newState); // Update Mount module status void setMountStatus(ISD::Telescope::Status newState); // Update Pier Side void setPierSide(ISD::Telescope::PierSide newSide); // Star Position void setStarPosition(const QVector3D &newCenter, bool updateNow); // Capture void setCaptureComplete(); // Send pulse to ST4 driver bool sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs); bool sendPulse(GuideDirection dir, int msecs); /** * @brief setDECSwap Change ST4 declination pulse direction. +DEC pulses increase DEC if swap is OFF. When on +DEC pulses result in decreasing DEC. * @param enable True to enable DEC swap. Off to disable it. */ void setDECSwap(bool enable); void refreshColorScheme(); void setupNSEWLabels(); //plot slots void handleVerticalPlotSizeChange(); void handleHorizontalPlotSizeChange(); void clearGuideGraphs(); + void clearCalibrationGraphs(); void slotAutoScaleGraphs(); void buildTarget(); void guideHistory(); void setLatestGuidePoint(bool isChecked); void toggleShowRAPlot(bool isChecked); void toggleShowDEPlot(bool isChecked); void toggleRACorrectionsPlot(bool isChecked); void toggleDECorrectionsPlot(bool isChecked); void exportGuideData(); void setCorrectionGraphScale(); void updateCorrectionsScaleVisibility(); void updateDirectionsFromPHD2(QString mode); void guideAfterMeridianFlip(); protected slots: void updateTelescopeType(int index); void updateCCDBin(int index); /** * @brief processCCDNumber Process number properties arriving from CCD. Currently, binning changes are processed. * @param nvp pointer to number property. */ void processCCDNumber(INumberVectorProperty *nvp); /** * @brief setTrackingStar Gets called when the user select a star in the guide frame * @param x X coordinate of star * @param y Y coordinate of star */ void setTrackingStar(int x, int y); void saveDefaultGuideExposure(); void updateTrackingBoxSize(int currentIndex); // Display guide information when hovering over the drift graph void driftMouseOverLine(QMouseEvent *event); // Reset graph if right clicked void driftMouseClicked(QMouseEvent *event); //void onXscaleChanged( int i ); //void onYscaleChanged( int i ); void onThresholdChanged(int i); void onInfoRateChanged(double val); void onEnableDirRA(bool enable); void onEnableDirDEC(bool enable); void syncSettings(); //void onRapidGuideChanged(bool enable); void setAxisDelta(double ra, double de); void setAxisSigma(double ra, double de); void setAxisPulse(double ra, double de); + void calibrationUpdate(GuideInterface::CalibrationUpdateType type, const QString& message = QString(""), double dx = 0, double dy = 0); void processGuideOptions(); void onControlDirectionChanged(bool enable); void updatePHD2Directions(); void showFITSViewer(); void processCaptureTimeout(); void ditherDirectly(); signals: void newLog(const QString &text); void newStatus(Ekos::GuideState status); void newStarPixmap(QPixmap &); void newProfilePixmap(QPixmap &); // Immediate deviations in arcsecs void newAxisDelta(double ra, double de); // Sigma deviations in arcsecs RMS void newAxisSigma(double ra, double de); void guideChipUpdated(ISD::CCDChip *); void driverTimedout(const QString &deviceName); private slots: void setDefaultST4(const QString &driver); void setDefaultCCD(const QString &ccd); private: void resizeEvent(QResizeEvent *event) override; /** * @brief updateGuideParams Update the guider and frame parameters due to any changes in the mount and/or ccd frame */ void updateGuideParams(); /** * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center */ void syncTrackingBoxPosition(); /** * @brief loadSettings Loads and applies all settings from KStars options */ void loadSettings(); /** * @brief saveSettings Saves all current settings into KStars options */ void saveSettings(); /** * @brief setBusy Indicate busy status within the module visually * @param enable True if module is busy, false otherwise */ void setBusy(bool enable); /** * @brief setBLOBEnabled Enable or disable BLOB reception from current CCD if using external guider * @param enable True to enable BLOB reception, false to disable BLOB reception * @param name CCD to enable to disable. If empty (default), then action is applied to all CCDs. */ void setExternalGuiderBLOBEnabled(bool enable); void handleManualDither(); // Operation stack void buildOperationStack(GuideState operation); bool executeOperationStack(); bool executeOneOperation(GuideState operation); // Init Functions void initPlots(); + void initDriftGraph(); + void initDriftPlot(); + void initCalibrationPlot(); void initView(); void initConnections(); bool captureOneFrame(); // Driver void reconnectDriver(const QString &camera, const QString &via); // Operation Stack QStack operationStack; // Devices ISD::CCD *currentCCD { nullptr }; QString lastPHD2CameraName; //This is for the configure PHD2 camera method. ISD::Telescope *currentTelescope { nullptr }; ISD::ST4 *ST4Driver { nullptr }; ISD::ST4 *AODriver { nullptr }; ISD::ST4 *GuideDriver { nullptr }; // Device Containers QList ST4List; QList CCDs; // Guider process GuideInterface *guider { nullptr }; GuiderType guiderType { GUIDE_INTERNAL }; // Star QVector3D starCenter; // Guide Params double ccdPixelSizeX { -1 }; double ccdPixelSizeY { -1 }; double aperture { -1 }; double focal_length { -1 }; double guideDeviationRA { 0 }; double guideDeviationDEC { 0 }; double pixScaleX { -1 }; double pixScaleY { -1 }; // Rapid Guide //bool rapidGuideReticleSet; // State GuideState state { GUIDE_IDLE }; // Guide timer QTime guideTimer; // Capture timeout timer QTimer captureTimeout; uint8_t m_CaptureTimeoutCounter { 0 }; uint8_t m_DeviceRestartCounter { 0 }; // Pulse Timer QTimer pulseTimer; // Log QStringList m_LogText; // Misc bool useGuideHead { false }; // Progress Activity Indicator QProgressIndicator *pi { nullptr }; // Options OpsCalibration *opsCalibration { nullptr }; OpsGuide *opsGuide { nullptr }; // Guide Frame FITSView *guideView { nullptr }; // Calibration done already? bool calibrationComplete { false }; // Was the modified frame subFramed? bool subFramed { false }; // CCD Chip frame settings QMap frameSettings; // Profile Pixmap QPixmap profilePixmap; // Flag to start auto calibration followed immediately by guiding //bool autoCalibrateGuide { false }; // Pointers of guider processes QPointer internalGuider; QPointer phd2Guider; QPointer linGuider; QPointer fv; double primaryFL = -1, primaryAperture = -1, guideFL = -1, guideAperture = -1; ISD::Telescope::Status m_MountStatus { ISD::Telescope::MOUNT_IDLE }; QCPCurve *centralTarget { nullptr }; QCPCurve *yellowTarget { nullptr }; QCPCurve *redTarget { nullptr }; QCPCurve *concentricRings { nullptr }; bool graphOnLatestPt = true; QUrl guideURLPath; //This is for enforcing the PHD2 Star lock when Guide is pressed, //autostar is not selected, and the user has chosen a star. //This connection storage is so that the connection can be disconnected after enforcement QMetaObject::Connection guideConnect; + + QCPItemText *calLabel { nullptr }; }; } diff --git a/kstars/ekos/guide/guide.ui b/kstars/ekos/guide/guide.ui index e581ebe58..48bee2e08 100644 --- a/kstars/ekos/guide/guide.ui +++ b/kstars/ekos/guide/guide.ui @@ -1,1709 +1,1744 @@ Guide 0 0 782 568 3 3 3 3 3 3 Control 3 3 3 3 3 3 Automatically select the calibration star. Auto Star Capture false Stop 22 22 22 22 Show in FITS Viewer 22 22 22 22 Clear calibration data. .. 22 22 false 22 22 22 22 Manual Dither .. 22 22 <html><head/><body><p>Subtract dark frame. If no dark frame is available, a new dark frame shall be captured and saved for future use.</p></body></html> Dark Loop false 0 0 Guide true true <html><head/><body><p>Subframe the image around the guide star. Or for PHD2, receive the Guide Star Image instead of the full image frame. For the Internal Guider, before checking this option, you must <span style=" font-weight:600;">first</span> capture an image and select a guide star. Uncheck it to take a full frame again.</p></body></html> Subframe 1 3 Exposure time in seconds Exp: Select which device receives the guiding correction commands. Via: Guide star tracking box size. Box size must be set in accordance to the selected star size. Box: false Disconnect from external guiding application. Disconnect Swap DEC direction pulses. This value is determined automatically from the calibration procedure, only override if necessary. Swap 8 16 32 64 128 Guide camera binning. It is recommended to set binning to 2x2 or higher. Bin: Qt::AlignCenter Directions Apply filter to image after capture to enhance it Effects 2 North Direction Guiding + South Direction Guiding - Guide Declination Axis DEC Guide Right Ascention Axis RA Guider: 3 60.000000000000000 1.000000000000000 -- false Connect to external guiding application. Connect 2 East Direction Guiding + West Direction Guiding - Guide Info 3 3 3 3 3 3 Scope: 0 0 <html><head/><body><p>Select which telescope to use when performing Field of View calculations.</p></body></html> Primary Scope Guide Scope 1 Guiding rate true 24 24 Mount guiding rate. Find out the guiding rate used by your mount and update the value here to get the <b>recommended</b> value of proportional gain suitable for your mount. Setting this value <b>does not</b> change your mount guiding rate. .. 24 24 true Mount guiding rate (x15"/sec) 3 0.100000000000000 2.000000000000000 0.100000000000000 <b>Recommended</b> proportional rate given the selected guiding rate. P=xxx Qt::AlignCenter Qt::Horizontal 3 Focal Focal Length (mm) QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Aperture Aperture (mm) QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Focal Ratio F/D QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter FOV Field of View (arcmin) QFrame::StyledPanel QFrame::Sunken YYxYY Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Qt::Horizontal 3 Pulse Length (ms) Guiding Delta " 1 Generated RA pulse QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Generated DEC pulse QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 Immediate Guiding RA deviation in arcseconds QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Immediate Guiding DEC deviation in arcseconds QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter RA Guiding RMS error RA RMS" QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter DEC Guiding RMS error DE RMS" QFrame::StyledPanel QFrame::Sunken xxx false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <b>Total RMS"</b> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter font-weight:bold; QFrame::StyledPanel QFrame::Sunken xxx Qt::AlignCenter Qt::Vertical 20 40 1 0 0 0 Qt::Vertical 0 0 320 240 40 30 Qt::Horizontal 2 2 Drift Graphics 1 3 3 3 3 0 Qt::Horizontal QSizePolicy::Minimum 1 20 200 200 16777215 16777215 Qt::Horizontal QSizePolicy::Minimum 1 20 <html><head/><body><p>Drag the slider to adjust the scale of the Corrections Graphs relative to the scale of the drift graphs.</p></body></html> 1 500 200 50 Qt::Vertical true false 0 0 225 200 1 - Control parameters + Control 0 0 0 0 Control Parameters 3 3 3 3 3 Qt::Horizontal 1 <html><head/><body><p>Proportional term accounts for present values of the error. For example, if the error is large and positive, the guider corrective pulse will also be large and positive.</p></body></html> Proportional gain 1 1000.000000000000000 1 1000.000000000000000 <html><head/><body><p>Integral term accounts for past values of the error. For example, if the current pulse is not sufficiently strong, the integral of the error will accumulate over time, and the guider will respond by applying a stronger action.</p></body></html> Integral gain 1 1000.000000000000000 1 1000.000000000000000 false Derivative term accounts for possible future trends of the error, based on its current rate of change. Derivative gain false 1 1000.000000000000000 false 1 1000.000000000000000 <html><head/><body><p>Maximum guide pulse that is generated by the guider and sent to the mount.</p></body></html> Maximum pulse 9999 9999 <html><head/><body><p>Minimum guide pulse that is sent to the mount. If the generated pulse is less than this value, then no pulse is sent to the mount.</p></body></html> Minimum pulse 9999 9999 false AO Limits false Maximum deviation to correct for using Adaptive Optics unit. If the guiding deviation exceeds this value, Ekos will guide the mount mechanically 1 30.000000000000000 0.500000000000000 2.000000000000000 false arcsecs Drift Plot 0 0 0 0 0 0 200 200 + + + Calibration Plot + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 200 + 200 + + + + + + 3 0 16777215 20 Graph: 2 0 <html><head/><body><p>Display the RA graph in the Drift Graphics plot.</p></body></html> RA true <html><head/><body><p>Display the RA Corrections graph in the Drift Graphics plot.</p></body></html> Corr 2 0 <html><head/><body><p>Display DEC graph in the Drift Graphics plot.</p></body></html> DEC true <html><head/><body><p>Display the DEC Corrections graph in the Drift Graphics plot.</p></body></html> Corr Trace: <html><head/><body><p>Drag the slider to scroll through guide history while displaying the RA and DEC error points on both graphs. Dragging to the far right will set the guide plots to display the latest guide data and autoscroll the graph.</p></body></html> 10 10 Qt::Horizontal <html><head/><body><p>Check to display the latest guide data and autoscroll the graph.</p></body></html> Max true 32 31 32 32 <html><head/><body><p>Autoscale both Guide Graphs to their default scale. If any points are located outside this range, the view is expanded to include them (with the exception of the time axis in the drift graphics).</p></body></html> 32 32 32 32 <html><head/><body><p>Export the guide data from the current session to a CSV file readable by a spreadsheet program.</p></body></html> 32 32 32 32 <html><head/><body><p>Clear all the recent guide data.</p></body></html> <html><head/><body><p>Set the desired guiding accuracy in the Drift Plot. The number represents the radius of the green concentric circle in arcseconds.</p></body></html> 1 20.000000000000000 0.500000000000000 2.000000000000000 QCustomPlot QWidget
auxiliary/qcustomplot.h
1
NonLinearDoubleSpinBox QDoubleSpinBox
auxiliary/nonlineardoublespinbox.h
captureB loopB showFITSViewerB guideB clearCalibrationB stopB guiderCombo ST4Combo exposureIN binningCombo boxSizeCombo filterCombo checkBox_DirRA checkBox_DirDEC swapCheck eastControlCheck westControlCheck northControlCheck southControlCheck externalConnectB externalDisconnectB FOVScopeCombo spinBox_GuideRate showRAPlotCheck showDECPlotCheck showRACorrectionsCheck showDECorrectionsCheck guideSlider correctionSlider tabWidget latestCheck guideAutoScaleGraphB guideSaveDataB guideDataClearB accuracyRadiusSpin spinBox_AOLimit spinBox_MaxPulseDEC spinBox_PropGainDEC spinBox_MinPulseDEC spinBox_MaxPulseRA spinBox_IntGainDEC spinBox_PropGainRA spinBox_IntGainRA spinBox_DerGainRA spinBox_DerGainDEC spinBox_MinPulseRA
diff --git a/kstars/ekos/guide/guideinterface.h b/kstars/ekos/guide/guideinterface.h index b28e630ac..031f92b45 100644 --- a/kstars/ekos/guide/guideinterface.h +++ b/kstars/ekos/guide/guideinterface.h @@ -1,87 +1,96 @@ /* Ekos guide class interface Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "ekos/ekos.h" #include #include #include class QString; namespace Ekos { /** * @class GuideInterface * @short Interface skeleton for implementation of different guiding applications and/or routines * * @author Jasem Mutlaq * @version 1.0 */ class GuideInterface : public QObject { Q_OBJECT public: GuideInterface() = default; virtual ~GuideInterface() override = default; virtual bool Connect() = 0; virtual bool Disconnect() = 0; virtual bool isConnected() = 0; virtual bool calibrate() = 0; virtual bool guide() = 0; virtual bool suspend() = 0; virtual bool resume() = 0; virtual bool abort() = 0; virtual bool dither(double pixels) = 0; virtual bool clearCalibration() = 0; virtual bool reacquire() { return false; } virtual bool setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength); virtual bool getGuiderParams(double *ccdPixelSizeX, double *ccdPixelSizeY, double *mountAperture, double *mountFocalLength); virtual bool setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY); virtual bool getFrameParams(uint16_t *x, uint16_t *y, uint16_t *w, uint16_t *h, uint16_t *binX, uint16_t *binY); virtual void setStarPosition(QVector3D& starCenter); + enum CalibrationUpdateType + { + RA_IN, + RA_OUT, + DEC_IN, + DEC_OUT, + CALIBRATION_MESSAGE_ONLY + }; + signals: void newLog(const QString &); void newStatus(Ekos::GuideState); void newAxisDelta(double delta_ra, double delta_dec); void newAxisSigma(double sigma_ra, double sigma_dec); void newAxisPulse(double pulse_ra, double pulse_dec); void newStarPosition(const QVector3D &newCenter, bool updateNow); void newStarPixmap(QPixmap &); - + void calibrationUpdate(CalibrationUpdateType type, const QString &message = QString(""), double x = 0, double y = 0); void frameCaptureRequested(); void guideEquipmentUpdated(); protected: Ekos::GuideState state { GUIDE_IDLE }; double ccdPixelSizeX { 0 }; double ccdPixelSizeY { 0 }; double mountAperture { 0 }; double mountFocalLength { 0 }; uint16_t subX { 0 }; uint16_t subY { 0 }; uint16_t subW { 0 }; uint16_t subH { 0 }; uint16_t subBinX { 1 }; uint16_t subBinY { 1 }; }; } diff --git a/kstars/ekos/guide/internalguide/internalguider.cpp b/kstars/ekos/guide/internalguide/internalguider.cpp index 8d09a4d00..d99e8dffe 100644 --- a/kstars/ekos/guide/internalguide/internalguider.cpp +++ b/kstars/ekos/guide/internalguide/internalguider.cpp @@ -1,1355 +1,1370 @@ /* Ekos Internal Guider Class Copyright (C) 2016 Jasem Mutlaq . Based on lin_guider This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "internalguider.h" #include "ekos_guide_debug.h" #include "gmath.h" #include "Options.h" #include "auxiliary/kspaths.h" #include "fitsviewer/fitsdata.h" #include "fitsviewer/fitsview.h" #include "ksnotification.h" #include #include #include #include #define MAX_GUIDE_STARS 10 namespace Ekos { InternalGuider::InternalGuider() { // Create math object pmath.reset(new cgmath()); connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition); state = GUIDE_IDLE; } bool InternalGuider::guide() { if (state == GUIDE_SUSPENDED) return true; if (state >= GUIDE_GUIDING) { if (m_ImageGuideEnabled) return processImageGuiding(); else return processGuiding(); } guideFrame->disconnect(this); pmath->start(); m_starLostCounter = 0; m_highRMSCounter = 0; // TODO re-enable rapid check later on #if 0 m_isStarted = true; m_useRapidGuide = ui.rapidGuideCheck->isChecked(); if (m_useRapidGuide) guideModule->startRapidGuide(); emit newStatus(Ekos::GUIDE_GUIDING); guideModule->setSuspended(false); first_frame = true; if (ui.subFrameCheck->isEnabled() && ui.subFrameCheck->isChecked() && m_isSubFramed == false) first_subframe = true; capture(); #endif m_isFirstFrame = true; state = GUIDE_GUIDING; emit newStatus(state); emit frameCaptureRequested(); return true; } bool InternalGuider::abort() { calibrationStage = CAL_IDLE; logFile.close(); if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) { if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) emit newStatus(GUIDE_DITHERING_ERROR); emit newStatus(GUIDE_ABORTED); qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state); } else { emit newStatus(GUIDE_IDLE); qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider."; } m_ProgressiveDither.clear(); m_starLostCounter = 0; m_highRMSCounter = 0; accumulator.first = accumulator.second = 0; pmath->suspend(false); state = GUIDE_IDLE; return true; } bool InternalGuider::suspend() { state = GUIDE_SUSPENDED; emit newStatus(state); pmath->suspend(true); return true; } bool InternalGuider::resume() { state = GUIDE_GUIDING; emit newStatus(state); pmath->suspend(false); emit frameCaptureRequested(); return true; } bool InternalGuider::ditherXY(double x, double y) { m_ProgressiveDither.clear(); m_DitherRetries = 0; double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); // Find out how many "jumps" we need to perform in order to get to target. // The current limit is now 1/4 of the box size to make sure the star stays within detection // threashold inside the window. double oneJump = (guideBoxSize / 4.0); double targetX = cur_x, targetY = cur_y; int xSign = (x >= cur_x) ? 1 : -1; int ySign = (y >= cur_y) ? 1 : -1; do { if (fabs(targetX - x) > oneJump) targetX += oneJump * xSign; else if (fabs(targetX - x) < oneJump) targetX = x; if (fabs(targetY - y) > oneJump) targetY += oneJump * ySign; else if (fabs(targetY - y) < oneJump) targetY = y; m_ProgressiveDither.enqueue(Vector(targetX, targetY, ret_angle)); } while (targetX != x || targetY != y); m_DitherTargetPosition = m_ProgressiveDither.dequeue(); pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, m_DitherTargetPosition.z); state = GUIDE_MANUAL_DITHERING; emit newStatus(state); processGuiding(); return true; } bool InternalGuider::dither(double pixels) { double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); pmath->getStarScreenPosition(&cur_x, &cur_y); Ekos::Matrix ROT_Z = pmath->getROTZ(); if (state != GUIDE_DITHERING) { m_DitherRetries = 0; auto seed = std::chrono::system_clock::now().time_since_epoch().count(); std::default_random_engine generator(seed); std::uniform_real_distribution angleMagnitude(0, 360); double angle = angleMagnitude(generator) * dms::DegToRad; double diff_x = pixels * cos(angle); double diff_y = pixels * sin(angle); if (pmath->declinationSwapEnabled()) diff_y *= -1; if (fabs(diff_x + accumulator.first) > MAX_DITHER_TRAVEL) diff_x *= -1.5; accumulator.first += diff_x; if (fabs(diff_y + accumulator.second) > MAX_DITHER_TRAVEL) diff_y *= -1.5; accumulator.second += diff_y; m_DitherTargetPosition = Vector(cur_x, cur_y, 0) + Vector(diff_x, diff_y, 0); qCDebug(KSTARS_EKOS_GUIDE) << "Dithering process started.. Reticle Target Pos X " << m_DitherTargetPosition.x << " Y " << m_DitherTargetPosition.y; pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, ret_angle); state = GUIDE_DITHERING; emit newStatus(state); processGuiding(); return true; } Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Dithering in progress. Diff star X:" << star_pos.x << "Y:" << star_pos.y; if (fabs(star_pos.x) < 1 && fabs(star_pos.y) < 1) { pmath->setReticleParameters(cur_x, cur_y, ret_angle); qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete."; if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { if (++m_DitherRetries > Options::ditherMaxIterations()) { if (Options::ditherFailAbortsAutoGuide()) { emit newStatus(Ekos::GUIDE_DITHERING_ERROR); abort(); return false; } else { emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case " "of dither failure.")); if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); return true; } } processGuiding(); } return true; } bool InternalGuider::processManualDithering() { double cur_x, cur_y, ret_angle; pmath->getReticleParameters(&cur_x, &cur_y, &ret_angle); pmath->getStarScreenPosition(&cur_x, &cur_y); Ekos::Matrix ROT_Z = pmath->getROTZ(); Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << star_pos.x << "Y:" << star_pos.y; if (fabs(star_pos.x) < guideBoxSize / 5.0 && fabs(star_pos.y) < guideBoxSize / 5.0) { if (m_ProgressiveDither.empty() == false) { m_DitherTargetPosition = m_ProgressiveDither.dequeue(); pmath->setReticleParameters(m_DitherTargetPosition.x, m_DitherTargetPosition.y, m_DitherTargetPosition.z); qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y; m_DitherRetries = 0; processGuiding(); return true; } if (fabs(star_pos.x) < 1 && fabs(star_pos.y) < 1) { pmath->setReticleParameters(cur_x, cur_y, ret_angle); qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete."; if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { processGuiding(); } } else { if (++m_DitherRetries > Options::ditherMaxIterations()) { emit newLog(i18n("Warning: Manual Dithering failed.")); if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); return true; } processGuiding(); } return true; } void InternalGuider::setDitherSettled() { emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); // Back to guiding state = GUIDE_GUIDING; } bool InternalGuider::calibrate() { bool ccdInfo = true, scopeInfo = true; QString errMsg; if (subW == 0 || subH == 0) { errMsg = "CCD"; ccdInfo = false; } if (mountAperture == 0.0 || mountFocalLength == 0.0) { scopeInfo = false; if (ccdInfo == false) errMsg += " & Telescope"; else errMsg += "Telescope"; } if (ccdInfo == false || scopeInfo == false) { KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg), i18n("Missing Information")); return false; } if (state != GUIDE_CALIBRATING) { calibrationStage = CAL_IDLE; state = GUIDE_CALIBRATING; emit newStatus(GUIDE_CALIBRATING); } if (calibrationStage > CAL_START) { processCalibration(); return true; } guideFrame->disconnect(this); // Must reset dec swap before we run any calibration procedure! emit DESwapChanged(false); pmath->setDeclinationSwapEnabled(false); pmath->setLostStar(false); calibrationStage = CAL_START; // automatic // If two axies (RA/DEC) are required if (Options::twoAxisEnabled()) calibrateRADECRecticle(false); else // Just RA calibrateRADECRecticle(true); return true; } void InternalGuider::processCalibration() { pmath->performProcessing(); if (pmath->isStarLost()) { emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration.")); reset(); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); - + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: Lost guide star.")); return; } switch (calibrationType) { case CAL_NONE: break; case CAL_RA_AUTO: calibrateRADECRecticle(true); break; case CAL_RA_DEC_AUTO: calibrateRADECRecticle(false); break; } } void InternalGuider::setGuideView(FITSView *guideView) { guideFrame = guideView; pmath->setGuideView(guideFrame); } void InternalGuider::reset() { state = GUIDE_IDLE; //calibrationStage = CAL_IDLE; connect(guideFrame, SIGNAL(trackingStarSelected(int, int)), this, SLOT(trackingStarSelected(int, int)), Qt::UniqueConnection); } void InternalGuider::calibrateRADECRecticle(bool ra_only) { bool axis_calibration_complete = false; Q_ASSERT(pmath); //int totalPulse = pulseDuration * Options::autoModeIterations(); if (ra_only) calibrationType = CAL_RA_AUTO; else calibrationType = CAL_RA_DEC_AUTO; switch (calibrationStage) { case CAL_START: //----- automatic mode ----- m_CalibrationParams.auto_drift_time = Options::autoModeIterations(); m_CalibrationParams.turn_back_time = m_CalibrationParams.auto_drift_time * 7; m_CalibrationParams.ra_iterations = 0; m_CalibrationParams.dec_iterations = 0; m_CalibrationParams.ra_total_pulse = m_CalibrationParams.de_total_pulse = 0; emit newLog(i18n("RA drifting forward...")); pmath->getReticleParameters(&m_CalibrationCoords.start_x1, &m_CalibrationCoords.start_y1, nullptr); m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); + emit calibrationUpdate(GuideInterface::RA_IN, i18n("Guide Star found."), 0, 0); qCDebug(KSTARS_EKOS_GUIDE) << "Auto Iteration #" << m_CalibrationParams.auto_drift_time << "Default pulse:" << m_CalibrationParams.last_pulse; qCDebug(KSTARS_EKOS_GUIDE) << "Start X1 " << m_CalibrationCoords.start_x1 << " Start Y1 " << m_CalibrationCoords.start_y1; axis_calibration_complete = false; m_CalibrationCoords.last_x = m_CalibrationCoords.start_x1; m_CalibrationCoords.last_y = m_CalibrationCoords.start_x2; emit newPulse(RA_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; calibrationStage = CAL_RA_INC; break; case CAL_RA_INC: { // Star position resulting from LAST guiding pulse to mount double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); + emit calibrationUpdate(GuideInterface::RA_IN, i18n("Calibrating RA Out"), + cur_x - m_CalibrationCoords.start_x1, cur_y - m_CalibrationCoords.start_y1); + qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.ra_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.ra_iterations << " Direction: RA_INC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; // Must pass at least 1.5 pixels to move on to the next stage if (m_CalibrationParams.ra_iterations >= m_CalibrationParams.auto_drift_time && (fabs(cur_x - m_CalibrationCoords.start_x1) > 1.5 || fabs(cur_y - m_CalibrationCoords.start_y1) > 1.5)) { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; calibrationStage = CAL_RA_DEC; m_CalibrationCoords.end_x1 = cur_x; m_CalibrationCoords.end_y1 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "End X1 " << m_CalibrationCoords.end_x1 << " End Y1 " << m_CalibrationCoords.end_y1; m_CalibrationParams.phi = pmath->calculatePhi(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1); ROT_Z = RotateZ(-M_PI * m_CalibrationParams.phi / 180.0); // derotates... m_CalibrationCoords.ra_distance = 0; m_CalibrationParams.backlash = 0; emit newPulse(RA_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; emit newLog(i18n("RA drifting reverse...")); } else if (m_CalibrationParams.ra_iterations > m_CalibrationParams.turn_back_time) { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); - + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: Drift too short.")); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); } else { // Aggressive pulse in case we're going slow if (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) { // 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; emit newPulse(RA_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; } } break; case CAL_RA_DEC: { //----- Z-check (new!) ----- double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); + emit calibrationUpdate(GuideInterface::RA_OUT, i18n("Calibrating RA In"), + cur_x - m_CalibrationCoords.start_x1, cur_y - m_CalibrationCoords.start_y1); qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.ra_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.ra_iterations << " Direction: RA_DEC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "Star x pos is " << star_pos.x << " from original point."; if (m_CalibrationCoords.ra_distance == 0.0) m_CalibrationCoords.ra_distance = star_pos.x; // start point reached... so exit if (star_pos.x < 1.5) { pmath->performProcessing(); m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); axis_calibration_complete = true; } // If we'not moving much, try increasing pulse to 200% to clear any backlash // Also increase pulse width if we are going FARTHER and not back to our original position else if ( (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) || star_pos.x > m_CalibrationCoords.ra_distance) { m_CalibrationParams.backlash++; // Increase pulse to 200% after we tried to fight against backlash 2 times at least if (m_CalibrationParams.backlash > 2) m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; else m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } else { m_CalibrationParams.ra_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); m_CalibrationParams.backlash = 0; } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; //----- Z-check end ----- if (axis_calibration_complete == false) { if (m_CalibrationParams.ra_iterations < m_CalibrationParams.turn_back_time) { emit newPulse(RA_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.ra_iterations++; break; } calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); - + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start.")); emit newLog(i18np("Guide RA: Scope cannot reach the start point after %1 iteration. Possible mount or " "backlash problems...", "GUIDE_RA: Scope cannot reach the start point after %1 iterations. Possible mount or " "backlash problems...", m_CalibrationParams.ra_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); break; } if (ra_only == false) { calibrationStage = CAL_DEC_INC; m_CalibrationCoords.start_x2 = cur_x; m_CalibrationCoords.start_y2 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Start X2 " << m_CalibrationCoords.start_x2 << " start Y2 " << m_CalibrationCoords.start_y2; emit newPulse(DEC_INC_DIR, Options::calibrationPulseDuration()); m_CalibrationParams.dec_iterations++; emit newLog(i18n("DEC drifting forward...")); break; } // calc orientation if (pmath->calculateAndSetReticle1D(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1, m_CalibrationParams.ra_total_pulse)) { calibrationStage = CAL_IDLE; emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); KSNotification::event(QLatin1String("CalibrationSuccessful"), i18n("Guiding calibration completed successfully")); } else { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); - + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: drift too short.")); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); } reset(); break; } case CAL_DEC_INC: { // Star position resulting from LAST guiding pulse to mount double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); + emit calibrationUpdate(GuideInterface::DEC_IN, i18n("Calibrating DEC Out"), + cur_x - m_CalibrationCoords.start_x2, cur_y - m_CalibrationCoords.start_y2); + qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.dec_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.dec_iterations << " Direction: DEC_INC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; if (m_CalibrationParams.dec_iterations >= m_CalibrationParams.auto_drift_time && (fabs(cur_x - m_CalibrationCoords.start_x2) > 1.5 || fabs(cur_y - m_CalibrationCoords.start_y2) > 1.5)) { calibrationStage = CAL_DEC_DEC; m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationCoords.end_x2 = cur_x; m_CalibrationCoords.end_y2 = cur_y; m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; axis_calibration_complete = false; qCDebug(KSTARS_EKOS_GUIDE) << "End X2 " << m_CalibrationCoords.end_x2 << " End Y2 " << m_CalibrationCoords.end_y2; m_CalibrationParams.phi = pmath->calculatePhi(m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, m_CalibrationCoords.end_x2, m_CalibrationCoords.end_y2); ROT_Z = RotateZ(-M_PI * m_CalibrationParams.phi / 180.0); // derotates... m_CalibrationCoords.de_distance = 0; emit newPulse(DEC_DEC_DIR, m_CalibrationParams.last_pulse); emit newLog(i18n("DEC drifting reverse...")); m_CalibrationParams.dec_iterations++; } else if (m_CalibrationParams.dec_iterations > m_CalibrationParams.turn_back_time) { calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); - + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start point.")); emit newLog(i18np("Guide DEC: Scope cannot reach the start point after %1 iteration.\nPossible mount " "or backlash problems...", "GUIDE DEC: Scope cannot reach the start point after %1 iterations.\nPossible mount " "or backlash problems...", m_CalibrationParams.dec_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); } else { if (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) { // Increase pulse by 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } m_CalibrationCoords.last_x = cur_x; m_CalibrationCoords.last_y = cur_y; emit newPulse(DEC_INC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.dec_iterations++; } } break; case CAL_DEC_DEC: { //----- Z-check (new!) ----- double cur_x, cur_y; pmath->getStarScreenPosition(&cur_x, &cur_y); + emit calibrationUpdate(GuideInterface::DEC_OUT, i18n("Calibrating DEC In"), + cur_x - m_CalibrationCoords.start_x2, cur_y - m_CalibrationCoords.start_y2); + // Star position resulting from LAST guiding pulse to mount qCDebug(KSTARS_EKOS_GUIDE) << "Iteration #" << m_CalibrationParams.dec_iterations << ": STAR " << cur_x << "," << cur_y; qCDebug(KSTARS_EKOS_GUIDE) << "Iteration " << m_CalibrationParams.dec_iterations << " Direction: DEC_DEC_DIR" << " Duration: " << m_CalibrationParams.last_pulse << " ms."; Vector star_pos = Vector(cur_x, cur_y, 0) - Vector(m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, 0); star_pos.y = -star_pos.y; star_pos = star_pos * ROT_Z; qCDebug(KSTARS_EKOS_GUIDE) << "start Pos X " << star_pos.x << " from original point."; // Keep track of distance if (m_CalibrationCoords.de_distance == 0.0) m_CalibrationCoords.de_distance = star_pos.x; // start point reached... so exit if (star_pos.x < 1.5) { pmath->performProcessing(); m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); axis_calibration_complete = true; } // Increase pulse if we're not moving much or if we are moving _away_ from target. else if ( (fabs(cur_x - m_CalibrationCoords.last_x) < 0.5 && fabs(cur_y - m_CalibrationCoords.last_y) < 0.5) || star_pos.x > m_CalibrationCoords.de_distance) { // Increase pulse by 200% m_CalibrationParams.last_pulse = Options::calibrationPulseDuration() * 2; } else { m_CalibrationParams.de_total_pulse += m_CalibrationParams.last_pulse; m_CalibrationParams.last_pulse = Options::calibrationPulseDuration(); } if (axis_calibration_complete == false) { if (m_CalibrationParams.dec_iterations < m_CalibrationParams.turn_back_time) { emit newPulse(DEC_DEC_DIR, m_CalibrationParams.last_pulse); m_CalibrationParams.dec_iterations++; break; } calibrationStage = CAL_ERROR; emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: couldn't reach start point.")); emit newLog(i18np("Guide DEC: Scope cannot reach the start point after %1 iteration.\nPossible mount " "or backlash problems...", "Guide DEC: Scope cannot reach the start point after %1 iterations.\nPossible mount " "or backlash problems...", m_CalibrationParams.dec_iterations)); KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); reset(); break; } bool swap_dec = false; // calc orientation if (pmath->calculateAndSetReticle2D(m_CalibrationCoords.start_x1, m_CalibrationCoords.start_y1, m_CalibrationCoords.end_x1, m_CalibrationCoords.end_y1, m_CalibrationCoords.start_x2, m_CalibrationCoords.start_y2, m_CalibrationCoords.end_x2, m_CalibrationCoords.end_y2, &swap_dec, m_CalibrationParams.ra_total_pulse, m_CalibrationParams.de_total_pulse)) { calibrationStage = CAL_IDLE; //fillInterface(); if (swap_dec) emit newLog(i18n("DEC swap enabled.")); else emit newLog(i18n("DEC swap disabled.")); emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS); emit DESwapChanged(swap_dec); + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Successful")); KSNotification::event(QLatin1String("CalibrationSuccessful"), i18n("Guiding calibration completed successfully")); //if (ui.autoStarCheck->isChecked()) //guideModule->selectAutoStar(); } else { emit newLog(i18n("Calibration rejected. Star drift is too short. Check for mount, cable, or backlash problems.")); + emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, i18n("Calibration Failed: drift too short.")); emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); //ui.startCalibrationLED->setColor(alertColor); calibrationStage = CAL_ERROR; KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed with errors"), KSNotification::EVENT_ALERT); } reset(); break; } default: break; } } void InternalGuider::setStarPosition(QVector3D &starCenter) { pmath->setReticleParameters(starCenter.x(), starCenter.y(), -1); } void InternalGuider::trackingStarSelected(int x, int y) { if (calibrationStage == CAL_IDLE) return; //int square_size = guide_squares[pmath->getSquareIndex()].size; pmath->setReticleParameters(x, y, -1); //pmath->moveSquare(x-square_size/(2*pmath->getBinX()), y-square_size/(2*pmath->getBinY())); //update_reticle_pos(x, y); //ui.selectStarLED->setColor(okColor); calibrationStage = CAL_START; //ui.pushButton_StartCalibration->setEnabled(true); /*QVector3D starCenter; starCenter.setX(x); starCenter.setY(y); emit newStarPosition(starCenter, true);*/ //if (ui.autoStarCheck->isChecked()) //if (Options::autoStarEnabled()) //calibrate(); } void InternalGuider::setDECSwap(bool enable) { pmath->setDeclinationSwapEnabled(enable); } void InternalGuider::setSquareAlgorithm(int index) { pmath->setSquareAlgorithm(index); } void InternalGuider::setReticleParameters(double x, double y, double angle) { pmath->setReticleParameters(x, y, angle); } bool InternalGuider::getReticleParameters(double *x, double *y, double *angle) { return pmath->getReticleParameters(x, y, angle); } bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, double mountFocalLength) { this->ccdPixelSizeX = ccdPixelSizeX; this->ccdPixelSizeY = ccdPixelSizeY; this->mountAperture = mountAperture; this->mountFocalLength = mountFocalLength; return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength); } bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY) { if (w <= 0 || h <= 0) return false; subX = x; subY = y; subW = w; subH = h; subBinX = binX; subBinY = binY; pmath->setVideoParameters(w, h, subBinX, subBinY); return true; } bool InternalGuider::processGuiding() { const cproc_out_params *out; uint32_t tick = 0; // On first frame, center the box (reticle) around the star so we do not start with an offset the results in // unnecessary guiding pulses. if (m_isFirstFrame) { if (state == GUIDE_GUIDING) { Vector star_pos = pmath->findLocalStarPosition(); pmath->setReticleParameters(star_pos.x, star_pos.y, -1); } m_isFirstFrame = false; } // calc math. it tracks square pmath->performProcessing(); if (pmath->isStarLost()) m_starLostCounter++; else m_starLostCounter = 0; // do pulse out = pmath->getOutputParameters(); bool sendPulses = true; // If within 95% of max pulse repeatedly, let's abort // if (out->pulse_length[GUIDE_RA] >= (0.95 * Options::rAMaximumPulse()) || // out->pulse_length[GUIDE_DEC] >= (0.95 * Options::dECMaximumPulse())) // { // // Stop sending pulses in case we are guiding and we already sent one high pulse before // // since we do not want to stray too much off the target to purse the guiding star // if (state == GUIDE_GUIDING && m_highPulseCounter > 0) // sendPulses = false; // m_highPulseCounter++; // } // else // m_highPulseCounter=0; double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); if (delta_rms > Options::guideMaxDeltaRMS()) { // Stop sending pulses on the 3rd time the delta RMS is high // so that we don't stray too far off the main target. if (state == GUIDE_GUIDING && m_highRMSCounter > 2) sendPulses = false; m_highRMSCounter++; } else m_highRMSCounter = 0; uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD; uint8_t abortRMSThreshold = (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD; if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold) { qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter << "m_highRMSCounter" << m_highRMSCounter << "delta_rms" << delta_rms; if (m_starLostCounter > abortStarLostThreshold) emit newLog(i18n("Lost track of the guide star. Searching for guide stars...")); else emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars...")); reacquireTimer.start(); rememberState = state; state = GUIDE_REACQUIRE; emit newStatus(state); return true; } if (sendPulses) { emit newPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC]); // Wait until pulse is over before capturing an image const int waitMS = qMax(out->pulse_length[GUIDE_RA], out->pulse_length[GUIDE_DEC]); // If less than MAX_IMMEDIATE_CAPTURE ms, then capture immediately if (waitMS > MAX_IMMEDIATE_CAPTURE) // Issue frame requests MAX_IMMEDIATE_CAPTURE ms before timeout to account for // propagation delays QTimer::singleShot(waitMS - PROPAGATION_DELAY, [&]() { emit frameCaptureRequested(); }); else emit frameCaptureRequested(); } else emit frameCaptureRequested(); if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) return true; tick = pmath->getTicks(); emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); double raPulse = out->pulse_length[GUIDE_RA]; double dePulse = out->pulse_length[GUIDE_DEC]; //If the pulse was not sent to the mount, it should have 0 value if(out->pulse_dir[GUIDE_RA] == NO_DIR) raPulse = 0; //If the pulse was not sent to the mount, it should have 0 value if(out->pulse_dir[GUIDE_DEC] == NO_DIR) dePulse = 0; //If the pulse was in the Negative direction, it should have a negative sign. if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR) raPulse = -raPulse; //If the pulse was in the Negative direction, it should have a negative sign. if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR) dePulse = -dePulse; emit newAxisPulse(raPulse, dePulse); emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); return true; } bool InternalGuider::processImageGuiding() { static int maxPulseCounter = 0; const cproc_out_params *out; uint32_t tick = 0; // calc math. it tracks square pmath->performProcessing(); if (pmath->isStarLost() && ++m_starLostCounter > 2) { emit newLog(i18n("Lost track of phase shift.")); abort(); return false; } else m_starLostCounter = 0; // do pulse out = pmath->getOutputParameters(); // If within 90% of max pulse repeatedly, let's abort if (out->pulse_length[GUIDE_RA] >= (0.9 * Options::rAMaximumPulse()) || out->pulse_length[GUIDE_DEC] >= (0.9 * Options::dECMaximumPulse())) maxPulseCounter++; else maxPulseCounter = 0; if (maxPulseCounter >= 3) { emit newLog(i18n("Lost track of phase shift. Aborting guiding...")); abort(); maxPulseCounter = 0; return false; } emit newPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC]); emit frameCaptureRequested(); if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) return true; tick = pmath->getTicks(); if (tick & 1) { // draw some params in window emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); emit newAxisPulse(out->pulse_length[GUIDE_RA], out->pulse_length[GUIDE_DEC]); emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); } return true; } bool InternalGuider::isImageGuideEnabled() const { return m_ImageGuideEnabled; } void InternalGuider::setImageGuideEnabled(bool value) { m_ImageGuideEnabled = value; pmath->setImageGuideEnabled(value); } void InternalGuider::setRegionAxis(uint32_t value) { pmath->setRegionAxis(value); } QList InternalGuider::getGuideStars() { return pmath->PSFAutoFind(); } bool InternalGuider::selectAutoStar() { FITSData *imageData = guideFrame->getImageData(); if (imageData == nullptr) return false; bool useNativeDetection = false; QList starCenters; if (Options::guideAlgorithm() != SEP_THRESHOLD) starCenters = pmath->PSFAutoFind(); if (starCenters.empty()) { if (Options::guideAlgorithm() == SEP_THRESHOLD) imageData->findStars(ALGORITHM_SEP); else imageData->findStars(); starCenters = imageData->getStarCenters(); if (starCenters.empty()) return false; useNativeDetection = true; // For SEP, prefer flux total if (Options::guideAlgorithm() == SEP_THRESHOLD) std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) { return a->val > b->val; }); else std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) { return a->width > b->width; }); guideFrame->setStarsEnabled(true); guideFrame->updateFrame(); } int maxX = imageData->width(); int maxY = imageData->height(); int scores[MAX_GUIDE_STARS]; int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count(); for (int i = 0; i < maxIndex; i++) { int score = 100; Edge *center = starCenters.at(i); if (useNativeDetection) { // Severely reject stars close to edges if (center->x < (center->width * 5) || center->y < (center->width * 5) || center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5)) score -= 1000; // Reject stars bigger than square if (center->width > float(guideBoxSize) / subBinX) score -= 1000; else { if (Options::guideAlgorithm() == SEP_THRESHOLD) score += sqrt(center->val); else // Moderately favor brighter stars score += center->width * center->width; } // Moderately reject stars close to other stars foreach (Edge *edge, starCenters) { if (edge == center) continue; if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2) { score -= 15; break; } } } else { score = center->val; } scores[i] = score; } int maxScore = -1; int maxScoreIndex = -1; for (int i = 0; i < maxIndex; i++) { if (scores[i] > maxScore) { maxScore = scores[i]; maxScoreIndex = i; } } if (maxScoreIndex < 0) { qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected."; return false; } /*if (ui.autoSquareSizeCheck->isEnabled() && ui.autoSquareSizeCheck->isChecked()) { // Select appropriate square size int idealSize = ceil(starCenters[maxScoreIndex]->width * 1.5); if (Options::guideLogging()) qDebug() << "Guide: Ideal calibration box size for star width: " << starCenters[maxScoreIndex]->width << " is " << idealSize << " pixels"; // TODO Set square size in GuideModule }*/ QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0); if (useNativeDetection == false) qDeleteAll(starCenters); emit newStarPosition(newStarCenter, true); return true; } bool InternalGuider::reacquire() { bool rc = selectAutoStar(); if (rc) { m_highRMSCounter = m_starLostCounter = 0; m_isFirstFrame = true; pmath->reset(); // If we were in the process of dithering, wait until settle and resume if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) { if (Options::ditherSettle() > 0) { state = GUIDE_DITHERING_SETTLE; emit newStatus(state); } QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); } else { state = GUIDE_GUIDING; emit newStatus(state); } } else if (reacquireTimer.elapsed() > static_cast(Options::guideLostStarTimeout() * 1000)) { emit newLog(i18n("Failed to find any suitable guide stars. Aborting...")); abort(); return false; } emit frameCaptureRequested(); return rc; } }