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{
73 for (auto job : jobs())
74 if (job->isLead())
75 result.append(job);
76
77 return result;
78}
79
80QList<SchedulerJob *> SchedulerModuleState::followerJobs()
81{
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()
321{
322 return (++m_focusFailureCount <= MAX_FAILURE_ATTEMPTS);
323}
324
325bool SchedulerModuleState::increaseGuideFailureCount()
326{
327 return (++m_guideFailureCount <= MAX_FAILURE_ATTEMPTS);
328}
329
330bool SchedulerModuleState::increaseAlignFailureCount()
331{
332 return (++m_alignFailureCount <= MAX_FAILURE_ATTEMPTS);
333}
334
335void SchedulerModuleState::setIndiState(INDIState state)
336{
337 if (m_indiState != state)
338 {
339 qCDebug(KSTARS_EKOS_SCHEDULER) << "INDI state changed from" << m_indiState << "to" << state;
340 m_indiState = state;
341 emit indiStateChanged(state);
342 }
343}
344
345qint64 SchedulerModuleState::getCurrentOperationMsec() const
346{
347 if (!currentOperationTimeStarted) return 0;
348 return currentOperationTime.msecsTo(KStarsData::Instance()->ut());
349}
350
351void SchedulerModuleState::startCurrentOperationTimer()
352{
353 currentOperationTimeStarted = true;
354 currentOperationTime = KStarsData::Instance()->ut();
355}
356
357void SchedulerModuleState::cancelGuidingTimer()
358{
359 m_restartGuidingInterval = -1;
360 m_restartGuidingTime = KStarsDateTime();
361}
362
363bool SchedulerModuleState::isGuidingTimerActive()
364{
365 return (m_restartGuidingInterval > 0 &&
366 m_restartGuidingTime.msecsTo(KStarsData::Instance()->ut()) >= 0);
367}
368
369void SchedulerModuleState::startGuidingTimer(int milliseconds)
370{
371 m_restartGuidingInterval = milliseconds;
372 m_restartGuidingTime = KStarsData::Instance()->ut();
373}
374
375// Allows for unit testing of static Scheduler methods,
376// as can't call KStarsData::Instance() during unit testing.
377KStarsDateTime *SchedulerModuleState::storedLocalTime = nullptr;
378KStarsDateTime SchedulerModuleState::getLocalTime()
379{
380 if (hasLocalTime())
381 return *storedLocalTime;
382 return KStarsData::Instance()->geo()->UTtoLT(KStarsData::Instance()->clock()->utc());
383}
384
385void SchedulerModuleState::calculateDawnDusk(const QDateTime &when, QDateTime &nDawn, QDateTime &nDusk)
386{
387 QDateTime startup = when;
388
389 if (!startup.isValid())
390 startup = getLocalTime();
391
392 // Our local midnight - the KStarsDateTime date+time constructor is safe for local times
393 // Exact midnight seems unreliable--offset it by a minute.
394 KStarsDateTime midnight(startup.date(), QTime(0, 1), Qt::LocalTime);
395
396 QDateTime dawn = startup, dusk = startup;
397
398 // Loop dawn and dusk calculation until the events found are the next events
399 for ( ; dawn <= startup || dusk <= startup ; midnight = midnight.addDays(1))
400 {
401 // KSAlmanac computes the closest dawn and dusk events from the local sidereal time corresponding to the midnight argument
402
403#if 0
404 KSAlmanac const ksal(midnight, getGeo());
405 // If dawn is in the past compared to this observation, fetch the next dawn
406 if (dawn <= startup)
407 dawn = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
408 3600.0));
409 // If dusk is in the past compared to this observation, fetch the next dusk
410 if (dusk <= startup)
411 dusk = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
412 3600.0));
413#else
414 // Creating these almanac instances seems expensive.
415 static QMap<QString, KSAlmanac const * > almanacMap;
416 const QString key = QString("%1 %2 %3").arg(midnight.toString()).arg(getGeo()->lat()->Degrees()).arg(
417 getGeo()->lng()->Degrees());
418 KSAlmanac const * ksal = almanacMap.value(key, nullptr);
419 if (ksal == nullptr)
420 {
421 if (almanacMap.size() > 5)
422 {
423 // don't allow this to grow too large.
424 qDeleteAll(almanacMap);
425 almanacMap.clear();
426 }
427 ksal = new KSAlmanac(midnight, getGeo());
428 almanacMap[key] = ksal;
429 }
430
431 // If dawn is in the past compared to this observation, fetch the next dawn
432 if (dawn <= startup)
433 dawn = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
434 3600.0));
435
436 // If dusk is in the past compared to this observation, fetch the next dusk
437 if (dusk <= startup)
438 dusk = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
439 3600.0));
440#endif
441 }
442
443 // Now we have the next events:
444 // - if dawn comes first, observation runs during the night
445 // - if dusk comes first, observation runs during the day
446 nDawn = dawn;
447 nDusk = dusk;
448}
449
450void SchedulerModuleState::calculateDawnDusk()
451{
452 calculateDawnDusk(QDateTime(), m_Dawn, m_Dusk);
453
454 m_PreDawnDateTime = m_Dawn.addSecs(-60.0 * abs(Options::preDawnTime()));
455 emit updateNightTime();
456}
457
458const GeoLocation *SchedulerModuleState::getGeo()
459{
460 if (hasGeo())
461 return storedGeo;
462 return KStarsData::Instance()->geo();
463}
464
465bool SchedulerModuleState::hasGeo()
466{
467 return storedGeo != nullptr;
468}
469
470void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState)
471{
472 setupNextIteration(nextState, m_UpdatePeriodMs);
473}
474
475void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState, int milliseconds)
476{
477 if (iterationSetup())
478 {
479 qCDebug(KSTARS_EKOS_SCHEDULER)
480 << QString("Multiple setupNextIteration calls: current %1 %2, previous %3 %4")
481 .arg(nextState).arg(milliseconds).arg(timerState()).arg(timerInterval());
482 }
483 setTimerState(nextState);
484 // check if setup is called from a thread outside of the iteration timer thread
485 if (iterationTimer().isActive())
486 {
487 // restart the timer to ensure the correct startup delay
488 int remaining = iterationTimer().remainingTime();
489 iterationTimer().stop();
490 setTimerInterval(std::max(0, milliseconds - remaining));
491 iterationTimer().start(timerInterval());
492 }
493 else
494 {
495 // setup called from inside the iteration timer thread
496 setTimerInterval(milliseconds);
497 }
498 setIterationSetup(true);
499}
500
501uint SchedulerModuleState::maxFailureAttempts()
502{
503 return MAX_FAILURE_ATTEMPTS;
504}
505
506void SchedulerModuleState::clearLog()
507{
508 logText().clear();
509 emit newLog(QString());
510}
511
512bool SchedulerModuleState::checkRepeatSequence()
513{
514 return (!Options::rememberJobProgress() && Options::schedulerRepeatEverything() &&
515 (Options::schedulerExecutionSequencesLimit() == 0
516 || sequenceExecutionCounter()) < Options::schedulerExecutionSequencesLimit());
517}
518} // Ekos namespace
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
A class that implements methods to find sun rise, sun set, twilight begin / end times,...
Definition ksalmanac.h:27
const KStarsDateTime & ut() const
Definition kstarsdata.h:159
GeoLocation * geo()
Definition kstarsdata.h:232
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
The SchedulerState class holds all attributes defining the scheduler's state.
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
QDateTime addDays(qint64 ndays) const const
QDateTime addSecs(qint64 s) const const
QDate date() const const
bool isValid() const const
void append(const QJsonValue &value)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
qsizetype count() const const
T & first()
bool removeOne(const AT &t)
void clear()
size_type size() const const
T value(const Key &key, const T &defaultValue) const const
QString arg(Args &&... args) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
LocalTime
void start()
void stop()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:57:48 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.