Index: kstars/data/indidrivers.xml =================================================================== --- kstars/data/indidrivers.xml +++ kstars/data/indidrivers.xml @@ -512,6 +512,14 @@ + + + indi_lx200stargo + 1.0 + + + + indi_dsi_ccd @@ -765,14 +773,14 @@ - - + + indi_starbook_telescope 0.2 - - + + Index: kstars/ekos/auxiliary/serialportassistant.h =================================================================== --- kstars/ekos/auxiliary/serialportassistant.h +++ kstars/ekos/auxiliary/serialportassistant.h @@ -33,9 +33,9 @@ bool loadRules(); bool removeActiveRule(); bool addRule(const QJsonObject &rule); - void addPage(ISD::GDInterface *device); - void gotoPage(ISD::GDInterface *device); - void resetPage(int index); + void addDevicePage(ISD::GDInterface *device); + void gotoDevicePage(ISD::GDInterface *device); + void resetCurrentPage(); void scanDevices(); void parseDevices(); @@ -46,7 +46,7 @@ QList devices; std::unique_ptr model; - ISD::GDInterface *currentDevice { nullptr }; + ISD::GDInterface *m_CurrentDevice { nullptr }; const ProfileInfo *m_Profile; QNetworkAccessManager manager; Index: kstars/ekos/auxiliary/serialportassistant.cpp =================================================================== --- kstars/ekos/auxiliary/serialportassistant.cpp +++ kstars/ekos/auxiliary/serialportassistant.cpp @@ -39,7 +39,7 @@ wizardPix->setPixmap(im); connect(nextB, &QPushButton::clicked, [&]() { - serialPortWizard->setCurrentIndex(serialPortWizard->currentIndex()+1); + gotoDevicePage(devices[devices.indexOf(m_CurrentDevice)+1]); }); loadRules(); @@ -56,19 +56,19 @@ }); connect(closeB, &QPushButton::clicked, [&]() { - serialPortWizard->setCurrentIndex(0); + gotoDevicePage(nullptr); close(); }); } void SerialPortAssistant::addDevice(ISD::GDInterface *device) { qCDebug(KSTARS_EKOS) << "Serial Port Assistant new device" << device->getDeviceName(); - addPage(device); + addDevicePage(device); } -void SerialPortAssistant::addPage(ISD::GDInterface *device) +void SerialPortAssistant::addDevicePage(ISD::GDInterface *device) { devices.append(device); @@ -93,12 +93,12 @@ QPushButton *homeButton = new QPushButton(QIcon::fromTheme("go-home"), i18n("Home"), devicePage); connect(homeButton, &QPushButton::clicked, [&]() { - serialPortWizard->setCurrentIndex(0); + gotoDevicePage(nullptr); }); QPushButton *skipButton = new QPushButton(i18n("Skip Device"), devicePage); connect(skipButton, &QPushButton::clicked, [&]() { - serialPortWizard->setCurrentIndex(serialPortWizard->currentIndex()+1); + gotoDevicePage(devices[devices.indexOf(m_CurrentDevice)+1]); }); QCheckBox *hardwareSlotC = new QCheckBox(i18n("Physical Port Mapping"), devicePage); hardwareSlotC->setObjectName("hardwareSlot"); @@ -147,16 +147,20 @@ }); } -void SerialPortAssistant::gotoPage(ISD::GDInterface *device) +void SerialPortAssistant::gotoDevicePage(ISD::GDInterface *device) { int index = devices.indexOf(device); + // reset to home page if (index < 0) + { + m_CurrentDevice = nullptr; + serialPortWizard->setCurrentIndex(0); return; + } - currentDevice = device; - - serialPortWizard->setCurrentIndex( (1 + index) * 2); + m_CurrentDevice = device; + serialPortWizard->setCurrentIndex(index+1); } bool SerialPortAssistant::loadRules() @@ -225,17 +229,21 @@ return false; } -void SerialPortAssistant::resetPage(int index) +void SerialPortAssistant::resetCurrentPage() { - QButtonGroup *actionGroup = serialPortWizard->widget(index)->findChild("actionGroup"); + // Reset all buttons + QButtonGroup *actionGroup = serialPortWizard->currentWidget()->findChild("actionGroup"); for (auto b : actionGroup->buttons()) b->setEnabled(true); - QPushButton *startButton = serialPortWizard->widget(index)->findChild("startButton"); + + // Set start button to start scanning + QPushButton *startButton = serialPortWizard->currentWidget()->findChild("startButton"); startButton->setText(i18n("Start Scanning")); - QLabel *animation = serialPortWizard->widget(index)->findChild("animation"); + + // Clear animation + QLabel *animation = serialPortWizard->currentWidget()->findChild("animation"); animation->movie()->stop(); animation->clear(); - serialPortWizard->setCurrentIndex(index); } void SerialPortAssistant::scanDevices() @@ -245,7 +253,7 @@ QNetworkReply *response = manager.get(QNetworkRequest(url)); // We need to disconnect the device first - devices[serialPortWizard->currentIndex()-1]->Disconnect(); + m_CurrentDevice->Disconnect(); connect(response, &QNetworkReply::finished, this, &SerialPortAssistant::parseDevices); } @@ -258,15 +266,15 @@ { qCCritical(KSTARS_EKOS) << response->errorString(); KSNotification::error(i18n("Failed to scan devices.")); - resetPage(serialPortWizard->currentIndex()); + resetCurrentPage(); return; } QJsonDocument jsonDoc = QJsonDocument::fromJson(response->readAll()); if (jsonDoc.isObject() == false) { KSNotification::error(i18n("Failed to detect any devices. Please make sure device is powered and connected to StellarMate via USB.")); - resetPage(serialPortWizard->currentIndex()); + resetCurrentPage(); return; } @@ -276,7 +284,7 @@ if (rule.contains("ID_VENDOR_ID") == false || rule["ID_VENDOR_ID"].toString().count() != 4) { KSNotification::error(i18n("Failed to detect any devices. Please make sure device is powered and connected to StellarMate via USB.")); - resetPage(serialPortWizard->currentIndex()); + resetCurrentPage(); return; } @@ -314,12 +322,16 @@ if (vidMatch && pidMatch) { KSNotification::error(i18n("Duplicate devices detected. You must remove one mapping or enable hardware slot mapping.")); - resetPage(serialPortWizard->currentIndex()); + resetCurrentPage(); return; } } + addRule(newRule); + // Remove current device page since it is no longer required. + serialPortWizard->removeWidget(serialPortWizard->currentWidget()); + gotoDevicePage(nullptr); } bool SerialPortAssistant::addRule(const QJsonObject &rule) @@ -329,22 +341,22 @@ if (INDI::WebManager::getWebManagerResponse(QNetworkAccessManager::PostOperation, url, nullptr, &data)) { KSNotification::info(i18n("Mapping is successful. Please unplug and replug your device again now.")); - ITextVectorProperty *devicePort = devices[serialPortWizard->currentIndex()-1]->getBaseDevice()->getText("DEVICE_PORT"); + ITextVectorProperty *devicePort = m_CurrentDevice->getBaseDevice()->getText("DEVICE_PORT"); if (devicePort) { // Set port in device and then save config IUSaveText(&devicePort->tp[0], QString("/dev/%1").arg(rule["symlink"].toString()).toLatin1().constData()); - devices[serialPortWizard->currentIndex()-1]->getDriverInfo()->getClientManager()->sendNewText(devicePort); - devices[serialPortWizard->currentIndex()-1]->setConfig(SAVE_CONFIG); - devices[serialPortWizard->currentIndex()-1]->Connect(); - serialPortWizard->setCurrentIndex(serialPortWizard->currentIndex()+1); + m_CurrentDevice->getDriverInfo()->getClientManager()->sendNewText(devicePort); + m_CurrentDevice->setConfig(SAVE_CONFIG); + m_CurrentDevice->Connect(); + //serialPortWizard->setCurrentIndex(serialPortWizard->currentIndex()+1); } loadRules(); return true; } KSNotification::sorry(i18n("Failed to add a new rule.")); - resetPage(serialPortWizard->currentIndex()); + resetCurrentPage(); return false; } Index: kstars/ekos/capture/capture.h =================================================================== --- kstars/ekos/capture/capture.h +++ 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_READY, 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); @@ -726,7 +720,7 @@ */ void updateHFRThreshold(); bool isInSequenceFocus { false }; - bool autoFocusReady { false }; + bool m_AutoFocusReady { false }; //bool requiredAutoFocusStarted { false }; //bool firstAutoFocus { true }; double focusHFR { 0 }; // HFR value as received from the Ekos focus module @@ -798,6 +792,7 @@ SchedulerJob::CapturedFramesMap capturedFramesMap; // Execute the meridian flip - bool executeMeridianFlip(); + void setMeridianFlipStage(MFStage status); + void processFlipCompleted(); }; } Index: kstars/ekos/capture/capture.cpp =================================================================== --- kstars/ekos/capture/capture.cpp +++ 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(); } @@ -488,7 +489,7 @@ if (guideDeviationCheck->isChecked() && autoGuideReady == false) appendLogText(i18n("Warning: Guide deviation is selected but autoguide process was not started.")); - if (autofocusCheck->isChecked() && autoFocusReady == false) + if (autofocusCheck->isChecked() && m_AutoFocusReady == false) appendLogText(i18n("Warning: in-sequence focusing is selected but autofocus process was not started.")); prepareJob(first_job); @@ -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,8 @@ seqTimer->stop(); activeJob = nullptr; + // meridian flip may take place if requested + setMeridianFlipStage(MF_READY); } void Capture::sendNewImage(const QString &filename, ISD::CCDChip *myChip) @@ -1220,11 +1224,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_READY); return false; } @@ -1344,11 +1350,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_READY); return false; } @@ -1492,7 +1500,7 @@ // Otherwise, let's prepare for next exposure after making sure in-sequence focus and dithering are complete if applicable. else { - isInSequenceFocus = (autoFocusReady && autofocusCheck->isChecked()/* && HFRPixels->value() > 0*/); + isInSequenceFocus = (m_AutoFocusReady && autofocusCheck->isChecked()/* && HFRPixels->value() > 0*/); // if (isInSequenceFocus) // requiredAutoFocusStarted = false; @@ -1899,7 +1907,7 @@ } #endif - if (m_State == CAPTURE_DITHERING && autoFocusReady && startFocusIfRequired()) + if (m_State == CAPTURE_DITHERING && m_AutoFocusReady && startFocusIfRequired()) return true; startNextExposure(); @@ -2822,7 +2830,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 +2872,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; } } @@ -2957,7 +2966,7 @@ if (focusState == FOCUS_COMPLETE) { // enable option to have a refocus event occur if HFR goes over threshold - autoFocusReady = true; + m_AutoFocusReady = true; //if (HFRPixels->value() == 0.0 && fileHFR == 0.0) if (fileHFR == 0.0) @@ -3083,24 +3092,105 @@ 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 (m_State == CAPTURE_PAUSED) + // paused after meridian flip + secondsLabel->setText(i18n("Paused...")); + else + secondsLabel->setText(""); + meridianFlipStage = status; + break; + + case MF_READY: + 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); + } + // in any other case, ignore it + 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) @@ -4133,62 +4223,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 +4282,7 @@ { resumeSequence(); // N.B. Set meridian flip stage AFTER resumeSequence() always - meridianFlipStage = MF_NONE; + setMeridianFlipStage(MF_NONE); } else { @@ -4208,119 +4292,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); + // signal that meridian flip may take place + if (meridianFlipStage == MF_REQUESTED) + setMeridianFlipStage(MF_READY); - // If we are autoguiding, we should resume autoguiding after flip - resumeGuidingAfterFlip = (guideState == GUIDE_GUIDING); - 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 +4375,7 @@ this->m_State = CAPTURE_ALIGNING; emit newStatus(Ekos::CAPTURE_ALIGNING); - meridianFlipStage = MF_ALIGNING; + setMeridianFlipStage(MF_ALIGNING); } } break; @@ -4814,8 +4816,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; @@ -5090,13 +5095,13 @@ // then the absolute focus position for Lum is recorded in the filter manager // when we take flats again, we always go back to the same focus position as the light frames to ensure // near identical focus for both frames. - if (activeJob->getFrameType() == FRAME_FLAT && Options::flatSyncFocus()) + if (activeJob->getFrameType() == FRAME_FLAT && + m_AutoFocusReady && + currentFilter != nullptr && + Options::flatSyncFocus()) { - if (currentFilter != nullptr) - { - if (filterManager->syncAbsoluteFocusPosition(activeJob->getTargetFilter()-1) == false) - return IPS_BUSY; - } + if (filterManager->syncAbsoluteFocusPosition(activeJob->getTargetFilter()-1) == false) + return IPS_BUSY; } calibrationStage = CAL_PRECAPTURE_COMPLETE; Index: kstars/ekos/ekos.h =================================================================== --- kstars/ekos/ekos.h +++ kstars/ekos/ekos.h @@ -80,6 +80,7 @@ CAPTURE_IDLE, CAPTURE_PROGRESS, CAPTURE_CAPTURING, + CAPTURE_PAUSE_PLANNED, CAPTURE_PAUSED, CAPTURE_SUSPENDED, CAPTURE_ABORTED, Index: kstars/ekos/guide/externalguide/linguider.cpp =================================================================== --- kstars/ekos/guide/externalguide/linguider.cpp +++ kstars/ekos/guide/externalguide/linguider.cpp @@ -245,7 +245,7 @@ double raDev = pos[0].toDouble(&raOK); double deDev = pos[1].toDouble(&deOK); - if (raDev && deDev) + if (raOK && deOK) emit newAxisDelta(raDev, deDev); } else Index: kstars/ekos/guide/guide.cpp =================================================================== --- kstars/ekos/guide/guide.cpp +++ kstars/ekos/guide/guide.cpp @@ -1659,21 +1659,24 @@ state = GUIDE_IDLE; emit newStatus(state); - ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); - - if (frameSettings.contains(targetChip)) + if (guiderType == GUIDE_INTERNAL) { - targetChip->resetFrame(); - int x, y, w, h; - targetChip->getFrame(&x, &y, &w, &h); - QVariantMap settings = frameSettings[targetChip]; - settings["x"] = x; - settings["y"] = y; - settings["w"] = w; - settings["h"] = h; - frameSettings[targetChip] = settings; - - subFramed = false; + ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD); + + if (frameSettings.contains(targetChip)) + { + targetChip->resetFrame(); + int x, y, w, h; + targetChip->getFrame(&x, &y, &w, &h); + QVariantMap settings = frameSettings[targetChip]; + settings["x"] = x; + settings["y"] = y; + settings["w"] = w; + settings["h"] = h; + frameSettings[targetChip] = settings; + + subFramed = false; + } } saveSettings(); Index: kstars/ekos/guide/internalguide/internalguider.cpp =================================================================== --- kstars/ekos/guide/internalguide/internalguider.cpp +++ kstars/ekos/guide/internalguide/internalguider.cpp @@ -1049,34 +1049,27 @@ tick = pmath->getTicks(); - if (tick & 1) - { - // draw some params in window - emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); - - double raPulse = out->pulse_length[GUIDE_RA]; - double dePulse = out->pulse_length[GUIDE_DEC]; - - if(out->pulse_dir[GUIDE_RA]==NO_DIR) //If the pulse was not sent to the mount, it should have 0 value - raPulse = 0; - if(out->pulse_dir[GUIDE_DEC]==NO_DIR) //If the pulse was not sent to the mount, it should have 0 value - dePulse = 0; - if(out->pulse_dir[GUIDE_RA]==RA_INC_DIR) //If the pulse was in the Negative direction, it should have a negative sign. - raPulse = -raPulse; - if(out->pulse_dir[GUIDE_DEC]==DEC_INC_DIR) //If the pulse was in the Negative direction, it should have a negative sign. - dePulse = -dePulse; - - emit newAxisPulse(raPulse, dePulse); - - emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); - } - - // skip half frames - //if( half_refresh_rate && (tick & 1) ) - //return; - - //drift_graph->on_paint(); - //pDriftOut->update(); + emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); + + double raPulse = out->pulse_length[GUIDE_RA]; + double dePulse = out->pulse_length[GUIDE_DEC]; + + //If the pulse was not sent to the mount, it should have 0 value + if(out->pulse_dir[GUIDE_RA]==NO_DIR) + raPulse = 0; + //If the pulse was not sent to the mount, it should have 0 value + if(out->pulse_dir[GUIDE_DEC]==NO_DIR) + dePulse = 0; + //If the pulse was in the Negative direction, it should have a negative sign. + if(out->pulse_dir[GUIDE_RA]==RA_INC_DIR) + raPulse = -raPulse; + //If the pulse was in the Negative direction, it should have a negative sign. + if(out->pulse_dir[GUIDE_DEC]==DEC_INC_DIR) + dePulse = -dePulse; + + emit newAxisPulse(raPulse, dePulse); + + emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); return true; } Index: kstars/ekos/manager.h =================================================================== --- kstars/ekos/manager.h +++ kstars/ekos/manager.h @@ -12,7 +12,7 @@ #ifdef USE_QT5_INDI #include #else -#include +#include #endif #include "ui_manager.h" @@ -55,7 +55,7 @@ /** * @class Manager * @short Primary class to handle all Ekos modules. - * The Ekos Manager class manages startup and shutdown of INDI devices and registeration of devices within Ekos Modules. Ekos module consist of \ref Mount, \ref Capture, \ref Focus, \ref Guide, and \ref Align modules. + * The Ekos Manager class manages startup and shutdown of INDI devices and registeration of devices within Ekos Modules. Ekos module consist of \ref Ekos::Mount, \ref Ekos::Capture, \ref Ekos::Focus, \ref Ekos::Guide, and \ref Ekos::Align modules. * \defgroup EkosDBusInterface "Ekos DBus Interface" provides high level functions to control devices and Ekos modules for a total robotic operation: *
    *
  • \ref CaptureDBusInterface "Capture Module DBus Interface"
  • Index: kstars/ekos/manager.cpp =================================================================== --- kstars/ekos/manager.cpp +++ 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); Index: kstars/ekos/mount/mount.h =================================================================== --- kstars/ekos/mount/mount.h +++ 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,6 +248,13 @@ 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 */ @@ -341,6 +347,8 @@ void toggleMountToolBox(); + void meridianFlipStatusChanged(MeridianFlipStatus status); + private slots: /** @@ -357,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; @@ -392,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; }; } Index: kstars/ekos/mount/mount.cpp =================================================================== --- kstars/ekos/mount/mount.cpp +++ 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,8 +492,18 @@ 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) { + 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()); unparkB->setEnabled(currentTelescope->isParked()); @@ -507,7 +522,6 @@ if (trackingGroup->isEnabled()) { - bool isTracking = (currentStatus == ISD::Telescope::MOUNT_TRACKING); trackOnB->setChecked(isTracking); trackOffB->setChecked(!isTracking); } @@ -524,6 +538,12 @@ remainingTime = remainingTime.addMSecs(autoParkTimer.remainingTime()); countdownLabel->setText(remainingTime.toString("hh:mm:ss")); } + + if (isTracking) + checkMeridianFlip(lst); + else if (m_MFStatus == FLIP_PLANNED || m_MFStatus == FLIP_WAITING || m_MFStatus == FLIP_ACCEPTED) + // reset the meridian flip status in case that the mount is not tracking + setMeridianFlipStatus(FLIP_NONE); } else updateTimer.stop(); @@ -580,6 +600,16 @@ 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")) @@ -827,35 +857,162 @@ 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; + } + + if (currentTelescope->isTracking() == false) + { + meridianFlipStatusText->setText("Status: inactive (not tracking)"); + 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; + if (currentTelescope->status() != ISD::Telescope::MOUNT_TRACKING) + { + // no meridian flip necessary + setMeridianFlipStatus(FLIP_NONE); + 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; } Index: kstars/ekos/mount/mount.ui =================================================================== --- kstars/ekos/mount/mount.ui +++ kstars/ekos/mount/mount.ui @@ -353,7 +353,7 @@ Park Telescope - + false @@ -363,7 +363,7 @@ - + false @@ -402,6 +402,13 @@ + + + + + + + @@ -411,7 +418,7 @@ Alignment Model - + Clear Model Index: kstars/ekos/scheduler/scheduler.h =================================================================== --- kstars/ekos/scheduler/scheduler.h +++ kstars/ekos/scheduler/scheduler.h @@ -14,7 +14,7 @@ #include "ui_scheduler.h" #include "ekos/align/align.h" -#include +#include #include #include Index: kstars/ekos/scheduler/scheduler.cpp =================================================================== --- kstars/ekos/scheduler/scheduler.cpp +++ 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()) Index: kstars/ekos/scheduler/schedulerjob.h =================================================================== --- kstars/ekos/scheduler/schedulerjob.h +++ kstars/ekos/scheduler/schedulerjob.h @@ -348,23 +348,23 @@ bool isDuplicateOf(SchedulerJob const *a_job) const { return this != a_job && name == a_job->name && sequenceFile == a_job->sequenceFile; } /** @brief Compare ::SchedulerJob instances based on score. - * @fixme This is a qSort predicate, deprecated in QT5. + * @todo This is a qSort predicate, deprecated in QT5. * @arg a, b are ::SchedulerJob instances to compare. * @return true if the score of b is lower than the score of a. * @return false if the score of b is higher than or equal to the score of a. */ static bool decreasingScoreOrder(SchedulerJob const *a, SchedulerJob const *b); /** @brief Compare ::SchedulerJob instances based on priority. - * @fixme This is a qSort predicate, deprecated in QT5. + * @todo This is a qSort predicate, deprecated in QT5. * @arg a, b are ::SchedulerJob instances to compare. * @return true if the priority of a is lower than the priority of b. * @return false if the priority of a is higher than or equal to the priority of b. */ static bool increasingPriorityOrder(SchedulerJob const *a, SchedulerJob const *b); /** @brief Compare ::SchedulerJob instances based on altitude and movement in sky at startup time. - * @fixme This is a qSort predicate, deprecated in QT5. + * @todo This is a qSort predicate, deprecated in QT5. * @arg a, b are ::SchedulerJob instances to compare. * @arg when is the date/time to use to calculate the altitude to sort with, defaulting to a's startup time. * @note To obtain proper sort between several SchedulerJobs, all should have the same startup time. @@ -377,7 +377,7 @@ static bool decreasingAltitudeOrder(SchedulerJob const *a, SchedulerJob const *b, QDateTime const &when = QDateTime()); /** @brief Compare ::SchedulerJob instances based on startup time. - * @fixme This is a qSort predicate, deprecated in QT5. + * @todo This is a qSort predicate, deprecated in QT5. * @arg a, b are ::SchedulerJob instances to compare. * @return true if the startup time of a is sooner than the priority of b. * @return false if the startup time of a is later than or equal to the priority of b. Index: kstars/indi/indiccd.h =================================================================== --- kstars/indi/indiccd.h +++ kstars/indi/indiccd.h @@ -132,7 +132,6 @@ public: explicit CCD(GDInterface *iPtr); - ~CCD(); typedef enum { UPLOAD_CLIENT, UPLOAD_LOCAL, UPLOAD_BOTH } UploadMode; typedef enum { FORMAT_FITS, FORMAT_NATIVE } TransferFormat; @@ -224,7 +223,6 @@ // Update FITS Header bool setFITSHeader(const QMap &values); - FITSViewer *getViewer() { return fv; } CCDChip *getChip(CCDChip::ChipType cType); void setFITSDir(const QString &dir) { fitsDir = dir; } @@ -288,7 +286,7 @@ INumber *gainN { nullptr }; IPerm gainPerm { IP_RO }; - QPointer fv; - QPointer imageViewer; + QPointer m_FITSViewerWindows; + QPointer m_ImageViewerWindow; }; } Index: kstars/indi/indiccd.cpp =================================================================== --- kstars/indi/indiccd.cpp +++ kstars/indi/indiccd.cpp @@ -784,11 +784,6 @@ }); } -CCD::~CCD() -{ - delete fv; -} - void CCD::registerProperty(INDI::Property *prop) { if (isConnected()) @@ -1403,10 +1398,10 @@ if (Options::useDSLRImageViewer() || targetChip->isBatchMode() == false) { - if (imageViewer.isNull()) - imageViewer = new ImageViewer(getDeviceName(), KStars::Instance()); + if (m_ImageViewerWindow.isNull()) + m_ImageViewerWindow = new ImageViewer(getDeviceName(), KStars::Instance()); - imageViewer->loadImage(filename); + m_ImageViewerWindow->loadImage(filename); } } // Unless we have cfitsio, we're done. @@ -1420,20 +1415,20 @@ // this should be fixed in the future and should only use FITSData if (Options::useFITSViewer() || targetChip->isBatchMode() == false) { - if (fv.isNull() && targetChip->getCaptureMode() != FITS_GUIDE && + if (m_FITSViewerWindows.isNull() && targetChip->getCaptureMode() != FITS_GUIDE && targetChip->getCaptureMode() != FITS_FOCUS && targetChip->getCaptureMode() != FITS_ALIGN) { normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1; if (Options::singleWindowCapturedFITS()) - fv = KStars::Instance()->genericFITSViewer(); + m_FITSViewerWindows = KStars::Instance()->genericFITSViewer(); else { - fv = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); - KStars::Instance()->addFITSViewer(fv); + m_FITSViewerWindows = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); + KStars::Instance()->addFITSViewer(m_FITSViewerWindows); } - connect(fv, &FITSViewer::closed, [&](int tabIndex) { + connect(m_FITSViewerWindows, &FITSViewer::closed, [&](int tabIndex) { if (tabIndex == normalTabID) normalTabID = -1; else if (tabIndex == calibrationTabID) @@ -1479,27 +1474,27 @@ // Check if we need to display the image if (Options::useFITSViewer() || targetChip->isBatchMode() == false) { - fv->disconnect(this); + m_FITSViewerWindows->disconnect(this); auto m_Loaded = std::make_shared(); - *m_Loaded = connect(fv, &FITSViewer::loaded, [=](int tabIndex) { + *m_Loaded = connect(m_FITSViewerWindows, &FITSViewer::loaded, [=](int tabIndex) { *tabID = tabIndex; - targetChip->setImageView(fv->getView(tabIndex), captureMode); + targetChip->setImageView(m_FITSViewerWindows->getView(tabIndex), captureMode); QObject::disconnect(*m_Loaded); emit BLOBUpdated(bp); }); auto m_Failed = std::make_shared(); - *m_Failed = connect(fv, &FITSViewer::failed, [=]() { + *m_Failed = connect(m_FITSViewerWindows, &FITSViewer::failed, [=]() { // If opening file fails, we treat it the same as exposure failure and recapture again if possible emit newExposureValue(targetChip, 0, IPS_ALERT); QObject::disconnect(*m_Failed); return; }); if (*tabID == -1 || Options::singlePreviewFITS() == false) - fv->addFITS(fileURL, captureMode, captureFilter, previewTitle); + m_FITSViewerWindows->addFITS(fileURL, captureMode, captureFilter, previewTitle); else - fv->updateFITS(fileURL, *tabID, captureFilter); + m_FITSViewerWindows->updateFITS(fileURL, *tabID, captureFilter); } else // If not displayed in FITS Viewer then we just inform that a blob was received. @@ -1535,7 +1530,7 @@ // Image type is either NORMAL or CALIBRATION since the rest have their dedicated windows. // NORMAL is used for raw INDI drivers without Ekos. if ( (Options::useFITSViewer() || targetChip->isBatchMode() == false) && (mode == FITS_NORMAL || mode == FITS_CALIBRATE)) - fv->show(); + m_FITSViewerWindows->show(); QObject::disconnect(*m_Loaded); emit BLOBUpdated(bp); @@ -1612,7 +1607,7 @@ void CCD::FITSViewerDestroyed() { - fv = nullptr; + m_FITSViewerWindows = nullptr; normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1; } Index: kstars/kstars.cpp =================================================================== --- kstars/kstars.cpp +++ kstars/kstars.cpp @@ -316,6 +316,10 @@ // GUIManager::Instance()->close(); #endif +#ifdef HAVE_CFITSIO + qDeleteAll(m_FITSViewers); +#endif + QSqlDatabase::removeDatabase("userdb"); QSqlDatabase::removeDatabase("skydb"); } @@ -383,6 +387,7 @@ //Note: This uses style sheets to set the dark colors, this should be cross platform. Palettes have a different behavior on OS X and Windows as opposed to Linux. //It might be a good idea to use stylesheets in the future instead of palettes but this will work for now for OS X. //This is also in KStarsDbus.cpp. If you change it, change it in BOTH places. +@code #ifdef Q_OS_OSX if (Options::darkAppColors()) qApp->setStyleSheet( @@ -417,6 +422,7 @@ else qApp->setStyleSheet(""); #endif +@endcode **/ //Set toolbar options from config file Index: kstars/kstars.notifyrc =================================================================== --- kstars/kstars.notifyrc +++ 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] Index: kstars/kstarslite/skyitems/deepskyitem.h =================================================================== --- kstars/kstarslite/skyitems/deepskyitem.h +++ kstars/kstarslite/skyitems/deepskyitem.h @@ -44,14 +44,14 @@ DeepSkyIndex *m_index { nullptr }; QSGNode *m_trixels { nullptr }; - /** @short m_labelType - holds label type of this catalog */ + /** @short m_labelType holds label type of this catalog */ LabelsItem::label_t m_labelType; - /** @short schemeColor - holds the color, with which nodes of this catalog should be drawn */ + /** @short schemeColor holds the color, with which nodes of this catalog should be drawn */ QString schemeColor; }; /** - * @short The DSOTrixelNode class - represents trixel. Symbols should be appended to m_symbols, labels to + * @short The DSOTrixelNode class represents trixel. Symbols should be appended to m_symbols, labels to * m_labels and DeepSkyNodes directly to DSOTrixelNode */ class DSOTrixelNode : public TrixelNode Index: kstars/org.kde.kstars.Ekos.Mount.xml =================================================================== --- kstars/org.kde.kstars.Ekos.Mount.xml +++ kstars/org.kde.kstars.Ekos.Mount.xml @@ -7,9 +7,6 @@ - - - @@ -22,7 +19,6 @@ - Index: kstars/skycomponents/stars.dox =================================================================== --- kstars/skycomponents/stars.dox +++ kstars/skycomponents/stars.dox @@ -117,10 +117,11 @@ \subsection MemoryMap Memory map Memory map of unnamed stars: + StarComponent - + One DeepStarComponent for every catalog - + One StarBlockList for each trixel + StarBlockFactory churns out StarBlocks - + Several StarBlocks are attached / detached dynamically to a StarBlockList - + Several StarObjects (usu. 100) in a StarBlock + + One DeepStarComponent for every catalog + + One StarBlockList for each trixel + + StarBlockFactory churns out StarBlocks + + Several StarBlocks are attached / detached dynamically to a StarBlockList + + Several StarObjects (usu. 100) in a StarBlock \subsection Loading Loading unnamed stars As mentioned earlier, unnamed stars may be both static and dynamic, and could be both deep and shallow.