Kstars

capturemodulestate.cpp
1/* Ekos state machine for the Capture module
2 SPDX-FileCopyrightText: 2022 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "capturemodulestate.h"
8#include "ekos/manager/meridianflipstate.h"
9#include "ekos/capture/sequencejob.h"
10#include "ekos/capture/sequencequeue.h"
11#include "fitsviewer/fitsdata.h"
12
13#include "ksnotification.h"
14#include <ekos_capture_debug.h>
15
16#define GD_TIMER_TIMEOUT 60000
17
18namespace Ekos
19{
20CaptureModuleState::CaptureModuleState(QObject *parent): QObject{parent}
21{
22 m_sequenceQueue.reset(new SequenceQueue());
23 m_refocusState.reset(new RefocusState());
24 m_TargetADUTolerance = Options::calibrationADUValueTolerance();
25 connect(m_refocusState.get(), &RefocusState::newLog, this, &CaptureModuleState::newLog);
26
27 getGuideDeviationTimer().setInterval(GD_TIMER_TIMEOUT);
28 connect(&m_guideDeviationTimer, &QTimer::timeout, this, &CaptureModuleState::checkGuideDeviationTimeout);
29
30 setCalibrationPreAction(Options::calibrationPreActionIndex());
31 setFlatFieldDuration(static_cast<FlatFieldDuration>(Options::calibrationFlatDurationIndex()));
32 wallCoord().setAz(Options::calibrationWallAz());
33 wallCoord().setAlt(Options::calibrationWallAlt());
34 setTargetADU(Options::calibrationADUValue());
35 setSkyFlat(Options::calibrationSkyFlat());
36}
37
38QList<SequenceJob *> &CaptureModuleState::allJobs()
39{
40 return m_sequenceQueue->allJobs();
41}
42
43const QUrl &CaptureModuleState::sequenceURL() const
44{
45 return m_sequenceQueue->sequenceURL();
46}
47
48void CaptureModuleState::setSequenceURL(const QUrl &newSequenceURL)
49{
50 m_sequenceQueue->setSequenceURL(newSequenceURL);
51}
52
53void CaptureModuleState::setActiveJob(SequenceJob *value)
54{
55 // do nothing if active job is not changed
56 if (m_activeJob == value)
57 return;
58
59 // clear existing job connections
60 if (m_activeJob != nullptr)
61 {
62 disconnect(this, nullptr, m_activeJob, nullptr);
63 disconnect(m_activeJob, nullptr, this, nullptr);
64 // ensure that the device adaptor does not send any new events
65 m_activeJob->disconnectDeviceAdaptor();
66 }
67
68 // set the new value
69 m_activeJob = value;
70
71 // create job connections
72 if (m_activeJob != nullptr)
73 {
74 // connect job with device adaptor events
75 m_activeJob->connectDeviceAdaptor();
76 // forward signals to the sequence job
77 connect(this, &CaptureModuleState::newGuiderDrift, m_activeJob, &SequenceJob::updateGuiderDrift);
78 // react upon sequence job signals
79 connect(m_activeJob, &SequenceJob::prepareState, this, &CaptureModuleState::updatePrepareState);
80 connect(m_activeJob, &SequenceJob::prepareComplete, this, [this](bool success)
81 {
82 if (success)
83 {
84 setCaptureState(CAPTURE_PROGRESS);
85 emit executeActiveJob();
86 }
87 else
88 {
89 qWarning(KSTARS_EKOS_CAPTURE) << "Capture preparation failed, aborting.";
90 setCaptureState(CAPTURE_ABORTED);
91 emit abortCapture();
92 }
94 connect(m_activeJob, &SequenceJob::abortCapture, this, &CaptureModuleState::abortCapture);
95 connect(m_activeJob, &SequenceJob::captureStarted, this, &CaptureModuleState::captureStarted);
96 connect(m_activeJob, &SequenceJob::newLog, this, &CaptureModuleState::newLog);
97 // forward the devices and attributes
98 m_activeJob->updateDeviceStates();
99 m_activeJob->setAutoFocusReady(getRefocusState()->isAutoFocusReady());
100 }
101
102}
103
104int CaptureModuleState::activeJobID()
105{
106 if (m_activeJob == nullptr)
107 return -1;
108
109 for (int i = 0; i < allJobs().count(); i++)
110 {
111 if (m_activeJob == allJobs().at(i))
112 return i;
113 }
114
115 return -1;
116
117}
118
119void CaptureModuleState::initCapturePreparation()
120{
121 setStartingCapture(false);
122
123 // 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
124 setIgnoreJobProgress(!hasCapturedFramesMap() && Options::alwaysResetSequenceWhenStarting());
125
126 // Refocus timer should not be reset on deviation error
127 if (isGuidingDeviationDetected() == false && getCaptureState() != CAPTURE_SUSPENDED)
128 {
129 // start timer to measure time until next forced refocus
130 getRefocusState()->startRefocusTimer();
131 }
132
133 // Only reset these counters if we are NOT restarting from deviation errors
134 // So when starting a new job or fresh then we reset them.
135 if (isGuidingDeviationDetected() == false)
136 {
137 resetDitherCounter();
138 getRefocusState()->resetInSequenceFocusCounter();
139 getRefocusState()->setAdaptiveFocusDone(false);
140 }
141
142 setGuidingDeviationDetected(false);
143 resetSpikesDetected();
144
145 setCaptureState(CAPTURE_PROGRESS);
146 setBusy(true);
147
148 if (Options::enforceGuideDeviation() && isGuidingOn() == false)
149 emit newLog(i18n("Warning: Guide deviation is selected but autoguide process was not started."));
150
151
152}
153
154void CaptureModuleState::setCaptureState(CaptureState value)
155{
156 bool pause_planned = false;
157 // handle new capture state
158 switch (value)
159 {
160 case CAPTURE_IDLE:
161 case CAPTURE_ABORTED:
163 case CAPTURE_PAUSED:
164 // meridian flip may take place if requested
165 if (mf_state->getMeridianFlipStage() == MeridianFlipState::MF_REQUESTED)
166 mf_state->updateMeridianFlipStage(MeridianFlipState::MF_READY);
167 break;
169 // remember pause planning before receiving an image
170 pause_planned = (m_CaptureState == CAPTURE_PAUSE_PLANNED);
171 break;
172 default:
173 // do nothing
174 break;
175 }
176
177 // Only emit status if it changed
178 if (m_CaptureState != value)
179 {
180 qCDebug(KSTARS_EKOS_CAPTURE()) << "Capture State changes from" << getCaptureStatusString(
181 m_CaptureState) << "to" << getCaptureStatusString(value);
182 m_CaptureState = value;
183 getMeridianFlipState()->setCaptureState(m_CaptureState);
184 emit newStatus(m_CaptureState);
185 // reset to planned state if necessary
186 if (pause_planned)
187 {
188 m_CaptureState = CAPTURE_PAUSE_PLANNED;
189 emit newStatus(m_CaptureState);
190 }
191 }
192}
193
194void CaptureModuleState::setGuideState(GuideState state)
195{
196 if (state != m_GuideState)
197 qCDebug(KSTARS_EKOS_CAPTURE) << "Guiding state changed from" <<
198 Ekos::getGuideStatusString(m_GuideState)
199 << "to" << Ekos::getGuideStatusString(state);
200 switch (state)
201 {
202 case GUIDE_IDLE:
203 case GUIDE_GUIDING:
204 case GUIDE_CALIBRATION_SUCCESS:
205 break;
206
207 case GUIDE_ABORTED:
208 case GUIDE_CALIBRATION_ERROR:
209 processGuidingFailed();
210 break;
211
212 case GUIDE_DITHERING_SUCCESS:
213 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering succeeded, capture state" << getCaptureStatusString(
214 getCaptureState());
215 // do nothing if something happened during dithering
216 appendLogText(i18n("Dithering succeeded."));
217 if (getCaptureState() != CAPTURE_DITHERING)
218 break;
219
220 if (Options::guidingSettle() > 0)
221 {
222 // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that.
223 appendLogText(i18n("Dither complete. Resuming in %1 seconds...", Options::guidingSettle()));
224 QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]()
225 {
226 setDitheringState(IPS_OK);
227 });
228 }
229 else
230 {
231 appendLogText(i18n("Dither complete."));
232 setDitheringState(IPS_OK);
233 }
234 break;
235
236 case GUIDE_DITHERING_ERROR:
237 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering failed, capture state" << getCaptureStatusString(
238 getCaptureState());
239 if (getCaptureState() != CAPTURE_DITHERING)
240 break;
241
242 if (Options::guidingSettle() > 0)
243 {
244 // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that.
245 appendLogText(i18n("Warning: Dithering failed. Resuming in %1 seconds...", Options::guidingSettle()));
246 // set dithering state to OK after settling time and signal to proceed
247 QTimer::singleShot(Options::guidingSettle() * 1000, this, [this]()
248 {
249 setDitheringState(IPS_OK);
250 });
251 }
252 else
253 {
254 appendLogText(i18n("Warning: Dithering failed."));
255 // signal OK so that capturing may continue although dithering failed
256 setDitheringState(IPS_OK);
257 }
258
259 break;
260
261 default:
262 break;
263 }
264
265 m_GuideState = state;
266 // forward it to the currently active sequence job
267 if (m_activeJob != nullptr)
268 m_activeJob->setCoreProperty(SequenceJob::SJ_GuiderActive, isActivelyGuiding());
269}
270
271
272
273void CaptureModuleState::setCurrentFilterPosition(int position, const QString &name, const QString &focusFilterName)
274{
275 m_CurrentFilterPosition = position;
276 if (position > 0)
277 {
278 m_CurrentFilterName = name;
279 m_CurrentFocusFilterName = focusFilterName;
280 }
281 else
282 {
283 m_CurrentFilterName = "--";
284 m_CurrentFocusFilterName = "--";
285 }
286}
287
288QSharedPointer<MeridianFlipState> CaptureModuleState::getMeridianFlipState()
289{
290 // lazy instantiation
291 if (mf_state.isNull())
292 mf_state.reset(new MeridianFlipState());
293
294 return mf_state;
295}
296
297void CaptureModuleState::setMeridianFlipState(QSharedPointer<MeridianFlipState> state)
298{
299 // clear old state machine
300 if (! mf_state.isNull())
301 {
302 mf_state->disconnect(this);
303 mf_state->deleteLater();
304 }
305
306 mf_state = state;
307 connect(mf_state.data(), &Ekos::MeridianFlipState::newMountMFStatus, this, &Ekos::CaptureModuleState::updateMFMountState,
309}
310
311void CaptureModuleState::setObserverName(const QString &value)
312{
313 m_ObserverName = value;
314 Options::setDefaultObserver(value);
315}
316
317void CaptureModuleState::setBusy(bool busy)
318{
319 m_Busy = busy;
320 emit captureBusy(busy);
321}
322
323void CaptureModuleState::decreaseDitherCounter()
324{
325 if (m_ditherCounter > 0)
326 --m_ditherCounter;
327}
328
329void CaptureModuleState::resetDitherCounter()
330{
331 uint value = 0;
332 if (m_activeJob)
333 value = m_activeJob->getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt(0);
334
335 if (value > 0)
336 m_ditherCounter = value;
337 else
338 m_ditherCounter = Options::ditherFrames();
339}
340
341bool CaptureModuleState::checkDithering()
342{
343 // No need if preview only
344 if (m_activeJob && m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW)
345 return false;
346
347 if ( (Options::ditherEnabled() || Options::ditherNoGuiding())
348 // 2017-09-20 Jasem: No need to dither after post meridian flip guiding
349 && getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_GUIDING
350 // We must be either in guide mode or if non-guide dither (via pulsing) is enabled
351 && (getGuideState() == GUIDE_GUIDING || Options::ditherNoGuiding())
352 // Must be only done for light frames
353 && (m_activeJob != nullptr && m_activeJob->getFrameType() == FRAME_LIGHT)
354 // Check dither counter
355 && m_ditherCounter == 0)
356 {
357 // reset the dither counter
358 resetDitherCounter();
359
360 qCInfo(KSTARS_EKOS_CAPTURE) << "Dithering...";
361 appendLogText(i18n("Dithering..."));
362
363 setCaptureState(CAPTURE_DITHERING);
364 setDitheringState(IPS_BUSY);
365
366 return true;
367 }
368 // no dithering required
369 return false;
370}
371
372void CaptureModuleState::updateMFMountState(MeridianFlipState::MeridianFlipMountState status)
373{
374 qCDebug(KSTARS_EKOS_CAPTURE) << "updateMFMountState: " << MeridianFlipState::meridianFlipStatusString(status);
375
376 switch (status)
377 {
378 case MeridianFlipState::MOUNT_FLIP_NONE:
379 // MF_NONE as external signal ignored so that re-alignment and guiding are processed first
380 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED)
381 updateMeridianFlipStage(MeridianFlipState::MF_NONE);
382 break;
383
384 case MeridianFlipState::MOUNT_FLIP_PLANNED:
385 if (getMeridianFlipState()->getMeridianFlipStage() > MeridianFlipState::MF_REQUESTED)
386 {
387 // This should never happen, since a meridian flip seems to be ongoing
388 qCritical(KSTARS_EKOS_CAPTURE) << "Accepting meridian flip request while being in stage " <<
389 getMeridianFlipState()->getMeridianFlipStage();
390 }
391
392 // If we are autoguiding, we should resume autoguiding after flip
393 getMeridianFlipState()->setResumeGuidingAfterFlip(isGuidingOn());
394
395 // mark flip as requested
396 updateMeridianFlipStage(MeridianFlipState::MF_REQUESTED);
397 // if capture is not running, immediately accept it
398 if (m_CaptureState == CAPTURE_IDLE || m_CaptureState == CAPTURE_ABORTED
399 || m_CaptureState == CAPTURE_COMPLETE || m_CaptureState == CAPTURE_PAUSED)
400 getMeridianFlipState()->updateMFMountState(MeridianFlipState::MOUNT_FLIP_ACCEPTED);
401
402 break;
403
404 case MeridianFlipState::MOUNT_FLIP_RUNNING:
405 updateMeridianFlipStage(MeridianFlipState::MF_INITIATED);
406 setCaptureState(CAPTURE_MERIDIAN_FLIP);
407 break;
408
409 case MeridianFlipState::MOUNT_FLIP_COMPLETED:
410 updateMeridianFlipStage(MeridianFlipState::MF_COMPLETED);
411 break;
412
413 default:
414 break;
415
416 }
417}
418
419void CaptureModuleState::updateMeridianFlipStage(const MeridianFlipState::MFStage &stage)
420{
421 // forward the stage to the module state
422 getMeridianFlipState()->updateMeridianFlipStage(stage);
423
424 // handle state changes for other modules
425 switch (stage)
426 {
427 case MeridianFlipState::MF_READY:
428 break;
429
430 case MeridianFlipState::MF_INITIATED:
431 emit meridianFlipStarted();
432 break;
433
434 case MeridianFlipState::MF_COMPLETED:
435
436 // Reset HFR Check counter after meridian flip
437 if (getRefocusState()->isInSequenceFocus())
438 {
439 qCDebug(KSTARS_EKOS_CAPTURE) << "Resetting HFR Check counter after meridian flip.";
440 //firstAutoFocus = true;
441 getRefocusState()->setInSequenceFocusCounter(0);
442 }
443
444 // after a meridian flip we do not need to dither
445 if ( Options::ditherEnabled() || Options::ditherNoGuiding())
446 resetDitherCounter();
447
448 // if requested set flag so it perform refocus before next frame
449 if (Options::refocusAfterMeridianFlip() == true)
450 getRefocusState()->setRefocusAfterMeridianFlip(true);
451 // If dome is syncing, wait until it stops
452 if (hasDome && (m_domeState == ISD::Dome::DOME_MOVING_CW || m_domeState == ISD::Dome::DOME_MOVING_CCW))
453 return;
454
455 KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"),
456 KSNotification::Capture);
457
458 getMeridianFlipState()->processFlipCompleted();
459
460 // if the capturing has been paused before the flip, reset the state to paused, otherwise to idle
461 setCaptureState(m_ContinueAction == CONTINUE_ACTION_NONE ? CAPTURE_IDLE : CAPTURE_PAUSED);
462 break;
463
464 default:
465 break;
466 }
467 // forward the new stage
468 emit newMeridianFlipStage(stage);
469}
470
471bool CaptureModuleState::checkMeridianFlipActive()
472{
473 return (getMeridianFlipState()->checkMeridianFlipRunning() ||
474 checkPostMeridianFlipActions() ||
475 checkMeridianFlipReady());
476}
477
478bool CaptureModuleState::checkMeridianFlipReady()
479{
480 if (hasTelescope == false)
481 return false;
482
483 // If active job is taking flat field image at a wall source
484 // then do not flip.
485 if (m_activeJob && m_activeJob->getFrameType() == FRAME_FLAT && m_activeJob->getCalibrationPreAction() & ACTION_WALL)
486 return false;
487
488 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED)
489 // if no flip has been requested or is already ongoing
490 return false;
491
492 // meridian flip requested or already in action
493
494 // Reset frame if we need to do focusing later on
495 if (m_refocusState->isInSequenceFocus() ||
496 (Options::enforceRefocusEveryN() && m_refocusState->getRefocusEveryNTimerElapsedSec() > 0))
497 emit resetFocus();
498
499 // signal that meridian flip may take place
500 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_REQUESTED)
501 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_READY);
502
503
504 return true;
505}
506
507bool CaptureModuleState::checkPostMeridianFlipActions()
508{
509 // step 1: check if post flip alignment is running
510 if (m_CaptureState == CAPTURE_ALIGNING || checkAlignmentAfterFlip())
511 return true;
512
513 // step 2: check if post flip guiding is running
514 // MF_NONE is set as soon as guiding is running and the guide deviation is below the limit
515 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED && m_GuideState != GUIDE_GUIDING
516 && checkGuidingAfterFlip())
517 return true;
518
519 // step 3: in case that a meridian flip has been completed and a guide deviation limit is set, we wait
520 // until the guide deviation is reported to be below the limit (@see setGuideDeviation(double, double)).
521 // Otherwise the meridian flip is complete
522 if (m_CaptureState == CAPTURE_CALIBRATING
523 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
524 {
525 if (Options::enforceGuideDeviation() || Options::enforceStartGuiderDrift())
526 return true;
527 else
528 updateMeridianFlipStage(MeridianFlipState::MF_NONE);
529 }
530
531 // all actions completed or no flip running
532 return false;
533}
534
535bool CaptureModuleState::checkGuidingAfterFlip()
536{
537 // if no meridian flip has completed, we do not touch guiding
538 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED)
539 return false;
540 // If we're not autoguiding then we're done
541 if (getMeridianFlipState()->resumeGuidingAfterFlip() == false)
542 {
543 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE);
544 return false;
545 }
546
547 // if we are waiting for a calibration, start it
548 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED
549 && getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_GUIDING)
550 {
551 appendLogText(i18n("Performing post flip re-calibration and guiding..."));
552
553 setCaptureState(CAPTURE_CALIBRATING);
554
555 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_GUIDING);
556 emit guideAfterMeridianFlip();
557 return true;
558 }
559 else if (m_CaptureState == CAPTURE_CALIBRATING)
560 {
561 if (getGuideState() == GUIDE_CALIBRATION_ERROR || getGuideState() == GUIDE_ABORTED)
562 {
563 // restart guiding after failure
564 appendLogText(i18n("Post meridian flip calibration error. Restarting..."));
565 emit guideAfterMeridianFlip();
566 return true;
567 }
568 else if (getGuideState() != GUIDE_GUIDING)
569 // waiting for guiding to start
570 return true;
571 else
572 // guiding is running
573 return false;
574 }
575 else
576 // in all other cases, do not touch
577 return false;
578}
579
580void CaptureModuleState::processGuidingFailed()
581{
582 if (m_FocusState > FOCUS_PROGRESS)
583 {
584 appendLogText(i18n("Autoguiding stopped. Waiting for autofocus to finish..."));
585 }
586 // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip)
587 else if (isGuidingOn()
588 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_NONE &&
589 // JM 2022.08.03: Only abort if the current job is LIGHT. For calibration frames, we can ignore guide failures.
590 ((m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT) ||
591 m_CaptureState == CAPTURE_SUSPENDED || m_CaptureState == CAPTURE_PAUSED))
592 {
593 appendLogText(i18n("Autoguiding stopped. Aborting..."));
594 emit abortCapture();
595 }
596 else if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
597 {
598 if (increaseAlignmentRetries() >= 3)
599 {
600 appendLogText(i18n("Post meridian flip calibration error. Aborting..."));
601 emit abortCapture();
602 }
603 }
604}
605
606void CaptureModuleState::updateAdaptiveFocusState(bool success)
607{
608 m_refocusState->setAdaptiveFocusDone(true);
609
610 // Always process the adaptive focus state change, incl if a MF has also just started
611 if (success)
612 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus completed successfully";
613 else
614 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus failed";
615
616 m_refocusState->setAutoFocusReady(true);
617 // forward to the active job
618 if (m_activeJob != nullptr)
619 m_activeJob->setAutoFocusReady(true);
620
621 setFocusState(FOCUS_COMPLETE);
622 emit newLog(i18n(success ? "Adaptive focus complete." : "Adaptive focus failed. Continuing..."));
623}
624
625void CaptureModuleState::updateFocusState(FocusState state)
626{
627 if (state != m_FocusState)
628 qCDebug(KSTARS_EKOS_CAPTURE) << "Focus State changed from" <<
629 Ekos::getFocusStatusString(m_FocusState) <<
630 "to" << Ekos::getFocusStatusString(state);
631 setFocusState(state);
632
633 // Do not process if meridian flip in progress
634 if (getMeridianFlipState()->checkMeridianFlipRunning())
635 return;
636
637 switch (state)
638 {
639 // Do not process when aborted
640 case FOCUS_ABORTED:
641 if (!(getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_INITIATED))
642 {
643 // Meridian flip will abort focusing. In this case, after the meridian flip has completed capture
644 // will restart the re-focus attempt. Therefore we only abort capture if meridian flip is not running.
645 emit newFocusStatus(state);
646 appendLogText(i18n("Autofocus failed. Aborting exposure..."));
647 emit abortCapture();
648 }
649 break;
650 case FOCUS_COMPLETE:
651 // enable option to have a refocus event occur if HFR goes over threshold
652 m_refocusState->setAutoFocusReady(true);
653 // forward to the active job
654 if (m_activeJob != nullptr)
655 m_activeJob->setAutoFocusReady(true);
656 // reset the timer if a full autofocus was run (rather than an HFR check)
657 if (m_refocusState->getFocusHFRInAutofocus())
658 m_refocusState->startRefocusTimer(true);
659
660 // update HFR Threshold for those algorithms that use Autofocus as reference
661 if (Options::hFRCheckAlgorithm() == HFR_CHECK_MEDIAN_MEASURE ||
662 (m_refocusState->getFocusHFRInAutofocus() && Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS))
663 {
664 m_refocusState->addHFRValue(getFocusFilterName());
665 updateHFRThreshold();
666 }
667 emit newFocusStatus(state);
668 break;
669 default:
670 break;
671 }
672
673 if (m_activeJob != nullptr)
674 m_activeJob->setFocusStatus(state);
675}
676
677AutofocusReason CaptureModuleState::getAFReason(RefocusState::RefocusReason state, QString &reasonInfo)
678{
679 AutofocusReason afReason;
680 reasonInfo = "";
681 switch (state)
682 {
683 case RefocusState::REFOCUS_USER_REQUEST:
684 afReason = AutofocusReason::FOCUS_USER_REQUEST;
685 break;
686 case RefocusState::REFOCUS_TEMPERATURE:
687 afReason = AutofocusReason::FOCUS_TEMPERATURE;
688 reasonInfo = i18n("Limit: %1 °C", QString::number(Options::maxFocusTemperatureDelta(), 'f', 2));
689 break;
690 case RefocusState::REFOCUS_TIME_ELAPSED:
691 afReason = AutofocusReason::FOCUS_TIME;
692 reasonInfo = i18n("Limit: %1 mins", Options::refocusEveryN());
693 break;
694 case RefocusState::REFOCUS_POST_MF:
695 afReason = AutofocusReason::FOCUS_MERIDIAN_FLIP;
696 break;
697 default:
698 afReason = AutofocusReason::FOCUS_NONE;
699 }
700 return afReason;
701}
702
703bool CaptureModuleState::startFocusIfRequired()
704{
705 // Do not start focus or adaptive focus if:
706 // 1. There is no active job, or
707 // 2. Target frame is not LIGHT
708 // 3. Capture is preview only
709 if (m_activeJob == nullptr || m_activeJob->getFrameType() != FRAME_LIGHT
710 || m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW)
711 return false;
712
713 RefocusState::RefocusReason reason = m_refocusState->checkFocusRequired();
714
715 // no focusing necessary
716 if (reason == RefocusState::REFOCUS_NONE)
717 return false;
718
719 // clear the flag for refocusing after the meridian flip
720 m_refocusState->setRefocusAfterMeridianFlip(false);
721
722 // Post meridian flip we need to reset filter _before_ running in-sequence focusing
723 // as it could have changed for whatever reason (e.g. alignment used a different filter).
724 // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it
725 // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager.
726 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_NONE)
727 {
728 int targetFilterPosition = m_activeJob->getTargetFilter();
729 if (targetFilterPosition > 0 && targetFilterPosition != getCurrentFilterPosition())
730 emit newFilterPosition(targetFilterPosition);
731 }
732
733 emit abortFastExposure();
734 updateFocusState(FOCUS_PROGRESS);
735 QString reasonInfo;
736 AutofocusReason afReason;
737
738 switch (reason)
739 {
740 case RefocusState::REFOCUS_HFR:
741 m_refocusState->resetInSequenceFocusCounter();
742 emit checkFocus(Options::hFRDeviation());
743 qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started...";
744 break;
745 case RefocusState::REFOCUS_ADAPTIVE:
746 m_refocusState->setAdaptiveFocusDone(true);
747 emit adaptiveFocus();
748 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus started...";
749 break;
750 case RefocusState::REFOCUS_USER_REQUEST:
751 case RefocusState::REFOCUS_TEMPERATURE:
752 case RefocusState::REFOCUS_TIME_ELAPSED:
753 case RefocusState::REFOCUS_POST_MF:
754 // If we are over 30 mins since last autofocus, we'll reset frame.
755 if (m_refocusState->getRefocusEveryNTimerElapsedSec() >= 1800)
756 emit resetFocus();
757
758 // force refocus
759 afReason = getAFReason(reason, reasonInfo);
760 emit runAutoFocus(afReason, reasonInfo);
761 // restart in sequence counting
762 m_refocusState->resetInSequenceFocusCounter();
763 qCDebug(KSTARS_EKOS_CAPTURE) << "Refocusing started...";
764 break;
765 default:
766 // this should not happen, since this case is handled above
767 return false;
768 }
769
770 setCaptureState(CAPTURE_FOCUSING);
771 return true;
772}
773
774void CaptureModuleState::updateHFRThreshold()
775{
776 // For Ficed algo no need to update the HFR threshold
777 if (Options::hFRCheckAlgorithm() == HFR_CHECK_FIXED)
778 return;
779
780 QString finalFilter = getFocusFilterName();
781 QList<double> filterHFRList = m_refocusState->getHFRMap()[finalFilter];
782
783 // Update the limit only if HFR values have been measured for the current filter
784 if (filterHFRList.empty())
785 return;
786
787 double value = 0;
788 if (Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS)
789 value = filterHFRList.last();
790 else // algo = Median Measure
791 {
792 int count = filterHFRList.size();
793 if (count > 1)
794 value = (count % 2) ? filterHFRList[count / 2] : (filterHFRList[count / 2 - 1] + filterHFRList[count / 2]) / 2.0;
795 else if (count == 1)
796 value = filterHFRList[0];
797 }
798 value += value * (Options::hFRThresholdPercentage() / 100.0);
799 Options::setHFRDeviation(value);
800 emit newLimitFocusHFR(value); // Updates the limits UI with the new HFR threshold
801}
802
803QString CaptureModuleState::getFocusFilterName()
804{
806 if (m_CurrentFilterPosition > 0)
807 // If we are using filters, then we retrieve which filter is currently active.
808 // We check if filter lock is used, and store that instead of the current filter.
809 // e.g. If current filter HA, but lock filter is L, then the HFR value is stored for L filter.
810 // If no lock filter exists, then we store as is (HA)
811 finalFilter = (m_CurrentFocusFilterName == "--" ? m_CurrentFilterName : m_CurrentFocusFilterName);
812 else
813 // No filters
814 finalFilter = "--";
815 return finalFilter;
816}
817
818bool CaptureModuleState::checkAlignmentAfterFlip()
819{
820 // if no meridian flip has completed, we do not touch guiding
821 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED)
822 return false;
823 // If we do not need to align then we're done
824 if (getMeridianFlipState()->resumeAlignmentAfterFlip() == false)
825 return false;
826
827 // if we are waiting for a calibration, start it
828 if (m_CaptureState < CAPTURE_ALIGNING)
829 {
830 appendLogText(i18n("Performing post flip re-alignment..."));
831
832 resetAlignmentRetries();
833 setCaptureState(CAPTURE_ALIGNING);
834
835 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_ALIGNING);
836 return true;
837 }
838 else
839 // in all other cases, do not touch
840 return false;
841}
842
843void CaptureModuleState::checkGuideDeviationTimeout()
844{
845 if (m_activeJob && m_activeJob->getStatus() == JOB_ABORTED
846 && isGuidingDeviationDetected())
847 {
848 appendLogText(i18n("Guide module timed out."));
849 setGuidingDeviationDetected(false);
850
851 // If capture was suspended, it should be aborted (failed) now.
852 if (m_CaptureState == CAPTURE_SUSPENDED)
853 {
854 setCaptureState(CAPTURE_ABORTED);
855 }
856 }
857}
858
859void CaptureModuleState::setGuideDeviation(double deviation_rms)
860{
861 // communicate the new guiding deviation
862 emit newGuiderDrift(deviation_rms);
863
864 const QString deviationText = QString("%1").arg(deviation_rms, 0, 'f', 3);
865
866 // if guiding deviations occur and no job is active, check if a meridian flip is ready to be executed
867 if (m_activeJob == nullptr && checkMeridianFlipReady())
868 return;
869
870 // 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
871 if (m_CaptureState == CAPTURE_PROGRESS &&
872 getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED &&
873 getMeridianFlipState()->checkMeridianFlipRunning() == false)
874 {
875 // initial guiding deviation irrelevant or below limit
876 if (Options::enforceStartGuiderDrift() == false || deviation_rms < Options::startGuideDeviation())
877 {
878 setCaptureState(CAPTURE_CALIBRATING);
879 if (Options::enforceStartGuiderDrift())
880 appendLogText(i18n("Initial guiding deviation %1 below limit value of %2 arcsecs",
881 deviationText, Options::startGuideDeviation()));
882 setGuidingDeviationDetected(false);
883 setStartingCapture(false);
884 }
885 else
886 {
887 // warn only once
888 if (isGuidingDeviationDetected() == false)
889 appendLogText(i18n("Initial guiding deviation %1 exceeded limit value of %2 arcsecs",
890 deviationText, Options::startGuideDeviation()));
891
892 setGuidingDeviationDetected(true);
893
894 // Check if we need to start meridian flip. If yes, we need to start capturing
895 // to ensure that capturing is recovered after the flip
896 if (checkMeridianFlipReady())
897 emit startCapture();
898 }
899
900 // in any case, do not proceed
901 return;
902 }
903
904 // If guiding is started after a meridian flip we will start getting guide deviations again
905 // if the guide deviations are within our limits, we resume the sequence
906 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
907 {
908 // If the user didn't select any guiding deviation, the meridian flip is completed
909 if (Options::enforceGuideDeviation() == false || deviation_rms < Options::guideDeviation())
910 {
911 appendLogText(i18n("Post meridian flip calibration completed successfully."));
912 // N.B. Set meridian flip stage AFTER resumeSequence() always
913 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE);
914 return;
915 }
916 }
917
918 // Check for initial deviation in the middle of a sequence (above just checks at the start of a sequence).
919 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT
920 && isStartingCapture() && Options::enforceStartGuiderDrift())
921 {
922 setStartingCapture(false);
923 if (deviation_rms > Options::startGuideDeviation())
924 {
925 appendLogText(i18n("Guiding deviation at capture startup %1 exceeded limit %2 arcsecs.",
926 deviationText, Options::startGuideDeviation()));
927 emit suspendCapture();
928 setGuidingDeviationDetected(true);
929
930 // Check if we need to start meridian flip. If yes, we need to start capturing
931 // to ensure that capturing is recovered after the flip
932 if (checkMeridianFlipReady())
933 emit startCapture();
934 else
935 getGuideDeviationTimer().start();
936 return;
937 }
938 else
939 appendLogText(i18n("Guiding deviation at capture startup %1 below limit value of %2 arcsecs",
940 deviationText, Options::startGuideDeviation()));
941 }
942
943 if (m_CaptureState != CAPTURE_SUSPENDED)
944 {
945
946 // We don't enforce limit on previews or non-LIGHT frames
947 if ((Options::enforceGuideDeviation() == false)
948 ||
949 (m_activeJob && (m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW ||
950 m_activeJob->getExposeLeft() == 0.0 ||
951 m_activeJob->getFrameType() != FRAME_LIGHT)))
952 return;
953
954 // If we have an active busy job, let's abort it if guiding deviation is exceeded.
955 // And we accounted for the spike
956 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT)
957 {
958 if (deviation_rms <= Options::guideDeviation())
959 resetSpikesDetected();
960 else
961 {
962 // Require several consecutive spikes to fail.
963 if (increaseSpikesDetected() < Options::guideDeviationReps())
964 return;
965
966 appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 arcsecs for %4 consecutive samples, "
967 "suspending exposure and waiting for guider up to %3 seconds.",
968 deviationText, Options::guideDeviation(),
969 QString("%L1").arg(getGuideDeviationTimer().interval() / 1000.0, 0, 'f', 3),
970 Options::guideDeviationReps()));
971
972 emit suspendCapture();
973
974 resetSpikesDetected();
975 setGuidingDeviationDetected(true);
976
977 // Check if we need to start meridian flip. If yes, we need to start capturing
978 // to ensure that capturing is recovered after the flip
979 if (checkMeridianFlipReady())
980 emit startCapture();
981 else
982 getGuideDeviationTimer().start();
983 }
984 return;
985 }
986 }
987
988 // Find the first aborted job
989 SequenceJob *abortedJob = nullptr;
990 for(auto &job : allJobs())
991 {
992 if (job->getStatus() == JOB_ABORTED)
993 {
994 abortedJob = job;
995 break;
996 }
997 }
998
999 if (abortedJob != nullptr && isGuidingDeviationDetected())
1000 {
1001 if (deviation_rms <= Options::startGuideDeviation())
1002 {
1003 getGuideDeviationTimer().stop();
1004
1005 // Start with delay if start hasn't been triggered before
1006 if (! getCaptureDelayTimer().isActive())
1007 {
1008 // if capturing has been suspended, restart it
1009 if (m_CaptureState == CAPTURE_SUSPENDED)
1010 {
1011 const int seqDelay = abortedJob->getCoreProperty(SequenceJob::SJ_Delay).toInt();
1012 if (seqDelay == 0)
1013 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, "
1014 "resuming exposure.",
1015 deviationText, Options::startGuideDeviation()));
1016 else
1017 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, "
1018 "resuming exposure in %3 seconds.",
1019 deviationText, Options::startGuideDeviation(), seqDelay / 1000.0));
1020
1021 emit startCapture();
1022 }
1023 }
1024 return;
1025 }
1026 else
1027 {
1028 // stop the delayed capture start if necessary
1029 if (getCaptureDelayTimer().isActive())
1030 getCaptureDelayTimer().stop();
1031
1032 appendLogText(i18n("Guiding deviation %1 is still higher than limit value of %2 arcsecs.",
1033 deviationText, Options::startGuideDeviation()));
1034 }
1035 }
1036}
1037
1038void CaptureModuleState::addDownloadTime(double time)
1039{
1040 totalDownloadTime += time;
1041 downloadsCounter++;
1042}
1043
1044int CaptureModuleState::pendingJobCount()
1045{
1046 int completedJobs = 0;
1047
1048 foreach (SequenceJob * job, allJobs())
1049 {
1050 if (job->getStatus() == JOB_DONE)
1051 completedJobs++;
1052 }
1053
1054 return (allJobs().count() - completedJobs);
1055
1056}
1057
1058QString CaptureModuleState::jobState(int id)
1059{
1060 if (id < allJobs().count())
1061 {
1062 SequenceJob * job = allJobs().at(id);
1063 return job->getStatusString();
1064 }
1065
1066 return QString();
1067
1068}
1069
1070QString CaptureModuleState::jobFilterName(int id)
1071{
1072 if (id < allJobs().count())
1073 {
1074 SequenceJob * job = allJobs().at(id);
1075 return job->getCoreProperty(SequenceJob::SJ_Filter).toString();
1076 }
1077
1078 return QString();
1079
1080}
1081
1082CCDFrameType CaptureModuleState::jobFrameType(int id)
1083{
1084 if (id < allJobs().count())
1085 {
1086 SequenceJob * job = allJobs().at(id);
1087 return job->getFrameType();
1088 }
1089
1090 return FRAME_NONE;
1091}
1092
1093int CaptureModuleState::jobImageProgress(int id)
1094{
1095 if (id < allJobs().count())
1096 {
1097 SequenceJob * job = allJobs().at(id);
1098 return job->getCompleted();
1099 }
1100
1101 return -1;
1102}
1103
1104int CaptureModuleState::jobImageCount(int id)
1105{
1106 if (id < allJobs().count())
1107 {
1108 SequenceJob * job = allJobs().at(id);
1109 return job->getCoreProperty(SequenceJob::SJ_Count).toInt();
1110 }
1111
1112 return -1;
1113}
1114
1115double CaptureModuleState::jobExposureProgress(int id)
1116{
1117 if (id < allJobs().count())
1118 {
1119 SequenceJob * job = allJobs().at(id);
1120 return job->getExposeLeft();
1121 }
1122
1123 return -1;
1124}
1125
1126double CaptureModuleState::jobExposureDuration(int id)
1127{
1128 if (id < allJobs().count())
1129 {
1130 SequenceJob * job = allJobs().at(id);
1131 return job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
1132 }
1133
1134 return -1;
1135}
1136
1137double CaptureModuleState::progressPercentage()
1138{
1139 int totalImageCount = 0;
1140 int totalImageCompleted = 0;
1141
1142 foreach (SequenceJob * job, allJobs())
1143 {
1144 totalImageCount += job->getCoreProperty(SequenceJob::SJ_Count).toInt();
1145 totalImageCompleted += job->getCompleted();
1146 }
1147
1148 if (totalImageCount != 0)
1149 return ((static_cast<double>(totalImageCompleted) / totalImageCount) * 100.0);
1150 else
1151 return -1;
1152}
1153
1154int CaptureModuleState::activeJobRemainingTime()
1155{
1156 if (m_activeJob == nullptr)
1157 return -1;
1158
1159 return m_activeJob->getJobRemainingTime(averageDownloadTime());
1160}
1161
1162int CaptureModuleState::overallRemainingTime()
1163{
1164 int remaining = 0;
1165 double estimatedDownloadTime = averageDownloadTime();
1166
1167 foreach (SequenceJob * job, allJobs())
1168 remaining += job->getJobRemainingTime(estimatedDownloadTime);
1169
1170 return remaining;
1171}
1172
1173QString CaptureModuleState::sequenceQueueStatus()
1174{
1175 if (allJobs().count() == 0)
1176 return "Invalid";
1177
1178 if (isBusy())
1179 return "Running";
1180
1181 int idle = 0, error = 0, complete = 0, aborted = 0, running = 0;
1182
1183 foreach (SequenceJob * job, allJobs())
1184 {
1185 switch (job->getStatus())
1186 {
1187 case JOB_ABORTED:
1188 aborted++;
1189 break;
1190 case JOB_BUSY:
1191 running++;
1192 break;
1193 case JOB_DONE:
1194 complete++;
1195 break;
1196 case JOB_ERROR:
1197 error++;
1198 break;
1199 case JOB_IDLE:
1200 idle++;
1201 break;
1202 }
1203 }
1204
1205 if (error > 0)
1206 return "Error";
1207
1208 if (aborted > 0)
1209 {
1210 if (m_CaptureState == CAPTURE_SUSPENDED)
1211 return "Suspended";
1212 else
1213 return "Aborted";
1214 }
1215
1216 if (running > 0)
1217 return "Running";
1218
1219 if (idle == allJobs().count())
1220 return "Idle";
1221
1222 if (complete == allJobs().count())
1223 return "Complete";
1224
1225 return "Invalid";
1226}
1227
1228QJsonObject CaptureModuleState::calibrationSettings()
1229{
1230 QJsonObject settings =
1231 {
1232 {"preAction", static_cast<int>(calibrationPreAction())},
1233 {"duration", flatFieldDuration()},
1234 {"az", wallCoord().az().Degrees()},
1235 {"al", wallCoord().alt().Degrees()},
1236 {"adu", targetADU()},
1237 {"tolerance", targetADUTolerance()},
1238 {"skyflat", skyFlat()},
1239 };
1240
1241 return settings;
1242}
1243
1244void CaptureModuleState::setCalibrationSettings(const QJsonObject &settings)
1245{
1246 const int preAction = settings["preAction"].toInt(calibrationPreAction());
1247 const int duration = settings["duration"].toInt(flatFieldDuration());
1248 const double az = settings["az"].toDouble(wallCoord().az().Degrees());
1249 const double al = settings["al"].toDouble(wallCoord().alt().Degrees());
1250 const int adu = settings["adu"].toInt(static_cast<int>(std::round(targetADU())));
1251 const int tolerance = settings["tolerance"].toInt(static_cast<int>(std::round(targetADUTolerance())));
1252 const int skyflat = settings["skyflat"].toBool();
1253
1254 setCalibrationPreAction(static_cast<CalibrationPreActions>(preAction));
1255 setFlatFieldDuration(static_cast<FlatFieldDuration>(duration));
1256 wallCoord().setAz(az);
1257 wallCoord().setAlt(al);
1258 setTargetADU(adu);
1259 setTargetADUTolerance(tolerance);
1260 setSkyFlat(skyflat);
1261}
1262
1263bool CaptureModuleState::setDarkFlatExposure(SequenceJob *job)
1264{
1265 const auto darkFlatFilter = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
1266 const auto darkFlatBinning = job->getCoreProperty(SequenceJob::SJ_Binning).toPoint();
1267 const auto darkFlatADU = job->getCoreProperty(SequenceJob::SJ_TargetADU).toInt();
1268
1269 for (auto &oneJob : allJobs())
1270 {
1271 if (oneJob->getFrameType() != FRAME_FLAT)
1272 continue;
1273
1274 const auto filter = oneJob->getCoreProperty(SequenceJob::SJ_Filter).toString();
1275
1276 // Match filter, if one exists.
1277 if (!darkFlatFilter.isEmpty() && darkFlatFilter != filter)
1278 continue;
1279
1280 // Match binning
1281 const auto binning = oneJob->getCoreProperty(SequenceJob::SJ_Binning).toPoint();
1282 if (darkFlatBinning != binning)
1283 continue;
1284
1285 // Match ADU, if used.
1286 const auto adu = oneJob->getCoreProperty(SequenceJob::SJ_TargetADU).toInt();
1287 if (job->getFlatFieldDuration() == DURATION_ADU)
1288 {
1289 if (darkFlatADU != adu)
1290 continue;
1291 }
1292
1293 // Now get the exposure
1294 job->setCoreProperty(SequenceJob::SJ_Exposure, oneJob->getCoreProperty(SequenceJob::SJ_Exposure).toDouble());
1295
1296 return true;
1297 }
1298 return false;
1299}
1300
1301void CaptureModuleState::checkSeqBoundary(QUrl sequenceURL)
1302{
1303 // No updates during meridian flip
1304 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_ALIGNING)
1305 return;
1306
1307 auto placeholderPath = PlaceholderPath(sequenceURL.toLocalFile());
1308 setNextSequenceID(placeholderPath.checkSeqBoundary(*getActiveJob()));
1309}
1310
1311bool CaptureModuleState::isModelinDSLRInfo(const QString &model)
1312{
1313 auto pos = std::find_if(m_DSLRInfos.begin(), m_DSLRInfos.end(), [model](QMap<QString, QVariant> &oneDSLRInfo)
1314 {
1315 return (oneDSLRInfo["Model"] == model);
1316 });
1317
1318 return (pos != m_DSLRInfos.end());
1319}
1320
1321void CaptureModuleState::setCapturedFramesCount(const QString &signature, uint16_t count)
1322{
1323 m_capturedFramesMap[signature] = count;
1324 qCDebug(KSTARS_EKOS_CAPTURE) <<
1325 QString("Client module indicates that storage for '%1' has already %2 captures processed.").arg(signature).arg(count);
1326 // Scheduler's captured frame map overrides the progress option of the Capture module
1327 setIgnoreJobProgress(false);
1328}
1329
1330void CaptureModuleState::changeSequenceValue(int index, QString key, QString value)
1331{
1332 QJsonArray seqArray = getSequence();
1333 QJsonObject oneSequence = seqArray[index].toObject();
1334 oneSequence[key] = value;
1335 seqArray.replace(index, oneSequence);
1336 setSequence(seqArray);
1337 emit sequenceChanged(seqArray);
1338}
1339
1340void CaptureModuleState::addCapturedFrame(const QString &signature)
1341{
1342 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature);
1343 if (m_capturedFramesMap.end() != frame_item)
1344 frame_item.value()++;
1345 else m_capturedFramesMap[signature] = 1;
1346
1347}
1348
1349void CaptureModuleState::removeCapturedFrameCount(const QString &signature, uint16_t count)
1350{
1351 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature);
1352 if (m_capturedFramesMap.end() != frame_item)
1353 {
1354 // remove the frame count
1355 frame_item.value() = frame_item.value() - count;
1356 // clear signature entry if none left
1357 if (frame_item.value() <= 0)
1358 m_capturedFramesMap.remove(signature);
1359 }
1360}
1361
1362
1363
1364void CaptureModuleState::appendLogText(const QString &message)
1365{
1366 qCInfo(KSTARS_EKOS_CAPTURE()) << message;
1367 emit newLog(message);
1368}
1369
1370bool CaptureModuleState::isGuidingOn()
1371{
1372 // In case we are doing non guiding dither, then we are not performing autoguiding.
1373 if (Options::ditherNoGuiding())
1374 return false;
1375
1376 return (m_GuideState == GUIDE_GUIDING ||
1377 m_GuideState == GUIDE_CALIBRATING ||
1378 m_GuideState == GUIDE_CALIBRATION_SUCCESS ||
1379 m_GuideState == GUIDE_DARK ||
1380 m_GuideState == GUIDE_SUBFRAME ||
1381 m_GuideState == GUIDE_STAR_SELECT ||
1382 m_GuideState == GUIDE_REACQUIRE ||
1383 m_GuideState == GUIDE_DITHERING ||
1384 m_GuideState == GUIDE_DITHERING_SUCCESS ||
1385 m_GuideState == GUIDE_DITHERING_ERROR ||
1386 m_GuideState == GUIDE_DITHERING_SETTLE ||
1387 m_GuideState == GUIDE_SUSPENDED
1388 );
1389}
1390
1391bool CaptureModuleState::isActivelyGuiding()
1392{
1393 return isGuidingOn() && (m_GuideState == GUIDE_GUIDING);
1394}
1395
1396void CaptureModuleState::setAlignState(AlignState value)
1397{
1398 if (value != m_AlignState)
1399 qCDebug(KSTARS_EKOS_CAPTURE) << "Align State changed from" << Ekos::getAlignStatusString(
1400 m_AlignState) << "to" << Ekos::getAlignStatusString(value);
1401 m_AlignState = value;
1402
1403 getMeridianFlipState()->setResumeAlignmentAfterFlip(true);
1404
1405 switch (value)
1406 {
1407 case ALIGN_COMPLETE:
1408 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING)
1409 {
1410 appendLogText(i18n("Post flip re-alignment completed successfully."));
1411 resetAlignmentRetries();
1412 // Trigger guiding if necessary.
1413 if (checkGuidingAfterFlip() == false)
1414 {
1415 // If no guiding is required, the meridian flip is complete
1416 updateMeridianFlipStage(MeridianFlipState::MF_NONE);
1417 setCaptureState(CAPTURE_WAITING);
1418 }
1419 }
1420 break;
1421
1422 case ALIGN_ABORTED:
1423 case ALIGN_FAILED:
1424 // TODO run it 3 times before giving up
1425 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING)
1426 {
1427 if (increaseAlignmentRetries() >= 3)
1428 {
1429 appendLogText(i18n("Post-flip alignment failed."));
1430 emit abortCapture();
1431 }
1432 else
1433 {
1434 appendLogText(i18n("Post-flip alignment failed. Retrying..."));
1435 // set back the stage
1436 updateMeridianFlipStage(MeridianFlipState::MF_COMPLETED);
1437 }
1438 }
1439 break;
1440
1441 default:
1442 break;
1443 }
1444}
1445
1446} // namespace
Sequence Job is a container for the details required to capture a series of images.
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
@ ALIGN_FAILED
Alignment failed.
Definition ekos.h:148
@ ALIGN_ABORTED
Alignment aborted by user or agent.
Definition ekos.h:149
@ ALIGN_COMPLETE
Alignment successfully completed.
Definition ekos.h:147
@ CAPTURE_PAUSE_PLANNED
Definition ekos.h:96
@ CAPTURE_PAUSED
Definition ekos.h:97
@ CAPTURE_IMAGE_RECEIVED
Definition ekos.h:101
@ CAPTURE_SUSPENDED
Definition ekos.h:98
@ CAPTURE_ABORTED
Definition ekos.h:99
@ CAPTURE_IDLE
Definition ekos.h:93
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString name(StandardShortcut id)
Int toInt() const const
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
UniqueConnection
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
QString toLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.