diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h --- a/kstars/ekos/capture/capture.h +++ b/kstars/ekos/capture/capture.h @@ -126,7 +126,7 @@ ADU_POLYNOMIAL } ADUAlgorithm; - typedef bool (Capture::*PauseFunctionPointer)(); + typedef IPState (Capture::*PauseFunctionPointer)(); Capture(); ~Capture(); @@ -566,7 +566,7 @@ /** * @brief resumeCapture Resume capture after dither and/or focusing processes are complete. */ - bool resumeCapture(); + IPState resumeCapture(); /** * @brief updateCCDTemperature Update CCD temperature in capture module. @@ -631,7 +631,7 @@ // Meridian flip void meridianFlipStatusChanged(Mount::MeridianFlipStatus status); - private slots: +private slots: /** * @brief setDirty Set dirty bit to indicate sequence queue file was modified and needs saving. @@ -680,18 +680,29 @@ bool processPostCaptureCalibrationStage(); void updatePreCaptureCalibrationStatus(); - // Frame Type calibration checks + /* Frame Type calibration checks */ + + /** + * @brief Check if some tasks are pending before the active job + * can start light frame capturing + * @return IPS_OK iff the light frame capturing sequence is ready to start + */ IPState checkLightFramePendingTasks(); - IPState checkLightFrameAuxiliaryTasks(); + + /** + * @brief Check whether the scope cover is removed (manual cover, flat covers, dark covers etc.) + * @return true iff scope cover is open + */ + IPState checkLightFrameScopeCoverOpen(); IPState checkFlatFramePendingTasks(); IPState checkDarkFramePendingTasks(); // Send image info void sendNewImage(const QString &filename, ISD::CCDChip *myChip); // Capture - bool setCaptureComplete(); + IPState setCaptureComplete(); // post capture script void postScriptFinished(int exitCode, QProcess::ExitStatus status); @@ -744,8 +755,14 @@ private: void setBusy(bool enable); - bool resumeSequence(); - bool startNextExposure(); + IPState resumeSequence(); + IPState startNextExposure(); + + /** + * @brief Loop retrying #startNextExposure() if there are pending preparation tasks. + */ + void checkNextExposure(); + // reset = 0 --> Do not reset // reset = 1 --> Full reset // reset = 2 --> Only update limits if needed @@ -771,12 +788,31 @@ bool isModelinDSLRInfo(const QString &model); /* Meridian Flip */ - bool checkMeridianFlip(); - void checkGuidingAfterFlip(); + /** + * @brief Check if a meridian flip has already been started + * @return true iff the scope has started the meridian flip + */ + inline bool checkMeridianFlipRunning() { + return meridianFlipStage == MF_INITIATED || meridianFlipStage == MF_FLIPPING || meridianFlipStage == MF_SLEWING;} + + /** + * @brief Check whether a meridian flip has been requested and trigger it + * @return true iff a meridian flip has been triggered + */ + bool checkMeridianFlipReady(); + + bool checkGuidingAfterFlip(); + bool checkAlignmentAfterFlip(); // check if a pause has been planned bool checkPausing(); + /** + * @brief Check whether dithering is necessary and trigger it if required. + * @return true iff dithering has been triggered + */ + bool checkDithering(); + // Remaining Time in seconds int getJobRemainingTime(SequenceJob *job); @@ -927,6 +963,7 @@ CaptureState m_State { CAPTURE_IDLE }; FocusState focusState { FOCUS_IDLE }; GuideState guideState { GUIDE_IDLE }; + IPState ditheringState {IPS_IDLE}; AlignState alignState { ALIGN_IDLE }; FilterState filterManagerState { FILTER_IDLE }; @@ -942,8 +979,8 @@ std::unique_ptr rotatorSettings; // How many images to capture before dithering operation is executed? - uint8_t ditherCounter { 0 }; - uint8_t inSequenceFocusCounter { 0 }; + uint ditherCounter { 0 }; + uint inSequenceFocusCounter { 0 }; std::unique_ptr customPropertiesDialog; @@ -971,5 +1008,6 @@ QList downloadTimes; QTime downloadTimer; QTimer downloadProgressTimer; + void processGuidingFailed(); }; } diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp --- a/kstars/ekos/capture/capture.cpp +++ b/kstars/ekos/capture/capture.cpp @@ -268,10 +268,10 @@ }); // 8. Refocus Every Value - refocusEveryN->setValue(Options::refocusEveryN()); + refocusEveryN->setValue(static_cast(Options::refocusEveryN())); connect(refocusEveryN, &QDoubleSpinBox::editingFinished, [ = ]() { - Options::setRefocusEveryN(refocusEveryN->value()); + Options::setRefocusEveryN(static_cast(refocusEveryN->value())); }); // 9. File settings: filter name @@ -375,7 +375,7 @@ // Keep track of TARGET transfer format when changing CCDs (FITS or NATIVE). Actual format is not changed until capture connect( transferFormatCombo, static_cast(&QComboBox::activated), this, - [&](int index) + [&](uint index) { if (currentCCD) currentCCD->setTargetTransferFormat(static_cast(index)); @@ -725,7 +725,7 @@ currentLightBox->SetLightEnabled(false); } - if (meridianFlipStage == MF_NONE) + if (meridianFlipStage == MF_NONE || meridianFlipStage >= MF_COMPLETED) secondsLabel->clear(); disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS); disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress); @@ -988,16 +988,16 @@ //transferFormatCombo->setCurrentIndex(currentCCD->getTargetTransferFormat()); // 2018-05-07 JM: Set value to the value in options - transferFormatCombo->setCurrentIndex(Options::captureFormatIndex()); + transferFormatCombo->setCurrentIndex(static_cast(Options::captureFormatIndex())); uint16_t w, h; uint8_t bbp {8}; double pixelX = 0, pixelY = 0; bool rc = targetChip->getImageInfo(w, h, pixelX, pixelY, bbp); bool isModelInDB = isModelinDSLRInfo(QString(currentCCD->getDeviceName())); // If rc == true, then the property has been defined by the driver already // Only then we check if the pixels are zero - if (rc == true && (pixelX == 0 || pixelY == 0 || isModelInDB == false)) + if (rc == true && (pixelX == 0.0 || pixelY == 0.0 || isModelInDB == false)) { // If model is already in database, no need to show dialog // The zeros above are the initial packets so we can safely ignore them @@ -1118,15 +1118,15 @@ return; } - if (step == 0) + if (step == 0.0) xstep = static_cast(max * 0.05); else - xstep = step; + xstep = static_cast(step); if (min >= 0 && max > 0) { - frameWIN->setMinimum(min); - frameWIN->setMaximum(max); + frameWIN->setMinimum(static_cast(min)); + frameWIN->setMaximum(static_cast(max)); frameWIN->setSingleStep(xstep); } } @@ -1141,15 +1141,15 @@ return; } - if (step == 0) + if (step == 0.0) ystep = static_cast(max * 0.05); else - ystep = step; + ystep = static_cast(step); if (min >= 0 && max > 0) { - frameHIN->setMinimum(min); - frameHIN->setMaximum(max); + frameHIN->setMinimum(static_cast(min)); + frameHIN->setMaximum(static_cast(max)); frameHIN->setSingleStep(ystep); } } @@ -1164,14 +1164,14 @@ return; } - if (step == 0) + if (step == 0.0) step = xstep; if (min >= 0 && max > 0) { - frameXIN->setMinimum(min); - frameXIN->setMaximum(max); - frameXIN->setSingleStep(step); + frameXIN->setMinimum(static_cast(min)); + frameXIN->setMaximum(static_cast(max)); + frameXIN->setSingleStep(static_cast(step)); } } else @@ -1185,14 +1185,14 @@ return; } - if (step == 0) + if (step == 0.0) step = ystep; if (min >= 0 && max > 0) { - frameYIN->setMinimum(min); - frameYIN->setMaximum(max); - frameYIN->setSingleStep(step); + frameYIN->setMinimum(static_cast(min)); + frameYIN->setMaximum(static_cast(max)); + frameYIN->setSingleStep(static_cast(step)); } } else @@ -1438,35 +1438,35 @@ } } -bool Capture::startNextExposure() +IPState Capture::startNextExposure() { - // check if pausing has been requested - if (checkPausing() == true) - { - pauseFunction = &Capture::startNextExposure; - return false; - } - - if (checkMeridianFlip()) - // execute flip before next capture - return false; - - if (startFocusIfRequired()) - // re-focus before next capture - return false; + IPState pending = checkLightFramePendingTasks(); + if (pending != IPS_OK) + // there are still some jobs pending + return pending; + // nothing pending, let's start the next exposure if (seqDelay > 0) { secondsLabel->setText(i18n("Waiting...")); m_State = CAPTURE_WAITING; emit newStatus(Ekos::CAPTURE_WAITING); } - seqTimer->start(seqDelay); - return true; + return IPS_OK; } +void Capture::checkNextExposure() +{ + IPState started = startNextExposure(); + // if starting the next exposure did not succeed due to pending jobs running, + // we retry after 1 second + if (started == IPS_BUSY) + QTimer::singleShot(1000, this, &Ekos::Capture::checkNextExposure); +} + + void Capture::newFITS(IBLOB * bp) { ISD::CCDChip * tChip = nullptr; @@ -1544,8 +1544,8 @@ { FITSView * currentImage = targetChip->getImageView(FITS_NORMAL); FITSData * darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, activeJob->getExposure()); - uint16_t offsetX = activeJob->getSubX() / activeJob->getXBin(); - uint16_t offsetY = activeJob->getSubY() / activeJob->getYBin(); + uint16_t offsetX = static_cast(activeJob->getSubX() / activeJob->getXBin()); + uint16_t offsetY = static_cast(activeJob->getSubY() / activeJob->getYBin()); connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed) { @@ -1574,7 +1574,7 @@ setCaptureComplete(); } -bool Capture::setCaptureComplete() +IPState Capture::setCaptureComplete() { captureTimeout.stop(); m_CaptureTimeoutCounter = 0; @@ -1587,7 +1587,7 @@ sendNewImage(blobFilename, blobChip); secondsLabel->setText(i18n("Framing...")); activeJob->capture(darkSubCheck->isChecked() ? true : false); - return true; + return IPS_OK; } if (currentCCD->isLooping() == false) @@ -1611,7 +1611,6 @@ secondsLabel->setText(i18n("Complete.")); - // Do not display notifications for very short captures if (activeJob->getExposure() >= 1) KSNotification::event(QLatin1String("EkosCaptureImageReceived"), i18n("Captured image received"), @@ -1633,14 +1632,14 @@ m_State = CAPTURE_IDLE; emit newStatus(Ekos::CAPTURE_IDLE); - return true; + return IPS_OK; } // check if pausing has been requested if (checkPausing() == true) { pauseFunction = &Capture::setCaptureComplete; - return false; + return IPS_BUSY; } if (! activeJob->isPreview()) @@ -1651,6 +1650,9 @@ inSequenceFocusCounter--; } + /* Decrease the dithering counter */ + ditherCounter--; + // Do not send new image if the image was stored on the server. if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) sendNewImage(blobFilename, blobChip); @@ -1663,7 +1665,7 @@ if (activeJob->getFrameType() != FRAME_LIGHT) { if (processPostCaptureCalibrationStage() == false) - return true; + return IPS_OK; if (calibrationStage == CAL_CALIBRATION_COMPLETE) calibrationStage = CAL_CAPTURING; @@ -1684,14 +1686,14 @@ { postCaptureScript.start(activeJob->getPostCaptureScript()); appendLogText(i18n("Executing post capture script %1", activeJob->getPostCaptureScript())); - return true; + return IPS_OK; } // if we're done if (activeJob->getCount() <= activeJob->getCompleted()) { processJobCompletion(); - return true; + return IPS_OK; } return resumeSequence(); @@ -1713,7 +1715,7 @@ stop(); // Check if there are more pending jobs and execute them - if (resumeSequence()) + if (resumeSequence() == IPS_OK) return; // Otherwise, we're done. We park if required and resume guiding if no parking is done and autoguiding was engaged before. else @@ -1734,16 +1736,42 @@ } } -bool Capture::resumeSequence() +bool Capture::checkDithering() { - if (m_State == CAPTURE_PAUSED) + if ( (Options::ditherEnabled() || Options::ditherNoGuiding()) + // 2017-09-20 Jasem: No need to dither after post meridian flip guiding + && meridianFlipStage != MF_GUIDING + // If CCD is looping, we cannot dither UNLESS a different camera and NOT a guide chip is doing the guiding for us. + && (currentCCD->isLooping() == false || guideChip == nullptr) + // We must be either in guide mode or if non-guide dither (via pulsing) is enabled + && (guideState == GUIDE_GUIDING || Options::ditherNoGuiding()) + // Must be only done for light frames + && activeJob->getFrameType() == FRAME_LIGHT + // Check dither counter + && ditherCounter <= 0) { - pauseFunction = &Capture::resumeSequence; - appendLogText(i18n("Sequence paused.")); - secondsLabel->setText(i18n("Paused...")); - return false; + ditherCounter = Options::ditherFrames(); + + secondsLabel->setText(i18n("Dithering...")); + + qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering..."; + appendLogText(i18n("Dithering...")); + + if (currentCCD->isLooping()) + targetChip->abortExposure(); + + m_State = CAPTURE_DITHERING; + ditheringState = IPS_BUSY; + emit newStatus(Ekos::CAPTURE_DITHERING); + + return true; } + // no dithering required + return false; +} +IPState Capture::resumeSequence() +{ // If no job is active, we have to find if there are more pending jobs in the queue if (!activeJob) { @@ -1770,147 +1798,52 @@ emit resumeGuiding(); } - return true; + return IPS_OK; } else { qCDebug(KSTARS_EKOS_CAPTURE) << "All capture jobs complete."; - return false; + return IPS_BUSY; } } - // Otherwise, let's prepare for next exposure after making sure in-sequence focus and dithering are complete if applicable. + // Otherwise, let's prepare for next exposure. else { - isInSequenceFocus = (m_AutoFocusReady && autofocusCheck->isChecked()/* && HFRPixels->value() > 0*/); - // if (isInSequenceFocus) - // requiredAutoFocusStarted = false; isTemperatureDeltaCheckActive = (m_AutoFocusReady && temperatureDeltaCheck->isChecked()); - // Reset HFR pixels to file value after meridian flip - if (isInSequenceFocus && meridianFlipStage != MF_NONE && meridianFlipStage != MF_READY) - { - qCDebug(KSTARS_EKOS_CAPTURE) << "Resetting HFR value to file value of" << fileHFR << "pixels after meridian flip."; - //firstAutoFocus = true; - HFRPixels->setValue(fileHFR); - } - // If we suspended guiding due to primary chip download, resume guide chip guiding now if (guideState == GUIDE_SUSPENDED && suspendGuideOnDownload) { qCInfo(KSTARS_EKOS_CAPTURE) << "Resuming guiding..."; emit resumeGuiding(); } - // Dither either when guiding or IF Non-Guide either option is enabled - if ( (Options::ditherEnabled() || Options::ditherNoGuiding()) - // 2017-09-20 Jasem: No need to dither after post meridian flip guiding - && meridianFlipStage != MF_GUIDING - // If CCD is looping, we cannot dither UNLESS a different camera and NOT a guide chip is doing the guiding for us. - && (currentCCD->isLooping() == false || guideChip == nullptr) - // We must be either in guide mode or if non-guide dither (via pulsing) is enabled - && (guideState == GUIDE_GUIDING || Options::ditherNoGuiding()) - // Must be only done for light frames - && activeJob->getFrameType() == FRAME_LIGHT - // Check dither counter - && --ditherCounter == 0) - { - ditherCounter = Options::ditherFrames(); - - secondsLabel->setText(i18n("Dithering...")); - - qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering..."; - - if (currentCCD->isLooping()) - targetChip->abortExposure(); - - m_State = CAPTURE_DITHERING; - emit newStatus(Ekos::CAPTURE_DITHERING); - } -#if 0 - else if (isRefocus && activeJob->getFrameType() == FRAME_LIGHT) - { - appendLogText(i18n("Scheduled refocus starting after %1 seconds...", getRefocusEveryNTimerElapsedSec())); - - secondsLabel->setText(i18n("Focusing...")); - - if (currentCCD->isLooping()) - targetChip->abortExposure(); - - // If we are over 30 mins since last autofocus, we'll reset frame. - if (refocusEveryN->value() >= 30) - emit resetFocus(); - - // force refocus - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1812."; - emit checkFocus(0.1); - - m_State = CAPTURE_FOCUSING; - emit newStatus(Ekos::CAPTURE_FOCUSING); - } - else if (isInSequenceFocus && activeJob->getFrameType() == FRAME_LIGHT && --inSequenceFocusCounter == 0) - { - inSequenceFocusCounter = Options::inSequenceCheckFrames(); - - // Post meridian flip we need to reset filter _before_ running in-sequence focusing - // as it could have changed for whatever reason (e.g. alignment used a different filter). - // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it - // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager. - if (meridianFlipStage != MF_NONE && currentFilter) - { - int targetFilterPosition = activeJob->getTargetFilter(); - int currentFilterPosition = filterManager->getFilterPosition(); - if (targetFilterPosition > 0 && targetFilterPosition != currentFilterPosition) - currentFilter->runCommand(INDI_SET_FILTER, &targetFilterPosition); - } - - secondsLabel->setText(i18n("Focusing...")); - - if (currentCCD->isLooping()) - targetChip->abortExposure(); - - if (HFRPixels->value() == 0) - { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1841."; - emit checkFocus(0.1); - } - else - { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1846."; - emit checkFocus(HFRPixels->value()); - } - - qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started..."; - - m_State = CAPTURE_FOCUSING; - emit newStatus(Ekos::CAPTURE_FOCUSING); - } -#endif - // Check if we need to do autofocus, if not let's check if we need looping or start next exposure - else if (startFocusIfRequired() == false) + // If looping, we just increment the file system image count + if (currentCCD->isLooping()) { - // If looping, we just increment the file system image count - if (currentCCD->isLooping()) + if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) { - if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL) - { - checkSeqBoundary(activeJob->getSignature()); - currentCCD->setNextSequenceID(nextSequenceID); - } + checkSeqBoundary(activeJob->getSignature()); + currentCCD->setNextSequenceID(nextSequenceID); } - else - startNextExposure(); } + // otherwise we loop starting the next exposure until all pending + // jobs are completed + else + checkNextExposure(); } - return true; + return IPS_OK; } + bool Capture::startFocusIfRequired() { if (activeJob == nullptr || activeJob->getFrameType() != FRAME_LIGHT) return false; isRefocus = false; + isInSequenceFocus = (m_AutoFocusReady && autofocusCheck->isChecked()); // check if time for forced refocus if (refocusEveryNCheck->isChecked()) @@ -1951,6 +1884,7 @@ // force refocus qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; + setFocusStatus(FOCUS_PROGRESS); emit checkFocus(0.1); m_State = CAPTURE_FOCUSING; @@ -1978,19 +1912,10 @@ if (currentCCD->isLooping()) targetChip->abortExposure(); - if (HFRPixels->value() == 0) - { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; - emit checkFocus(0.1); - } - else - { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; - emit checkFocus(HFRPixels->value()); - } + setFocusStatus(FOCUS_PROGRESS); + emit checkFocus(HFRPixels->value() == 0.0 ? 0.1: HFRPixels->value()); qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started..."; - m_State = CAPTURE_FOCUSING; emit newStatus(Ekos::CAPTURE_FOCUSING); return true; @@ -2098,7 +2023,7 @@ { int remaining = activeJob->getCount() - activeJob->getCompleted(); if (remaining > 1) - currentCCD->setExposureLoopCount(remaining); + currentCCD->setExposureLoopCount(static_cast(remaining)); } connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS, Qt::UniqueConnection); @@ -2172,7 +2097,7 @@ { appendLogText(i18n("Capturing %1-second %2 image...", QString("%L1").arg(activeJob->getExposure(), 0, 'f', 3), activeJob->getFilterName())); - captureTimeout.start(activeJob->getExposure() * 1000 + CAPTURE_TIMEOUT_THRESHOLD); + captureTimeout.start(static_cast(activeJob->getExposure()) * 1000 + CAPTURE_TIMEOUT_THRESHOLD); if (activeJob->isPreview() == false) { int index = jobs.indexOf(activeJob); @@ -2207,58 +2132,22 @@ } } -bool Capture::resumeCapture() +IPState Capture::resumeCapture() { if (m_State == CAPTURE_PAUSED) { pauseFunction = &Capture::resumeCapture; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); - return false; - } - -#if 0 - /* Refresh isRefocus when resuming */ - if (autoFocusReady && refocusEveryNCheck->isChecked()) - { - qCDebug(KSTARS_EKOS_CAPTURE) << "NFocus Elapsed Time (secs): " << getRefocusEveryNTimerElapsedSec() << - " Requested Interval (secs): " << refocusEveryN->value() * 60; - isRefocus = getRefocusEveryNTimerElapsedSec() >= refocusEveryN->value() * 60; - } - - // FIXME ought to be able to combine these - only different is value passed - // to checkFocus() - // 2018-08-23 Jasem: For now in-sequence-focusing takes precedence. - if (isInSequenceFocus && requiredAutoFocusStarted == false) - { - requiredAutoFocusStarted = true; - secondsLabel->setText(i18n("Focusing...")); - qCDebug(KSTARS_EKOS_CAPTURE) << "Requesting focusing if HFR >" << HFRPixels->value(); - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 2186."; - emit checkFocus(HFRPixels->value()); - m_State = CAPTURE_FOCUSING; - emit newStatus(Ekos::CAPTURE_FOCUSING); - return true; + return IPS_OK; } - else if (isRefocus) - { - appendLogText(i18n("Scheduled refocus started...")); - - secondsLabel->setText(i18n("Focusing...")); - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 2197."; - emit checkFocus(0.1); - m_State = CAPTURE_FOCUSING; - emit newStatus(Ekos::CAPTURE_FOCUSING); - return true; - } -#endif if (m_State == CAPTURE_DITHERING && m_AutoFocusReady && startFocusIfRequired()) - return true; + return IPS_OK; startNextExposure(); - return true; + return IPS_OK; } /*******************************************************************************/ @@ -3093,65 +2982,6 @@ * But in the end, it's not entirely clear what the intent was. Note there is still a warning that a preliminary autofocus * procedure is important to avoid any surprise that could make the whole schedule ineffective. */ -#if 0 - // If we haven't performed a single autofocus yet, we stop - //if (!job->isPreview() && Options::enforceRefocusEveryN() && autoFocusReady && isInSequenceFocus == false && firstAutoFocus == true) - if (!job->isPreview() && Options::enforceRefocusEveryN() && autoFocusReady == false && isInSequenceFocus == false) - { - appendLogText(i18n("Manual scheduled focusing is not supported. Run Autofocus process before trying again.")); - abort(); - return; - } -#endif - -#if 0 - if (currentFilterPosition > 0) - { - // If we haven't performed a single autofocus yet, we stop - if (!job->isPreview() && Options::autoFocusOnFilterChange() && (isInSequenceFocus == false && firstAutoFocus == true)) - { - appendLogText(i18n( - "Manual focusing post filter change is not supported. Run Autofocus process before trying again.")); - abort(); - return; - } - - /* - if (currentFilterPosition != activeJob->getTargetFilter() && filterFocusOffsets.empty() == false) - { - int16_t targetFilterOffset = 0; - foreach (FocusOffset *offset, filterFocusOffsets) - { - if (offset->filter == activeJob->getFilterName()) - { - targetFilterOffset = offset->offset - lastFilterOffset; - lastFilterOffset = offset->offset; - break; - } - } - - if (targetFilterOffset != 0 && - (activeJob->getFrameType() == FRAME_LIGHT || activeJob->getFrameType() == FRAME_FLAT)) - { - appendLogText(i18n("Adjust focus offset by %1 steps", targetFilterOffset)); - secondsLabel->setText(i18n("Adjusting filter offset")); - - if (activeJob->isPreview() == false) - { - state = CAPTURE_FILTER_FOCUS; - emit newStatus(Ekos::CAPTURE_FILTER_FOCUS); - } - - setBusy(true); - - emit newFocusOffset(targetFilterOffset); - - return; - } - } - */ - } -#endif preparePreCaptureActions(); } @@ -3303,6 +3133,9 @@ // if (activeJob == nullptr) // return; // } + // if guiding deviations occur and no job is active, check if a meridian flip is ready to be executed + if (activeJob == nullptr && checkMeridianFlipReady()) + return; // If guiding is started after a meridian flip we will start getting guide deviations again // if the guide deviations are within our limits, we resume the sequence @@ -3314,15 +3147,14 @@ if (guideDeviationCheck->isChecked() == false || deviation_rms < guideDeviation->value()) { appendLogText(i18n("Post meridian flip calibration completed successfully.")); - resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always setMeridianFlipStage(MF_NONE); return; } } // We don't enforce limit on previews - if (guideDeviationCheck->isChecked() == false || (activeJob && (activeJob->isPreview() || activeJob->getExposeLeft() == 0))) + if (guideDeviationCheck->isChecked() == false || (activeJob && (activeJob->isPreview() || activeJob->getExposeLeft() == 0.0))) return; double deviation_rms = sqrt( (delta_ra * delta_ra + delta_dec * delta_dec) / 2.0); @@ -3352,7 +3184,7 @@ m_SpikeDetected = false; // Check if we need to start meridian flip - if (checkMeridianFlip()) + if (checkMeridianFlipReady()) return; m_DeviationDetected = true; @@ -3447,39 +3279,10 @@ HFRPixels->setValue(median + (median * (Options::hFRThresholdPercentage() / 100.0))); } -#if 0 - if (focusHFR > 0 && firstAutoFocus && HFRPixels->value() == 0 && fileHFR == 0) - { - firstAutoFocus = false; - // Add 2.5% (default) to the automatic initial HFR value to allow for minute changes in HFR without need to refocus - // in case in-sequence-focusing is used. - HFRPixels->setValue(focusHFR + (focusHFR * (Options::hFRThresholdPercentage() / 100.0))); - } -#endif - // successful focus so reset elapsed time restartRefocusEveryNTimer(); } -#if 0 - if (activeJob && - (activeJob->getStatus() == SequenceJob::JOB_ABORTED || activeJob->getStatus() == SequenceJob::JOB_IDLE)) - { - if (focusState == FOCUS_COMPLETE) - { - //HFRPixels->setValue(focusHFR + (focusHFR * 0.025)); - appendLogText(i18n("Focus complete.")); - } - else if (focusState == FOCUS_FAILED) - { - appendLogText(i18n("Autofocus failed. Aborting exposure...")); - secondsLabel->setText(""); - abort(); - } - return; - } -#endif - if ((isRefocus || isInSequenceFocus) && activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY) { // if the focusing has been started during the post-calibration, return to the calibration @@ -3491,7 +3294,7 @@ secondsLabel->setText(i18n("Focus complete.")); m_State = CAPTURE_PROGRESS; } - else if (focusState == FOCUS_FAILED) + else if (focusState == FOCUS_FAILED || focusState == FOCUS_ABORTED) { appendLogText(i18n("Autofocus failed.")); secondsLabel->setText(i18n("Autofocus failed.")); @@ -3502,9 +3305,8 @@ { appendLogText(i18n("Focus complete.")); secondsLabel->setText(i18n("Focus complete.")); - startNextExposure(); } - else if (focusState == FOCUS_FAILED) + else if (focusState == FOCUS_FAILED || focusState == FOCUS_ABORTED) { appendLogText(i18n("Autofocus failed. Aborting exposure...")); secondsLabel->setText(i18n("Autofocus failed.")); @@ -3582,6 +3384,14 @@ meridianFlipStage = stage; emit newMeridianFlipStatus(Mount::FLIP_ACCEPTED); } + else if (!checkMeridianFlipRunning()) + { + // if beither a MF has been requested (checked above) or is in a post + // MF calibration phase, no MF needs to take place. + // Hence we set to the stage to NONE + meridianFlipStage = MF_NONE; + break; + } // in any other case, ignore it break; @@ -3603,6 +3413,20 @@ case MF_COMPLETED: secondsLabel->setText(i18n("Flip complete.")); + meridianFlipStage = MF_COMPLETED; + + // Reset HFR pixels to file value after meridian flip + if (isInSequenceFocus) + { + qCDebug(KSTARS_EKOS_CAPTURE) << "Resetting HFR value to file value of" << fileHFR << "pixels after meridian flip."; + //firstAutoFocus = true; + HFRPixels->setValue(fileHFR); + } + + // after a meridian flip we do not need to dither + if ( Options::ditherEnabled() || Options::ditherNoGuiding()) + ditherCounter = Options::ditherFrames(); + break; default: @@ -3801,7 +3625,7 @@ if (root) { - double sqVersion = cLocale.toFloat(findXMLAttValu(root, "version")); + double sqVersion = cLocale.toDouble(findXMLAttValu(root, "version")); if (sqVersion < SQ_COMPAT_VERSION) { appendLogText(i18n("Deprecated sequence file format version %1. Please construct a new sequence file.", @@ -4646,8 +4470,8 @@ int Capture::getJobRemainingTime(SequenceJob * job) { - int remaining = (job->getExposure() + getEstimatedDownloadTime() + job->getDelay() / 1000) * - (job->getCount() - job->getCompleted()); + int remaining = static_cast((job->getExposure() + getEstimatedDownloadTime() + job->getDelay() / 1000) * + (job->getCount() - job->getCompleted())); if (job->getStatus() == SequenceJob::JOB_BUSY) remaining += job->getExposeLeft() + getEstimatedDownloadTime(); @@ -4657,7 +4481,7 @@ int Capture::getOverallRemainingTime() { - double remaining = 0; + int remaining = 0; foreach (SequenceJob * job, jobs) remaining += getJobRemainingTime(job); @@ -4800,40 +4624,26 @@ KSNotification::EVENT_INFO); - // resume only if capturing was running if (m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED || m_State == CAPTURE_COMPLETE || m_State == CAPTURE_PAUSED) - return; - - if (resumeAlignmentAfterFlip == true) { - appendLogText(i18n("Performing post flip re-alignment...")); - secondsLabel->setText(i18n("Aligning...")); - - retries = 0; - m_State = CAPTURE_ALIGNING; - emit newStatus(Ekos::CAPTURE_ALIGNING); - - setMeridianFlipStage(MF_ALIGNING); - //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); - //emit meridialFlipTracked(); + // reset the meridian flip stage and jump directly MF_NONE, since no + // restart of guiding etc. necessary + setMeridianFlipStage(MF_NONE); return; } - - retries = 0; - checkGuidingAfterFlip(); - } -void Capture::checkGuidingAfterFlip() +bool Capture::checkGuidingAfterFlip() { + // if no meridian flip has completed, we do not touch guiding + if (meridianFlipStage < MF_COMPLETED) + return false; // If we're not autoguiding then we're done if (resumeGuidingAfterFlip == false) - { - resumeSequence(); - // N.B. Set meridian flip stage AFTER resumeSequence() always - setMeridianFlipStage(MF_NONE); - } - else + return false; + + // if we are waiting for a calibration, start it + if (m_State < CAPTURE_CALIBRATING) { appendLogText(i18n("Performing post flip re-calibration and guiding...")); secondsLabel->setText(i18n("Calibrating...")); @@ -4843,9 +4653,50 @@ setMeridianFlipStage(MF_GUIDING); emit meridianFlipCompleted(); + return true; + } + else if (m_State == CAPTURE_CALIBRATING && (guideState == GUIDE_CALIBRATION_ERROR || guideState == GUIDE_ABORTED)) + { + // restart guiding after failure + appendLogText(i18n("Post meridian flip calibration error. Restarting...")); + emit meridianFlipCompleted(); + return true; + } + else + // in all other cases, do not touch + return false; +} + +bool Capture::checkAlignmentAfterFlip() +{ + // if no meridian flip has completed, we do not touch guiding + if (meridianFlipStage < MF_COMPLETED) + return false; + // If we do not need to align then we're done + if (resumeAlignmentAfterFlip == false) + return false; + + // if we are waiting for a calibration, start it + if (m_State < CAPTURE_ALIGNING) + { + appendLogText(i18n("Performing post flip re-alignment...")); + secondsLabel->setText(i18n("Aligning...")); + + retries = 0; + m_State = CAPTURE_ALIGNING; + emit newStatus(Ekos::CAPTURE_ALIGNING); + + setMeridianFlipStage(MF_ALIGNING); + //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); + //emit meridialFlipTracked(); + return true; } + else + // in all other cases, do not touch + return false; } + bool Capture::checkPausing() { if (m_State == CAPTURE_PAUSE_PLANNED) @@ -4864,7 +4715,7 @@ } -bool Capture::checkMeridianFlip() +bool Capture::checkMeridianFlipReady() { if (currentTelescope == nullptr) return false; @@ -4963,56 +4814,36 @@ switch (state) { case GUIDE_IDLE: - case GUIDE_ABORTED: - // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip) - if (isGuidingActive() && meridianFlipStage == MF_NONE && - ((activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY) || - this->m_State == CAPTURE_SUSPENDED || this->m_State == CAPTURE_PAUSED)) - { - appendLogText(i18n("Autoguiding stopped. Aborting...")); - abort(); - } break; case GUIDE_GUIDING: case GUIDE_CALIBRATION_SUCESS: autoGuideReady = true; break; + case GUIDE_ABORTED: case GUIDE_CALIBRATION_ERROR: - // TODO try restarting calibration a couple of times before giving up - if (meridianFlipStage == MF_GUIDING) - { - if (++retries == 3) - { - appendLogText(i18n("Post meridian flip calibration error. Aborting...")); - abort(); - } - else - { - appendLogText(i18n("Post meridian flip calibration error. Restarting...")); - checkGuidingAfterFlip(); - } - } - autoGuideReady = false; + processGuidingFailed(); + guideState = state; break; case GUIDE_DITHERING_SUCCESS: qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering succeeded, capture state" << getCaptureStatusString(m_State); // do nothing if something happened during dithering + appendLogText(i18n("Dithering succeeded.")); if (m_State != CAPTURE_DITHERING) break; if (Options::guidingSettle() > 0) { // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. - appendLogText(i18n("Dither complete. Resuming capture in %1 seconds...", Options::guidingSettle())); - QTimer::singleShot(Options::guidingSettle() * 1000, this, &Ekos::Capture::resumeCapture); + appendLogText(i18n("Dither complete. Resuming in %1 seconds...", Options::guidingSettle())); + QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]() {ditheringState = IPS_OK;}); } else { appendLogText(i18n("Dither complete.")); - resumeCapture(); + ditheringState = IPS_OK; } break; @@ -5024,13 +4855,15 @@ if (Options::guidingSettle() > 0) { // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. - appendLogText(i18n("Warning: Dithering failed. Resuming capture in %1 seconds...", Options::guidingSettle())); - QTimer::singleShot(Options::guidingSettle() * 1000, this, &Ekos::Capture::resumeCapture); + appendLogText(i18n("Warning: Dithering failed. Resuming in %1 seconds...", Options::guidingSettle())); + // set dithering state to OK after settling time and signal to proceed + QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]() {ditheringState = IPS_OK;}); } else { appendLogText(i18n("Warning: Dithering failed.")); - resumeCapture(); + // signal OK so that capturing may continue although dithering failed + ditheringState = IPS_OK; } break; @@ -5042,6 +4875,28 @@ guideState = state; } + +void Capture::processGuidingFailed() +{ + // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip) + if (isGuidingActive() && meridianFlipStage == MF_NONE && + ((activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY) || + this->m_State == CAPTURE_SUSPENDED || this->m_State == CAPTURE_PAUSED)) + { + appendLogText(i18n("Autoguiding stopped. Aborting...")); + abort(); + } + else if (meridianFlipStage == MF_GUIDING) + { + if (++retries >= 3) + { + appendLogText(i18n("Post meridian flip calibration error. Aborting...")); + abort(); + } + } + autoGuideReady = false; +} + void Capture::checkFrameType(int index) { if (index == FRAME_LIGHT) @@ -5119,7 +4974,7 @@ llsq(ExpRaw, ADURaw, a, b); // If we have valid results, let's calculate next exposure - if (a != 0) + if (a != 0.0) { nextExposure = (targetADU - b) / a; // If we get invalid value, let's just proceed iteratively @@ -5129,7 +4984,7 @@ } } - if (nextExposure == 0) + if (nextExposure == 0.0) { if (value < targetADU) nextExposure = activeJob->getExposure() * 1.25; @@ -5278,8 +5133,8 @@ case DURATION_ADU: calibrationOptions.ADUC->setChecked(true); - calibrationOptions.ADUValue->setValue(targetADU); - calibrationOptions.ADUTolerance->setValue(targetADUTolerance); + calibrationOptions.ADUValue->setValue(static_cast(std::round(targetADU))); + calibrationOptions.ADUTolerance->setValue(static_cast(std::round(targetADUTolerance))); break; } @@ -5332,38 +5187,74 @@ Options::setCalibrationFlatDurationIndex(flatFieldDuration); Options::setCalibrationWallAz(wallCoord.az().Degrees()); Options::setCalibrationWallAlt(wallCoord.alt().Degrees()); - Options::setCalibrationADUValue(targetADU); - Options::setCalibrationADUValueTolerance(targetADUTolerance); + Options::setCalibrationADUValue(static_cast(std::round(targetADU))); + Options::setCalibrationADUValueTolerance(static_cast(std::round(targetADUTolerance))); } } -IPState Capture::checkLightFrameAuxiliaryTasks() + +IPState Capture::checkLightFramePendingTasks() { - // step 2: check if meridian flip already is ongoing - if (meridianFlipStage != MF_NONE && meridianFlipStage != MF_READY) - return IPS_BUSY; - // step 3: check if meridian flip is required - if (checkMeridianFlip()) - return IPS_BUSY; - // step 4: check if re-focusing is required - if (m_State == CAPTURE_FOCUSING || startFocusIfRequired()) + // step 0: did one of the pending jobs fail or has the user aborted the capture? + if (m_State == CAPTURE_ABORTED) + return IPS_ALERT; + + // step 1: ensure that the scope cover is open and wait until it's open + IPState coverState = checkLightFrameScopeCoverOpen(); + if (coverState != IPS_OK) + return coverState; + + // step 2: check if pausing has been requested + if (checkPausing() == true) { - m_State = CAPTURE_FOCUSING; + pauseFunction = &Capture::startNextExposure; return IPS_BUSY; } + // step 3: check if meridian flip already is already running + if (checkMeridianFlipRunning()) + return IPS_BUSY; + + // step 4: check if meridian flip is required + if (checkMeridianFlipReady()) + // execute flip before next capture + return IPS_BUSY; + + // step 5: check if post flip alignment is running + if (m_State == CAPTURE_ALIGNING || checkAlignmentAfterFlip()) + return IPS_BUSY; + + // step 6: check if post flip guiding is running + // MF_NONE is set as soon as guiding is running and the guide deviation is below the limit + if (checkGuidingAfterFlip()) + return IPS_BUSY; + + // step 7: check if re-focusing is required + if ((m_State == CAPTURE_FOCUSING && focusState != FOCUS_COMPLETE) || startFocusIfRequired()) + return IPS_BUSY; + + // step 8: check if dithering is required or running + if ((m_State == CAPTURE_DITHERING && ditheringState != IPS_OK) || checkDithering()) + return IPS_BUSY; + + // step 9: resume guiding if it was suspended if (guideState == GUIDE_SUSPENDED) { appendLogText(i18n("Autoguiding resumed.")); emit resumeGuiding(); + // No need to return IPS_BUSY here, we can continue immediately. + // In the case that the capturing sequence has a guiding limit, + // capturing will be interrupted by setGuideDeviation(). } + // everything is ready for capturing light frames calibrationStage = CAL_PRECAPTURE_COMPLETE; return IPS_OK; + } -IPState Capture::checkLightFramePendingTasks() +IPState Capture::checkLightFrameScopeCoverOpen() { switch (activeJob->getFlatFieldSource()) { @@ -5460,10 +5351,12 @@ } break; } - - return checkLightFrameAuxiliaryTasks(); + // scope cover open (or no scope cover) + return IPS_OK; } + + IPState Capture::checkDarkFramePendingTasks() { QStringList shutterfulCCDs = Options::shutterfulCCDs(); @@ -6179,7 +6072,7 @@ processJobCompletion(); } // Else check if meridian condition is met. - else if (checkMeridianFlip()) + else if (checkMeridianFlipReady()) { appendLogText(i18n("Processing meridian flip...")); } @@ -6327,9 +6220,9 @@ if (refocusEveryNCheck->isChecked()) { // How much time passed since we last started the time - uint32_t elapsedSecs = refocusEveryNTimer.elapsed() / 1000; + long elapsedSecs = refocusEveryNTimer.elapsed() / 1000; // How many seconds do we wait for between focusing (60 mins ==> 3600 secs) - uint32_t totalSecs = refocusEveryN->value() * 60; + int totalSecs = refocusEveryN->value() * 60; if (!refocusEveryNTimer.isValid() || forced) { @@ -6352,7 +6245,7 @@ int Capture::getRefocusEveryNTimerElapsedSec() { /* If timer isn't valid, consider there is no focus to be done, that is, that focus was just done */ - return refocusEveryNTimer.isValid() ? refocusEveryNTimer.elapsed() / 1000 : 0; + return refocusEveryNTimer.isValid() ? static_cast(refocusEveryNTimer.elapsed() / 1000) : 0; } void Capture::setAlignResults(double orientation, double ra, double de, double pixscale) @@ -6530,7 +6423,7 @@ void Capture::setCapturedFramesMap(const QString &signature, int count) { - capturedFramesMap[signature] = count; + capturedFramesMap[signature] = static_cast(count); qCDebug(KSTARS_EKOS_CAPTURE) << QString("Client module indicates that storage for '%1' has already %2 captures processed.").arg(signature).arg(count); // Scheduler's captured frame map overrides the progress option of the Capture module @@ -6656,7 +6549,7 @@ 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); + captureTimeout.start(static_cast(exposureIN->value() * 1000 + CAPTURE_TIMEOUT_THRESHOLD)); }; m_CaptureTimeoutCounter++; @@ -6698,10 +6591,10 @@ connect(dslrInfoDialog.get(), &DSLRInfo::infoChanged, [this]() { addDSLRInfo(QString(currentCCD->getDeviceName()), - dslrInfoDialog->sensorMaxWidth, - dslrInfoDialog->sensorMaxHeight, - dslrInfoDialog->sensorPixelW, - dslrInfoDialog->sensorPixelH); + static_cast(dslrInfoDialog->sensorMaxWidth), + static_cast(dslrInfoDialog->sensorMaxHeight), + static_cast(dslrInfoDialog->sensorPixelW), + static_cast(dslrInfoDialog->sensorPixelH)); }); dslrInfoDialog->show(); diff --git a/kstars/ekos/focus/focus.cpp b/kstars/ekos/focus/focus.cpp --- a/kstars/ekos/focus/focus.cpp +++ b/kstars/ekos/focus/focus.cpp @@ -941,6 +941,7 @@ } captureInProgress = true; + emit newStatus(FOCUS_PROGRESS); focusView->setBaseSize(focusingWidget->size()); diff --git a/kstars/ekos/mount/mount.cpp b/kstars/ekos/mount/mount.cpp --- a/kstars/ekos/mount/mount.cpp +++ b/kstars/ekos/mount/mount.cpp @@ -1401,6 +1401,7 @@ case FLIP_WAITING: meridianFlipStatusText->setText("Meridian flip waiting..."); + appendLogText(i18n("Meridian flip waiting.")); break; case FLIP_ACCEPTED: @@ -1412,10 +1413,12 @@ case FLIP_RUNNING: meridianFlipStatusText->setText("Meridian flip running..."); + appendLogText(i18n("Meridian flip started.")); break; case FLIP_COMPLETED: meridianFlipStatusText->setText("Meridian flip completed."); + appendLogText(i18n("Meridian flip completed.")); break; default: