Changeset View
Changeset View
Standalone 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 47 Lines • ▼ Show 20 Line(s) | 45 | { | |||
---|---|---|---|---|---|
48 | qRegisterMetaType<Ekos::SchedulerState>("Ekos::SchedulerState"); | 48 | qRegisterMetaType<Ekos::SchedulerState>("Ekos::SchedulerState"); | ||
49 | qDBusRegisterMetaType<Ekos::SchedulerState>(); | 49 | qDBusRegisterMetaType<Ekos::SchedulerState>(); | ||
50 | 50 | | |||
51 | new SchedulerAdaptor(this); | 51 | new SchedulerAdaptor(this); | ||
52 | QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Scheduler", this); | 52 | QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Scheduler", this); | ||
53 | 53 | | |||
54 | dirPath = QUrl::fromLocalFile(QDir::homePath()); | 54 | dirPath = QUrl::fromLocalFile(QDir::homePath()); | ||
55 | 55 | | |||
56 | strategy = new ScheduleStrategy(); | ||||
57 | | ||||
56 | // Get current KStars time and set seconds to zero | 58 | // Get current KStars time and set seconds to zero | ||
57 | QDateTime currentDateTime = KStarsData::Instance()->lt(); | 59 | QDateTime currentDateTime = KStarsData::Instance()->lt(); | ||
58 | QTime currentTime = currentDateTime.time(); | 60 | QTime currentTime = currentDateTime.time(); | ||
59 | currentTime.setHMS(currentTime.hour(), currentTime.minute(), 0); | 61 | currentTime.setHMS(currentTime.hour(), currentTime.minute(), 0); | ||
60 | currentDateTime.setTime(currentTime); | 62 | currentDateTime.setTime(currentTime); | ||
61 | 63 | | |||
62 | // Set initial time for startup and completion times | 64 | // Set initial time for startup and completion times | ||
63 | startupTimeEdit->setDateTime(currentDateTime); | 65 | startupTimeEdit->setDateTime(currentDateTime); | ||
64 | completionTimeEdit->setDateTime(currentDateTime); | 66 | completionTimeEdit->setDateTime(currentDateTime); | ||
65 | 67 | | |||
66 | // Set up DBus interfaces | 68 | // Set up DBus interfaces | ||
67 | QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Scheduler", this); | 69 | QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Scheduler", this); | ||
68 | ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", | 70 | ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", | ||
69 | QDBusConnection::sessionBus(), this); | 71 | QDBusConnection::sessionBus(), this); | ||
70 | 72 | | |||
71 | // Example of connecting DBus signals | 73 | // Example of connecting DBus signals | ||
72 | //connect(ekosInterface, SIGNAL(indiStatusChanged(Ekos::CommunicationStatus)), this, SLOT(setINDICommunicationStatus(Ekos::CommunicationStatus))); | 74 | //connect(ekosInterface, SIGNAL(indiStatusChanged(Ekos::CommunicationStatus)), this, SLOT(setINDICommunicationStatus(Ekos::CommunicationStatus))); | ||
73 | //connect(ekosInterface, SIGNAL(ekosStatusChanged(Ekos::CommunicationStatus)), this, SLOT(setEkosCommunicationStatus(Ekos::CommunicationStatus))); | 75 | //connect(ekosInterface, SIGNAL(ekosStatusChanged(Ekos::CommunicationStatus)), this, SLOT(setEkosCommunicationStatus(Ekos::CommunicationStatus))); | ||
74 | //connect(ekosInterface, SIGNAL(newModule(QString)), this, SLOT(registerNewModule(QString))); | 76 | //connect(ekosInterface, SIGNAL(newModule(QString)), this, SLOT(registerNewModule(QString))); | ||
75 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this, SLOT(registerNewModule(QString))); | 77 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this, SLOT(registerNewModule(QString))); | ||
76 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "indiStatusChanged", this, SLOT(setINDICommunicationStatus(Ekos::CommunicationStatus))); | 78 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "indiStatusChanged", this, SLOT(setINDICommunicationStatus(Ekos::CommunicationStatus))); | ||
77 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "ekosStatusChanged", this, SLOT(setEkosCommunicationStatus(Ekos::CommunicationStatus))); | 79 | QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "ekosStatusChanged", this, SLOT(setEkosCommunicationStatus(Ekos::CommunicationStatus))); | ||
78 | 80 | | |||
79 | moon = dynamic_cast<KSMoon *>(KStarsData::Instance()->skyComposite()->findByName("Moon")); | | |||
80 | | ||||
81 | sleepLabel->setPixmap( | 81 | sleepLabel->setPixmap( | ||
82 | QIcon::fromTheme("chronometer").pixmap(QSize(32, 32))); | 82 | QIcon::fromTheme("chronometer").pixmap(QSize(32, 32))); | ||
83 | sleepLabel->hide(); | 83 | sleepLabel->hide(); | ||
84 | 84 | | |||
85 | connect(&sleepTimer, &QTimer::timeout, this, &Scheduler::wakeUpScheduler); | 85 | connect(&sleepTimer, &QTimer::timeout, this, &Scheduler::wakeUpScheduler); | ||
86 | schedulerTimer.setInterval(UPDATE_PERIOD_MS); | 86 | schedulerTimer.setInterval(UPDATE_PERIOD_MS); | ||
87 | jobTimer.setInterval(UPDATE_PERIOD_MS); | 87 | jobTimer.setInterval(UPDATE_PERIOD_MS); | ||
88 | 88 | | |||
▲ Show 20 Lines • Show All 492 Lines • ▼ Show 20 Line(s) | 458 | { | |||
581 | // twilight constraints | 581 | // twilight constraints | ||
582 | job->setEnforceTwilight(twilightCheck->isChecked()); | 582 | job->setEnforceTwilight(twilightCheck->isChecked()); | ||
583 | 583 | | |||
584 | /* Verifications */ | 584 | /* Verifications */ | ||
585 | /* FIXME: perhaps use a method more visible to the end-user */ | 585 | /* FIXME: perhaps use a method more visible to the end-user */ | ||
586 | if (SchedulerJob::START_AT == job->getFileStartupCondition()) | 586 | if (SchedulerJob::START_AT == job->getFileStartupCondition()) | ||
587 | { | 587 | { | ||
588 | /* Warn if appending a job which startup time doesn't allow proper score */ | 588 | /* Warn if appending a job which startup time doesn't allow proper score */ | ||
589 | if (calculateJobScore(job, job->getStartupTime()) < 0) | 589 | if (strategy->calculateJobScore(job, job->getStartupTime()) < 0) | ||
590 | appendLogText(i18n("Warning: job '%1' has startup time %2 resulting in a negative score, and will be marked invalid when processed.", | 590 | appendLogText(i18n("Warning: job '%1' has startup time %2 resulting in a negative score, and will be marked invalid when processed.", | ||
591 | job->getName(), job->getStartupTime().toString(job->getDateTimeDisplayFormat()))); | 591 | job->getName(), job->getStartupTime().toString(job->getDateTimeDisplayFormat()))); | ||
592 | 592 | | |||
593 | } | 593 | } | ||
594 | 594 | | |||
595 | // #3 Completion conditions | 595 | // #3 Completion conditions | ||
596 | if (sequenceCompletionR->isChecked()) | 596 | if (sequenceCompletionR->isChecked()) | ||
597 | { | 597 | { | ||
▲ Show 20 Lines • Show All 725 Lines • ▼ Show 20 Line(s) | 1318 | { | |||
1323 | /* FIXME: it is possible to evaluate jobs while KStars has a time offset, so warn the user about this */ | 1323 | /* FIXME: it is possible to evaluate jobs while KStars has a time offset, so warn the user about this */ | ||
1324 | QDateTime const now = KStarsData::Instance()->lt(); | 1324 | QDateTime const now = KStarsData::Instance()->lt(); | ||
1325 | 1325 | | |||
1326 | /* Start by refreshing the number of captures already present - unneeded if not remembering job progress */ | 1326 | /* Start by refreshing the number of captures already present - unneeded if not remembering job progress */ | ||
1327 | if (Options::rememberJobProgress()) | 1327 | if (Options::rememberJobProgress()) | ||
1328 | updateCompletedJobsCount(); | 1328 | updateCompletedJobsCount(); | ||
1329 | 1329 | | |||
1330 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | 1330 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | ||
1331 | calculateDawnDusk(); | 1331 | strategy->calculateDawnDusk(); | ||
1332 | 1332 | | |||
1333 | /* First, filter out non-schedulable jobs */ | 1333 | /* First, filter out non-schedulable jobs */ | ||
1334 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | 1334 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | ||
1335 | QList<SchedulerJob *> sortedJobs = jobs; | 1335 | QList<SchedulerJob *> sortedJobs = jobs; | ||
1336 | sortedJobs.erase(std::remove_if(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | 1336 | sortedJobs.erase(std::remove_if(sortedJobs.begin(), sortedJobs.end(), [](SchedulerJob * job) | ||
1337 | { | 1337 | { | ||
1338 | return SchedulerJob::JOB_ABORTED < job->getState(); | 1338 | return SchedulerJob::JOB_ABORTED < job->getState(); | ||
1339 | }), sortedJobs.end()); | 1339 | }), sortedJobs.end()); | ||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Line(s) | 1343 | { | |||
1428 | job->setState(SchedulerJob::JOB_EVALUATION); | 1428 | job->setState(SchedulerJob::JOB_EVALUATION); | ||
1429 | } | 1429 | } | ||
1430 | 1430 | | |||
1431 | /* | 1431 | /* | ||
1432 | * At this step, we prepare scheduling of jobs. | 1432 | * At this step, we prepare scheduling of jobs. | ||
1433 | * We filter out jobs that won't run now, and make sure jobs are not all starting at the same time. | 1433 | * We filter out jobs that won't run now, and make sure jobs are not all starting at the same time. | ||
1434 | */ | 1434 | */ | ||
1435 | 1435 | | |||
1436 | updatePreDawn(); | 1436 | strategy->updatePreDawn(); | ||
1437 | 1437 | | |||
1438 | /* This predicate matches jobs not being evaluated and not aborted */ | 1438 | /* This predicate matches jobs not being evaluated and not aborted */ | ||
1439 | auto neither_evaluated_nor_aborted = [](SchedulerJob const * const job) | 1439 | auto neither_evaluated_nor_aborted = [](SchedulerJob const * const job) | ||
1440 | { | 1440 | { | ||
1441 | SchedulerJob::JOBStatus const s = job->getState(); | 1441 | SchedulerJob::JOBStatus const s = job->getState(); | ||
1442 | return SchedulerJob::JOB_EVALUATION != s && SchedulerJob::JOB_ABORTED != s; | 1442 | return SchedulerJob::JOB_EVALUATION != s && SchedulerJob::JOB_ABORTED != s; | ||
1443 | }; | 1443 | }; | ||
1444 | 1444 | | |||
▲ Show 20 Lines • Show All 138 Lines • ▼ Show 20 Line(s) | 1581 | { | |||
1583 | 1583 | | |||
1584 | 1584 | | |||
1585 | appendLogText(i18n("Warning: job '%1' has fixed startup time %2 set in the past, marking invalid.", | 1585 | appendLogText(i18n("Warning: job '%1' has fixed startup time %2 set in the past, marking invalid.", | ||
1586 | currentJob->getName(), currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat()))); | 1586 | currentJob->getName(), currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat()))); | ||
1587 | 1587 | | |||
1588 | break; | 1588 | break; | ||
1589 | } | 1589 | } | ||
1590 | // Check whether the current job has a positive dark sky score at the time of startup | 1590 | // Check whether the current job has a positive dark sky score at the time of startup | ||
1591 | else if (true == currentJob->getEnforceTwilight() && getDarkSkyScore(currentJob->getStartupTime()) < 0) | 1591 | else if (true == currentJob->getEnforceTwilight() && strategy->getDarkSkyScore(currentJob->getStartupTime()) < 0) | ||
1592 | { | 1592 | { | ||
1593 | currentJob->setState(SchedulerJob::JOB_INVALID); | 1593 | currentJob->setState(SchedulerJob::JOB_INVALID); | ||
1594 | 1594 | | |||
1595 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its twilight restriction, marking invalid.", | 1595 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its twilight restriction, marking invalid.", | ||
1596 | currentJob->getName())); | 1596 | currentJob->getName())); | ||
1597 | 1597 | | |||
1598 | break; | 1598 | break; | ||
1599 | } | 1599 | } | ||
1600 | // Check whether the current job has a positive altitude score at the time of startup | 1600 | // Check whether the current job has a positive altitude score at the time of startup | ||
1601 | else if (-90 < currentJob->getMinAltitude() && getAltitudeScore(currentJob, currentJob->getStartupTime()) < 0) | 1601 | else if (-90 < currentJob->getMinAltitude() && strategy->getAltitudeScore(currentJob, currentJob->getStartupTime()) < 0) | ||
1602 | { | 1602 | { | ||
1603 | currentJob->setState(SchedulerJob::JOB_INVALID); | 1603 | currentJob->setState(SchedulerJob::JOB_INVALID); | ||
1604 | 1604 | | |||
1605 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its altitude restriction, marking invalid.", | 1605 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its altitude restriction, marking invalid.", | ||
1606 | currentJob->getName())); | 1606 | currentJob->getName())); | ||
1607 | 1607 | | |||
1608 | break; | 1608 | break; | ||
1609 | } | 1609 | } | ||
1610 | // Check whether the current job has a positive Moon separation score at the time of startup | 1610 | // Check whether the current job has a positive Moon separation score at the time of startup | ||
1611 | else if (0 < currentJob->getMinMoonSeparation() && getMoonSeparationScore(currentJob, currentJob->getStartupTime()) < 0) | 1611 | else if (0 < currentJob->getMinMoonSeparation() && strategy->getMoonSeparationScore(currentJob, currentJob->getStartupTime()) < 0) | ||
1612 | { | 1612 | { | ||
1613 | currentJob->setState(SchedulerJob::JOB_INVALID); | 1613 | currentJob->setState(SchedulerJob::JOB_INVALID); | ||
1614 | 1614 | | |||
1615 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its Moon separation restriction, marking invalid.", | 1615 | appendLogText(i18n("Warning: job '%1' has a fixed start time incompatible with its Moon separation restriction, marking invalid.", | ||
1616 | currentJob->getName())); | 1616 | currentJob->getName())); | ||
1617 | 1617 | | |||
1618 | break; | 1618 | break; | ||
1619 | } | 1619 | } | ||
Show All 14 Lines | 1629 | { | |||
1634 | 1634 | | |||
1635 | break; | 1635 | break; | ||
1636 | } | 1636 | } | ||
1637 | 1637 | | |||
1638 | currentJob->setLeadTime(previousJob->getCompletionTime().secsTo(currentJob->getStartupTime())); | 1638 | currentJob->setLeadTime(previousJob->getCompletionTime().secsTo(currentJob->getStartupTime())); | ||
1639 | } | 1639 | } | ||
1640 | 1640 | | |||
1641 | // This job is non-movable, we're done | 1641 | // This job is non-movable, we're done | ||
1642 | currentJob->setScore(calculateJobScore(currentJob, now)); | 1642 | currentJob->setScore(strategy->calculateJobScore(currentJob, now)); | ||
1643 | currentJob->setState(SchedulerJob::JOB_SCHEDULED); | 1643 | currentJob->setState(SchedulerJob::JOB_SCHEDULED); | ||
1644 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with fixed startup time requirement.") | 1644 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with fixed startup time requirement.") | ||
1645 | .arg(currentJob->getName()) | 1645 | .arg(currentJob->getName()) | ||
1646 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())); | 1646 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())); | ||
1647 | 1647 | | |||
1648 | break; | 1648 | break; | ||
1649 | } | 1649 | } | ||
1650 | 1650 | | |||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Line(s) | |||||
1708 | 1708 | | |||
1709 | if (currentJob->getEnforceTwilight()) | 1709 | if (currentJob->getEnforceTwilight()) | ||
1710 | { | 1710 | { | ||
1711 | // During that check, we don't verify the current job can actually complete before dawn. | 1711 | // During that check, we don't verify the current job can actually complete before dawn. | ||
1712 | // If the job is interrupted while running, it will be aborted and rescheduled at a later time. | 1712 | // If the job is interrupted while running, it will be aborted and rescheduled at a later time. | ||
1713 | 1713 | | |||
1714 | // We wouldn't start observation 30 mins (default) before dawn. | 1714 | // We wouldn't start observation 30 mins (default) before dawn. | ||
1715 | // FIXME: Refactor duplicated dawn/dusk calculations | 1715 | // FIXME: Refactor duplicated dawn/dusk calculations | ||
1716 | double const earlyDawn = Dawn - Options::preDawnTime() / (60.0 * 24.0); | 1716 | double const earlyDawn = strategy->getDawn() - Options::preDawnTime() / (60.0 * 24.0); | ||
1717 | 1717 | | |||
1718 | // Compute dawn time for the startup date of the job | 1718 | // Compute dawn time for the startup date of the job | ||
1719 | // FIXME: Use KAlmanac to find the real dawn/dusk time for the day the job is supposed to be processed | 1719 | // FIXME: Use KAlmanac to find the real dawn/dusk time for the day the job is supposed to be processed | ||
1720 | QDateTime const dawnDateTime(currentJob->getStartupTime().date(), QTime(0, 0).addSecs(earlyDawn * 24 * 3600)); | 1720 | QDateTime const dawnDateTime(currentJob->getStartupTime().date(), QTime(0, 0).addSecs(earlyDawn * 24 * 3600)); | ||
1721 | 1721 | | |||
1722 | // Check if the job starts after dawn | 1722 | // Check if the job starts after dawn | ||
1723 | if (dawnDateTime < currentJob->getStartupTime()) | 1723 | if (dawnDateTime < currentJob->getStartupTime()) | ||
1724 | { | 1724 | { | ||
1725 | // Compute dusk time for the startup date of the job - no lead time on dusk | 1725 | // Compute dusk time for the startup date of the job - no lead time on dusk | ||
1726 | QDateTime const duskDateTime(currentJob->getStartupTime().date(), QTime(0, 0).addSecs(Dusk * 24 * 3600)); | 1726 | QDateTime const duskDateTime(currentJob->getStartupTime().date(), QTime(0, 0).addSecs(strategy->getDusk() * 24 * 3600)); | ||
1727 | 1727 | | |||
1728 | // Check if the job starts before dusk | 1728 | // Check if the job starts before dusk | ||
1729 | if (currentJob->getStartupTime() < duskDateTime) | 1729 | if (currentJob->getStartupTime() < duskDateTime) | ||
1730 | { | 1730 | { | ||
1731 | // Delay job to next dusk - we will check other requirements later on | 1731 | // Delay job to next dusk - we will check other requirements later on | ||
1732 | currentJob->setStartupTime(duskDateTime); | 1732 | currentJob->setStartupTime(duskDateTime); | ||
1733 | 1733 | | |||
1734 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with night time requirement.") | 1734 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with night time requirement.") | ||
1735 | .arg(currentJob->getName()) | 1735 | .arg(currentJob->getName()) | ||
1736 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())); | 1736 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())); | ||
1737 | 1737 | | |||
1738 | continue; | 1738 | continue; | ||
1739 | } | 1739 | } | ||
1740 | } | 1740 | } | ||
1741 | 1741 | | |||
1742 | // Compute dawn time for the day following the startup time, but disregard the pre-dawn offset as we'll consider completion | 1742 | // Compute dawn time for the day following the startup time, but disregard the pre-dawn offset as we'll consider completion | ||
1743 | // FIXME: Use KAlmanac to find the real dawn/dusk time for the day next to the day the job is supposed to be processed | 1743 | // FIXME: Use KAlmanac to find the real dawn/dusk time for the day next to the day the job is supposed to be processed | ||
1744 | QDateTime const nextDawnDateTime(currentJob->getStartupTime().date().addDays(1), QTime(0, 0).addSecs(Dawn * 24 * 3600)); | 1744 | QDateTime const nextDawnDateTime(currentJob->getStartupTime().date().addDays(1), QTime(0, 0).addSecs(strategy->getDawn() * 24 * 3600)); | ||
1745 | 1745 | | |||
1746 | // Check if the completion date overlaps the next dawn, and issue a warning if so | 1746 | // Check if the completion date overlaps the next dawn, and issue a warning if so | ||
1747 | if (nextDawnDateTime < currentJob->getCompletionTime()) | 1747 | if (nextDawnDateTime < currentJob->getCompletionTime()) | ||
1748 | { | 1748 | { | ||
1749 | appendLogText(i18n("Warning: job '%1' execution overlaps daylight, it will be interrupted at dawn and rescheduled on next night time.", | 1749 | appendLogText(i18n("Warning: job '%1' execution overlaps daylight, it will be interrupted at dawn and rescheduled on next night time.", | ||
1750 | currentJob->getName())); | 1750 | currentJob->getName())); | ||
1751 | } | 1751 | } | ||
1752 | 1752 | | |||
1753 | 1753 | | |||
1754 | Q_ASSERT_X(0 <= getDarkSkyScore(currentJob->getStartupTime()), __FUNCTION__, "Consolidated startup time results in a positive dark sky score."); | 1754 | Q_ASSERT_X(0 <= strategy->getDarkSkyScore(currentJob->getStartupTime()), __FUNCTION__, "Consolidated startup time results in a positive dark sky score."); | ||
1755 | } | 1755 | } | ||
1756 | 1756 | | |||
1757 | 1757 | | |||
1758 | // ----- #4 Should we delay the current job because of its target culmination? | 1758 | // ----- #4 Should we delay the current job because of its target culmination? | ||
1759 | // | 1759 | // | ||
1760 | // Culmination uses the transit time, and fixes the startup time of the job to a particular offset around this transit time. | 1760 | // Culmination uses the transit time, and fixes the startup time of the job to a particular offset around this transit time. | ||
1761 | // This restriction may be used to start a job at the least air mass, or after a meridian flip. | 1761 | // This restriction may be used to start a job at the least air mass, or after a meridian flip. | ||
1762 | // Culmination is scheduled before altitude restriction because it is normally more restrictive for the resulting startup time. | 1762 | // Culmination is scheduled before altitude restriction because it is normally more restrictive for the resulting startup time. | ||
1763 | // It may happen that a target cannot rise enough to comply with the altitude restriction, but a culmination time is always valid. | 1763 | // It may happen that a target cannot rise enough to comply with the altitude restriction, but a culmination time is always valid. | ||
1764 | 1764 | | |||
1765 | if (SchedulerJob::START_CULMINATION == currentJob->getFileStartupCondition()) | 1765 | if (SchedulerJob::START_CULMINATION == currentJob->getFileStartupCondition()) | ||
1766 | { | 1766 | { | ||
1767 | // Consolidate the culmination time, with offset, of the current job | 1767 | // Consolidate the culmination time, with offset, of the current job | ||
1768 | QDateTime const nextCulminationTime = calculateCulmination(currentJob, currentJob->getCulminationOffset(), currentJob->getStartupTime()); | 1768 | QDateTime const nextCulminationTime = strategy->calculateCulmination(currentJob, currentJob->getCulminationOffset(), currentJob->getStartupTime()); | ||
1769 | 1769 | | |||
1770 | if (nextCulminationTime.isValid()) // Guaranteed | 1770 | if (nextCulminationTime.isValid()) // Guaranteed | ||
1771 | { | 1771 | { | ||
1772 | if (currentJob->getStartupTime() < nextCulminationTime) | 1772 | if (currentJob->getStartupTime() < nextCulminationTime) | ||
1773 | { | 1773 | { | ||
1774 | currentJob->setStartupTime(nextCulminationTime); | 1774 | currentJob->setStartupTime(nextCulminationTime); | ||
1775 | 1775 | | |||
1776 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with culmination requirements.") | 1776 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with culmination requirements.") | ||
Show All 24 Lines | |||||
1801 | // Altitude time ensures the job is assigned a startup time when its target is high enough. | 1801 | // Altitude time ensures the job is assigned a startup time when its target is high enough. | ||
1802 | // As other restrictions, the altitude is only considered for startup time, completion time is managed while the job is running. | 1802 | // As other restrictions, the altitude is only considered for startup time, completion time is managed while the job is running. | ||
1803 | // Because a target setting down is a problem for the schedule, a cutoff altitude is added in the case the job target is past the meridian at startup time. | 1803 | // Because a target setting down is a problem for the schedule, a cutoff altitude is added in the case the job target is past the meridian at startup time. | ||
1804 | // FIXME: though arguable, Moon separation is also considered in that restriction check - move it to a separate case. | 1804 | // FIXME: though arguable, Moon separation is also considered in that restriction check - move it to a separate case. | ||
1805 | 1805 | | |||
1806 | if (-90 < currentJob->getMinAltitude()) | 1806 | if (-90 < currentJob->getMinAltitude()) | ||
1807 | { | 1807 | { | ||
1808 | // Consolidate a new altitude time from the startup time of the current job | 1808 | // Consolidate a new altitude time from the startup time of the current job | ||
1809 | QDateTime const nextAltitudeTime = calculateAltitudeTime(currentJob, currentJob->getMinAltitude(), currentJob->getMinMoonSeparation(), currentJob->getStartupTime()); | 1809 | QDateTime const nextAltitudeTime = strategy->calculateAltitudeTime(currentJob, currentJob->getMinAltitude(), currentJob->getMinMoonSeparation(), currentJob->getStartupTime()); | ||
1810 | 1810 | | |||
1811 | if (nextAltitudeTime.isValid()) | 1811 | if (nextAltitudeTime.isValid()) | ||
1812 | { | 1812 | { | ||
1813 | if (currentJob->getStartupTime() < nextAltitudeTime) | 1813 | if (currentJob->getStartupTime() < nextAltitudeTime) | ||
1814 | { | 1814 | { | ||
1815 | currentJob->setStartupTime(nextAltitudeTime); | 1815 | currentJob->setStartupTime(nextAltitudeTime); | ||
1816 | 1816 | | |||
1817 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with altitude and Moon separation requirements.") | 1817 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with altitude and Moon separation requirements.") | ||
Show All 12 Lines | 1828 | appendLogText(i18n("Warning: job '%1' requires minimum altitude %2 and Moon separation %3, not achievable, marking invalid.", | |||
1830 | QString("%L1").arg(static_cast<double>(currentJob->getMinAltitude()), 0, 'f', minAltitude->decimals()), | 1830 | QString("%L1").arg(static_cast<double>(currentJob->getMinAltitude()), 0, 'f', minAltitude->decimals()), | ||
1831 | 0.0 < currentJob->getMinMoonSeparation() ? | 1831 | 0.0 < currentJob->getMinMoonSeparation() ? | ||
1832 | QString("%L1").arg(static_cast<double>(currentJob->getMinMoonSeparation()), 0, 'f', minMoonSeparation->decimals()) : | 1832 | QString("%L1").arg(static_cast<double>(currentJob->getMinMoonSeparation()), 0, 'f', minMoonSeparation->decimals()) : | ||
1833 | QString("-"))); | 1833 | QString("-"))); | ||
1834 | 1834 | | |||
1835 | break; | 1835 | break; | ||
1836 | } | 1836 | } | ||
1837 | 1837 | | |||
1838 | Q_ASSERT_X(0 <= getAltitudeScore(currentJob, currentJob->getStartupTime()), __FUNCTION__, "Consolidated altitude time results in a positive altitude score."); | 1838 | Q_ASSERT_X(0 <= strategy->getAltitudeScore(currentJob, currentJob->getStartupTime()), __FUNCTION__, "Consolidated altitude time results in a positive altitude score."); | ||
1839 | } | 1839 | } | ||
1840 | 1840 | | |||
1841 | 1841 | | |||
1842 | // ----- #6 Should we reject the current job because it overlaps the next job and that next job is not movable? | 1842 | // ----- #6 Should we reject the current job because it overlaps the next job and that next job is not movable? | ||
1843 | // | 1843 | // | ||
1844 | // If we have a blocker next to the current job, we compare the completion time of the current job and the startup time of this next job, taking lead time into account. | 1844 | // If we have a blocker next to the current job, we compare the completion time of the current job and the startup time of this next job, taking lead time into account. | ||
1845 | // This verification obviously relies on the imaging time to be reliable, but there's not much we can do at this stage of the implementation. | 1845 | // This verification obviously relies on the imaging time to be reliable, but there's not much we can do at this stage of the implementation. | ||
1846 | 1846 | | |||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Line(s) | |||||
1897 | // currentJob->setState(SchedulerJob::JOB_ABORTED); | 1897 | // currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
1898 | // | 1898 | // | ||
1899 | // appendLogText(i18n("Job '%1' cannot run now because of bad weather, marking aborted.", currentJob->getName())); | 1899 | // appendLogText(i18n("Job '%1' cannot run now because of bad weather, marking aborted.", currentJob->getName())); | ||
1900 | //} | 1900 | //} | ||
1901 | 1901 | | |||
1902 | 1902 | | |||
1903 | // ----- #9 Update score for current time and mark evaluating jobs as scheduled | 1903 | // ----- #9 Update score for current time and mark evaluating jobs as scheduled | ||
1904 | 1904 | | |||
1905 | currentJob->setScore(calculateJobScore(currentJob, now)); | 1905 | currentJob->setScore(strategy->calculateJobScore(currentJob, now)); | ||
1906 | currentJob->setState(SchedulerJob::JOB_SCHEDULED); | 1906 | currentJob->setState(SchedulerJob::JOB_SCHEDULED); | ||
1907 | 1907 | | |||
1908 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' on row #%2 passed all checks after %3 attempts, will proceed at %4 for approximately %5 seconds, marking scheduled") | 1908 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' on row #%2 passed all checks after %3 attempts, will proceed at %4 for approximately %5 seconds, marking scheduled") | ||
1909 | .arg(currentJob->getName()) | 1909 | .arg(currentJob->getName()) | ||
1910 | .arg(index + 1) | 1910 | .arg(index + 1) | ||
1911 | .arg(attempt) | 1911 | .arg(attempt) | ||
1912 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())) | 1912 | .arg(currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat())) | ||
1913 | .arg(currentJob->getEstimatedTime()); | 1913 | .arg(currentJob->getEstimatedTime()); | ||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Line(s) | 2001 | { | |||
2003 | setCurrentJob(nullptr); | 2003 | setCurrentJob(nullptr); | ||
2004 | jobEvaluationOnly = false; | 2004 | jobEvaluationOnly = false; | ||
2005 | return; | 2005 | return; | ||
2006 | } | 2006 | } | ||
2007 | 2007 | | |||
2008 | /* Check if job can be processed right now */ | 2008 | /* Check if job can be processed right now */ | ||
2009 | SchedulerJob * const job_to_execute = *job_to_execute_iterator; | 2009 | SchedulerJob * const job_to_execute = *job_to_execute_iterator; | ||
2010 | if (job_to_execute->getFileStartupCondition() == SchedulerJob::START_ASAP) | 2010 | if (job_to_execute->getFileStartupCondition() == SchedulerJob::START_ASAP) | ||
2011 | if( 0 <= calculateJobScore(job_to_execute, now)) | 2011 | if( 0 <= strategy->calculateJobScore(job_to_execute, now)) | ||
2012 | job_to_execute->setStartupTime(now); | 2012 | job_to_execute->setStartupTime(now); | ||
2013 | 2013 | | |||
2014 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is selected for next observation with priority #%2 and score %3.") | 2014 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is selected for next observation with priority #%2 and score %3.") | ||
2015 | .arg(job_to_execute->getName()) | 2015 | .arg(job_to_execute->getName()) | ||
2016 | .arg(job_to_execute->getPriority()) | 2016 | .arg(job_to_execute->getPriority()) | ||
2017 | .arg(job_to_execute->getScore()); | 2017 | .arg(job_to_execute->getScore()); | ||
2018 | 2018 | | |||
2019 | // Set the current job, and let the status timer execute it when ready | 2019 | // Set the current job, and let the status timer execute it when ready | ||
Show All 17 Lines | 2036 | if (state == SCHEDULER_RUNNIG) | |||
2037 | appendLogText(i18n("Scheduler is awake. Jobs shall be started when ready...")); | 2037 | appendLogText(i18n("Scheduler is awake. Jobs shall be started when ready...")); | ||
2038 | else | 2038 | else | ||
2039 | appendLogText(i18n("Scheduler is awake. Jobs shall be started when scheduler is resumed.")); | 2039 | appendLogText(i18n("Scheduler is awake. Jobs shall be started when scheduler is resumed.")); | ||
2040 | 2040 | | |||
2041 | schedulerTimer.start(); | 2041 | schedulerTimer.start(); | ||
2042 | } | 2042 | } | ||
2043 | } | 2043 | } | ||
2044 | 2044 | | |||
2045 | double Scheduler::findAltitude(const SkyPoint &target, const QDateTime &when, bool * is_setting, bool debug) | | |||
2046 | { | | |||
2047 | // FIXME: block calculating target coordinates at a particular time is duplicated in several places | | |||
2048 | | ||||
2049 | GeoLocation * const geo = KStarsData::Instance()->geo(); | | |||
2050 | | ||||
2051 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2052 | KStarsDateTime ltWhen(when.isValid() ? | | |||
2053 | Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : | | |||
2054 | KStarsData::Instance()->lt()); | | |||
2055 | | ||||
2056 | // Create a sky object with the target catalog coordinates | | |||
2057 | SkyObject o; | | |||
2058 | o.setRA0(target.ra0()); | | |||
2059 | o.setDec0(target.dec0()); | | |||
2060 | | ||||
2061 | // Update RA/DEC of the target for the current fraction of the day | | |||
2062 | KSNumbers numbers(ltWhen.djd()); | | |||
2063 | o.updateCoordsNow(&numbers); | | |||
2064 | | ||||
2065 | // Calculate alt/az coordinates using KStars instance's geolocation | | |||
2066 | CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); | | |||
2067 | o.EquatorialToHorizontal(&LST, geo->lat()); | | |||
2068 | | ||||
2069 | // Hours are reduced to [0,24[, meridian being at 0 | | |||
2070 | double offset = LST.Hours() - o.ra().Hours(); | | |||
2071 | if (24.0 <= offset) | | |||
2072 | offset -= 24.0; | | |||
2073 | else if (offset < 0.0) | | |||
2074 | offset += 24.0; | | |||
2075 | bool const passed_meridian = 0.0 <= offset && offset < 12.0; | | |||
2076 | | ||||
2077 | if (debug) | | |||
2078 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("When:%9 LST:%8 RA:%1 RA0:%2 DEC:%3 DEC0:%4 alt:%5 setting:%6 HA:%7") | | |||
2079 | .arg(o.ra().toHMSString()) | | |||
2080 | .arg(o.ra0().toHMSString()) | | |||
2081 | .arg(o.dec().toHMSString()) | | |||
2082 | .arg(o.dec0().toHMSString()) | | |||
2083 | .arg(o.alt().Degrees()) | | |||
2084 | .arg(passed_meridian ? "yes" : "no") | | |||
2085 | .arg(o.ra().Hours()) | | |||
2086 | .arg(LST.toHMSString()) | | |||
2087 | .arg(ltWhen.toString("HH:mm:ss")); | | |||
2088 | | ||||
2089 | if (is_setting) | | |||
2090 | *is_setting = passed_meridian; | | |||
2091 | | ||||
2092 | return o.alt().Degrees(); | | |||
2093 | } | | |||
2094 | | ||||
2095 | QDateTime Scheduler::calculateAltitudeTime(SchedulerJob const *job, double minAltitude, double minMoonAngle, QDateTime const &when) const | | |||
2096 | { | | |||
2097 | // FIXME: block calculating target coordinates at a particular time is duplicated in several places | | |||
2098 | | ||||
2099 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2100 | KStarsDateTime ltWhen(when.isValid() ? | | |||
2101 | Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : | | |||
2102 | KStarsData::Instance()->lt()); | | |||
2103 | | ||||
2104 | // Create a sky object with the target catalog coordinates | | |||
2105 | SkyPoint const target = job->getTargetCoords(); | | |||
2106 | SkyObject o; | | |||
2107 | o.setRA0(target.ra0()); | | |||
2108 | o.setDec0(target.dec0()); | | |||
2109 | | ||||
2110 | // Calculate the UT at the argument time | | |||
2111 | KStarsDateTime const ut = geo->LTtoUT(ltWhen); | | |||
2112 | | ||||
2113 | double const SETTING_ALTITUDE_CUTOFF = Options::settingAltitudeCutoff(); | | |||
2114 | | ||||
2115 | // Within the next 24 hours, search when the job target matches the altitude and moon constraints | | |||
2116 | for (unsigned int minute = 0; minute < 24 * 60; minute++) | | |||
2117 | { | | |||
2118 | KStarsDateTime const ltOffset(ltWhen.addSecs(minute * 60)); | | |||
2119 | | ||||
2120 | // Update RA/DEC of the target for the current fraction of the day | | |||
2121 | KSNumbers numbers(ltOffset.djd()); | | |||
2122 | o.updateCoordsNow(&numbers); | | |||
2123 | | ||||
2124 | // Compute local sidereal time for the current fraction of the day, calculate altitude | | |||
2125 | CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltOffset).gst()); | | |||
2126 | o.EquatorialToHorizontal(&LST, geo->lat()); | | |||
2127 | double const altitude = o.alt().Degrees(); | | |||
2128 | | ||||
2129 | if (minAltitude <= altitude) | | |||
2130 | { | | |||
2131 | // Don't test proximity to dawn in this situation, we only cater for altitude here | | |||
2132 | | ||||
2133 | // Continue searching if Moon separation is not good enough | | |||
2134 | if (0 < minMoonAngle && getMoonSeparationScore(job, ltOffset) < 0) | | |||
2135 | continue; | | |||
2136 | | ||||
2137 | // Continue searching if target is setting and under the cutoff | | |||
2138 | double offset = LST.Hours() - o.ra().Hours(); | | |||
2139 | if (24.0 <= offset) | | |||
2140 | offset -= 24.0; | | |||
2141 | else if (offset < 0.0) | | |||
2142 | offset += 24.0; | | |||
2143 | if (0.0 <= offset && offset < 12.0) | | |||
2144 | if (altitude - SETTING_ALTITUDE_CUTOFF < minAltitude) | | |||
2145 | continue; | | |||
2146 | | ||||
2147 | return ltOffset; | | |||
2148 | } | | |||
2149 | } | | |||
2150 | | ||||
2151 | return QDateTime(); | | |||
2152 | } | | |||
2153 | | ||||
2154 | QDateTime Scheduler::calculateCulmination(SchedulerJob const *job, int offset_minutes, QDateTime const &when) const | | |||
2155 | { | | |||
2156 | // FIXME: culmination calculation is a min altitude requirement, should be an interval altitude requirement | | |||
2157 | // FIXME: block calculating target coordinates at a particular time is duplicated in calculateCulmination | | |||
2158 | | ||||
2159 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2160 | KStarsDateTime ltWhen(when.isValid() ? | | |||
2161 | Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : | | |||
2162 | KStarsData::Instance()->lt()); | | |||
2163 | | ||||
2164 | // Create a sky object with the target catalog coordinates | | |||
2165 | SkyPoint const target = job->getTargetCoords(); | | |||
2166 | SkyObject o; | | |||
2167 | o.setRA0(target.ra0()); | | |||
2168 | o.setDec0(target.dec0()); | | |||
2169 | | ||||
2170 | // Update RA/DEC for the argument date/time | | |||
2171 | KSNumbers numbers(ltWhen.djd()); | | |||
2172 | o.updateCoordsNow(&numbers); | | |||
2173 | | ||||
2174 | // Calculate transit date/time at the argument date - transitTime requires UT and returns LocalTime | | |||
2175 | KStarsDateTime transitDateTime(ltWhen.date(), o.transitTime(geo->LTtoUT(ltWhen), geo), Qt::LocalTime); | | |||
2176 | | ||||
2177 | // Shift transit date/time by the argument offset | | |||
2178 | KStarsDateTime observationDateTime = transitDateTime.addSecs(offset_minutes * 60); | | |||
2179 | | ||||
2180 | // Relax observation time, culmination calculation is stable at minute only | | |||
2181 | KStarsDateTime relaxedDateTime = observationDateTime.addSecs(Options::leadTime() * 60); | | |||
2182 | | ||||
2183 | // Verify resulting observation time is under lead time vs. argument time | | |||
2184 | // If sooner, delay by 8 hours to get to the next transit - perhaps in a third call | | |||
2185 | if (relaxedDateTime < ltWhen) | | |||
2186 | { | | |||
2187 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' startup %2 is posterior to transit %3, shifting by 8 hours.") | | |||
2188 | .arg(job->getName()) | | |||
2189 | .arg(ltWhen.toString(job->getDateTimeDisplayFormat())) | | |||
2190 | .arg(relaxedDateTime.toString(job->getDateTimeDisplayFormat())); | | |||
2191 | | ||||
2192 | return calculateCulmination(job, offset_minutes, when.addSecs(8 * 60 * 60)); | | |||
2193 | } | | |||
2194 | | ||||
2195 | // Guarantees - culmination calculation is stable at minute level, so relax by lead time | | |||
2196 | Q_ASSERT_X(observationDateTime.isValid(), __FUNCTION__, "Observation time for target culmination is valid."); | | |||
2197 | Q_ASSERT_X(ltWhen <= relaxedDateTime, __FUNCTION__, "Observation time for target culmination is at or after than argument time"); | | |||
2198 | | ||||
2199 | // Return consolidated culmination time | | |||
2200 | return Qt::UTC == observationDateTime.timeSpec() ? geo->UTtoLT(observationDateTime) : observationDateTime; | | |||
2201 | } | | |||
2202 | | ||||
2203 | int16_t Scheduler::getWeatherScore() const | | |||
2204 | { | | |||
2205 | if (weatherCheck->isEnabled() == false || weatherCheck->isChecked() == false) | | |||
2206 | return 0; | | |||
2207 | | ||||
2208 | if (weatherStatus == IPS_BUSY) | | |||
2209 | return BAD_SCORE / 2; | | |||
2210 | else if (weatherStatus == IPS_ALERT) | | |||
2211 | return BAD_SCORE; | | |||
2212 | | ||||
2213 | return 0; | | |||
2214 | } | | |||
2215 | | ||||
2216 | int16_t Scheduler::getDarkSkyScore(QDateTime const &when) const | | |||
2217 | { | | |||
2218 | double const secsPerDay = 24.0 * 3600.0; | | |||
2219 | double const minsPerDay = 24.0 * 60.0; | | |||
2220 | | ||||
2221 | // Dark sky score is calculated based on distance to today's dawn and next dusk. | | |||
2222 | // Option "Pre-dawn Time" avoids executing a job when dawn is approaching, and is a value in minutes. | | |||
2223 | // - If observation is between option "Pre-dawn Time" and dawn, score is BAD_SCORE/50. | | |||
2224 | // - If observation is before dawn today, score is fraction of the day from beginning of observation to dawn time, as percentage. | | |||
2225 | // - If observation is after dusk, score is fraction of the day from dusk to beginning of observation, as percentage. | | |||
2226 | // - If observation is between dawn and dusk, score is BAD_SCORE. | | |||
2227 | // | | |||
2228 | // If observation time is invalid, the score is calculated for the current day time. | | |||
2229 | // Note exact dusk time is considered valid in terms of night time, and will return a positive, albeit null, score. | | |||
2230 | | ||||
2231 | // FIXME: Dark sky score should consider the middle of the local night as best value. | | |||
2232 | // FIXME: Current algorithm uses the dawn and dusk of today, instead of the day of the observation. | | |||
2233 | | ||||
2234 | int const earlyDawnSecs = static_cast <int> ((Dawn - static_cast <double> (Options::preDawnTime()) / minsPerDay) * secsPerDay); | | |||
2235 | int const dawnSecs = static_cast <int> (Dawn * secsPerDay); | | |||
2236 | int const duskSecs = static_cast <int> (Dusk * secsPerDay); | | |||
2237 | int const obsSecs = (when.isValid() ? when : KStarsData::Instance()->lt()).time().msecsSinceStartOfDay() / 1000; | | |||
2238 | | ||||
2239 | int16_t score = 0; | | |||
2240 | | ||||
2241 | if (earlyDawnSecs <= obsSecs && obsSecs < dawnSecs) | | |||
2242 | { | | |||
2243 | score = BAD_SCORE / 50; | | |||
2244 | | ||||
2245 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Dark sky score at %1 is %2 (between pre-dawn and dawn).") | | |||
2246 | // .arg(observationDateTime.toString()) | | |||
2247 | // .arg(QString::asprintf("%+d", score)); | | |||
2248 | } | | |||
2249 | else if (obsSecs < dawnSecs) | | |||
2250 | { | | |||
2251 | score = static_cast <int16_t> ((dawnSecs - obsSecs) / secsPerDay) * 100; | | |||
2252 | | ||||
2253 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Dark sky score at %1 is %2 (before dawn).") | | |||
2254 | // .arg(observationDateTime.toString()) | | |||
2255 | // .arg(QString::asprintf("%+d", score)); | | |||
2256 | } | | |||
2257 | else if (duskSecs <= obsSecs) | | |||
2258 | { | | |||
2259 | score = static_cast <int16_t> ((obsSecs - duskSecs) / secsPerDay) * 100; | | |||
2260 | | ||||
2261 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Dark sky score at %1 is %2 (after dusk).") | | |||
2262 | // .arg(observationDateTime.toString()) | | |||
2263 | // .arg(QString::asprintf("%+d", score)); | | |||
2264 | } | | |||
2265 | else | | |||
2266 | { | | |||
2267 | score = BAD_SCORE; | | |||
2268 | | ||||
2269 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Dark sky score at %1 is %2 (during daylight).") | | |||
2270 | // .arg(observationDateTime.toString()) | | |||
2271 | // .arg(QString::asprintf("%+d", score)); | | |||
2272 | } | | |||
2273 | | ||||
2274 | return score; | | |||
2275 | } | | |||
2276 | | ||||
2277 | int16_t Scheduler::calculateJobScore(SchedulerJob const *job, QDateTime const &when) const | | |||
2278 | { | | |||
2279 | if (nullptr == job) | | |||
2280 | return BAD_SCORE; | | |||
2281 | | ||||
2282 | /* Only consolidate the score if light frames are required, calibration frames can run whenever needed */ | | |||
2283 | if (!job->getLightFramesRequired()) | | |||
2284 | return 1000; | | |||
2285 | | ||||
2286 | int16_t total = 0; | | |||
2287 | | ||||
2288 | /* As soon as one score is negative, it's a no-go and other scores are unneeded */ | | |||
2289 | | ||||
2290 | if (job->getEnforceTwilight()) | | |||
2291 | { | | |||
2292 | int16_t const darkSkyScore = getDarkSkyScore(when); | | |||
2293 | | ||||
2294 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' dark sky score is %2 at %3") | | |||
2295 | .arg(job->getName()) | | |||
2296 | .arg(QString::asprintf("%+d", darkSkyScore)) | | |||
2297 | .arg(when.toString(job->getDateTimeDisplayFormat())); | | |||
2298 | | ||||
2299 | total += darkSkyScore; | | |||
2300 | } | | |||
2301 | | ||||
2302 | /* We still enforce altitude if the job is neither required to track nor guide, because this is too confusing for the end-user. | | |||
2303 | * If we bypass calculation here, it must also be bypassed when checking job constraints in checkJobStage. | | |||
2304 | */ | | |||
2305 | if (0 <= total /*&& ((job->getStepPipeline() & SchedulerJob::USE_TRACK) || (job->getStepPipeline() & SchedulerJob::USE_GUIDE))*/) | | |||
2306 | { | | |||
2307 | int16_t const altitudeScore = getAltitudeScore(job, when); | | |||
2308 | | ||||
2309 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' altitude score is %2 at %3") | | |||
2310 | .arg(job->getName()) | | |||
2311 | .arg(QString::asprintf("%+d", altitudeScore)) | | |||
2312 | .arg(when.toString(job->getDateTimeDisplayFormat())); | | |||
2313 | | ||||
2314 | total += altitudeScore; | | |||
2315 | } | | |||
2316 | | ||||
2317 | if (0 <= total) | | |||
2318 | { | | |||
2319 | int16_t const moonSeparationScore = getMoonSeparationScore(job, when); | | |||
2320 | | ||||
2321 | qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' Moon separation score is %2 at %3") | | |||
2322 | .arg(job->getName()) | | |||
2323 | .arg(QString::asprintf("%+d", moonSeparationScore)) | | |||
2324 | .arg(when.toString(job->getDateTimeDisplayFormat())); | | |||
2325 | | ||||
2326 | total += moonSeparationScore; | | |||
2327 | } | | |||
2328 | | ||||
2329 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' has a total score of %2 at %3.") | | |||
2330 | .arg(job->getName()) | | |||
2331 | .arg(QString::asprintf("%+d", total)) | | |||
2332 | .arg(when.toString(job->getDateTimeDisplayFormat())); | | |||
2333 | | ||||
2334 | return total; | | |||
2335 | } | | |||
2336 | | ||||
2337 | int16_t Scheduler::getAltitudeScore(SchedulerJob const *job, QDateTime const &when) const | | |||
2338 | { | | |||
2339 | // FIXME: block calculating target coordinates at a particular time is duplicated in several places | | |||
2340 | | ||||
2341 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2342 | KStarsDateTime ltWhen(when.isValid() ? | | |||
2343 | Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : | | |||
2344 | KStarsData::Instance()->lt()); | | |||
2345 | | ||||
2346 | // Create a sky object with the target catalog coordinates | | |||
2347 | SkyPoint const target = job->getTargetCoords(); | | |||
2348 | SkyObject o; | | |||
2349 | o.setRA0(target.ra0()); | | |||
2350 | o.setDec0(target.dec0()); | | |||
2351 | | ||||
2352 | // Update RA/DEC of the target for the current fraction of the day | | |||
2353 | KSNumbers numbers(ltWhen.djd()); | | |||
2354 | o.updateCoordsNow(&numbers); | | |||
2355 | | ||||
2356 | // Compute local sidereal time for the current fraction of the day, calculate altitude | | |||
2357 | CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); | | |||
2358 | o.EquatorialToHorizontal(&LST, geo->lat()); | | |||
2359 | double const altitude = o.alt().Degrees(); | | |||
2360 | | ||||
2361 | double const SETTING_ALTITUDE_CUTOFF = Options::settingAltitudeCutoff(); | | |||
2362 | int16_t score = BAD_SCORE - 1; | | |||
2363 | | ||||
2364 | // If altitude is negative, bad score | | |||
2365 | // FIXME: some locations may allow negative altitudes | | |||
2366 | if (altitude < 0) | | |||
2367 | { | | |||
2368 | score = BAD_SCORE; | | |||
2369 | } | | |||
2370 | else if (-90 < job->getMinAltitude()) | | |||
2371 | { | | |||
2372 | // If under altitude constraint, bad score | | |||
2373 | if (altitude < job->getMinAltitude()) | | |||
2374 | score = BAD_SCORE; | | |||
2375 | // Else if setting and under altitude cutoff, job would end soon after starting, bad score | | |||
2376 | // FIXME: half bad score when under altitude cutoff risk getting positive again | | |||
2377 | else | | |||
2378 | { | | |||
2379 | double offset = LST.Hours() - o.ra().Hours(); | | |||
2380 | if (24.0 <= offset) | | |||
2381 | offset -= 24.0; | | |||
2382 | else if (offset < 0.0) | | |||
2383 | offset += 24.0; | | |||
2384 | if (0.0 <= offset && offset < 12.0) | | |||
2385 | if (altitude - SETTING_ALTITUDE_CUTOFF < job->getMinAltitude()) | | |||
2386 | score = BAD_SCORE / 2; | | |||
2387 | } | | |||
2388 | } | | |||
2389 | // If not constrained but below minimum hard altitude, set score to 10% of altitude value | | |||
2390 | else if (altitude < minAltitude->minimum()) | | |||
2391 | { | | |||
2392 | score = static_cast <int16_t> (altitude / 10.0); | | |||
2393 | } | | |||
2394 | | ||||
2395 | // Else default score calculation without altitude constraint | | |||
2396 | if (score < BAD_SCORE) | | |||
2397 | score = static_cast <int16_t> ((1.5 * pow(1.06, altitude)) - (minAltitude->minimum() / 10.0)); | | |||
2398 | | ||||
2399 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' target altitude is %3 degrees at %2 (score %4).") | | |||
2400 | // .arg(job->getName()) | | |||
2401 | // .arg(when.toString(job->getDateTimeDisplayFormat())) | | |||
2402 | // .arg(currentAlt, 0, 'f', minAltitude->decimals()) | | |||
2403 | // .arg(QString::asprintf("%+d", score)); | | |||
2404 | | ||||
2405 | return score; | | |||
2406 | } | | |||
2407 | | ||||
2408 | double Scheduler::getCurrentMoonSeparation(SchedulerJob const *job) const | | |||
2409 | { | | |||
2410 | // FIXME: block calculating target coordinates at a particular time is duplicated in several places | | |||
2411 | | ||||
2412 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2413 | KStarsDateTime ltWhen(KStarsData::Instance()->lt()); | | |||
2414 | | ||||
2415 | // Create a sky object with the target catalog coordinates | | |||
2416 | SkyPoint const target = job->getTargetCoords(); | | |||
2417 | SkyObject o; | | |||
2418 | o.setRA0(target.ra0()); | | |||
2419 | o.setDec0(target.dec0()); | | |||
2420 | | ||||
2421 | // Update RA/DEC of the target for the current fraction of the day | | |||
2422 | KSNumbers numbers(ltWhen.djd()); | | |||
2423 | o.updateCoordsNow(&numbers); | | |||
2424 | | ||||
2425 | // Update moon | | |||
2426 | //ut = geo->LTtoUT(ltWhen); | | |||
2427 | //KSNumbers ksnum(ut.djd()); // BUG: possibly LT.djd() != UT.djd() because of translation | | |||
2428 | //LST = geo->GSTtoLST(ut.gst()); | | |||
2429 | CachingDms LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); | | |||
2430 | moon->updateCoords(&numbers, true, geo->lat(), &LST, true); | | |||
2431 | | ||||
2432 | // Moon/Sky separation p | | |||
2433 | return moon->angularDistanceTo(&o).Degrees(); | | |||
2434 | } | | |||
2435 | | ||||
2436 | int16_t Scheduler::getMoonSeparationScore(SchedulerJob const *job, QDateTime const &when) const | | |||
2437 | { | | |||
2438 | // FIXME: block calculating target coordinates at a particular time is duplicated in several places | | |||
2439 | | ||||
2440 | // Retrieve the argument date/time, or fall back to current time - don't use QDateTime's timezone! | | |||
2441 | KStarsDateTime ltWhen(when.isValid() ? | | |||
2442 | Qt::UTC == when.timeSpec() ? geo->UTtoLT(KStarsDateTime(when)) : when : | | |||
2443 | KStarsData::Instance()->lt()); | | |||
2444 | | ||||
2445 | // Create a sky object with the target catalog coordinates | | |||
2446 | SkyPoint const target = job->getTargetCoords(); | | |||
2447 | SkyObject o; | | |||
2448 | o.setRA0(target.ra0()); | | |||
2449 | o.setDec0(target.dec0()); | | |||
2450 | | ||||
2451 | // Update RA/DEC of the target for the current fraction of the day | | |||
2452 | KSNumbers numbers(ltWhen.djd()); | | |||
2453 | o.updateCoordsNow(&numbers); | | |||
2454 | | ||||
2455 | // Update moon | | |||
2456 | //ut = geo->LTtoUT(ltWhen); | | |||
2457 | //KSNumbers ksnum(ut.djd()); // BUG: possibly LT.djd() != UT.djd() because of translation | | |||
2458 | //LST = geo->GSTtoLST(ut.gst()); | | |||
2459 | CachingDms LST = geo->GSTtoLST(geo->LTtoUT(ltWhen).gst()); | | |||
2460 | moon->updateCoords(&numbers, true, geo->lat(), &LST, true); | | |||
2461 | | ||||
2462 | double const moonAltitude = moon->alt().Degrees(); | | |||
2463 | | ||||
2464 | // Lunar illumination % | | |||
2465 | double const illum = moon->illum() * 100.0; | | |||
2466 | | ||||
2467 | // Moon/Sky separation p | | |||
2468 | double const separation = moon->angularDistanceTo(&o).Degrees(); | | |||
2469 | | ||||
2470 | // Zenith distance of the moon | | |||
2471 | double const zMoon = (90 - moonAltitude); | | |||
2472 | // Zenith distance of target | | |||
2473 | double const zTarget = (90 - o.alt().Degrees()); | | |||
2474 | | ||||
2475 | int16_t score = 0; | | |||
2476 | | ||||
2477 | // If target = Moon, or no illuminiation, or moon below horizon, return static score. | | |||
2478 | if (zMoon == zTarget || illum == 0 || zMoon >= 90) | | |||
2479 | score = 100; | | |||
2480 | else | | |||
2481 | { | | |||
2482 | // JM: Some magic voodoo formula I came up with! | | |||
2483 | double moonEffect = (pow(separation, 1.7) * pow(zMoon, 0.5)) / (pow(zTarget, 1.1) * pow(illum, 0.5)); | | |||
2484 | | ||||
2485 | // Limit to 0 to 100 range. | | |||
2486 | moonEffect = KSUtils::clamp(moonEffect, 0.0, 100.0); | | |||
2487 | | ||||
2488 | if (job->getMinMoonSeparation() > 0) | | |||
2489 | { | | |||
2490 | if (separation < job->getMinMoonSeparation()) | | |||
2491 | score = BAD_SCORE * 5; | | |||
2492 | else | | |||
2493 | score = moonEffect; | | |||
2494 | } | | |||
2495 | else | | |||
2496 | score = moonEffect; | | |||
2497 | } | | |||
2498 | | ||||
2499 | // Limit to 0 to 20 | | |||
2500 | score /= 5.0; | | |||
2501 | | ||||
2502 | //qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' target is %L3 degrees from Moon (score %2).") | | |||
2503 | // .arg(job->getName()) | | |||
2504 | // .arg(separation, 0, 'f', 3) | | |||
2505 | // .arg(QString::asprintf("%+d", score)); | | |||
2506 | | ||||
2507 | return score; | | |||
2508 | } | | |||
2509 | | ||||
2510 | void Scheduler::calculateDawnDusk() | | |||
2511 | { | | |||
2512 | KSAlmanac ksal; | | |||
2513 | Dawn = ksal.getDawnAstronomicalTwilight(); | | |||
2514 | Dusk = ksal.getDuskAstronomicalTwilight(); | | |||
2515 | | ||||
2516 | QTime now = KStarsData::Instance()->lt().time(); | | |||
2517 | QTime dawn = QTime(0, 0, 0).addSecs(Dawn * 24 * 3600); | | |||
2518 | QTime dusk = QTime(0, 0, 0).addSecs(Dusk * 24 * 3600); | | |||
2519 | | ||||
2520 | duskDateTime.setDate(KStars::Instance()->data()->lt().date()); | | |||
2521 | duskDateTime.setTime(dusk); | | |||
2522 | | ||||
2523 | // FIXME: reduce spam by moving twilight time to a text label | | |||
2524 | //appendLogText(i18n("Astronomical twilight: dusk at %1, dawn at %2, and current time is %3", | | |||
2525 | // dusk.toString(), dawn.toString(), now.toString())); | | |||
2526 | } | | |||
2527 | | ||||
2528 | void Scheduler::executeJob(SchedulerJob *job) | 2045 | void Scheduler::executeJob(SchedulerJob *job) | ||
2529 | { | 2046 | { | ||
2530 | // Some states have executeJob called after current job is cancelled - checkStatus does this | 2047 | // Some states have executeJob called after current job is cancelled - checkStatus does this | ||
2531 | if (job == nullptr) | 2048 | if (job == nullptr) | ||
2532 | return; | 2049 | return; | ||
2533 | 2050 | | |||
2534 | // Don't execute the current job if it is already busy | 2051 | // Don't execute the current job if it is already busy | ||
2535 | if (currentJob == job && SchedulerJob::JOB_BUSY == currentJob->getState()) | 2052 | if (currentJob == job && SchedulerJob::JOB_BUSY == currentJob->getState()) | ||
Show All 18 Lines | |||||
2554 | 2071 | | |||
2555 | // From this point job can be executed now | 2072 | // From this point job can be executed now | ||
2556 | 2073 | | |||
2557 | if (job->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE && Options::rememberJobProgress()) | 2074 | if (job->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE && Options::rememberJobProgress()) | ||
2558 | { | 2075 | { | ||
2559 | captureInterface->setProperty("targetName", job->getName().replace(' ', "")); | 2076 | captureInterface->setProperty("targetName", job->getName().replace(' ', "")); | ||
2560 | } | 2077 | } | ||
2561 | 2078 | | |||
2562 | updatePreDawn(); | 2079 | strategy->updatePreDawn(); | ||
2563 | 2080 | | |||
2564 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Executing Job " << currentJob->getName(); | 2081 | qCInfo(KSTARS_EKOS_SCHEDULER) << "Executing Job " << currentJob->getName(); | ||
2565 | 2082 | | |||
2566 | currentJob->setState(SchedulerJob::JOB_BUSY); | 2083 | currentJob->setState(SchedulerJob::JOB_BUSY); | ||
2567 | 2084 | | |||
2568 | KNotification::event(QLatin1String("EkosSchedulerJobStart"), | 2085 | KNotification::event(QLatin1String("EkosSchedulerJobStart"), | ||
2569 | i18n("Ekos job started (%1)", currentJob->getName())); | 2086 | i18n("Ekos job started (%1)", currentJob->getName())); | ||
2570 | 2087 | | |||
▲ Show 20 Lines • Show All 820 Lines • ▼ Show 20 Line(s) | 2846 | { | |||
3391 | } | 2908 | } | ||
3392 | 2909 | | |||
3393 | // #3 Check if moon separation is still valid | 2910 | // #3 Check if moon separation is still valid | ||
3394 | if (currentJob->getMinMoonSeparation() > 0) | 2911 | if (currentJob->getMinMoonSeparation() > 0) | ||
3395 | { | 2912 | { | ||
3396 | SkyPoint p = currentJob->getTargetCoords(); | 2913 | SkyPoint p = currentJob->getTargetCoords(); | ||
3397 | p.EquatorialToHorizontal(KStarsData::Instance()->lst(), geo->lat()); | 2914 | p.EquatorialToHorizontal(KStarsData::Instance()->lst(), geo->lat()); | ||
3398 | 2915 | | |||
3399 | double moonSeparation = getCurrentMoonSeparation(currentJob); | 2916 | double moonSeparation = strategy->getCurrentMoonSeparation(currentJob); | ||
3400 | 2917 | | |||
3401 | if (moonSeparation < currentJob->getMinMoonSeparation()) | 2918 | if (moonSeparation < currentJob->getMinMoonSeparation()) | ||
3402 | { | 2919 | { | ||
3403 | // Only terminate job due to moon separation limitation if mount is NOT parked. | 2920 | // Only terminate job due to moon separation limitation if mount is NOT parked. | ||
3404 | if (isMountParked() == false) | 2921 | if (isMountParked() == false) | ||
3405 | { | 2922 | { | ||
3406 | appendLogText(i18n("Job '%2' current moon separation (%1 degrees) is lower than minimum constraint (%3 " | 2923 | appendLogText(i18n("Job '%2' current moon separation (%1 degrees) is lower than minimum constraint (%3 " | ||
3407 | "degrees), marking aborted.", | 2924 | "degrees), marking aborted.", | ||
3408 | moonSeparation, currentJob->getName(), currentJob->getMinMoonSeparation())); | 2925 | moonSeparation, currentJob->getName(), currentJob->getMinMoonSeparation())); | ||
3409 | 2926 | | |||
3410 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 2927 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3411 | stopCurrentJobAction(); | 2928 | stopCurrentJobAction(); | ||
3412 | stopGuiding(); | 2929 | stopGuiding(); | ||
3413 | findNextJob(); | 2930 | findNextJob(); | ||
3414 | return; | 2931 | return; | ||
3415 | } | 2932 | } | ||
3416 | } | 2933 | } | ||
3417 | } | 2934 | } | ||
3418 | 2935 | | |||
3419 | // #4 Check if we're not at dawn | 2936 | // #4 Check if we're not at dawn | ||
3420 | if (currentJob->getEnforceTwilight() && now > KStarsDateTime(preDawnDateTime)) | 2937 | if (currentJob->getEnforceTwilight() && now > KStarsDateTime(strategy->getPreDawnDateTime())) | ||
3421 | { | 2938 | { | ||
3422 | // If either mount or dome are not parked, we shutdown if we approach dawn | 2939 | // If either mount or dome are not parked, we shutdown if we approach dawn | ||
3423 | if (isMountParked() == false || (parkDomeCheck->isEnabled() && isDomeParked() == false)) | 2940 | if (isMountParked() == false || (parkDomeCheck->isEnabled() && isDomeParked() == false)) | ||
3424 | { | 2941 | { | ||
3425 | // Minute is a DOUBLE value, do not use i18np | 2942 | // Minute is a DOUBLE value, do not use i18np | ||
3426 | appendLogText(i18n( | 2943 | appendLogText(i18n( | ||
3427 | "Job '%3' is now approaching astronomical twilight rise limit at %1 (%2 minutes safety margin), marking aborted.", | 2944 | "Job '%3' is now approaching astronomical twilight rise limit at %1 (%2 minutes safety margin), marking aborted.", | ||
3428 | preDawnDateTime.toString(), Options::preDawnTime(), currentJob->getName())); | 2945 | strategy->getPreDawnDateTime().toString(), Options::preDawnTime(), currentJob->getName())); | ||
3429 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 2946 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
3430 | stopCurrentJobAction(); | 2947 | stopCurrentJobAction(); | ||
3431 | stopGuiding(); | 2948 | stopGuiding(); | ||
3432 | findNextJob(); | 2949 | findNextJob(); | ||
3433 | return; | 2950 | return; | ||
3434 | } | 2951 | } | ||
3435 | } | 2952 | } | ||
3436 | 2953 | | |||
▲ Show 20 Lines • Show All 2554 Lines • ▼ Show 20 Line(s) | 5507 | { | |||
5991 | for (SchedulerJob * job : jobs) | 5508 | for (SchedulerJob * job : jobs) | ||
5992 | job->reset(); | 5509 | job->reset(); | ||
5993 | 5510 | | |||
5994 | jobEvaluationOnly = true; | 5511 | jobEvaluationOnly = true; | ||
5995 | evaluateJobs(); | 5512 | evaluateJobs(); | ||
5996 | } | 5513 | } | ||
5997 | } | 5514 | } | ||
5998 | 5515 | | |||
5999 | void Scheduler::updatePreDawn() | | |||
6000 | { | | |||
6001 | double earlyDawn = Dawn - Options::preDawnTime() / (60.0 * 24.0); | | |||
6002 | int dayOffset = 0; | | |||
6003 | QTime dawn = QTime(0, 0, 0).addSecs(Dawn * 24 * 3600); | | |||
6004 | if (KStarsData::Instance()->lt().time() >= dawn) | | |||
6005 | dayOffset = 1; | | |||
6006 | preDawnDateTime.setDate(KStarsData::Instance()->lt().date().addDays(dayOffset)); | | |||
6007 | preDawnDateTime.setTime(QTime::fromMSecsSinceStartOfDay(earlyDawn * 24 * 3600 * 1000)); | | |||
6008 | } | | |||
6009 | | ||||
6010 | bool Scheduler::isWeatherOK(SchedulerJob *job) | | |||
6011 | { | | |||
6012 | if (weatherStatus == IPS_OK || weatherCheck->isChecked() == false) | | |||
6013 | return true; | | |||
6014 | else if (weatherStatus == IPS_IDLE) | | |||
6015 | { | | |||
6016 | if (indiState == INDI_READY) | | |||
6017 | appendLogText(i18n("Weather information is pending...")); | | |||
6018 | return true; | | |||
6019 | } | | |||
6020 | | ||||
6021 | // Temporary BUSY is ALSO accepted for now | | |||
6022 | // TODO Figure out how to exactly handle this | | |||
6023 | if (weatherStatus == IPS_BUSY) | | |||
6024 | return true; | | |||
6025 | | ||||
6026 | if (weatherStatus == IPS_ALERT) | | |||
6027 | { | | |||
6028 | job->setState(SchedulerJob::JOB_ABORTED); | | |||
6029 | appendLogText(i18n("Job '%1' suffers from bad weather, marking aborted.", job->getName())); | | |||
6030 | } | | |||
6031 | /*else if (weatherStatus == IPS_BUSY) | | |||
6032 | { | | |||
6033 | appendLogText(i18n("%1 observation job delayed due to bad weather.", job->getName())); | | |||
6034 | schedulerTimer.stop(); | | |||
6035 | connect(this, &Scheduler::weatherChanged, this, &Scheduler::resumeCheckStatus); | | |||
6036 | }*/ | | |||
6037 | | ||||
6038 | return false; | | |||
6039 | } | | |||
6040 | | ||||
6041 | void Scheduler::resumeCheckStatus() | 5516 | void Scheduler::resumeCheckStatus() | ||
6042 | { | 5517 | { | ||
6043 | disconnect(this, &Scheduler::weatherChanged, this, &Scheduler::resumeCheckStatus); | 5518 | disconnect(this, &Scheduler::weatherChanged, this, &Scheduler::resumeCheckStatus); | ||
6044 | schedulerTimer.start(); | 5519 | schedulerTimer.start(); | ||
6045 | } | 5520 | } | ||
6046 | 5521 | | |||
6047 | void Scheduler::startMosaicTool() | 5522 | void Scheduler::startMosaicTool() | ||
6048 | { | 5523 | { | ||
6049 | bool raOk = false, decOk = false; | 5524 | bool raOk = false, decOk = false; | ||
6050 | dms ra(raBox->createDms(false, &raOk)); //false means expressed in hours | 5525 | dms ra(raBox->createDms(false, &raOk)); //false means expressed in hours | ||
6051 | dms dec(decBox->createDms(true, &decOk)); | 5526 | dms dec(decBox->createDms(true, &decOk)); | ||
6052 | 5527 | | |||
6053 | if (raOk == false) | 5528 | if (raOk == false) | ||
6054 | { | 5529 | { | ||
6055 | appendLogText(i18n("Warning: RA value %1 is invalid.", raBox->text())); | 5530 | appendLogText(i18n("Warning: RA value %1 is invalid.", raBox->text())); | ||
6056 | return; | 5531 | return; | ||
6057 | } | 5532 | } | ||
6058 | 5533 | | |||
6059 | if (decOk == false) | 5534 | if (decOk == false) | ||
6060 | { | 5535 | { | ||
6061 | appendLogText(i18n("Warning: DEC value %1 is invalid.", decBox->text())); | 5536 | qCInfo(KSTARS_EKOS_SCHEDULER) << i18n("Warning: DEC value %1 is invalid.", decBox->text()); | ||
6062 | return; | 5537 | return; | ||
6063 | } | 5538 | } | ||
6064 | 5539 | | |||
6065 | Mosaic mosaicTool; | 5540 | Mosaic mosaicTool; | ||
6066 | 5541 | | |||
6067 | SkyPoint center; | 5542 | SkyPoint center; | ||
6068 | center.setRA0(ra); | 5543 | center.setRA0(ra); | ||
6069 | center.setDec0(dec); | 5544 | center.setDec0(dec); | ||
▲ Show 20 Lines • Show All 1042 Lines • ▼ Show 20 Line(s) | 6578 | { | |||
7112 | case IPS_ALERT: | 6587 | case IPS_ALERT: | ||
7113 | statusString = i18n("Caution: weather conditions are in the DANGER zone!"); | 6588 | statusString = i18n("Caution: weather conditions are in the DANGER zone!"); | ||
7114 | break; | 6589 | break; | ||
7115 | 6590 | | |||
7116 | default: | 6591 | default: | ||
7117 | break; | 6592 | break; | ||
7118 | } | 6593 | } | ||
7119 | 6594 | | |||
7120 | if (newStatus != weatherStatus) | 6595 | if (newStatus != strategy->getWeatherStatus()) | ||
7121 | { | 6596 | { | ||
7122 | weatherStatus = newStatus; | 6597 | strategy->setWeatherStatus(newStatus); | ||
7123 | 6598 | | |||
7124 | qCDebug(KSTARS_EKOS_SCHEDULER) << statusString; | 6599 | qCDebug(KSTARS_EKOS_SCHEDULER) << statusString; | ||
7125 | 6600 | | |||
7126 | if (weatherStatus == IPS_OK) | 6601 | if (newStatus == IPS_OK) | ||
7127 | weatherLabel->setPixmap( | 6602 | weatherLabel->setPixmap( | ||
7128 | QIcon::fromTheme("security-high") | 6603 | QIcon::fromTheme("security-high") | ||
7129 | .pixmap(QSize(32, 32))); | 6604 | .pixmap(QSize(32, 32))); | ||
7130 | else if (weatherStatus == IPS_BUSY) | 6605 | else if (newStatus == IPS_BUSY) | ||
7131 | { | 6606 | { | ||
7132 | weatherLabel->setPixmap( | 6607 | weatherLabel->setPixmap( | ||
7133 | QIcon::fromTheme("security-medium") | 6608 | QIcon::fromTheme("security-medium") | ||
7134 | .pixmap(QSize(32, 32))); | 6609 | .pixmap(QSize(32, 32))); | ||
7135 | KNotification::event(QLatin1String("WeatherWarning"), i18n("Weather conditions in warning zone")); | 6610 | KNotification::event(QLatin1String("WeatherWarning"), i18n("Weather conditions in warning zone")); | ||
7136 | } | 6611 | } | ||
7137 | else if (weatherStatus == IPS_ALERT) | 6612 | else if (newStatus == IPS_ALERT) | ||
7138 | { | 6613 | { | ||
7139 | weatherLabel->setPixmap( | 6614 | weatherLabel->setPixmap( | ||
7140 | QIcon::fromTheme("security-low") | 6615 | QIcon::fromTheme("security-low") | ||
7141 | .pixmap(QSize(32, 32))); | 6616 | .pixmap(QSize(32, 32))); | ||
7142 | KNotification::event(QLatin1String("WeatherAlert"), | 6617 | KNotification::event(QLatin1String("WeatherAlert"), | ||
7143 | i18n("Weather conditions are critical. Observatory shutdown is imminent")); | 6618 | i18n("Weather conditions are critical. Observatory shutdown is imminent")); | ||
7144 | } | 6619 | } | ||
7145 | else | 6620 | else | ||
7146 | weatherLabel->setPixmap(QIcon::fromTheme("chronometer") | 6621 | weatherLabel->setPixmap(QIcon::fromTheme("chronometer") | ||
7147 | .pixmap(QSize(32, 32))); | 6622 | .pixmap(QSize(32, 32))); | ||
7148 | 6623 | | |||
7149 | weatherLabel->show(); | 6624 | weatherLabel->show(); | ||
7150 | weatherLabel->setToolTip(statusString); | 6625 | weatherLabel->setToolTip(statusString); | ||
7151 | 6626 | | |||
7152 | appendLogText(statusString); | 6627 | appendLogText(statusString); | ||
7153 | 6628 | | |||
7154 | emit weatherChanged(weatherStatus); | 6629 | emit weatherChanged(newStatus); | ||
7155 | } | 6630 | } | ||
7156 | 6631 | | |||
7157 | if (weatherStatus == IPS_ALERT) | 6632 | if (newStatus == IPS_ALERT) | ||
7158 | { | 6633 | { | ||
7159 | appendLogText(i18n("Starting shutdown procedure due to severe weather.")); | 6634 | appendLogText(i18n("Starting shutdown procedure due to severe weather.")); | ||
7160 | if (currentJob) | 6635 | if (currentJob) | ||
7161 | { | 6636 | { | ||
7162 | currentJob->setState(SchedulerJob::JOB_ABORTED); | 6637 | currentJob->setState(SchedulerJob::JOB_ABORTED); | ||
7163 | stopCurrentJobAction(); | 6638 | stopCurrentJobAction(); | ||
7164 | stopGuiding(); | 6639 | stopGuiding(); | ||
7165 | jobTimer.stop(); | 6640 | jobTimer.stop(); | ||
▲ Show 20 Lines • Show All 94 Lines • Show Last 20 Lines |