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"));
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
70void SchedulerModuleState::updateJobStage(SchedulerJobStage stage)
71{
72 if (activeJob() == nullptr)
73 {
74 emit jobStageChanged(SCHEDSTAGE_IDLE);
75 }
76 else
77 {
78 activeJob()->setStage(stage);
79 emit jobStageChanged(stage);
80 }
81}
82
83QJsonArray SchedulerModuleState::getJSONJobs()
84{
86
87 for (const auto &oneJob : jobs())
88 jobArray.append(oneJob->toJson());
89
90 return jobArray;
91}
92
93void SchedulerModuleState::setSchedulerState(const SchedulerState &newState)
94{
95 m_schedulerState = newState;
96 emit schedulerStateChanged(newState);
97}
98
99void SchedulerModuleState::setCurrentPosition(int newCurrentPosition)
100{
101 m_currentPosition = newCurrentPosition;
102 emit currentPositionChanged(newCurrentPosition);
103}
104
105void SchedulerModuleState::setStartupState(StartupState state)
106{
107 if (m_startupState != state)
108 {
109 m_startupState = state;
110 emit startupStateChanged(state);
111 }
112}
113
114void SchedulerModuleState::setShutdownState(ShutdownState state)
115{
116 if (m_shutdownState != state)
117 {
118 m_shutdownState = state;
119 emit shutdownStateChanged(state);
120 }
121}
122
123void SchedulerModuleState::setParkWaitState(ParkWaitState state)
124{
125 if (m_parkWaitState != state)
126 {
127 m_parkWaitState = state;
128 emit parkWaitStateChanged(state);
129 }
130}
131
132bool SchedulerModuleState::removeJob(const int currentRow)
133{
134 /* Don't remove a row that is not selected */
135 if (currentRow < 0)
136 return false;
137
138 /* Grab the job currently selected */
139 SchedulerJob * const job = jobs().at(currentRow);
140
141 // Can't delete the currently running job
142 if (job == m_activeJob)
143 {
144 emit newLog(i18n("Cannot delete currently running job '%1'.", job->getName()));
145 return false;
146 }
147 else if (job == nullptr || (activeJob() == nullptr && schedulerState() != SCHEDULER_IDLE))
148 {
149 // Don't allow delete--worried that we're about to schedule job that's being deleted.
150 emit newLog(i18n("Cannot delete job. Scheduler state: %1",
151 getSchedulerStatusString(schedulerState(), true)));
152 return false;
153 }
154
155 qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' at row #%2 is being deleted.").arg(job->getName()).arg(currentRow + 1);
156
157 /* Remove the job object */
158 mutlableJobs().removeOne(job);
159 delete (job);
160
161 // Reduce the current position if the last element has been deleted
162 if (currentPosition() >= jobs().count())
163 setCurrentPosition(jobs().count() - 1);
164
165 setDirty(true);
166 // success
167 return true;
168}
169
170void SchedulerModuleState::enablePreemptiveShutdown(const QDateTime &wakeupTime)
171{
172 m_preemptiveShutdownWakeupTime = wakeupTime;
173}
174
175void SchedulerModuleState::disablePreemptiveShutdown()
176{
177 m_preemptiveShutdownWakeupTime = QDateTime();
178}
179
180const QDateTime &SchedulerModuleState::preemptiveShutdownWakeupTime() const
181{
182 return m_preemptiveShutdownWakeupTime;
183}
184
185bool SchedulerModuleState::preemptiveShutdown() const
186{
187 return m_preemptiveShutdownWakeupTime.isValid();
188}
189
190void SchedulerModuleState::setEkosState(EkosState state)
191{
192 if (m_ekosState != state)
193 {
194 qCDebug(KSTARS_EKOS_SCHEDULER) << "EKOS state changed from" << m_ekosState << "to" << state;
195 m_ekosState = state;
196 emit ekosStateChanged(state);
197 }
198}
199
200bool SchedulerModuleState::increaseEkosConnectFailureCount()
201{
202 return (++m_ekosConnectFailureCount <= MAX_FAILURE_ATTEMPTS);
203}
204
205bool SchedulerModuleState::increaseParkingCapFailureCount()
206{
207 return (++m_parkingCapFailureCount <= MAX_FAILURE_ATTEMPTS);
208}
209
210bool SchedulerModuleState::increaseParkingMountFailureCount()
211{
212 return (++m_parkingMountFailureCount <= MAX_FAILURE_ATTEMPTS);
213}
214
215bool SchedulerModuleState::increaseParkingDomeFailureCount()
216{
217 return (++m_parkingDomeFailureCount <= MAX_FAILURE_ATTEMPTS);
218}
219
220void SchedulerModuleState::resetFailureCounters()
221{
222 resetIndiConnectFailureCount();
223 resetEkosConnectFailureCount();
224 resetFocusFailureCount();
225 resetGuideFailureCount();
226 resetAlignFailureCount();
227 resetCaptureFailureCount();
228}
229
230bool SchedulerModuleState::increaseIndiConnectFailureCount()
231{
232 return (++m_indiConnectFailureCount <= MAX_FAILURE_ATTEMPTS);
233}
234
235bool SchedulerModuleState::increaseCaptureFailureCount()
236{
237 return (++m_captureFailureCount <= MAX_FAILURE_ATTEMPTS);
238}
239
240bool SchedulerModuleState::increaseFocusFailureCount()
241{
242 return (++m_focusFailureCount <= MAX_FAILURE_ATTEMPTS);
243}
244
245bool SchedulerModuleState::increaseGuideFailureCount()
246{
247 return (++m_guideFailureCount <= MAX_FAILURE_ATTEMPTS);
248}
249
250bool SchedulerModuleState::increaseAlignFailureCount()
251{
252 return (++m_alignFailureCount <= MAX_FAILURE_ATTEMPTS);
253}
254
255void SchedulerModuleState::setIndiState(INDIState state)
256{
257 if (m_indiState != state)
258 {
259 qCDebug(KSTARS_EKOS_SCHEDULER) << "INDI state changed from" << m_indiState << "to" << state;
260 m_indiState = state;
261 emit indiStateChanged(state);
262 }
263}
264
265qint64 SchedulerModuleState::getCurrentOperationMsec() const
266{
267 if (!currentOperationTimeStarted) return 0;
268 return currentOperationTime.msecsTo(KStarsData::Instance()->ut());
269}
270
271void SchedulerModuleState::startCurrentOperationTimer()
272{
273 currentOperationTimeStarted = true;
274 currentOperationTime = KStarsData::Instance()->ut();
275}
276
277void SchedulerModuleState::cancelGuidingTimer()
278{
279 m_restartGuidingInterval = -1;
280 m_restartGuidingTime = KStarsDateTime();
281}
282
283bool SchedulerModuleState::isGuidingTimerActive()
284{
285 return (m_restartGuidingInterval > 0 &&
286 m_restartGuidingTime.msecsTo(KStarsData::Instance()->ut()) >= 0);
287}
288
289void SchedulerModuleState::startGuidingTimer(int milliseconds)
290{
291 m_restartGuidingInterval = milliseconds;
292 m_restartGuidingTime = KStarsData::Instance()->ut();
293}
294
295// Allows for unit testing of static Scheduler methods,
296// as can't call KStarsData::Instance() during unit testing.
297KStarsDateTime *SchedulerModuleState::storedLocalTime = nullptr;
298KStarsDateTime SchedulerModuleState::getLocalTime()
299{
300 if (hasLocalTime())
301 return *storedLocalTime;
302 return KStarsData::Instance()->geo()->UTtoLT(KStarsData::Instance()->clock()->utc());
303}
304
305void SchedulerModuleState::calculateDawnDusk(const QDateTime &when, QDateTime &nDawn, QDateTime &nDusk)
306{
307 QDateTime startup = when;
308
309 if (!startup.isValid())
310 startup = getLocalTime();
311
312 // Our local midnight - the KStarsDateTime date+time constructor is safe for local times
313 // Exact midnight seems unreliable--offset it by a minute.
315
316 QDateTime dawn = startup, dusk = startup;
317
318 // Loop dawn and dusk calculation until the events found are the next events
319 for ( ; dawn <= startup || dusk <= startup ; midnight = midnight.addDays(1))
320 {
321 // KSAlmanac computes the closest dawn and dusk events from the local sidereal time corresponding to the midnight argument
322
323#if 0
324 KSAlmanac const ksal(midnight, getGeo());
325 // If dawn is in the past compared to this observation, fetch the next dawn
326 if (dawn <= startup)
327 dawn = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
328 3600.0));
329 // If dusk is in the past compared to this observation, fetch the next dusk
330 if (dusk <= startup)
331 dusk = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
332 3600.0));
333#else
334 // Creating these almanac instances seems expensive.
336 const QString key = QString("%1 %2 %3").arg(midnight.toString()).arg(getGeo()->lat()->Degrees()).arg(
337 getGeo()->lng()->Degrees());
338 KSAlmanac const * ksal = almanacMap.value(key, nullptr);
339 if (ksal == nullptr)
340 {
341 if (almanacMap.size() > 5)
342 {
343 // don't allow this to grow too large.
345 almanacMap.clear();
346 }
347 ksal = new KSAlmanac(midnight, getGeo());
348 almanacMap[key] = ksal;
349 }
350
351 // If dawn is in the past compared to this observation, fetch the next dawn
352 if (dawn <= startup)
353 dawn = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) *
354 3600.0));
355
356 // If dusk is in the past compared to this observation, fetch the next dusk
357 if (dusk <= startup)
358 dusk = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) *
359 3600.0));
360#endif
361 }
362
363 // Now we have the next events:
364 // - if dawn comes first, observation runs during the night
365 // - if dusk comes first, observation runs during the day
366 nDawn = dawn;
367 nDusk = dusk;
368}
369
370void SchedulerModuleState::calculateDawnDusk()
371{
372 calculateDawnDusk(QDateTime(), m_Dawn, m_Dusk);
373
374 m_PreDawnDateTime = m_Dawn.addSecs(-60.0 * abs(Options::preDawnTime()));
375 emit updateNightTime();
376}
377
378const GeoLocation *SchedulerModuleState::getGeo()
379{
380 if (hasGeo())
381 return storedGeo;
382 return KStarsData::Instance()->geo();
383}
384
385bool SchedulerModuleState::hasGeo()
386{
387 return storedGeo != nullptr;
388}
389
390void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState)
391{
392 setupNextIteration(nextState, m_UpdatePeriodMs);
393}
394
395void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState, int milliseconds)
396{
397 if (iterationSetup())
398 {
399 qCDebug(KSTARS_EKOS_SCHEDULER)
400 << QString("Multiple setupNextIteration calls: current %1 %2, previous %3 %4")
401 .arg(nextState).arg(milliseconds).arg(timerState()).arg(timerInterval());
402 }
403 setTimerState(nextState);
404 // check if setup is called from a thread outside of the iteration timer thread
405 if (iterationTimer().isActive())
406 {
407 // restart the timer to ensure the correct startup delay
408 int remaining = iterationTimer().remainingTime();
409 iterationTimer().stop();
410 setTimerInterval(std::max(0, milliseconds - remaining));
411 iterationTimer().start(timerInterval());
412 }
413 else
414 {
415 // setup called from inside the iteration timer thread
416 setTimerInterval(milliseconds);
417 }
418 setIterationSetup(true);
419}
420
421uint SchedulerModuleState::maxFailureAttempts()
422{
423 return MAX_FAILURE_ATTEMPTS;
424}
425
426void SchedulerModuleState::clearLog()
427{
428 logText().clear();
429 emit newLog(QString());
430}
431
432bool SchedulerModuleState::checkRepeatSequence()
433{
434 return (!Options::rememberJobProgress() && Options::schedulerRepeatEverything() &&
435 (Options::schedulerExecutionSequencesLimit() == 0
436 || sequenceExecutionCounter()) < Options::schedulerExecutionSequencesLimit());
437}
438} // 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
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
KStarsDateTime addSecs(double s) const
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:79
QDate date() const const
bool isValid() const const
qint64 msecsTo(const QDateTime &other) const const
void clear()
T & first()
bool removeOne(const AT &t)
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 May 3 2024 11:49:51 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.