File indexing completed on 2024-04-14 14:12:04
0001 /* 0002 Helper class of KStars UI capture tests 0003 0004 SPDX-FileCopyrightText: 2020 Wolfgang Reissenberger <sterne-jaeger@openfuture.de> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "config-kstars.h" 0010 #include "test_ekos_debug.h" 0011 #include "test_ekos_helper.h" 0012 #include "test_ekos_simulator.h" 0013 0014 #include "ekos/profileeditor.h" 0015 #include "ekos/capture/placeholderpath.h" 0016 0017 #include <QObject> 0018 0019 #pragma once 0020 0021 /** @brief Helper to retrieve a gadget in KStars. 0022 * @param klass is the class of the gadget to look for. 0023 * @param name is the gadget name to look for in the UI configuration. 0024 * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Capture module 0025 */ 0026 #define KTRY_KSTARS_GADGET(klass, name) klass * const name = KStars::Instance()->findChild<klass*>(#name); \ 0027 QVERIFY2(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) 0028 0029 /** @brief Helper to show the Capture tab 0030 */ 0031 #define KTRY_CAPTURE_SHOW() do { \ 0032 QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->captureModule() != nullptr, 5000); \ 0033 KTRY_EKOS_GADGET(QTabWidget, toolsWidget); \ 0034 toolsWidget->setCurrentWidget(Ekos::Manager::Instance()->captureModule()); \ 0035 QTRY_COMPARE_WITH_TIMEOUT(toolsWidget->currentWidget(), Ekos::Manager::Instance()->captureModule(), 5000); \ 0036 QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->captureModule()->camera().isEmpty(), 5000); } while (false) 0037 0038 /** @brief Helper to retrieve a gadget in the Capture tab specifically. 0039 * @param klass is the class of the gadget to look for. 0040 * @param name is the gadget name to look for in the UI configuration. 0041 * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Capture module 0042 */ 0043 #define KTRY_CAPTURE_GADGET(klass, name) klass * const name = Ekos::Manager::Instance()->captureModule()->findChild<klass*>(#name); \ 0044 QVERIFY2(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) 0045 0046 0047 /** @brief Helper to click a button in the Capture tab specifically. 0048 * @param button is the gadget name of the button to look for in the UI configuration. 0049 * @warning Fails the test if the button is not currently enabled. 0050 */ 0051 #define KTRY_CAPTURE_CLICK(button) do { \ 0052 QTimer::singleShot(100, Ekos::Manager::Instance(), []() { \ 0053 KTRY_CAPTURE_GADGET(QPushButton, button); \ 0054 QTRY_VERIFY2_WITH_TIMEOUT(button->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(#button).toStdString().c_str(), 10000); \ 0055 QTest::mouseClick(button, Qt::LeftButton); }); \ 0056 QTest::qWait(200); } while(false) 0057 0058 /** @brief Helper to set a string text into a QComboBox in the Capture module. 0059 * @param combobox is the gadget name of the QComboBox to look for in the UI configuration. 0060 * @param text is the string text to set in the gadget. 0061 * @note This is a contrived method to set a text into a QComboBox programmatically *and* emit the "activated" message. 0062 * @warning Fails the test if the name does not exist in the Capture UI or if the text cannot be set in the gadget. 0063 */ 0064 #define KTRY_CAPTURE_COMBO_SET(combobox, text) do { \ 0065 KTRY_CAPTURE_GADGET(QComboBox, combobox); \ 0066 int const cbIndex = combobox->findText(text); \ 0067 QVERIFY(0 <= cbIndex); \ 0068 combobox->setCurrentIndex(cbIndex); \ 0069 combobox->activated(cbIndex); \ 0070 QCOMPARE(combobox->currentText(), QString(text)); } while(false); 0071 0072 /** @brief Helper to configure a frame. 0073 * @param frametype Light, Dark, Bias or Flat 0074 * @param exposure is the exposure duration. 0075 * @param count is the number of exposures to execute. 0076 * @param delay is the delay after exposure. 0077 * @param filter is the filter name to set. 0078 * @param target name of the target. 0079 * @param destination is the folder to store fames to. 0080 */ 0081 #define KTRY_CAPTURE_CONFIGURE_FRAME(frametype, exposure, count, delay, filter, target, destination) do { \ 0082 KTRY_CAPTURE_GADGET(QDoubleSpinBox, captureExposureN); \ 0083 captureExposureN->setValue(static_cast<double>(exposure)); \ 0084 KTRY_CAPTURE_GADGET(QSpinBox, captureCountN); \ 0085 captureCountN->setValue(static_cast<int>(count)); \ 0086 KTRY_CAPTURE_GADGET(QSpinBox, captureDelayN); \ 0087 captureDelayN->setValue(static_cast<int>(delay)); \ 0088 KTRY_CAPTURE_GADGET(QComboBox, captureTypeS); \ 0089 KTRY_CAPTURE_COMBO_SET(captureTypeS, frametype); \ 0090 KTRY_CAPTURE_GADGET(QComboBox, FilterPosCombo); \ 0091 KTRY_CAPTURE_COMBO_SET(FilterPosCombo, (filter)); \ 0092 KTRY_CAPTURE_GADGET(QLineEdit, targetNameT); \ 0093 targetNameT->setText(target); \ 0094 KTRY_CAPTURE_GADGET(QLineEdit, fileDirectoryT); \ 0095 fileDirectoryT->setText(destination); } while(false) 0096 0097 /** @brief Helper to add a frame to a Capture job. 0098 * @param frametype Light, Dark, Bias or Flat 0099 * @param exposure is the exposure duration. 0100 * @param count is the number of exposures to execute. 0101 * @param delay is the delay after exposure. 0102 * @param filter is the filter name to set. 0103 * @param target name of the target. 0104 * @param destination is the folder to store fames to. 0105 */ 0106 #define KTRY_CAPTURE_ADD_FRAME(frametype, exposure, count, delay, filter, target, destination) do { \ 0107 KTRY_CAPTURE_GADGET(QTableWidget, queueTable); \ 0108 int const jcount = queueTable->rowCount(); \ 0109 KTRY_CAPTURE_CONFIGURE_FRAME(frametype, exposure, count, delay, filter, target, destination); \ 0110 KTRY_CAPTURE_CLICK(addToQueueB); \ 0111 QTRY_VERIFY_WITH_TIMEOUT(queueTable->rowCount() == (jcount+1), 1000); } while(false); 0112 0113 0114 /** @brief Helper to configure a Light frame. 0115 * @param exposure is the exposure duration. 0116 * @param count is the number of exposures to execute. 0117 * @param delay is the delay after exposure. 0118 * @param filter is the filter name to set. 0119 * @param target name of the target. 0120 * @param destination is the folder to store fames to. 0121 */ 0122 #define KTRY_CAPTURE_CONFIGURE_LIGHT(exposure, count, delay, filter, target, destination) \ 0123 KTRY_CAPTURE_CONFIGURE_FRAME("Light", exposure, count, delay, filter, target, destination) 0124 0125 /** @brief Helper to add a Light frame to a Capture job. 0126 * @param exposure is the exposure duration. 0127 * @param count is the number of exposures to execute. 0128 * @param delay is the delay after exposure. 0129 * @param filter is the filter name to set. 0130 * @param target name of the target. 0131 * @param destination is the folder to store fames to. 0132 */ 0133 #define KTRY_CAPTURE_ADD_LIGHT(exposure, count, delay, filter, target, destination) \ 0134 KTRY_CAPTURE_ADD_FRAME("Light", exposure, count, delay, filter, target, destination) 0135 0136 0137 /** @brief Helper to configure a Flat frame. 0138 * @param exposure is the exposure duration. 0139 * @param count is the number of exposures to execute. 0140 * @param delay is the delay after exposure. 0141 * @param filter is the filter name to set. 0142 * @param destination is the folder to store fames to. 0143 */ 0144 #define KTRY_CAPTURE_CONFIGURE_FLAT(exposure, count, delay, filter, destination) \ 0145 KTRY_CAPTURE_CONFIGURE_FRAME("Flat", exposure, count, delay, filter, "", destination) 0146 0147 /** @brief Helper to add a flat frame to a Capture job. 0148 * @param exposure is the exposure duration. 0149 * @param count is the number of exposures to execute. 0150 * @param delay is the delay after exposure. 0151 * @param filter is the filter name to set. 0152 * @param destination is the folder to store fames to. 0153 */ 0154 #define KTRY_CAPTURE_ADD_FLAT(exposure, count, delay, filter, destination) \ 0155 KTRY_CAPTURE_ADD_FRAME("Flat", exposure, count, delay, filter, "", destination) 0156 0157 0158 0159 class TestEkosCaptureHelper : public TestEkosHelper 0160 { 0161 0162 public: 0163 0164 struct OptDouble { 0165 bool enabled = false; 0166 double value = 0.0; 0167 }; 0168 0169 struct OptInt { 0170 bool enabled = false; 0171 double value = 0; 0172 }; 0173 0174 typedef enum { 0175 ESQ_VERSION_2_4, 0176 ESQ_VERSION_2_5, 0177 ESQ_VERSION_2_6 0178 } ESQVersion; 0179 0180 const QList<QString> esqVersionNames = {"2.4", "2.5", "2.6"}; 0181 0182 struct CaptureSettings { 0183 QString observer = ""; 0184 OptDouble guideDeviation, startGuideDeviation; 0185 OptDouble inSequenceFocus, autofocusOnTemperature; 0186 OptInt refocusEveryN; 0187 bool refocusAfterMeridianFlip = false; 0188 }; 0189 0190 struct SimpleCaptureLightsJob 0191 { 0192 ESQVersion version = ESQ_VERSION_2_6; 0193 int exposureTime = 1.0; 0194 int count = 1; 0195 int delayMS = 0; // delay in milliseconds 0196 int binX = 1, binY = 1; 0197 int x = 0, y = 0, w = 1280, h = 1080; 0198 QString filterName = "Luminance"; 0199 QString type = "Light"; 0200 QString encoding = "FITS"; 0201 QString targetName = "test"; 0202 QString fitsDirectory = "/home/pi"; 0203 QString placeholderFormat = "/%t/%T/%F/%t_%T_%F_%e_%D"; 0204 int formatSuffix = 3; 0205 OptDouble cameraTemperature = {false, 20.0}; 0206 int uploadMode = 0; 0207 }; 0208 0209 struct SimpleCaptureCalibratingJob 0210 { 0211 ESQVersion version = ESQ_VERSION_2_6; 0212 int exposureTime = 1.0; 0213 QString type = "Flat"; 0214 int count = 1; 0215 uint preAction = 0; 0216 double wall_az = 90, wall_alt = 0; 0217 bool duration_manual = true, duration_adu = false; 0218 int adu = 10000, tolerance = 1000; 0219 }; 0220 0221 explicit TestEkosCaptureHelper(QString guider = nullptr); 0222 0223 /** 0224 * @brief Initialization ahead of executing the test cases. 0225 */ 0226 void init() override; 0227 0228 /** 0229 * @brief Cleanup after test cases have been executed. 0230 */ 0231 void cleanup() override; 0232 0233 /** 0234 * @brief Helper function for start of capturing 0235 * @param checkCapturing set to true if check of capturing should be included 0236 */ 0237 bool startCapturing(bool checkCapturing = true); 0238 0239 /** 0240 * @brief Helper function to stop capturing 0241 */ 0242 bool stopCapturing(); 0243 0244 /** 0245 * @brief Fill the capture sequences in the Capture GUI 0246 * @param target capturing target name 0247 * @param sequence comma separated list of <filter>:<count> 0248 * @param exptime exposure time 0249 * @param fitsDirectory directory where the captures will be placed 0250 * @param format image location for 0251 * @param delay delay between frame captures 0252 * @return true if everything was successful 0253 */ 0254 bool fillCaptureSequences(QString target, QString sequence, double exptime, QString fitsDirectory, int delay = 0, QString format = Ekos::PlaceholderPath::defaultFormat(true, false, false)); 0255 0256 /** 0257 * @brief Fill the fields of the script manager in the capture module 0258 * @param scripts Pre-... and post-... scripts 0259 */ 0260 bool fillScriptManagerDialog(const QMap<Ekos::ScriptTypes, QString> &scripts); 0261 0262 /** 0263 * @brief Stop and clean up scheduler 0264 */ 0265 void cleanupScheduler(); 0266 0267 /** 0268 * @brief getSimpleEsqContent Create a simple lights capture sequence file 0269 * @param settings overall settings 0270 * @param jobs list of sequence jobs 0271 * @param version file version 0272 * @return XML string list 0273 */ 0274 QStringList getSimpleEsqContent(CaptureSettings settings, QVector<SimpleCaptureLightsJob> jobs, ESQVersion version = ESQ_VERSION_2_6); 0275 0276 /** 0277 * @brief getSimpleEsqContent Create a simple flats capture sequence file 0278 * @param settings overall settings 0279 * @param jobs list of sequence jobs 0280 * @param version file version 0281 * @return XML string list 0282 */ 0283 QStringList getSimpleEsqContent(CaptureSettings settings, QVector<SimpleCaptureCalibratingJob> jobs, ESQVersion version = ESQ_VERSION_2_6); 0284 0285 /** 0286 * @brief serializeGeneralSettings Create the XML representation of the general settings 0287 */ 0288 QStringList serializeGeneralSettings(CaptureSettings settings, ESQVersion version = ESQ_VERSION_2_6); 0289 0290 /** 0291 * @brief serializeJob Create the XML representation of a single lights job 0292 */ 0293 QStringList serializeJob(const SimpleCaptureLightsJob &job, ESQVersion version = ESQ_VERSION_2_6); 0294 0295 /** 0296 * @brief serializeJob Create the XML representation of a single flats job 0297 */ 0298 QStringList serializeJob(const SimpleCaptureCalibratingJob &job, ESQVersion version = ESQ_VERSION_2_6); 0299 0300 /** 0301 * @brief calculateSignature Calculate the signature of a given filter 0302 * @param filter filter name 0303 * @param fitsDirectory base directory holding all images 0304 * @return signature 0305 */ 0306 QString calculateSignature(QString target, QString filter, QString fitsDirectory); 0307 0308 /** 0309 * @brief Search for FITS files recursively 0310 * @param dir starting directory 0311 * @return list of file names 0312 */ 0313 QStringList searchFITS(QDir const &dir) const; 0314 0315 /** 0316 * @brief Ensure that it is known whether the CCD device has a shutter or not 0317 * @param shutter set to true iff a shutter should be present 0318 */ 0319 void ensureCCDShutter(bool shutter); 0320 0321 QDir *getImageLocation(); 0322 0323 // destination where images will be located 0324 QTemporaryDir *destination; 0325 0326 private: 0327 QDir *imageLocation = nullptr; 0328 0329 0330 };