Kstars

schedulermodulestate.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6#include "schedulermodulestate.h"
7#include "schedulerjob.h"
8#include <ekos_scheduler_debug.h>
9#include "schedulerprocess.h"
10#include "schedulerjob.h"
11#include "kstarsdata.h"
12#include "ksalmanac.h"
13#include "Options.h"
14
15#define MAX_FAILURE_ATTEMPTS 5
16
17namespace Ekos
18{
19// constants definition
20QDateTime SchedulerModuleState::m_Dawn, SchedulerModuleState::m_Dusk, SchedulerModuleState::m_PreDawnDateTime;
21GeoLocation *SchedulerModuleState::storedGeo = nullptr;
22
23SchedulerModuleState::SchedulerModuleState() {}
24
25void SchedulerModuleState::init()
26{
27 // This is needed to get wakeupScheduler() to call start() and startup,
28 // instead of assuming it is already initialized (if preemptiveShutdown was not set).
29 // The time itself is not used.
30 enablePreemptiveShutdown(SchedulerModuleState::getLocalTime());
31
32 setIterationSetup(false);
33 setupNextIteration(RUN_WAKEUP, 10);
34}
35
36void SchedulerModuleState::setCurrentProfile(const QString &newName, bool signal)
37{
38 bool changed = (newName != m_currentProfile);
39
40 if (m_profiles.contains(newName))
41 m_currentProfile = newName;
42 else
43 {
44 changed = (m_currentProfile != m_profiles.first());
45 m_currentProfile = m_profiles.first();
46 }
47 // update the UI
48 if (signal && changed)
49 emit currentProfileChanged();
50}
51
52void SchedulerModuleState::updateProfiles(const QStringList &newProfiles)
53{
54 QString selected = currentProfile();
55 // Default profile is always the first one
56 QStringList allProfiles(i18n("Default"));
57 allProfiles.append(newProfiles);
58
59 m_profiles = allProfiles;
60 // ensure that the selected profile still exists
61 setCurrentProfile(selected, false);
62 emit profilesChanged();
63}
64
65void SchedulerModuleState::setActiveJob(SchedulerJob *newActiveJob)
66{
67 m_activeJob = newActiveJob;
68}
69
70QList<SchedulerJob *> SchedulerModuleState::leadJobs()
71{
72 QList<SchedulerJob *> result;
73 for (auto job : jobs())
74 if (job->isLead())
75 result.append(job);
76
77 return result;
78}
79
80QList<SchedulerJob *> SchedulerModuleState::followerJobs()
81{
82 QList<SchedulerJob *> result;
83 for (auto job : jobs())
84 if (!job->isLead())
85 result.append(job);
86
87 return result;
88}
89
90void SchedulerModuleState::updateJobStage(SchedulerJobStage stage)
91{
92 if (activeJob() == nullptr)
93 {
94 emit jobStageChanged(SCHEDSTAGE_IDLE);
95 }
96 else
97 {
98 activeJob()->setStage(stage);
99 emit jobStageChanged(stage);
100 }
101}
102
103QJsonArray SchedulerModuleState::getJSONJobs()
104{
105 QJsonArray jobArray;
106
107 for (const auto &oneJob : jobs())
108 jobArray.append(oneJob->toJson());
109
110 return jobArray;
111}
112
113void SchedulerModuleState::setSchedulerState(const SchedulerState &newState)
114{
115 m_schedulerState = newState;
116 emit schedulerStateChanged(newState);
117}
118
119void SchedulerModuleState::setCurrentPosition(int newCurrentPosition)
120{
121 m_currentPosition = newCurrentPosition;
122 emit currentPositionChanged(newCurrentPosition);
123}
124
125void SchedulerModuleState::setStartupState(StartupState state)
126{
127 if (m_startupState != state)
128 {
129 m_startupState = state;
130 emit startupStateChanged(state);
131 }
132}
133
134void SchedulerModuleState::setShutdownState(ShutdownState state)
135{
136 if (m_shutdownState != state)
137 {
138 m_shutdownState = state;
139 emit shutdownStateChanged(state);
140 }
141}
142
143void SchedulerModuleState::setParkWaitState(ParkWaitState state)
144{
145 if (m_parkWaitState != state)
146 {
147 m_parkWaitState = state;
148 emit parkWaitStateChanged(state);
149 }
150}
151
152bool SchedulerModuleState::removeJob(const int currentRow)
153{
154 /* Don't remove a row that is not selected */
155 if (currentRow < 0)
156 return false;
157
158 /* Grab the job currently selected */
159 SchedulerJob * const job = jobs().at(currentRow);
160
161 // Can't delete the currently running job
162 if (job == m_activeJob)
163 {
164 emit newLog(i18n("Cannot delete currently running job '%1'.", job->getName()));
165 return false;
166 }
167 else if (job == nullptr || (activeJob() == nullptr && schedulerState() != SCHEDULER_IDLE))
168 {
169 // Don't allow delete--worried that we're about to schedule job that's being deleted.
170 emit newLog(i18n("Cannot delete job. Scheduler state: %1",
171 getSchedulerStatusString(schedulerState(), true)));
172 return false;
173 }
174
175 qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' at row #%2 is being deleted.").arg(job->getName()).arg(currentRow + 1);
176
177 // if it was a lead job, re-arrange its follower (if necessary)
178 if (job->isLead() && job->followerJobs().count() > 0)
179 {
180 // search the new lead
181 SchedulerJob *newLead = findLead(currentRow - 1);
182 // if it does not exist, do not allow job deletion
183 if (newLead == nullptr)
184 {
185 return false;
186 }
187 else
188 {
189 // set the new lead and add the follower jobs to the new lead
190 for (auto follower : job->followerJobs())
191 {
192 follower->setLeadJob(newLead);
193 newLead->followerJobs().append(follower);
194 }
195 }
196 }
197 /* Remove the job object */
198 mutlableJobs().removeOne(job);
199
200 delete (job);
201
202 // Reduce the current position if the last element has been deleted
203 if (currentPosition() >= jobs().count())
204 setCurrentPosition(jobs().count() - 1);
205
206 setDirty(true);
207 // success
208 return true;
209}
210
211void SchedulerModuleState::refreshFollowerLists()
212{
213 // clear the follower lists
214 for (auto job : m_jobs)
215 job->followerJobs().clear();
216
217 // iterate over all jobs and append the follower to its lead
218 for (auto job : m_jobs)
219 {
220 SchedulerJob *lead = job->leadJob();
221 if (job->isLead() == false && lead != nullptr)
222 {
223 lead->followerJobs().append(job);
224 lead->updateSharedFollowerAttributes();
225 }
226 }
227}
228
229SchedulerJob *SchedulerModuleState::findLead(int position, bool upward)
230{
231 auto start = std::min(position, static_cast<int>(jobs().count()));
232
233 if (upward)
234 {
235 for (int i = start; i >= 0; i--)
236 if (jobs().at(i)->isLead())
237 return jobs().at(i);
238 }
239 else
240 {
241 for (int i = start; i < jobs().count(); i++)
242 if (jobs().at(i)->isLead())
243 return jobs().at(i);
244 }
245
246 // nothing found
247 return nullptr;
248}
249
250void SchedulerModuleState::enablePreemptiveShutdown(const QDateTime &wakeupTime)
251{
252 m_preemptiveShutdownWakeupTime = wakeupTime;
253}
254
255void SchedulerModuleState::disablePreemptiveShutdown()
256{
257 m_preemptiveShutdownWakeupTime = QDateTime();
258}
259
260const QDateTime &SchedulerModuleState::preemptiveShutdownWakeupTime() const
261{
262 return m_preemptiveShutdownWakeupTime;
263}
264
265bool SchedulerModuleState::preemptiveShutdown() const
266{
267 return m_preemptiveShutdownWakeupTime.isValid();
268}
269
270void SchedulerModuleState::setEkosState(EkosState state)
271{
272 if (m_ekosState != state)
273 {
274 qCDebug(KSTARS_EKOS_SCHEDULER) << "EKOS state changed from" << m_ekosState << "to" << state;
275 m_ekosState = state;
276 emit ekosStateChanged(state);
277 }
278}
279
280bool SchedulerModuleState::increaseEkosConnectFailureCount()
281{
282 return (++m_ekosConnectFailureCount <= MAX_FAILURE_ATTEMPTS);
283}
284
285bool SchedulerModuleState::increaseParkingCapFailureCount()
286{
287 return (++m_parkingCapFailureCount <= MAX_FAILURE_ATTEMPTS);
288}
289
290bool SchedulerModuleState::increaseParkingMountFailureCount()
291{
292 return (++m_parkingMountFailureCount <= MAX_FAILURE_ATTEMPTS);
293}
294
295bool SchedulerModuleState::increaseParkingDomeFailureCount()
296{
297 return (++m_parkingDomeFailureCount <= MAX_FAILURE_ATTEMPTS);
298}
299
300void SchedulerModuleState::resetFailureCounters()
301{
302 resetIndiConnectFailureCount();
303 resetEkosConnectFailureCount();
304 resetFocusFailureCount();
305 resetGuideFailureCount();
306 resetAlignFailureCount();
307 resetCaptureFailureCount();
308}
309
310bool SchedulerModuleState::increaseIndiConnectFailureCount()
311{
312 return (++m_indiConnectFailureCount <= MAX_FAILURE_ATTEMPTS);
313}
314
315bool SchedulerModuleState::increaseCaptureFailureCount()
316{
317 return (++m_captureFailureCount <= MAX_FAILURE_ATTEMPTS);
318}
319
320bool SchedulerModuleState::increaseFocusFailureCount(const QString &trainname)
321{
322 return (++m_focusFailureCount[trainname] <= MAX_FAILURE_ATTEMPTS);
323}
324
325bool SchedulerModuleState::increaseAllFocusFailureCounts()
326{
327 bool result = true;
328
329 // if one of the counters is beyond the threshold, we return false.
330 for (QMap<QString, bool>::const_iterator it = m_autofocusCompleted.cbegin(); it != m_autofocusCompleted.cend(); it++)
331 result &= increaseFocusFailureCount(it.key());
332
333 return result;
334}
335
336bool SchedulerModuleState::autofocusCompleted(const QString &trainname) const
337{
338 if (!trainname.isEmpty())
339 return m_autofocusCompleted[trainname];
340 else
341 return autofocusCompleted();
342}
343
344void SchedulerModuleState::setAutofocusCompleted(const QString &trainname, bool value)
345{
346 if (!trainname.isEmpty())
347 m_autofocusCompleted[trainname] = value;
348 else
349 m_autofocusCompleted.clear();
350}
351
352bool SchedulerModuleState::autofocusCompleted() const
353{
354 if (m_autofocusCompleted.isEmpty())
355 return false;
356
357 for (QMap<QString, bool>::const_iterator it = m_autofocusCompleted.cbegin(); it != m_autofocusCompleted.cend(); it++)
358 {
359 if (it.value() == false)
360 return false;
361 }
362 // all are completed
363 return true;
364}
365
366bool SchedulerModuleState::increaseGuideFailureCount()
367{
368 return (++m_guideFailureCount <= MAX_FAILURE_ATTEMPTS);
369}
370
371bool SchedulerModuleState::increaseAlignFailureCount()
372{
373 return (++m_alignFailureCount <= MAX_FAILURE_ATTEMPTS);
374}
375
376void SchedulerModuleState::setIndiState(INDIState state)
377{
378 if (m_indiState != state)
379 {
380 qCDebug(KSTARS_EKOS_SCHEDULER) << "INDI state changed from" << m_indiState << "to" << state;
381 m_indiState = state;
382 emit indiStateChanged(state);
383 }
384}
385
386qint64 SchedulerModuleState::getCurrentOperationMsec() const
387{
388 if (!currentOperationTimeStarted) return 0;
389 return currentOperationTime.msecsTo(KStarsData::Instance()->ut());
390}
391
392void SchedulerModuleState::startCurrentOperationTimer()
393{
394 currentOperationTimeStarted = true;
395 currentOperationTime = KStarsData::Instance()->ut();
396}
397
398void SchedulerModuleState::cancelGuidingTimer()
399{
400 m_restartGuidingInterval = -1;
401 m_restartGuidingTime = KStarsDateTime();
402}
403
404bool SchedulerModuleState::isGuidingTimerActive()
405{
406 return (m_restartGuidingInterval > 0 &&
407 m_restartGuidingTime.msecsTo(KStarsData::Instance()->ut()) >= 0);
408}
409
410void SchedulerModuleState::startGuidingTimer(int milliseconds)
411{
412 m_restartGuidingInterval = milliseconds;
413 m_restartGuidingTime = KStarsData::Instance()->ut();
414}
415
416// Allows for unit testing of static Scheduler methods,
417// as can't call KStarsData::Instance() during unit testing.
418KStarsDateTime *SchedulerModuleState::storedLocalTime = nullptr;
419KStarsDateTime SchedulerModuleState::getLocalTime()
420{
421 if (hasLocalTime())
422 return *storedLocalTime;
423 return KStarsData::Instance()->geo()->UTtoLT(KStarsData::Instance()->clock()->utc());
424}
425
426void SchedulerModuleState::calculateDawnDusk(const QDateTime &when, QDateTime &nDawn, QDateTime &nDusk)
427{
428 QDateTime startup = when;
429
430 if (!startup.isValid())
431 startup = getLocalTime();
432
433 // Our local midnight - the KStarsDateTime date+time constructor is safe for local times
434 // Exact midnight seems unreliable--offset it by a minute.
435 KStarsDateTime midnight(startup.date(), QTime(0, 1), Qt::LocalTime);
436
437 QDateTime dawn = startup, dusk = startup;
438
439 // Loop dawn and dusk calculation until the events found are the next events
440 for ( ; dawn <= startup || dusk <= startup ; midnight = midnight.addDays(1))
441 {
442 // KSAlmanac computes the closest dawn and dusk events from the local sidereal time corresponding to the midnight argument
443
444#if 0
445 KSAlmanac const ksal(midnight, getGeo());
446 // If dawn is in the past compared to this observation, fetch the next dawn
447 if (dawn <= startup)
448 dawn = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
449 3600.0));
450 // If dusk is in the past compared to this observation, fetch the next dusk
451 if (dusk <= startup)
452 dusk = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
453 3600.0));
454#else
455 // Creating these almanac instances seems expensive.
456 static QMap<QString, KSAlmanac const * > almanacMap;
457 const QString key = QString("%1 %2 %3").arg(midnight.toString()).arg(getGeo()->lat()->Degrees()).arg(
458 getGeo()->lng()->Degrees());
459 KSAlmanac const * ksal = almanacMap.value(key, nullptr);
460 if (ksal == nullptr)
461 {
462 if (almanacMap.size() > 5)
463 {
464 // don't allow this to grow too large.
465 qDeleteAll(almanacMap);
466 almanacMap.clear();
467 }
468 ksal = new KSAlmanac(midnight, getGeo());
469 almanacMap[key] = ksal;
470 }
471
472 // If dawn is in the past compared to this observation, fetch the next dawn
473 if (dawn <= startup)
474 dawn = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
475 3600.0));
476
477 // If dusk is in the past compared to this observation, fetch the next dusk
478 if (dusk <= startup)
479 dusk = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
480 3600.0));
481#endif
482 }
483
484 // Now we have the next events:
485 // - if dawn comes first, observation runs during the night
486 // - if dusk comes first, observation runs during the day
487 nDawn = dawn;
488 nDusk = dusk;
489}
490
491void SchedulerModuleState::calculateDawnDusk()
492{
493 calculateDawnDusk(QDateTime(), m_Dawn, m_Dusk);
494
495 m_PreDawnDateTime = m_Dawn.addSecs(-60.0 * abs(Options::preDawnTime()));
496 emit updateNightTime();
497}
498
499const GeoLocation *SchedulerModuleState::getGeo()
500{
501 if (hasGeo())
502 return storedGeo;
503 return KStarsData::Instance()->geo();
504}
505
506bool SchedulerModuleState::hasGeo()
507{
508 return storedGeo != nullptr;
509}
510
511void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState)
512{
513 setupNextIteration(nextState, m_UpdatePeriodMs);
514}
515
516void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState, int milliseconds)
517{
518 if (iterationSetup())
519 {
520 qCDebug(KSTARS_EKOS_SCHEDULER)
521 << QString("Multiple setupNextIteration calls: current %1 %2, previous %3 %4")
522 .arg(nextState).arg(milliseconds).arg(timerState()).arg(timerInterval());
523 }
524 setTimerState(nextState);
525 // check if setup is called from a thread outside of the iteration timer thread
526 if (iterationTimer().isActive())
527 {
528 // restart the timer to ensure the correct startup delay
529 int remaining = iterationTimer().remainingTime();
530 iterationTimer().stop();
531 setTimerInterval(std::max(0, milliseconds - remaining));
532 iterationTimer().start(timerInterval());
533 }
534 else
535 {
536 // setup called from inside the iteration timer thread
537 setTimerInterval(milliseconds);
538 }
539 setIterationSetup(true);
540}
541
542uint SchedulerModuleState::maxFailureAttempts()
543{
544 return MAX_FAILURE_ATTEMPTS;
545}
546
547void SchedulerModuleState::clearLog()
548{
549 logText().clear();
550 emit newLog(QString());
551}
552
553bool SchedulerModuleState::checkRepeatSequence()
554{
555 return (!Options::rememberJobProgress() && Options::schedulerRepeatEverything() &&
556 (Options::schedulerExecutionSequencesLimit() == 0
557 || sequenceExecutionCounter()) < Options::schedulerExecutionSequencesLimit());
558}
559} // Ekos namespace
const KStarsDateTime & ut() const
Definition kstarsdata.h:159
GeoLocation * geo()
Definition kstarsdata.h:238
KStarsDateTime addSecs(double s) const
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
SchedulerJobStage
Running stages of a SchedulerJob.
SchedulerTimerState
IterationTypes, the different types of scheduler iterations that are run.
QDateTime addDays(qint64 ndays) const const
QDate date() const const
bool isValid() const const
void append(const QJsonValue &value)
void append(QList< T > &&value)
void clear()
size_type size() const const
T value(const Key &key, const T &defaultValue) const const
bool isEmpty() const const
LocalTime
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 7 2025 11:55:44 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.