File indexing completed on 2024-04-28 07:33:07
0001 /* KStars UI tests 0002 SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 0008 #include "test_ekos_guide.h" 0009 0010 #if defined(HAVE_INDI) && HAVE_INDI 0011 0012 #include "kstars_ui_tests.h" 0013 #include "test_ekos.h" 0014 #include "test_ekos_simulator.h" 0015 #include "test_ekos_mount.h" // KTRY_MOUNT_SYNC 0016 #include "test_ekos_focus.h" // KTRY_FOCUS_MOVETO 0017 0018 #include "ekos/manager.h" 0019 #include "ekos/profileeditor.h" 0020 #include "Options.h" 0021 0022 TestEkosGuide::TestEkosGuide(QObject *parent) : QObject(parent) 0023 { 0024 } 0025 0026 void TestEkosGuide::initTestCase() 0027 { 0028 KVERIFY_EKOS_IS_HIDDEN(); 0029 KTRY_OPEN_EKOS(); 0030 KVERIFY_EKOS_IS_OPENED(); 0031 0032 // Update simulator profile to use PHD2 as guider 0033 { 0034 bool isDone = false; 0035 Ekos::Manager * const ekos = Ekos::Manager::Instance(); 0036 QTimer::singleShot(1000, ekos, [&] 0037 { 0038 ProfileEditor* profileEditor = ekos->findChild<ProfileEditor*>("profileEditorDialog"); 0039 0040 // Change guider to PHD2 0041 QString const gt("PHD2"); 0042 KTRY_PROFILEEDITOR_GADGET(QComboBox, guideTypeCombo); 0043 guideTypeCombo->setCurrentText(gt); 0044 QTRY_COMPARE(guideTypeCombo->currentText(), gt); 0045 0046 // Create a test profile 0047 KTRY_PROFILEEDITOR_GADGET(QLineEdit, profileIN); 0048 profileIN->setText(testProfileName); 0049 QCOMPARE(profileIN->text(), testProfileName); 0050 0051 // Disable Port Selector 0052 KTRY_PROFILEEDITOR_GADGET(QCheckBox, portSelectorCheck); 0053 portSelectorCheck->setChecked(false); 0054 QCOMPARE(portSelectorCheck->isChecked(), false); 0055 0056 // Write PHD2 server specs 0057 KTRY_PROFILEEDITOR_GADGET(QLineEdit, externalGuideHost); 0058 externalGuideHost->setText(guider_host); 0059 QCOMPARE(externalGuideHost->text(), guider_host); 0060 KTRY_PROFILEEDITOR_GADGET(QLineEdit, externalGuidePort); 0061 externalGuidePort->setText(guider_port); 0062 QCOMPARE(externalGuidePort->text(), guider_port); 0063 0064 // Setting an item programmatically in a treeview combobox... 0065 KTRY_PROFILEEDITOR_TREE_COMBOBOX(mountCombo, "Telescope Simulator"); 0066 KTRY_PROFILEEDITOR_TREE_COMBOBOX(ccdCombo, "CCD Simulator"); 0067 KTRY_PROFILEEDITOR_TREE_COMBOBOX(focuserCombo, "Focuser Simulator"); 0068 0069 // Save the profile using the "Save" button 0070 QDialogButtonBox* buttons = profileEditor->findChild<QDialogButtonBox*>("dialogButtons"); 0071 QVERIFY(nullptr != buttons); 0072 QTest::mouseClick(buttons->button(QDialogButtonBox::Save), Qt::LeftButton); 0073 0074 isDone = true; 0075 }); 0076 0077 // Cancel the profile editor if test fails 0078 QTimer * closeDialog = new QTimer(this); 0079 closeDialog->setSingleShot(true); 0080 closeDialog->setInterval(5000); 0081 ekos->connect(closeDialog, &QTimer::timeout, [&] 0082 { 0083 ProfileEditor* profileEditor = ekos->findChild<ProfileEditor*>("profileEditorDialog"); 0084 if (profileEditor != nullptr) 0085 profileEditor->reject(); 0086 }); 0087 0088 // Click on "New profile" button, and let the async tests run on the modal dialog 0089 KTRY_EKOS_GADGET(QPushButton, addProfileB); 0090 QTRY_VERIFY_WITH_TIMEOUT(addProfileB->isEnabled(), 1000); 0091 QTest::mouseClick(addProfileB, Qt::LeftButton); 0092 0093 // Click handler returned, stop the timer closing the dialog on failure 0094 closeDialog->stop(); 0095 delete closeDialog; 0096 0097 // Verification of the first test step 0098 QVERIFY(isDone); 0099 } 0100 } 0101 0102 void TestEkosGuide::cleanupTestCase() 0103 { 0104 KTRY_CLOSE_EKOS(); 0105 KVERIFY_EKOS_IS_HIDDEN(); 0106 } 0107 0108 void TestEkosGuide::init() 0109 { 0110 // In case the previous test failed 0111 stopPHD2(); 0112 0113 // Start a parallel PHD2 instance 0114 phd2 = new QProcess(this); 0115 0116 // No success using that strange --load option, it loads and exits, but does not save anywhere 0117 #if 0 0118 QStringList args = { "--load=phd2_Simulators.phd" }; 0119 phd2.start(QString("phd2"), args); 0120 if (!phd2.waitForStarted(1000)) 0121 { 0122 QString text = QString("Bypassing PHD2 tests, phd2 executable failing or not found. ") + phd2.errorString(); 0123 QWARN(text.toStdString().c_str()); 0124 return; 0125 } 0126 // PHD2 will exit after loading the configuration 0127 QTRY_VERIFY_WITH_TIMEOUT(phd2.state() == QProcess::NotRunning, 500); 0128 #else 0129 QString const phd2_config = ".PHDGuidingV2"; 0130 QStandardPaths::enableTestMode(false); 0131 QFileInfo phd2_config_home(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), phd2_config); 0132 QStandardPaths::enableTestMode(true); 0133 QWARN(QString("Writing PHD configuration file to '%1'").arg(phd2_config_home.filePath()).toStdString().c_str()); 0134 if (phd2_config_home.exists()) 0135 QVERIFY(QFile::remove(phd2_config_home.filePath())); 0136 QVERIFY(QFile::copy(phd2_config, phd2_config_home.filePath())); 0137 #endif 0138 0139 // Start PHD2 with the proper configuration 0140 phd2->start(QString("phd2")); 0141 QVERIFY(phd2->waitForStarted(3000)); 0142 QTest::qWait(2000); 0143 QTRY_VERIFY_WITH_TIMEOUT(phd2->state() == QProcess::Running, 1000); 0144 0145 // Try to connect to the PHD2 server 0146 QTcpSocket phd2_server(this); 0147 //phd2_server.connectToHost("localhost", guider_port.toUInt()); 0148 phd2_server.connectToHost(guider_host, guider_port.toUInt(), QIODevice::ReadOnly, QAbstractSocket::IPv4Protocol); 0149 if(!phd2_server.waitForConnected(30000)) 0150 { 0151 QSKIP(QString("Cannot continue, PHD2 server is unavailable (%1)").arg(phd2_server.errorString()).toStdString().c_str()); 0152 } 0153 else 0154 { 0155 phd2_server.disconnectFromHost(); 0156 if (phd2_server.state() == QTcpSocket::ConnectedState) 0157 QVERIFY(phd2_server.waitForDisconnected(1000)); 0158 QWARN(qPrintable(QString("Connection test OK, phd2 found listening on %1:%2").arg(guider_host).arg(guider_port))); 0159 } 0160 0161 // Start Ekos 0162 KTRY_EKOS_START_PROFILE(testProfileName); 0163 0164 // Do not reset clock, we are using the Telescope Simulator which does not receive simulated time 0165 // HACK: Reset clock to initial conditions 0166 // KHACK_RESET_EKOS_TIME(); 0167 0168 // Wait for Focus to come up, adjust focus 0169 KTRY_FOCUS_SHOW(); 0170 //KTRY_FOCUS_MOVETO(35000); 0171 // HACK HACK HACK adjust ccd gain 0172 KTRY_FOCUS_EXPOSURE(1, 99); 0173 0174 // Wait for Guide to come up, switch to guide module 0175 KTRY_GUIDE_SHOW(); 0176 0177 // Verify the phd2 server was connected successfully - by default, PHD2 has a 16-second timeout to camera connection 0178 KTRY_GUIDE_GADGET(QPushButton, externalConnectB); 0179 QTRY_VERIFY_WITH_TIMEOUT(!externalConnectB->isEnabled(), 30000); 0180 KTRY_GUIDE_GADGET(QPushButton, externalDisconnectB); 0181 QTRY_VERIFY_WITH_TIMEOUT(externalDisconnectB->isEnabled(), 10000); 0182 } 0183 0184 void TestEkosGuide::cleanup() 0185 { 0186 KTRY_EKOS_STOP_SIMULATORS(); 0187 stopPHD2(); 0188 } 0189 0190 void TestEkosGuide::stopPHD2() 0191 { 0192 // Stop PHD2 if needed 0193 if (phd2) 0194 { 0195 phd2->terminate(); 0196 phd2->waitForFinished(5000); 0197 delete phd2; 0198 phd2 = nullptr; 0199 } 0200 } 0201 0202 void TestEkosGuide::testPHD2ConnectionStability() 0203 { 0204 KTRY_GUIDE_GADGET(QPushButton, guideB); 0205 KTRY_GUIDE_GADGET(QPushButton, stopB); 0206 KTRY_GUIDE_GADGET(QPushButton, captureB); 0207 KTRY_GUIDE_GADGET(QPushButton, loopB); 0208 KTRY_GUIDE_GADGET(QPushButton, externalConnectB); 0209 KTRY_GUIDE_GADGET(QPushButton, externalDisconnectB); 0210 0211 // Run a few connect/disconnect cycles 0212 for (int count = 0; count < 10; count++) 0213 { 0214 QVERIFY(guideB->isEnabled()); 0215 QVERIFY(captureB->isEnabled()); 0216 QVERIFY(loopB->isEnabled()); 0217 QVERIFY(!stopB->isEnabled()); 0218 KTRY_GUIDE_CLICK(externalDisconnectB); 0219 QTRY_VERIFY_WITH_TIMEOUT(externalConnectB->isEnabled(), 20000); 0220 QVERIFY(!guideB->isEnabled()); 0221 QVERIFY(!captureB->isEnabled()); 0222 QVERIFY(!loopB->isEnabled()); 0223 QVERIFY(!stopB->isEnabled()); 0224 KTRY_GUIDE_CLICK(externalConnectB); 0225 QTRY_VERIFY_WITH_TIMEOUT(externalDisconnectB->isEnabled(), 60000); 0226 } 0227 0228 // Disconnect 0229 KTRY_GUIDE_CLICK(externalDisconnectB); 0230 QTRY_VERIFY_WITH_TIMEOUT(externalConnectB->isEnabled(), 10000); 0231 } 0232 0233 void TestEkosGuide::testPHD2CaptureStability() 0234 { 0235 KTRY_GUIDE_GADGET(QPushButton, stopB); 0236 KTRY_GUIDE_GADGET(QPushButton, captureB); 0237 KTRY_GUIDE_GADGET(QPushButton, loopB); 0238 KTRY_GUIDE_GADGET(QWidget, idlingStateLed); 0239 KTRY_GUIDE_GADGET(QWidget, preparingStateLed); 0240 KTRY_GUIDE_GADGET(QWidget, runningStateLed); 0241 KTRY_GUIDE_GADGET(QPushButton, externalConnectB); 0242 KTRY_GUIDE_GADGET(QPushButton, externalDisconnectB); 0243 0244 // When connected, capture, loop and guide are enabled 0245 // Run a few guide/stop cycles 0246 QWARN("Because Guide is changing its state without waiting for PHD2 to reply, we insert a forced delay after each click"); 0247 for (int count = 0; count < 10; count++) 0248 { 0249 KTRY_GUIDE_CLICK(loopB); 0250 QTest::qWait(1000); 0251 QTRY_VERIFY_WITH_TIMEOUT(stopB->isEnabled(), 10000); 0252 QEXPECT_FAIL("", "Capture button remains active while looping.", Continue); 0253 QVERIFY(!captureB->isEnabled()); 0254 QEXPECT_FAIL("", "Loop button remains active while looping.", Continue); 0255 QVERIFY(!loopB->isEnabled()); 0256 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (preparingStateLed))->state() == KLed::On, 5000); 0257 QVERIFY((dynamic_cast <KLed*> (idlingStateLed))->state() == KLed::Off); 0258 QVERIFY((dynamic_cast <KLed*> (runningStateLed))->state() == KLed::Off); 0259 KTRY_GUIDE_CLICK(stopB); 0260 QTest::qWait(1000); 0261 QTRY_VERIFY_WITH_TIMEOUT(loopB->isEnabled(), 10000); 0262 QEXPECT_FAIL("", "Stop button remains active after stopping.", Continue); 0263 QVERIFY(!stopB->isEnabled()); 0264 QVERIFY(captureB->isEnabled()); 0265 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (preparingStateLed))->state() == KLed::Off, 5000); 0266 QVERIFY((dynamic_cast <KLed*> (preparingStateLed))->state() == KLed::Off); 0267 QVERIFY((dynamic_cast <KLed*> (runningStateLed))->state() == KLed::Off); 0268 } 0269 0270 // Disconnect 0271 KTRY_GUIDE_CLICK(externalDisconnectB); 0272 QTRY_VERIFY_WITH_TIMEOUT(externalConnectB->isEnabled(), 10000); 0273 } 0274 0275 void TestEkosGuide::testPHD2Calibration() 0276 { 0277 KTRY_GUIDE_GADGET(QPushButton, guideB); 0278 KTRY_GUIDE_GADGET(QPushButton, stopB); 0279 KTRY_GUIDE_GADGET(QPushButton, clearCalibrationB); 0280 KTRY_GUIDE_GADGET(QWidget, idlingStateLed); 0281 KTRY_GUIDE_GADGET(QWidget, preparingStateLed); 0282 KTRY_GUIDE_GADGET(QWidget, runningStateLed); 0283 KTRY_GUIDE_GADGET(QPushButton, externalConnectB); 0284 KTRY_GUIDE_GADGET(QPushButton, externalDisconnectB); 0285 0286 // We will need to wait a bit more than 30+20 times the default exposure, which is 1 second, plus processing, 2s 0287 0288 uint const calibration_timeout = Options::guideCalibrationTimeout(); 0289 Options::setGuideCalibrationTimeout(50 * 3); 0290 0291 uint const loststar_timeout = Options::guideLostStarTimeout(); 0292 Options::setGuideLostStarTimeout(10); 0293 0294 QWARN("As of 202103 it is not possible to use the CCD Simulator at NCP because of jitter - skipping"); 0295 if (false) 0296 { 0297 // Run a calibration with the telescope pointing at NCP (default position) - Calibration is bound to fail bcause of RA 0298 // Two options: PHD2 either fails to see movement, or just switches to looping without doing anything 0299 // KTRY_MOUNT_SYNC(90, false, -1); 0300 QTRY_VERIFY_WITH_TIMEOUT(guideB->isEnabled(), 500); 0301 KTRY_GUIDE_CLICK(guideB); 0302 0303 // Wait for calibration to start"); 0304 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (preparingStateLed))->state() == KLed::On, 5000); 0305 0306 // Wait for the calibration to fail 0307 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::red, 0308 Options::guideCalibrationTimeout() * 1200); 0309 } 0310 0311 KTELL("Run a calibration with the telescope pointing after Meridian"); 0312 // And don't forget to track! 0313 //KTRY_MOUNT_SYNC(20, true, -1); 0314 KTRY_MOUNT_SYNC_NAMED("Mizar", true); 0315 QTRY_VERIFY_WITH_TIMEOUT(guideB->isEnabled(), 500); 0316 KTRY_GUIDE_CLICK(guideB); 0317 0318 double const ra = dms(Ekos::Manager::Instance()->mountModule()->raOUT->text(), false).Hours(); 0319 double const dec = dms(Ekos::Manager::Instance()->mountModule()->decOUT->text(), true).Degrees(); 0320 0321 KTELL("We need to wait a bit more than 50 times the default exposure, with a processing time of 3 seconds"); 0322 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, 0323 Options::guideCalibrationTimeout() * 1200); 0324 0325 KTELL("Wait a bit while guiding"); 0326 QTest::qWait(5000); 0327 0328 KTELL("We can stop guiding now that calibration is done"); 0329 KTRY_GUIDE_CLICK(stopB); 0330 QWARN("Guide aborts without waiting for PHD2 to report abort, so wait after stopping before restarting."); 0331 QTest::qWait(500); 0332 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::red, 10000); 0333 0334 KTELL("We can restart, and there will be no calibration to wait for"); 0335 QTRY_VERIFY_WITH_TIMEOUT(guideB->isEnabled(), 5000); 0336 QTest::qWait(500); 0337 KTRY_GUIDE_CLICK(guideB); 0338 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, 10000); 0339 0340 KTELL("Sync the telescope just enough for the star mass to drop, PHD2 will notify star lost"); 0341 Ekos::Manager::Instance()->mountModule()->sync(ra - 0.01, dec); 0342 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::yellow, 10000); 0343 0344 KTELL("Sync the telescope back for the star mass to return, PHD2 will notify star selected"); 0345 Ekos::Manager::Instance()->mountModule()->sync(ra - 0.00, dec); 0346 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, 10000); 0347 0348 KTELL("Sync the telescope just enough for the star mass to drop, PHD2 will notify star lost again"); 0349 Ekos::Manager::Instance()->mountModule()->sync(ra - 0.01, dec); 0350 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::yellow, 10000); 0351 0352 KTELL("Wait for guiding to abort"); 0353 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::red, 0354 Options::guideLostStarTimeout() * 1200); 0355 QVERIFY((dynamic_cast <KLed*> (preparingStateLed))->color() == Qt::red); 0356 QVERIFY((dynamic_cast <KLed*> (idlingStateLed))->color() == Qt::green); 0357 0358 // We can restart, and wait for calibration to end"); 0359 // However that test is not stable - sometimes PHD2 will refuse to continue, sometimes will catch something 0360 QWARN("Restarting to guide when there is no star locked may or may not look for a lock position, bypassing test."); 0361 //KTRY_GUIDE_CLICK(guideB); 0362 //QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, Options::guideCalibrationTimeout() * 1200); 0363 0364 KTELL("Clear calibration and restart"); 0365 QTRY_VERIFY_WITH_TIMEOUT(clearCalibrationB->isEnabled(), 10000); 0366 KTRY_GUIDE_CLICK(clearCalibrationB); 0367 QWARN("No feedback available on PHD2 calibration removal, so wait a bit."); 0368 QTest::qWait(1000); 0369 KTRY_GUIDE_CLICK(guideB); 0370 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, 0371 Options::guideCalibrationTimeout() * 1200); 0372 0373 // It is apparently not possible to check the option "Stop guiding when mount moves" through the server connection 0374 // TODO: update options to enable this 0375 #if 0 0376 // Slew the telescope somewhere else, PHD2 will notify guiding abort 0377 QVERIFY(Ekos::Manager::Instance()->mountModule()->slew(3.1, 0)); 0378 QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->status() == ISD::Mount::MOUNT_TRACKING, 10000); 0379 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::red, 10000); 0380 0381 // We can restart, and there will be no calibration to wait for 0382 KTRY_GUIDE_CLICK(guideB); 0383 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->color() == Qt::green, 5000); 0384 #endif 0385 0386 // Run device disconnection tests 0387 // TODO: Ekos::Manager::Instance()->mountModule()->currentTelescope->Disconnect(); 0388 // TODO: Ekos::Manager::Instance()->captureModule()->currentCCD->Disconnect(); 0389 0390 // TODO: Manipulate PHD2 directly to dis/connect and loop 0391 0392 // Stop now - if the previous test successfully started to guide 0393 if (stopB->isEnabled()) 0394 { 0395 KTRY_GUIDE_CLICK(stopB); 0396 QWARN("Guide aborts without waiting for PHD2 to report abort, so wait after stopping before restarting."); 0397 QTest::qWait(500); 0398 QTRY_VERIFY_WITH_TIMEOUT((dynamic_cast <KLed*> (runningStateLed))->state() == KLed::Off, 5000); 0399 QTRY_VERIFY_WITH_TIMEOUT(guideB->isEnabled(), 2000); 0400 } 0401 0402 // Revert timeout options 0403 Options::setGuideCalibrationTimeout(calibration_timeout); 0404 Options::setGuideLostStarTimeout(loststar_timeout); 0405 0406 // Disconnect 0407 KTRY_GUIDE_CLICK(externalDisconnectB); 0408 QTRY_VERIFY_WITH_TIMEOUT(externalConnectB->isEnabled(), 10000); 0409 } 0410 0411 QTEST_KSTARS_MAIN(TestEkosGuide) 0412 0413 #endif