Changeset View
Standalone View
kstars/ekos/scheduler/scheduler.cpp
- This file is larger than 256 KB, so syntax highlighting is disabled by default.
Show First 20 Lines • Show All 190 Lines • ▼ Show 20 Line(s) | 46 | { | |||
---|---|---|---|---|---|
191 | connect(pauseB, &QPushButton::clicked, this, &Scheduler::pause); | 191 | connect(pauseB, &QPushButton::clicked, this, &Scheduler::pause); | ||
192 | 192 | | |||
193 | connect(queueSaveAsB, &QPushButton::clicked, this, &Scheduler::saveAs); | 193 | connect(queueSaveAsB, &QPushButton::clicked, this, &Scheduler::saveAs); | ||
194 | connect(queueSaveB, &QPushButton::clicked, this, &Scheduler::save); | 194 | connect(queueSaveB, &QPushButton::clicked, this, &Scheduler::save); | ||
195 | connect(queueLoadB, &QPushButton::clicked, this, &Scheduler::load); | 195 | connect(queueLoadB, &QPushButton::clicked, this, &Scheduler::load); | ||
196 | 196 | | |||
197 | connect(twilightCheck, &QCheckBox::toggled, this, &Scheduler::checkTwilightWarning); | 197 | connect(twilightCheck, &QCheckBox::toggled, this, &Scheduler::checkTwilightWarning); | ||
198 | 198 | | |||
199 | // restore default values for error handling strategy | ||||
200 | setErrorHandlingStrategy(static_cast<ErrorHandlingStrategy>(Options::errorHandlingStrategy())); | ||||
201 | errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors()); | ||||
202 | errorHandlingDelaySB->setValue(Options::errorHandlingStrategyDelay()); | ||||
203 | | ||||
204 | // save new default values for error handling strategy | ||||
205 | | ||||
206 | connect(errorHandlingRescheduleErrorsCB, &QPushButton::clicked, [this](bool checked) | ||||
207 | { | ||||
208 | Options::setRescheduleErrors(checked); | ||||
209 | }); | ||||
210 | connect(errorHandlingButtonGroup, static_cast<void (QButtonGroup::*)(QAbstractButton *)>(&QButtonGroup::buttonClicked), [this](QAbstractButton *button) | ||||
211 | { | ||||
212 | Q_UNUSED(button); | ||||
213 | Options::setErrorHandlingStrategy(getErrorHandlingStrategy()); | ||||
214 | }); | ||||
215 | connect(errorHandlingDelaySB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int value) | ||||
216 | { | ||||
217 | Options::setErrorHandlingStrategyDelay(value); | ||||
218 | }); | ||||
219 | | ||||
220 | | ||||
199 | loadProfiles(); | 221 | loadProfiles(); | ||
200 | 222 | | |||
201 | watchJobChanges(true); | 223 | watchJobChanges(true); | ||
202 | } | 224 | } | ||
203 | 225 | | |||
204 | QString Scheduler::getCurrentJobName() | 226 | QString Scheduler::getCurrentJobName() | ||
205 | { | 227 | { | ||
206 | return (currentJob != nullptr ? currentJob->getName() : ""); | 228 | return (currentJob != nullptr ? currentJob->getName() : ""); | ||
Show All 26 Lines | 232 | { | |||
233 | QComboBox * const comboBoxes[] = | 255 | QComboBox * const comboBoxes[] = | ||
234 | { | 256 | { | ||
235 | schedulerProfileCombo | 257 | schedulerProfileCombo | ||
236 | }; | 258 | }; | ||
237 | 259 | | |||
238 | QButtonGroup * const buttonGroups[] = | 260 | QButtonGroup * const buttonGroups[] = | ||
239 | { | 261 | { | ||
240 | stepsButtonGroup, | 262 | stepsButtonGroup, | ||
263 | errorHandlingButtonGroup, | ||||
241 | startupButtonGroup, | 264 | startupButtonGroup, | ||
242 | constraintButtonGroup, | 265 | constraintButtonGroup, | ||
243 | completionButtonGroup, | 266 | completionButtonGroup, | ||
244 | startupProcedureButtonGroup, | 267 | startupProcedureButtonGroup, | ||
245 | shutdownProcedureGroup | 268 | shutdownProcedureGroup | ||
246 | }; | 269 | }; | ||
247 | 270 | | |||
271 | QAbstractButton * const buttons[] = | ||||
272 | { | ||||
273 | errorHandlingRescheduleErrorsCB | ||||
274 | }; | ||||
275 | | ||||
248 | QSpinBox * const spinBoxes[] = | 276 | QSpinBox * const spinBoxes[] = | ||
249 | { | 277 | { | ||
250 | culminationOffset, | 278 | culminationOffset, | ||
251 | repeatsSpin, | 279 | repeatsSpin, | ||
252 | prioritySpin | 280 | prioritySpin, | ||
281 | errorHandlingDelaySB | ||||
253 | }; | 282 | }; | ||
254 | 283 | | |||
255 | QDoubleSpinBox * const dspinBoxes[] = | 284 | QDoubleSpinBox * const dspinBoxes[] = | ||
256 | { | 285 | { | ||
257 | minMoonSeparation, | 286 | minMoonSeparation, | ||
258 | minAltitude | 287 | minAltitude | ||
259 | }; | 288 | }; | ||
260 | 289 | | |||
Show All 23 Lines | 291 | { | |||
284 | { | 313 | { | ||
285 | setDirty(); | 314 | setDirty(); | ||
286 | }); | 315 | }); | ||
287 | for (auto * const control : buttonGroups) | 316 | for (auto * const control : buttonGroups) | ||
288 | connect(control, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), this, [this](int, bool) | 317 | connect(control, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), this, [this](int, bool) | ||
289 | { | 318 | { | ||
290 | setDirty(); | 319 | setDirty(); | ||
291 | }); | 320 | }); | ||
321 | for (auto * const control : buttons) | ||||
322 | connect(control, static_cast<void (QAbstractButton::*)(bool)>(&QAbstractButton::clicked), this, [this](bool) | ||||
323 | { | ||||
324 | setDirty(); | ||||
325 | }); | ||||
292 | for (auto * const control : spinBoxes) | 326 | for (auto * const control : spinBoxes) | ||
293 | connect(control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() | 327 | connect(control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() | ||
294 | { | 328 | { | ||
295 | setDirty(); | 329 | setDirty(); | ||
296 | }); | 330 | }); | ||
297 | for (auto * const control : dspinBoxes) | 331 | for (auto * const control : dspinBoxes) | ||
298 | connect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [this](double) | 332 | connect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [this](double) | ||
299 | { | 333 | { | ||
300 | setDirty(); | 334 | setDirty(); | ||
301 | }); | 335 | }); | ||
302 | } | 336 | } | ||
303 | else | 337 | else | ||
304 | { | 338 | { | ||
305 | /* Disconnect the relevant signal from each widget. Actually, this method removes all signals from the widgets, | 339 | /* Disconnect the relevant signal from each widget. Actually, this method removes all signals from the widgets, | ||
306 | * because we did not take care to keep the connection object when connecting. No problem in our case, we do not | 340 | * because we did not take care to keep the connection object when connecting. No problem in our case, we do not | ||
307 | * expect other signals to be connected. Because we used a lambda, we cannot use the same function object to | 341 | * expect other signals to be connected. Because we used a lambda, we cannot use the same function object to | ||
308 | * disconnect selectively. | 342 | * disconnect selectively. | ||
309 | */ | 343 | */ | ||
310 | for (auto * const control : lineEdits) | 344 | for (auto * const control : lineEdits) | ||
311 | disconnect(control, &QLineEdit::editingFinished, this, nullptr); | 345 | disconnect(control, &QLineEdit::editingFinished, this, nullptr); | ||
312 | for (auto * const control : dateEdits) | 346 | for (auto * const control : dateEdits) | ||
313 | disconnect(control, &QDateTimeEdit::editingFinished, this, nullptr); | 347 | disconnect(control, &QDateTimeEdit::editingFinished, this, nullptr); | ||
314 | for (auto * const control : comboBoxes) | 348 | for (auto * const control : comboBoxes) | ||
315 | disconnect(control, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, nullptr); | 349 | disconnect(control, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, nullptr); | ||
350 | for (auto * const control : buttons) | ||||
351 | disconnect(control, static_cast<void (QAbstractButton::*)(bool)>(&QAbstractButton::clicked), this, nullptr); | ||||
316 | for (auto * const control : buttonGroups) | 352 | for (auto * const control : buttonGroups) | ||
317 | disconnect(control, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), this, nullptr); | 353 | disconnect(control, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), this, nullptr); | ||
318 | for (auto * const control : spinBoxes) | 354 | for (auto * const control : spinBoxes) | ||
319 | disconnect(control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, nullptr); | 355 | disconnect(control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, nullptr); | ||
320 | for (auto * const control : dspinBoxes) | 356 | for (auto * const control : dspinBoxes) | ||
321 | disconnect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, nullptr); | 357 | disconnect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, nullptr); | ||
322 | } | 358 | } | ||
323 | 359 | | |||
▲ Show 20 Lines • Show All 1063 Lines • ▼ Show 20 Line(s) | 1414 | { | |||
1387 | if (Options::rememberJobProgress()) | 1423 | if (Options::rememberJobProgress()) | ||
1388 | updateCompletedJobsCount(); | 1424 | updateCompletedJobsCount(); | ||
1389 | 1425 | | |||
1390 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | 1426 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | ||
1391 | calculateDawnDusk(); | 1427 | calculateDawnDusk(); | ||
1392 | 1428 | | |||
1393 | /* First, filter out non-schedulable jobs */ | 1429 | /* First, filter out non-schedulable jobs */ | ||
1394 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | 1430 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | ||
1395 | QList<SchedulerJob *> sortedJobs = jobs; | 1431 | QList<SchedulerJob *> sortedJobs = jobs; | ||
1396 | sortedJobs.erase(std::remove_if(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | | |||
1397 | { | | |||
1398 | return SchedulerJob::JOB_ABORTED < job->getState(); | | |||
1399 | }), sortedJobs.end()); | | |||
1400 | 1432 | | |||
TallFurryMan: Here I tend to disagree. We have JOB_INVALID, JOB_ERROR and JOB_ABORTED.
JOB_INVALID indicates… | |||||
I would agree if JOB_ERROR is only used when an error is non recoverable. But for example what error state should be set if a slew command fails? It is definitely recoverable, since sending the command again solves the problem. The same is true if e.g. we loose network connection. The problem is, that we do not have a JOB_FATAL state. For those, I absolutely agree, we should not try to restart. That's why I added the option to handle errors like aborts. I am quite sure that there are setups where restarting of errors is dangerous. But with my setting, its absolutely fine. wreissenberger: I would agree if JOB_ERROR is only used when an error is non recoverable. But for example what… | |||||
I believe the current JOB_ERROR and your example JOB_FATAL are equivalent. If the failures of a slew command leads to JOB_ERROR currently and you think it is recoverable, then we should change the state resulting from such failure to JOB_ABORTED. TallFurryMan: I believe the current JOB_ERROR and your example JOB_FATAL are equivalent. If the failures of a… | |||||
1401 | /* Then enumerate SchedulerJobs to consolidate imaging time */ | 1433 | /* Then enumerate SchedulerJobs to consolidate imaging time */ | ||
1402 | foreach (SchedulerJob *job, sortedJobs) | 1434 | foreach (SchedulerJob *job, sortedJobs) | ||
1403 | { | 1435 | { | ||
1404 | /* Let aborted jobs be rescheduled later instead of forgetting them */ | 1436 | /* Let aborted jobs be rescheduled later instead of forgetting them */ | ||
1405 | switch (job->getState()) | 1437 | switch (job->getState()) | ||
1406 | { | 1438 | { | ||
1407 | case SchedulerJob::JOB_SCHEDULED: | 1439 | case SchedulerJob::JOB_SCHEDULED: | ||
1408 | /* If job is scheduled, keep it for evaluation against others */ | 1440 | /* If job is scheduled, keep it for evaluation against others */ | ||
1409 | break; | 1441 | break; | ||
1410 | 1442 | | |||
1411 | case SchedulerJob::JOB_ERROR: | | |||
1412 | case SchedulerJob::JOB_INVALID: | 1443 | case SchedulerJob::JOB_INVALID: | ||
1413 | case SchedulerJob::JOB_COMPLETE: | 1444 | case SchedulerJob::JOB_COMPLETE: | ||
1414 | /* If job is in error, invalid or complete, bypass evaluation */ | 1445 | /* If job is invalid or complete, bypass evaluation */ | ||
1415 | continue; | 1446 | continue; | ||
1416 | 1447 | | |||
1417 | case SchedulerJob::JOB_BUSY: | 1448 | case SchedulerJob::JOB_BUSY: | ||
1418 | /* If job is busy, edge case, bypass evaluation */ | 1449 | /* If job is busy, edge case, bypass evaluation */ | ||
1419 | continue; | 1450 | continue; | ||
1420 | 1451 | | |||
1452 | case SchedulerJob::JOB_ERROR: | ||||
1421 | case SchedulerJob::JOB_ABORTED: | 1453 | case SchedulerJob::JOB_ABORTED: | ||
1422 | /* If job is aborted and we're running, keep its evaluation until there is nothing else to do */ | 1454 | /* If job is in error or aborted and we're running, keep its evaluation until there is nothing else to do */ | ||
1423 | if (state == SCHEDULER_RUNNING) | 1455 | if (state == SCHEDULER_RUNNING) | ||
1424 | continue; | 1456 | continue; | ||
1425 | /* Fall through */ | 1457 | /* Fall through */ | ||
1426 | case SchedulerJob::JOB_IDLE: | 1458 | case SchedulerJob::JOB_IDLE: | ||
1427 | case SchedulerJob::JOB_EVALUATION: | 1459 | case SchedulerJob::JOB_EVALUATION: | ||
1428 | default: | 1460 | default: | ||
1429 | /* If job is idle, re-evaluate completely */ | 1461 | /* If job is idle, re-evaluate completely */ | ||
1430 | job->setEstimatedTime(-1); | 1462 | job->setEstimatedTime(-1); | ||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Line(s) | |||||
1497 | 1529 | | |||
1498 | /* This predicate matches jobs not being evaluated and not aborted */ | 1530 | /* This predicate matches jobs not being evaluated and not aborted */ | ||
1499 | auto neither_evaluated_nor_aborted = [](SchedulerJob const * const job) | 1531 | auto neither_evaluated_nor_aborted = [](SchedulerJob const * const job) | ||
1500 | { | 1532 | { | ||
1501 | SchedulerJob::JOBStatus const s = job->getState(); | 1533 | SchedulerJob::JOBStatus const s = job->getState(); | ||
1502 | return SchedulerJob::JOB_EVALUATION != s && SchedulerJob::JOB_ABORTED != s; | 1534 | return SchedulerJob::JOB_EVALUATION != s && SchedulerJob::JOB_ABORTED != s; | ||
1503 | }; | 1535 | }; | ||
1504 | 1536 | | |||
1537 | /* This predicate matches jobs neither being evaluated nor aborted nor in error state */ | ||||
TallFurryMan: OK for this block, interesting feature indeed. | |||||
1538 | auto neither_evaluated_nor_aborted_nor_error = [](SchedulerJob const * const job) | ||||
1539 | { | ||||
1540 | SchedulerJob::JOBStatus const s = job->getState(); | ||||
1541 | return SchedulerJob::JOB_EVALUATION != s && SchedulerJob::JOB_ABORTED != s && SchedulerJob::JOB_ERROR != s; | ||||
1542 | }; | ||||
1543 | | ||||
1505 | /* This predicate matches jobs that aborted, or completed for whatever reason */ | 1544 | /* This predicate matches jobs that aborted, or completed for whatever reason */ | ||
1506 | auto finished_or_aborted = [](SchedulerJob const * const job) | 1545 | auto finished_or_aborted = [](SchedulerJob const * const job) | ||
1507 | { | 1546 | { | ||
1508 | SchedulerJob::JOBStatus const s = job->getState(); | 1547 | SchedulerJob::JOBStatus const s = job->getState(); | ||
1509 | return SchedulerJob::JOB_ERROR <= s || SchedulerJob::JOB_ABORTED == s; | 1548 | return SchedulerJob::JOB_ERROR <= s || SchedulerJob::JOB_ABORTED == s; | ||
1510 | }; | 1549 | }; | ||
1511 | 1550 | | |||
1551 | bool nea = std::all_of(sortedJobs.begin(), sortedJobs.end(), neither_evaluated_nor_aborted); | ||||
1552 | bool neae = std::all_of(sortedJobs.begin(), sortedJobs.end(), neither_evaluated_nor_aborted_nor_error); | ||||
1553 | | ||||
1512 | /* If there are no jobs left to run in the filtered list, stop evaluation */ | 1554 | /* If there are no jobs left to run in the filtered list, stop evaluation */ | ||
1513 | if (sortedJobs.isEmpty() || std::all_of(sortedJobs.begin(), sortedJobs.end(), neither_evaluated_nor_aborted)) | 1555 | if (sortedJobs.isEmpty() || (!errorHandlingRescheduleErrorsCB->isChecked() && nea) || (errorHandlingRescheduleErrorsCB->isChecked() && neae)) | ||
1514 | { | 1556 | { | ||
1515 | appendLogText(i18n("No jobs left in the scheduler queue.")); | 1557 | appendLogText(i18n("No jobs left in the scheduler queue.")); | ||
1516 | setCurrentJob(nullptr); | 1558 | setCurrentJob(nullptr); | ||
1517 | jobEvaluationOnly = false; | 1559 | jobEvaluationOnly = false; | ||
1518 | return; | 1560 | return; | ||
1519 | } | 1561 | } | ||
1520 | 1562 | | |||
1521 | /* If there are only aborted jobs that can run, reschedule those */ | 1563 | /* If there are only aborted jobs that can run, reschedule those */ | ||
1522 | if (std::all_of(sortedJobs.begin(), sortedJobs.end(), finished_or_aborted)) | 1564 | if (std::all_of(sortedJobs.begin(), sortedJobs.end(), finished_or_aborted) && | ||
1565 | errorHandlingDontRestartButton->isChecked() == false) | ||||
1523 | { | 1566 | { | ||
1524 | appendLogText(i18n("Only aborted jobs left in the scheduler queue, rescheduling those.")); | 1567 | appendLogText(i18n("Only %1 jobs left in the scheduler queue, rescheduling those.", | ||
1525 | std::for_each(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | 1568 | errorHandlingRescheduleErrorsCB->isChecked() ? "aborted or error" : "aborted")); | ||
1569 | | ||||
1570 | // set aborted and error jobs to evaluation state | ||||
1571 | for (int index = 0; index < sortedJobs.size(); index++) | ||||
1526 | { | 1572 | { | ||
1527 | if (SchedulerJob::JOB_ABORTED == job->getState()) | 1573 | SchedulerJob * const job = sortedJobs.at(index); | ||
1574 | if (SchedulerJob::JOB_ABORTED == job->getState() || | ||||
1575 | (errorHandlingRescheduleErrorsCB->isChecked() && SchedulerJob::JOB_ERROR == job->getState())) | ||||
1528 | job->setState(SchedulerJob::JOB_EVALUATION); | 1576 | job->setState(SchedulerJob::JOB_EVALUATION); | ||
1529 | }); | 1577 | } | ||
1578 | | ||||
1579 | if (errorHandlingRestartAfterAllButton->isChecked()) | ||||
I'm not sure about this block, it really gets in the way of the regular method of scheduling. TallFurryMan: I'm not sure about this block, it really gets in the way of the regular method of scheduling. | |||||
This section simply extends the old predicate based to a loop so that it can take into account whether the "re-schedule errors" checkbox is selected. I needed to switch to a loop since the checkbox is not static. wreissenberger: This section simply extends the old predicate based to a loop so that it can take into account… | |||||
TallFurryMan: OK. I'm still worried about the edge cases I listed. | |||||
1580 | { | ||||
1581 | // interrupt regular status checks during the sleep time | ||||
1582 | schedulerTimer.stop(); | ||||
1583 | | ||||
1584 | // but before we restart them, we wait for the given delay. | ||||
1585 | appendLogText(i18n("All jobs aborted. Waiting %1 seconds to re-schedule.", errorHandlingDelaySB->value())); | ||||
1586 | | ||||
1587 | // wait the given delay until the jobs will be evaluated again | ||||
1588 | sleepTimer.setInterval(( errorHandlingDelaySB->value() * 1000)); | ||||
1589 | sleepTimer.start(); | ||||
1590 | sleepLabel->setToolTip(i18n("Scheduler waits for a retry.")); | ||||
1591 | sleepLabel->show(); | ||||
1592 | // we continue to determine which job should be running, when the delay is over | ||||
1593 | } | ||||
1530 | } | 1594 | } | ||
1531 | 1595 | | |||
1532 | /* If option says so, reorder by altitude and priority before sequencing */ | 1596 | /* If option says so, reorder by altitude and priority before sequencing */ | ||
1533 | /* FIXME: refactor so all sorts are using the same predicates */ | 1597 | /* FIXME: refactor so all sorts are using the same predicates */ | ||
1534 | /* FIXME: use std::stable_sort as qStableSort is deprecated */ | 1598 | /* FIXME: use std::stable_sort as qStableSort is deprecated */ | ||
1535 | /* FIXME: dissociate altitude and priority, it's difficult to choose which predicate to use first */ | 1599 | /* FIXME: dissociate altitude and priority, it's difficult to choose which predicate to use first */ | ||
1536 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Option to sort jobs based on priority and altitude is" << Options::sortSchedulerJobs(); | 1600 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Option to sort jobs based on priority and altitude is" << Options::sortSchedulerJobs(); | ||
1537 | if (Options::sortSchedulerJobs()) | 1601 | if (Options::sortSchedulerJobs()) | ||
1538 | { | 1602 | { | ||
1539 | // If we reorder, remove all non-runnable jobs so that they end up at the end of the list and do not disturb the reorder | | |||
1540 | // We tested that the list could not be empty after that operation above | | |||
1541 | sortedJobs.erase(std::remove_if(sortedJobs.begin(), sortedJobs.end(), neither_evaluated_nor_aborted), sortedJobs.end()); | | |||
1542 | | ||||
1543 | using namespace std::placeholders; | 1603 | using namespace std::placeholders; | ||
OK, only if scheduling algorithm takes the presence of JOB_ABORTED into account. TallFurryMan: OK, only if scheduling algorithm takes the presence of JOB_ABORTED into account. | |||||
wreissenberger: Sorry, don't get the point. | |||||
1544 | std::stable_sort(sortedJobs.begin(), sortedJobs.end(), | 1604 | std::stable_sort(sortedJobs.begin(), sortedJobs.end(), | ||
1545 | std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, KStarsData::Instance()->lt())); | 1605 | std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, KStarsData::Instance()->lt())); | ||
1546 | std::stable_sort(sortedJobs.begin(), sortedJobs.end(), SchedulerJob::increasingPriorityOrder); | 1606 | std::stable_sort(sortedJobs.begin(), sortedJobs.end(), SchedulerJob::increasingPriorityOrder); | ||
1547 | } | 1607 | } | ||
1548 | 1608 | | |||
1549 | /* The first reordered job has no lead time - this could also be the delay from now to startup */ | 1609 | /* The first reordered job has no lead time - this could also be the delay from now to startup */ | ||
1550 | sortedJobs.first()->setLeadTime(0); | 1610 | sortedJobs.first()->setLeadTime(0); | ||
1551 | 1611 | | |||
▲ Show 20 Lines • Show All 445 Lines • ▼ Show 20 Line(s) | 2051 | #if 0 | |||
1997 | 2057 | | |||
1998 | if (SchedulerJob::START_CULMINATION == currentJob->getFileStartupCondition() && Options::leadTime() < 5) | 2058 | if (SchedulerJob::START_CULMINATION == currentJob->getFileStartupCondition() && Options::leadTime() < 5) | ||
1999 | appendLogText(i18n("Job '%1' may require increasing the current lead time of %2 minutes to make transit time calculation stable.", | 2059 | appendLogText(i18n("Job '%1' may require increasing the current lead time of %2 minutes to make transit time calculation stable.", | ||
2000 | currentJob->getName(), | 2060 | currentJob->getName(), | ||
2001 | Options::leadTime())); | 2061 | Options::leadTime())); | ||
2002 | #endif | 2062 | #endif | ||
2003 | } | 2063 | } | ||
2004 | } | 2064 | } | ||
2005 | 2065 | | |||
2006 | /* Remove unscheduled jobs that may have appeared during the last step - safeguard */ | | |||
2007 | sortedJobs.erase(std::remove_if(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | | |||
2008 | { | | |||
2009 | SchedulerJob::JOBStatus const s = job->getState(); | | |||
2010 | return SchedulerJob::JOB_SCHEDULED != s && SchedulerJob::JOB_ABORTED != s; | | |||
2011 | }), sortedJobs.end()); | | |||
2012 | | ||||
2013 | /* Apply sorting to queue table, and mark it for saving if it changes */ | 2066 | /* Apply sorting to queue table, and mark it for saving if it changes */ | ||
OK, only if scheduling algorithm takes the presence of JOB_ABORTED into account. TallFurryMan: OK, only if scheduling algorithm takes the presence of JOB_ABORTED into account. | |||||
2014 | mDirty = reorderJobs(sortedJobs) | mDirty; | 2067 | mDirty = reorderJobs(sortedJobs) | mDirty; | ||
2015 | 2068 | | |||
2016 | if (jobEvaluationOnly || state != SCHEDULER_RUNNING) | 2069 | if (jobEvaluationOnly || state != SCHEDULER_RUNNING) | ||
2017 | { | 2070 | { | ||
2018 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Ekos finished evaluating jobs, no job selection required."; | 2071 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Ekos finished evaluating jobs, no job selection required."; | ||
2019 | jobEvaluationOnly = false; | 2072 | jobEvaluationOnly = false; | ||
2020 | return; | 2073 | return; | ||
2021 | } | 2074 | } | ||
Show All 14 Lines | |||||
2036 | if (sortedJobs.isEmpty() || std::all_of(sortedJobs.begin(), sortedJobs.end(), neither_scheduled_nor_aborted)) | 2089 | if (sortedJobs.isEmpty() || std::all_of(sortedJobs.begin(), sortedJobs.end(), neither_scheduled_nor_aborted)) | ||
2037 | { | 2090 | { | ||
2038 | appendLogText(i18n("No jobs left in the scheduler queue after evaluating.")); | 2091 | appendLogText(i18n("No jobs left in the scheduler queue after evaluating.")); | ||
2039 | setCurrentJob(nullptr); | 2092 | setCurrentJob(nullptr); | ||
2040 | jobEvaluationOnly = false; | 2093 | jobEvaluationOnly = false; | ||
2041 | return; | 2094 | return; | ||
2042 | } | 2095 | } | ||
2043 | /* If there are only aborted jobs that can run, reschedule those and let Scheduler restart one loop */ | 2096 | /* If there are only aborted jobs that can run, reschedule those and let Scheduler restart one loop */ | ||
2044 | else if (std::all_of(sortedJobs.begin(), sortedJobs.end(), finished_or_aborted)) | 2097 | else if (std::all_of(sortedJobs.begin(), sortedJobs.end(), finished_or_aborted) && | ||
2098 | errorHandlingDontRestartButton->isChecked() == false) | ||||
2045 | { | 2099 | { | ||
2046 | appendLogText(i18n("Only aborted jobs left in the scheduler queue after evaluating, rescheduling those.")); | 2100 | appendLogText(i18n("Only aborted jobs left in the scheduler queue after evaluating, rescheduling those.")); | ||
2047 | std::for_each(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | 2101 | std::for_each(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | ||
2048 | { | 2102 | { | ||
2049 | if (SchedulerJob::JOB_ABORTED == job->getState()) | 2103 | if (SchedulerJob::JOB_ABORTED == job->getState()) | ||
2050 | job->setState(SchedulerJob::JOB_EVALUATION); | 2104 | job->setState(SchedulerJob::JOB_EVALUATION); | ||
2051 | }); | 2105 | }); | ||
2052 | 2106 | | |||
▲ Show 20 Lines • Show All 1058 Lines • ▼ Show 20 Line(s) | 3147 | { | |||
3111 | if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_AT && | 3165 | if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_AT && | ||
3112 | currentJob->getState() == SchedulerJob::JOB_BUSY) | 3166 | currentJob->getState() == SchedulerJob::JOB_BUSY) | ||
3113 | { | 3167 | { | ||
3114 | // If the job reached it COMPLETION time, we stop it. | 3168 | // If the job reached it COMPLETION time, we stop it. | ||
3115 | if (now.secsTo(currentJob->getCompletionTime()) <= 0) | 3169 | if (now.secsTo(currentJob->getCompletionTime()) <= 0) | ||
3116 | { | 3170 | { | ||
3117 | appendLogText(i18n("Job '%1' reached completion time %2, stopping.", currentJob->getName(), | 3171 | appendLogText(i18n("Job '%1' reached completion time %2, stopping.", currentJob->getName(), | ||
3118 | currentJob->getCompletionTime().toString(currentJob->getDateTimeDisplayFormat()))); | 3172 | currentJob->getCompletionTime().toString(currentJob->getDateTimeDisplayFormat()))); | ||
3119 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3173 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | ||
This is a behavior change, but well, makes sense now. TallFurryMan: This is a behavior change, but well, makes sense now.
Note that currently, the completion date… | |||||
wreissenberger: Oops, multi-day schedules? That's tricky.
| |||||
I'm afraid with multi-day schedules we have a conceptual problem with manually sorted lists. As soon as a job reaches its completion time we need to change the order. Otherwise re-scheduling it to the next day would mean that the following job is also postponed to the next day. wreissenberger: I'm afraid with multi-day schedules we have a conceptual problem with manually sorted lists. As… | |||||
Please take a look into this discussion: https://indilib.org/forum/general/5423-potential-bug-in-the-scheduler-mount-doesn-t-get-parked-when-guiding-aborted.html It seems like the current behavior is not very intuitive regarding restarting jobs that hit limiting constraints. wreissenberger: Please take a look into this discussion: https://indilib.org/forum/general/5423-potential-bug… | |||||
Agreed, there is still work to do. From my tests, the best way to execute multi-day schedules is to automatically order targets per altitude and set targets to capture indefinitely with a high altitude restriction. This needs to be tested with your auto-restart mechanism. If of course someone is willing to spent a few clear nights to let the setup work its schedule on its own :) TallFurryMan: Agreed, there is still work to do. From my tests, the best way to execute multi-day schedules… | |||||
3120 | stopCurrentJobAction(); | 3174 | stopCurrentJobAction(); | ||
3121 | stopGuiding(); | 3175 | stopGuiding(); | ||
3122 | findNextJob(); | 3176 | findNextJob(); | ||
3123 | return; | 3177 | return; | ||
3124 | } | 3178 | } | ||
3125 | } | 3179 | } | ||
3126 | 3180 | | |||
3127 | // #2 Check if altitude restriction still holds true | 3181 | // #2 Check if altitude restriction still holds true | ||
Show All 9 Lines | 3190 | { | |||
3137 | // Only terminate job due to altitude limitation if mount is NOT parked. | 3191 | // Only terminate job due to altitude limitation if mount is NOT parked. | ||
3138 | if (isMountParked() == false) | 3192 | if (isMountParked() == false) | ||
3139 | { | 3193 | { | ||
3140 | appendLogText(i18n("Job '%1' current altitude (%2 degrees) crossed minimum constraint altitude (%3 degrees), " | 3194 | appendLogText(i18n("Job '%1' current altitude (%2 degrees) crossed minimum constraint altitude (%3 degrees), " | ||
3141 | "marking aborted.", currentJob->getName(), | 3195 | "marking aborted.", currentJob->getName(), | ||
3142 | QString("%L1").arg(p.alt().Degrees(), 0, 'f', minAltitude->decimals()), | 3196 | QString("%L1").arg(p.alt().Degrees(), 0, 'f', minAltitude->decimals()), | ||
3143 | QString("%L1").arg(currentJob->getMinAltitude(), 0, 'f', minAltitude->decimals()))); | 3197 | QString("%L1").arg(currentJob->getMinAltitude(), 0, 'f', minAltitude->decimals()))); | ||
3144 | 3198 | | |||
3145 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3199 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | ||
I disagree: this job must be rescheduled to next observation time, thus JOB_ABORTED. TallFurryMan: I disagree: this job must be rescheduled to next observation time, thus JOB_ABORTED. | |||||
Maybe neither COMPLETE nor ABORTED. What about if we set it to EVALUATION? wreissenberger: Maybe neither COMPLETE nor ABORTED. What about if we set it to EVALUATION? | |||||
I have to correct, EVALUATION does not help - see comment above. If we have the option for immediate restart selected, aborting a job with completion time exceeded leads to the situation, that it is scheduled for the following day and the next job as consequence will be shifted to the next day as well - which is not the desired behavior. wreissenberger: I have to correct, EVALUATION does not help - see comment above. If we have the option for… | |||||
I think I understand why you changed that: it conflicts with your auto-restart mechanism. What if you set it to complete when auto-restarting, with a warning, and to aborted when not auto-restarting? TallFurryMan: I think I understand why you changed that: it conflicts with your auto-restart mechanism. What… | |||||
3146 | stopCurrentJobAction(); | 3200 | stopCurrentJobAction(); | ||
3147 | stopGuiding(); | 3201 | stopGuiding(); | ||
3148 | findNextJob(); | 3202 | findNextJob(); | ||
3149 | return; | 3203 | return; | ||
3150 | } | 3204 | } | ||
3151 | } | 3205 | } | ||
3152 | } | 3206 | } | ||
3153 | 3207 | | |||
Show All 9 Lines | 3210 | { | |||
3163 | { | 3217 | { | ||
3164 | // Only terminate job due to moon separation limitation if mount is NOT parked. | 3218 | // Only terminate job due to moon separation limitation if mount is NOT parked. | ||
3165 | if (isMountParked() == false) | 3219 | if (isMountParked() == false) | ||
3166 | { | 3220 | { | ||
3167 | appendLogText(i18n("Job '%2' current moon separation (%1 degrees) is lower than minimum constraint (%3 " | 3221 | appendLogText(i18n("Job '%2' current moon separation (%1 degrees) is lower than minimum constraint (%3 " | ||
3168 | "degrees), marking aborted.", | 3222 | "degrees), marking aborted.", | ||
3169 | moonSeparation, currentJob->getName(), currentJob->getMinMoonSeparation())); | 3223 | moonSeparation, currentJob->getName(), currentJob->getMinMoonSeparation())); | ||
3170 | 3224 | | |||
3171 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3225 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | ||
I disagree: this job must be rescheduled to next observation time (albeit quite a far one!), thus JOB_ABORTED. TallFurryMan: I disagree: this job must be rescheduled to next observation time (albeit quite a far one!)… | |||||
wreissenberger: see above on line 3191. | |||||
TallFurryMan: Same as previous comment. | |||||
3172 | stopCurrentJobAction(); | 3226 | stopCurrentJobAction(); | ||
3173 | stopGuiding(); | 3227 | stopGuiding(); | ||
3174 | findNextJob(); | 3228 | findNextJob(); | ||
3175 | return; | 3229 | return; | ||
3176 | } | 3230 | } | ||
3177 | } | 3231 | } | ||
3178 | } | 3232 | } | ||
3179 | 3233 | | |||
3180 | // #4 Check if we're not at dawn | 3234 | // #4 Check if we're not at dawn | ||
3181 | if (currentJob->getEnforceTwilight() && now > KStarsDateTime(preDawnDateTime)) | 3235 | if (currentJob->getEnforceTwilight() && now > KStarsDateTime(preDawnDateTime)) | ||
3182 | { | 3236 | { | ||
3183 | // If either mount or dome are not parked, we shutdown if we approach dawn | 3237 | // If either mount or dome are not parked, we shutdown if we approach dawn | ||
3184 | if (isMountParked() == false || (parkDomeCheck->isEnabled() && isDomeParked() == false)) | 3238 | if (isMountParked() == false || (parkDomeCheck->isEnabled() && isDomeParked() == false)) | ||
3185 | { | 3239 | { | ||
3186 | // Minute is a DOUBLE value, do not use i18np | 3240 | // Minute is a DOUBLE value, do not use i18np | ||
3187 | appendLogText(i18n( | 3241 | appendLogText(i18n( | ||
3188 | "Job '%3' is now approaching astronomical twilight rise limit at %1 (%2 minutes safety margin), marking aborted.", | 3242 | "Job '%3' is now approaching astronomical twilight rise limit at %1 (%2 minutes safety margin), marking aborted.", | ||
3189 | preDawnDateTime.toString(), Options::preDawnTime(), currentJob->getName())); | 3243 | preDawnDateTime.toString(), Options::preDawnTime(), currentJob->getName())); | ||
3190 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3244 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | ||
I disagree: this job must be rescheduled to the next observation time, thus JOB_ABORTED. TallFurryMan: I disagree: this job must be rescheduled to the next observation time, thus JOB_ABORTED. | |||||
TallFurryMan: Same as previous comment. | |||||
3191 | stopCurrentJobAction(); | 3245 | stopCurrentJobAction(); | ||
3192 | stopGuiding(); | 3246 | stopGuiding(); | ||
3193 | findNextJob(); | 3247 | findNextJob(); | ||
3194 | return; | 3248 | return; | ||
3195 | } | 3249 | } | ||
3196 | } | 3250 | } | ||
3197 | 3251 | | |||
3198 | // #5 Check system status to improve robustness | 3252 | // #5 Check system status to improve robustness | ||
Show All 20 Lines | 3268 | { | |||
3219 | { | 3273 | { | ||
3220 | if (alignFailureCount++ < MAX_FAILURE_ATTEMPTS) | 3274 | if (alignFailureCount++ < MAX_FAILURE_ATTEMPTS) | ||
3221 | { | 3275 | { | ||
3222 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Align module timed out. Restarting request..."; | 3276 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Align module timed out. Restarting request..."; | ||
3223 | startAstrometry(); | 3277 | startAstrometry(); | ||
3224 | } | 3278 | } | ||
3225 | else | 3279 | else | ||
3226 | { | 3280 | { | ||
3227 | appendLogText(i18n("Warning: job '%1' alignment procedure failed, aborting job.", currentJob->getName())); | 3281 | appendLogText(i18n("Warning: job '%1' alignment procedure failed, marking aborted.", currentJob->getName())); | ||
3228 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3282 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3229 | findNextJob(); | 3283 | findNextJob(); | ||
3230 | } | 3284 | } | ||
3231 | } | 3285 | } | ||
3232 | else | 3286 | else | ||
3233 | currentOperationTime.restart(); | 3287 | currentOperationTime.restart(); | ||
3234 | } | 3288 | } | ||
3235 | break; | 3289 | break; | ||
Show All 9 Lines | 3294 | { | |||
3245 | { | 3299 | { | ||
3246 | if (captureFailureCount++ < MAX_FAILURE_ATTEMPTS) | 3300 | if (captureFailureCount++ < MAX_FAILURE_ATTEMPTS) | ||
3247 | { | 3301 | { | ||
3248 | qCDebug(KSTARS_EKOS_SCHEDULER) << "capture module timed out. Restarting request..."; | 3302 | qCDebug(KSTARS_EKOS_SCHEDULER) << "capture module timed out. Restarting request..."; | ||
3249 | startCapture(); | 3303 | startCapture(); | ||
3250 | } | 3304 | } | ||
3251 | else | 3305 | else | ||
3252 | { | 3306 | { | ||
3253 | appendLogText(i18n("Warning: job '%1' capture procedure failed, aborting job.", currentJob->getName())); | 3307 | appendLogText(i18n("Warning: job '%1' capture procedure failed, marking aborted.", currentJob->getName())); | ||
Just a warning not about translations. If you change this, translators need to change stuff, perhaps needlessly. TallFurryMan: Just a warning not about translations. If you change this, translators need to change stuff… | |||||
3254 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3308 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3255 | findNextJob(); | 3309 | findNextJob(); | ||
3256 | } | 3310 | } | ||
3257 | } | 3311 | } | ||
3258 | else currentOperationTime.restart(); | 3312 | else currentOperationTime.restart(); | ||
3259 | } | 3313 | } | ||
3260 | break; | 3314 | break; | ||
3261 | 3315 | | |||
3262 | case SchedulerJob::STAGE_FOCUSING: | 3316 | case SchedulerJob::STAGE_FOCUSING: | ||
3263 | // Let's make sure focus module does not become unresponsive | 3317 | // Let's make sure focus module does not become unresponsive | ||
3264 | if (currentOperationTime.elapsed() > FOCUS_INACTIVITY_TIMEOUT) | 3318 | if (currentOperationTime.elapsed() > FOCUS_INACTIVITY_TIMEOUT) | ||
3265 | { | 3319 | { | ||
3266 | QVariant const status = focusInterface->property("status"); | 3320 | QVariant const status = focusInterface->property("status"); | ||
3267 | Ekos::FocusState focusStatus = static_cast<Ekos::FocusState>(status.toInt()); | 3321 | Ekos::FocusState focusStatus = static_cast<Ekos::FocusState>(status.toInt()); | ||
3268 | 3322 | | |||
3269 | if (focusStatus == Ekos::FOCUS_IDLE || focusStatus == Ekos::FOCUS_WAITING) | 3323 | if (focusStatus == Ekos::FOCUS_IDLE || focusStatus == Ekos::FOCUS_WAITING) | ||
3270 | { | 3324 | { | ||
3271 | if (focusFailureCount++ < MAX_FAILURE_ATTEMPTS) | 3325 | if (focusFailureCount++ < MAX_FAILURE_ATTEMPTS) | ||
3272 | { | 3326 | { | ||
3273 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Focus module timed out. Restarting request..."; | 3327 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Focus module timed out. Restarting request..."; | ||
3274 | startFocusing(); | 3328 | startFocusing(); | ||
3275 | } | 3329 | } | ||
3276 | else | 3330 | else | ||
3277 | { | 3331 | { | ||
3278 | appendLogText(i18n("Warning: job '%1' focusing procedure failed, aborting job.", currentJob->getName())); | 3332 | appendLogText(i18n("Warning: job '%1' focusing procedure failed, marking aborted.", currentJob->getName())); | ||
3279 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3333 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3280 | findNextJob(); | 3334 | findNextJob(); | ||
3281 | } | 3335 | } | ||
3282 | } | 3336 | } | ||
3283 | else currentOperationTime.restart(); | 3337 | else currentOperationTime.restart(); | ||
3284 | } | 3338 | } | ||
3285 | break; | 3339 | break; | ||
3286 | 3340 | | |||
3287 | case SchedulerJob::STAGE_GUIDING: | 3341 | case SchedulerJob::STAGE_GUIDING: | ||
3288 | // Let's make sure guide module does not become unresponsive | 3342 | // Let's make sure guide module does not become unresponsive | ||
3289 | if (currentOperationTime.elapsed() > GUIDE_INACTIVITY_TIMEOUT) | 3343 | if (currentOperationTime.elapsed() > GUIDE_INACTIVITY_TIMEOUT) | ||
3290 | { | 3344 | { | ||
3291 | GuideState guideStatus = getGuidingStatus(); | 3345 | GuideState guideStatus = getGuidingStatus(); | ||
3292 | 3346 | | |||
3293 | if (guideStatus == Ekos::GUIDE_IDLE || guideStatus == Ekos::GUIDE_CONNECTED || guideStatus == Ekos::GUIDE_DISCONNECTED) | 3347 | if (guideStatus == Ekos::GUIDE_IDLE || guideStatus == Ekos::GUIDE_CONNECTED || guideStatus == Ekos::GUIDE_DISCONNECTED) | ||
3294 | { | 3348 | { | ||
3295 | if (guideFailureCount++ < MAX_FAILURE_ATTEMPTS) | 3349 | if (guideFailureCount++ < MAX_FAILURE_ATTEMPTS) | ||
3296 | { | 3350 | { | ||
3297 | qCDebug(KSTARS_EKOS_SCHEDULER) << "guide module timed out. Restarting request..."; | 3351 | qCDebug(KSTARS_EKOS_SCHEDULER) << "guide module timed out. Restarting request..."; | ||
3298 | startGuiding(); | 3352 | startGuiding(); | ||
3299 | } | 3353 | } | ||
3300 | else | 3354 | else | ||
3301 | { | 3355 | { | ||
3302 | appendLogText(i18n("Warning: job '%1' guiding procedure failed, aborting job.", currentJob->getName())); | 3356 | appendLogText(i18n("Warning: job '%1' guiding procedure failed, marking aborted.", currentJob->getName())); | ||
3303 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 3357 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3304 | findNextJob(); | 3358 | findNextJob(); | ||
3305 | } | 3359 | } | ||
3306 | } | 3360 | } | ||
3307 | else currentOperationTime.restart(); | 3361 | else currentOperationTime.restart(); | ||
3308 | } | 3362 | } | ||
3309 | break; | 3363 | break; | ||
3310 | 3364 | | |||
▲ Show 20 Lines • Show All 524 Lines • ▼ Show 20 Line(s) | 3869 | { | |||
3835 | 3889 | | |||
3836 | qDeleteAll(jobs); | 3890 | qDeleteAll(jobs); | ||
3837 | jobs.clear(); | 3891 | jobs.clear(); | ||
3838 | 3892 | | |||
3839 | LilXML *xmlParser = newLilXML(); | 3893 | LilXML *xmlParser = newLilXML(); | ||
3840 | char errmsg[MAXRBUF]; | 3894 | char errmsg[MAXRBUF]; | ||
3841 | XMLEle *root = nullptr; | 3895 | XMLEle *root = nullptr; | ||
3842 | XMLEle *ep = nullptr; | 3896 | XMLEle *ep = nullptr; | ||
3897 | XMLEle *subEP = nullptr; | ||||
3843 | char c; | 3898 | char c; | ||
3844 | 3899 | | |||
3900 | // We expect all data read from the XML to be in the C locale - QLocale::c() | ||||
TallFurryMan: OK! I thought this was already in. | |||||
3901 | QLocale cLocale = QLocale::c(); | ||||
3902 | | ||||
3845 | while (sFile.getChar(&c)) | 3903 | while (sFile.getChar(&c)) | ||
3846 | { | 3904 | { | ||
3847 | root = readXMLEle(xmlParser, c, errmsg); | 3905 | root = readXMLEle(xmlParser, c, errmsg); | ||
3848 | 3906 | | |||
3849 | if (root) | 3907 | if (root) | ||
3850 | { | 3908 | { | ||
3851 | for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) | 3909 | for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) | ||
3852 | { | 3910 | { | ||
3853 | const char *tag = tagXMLEle(ep); | 3911 | const char *tag = tagXMLEle(ep); | ||
3854 | if (!strcmp(tag, "Job")) | 3912 | if (!strcmp(tag, "Job")) | ||
3855 | processJobInfo(ep); | 3913 | processJobInfo(ep); | ||
3856 | else if (!strcmp(tag, "Profile")) | 3914 | else if (!strcmp(tag, "Profile")) | ||
3857 | { | 3915 | { | ||
3858 | schedulerProfileCombo->setCurrentText(pcdataXMLEle(ep)); | 3916 | schedulerProfileCombo->setCurrentText(pcdataXMLEle(ep)); | ||
3859 | } | 3917 | } | ||
3918 | else if (!strcmp(tag, "ErrorHandlingStrategy")) | ||||
TallFurryMan: OK. | |||||
3919 | { | ||||
3920 | setErrorHandlingStrategy(static_cast<ErrorHandlingStrategy>(cLocale.toInt(findXMLAttValu(ep, "value")))); | ||||
Enum-to-int conversions are dangerous, we should review this method at some point... TallFurryMan: Enum-to-int conversions are dangerous, we should review this method at some point... | |||||
wreissenberger: Agreed. | |||||
3921 | | ||||
3922 | subEP = findXMLEle(ep, "delay"); | ||||
3923 | if (subEP) | ||||
3924 | { | ||||
3925 | errorHandlingDelaySB->setValue(cLocale.toInt(pcdataXMLEle(subEP))); | ||||
3926 | } | ||||
3927 | subEP = findXMLEle(ep, "RescheduleErrors"); | ||||
3928 | errorHandlingRescheduleErrorsCB->setChecked(subEP != nullptr); | ||||
3929 | } | ||||
3860 | else if (!strcmp(tag, "StartupProcedure")) | 3930 | else if (!strcmp(tag, "StartupProcedure")) | ||
The default value "true" is a bit lost in the code here, can we do better? TallFurryMan: The default value "true" is a bit lost in the code here, can we do better?
Line 3942 for… | |||||
wreissenberger: Good point, changed. | |||||
3861 | { | 3931 | { | ||
3862 | XMLEle *procedure; | 3932 | XMLEle *procedure; | ||
3863 | startupScript->clear(); | 3933 | startupScript->clear(); | ||
3864 | unparkDomeCheck->setChecked(false); | 3934 | unparkDomeCheck->setChecked(false); | ||
3865 | unparkMountCheck->setChecked(false); | 3935 | unparkMountCheck->setChecked(false); | ||
3866 | uncapCheck->setChecked(false); | 3936 | uncapCheck->setChecked(false); | ||
3867 | 3937 | | |||
3868 | for (procedure = nextXMLEle(ep, 1); procedure != nullptr; procedure = nextXMLEle(ep, 0)) | 3938 | for (procedure = nextXMLEle(ep, 1); procedure != nullptr; procedure = nextXMLEle(ep, 0)) | ||
▲ Show 20 Lines • Show All 333 Lines • ▼ Show 20 Line(s) | 4271 | if (job->getStepPipeline() & SchedulerJob::USE_ALIGN) | |||
4202 | outstream << "<Step>Align</Step>" << endl; | 4272 | outstream << "<Step>Align</Step>" << endl; | ||
4203 | if (job->getStepPipeline() & SchedulerJob::USE_GUIDE) | 4273 | if (job->getStepPipeline() & SchedulerJob::USE_GUIDE) | ||
4204 | outstream << "<Step>Guide</Step>" << endl; | 4274 | outstream << "<Step>Guide</Step>" << endl; | ||
4205 | outstream << "</Steps>" << endl; | 4275 | outstream << "</Steps>" << endl; | ||
4206 | 4276 | | |||
4207 | outstream << "</Job>" << endl; | 4277 | outstream << "</Job>" << endl; | ||
4208 | } | 4278 | } | ||
4209 | 4279 | | |||
4280 | outstream << "<ErrorHandlingStrategy value='" << getErrorHandlingStrategy() << "'>" << endl; | ||||
4281 | if (errorHandlingRescheduleErrorsCB->isChecked()) | ||||
4282 | outstream << "<RescheduleErrors />" << endl; | ||||
4283 | outstream << "<delay>" << errorHandlingDelaySB->value() << "</delay>" << endl; | ||||
4284 | outstream << "</ErrorHandlingStrategy>" << endl; | ||||
4285 | | ||||
4210 | outstream << "<StartupProcedure>" << endl; | 4286 | outstream << "<StartupProcedure>" << endl; | ||
4211 | if (startupScript->text().isEmpty() == false) | 4287 | if (startupScript->text().isEmpty() == false) | ||
4212 | outstream << "<Procedure value='" << startupScript->text() << "'>StartupScript</Procedure>" << endl; | 4288 | outstream << "<Procedure value='" << startupScript->text() << "'>StartupScript</Procedure>" << endl; | ||
4213 | if (unparkDomeCheck->isChecked()) | 4289 | if (unparkDomeCheck->isChecked()) | ||
4214 | outstream << "<Procedure>UnparkDome</Procedure>" << endl; | 4290 | outstream << "<Procedure>UnparkDome</Procedure>" << endl; | ||
4215 | if (unparkMountCheck->isChecked()) | 4291 | if (unparkMountCheck->isChecked()) | ||
4216 | outstream << "<Procedure>UnparkMount</Procedure>" << endl; | 4292 | outstream << "<Procedure>UnparkMount</Procedure>" << endl; | ||
4217 | if (uncapCheck->isChecked()) | 4293 | if (uncapCheck->isChecked()) | ||
▲ Show 20 Lines • Show All 163 Lines • ▼ Show 20 Line(s) | 4444 | { | |||
4381 | jobTimer.stop(); | 4457 | jobTimer.stop(); | ||
4382 | 4458 | | |||
4383 | // Reset failed count | 4459 | // Reset failed count | ||
4384 | alignFailureCount = guideFailureCount = focusFailureCount = captureFailureCount = 0; | 4460 | alignFailureCount = guideFailureCount = focusFailureCount = captureFailureCount = 0; | ||
4385 | 4461 | | |||
4386 | /* FIXME: Other debug logs in that function probably */ | 4462 | /* FIXME: Other debug logs in that function probably */ | ||
4387 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Find next job..."; | 4463 | qCDebug(KSTARS_EKOS_SCHEDULER) << "Find next job..."; | ||
4388 | 4464 | | |||
4389 | if (currentJob->getState() == SchedulerJob::JOB_ERROR) | 4465 | if (currentJob->getState() == SchedulerJob::JOB_ERROR || currentJob->getState() == SchedulerJob::JOB_ABORTED) | ||
As mentioned earlier, JOB_ERROR jobs should be left cancelled per definition. TallFurryMan: As mentioned earlier, JOB_ERROR jobs should be left cancelled per definition. | |||||
TallFurryMan: Still stands. | |||||
4390 | { | 4466 | { | ||
4391 | captureBatch = 0; | 4467 | captureBatch = 0; | ||
4392 | // Stop Guiding if it was used | 4468 | // Stop Guiding if it was used | ||
4393 | stopGuiding(); | 4469 | stopGuiding(); | ||
4394 | 4470 | | |||
4471 | if (currentJob->getState() == SchedulerJob::JOB_ERROR) | ||||
4395 | appendLogText(i18n("Job '%1' is terminated due to errors.", currentJob->getName())); | 4472 | appendLogText(i18n("Job '%1' is terminated due to errors.", currentJob->getName())); | ||
4473 | else | ||||
4474 | appendLogText(i18n("Job '%1' is aborted.", currentJob->getName())); | ||||
4396 | 4475 | | |||
4397 | // Always reset job stage | 4476 | // Always reset job stage | ||
4398 | currentJob->setStage(SchedulerJob::STAGE_IDLE); | 4477 | currentJob->setStage(SchedulerJob::STAGE_IDLE); | ||
4399 | 4478 | | |||
4400 | setCurrentJob(nullptr); | 4479 | // restart aborted jobs immediately, if error handling strategy is set to "restart immediately" | ||
4401 | schedulerTimer.start(); | 4480 | if (errorHandlingRestartImmediatelyButton->isChecked() && | ||
4402 | } | 4481 | (currentJob->getState() == SchedulerJob::JOB_ABORTED || | ||
4403 | else if (currentJob->getState() == SchedulerJob::JOB_ABORTED) | 4482 | (currentJob->getState() == SchedulerJob::JOB_ERROR && errorHandlingRescheduleErrorsCB->isChecked()))) | ||
4404 | { | 4483 | { | ||
4405 | // Stop Guiding if it was used | 4484 | // reset the state so that it will be restarted | ||
4406 | stopGuiding(); | 4485 | currentJob->setState(SchedulerJob::JOB_SCHEDULED); | ||
4407 | 4486 | | |||
4408 | appendLogText(i18n("Job '%1' is aborted.", currentJob->getName())); | 4487 | appendLogText(i18n("Waiting %1 seconds to restart job '%2'.", errorHandlingDelaySB->value(), currentJob->getName())); | ||
TallFurryMan: Same issue as described earlier. | |||||
4409 | 4488 | | |||
4410 | // Always reset job stage | 4489 | // wait the given delay until the jobs will be evaluated again | ||
4411 | currentJob->setStage(SchedulerJob::STAGE_IDLE); | 4490 | sleepTimer.setInterval(( errorHandlingDelaySB->value() * 1000)); | ||
4491 | sleepTimer.start(); | ||||
4492 | sleepLabel->setToolTip(i18n("Scheduler waits for a retry.")); | ||||
4493 | sleepLabel->show(); | ||||
4494 | return; | ||||
4495 | } | ||||
4412 | 4496 | | |||
4497 | // otherwise start re-evaluation | ||||
4413 | setCurrentJob(nullptr); | 4498 | setCurrentJob(nullptr); | ||
TallFurryMan: Comment on the reason of the setCurrentJob move? | |||||
wreissenberger: There's no reason for - reverting it. | |||||
4414 | schedulerTimer.start(); | 4499 | schedulerTimer.start(); | ||
4415 | } | 4500 | } | ||
4416 | // Job is complete, so check completion criteria to optimize processing | 4501 | // Job is complete, so check completion criteria to optimize processing | ||
4417 | // In any case, we're done whether the job completed successfully or not. | 4502 | // In any case, we're done whether the job completed successfully or not. | ||
4418 | else if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE) | 4503 | else if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE) | ||
4419 | { | 4504 | { | ||
4420 | /* If we remember job progress, mark the job idle as well as all its duplicates for re-evaluation */ | 4505 | /* If we remember job progress, mark the job idle as well as all its duplicates for re-evaluation */ | ||
4421 | if (Options::rememberJobProgress()) | 4506 | if (Options::rememberJobProgress()) | ||
▲ Show 20 Lines • Show All 1391 Lines • ▼ Show 20 Line(s) | 5897 | { | |||
5813 | for (SchedulerJob * job : jobs) | 5898 | for (SchedulerJob * job : jobs) | ||
5814 | job->reset(); | 5899 | job->reset(); | ||
5815 | 5900 | | |||
5816 | jobEvaluationOnly = true; | 5901 | jobEvaluationOnly = true; | ||
5817 | evaluateJobs(); | 5902 | evaluateJobs(); | ||
5818 | } | 5903 | } | ||
5819 | } | 5904 | } | ||
5820 | 5905 | | |||
5906 | | ||||
5821 | void Scheduler::updatePreDawn() | 5907 | void Scheduler::updatePreDawn() | ||
5822 | { | 5908 | { | ||
5823 | double earlyDawn = Dawn - Options::preDawnTime() / (60.0 * 24.0); | 5909 | double earlyDawn = Dawn - Options::preDawnTime() / (60.0 * 24.0); | ||
5824 | int dayOffset = 0; | 5910 | int dayOffset = 0; | ||
5825 | QTime dawn = QTime(0, 0, 0).addSecs(Dawn * 24 * 3600); | 5911 | QTime dawn = QTime(0, 0, 0).addSecs(Dawn * 24 * 3600); | ||
5826 | if (KStarsData::Instance()->lt().time() >= dawn) | 5912 | if (KStarsData::Instance()->lt().time() >= dawn) | ||
5827 | dayOffset = 1; | 5913 | dayOffset = 1; | ||
5828 | preDawnDateTime.setDate(KStarsData::Instance()->lt().date().addDays(dayOffset)); | 5914 | preDawnDateTime.setDate(KStarsData::Instance()->lt().date().addDays(dayOffset)); | ||
Show All 32 Lines | |||||
5861 | } | 5947 | } | ||
5862 | 5948 | | |||
5863 | void Scheduler::resumeCheckStatus() | 5949 | void Scheduler::resumeCheckStatus() | ||
5864 | { | 5950 | { | ||
5865 | disconnect(this, &Scheduler::weatherChanged, this, &Scheduler::resumeCheckStatus); | 5951 | disconnect(this, &Scheduler::weatherChanged, this, &Scheduler::resumeCheckStatus); | ||
5866 | schedulerTimer.start(); | 5952 | schedulerTimer.start(); | ||
5867 | } | 5953 | } | ||
5868 | 5954 | | |||
5955 | Scheduler::ErrorHandlingStrategy Scheduler::getErrorHandlingStrategy() | ||||
TallFurryMan: OK | |||||
5956 | { | ||||
5957 | // The UI holds the state | ||||
5958 | if (errorHandlingRestartAfterAllButton->isChecked()) | ||||
5959 | return ERROR_RESTART_AFTER_TERMINATION; | ||||
5960 | else if (errorHandlingRestartImmediatelyButton->isChecked()) | ||||
5961 | return ERROR_RESTART_IMMEDIATELY; | ||||
5962 | else | ||||
5963 | return ERROR_DONT_RESTART; | ||||
5964 | | ||||
5965 | } | ||||
5966 | | ||||
5967 | void Scheduler::setErrorHandlingStrategy(Scheduler::ErrorHandlingStrategy strategy) | ||||
5968 | { | ||||
5969 | errorHandlingWaitLabel->setEnabled(strategy != ERROR_DONT_RESTART); | ||||
5970 | errorHandlingDelaySB->setEnabled(strategy != ERROR_DONT_RESTART); | ||||
5971 | | ||||
5972 | switch (strategy) { | ||||
5973 | case ERROR_RESTART_AFTER_TERMINATION: | ||||
5974 | errorHandlingRestartAfterAllButton->setChecked(true); | ||||
5975 | break; | ||||
5976 | case ERROR_RESTART_IMMEDIATELY: | ||||
5977 | errorHandlingRestartImmediatelyButton->setChecked(true); | ||||
5978 | break; | ||||
5979 | default: | ||||
5980 | errorHandlingDontRestartButton->setChecked(true); | ||||
5981 | break; | ||||
5982 | } | ||||
5983 | } | ||||
5984 | | ||||
5985 | | ||||
5986 | | ||||
5869 | void Scheduler::startMosaicTool() | 5987 | void Scheduler::startMosaicTool() | ||
5870 | { | 5988 | { | ||
5871 | bool raOk = false, decOk = false; | 5989 | bool raOk = false, decOk = false; | ||
5872 | dms ra(raBox->createDms(false, &raOk)); //false means expressed in hours | 5990 | dms ra(raBox->createDms(false, &raOk)); //false means expressed in hours | ||
5873 | dms dec(decBox->createDms(true, &decOk)); | 5991 | dms dec(decBox->createDms(true, &decOk)); | ||
5874 | 5992 | | |||
5875 | if (raOk == false) | 5993 | if (raOk == false) | ||
5876 | { | 5994 | { | ||
▲ Show 20 Lines • Show All 771 Lines • ▼ Show 20 Line(s) | 6765 | { | |||
6648 | appendLogText(i18n("Warning: job '%1' forcing mount model reset after failing alignment #%2.", currentJob->getName(), alignFailureCount)); | 6766 | appendLogText(i18n("Warning: job '%1' forcing mount model reset after failing alignment #%2.", currentJob->getName(), alignFailureCount)); | ||
6649 | mountInterface->call(QDBus::AutoDetect, "resetModel"); | 6767 | mountInterface->call(QDBus::AutoDetect, "resetModel"); | ||
6650 | } | 6768 | } | ||
6651 | appendLogText(i18n("Restarting %1 alignment procedure...", currentJob->getName())); | 6769 | appendLogText(i18n("Restarting %1 alignment procedure...", currentJob->getName())); | ||
6652 | startAstrometry(); | 6770 | startAstrometry(); | ||
6653 | } | 6771 | } | ||
6654 | else | 6772 | else | ||
6655 | { | 6773 | { | ||
6656 | appendLogText(i18n("Warning: job '%1' alignment procedure failed, aborting job.", currentJob->getName())); | 6774 | appendLogText(i18n("Warning: job '%1' alignment procedure failed, marking aborted.", currentJob->getName())); | ||
6657 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 6775 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
6658 | 6776 | | |||
6659 | findNextJob(); | 6777 | findNextJob(); | ||
6660 | } | 6778 | } | ||
6661 | } | 6779 | } | ||
6662 | } | 6780 | } | ||
6663 | } | 6781 | } | ||
6664 | 6782 | | |||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Line(s) | 6829 | { | |||
6722 | else | 6840 | else | ||
6723 | { | 6841 | { | ||
6724 | appendLogText(i18n("Job '%1' is guiding, guiding procedure will be restarted in %2 seconds.", currentJob->getName(), (RESTART_GUIDING_DELAY_MS * guideFailureCount) / 1000)); | 6842 | appendLogText(i18n("Job '%1' is guiding, guiding procedure will be restarted in %2 seconds.", currentJob->getName(), (RESTART_GUIDING_DELAY_MS * guideFailureCount) / 1000)); | ||
6725 | restartGuidingTimer.start(RESTART_GUIDING_DELAY_MS * guideFailureCount); | 6843 | restartGuidingTimer.start(RESTART_GUIDING_DELAY_MS * guideFailureCount); | ||
6726 | } | 6844 | } | ||
6727 | } | 6845 | } | ||
6728 | else | 6846 | else | ||
6729 | { | 6847 | { | ||
6730 | appendLogText(i18n("Warning: job '%1' guiding procedure failed, marking terminated due to errors.", currentJob->getName())); | 6848 | appendLogText(i18n("Warning: job '%1' guiding procedure failed, marking aborted.", currentJob->getName())); | ||
6731 | currentJob->setState(SchedulerJob::JOB_ERROR); | 6849 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
TallFurryMan: OK! But change the log. | |||||
6732 | 6850 | | |||
6733 | findNextJob(); | 6851 | findNextJob(); | ||
6734 | } | 6852 | } | ||
6735 | } | 6853 | } | ||
6736 | } | 6854 | } | ||
6737 | } | 6855 | } | ||
6738 | 6856 | | |||
6739 | GuideState Scheduler::getGuidingStatus() | 6857 | GuideState Scheduler::getGuidingStatus() | ||
▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Line(s) | 6976 | { | |||
6859 | appendLogText(i18n("Job '%1' is restarting its focusing procedure.", currentJob->getName())); | 6977 | appendLogText(i18n("Job '%1' is restarting its focusing procedure.", currentJob->getName())); | ||
6860 | // Reset frame to original size. | 6978 | // Reset frame to original size. | ||
6861 | focusInterface->call(QDBus::AutoDetect, "resetFrame"); | 6979 | focusInterface->call(QDBus::AutoDetect, "resetFrame"); | ||
6862 | // Restart focusing | 6980 | // Restart focusing | ||
6863 | startFocusing(); | 6981 | startFocusing(); | ||
6864 | } | 6982 | } | ||
6865 | else | 6983 | else | ||
6866 | { | 6984 | { | ||
6867 | appendLogText(i18n("Warning: job '%1' focusing procedure failed, marking terminated due to errors.", currentJob->getName())); | 6985 | appendLogText(i18n("Warning: job '%1' focusing procedure failed, marking aborted.", currentJob->getName())); | ||
6868 | currentJob->setState(SchedulerJob::JOB_ERROR); | 6986 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
TallFurryMan: OK! But change the log. | |||||
6869 | 6987 | | |||
6870 | findNextJob(); | 6988 | findNextJob(); | ||
6871 | } | 6989 | } | ||
6872 | } | 6990 | } | ||
6873 | } | 6991 | } | ||
6874 | } | 6992 | } | ||
6875 | 6993 | | |||
6876 | void Scheduler::setMountStatus(ISD::Telescope::Status status) | 6994 | void Scheduler::setMountStatus(ISD::Telescope::Status status) | ||
▲ Show 20 Lines • Show All 232 Lines • Show Last 20 Lines |
Here I tend to disagree. We have JOB_INVALID, JOB_ERROR and JOB_ABORTED.
JOB_INVALID indicates a configuration error, and we cannot proceed with the job at all.
JOB_ERROR indicates a fatal error during processing, and we cannot proceed with the job anymore.
JOB_ABORTED indicates a transitory error during processing, and we should retry at some point.
Based on these definitions, we should not re-evaluate JOB_ERROR.
Now, I know that the enums are not properly ordered, but to me JOB_ERROR jobs should still be removed.