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 @@ -47,6 +47,7 @@ * - Auto and manual focus modes using Half-Flux-Radius (HFR) method. * - Automated unattended meridian flip. Ekos performs post meridian flip alignment, calibration, and guiding to resume the capture session. * - Automatic focus between exposures when a user-configurable HFR limit is exceeded. + * - Automatic focus between exposures when the temperature has changed a lot since last focus. * - Auto guiding with support for automatic dithering between exposures and support for Adaptive Optics devices in addition to traditional guiders. * - Powerful sequence queue for batch capture of images with optional prefixes, timestamps, filter wheel selection, and much more! * - Export and import sequence queue sets as Ekos Sequence Queue (.esq) files. @@ -607,6 +608,7 @@ { focusHFR = newHFR; } + void setFocusTemperatureDelta(double focusTemperatureDelta); // Return TRUE if we need to run focus/autofocus. Otherwise false if not necessary bool startFocusIfRequired(); @@ -874,8 +876,14 @@ QMap> HFRMap; double fileHFR { 0 }; // HFR value as loaded from the sequence file - // Refocus every N minutes + // Refocus in progress because of time forced refocus or temperature change bool isRefocus { false }; + + // Focus on Temperature change + bool isTemperatureDeltaCheckActive { false }; + double focusTemperatureDelta { 0 }; // Temperature delta as received from the Ekos focus module + + // Refocus every N minutes int refocusEveryNMinutesValue { 0 }; // number of minutes between forced refocus QElapsedTimer refocusEveryNTimer; // used to determine when next force refocus should occur 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 @@ -242,35 +242,51 @@ Options::setHFRDeviation(HFRPixels->value()); }); - // 5. Refocus Every Check + // 5. Autofocus temperature Check + temperatureDeltaCheck->setChecked(Options::enforceAutofocusOnTemperature()); + connect(temperatureDeltaCheck, &QCheckBox::toggled, [ = ](bool checked) + { + Options::setEnforceAutofocusOnTemperature(checked); + if (checked == false) + isTemperatureDeltaCheckActive = false; + }); + + // 6. Autofocus temperature Delta + temperatureDelta->setValue(Options::maxFocusTemperatureDelta()); + connect(temperatureDelta, &QDoubleSpinBox::editingFinished, [ = ]() + { + Options::setMaxFocusTemperatureDelta(temperatureDelta->value()); + }); + + // 7. Refocus Every Check refocusEveryNCheck->setChecked(Options::enforceRefocusEveryN()); connect(refocusEveryNCheck, &QCheckBox::toggled, [ = ](bool checked) { Options::setEnforceRefocusEveryN(checked); }); - // 6. Refocus Every Value + // 8. Refocus Every Value refocusEveryN->setValue(Options::refocusEveryN()); connect(refocusEveryN, &QDoubleSpinBox::editingFinished, [ = ]() { Options::setRefocusEveryN(refocusEveryN->value()); }); - // 7. File settings: filter name + // 9. File settings: filter name filterCheck->setChecked(Options::fileSettingsUseFilter()); connect(filterCheck, &QCheckBox::toggled, [ = ](bool checked) { Options::setFileSettingsUseFilter(checked); }); - // 8. File settings: duration + // 10. File settings: duration expDurationCheck->setChecked(Options::fileSettingsUseDuration()); connect(expDurationCheck, &QCheckBox::toggled, [ = ](bool checked) { Options::setFileSettingsUseDuration(checked); }); - // 9. File settings: timestamp + // 11. File settings: timestamp ISOCheck->setChecked(Options::fileSettingsUseTimestamp()); connect(ISOCheck, &QCheckBox::toggled, [ = ](bool checked) { @@ -289,6 +305,7 @@ QDoubleSpinBox * const dspinBoxes[] { HFRPixels, + temperatureDelta, guideDeviation, }; for (const QDoubleSpinBox * control : dspinBoxes) @@ -610,7 +627,8 @@ appendLogText(i18n("Warning: Guide deviation is selected but autoguide process was not started.")); if (autofocusCheck->isChecked() && m_AutoFocusReady == false) appendLogText(i18n("Warning: in-sequence focusing is selected but autofocus process was not started.")); - + if (temperatureDeltaCheck->isChecked() && m_AutoFocusReady == false) + appendLogText(i18n("Warning: temperature delta check is selected but autofocus process was not started.")); prepareJob(first_job); } @@ -1761,6 +1779,7 @@ isInSequenceFocus = (m_AutoFocusReady && autofocusCheck->isChecked()/* && HFRPixels->value() > 0*/); // if (isInSequenceFocus) // requiredAutoFocusStarted = false; + isTemperatureDeltaCheckActive = (m_AutoFocusReady && temperatureDeltaCheck->isChecked()); // Reset HFR pixels to file value after meridian flip if (isInSequenceFocus && meridianFlipStage != MF_NONE && meridianFlipStage != MF_READY) @@ -1886,20 +1905,36 @@ if (activeJob == nullptr || activeJob->getFrameType() != FRAME_LIGHT) return false; + isRefocus = false; + // check if time for forced refocus if (refocusEveryNCheck->isChecked()) { qCDebug(KSTARS_EKOS_CAPTURE) << "Focus elapsed time (secs): " << getRefocusEveryNTimerElapsedSec() << ". Requested Interval (secs): " << refocusEveryN->value() * 60; - isRefocus = getRefocusEveryNTimerElapsedSec() >= refocusEveryN->value() * 60; + + if (getRefocusEveryNTimerElapsedSec() >= refocusEveryN->value() * 60) + { + isRefocus = true; + appendLogText(i18n("Scheduled refocus starting after %1 seconds...", getRefocusEveryNTimerElapsedSec())); + } } - else - isRefocus = false; - if (isRefocus) + if (!isRefocus && isTemperatureDeltaCheckActive) { - appendLogText(i18n("Scheduled refocus starting after %1 seconds...", getRefocusEveryNTimerElapsedSec())); + qCDebug(KSTARS_EKOS_CAPTURE) << "Focus temperature delta (°C): " << focusTemperatureDelta << + ". Requested maximum delta (°C): " << temperatureDelta->value(); + if (focusTemperatureDelta > temperatureDelta->value()) + { + isRefocus = true; + appendLogText(i18n("Refocus starting because of temperature change of %1 °C...", focusTemperatureDelta)); + } + } + + // Either it is time to force autofocus or temperature has changed + if (isRefocus) + { secondsLabel->setText(i18n("Focusing...")); if (currentCCD->isLooping()) @@ -1910,7 +1945,7 @@ emit resetFocus(); // force refocus - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1904."; + qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; emit checkFocus(0.1); m_State = CAPTURE_FOCUSING; @@ -1940,12 +1975,12 @@ if (HFRPixels->value() == 0) { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1934."; + qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; emit checkFocus(0.1); } else { - qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line 1939."; + qCDebug(KSTARS_EKOS_CAPTURE) << "Capture is triggering autofocus on line " << __LINE__; emit checkFocus(HFRPixels->value()); } @@ -3237,6 +3272,12 @@ captureImage(); } +void Capture::setFocusTemperatureDelta(double focusTemperatureDelta) +{ + qCDebug(KSTARS_EKOS_CAPTURE) << "setFocusTemperatureDelta: " << focusTemperatureDelta; + this->focusTemperatureDelta = focusTemperatureDelta; +} + void Capture::setGuideDeviation(double delta_ra, double delta_dec) { // if (activeJob == nullptr) @@ -3783,6 +3824,12 @@ fileHFR = HFRValue > 0.0 ? HFRValue : 0.0; HFRPixels->setValue(fileHFR); } + else if (!strcmp(tagXMLEle(ep), "RefocusOnTemperatureDelta")) + { + temperatureDeltaCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); + double const deltaValue = cLocale.toDouble(pcdataXMLEle(ep)); + temperatureDelta->setValue(deltaValue); + } else if (!strcmp(tagXMLEle(ep), "RefocusEveryN")) { refocusEveryNCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true")); @@ -4147,6 +4194,8 @@ "Current HFR value will not be written to sequence file.")); outstream << "" << cLocale.toString(Options::saveHFRToFile() ? HFRPixels->value() : 0) << "" << endl; + outstream << "" + << cLocale.toString(temperatureDelta->value()) << "" << endl; outstream << "" << cLocale.toString(refocusEveryN->value()) << "" << endl; foreach (SequenceJob * job, jobs) diff --git a/kstars/ekos/capture/capture.ui b/kstars/ekos/capture/capture.ui --- a/kstars/ekos/capture/capture.ui +++ b/kstars/ekos/capture/capture.ui @@ -1118,7 +1118,29 @@ - + + + + true + + + 3 + + + 0.100000000000000 + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + true @@ -1144,7 +1166,7 @@ - + true @@ -1173,6 +1195,19 @@ + + + + true + + + <html><head/><body><p>Perform autofocus when the change in temperature since last focus exceeded this limit</p></body></html> + + + Autofocus if T° change > + + + @@ -1202,14 +1237,14 @@ - + minutes - + Qt::Vertical @@ -1222,6 +1257,13 @@ + + + + °C + + + @@ -1590,12 +1632,12 @@ QAbstractItemView::SelectRows - - 30 - 80 + + 30 + Status diff --git a/kstars/ekos/focus/focus.h b/kstars/ekos/focus/focus.h --- a/kstars/ekos/focus/focus.h +++ b/kstars/ekos/focus/focus.h @@ -367,6 +367,7 @@ void newLog(const QString &text); void newStatus(Ekos::FocusState state); void newHFR(double hfr, int position); + void newFocusTemperatureDelta(double delta); void absolutePositionChanged(int value); void focusPositionAdjusted(); @@ -448,6 +449,8 @@ */ bool appendHFR(double newHFR); + void getCurrentFocuserTemperature(); + /// Focuser device needed for focus operation ISD::Focuser *currentFocuser { nullptr }; /// CCD device needed for focus operation @@ -639,5 +642,8 @@ int linearRequestedPosition { 0 }; bool hasDeviation { false }; + + double currentTemperature { INVALID_VALUE }; + double lastFocusTemperature { INVALID_VALUE }; }; } diff --git a/kstars/ekos/focus/focus.cpp b/kstars/ekos/focus/focus.cpp --- a/kstars/ekos/focus/focus.cpp +++ b/kstars/ekos/focus/focus.cpp @@ -524,6 +524,8 @@ focusBacklashSpin->setValue(0); } + getCurrentFocuserTemperature(); + connect(currentFocuser, &ISD::GDInterface::numberUpdated, this, &Ekos::Focus::processFocusNumber, Qt::UniqueConnection); //connect(currentFocuser, SIGNAL(propertyDefined(INDI::Property*)), this, &Ekos::Focus::(registerFocusProperty(INDI::Property*)), Qt::UniqueConnection); @@ -572,6 +574,22 @@ } } +void Focus::getCurrentFocuserTemperature() +{ + INumberVectorProperty *focuserTemperature = currentFocuser->getBaseDevice()->getNumber("FOCUS_TEMPERATURE"); + + if (focuserTemperature && focuserTemperature->s != IPS_ALERT) + { + currentTemperature = focuserTemperature->np[0].value; + qCDebug(KSTARS_EKOS_FOCUS) << QString("Setting current focuser temperature: %1").arg(currentTemperature, 0, 'f', 2); + } + else + { + currentTemperature = INVALID_VALUE; + qCDebug(KSTARS_EKOS_FOCUS) << QString("Focuser temperature is not available"); + } +} + void Focus::start() { if (currentCCD == nullptr) @@ -590,6 +608,10 @@ lastHFR = 0; + // Forget last focus temperature, reset temperature delta + lastFocusTemperature = INVALID_VALUE; + emit newFocusTemperatureDelta(0); + if (canAbsMove) { absIterations = 0; @@ -2308,15 +2330,29 @@ if (QString(nvp->name).contains("focus", Qt::CaseInsensitive) == false) return; - qCDebug(KSTARS_EKOS_FOCUS) << QString("processFocusNumber %1 %2") - .arg(nvp->name).arg(nvp->s == IPS_OK ? "OK" : "ERROR"); + qCDebug(KSTARS_EKOS_FOCUS) << QString("processFocusNumber %1 state: %2") + .arg(nvp->name).arg(nvp->s); if (!strcmp(nvp->name, "FOCUS_BACKLASH_STEPS")) { focusBacklashSpin->setValue(nvp->np[0].value); return; } + if (!strcmp(nvp->name, "FOCUS_TEMPERATURE")) + { + currentTemperature = nvp->np[0].value; + if (lastFocusTemperature != INVALID_VALUE && currentTemperature != INVALID_VALUE) + { + emit newFocusTemperatureDelta(abs(currentTemperature - lastFocusTemperature)); + } + else + { + emit newFocusTemperatureDelta(0); + } + return; + } + if (!strcmp(nvp->name, "ABS_FOCUS_POSITION")) { INumber *pos = IUFindNumber(nvp, "FOCUS_ABSOLUTE_POSITION"); @@ -2790,15 +2826,10 @@ { // CR add auto focus position, temperature and filter to log in CSV format // this will help with setting up focus offsets and temperature compensation - INDI::Property * np = currentFocuser->getProperty("TemperatureNP"); - double temperature = -274; // impossible temperature as a signal that it isn't available - if (np != nullptr) - { - INumberVectorProperty * tnp = np->getNumber(); - temperature = tnp->np[0].value; - } - qCInfo(KSTARS_EKOS_FOCUS) << "Autofocus values: position, " << currentPosition << ", temperature, " << temperature << - ", filter, " << filter(); + qCInfo(KSTARS_EKOS_FOCUS) << "Autofocus values: position, " << currentPosition << ", temperature, " + << currentTemperature << ", filter, " << filter(); + lastFocusTemperature = currentTemperature; + emit newFocusTemperatureDelta(0); } // In case of failure, go back to last position if the focuser is absolute diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -3336,6 +3336,7 @@ // Check focus HFR value connect(captureProcess.get(), &Ekos::Capture::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); + // Reset Focus connect(captureProcess.get(), &Ekos::Capture::resetFocus, focusProcess.get(), &Ekos::Focus::resetFrame, Qt::UniqueConnection); @@ -3345,6 +3346,9 @@ Qt::UniqueConnection); // New Focus HFR connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); + + // New Focus temperature delta + connect(focusProcess.get(), &Ekos::Focus::newFocusTemperatureDelta, captureProcess.get(), &Ekos::Capture::setFocusTemperatureDelta, Qt::UniqueConnection); } // Capture <---> Align connections diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg --- a/kstars/kstars.kcfg +++ b/kstars/kstars.kcfg @@ -1622,14 +1622,23 @@ If HFR deviation exceeds this limit, the autofocus routine will be automatically started. 0.5 + + + If the temperature change exceeds this limit, the autofocus routine will be automatically started. + 1 + false false + + + false + false