File indexing completed on 2024-04-21 14:47:24

0001 /*
0002     Helper class of KStars UI tests
0003 
0004     SPDX-FileCopyrightText: 2021 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "test_ekos_helper.h"
0010 #include "ksutils.h"
0011 #include "Options.h"
0012 #include "ekos/profileeditor.h"
0013 #include "ekos/guide/internalguide/gmath.h"
0014 #include "ekos/auxiliary/opticaltrainmanager.h"
0015 #include "ekos/align/align.h"
0016 #include "ekos/capture/capture.h"
0017 #include "ekos/scheduler/scheduler.h"
0018 #include "ekos/auxiliary/filtermanager.h"
0019 #include "kstarsdata.h"
0020 
0021 TestEkosHelper::TestEkosHelper(QString guider)
0022 {
0023     m_Guider = guider;
0024     m_MountDevice = "Telescope Simulator";
0025     m_CCDDevice   = "CCD Simulator";
0026     if (guider != nullptr)
0027         m_GuiderDevice = "Guide Simulator";
0028     m_astrometry_available = false;
0029 }
0030 
0031 
0032 void TestEkosHelper::createEkosProfile(QString name, bool isPHD2, bool *isDone)
0033 {
0034     ProfileEditor* profileEditor = Ekos::Manager::Instance()->findChild<ProfileEditor*>("profileEditorDialog");
0035 
0036     // Disable Port Selector
0037     KTRY_SET_CHECKBOX(profileEditor, portSelectorCheck, false);
0038     // Set the profile name
0039     KTRY_SET_LINEEDIT(profileEditor, profileIN, name);
0040     // select the guider type
0041     KTRY_SET_COMBO(profileEditor, guideTypeCombo, isPHD2 ? "PHD2" : "Internal");
0042     if (isPHD2)
0043     {
0044         // Write PHD2 server specs
0045         KTRY_SET_LINEEDIT(profileEditor, externalGuideHost, "localhost");
0046         KTRY_SET_LINEEDIT(profileEditor, externalGuidePort, "4400");
0047     }
0048 
0049     qCInfo(KSTARS_EKOS_TEST) << "Ekos profile " << name << " created.";
0050     // and now continue with filling the profile
0051     fillProfile(isDone);
0052 }
0053 
0054 void TestEkosHelper::fillProfile(bool *isDone)
0055 {
0056     qCInfo(KSTARS_EKOS_TEST) << "Fill profile: starting...";
0057     ProfileEditor* profileEditor = Ekos::Manager::Instance()->findChild<ProfileEditor*>("profileEditorDialog");
0058 
0059     // Select the mount device
0060     if (m_MountDevice != "")
0061     {
0062         KTRY_PROFILEEDITOR_GADGET(QComboBox, mountCombo);
0063         setTreeviewCombo(mountCombo, m_MountDevice);
0064         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Mount selected.";
0065     }
0066 
0067     // Selet the CCD device
0068     if (m_CCDDevice != "")
0069     {
0070         KTRY_PROFILEEDITOR_GADGET(QComboBox, ccdCombo);
0071         setTreeviewCombo(ccdCombo, m_CCDDevice);
0072         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: CCD selected.";
0073     }
0074 
0075     // Select the focuser device
0076     if (m_FocuserDevice != "")
0077     {
0078         KTRY_PROFILEEDITOR_GADGET(QComboBox, focuserCombo);
0079         setTreeviewCombo(focuserCombo, m_FocuserDevice);
0080         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Focuser selected.";
0081     }
0082 
0083     // Select the guider device, if not empty and different from CCD
0084     if (m_GuiderDevice != "" && m_GuiderDevice != m_CCDDevice)
0085     {
0086         KTRY_PROFILEEDITOR_GADGET(QComboBox, guiderCombo);
0087         setTreeviewCombo(guiderCombo, m_GuiderDevice);
0088         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Guider selected.";
0089     }
0090 
0091     // Select the light panel device for flats capturing
0092     if (m_LightPanelDevice != "")
0093     {
0094         KTRY_PROFILEEDITOR_GADGET(QComboBox, aux1Combo);
0095         setTreeviewCombo(aux1Combo, m_LightPanelDevice);
0096         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Light panel selected.";
0097     }
0098 
0099     // Select the dome device
0100     if (m_DomeDevice != "")
0101     {
0102         KTRY_PROFILEEDITOR_GADGET(QComboBox, domeCombo);
0103         setTreeviewCombo(domeCombo, m_DomeDevice);
0104         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Dome selected.";
0105     }
0106 
0107     // Select the rotator device
0108     if (m_RotatorDevice != "")
0109     {
0110         KTRY_PROFILEEDITOR_GADGET(QComboBox, aux2Combo);
0111         setTreeviewCombo(aux2Combo, m_RotatorDevice);
0112         qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Rotator selected.";
0113     }
0114 
0115     // wait a short time to make the setup visible
0116     QTest::qWait(1000);
0117     // Save the profile using the "Save" button
0118     QDialogButtonBox* buttons = profileEditor->findChild<QDialogButtonBox*>("dialogButtons");
0119     QVERIFY(nullptr != buttons);
0120     QTest::mouseClick(buttons->button(QDialogButtonBox::Save), Qt::LeftButton);
0121 
0122     qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Selections saved.";
0123 
0124     *isDone = true;
0125 }
0126 
0127 bool TestEkosHelper::setupEkosProfile(QString name, bool isPHD2)
0128 {
0129     qCInfo(KSTARS_EKOS_TEST) << "Setting up Ekos profile...";
0130     bool isDone = false;
0131     Ekos::Manager * const ekos = Ekos::Manager::Instance();
0132     // check if the profile with the given name exists
0133     KTRY_GADGET_SUB(ekos, QComboBox, profileCombo);
0134     if (profileCombo->findText(name) >= 0)
0135     {
0136         KTRY_GADGET_SUB(ekos, QPushButton, editProfileB);
0137 
0138         // edit Simulators profile
0139         KWRAP_SUB(KTRY_EKOS_SELECT_PROFILE(name));
0140 
0141         // edit only editable profiles
0142         if (editProfileB->isEnabled())
0143         {
0144             // start with a delay of 1 sec a new thread that edits the profile
0145             QTimer::singleShot(1000, ekos, [&] {fillProfile(&isDone);});
0146             KTRY_CLICK_SUB(ekos, editProfileB);
0147         }
0148         else
0149         {
0150             qCInfo(KSTARS_EKOS_TEST) << "Profile " << name << " not editable, setup finished.";
0151             isDone = true;
0152             return true;
0153         }
0154     }
0155     else
0156     {
0157         // start with a delay of 1 sec a new thread that edits the profile
0158         qCInfo(KSTARS_EKOS_TEST) << "Creating new profile " << name << " ...";
0159         QTimer::singleShot(1000, ekos, [&] {createEkosProfile(name, isPHD2, &isDone);});
0160         // create new profile addProfileB
0161         KTRY_CLICK_SUB(ekos, addProfileB);
0162     }
0163 
0164     // Cancel the profile editor if test did not close the editor dialog within 10 sec
0165     QTimer * closeDialog = new QTimer(this);
0166     closeDialog->setSingleShot(true);
0167     closeDialog->setInterval(10000);
0168     ekos->connect(closeDialog, &QTimer::timeout, this, [&]
0169     {
0170         ProfileEditor* profileEditor = ekos->findChild<ProfileEditor*>("profileEditorDialog");
0171         if (profileEditor != nullptr)
0172         {
0173             profileEditor->reject();
0174             qWarning(KSTARS_EKOS_TEST) << "Editing profile aborted.";
0175         }
0176     });
0177 
0178 
0179     // Click handler returned, stop the timer closing the dialog on failure
0180     closeDialog->stop();
0181     delete closeDialog;
0182 
0183     // Verification of the first test step
0184     return isDone;
0185 
0186 }
0187 
0188 void TestEkosHelper::connectModules()
0189 {
0190     Ekos::Manager * const ekos = Ekos::Manager::Instance();
0191 
0192     // wait for modules startup
0193     if (m_MountDevice != "")
0194         QTRY_VERIFY_WITH_TIMEOUT(ekos->mountModule() != nullptr, 10000);
0195     if (m_CCDDevice != "")
0196     {
0197         QTRY_VERIFY_WITH_TIMEOUT(ekos->captureModule() != nullptr, 10000);
0198         QTRY_VERIFY_WITH_TIMEOUT(ekos->alignModule() != nullptr, 10000);
0199     }
0200     if (m_GuiderDevice != "")
0201         QTRY_VERIFY_WITH_TIMEOUT(ekos->guideModule() != nullptr, 10000);
0202     if (m_FocuserDevice != "")
0203         QTRY_VERIFY_WITH_TIMEOUT(ekos->focusModule() != nullptr, 10000);
0204 
0205     // connect to the alignment process to receive align status changes
0206     connect(ekos->alignModule(), &Ekos::Align::newStatus, this, &TestEkosHelper::alignStatusChanged,
0207             Qt::UniqueConnection);
0208 
0209     if (m_MountDevice != "")
0210     {
0211         // connect to the mount process to rmount status changes
0212         connect(ekos->mountModule(), &Ekos::Mount::newStatus, this,
0213                 &TestEkosHelper::mountStatusChanged, Qt::UniqueConnection);
0214 
0215         // connect to the mount process to receive meridian flip status changes
0216         connect(ekos->mountModule()->getMeridianFlipState().get(), &Ekos::MeridianFlipState::newMountMFStatus, this,
0217                 &TestEkosHelper::meridianFlipStatusChanged, Qt::UniqueConnection);
0218     }
0219 
0220     if (m_GuiderDevice != "")
0221     {
0222         // connect to the guiding process to receive guiding status changes
0223         connect(ekos->guideModule(), &Ekos::Guide::newStatus, this, &TestEkosHelper::guidingStatusChanged,
0224                 Qt::UniqueConnection);
0225 
0226         connect(ekos->guideModule(), &Ekos::Guide::newAxisDelta, this, &TestEkosHelper::guideDeviationChanged,
0227                 Qt::UniqueConnection);
0228     }
0229 
0230     // connect to the capture process to receive capture status changes
0231     if (m_CCDDevice != "")
0232         connect(ekos->captureModule(), &Ekos::Capture::newStatus, this, &TestEkosHelper::captureStatusChanged,
0233                 Qt::UniqueConnection);
0234 
0235     // connect to the scheduler process to receive scheduler status changes
0236     connect(ekos->schedulerModule(), &Ekos::Scheduler::newStatus, this, &TestEkosHelper::schedulerStatusChanged,
0237             Qt::UniqueConnection);
0238 
0239     // connect to the focus process to receive focus status changes
0240     if (m_FocuserDevice != "")
0241         connect(ekos->focusModule(), &Ekos::Focus::newStatus, this, &TestEkosHelper::focusStatusChanged,
0242                 Qt::UniqueConnection);
0243 
0244     // connect to the dome process to receive dome status changes
0245     //    connect(ekos->domeModule(), &Ekos::Dome::newStatus, this, &TestEkosHelper::domeStatusChanged,
0246     //            Qt::UniqueConnection);
0247 }
0248 
0249 bool TestEkosHelper::startEkosProfile()
0250 {
0251     Ekos::Manager * const ekos = Ekos::Manager::Instance();
0252 
0253     KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(ekos->setupTab, 1000));
0254 
0255     if (m_Guider == "PHD2")
0256     {
0257         // Start a PHD2 instance
0258         startPHD2();
0259         // setup the EKOS profile
0260         KWRAP_SUB(QVERIFY(setupEkosProfile("Test profile (PHD2)", true)));
0261     }
0262     else
0263         KWRAP_SUB(QVERIFY(setupEkosProfile("Test profile", false)));
0264 
0265     // start the profile
0266     KTRY_EKOS_CLICK(processINDIB);
0267     // wait for the devices to come up
0268     KWRAP_SUB(QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->indiStatus() == Ekos::Success, 10000));
0269     // receive status updates from all devices
0270     connectModules();
0271 
0272     // Everything completed successfully
0273     return true;
0274 }
0275 
0276 bool TestEkosHelper::shutdownEkosProfile()
0277 {
0278     // disconnect to the dome process to receive dome status changes
0279     //    disconnect(Ekos::Manager::Instance()->domeModule(), &Ekos::Dome::newStatus, this,
0280     //               &TestEkosHelper::domeStatusChanged);
0281     // disconnect to the focus process to receive focus status changes
0282     disconnect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::newStatus, this,
0283                &TestEkosHelper::focusStatusChanged);
0284     // disconnect the guiding process to receive the current guiding status
0285     disconnect(Ekos::Manager::Instance()->guideModule(), &Ekos::Guide::newStatus, this,
0286                &TestEkosHelper::guidingStatusChanged);
0287     // disconnect the mount process to receive mount status changes
0288     disconnect(Ekos::Manager::Instance()->mountModule(), &Ekos::Mount::newStatus, this,
0289                &TestEkosHelper::mountStatusChanged);
0290     // disconnect the mount process to receive meridian flip status changes
0291     disconnect(Ekos::Manager::Instance()->mountModule()->getMeridianFlipState().get(),
0292                &Ekos::MeridianFlipState::newMountMFStatus, this,
0293                &TestEkosHelper::meridianFlipStatusChanged);
0294     // disconnect to the scheduler process to receive scheduler status changes
0295     disconnect(Ekos::Manager::Instance()->schedulerModule(), &Ekos::Scheduler::newStatus, this,
0296                &TestEkosHelper::schedulerStatusChanged);
0297     // disconnect to the capture process to receive capture status changes
0298     disconnect(Ekos::Manager::Instance()->captureModule(), &Ekos::Capture::newStatus, this,
0299                &TestEkosHelper::captureStatusChanged);
0300     // disconnect to the alignment process to receive align status changes
0301     disconnect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus, this,
0302                &TestEkosHelper::alignStatusChanged);
0303 
0304     if (m_Guider == "PHD2")
0305     {
0306         KTRY_GADGET_SUB(Ekos::Manager::Instance()->guideModule(), QPushButton, externalDisconnectB);
0307         // ensure that PHD2 is connected
0308         if (externalDisconnectB->isEnabled())
0309         {
0310             // click "Disconnect"
0311             KTRY_CLICK_SUB(Ekos::Manager::Instance()->guideModule(), externalDisconnectB);
0312             // Stop PHD2
0313             stopPHD2();
0314         }
0315     }
0316 
0317     qCInfo(KSTARS_EKOS_TEST) << "Stopping profile ...";
0318     KWRAP_SUB(KTRY_EKOS_STOP_SIMULATORS());
0319     qCInfo(KSTARS_EKOS_TEST) << "Stopping profile ... (done)";
0320     // Wait until the profile selection is enabled. This shows, that all devices are disconnected.
0321     KTRY_VERIFY_WITH_TIMEOUT_SUB(Ekos::Manager::Instance()->profileGroup->isEnabled() == true, 10000);
0322     // Everything completed successfully
0323     return true;
0324 }
0325 
0326 void TestEkosHelper::startPHD2()
0327 {
0328     phd2 = new QProcess(this);
0329     QStringList arguments;
0330     // Start PHD2 with the proper configuration
0331     phd2->start(QString("phd2"), arguments);
0332     QVERIFY(phd2->waitForStarted(3000));
0333     QTest::qWait(2000);
0334     QTRY_VERIFY_WITH_TIMEOUT(phd2->state() == QProcess::Running, 1000);
0335 
0336     // Try to connect to the PHD2 server
0337     QTcpSocket phd2_server(this);
0338     phd2_server.connectToHost(phd2_guider_host, phd2_guider_port.toUInt(), QIODevice::ReadOnly, QAbstractSocket::IPv4Protocol);
0339     if(!phd2_server.waitForConnected(5000))
0340     {
0341         QWARN(QString("Cannot continue, PHD2 server is unavailable (%1)").arg(phd2_server.errorString()).toStdString().c_str());
0342         return;
0343     }
0344     phd2_server.disconnectFromHost();
0345     if (phd2_server.state() == QTcpSocket::ConnectedState)
0346         QVERIFY(phd2_server.waitForDisconnected(1000));
0347 }
0348 
0349 void TestEkosHelper::stopPHD2()
0350 {
0351     phd2->terminate();
0352     QVERIFY(phd2->waitForFinished(5000));
0353 }
0354 
0355 void TestEkosHelper::preparePHD2()
0356 {
0357     QString const phd2_config_name = ".PHDGuidingV2";
0358     QString const phd2_config_bak_name = ".PHDGuidingV2.bak";
0359     QString const phd2_config_orig_name = ".PHDGuidingV2_mf";
0360     QStandardPaths::setTestModeEnabled(false);
0361     QFileInfo phd2_config_home(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), phd2_config_name);
0362     QFileInfo phd2_config_home_bak(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), phd2_config_bak_name);
0363     QFileInfo phd2_config_orig(phd2_config_orig_name);
0364     QStandardPaths::setTestModeEnabled(true);
0365     QWARN(QString("Writing PHD configuration file to '%1'").arg(phd2_config_home.filePath()).toStdString().c_str());
0366     if (phd2_config_home.exists())
0367     {
0368         // remove existing backup file
0369         if (phd2_config_home_bak.exists())
0370             QVERIFY(QFile::remove(phd2_config_home_bak.filePath()));
0371         // rename existing file to backup file
0372         QVERIFY(QFile::rename(phd2_config_home.filePath(), phd2_config_home_bak.filePath()));
0373     }
0374     QVERIFY2(phd2_config_orig.exists(), phd2_config_orig_name.toLocal8Bit() + " not found in current directory!");
0375     QVERIFY(QFile::copy(phd2_config_orig_name, phd2_config_home.filePath()));
0376 }
0377 
0378 void TestEkosHelper::cleanupPHD2()
0379 {
0380     QString const phd2_config = ".PHDGuidingV2";
0381     QString const phd2_config_bak = ".PHDGuidingV2.bak";
0382     QStandardPaths::setTestModeEnabled(false);
0383     QFileInfo phd2_config_home(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), phd2_config);
0384     QFileInfo phd2_config_home_bak(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), phd2_config_bak);
0385     // remove PHD2 test config
0386     if (phd2_config_home.exists())
0387         QVERIFY(QFile::remove(phd2_config_home.filePath()));
0388     // restore the backup
0389     if (phd2_config_home_bak.exists())
0390         QVERIFY(QFile::rename(phd2_config_home_bak.filePath(), phd2_config_home.filePath()));
0391 }
0392 
0393 void TestEkosHelper::prepareOpticalTrains()
0394 {
0395     Ekos::OpticalTrainManager *otm = Ekos::OpticalTrainManager::Instance();
0396     // close window, we change everything programatically
0397     otm->close();
0398     // setup train with main scope and camera
0399     QVariantMap primaryTrain = otm->getOpticalTrain(m_primaryTrain);
0400     primaryTrain["mount"] = m_MountDevice;
0401     primaryTrain["camera"] = m_CCDDevice;
0402     primaryTrain["filterwheel"] = m_CCDDevice;
0403     primaryTrain["focuser"] = m_FocuserDevice == "" ? "-" : m_FocuserDevice;
0404     primaryTrain["rotator"] = m_RotatorDevice == "" ? "-" : m_RotatorDevice;
0405     primaryTrain["lightbox"] = m_LightPanelDevice == "" ? "-" : m_LightPanelDevice;
0406     primaryTrain["dustcap"] = m_DustCapDevice == "" ? "-" : m_DustCapDevice;
0407     KStarsData::Instance()->userdb()->UpdateOpticalTrain(primaryTrain, primaryTrain["id"].toInt());
0408     if (m_GuiderDevice != "")
0409     {
0410         // setup guiding scope train
0411         QVariantMap guidingTrain = otm->getOpticalTrain(m_guidingTrain);
0412         bool isNew = guidingTrain.size() == 0;
0413         guidingTrain["mount"] = m_MountDevice;
0414         guidingTrain["camera"] = m_GuiderDevice;
0415         guidingTrain["filterwheel"] = "-";
0416         guidingTrain["focuser"] = "-";
0417         guidingTrain["guider"] = m_MountDevice;
0418         if (isNew)
0419         {
0420             // create guiding train if missing
0421             guidingTrain["name"] = m_guidingTrain;
0422             otm->addOpticalTrain(QJsonObject::fromVariantMap(guidingTrain));
0423         }
0424         else
0425             KStarsData::Instance()->userdb()->UpdateOpticalTrain(guidingTrain, guidingTrain["id"].toInt());
0426     }
0427     // ensure that the OTM initializes from the database
0428     otm->refreshModel();
0429     otm->refreshTrains();
0430 }
0431 
0432 void TestEkosHelper::prepareAlignmentModule()
0433 {
0434 
0435     // check if astrometry files exist
0436     QTRY_VERIFY(isAstrometryAvailable() == true);
0437     // switch to alignment module
0438     KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->alignModule(), 1000);
0439     // select the primary train for alignment
0440     KTRY_SET_COMBO(Ekos::Manager::Instance()->alignModule(), opticalTrainCombo, m_primaryTrain);
0441     // select local solver
0442     Ekos::Manager::Instance()->alignModule()->setSolverMode(Ekos::Align::SOLVER_LOCAL);
0443     // select internal SEP method
0444     Options::setSolveSextractorType(SSolver::EXTRACTOR_BUILTIN);
0445     // select StellarSolver
0446     Options::setSolverType(SSolver::SOLVER_STELLARSOLVER);
0447     // select fast solve profile option
0448     Options::setSolveOptionsProfile(SSolver::Parameters::SINGLE_THREAD_SOLVING);
0449     // select the "Slew to Target" mode
0450     KTRY_SET_RADIOBUTTON(Ekos::Manager::Instance()->alignModule(), slewR, true);
0451     // disable rotator check in alignment
0452     Options::setAstrometryUseRotator(false);
0453     // select the Luminance filter
0454     KTRY_SET_COMBO(Ekos::Manager::Instance()->alignModule(), alignFilter, "Luminance");
0455     // set the exposure time to a standard
0456     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->alignModule(), alignExposure, 3.0);
0457     // reduce the accuracy to avoid testing problems
0458     KTRY_SET_SPINBOX(Ekos::Manager::Instance()->alignModule(), alignAccuracyThreshold, 300);
0459     // disable re-alignment
0460     Options::setAlignCheckFrequency(0);
0461 }
0462 
0463 void TestEkosHelper::prepareCaptureModule()
0464 {
0465     Ekos::Capture *capture = Ekos::Manager::Instance()->captureModule();
0466     // clear refocusing limits
0467     KTRY_SET_CHECKBOX(capture, limitRefocusS, false);
0468     KTRY_SET_CHECKBOX(capture, limitFocusHFRS, false);
0469     KTRY_SET_CHECKBOX(capture, limitFocusDeltaTS, false);
0470     // clear the guiding limits
0471     KTRY_SET_CHECKBOX(capture, startGuiderDriftS, false);
0472     KTRY_SET_CHECKBOX(capture, limitGuideDeviationS, false);
0473 
0474 }
0475 
0476 void TestEkosHelper::prepareFocusModule()
0477 {
0478     // set focus mode defaults
0479     KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->focusModule(), 1000);
0480     // select the primary train for focusing
0481     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), opticalTrainCombo, m_primaryTrain);
0482     // use full field
0483     KTRY_SET_RADIOBUTTON(Ekos::Manager::Instance()->focusModule(), focusUseFullField, true);
0484     //initial step size 5000
0485     KTRY_SET_SPINBOX(Ekos::Manager::Instance()->focusModule(), focusTicks, 5000);
0486     // max travel 100000
0487     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusMaxTravel, 100000.0);
0488     // focus tolerance 20% - make focus fast and robust, precision does not matter
0489     KTRY_GADGET(Ekos::Manager::Instance()->focusModule(), QDoubleSpinBox, focusTolerance);
0490     focusTolerance->setMaximum(20.0);
0491     focusTolerance->setValue(20.0);
0492     // use single pass linear algorithm
0493     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), focusAlgorithm, "Linear 1 Pass");
0494     // select star detection
0495     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), focusSEPProfile, "1-Focus-Default");
0496     // set annulus to 0% - 100%
0497     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusFullFieldInnerRadius, 0.0);
0498     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusFullFieldOuterRadius, 100.0);
0499     // try to make focusing fast, precision is not relevant here
0500     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusOutSteps, 3.0);
0501     // select the Luminance filter
0502     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), focusFilter, "Luminance");
0503     // select SEP algorithm for star detection
0504     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), focusDetection, "SEP");
0505     // select HFR a star focus measure
0506     KTRY_SET_COMBO(Ekos::Manager::Instance()->focusModule(), focusStarMeasure, "HFR");
0507     // set exp time for current filter
0508     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusExposure, 1.0);
0509     // set exposure times for all filters
0510     auto filtermanager = Ekos::Manager::Instance()->focusModule()->filterManager();
0511     for (int pos = 0; pos < filtermanager->getFilterLabels().count(); pos++)
0512     {
0513         filtermanager->setFilterExposure(pos, 1.0);
0514         filtermanager->setFilterLock(pos, "Luminance");
0515     }
0516 
0517     // Eliminate the noise setting to ensure a working focusing and plate solving
0518     KTRY_INDI_PROPERTY(m_CCDDevice, "Simulator Config", "SIMULATOR_SETTINGS", ccd_settings);
0519     INDI_E *noise_setting = ccd_settings->getElement("SIM_NOISE");
0520     QVERIFY(ccd_settings != nullptr);
0521     noise_setting->setValue(0.0);
0522     ccd_settings->processSetButton();
0523 
0524     // gain 100
0525     KTRY_SET_DOUBLESPINBOX(Ekos::Manager::Instance()->focusModule(), focusGain, 100.0);
0526     // suspend guiding while focusing
0527     KTRY_SET_CHECKBOX(Ekos::Manager::Instance()->focusModule(), focusSuspendGuiding, true);
0528 }
0529 
0530 void TestEkosHelper::prepareGuidingModule()
0531 {
0532     // switch to guiding module
0533     KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->guideModule(), 1000);
0534 
0535     // preserve guiding calibration as good as possible
0536     Options::setReuseGuideCalibration(true);
0537     Options::setResetGuideCalibration(false);
0538     // guide calibration captured with fsq-85 as guiding scope, clear if it creates problems
0539     // KTRY_CLICK(Ekos::Manager::Instance()->guideModule(), clearCalibrationB);
0540     Options::setSerializedCalibration("Cal v1.0,bx=1,by=1,pw=0.0024,ph=0.0024,fl=450,ang=268.349,angR=270.023,angD=176.674,ramspas=139.764,decmspas=134.438,swap=0,ra= 27:21:00,dec=00:25:52,side=0,when=2023-02-18 16:46:48,calEnd");
0541     // 0.5 pixel dithering
0542     Options::setDitherPixels(0.5);
0543     // auto star select
0544     KTRY_SET_CHECKBOX(Ekos::Manager::Instance()->guideModule(), guideAutoStar, true);
0545     // set the guide star box to size 32
0546     KTRY_SET_COMBO(Ekos::Manager::Instance()->guideModule(), guideSquareSize, "32");
0547     // use 1x1 binning for guiding
0548     KTRY_SET_COMBO(Ekos::Manager::Instance()->guideModule(), guideBinning, "1x1");
0549     if (m_Guider == "PHD2")
0550     {
0551         KTRY_GADGET(Ekos::Manager::Instance()->guideModule(), QPushButton, externalConnectB);
0552         // ensure that PHD2 is connected
0553         if (externalConnectB->isEnabled())
0554         {
0555             // click "Connect"
0556             KTRY_CLICK(Ekos::Manager::Instance()->guideModule(), externalConnectB);
0557             // wait max 60 sec that PHD2 is connected (sometimes INDI connections hang)
0558             QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->guideModule()->status() == Ekos::GUIDE_CONNECTED, 60000);
0559             qCInfo(KSTARS_EKOS_TEST) << "PHD2 connected successfully.";
0560         }
0561     }
0562     else
0563     {
0564         // select the secondary train for guiding
0565         KTRY_SET_COMBO(Ekos::Manager::Instance()->guideModule(), opticalTrainCombo, m_guidingTrain);
0566         // select multi-star
0567         Options::setGuideAlgorithm(SEP_MULTISTAR);
0568         // select small star profile
0569         Options::setGuideOptionsProfile(2);
0570         // set 2 sec guiding calibration pulse duration
0571         Options::setCalibrationPulseDuration(2000);
0572         // use three steps in each direction for calibration
0573         Options::setAutoModeIterations(3);
0574         // set the guiding aggressiveness
0575         Options::setRAProportionalGain(0.5);
0576         Options::setDECProportionalGain(0.5);
0577         // use simulator's guide head
0578         Options::setUseGuideHead(true);
0579     }
0580 }
0581 
0582 Scope *TestEkosHelper::createScopeIfNecessary(QString model, QString vendor, QString type, double aperture,
0583         double focallenght)
0584 {
0585     QList<Scope *> scope_list;
0586     KStarsData::Instance()->userdb()->GetAllScopes(scope_list);
0587 
0588     for (Scope *scope : scope_list)
0589     {
0590         if (scope->model() == model && scope->vendor() == vendor && scope->type() == type && scope->aperture() == aperture
0591                 && scope->focalLength() == focallenght)
0592             return scope;
0593     }
0594     // no match found, create it again
0595     KStarsData::Instance()->userdb()->AddScope(model, vendor, type, aperture, focallenght);
0596     Ekos::OpticalTrainManager::Instance()->refreshOpticalElements();
0597     // find it
0598     scope_list.clear();
0599     KStarsData::Instance()->userdb()->GetAllScopes(scope_list);
0600     for (Scope *scope : scope_list)
0601     {
0602         if (scope->model() == model && scope->vendor() == vendor && scope->type() == type && scope->aperture() == aperture
0603                 && scope->focalLength() == focallenght)
0604             return scope;
0605     }
0606     // this should never happen
0607     return nullptr;
0608 }
0609 
0610 
0611 
0612 OAL::Scope *TestEkosHelper::getScope(TestEkosHelper::ScopeType type)
0613 {
0614     switch (type)
0615     {
0616         case SCOPE_FSQ85:
0617             return fsq85;
0618         case SCOPE_NEWTON_10F4:
0619             return newton_10F4;
0620         case SCOPE_TAKFINDER10x50:
0621             return takfinder10x50;
0622     }
0623     // this should never happen
0624     return fsq85;
0625 }
0626 
0627 void TestEkosHelper::prepareMountModule(ScopeType primary, ScopeType guiding)
0628 {
0629     Ekos::OpticalTrainManager *otm = Ekos::OpticalTrainManager::Instance();
0630     // set mount defaults
0631     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule() != nullptr, 5000);
0632     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->slewStatus() != IPState::IPS_ALERT, 1000);
0633 
0634     // define a set of scopes
0635     fsq85          = createScopeIfNecessary("FSQ-85", "Takahashi", "Refractor", 85.0, 450.0);
0636     newton_10F4    = createScopeIfNecessary("ONTC", "Teleskop-Service", "Newtonian", 254.0, 1000.0);
0637     takfinder10x50 = createScopeIfNecessary("Finder 7x50", "Takahashi", "Refractor", 50.0, 170.0);
0638 
0639     QVERIFY(fsq85 != nullptr);
0640     QVERIFY(newton_10F4 != nullptr);
0641     QVERIFY(takfinder10x50 != nullptr);
0642 
0643     OAL::Scope *primaryScope = getScope(primary);
0644     OAL::Scope *guidingScope = getScope(guiding);
0645 
0646     // setup the primary train
0647     QVariantMap primaryTrain = otm->getOpticalTrain(m_primaryTrain);
0648     primaryTrain["scope"] = primaryScope->name();
0649     primaryTrain["reducer"] = 1.0;
0650     KStarsData::Instance()->userdb()->UpdateOpticalTrain(primaryTrain, primaryTrain["id"].toInt());
0651 
0652     // setup the guiding train
0653     QVariantMap guidingTrain = otm->getOpticalTrain(m_guidingTrain);
0654     guidingTrain["scope"] = guidingScope->name();
0655     guidingTrain["reducer"] = 1.0;
0656     KStarsData::Instance()->userdb()->UpdateOpticalTrain(guidingTrain, guidingTrain["id"].toInt());
0657     // ensure that the OTM initializes from the database
0658     otm->refreshModel();
0659     otm->refreshTrains();
0660 
0661     // Set the scope parameters also in the telescope simulator to ensure that the CCD simulator creates the right FOW
0662     if (m_CCDDevice != nullptr)
0663     {
0664         KTRY_INDI_PROPERTY(m_CCDDevice, "Options", "SCOPE_INFO", ccd_scope_info);
0665         INDI_E *primary_aperture = ccd_scope_info->getElement("APERTURE");
0666         INDI_E *primary_focallength = ccd_scope_info->getElement("FOCAL_LENGTH");
0667         QVERIFY(primary_aperture != nullptr);
0668         QVERIFY(primary_focallength != nullptr);
0669         primary_aperture->setValue(primaryTrain["aperture"].toDouble());
0670         primary_focallength->setValue(primaryTrain["focal_length"].toDouble() * primaryTrain["reducer"].toDouble());
0671         ccd_scope_info->processSetButton();
0672     }
0673     if (m_GuiderDevice != nullptr)
0674     {
0675         KTRY_INDI_PROPERTY(m_GuiderDevice, "Options", "SCOPE_INFO", guider_scope_info);
0676         INDI_E *guider_aperture = guider_scope_info->getElement("APERTURE");
0677         INDI_E *guider_focallength = guider_scope_info->getElement("FOCAL_LENGTH");
0678         QVERIFY(guider_aperture != nullptr);
0679         QVERIFY(guider_focallength != nullptr);
0680         guider_aperture->setValue(guidingTrain["aperture"].toDouble());
0681         guider_focallength->setValue(guidingTrain["focal_length"].toDouble() * guidingTrain["reducer"].toDouble());
0682         guider_scope_info
0683         ->processSetButton();
0684     }
0685 
0686     // select the primary train for the mount
0687     KTRY_SET_COMBO(Ekos::Manager::Instance()->mountModule(), opticalTrainCombo, m_primaryTrain);
0688 }
0689 
0690 bool TestEkosHelper::slewTo(double RA, double DEC, bool fast)
0691 {
0692     if (fast)
0693     {
0694         // reset mount model
0695         Ekos::Manager::Instance()->mountModule()->resetModel();
0696         // sync to a point close before the meridian to speed up slewing
0697         Ekos::Manager::Instance()->mountModule()->sync(RA + 0.002, DEC);
0698     }
0699     // now slew very close before the meridian
0700     Ekos::Manager::Instance()->mountModule()->slew(RA, DEC);
0701     // wait a certain time until the mount slews
0702     QTest::qWait(3000);
0703     // wait until the mount is tracking
0704     KTRY_VERIFY_WITH_TIMEOUT_SUB(Ekos::Manager::Instance()->mountModule()->status() == ISD::Mount::MOUNT_TRACKING, 10000);
0705 
0706     // everything succeeded
0707     return true;
0708 }
0709 
0710 bool TestEkosHelper::startGuiding(double expTime)
0711 {
0712     if (getGuidingStatus() == Ekos::GUIDE_GUIDING)
0713     {
0714         QWARN("Start guiding ignored, guiding already running!");
0715         return false;
0716     }
0717 
0718     // setup guiding
0719     prepareGuidingModule();
0720 
0721     //set the exposure time
0722     KTRY_SET_DOUBLESPINBOX_SUB(Ekos::Manager::Instance()->guideModule(), guideExposure, expTime);
0723 
0724     // start guiding
0725     KTRY_GADGET_SUB(Ekos::Manager::Instance()->guideModule(), QPushButton, guideB);
0726     // ensure that the guiding button is enabled (after MF it may take a while)
0727     KTRY_VERIFY_WITH_TIMEOUT_SUB(guideB->isEnabled(), 10000);
0728     expectedGuidingStates.enqueue(Ekos::GUIDE_GUIDING);
0729     KTRY_CLICK_SUB(Ekos::Manager::Instance()->guideModule(), guideB);
0730     KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedGuidingStates, 120000);
0731     qCInfo(KSTARS_EKOS_TEST) << "Guiding started.";
0732     qCInfo(KSTARS_EKOS_TEST) << "Guiding calibration: " << Options::serializedCalibration();
0733     qCInfo(KSTARS_EKOS_TEST) << "Waiting 2sec for settle guiding ...";
0734     QTest::qWait(2000);
0735     // all checks succeeded, remember that guiding is running
0736     use_guiding = true;
0737 
0738     return true;
0739 }
0740 
0741 bool TestEkosHelper::stopGuiding()
0742 {
0743     // check whether guiding is not running or already stopped
0744     if (use_guiding == false || getGuidingStatus() == Ekos::GUIDE_IDLE || getGuidingStatus() == Ekos::GUIDE_ABORTED)
0745         return true;
0746 
0747     // switch to guiding module
0748     KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->guideModule(), 1000));
0749 
0750     // stop guiding
0751     KTRY_GADGET_SUB(Ekos::Manager::Instance()->guideModule(), QPushButton, stopB);
0752     // check if guiding could be stopped
0753     if (stopB->isEnabled() == false)
0754         return true;
0755     KTRY_CLICK_SUB(Ekos::Manager::Instance()->guideModule(), stopB);
0756     KTRY_VERIFY_WITH_TIMEOUT_SUB(getGuidingStatus() == Ekos::GUIDE_IDLE || getGuidingStatus() == Ekos::GUIDE_ABORTED, 15000);
0757     qCInfo(KSTARS_EKOS_TEST) << "Guiding stopped.";
0758     qCInfo(KSTARS_EKOS_TEST) << "Waiting 2sec for settle guiding stop..."; // Avoid overlapping with focus pausing
0759     QTest::qWait(2000);
0760     // all checks succeeded
0761     return true;
0762 }
0763 
0764 /* *********************************************************************************
0765  *
0766  * Alignment support
0767  *
0768  * ********************************************************************************* */
0769 bool TestEkosHelper::startAligning(double expTime)
0770 {
0771     KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->alignModule(), 1000));
0772     // set the exposure time to the given value
0773     KTRY_SET_DOUBLESPINBOX_SUB(Ekos::Manager::Instance()->alignModule(), alignExposure, expTime);
0774     // reduce the accuracy to avoid testing problems
0775     KTRY_SET_SPINBOX_SUB(Ekos::Manager::Instance()->alignModule(), alignAccuracyThreshold, 300);
0776     // select the Luminance filter
0777     KTRY_SET_COMBO_SUB(Ekos::Manager::Instance()->alignModule(), alignFilter, "Luminance");
0778 
0779     // start alignment
0780     KTRY_GADGET_SUB(Ekos::Manager::Instance()->alignModule(), QPushButton, solveB);
0781     // ensure that the solve button is enabled (after MF it may take a while)
0782     KTRY_VERIFY_WITH_TIMEOUT_SUB(solveB->isEnabled(), 60000);
0783     KTRY_CLICK_SUB(Ekos::Manager::Instance()->alignModule(), solveB);
0784     // alignment started
0785     return true;
0786 }
0787 
0788 
0789 bool TestEkosHelper::checkAstrometryFiles()
0790 {
0791     // first check the current configuration
0792     QStringList dataDirs = KSUtils::getAstrometryDataDirs();
0793 
0794     // search if configured directories contain some index files
0795     for(QString dirname : dataDirs)
0796     {
0797         QDir dir(dirname);
0798         dir.setNameFilters(QStringList("index*"));
0799         dir.setFilter(QDir::Files);
0800         if (! dir.entryList().isEmpty())
0801             return true;
0802     }
0803     QWARN(QString("No astrometry index files found in %1").arg(
0804               KSUtils::getAstrometryDataDirs().join(", ")).toStdString().c_str());
0805     return false;
0806 }
0807 
0808 bool TestEkosHelper::isAstrometryAvailable()
0809 {
0810     // avoid double checks if already found
0811     if (! m_astrometry_available)
0812         m_astrometry_available = checkAstrometryFiles();
0813 
0814     return m_astrometry_available;
0815 }
0816 
0817 bool TestEkosHelper::executeFocusing(int initialFocusPosition)
0818 {
0819     // check whether focusing is already running
0820     if (! (getFocusStatus() == Ekos::FOCUS_IDLE || getFocusStatus() == Ekos::FOCUS_COMPLETE
0821             || getFocusStatus() == Ekos::FOCUS_ABORTED))
0822         return true;
0823 
0824     // prepare for focusing tests
0825     prepareFocusModule();
0826 
0827     initialFocusPosition = 40000;
0828     KTRY_SET_SPINBOX_SUB(Ekos::Manager::Instance()->focusModule(), absTicksSpin, initialFocusPosition);
0829     // start focusing
0830     KTRY_CLICK_SUB(Ekos::Manager::Instance()->focusModule(), startGotoB);
0831     // wait one second for settling
0832     QTest::qWait(3000);
0833     // start focusing
0834     expectedFocusStates.append(Ekos::FOCUS_COMPLETE);
0835     KTRY_CLICK_SUB(Ekos::Manager::Instance()->focusModule(), startFocusB);
0836     // wait for successful completion
0837     KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedFocusStates, 180000);
0838     qCInfo(KSTARS_EKOS_TEST) << "Focusing finished.";
0839     // all checks succeeded
0840     return true;
0841 }
0842 
0843 bool TestEkosHelper::stopFocusing()
0844 {
0845     // check whether focusing is already stopped
0846     if (getFocusStatus() == Ekos::FOCUS_IDLE || getFocusStatus() == Ekos::FOCUS_COMPLETE
0847             || getFocusStatus() == Ekos::FOCUS_ABORTED)
0848         return true;
0849 
0850     // switch to focus module
0851     KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->focusModule(), 1000));
0852     // stop focusing if necessary
0853     KTRY_GADGET_SUB(Ekos::Manager::Instance()->focusModule(), QPushButton, stopFocusB);
0854     if (stopFocusB->isEnabled())
0855         KTRY_CLICK_SUB(Ekos::Manager::Instance()->focusModule(), stopFocusB);
0856     KTRY_VERIFY_WITH_TIMEOUT_SUB(getFocusStatus() == Ekos::FOCUS_IDLE
0857                                  || getFocusStatus() == Ekos::FOCUS_COMPLETE ||
0858                                  getFocusStatus() == Ekos::FOCUS_ABORTED || getFocusStatus() == Ekos::FOCUS_FAILED, 15000);
0859 
0860     // all checks succeeded
0861     return true;
0862 }
0863 
0864 int TestEkosHelper::secondsToMF(QString message)
0865 {
0866     QRegExp mfPattern("Meridian flip in (\\d+):(\\d+):(\\d+)");
0867 
0868     int pos = mfPattern.indexIn(message);
0869     if (pos > -1)
0870     {
0871         int hh  = mfPattern.cap(1).toInt();
0872         int mm  = mfPattern.cap(2).toInt();
0873         int sec = mfPattern.cap(3).toInt();
0874         if (hh >= 0)
0875             return (((hh * 60) + mm) * 60 + sec);
0876         else
0877             return (((hh * 60) - mm) * 60 - sec);
0878     }
0879 
0880     // unknown time
0881     return -1;
0882 
0883 }
0884 
0885 void TestEkosHelper::updateJ2000Coordinates(SkyPoint *target)
0886 {
0887     SkyPoint J2000Coord(target->ra(), target->dec());
0888     J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
0889     target->setRA0(J2000Coord.ra());
0890     target->setDec0(J2000Coord.dec());
0891 }
0892 
0893 void TestEkosHelper::init()
0894 {
0895     // initialize the recorded states
0896     m_AlignStatus   = Ekos::ALIGN_IDLE;
0897     m_CaptureStatus = Ekos::CAPTURE_IDLE;
0898     m_FocusStatus   = Ekos::FOCUS_IDLE;
0899     m_GuideStatus   = Ekos::GUIDE_IDLE;
0900     m_MFStatus      = Ekos::MeridianFlipState::MOUNT_FLIP_NONE;
0901     m_DomeStatus    = ISD::Dome::DOME_IDLE;
0902     // initialize the event queues
0903     expectedAlignStates.clear();
0904     expectedCaptureStates.clear();
0905     expectedFocusStates.clear();
0906     expectedGuidingStates.clear();
0907     expectedMountStates.clear();
0908     expectedDomeStates.clear();
0909     expectedMeridianFlipStates.clear();
0910     expectedSchedulerStates.clear();
0911 
0912 
0913     // disable by default
0914     use_guiding = false;
0915     // reset dithering flag
0916     dithered = false;
0917     // disable dithering by default
0918     Options::setDitherEnabled(false);
0919     // clear the script map
0920     scripts.clear();
0921     // do not open INDI window on Ekos startup
0922     Options::setShowINDIwindowInitially(false);
0923 }
0924 void TestEkosHelper::cleanup()
0925 {
0926 
0927 }
0928 
0929 /* *********************************************************************************
0930  *
0931  * Helper functions
0932  *
0933  * ********************************************************************************* */
0934 void TestEkosHelper::setTreeviewCombo(QComboBox *combo, QString lookup)
0935 {
0936     // Match the text recursively in the model, this results in a model index with a parent
0937     QModelIndexList const list = combo->model()->match(combo->model()->index(0, 0), Qt::DisplayRole,
0938                                  QVariant::fromValue(lookup), 1, Qt::MatchRecursive);
0939     QVERIFY(0 < list.count());
0940     QModelIndex const &index = list.first();
0941     QCOMPARE(list.value(0).data().toString(), lookup);
0942     QVERIFY(!index.parent().parent().isValid());
0943     // Now set the combobox model root to the match's parent
0944     combo->setRootModelIndex(index.parent());
0945     combo->setModelColumn(index.column());
0946     combo->setCurrentIndex(index.row());
0947 
0948     // Now reset
0949     combo->setRootModelIndex(QModelIndex());
0950     combo->view()->setCurrentIndex(index);
0951 
0952     // Check, if everything went well
0953     QCOMPARE(combo->currentText(), lookup);
0954 }
0955 
0956 
0957 // Simple write-string-to-file utility.
0958 bool TestEkosHelper::writeFile(const QString &filename, const QStringList &lines, QFileDevice::Permissions permissions)
0959 {
0960     QFile qFile(filename);
0961     if (qFile.open(QIODevice::WriteOnly | QIODevice::Text))
0962     {
0963         QTextStream out(&qFile);
0964         for (QStringList::const_iterator it = lines.begin(); it != lines.end(); it++)
0965             out << *it + QChar::LineFeed;
0966         qFile.close();
0967         qFile.setPermissions(filename, permissions);
0968         return true;
0969     }
0970     return false;
0971 }
0972 
0973 bool TestEkosHelper::createCountingScript(Ekos::ScriptTypes scripttype, const QString scriptname)
0974 {
0975     QString logfilename = scriptname;
0976     logfilename.replace(scriptname.lastIndexOf(".sh"), 3, ".log");
0977     QStringList script_content({"#!/bin/sh",
0978                                 QString("nr=`head -1 %1|| echo 0` 2> /dev/null").arg(logfilename),
0979                                 QString("nr=$(($nr+1))\necho $nr > %1").arg(logfilename)});
0980     // create executable script
0981     bool result = writeFile(scriptname, script_content,
0982                             QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
0983     scripts.insert(scripttype, scriptname);
0984     // clear log file
0985     QFile logfile(logfilename);
0986     result |= logfile.remove();
0987     // ready
0988     return result;
0989 }
0990 
0991 bool TestEkosHelper::createAllCaptureScripts(QTemporaryDir *destination)
0992 {
0993     KWRAP_SUB(QVERIFY2(createCountingScript(Ekos::SCRIPT_PRE_JOB,
0994                                             destination->path() + "/prejob.sh"), "Creating pre-job script failed!"));
0995     KWRAP_SUB(QVERIFY2(createCountingScript(Ekos::SCRIPT_PRE_CAPTURE,
0996                                             destination->path() + "/precapture.sh"), "Creating pre-capture script failed!"));
0997     KWRAP_SUB(QVERIFY2(createCountingScript(Ekos::SCRIPT_POST_CAPTURE,
0998                                             destination->path() + "/postcapture.sh"), "Creating post-capture script failed!"));
0999     KWRAP_SUB(QVERIFY2(createCountingScript(Ekos::SCRIPT_POST_JOB,
1000                                             destination->path() + "/postjob.sh"), "Creating post-job script failed!"));
1001     // everything succeeded
1002     return true;
1003 }
1004 
1005 int TestEkosHelper::countScriptRuns(Ekos::ScriptTypes scripttype)
1006 {
1007     if (scripts.contains(scripttype))
1008     {
1009         QString scriptname = scripts[scripttype];
1010         QString logfilename = scriptname;
1011         logfilename.replace(scriptname.lastIndexOf(".sh"), 3, ".log");
1012         QFile logfile(logfilename);
1013         logfile.open(QIODevice::ReadOnly | QIODevice::Text);
1014         QTextStream in(&logfile);
1015         QString countstr = in.readLine();
1016         logfile.close();
1017         return countstr.toInt();
1018     }
1019     else
1020         // no script found
1021         return -1;
1022 }
1023 
1024 bool TestEkosHelper::checkScriptRuns(int captures_per_sequence, int sequences)
1025 {
1026     // check the log file if it holds the expected number
1027     int runs = countScriptRuns(Ekos::SCRIPT_PRE_JOB);
1028     KWRAP_SUB(QVERIFY2(runs == sequences,
1029                        QString("Pre-job script not executed as often as expected: %1 expected, %2 detected.")
1030                        .arg(captures_per_sequence).arg(runs).toLocal8Bit()));
1031     runs = countScriptRuns(Ekos::SCRIPT_PRE_CAPTURE);
1032     KWRAP_SUB( QVERIFY2(runs == sequences * captures_per_sequence,
1033                         QString("Pre-capture script not executed as often as expected: %1 expected, %2 detected.")
1034                         .arg(captures_per_sequence).arg(runs).toLocal8Bit()));
1035     runs = countScriptRuns(Ekos::SCRIPT_POST_JOB);
1036     KWRAP_SUB(QVERIFY2(runs == sequences,
1037                        QString("Post-job script not executed as often as expected: %1 expected, %2 detected.")
1038                        .arg(captures_per_sequence).arg(runs).toLocal8Bit()));
1039     runs = countScriptRuns(Ekos::SCRIPT_POST_CAPTURE);
1040     KWRAP_SUB(QVERIFY2(runs == sequences * captures_per_sequence,
1041                        QString("Post-capture script not executed as often as expected: %1 expected, %2 detected.")
1042                        .arg(captures_per_sequence).arg(runs).toLocal8Bit()));
1043     // everything succeeded
1044     return true;
1045 }
1046 
1047 /* *********************************************************************************
1048  *
1049  * Slots for catching state changes
1050  *
1051  * ********************************************************************************* */
1052 
1053 void TestEkosHelper::alignStatusChanged(Ekos::AlignState status)
1054 {
1055     m_AlignStatus = status;
1056     // check if the new state is the next one expected, then remove it from the stack
1057     if (!expectedAlignStates.isEmpty() && expectedAlignStates.head() == status)
1058         expectedAlignStates.dequeue();
1059 }
1060 
1061 void TestEkosHelper::mountStatusChanged(ISD::Mount::Status status)
1062 {
1063     m_MountStatus = status;
1064     // check if the new state is the next one expected, then remove it from the stack
1065     if (!expectedMountStates.isEmpty() && expectedMountStates.head() == status)
1066         expectedMountStates.dequeue();
1067 }
1068 
1069 void TestEkosHelper::meridianFlipStatusChanged(Ekos::MeridianFlipState::MeridianFlipMountState status)
1070 {
1071     m_MFStatus = status;
1072     // check if the new state is the next one expected, then remove it from the stack
1073     if (!expectedMeridianFlipStates.isEmpty() && expectedMeridianFlipStates.head() == status)
1074         expectedMeridianFlipStates.dequeue();
1075 }
1076 
1077 void TestEkosHelper::focusStatusChanged(Ekos::FocusState status)
1078 {
1079     m_FocusStatus = status;
1080     // check if the new state is the next one expected, then remove it from the stack
1081     if (!expectedFocusStates.isEmpty() && expectedFocusStates.head() == status)
1082         expectedFocusStates.dequeue();
1083 }
1084 
1085 void TestEkosHelper::guidingStatusChanged(Ekos::GuideState status)
1086 {
1087     m_GuideStatus = status;
1088     // check if the new state is the next one expected, then remove it from the stack
1089     if (!expectedGuidingStates.isEmpty() && expectedGuidingStates.head() == status)
1090         expectedGuidingStates.dequeue();
1091     // dithering detected?
1092     if (status == Ekos::GUIDE_DITHERING || status == Ekos::GUIDE_DITHERING_SUCCESS || status == Ekos::GUIDE_DITHERING_ERROR)
1093         dithered = true;
1094 
1095 }
1096 
1097 void TestEkosHelper::guideDeviationChanged(double delta_ra, double delta_dec)
1098 {
1099     m_GuideDeviation = std::hypot(delta_ra, delta_dec);
1100 }
1101 
1102 void TestEkosHelper::captureStatusChanged(Ekos::CaptureState status)
1103 {
1104     m_CaptureStatus = status;
1105     // check if the new state is the next one expected, then remove it from the stack
1106     if (!expectedCaptureStates.isEmpty() && expectedCaptureStates.head() == status)
1107         expectedCaptureStates.dequeue();
1108 }
1109 
1110 void TestEkosHelper::schedulerStatusChanged(Ekos::SchedulerState status)
1111 {
1112     m_SchedulerStatus = status;
1113     // check if the new state is the next one expected, then remove it from the stack
1114     if (!expectedSchedulerStates.isEmpty() && expectedSchedulerStates.head() == status)
1115         expectedSchedulerStates.dequeue();
1116 }
1117 
1118 void TestEkosHelper::domeStatusChanged(ISD::Dome::Status status)
1119 {
1120     m_DomeStatus = status;
1121     // check if the new state is the next one expected, then remove it from the stack
1122     if (!expectedDomeStates.isEmpty() && expectedDomeStates.head() == status)
1123         expectedDomeStates.dequeue();
1124 }