File indexing completed on 2024-04-28 03:43:39

0001 /*  Ekos state machine for the meridian flip
0002     SPDX-FileCopyrightText: Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include <QObject>
0010 #include "skypoint.h"
0011 
0012 #include <ekos_mount_debug.h>
0013 #include "ekos/ekos.h"
0014 
0015 #include "indi/indistd.h"
0016 #include "indi/indimount.h"
0017 
0018 /**
0019  * @brief A meridian flip is executed by issueing a scope motion to the target.
0020  *
0021  * Essentially, the meridian flip relies upon that the mount hardware selects the
0022  * pier side of the scope on the appropriate side depending whether the scope points east
0023  * or west of the meridian.
0024  *
0025  * While tracking a certain object a pier side change is necessary as soon as the position
0026  * crosses the meridian. As soon as this is detected, the meridian flip works as follows:
0027  * - A meridian flip request is sent to Capture so that a currently running capture can
0028  *   be completed before the flip starts.
0029  * - Capture completes the currently running capture, stops capturing and sends a confirmation
0030  *   so that the flip may start.
0031  * - Now a slew command is issued to the current target (i.e. the last position where a slew has
0032  *   been executed). This will force the mount hardware to reconsider the pier side and slews
0033  *   to the same position but changing the pier side.
0034  * - As soon as the slew has been completed alignment is executed, guiding restarted and Capture
0035  *   informed that capturing may continue.
0036  */
0037 
0038 namespace Ekos
0039 {
0040 
0041 class Mount;
0042 
0043 class MeridianFlipState : public QObject
0044 {
0045     Q_OBJECT
0046 public:
0047     explicit MeridianFlipState(QObject *parent = nullptr);
0048 
0049 
0050     // Meridian flip state of the mount
0051     typedef enum
0052     {
0053         MOUNT_FLIP_NONE,      // this is the default state, comparing the hour angle with the next flip position
0054                               // it moves to MOUNT_FLIP_PLANNED when a flip is needed
0055         MOUNT_FLIP_PLANNED,   // a meridian flip is ready to be started due to the target position and the
0056                               // configured offsets and signals to the Capture class that a flip is required
0057         MOUNT_FLIP_WAITING,   // step after FLUP_PLANNED waiting until Capture completes a running exposure
0058         MOUNT_FLIP_ACCEPTED,  // Capture is idle or has completed the exposure and will wait until the flip
0059                               // is completed.
0060         MOUNT_FLIP_RUNNING,   // this signals that a flip slew is in progress, when the slew is completed
0061                               // the state is set to MOUNT_FLIP_COMPLETED
0062         MOUNT_FLIP_COMPLETED, // this checks that the flip was completed successfully or not and after tidying up
0063                               // moves to MOUNT_FLIP_NONE to wait for the next flip requirement.
0064                               // Capture sees this and resumes.
0065         MOUNT_FLIP_ERROR      // errors in the flip process should end up here
0066     } MeridianFlipMountState;
0067 
0068     // overall meridian flip stage
0069     typedef enum {
0070         MF_NONE,       /* no meridian flip planned */
0071         MF_REQUESTED,  /* meridian flip necessary, confirmation requested to start the flip */
0072         MF_READY,      /* confirmations received, the meridian flip may start               */
0073         MF_INITIATED,  /* meridian flip started                                             */
0074         MF_FLIPPING,   /* slew started to the target position                               */
0075         MF_COMPLETED,  /* meridian flip completed, re-calibration required                  */
0076         MF_ALIGNING,   /* alignment running after a successful flip                         */
0077         MF_GUIDING     /* guiding started after a successful flip                           */
0078     } MFStage;
0079 
0080     // mount position
0081     typedef struct MountPosition {
0082          SkyPoint position;
0083          ISD::Mount::PierSide pierSide;
0084          dms ha;
0085          bool valid = false;
0086     } MountPosition;
0087 
0088     // flag if alignment should be executed after the meridian flip
0089     bool resumeAlignmentAfterFlip() { return m_resumeAlignmentAfterFlip; }
0090     void setResumeAlignmentAfterFlip(bool resume) { m_resumeAlignmentAfterFlip = resume; }
0091 
0092     // flag if guiding should be resetarted after the meridian flip
0093     bool resumeGuidingAfterFlip() { return m_resumeGuidingAfterFlip; }
0094     void setResumeGuidingAfterFlip(bool resume) { m_resumeGuidingAfterFlip = resume; }
0095 
0096     /**
0097      * @brief Translate the state to a string value.
0098      */
0099     static QString MFStageString(MFStage stage);
0100 
0101     // Is the meridian flip enabled?
0102     bool isEnabled() const { return m_enabled; }
0103     void setEnabled(bool value);
0104     // offset past the meridian
0105     double getOffset() const { return m_offset; }
0106     void setOffset(double newOffset) { m_offset = newOffset; }
0107 
0108     /**
0109      * @brief connectMount Establish the connection to the mount
0110      */
0111     void connectMount(Mount *mount);
0112 
0113     MeridianFlipState::MFStage getMeridianFlipStage() const { return meridianFlipStage; };
0114     const QString &getMeridianStatusText()
0115     {
0116         return m_lastStatusText;
0117     }
0118 
0119     /**
0120      * @brief Update the meridian flip stage
0121      */
0122     void updateMeridianFlipStage(const MFStage &stage);
0123 
0124     /**
0125      * @brief Stop a meridian flip if necessary.
0126      * @return true if a meridian flip was running
0127      */
0128     bool resetMeridianFlip();
0129 
0130     /**
0131      * @brief Execute actions after the flipping slew has completed.
0132      */
0133     void processFlipCompleted();
0134 
0135     /**
0136      * @brief Check if a meridian flip has already been started
0137      * @return true iff the scope has started the meridian flip
0138      */
0139     inline bool checkMeridianFlipRunning()
0140     {
0141         return meridianFlipStage == MF_INITIATED || meridianFlipStage == MF_FLIPPING;
0142     }
0143 
0144     /**
0145      * @brief Check if a meridian flip is ready to start, running or some post flip actions are not comleted.
0146      */
0147     inline bool checkMeridianFlipActive()
0148     {
0149         return meridianFlipStage != MF_NONE && meridianFlipStage != MF_REQUESTED;
0150     }
0151 
0152     /**
0153      * @brief Update the current target position
0154      * @param pos new target (may be null if no target is set)
0155      */
0156     void setTargetPosition(SkyPoint *pos);
0157 
0158     /**
0159      * @brief Make current target position invalid
0160      */
0161     void clearTargetPosition() { targetPosition.valid = false; }
0162 
0163     /**
0164      * @brief Get the hour angle of that time the mount has slewed to the current position.
0165      *        his is used to manage the meridian flip for mounts which do not report pier side.
0166      *        (-12.0 < HA <= 12.0)
0167      */
0168     double initialPositionHA() const;
0169 
0170     /**
0171      * @brief access to the meridian flip mount state
0172      */
0173     MeridianFlipMountState getMeridianFlipMountState() const { return meridianFlipMountState; }
0174 
0175     double getFlipDelayHrs() const { return flipDelayHrs; }
0176     void setFlipDelayHrs(double value) { flipDelayHrs = value; }
0177 
0178     /**
0179      * @brief Change the meridian flip mount state
0180      */
0181     void updateMFMountState(MeridianFlipState::MeridianFlipMountState status);
0182 
0183     /**
0184      * @brief return the string for the status
0185     */
0186     static QString meridianFlipStatusString(MeridianFlipMountState status);
0187 
0188     // Access to the current mount device
0189     void setMountConnected(bool connected) { m_hasMount = connected; }
0190 
0191     // Access to the capture device
0192     void setHasCaptureInterface(bool present) { m_hasCaptureInterface = present; }
0193 
0194 public slots:
0195     /**
0196      * @brief Slot for receiving an update of the capture status
0197      */
0198     void setCaptureState(CaptureState state) { m_CaptureState = state; }
0199     /**
0200      * @brief Slot for receiving an update to the mount status
0201      */
0202     void setMountStatus(ISD::Mount::Status status);
0203     /**
0204      * @brief Slot for receiving an update to the mount park status
0205      */
0206     void setMountParkStatus(ISD::ParkStatus status);
0207     /**
0208      * @brief Slot for receiving a new telescope position
0209      * @param position new position
0210      * @param pierSide current pier side
0211      * @param ha current hour angle
0212      */
0213     void updateTelescopeCoord(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha);
0214 
0215 signals:
0216     // mount meridian flip status update event
0217     void newMountMFStatus(MeridianFlipMountState status);
0218     // Communicate a new meridian flip mount status message
0219     void newMeridianFlipMountStatusText(const QString &text);
0220     // slew the telescope to a target
0221     void slewTelescope(SkyPoint &target);
0222     // new log text for the module log window
0223     void newLog(const QString &text);
0224 
0225 private:
0226     // flag if meridian flip is enabled
0227     bool m_enabled = false;
0228     // offset post meridian (in degrees)
0229     double m_offset;
0230     // flag if alignment should be executed after the meridian flip
0231     bool m_resumeAlignmentAfterFlip { false };
0232     // flag if guiding should be resetarted after the meridian flip
0233     bool m_resumeGuidingAfterFlip { false };
0234 
0235     // the mount device
0236     bool m_hasMount { false };
0237     // capture interface
0238     bool m_hasCaptureInterface { false };
0239     // the current capture status
0240     CaptureState m_CaptureState { CAPTURE_IDLE };
0241     // the current mount status
0242     ISD::Mount::Status m_MountStatus = ISD::Mount::MOUNT_IDLE;
0243     // the previous mount status
0244     ISD::Mount::Status m_PrevMountStatus = ISD::Mount::MOUNT_IDLE;
0245     // mount park status
0246     ISD::ParkStatus m_MountParkStatus = ISD::PARK_UNKNOWN;
0247     // current overall meridian flip state
0248     MFStage meridianFlipStage { MF_NONE };
0249 
0250     // current mount meridian flip state
0251     MeridianFlipMountState meridianFlipMountState { MOUNT_FLIP_NONE };
0252     // last message published to avoid double entries
0253     QString m_lastStatusText = "";
0254 
0255     SkyPoint initialMountCoords;
0256 
0257     // current position of the mount
0258     MountPosition currentPosition;
0259     // current target position (might be different from current position!)
0260     MountPosition targetPosition;
0261 
0262     static void updatePosition(MountPosition &pos, const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha, const bool isValid);
0263 
0264     // A meridian flip requires a slew of 180 degrees in the hour angle axis so will take a certain
0265     // amount of time. Within this time, a slewing state change will be ignored for pier side change checks.
0266     QDateTime minMeridianFlipEndTime;
0267 
0268     double flipDelayHrs = 0.0;      // delays the next flip attempt if it fails
0269 
0270     /**
0271      * @brief Internal method for changing the mount meridian flip state. From extermal, use {@see updateMFMountState()}
0272      */
0273     void setMeridianFlipMountState(MeridianFlipMountState newMeridianFlipMountState);
0274 
0275     /**
0276      * @brief Check if a meridian flip if necessary.
0277      * @param lst local sideral time
0278      */
0279     bool checkMeridianFlip(dms lst);
0280 
0281     /**
0282      * @brief Start a meridian flip if necessary.
0283      * @return true if a meridian flip was started
0284      */
0285     void startMeridianFlip();
0286 
0287     /**
0288      * @brief Calculate the minimal end time from now plus {@see minMeridianFlipEndTime}
0289      */
0290     void updateMinMeridianFlipEndTime();
0291 
0292     /**
0293      * @brief React upon a meridian flip status change of the mount.
0294      */
0295     void publishMFMountStatus(MeridianFlipMountState status);
0296 
0297     /**
0298      * @brief publish a new meridian flip mount status text
0299      */
0300     void publishMFMountStatusText(QString text);
0301 
0302     /**
0303      * @brief Add log message
0304      */
0305     void appendLogText(QString message);
0306 
0307 };
0308 } // namespace