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

KDE's Doxygen guidelines are available online.