File indexing completed on 2024-04-21 03:43:38

0001 /*
0002     SPDX-FileCopyrightText: 2023 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "schedulertypes.h"
0010 #include "ekos/auxiliary/modulelogger.h"
0011 #include "ekos/align/align.h"
0012 #include "indi/indiweather.h"
0013 #include "dms.h"
0014 
0015 #include <QObject>
0016 #include <QPointer>
0017 #include <QDBusInterface>
0018 #include <QProcess>
0019 
0020 namespace Ekos
0021 {
0022 
0023 class SchedulerJob;
0024 class GreedyScheduler;
0025 class SchedulerModuleState;
0026 
0027 /**
0028  * @class SchedulerProcess
0029  * @brief The SchedulerProcess class holds the entire business logic for controlling the
0030  * execution of the EKOS scheduler.
0031  */
0032 class SchedulerProcess : public QObject, public ModuleLogger
0033 {
0034     Q_OBJECT
0035 
0036 public:
0037     SchedulerProcess(QSharedPointer<SchedulerModuleState> state);
0038 
0039     // ////////////////////////////////////////////////////////////////////
0040     // process steps
0041     // ////////////////////////////////////////////////////////////////////
0042 
0043     /**
0044      * @brief execute Execute the schedule, start if idle or paused.
0045      */
0046     void execute();
0047 
0048     /**
0049      * @brief findNextJob Check if the job met the completion criteria, and if it did, then it search for next job candidate.
0050      *  If no jobs are found, it starts the shutdown stage.
0051      */
0052     void findNextJob();
0053 
0054     /**
0055      * @brief stopCurrentJobAction Stop whatever action taking place in the current job (eg. capture, guiding...etc).
0056      */
0057     void stopCurrentJobAction();
0058 
0059     /**
0060       * @brief executeJob After the best job is selected, we call this in order to start the process that will execute the job.
0061       * checkJobStatus slot will be connected in order to figure the exact state of the current job each second
0062       * @return True if job is accepted and can be executed, false otherwise.
0063       */
0064     bool executeJob(SchedulerJob *job);
0065 
0066     /**
0067      * @brief wakeUpScheduler Wake up scheduler from sleep state
0068      */
0069     void wakeUpScheduler();
0070 
0071     /**
0072      * @brief Setup the main loop and start.
0073      */
0074     void startScheduler();
0075 
0076     /**
0077      * @brief stopScheduler Stop the scheduler execution. If stopping succeeded,
0078      * a {@see #schedulerStopped()} signal is emitted
0079      */
0080     void stopScheduler();
0081 
0082     /**
0083      * @brief shouldSchedulerSleep Check if the scheduler needs to sleep until the job is ready
0084      * @param job Job to check
0085      * @return True if we set the scheduler to sleep mode. False, if not required and we need to execute now
0086      */
0087     bool shouldSchedulerSleep(SchedulerJob *job);
0088 
0089     /**
0090      * @brief startSlew DBus call for initiating slew
0091      */
0092     void startSlew();
0093 
0094     /**
0095      * @brief startFocusing DBus call for feeding ekos the specified settings and initiating focus operation
0096      */
0097     void startFocusing();
0098 
0099     /**
0100      * @brief startAstrometry initiation of the capture and solve operation. We change the job state
0101      * after solver is started
0102      */
0103     void startAstrometry();
0104 
0105     /**
0106      * @brief startGuiding After ekos is fed the calibration options, we start the guiding process
0107      * @param resetCalibration By default calibration is not reset until it is explicitly requested
0108      */
0109     void startGuiding(bool resetCalibration = false);
0110 
0111     /**
0112      * @brief stopGuiding After guiding is done we need to stop the process
0113      */
0114     void stopGuiding();
0115 
0116     /**
0117      * @brief processGuidingTimer Check the guiding timer, and possibly restart guiding.
0118      */
0119     void processGuidingTimer();
0120 
0121     /**
0122      * @brief startCapture The current job file name is solved to an url which is fed to ekos. We then start the capture process
0123      * @param restart Set to true if the goal to restart an existing sequence. The only difference is that when a sequence is restarted, sequence file
0124      * is not loaded from disk again since that results in erasing all the history of the capture process.
0125      */
0126     void startCapture(bool restart = false);
0127 
0128     /**
0129      * @brief updateCompletedJobsCount For each scheduler job, examine sequence job storage and count captures.
0130      * @param forced forces recounting captures unconditionally if true, else only IDLE, EVALUATION or new jobs are examined.
0131      */
0132     void updateCompletedJobsCount(bool forced = false);
0133 
0134     /**
0135      * @brief setSolverAction set the GOTO mode for the solver
0136      * @param mode 0 For Sync, 1 for SlewToTarget, 2 for Nothing
0137      */
0138     void setSolverAction(Align::GotoMode mode);
0139 
0140     /**
0141      * @brief loadProfiles Load the existing EKOS profiles
0142      */
0143     void loadProfiles();
0144 
0145     /**
0146      * @brief checkEkosState Check ekos startup stages and take whatever action necessary to get Ekos up and running
0147      * @return True if Ekos is running, false if Ekos start up is in progress.
0148      */
0149     bool checkEkosState();
0150 
0151     /**
0152      * @brief checkINDIState Check INDI startup stages and take whatever action necessary to get INDI devices connected.
0153      * @return True if INDI devices are connected, false if it is under progress.
0154      */
0155     bool checkINDIState();
0156 
0157     /**
0158      * @brief completeShutdown Try to complete the scheduler shutdown
0159      * @return false iff some further action is required
0160      */
0161     bool completeShutdown();
0162 
0163     /**
0164      * @brief disconnectINDI disconnect all INDI devices from server.
0165      */
0166     void disconnectINDI();
0167 
0168     /**
0169      * @brief manageConnectionLoss Mitigate loss of connection with the INDI server.
0170      * @return true if connection to Ekos/INDI should be attempted again, false if not mitigation is available or needed.
0171      */
0172     bool manageConnectionLoss();
0173 
0174     /**
0175      * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly.
0176      */
0177     void checkCapParkingStatus();
0178 
0179     /**
0180      * @brief checkStartupState Check startup procedure stages and make sure all stages are complete.
0181      * @return True if startup is complete, false otherwise.
0182      */
0183     bool checkStartupState();
0184     /**
0185      * @brief checkShutdownState Check shutdown procedure stages and make sure all stages are complete.
0186      * @return
0187      */
0188     bool checkShutdownState();
0189 
0190     /**
0191      * @brief checkParkWaitState Check park wait state.
0192      * @return If parking/unparking in progress, return false. If parking/unparking complete, return true.
0193      */
0194     bool checkParkWaitState();
0195 
0196     /**
0197      * @brief runStartupProcedure Execute the startup of the scheduler itself to be prepared
0198      * for running scheduler jobs.
0199      */
0200     void runStartupProcedure();
0201 
0202     /**
0203      * @brief runShutdownProcedure Shutdown the scheduler itself and EKOS (if configured to do so).
0204      */
0205     void runShutdownProcedure();
0206 
0207     /**
0208      * @brief setPaused pausing the scheduler
0209      */
0210     void setPaused();
0211 
0212     /**
0213      * @brief resetJobs Reset all jobs counters
0214      */
0215     void resetJobs();
0216 
0217     /**
0218      * @brief selectActiveJob Select the job that should be executed
0219      */
0220     void selectActiveJob(const QList<SchedulerJob *> &jobs);
0221 
0222     /**
0223      * @brief evaluateJobs evaluates the current state of each objects and gives each one a score based on the constraints.
0224      * Given that score, the scheduler will decide which is the best job that needs to be executed.
0225      */
0226     void evaluateJobs(bool evaluateOnly);
0227 
0228     /**
0229      * @brief checkJobStatus Check the overall state of the scheduler, Ekos, and INDI. When all is OK, it calls evaluateJobs() when no job is current or executeJob() if a job is selected.
0230      * @return False if this function needs to be called again later, true if situation is stable and operations may continue.
0231      */
0232     bool checkStatus();
0233 
0234     /**
0235      * @brief getNextAction Checking for the next appropriate action regarding the current state of the scheduler  and execute it
0236      */
0237     void getNextAction();
0238 
0239     /**
0240      * @brief Repeatedly runs a scheduler iteration and then sleeps timerInterval millisconds
0241      * and run the next iteration. This continues until the sleep time is negative.
0242      */
0243     void iterate();
0244 
0245     /**
0246      * @brief Run a single scheduler iteration.
0247      */
0248     int runSchedulerIteration();
0249 
0250     /**
0251      * @brief checkJobStage Check the progress of the job states and make DBUS calls to start the next stage until the job is complete.
0252      */
0253     void checkJobStage();
0254     void checkJobStageEpilogue();
0255 
0256 
0257     /**
0258      * @brief saveScheduler Save scheduler jobs to a file
0259      * @param path path of a file
0260      * @return true on success, false on failure.
0261      */
0262     bool saveScheduler(const QUrl &fileURL);
0263 
0264     /**
0265      * @brief appendEkosScheduleList Append the contents of an ESL file to the queue.
0266      * @param fileURL File URL to load contents from.
0267      * @return True if contents were loaded successfully, else false.
0268      */
0269     bool appendEkosScheduleList(const QString &fileURL);
0270 
0271     /**
0272      * @brief appendLogText Append a new line to the logging.
0273      */
0274     void appendLogText(const QString &logentry) override
0275     {
0276         emit newLog(logentry);
0277     }
0278 
0279     // ////////////////////////////////////////////////////////////////////
0280     // device handling
0281     // ////////////////////////////////////////////////////////////////////
0282     void setAlignStatus(Ekos::AlignState status);
0283     void setGuideStatus(Ekos::GuideState status);
0284     void setCaptureStatus(Ekos::CaptureState status);
0285     void setFocusStatus(Ekos::FocusState status);
0286     void setMountStatus(ISD::Mount::Status status);
0287     void setWeatherStatus(ISD::Weather::Status status);
0288 
0289     /**
0290      * @return True if mount is parked
0291      */
0292     bool isMountParked();
0293     /**
0294      * @return True if dome is parked
0295      */
0296     bool isDomeParked();
0297 
0298     // ////////////////////////////////////////////////////////////////////
0299     // state machine and scheduler
0300     // ////////////////////////////////////////////////////////////////////
0301     QSharedPointer<SchedulerModuleState> m_moduleState;
0302     QSharedPointer<SchedulerModuleState> moduleState() const
0303     {
0304         return m_moduleState;
0305     }
0306 
0307     QPointer<Ekos::GreedyScheduler> m_GreedyScheduler;
0308     QPointer<GreedyScheduler> &getGreedyScheduler()
0309     {
0310         return m_GreedyScheduler;
0311     }
0312 
0313     // ////////////////////////////////////////////////////////////////////
0314     // DBUS interfaces to devices
0315     // ////////////////////////////////////////////////////////////////////
0316     QPointer<QDBusInterface> ekosInterface() const
0317     {
0318         return m_ekosInterface;
0319     }
0320     void setEkosInterface(QPointer<QDBusInterface> newInterface)
0321     {
0322         m_ekosInterface = newInterface;
0323     }
0324     QPointer<QDBusInterface> indiInterface() const
0325     {
0326         return m_indiInterface;
0327     }
0328     void setIndiInterface(QPointer<QDBusInterface> newInterface)
0329     {
0330         m_indiInterface = newInterface;
0331     }
0332     QPointer<QDBusInterface> focusInterface() const
0333     {
0334         return m_focusInterface;
0335     }
0336     void setFocusInterface(QPointer<QDBusInterface> newInterface)
0337     {
0338         m_focusInterface = newInterface;
0339     }
0340     QPointer<QDBusInterface> captureInterface() const
0341     {
0342         return m_captureInterface;
0343     }
0344     void setCaptureInterface(QPointer<QDBusInterface> newInterface)
0345     {
0346         m_captureInterface = newInterface;
0347     }
0348     QPointer<QDBusInterface> mountInterface() const
0349     {
0350         return m_mountInterface;
0351     }
0352     void setMountInterface(QPointer<QDBusInterface> newInterface)
0353     {
0354         m_mountInterface = newInterface;
0355     }
0356     QPointer<QDBusInterface> alignInterface() const
0357     {
0358         return m_alignInterface;
0359     }
0360     void setAlignInterface(QPointer<QDBusInterface> newInterface)
0361     {
0362         m_alignInterface = newInterface;
0363     }
0364     QPointer<QDBusInterface> guideInterface() const
0365     {
0366         return m_guideInterface;
0367     }
0368     void setGuideInterface(QPointer<QDBusInterface> newInterface)
0369     {
0370         m_guideInterface = newInterface;
0371     }
0372     QPointer<QDBusInterface> domeInterface() const
0373     {
0374         return m_domeInterface;
0375     }
0376     void setDomeInterface(QPointer<QDBusInterface> newInterface)
0377     {
0378         m_domeInterface = newInterface;
0379     }
0380     QPointer<QDBusInterface> weatherInterface() const
0381     {
0382         return m_weatherInterface;
0383     }
0384     void setWeatherInterface(QPointer<QDBusInterface> newInterface)
0385     {
0386         m_weatherInterface = newInterface;
0387     }
0388     QPointer<QDBusInterface> capInterface() const
0389     {
0390         return m_capInterface;
0391     }
0392     void setCapInterface(QPointer<QDBusInterface> newInterface)
0393     {
0394         m_capInterface = newInterface;
0395     }
0396 
0397     /**
0398      * @brief createJobSequence Creates a job sequence for the mosaic tool given the prefix and output dir. The currently selected sequence file is modified
0399      * and a new version given the supplied parameters are saved to the output directory
0400      * @param prefix Prefix to set for the job sequence
0401      * @param outputDir Output dir to set for the job sequence
0402      * @return True if new file is saved, false otherwise
0403      */
0404     bool createJobSequence(XMLEle *root, const QString &prefix, const QString &outputDir);
0405 
0406     /**
0407      * @brief getSequenceJobRoot Read XML data from capture sequence job
0408      * @param filename
0409      * @return
0410      */
0411     XMLEle *getSequenceJobRoot(const QString &filename) const;
0412 
0413     /**
0414      * @brief getGuidingStatus Retrieve the guiding status.
0415      */
0416     GuideState getGuidingStatus();
0417 
0418     QProcess &scriptProcess()
0419     {
0420         return m_scriptProcess;
0421     }
0422 
0423 signals:
0424     // new log text for the module log window
0425     void newLog(const QString &text);
0426     // status updates
0427     void schedulerStopped();
0428     void shutdownStarted();
0429     void schedulerSleeping(bool shutdown, bool sleep);
0430     void schedulerPaused();
0431     void changeSleepLabel(QString text, bool show = true);
0432     // state changes
0433     void jobsUpdated(QJsonArray jobsList);
0434     void updateJobTable(SchedulerJob *job = nullptr);
0435     // loading jobs
0436     void addJob(SchedulerJob *job);
0437     void syncGreedyParams();
0438     void syncGUIToGeneralSettings();
0439     void updateSchedulerURL(const QString &fileURL);
0440     // required for Analyze timeline
0441     void jobStarted(const QString &jobName);
0442     void jobEnded(const QString &jobName, const QString &endReason);
0443 
0444 
0445 private:
0446     // DBus interfaces to devices
0447     QPointer<QDBusInterface> m_ekosInterface { nullptr };
0448     QPointer<QDBusInterface> m_indiInterface { nullptr };
0449     QPointer<QDBusInterface> m_focusInterface { nullptr };
0450     QPointer<QDBusInterface> m_captureInterface { nullptr };
0451     QPointer<QDBusInterface> m_mountInterface { nullptr };
0452     QPointer<QDBusInterface> m_alignInterface { nullptr };
0453     QPointer<QDBusInterface> m_guideInterface { nullptr };
0454     QPointer<QDBusInterface> m_domeInterface { nullptr };
0455     QPointer<QDBusInterface> m_weatherInterface { nullptr };
0456     QPointer<QDBusInterface> m_capInterface { nullptr };
0457 
0458     // When a module is commanded to perform an action, wait this many milliseconds
0459     // before check its state again. If State is still IDLE, then it either didn't received the command
0460     // or there is another problem.
0461     static const uint32_t ALIGN_INACTIVITY_TIMEOUT      = 120000;
0462     static const uint32_t FOCUS_INACTIVITY_TIMEOUT      = 120000;
0463     static const uint32_t CAPTURE_INACTIVITY_TIMEOUT    = 120000;
0464     static const uint16_t GUIDE_INACTIVITY_TIMEOUT      = 60000;
0465     /// Counter to keep debug logging in check
0466     uint8_t checkJobStageCounter { 0 };
0467 
0468     // Startup and Shutdown scripts process
0469     QProcess m_scriptProcess;
0470     // ////////////////////////////////////////////////////////////////////
0471     // process steps
0472     // ////////////////////////////////////////////////////////////////////
0473 
0474     /**
0475      * @brief executeScript Execute pre- or post job script
0476      */
0477     void executeScript(const QString &filename);
0478 
0479     /**
0480      * @brief stopEkos shutdown Ekos completely
0481      */
0482     void stopEkos();
0483 
0484     /**
0485      * @brief checkMountParkingStatus check mount parking status and updating corresponding states accordingly.
0486      */
0487     void checkMountParkingStatus();
0488 
0489     /**
0490      * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly.
0491      */
0492     void checkDomeParkingStatus();
0493 
0494     // ////////////////////////////////////////////////////////////////////
0495     // device handling
0496     // ////////////////////////////////////////////////////////////////////
0497     /**
0498      * @brief parkCap Close dust cover
0499      */
0500     void parkCap();
0501 
0502     /**
0503      * @brief unCap Open dust cover
0504      */
0505     void unParkCap();
0506 
0507     /**
0508      * @brief parkMount Park mount
0509      */
0510     void parkMount();
0511 
0512     /**
0513      * @brief unParkMount Unpark mount
0514      */
0515     void unParkMount();
0516 
0517     /**
0518      * @brief parkDome Park dome
0519      */
0520     void parkDome();
0521 
0522     /**
0523      * @brief unParkDome Unpark dome
0524      */
0525     void unParkDome();
0526 
0527     // ////////////////////////////////////////////////////////////////////
0528     // helper functions
0529     // ////////////////////////////////////////////////////////////////////
0530 
0531     /**
0532      * @brief checkStartupProcedure restart regularly {@see #checkStartupState()} until completed
0533      */
0534     void checkStartupProcedure();
0535 
0536     /**
0537      * @brief checkShutdownProcedure Check regularly if the shutdown has completed (see
0538      * {@see #checkShutdownState()}) and stop EKOS if the corresponding configuration flag is set.
0539      */
0540     void checkShutdownProcedure();
0541 
0542     /**
0543      * @brief checkProcessExit Check script process exist status. This is called when the process exists either normally or abnormally.
0544      * @param exitCode exit code from the script process. Depending on the exist code, the status of startup/shutdown procedure is set accordingly.
0545      */
0546     void checkProcessExit(int exitCode);
0547 
0548     /**
0549      * @brief readProcessOutput read running script process output and display it in Ekos
0550      */
0551     void readProcessOutput();
0552 
0553     /**
0554      * @brief Returns true if the job is storing its captures on the same machine as the scheduler.
0555      */
0556     bool canCountCaptures(const SchedulerJob &job);
0557 
0558     /**
0559      * @brief activeJob Shortcut to the active job held in the state machine
0560      */
0561     SchedulerJob *activeJob();
0562 
0563     // Prints all the relative state variables set during an iteration. For debugging.
0564     void printStates(const QString &label);
0565 
0566 };
0567 } // Ekos namespace