File indexing completed on 2024-05-05 07:42:12
0001 /* 0002 SPDX-FileCopyrightText: 2023 Wolfgang Reissenberger <sterne-jaeger@openfuture.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "schedulermodulestate.h" 0007 #include "schedulerjob.h" 0008 #include <ekos_scheduler_debug.h> 0009 #include "schedulerprocess.h" 0010 #include "schedulerjob.h" 0011 #include "kstarsdata.h" 0012 #include "ksalmanac.h" 0013 #include "Options.h" 0014 0015 #define MAX_FAILURE_ATTEMPTS 5 0016 0017 namespace Ekos 0018 { 0019 // constants definition 0020 QDateTime SchedulerModuleState::m_Dawn, SchedulerModuleState::m_Dusk, SchedulerModuleState::m_PreDawnDateTime; 0021 GeoLocation *SchedulerModuleState::storedGeo = nullptr; 0022 0023 SchedulerModuleState::SchedulerModuleState() {} 0024 0025 void SchedulerModuleState::init() 0026 { 0027 // This is needed to get wakeupScheduler() to call start() and startup, 0028 // instead of assuming it is already initialized (if preemptiveShutdown was not set). 0029 // The time itself is not used. 0030 enablePreemptiveShutdown(SchedulerModuleState::getLocalTime()); 0031 0032 setIterationSetup(false); 0033 setupNextIteration(RUN_WAKEUP, 10); 0034 } 0035 0036 void SchedulerModuleState::setCurrentProfile(const QString &newName, bool signal) 0037 { 0038 bool changed = (newName != m_currentProfile); 0039 0040 if (m_profiles.contains(newName)) 0041 m_currentProfile = newName; 0042 else 0043 { 0044 changed = (m_currentProfile != m_profiles.first()); 0045 m_currentProfile = m_profiles.first(); 0046 } 0047 // update the UI 0048 if (signal && changed) 0049 emit currentProfileChanged(); 0050 } 0051 0052 void SchedulerModuleState::updateProfiles(const QStringList &newProfiles) 0053 { 0054 QString selected = currentProfile(); 0055 // Default profile is always the first one 0056 QStringList allProfiles(i18n("Default")); 0057 allProfiles.append(newProfiles); 0058 0059 m_profiles = allProfiles; 0060 // ensure that the selected profile still exists 0061 setCurrentProfile(selected, false); 0062 emit profilesChanged(); 0063 } 0064 0065 void SchedulerModuleState::setActiveJob(SchedulerJob *newActiveJob) 0066 { 0067 m_activeJob = newActiveJob; 0068 } 0069 0070 void SchedulerModuleState::updateJobStage(SchedulerJobStage stage) 0071 { 0072 if (activeJob() == nullptr) 0073 { 0074 emit jobStageChanged(SCHEDSTAGE_IDLE); 0075 } 0076 else 0077 { 0078 activeJob()->setStage(stage); 0079 emit jobStageChanged(stage); 0080 } 0081 } 0082 0083 QJsonArray SchedulerModuleState::getJSONJobs() 0084 { 0085 QJsonArray jobArray; 0086 0087 for (const auto &oneJob : jobs()) 0088 jobArray.append(oneJob->toJson()); 0089 0090 return jobArray; 0091 } 0092 0093 void SchedulerModuleState::setSchedulerState(const SchedulerState &newState) 0094 { 0095 m_schedulerState = newState; 0096 emit schedulerStateChanged(newState); 0097 } 0098 0099 void SchedulerModuleState::setCurrentPosition(int newCurrentPosition) 0100 { 0101 m_currentPosition = newCurrentPosition; 0102 emit currentPositionChanged(newCurrentPosition); 0103 } 0104 0105 void SchedulerModuleState::setStartupState(StartupState state) 0106 { 0107 if (m_startupState != state) 0108 { 0109 m_startupState = state; 0110 emit startupStateChanged(state); 0111 } 0112 } 0113 0114 void SchedulerModuleState::setShutdownState(ShutdownState state) 0115 { 0116 if (m_shutdownState != state) 0117 { 0118 m_shutdownState = state; 0119 emit shutdownStateChanged(state); 0120 } 0121 } 0122 0123 void SchedulerModuleState::setParkWaitState(ParkWaitState state) 0124 { 0125 if (m_parkWaitState != state) 0126 { 0127 m_parkWaitState = state; 0128 emit parkWaitStateChanged(state); 0129 } 0130 } 0131 0132 bool SchedulerModuleState::removeJob(const int currentRow) 0133 { 0134 /* Don't remove a row that is not selected */ 0135 if (currentRow < 0) 0136 return false; 0137 0138 /* Grab the job currently selected */ 0139 SchedulerJob * const job = jobs().at(currentRow); 0140 0141 // Can't delete the currently running job 0142 if (job == m_activeJob) 0143 { 0144 emit newLog(i18n("Cannot delete currently running job '%1'.", job->getName())); 0145 return false; 0146 } 0147 else if (job == nullptr || (activeJob() == nullptr && schedulerState() != SCHEDULER_IDLE)) 0148 { 0149 // Don't allow delete--worried that we're about to schedule job that's being deleted. 0150 emit newLog(i18n("Cannot delete job. Scheduler state: %1", 0151 getSchedulerStatusString(schedulerState(), true))); 0152 return false; 0153 } 0154 0155 qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' at row #%2 is being deleted.").arg(job->getName()).arg(currentRow + 1); 0156 0157 /* Remove the job object */ 0158 mutlableJobs().removeOne(job); 0159 delete (job); 0160 0161 // Reduce the current position if the last element has been deleted 0162 if (currentPosition() >= jobs().count()) 0163 setCurrentPosition(jobs().count() - 1); 0164 0165 setDirty(true); 0166 // success 0167 return true; 0168 } 0169 0170 void SchedulerModuleState::enablePreemptiveShutdown(const QDateTime &wakeupTime) 0171 { 0172 m_preemptiveShutdownWakeupTime = wakeupTime; 0173 } 0174 0175 void SchedulerModuleState::disablePreemptiveShutdown() 0176 { 0177 m_preemptiveShutdownWakeupTime = QDateTime(); 0178 } 0179 0180 const QDateTime &SchedulerModuleState::preemptiveShutdownWakeupTime() const 0181 { 0182 return m_preemptiveShutdownWakeupTime; 0183 } 0184 0185 bool SchedulerModuleState::preemptiveShutdown() const 0186 { 0187 return m_preemptiveShutdownWakeupTime.isValid(); 0188 } 0189 0190 void SchedulerModuleState::setEkosState(EkosState state) 0191 { 0192 if (m_ekosState != state) 0193 { 0194 qCDebug(KSTARS_EKOS_SCHEDULER) << "EKOS state changed from" << m_ekosState << "to" << state; 0195 m_ekosState = state; 0196 emit ekosStateChanged(state); 0197 } 0198 } 0199 0200 bool SchedulerModuleState::increaseEkosConnectFailureCount() 0201 { 0202 return (++m_ekosConnectFailureCount <= MAX_FAILURE_ATTEMPTS); 0203 } 0204 0205 bool SchedulerModuleState::increaseParkingCapFailureCount() 0206 { 0207 return (++m_parkingCapFailureCount <= MAX_FAILURE_ATTEMPTS); 0208 } 0209 0210 bool SchedulerModuleState::increaseParkingMountFailureCount() 0211 { 0212 return (++m_parkingMountFailureCount <= MAX_FAILURE_ATTEMPTS); 0213 } 0214 0215 bool SchedulerModuleState::increaseParkingDomeFailureCount() 0216 { 0217 return (++m_parkingDomeFailureCount <= MAX_FAILURE_ATTEMPTS); 0218 } 0219 0220 void SchedulerModuleState::resetFailureCounters() 0221 { 0222 resetIndiConnectFailureCount(); 0223 resetEkosConnectFailureCount(); 0224 resetFocusFailureCount(); 0225 resetGuideFailureCount(); 0226 resetAlignFailureCount(); 0227 resetCaptureFailureCount(); 0228 } 0229 0230 bool SchedulerModuleState::increaseIndiConnectFailureCount() 0231 { 0232 return (++m_indiConnectFailureCount <= MAX_FAILURE_ATTEMPTS); 0233 } 0234 0235 bool SchedulerModuleState::increaseCaptureFailureCount() 0236 { 0237 return (++m_captureFailureCount <= MAX_FAILURE_ATTEMPTS); 0238 } 0239 0240 bool SchedulerModuleState::increaseFocusFailureCount() 0241 { 0242 return (++m_focusFailureCount <= MAX_FAILURE_ATTEMPTS); 0243 } 0244 0245 bool SchedulerModuleState::increaseGuideFailureCount() 0246 { 0247 return (++m_guideFailureCount <= MAX_FAILURE_ATTEMPTS); 0248 } 0249 0250 bool SchedulerModuleState::increaseAlignFailureCount() 0251 { 0252 return (++m_alignFailureCount <= MAX_FAILURE_ATTEMPTS); 0253 } 0254 0255 void SchedulerModuleState::setIndiState(INDIState state) 0256 { 0257 if (m_indiState != state) 0258 { 0259 qCDebug(KSTARS_EKOS_SCHEDULER) << "INDI state changed from" << m_indiState << "to" << state; 0260 m_indiState = state; 0261 emit indiStateChanged(state); 0262 } 0263 } 0264 0265 qint64 SchedulerModuleState::getCurrentOperationMsec() const 0266 { 0267 if (!currentOperationTimeStarted) return 0; 0268 return currentOperationTime.msecsTo(KStarsData::Instance()->ut()); 0269 } 0270 0271 void SchedulerModuleState::startCurrentOperationTimer() 0272 { 0273 currentOperationTimeStarted = true; 0274 currentOperationTime = KStarsData::Instance()->ut(); 0275 } 0276 0277 void SchedulerModuleState::cancelGuidingTimer() 0278 { 0279 m_restartGuidingInterval = -1; 0280 m_restartGuidingTime = KStarsDateTime(); 0281 } 0282 0283 bool SchedulerModuleState::isGuidingTimerActive() 0284 { 0285 return (m_restartGuidingInterval > 0 && 0286 m_restartGuidingTime.msecsTo(KStarsData::Instance()->ut()) >= 0); 0287 } 0288 0289 void SchedulerModuleState::startGuidingTimer(int milliseconds) 0290 { 0291 m_restartGuidingInterval = milliseconds; 0292 m_restartGuidingTime = KStarsData::Instance()->ut(); 0293 } 0294 0295 // Allows for unit testing of static Scheduler methods, 0296 // as can't call KStarsData::Instance() during unit testing. 0297 KStarsDateTime *SchedulerModuleState::storedLocalTime = nullptr; 0298 KStarsDateTime SchedulerModuleState::getLocalTime() 0299 { 0300 if (hasLocalTime()) 0301 return *storedLocalTime; 0302 return KStarsData::Instance()->geo()->UTtoLT(KStarsData::Instance()->clock()->utc()); 0303 } 0304 0305 void SchedulerModuleState::calculateDawnDusk(const QDateTime &when, QDateTime &nDawn, QDateTime &nDusk) 0306 { 0307 QDateTime startup = when; 0308 0309 if (!startup.isValid()) 0310 startup = getLocalTime(); 0311 0312 // Our local midnight - the KStarsDateTime date+time constructor is safe for local times 0313 // Exact midnight seems unreliable--offset it by a minute. 0314 KStarsDateTime midnight(startup.date(), QTime(0, 1), Qt::LocalTime); 0315 0316 QDateTime dawn = startup, dusk = startup; 0317 0318 // Loop dawn and dusk calculation until the events found are the next events 0319 for ( ; dawn <= startup || dusk <= startup ; midnight = midnight.addDays(1)) 0320 { 0321 // KSAlmanac computes the closest dawn and dusk events from the local sidereal time corresponding to the midnight argument 0322 0323 #if 0 0324 KSAlmanac const ksal(midnight, getGeo()); 0325 // If dawn is in the past compared to this observation, fetch the next dawn 0326 if (dawn <= startup) 0327 dawn = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) * 0328 3600.0)); 0329 // If dusk is in the past compared to this observation, fetch the next dusk 0330 if (dusk <= startup) 0331 dusk = getGeo()->UTtoLT(ksal.getDate().addSecs((ksal.getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) * 0332 3600.0)); 0333 #else 0334 // Creating these almanac instances seems expensive. 0335 static QMap<QString, KSAlmanac const * > almanacMap; 0336 const QString key = QString("%1 %2 %3").arg(midnight.toString()).arg(getGeo()->lat()->Degrees()).arg( 0337 getGeo()->lng()->Degrees()); 0338 KSAlmanac const * ksal = almanacMap.value(key, nullptr); 0339 if (ksal == nullptr) 0340 { 0341 if (almanacMap.size() > 5) 0342 { 0343 // don't allow this to grow too large. 0344 qDeleteAll(almanacMap); 0345 almanacMap.clear(); 0346 } 0347 ksal = new KSAlmanac(midnight, getGeo()); 0348 almanacMap[key] = ksal; 0349 } 0350 0351 // If dawn is in the past compared to this observation, fetch the next dawn 0352 if (dawn <= startup) 0353 dawn = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDawnAstronomicalTwilight() * 24.0 + Options::dawnOffset()) * 0354 3600.0)); 0355 0356 // If dusk is in the past compared to this observation, fetch the next dusk 0357 if (dusk <= startup) 0358 dusk = getGeo()->UTtoLT(ksal->getDate().addSecs((ksal->getDuskAstronomicalTwilight() * 24.0 + Options::duskOffset()) * 0359 3600.0)); 0360 #endif 0361 } 0362 0363 // Now we have the next events: 0364 // - if dawn comes first, observation runs during the night 0365 // - if dusk comes first, observation runs during the day 0366 nDawn = dawn; 0367 nDusk = dusk; 0368 } 0369 0370 void SchedulerModuleState::calculateDawnDusk() 0371 { 0372 calculateDawnDusk(QDateTime(), m_Dawn, m_Dusk); 0373 0374 m_PreDawnDateTime = m_Dawn.addSecs(-60.0 * abs(Options::preDawnTime())); 0375 emit updateNightTime(); 0376 } 0377 0378 const GeoLocation *SchedulerModuleState::getGeo() 0379 { 0380 if (hasGeo()) 0381 return storedGeo; 0382 return KStarsData::Instance()->geo(); 0383 } 0384 0385 bool SchedulerModuleState::hasGeo() 0386 { 0387 return storedGeo != nullptr; 0388 } 0389 0390 void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState) 0391 { 0392 setupNextIteration(nextState, m_UpdatePeriodMs); 0393 } 0394 0395 void SchedulerModuleState::setupNextIteration(SchedulerTimerState nextState, int milliseconds) 0396 { 0397 if (iterationSetup()) 0398 { 0399 qCDebug(KSTARS_EKOS_SCHEDULER) 0400 << QString("Multiple setupNextIteration calls: current %1 %2, previous %3 %4") 0401 .arg(nextState).arg(milliseconds).arg(timerState()).arg(timerInterval()); 0402 } 0403 setTimerState(nextState); 0404 // check if setup is called from a thread outside of the iteration timer thread 0405 if (iterationTimer().isActive()) 0406 { 0407 // restart the timer to ensure the correct startup delay 0408 int remaining = iterationTimer().remainingTime(); 0409 iterationTimer().stop(); 0410 setTimerInterval(std::max(0, milliseconds - remaining)); 0411 iterationTimer().start(timerInterval()); 0412 } 0413 else 0414 { 0415 // setup called from inside the iteration timer thread 0416 setTimerInterval(milliseconds); 0417 } 0418 setIterationSetup(true); 0419 } 0420 0421 uint SchedulerModuleState::maxFailureAttempts() 0422 { 0423 return MAX_FAILURE_ATTEMPTS; 0424 } 0425 0426 bool SchedulerModuleState::checkRepeatSequence() 0427 { 0428 return (!Options::rememberJobProgress() && Options::schedulerRepeatSequences() && 0429 (Options::schedulerExecutionSequencesLimit() == 0 0430 || sequenceExecutionCounter()) < Options::schedulerExecutionSequencesLimit()); 0431 } 0432 } // Ekos namespace