Kstars

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

KDE's Doxygen guidelines are available online.