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 };