File indexing completed on 2024-04-28 03:43:14
0001 /* Ekos state machine for the Capture module 0002 SPDX-FileCopyrightText: 2022 Wolfgang Reissenberger <sterne-jaeger@openfuture.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "capturemodulestate.h" 0008 #include "ekos/manager/meridianflipstate.h" 0009 #include "ekos/capture/sequencejob.h" 0010 #include "ekos/capture/sequencequeue.h" 0011 #include "ekos/capture/refocusstate.h" 0012 #include "fitsviewer/fitsdata.h" 0013 0014 #include "ksnotification.h" 0015 #include <ekos_capture_debug.h> 0016 0017 #define GD_TIMER_TIMEOUT 60000 0018 0019 namespace Ekos 0020 { 0021 CaptureModuleState::CaptureModuleState(QObject *parent): QObject{parent} 0022 { 0023 m_sequenceQueue.reset(new SequenceQueue()); 0024 m_refocusState.reset(new RefocusState()); 0025 m_TargetADUTolerance = Options::calibrationADUValueTolerance(); 0026 connect(m_refocusState.get(), &RefocusState::newLog, this, &CaptureModuleState::newLog); 0027 0028 getGuideDeviationTimer().setInterval(GD_TIMER_TIMEOUT); 0029 connect(&m_guideDeviationTimer, &QTimer::timeout, this, &CaptureModuleState::checkGuideDeviationTimeout); 0030 0031 setCalibrationPreAction(Options::calibrationPreActionIndex()); 0032 setFlatFieldDuration(static_cast<FlatFieldDuration>(Options::calibrationFlatDurationIndex())); 0033 wallCoord().setAz(Options::calibrationWallAz()); 0034 wallCoord().setAlt(Options::calibrationWallAlt()); 0035 setTargetADU(Options::calibrationADUValue()); 0036 } 0037 0038 QList<SequenceJob *> &CaptureModuleState::allJobs() 0039 { 0040 return m_sequenceQueue->allJobs(); 0041 } 0042 0043 const QUrl &CaptureModuleState::sequenceURL() const 0044 { 0045 return m_sequenceQueue->sequenceURL(); 0046 } 0047 0048 void CaptureModuleState::setSequenceURL(const QUrl &newSequenceURL) 0049 { 0050 m_sequenceQueue->setSequenceURL(newSequenceURL); 0051 } 0052 0053 void CaptureModuleState::setActiveJob(SequenceJob *value) 0054 { 0055 // do nothing if active job is not changed 0056 if (m_activeJob == value) 0057 return; 0058 0059 // clear existing job connections 0060 if (m_activeJob != nullptr) 0061 { 0062 disconnect(this, nullptr, m_activeJob, nullptr); 0063 disconnect(m_activeJob, nullptr, this, nullptr); 0064 // ensure that the device adaptor does not send any new events 0065 m_activeJob->disconnectDeviceAdaptor(); 0066 } 0067 0068 // set the new value 0069 m_activeJob = value; 0070 0071 // create job connections 0072 if (m_activeJob != nullptr) 0073 { 0074 // connect job with device adaptor events 0075 m_activeJob->connectDeviceAdaptor(); 0076 // forward signals to the sequence job 0077 connect(this, &CaptureModuleState::newGuiderDrift, m_activeJob, &SequenceJob::updateGuiderDrift); 0078 // react upon sequence job signals 0079 connect(m_activeJob, &SequenceJob::prepareState, this, &CaptureModuleState::updatePrepareState); 0080 connect(m_activeJob, &SequenceJob::prepareComplete, this, [this](bool success) 0081 { 0082 if (success) 0083 { 0084 setCaptureState(CAPTURE_PROGRESS); 0085 emit executeActiveJob(); 0086 } 0087 else 0088 { 0089 qWarning(KSTARS_EKOS_CAPTURE) << "Capture preparation failed, aborting."; 0090 setCaptureState(CAPTURE_ABORTED); 0091 emit abortCapture(); 0092 } 0093 }, Qt::UniqueConnection); 0094 connect(m_activeJob, &SequenceJob::abortCapture, this, &CaptureModuleState::abortCapture); 0095 connect(m_activeJob, &SequenceJob::captureStarted, this, &CaptureModuleState::captureStarted); 0096 connect(m_activeJob, &SequenceJob::newLog, this, &CaptureModuleState::newLog); 0097 // forward the devices and attributes 0098 m_activeJob->updateDeviceStates(); 0099 m_activeJob->setAutoFocusReady(getRefocusState()->isAutoFocusReady()); 0100 } 0101 0102 } 0103 0104 int CaptureModuleState::activeJobID() 0105 { 0106 if (m_activeJob == nullptr) 0107 return -1; 0108 0109 for (int i = 0; i < allJobs().count(); i++) 0110 { 0111 if (m_activeJob == allJobs().at(i)) 0112 return i; 0113 } 0114 0115 return -1; 0116 0117 } 0118 0119 void CaptureModuleState::initCapturePreparation() 0120 { 0121 setStartingCapture(false); 0122 0123 // Reset progress option if there is no captured frame map set at the time of start - fixes the end-user setting the option just before starting 0124 setIgnoreJobProgress(!hasCapturedFramesMap() && Options::alwaysResetSequenceWhenStarting()); 0125 0126 // Refocus timer should not be reset on deviation error 0127 if (isGuidingDeviationDetected() == false && getCaptureState() != CAPTURE_SUSPENDED) 0128 { 0129 // start timer to measure time until next forced refocus 0130 getRefocusState()->startRefocusTimer(); 0131 } 0132 0133 // Only reset these counters if we are NOT restarting from deviation errors 0134 // So when starting a new job or fresh then we reset them. 0135 if (isGuidingDeviationDetected() == false) 0136 { 0137 resetDitherCounter(); 0138 getRefocusState()->resetInSequenceFocusCounter(); 0139 getRefocusState()->setAdaptiveFocusDone(false); 0140 } 0141 0142 setGuidingDeviationDetected(false); 0143 resetSpikesDetected(); 0144 0145 setCaptureState(CAPTURE_PROGRESS); 0146 setBusy(true); 0147 0148 if (Options::enforceGuideDeviation() && isGuidingOn() == false) 0149 emit newLog(i18n("Warning: Guide deviation is selected but autoguide process was not started.")); 0150 0151 0152 } 0153 0154 void CaptureModuleState::setCaptureState(CaptureState value) 0155 { 0156 bool pause_planned = false; 0157 // handle new capture state 0158 switch (value) 0159 { 0160 case CAPTURE_IDLE: 0161 case CAPTURE_ABORTED: 0162 case CAPTURE_SUSPENDED: 0163 case CAPTURE_PAUSED: 0164 // meridian flip may take place if requested 0165 if (mf_state->getMeridianFlipStage() == MeridianFlipState::MF_REQUESTED) 0166 mf_state->updateMeridianFlipStage(MeridianFlipState::MF_READY); 0167 break; 0168 case CAPTURE_IMAGE_RECEIVED: 0169 // remember pause planning before receiving an image 0170 pause_planned = (m_CaptureState == CAPTURE_PAUSE_PLANNED); 0171 break; 0172 default: 0173 // do nothing 0174 break; 0175 } 0176 0177 // Only emit status if it changed 0178 if (m_CaptureState != value) 0179 { 0180 qCDebug(KSTARS_EKOS_CAPTURE()) << "Capture State changes from" << getCaptureStatusString( 0181 m_CaptureState) << "to" << getCaptureStatusString(value); 0182 m_CaptureState = value; 0183 getMeridianFlipState()->setCaptureState(m_CaptureState); 0184 emit newStatus(m_CaptureState); 0185 // reset to planned state if necessary 0186 if (pause_planned) 0187 { 0188 m_CaptureState = CAPTURE_PAUSE_PLANNED; 0189 emit newStatus(m_CaptureState); 0190 } 0191 } 0192 } 0193 0194 void CaptureModuleState::setGuideState(GuideState state) 0195 { 0196 if (state != m_GuideState) 0197 qCDebug(KSTARS_EKOS_CAPTURE) << "Guiding state changed from" << 0198 Ekos::getGuideStatusString(m_GuideState) 0199 << "to" << Ekos::getGuideStatusString(state); 0200 switch (state) 0201 { 0202 case GUIDE_IDLE: 0203 case GUIDE_GUIDING: 0204 case GUIDE_CALIBRATION_SUCCESS: 0205 break; 0206 0207 case GUIDE_ABORTED: 0208 case GUIDE_CALIBRATION_ERROR: 0209 processGuidingFailed(); 0210 break; 0211 0212 case GUIDE_DITHERING_SUCCESS: 0213 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering succeeded, capture state" << getCaptureStatusString( 0214 getCaptureState()); 0215 // do nothing if something happened during dithering 0216 appendLogText(i18n("Dithering succeeded.")); 0217 if (getCaptureState() != CAPTURE_DITHERING) 0218 break; 0219 0220 if (Options::guidingSettle() > 0) 0221 { 0222 // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. 0223 appendLogText(i18n("Dither complete. Resuming in %1 seconds...", Options::guidingSettle())); 0224 QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]() 0225 { 0226 setDitheringState(IPS_OK); 0227 }); 0228 } 0229 else 0230 { 0231 appendLogText(i18n("Dither complete.")); 0232 setDitheringState(IPS_OK); 0233 } 0234 break; 0235 0236 case GUIDE_DITHERING_ERROR: 0237 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering failed, capture state" << getCaptureStatusString( 0238 getCaptureState()); 0239 if (getCaptureState() != CAPTURE_DITHERING) 0240 break; 0241 0242 if (Options::guidingSettle() > 0) 0243 { 0244 // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that. 0245 appendLogText(i18n("Warning: Dithering failed. Resuming in %1 seconds...", Options::guidingSettle())); 0246 // set dithering state to OK after settling time and signal to proceed 0247 QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]() 0248 { 0249 setDitheringState(IPS_OK); 0250 }); 0251 } 0252 else 0253 { 0254 appendLogText(i18n("Warning: Dithering failed.")); 0255 // signal OK so that capturing may continue although dithering failed 0256 setDitheringState(IPS_OK); 0257 } 0258 0259 break; 0260 0261 default: 0262 break; 0263 } 0264 0265 m_GuideState = state; 0266 // forward it to the currently active sequence job 0267 if (m_activeJob != nullptr) 0268 m_activeJob->setCoreProperty(SequenceJob::SJ_GuiderActive, isActivelyGuiding()); 0269 } 0270 0271 0272 0273 void CaptureModuleState::setCurrentFilterPosition(int position, const QString &name, const QString &focusFilterName) 0274 { 0275 m_CurrentFilterPosition = position; 0276 if (position > 0) 0277 { 0278 m_CurrentFilterName = name; 0279 m_CurrentFocusFilterName = focusFilterName; 0280 } 0281 else 0282 { 0283 m_CurrentFilterName = "--"; 0284 m_CurrentFocusFilterName = "--"; 0285 } 0286 } 0287 0288 QSharedPointer<MeridianFlipState> CaptureModuleState::getMeridianFlipState() 0289 { 0290 // lazy instantiation 0291 if (mf_state.isNull()) 0292 mf_state.reset(new MeridianFlipState()); 0293 0294 return mf_state; 0295 } 0296 0297 void CaptureModuleState::setMeridianFlipState(QSharedPointer<MeridianFlipState> state) 0298 { 0299 // clear old state machine 0300 if (! mf_state.isNull()) 0301 { 0302 mf_state->disconnect(this); 0303 mf_state->deleteLater(); 0304 } 0305 0306 mf_state = state; 0307 connect(mf_state.data(), &Ekos::MeridianFlipState::newMountMFStatus, this, &Ekos::CaptureModuleState::updateMFMountState, 0308 Qt::UniqueConnection); 0309 } 0310 0311 void CaptureModuleState::setObserverName(const QString &value) 0312 { 0313 m_ObserverName = value; 0314 Options::setDefaultObserver(value); 0315 } 0316 0317 void CaptureModuleState::setBusy(bool busy) 0318 { 0319 m_Busy = busy; 0320 emit captureBusy(busy); 0321 } 0322 0323 void CaptureModuleState::decreaseDitherCounter() 0324 { 0325 if (m_ditherCounter > 0) 0326 --m_ditherCounter; 0327 } 0328 0329 void CaptureModuleState::resetDitherCounter() 0330 { 0331 uint value = 0; 0332 if (m_activeJob) 0333 value = m_activeJob->getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt(0); 0334 0335 if (value > 0) 0336 m_ditherCounter = value; 0337 else 0338 m_ditherCounter = Options::ditherFrames(); 0339 } 0340 0341 bool CaptureModuleState::checkDithering() 0342 { 0343 // No need if preview only 0344 if (m_activeJob && m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW) 0345 return false; 0346 0347 if ( (Options::ditherEnabled() || Options::ditherNoGuiding()) 0348 // 2017-09-20 Jasem: No need to dither after post meridian flip guiding 0349 && getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_GUIDING 0350 // We must be either in guide mode or if non-guide dither (via pulsing) is enabled 0351 && (getGuideState() == GUIDE_GUIDING || Options::ditherNoGuiding()) 0352 // Must be only done for light frames 0353 && (m_activeJob != nullptr && m_activeJob->getFrameType() == FRAME_LIGHT) 0354 // Check dither counter 0355 && m_ditherCounter == 0) 0356 { 0357 // reset the dither counter 0358 resetDitherCounter(); 0359 0360 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering..."; 0361 appendLogText(i18n("Dithering...")); 0362 0363 setCaptureState(CAPTURE_DITHERING); 0364 setDitheringState(IPS_BUSY); 0365 0366 return true; 0367 } 0368 // no dithering required 0369 return false; 0370 } 0371 0372 void CaptureModuleState::updateMFMountState(MeridianFlipState::MeridianFlipMountState status) 0373 { 0374 qCDebug(KSTARS_EKOS_CAPTURE) << "updateMFMountState: " << MeridianFlipState::meridianFlipStatusString(status); 0375 0376 switch (status) 0377 { 0378 case MeridianFlipState::MOUNT_FLIP_NONE: 0379 // MF_NONE as external signal ignored so that re-alignment and guiding are processed first 0380 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED) 0381 updateMeridianFlipStage(MeridianFlipState::MF_NONE); 0382 break; 0383 0384 case MeridianFlipState::MOUNT_FLIP_PLANNED: 0385 if (getMeridianFlipState()->getMeridianFlipStage() > MeridianFlipState::MF_REQUESTED) 0386 { 0387 // This should never happen, since a meridian flip seems to be ongoing 0388 qCritical(KSTARS_EKOS_CAPTURE) << "Accepting meridian flip request while being in stage " << 0389 getMeridianFlipState()->getMeridianFlipStage(); 0390 } 0391 0392 // If we are autoguiding, we should resume autoguiding after flip 0393 getMeridianFlipState()->setResumeGuidingAfterFlip(isGuidingOn()); 0394 0395 // mark flip as requested 0396 updateMeridianFlipStage(MeridianFlipState::MF_REQUESTED); 0397 // if capture is not running, immediately accept it 0398 if (m_CaptureState == CAPTURE_IDLE || m_CaptureState == CAPTURE_ABORTED 0399 || m_CaptureState == CAPTURE_COMPLETE || m_CaptureState == CAPTURE_PAUSED) 0400 getMeridianFlipState()->updateMFMountState(MeridianFlipState::MOUNT_FLIP_ACCEPTED); 0401 0402 break; 0403 0404 case MeridianFlipState::MOUNT_FLIP_RUNNING: 0405 updateMeridianFlipStage(MeridianFlipState::MF_INITIATED); 0406 setCaptureState(CAPTURE_MERIDIAN_FLIP); 0407 break; 0408 0409 case MeridianFlipState::MOUNT_FLIP_COMPLETED: 0410 updateMeridianFlipStage(MeridianFlipState::MF_COMPLETED); 0411 break; 0412 0413 default: 0414 break; 0415 0416 } 0417 } 0418 0419 void CaptureModuleState::updateMeridianFlipStage(const MeridianFlipState::MFStage &stage) 0420 { 0421 // forward the stage to the module state 0422 getMeridianFlipState()->updateMeridianFlipStage(stage); 0423 0424 // handle state changes for other modules 0425 switch (stage) 0426 { 0427 case MeridianFlipState::MF_READY: 0428 break; 0429 0430 case MeridianFlipState::MF_INITIATED: 0431 emit meridianFlipStarted(); 0432 break; 0433 0434 case MeridianFlipState::MF_COMPLETED: 0435 0436 // Reset HFR Check counter after meridian flip 0437 if (getRefocusState()->isInSequenceFocus()) 0438 { 0439 qCDebug(KSTARS_EKOS_CAPTURE) << "Resetting HFR Check counter after meridian flip."; 0440 //firstAutoFocus = true; 0441 getRefocusState()->setInSequenceFocusCounter(0); 0442 } 0443 0444 // after a meridian flip we do not need to dither 0445 if ( Options::ditherEnabled() || Options::ditherNoGuiding()) 0446 resetDitherCounter(); 0447 0448 // if requested set flag so it perform refocus before next frame 0449 if (Options::refocusAfterMeridianFlip() == true) 0450 getRefocusState()->setRefocusAfterMeridianFlip(true); 0451 // If dome is syncing, wait until it stops 0452 if (hasDome && (m_domeState == ISD::Dome::DOME_MOVING_CW || m_domeState == ISD::Dome::DOME_MOVING_CCW)) 0453 return; 0454 0455 KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"), 0456 KSNotification::Capture); 0457 0458 getMeridianFlipState()->processFlipCompleted(); 0459 0460 // if the capturing has been paused before the flip, reset the state to paused, otherwise to idle 0461 setCaptureState(m_ContinueAction == CONTINUE_ACTION_NONE ? CAPTURE_IDLE : CAPTURE_PAUSED); 0462 break; 0463 0464 default: 0465 break; 0466 } 0467 // forward the new stage 0468 emit newMeridianFlipStage(stage); 0469 } 0470 0471 bool CaptureModuleState::checkMeridianFlipActive() 0472 { 0473 return (getMeridianFlipState()->checkMeridianFlipRunning() || 0474 checkPostMeridianFlipActions() || 0475 checkMeridianFlipReady()); 0476 } 0477 0478 bool CaptureModuleState::checkMeridianFlipReady() 0479 { 0480 if (hasTelescope == false) 0481 return false; 0482 0483 // If active job is taking flat field image at a wall source 0484 // then do not flip. 0485 if (m_activeJob && m_activeJob->getFrameType() == FRAME_FLAT && m_activeJob->getCalibrationPreAction() & ACTION_WALL) 0486 return false; 0487 0488 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED) 0489 // if no flip has been requested or is already ongoing 0490 return false; 0491 0492 // meridian flip requested or already in action 0493 0494 // Reset frame if we need to do focusing later on 0495 if (m_refocusState->isInSequenceFocus() || 0496 (Options::enforceRefocusEveryN() && m_refocusState->getRefocusEveryNTimerElapsedSec() > 0)) 0497 emit resetFocus(); 0498 0499 // signal that meridian flip may take place 0500 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_REQUESTED) 0501 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_READY); 0502 0503 0504 return true; 0505 } 0506 0507 bool CaptureModuleState::checkPostMeridianFlipActions() 0508 { 0509 // step 1: check if post flip alignment is running 0510 if (m_CaptureState == CAPTURE_ALIGNING || checkAlignmentAfterFlip()) 0511 return true; 0512 0513 // step 2: check if post flip guiding is running 0514 // MF_NONE is set as soon as guiding is running and the guide deviation is below the limit 0515 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED && m_GuideState != GUIDE_GUIDING 0516 && checkGuidingAfterFlip()) 0517 return true; 0518 0519 // step 3: in case that a meridian flip has been completed and a guide deviation limit is set, we wait 0520 // until the guide deviation is reported to be below the limit (@see setGuideDeviation(double, double)). 0521 // Otherwise the meridian flip is complete 0522 if (m_CaptureState == CAPTURE_CALIBRATING 0523 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING) 0524 { 0525 if (Options::enforceGuideDeviation() || Options::enforceStartGuiderDrift()) 0526 return true; 0527 else 0528 updateMeridianFlipStage(MeridianFlipState::MF_NONE); 0529 } 0530 0531 // all actions completed or no flip running 0532 return false; 0533 } 0534 0535 bool CaptureModuleState::checkGuidingAfterFlip() 0536 { 0537 // if no meridian flip has completed, we do not touch guiding 0538 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED) 0539 return false; 0540 // If we're not autoguiding then we're done 0541 if (getMeridianFlipState()->resumeGuidingAfterFlip() == false) 0542 { 0543 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE); 0544 return false; 0545 } 0546 0547 // if we are waiting for a calibration, start it 0548 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED 0549 && getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_GUIDING) 0550 { 0551 appendLogText(i18n("Performing post flip re-calibration and guiding...")); 0552 0553 setCaptureState(CAPTURE_CALIBRATING); 0554 0555 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_GUIDING); 0556 emit guideAfterMeridianFlip(); 0557 return true; 0558 } 0559 else if (m_CaptureState == CAPTURE_CALIBRATING) 0560 { 0561 if (getGuideState() == GUIDE_CALIBRATION_ERROR || getGuideState() == GUIDE_ABORTED) 0562 { 0563 // restart guiding after failure 0564 appendLogText(i18n("Post meridian flip calibration error. Restarting...")); 0565 emit guideAfterMeridianFlip(); 0566 return true; 0567 } 0568 else if (getGuideState() != GUIDE_GUIDING) 0569 // waiting for guiding to start 0570 return true; 0571 else 0572 // guiding is running 0573 return false; 0574 } 0575 else 0576 // in all other cases, do not touch 0577 return false; 0578 } 0579 0580 void CaptureModuleState::processGuidingFailed() 0581 { 0582 if (m_FocusState > FOCUS_PROGRESS) 0583 { 0584 appendLogText(i18n("Autoguiding stopped. Waiting for autofocus to finish...")); 0585 } 0586 // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip) 0587 else if (isGuidingOn() 0588 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_NONE && 0589 // JM 2022.08.03: Only abort if the current job is LIGHT. For calibration frames, we can ignore guide failures. 0590 ((m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT) || 0591 m_CaptureState == CAPTURE_SUSPENDED || m_CaptureState == CAPTURE_PAUSED)) 0592 { 0593 appendLogText(i18n("Autoguiding stopped. Aborting...")); 0594 emit abortCapture(); 0595 } 0596 else if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING) 0597 { 0598 if (increaseAlignmentRetries() >= 3) 0599 { 0600 appendLogText(i18n("Post meridian flip calibration error. Aborting...")); 0601 emit abortCapture(); 0602 } 0603 } 0604 } 0605 0606 void CaptureModuleState::updateAdaptiveFocusState(bool success) 0607 { 0608 m_refocusState->setAdaptiveFocusDone(true); 0609 0610 // Always process the adaptive focus state change, incl if a MF has also just started 0611 if (success) 0612 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus completed successfully"; 0613 else 0614 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus failed"; 0615 0616 m_refocusState->setAutoFocusReady(true); 0617 // forward to the active job 0618 if (m_activeJob != nullptr) 0619 m_activeJob->setAutoFocusReady(true); 0620 0621 setFocusState(FOCUS_COMPLETE); 0622 emit newLog(i18n(success ? "Adaptive focus complete." : "Adaptive focus failed. Continuing...")); 0623 } 0624 0625 void CaptureModuleState::updateFocusState(FocusState state) 0626 { 0627 if (state != m_FocusState) 0628 qCDebug(KSTARS_EKOS_CAPTURE) << "Focus State changed from" << 0629 Ekos::getFocusStatusString(m_FocusState) << 0630 "to" << Ekos::getFocusStatusString(state); 0631 setFocusState(state); 0632 0633 // Do not process if meridian flip in progress 0634 if (getMeridianFlipState()->checkMeridianFlipRunning()) 0635 return; 0636 0637 switch (state) 0638 { 0639 // Do not process when aborted 0640 case FOCUS_ABORTED: 0641 if (!(getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_INITIATED)) 0642 { 0643 // Meridian flip will abort focusing. In this case, after the meridian flip has completed capture 0644 // will restart the re-focus attempt. Therefore we only abort capture if meridian flip is not running. 0645 emit newFocusStatus(state); 0646 appendLogText(i18n("Autofocus failed. Aborting exposure...")); 0647 emit abortCapture(); 0648 } 0649 break; 0650 case FOCUS_COMPLETE: 0651 // enable option to have a refocus event occur if HFR goes over threshold 0652 m_refocusState->setAutoFocusReady(true); 0653 // forward to the active job 0654 if (m_activeJob != nullptr) 0655 m_activeJob->setAutoFocusReady(true); 0656 // reset the timer if a full autofocus was run (rather than an HFR check) 0657 if (m_refocusState->getFocusHFRInAutofocus()) 0658 m_refocusState->startRefocusTimer(true); 0659 0660 // update HFR Threshold for those algorithms that use Autofocus as reference 0661 if (Options::hFRCheckAlgorithm() == HFR_CHECK_MEDIAN_MEASURE || 0662 (m_refocusState->getFocusHFRInAutofocus() && Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS)) 0663 { 0664 m_refocusState->addHFRValue(getFocusFilterName()); 0665 updateHFRThreshold(); 0666 } 0667 emit newFocusStatus(state); 0668 break; 0669 default: 0670 break; 0671 } 0672 0673 if (m_activeJob != nullptr) 0674 m_activeJob->setFocusStatus(state); 0675 } 0676 0677 bool CaptureModuleState::startFocusIfRequired() 0678 { 0679 // Do not start focus or adaptive focus if: 0680 // 1. There is no active job, or 0681 // 2. Target frame is not LIGHT 0682 // 3. Capture is preview only 0683 if (m_activeJob == nullptr || m_activeJob->getFrameType() != FRAME_LIGHT 0684 || m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW) 0685 return false; 0686 0687 RefocusState::RefocusReason reason = m_refocusState->checkFocusRequired(); 0688 0689 // no focusing necessary 0690 if (reason == RefocusState::REFOCUS_NONE) 0691 return false; 0692 0693 // clear the flag for refocusing after the meridian flip 0694 m_refocusState->setRefocusAfterMeridianFlip(false); 0695 0696 // Post meridian flip we need to reset filter _before_ running in-sequence focusing 0697 // as it could have changed for whatever reason (e.g. alignment used a different filter). 0698 // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it 0699 // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager. 0700 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_NONE) 0701 { 0702 int targetFilterPosition = m_activeJob->getTargetFilter(); 0703 if (targetFilterPosition > 0 && targetFilterPosition != getCurrentFilterPosition()) 0704 emit newFilterPosition(targetFilterPosition); 0705 } 0706 0707 emit abortFastExposure(); 0708 updateFocusState(FOCUS_PROGRESS); 0709 0710 switch (reason) 0711 { 0712 case RefocusState::REFOCUS_HFR: 0713 m_refocusState->resetInSequenceFocusCounter(); 0714 (Options::hFRDeviation() == 0.0) ? emit runAutoFocus(false) : emit checkFocus(Options::hFRDeviation()); 0715 qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started..."; 0716 break; 0717 case RefocusState::REFOCUS_ADAPTIVE: 0718 m_refocusState->setAdaptiveFocusDone(true); 0719 emit adaptiveFocus(); 0720 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus started..."; 0721 break; 0722 case RefocusState::REFOCUS_TEMPERATURE: 0723 case RefocusState::REFOCUS_TIME_ELAPSED: 0724 case RefocusState::REFOCUS_POST_MF: 0725 // If we are over 30 mins since last autofocus, we'll reset frame. 0726 if (m_refocusState->getRefocusEveryNTimerElapsedSec() >= 1800) 0727 emit resetFocus(); 0728 0729 // force refocus 0730 emit runAutoFocus(false); 0731 // restart in sequence counting 0732 m_refocusState->resetInSequenceFocusCounter(); 0733 qCDebug(KSTARS_EKOS_CAPTURE) << "Refocusing started..."; 0734 break; 0735 default: 0736 // this should not happen, since this case is handled above 0737 return false; 0738 } 0739 0740 setCaptureState(CAPTURE_FOCUSING); 0741 return true; 0742 } 0743 0744 void CaptureModuleState::updateHFRThreshold() 0745 { 0746 // For Ficed algo no need to update the HFR threshold 0747 if (Options::hFRCheckAlgorithm() == HFR_CHECK_FIXED) 0748 return; 0749 0750 QString finalFilter = getFocusFilterName(); 0751 QList<double> filterHFRList = m_refocusState->getHFRMap()[finalFilter]; 0752 0753 // Update the limit only if HFR values have been measured for the current filter 0754 if (filterHFRList.empty()) 0755 return; 0756 0757 double value = 0; 0758 if (Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS) 0759 value = filterHFRList.last(); 0760 else // algo = Median Measure 0761 { 0762 int count = filterHFRList.size(); 0763 if (count > 1) 0764 value = (count % 2) ? filterHFRList[count / 2] : (filterHFRList[count / 2 - 1] + filterHFRList[count / 2]) / 2.0; 0765 else if (count == 1) 0766 value = filterHFRList[0]; 0767 } 0768 value += value * (Options::hFRThresholdPercentage() / 100.0); 0769 Options::setHFRDeviation(value); 0770 emit newLimitFocusHFR(value); // Updates the limits UI with the new HFR threshold 0771 } 0772 0773 QString CaptureModuleState::getFocusFilterName() 0774 { 0775 QString finalFilter; 0776 if (m_CurrentFilterPosition > 0) 0777 // If we are using filters, then we retrieve which filter is currently active. 0778 // We check if filter lock is used, and store that instead of the current filter. 0779 // e.g. If current filter HA, but lock filter is L, then the HFR value is stored for L filter. 0780 // If no lock filter exists, then we store as is (HA) 0781 finalFilter = (m_CurrentFocusFilterName == "--" ? m_CurrentFilterName : m_CurrentFocusFilterName); 0782 else 0783 // No filters 0784 finalFilter = "--"; 0785 return finalFilter; 0786 } 0787 0788 bool CaptureModuleState::checkAlignmentAfterFlip() 0789 { 0790 // if no meridian flip has completed, we do not touch guiding 0791 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED) 0792 return false; 0793 // If we do not need to align then we're done 0794 if (getMeridianFlipState()->resumeAlignmentAfterFlip() == false) 0795 return false; 0796 0797 // if we are waiting for a calibration, start it 0798 if (m_CaptureState < CAPTURE_ALIGNING) 0799 { 0800 appendLogText(i18n("Performing post flip re-alignment...")); 0801 0802 resetAlignmentRetries(); 0803 setCaptureState(CAPTURE_ALIGNING); 0804 0805 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_ALIGNING); 0806 return true; 0807 } 0808 else 0809 // in all other cases, do not touch 0810 return false; 0811 } 0812 0813 void CaptureModuleState::checkGuideDeviationTimeout() 0814 { 0815 if (m_activeJob && m_activeJob->getStatus() == JOB_ABORTED 0816 && isGuidingDeviationDetected()) 0817 { 0818 appendLogText(i18n("Guide module timed out.")); 0819 setGuidingDeviationDetected(false); 0820 0821 // If capture was suspended, it should be aborted (failed) now. 0822 if (m_CaptureState == CAPTURE_SUSPENDED) 0823 { 0824 setCaptureState(CAPTURE_ABORTED); 0825 } 0826 } 0827 } 0828 0829 void CaptureModuleState::setGuideDeviation(double deviation_rms) 0830 { 0831 // communicate the new guiding deviation 0832 emit newGuiderDrift(deviation_rms); 0833 0834 const QString deviationText = QString("%1").arg(deviation_rms, 0, 'f', 3); 0835 0836 // if guiding deviations occur and no job is active, check if a meridian flip is ready to be executed 0837 if (m_activeJob == nullptr && checkMeridianFlipReady()) 0838 return; 0839 0840 // if the job is in the startup phase and no flip is neither requested nor running, check if the deviation is below the initial guiding limit 0841 if (m_CaptureState == CAPTURE_PROGRESS && 0842 getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED && 0843 getMeridianFlipState()->checkMeridianFlipRunning() == false) 0844 { 0845 // initial guiding deviation irrelevant or below limit 0846 if (Options::enforceStartGuiderDrift() == false || deviation_rms < Options::startGuideDeviation()) 0847 { 0848 setCaptureState(CAPTURE_CALIBRATING); 0849 if (Options::enforceStartGuiderDrift()) 0850 appendLogText(i18n("Initial guiding deviation %1 below limit value of %2 arcsecs", 0851 deviationText, Options::startGuideDeviation())); 0852 setGuidingDeviationDetected(false); 0853 setStartingCapture(false); 0854 } 0855 else 0856 { 0857 // warn only once 0858 if (isGuidingDeviationDetected() == false) 0859 appendLogText(i18n("Initial guiding deviation %1 exceeded limit value of %2 arcsecs", 0860 deviationText, Options::startGuideDeviation())); 0861 0862 setGuidingDeviationDetected(true); 0863 0864 // Check if we need to start meridian flip. If yes, we need to start capturing 0865 // to ensure that capturing is recovered after the flip 0866 if (checkMeridianFlipReady()) 0867 emit startCapture(); 0868 } 0869 0870 // in any case, do not proceed 0871 return; 0872 } 0873 0874 // If guiding is started after a meridian flip we will start getting guide deviations again 0875 // if the guide deviations are within our limits, we resume the sequence 0876 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING) 0877 { 0878 // If the user didn't select any guiding deviation, the meridian flip is completed 0879 if (Options::enforceGuideDeviation() == false || deviation_rms < Options::guideDeviation()) 0880 { 0881 appendLogText(i18n("Post meridian flip calibration completed successfully.")); 0882 // N.B. Set meridian flip stage AFTER resumeSequence() always 0883 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE); 0884 return; 0885 } 0886 } 0887 0888 // Check for initial deviation in the middle of a sequence (above just checks at the start of a sequence). 0889 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT 0890 && isStartingCapture() && Options::enforceStartGuiderDrift()) 0891 { 0892 setStartingCapture(false); 0893 if (deviation_rms > Options::startGuideDeviation()) 0894 { 0895 appendLogText(i18n("Guiding deviation at capture startup %1 exceeded limit %2 arcsecs.", 0896 deviationText, Options::startGuideDeviation())); 0897 emit suspendCapture(); 0898 setGuidingDeviationDetected(true); 0899 0900 // Check if we need to start meridian flip. If yes, we need to start capturing 0901 // to ensure that capturing is recovered after the flip 0902 if (checkMeridianFlipReady()) 0903 emit startCapture(); 0904 else 0905 getGuideDeviationTimer().start(); 0906 return; 0907 } 0908 else 0909 appendLogText(i18n("Guiding deviation at capture startup %1 below limit value of %2 arcsecs", 0910 deviationText, Options::startGuideDeviation())); 0911 } 0912 0913 if (m_CaptureState != CAPTURE_SUSPENDED) 0914 { 0915 0916 // We don't enforce limit on previews or non-LIGHT frames 0917 if ((Options::enforceGuideDeviation() == false) 0918 || 0919 (m_activeJob && (m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW || 0920 m_activeJob->getExposeLeft() == 0.0 || 0921 m_activeJob->getFrameType() != FRAME_LIGHT))) 0922 return; 0923 0924 // If we have an active busy job, let's abort it if guiding deviation is exceeded. 0925 // And we accounted for the spike 0926 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT) 0927 { 0928 if (deviation_rms <= Options::guideDeviation()) 0929 resetSpikesDetected(); 0930 else 0931 { 0932 // Require several consecutive spikes to fail. 0933 if (increaseSpikesDetected() < Options::guideDeviationReps()) 0934 return; 0935 0936 appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 arcsecs for %4 consecutive samples, " 0937 "suspending exposure and waiting for guider up to %3 seconds.", 0938 deviationText, Options::guideDeviation(), 0939 QString("%L1").arg(getGuideDeviationTimer().interval() / 1000.0, 0, 'f', 3), 0940 Options::guideDeviationReps())); 0941 0942 emit suspendCapture(); 0943 0944 resetSpikesDetected(); 0945 setGuidingDeviationDetected(true); 0946 0947 // Check if we need to start meridian flip. If yes, we need to start capturing 0948 // to ensure that capturing is recovered after the flip 0949 if (checkMeridianFlipReady()) 0950 emit startCapture(); 0951 else 0952 getGuideDeviationTimer().start(); 0953 } 0954 return; 0955 } 0956 } 0957 0958 // Find the first aborted job 0959 SequenceJob *abortedJob = nullptr; 0960 for(auto &job : allJobs()) 0961 { 0962 if (job->getStatus() == JOB_ABORTED) 0963 { 0964 abortedJob = job; 0965 break; 0966 } 0967 } 0968 0969 if (abortedJob != nullptr && isGuidingDeviationDetected()) 0970 { 0971 if (deviation_rms <= Options::startGuideDeviation()) 0972 { 0973 getGuideDeviationTimer().stop(); 0974 0975 // Start with delay if start hasn't been triggered before 0976 if (! m_captureDelayTimer.isActive()) 0977 { 0978 // if capturing has been suspended, restart it 0979 if (m_CaptureState == CAPTURE_SUSPENDED) 0980 { 0981 const int seqDelay = abortedJob->getCoreProperty(SequenceJob::SJ_Delay).toInt(); 0982 if (seqDelay == 0) 0983 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, " 0984 "resuming exposure.", 0985 deviationText, Options::startGuideDeviation())); 0986 else 0987 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, " 0988 "resuming exposure in %3 seconds.", 0989 deviationText, Options::startGuideDeviation(), seqDelay / 1000.0)); 0990 0991 m_captureDelayTimer.start(seqDelay); 0992 } 0993 } 0994 return; 0995 } 0996 else 0997 { 0998 // stop the delayed capture start if necessary 0999 if (m_captureDelayTimer.isActive()) 1000 m_captureDelayTimer.stop(); 1001 1002 appendLogText(i18n("Guiding deviation %1 is still higher than limit value of %2 arcsecs.", 1003 deviationText, Options::startGuideDeviation())); 1004 } 1005 } 1006 } 1007 1008 void CaptureModuleState::addDownloadTime(double time) 1009 { 1010 totalDownloadTime += time; 1011 downloadsCounter++; 1012 } 1013 1014 int CaptureModuleState::pendingJobCount() 1015 { 1016 int completedJobs = 0; 1017 1018 foreach (SequenceJob * job, allJobs()) 1019 { 1020 if (job->getStatus() == JOB_DONE) 1021 completedJobs++; 1022 } 1023 1024 return (allJobs().count() - completedJobs); 1025 1026 } 1027 1028 QString CaptureModuleState::jobState(int id) 1029 { 1030 if (id < allJobs().count()) 1031 { 1032 SequenceJob * job = allJobs().at(id); 1033 return job->getStatusString(); 1034 } 1035 1036 return QString(); 1037 1038 } 1039 1040 QString CaptureModuleState::jobFilterName(int id) 1041 { 1042 if (id < allJobs().count()) 1043 { 1044 SequenceJob * job = allJobs().at(id); 1045 return job->getCoreProperty(SequenceJob::SJ_Filter).toString(); 1046 } 1047 1048 return QString(); 1049 1050 } 1051 1052 CCDFrameType CaptureModuleState::jobFrameType(int id) 1053 { 1054 if (id < allJobs().count()) 1055 { 1056 SequenceJob * job = allJobs().at(id); 1057 return job->getFrameType(); 1058 } 1059 1060 return FRAME_NONE; 1061 } 1062 1063 int CaptureModuleState::jobImageProgress(int id) 1064 { 1065 if (id < allJobs().count()) 1066 { 1067 SequenceJob * job = allJobs().at(id); 1068 return job->getCompleted(); 1069 } 1070 1071 return -1; 1072 } 1073 1074 int CaptureModuleState::jobImageCount(int id) 1075 { 1076 if (id < allJobs().count()) 1077 { 1078 SequenceJob * job = allJobs().at(id); 1079 return job->getCoreProperty(SequenceJob::SJ_Count).toInt(); 1080 } 1081 1082 return -1; 1083 } 1084 1085 double CaptureModuleState::jobExposureProgress(int id) 1086 { 1087 if (id < allJobs().count()) 1088 { 1089 SequenceJob * job = allJobs().at(id); 1090 return job->getExposeLeft(); 1091 } 1092 1093 return -1; 1094 } 1095 1096 double CaptureModuleState::jobExposureDuration(int id) 1097 { 1098 if (id < allJobs().count()) 1099 { 1100 SequenceJob * job = allJobs().at(id); 1101 return job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(); 1102 } 1103 1104 return -1; 1105 } 1106 1107 double CaptureModuleState::progressPercentage() 1108 { 1109 int totalImageCount = 0; 1110 int totalImageCompleted = 0; 1111 1112 foreach (SequenceJob * job, allJobs()) 1113 { 1114 totalImageCount += job->getCoreProperty(SequenceJob::SJ_Count).toInt(); 1115 totalImageCompleted += job->getCompleted(); 1116 } 1117 1118 if (totalImageCount != 0) 1119 return ((static_cast<double>(totalImageCompleted) / totalImageCount) * 100.0); 1120 else 1121 return -1; 1122 } 1123 1124 int CaptureModuleState::activeJobRemainingTime() 1125 { 1126 if (m_activeJob == nullptr) 1127 return -1; 1128 1129 return m_activeJob->getJobRemainingTime(averageDownloadTime()); 1130 } 1131 1132 int CaptureModuleState::overallRemainingTime() 1133 { 1134 int remaining = 0; 1135 double estimatedDownloadTime = averageDownloadTime(); 1136 1137 foreach (SequenceJob * job, allJobs()) 1138 remaining += job->getJobRemainingTime(estimatedDownloadTime); 1139 1140 return remaining; 1141 } 1142 1143 QString CaptureModuleState::sequenceQueueStatus() 1144 { 1145 if (allJobs().count() == 0) 1146 return "Invalid"; 1147 1148 if (isBusy()) 1149 return "Running"; 1150 1151 int idle = 0, error = 0, complete = 0, aborted = 0, running = 0; 1152 1153 foreach (SequenceJob * job, allJobs()) 1154 { 1155 switch (job->getStatus()) 1156 { 1157 case JOB_ABORTED: 1158 aborted++; 1159 break; 1160 case JOB_BUSY: 1161 running++; 1162 break; 1163 case JOB_DONE: 1164 complete++; 1165 break; 1166 case JOB_ERROR: 1167 error++; 1168 break; 1169 case JOB_IDLE: 1170 idle++; 1171 break; 1172 } 1173 } 1174 1175 if (error > 0) 1176 return "Error"; 1177 1178 if (aborted > 0) 1179 { 1180 if (m_CaptureState == CAPTURE_SUSPENDED) 1181 return "Suspended"; 1182 else 1183 return "Aborted"; 1184 } 1185 1186 if (running > 0) 1187 return "Running"; 1188 1189 if (idle == allJobs().count()) 1190 return "Idle"; 1191 1192 if (complete == allJobs().count()) 1193 return "Complete"; 1194 1195 return "Invalid"; 1196 } 1197 1198 QJsonObject CaptureModuleState::calibrationSettings() 1199 { 1200 QJsonObject settings = 1201 { 1202 {"preAction", static_cast<int>(calibrationPreAction())}, 1203 {"duration", flatFieldDuration()}, 1204 {"az", wallCoord().az().Degrees()}, 1205 {"al", wallCoord().alt().Degrees()}, 1206 {"adu", targetADU()}, 1207 {"tolerance", targetADUTolerance()}, 1208 }; 1209 1210 return settings; 1211 } 1212 1213 void CaptureModuleState::setCalibrationSettings(const QJsonObject &settings) 1214 { 1215 const int preAction = settings["preAction"].toInt(calibrationPreAction()); 1216 const int duration = settings["duration"].toInt(flatFieldDuration()); 1217 const double az = settings["az"].toDouble(wallCoord().az().Degrees()); 1218 const double al = settings["al"].toDouble(wallCoord().alt().Degrees()); 1219 const int adu = settings["adu"].toInt(static_cast<int>(std::round(targetADU()))); 1220 const int tolerance = settings["tolerance"].toInt(static_cast<int>(std::round(targetADUTolerance()))); 1221 1222 setCalibrationPreAction(static_cast<CalibrationPreActions>(preAction)); 1223 setFlatFieldDuration(static_cast<FlatFieldDuration>(duration)); 1224 wallCoord().setAz(az); 1225 wallCoord().setAlt(al); 1226 setTargetADU(adu); 1227 setTargetADUTolerance(tolerance); 1228 } 1229 1230 bool CaptureModuleState::setDarkFlatExposure(SequenceJob *job) 1231 { 1232 const auto darkFlatFilter = job->getCoreProperty(SequenceJob::SJ_Filter).toString(); 1233 const auto darkFlatBinning = job->getCoreProperty(SequenceJob::SJ_Binning).toPoint(); 1234 const auto darkFlatADU = job->getCoreProperty(SequenceJob::SJ_TargetADU).toInt(); 1235 1236 for (auto &oneJob : allJobs()) 1237 { 1238 if (oneJob->getFrameType() != FRAME_FLAT) 1239 continue; 1240 1241 const auto filter = oneJob->getCoreProperty(SequenceJob::SJ_Filter).toString(); 1242 1243 // Match filter, if one exists. 1244 if (!darkFlatFilter.isEmpty() && darkFlatFilter != filter) 1245 continue; 1246 1247 // Match binning 1248 const auto binning = oneJob->getCoreProperty(SequenceJob::SJ_Binning).toPoint(); 1249 if (darkFlatBinning != binning) 1250 continue; 1251 1252 // Match ADU, if used. 1253 const auto adu = oneJob->getCoreProperty(SequenceJob::SJ_TargetADU).toInt(); 1254 if (job->getFlatFieldDuration() == DURATION_ADU) 1255 { 1256 if (darkFlatADU != adu) 1257 continue; 1258 } 1259 1260 // Now get the exposure 1261 job->setCoreProperty(SequenceJob::SJ_Exposure, oneJob->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()); 1262 1263 return true; 1264 } 1265 return false; 1266 } 1267 1268 void CaptureModuleState::checkSeqBoundary(QUrl sequenceURL) 1269 { 1270 // No updates during meridian flip 1271 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_ALIGNING) 1272 return; 1273 1274 auto placeholderPath = PlaceholderPath(sequenceURL.toLocalFile()); 1275 setNextSequenceID(placeholderPath.checkSeqBoundary(*getActiveJob())); 1276 } 1277 1278 bool CaptureModuleState::isModelinDSLRInfo(const QString &model) 1279 { 1280 auto pos = std::find_if(m_DSLRInfos.begin(), m_DSLRInfos.end(), [model](QMap<QString, QVariant> &oneDSLRInfo) 1281 { 1282 return (oneDSLRInfo["Model"] == model); 1283 }); 1284 1285 return (pos != m_DSLRInfos.end()); 1286 } 1287 1288 void CaptureModuleState::setCapturedFramesCount(const QString &signature, uint16_t count) 1289 { 1290 m_capturedFramesMap[signature] = count; 1291 qCDebug(KSTARS_EKOS_CAPTURE) << 1292 QString("Client module indicates that storage for '%1' has already %2 captures processed.").arg(signature).arg(count); 1293 // Scheduler's captured frame map overrides the progress option of the Capture module 1294 setIgnoreJobProgress(false); 1295 } 1296 1297 void CaptureModuleState::changeSequenceValue(int index, QString key, QString value) 1298 { 1299 QJsonArray seqArray = getSequence(); 1300 QJsonObject oneSequence = seqArray[index].toObject(); 1301 oneSequence[key] = value; 1302 seqArray.replace(index, oneSequence); 1303 setSequence(seqArray); 1304 emit sequenceChanged(seqArray); 1305 } 1306 1307 void CaptureModuleState::addCapturedFrame(const QString &signature) 1308 { 1309 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature); 1310 if (m_capturedFramesMap.end() != frame_item) 1311 frame_item.value()++; 1312 else m_capturedFramesMap[signature] = 1; 1313 1314 } 1315 1316 void CaptureModuleState::removeCapturedFrameCount(const QString &signature, uint16_t count) 1317 { 1318 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature); 1319 if (m_capturedFramesMap.end() != frame_item) 1320 { 1321 // remove the frame count 1322 frame_item.value() = frame_item.value() - count; 1323 // clear signature entry if none left 1324 if (frame_item.value() <= 0) 1325 m_capturedFramesMap.remove(signature); 1326 } 1327 } 1328 1329 1330 1331 void CaptureModuleState::appendLogText(const QString &message) 1332 { 1333 qCInfo(KSTARS_EKOS_CAPTURE()) << message; 1334 emit newLog(message); 1335 } 1336 1337 bool CaptureModuleState::isGuidingOn() 1338 { 1339 // In case we are doing non guiding dither, then we are not performing autoguiding. 1340 if (Options::ditherNoGuiding()) 1341 return false; 1342 1343 return (m_GuideState == GUIDE_GUIDING || 1344 m_GuideState == GUIDE_CALIBRATING || 1345 m_GuideState == GUIDE_CALIBRATION_SUCCESS || 1346 m_GuideState == GUIDE_DARK || 1347 m_GuideState == GUIDE_SUBFRAME || 1348 m_GuideState == GUIDE_STAR_SELECT || 1349 m_GuideState == GUIDE_REACQUIRE || 1350 m_GuideState == GUIDE_DITHERING || 1351 m_GuideState == GUIDE_DITHERING_SUCCESS || 1352 m_GuideState == GUIDE_DITHERING_ERROR || 1353 m_GuideState == GUIDE_DITHERING_SETTLE || 1354 m_GuideState == GUIDE_SUSPENDED 1355 ); 1356 } 1357 1358 bool CaptureModuleState::isActivelyGuiding() 1359 { 1360 return isGuidingOn() && (m_GuideState == GUIDE_GUIDING); 1361 } 1362 1363 void CaptureModuleState::setAlignState(AlignState value) 1364 { 1365 if (value != m_AlignState) 1366 qCDebug(KSTARS_EKOS_CAPTURE) << "Align State changed from" << Ekos::getAlignStatusString( 1367 m_AlignState) << "to" << Ekos::getAlignStatusString(value); 1368 m_AlignState = value; 1369 1370 getMeridianFlipState()->setResumeAlignmentAfterFlip(true); 1371 1372 switch (value) 1373 { 1374 case ALIGN_COMPLETE: 1375 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING) 1376 { 1377 appendLogText(i18n("Post flip re-alignment completed successfully.")); 1378 resetAlignmentRetries(); 1379 // Trigger guiding if necessary. 1380 if (checkGuidingAfterFlip() == false) 1381 { 1382 // If no guiding is required, the meridian flip is complete 1383 updateMeridianFlipStage(MeridianFlipState::MF_NONE); 1384 setCaptureState(CAPTURE_WAITING); 1385 } 1386 } 1387 break; 1388 1389 case ALIGN_ABORTED: 1390 case ALIGN_FAILED: 1391 // TODO run it 3 times before giving up 1392 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING) 1393 { 1394 if (increaseAlignmentRetries() >= 3) 1395 { 1396 appendLogText(i18n("Post-flip alignment failed.")); 1397 emit abortCapture(); 1398 } 1399 else 1400 { 1401 appendLogText(i18n("Post-flip alignment failed. Retrying...")); 1402 // set back the stage 1403 updateMeridianFlipStage(MeridianFlipState::MF_COMPLETED); 1404 } 1405 } 1406 break; 1407 1408 default: 1409 break; 1410 } 1411 } 1412 1413 } // namespace