diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h --- a/kstars/ekos/capture/capture.h +++ b/kstars/ekos/capture/capture.h @@ -13,6 +13,7 @@ #include "customproperties.h" #include "oal/filter.h" #include "ekos/ekos.h" +#include "ekos/mount/mount.h" #include "indi/indiccd.h" #include "indi/indicap.h" #include "indi/indidome.h" @@ -90,7 +91,7 @@ Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) public: - typedef enum { MF_NONE, MF_INITIATED, MF_FLIPPING, MF_SLEWING, MF_ALIGNING, MF_GUIDING } MFStage; + typedef enum { MF_NONE, MF_REQUESTED, MF_INITIATED, MF_FLIPPING, MF_SLEWING, MF_COMPLETED, MF_ALIGNING, MF_GUIDING } MFStage; typedef enum { CAL_NONE, CAL_DUSTCAP_PARKING, @@ -311,8 +312,6 @@ */ void setSettings(const QJsonObject &settings); - SkyPoint getInitialMountCoords() const; - public slots: /** \addtogroup CaptureDBusInterface @@ -512,6 +511,9 @@ // Clear Camera Configuration void clearCameraConfiguration(); + // Meridian flip + void meridianFlipStatusChanged(Mount::MeridianFlipStatus status); + private slots: /** @@ -540,10 +542,6 @@ void resetJobEdit(); void executeJob(); - // Meridian Flip - void checkMeridianFlipTimeout(); - //void checkAlignmentSlewComplete(); - // AutoGuide void checkGuideDeviationTimeout(); @@ -606,6 +604,7 @@ void newExposureProgress(Ekos::SequenceJob *job); void sequenceChanged(const QJsonArray &sequence); void settingsUpdated(const QJsonObject &settings); + void newMeridianFlipStatus(Mount::MeridianFlipStatus status); private: void setBusy(bool enable); @@ -633,17 +632,12 @@ /* Meridian Flip */ bool checkMeridianFlip(); void checkGuidingAfterFlip(); - double getCurrentHA(); - double getInitialHA(); // Remaining Time in seconds int getJobRemainingTime(SequenceJob *job); void resetFrameToZero(); - /* Slewing - true iff start slewing was successful */ - bool slew(const SkyPoint target); - /* Refocus */ void startRefocusTimer(bool forced = false); @@ -798,6 +792,7 @@ SchedulerJob::CapturedFramesMap capturedFramesMap; // Execute the meridian flip - bool executeMeridianFlip(); + void setMeridianFlipStage(MFStage status); + void processFlipCompleted(); }; } diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp --- a/kstars/ekos/capture/capture.cpp +++ b/kstars/ekos/capture/capture.cpp @@ -337,8 +337,8 @@ void Capture::pause() { pauseFunction = nullptr; - m_State = CAPTURE_PAUSED; - emit newStatus(Ekos::CAPTURE_PAUSED); + m_State = CAPTURE_PAUSE_PLANNED; + emit newStatus(Ekos::CAPTURE_PAUSE_PLANNED); appendLogText(i18n("Sequence shall be paused after current exposure is complete.")); pauseB->setEnabled(false); @@ -348,7 +348,7 @@ void Capture::toggleSequence() { - if (m_State == CAPTURE_PAUSED) + if (m_State == CAPTURE_PAUSE_PLANNED || m_State == CAPTURE_PAUSED) { startB->setIcon( QIcon::fromTheme("media-playback-stop")); @@ -376,11 +376,13 @@ void Capture::registerNewModule(const QString &name) { + qCDebug(KSTARS_EKOS_CAPTURE) << "Registering new Module (" << name << ")"; if (name == "Mount") { delete mountInterface; mountInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos/Mount", "org.kde.kstars.Ekos.Mount", QDBusConnection::sessionBus(), this); + } } @@ -461,7 +463,6 @@ // Refocus timer should not be reset on deviation error if (m_DeviationDetected == false && m_State != CAPTURE_SUSPENDED) { - meridianFlipStage = MF_NONE; // start timer to measure time until next forced refocus startRefocusEveryNTimer(); } @@ -578,7 +579,8 @@ lightBox->SetLightEnabled(false); } - secondsLabel->clear(); + if (meridianFlipStage == MF_NONE) + secondsLabel->clear(); disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS); disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Capture::setExposureProgress); disconnect(currentCCD, &ISD::CCD::ready, this, &Ekos::Capture::ready); @@ -612,6 +614,7 @@ seqTimer->stop(); activeJob = nullptr; + setMeridianFlipStage(MF_NONE); } void Capture::sendNewImage(const QString &filename, ISD::CCDChip *myChip) @@ -1220,11 +1223,13 @@ bool Capture::startNextExposure() { - if (m_State == CAPTURE_PAUSED) + if (m_State == CAPTURE_PAUSE_PLANNED) { pauseFunction = &Capture::startNextExposure; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); + m_State = CAPTURE_PAUSED; + setMeridianFlipStage(MF_NONE); return false; } @@ -1344,11 +1349,13 @@ return true; } - if (m_State == CAPTURE_PAUSED) + if (m_State == CAPTURE_PAUSE_PLANNED) { pauseFunction = &Capture::setCaptureComplete; appendLogText(i18n("Sequence paused.")); secondsLabel->setText(i18n("Paused...")); + m_State = CAPTURE_PAUSED; + setMeridianFlipStage(MF_NONE); return false; } @@ -2822,7 +2829,8 @@ return; else if (rc == IPS_BUSY) { - secondsLabel->clear(); + if (meridianFlipStage == MF_NONE) + secondsLabel->clear(); QTimer::singleShot(1000, this, &Ekos::Capture::updatePreCaptureCalibrationStatus); return; } @@ -2863,7 +2871,7 @@ appendLogText(i18n("Post meridian flip calibration completed successfully.")); resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always - meridianFlipStage = MF_NONE; + setMeridianFlipStage(MF_NONE); return; } } @@ -3083,24 +3091,101 @@ HFRPixels->setValue(median + (median * (Options::hFRThresholdPercentage() / 100.0))); } -SkyPoint Capture::getInitialMountCoords() const +void Capture::setMeridianFlipStage(MFStage status) { - QVariant const result = mountInterface->property("currentTarget"); - SkyPoint point = result.value(); - return point; + if (meridianFlipStage != status) + { + switch (status) + { + case MF_NONE: + if (meridianFlipStage == MF_REQUESTED) + { + // we keep the stage on requested until the mount starts the meridian flip + secondsLabel->setText(i18n("Meridian Flip...")); + emit newMeridianFlipStatus(Mount::FLIP_ACCEPTED); + } + else if (meridianFlipStage == MF_INITIATED) + { + secondsLabel->setText(i18n("Meridian Flip...")); + } + else if (m_State == CAPTURE_PAUSED) + { + // paused after meridian flip requested + secondsLabel->setText(i18n("Paused...")); + meridianFlipStage = status; + emit newMeridianFlipStatus(Mount::FLIP_ACCEPTED); + } + else + { + secondsLabel->setText(""); + meridianFlipStage = status; + } + + break; + + case MF_INITIATED: + meridianFlipStage = MF_INITIATED; + emit meridianFlipStarted(); + secondsLabel->setText(i18n("Meridian Flip...")); + KSNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"), KSNotification::EVENT_INFO); + break; + + case MF_REQUESTED: + if (m_State == CAPTURE_PAUSED) + // paused before meridian flip requested + emit newMeridianFlipStatus(Mount::FLIP_ACCEPTED); + else + emit newMeridianFlipStatus(Mount::FLIP_WAITING); + meridianFlipStage = status; + break; + + case MF_COMPLETED: + secondsLabel->setText(i18n("Flip complete.")); + break; + + default: + meridianFlipStage = status; + break; + } + } } -bool Capture::executeMeridianFlip() { - QDBusReply const reply = mountInterface->call("executeMeridianFlip"); +void Capture::meridianFlipStatusChanged(Mount::MeridianFlipStatus status) { + switch (status) + { + case Mount::FLIP_NONE: + // MF_NONE as external signal ignored so that re-alignment and guiding are processed first + if (meridianFlipStage != MF_COMPLETED) + setMeridianFlipStage(MF_NONE); + break; + + case Mount::FLIP_PLANNED: + // If we are autoguiding, we should resume autoguiding after flip + resumeGuidingAfterFlip = (guideState == GUIDE_GUIDING); + + if (m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED || m_State == CAPTURE_COMPLETE || m_State == CAPTURE_PAUSED) + { + setMeridianFlipStage(MF_INITIATED); + emit newMeridianFlipStatus(Mount::FLIP_ACCEPTED); + } + else + setMeridianFlipStage(MF_REQUESTED); + break; - if (reply.error().type() == QDBusError::NoError) - return reply.value(); + case Mount::FLIP_RUNNING: + setMeridianFlipStage(MF_INITIATED); + emit newStatus(Ekos::CAPTURE_MERIDIAN_FLIP); + break; - // error occured - qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: execute meridian flip request received DBUS error: %1").arg(QDBusError::errorString(reply.error().type())); + case Mount::FLIP_COMPLETED: + setMeridianFlipStage(MF_COMPLETED); + processFlipCompleted(); + break; - return false; + default: + break; + } } int Capture::getTotalFramesCount(QString signature) @@ -3271,8 +3356,8 @@ } else if (!strcmp(tagXMLEle(ep), "MeridianFlip")) { - meridianCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); - meridianHours->setValue(cLocale.toDouble(pcdataXMLEle(ep))); + setMeridianFlip(!strcmp(findXMLAttValu(ep, "enabled"), "true")); + setMeridianFlipHour(cLocale.toDouble(pcdataXMLEle(ep))); } else if (!strcmp(tagXMLEle(ep), "CCD")) { @@ -4133,62 +4218,56 @@ case MF_INITIATED: { if (nvp->s == IPS_BUSY) - meridianFlipStage = MF_FLIPPING; + setMeridianFlipStage(MF_FLIPPING); } break; case MF_FLIPPING: { - double ra, dec; - currentTelescope->getEqCoords(&ra, &dec); - double diffRA = getInitialMountCoords().ra().Hours() - ra; - // If the mount is actually flipping then we should see a difference in RA - // which if it exceeded MF_RA_DIFF_LIMIT (4 hours) then we consider it to be - // undertaking the flip. Otherwise, it's not flipping and let timeout takes care of - // of that - // Are there any mounts that do NOT change RA while flipping? i.e. do it silently? - // Need to investigate that bit - if (fabs(diffRA) > MF_RA_DIFF_LIMIT /* || nvp->s == IPS_OK*/) - meridianFlipStage = MF_SLEWING; + if (currentTelescope != nullptr && currentTelescope->isSlewing()) + setMeridianFlipStage(MF_SLEWING); } break; - case MF_SLEWING: - - if (nvp->s != IPS_OK) - break; + default: + break; + } +} - // If dome is syncing, wait until it stops - if (dome && dome->isMoving()) - break; +void Capture::processFlipCompleted() +{ + // If dome is syncing, wait until it stops + if (dome && dome->isMoving()) + return; - appendLogText(i18n("Telescope completed the meridian flip.")); + appendLogText(i18n("Telescope completed the meridian flip.")); - //KNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed")); - KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"), KSNotification::EVENT_INFO); + //KNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed")); + KSNotification::event(QLatin1String("MeridianFlipCompleted"), i18n("Meridian flip is successfully completed"), KSNotification::EVENT_INFO); - if (resumeAlignmentAfterFlip == true) - { - appendLogText(i18n("Performing post flip re-alignment...")); - secondsLabel->setText(i18n("Aligning...")); - retries = 0; - m_State = CAPTURE_ALIGNING; - emit newStatus(Ekos::CAPTURE_ALIGNING); + // resume only if capturing was running + if (m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED || m_State == CAPTURE_COMPLETE) + return; - meridianFlipStage = MF_ALIGNING; - //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); - //emit meridialFlipTracked(); - return; - } + if (resumeAlignmentAfterFlip == true) + { + appendLogText(i18n("Performing post flip re-alignment...")); + secondsLabel->setText(i18n("Aligning...")); retries = 0; - checkGuidingAfterFlip(); - break; + m_State = CAPTURE_ALIGNING; + emit newStatus(Ekos::CAPTURE_ALIGNING); - default: - break; + setMeridianFlipStage(MF_ALIGNING); + //QTimer::singleShot(Options::settlingTime(), [this]() {emit meridialFlipTracked();}); + //emit meridialFlipTracked(); + return; } + + retries = 0; + checkGuidingAfterFlip(); + } void Capture::checkGuidingAfterFlip() @@ -4198,7 +4277,7 @@ { resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always - meridianFlipStage = MF_NONE; + setMeridianFlipStage(MF_NONE); } else { @@ -4208,119 +4287,37 @@ m_State = CAPTURE_CALIBRATING; emit newStatus(Ekos::CAPTURE_CALIBRATING); - meridianFlipStage = MF_GUIDING; + setMeridianFlipStage(MF_GUIDING); emit meridianFlipCompleted(); } } -double Capture::getCurrentHA() -{ - QVariant HA = mountInterface->property("hourAngle"); - return HA.toDouble(); -} - -double Capture::getInitialHA() -{ - QVariant HA = mountInterface->property("initialHA"); - return HA.toDouble(); -} - -bool Capture::slew(const SkyPoint target) -{ - QList telescopeSlew; - telescopeSlew.append(target.ra().Hours()); - telescopeSlew.append(target.dec().Degrees()); - - QDBusReply const slewModeReply = mountInterface->callWithArgumentList(QDBus::AutoDetect, "slew", telescopeSlew); - - if (slewModeReply.error().type() == QDBusError::NoError) - return true; - - // error occured - qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: slew request received DBUS error: %1").arg(QDBusError::errorString(slewModeReply.error().type())); - return false; -} - bool Capture::checkMeridianFlip() { - if (currentTelescope == nullptr || meridianCheck->isChecked() == false || getInitialHA() > 0) + if (currentTelescope == nullptr || meridianCheck->isChecked() == false) return false; // If active job is taking flat field image at a wall source // then do not flip. if (activeJob && activeJob->getFrameType() == FRAME_FLAT && activeJob->getFlatFieldSource() == SOURCE_WALL) return false; - double currentHA = getCurrentHA(); - - //appendLogText(i18n("Current hour angle %1", currentHA)); - - if (currentHA == Ekos::INVALID_VALUE) + if (meridianFlipStage == MF_NONE) return false; - if (currentHA > meridianHours->value()) - { - //NOTE: DO NOT make the follow sentence PLURAL as the value is in double - appendLogText( - i18n("Current hour angle %1 hours exceeds meridian flip limit of %2 hours. Auto meridian flip is initiated.", - QString("%L1").arg(currentHA, 0, 'f', 2), QString("%L1").arg(meridianHours->value(), 0, 'f', 2))); - meridianFlipStage = MF_INITIATED; + // meridian flip requested or already in action - //KNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started")); - KSNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"), KSNotification::EVENT_INFO); + // Reset frame if we need to do focusing later on + if (isInSequenceFocus || (refocusEveryNCheck->isChecked() && getRefocusEveryNTimerElapsedSec() > 0)) + emit resetFocus(); - // Suspend guiding first before commanding a meridian flip - //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) - // emit suspendGuiding(false); - - // If we are autoguiding, we should resume autoguiding after flip - resumeGuidingAfterFlip = (guideState == GUIDE_GUIDING); + // signal that meridian flip may take place + if (meridianFlipStage == MF_REQUESTED) + setMeridianFlipStage(MF_NONE); - emit meridianFlipStarted(); - // Reset frame if we need to do focusing later on - if (isInSequenceFocus || (refocusEveryNCheck->isChecked() && getRefocusEveryNTimerElapsedSec() > 0)) - emit resetFocus(); - - if (executeMeridianFlip()) - { - secondsLabel->setText(i18n("Meridian Flip...")); - - retries = 0; - - m_State = CAPTURE_MERIDIAN_FLIP; - emit newStatus(Ekos::CAPTURE_MERIDIAN_FLIP); - - QTimer::singleShot(MF_TIMER_TIMEOUT, this, &Ekos::Capture::checkMeridianFlipTimeout); - return true; - } - } - - return false; -} - -void Capture::checkMeridianFlipTimeout() -{ - if (meridianFlipStage == MF_NONE) - return; - - if (meridianFlipStage < MF_ALIGNING) - { - appendLogText(i18n("Telescope meridian flip timed out. Please make sure your mount supports meridian flip.")); - - if (++retries == 3) - { - //KNotification::event(QLatin1String("MeridianFlipFailed"), i18n("Meridian flip failed")); - KSNotification::event(QLatin1String("MeridianFlipFailed"), i18n("Meridian flip failed"), KSNotification::EVENT_ALERT); - abort(); - } - else - { - if (executeMeridianFlip()) - appendLogText(i18n("Retrying meridian flip again...")); - } - } + return true; } void Capture::checkGuideDeviationTimeout() @@ -4373,7 +4370,7 @@ this->m_State = CAPTURE_ALIGNING; emit newStatus(Ekos::CAPTURE_ALIGNING); - meridianFlipStage = MF_ALIGNING; + setMeridianFlipStage(MF_ALIGNING); } } break; @@ -4612,11 +4609,20 @@ void Capture::setMeridianFlip(bool enable) { meridianCheck->setChecked(enable); + QDBusReply const reply = mountInterface->call("activateMeridianFlip", enable); + + if (reply.error().type() != QDBusError::NoError) + qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: setting meridian flip request received DBUS error: %1").arg(QDBusError::errorString(reply.error().type())); } void Capture::setMeridianFlipHour(double hours) { meridianHours->setValue(hours); + + QDBusReply const reply = mountInterface->call("setMeridianFlipLimit", hours); + + if (reply.error().type() != QDBusError::NoError) + qCCritical(KSTARS_EKOS_CAPTURE) << QString("Warning: setting meridian flip time limit request received DBUS error: %1").arg(QDBusError::errorString(reply.error().type())); } bool Capture::hasCoolerControl() @@ -4805,8 +4811,11 @@ } } - // step 2: check if meridian flip is required - if (meridianFlipStage != MF_NONE || checkMeridianFlip()) + // step 2: check if meridian flip already is ongoing + if (meridianFlipStage != MF_NONE) + return IPS_BUSY; + // step 3: check if meridian flip is required + if (checkMeridianFlip()) return IPS_BUSY; calibrationStage = CAL_PRECAPTURE_COMPLETE; diff --git a/kstars/ekos/ekos.h b/kstars/ekos/ekos.h --- a/kstars/ekos/ekos.h +++ b/kstars/ekos/ekos.h @@ -80,6 +80,7 @@ CAPTURE_IDLE, CAPTURE_PROGRESS, CAPTURE_CAPTURING, + CAPTURE_PAUSE_PLANNED, CAPTURE_PAUSED, CAPTURE_SUSPENDED, CAPTURE_ABORTED, diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -2927,6 +2927,8 @@ // Meridian Flip states connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, mountProcess.get(), &Ekos::Mount::disableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, mountProcess.get(), &Ekos::Mount::enableAltLimits, Qt::UniqueConnection); + connect(captureProcess.get(), &Ekos::Capture::newMeridianFlipStatus, mountProcess.get(), &Ekos::Mount::meridianFlipStatusChanged, Qt::UniqueConnection); + connect(mountProcess.get(), &Ekos::Mount::newMeridianFlipStatus, captureProcess.get(), &Ekos::Capture::meridianFlipStatusChanged, Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), &Ekos::Mount::newStatus, captureProcess.get(), &Ekos::Capture::setMountStatus, Qt::UniqueConnection); diff --git a/kstars/ekos/mount/mount.h b/kstars/ekos/mount/mount.h --- a/kstars/ekos/mount/mount.h +++ b/kstars/ekos/mount/mount.h @@ -42,9 +42,7 @@ Q_PROPERTY(QList equatorialCoords READ equatorialCoords) Q_PROPERTY(QList horizontalCoords READ horizontalCoords) Q_PROPERTY(QList telescopeInfo READ telescopeInfo WRITE setTelescopeInfo) - Q_PROPERTY(SkyPoint currentTarget READ currentTarget) Q_PROPERTY(double hourAngle READ hourAngle) - Q_PROPERTY(double initialHA READ initialHA) Q_PROPERTY(int slewRate READ slewRate WRITE setSlewRate) Q_PROPERTY(int slewStatus READ slewStatus) Q_PROPERTY(bool canPark READ canPark) @@ -56,6 +54,7 @@ //typedef enum { PARKING_IDLE, PARKING_OK, UNPARKING_OK, PARKING_BUSY, UNPARKING_BUSY, PARKING_ERROR } ParkingStatus; + typedef enum { FLIP_NONE, FLIP_PLANNED, FLIP_WAITING, FLIP_ACCEPTED, FLIP_RUNNING, FLIP_COMPLETED, FLIP_ERROR } MeridianFlipStatus; /** * @brief setTelescope Sets the mount module telescope interface @@ -116,7 +115,7 @@ /** @brief Like above but RA and DEC are strings HH:MM:SS and DD:MM:SS */ - Q_INVOKABLE bool slew(const QString &RA, const QString &DEC); + Q_INVOKABLE bool slew(const QString &RA, const QString &DEC); /** DBUS interface function. * Slew the mount to the target. Target name must be valid in KStars. @@ -249,11 +248,30 @@ QJsonArray getScopes() const; /* + * @brief Check if a meridian flip if necessary. + * @param lst current local sideral time + * @return true if a meridian flip is necessary + */ + void checkMeridianFlip(dms lst); + + /* * @brief Execute a meridian flip if necessary. * @return true if a meridian flip was necessary */ Q_INVOKABLE bool executeMeridianFlip(); + /* + * @brief activate meridian flip function + * @param activate true iff the meridian flip function should be enabled + */ + Q_INVOKABLE Q_NOREPLY void activateMeridianFlip(bool activate); + + /* + * @brief set the time beyond the meridian when the meridian flip + * @param hours time limit + */ + Q_INVOKABLE Q_NOREPLY void setMeridianFlipLimit(double hours); + public slots: /** @@ -329,6 +347,8 @@ void toggleMountToolBox(); + void meridianFlipStatusChanged(MeridianFlipStatus status); + private slots: /** @@ -345,26 +365,30 @@ signals: void newLog(const QString &text); void newCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt); - void newTarget(const QString &name); + void newTarget(const QString &name); void newStatus(ISD::Telescope::Status status); void newParkStatus(ISD::ParkStatus status); void pierSideChanged(ISD::Telescope::PierSide side); void slewRateChanged(int index); void ready(); + void newMeridianFlipStatus(MeridianFlipStatus status); - private: +private: void syncGPS(); + MeridianFlipStatus m_MFStatus = FLIP_NONE; + void setMeridianFlipStatus(MeridianFlipStatus status); QPointer captureInterface { nullptr }; ISD::Telescope *currentTelescope = nullptr; ISD::GDInterface *currentGPS = nullptr; QStringList m_LogText; - SkyPoint currentTargetPosition; + SkyPoint *currentTargetPosition = nullptr; SkyPoint telescopeCoord; QString lastNotificationMessage; QTimer updateTimer; QTimer autoParkTimer; + QTimer meridianFlipTimer; double lastAlt; int abortDispatch; bool altLimitEnabled; @@ -380,7 +404,7 @@ QQuickItem *m_SpeedSlider = nullptr, *m_SpeedLabel = nullptr, *m_raValue = nullptr, *m_deValue = nullptr, *m_azValue = nullptr, *m_altValue = nullptr, *m_haValue = nullptr, *m_zaValue = nullptr, *m_targetText = nullptr, *m_targetRAText = nullptr, *m_targetDEText = nullptr, *m_Park = nullptr, - *m_Unpark = nullptr, *m_statusText = nullptr, *m_J2000Check = nullptr, *m_JNowCheck=nullptr; + *m_Unpark = nullptr, *m_statusText = nullptr, *m_J2000Check = nullptr, *m_JNowCheck=nullptr; }; } diff --git a/kstars/ekos/mount/mount.cpp b/kstars/ekos/mount/mount.cpp --- a/kstars/ekos/mount/mount.cpp +++ b/kstars/ekos/mount/mount.cpp @@ -43,6 +43,7 @@ #define UPDATE_DELAY 1000 #define ABORT_DISPATCH_LIMIT 3 +#define MF_DELAY 12000 namespace Ekos { @@ -87,6 +88,10 @@ updateTimer.setInterval(UPDATE_DELAY); connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateTelescopeCoords())); + meridianFlipTimer.setInterval(MF_DELAY); + meridianFlipTimer.setSingleShot(true); + connect(&meridianFlipTimer, SIGNAL(timeout()), this, SLOT(executeMeridianFlip())); + QDateTime now = KStarsData::Instance()->lt(); // Set seconds to zero now = now.addSecs(now.time().second()*-1); @@ -487,11 +492,17 @@ emit newCoords(raOUT->text(), decOUT->text(), azOUT->text(), altOUT->text()); ISD::Telescope::Status currentStatus = currentTelescope->status(); + bool isTracking = (currentStatus == ISD::Telescope::MOUNT_TRACKING); if (m_Status != currentStatus) { - // If we just finished a slew, let's update initialHA - if (currentStatus == ISD::Telescope::MOUNT_TRACKING && m_Status == ISD::Telescope::MOUNT_SLEWING) - setInitialHA(ha.Hours()); + qCDebug(KSTARS_EKOS_MOUNT) << "Mount status changed from " << m_Status << " to " << currentStatus; + // If we just finished a slew, let's update initialHA and the current target's position + if (currentStatus == ISD::Telescope::MOUNT_TRACKING) + { + setInitialHA((sgn == '-' ? -1: 1) * ha.Hours()); + delete currentTargetPosition; + currentTargetPosition = new SkyPoint(telescopeCoord.ra(), telescopeCoord.dec()); + } m_Status = currentStatus; parkB->setEnabled(!currentTelescope->isParked()); @@ -511,7 +522,6 @@ if (trackingGroup->isEnabled()) { - bool isTracking = (currentStatus == ISD::Telescope::MOUNT_TRACKING); trackOnB->setChecked(isTracking); trackOffB->setChecked(!isTracking); } @@ -528,6 +538,9 @@ remainingTime = remainingTime.addMSecs(autoParkTimer.remainingTime()); countdownLabel->setText(remainingTime.toString("hh:mm:ss")); } + + if (isTracking) + checkMeridianFlip(lst); } else updateTimer.stop(); @@ -574,6 +587,26 @@ return false; } +void Mount::activateMeridianFlip(bool activate) +{ + meridianFlipCheckBox->setChecked(activate); +} + +void Mount::setMeridianFlipLimit(double hours) +{ + meridianFlipTimeBox->setValue(hours); +} + +void Mount::setMeridianFlipStatus(MeridianFlipStatus status) +{ + if (m_MFStatus != status) + { + m_MFStatus = status; + meridianFlipStatusChanged(status); + emit newMeridianFlipStatus(status); + } +} + void Mount::updateSwitch(ISwitchVectorProperty *svp) { if (!strcmp(svp->name, "TELESCOPE_SLEW_RATE")) @@ -817,40 +850,151 @@ if (currentTelescope == nullptr || currentTelescope->isConnected() == false) return false; - // JM 2019-01-05: This is not required since the mount module monitors the mount status - // and sets the initialHA accordingly. -// dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst()); -// double HA = lst.Hours() - RA; -// if (HA > 12.0) -// HA -= 24.0; -// setInitialHA(HA); + dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst()); + double HA = lst.Hours() - RA; + if (HA > 12.0) + HA -= 24.0; + setInitialHA(HA); + // reset the meridian flip status if the slew is not the meridian flip itself + if (m_MFStatus != FLIP_RUNNING) + setMeridianFlipStatus(FLIP_NONE); - currentTargetPosition.setRA(RA); - currentTargetPosition.setDec(DEC); + delete currentTargetPosition; + currentTargetPosition = new SkyPoint(RA, DEC); return currentTelescope->Slew(RA, DEC); } +void Mount::checkMeridianFlip(dms lst) +{ + + if (currentTelescope == nullptr || currentTelescope->isConnected() == false) + { + meridianFlipStatusText->setText("Status: inactive"); + setMeridianFlipStatus(FLIP_NONE); + return; + } + + double deltaHA = meridianFlipTimeBox->value() - lst.Hours() + telescopeCoord.ra().Hours(); + int hh = (int) deltaHA; + int mm = (int) ((deltaHA - hh)*60); + int ss = (int) ((deltaHA - hh - mm/60.0)*3600); + + switch (m_MFStatus) + { + case FLIP_NONE: + + if (initialHA() >= 0 || meridianFlipCheckBox->isChecked() == false) + { + meridianFlipStatusText->setText("Status: inactive"); + return; + } + + if (deltaHA > 0) + { + QString message = QString("Meridian flip in %1h:%2m:%3s") + .arg(hh, 2, 10, QChar('0')) + .arg(mm, 2, 10, QChar('0')) + .arg(ss, 2, 10, QChar('0')); + meridianFlipStatusText->setText(message); + return; + } + else if (initialHA() < 0) + { + setMeridianFlipStatus(FLIP_PLANNED); + } + break; + + case FLIP_PLANNED: + break; + + case FLIP_RUNNING: + if (currentTelescope->isTracking()) + { + // meridian flip completed + setMeridianFlipStatus(FLIP_COMPLETED); + } + break; + + case FLIP_COMPLETED: + setMeridianFlipStatus(FLIP_NONE); + break; + + default: + // do nothing + break; + } + return; +} + bool Mount::executeMeridianFlip() { if (initialHA() > 0) // no meridian flip necessary return false; dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst()); - double HA = lst.Hours() - currentTargetPosition.ra().Hours(); + double HA = lst.Hours() - currentTargetPosition->ra().Hours(); if (HA > 12.0) // no meridian flip necessary return false; // execute meridian flip - slew(currentTargetPosition.ra().Hours(), currentTargetPosition.dec().Degrees()); - return true; + qCDebug(KSTARS_EKOS_MOUNT) << "Meridian flip: slewing to RA=" << + currentTargetPosition->ra().toHMSString() << + "DEC=" << currentTargetPosition->dec().toDMSString(); + setMeridianFlipStatus(FLIP_RUNNING); + + bool result = slew(currentTargetPosition->ra().Hours(), currentTargetPosition->dec().Degrees()); + + if (result == false) + qCWarning(KSTARS_EKOS_MOUNT) << "Meridian flip FAILED: slewing to RA=" << + currentTargetPosition->ra().toHMSString() << + "DEC=" << currentTargetPosition->dec().toDMSString(); + return result; + +} + +void Mount::meridianFlipStatusChanged(Mount::MeridianFlipStatus status) { + + m_MFStatus = status; + + switch (status) + { + case FLIP_NONE: + meridianFlipStatusText->setText("Status: inactive"); + break; + + case FLIP_PLANNED: + meridianFlipStatusText->setText("Meridian flip planned..."); + meridianFlipTimer.start(); + break; + + case FLIP_WAITING: + meridianFlipStatusText->setText("Meridian flip waiting..."); + meridianFlipTimer.stop(); + break; + + case FLIP_ACCEPTED: + executeMeridianFlip(); + break; + + case FLIP_RUNNING: + meridianFlipStatusText->setText("Meridian flip running..."); + break; + + case FLIP_COMPLETED: + meridianFlipStatusText->setText("Meridian flip completed."); + break; + default: + break; + } + } SkyPoint Mount::currentTarget() { - return currentTargetPosition; + return *currentTargetPosition; } diff --git a/kstars/ekos/mount/mount.ui b/kstars/ekos/mount/mount.ui --- a/kstars/ekos/mount/mount.ui +++ b/kstars/ekos/mount/mount.ui @@ -6,8 +6,8 @@ 0 0 - 498 - 319 + 703 + 464 @@ -348,46 +348,84 @@ - - - false - - - Park + + + Park Telescope + + + + + false + + + Park + + + + + + + false + + + UnPark + + + + - - - false - - - UnPark + + + Meridian Flip + + + + + + + + <html><head/><body><p>Request a meridian flip if the hour angle exceeds the specified value. Capture and Guiding will be suspended and resumed after the flip is complete.</p></body></html> + + + Flip if HA > + + + + + + + h + + + + + + + + + + + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Reset mount alignment model - - - Clear Alignment Model + + + Alignment Model + + + + + Clear Model + + + + @@ -676,8 +714,8 @@ 3 - - + + false @@ -687,28 +725,34 @@ 0 - - Abort mount if it slews lower than this limit. - - - -10.000000000000000 - 90.000000000000000 + + 90.000000000000000 + - - - - <html><head/><body><p>Abort mount <span style=" font-weight:600;">slew/goto</span> motion if it falls outside the minimum and maximum altitude limits. This does not apply to tracking.</p></body></html> + + + + false - - Enable Limits + + + 0 + 0 + + + + -10.000000000000000 + + + 90.000000000000000 - + false @@ -721,42 +765,27 @@ - - - - false - - - <html><head/><body><p>Maximum telescope altitude limit. If the telescope is above this limit, it will be commanded to stop.</p></body></html> - + + - Max. Alt: + Enable Limits - - + + false - - - 0 - 0 - - - Abort mount if it slews higher than this limit. - - - 90.000000000000000 + <html><head/><body><p>Maximum telescope altitude limit. If the telescope is above this limit, it will be commanded to stop.</p></body></html> - - 90.000000000000000 + + Max. Alt: - + Qt::Vertical @@ -817,9 +846,6 @@ lstOUT minAltLimit maxAltLimit - parkB - unparkB - clearAlignmentModelB enableLimitsCheck diff --git a/kstars/ekos/scheduler/scheduler.cpp b/kstars/ekos/scheduler/scheduler.cpp --- a/kstars/ekos/scheduler/scheduler.cpp +++ b/kstars/ekos/scheduler/scheduler.cpp @@ -1316,10 +1316,7 @@ switch (job->getState()) { case SchedulerJob::JOB_SCHEDULED: - /* If job is scheduled, bypass if set to start later with a fixed time */ - if (SchedulerJob::START_AT == job->getFileStartupCondition()) - if (now < job->getStartupTime()) - continue; + /* If job is scheduled, keep it for evaluation against others */ break; case SchedulerJob::JOB_ERROR: @@ -1559,7 +1556,28 @@ break; } + // Check whether a previous job overlaps the current job + if (nullptr != previousJob && previousJob->getCompletionTime().isValid()) + { + // Calculate time we should be at after finishing the previous job + QDateTime const previousCompletionTime = previousJob->getCompletionTime().addSecs(static_cast (ceil(Options::leadTime()*60.0))); + + // Make this job invalid if startup time is not achievable because a START_AT job is non-movable + if (currentJob->getStartupTime() < previousCompletionTime) + { + currentJob->setState(SchedulerJob::JOB_INVALID); + + appendLogText(i18n("Warning: job '%1' has fixed startup time %2 unachievable due to the completion time of its previous sibling, marking invalid.", + currentJob->getName(), currentJob->getStartupTime().toString(currentJob->getDateTimeDisplayFormat()))); + + break; + } + + currentJob->setLeadTime(previousJob->getCompletionTime().secsTo(currentJob->getStartupTime())); + } + // This job is non-movable, we're done + currentJob->setScore(calculateJobScore(currentJob, now)); currentJob->setState(SchedulerJob::JOB_SCHEDULED); qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' is scheduled to start at %2, in compliance with fixed startup time requirement.") .arg(currentJob->getName()) diff --git a/kstars/kstars.notifyrc b/kstars/kstars.notifyrc --- a/kstars/kstars.notifyrc +++ b/kstars/kstars.notifyrc @@ -873,7 +873,7 @@ Comment[tr]=Kaide parkı devam ediyor Comment[uk]=Виконуємо паркування лафета Comment[x-test]=xxMount parking is in progressxx -Comment[zh_CN]=正在泊位赤道仪 +Comment[zh_CN]=正在归位赤道仪 Comment[zh_TW]=正在停止旋轉 Action=None [Event/MountParked] diff --git a/kstars/org.kde.kstars.Ekos.Mount.xml b/kstars/org.kde.kstars.Ekos.Mount.xml --- a/kstars/org.kde.kstars.Ekos.Mount.xml +++ b/kstars/org.kde.kstars.Ekos.Mount.xml @@ -7,9 +7,6 @@ - - - @@ -22,7 +19,6 @@ - @@ -60,6 +56,12 @@ + + + + + +