Kstars

capturemodulestate.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 SPDX-FileCopyrightText: 2024 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "capturemodulestate.h"
9#include "camera.h"
10
11#include "ekos_capture_debug.h"
12
13namespace Ekos
14{
15
16CaptureModuleState::CaptureModuleState(QObject *parent): QObject{parent} {}
17
18void CaptureModuleState::addCamera(QSharedPointer<Camera> newCamera)
19{
20 mutableCameras().append(newCamera);
21 connect(newCamera.get(), &Camera::newStatus, this, &CaptureModuleState::captureStateChanged);
22 connect(newCamera.get(), &Camera::requestAction, this, &CaptureModuleState::handleActionRequest);
23
24 // prepare dithering enforcing timeout
25 m_DitheringTimer.setSingleShot(true);
26 connect(&m_DitheringTimer, &QTimer::timeout, this, &CaptureModuleState::startDithering);
27 // check what to do next
28 checkActiveActions();
29}
30
31void CaptureModuleState::removeCamera(int pos)
32{
33 if (pos > m_Cameras.count() - 1)
34 return;
35
36 auto cam = m_Cameras.at(pos);
37 // clear connections
38 cam->disconnect(this);
39
40 // remove the camera from the list
41 m_Cameras.removeAt(pos);
42 // check what to do next
43 checkActiveActions();
44}
45
46void CaptureModuleState::captureStateChanged(CaptureState newstate, const QString &trainname, int cameraID)
47{
48 Q_UNUSED(trainname)
49
50 switch (newstate)
51 {
52 case CAPTURE_IDLE:
53 case CAPTURE_PAUSED:
57 case CAPTURE_WAITING:
58 if (activeAction(cameraID) == CAPTURE_ACTION_PAUSE)
59 // capturing of a single frame completed, clear the active action
60 setActiveAction(cameraID, CAPTURE_ACTION_NONE);
61 break;
62 case CAPTURE_ABORTED:
63 clearAllActions(cameraID);
64 break;
66 // abort capturing of followers if the meridian flip is running
67 if (checkMeridianFlipActive())
68 pauseCapturingImmediately(cameraID);
69 break;
70 default:
71 // do nothing
72 break;
73 }
74 // check what to do next
75 checkActiveActions();
76}
77
78void CaptureModuleState::setGuideStatus(GuideState newstate)
79{
80 switch (newstate)
81 {
82 case GUIDE_DITHERING_SUCCESS:
83 case GUIDE_DITHERING_ERROR:
84 foreach (auto cam, cameras())
85 {
86 // dithering action finished
87 if (activeAction(cam->cameraId()) == CAPTURE_ACTION_DITHER)
88 setActiveAction(cam->cameraId(), CAPTURE_ACTION_NONE);
89 // restart all cameras that have been stopped for dithering
90 if (cam->state()->isCaptureStopped() || cam->state()->isCapturePausing())
91 enqueueAction(cam->cameraId(), CAPTURE_ACTION_START);
92 }
93 break;
94 case GUIDE_GUIDING:
95 foreach (auto cam, cameras())
96 {
97 // successful wait for guiding
98 if (activeAction(cam->cameraId()) == CAPTURE_ACTION_CHECK_GUIDING)
99 setActiveAction(cam->cameraId(), CAPTURE_ACTION_NONE);
100 }
101 break;
102 default:
103 // do nothing
104 break;
105 }
106
107 // forward state to cameras
108 foreach (auto cam, cameras())
109 cam->state()->setGuideState(newstate);
110
111 // check what to do next
112 checkActiveActions();
113}
114
115void CaptureModuleState::setGuideDeviation(double delta_ra, double delta_dec)
116{
117 const double deviation_rms = std::hypot(delta_ra, delta_dec);
118 // forward the deviation to all cameras
119 foreach (auto cam, cameras())
120 cam->state()->setGuideDeviation(deviation_rms);
121}
122
123void Ekos::CaptureModuleState::pauseCapturingImmediately(int cameraID, bool followersOnly)
124{
125 // execute for all cameras
126 if (cameraID < 0)
127 {
128 foreach (auto cam, cameras())
129 {
130 pauseCapturingImmediately(cam->cameraId(), followersOnly);
131 }
132 return;
133 }
134
135 // protect against unexpected behaviour
136 if (cameraID >= cameras().count())
137 {
138 qCWarning(KSTARS_EKOS_CAPTURE) << "pauseCapturingImmediately(): unknown camera ID =" << cameraID;
139 return;
140 }
141
142 // check if capturing is running
143 if ((!followersOnly || cameraID > 0) && cameras()[cameraID]->state()->isCaptureRunning())
144 {
145 // pause to avoid that capturing gets restarted automatically after suspending
146 enqueueAction(cameraID, CAPTURE_ACTION_PAUSE);
147 // suspend, it would take to long to finish
148 enqueueAction(cameraID, CAPTURE_ACTION_SUSPEND);
149 }
150}
151
152bool CaptureModuleState::checkMeridianFlipActive()
153{
154 if (leadState().isNull())
155 return false;
156
157 return leadState()->getMeridianFlipState()->checkMeridianFlipActive();
158}
159
160const QSharedPointer<CameraState> CaptureModuleState::leadState()
161{
162 if (cameras().size() <= 0)
164
165 return cameras()[0]->state();
166}
167
168void CaptureModuleState::updateMFMountState(MeridianFlipState::MeridianFlipMountState status)
169{
170 // avoid doubled actions
171 if (status == m_MFMountState)
172 return;
173
174 switch (status)
175 {
176 case MeridianFlipState::MOUNT_FLIP_ACCEPTED:
177 case MeridianFlipState::MOUNT_FLIP_RUNNING:
178 // suspend capturing of all follower cameras
179 pauseCapturingImmediately();
180 break;
181 case MeridianFlipState::MOUNT_FLIP_COMPLETED:
182 setupRestartPostMF();
183 break;
184 default:
185 break;
186 }
187
188 m_MFMountState = status;
189
190 // check what to do next
191 checkActiveActions();
192}
193
194CaptureWorkflowActionType CaptureModuleState::activeAction(int cameraID)
195{
196 if (m_activeActions.contains(cameraID))
197 return m_activeActions[cameraID];
198 else
199 return CAPTURE_ACTION_NONE;
200}
201
202void CaptureModuleState::setActiveAction(int cameraID, CaptureWorkflowActionType action)
203{
204 if (action == CAPTURE_ACTION_NONE)
205 m_activeActions.remove(cameraID);
206 else
207 m_activeActions[cameraID] = action;
208}
209
210void CaptureModuleState::handleActionRequest(int cameraID, CaptureWorkflowActionType action)
211{
212 qCDebug(KSTARS_EKOS_CAPTURE) << "Handling" << action << "request for camera" << cameraID;
213 switch(action)
214 {
215 case CAPTURE_ACTION_DITHER_REQUEST:
216 setActiveAction(cameraID, action);
217 prepareDitheringAction(cameraID);
218 break;
219 default:
220 // do nothing
221 break;
222 }
223 // check what's up next
224 checkActiveActions();
225}
226
227void CaptureModuleState::enqueueAction(int cameraID, CaptureWorkflowActionType action)
228{
229 getActionQueue(cameraID).enqueue(action);
230}
231
232void CaptureModuleState::checkActiveActions()
233{
234 foreach (auto cam, cameras())
235 {
236 const int cameraID = cam->cameraId();
237 switch (activeAction(cameraID))
238 {
239 case CAPTURE_ACTION_NONE:
240 checkNextActionExecution(cameraID);
241 break;
242 case CAPTURE_ACTION_DITHER_REQUEST:
243 checkReadyForDithering(cameraID);
244 break;
245 default:
246 // nothing to do
247 break;
248 }
249 }
250}
251
252QQueue<CaptureWorkflowActionType> &CaptureModuleState::getActionQueue(int cameraId)
253{
254 if (!m_actionQueues.contains(cameraId))
255 m_actionQueues[cameraId] = QQueue<CaptureWorkflowActionType>();
256
257 return m_actionQueues[cameraId];
258}
259
260void CaptureModuleState::checkNextActionExecution(int cameraID)
261{
262 if (cameraID == -1)
263 {
264 foreach (auto cam, cameras())
265 checkNextActionExecution(cam->cameraId());
266
267 return;
268 }
269 QQueue<CaptureWorkflowActionType> &actionQueue = getActionQueue(cameraID);
270
271 // nothing to do, if either there is an active action or the queue is empty
272 if (m_activeActions[cameraID] != CAPTURE_ACTION_NONE || actionQueue.isEmpty())
273 return;
274
275 auto action = actionQueue.dequeue();
276 QSharedPointer<Camera> cam = cameras()[cameraID];
277 switch (action)
278 {
279 case CAPTURE_ACTION_START:
280 if (cam->state()->isCaptureStopped())
281 cam->start();
282 else if (cam->state()->isCapturePausing())
283 cam->toggleSequence();
284 break;
285 case CAPTURE_ACTION_PAUSE:
286 setActiveAction(cameraID, CAPTURE_ACTION_PAUSE);
287 cam->pause();
288 // pause immediately by suspending
289 if (!getActionQueue(cameraID).isEmpty() && actionQueue.head() == CAPTURE_ACTION_SUSPEND)
290 {
291 actionQueue.dequeue();
292 cam->suspend();
293 }
294 break;
295 case CAPTURE_ACTION_SUSPEND:
296 setActiveAction(cameraID, CAPTURE_ACTION_SUSPEND);
297 cam->suspend();
298 break;
299 case CAPTURE_ACTION_CHECK_GUIDING:
300 setActiveAction(cameraID, CAPTURE_ACTION_CHECK_GUIDING);
301 break;
302 default:
303 qCWarning(KSTARS_EKOS_CAPTURE) << "No activity defined for action" << action;
304 break;
305 }
306 // check what to do next
307 checkActiveActions();
308}
309
310void CaptureModuleState::clearAllActions(int cameraID)
311{
312 setActiveAction(cameraID, CAPTURE_ACTION_NONE);
313 getActionQueue(cameraID).clear();
314}
315
316void CaptureModuleState::prepareDitheringAction(int cameraId)
317{
318 const double maxRemainingExposureTime = 0.5 * mutableCameras()[cameraId]->activeJob()->getCoreProperty(
319 SequenceJob::SJ_Exposure).toDouble();
320 foreach (auto cam, cameras())
321 {
322 if (cam->state()->isCaptureRunning() && cam->cameraId() != cameraId)
323 {
324 if (cameraId > 0 || cam->activeJob()->getExposeLeft() < maxRemainingExposureTime)
325 {
326 // wait until this capture finishes, request pausing (except for lead camera)
327 if (cam->cameraId() > 0)
328 enqueueAction(cam->cameraId(), CAPTURE_ACTION_PAUSE);
329 }
330 else
331 {
332 // pause to avoid that capturing gets restarted automatically after suspending
333 enqueueAction(cam->cameraId(), CAPTURE_ACTION_PAUSE);
334 // suspend, it would take to long to finish
335 enqueueAction(cam->cameraId(), CAPTURE_ACTION_SUSPEND);
336 }
337 }
338 }
339 // check what to do next
340 checkActiveActions();
341 // start the dithering timer to ensure that we do not wait infinitely
342 if (! m_DitheringTimer.isActive())
343 m_DitheringTimer.start(maxRemainingExposureTime * 1000);
344}
345
346void CaptureModuleState::checkReadyForDithering(int cameraId)
347{
348 // check if there is still another camera capturing
349 bool ready = true;
350 foreach (auto cam, cameras())
351 if (cam->state()->isCaptureRunning() && cam->cameraId() != cameraId)
352 {
353 ready = false;
354 qCDebug(KSTARS_EKOS_CAPTURE) << "Dithering requested by camera" << cameraId << "blocked by camera" << cam->cameraId();
355 break;
356 }
357
358 if (ready)
359 {
360 qCInfo(KSTARS_EKOS_CAPTURE) << "Execute dithering requested by camera" << cameraId;
361 setActiveAction(cameraId, CAPTURE_ACTION_DITHER);
362 foreach(auto cam, cameras())
363 {
364 const int id = cam->cameraId();
365 // clear dithering requests for all other cameras
366 if (id != cameraId && activeAction(id) == CAPTURE_ACTION_DITHER_REQUEST)
367 setActiveAction(id, CAPTURE_ACTION_DITHER);
368 }
369 // stop the timeout
370 if (m_DitheringTimer.isActive())
371 m_DitheringTimer.stop();
372
373 startDithering();
374 }
375}
376
377void CaptureModuleState::startDithering()
378{
379 // find first camera that has requested dithering
380 bool found = false;
381 foreach (auto id, m_activeActions.keys())
382 if (activeAction(id) == CAPTURE_ACTION_DITHER_REQUEST)
383 {
384 setActiveAction(id, CAPTURE_ACTION_DITHER);
385 found = true;
386 }
387 else if (activeAction(id) == CAPTURE_ACTION_DITHER)
388 found = true;
389
390 // do nothing if no request found
391 if (found == false)
392 return;
393
394 // abort all other running captures that do not requested a dither
395 foreach (auto cam, cameras())
396 if (cam->state()->isCaptureRunning() && activeAction(cam->cameraId()) != CAPTURE_ACTION_DITHER)
397 {
398 qCInfo(KSTARS_EKOS_CAPTURE) << "Aborting capture of camera" << cam->cameraId() << "before dithering starts";
399 cam->abort();
400 }
401
402 // dither
403 emit newLog(i18n("Dithering..."));
404 emit dither();
405}
406
407void CaptureModuleState::setupRestartPostMF()
408{
409 // do nothing of we do not have any cameras
410 if (cameras().size() == 0)
411 return;
412
413 const bool waitForGuiding = leadState()->getMeridianFlipState()->resumeGuidingAfterFlip()
414 && leadState()->getGuideState() != GUIDE_GUIDING;
415
416 foreach (auto cam, cameras())
417 {
418 if (cam->cameraId() > 0 && (cam->state()->isCaptureStopped() || cam->state()->isCapturePausing()))
419 {
420 // add a guiding check if guiding was running before the flip started
421 if (waitForGuiding)
422 enqueueAction(cam->cameraId(), CAPTURE_ACTION_CHECK_GUIDING);
423
424 // restart all suspended and aborted cameras
425 enqueueAction(cam->cameraId(), CAPTURE_ACTION_START);
426 }
427 }
428}
429
430} // namespace Ekos
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
@ CAPTURE_WAITING
Definition ekos.h:100
@ 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_COMPLETE
Definition ekos.h:112
@ CAPTURE_CAPTURING
Definition ekos.h:95
@ CAPTURE_IDLE
Definition ekos.h:93
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
bool isEmpty() const const
bool contains(const Key &key) const const
QList< Key > keys() const const
size_type remove(const Key &key)
T dequeue()
T & head()
T * get() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isActive() const const
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.