diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp index 6e57a2584..2c0fceba1 100644 --- a/kstars/ekos/guide/guide.cpp +++ b/kstars/ekos/guide/guide.cpp @@ -1,3375 +1,3382 @@ /* 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 "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); - // Devices - currentCCD = nullptr; - currentTelescope = nullptr; - guider = nullptr; - - // AO Driver - AODriver = nullptr; + // #3 Init Plots + initPlots(); - // ST4 Driver - GuideDriver = nullptr; + // #4 Init View + initView(); - // Subframe - subFramed = false; - - // To do calibrate + guide in one command - //autoCalibrateGuide = false; - connect(showGuideRateToolTipB, &QPushButton::clicked, [this]() - { - QToolTip::showText(showGuideRateToolTipB->mapToGlobal(QPoint(10, 10)), - showGuideRateToolTipB->toolTip(), - showGuideRateToolTipB); - }); + // #5 Load all settings + loadSettings(); - kcfg_GuideAutoStarEnabled->setChecked(Options::guideAutoStarEnabled()); - connect(kcfg_GuideAutoStarEnabled, &QCheckBox::toggled, [](bool enabled) - { - Options::setGuideAutoStarEnabled(enabled); - }); - connect(manualDitherB, &QPushButton::clicked, this, &Guide::handleManualDither); + // #6 Init Connections + initConnections(); - 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); - ccdPixelSizeX = ccdPixelSizeY = aperture = focal_length = pixScaleX = pixScaleY = -1; - guideDeviationRA = guideDeviationDEC = 0; - useGuideHead = false; - //rapidGuideReticleSet = false; - // Guiding Rate - Advisory only - connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); - // Load all settings - loadSettings(); // 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); - // 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); - } - else if (index >= 0) - { - // Disable or enable selected CCD based on options - QString ccdName = guiderCombo->currentText().remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - 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 - connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); + // 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(); - // ST4 Selection - connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) + connect(opsGuide, &OpsGuide::settingsUpdated, [this]() { - setDefaultST4(text); - setST4(text); + onThresholdChanged(Options::guideAlgorithm()); }); - // 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); + page = dialog->addPage(opsGuide, i18n("Guide")); + page->setIcon(QIcon::fromTheme("kstars_guides")); - // Declination Swap - connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); + internalGuider->setGuideView(guideView); - // PID Control - Proportional Gain - connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); + // Set current guide type + setGuiderType(-1); - // PID Control - Integral Gain - connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); + //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); +} - // PID Control - Derivative Gain - connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +Guide::~Guide() +{ + delete guider; +} - // Max Pulse Duration (ms) - connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +void Guide::handleHorizontalPlotSizeChange() +{ + driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); + driftPlot->replot(); +} - // Min Pulse Duration (ms) - connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); - connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::onInputParamChanged); +void Guide::handleVerticalPlotSizeChange() +{ + driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); + driftPlot->replot(); +} - // Capture - connect(captureB, &QPushButton::clicked, this, [this]() +void Guide::resizeEvent(QResizeEvent *event) +{ + if (event->oldSize().width() != -1) { - state = GUIDE_CAPTURE; - emit newStatus(state); + if (event->oldSize().width() != size().width()) + handleHorizontalPlotSizeChange(); + else if (event->oldSize().height() != size().height()) + handleVerticalPlotSizeChange(); + } + else + { + QTimer::singleShot(10, this, &Ekos::Guide::handleHorizontalPlotSizeChange); + } +} - capture(); - }); +void Guide::buildTarget() +{ + double accuracyRadius = accuracyRadiusSpin->value(); + Options::setGuiderAccuracyThreshold(accuracyRadius); - connect(loopB, &QPushButton::clicked, this, [this]() + if (centralTarget) { - state = GUIDE_LOOPING; - emit newStatus(state); + 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); - capture(); - }); + int circleRingPt = 0; + for (int i = 0; i < pointCount; i++) + { + double theta = i / static_cast(pointCount) * 2 * M_PI; - // Stop - connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); + 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++; + } + } + } - // Clear Calibrate - //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); - connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); + 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)); + } - // Guide - connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); + concentricRings->setLineStyle(QCPCurve::lsNone); + concentricRings->setScatterSkip(0); + concentricRings->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, QColor(255, 255, 255, 150), 1)); - // Connect External Guide - connect(externalConnectB, &QPushButton::clicked, this, [&]() - { - setBLOBEnabled(false); - guider->Connect(); - }); - connect(externalDisconnectB, &QPushButton::clicked, this, [&]() - { - setBLOBEnabled(true); - guider->Disconnect(); - }); + concentricRings->data()->set(circleRings, true); + redTarget->data()->set(circleRed, true); + yellowTarget->data()->set(circleYellow, true); + centralTarget->data()->set(circleCentral, true); - // Pulse Timer - pulseTimer.setSingleShot(true); - connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); + concentricRings->setPen(QPen(Qt::white)); + redTarget->setPen(QPen(Qt::red)); + yellowTarget->setPen(QPen(Qt::yellow)); + centralTarget->setPen(QPen(Qt::green)); - // 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); + 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))); - //Horizontal Axis Time Ticker Settings - QSharedPointer timeTicker(new QCPAxisTickerTime); - timeTicker->setTimeFormat("%m:%s"); - driftGraph->xAxis->setTicker(timeTicker); + if (driftPlot->size().width() > 0) + driftPlot->replot(); +} - //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)")); - - //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); +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(); +} - // 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); +void Guide::slotAutoScaleGraphs() +{ + double accuracyRadius = accuracyRadiusSpin->value(); - // 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); + double key = guideTimer.elapsed() / 1000.0; + driftGraph->xAxis->setRange(key - 60, key); + driftGraph->yAxis->setRange(-3, 3); + driftGraph->graph(0)->rescaleValueAxis(true); + driftGraph->replot(); - // 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)); + driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->graph(0)->rescaleAxes(true); - // 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)); + driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); + driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); - // 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); + driftPlot->replot(); +} - // 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); +void Guide::guideHistory() +{ + int sliderValue = guideSlider->value(); + latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); - //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); + 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 - connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); - connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); + //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(); - //drift plot - double accuracyRadius = 2; + if(!graphOnLatestPt) + { + QTime localTime = guideTimer; + localTime = localTime.addSecs(t); - driftPlot->setBackground(QBrush(Qt::black)); - driftPlot->setSelectionTolerance(10); + QPoint localTooltipCoordinates = driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); + QPoint globalTooltipCoordinates = driftGraph->mapToGlobal(localTooltipCoordinates); - driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); - driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); + 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. + } - 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)); +void Guide::setLatestGuidePoint(bool isChecked) +{ + graphOnLatestPt = isChecked; + if(isChecked) + guideSlider->setValue(guideSlider->maximum()); +} - driftPlot->xAxis->setTickLabelColor(Qt::white); - driftPlot->yAxis->setTickLabelColor(Qt::white); +void Guide::toggleShowRAPlot(bool isChecked) +{ + Options::setRADisplayedOnGuideGraph(isChecked); + driftGraph->graph(0)->setVisible(isChecked); + driftGraph->graph(2)->setVisible(isChecked); + driftGraph->replot(); +} - driftPlot->xAxis->setLabelColor(Qt::white); - driftPlot->yAxis->setLabelColor(Qt::white); +void Guide::toggleShowDEPlot(bool isChecked) +{ + Options::setDEDisplayedOnGuideGraph(isChecked); + driftGraph->graph(1)->setVisible(isChecked); + driftGraph->graph(3)->setVisible(isChecked); + driftGraph->replot(); +} - 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)); +void Guide::toggleRACorrectionsPlot(bool isChecked) +{ + Options::setRACorrDisplayedOnGuideGraph(isChecked); + driftGraph->graph(4)->setVisible(isChecked); + updateCorrectionsScaleVisibility(); +} - driftPlot->xAxis->setLabelPadding(2); - driftPlot->yAxis->setLabelPadding(2); +void Guide::toggleDECorrectionsPlot(bool isChecked) +{ + Options::setDECorrDisplayedOnGuideGraph(isChecked); + driftGraph->graph(5)->setVisible(isChecked); + updateCorrectionsScaleVisibility(); +} - 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)); +void Guide::updateCorrectionsScaleVisibility() +{ + bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); + driftGraph->yAxis2->setVisible(isVisible); + correctionSlider->setVisible(isVisible); + driftGraph->replot(); +} - driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); - driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); +void Guide::setCorrectionGraphScale() +{ + driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); + driftGraph->replot(); +} - driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); +void Guide::exportGuideData() +{ + int numPoints = driftGraph->graph(0)->dataCount(); + if (numPoints == 0) + return; - driftPlot->setInteractions(QCP::iRangeZoom); - driftPlot->setInteraction(QCP::iRangeDrag, true); + 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"); - driftPlot->addGraph(); - driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); - driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); + QString path = exportFile.toLocalFile(); - driftPlot->addGraph(); - driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); - driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); + 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; + } - connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); - connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); + if (!exportFile.isValid()) + { + QString message = i18n("Invalid URL: %1", exportFile.url()); + KMessageBox::sorry(KStars::Instance(), message, i18n("Invalid URL")); + return; + } - //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()); + QFile file; + file.setFileName(path); + if (!file.open(QIODevice::WriteOnly)) + { + QString message = i18n("Unable to write to file %1", path); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); + return; + } - //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(); + QTextStream outstream(&file); - buildTarget(); + outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; - //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); + 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); - driftPlot->resize(190, 190); - driftPlot->replot(); + 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(); +} - // 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(); +void Guide::addCamera(ISD::GDInterface *newCCD) +{ + ISD::CCD *ccd = static_cast(newCCD); - connect(opsGuide, &OpsGuide::settingsUpdated, [this]() + if (CCDs.contains(ccd)) + return; + if (guiderType != GUIDE_INTERNAL) { - onThresholdChanged(Options::guideAlgorithm()); - }); - - page = dialog->addPage(opsGuide, i18n("Guide")); - page->setIcon(QIcon::fromTheme("kstars_guides")); - - internalGuider->setGuideView(guideView); + connect(ccd, &ISD::CCD::newBLOBManager, [ccd](INDI::Property * prop) + { + if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) + ccd->setBLOBEnabled(Options::guideRemoteImagesEnabled(), prop->getName()); + }); + guiderCombo->clear(); + guiderCombo->setEnabled(false); + if (guiderType == GUIDE_PHD2) + guiderCombo->addItem("PHD2"); + else + guiderCombo->addItem("LinGuider"); + return; + } + else + guiderCombo->setEnabled(true); - state = GUIDE_IDLE; + CCDs.append(ccd); - // Set current guide type - setGuiderType(-1); + guiderCombo->addItem(ccd->getDeviceName()); - //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); + checkCCD(); } -Guide::~Guide() +void Guide::addGuideHead(ISD::GDInterface *newCCD) { - delete guider; + 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::handleHorizontalPlotSizeChange() +void Guide::setTelescope(ISD::GDInterface *newTelescope) { - driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); - driftPlot->replot(); + currentTelescope = dynamic_cast(newTelescope); + + syncTelescopeInfo(); } -void Guide::handleVerticalPlotSizeChange() +bool Guide::setCamera(const QString &device) { - driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); - driftPlot->replot(); + 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; } -void Guide::resizeEvent(QResizeEvent *event) +QString Guide::camera() { - 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); - } + if (currentCCD) + return currentCCD->getDeviceName(); + + return QString(); } -void Guide::buildTarget() +void Guide::checkCCD(int ccdNum) { - double accuracyRadius = accuracyRadiusSpin->value(); - Options::setGuiderAccuracyThreshold(accuracyRadius); + if (guiderType != GUIDE_INTERNAL) + return; - if (centralTarget) - { - concentricRings->data()->clear(); - redTarget->data()->clear(); - yellowTarget->data()->clear(); - centralTarget->data()->clear(); - } - else + if (ccdNum == -1) { - 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); + ccdNum = guiderCombo->currentIndex(); + + if (ccdNum == -1) + return; } - 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++) + if (ccdNum <= CCDs.count()) { - double theta = i / static_cast(pointCount) * 2 * M_PI; + currentCCD = CCDs.at(ccdNum); - for (double ring = 1; ring < 8; ring++) + 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) { - 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++; - } - } + syncCCDInfo(); + return; } - 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)); + //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); - 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)); + // If guider is external and already connected and remote images option was disabled AND it was already + // disabled, then let's go ahead and disable it. +#if 0 + if (guiderType != GUIDE_INTERNAL && Options::guideRemoteImagesEnabled() == false && guider->isConnected()) + { + for (int i = 0; i < CCDs.count(); i++) + { + ISD::CCD * oneCCD = CCDs[i]; + if (i == ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") != B_NEVER) + { + appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD1"); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD2"); + } + // If it was already disabled, enable it back + else if (i != ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") == B_NEVER) + { + appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD1"); + oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD2"); + } + } + } +#endif - 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))); + targetChip->setImageView(guideView, FITS_GUIDE); - if (driftPlot->size().width() > 0) - driftPlot->replot(); + syncCCDInfo(); + } } -void Guide::clearGuideGraphs() +void Guide::syncCCDInfo() { - 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(); -} + INumberVectorProperty *nvp = nullptr; -void Guide::slotAutoScaleGraphs() -{ - double accuracyRadius = accuracyRadiusSpin->value(); + if (currentCCD == nullptr) + return; - double key = guideTimer.elapsed() / 1000.0; - driftGraph->xAxis->setRange(key - 60, key); - driftGraph->yAxis->setRange(-3, 3); - driftGraph->graph(0)->rescaleValueAxis(true); - driftGraph->replot(); + if (useGuideHead) + nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); + else + nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); - driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - driftPlot->graph(0)->rescaleAxes(true); + if (nvp) + { + INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); + if (np) + ccdPixelSizeX = np->value; - driftPlot->yAxis->setScaleRatio(driftPlot->xAxis, 1.0); - driftPlot->xAxis->setScaleRatio(driftPlot->yAxis, 1.0); + np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); + if (np) + ccdPixelSizeY = np->value; - driftPlot->replot(); + np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); + if (np) + ccdPixelSizeY = np->value; + } + + updateGuideParams(); } -void Guide::guideHistory() +void Guide::setTelescopeInfo(double primaryFocalLength, double primaryAperture, double guideFocalLength, double guideAperture) { - int sliderValue = guideSlider->value(); - latestCheck->setChecked(sliderValue == guideSlider->maximum() - 1 || sliderValue == guideSlider->maximum()); + 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; - 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 + updateGuideParams(); +} - //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(); +void Guide::syncTelescopeInfo() +{ + if (currentTelescope == nullptr || currentTelescope->isConnected() == false) + return; - driftPlot->graph(1)->addData(ra, de); //Set guide highlighted point - driftPlot->replot(); + INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); - if(!graphOnLatestPt) + if (nvp) { - QTime localTime = guideTimer; - localTime = localTime.addSecs(t); - - QPoint localTooltipCoordinates = driftGraph->graph(0)->dataPixelPosition(sliderValue).toPoint(); - QPoint globalTooltipCoordinates = driftGraph->mapToGlobal(localTooltipCoordinates); + INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); - 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. - } + if (np && np->value > 0) + primaryAperture = np->value; - } -} + np = IUFindNumber(nvp, "GUIDER_APERTURE"); + if (np && np->value > 0) + guideAperture = np->value; -void Guide::setLatestGuidePoint(bool isChecked) -{ - graphOnLatestPt = isChecked; - if(isChecked) - guideSlider->setValue(guideSlider->maximum()); -} + aperture = primaryAperture; -void Guide::toggleShowRAPlot(bool isChecked) -{ - Options::setRADisplayedOnGuideGraph(isChecked); - driftGraph->graph(0)->setVisible(isChecked); - driftGraph->graph(2)->setVisible(isChecked); - driftGraph->replot(); -} + //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) + if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) + aperture = guideAperture; -void Guide::toggleShowDEPlot(bool isChecked) -{ - Options::setDEDisplayedOnGuideGraph(isChecked); - driftGraph->graph(1)->setVisible(isChecked); - driftGraph->graph(3)->setVisible(isChecked); - driftGraph->replot(); -} + np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); + if (np && np->value > 0) + primaryFL = np->value; -void Guide::toggleRACorrectionsPlot(bool isChecked) -{ - Options::setRACorrDisplayedOnGuideGraph(isChecked); - driftGraph->graph(4)->setVisible(isChecked); - updateCorrectionsScaleVisibility(); -} + np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); + if (np && np->value > 0) + guideFL = np->value; -void Guide::toggleDECorrectionsPlot(bool isChecked) -{ - Options::setDECorrDisplayedOnGuideGraph(isChecked); - driftGraph->graph(5)->setVisible(isChecked); - updateCorrectionsScaleVisibility(); -} + focal_length = primaryFL; -void Guide::updateCorrectionsScaleVisibility() -{ - bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph()); - driftGraph->yAxis2->setVisible(isVisible); - correctionSlider->setVisible(isVisible); - driftGraph->replot(); -} + //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) + if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) + focal_length = guideFL; + } -void Guide::setCorrectionGraphScale() -{ - driftGraph->yAxis2->setRange(driftGraph->yAxis->range().lower * correctionSlider->value(), driftGraph->yAxis->range().upper * correctionSlider->value()); - driftGraph->replot(); + updateGuideParams(); } -void Guide::exportGuideData() +void Guide::updateGuideParams() { - int numPoints = driftGraph->graph(0)->dataCount(); - if (numPoints == 0) + if (currentCCD == nullptr) 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"); + if (currentCCD->hasGuideHead() == false) + useGuideHead = false; - QString path = exportFile.toLocalFile(); + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (QFile::exists(path)) + if (targetChip == nullptr) { - 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()); - KMessageBox::sorry(KStars::Instance(), message, i18n("Invalid URL")); + appendLogText(i18n("Connection to the guide CCD is lost.")); return; } - QFile file; - file.setFileName(path); - if (!file.open(QIODevice::WriteOnly)) + binningCombo->setEnabled(targetChip->canBin()); + int subBinX = 1, subBinY = 1; + if (targetChip->canBin()) { - QString message = i18n("Unable to write to file %1", path); - KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); - return; - } + int maxBinX, maxBinY; + targetChip->getBinning(&subBinX, &subBinY); + targetChip->getMaxBin(&maxBinX, &maxBinY); - QTextStream outstream(&file); + binningCombo->blockSignals(true); - outstream << "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse (ms), DE Pulse (ms)" << endl; + binningCombo->clear(); - 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); + for (int i = 1; i <= maxBinX; i++) + binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); - QTime localTime = guideTimer; - localTime = localTime.addSecs(t); + binningCombo->setCurrentIndex(subBinX - 1); - outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' << dePulse << ',' << endl; + binningCombo->blockSignals(false); } - appendLogText(i18n("Guide Data Saved as: %1", path)); - file.close(); -} -QString Guide::setRecommendedExposureValues(QList values) -{ - exposureIN->setRecommendedValues(values); - return exposureIN->getRecommendedValuesString(); -} + 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); -void Guide::addCamera(ISD::GDInterface *newCCD) -{ - ISD::CCD *ccd = static_cast(newCCD); + QVariantMap settings; - if (CCDs.contains(ccd)) - return; - if (guiderType != GUIDE_INTERNAL) + 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) { - connect(ccd, &ISD::CCD::newBLOBManager, [ccd](INDI::Property * prop) + 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)) { - if (!strcmp(prop->getName(), "CCD1") || !strcmp(prop->getName(), "CCD2")) - ccd->setBLOBEnabled(Options::guideRemoteImagesEnabled(), prop->getName()); - }); - guiderCombo->clear(); - guiderCombo->setEnabled(false); - if (guiderType == GUIDE_PHD2) - guiderCombo->addItem("PHD2"); - else - guiderCombo->addItem("LinGuider"); - return; - } - else - guiderCombo->setEnabled(true); + guider->setFrameParams(x, y, w, h, subBinX, subBinY); + } - CCDs.append(ccd); + 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; + } - guiderCombo->addItem(ccd->getDeviceName()); + // FOV in arcmin + double fov_w = (w * pixScaleX) / 60.0; + double fov_h = (h * pixScaleY) / 60.0; - checkCCD(); + l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); + } } -void Guide::addGuideHead(ISD::GDInterface *newCCD) +void Guide::addST4(ISD::ST4 *newST4) { 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) + foreach (ISD::ST4 *guidePort, ST4List) { - guiderCombo->addItem(guiderName); - //CCDs.append(static_cast (newCCD)); + if (!strcmp(guidePort->getDeviceName(), newST4->getDeviceName())) + return; } - //checkCCD(CCDs.count()-1); - //guiderCombo->setCurrentIndex(CCDs.count()-1); - - //setGuiderProcess(Options::useEkosGuider() ? GUIDE_INTERNAL : GUIDE_PHD2); -} + ST4List.append(newST4); -void Guide::setTelescope(ISD::GDInterface *newTelescope) -{ - currentTelescope = dynamic_cast(newTelescope); + ST4Combo->addItem(newST4->getDeviceName()); - syncTelescopeInfo(); + setST4(0); } -bool Guide::setCamera(const QString &device) +bool Guide::setST4(const QString &device) { if (guiderType != GUIDE_INTERNAL) return true; - for (int i = 0; i < guiderCombo->count(); i++) - if (device == guiderCombo->itemText(i)) + for (int i = 0; i < ST4List.count(); i++) + if (ST4List.at(i)->getDeviceName() == device) { - guiderCombo->setCurrentIndex(i); - checkCCD(i); + ST4Combo->setCurrentIndex(i); + setST4(i); return true; } return false; } -QString Guide::camera() +QString Guide::st4() { - if (currentCCD) - return currentCCD->getDeviceName(); + if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) + return QString(); - return QString(); + return ST4Combo->currentText(); } -void Guide::checkCCD(int ccdNum) +void Guide::setST4(int index) { - if (guiderType != GUIDE_INTERNAL) + if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) return; - if (ccdNum == -1) - { - ccdNum = guiderCombo->currentIndex(); + ST4Driver = ST4List.at(index); - if (ccdNum == -1) - return; - } + GuideDriver = ST4Driver; +} - if (ccdNum <= CCDs.count()) +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) { - currentCCD = CCDs.at(ccdNum); + appendLogText(i18n("Error: lost connection to CCD.")); + return false; + } - if (currentCCD->hasGuideHead() && guiderCombo->currentText().contains("Guider")) - useGuideHead = true; - else - useGuideHead = false; + // If CCD Telescope Type does not match desired scope type, change it + if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) + currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); - ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip && targetChip->isCapturing()) - return; + double seqExpose = exposureIN->value(); - if (guiderType != GUIDE_INTERNAL) - { - syncCCDInfo(); - return; - } + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - //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->setCaptureMode(FITS_GUIDE); + targetChip->setFrameType(FRAME_LIGHT); - // If guider is external and already connected and remote images option was disabled AND it was already - // disabled, then let's go ahead and disable it. -#if 0 - if (guiderType != GUIDE_INTERNAL && Options::guideRemoteImagesEnabled() == false && guider->isConnected()) - { - for (int i = 0; i < CCDs.count(); i++) - { - ISD::CCD * oneCCD = CCDs[i]; - if (i == ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") != B_NEVER) - { - appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD1"); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_NEVER, oneCCD->getDeviceName(), "CCD2"); - } - // If it was already disabled, enable it back - else if (i != ccdNum && oneCCD->getDriverInfo()->getClientManager()->getBLOBMode(oneCCD->getDeviceName(), "CCD1") == B_NEVER) - { - appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD1"); - oneCCD->getDriverInfo()->getClientManager()->setBLOBMode(B_ALSO, oneCCD->getDeviceName(), "CCD2"); - } - } - } -#endif + if (darkFrameCheck->isChecked()) + targetChip->setCaptureFilter(FITS_NONE); + else + targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); - targetChip->setImageView(guideView, FITS_GUIDE); + guideView->setBaseSize(guideWidget->size()); + setBusy(true); - syncCCDInfo(); + if (frameSettings.contains(targetChip)) + { + QVariantMap settings = frameSettings[targetChip]; + targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), + settings["h"].toInt()); } -} -void Guide::syncCCDInfo() -{ - INumberVectorProperty *nvp = nullptr; +#if 0 + switch (state) + { + case GUIDE_GUIDING: + if (Options::rapidGuideEnabled() == false) + connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, &Ekos::Guide::newFITS(IBLOB *)), Qt::UniqueConnection); + targetChip->capture(seqExpose); + return true; + break; - if (currentCCD == nullptr) - return; + default: + break; + } +#endif - if (useGuideHead) - nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO"); - else - nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO"); + currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); - if (nvp) - { - INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X"); - if (np) - ccdPixelSizeX = np->value; + connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); + qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; - np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); - if (np) - ccdPixelSizeY = np->value; + double finalExposure = seqExpose; - np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y"); - if (np) - ccdPixelSizeY = np->value; - } + // 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; - updateGuideParams(); -} + // Timeout is exposure duration + timeout threshold in seconds + captureTimeout.start(finalExposure * 1000 + CAPTURE_TIMEOUT_THRESHOLD); -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; + targetChip->capture(finalExposure); - updateGuideParams(); + return true; } -void Guide::syncTelescopeInfo() +bool Guide::abort() { - if (currentTelescope == nullptr || currentTelescope->isConnected() == false) - return; + 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(); + } - INumberVectorProperty *nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO"); + manualDitherB->setEnabled(false); - if (nvp) + setBusy(false); + + switch (state) { - INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE"); + case GUIDE_IDLE: + case GUIDE_CONNECTED: + setBLOBEnabled(false); + break; + case GUIDE_DISCONNECTED: + setBLOBEnabled(true); + break; - if (np && np->value > 0) - primaryAperture = np->value; + case GUIDE_CALIBRATING: + case GUIDE_DITHERING: + case GUIDE_STAR_SELECT: + case GUIDE_CAPTURE: + case GUIDE_GUIDING: + case GUIDE_LOOPING: + guider->abort(); + break; - np = IUFindNumber(nvp, "GUIDER_APERTURE"); - if (np && np->value > 0) - guideAperture = np->value; + default: + break; + } - aperture = primaryAperture; + return true; +} - //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) - if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) - aperture = guideAperture; +void Guide::setBusy(bool enable) +{ + if (enable && pi->isAnimated()) + return; + else if (enable == false && pi->isAnimated() == false) + return; - np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH"); - if (np && np->value > 0) - primaryFL = np->value; + if (enable) + { + clearCalibrationB->setEnabled(false); + guideB->setEnabled(false); + captureB->setEnabled(false); + loopB->setEnabled(false); - np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH"); - if (np && np->value > 0) - guideFL = np->value; + darkFrameCheck->setEnabled(false); + subFrameCheck->setEnabled(false); + autoStarCheck->setEnabled(false); - focal_length = primaryFL; + stopB->setEnabled(true); - //if (currentCCD && currentCCD->getTelescopeType() == ISD::CCD::TELESCOPE_GUIDE) - if (FOVScopeCombo->currentIndex() == ISD::CCD::TELESCOPE_GUIDE) - focal_length = guideFL; + pi->startAnimation(); + + //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); } + else + { + if (guiderType == GUIDE_INTERNAL) + { + captureB->setEnabled(true); + loopB->setEnabled(true); + darkFrameCheck->setEnabled(true); + subFrameCheck->setEnabled(true); + autoStarCheck->setEnabled(true); + } - updateGuideParams(); + 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::updateGuideParams() +void Guide::processCaptureTimeout() { - if (currentCCD == nullptr) - return; - - if (currentCCD->hasGuideHead() == false) - useGuideHead = false; - - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + captureTimeoutCounter++; - if (targetChip == nullptr) + if (captureTimeoutCounter >= 3) { - appendLogText(i18n("Connection to the guide CCD is lost.")); + captureTimeoutCounter = 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; } - binningCombo->setEnabled(targetChip->canBin()); - int subBinX = 1, subBinY = 1; - if (targetChip->canBin()) - { - int maxBinX, maxBinY; - targetChip->getBinning(&subBinX, &subBinY); - targetChip->getMaxBin(&maxBinX, &maxBinY); + 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); +} - binningCombo->blockSignals(true); +void Guide::newFITS(IBLOB *bp) +{ + INDI_UNUSED(bp); - binningCombo->clear(); + captureTimeout.stop(); + captureTimeoutCounter = 0; - for (int i = 1; i <= maxBinX; i++) - binningCombo->addItem(QString("%1x%2").arg(i).arg(i)); + disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); - binningCombo->setCurrentIndex(subBinX - 1); + qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; - binningCombo->blockSignals(false); - } + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (frameSettings.contains(targetChip) == false) + int subBinX = 1, subBinY = 1; + targetChip->getBinning(&subBinX, &subBinY); + + if (starCenter.x() == 0 && starCenter.y() == 0) { - 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); + int x = 0, y = 0, w = 0, h = 0; - QVariantMap settings; + 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); - 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; + starCenter.setX(w / (2 * subBinX)); + starCenter.setY(h / (2 * subBinY)); + starCenter.setZ(subBinX); + } - frameSettings[targetChip] = settings; - } - } + syncTrackingBoxPosition(); + + setCaptureComplete(); +} + +void Guide::setCaptureComplete() +{ + if (operationStack.isEmpty() == false) + { + executeOperationStack(); + return; } - if (ccdPixelSizeX != -1 && ccdPixelSizeY != -1 && aperture != -1 && focal_length != -1) + DarkLibrary::Instance()->disconnect(this); + + switch (state) { - 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); + 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; - guider->setGuiderParams(ccdPixelSizeX, ccdPixelSizeY, aperture, focal_length); - emit guideChipUpdated(targetChip); + case GUIDE_CAPTURE: + state = GUIDE_IDLE; + emit newStatus(state); + setBusy(false); + break; - int x, y, w, h; - if (targetChip->getFrame(&x, &y, &w, &h)) - { - guider->setFrameParams(x, y, w, h, subBinX, subBinY); - } + case GUIDE_LOOPING: + capture(); + break; - 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; - } + case GUIDE_CALIBRATING: + guider->calibrate(); + break; - // FOV in arcmin - double fov_w = (w * pixScaleX) / 60.0; - double fov_h = (h * pixScaleY) / 60.0; + case GUIDE_GUIDING: + guider->guide(); + break; - l_FOV->setText(QString("%1x%2").arg(QString::number(fov_w, 'f', 1), QString::number(fov_h, 'f', 1))); - } -} + case GUIDE_DITHERING: + guider->dither(Options::ditherPixels()); + break; -void Guide::addST4(ISD::ST4 *newST4) -{ - if (guiderType != GUIDE_INTERNAL) - return; + // Feature only of internal guider + case GUIDE_MANUAL_DITHERING: + dynamic_cast(guider)->processManualDithering(); + break; - foreach (ISD::ST4 *guidePort, ST4List) - { - if (!strcmp(guidePort->getDeviceName(), newST4->getDeviceName())) - return; - } + case GUIDE_REACQUIRE: + guider->reacquire(); + break; - ST4List.append(newST4); + case GUIDE_DITHERING_SETTLE: + if (Options::ditherNoGuiding()) + return; + capture(); + break; - ST4Combo->addItem(newST4->getDeviceName()); + default: + break; + } - setST4(0); + emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } -bool Guide::setST4(const QString &device) +void Guide::appendLogText(const QString &text) { - if (guiderType != GUIDE_INTERNAL) - return true; + m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); - for (int i = 0; i < ST4List.count(); i++) - if (ST4List.at(i)->getDeviceName() == device) - { - ST4Combo->setCurrentIndex(i); - setST4(i); - return true; - } + qCInfo(KSTARS_EKOS_GUIDE) << text; - return false; + emit newLog(text); } -QString Guide::st4() +void Guide::clearLog() { - if (guiderType != GUIDE_INTERNAL || ST4Combo->currentIndex() == -1) - return QString(); - - return ST4Combo->currentText(); + m_LogText.clear(); + emit newLog(QString()); } -void Guide::setST4(int index) +void Guide::setDECSwap(bool enable) { - if (ST4List.empty() || index >= ST4List.count() || guiderType != GUIDE_INTERNAL) + if (ST4Driver == nullptr || guider == nullptr) return; - ST4Driver = ST4List.at(index); - - GuideDriver = ST4Driver; + if (guiderType == GUIDE_INTERNAL) + { + dynamic_cast(guider)->setDECSwap(enable); + ST4Driver->setDECSwap(enable); + } } -void Guide::setAO(ISD::ST4 *newAO) +bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { - AODriver = newAO; - //guider->setAO(true); -} + if (GuideDriver == nullptr || (ra_dir == NO_DIR && dec_dir == NO_DIR)) + return false; -bool Guide::capture() -{ - buildOperationStack(GUIDE_CAPTURE); + if (state == GUIDE_CALIBRATING) + pulseTimer.start((ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100); - return executeOperationStack(); + return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs); } -bool Guide::captureOneFrame() +bool Guide::sendPulse(GuideDirection dir, int msecs) { - captureTimeout.stop(); - - if (currentCCD == nullptr) + if (GuideDriver == nullptr || dir == NO_DIR) return false; - if (currentCCD->isConnected() == false) - { - appendLogText(i18n("Error: lost connection to CCD.")); - return false; - } + if (state == GUIDE_CALIBRATING) + pulseTimer.start(msecs + 100); - // If CCD Telescope Type does not match desired scope type, change it - if (currentCCD->getTelescopeType() != FOVScopeCombo->currentIndex()) - currentCCD->setTelescopeType(static_cast(FOVScopeCombo->currentIndex())); + return GuideDriver->doPulse(dir, msecs); +} - double seqExpose = exposureIN->value(); +QStringList Guide::getST4Devices() +{ + QStringList devices; - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + foreach (ISD::ST4 *driver, ST4List) + devices << driver->getDeviceName(); - targetChip->setCaptureMode(FITS_GUIDE); - targetChip->setFrameType(FRAME_LIGHT); + return devices; +} - if (darkFrameCheck->isChecked()) - targetChip->setCaptureFilter(FITS_NONE); - else - targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); +#if 0 +void Guide::processRapidStarData(ISD::CCDChip * targetChip, double dx, double dy, double fit) +{ + // Check if guide star is lost + if (dx == -1 && dy == -1 && fit == -1) + { + KMessageBox::error(nullptr, i18n("Lost track of the guide star. Rapid guide aborted.")); + guider->abort(); + return; + } - guideView->setBaseSize(guideWidget->size()); - setBusy(true); + FITSView * targetImage = targetChip->getImage(FITS_GUIDE); - if (frameSettings.contains(targetChip)) + if (targetImage == nullptr) { - QVariantMap settings = frameSettings[targetChip]; - targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), - settings["h"].toInt()); + pmath->setImageView(nullptr); + guider->setImageView(nullptr); + calibration->setImageView(nullptr); } -#if 0 - switch (state) + if (rapidGuideReticleSet == false) { - case GUIDE_GUIDING: - if (Options::rapidGuideEnabled() == false) - connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, &Ekos::Guide::newFITS(IBLOB *)), Qt::UniqueConnection); - targetChip->capture(seqExpose); - return true; - break; - - default: - break; - } -#endif - - currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); + // Let's set reticle parameter on first capture to those of the star, then we check if there + // is any set + double x, y, angle; + pmath->getReticleParameters(&x, &y, &angle); + pmath->setReticleParameters(dx, dy, angle); + rapidGuideReticleSet = true; + } - connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection); - qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame..."; + pmath->setRapidStarData(dx, dy); - 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); + if (guider->isDithering()) + { + pmath->performProcessing(); + if (guider->dither() == false) + { + appendLogText(i18n("Dithering failed. Autoguiding aborted.")); + emit newStatus(GUIDE_DITHERING_ERROR); + guider->abort(); + //emit ditherFailed(); + } + } + else + { + guider->guide(); + capture(); + } - return true; } -bool Guide::abort() +void Guide::startRapidGuide() { - if (currentCCD && guiderType == GUIDE_INTERNAL) + ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + + if (currentCCD->setRapidGuide(targetChip, true) == false) { - captureTimeout.stop(); - pulseTimer.stop(); - ISD::CCDChip *targetChip = - currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip->isCapturing()) - targetChip->abortExposure(); + appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting...")); + guider->abort(); + return; } - manualDitherB->setEnabled(false); + rapidGuideReticleSet = false; - setBusy(false); + pmath->setRapidGuide(true); + currentCCD->configureRapidGuide(targetChip, true); + connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double)), this, &Ekos::Guide::processRapidStarData(ISD::CCDChip *, double, double, double))); +} - switch (state) - { - case GUIDE_IDLE: - case GUIDE_CONNECTED: - setBLOBEnabled(false); - break; - case GUIDE_DISCONNECTED: - setBLOBEnabled(true); - break; +void Guide::stopRapidGuide() +{ + ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - case GUIDE_CALIBRATING: - case GUIDE_DITHERING: - case GUIDE_STAR_SELECT: - case GUIDE_CAPTURE: - case GUIDE_GUIDING: - case GUIDE_LOOPING: - guider->abort(); - break; + pmath->setRapidGuide(false); - default: - break; - } + rapidGuideReticleSet = false; - return true; + currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double))); + + currentCCD->configureRapidGuide(targetChip, false, false, false); + + currentCCD->setRapidGuide(targetChip, false); } +#endif -void Guide::setBusy(bool enable) +bool Guide::calibrate() { - if (enable && pi->isAnimated()) - return; - else if (enable == false && pi->isAnimated() == false) - return; + // Set status to idle and let the operations change it as they get executed + state = GUIDE_IDLE; + emit newStatus(state); - if (enable) + if (guiderType == GUIDE_INTERNAL) { - clearCalibrationB->setEnabled(false); - guideB->setEnabled(false); - captureB->setEnabled(false); - loopB->setEnabled(false); + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - darkFrameCheck->setEnabled(false); - subFrameCheck->setEnabled(false); - kcfg_GuideAutoStarEnabled->setEnabled(false); + 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; - stopB->setEnabled(true); + subFramed = false; + } + } - pi->startAnimation(); + saveSettings(); - //disconnect(guideView, SIGNAL(trackingStarSelected(int,int)), this, &Ekos::Guide::setTrackingStar(int,int))); - } - else - { - if (guiderType == GUIDE_INTERNAL) - { - captureB->setEnabled(true); - loopB->setEnabled(true); - darkFrameCheck->setEnabled(true); - subFrameCheck->setEnabled(true); - kcfg_GuideAutoStarEnabled->setEnabled(true); - } + buildOperationStack(GUIDE_CALIBRATING); - if (calibrationComplete) - clearCalibrationB->setEnabled(true); - guideB->setEnabled(true); - stopB->setEnabled(false); - pi->stopAnimation(); + executeOperationStack(); - connect(guideView, &FITSView::trackingStarSelected, this, &Ekos::Guide::setTrackingStar, Qt::UniqueConnection); - } + qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); + + return true; } -void Guide::processCaptureTimeout() +bool Guide::guide() { - captureTimeoutCounter++; - - if (captureTimeoutCounter >= 3) + if (Options::defaultCaptureCCD() == guiderCombo->currentText()) { - captureTimeoutCounter = 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 (KMessageBox::questionYesNo(nullptr, i18n("The guide camera is identical to the capture camera. Are you sure you want to continue?")) == + KMessageBox::No) + return false; } - 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); -} + if(guiderType != GUIDE_PHD2) + { + if (calibrationComplete == false) + return calibrate(); + } -void Guide::newFITS(IBLOB *bp) -{ - INDI_UNUSED(bp); + saveSettings(); - captureTimeout.stop(); - captureTimeoutCounter = 0; + bool rc = guider->guide(); - disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS); + return rc; +} - qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame."; +bool Guide::dither() +{ + if (Options::ditherNoGuiding() && state == GUIDE_IDLE) + { + ditherDirectly(); + return true; + } - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) + return true; - int subBinX = 1, subBinY = 1; - targetChip->getBinning(&subBinX, &subBinY); + //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 (starCenter.x() == 0 && starCenter.y() == 0) + if (guiderType == GUIDE_INTERNAL) { - int x = 0, y = 0, w = 0, h = 0; + if (state != GUIDE_GUIDING) + capture(); - 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); + setStatus(GUIDE_DITHERING); - starCenter.setX(w / (2 * subBinX)); - starCenter.setY(h / (2 * subBinY)); - starCenter.setZ(subBinX); + return true; } + else + return guider->dither(Options::ditherPixels()); +} - syncTrackingBoxPosition(); +bool Guide::suspend() +{ + if (state == GUIDE_SUSPENDED) + return true; + else if (state >= GUIDE_CAPTURE) + return guider->suspend(); + else + return false; +} - setCaptureComplete(); +bool Guide::resume() +{ + if (state == GUIDE_GUIDING) + return true; + else if (state == GUIDE_SUSPENDED) + return guider->resume(); + else + return false; } -void Guide::setCaptureComplete() +void Guide::setCaptureStatus(CaptureState newState) { - if (operationStack.isEmpty() == false) + switch (newState) { - executeOperationStack(); - return; - } + case CAPTURE_DITHERING: + dither(); + break; - DarkLibrary::Instance()->disconnect(this); - - 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(); + default: break; + } +} - case GUIDE_CALIBRATING: - guider->calibrate(); - break; +void Guide::setPierSide(ISD::Telescope::PierSide newSide) +{ + Q_UNUSED(newSide); - case GUIDE_GUIDING: - guider->guide(); - break; + // 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.")); + } +} - case GUIDE_DITHERING: - guider->dither(Options::ditherPixels()); - break; +void Guide::setMountStatus(ISD::Telescope::Status newState) +{ + // If we're guiding, and the mount either slews or parks, then we abort. + if ((state == GUIDE_GUIDING || state == GUIDE_DITHERING) && (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING)) + { + if (newState == ISD::Telescope::MOUNT_PARKING) + appendLogText(i18n("Mount is parking. Aborting guide...")); + else + appendLogText(i18n("Mount is slewing. Aborting guide...")); - // Feature only of internal guider - case GUIDE_MANUAL_DITHERING: - dynamic_cast(guider)->processManualDithering(); - break; + abort(); + } - case GUIDE_REACQUIRE: - guider->reacquire(); - break; + if (guiderType != GUIDE_INTERNAL) + return; - case GUIDE_DITHERING_SETTLE: - if (Options::ditherNoGuiding()) - return; - capture(); + 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: - break; + if (pi->isAnimated() == false) + { + captureB->setEnabled(true); + loopB->setEnabled(true); + clearCalibrationB->setEnabled(true); + } } - - emit newStarPixmap(guideView->getTrackingBoxPixmap(10)); } -void Guide::appendLogText(const QString &text) +void Guide::setExposure(double value) { - m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", - QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); - - qCInfo(KSTARS_EKOS_GUIDE) << text; - - emit newLog(text); + exposureIN->setValue(value); } -void Guide::clearLog() +void Guide::setImageFilter(const QString &value) { - m_LogText.clear(); - emit newLog(QString()); + for (int i = 0; i < filterCombo->count(); i++) + if (filterCombo->itemText(i) == value) + { + filterCombo->setCurrentIndex(i); + break; + } } -void Guide::setDECSwap(bool enable) +void Guide::setCalibrationTwoAxis(bool enable) { - if (ST4Driver == nullptr || guider == nullptr) - return; - - if (guiderType == GUIDE_INTERNAL) - { - dynamic_cast(guider)->setDECSwap(enable); - ST4Driver->setDECSwap(enable); - } + Options::setTwoAxisEnabled(enable); } -bool Guide::sendPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) +void Guide::setCalibrationAutoStar(bool enable) { - 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); + autoStarCheck->setChecked(enable); } -bool Guide::sendPulse(GuideDirection dir, int msecs) +void Guide::setCalibrationAutoSquareSize(bool enable) { - if (GuideDriver == nullptr || dir == NO_DIR) - return false; - - if (state == GUIDE_CALIBRATING) - pulseTimer.start(msecs + 100); + Options::setGuideAutoSquareSizeEnabled(enable); +} - return GuideDriver->doPulse(dir, msecs); +void Guide::setCalibrationPulseDuration(int pulseDuration) +{ + Options::setCalibrationPulseDuration(pulseDuration); } -QStringList Guide::getST4Devices() +void Guide::setGuideBoxSizeIndex(int index) { - QStringList devices; + Options::setGuideSquareSizeIndex(index); +} - foreach (ISD::ST4 *driver, ST4List) - devices << driver->getDeviceName(); +void Guide::setGuideAlgorithmIndex(int index) +{ + Options::setGuideAlgorithm(index); +} - return devices; +void Guide::setSubFrameEnabled(bool enable) +{ + Options::setGuideSubframeEnabled(enable); + if (subFrameCheck->isChecked() != enable) + subFrameCheck->setChecked(enable); } #if 0 -void Guide::processRapidStarData(ISD::CCDChip * targetChip, double dx, double dy, double fit) +void Guide::setGuideRapidEnabled(bool enable) { - // Check if guide star is lost - if (dx == -1 && dy == -1 && fit == -1) - { - KMessageBox::error(nullptr, i18n("Lost track of the guide star. Rapid guide aborted.")); - guider->abort(); - return; - } + //guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable); +} +#endif - FITSView * targetImage = targetChip->getImage(FITS_GUIDE); +void Guide::setDitherSettings(bool enable, double value) +{ + Options::setDitherEnabled(enable); + Options::setDitherPixels(value); +} - if (targetImage == nullptr) - { - pmath->setImageView(nullptr); - guider->setImageView(nullptr); - calibration->setImageView(nullptr); - } +#if 0 +void Guide::startAutoCalibrateGuide() +{ + // A must for auto stuff + Options::setGuideAutoStarEnabled(true); - if (rapidGuideReticleSet == false) - { - // Let's set reticle parameter on first capture to those of the star, then we check if there - // is any set - double x, y, angle; - pmath->getReticleParameters(&x, &y, &angle); - pmath->setReticleParameters(dx, dy, angle); - rapidGuideReticleSet = true; - } + if (Options::resetGuideCalibration()) + clearCalibration(); - pmath->setRapidStarData(dx, dy); + guide(); - if (guider->isDithering()) +#if 0 + if (guiderType == GUIDE_INTERNAL) { - pmath->performProcessing(); - if (guider->dither() == false) - { - appendLogText(i18n("Dithering failed. Autoguiding aborted.")); - emit newStatus(GUIDE_DITHERING_ERROR); - guider->abort(); - //emit ditherFailed(); - } + calibrationComplete = false; + autoCalibrateGuide = true; + calibrate(); } else { - guider->guide(); - capture(); + calibrationComplete = true; + autoCalibrateGuide = true; + guide(); } - +#endif } +#endif -void Guide::startRapidGuide() +void Guide::clearCalibration() { - ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - - if (currentCCD->setRapidGuide(targetChip, true) == false) - { - appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting...")); - guider->abort(); - return; - } + calibrationComplete = false; - rapidGuideReticleSet = false; + guider->clearCalibration(); - pmath->setRapidGuide(true); - currentCCD->configureRapidGuide(targetChip, true); - connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double)), this, &Ekos::Guide::processRapidStarData(ISD::CCDChip *, double, double, double))); + appendLogText(i18n("Calibration is cleared.")); } -void Guide::stopRapidGuide() +void Guide::setStatus(Ekos::GuideState newState) { - ISD::CCDChip * targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - - pmath->setRapidGuide(false); - - rapidGuideReticleSet = false; + if (newState == state) + return; - currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*, double, double, double))); + GuideState previousState = state; - currentCCD->configureRapidGuide(targetChip, false, false, false); + state = newState; + emit newStatus(state); - currentCCD->setRapidGuide(targetChip, false); -} + switch (state) + { + case GUIDE_CONNECTED: + appendLogText(i18n("External guider connected.")); + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(true); + captureB->setEnabled(false); + loopB->setEnabled(false); + clearCalibrationB->setEnabled(true); + guideB->setEnabled(true); + setBLOBEnabled(false); + 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); + setBLOBEnabled(true); +#ifdef Q_OS_OSX + repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif + break; -bool Guide::calibrate() -{ - // Set status to idle and let the operations change it as they get executed - state = GUIDE_IDLE; - emit newStatus(state); + 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; - if (guiderType == GUIDE_INTERNAL) - { - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + case GUIDE_IDLE: + case GUIDE_CALIBRATION_ERROR: + setBusy(false); + manualDitherB->setEnabled(false); + break; - 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; + case GUIDE_CALIBRATING: + appendLogText(i18n("Calibration started.")); + setBusy(true); + break; - subFramed = false; - } - } + case GUIDE_GUIDING: + if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) + appendLogText(i18n("Guiding resumed.")); + else + { + appendLogText(i18n("Autoguiding started.")); + setBusy(true); - saveSettings(); + clearGuideGraphs(); + guideTimer = QTime::currentTime(); + refreshColorScheme(); + } + manualDitherB->setEnabled(true); - buildOperationStack(GUIDE_CALIBRATING); + break; - executeOperationStack(); + case GUIDE_ABORTED: + appendLogText(i18n("Autoguiding aborted.")); + setBusy(false); + break; - qCDebug(KSTARS_EKOS_GUIDE) << "Starting calibration using CCD:" << currentCCD->getDeviceName() << "via" << ST4Combo->currentText(); + case GUIDE_SUSPENDED: + appendLogText(i18n("Guiding suspended.")); + break; - return true; + 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; + } } -bool Guide::guide() +void Guide::updateCCDBin(int index) { - if (Options::defaultCaptureCCD() == guiderCombo->currentText()) - { - if (KMessageBox::questionYesNo(nullptr, i18n("The guide camera is identical to the capture camera. Are you sure you want to continue?")) == - KMessageBox::No) - return false; - } + if (currentCCD == nullptr || guiderType != GUIDE_INTERNAL) + return; - if(guiderType != GUIDE_PHD2) - { - if (calibrationComplete == false) - return calibrate(); - } + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - saveSettings(); + targetChip->setBinning(index + 1, index + 1); - bool rc = guider->guide(); + QVariantMap settings = frameSettings[targetChip]; + settings["binx"] = index + 1; + settings["biny"] = index + 1; + frameSettings[targetChip] = settings; - return rc; + guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), + settings["binx"].toInt(), settings["biny"].toInt()); } -bool Guide::dither() +void Guide::processCCDNumber(INumberVectorProperty *nvp) { - if (Options::ditherNoGuiding() && state == GUIDE_IDLE) + if (currentCCD == nullptr || strcmp(nvp->device, currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) + return; + + if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || + (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) { - ditherDirectly(); - return true; + binningCombo->disconnect(); + binningCombo->setCurrentIndex(nvp->np[0].value - 1); + connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); } +} - if (state == GUIDE_DITHERING || state == GUIDE_DITHERING_SETTLE) - return true; +void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) +{ + if (guiderType != GUIDE_INTERNAL) + return; - //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)); + INDI_UNUSED(exposure); - if (guiderType == GUIDE_INTERNAL) + if (expState == IPS_ALERT && + ((state == GUIDE_GUIDING) || (state == GUIDE_DITHERING) || (state == GUIDE_CALIBRATING))) { - if (state != GUIDE_GUIDING) - capture(); - - setStatus(GUIDE_DITHERING); - - return true; + appendLogText(i18n("Exposure failed. Restarting exposure...")); + currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS); + targetChip->capture(exposureIN->value()); } - else - return guider->dither(Options::ditherPixels()); } -bool Guide::suspend() +void Guide::setDarkFrameEnabled(bool enable) { - if (state == GUIDE_SUSPENDED) - return true; - else if (state >= GUIDE_CAPTURE) - return guider->suspend(); - else - return false; + Options::setGuideDarkFrameEnabled(enable); + if (darkFrameCheck->isChecked() != enable) + darkFrameCheck->setChecked(enable); } -bool Guide::resume() +void Guide::saveDefaultGuideExposure() { - if (state == GUIDE_GUIDING) - return true; - else if (state == GUIDE_SUSPENDED) - return guider->resume(); - else - return false; + Options::setGuideExposure(exposureIN->value()); + if(guiderType == GUIDE_PHD2) + phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); } -void Guide::setCaptureStatus(CaptureState newState) +void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) { - switch (newState) - { - case CAPTURE_DITHERING: - dither(); - break; + starCenter.setX(newCenter.x()); + starCenter.setY(newCenter.y()); + if (newCenter.z() > 0) + starCenter.setZ(newCenter.z()); - default: - break; - } + if (updateNow) + syncTrackingBoxPosition(); } -void Guide::setPierSide(ISD::Telescope::PierSide newSide) +void Guide::syncTrackingBoxPosition() { - Q_UNUSED(newSide); + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + Q_ASSERT(targetChip); - // 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) -{ - // If we're guiding, and the mount either slews or parks, then we abort. - if ((state == GUIDE_GUIDING || state == GUIDE_DITHERING) && (newState == ISD::Telescope::MOUNT_PARKING || newState == ISD::Telescope::MOUNT_SLEWING)) - { - 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; + int subBinX = 1, subBinY = 1; + targetChip->getBinning(&subBinX, &subBinY); - switch (newState) + if (starCenter.isNull() == false) { - 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; + 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; + } - default: - if (pi->isAnimated() == false) + // If binning changed, update coords accordingly + if (subBinX != starCenter.z()) + { + if (starCenter.z() > 0) { - captureB->setEnabled(true); - loopB->setEnabled(true); - clearCalibrationB->setEnabled(true); + starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); + starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); } - } -} - -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; + starCenter.setZ(subBinX); } -} - -void Guide::setCalibrationTwoAxis(bool enable) -{ - Options::setTwoAxisEnabled(enable); -} -void Guide::setCalibrationAutoStar(bool enable) -{ - kcfg_GuideAutoStarEnabled->setChecked(enable); + QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), + boxSize / subBinX, boxSize / subBinY); + guideView->setTrackingBoxEnabled(true); + guideView->setTrackingBox(starRect); + } } -void Guide::setCalibrationAutoSquareSize(bool enable) +bool Guide::setGuiderType(int type) { - Options::setGuideAutoSquareSizeEnabled(enable); -} + // Use default guider option + if (type == -1) + type = Options::guiderType(); + else if (type == guiderType) + return true; -void Guide::setCalibrationPulseDuration(int pulseDuration) -{ - Options::setCalibrationPulseDuration(pulseDuration); -} + if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) + { + appendLogText(i18n("Cannot change guider type while active.")); + return false; + } -void Guide::setGuideBoxSizeIndex(int index) -{ - Options::setGuideSquareSizeIndex(index); -} + if (guider != nullptr) + { + // Disconnect from host + if (guider->isConnected()) + guider->Disconnect(); -void Guide::setGuideAlgorithmIndex(int index) -{ - Options::setGuideAlgorithm(index); -} + // Disconnect signals + guider->disconnect(); + } -void Guide::setSubFrameEnabled(bool enable) -{ - Options::setGuideSubframeEnabled(enable); - if (subFrameCheck->isChecked() != enable) - subFrameCheck->setChecked(enable); -} + guiderType = static_cast(type); -#if 0 -void Guide::setGuideRapidEnabled(bool enable) -{ - //guider->setGuideOptions(guider->getAlgorithm(), guider->useSubFrame() , enable); -} -#endif + 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 &))); -void Guide::setDitherSettings(bool enable, double value) -{ - Options::setDitherEnabled(enable); - Options::setDitherPixels(value); -} + guider = internalGuider; -#if 0 -void Guide::startAutoCalibrateGuide() -{ - // A must for auto stuff - Options::setGuideAutoStarEnabled(true); + internalGuider->setSquareAlgorithm(opsGuide->kcfg_GuideAlgorithm->currentIndex()); + internalGuider->setRegionAxis(opsGuide->kcfg_GuideRegionAxis->currentText().toInt()); - if (Options::resetGuideCalibration()) - clearCalibration(); + clearCalibrationB->setEnabled(true); + guideB->setEnabled(true); + captureB->setEnabled(true); + loopB->setEnabled(true); + darkFrameCheck->setEnabled(true); + subFrameCheck->setEnabled(true); + autoStarCheck->setEnabled(true); - guide(); + guiderCombo->setEnabled(true); + ST4Combo->setEnabled(true); + exposureIN->setEnabled(true); + binningCombo->setEnabled(true); + boxSizeCombo->setEnabled(true); + filterCombo->setEnabled(true); -#if 0 - if (guiderType == GUIDE_INTERNAL) - { - calibrationComplete = false; - autoCalibrateGuide = true; - calibrate(); - } - else - { - calibrationComplete = true; - autoCalibrateGuide = true; - guide(); - } -#endif -} -#endif + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(false); -void Guide::clearCalibration() -{ - calibrationComplete = 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); - guider->clearCalibration(); + guiderCombo->setToolTip(i18n("Select guide camera.")); - appendLogText(i18n("Calibration is cleared.")); -} + updateGuideParams(); + } + break; -void Guide::setStatus(Ekos::GuideState newState) -{ - if (newState == state) - return; + case GUIDE_PHD2: + if (phd2Guider.isNull()) + phd2Guider = new PHD2(); - GuideState previousState = state; + guider = phd2Guider; + phd2Guider->setGuideView(guideView); - state = newState; - emit newStatus(state); + connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); - switch (state) - { - case GUIDE_CONNECTED: - appendLogText(i18n("External guider connected.")); - externalConnectB->setEnabled(false); - externalDisconnectB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); clearCalibrationB->setEnabled(true); - guideB->setEnabled(true); - setBLOBEnabled(false); - 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); - setBLOBEnabled(true); -#ifdef Q_OS_OSX - repaint(); //This is a band-aid for a bug in QT 5.10.0 -#endif - break; + 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); - 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; + checkBox_DirRA->setEnabled(false); + eastControlCheck->setEnabled(false); + westControlCheck->setEnabled(false); + swapCheck->setEnabled(false); - case GUIDE_IDLE: - case GUIDE_CALIBRATION_ERROR: - setBusy(false); - manualDitherB->setEnabled(false); - break; - case GUIDE_CALIBRATING: - appendLogText(i18n("Calibration started.")); - setBusy(true); - break; + 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); - case GUIDE_GUIDING: - if (previousState == GUIDE_SUSPENDED || previousState == GUIDE_DITHERING_SUCCESS) - appendLogText(i18n("Guiding resumed.")); - else - { - appendLogText(i18n("Autoguiding started.")); - setBusy(true); + ST4Combo->setEnabled(false); + exposureIN->setEnabled(true); + binningCombo->setEnabled(false); + boxSizeCombo->setEnabled(false); + filterCombo->setEnabled(false); - clearGuideGraphs(); - guideTimer = QTime::currentTime(); - refreshColorScheme(); + if (Options::guideRemoteImagesEnabled() == false) + { + //guiderCombo->setCurrentIndex(-1); + guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); } - manualDitherB->setEnabled(true); + else + guiderCombo->setEnabled(false); - break; + if (Options::resetGuideCalibration()) + appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); - case GUIDE_ABORTED: - appendLogText(i18n("Autoguiding aborted.")); - setBusy(false); + updateGuideParams(); break; - case GUIDE_SUSPENDED: - appendLogText(i18n("Guiding suspended.")); - break; + case GUIDE_LINGUIDER: + if (linGuider.isNull()) + linGuider = new LinGuider(); - case GUIDE_REACQUIRE: - capture(); - break; + guider = linGuider; - case GUIDE_MANUAL_DITHERING: - appendLogText(i18n("Manual dithering in progress.")); - break; + clearCalibrationB->setEnabled(true); + captureB->setEnabled(false); + loopB->setEnabled(false); + darkFrameCheck->setEnabled(false); + subFrameCheck->setEnabled(false); + autoStarCheck->setEnabled(false); + guideB->setEnabled(true); + externalConnectB->setEnabled(true); - case GUIDE_DITHERING: - appendLogText(i18n("Dithering in progress.")); - break; + controlGroup->setEnabled(false); + infoGroup->setEnabled(false); + driftGraphicsGroup->setEnabled(false); - 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; + ST4Combo->setEnabled(false); + exposureIN->setEnabled(false); + binningCombo->setEnabled(false); + boxSizeCombo->setEnabled(false); + filterCombo->setEnabled(false); - case GUIDE_DITHERING_ERROR: - appendLogText(i18n("Dithering failed.")); - // LinGuider guide continue after dithering failure - if (guiderType != GUIDE_LINGUIDER) + if (Options::guideRemoteImagesEnabled() == false) { - //state = GUIDE_IDLE; - state = GUIDE_ABORTED; - setBusy(false); + guiderCombo->setCurrentIndex(-1); + guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); } - break; + else + guiderCombo->setEnabled(false); + + updateGuideParams(); - 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; + 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); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + 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); + } - targetChip->setBinning(index + 1, index + 1); + externalConnectB->setEnabled(false); + externalDisconnectB->setEnabled(false); - QVariantMap settings = frameSettings[targetChip]; - settings["binx"] = index + 1; - settings["biny"] = index + 1; - frameSettings[targetChip] = settings; + if (guider != nullptr && guiderType != GUIDE_INTERNAL) + { + externalConnectB->setEnabled(!guider->isConnected()); + externalDisconnectB->setEnabled(guider->isConnected()); + } - guider->setFrameParams(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), settings["h"].toInt(), - settings["binx"].toInt(), settings["biny"].toInt()); + if (guider != nullptr) + guider->Connect(); + + return true; } -void Guide::processCCDNumber(INumberVectorProperty *nvp) +void Guide::updateTrackingBoxSize(int currentIndex) { - if (currentCCD == nullptr || strcmp(nvp->device, currentCCD->getDeviceName()) || guiderType != GUIDE_INTERNAL) - return; - - if ((!strcmp(nvp->name, "CCD_BINNING") && useGuideHead == false) || - (!strcmp(nvp->name, "GUIDER_BINNING") && useGuideHead)) + if (currentIndex >= 0) { - binningCombo->disconnect(); - binningCombo->setCurrentIndex(nvp->np[0].value - 1); - connect(binningCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::updateCCDBin); + Options::setGuideSquareSizeIndex(currentIndex); + + if (guiderType == GUIDE_INTERNAL) + dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); + + syncTrackingBoxPosition(); } } -void Guide::checkExposureValue(ISD::CCDChip *targetChip, double exposure, IPState expState) + +/* +void Guide::onXscaleChanged( int i ) { - if (guiderType != GUIDE_INTERNAL) - return; + int rx, ry; - INDI_UNUSED(exposure); + driftGraphics->getVisibleRanges( &rx, &ry ); + driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); + driftGraphics->update(); - 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) +void Guide::onYscaleChanged( int i ) { - Options::setGuideDarkFrameEnabled(enable); - if (darkFrameCheck->isChecked() != enable) - darkFrameCheck->setChecked(enable); + int rx, ry; + + driftGraphics->getVisibleRanges( &rx, &ry ); + driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); + driftGraphics->update(); } +*/ -void Guide::saveDefaultGuideExposure() +void Guide::onThresholdChanged(int index) { - Options::setGuideExposure(exposureIN->value()); - if(guiderType == GUIDE_PHD2) - phd2Guider->requestSetExposureTime(exposureIN->value() * 1000); + switch (guiderType) + { + case GUIDE_INTERNAL: + dynamic_cast(guider)->setSquareAlgorithm(index); + break; + + default: + break; + } } -void Guide::setStarPosition(const QVector3D &newCenter, bool updateNow) +void Guide::onInfoRateChanged(double val) { - starCenter.setX(newCenter.x()); - starCenter.setY(newCenter.y()); - if (newCenter.z() > 0) - starCenter.setZ(newCenter.z()); + Options::setGuidingRate(val); - if (updateNow) - syncTrackingBoxPosition(); + 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::syncTrackingBoxPosition() +void Guide::onEnableDirRA(bool enable) { - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - Q_ASSERT(targetChip); + Options::setRAGuideEnabled(enable); +} - int subBinX = 1, subBinY = 1; - targetChip->getBinning(&subBinX, &subBinY); +void Guide::onEnableDirDEC(bool enable) +{ + Options::setDECGuideEnabled(enable); + updatePHD2Directions(); +} - 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)); - } +void Guide::syncSettings() +{ + QSpinBox *pSB = nullptr; + QDoubleSpinBox *pDSB = nullptr; + QCheckBox *pCB = nullptr; - starCenter.setZ(subBinX); - } + QObject *obj = sender(); - QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), - boxSize / subBinX, boxSize / subBinY); - guideView->setTrackingBoxEnabled(true); - guideView->setTrackingBox(starRect); + 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()); } } -bool Guide::setGuiderType(int type) +void Guide::onControlDirectionChanged(bool enable) { - // Use default guider option - if (type == -1) - type = Options::guiderType(); - else if (type == guiderType) - return true; + QObject *obj = sender(); - if (state == GUIDE_CALIBRATING || state == GUIDE_GUIDING || state == GUIDE_DITHERING) + if (northControlCheck == dynamic_cast(obj)) { - appendLogText(i18n("Cannot change guider type while active.")); - return false; + Options::setNorthDECGuideEnabled(enable); + updatePHD2Directions(); } - - if (guider != nullptr) + else if (southControlCheck == dynamic_cast(obj)) { - // Disconnect from host - if (guider->isConnected()) - guider->Disconnect(); - - // Disconnect signals - guider->disconnect(); + Options::setSouthDECGuideEnabled(enable); + updatePHD2Directions(); } - - guiderType = static_cast(type); - - switch (type) + else if (westControlCheck == dynamic_cast(obj)) { - 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); - kcfg_GuideAutoStarEnabled->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); + 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); - connect(phd2Guider, SIGNAL(newStarPixmap(QPixmap &)), this, SIGNAL(newStarPixmap(QPixmap &))); + 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); + } - clearCalibrationB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); - darkFrameCheck->setEnabled(false); - subFrameCheck->setEnabled(false); - kcfg_GuideAutoStarEnabled->setEnabled(false); - guideB->setEnabled(false); //This will be enabled later when equipment connects (or not) - externalConnectB->setEnabled(false); + //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); +} - checkBox_DirRA->setEnabled(false); - eastControlCheck->setEnabled(false); - westControlCheck->setEnabled(false); - swapCheck->setEnabled(false); +#if 0 +void Guide::onRapidGuideChanged(bool enable) +{ + if (m_isStarted) + { + guideModule->appendLogText(i18n("You must stop auto guiding before changing this setting.")); + return; + } + m_useRapidGuide = enable; - 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); + if (m_useRapidGuide) + { + guideModule->appendLogText(i18n("Rapid Guiding is enabled. Guide star will be determined automatically by the CCD driver. No frames are sent to Ekos unless explicitly enabled by the user in the CCD driver settings.")); + } + else + guideModule->appendLogText(i18n("Rapid Guiding is disabled.")); +} +#endif - ST4Combo->setEnabled(false); - exposureIN->setEnabled(true); - binningCombo->setEnabled(false); - boxSizeCombo->setEnabled(false); - filterCombo->setEnabled(false); +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()); +} - if (Options::guideRemoteImagesEnabled() == false) - { - //guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); - } - else - guiderCombo->setEnabled(false); +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()); +} - if (Options::resetGuideCalibration()) - appendLogText(i18n("Warning: Reset Guiding Calibration is enabled. It is recommended to turn this option off for PHD2.")); +void Guide::setTrackingStar(int x, int y) +{ + QVector3D newStarPosition(x, y, -1); + setStarPosition(newStarPosition, true); - updateGuideParams(); - break; + /*if (state == GUIDE_STAR_SELECT) + { + guider->setStarPosition(newStarPosition); + guider->calibrate(); + }*/ - case GUIDE_LINGUIDER: - if (linGuider.isNull()) - linGuider = new LinGuider(); + if (operationStack.isEmpty() == false) + executeOperationStack(); +} - guider = linGuider; +void Guide::setAxisDelta(double ra, double de) +{ + // Time since timer started. + double key = guideTimer.elapsed() / 1000.0; - clearCalibrationB->setEnabled(true); - captureB->setEnabled(false); - loopB->setEnabled(false); - darkFrameCheck->setEnabled(false); - subFrameCheck->setEnabled(false); - kcfg_GuideAutoStarEnabled->setEnabled(false); - guideB->setEnabled(true); - externalConnectB->setEnabled(true); + ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. - controlGroup->setEnabled(false); - infoGroup->setEnabled(false); - driftGraphicsGroup->setEnabled(false); + driftGraph->graph(0)->addData(key, ra); + driftGraph->graph(1)->addData(key, de); - ST4Combo->setEnabled(false); - exposureIN->setEnabled(false); - binningCombo->setEnabled(false); - boxSizeCombo->setEnabled(false); - filterCombo->setEnabled(false); + int currentNumPoints = driftGraph->graph(0)->dataCount(); + guideSlider->setMaximum(currentNumPoints); + if(graphOnLatestPt) + guideSlider->setValue(currentNumPoints); - if (Options::guideRemoteImagesEnabled() == false) - { - guiderCombo->setCurrentIndex(-1); - guiderCombo->setToolTip(i18n("Select a camera to disable remote streaming.")); - } - else - guiderCombo->setEnabled(false); + // Expand range if it doesn't fit already + if (driftGraph->yAxis->range().contains(ra) == false) + driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); - updateGuideParams(); + if (driftGraph->yAxis->range().contains(de) == false) + driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); - break; + // 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(); - if (guider != nullptr) + //Add to Drift Plot + driftPlot->graph(0)->addData(ra, de); + if(graphOnLatestPt) { - 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); + driftPlot->graph(1)->data()->clear(); //Clear highlighted point + driftPlot->graph(1)->addData(ra, de); //Set highlighted point to latest point } - externalConnectB->setEnabled(false); - externalDisconnectB->setEnabled(false); - - if (guider != nullptr && guiderType != GUIDE_INTERNAL) + if (driftPlot->xAxis->range().contains(ra) == false || driftPlot->yAxis->range().contains(de) == false) { - externalConnectB->setEnabled(!guider->isConnected()); - externalDisconnectB->setEnabled(guider->isConnected()); + driftPlot->setBackground(QBrush(Qt::gray)); + QTimer::singleShot(300, this, [ = ]() + { + driftPlot->setBackground(QBrush(Qt::black)); + driftPlot->replot(); + }); } - if (guider != nullptr) - guider->Connect(); + driftPlot->replot(); - return true; + 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::updateTrackingBoxSize(int currentIndex) +void Guide::setAxisSigma(double ra, double de) { - if (currentIndex >= 0) - { - Options::setGuideSquareSizeIndex(currentIndex); + l_ErrRA->setText(QString::number(ra, 'f', 2)); + l_ErrDEC->setText(QString::number(de, 'f', 2)); + l_TotalRMS->setText(QString::number(sqrt(ra * ra + de * de), 'f', 2)); + emit newAxisSigma(ra, de); +} - if (guiderType == GUIDE_INTERNAL) - dynamic_cast(guider)->setGuideBoxSize(boxSizeCombo->currentText().toInt()); +QList Guide::axisDelta() +{ + QList delta; - syncTrackingBoxPosition(); - } -} + delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); + return delta; +} -/* -void Guide::onXscaleChanged( int i ) +QList Guide::axisSigma() { - int rx, ry; + QList sigma; - driftGraphics->getVisibleRanges( &rx, &ry ); - driftGraphics->setVisibleRanges( i*driftGraphics->getGridN(), ry ); - driftGraphics->update(); + sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); + return sigma; } -void Guide::onYscaleChanged( int i ) +void Guide::setAxisPulse(double ra, double de) { - int rx, ry; + l_PulseRA->setText(QString::number(static_cast(ra))); + l_PulseDEC->setText(QString::number(static_cast(de))); - driftGraphics->getVisibleRanges( &rx, &ry ); - driftGraphics->setVisibleRanges( rx, i*driftGraphics->getGridN() ); - driftGraphics->update(); + double key = guideTimer.elapsed() / 1000.0; + + driftGraph->graph(4)->addData(key, ra); + driftGraph->graph(5)->addData(key, de); } -*/ -void Guide::onThresholdChanged(int index) +void Guide::refreshColorScheme() { - switch (guiderType) + // Drift color legend + if (driftGraph) { - case GUIDE_INTERNAL: - dynamic_cast(guider)->setSquareAlgorithm(index); - break; + 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)); - default: - break; + 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::onInfoRateChanged(double val) +void Guide::driftMouseClicked(QMouseEvent *event) { - Options::setGuidingRate(val); + if (event->buttons() & Qt::RightButton) + { + driftGraph->yAxis->setRange(-3, 3); + } +} - double gain = 0; +void Guide::driftMouseOverLine(QMouseEvent *event) +{ + double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); - if (val > 0.01) - gain = 1000.0 / (val * 15.0); + if (driftGraph->xAxis->range().contains(key)) + { + QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); - l_RecommendedGain->setText(i18n("P: %1", QString().setNum(gain, 'f', 2))); -} + if (graph) + { + int raIndex = driftGraph->graph(0)->findBegin(key); + int deIndex = driftGraph->graph(1)->findBegin(key); -void Guide::onEnableDirRA(bool enable) -{ - Options::setRAGuideEnabled(enable); -} + double raDelta = driftGraph->graph(0)->dataMainValue(raIndex); + double deDelta = driftGraph->graph(1)->dataMainValue(deIndex); -void Guide::onEnableDirDEC(bool enable) -{ - Options::setDECGuideEnabled(enable); - updatePHD2Directions(); -} + 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 -void Guide::onInputParamChanged() -{ - QSpinBox *pSB; - QDoubleSpinBox *pDSB; + // Compute time value: + QTime localTime = guideTimer; - QObject *obj = sender(); + localTime = localTime.addSecs(key); - if ((pSB = dynamic_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 = dynamic_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()); + 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::onControlDirectionChanged(bool enable) +void Guide::buildOperationStack(GuideState operation) { - QObject *obj = sender(); + operationStack.clear(); - 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)) + switch (operation) { - 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); + 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); + } - 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); - } + operationStack.push(GUIDE_CAPTURE); - //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); -} + // 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); -#if 0 -void Guide::onRapidGuideChanged(bool enable) -{ - if (m_isStarted) - { - guideModule->appendLogText(i18n("You must stop auto guiding before changing this setting.")); - return; - } + // Subframe if required + operationStack.push(GUIDE_SUBFRAME); - m_useRapidGuide = enable; + } - if (m_useRapidGuide) - { - guideModule->appendLogText(i18n("Rapid Guiding is enabled. Guide star will be determined automatically by the CCD driver. No frames are sent to Ekos unless explicitly enabled by the user in the CCD driver settings.")); + // First capture an image + operationStack.push(GUIDE_CAPTURE); + } + + } + break; + + default: + break; } - else - guideModule->appendLogText(i18n("Rapid Guiding is disabled.")); } -#endif -void Guide::loadSettings() +bool Guide::executeOperationStack() { - // 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()); -} + if (operationStack.isEmpty()) + return false; -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()); -} + GuideState nextOperation = operationStack.pop(); -void Guide::setTrackingStar(int x, int y) -{ - QVector3D newStarPosition(x, y, -1); - setStarPosition(newStarPosition, true); + bool actionRequired = false; - /*if (state == GUIDE_STAR_SELECT) + switch (nextOperation) { - guider->setStarPosition(newStarPosition); - guider->calibrate(); - }*/ + case GUIDE_SUBFRAME: + actionRequired = executeOneOperation(nextOperation); + break; - if (operationStack.isEmpty() == false) - executeOperationStack(); -} + case GUIDE_DARK: + actionRequired = executeOneOperation(nextOperation); + break; -void Guide::setAxisDelta(double ra, double de) -{ - // Time since timer started. - double key = guideTimer.elapsed() / 1000.0; + case GUIDE_CAPTURE: + actionRequired = captureOneFrame(); + break; - ra = -ra; //The ra is backwards in sign from how it should be displayed on the graph. + case GUIDE_STAR_SELECT: + actionRequired = executeOneOperation(nextOperation); + break; - driftGraph->graph(0)->addData(key, ra); - driftGraph->graph(1)->addData(key, de); + case GUIDE_CALIBRATING: + if (guiderType == GUIDE_INTERNAL) + { + guider->setStarPosition(starCenter); + dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); - int currentNumPoints = driftGraph->graph(0)->dataCount(); - guideSlider->setMaximum(currentNumPoints); - if(graphOnLatestPt) - guideSlider->setValue(currentNumPoints); + // No need to calibrate + if (Options::imageGuidingEnabled()) + { + setStatus(GUIDE_CALIBRATION_SUCESS); + break; + } - // Expand range if it doesn't fit already - if (driftGraph->yAxis->range().contains(ra) == false) - driftGraph->yAxis->setRange(-1.25 * ra, 1.25 * ra); + // Tracking must be engaged + if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) + currentTelescope->setTrackEnabled(true); + } - if (driftGraph->yAxis->range().contains(de) == false) - driftGraph->yAxis->setRange(-1.25 * de, 1.25 * de); + 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; - // 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 + default: + break; } - 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 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); - if (driftPlot->xAxis->range().contains(ra) == false || driftPlot->yAxis->range().contains(de) == false) + switch (operation) { - driftPlot->setBackground(QBrush(Qt::gray)); - QTimer::singleShot(300, this, [ = ]() + case GUIDE_SUBFRAME: { - driftPlot->setBackground(QBrush(Qt::black)); - driftPlot->replot(); - }); - } - - driftPlot->replot(); + // 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); - l_DeltaRA->setText(QString::number(ra, 'f', 2)); - l_DeltaDEC->setText(QString::number(de, 'f', 2)); + int offset = boxSizeCombo->currentText().toInt() / subBinX; - emit newAxisDelta(ra, de); + int x = starCenter.x(); + int y = starCenter.y(); - profilePixmap = driftGraph->grab(); - emit newProfilePixmap(profilePixmap); -} + x = (x - offset * 2) * subBinX; + y = (y - offset * 2) * subBinY; + int w = offset * 4 * subBinX; + int h = offset * 4 * subBinY; -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(sqrt(ra * ra + de * de), 'f', 2)); - emit newAxisSigma(ra, de); -} + if (x < minX) + x = minX; + if (y < minY) + y = minY; + if ((x + w) > maxW) + w = maxW - x; + if ((y + h) > maxH) + h = maxH - y; -QList Guide::axisDelta() -{ - QList delta; + targetChip->setFrame(x, y, w, h); - delta << l_DeltaRA->text().toDouble() << l_DeltaDEC->text().toDouble(); + subFramed = true; + QVariantMap settings = frameSettings[targetChip]; + settings["x"] = x; + settings["y"] = y; + settings["w"] = w; + settings["h"] = h; + settings["binx"] = subBinX; + settings["biny"] = subBinY; - return delta; -} + frameSettings[targetChip] = settings; -QList Guide::axisSigma() -{ - QList sigma; + 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(); - sigma << l_ErrRA->text().toDouble() << l_ErrDEC->text().toDouble(); + int x, y, w, h; + targetChip->getFrame(&x, &y, &w, &h); - return sigma; -} + QVariantMap settings; + settings["x"] = x; + settings["y"] = y; + settings["w"] = w; + settings["h"] = h; + settings["binx"] = 1; + settings["biny"] = 1; + frameSettings[targetChip] = settings; -void Guide::setAxisPulse(double ra, double de) -{ - l_PulseRA->setText(QString::number(static_cast(ra))); - l_PulseDEC->setText(QString::number(static_cast(de))); + subFramed = false; - double key = guideTimer.elapsed() / 1000.0; + starCenter.setX(w / (2 * subBinX)); + starCenter.setY(h / (2 * subBinX)); - driftGraph->graph(4)->addData(key, ra); - driftGraph->graph(5)->addData(key, de); -} + //starCenter.setX(0); + //starCenter.setY(0); + } + } + break; -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)) + case GUIDE_DARK: { - 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)); + // Do we need to take a dark frame? + if (Options::guideDarkFrameEnabled()) + { + FITSData *darkData = nullptr; + QVariantMap settings = frameSettings[targetChip]; + uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); + uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); - QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")); - raPulseColor.setAlpha(75); - driftGraph->graph(4)->setPen(QPen(raPulseColor)); - driftGraph->graph(4)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern)); + darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); - QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")); - dePulseColor.setAlpha(75); - driftGraph->graph(5)->setPen(QPen(dePulseColor)); - driftGraph->graph(5)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern)); + connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, &Ekos::Guide::setCaptureComplete); + 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 + { + bool rc = DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), + offsetX, offsetY); + setDarkFrameEnabled(rc); + } + } + } + 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::driftMouseClicked(QMouseEvent *event) +void Guide::processGuideOptions() { - if (event->buttons() & Qt::RightButton) + if (Options::guiderType() != guiderType) { - driftGraph->yAxis->setRange(-3, 3); + guiderType = static_cast(Options::guiderType()); + setGuiderType(Options::guiderType()); } } -void Guide::driftMouseOverLine(QMouseEvent *event) +void Guide::showFITSViewer() { - double key = driftGraph->xAxis->pixelToCoord(event->localPos().x()); - - if (driftGraph->xAxis->range().contains(key)) + FITSData *data = guideView->getImageData(); + if (data) { - QCPGraph *graph = qobject_cast(driftGraph->plottableAt(event->pos(), false)); + QUrl url = QUrl::fromLocalFile(data->filename()); - if (graph) + if (fv.isNull()) { - 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))); - } + if (Options::singleWindowCapturedFITS()) + fv = KStars::Instance()->genericFITSViewer(); 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. + 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 - QToolTip::hideText(); + fv->updateFITS(url, 0); - driftGraph->replot(); + fv->show(); } } -void Guide::buildOperationStack(GuideState operation) +void Guide::setBLOBEnabled(bool enable, const QString &ccd) { - operationStack.clear(); + // Nothing to do if guider is international or remote images are enabled + if (guiderType == GUIDE_INTERNAL || Options::guideRemoteImagesEnabled()) + return; - switch (operation) + // If guider is external and remote images option is disabled AND BLOB is enabled, then we disabled it + + foreach(ISD::CCD *oneCCD, CCDs) { - case GUIDE_CAPTURE: - if (Options::guideDarkFrameEnabled()) - operationStack.push(GUIDE_DARK); + // If it's not the desired CCD, continue. + if (ccd.isEmpty() == false && QString(oneCCD->getDeviceName()) != ccd) + continue; - operationStack.push(GUIDE_CAPTURE); - operationStack.push(GUIDE_SUBFRAME); - break; + if (enable == false && oneCCD->isBLOBEnabled()) + { + appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->setBLOBEnabled(enable); + } + // Re-enable BLOB reception if it was disabled before when using external guiders + else if (enable && oneCCD->isBLOBEnabled() == false) + { + appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); + oneCCD->setBLOBEnabled(enable); + } + } +} - case GUIDE_CALIBRATING: - operationStack.push(GUIDE_CALIBRATING); - if (guiderType == GUIDE_INTERNAL) - { - if (Options::guideDarkFrameEnabled()) - operationStack.push(GUIDE_DARK); +void Guide::ditherDirectly() +{ + double ditherPulse = Options::ditherNoGuidingPulse(); - // 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); + // 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; - // Do not subframe and auto-select star on Image Guiding mode - if (Options::imageGuidingEnabled() == false) - { - operationStack.push(GUIDE_SUBFRAME); - operationStack.push(GUIDE_STAR_SELECT); - } + int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); + int de_polarity = (rand() % 2 == 0) ? 1 : -1; - operationStack.push(GUIDE_CAPTURE); + 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; - // 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); + 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); - // Subframe if required - operationStack.push(GUIDE_SUBFRAME); + 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; - // First capture an image - operationStack.push(GUIDE_CAPTURE); - } + focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; + aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; - } - break; + Options::setGuideScopeType(index); - default: - break; - } + syncTelescopeInfo(); } -bool Guide::executeOperationStack() +void Guide::setDefaultST4(const QString &driver) { - if (operationStack.isEmpty()) - return false; + Options::setDefaultST4Driver(driver); +} - GuideState nextOperation = operationStack.pop(); +void Guide::setDefaultCCD(const QString &ccd) +{ + if (guiderType == GUIDE_INTERNAL) + Options::setDefaultGuideCCD(ccd); + else if (ccd.isEmpty() == false) + { + QString ccdName = ccd; + ccdName = ccdName.remove(" Guider"); + setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); + } +} - bool actionRequired = false; +void Guide::handleManualDither() +{ + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + if (targetChip == nullptr) + return; - switch (nextOperation) + Ui::ManualDither ditherDialog; + QDialog container(this); + ditherDialog.setupUi(&container); + + if (guiderType != GUIDE_INTERNAL) { - case GUIDE_SUBFRAME: - actionRequired = executeOneOperation(nextOperation); - break; + ditherDialog.coordinatesR->setEnabled(false); + ditherDialog.x->setEnabled(false); + ditherDialog.y->setEnabled(false); + } - case GUIDE_DARK: - actionRequired = executeOneOperation(nextOperation); - break; + int minX, maxX, minY, maxY, minW, maxW, minH, maxH; + targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); - case GUIDE_CAPTURE: - actionRequired = captureOneFrame(); - break; + ditherDialog.x->setMinimum(minX); + ditherDialog.x->setMaximum(maxX); + ditherDialog.y->setMinimum(minY); + ditherDialog.y->setMaximum(maxY); - case GUIDE_STAR_SELECT: - actionRequired = executeOneOperation(nextOperation); - break; + ditherDialog.x->setValue(starCenter.x()); + ditherDialog.y->setValue(starCenter.y()); - case GUIDE_CALIBRATING: - if (guiderType == GUIDE_INTERNAL) - { - guider->setStarPosition(starCenter); - dynamic_cast(guider)->setImageGuideEnabled(Options::imageGuidingEnabled()); + 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()); + } + } +} - // No need to calibrate - if (Options::imageGuidingEnabled()) - { - setStatus(GUIDE_CALIBRATION_SUCESS); - break; - } +bool Guide::connectGuider() +{ + return guider->Connect(); +} - // Tracking must be engaged - if (currentTelescope && currentTelescope->canControlTrack() && currentTelescope->isTracking() == false) - currentTelescope->setTrackEnabled(true); - } +bool Guide::disconnectGuider() +{ + return guider->Disconnect(); +} - 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; +void Guide::initPlots() +{ + // 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)")); - default: - break; - } + //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); - // If an additional action is required, return return and continue later - if (actionRequired) - return true; - // Otherwise, continue processing the stack - else - return executeOperationStack(); -} + //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); -bool Guide::executeOneOperation(GuideState operation) -{ - bool actionRequired = false; + // 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); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + // 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); - int subBinX, subBinY; - targetChip->getBinning(&subBinX, &subBinY); + // 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)); - 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); + // 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)); - int offset = boxSizeCombo->currentText().toInt() / subBinX; + // 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); - int x = starCenter.x(); - int y = starCenter.y(); + // 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); - x = (x - offset * 2) * subBinX; - y = (y - offset * 2) * subBinY; - int w = offset * 4 * subBinX; - int h = offset * 4 * subBinY; + //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); - if (x < minX) - x = minX; - if (y < minY) - y = minY; - if ((x + w) > maxW) - w = maxW - x; - if ((y + h) > maxH) - h = maxH - y; + connect(driftGraph, &QCustomPlot::mouseMove, this, &Ekos::Guide::driftMouseOverLine); + connect(driftGraph, &QCustomPlot::mousePress, this, &Ekos::Guide::driftMouseClicked); - 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; + //drift plot + double accuracyRadius = 2; - frameSettings[targetChip] = settings; + driftPlot->setBackground(QBrush(Qt::black)); + driftPlot->setSelectionTolerance(10); - 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(); + driftPlot->xAxis->setBasePen(QPen(Qt::white, 1)); + driftPlot->yAxis->setBasePen(QPen(Qt::white, 1)); - int x, y, w, h; - targetChip->getFrame(&x, &y, &w, &h); + driftPlot->xAxis->setTickPen(QPen(Qt::white, 1)); + driftPlot->yAxis->setTickPen(QPen(Qt::white, 1)); - QVariantMap settings; - settings["x"] = x; - settings["y"] = y; - settings["w"] = w; - settings["h"] = h; - settings["binx"] = 1; - settings["biny"] = 1; - frameSettings[targetChip] = settings; + driftPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); + driftPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); - subFramed = false; + driftPlot->xAxis->setTickLabelColor(Qt::white); + driftPlot->yAxis->setTickLabelColor(Qt::white); - starCenter.setX(w / (2 * subBinX)); - starCenter.setY(h / (2 * subBinX)); + driftPlot->xAxis->setLabelColor(Qt::white); + driftPlot->yAxis->setLabelColor(Qt::white); - //starCenter.setX(0); - //starCenter.setY(0); - } - } - break; + 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)); - case GUIDE_DARK: - { - // Do we need to take a dark frame? - if (Options::guideDarkFrameEnabled()) - { - FITSData *darkData = nullptr; - QVariantMap settings = frameSettings[targetChip]; - uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); - uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); + driftPlot->xAxis->setLabelPadding(2); + driftPlot->yAxis->setLabelPadding(2); - darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value()); + 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)); - connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, &Ekos::Guide::setCaptureComplete); - connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Guide::appendLogText); + driftPlot->xAxis->setLabel(i18n("dRA (arcsec)")); + driftPlot->yAxis->setLabel(i18n("dDE (arcsec)")); - actionRequired = true; + driftPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); + driftPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3); - targetChip->setCaptureFilter(static_cast(filterCombo->currentIndex())); + driftPlot->setInteractions(QCP::iRangeZoom); + driftPlot->setInteraction(QCP::iRangeDrag, true); - if (darkData) - DarkLibrary::Instance()->subtract(darkData, guideView, targetChip->getCaptureFilter(), offsetX, - offsetY); - else - { - bool rc = DarkLibrary::Instance()->captureAndSubtract(targetChip, guideView, exposureIN->value(), - offsetX, offsetY); - setDarkFrameEnabled(rc); - } - } - } - break; + driftPlot->addGraph(); + driftPlot->graph(0)->setLineStyle(QCPGraph::lsNone); + driftPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, Qt::gray, 5)); - case GUIDE_STAR_SELECT: - { - state = GUIDE_STAR_SELECT; - emit newStatus(state); + driftPlot->addGraph(); + driftPlot->graph(1)->setLineStyle(QCPGraph::lsNone); + driftPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle, QPen(Qt::yellow, 2), QBrush(), 10)); - 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; + connect(rightLayout, &QSplitter::splitterMoved, this, &Ekos::Guide::handleVerticalPlotSizeChange); + connect(driftSplitter, &QSplitter::splitterMoved, this, &Ekos::Guide::handleHorizontalPlotSizeChange); - default: - break; - } + //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()); + + //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(); - return actionRequired; -} + driftPlot->resize(190, 190); + driftPlot->replot(); -void Guide::processGuideOptions() -{ - if (Options::guiderType() != guiderType) - { - guiderType = static_cast(Options::guiderType()); - setGuiderType(Options::guiderType()); - } + buildTarget(); } -void Guide::showFITSViewer() +void Guide::initView() { - 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(); - } + 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::setBLOBEnabled(bool enable, const QString &ccd) +void Guide::initConnections() { - // Nothing to do if guider is international or remote images are enabled - if (guiderType == GUIDE_INTERNAL || Options::guideRemoteImagesEnabled()) - return; + // Exposure Timeout + captureTimeout.setSingleShot(true); + connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Guide::processCaptureTimeout); - // If guider is external and remote images option is disabled AND BLOB is enabled, then we disabled it + // Guiding Box Size + connect(boxSizeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTrackingBoxSize); - foreach(ISD::CCD *oneCCD, CCDs) + // Guider CCD Selection + connect(guiderCombo, static_cast(&QComboBox::activated), this, &Ekos::Guide::setDefaultCCD); + connect(guiderCombo, static_cast(&QComboBox::activated), this, + [&](int index) { - // If it's not the desired CCD, continue. - if (ccd.isEmpty() == false && QString(oneCCD->getDeviceName()) != ccd) - continue; - - if (enable == false && oneCCD->isBLOBEnabled()) + if (guiderType == GUIDE_INTERNAL) { - appendLogText(i18n("Disabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); + starCenter = QVector3D(); + checkCCD(index); } - // Re-enable BLOB reception if it was disabled before when using external guiders - else if (enable && oneCCD->isBLOBEnabled() == false) + else if (index >= 0) { - appendLogText(i18n("Enabling remote image reception from %1", oneCCD->getDeviceName())); - oneCCD->setBLOBEnabled(enable); + // Disable or enable selected CCD based on options + QString ccdName = guiderCombo->currentText().remove(" Guider"); + setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); + checkCCD(index); } } -} + ); -void Guide::ditherDirectly() -{ - double ditherPulse = Options::ditherNoGuidingPulse(); + FOVScopeCombo->setCurrentIndex(Options::guideScopeType()); + connect(FOVScopeCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateTelescopeType); - // 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; + // Dark Frame Check + connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDarkFrameEnabled); + // Subframe check + connect(subFrameCheck, &QCheckBox::toggled, this, &Ekos::Guide::setSubFrameEnabled); + // ST4 Selection + connect(ST4Combo, static_cast(&QComboBox::activated), [&](const QString & text) + { + setDefaultST4(text); + setST4(text); + }); - int de_msec = static_cast((static_cast(rand()) / RAND_MAX) * ditherPulse / 2.0 + ditherPulse / 2.0); - int de_polarity = (rand() % 2 == 0) ? 1 : -1; + // Binning Combo Selection + connect(binningCombo, static_cast(&QComboBox::currentIndexChanged), this, &Ekos::Guide::updateCCDBin); - 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; + // RA/DEC Enable directions + connect(checkBox_DirRA, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirRA); + connect(checkBox_DirDEC, &QCheckBox::toggled, this, &Ekos::Guide::onEnableDirDEC); - 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); + // 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); - 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; - } -} + // Auto star check + connect(autoStarCheck, &QCheckBox::toggled, this, &Ekos::Guide::syncSettings); -void Guide::updateTelescopeType(int index) -{ - if (currentCCD == nullptr) - return; + // Declination Swap + connect(swapCheck, &QCheckBox::toggled, this, &Ekos::Guide::setDECSwap); - focal_length = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryFL : guideFL; - aperture = (index == ISD::CCD::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture; + // PID Control - Proportional Gain + connect(spinBox_PropGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_PropGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); - Options::setGuideScopeType(index); + // PID Control - Integral Gain + connect(spinBox_IntGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_IntGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); - syncTelescopeInfo(); -} + // PID Control - Derivative Gain + connect(spinBox_DerGainRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_DerGainDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::setDefaultST4(const QString &driver) -{ - Options::setDefaultST4Driver(driver); -} + // Max Pulse Duration (ms) + connect(spinBox_MaxPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_MaxPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::setDefaultCCD(const QString &ccd) -{ - if (guiderType == GUIDE_INTERNAL) - Options::setDefaultGuideCCD(ccd); - else if (ccd.isEmpty() == false) - { - QString ccdName = ccd; - ccdName = ccdName.remove(" Guider"); - setBLOBEnabled(Options::guideRemoteImagesEnabled(), ccdName); - } -} + // Min Pulse Duration (ms) + connect(spinBox_MinPulseRA, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); + connect(spinBox_MinPulseDEC, &QSpinBox::editingFinished, this, &Ekos::Guide::syncSettings); -void Guide::handleManualDither() -{ - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - if (targetChip == nullptr) - return; + // Capture + connect(captureB, &QPushButton::clicked, this, [this]() + { + state = GUIDE_CAPTURE; + emit newStatus(state); - Ui::ManualDither ditherDialog; - QDialog container(this); - ditherDialog.setupUi(&container); + capture(); + }); - if (guiderType != GUIDE_INTERNAL) + connect(loopB, &QPushButton::clicked, this, [this]() { - ditherDialog.coordinatesR->setEnabled(false); - ditherDialog.x->setEnabled(false); - ditherDialog.y->setEnabled(false); - } + state = GUIDE_LOOPING; + emit newStatus(state); - int minX, maxX, minY, maxY, minW, maxW, minH, maxH; - targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); + capture(); + }); - ditherDialog.x->setMinimum(minX); - ditherDialog.x->setMaximum(maxX); - ditherDialog.y->setMinimum(minY); - ditherDialog.y->setMaximum(maxY); + // Stop + connect(stopB, &QPushButton::clicked, this, &Ekos::Guide::abort); - ditherDialog.x->setValue(starCenter.x()); - ditherDialog.y->setValue(starCenter.y()); + // Clear Calibrate + //connect(calibrateB, &QPushButton::clicked, this, &Ekos::Guide::calibrate())); + connect(clearCalibrationB, &QPushButton::clicked, this, &Ekos::Guide::clearCalibration); - if (container.exec() == QDialog::Accepted) + // Guide + connect(guideB, &QPushButton::clicked, this, &Ekos::Guide::guide); + + // Connect External Guide + connect(externalConnectB, &QPushButton::clicked, this, [&]() { - if (ditherDialog.magnitudeR->isChecked()) - guider->dither(ditherDialog.magnitude->value()); - else - { - dynamic_cast(guider)->ditherXY(ditherDialog.x->value(), ditherDialog.y->value()); - } - } -} + setBLOBEnabled(false); + guider->Connect(); + }); + connect(externalDisconnectB, &QPushButton::clicked, this, [&]() + { + setBLOBEnabled(true); + guider->Disconnect(); + }); -bool Guide::connectGuider() -{ - return guider->Connect(); -} + // Pulse Timer + pulseTimer.setSingleShot(true); + connect(&pulseTimer, &QTimer::timeout, this, &Ekos::Guide::capture); -bool Guide::disconnectGuider() -{ - return guider->Disconnect(); + //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 + connect(spinBox_GuideRate, static_cast(&QDoubleSpinBox::valueChanged), this, &Ekos::Guide::onInfoRateChanged); } } diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h index f460f4d61..6b32752ad 100644 --- a/kstars/ekos/guide/guide.h +++ b/kstars/ekos/guide/guide.h @@ -1,627 +1,632 @@ /* 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 "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 setTelescope(ISD::GDInterface *newTelescope); void addST4(ISD::ST4 *setST4); void setAO(ISD::ST4 *newAO); 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(); //plot slots void handleVerticalPlotSizeChange(); void handleHorizontalPlotSizeChange(); void clearGuideGraphs(); 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); 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 onInputParamChanged(); + 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 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 *); 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 setBLOBEnabled(bool enable, const QString &ccd = QString()); void handleManualDither(); // Operation stack void buildOperationStack(GuideState operation); bool executeOperationStack(); bool executeOneOperation(GuideState operation); + // Init Functions + void initPlots(); + void initView(); + void initConnections(); + bool captureOneFrame(); // Operation Stack QStack operationStack; // Devices ISD::CCD *currentCCD { nullptr }; 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; // Star QVector3D starCenter; // Guide Params - double ccdPixelSizeX { 0 }; - double ccdPixelSizeY { 0 }; - double aperture { 0 }; - double focal_length { 0 }; + double ccdPixelSizeX { -1 }; + double ccdPixelSizeY { -1 }; + double aperture { -1 }; + double focal_length { -1 }; double guideDeviationRA { 0 }; double guideDeviationDEC { 0 }; - double pixScaleX { 0 }; - double pixScaleY { 0 }; + double pixScaleX { -1 }; + double pixScaleY { -1 }; // Rapid Guide //bool rapidGuideReticleSet; // State - GuideState state; + GuideState state { GUIDE_IDLE }; // Guide timer QTime guideTimer; // Capture timeout timer QTimer captureTimeout; uint8_t captureTimeoutCounter { 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; QCPCurve *centralTarget { nullptr }; QCPCurve *yellowTarget { nullptr }; QCPCurve *redTarget { nullptr }; QCPCurve *concentricRings { nullptr }; bool graphOnLatestPt = true; QUrl guideURLPath; }; } diff --git a/kstars/ekos/guide/guide.ui b/kstars/ekos/guide/guide.ui index 4ce9475ff..3d5d23a08 100644 --- a/kstars/ekos/guide/guide.ui +++ b/kstars/ekos/guide/guide.ui @@ -1,1708 +1,1709 @@ Guide 0 0 - 712 - 529 + 736 + 568 3 3 3 3 3 3 Control 3 3 3 3 3 3 Capture 22 22 22 22 Show in FITS Viewer Loop 22 22 22 22 Clear calibration data. .. 22 22 false 22 22 22 22 Manual Dither .. 22 22 false Stop <html><head/><body><p>Subframe the image around the guide star. 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 <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 - + Automatically select the calibration star. Auto Star false 0 0 Guide true true 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 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 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 - - NonLinearDoubleSpinBox - QDoubleSpinBox -
auxiliary/nonlineardoublespinbox.h
-
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