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
503 KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"),
504 KSNotification::Capture);
505
506 getMeridianFlipState()->processFlipCompleted();
507
508 // if the capturing has been paused before the flip, reset the state to paused, otherwise to idle
509 setCaptureState(m_ContinueAction == CAPTURE_CONTINUE_ACTION_NONE ? CAPTURE_IDLE : CAPTURE_PAUSED);
510 break;
511
512 default:
513 break;
514 }
515 // forward the new stage
516 emit newMeridianFlipStage(stage);
517}
518
519bool CameraState::checkMeridianFlipActive()
520{
521 return (getMeridianFlipState()->checkMeridianFlipRunning() ||
522 checkPostMeridianFlipActions() ||
523 checkMeridianFlipReady());
524}
525
526bool CameraState::checkMeridianFlipReady()
527{
528 if (hasTelescope == false)
529 return false;
530
531 // If active job is taking flat field image at a wall source
532 // then do not flip.
533 if (m_activeJob && m_activeJob->getFrameType() == FRAME_FLAT
534 && m_activeJob->getCalibrationPreAction() & CAPTURE_PREACTION_WALL)
535 return false;
536
537 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED)
538 // if no flip has been requested or is already ongoing
539 return false;
540
541 // meridian flip requested or already in action
542
543 // Reset frame if we need to do focusing later on
544 if (m_refocusState->isInSequenceFocus() ||
545 (Options::enforceRefocusEveryN() && m_refocusState->getRefocusEveryNTimerElapsedSec() > 0))
546 emit resetFocusFrame();
547
548 // signal that meridian flip may take place
549 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_REQUESTED)
550 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_READY);
551
552
553 return true;
554}
555
556bool CameraState::checkPostMeridianFlipActions()
557{
558 // step 1: If dome is syncing, wait until it stops
559 if (hasDome && (m_domeState == ISD::Dome::DOME_MOVING_CW || m_domeState == ISD::Dome::DOME_MOVING_CCW))
560 return true;
561
562 // step 2: check if post flip alignment is running
563 if (m_CaptureState == CAPTURE_ALIGNING || checkAlignmentAfterFlip())
564 return true;
565
566 // step 2: check if post flip guiding is running
567 // MF_NONE is set as soon as guiding is running and the guide deviation is below the limit
568 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED && m_GuideState != GUIDE_GUIDING
569 && checkGuidingAfterFlip())
570 return true;
571
572 // step 4: in case that a meridian flip has been completed and a guide deviation limit is set, we wait
573 // until the guide deviation is reported to be below the limit (@see setGuideDeviation(double, double)).
574 // Otherwise the meridian flip is complete
575 if (m_CaptureState == CAPTURE_CALIBRATING
576 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
577 {
578 if (Options::enforceGuideDeviation() || Options::enforceStartGuiderDrift())
579 return true;
580 else
581 updateMeridianFlipStage(MeridianFlipState::MF_NONE);
582 }
583
584 // all actions completed or no flip running
585 return false;
586}
587
588bool CameraState::checkGuidingAfterFlip()
589{
590 // if no meridian flip has completed, we do not touch guiding
591 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED)
592 return false;
593 // If we're not autoguiding then we're done
594 if (getMeridianFlipState()->resumeGuidingAfterFlip() == false)
595 {
596 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE);
597 return false;
598 }
599
600 // if we are waiting for a calibration, start it
601 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_COMPLETED
602 && getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_GUIDING)
603 {
604 appendLogText(i18n("Performing post flip re-calibration and guiding..."));
605
606 setCaptureState(CAPTURE_CALIBRATING);
607
608 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_GUIDING);
609 emit guideAfterMeridianFlip();
610 return true;
611 }
612 else if (m_CaptureState == CAPTURE_CALIBRATING)
613 {
614 if (getGuideState() == GUIDE_CALIBRATION_ERROR || getGuideState() == GUIDE_ABORTED)
615 {
616 // restart guiding after failure
617 appendLogText(i18n("Post meridian flip calibration error. Restarting..."));
618 emit guideAfterMeridianFlip();
619 return true;
620 }
621 else if (getGuideState() != GUIDE_GUIDING)
622 // waiting for guiding to start
623 return true;
624 else
625 // guiding is running
626 return false;
627 }
628 else
629 // in all other cases, do not touch
630 return false;
631}
632
633void CameraState::processGuidingFailed()
634{
635 if (m_FocusState > FOCUS_PROGRESS)
636 {
637 appendLogText(i18n("Autoguiding stopped. Waiting for autofocus to finish..."));
638 }
639 // If Autoguiding was started before and now stopped, let's abort (unless we're doing a meridian flip)
640 else if (isGuidingOn()
641 && getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_NONE &&
642 // JM 2022.08.03: Only abort if the current job is LIGHT. For calibration frames, we can ignore guide failures.
643 ((m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT) ||
644 m_CaptureState == CAPTURE_SUSPENDED || m_CaptureState == CAPTURE_PAUSED))
645 {
646 appendLogText(i18n("Autoguiding stopped. Aborting..."));
647 emit abortCapture();
648 }
649 else if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
650 {
651 if (increaseAlignmentRetries() >= 3)
652 {
653 appendLogText(i18n("Post meridian flip calibration error. Aborting..."));
654 emit abortCapture();
655 }
656 }
657}
658
659void CameraState::updateAdaptiveFocusState(bool success)
660{
661 m_refocusState->setAdaptiveFocusDone(true);
662
663 // Always process the adaptive focus state change, incl if a MF has also just started
664 if (success)
665 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus completed successfully";
666 else
667 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus failed";
668
669 m_refocusState->setAutoFocusReady(true);
670 // forward to the active job
671 if (m_activeJob != nullptr)
672 m_activeJob->setAutoFocusReady(true);
673
674 setFocusState(FOCUS_COMPLETE);
675 emit newLog(i18n(success ? "Adaptive focus complete." : "Adaptive focus failed. Continuing..."));
676}
677
678void CameraState::updateFocusState(FocusState state)
679{
680 if (state != m_FocusState)
681 qCDebug(KSTARS_EKOS_CAPTURE) << "Focus State changed from" <<
682 Ekos::getFocusStatusString(m_FocusState) <<
683 "to" << Ekos::getFocusStatusString(state);
684 setFocusState(state);
685
686 // Do not process if meridian flip in progress
687 if (getMeridianFlipState()->checkMeridianFlipRunning())
688 return;
689
690 switch (state)
691 {
692 // Do not process when aborted
693 case FOCUS_ABORTED:
694 if (!(getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_INITIATED))
695 {
696 // Meridian flip will abort focusing. In this case, after the meridian flip has completed capture
697 // will restart the re-focus attempt. Therefore we only abort capture if meridian flip is not running.
698 emit newFocusStatus(state);
699 appendLogText(i18n("Autofocus failed. Aborting exposure..."));
700 emit abortCapture();
701 }
702 break;
703 case FOCUS_COMPLETE:
704 // enable option to have a refocus event occur if HFR goes over threshold
705 m_refocusState->setAutoFocusReady(true);
706 // forward to the active job
707 if (m_activeJob != nullptr)
708 m_activeJob->setAutoFocusReady(true);
709 // reset the timer if a full autofocus was run (rather than an HFR check)
710 if (m_refocusState->getFocusHFRInAutofocus())
711 m_refocusState->startRefocusTimer(true);
712
713 // update HFR Threshold for those algorithms that use Autofocus as reference
714 if (Options::hFRCheckAlgorithm() == HFR_CHECK_MEDIAN_MEASURE ||
715 (m_refocusState->getFocusHFRInAutofocus() && Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS))
716 {
717 m_refocusState->addHFRValue(getFocusFilterName());
718 updateHFRThreshold();
719 }
720 emit newFocusStatus(state);
721 break;
722 default:
723 break;
724 }
725
726 if (m_activeJob != nullptr)
727 {
728 // Account for in-sequence-focus action that may lead to guide suspension and resumption after it is completed successfully.
729 // In this case, since the guiding was resumed, we should honor the guiding settle duration.
730 // To satisfy this, the guide state must be guiding and focus suspend guiding should be active.
731 if (state == FOCUS_COMPLETE && Options::guidingSettle() > 0 && Options::focusSuspendGuiding()
732 && m_GuideState == GUIDE_GUIDING)
733 {
734 // N.B. Do NOT convert to i18np since guidingRate is DOUBLE value (e.g. 1.36) so we always use plural with that.
735 appendLogText(i18n("Focus complete. Resuming in %1 seconds...", Options::guidingSettle()));
736 QTimer::singleShot(Options::guidingSettle() * 1000, this, [this, state]()
737 {
738 if (m_activeJob != nullptr)
739 m_activeJob->setFocusStatus(state);
740 });
741 }
742 else
743 m_activeJob->setFocusStatus(state);
744 }
745}
746
747AutofocusReason CameraState::getAFReason(RefocusState::RefocusReason state, QString &reasonInfo)
748{
749 AutofocusReason afReason;
750 reasonInfo = "";
751 switch (state)
752 {
753 case RefocusState::REFOCUS_USER_REQUEST:
754 afReason = AutofocusReason::FOCUS_USER_REQUEST;
755 break;
756 case RefocusState::REFOCUS_TEMPERATURE:
757 afReason = AutofocusReason::FOCUS_TEMPERATURE;
758 reasonInfo = i18n("Limit: %1 °C", QString::number(Options::maxFocusTemperatureDelta(), 'f', 2));
759 break;
760 case RefocusState::REFOCUS_TIME_ELAPSED:
761 afReason = AutofocusReason::FOCUS_TIME;
762 reasonInfo = i18n("Limit: %1 mins", Options::refocusEveryN());
763 break;
764 case RefocusState::REFOCUS_POST_MF:
765 afReason = AutofocusReason::FOCUS_MERIDIAN_FLIP;
766 break;
767 default:
768 afReason = AutofocusReason::FOCUS_NONE;
769 }
770 return afReason;
771}
772
773bool CameraState::startFocusIfRequired()
774{
775 // Do not start focus or adaptive focus if:
776 // 1. There is no active job, or
777 // 2. Target frame is not LIGHT
778 // 3. Capture is preview only
779 if (m_activeJob == nullptr || m_activeJob->getFrameType() != FRAME_LIGHT
780 || m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW)
781 return false;
782
783 RefocusState::RefocusReason reason = m_refocusState->checkFocusRequired();
784
785 // no focusing necessary
786 if (reason == RefocusState::REFOCUS_NONE)
787 return false;
788
789 // clear the flag for refocusing after the meridian flip
790 m_refocusState->setRefocusAfterMeridianFlip(false);
791
792 // Post meridian flip we need to reset filter _before_ running in-sequence focusing
793 // as it could have changed for whatever reason (e.g. alignment used a different filter).
794 // Then when focus process begins with the _target_ filter in place, it should take all the necessary actions to make it
795 // work for the next set of captures. This is direct reset to the filter device, not via Filter Manager.
796 if (getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_NONE)
797 {
798 int targetFilterPosition = m_activeJob->getTargetFilter();
799 if (targetFilterPosition > 0 && targetFilterPosition != getCurrentFilterPosition())
800 emit newFilterPosition(targetFilterPosition);
801 }
802
803 emit abortFastExposure();
804 updateFocusState(FOCUS_PROGRESS);
805 QString reasonInfo;
806 AutofocusReason afReason;
807
808 switch (reason)
809 {
810 case RefocusState::REFOCUS_HFR:
811 m_refocusState->resetInSequenceFocusCounter();
812 emit checkFocus(Options::hFRDeviation());
813 qCDebug(KSTARS_EKOS_CAPTURE) << "In-sequence focusing started...";
814 break;
815 case RefocusState::REFOCUS_ADAPTIVE:
816 m_refocusState->setAdaptiveFocusDone(true);
817 emit adaptiveFocus();
818 qCDebug(KSTARS_EKOS_CAPTURE) << "Adaptive focus started...";
819 break;
820 case RefocusState::REFOCUS_USER_REQUEST:
821 case RefocusState::REFOCUS_TEMPERATURE:
822 case RefocusState::REFOCUS_TIME_ELAPSED:
823 case RefocusState::REFOCUS_POST_MF:
824 // If we are over 30 mins since last autofocus, we'll reset frame.
825 if (m_refocusState->getRefocusEveryNTimerElapsedSec() >= 1800)
826 emit resetFocusFrame();
827
828 // force refocus
829 afReason = getAFReason(reason, reasonInfo);
830 emit runAutoFocus(afReason, reasonInfo);
831 // restart in sequence counting
832 m_refocusState->resetInSequenceFocusCounter();
833 qCDebug(KSTARS_EKOS_CAPTURE) << "Refocusing started...";
834 break;
835 default:
836 // this should not happen, since this case is handled above
837 return false;
838 }
839
840 setCaptureState(CAPTURE_FOCUSING);
841 return true;
842}
843
844void CameraState::updateHFRThreshold()
845{
846 // For Ficed algo no need to update the HFR threshold
847 if (Options::hFRCheckAlgorithm() == HFR_CHECK_FIXED)
848 return;
849
850 QString finalFilter = getFocusFilterName();
851 QList<double> filterHFRList = m_refocusState->getHFRMap()[finalFilter];
852
853 // Update the limit only if HFR values have been measured for the current filter
854 if (filterHFRList.empty())
855 return;
856
857 double value = 0;
858 if (Options::hFRCheckAlgorithm() == HFR_CHECK_LAST_AUTOFOCUS)
859 value = filterHFRList.last();
860 else // algo = Median Measure
861 {
862 int count = filterHFRList.size();
863 if (count > 1)
864 value = (count % 2) ? filterHFRList[count / 2] : (filterHFRList[count / 2 - 1] + filterHFRList[count / 2]) / 2.0;
865 else if (count == 1)
866 value = filterHFRList[0];
867 }
868 value += value * (Options::hFRThresholdPercentage() / 100.0);
869 Options::setHFRDeviation(value);
870 emit newLimitFocusHFR(value); // Updates the limits UI with the new HFR threshold
871}
872
873QString CameraState::getFocusFilterName()
874{
875 QString finalFilter;
876 if (m_CurrentFilterPosition > 0)
877 // If we are using filters, then we retrieve which filter is currently active.
878 // We check if filter lock is used, and store that instead of the current filter.
879 // e.g. If current filter HA, but lock filter is L, then the HFR value is stored for L filter.
880 // If no lock filter exists, then we store as is (HA)
881 finalFilter = (m_CurrentFocusFilterName == "--" ? m_CurrentFilterName : m_CurrentFocusFilterName);
882 else
883 // No filters
884 finalFilter = "--";
885 return finalFilter;
886}
887
888bool CameraState::checkAlignmentAfterFlip()
889{
890 // if no meridian flip has completed, we do not touch guiding
891 if (getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_COMPLETED)
892 {
893 qCDebug(KSTARS_EKOS_CAPTURE) << "checkAlignmentAfterFlip too early, meridian flip stage =" <<
894 getMeridianFlipState()->getMeridianFlipStage();
895 return false;
896 }
897 // If we do not need to align then we're done
898 if (getMeridianFlipState()->resumeAlignmentAfterFlip() == false)
899 {
900 qCDebug(KSTARS_EKOS_CAPTURE) << "No alignment after flip required.";
901 return false;
902 }
903
904 // if we are waiting for a calibration, start it
905 if (m_CaptureState < CAPTURE_ALIGNING)
906 {
907 appendLogText(i18n("Performing post flip re-alignment..."));
908
909 resetAlignmentRetries();
910 setCaptureState(CAPTURE_ALIGNING);
911
912 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_ALIGNING);
913 return true;
914 }
915 else
916 // in all other cases, do not touch
917 return false;
918}
919
920void CameraState::checkGuideDeviationTimeout()
921{
922 if (m_activeJob && m_activeJob->getStatus() == JOB_ABORTED
923 && isGuidingDeviationDetected())
924 {
925 appendLogText(i18n("Guide module timed out."));
926 setGuidingDeviationDetected(false);
927
928 // If capture was suspended, it should be aborted (failed) now.
929 if (m_CaptureState == CAPTURE_SUSPENDED)
930 {
931 setCaptureState(CAPTURE_ABORTED);
932 }
933 }
934}
935
936void CameraState::setGuideDeviation(double deviation_rms)
937{
938 // communicate the new guiding deviation
939 emit newGuiderDrift(deviation_rms);
940
941 const QString deviationText = QString("%1").arg(deviation_rms, 0, 'f', 3);
942
943 // if guiding deviations occur and no job is active, check if a meridian flip is ready to be executed
944 if (m_activeJob == nullptr && checkMeridianFlipReady())
945 return;
946
947 // 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
948 if (m_CaptureState == CAPTURE_PROGRESS &&
949 getMeridianFlipState()->getMeridianFlipStage() != MeridianFlipState::MF_REQUESTED &&
950 getMeridianFlipState()->checkMeridianFlipRunning() == false)
951 {
952 // initial guiding deviation irrelevant or below limit
953 if (Options::enforceStartGuiderDrift() == false || deviation_rms < Options::startGuideDeviation())
954 {
955 setCaptureState(CAPTURE_CALIBRATING);
956 if (Options::enforceStartGuiderDrift())
957 appendLogText(i18n("Initial guiding deviation %1 below limit value of %2 arcsecs",
958 deviationText, Options::startGuideDeviation()));
959 setGuidingDeviationDetected(false);
960 setStartingCapture(false);
961 }
962 else
963 {
964 // warn only once
965 if (isGuidingDeviationDetected() == false)
966 appendLogText(i18n("Initial guiding deviation %1 exceeded limit value of %2 arcsecs",
967 deviationText, Options::startGuideDeviation()));
968
969 setGuidingDeviationDetected(true);
970
971 // Check if we need to start meridian flip. If yes, we need to start capturing
972 // to ensure that capturing is recovered after the flip
973 if (checkMeridianFlipReady())
974 emit startCapture();
975 }
976
977 // in any case, do not proceed
978 return;
979 }
980
981 // If guiding is started after a meridian flip we will start getting guide deviations again
982 // if the guide deviations are within our limits, we resume the sequence
983 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_GUIDING)
984 {
985 // If the user didn't select any guiding deviation, the meridian flip is completed
986 if (Options::enforceGuideDeviation() == false || deviation_rms < Options::guideDeviation())
987 {
988 appendLogText(i18n("Post meridian flip calibration completed successfully."));
989 // N.B. Set meridian flip stage AFTER resumeSequence() always
990 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_NONE);
991 return;
992 }
993 }
994
995 // Check for initial deviation in the middle of a sequence (above just checks at the start of a sequence).
996 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT
997 && isStartingCapture() && Options::enforceStartGuiderDrift())
998 {
999 setStartingCapture(false);
1000 if (deviation_rms > Options::startGuideDeviation())
1001 {
1002 appendLogText(i18n("Guiding deviation at capture startup %1 exceeded limit %2 arcsecs.",
1003 deviationText, Options::startGuideDeviation()));
1004 emit suspendCapture();
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 return;
1014 }
1015 else
1016 appendLogText(i18n("Guiding deviation at capture startup %1 below limit value of %2 arcsecs",
1017 deviationText, Options::startGuideDeviation()));
1018 }
1019
1020 if (m_CaptureState != CAPTURE_SUSPENDED)
1021 {
1022
1023 // We don't enforce limit on previews or non-LIGHT frames
1024 if ((Options::enforceGuideDeviation() == false)
1025 ||
1026 (m_activeJob && (m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW ||
1027 m_activeJob->getExposeLeft() == 0.0 ||
1028 m_activeJob->getFrameType() != FRAME_LIGHT)))
1029 return;
1030
1031 // If we have an active busy job, let's abort it if guiding deviation is exceeded.
1032 // And we accounted for the spike
1033 if (m_activeJob && m_activeJob->getStatus() == JOB_BUSY && m_activeJob->getFrameType() == FRAME_LIGHT)
1034 {
1035 if (deviation_rms <= Options::guideDeviation())
1036 resetSpikesDetected();
1037 else
1038 {
1039 // Require several consecutive spikes to fail.
1040 if (increaseSpikesDetected() < Options::guideDeviationReps())
1041 return;
1042
1043 appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 arcsecs for %4 consecutive samples, "
1044 "suspending exposure and waiting for guider up to %3 seconds.",
1045 deviationText, Options::guideDeviation(),
1046 QString("%L1").arg(getGuideDeviationTimer().interval() / 1000.0, 0, 'f', 3),
1047 Options::guideDeviationReps()));
1048
1049 emit suspendCapture();
1050
1051 resetSpikesDetected();
1052 setGuidingDeviationDetected(true);
1053
1054 // Check if we need to start meridian flip. If yes, we need to start capturing
1055 // to ensure that capturing is recovered after the flip
1056 if (checkMeridianFlipReady())
1057 emit startCapture();
1058 else
1059 getGuideDeviationTimer().start();
1060 }
1061 return;
1062 }
1063 }
1064
1065 // Find the first aborted job
1066 SequenceJob *abortedJob = nullptr;
1067 for(auto &job : allJobs())
1068 {
1069 if (job->getStatus() == JOB_ABORTED)
1070 {
1071 abortedJob = job;
1072 break;
1073 }
1074 }
1075
1076 if (abortedJob != nullptr && isGuidingDeviationDetected())
1077 {
1078 if (deviation_rms <= Options::startGuideDeviation())
1079 {
1080 getGuideDeviationTimer().stop();
1081
1082 // Start with delay if start hasn't been triggered before
1083 if (! getCaptureDelayTimer().isActive())
1084 {
1085 // if capturing has been suspended, restart it
1086 if (m_CaptureState == CAPTURE_SUSPENDED)
1087 {
1088 const int seqDelay = abortedJob->getCoreProperty(SequenceJob::SJ_Delay).toInt();
1089 if (seqDelay == 0)
1090 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, "
1091 "resuming exposure.",
1092 deviationText, Options::startGuideDeviation()));
1093 else
1094 appendLogText(i18n("Guiding deviation %1 is now lower than limit value of %2 arcsecs, "
1095 "resuming exposure in %3 seconds.",
1096 deviationText, Options::startGuideDeviation(), seqDelay / 1000.0));
1097
1098 emit startCapture();
1099 }
1100 }
1101 return;
1102 }
1103 else
1104 {
1105 // stop the delayed capture start if necessary
1106 if (getCaptureDelayTimer().isActive())
1107 getCaptureDelayTimer().stop();
1108
1109 appendLogText(i18n("Guiding deviation %1 is still higher than limit value of %2 arcsecs.",
1110 deviationText, Options::startGuideDeviation()));
1111 }
1112 }
1113}
1114
1115void CameraState::addDownloadTime(double time)
1116{
1117 totalDownloadTime += time;
1118 downloadsCounter++;
1119}
1120
1121int CameraState::pendingJobCount()
1122{
1123 int completedJobs = 0;
1124
1125 foreach (SequenceJob * job, allJobs())
1126 {
1127 if (job->getStatus() == JOB_DONE)
1128 completedJobs++;
1129 }
1130
1131 return (allJobs().count() - completedJobs);
1132
1133}
1134
1135QString CameraState::jobState(int id)
1136{
1137 if (id < allJobs().count())
1138 {
1139 SequenceJob * job = allJobs().at(id);
1140 return job->getStatusString();
1141 }
1142
1143 return QString();
1144
1145}
1146
1147QString CameraState::jobFilterName(int id)
1148{
1149 if (id < allJobs().count())
1150 {
1151 SequenceJob * job = allJobs().at(id);
1152 return job->getCoreProperty(SequenceJob::SJ_Filter).toString();
1153 }
1154
1155 return QString();
1156
1157}
1158
1159CCDFrameType CameraState::jobFrameType(int id)
1160{
1161 if (id < allJobs().count())
1162 {
1163 SequenceJob * job = allJobs().at(id);
1164 return job->getFrameType();
1165 }
1166
1167 return FRAME_NONE;
1168}
1169
1170int CameraState::jobImageProgress(int id)
1171{
1172 if (id < allJobs().count())
1173 {
1174 SequenceJob * job = allJobs().at(id);
1175 return job->getCompleted();
1176 }
1177
1178 return -1;
1179}
1180
1181int CameraState::jobImageCount(int id)
1182{
1183 if (id < allJobs().count())
1184 {
1185 SequenceJob * job = allJobs().at(id);
1186 return job->getCoreProperty(SequenceJob::SJ_Count).toInt();
1187 }
1188
1189 return -1;
1190}
1191
1192double CameraState::jobExposureProgress(int id)
1193{
1194 if (id < allJobs().count())
1195 {
1196 SequenceJob * job = allJobs().at(id);
1197 return job->getExposeLeft();
1198 }
1199
1200 return -1;
1201}
1202
1203double CameraState::jobExposureDuration(int id)
1204{
1205 if (id < allJobs().count())
1206 {
1207 SequenceJob * job = allJobs().at(id);
1208 return job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
1209 }
1210
1211 return -1;
1212}
1213
1214double CameraState::progressPercentage()
1215{
1216 int totalImageCount = 0;
1217 int totalImageCompleted = 0;
1218
1219 foreach (SequenceJob * job, allJobs())
1220 {
1221 totalImageCount += job->getCoreProperty(SequenceJob::SJ_Count).toInt();
1222 totalImageCompleted += job->getCompleted();
1223 }
1224
1225 if (totalImageCount != 0)
1226 return ((static_cast<double>(totalImageCompleted) / totalImageCount) * 100.0);
1227 else
1228 return -1;
1229}
1230
1231bool CameraState::isActiveJobPreview()
1232{
1233 return m_activeJob && m_activeJob->jobType() == SequenceJob::JOBTYPE_PREVIEW;
1234}
1235
1236int CameraState::activeJobRemainingTime()
1237{
1238 if (m_activeJob == nullptr)
1239 return -1;
1240
1241 return m_activeJob->getJobRemainingTime(averageDownloadTime());
1242}
1243
1244int CameraState::overallRemainingTime()
1245{
1246 int remaining = 0;
1247 double estimatedDownloadTime = averageDownloadTime();
1248
1249 foreach (SequenceJob * job, allJobs())
1250 remaining += job->getJobRemainingTime(estimatedDownloadTime);
1251
1252 return remaining;
1253}
1254
1255QString CameraState::sequenceQueueStatus()
1256{
1257 if (allJobs().count() == 0)
1258 return "Invalid";
1259
1260 if (isBusy())
1261 return "Running";
1262
1263 int idle = 0, error = 0, complete = 0, aborted = 0, running = 0;
1264
1265 foreach (SequenceJob * job, allJobs())
1266 {
1267 switch (job->getStatus())
1268 {
1269 case JOB_ABORTED:
1270 aborted++;
1271 break;
1272 case JOB_BUSY:
1273 running++;
1274 break;
1275 case JOB_DONE:
1276 complete++;
1277 break;
1278 case JOB_ERROR:
1279 error++;
1280 break;
1281 case JOB_IDLE:
1282 idle++;
1283 break;
1284 }
1285 }
1286
1287 if (error > 0)
1288 return "Error";
1289
1290 if (aborted > 0)
1291 {
1292 if (m_CaptureState == CAPTURE_SUSPENDED)
1293 return "Suspended";
1294 else
1295 return "Aborted";
1296 }
1297
1298 if (running > 0)
1299 return "Running";
1300
1301 if (idle == allJobs().count())
1302 return "Idle";
1303
1304 if (complete == allJobs().count())
1305 return "Complete";
1306
1307 return "Invalid";
1308}
1309
1310QJsonObject CameraState::calibrationSettings()
1311{
1312 QJsonObject settings =
1313 {
1314 {"preAction", static_cast<int>(calibrationPreAction())},
1315 {"duration", flatFieldDuration()},
1316 {"az", wallCoord().az().Degrees()},
1317 {"al", wallCoord().alt().Degrees()},
1318 {"adu", targetADU()},
1319 {"tolerance", targetADUTolerance()},
1320 {"skyflat", skyFlat()},
1321 };
1322
1323 return settings;
1324}
1325
1326void CameraState::setCalibrationSettings(const QJsonObject &settings)
1327{
1328 const int preAction = settings["preAction"].toInt(calibrationPreAction());
1329 const int duration = settings["duration"].toInt(flatFieldDuration());
1330 const double az = settings["az"].toDouble(wallCoord().az().Degrees());
1331 const double al = settings["al"].toDouble(wallCoord().alt().Degrees());
1332 const int adu = settings["adu"].toInt(static_cast<int>(std::round(targetADU())));
1333 const int tolerance = settings["tolerance"].toInt(static_cast<int>(std::round(targetADUTolerance())));
1334 const int skyflat = settings["skyflat"].toBool();
1335
1336 setCalibrationPreAction(static_cast<CalibrationPreActions>(preAction));
1337 setFlatFieldDuration(static_cast<FlatFieldDuration>(duration));
1338 wallCoord().setAz(az);
1339 wallCoord().setAlt(al);
1340 setTargetADU(adu);
1341 setTargetADUTolerance(tolerance);
1342 setSkyFlat(skyflat);
1343}
1344
1345bool CameraState::setDarkFlatExposure(SequenceJob *job)
1346{
1347 const auto darkFlatFilter = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
1348 const auto darkFlatBinning = job->getCoreProperty(SequenceJob::SJ_Binning).toPoint();
1349 const auto darkFlatADU = job->getCoreProperty(SequenceJob::SJ_TargetADU).toInt();
1350
1351 for (auto &oneJob : allJobs())
1352 {
1353 if (oneJob->getFrameType() != FRAME_FLAT)
1354 continue;
1355
1356 const auto filter = oneJob->getCoreProperty(SequenceJob::SJ_Filter).toString();
1357
1358 // Match filter, if one exists.
1359 if (!darkFlatFilter.isEmpty() && darkFlatFilter != filter)
1360 continue;
1361
1362 // Match binning
1363 const auto binning = oneJob->getCoreProperty(SequenceJob::SJ_Binning).toPoint();
1364 if (darkFlatBinning != binning)
1365 continue;
1366
1367 // Match ADU, if used.
1368 const auto adu = oneJob->getCoreProperty(SequenceJob::SJ_TargetADU).toInt();
1369 if (job->getFlatFieldDuration() == DURATION_ADU)
1370 {
1371 if (darkFlatADU != adu)
1372 continue;
1373 }
1374
1375 // Now get the exposure
1376 job->setCoreProperty(SequenceJob::SJ_Exposure, oneJob->getCoreProperty(SequenceJob::SJ_Exposure).toDouble());
1377
1378 return true;
1379 }
1380 return false;
1381}
1382
1383void CameraState::checkSeqBoundary()
1384{
1385 // No updates during meridian flip
1386 if (getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_ALIGNING)
1387 return;
1388
1389 setNextSequenceID(placeholderPath().checkSeqBoundary(*getActiveJob()));
1390}
1391
1392bool CameraState::isModelinDSLRInfo(const QString &model)
1393{
1394 auto pos = std::find_if(m_DSLRInfos.begin(), m_DSLRInfos.end(), [model](QMap<QString, QVariant> &oneDSLRInfo)
1395 {
1396 return (oneDSLRInfo["Model"] == model);
1397 });
1398
1399 return (pos != m_DSLRInfos.end());
1400}
1401
1402void CameraState::setCapturedFramesCount(const QString &signature, uint16_t count)
1403{
1404 m_capturedFramesMap[signature] = count;
1405 qCDebug(KSTARS_EKOS_CAPTURE) <<
1406 QString("Client module indicates that storage for '%1' has already %2 captures processed.").arg(signature).arg(count);
1407 // Scheduler's captured frame map overrides the progress option of the Capture module
1408 setIgnoreJobProgress(false);
1409}
1410
1411void CameraState::changeSequenceValue(int index, QString key, QString value)
1412{
1413 QJsonArray seqArray = getSequence();
1414 QJsonObject oneSequence = seqArray[index].toObject();
1415 oneSequence[key] = value;
1416 seqArray.replace(index, oneSequence);
1417 setSequence(seqArray);
1418 emit sequenceChanged(seqArray);
1419}
1420
1421void CameraState::addCapturedFrame(const QString &signature)
1422{
1423 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature);
1424 if (m_capturedFramesMap.end() != frame_item)
1425 frame_item.value()++;
1426 else m_capturedFramesMap[signature] = 1;
1427}
1428
1429void CameraState::removeCapturedFrameCount(const QString &signature, uint16_t count)
1430{
1431 CapturedFramesMap::iterator frame_item = m_capturedFramesMap.find(signature);
1432 if (m_capturedFramesMap.end() != frame_item)
1433 {
1434 if (frame_item.value() <= count)
1435 // clear signature entry if none will be left
1436 m_capturedFramesMap.remove(signature);
1437 else
1438 // remove the frame count
1439 frame_item.value() = frame_item.value() - count;
1440 }
1441}
1442
1443void CameraState::appendLogText(const QString &message)
1444{
1445 qCInfo(KSTARS_EKOS_CAPTURE()) << message;
1446 emit newLog(message);
1447}
1448
1449bool CameraState::isGuidingOn()
1450{
1451 // In case we are doing non guiding dither, then we are not performing autoguiding.
1452 if (Options::ditherNoGuiding())
1453 return false;
1454
1455 return (m_GuideState == GUIDE_GUIDING ||
1456 m_GuideState == GUIDE_CALIBRATING ||
1457 m_GuideState == GUIDE_CALIBRATION_SUCCESS ||
1458 m_GuideState == GUIDE_DARK ||
1459 m_GuideState == GUIDE_SUBFRAME ||
1460 m_GuideState == GUIDE_STAR_SELECT ||
1461 m_GuideState == GUIDE_REACQUIRE ||
1462 m_GuideState == GUIDE_DITHERING ||
1463 m_GuideState == GUIDE_DITHERING_SUCCESS ||
1464 m_GuideState == GUIDE_DITHERING_ERROR ||
1465 m_GuideState == GUIDE_DITHERING_SETTLE ||
1466 m_GuideState == GUIDE_SUSPENDED
1467 );
1468}
1469
1470bool CameraState::isActivelyGuiding()
1471{
1472 return isGuidingOn() && (m_GuideState == GUIDE_GUIDING);
1473}
1474
1475void CameraState::setAlignState(AlignState value)
1476{
1477 if (value != m_AlignState)
1478 qCDebug(KSTARS_EKOS_CAPTURE) << "Align State changed from" << Ekos::getAlignStatusString(
1479 m_AlignState) << "to" << Ekos::getAlignStatusString(value);
1480 m_AlignState = value;
1481
1482 getMeridianFlipState()->setResumeAlignmentAfterFlip(true);
1483
1484 switch (value)
1485 {
1486 case ALIGN_COMPLETE:
1487 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING)
1488 {
1489 appendLogText(i18n("Post flip re-alignment completed successfully."));
1490 resetAlignmentRetries();
1491 // Trigger guiding if necessary.
1492 if (checkGuidingAfterFlip() == false)
1493 {
1494 // If no guiding is required, the meridian flip is complete
1495 updateMeridianFlipStage(MeridianFlipState::MF_NONE);
1496 setCaptureState(CAPTURE_WAITING);
1497 }
1498 }
1499 break;
1500
1501 case ALIGN_ABORTED:
1502 case ALIGN_FAILED:
1503 // TODO run it 3 times before giving up
1504 if (getMeridianFlipState()->getMeridianFlipStage() == MeridianFlipState::MF_ALIGNING)
1505 {
1506 if (increaseAlignmentRetries() >= 3)
1507 {
1508 appendLogText(i18n("Post-flip alignment failed."));
1509 emit abortCapture();
1510 }
1511 else
1512 {
1513 appendLogText(i18n("Post-flip alignment failed. Retrying..."));
1514 // set back the stage
1515 updateMeridianFlipStage(MeridianFlipState::MF_COMPLETED);
1516 }
1517 }
1518 break;
1519
1520 default:
1521 break;
1522 }
1523}
1524
1525void CameraState::setPrepareComplete(bool success)
1526{
1527
1528 if (success)
1529 {
1530 setCaptureState(CAPTURE_PROGRESS);
1531 emit executeActiveJob();
1532 }
1533 else
1534 {
1535 qWarning(KSTARS_EKOS_CAPTURE) << "Capture preparation failed, aborting.";
1536 setCaptureState(CAPTURE_ABORTED);
1537 emit abortCapture();
1538 }
1539
1540}
1541
1542} // 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 init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
QString name(GameStandardAction id)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
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 Fri Nov 29 2024 11:57:47 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.