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

KDE's Doxygen guidelines are available online.