File indexing completed on 2024-04-14 14:12:03
0001 /* 0002 KStars UI tests for alignment 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_align.h" 0010 0011 #include "kstars_ui_tests.h" 0012 #include "test_ekos.h" 0013 #include "test_ekos_scheduler_helper.h" 0014 #include "mountmodel.h" 0015 #include "Options.h" 0016 #include "indi/guimanager.h" 0017 #include "ekos/align/align.h" 0018 0019 #include "skymapcomposite.h" 0020 0021 TestEkosAlign::TestEkosAlign(QObject *parent) : QObject(parent) 0022 { 0023 m_CaptureHelper = new TestEkosCaptureHelper(); 0024 } 0025 0026 0027 void TestEkosAlign::testSlewAlign() 0028 { 0029 // slew to Kocab as target 0030 SkyObject *target = findTargetByName("Kocab"); 0031 QVERIFY(m_CaptureHelper->slewTo(target->ra().Hours(), target->dec().Degrees(), true)); 0032 0033 // execute an alignment and verify the result 0034 QVERIFY(executeAlignment(target)); 0035 } 0036 0037 void TestEkosAlign::testEmptySkyAlign() 0038 { 0039 SkyObject *emptySky = new SkyObject(SkyObject::TYPE_UNKNOWN, 15.254722222222222, 72.031388888888889); 0040 // update J2000 coordinates 0041 updateJ2000Coordinates(emptySky); 0042 0043 QVERIFY(m_CaptureHelper->slewTo(emptySky->ra().Hours(), emptySky->dec().Degrees(), true)); 0044 0045 // execute an alignment and verify the result 0046 QVERIFY(executeAlignment(emptySky)); 0047 } 0048 0049 void TestEkosAlign::testSlewDriftAlign() 0050 { 0051 // slew to Kocab as target 0052 SkyObject *target = findTargetByName("Kocab"); 0053 QVERIFY(m_CaptureHelper->slewTo(target->ra().Hours(), target->dec().Degrees(), true)); 0054 // execute an alignment and verify the result 0055 QVERIFY(executeAlignment(target)); 0056 // now send motion north and west to create a guiding deviation 0057 Ekos::Mount *mount = Ekos::Manager::Instance()->mountModule(); 0058 mount->doPulse(RA_INC_DIR, 5000, DEC_INC_DIR, 5000); 0059 // wait until the pulse ends 0060 QTest::qWait(6000); 0061 // execute an alignment and verify if the target is the same 0062 QVERIFY(executeAlignment(target)); 0063 } 0064 0065 void TestEkosAlign::testFitsAlign() 0066 { 0067 // check the FITS file 0068 QString const fits_file = "../Tests/kstars_ui/Kocab.fits"; 0069 QFileInfo target_fits(fits_file); 0070 QVERIFY(target_fits.exists()); 0071 0072 // define expected target (center of the FITS file, J2000) 0073 SkyObject targetObject(SkyObject::STAR, 14.844809110652733, 74.1587773591246, 1.8f, "Kocab"); 0074 targetObject.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd()); 0075 0076 // slew somewhere close to the target 0077 SkyObject *close_to_target = findTargetByName("Pherkab"); 0078 QVERIFY(m_CaptureHelper->slewTo(close_to_target->ra().Hours(), close_to_target->dec().Degrees(), true)); 0079 // wait until the slew has finished 0080 QTRY_VERIFY_WITH_TIMEOUT(m_TelescopeStatus == ISD::Mount::MOUNT_TRACKING, 60000); 0081 0082 // ensure that we wait until alignment completion 0083 expectedAlignStates.append(Ekos::ALIGN_PROGRESS); 0084 expectedAlignStates.append(Ekos::ALIGN_COMPLETE); 0085 0086 // execute the FITS alignment and verify the result 0087 Ekos::Manager::Instance()->alignModule()->loadAndSlew(target_fits.filePath()); 0088 0089 // wait until the alignment succeeds 0090 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(expectedAlignStates, 20000); 0091 0092 // check if the alignment target matches 0093 verifyAlignmentTarget(&targetObject); 0094 } 0095 0096 void TestEkosAlign::testAlignTargetScheduledJob() 0097 { 0098 // test scheduler with target 0099 alignWithScheduler(findTargetByName("Pherkab")); 0100 } 0101 0102 void TestEkosAlign::testFitsAlignTargetScheduledJob() 0103 { 0104 // check the FITS file 0105 QString const fits_file = "../Tests/kstars_ui/Kocab.fits"; 0106 QFileInfo target_fits(fits_file); 0107 QVERIFY(target_fits.exists()); 0108 0109 // define expected target (center of the FITS file, J2000) 0110 SkyObject targetObject(SkyObject::STAR, 14.844809110652733, 74.1587773591246, 1.8f, "Kocab"); 0111 targetObject.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd()); 0112 0113 // test scheduler with target 0114 alignWithScheduler(&targetObject, target_fits.absoluteFilePath()); 0115 } 0116 0117 void TestEkosAlign::testSyncOnlyAlign() 0118 { 0119 QSKIP("Align feature of sync only not implemented yet."); 0120 Ekos::Manager *ekos = Ekos::Manager::Instance(); 0121 // select to Pherkab as target 0122 SkyObject *pherkab = findTargetByName("Pherkab"); 0123 0124 KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(ekos->mountModule(), 1000); 0125 // ensure that the mount is tracking 0126 KTRY_CLICK(ekos->mountModule(), trackOnB); 0127 // sync to Pherkab (use JNow for sync) 0128 ekos->mountModule()->sync(pherkab->ra().Hours(), pherkab->dec().Degrees()); 0129 0130 // execute an alignment and verify the result 0131 // Align::getTargetCoords() returns J2000 values 0132 KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(ekos->alignModule(), 1000); 0133 QVERIFY(executeAlignment(pherkab)); 0134 } 0135 0136 void TestEkosAlign::testAlignRecoverFromSlewing() 0137 { 0138 // slew to Kocab 0139 m_CaptureHelper->slewTo(14.845, 74.106, true); 0140 // expect align events: suspend --> progress --> complete 0141 expectedAlignStates.append(Ekos::ALIGN_SUSPENDED); 0142 expectedAlignStates.append(Ekos::ALIGN_PROGRESS); 0143 expectedAlignStates.append(Ekos::ALIGN_COMPLETE); 0144 // start alignment 0145 QVERIFY(m_CaptureHelper->startAligning(5)); 0146 // ensure that capturing has started 0147 QTest::qWait(2000); 0148 // move the mount to interrupt alignment 0149 qCInfo(KSTARS_EKOS_TEST) << "Moving the mount to interrupt alignment..."; 0150 Ekos::Mount *mount = Ekos::Manager::Instance()->mountModule(); 0151 mount->slew(14.86, 74.106); 0152 mount->slew(14.845, 74.106); 0153 // check if event sequence has been completed 0154 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(expectedAlignStates, 30000); 0155 // check the number of images and plate solves 0156 KTRY_CHECK_ALIGNMENTS(1); 0157 } 0158 0159 void TestEkosAlign::testMountModel() 0160 { 0161 // test two runs 0162 for (int points = 2; points < 4; points++) 0163 { 0164 init(); 0165 // prepare the mount model 0166 QVERIFY(prepareMountModel(points)); 0167 // execute the mount model tool 0168 QVERIFY(runMountModelTool(points, false)); 0169 } 0170 } 0171 0172 void TestEkosAlign::testMountModelToolRecoverFromSlewing() 0173 { 0174 int points = 3; 0175 // prepare the mount model 0176 QVERIFY(prepareMountModel(points)); 0177 // execute the mount model tool 0178 QVERIFY(runMountModelTool(points, true)); 0179 } 0180 0181 /* ********************************************************************************* 0182 * 0183 * Test infrastructure 0184 * 0185 * ********************************************************************************* */ 0186 0187 void TestEkosAlign::initTestCase() 0188 { 0189 KVERIFY_EKOS_IS_HIDDEN(); 0190 KTRY_OPEN_EKOS(); 0191 KVERIFY_EKOS_IS_OPENED(); 0192 QStandardPaths::setTestModeEnabled(true); 0193 // create temporary directory 0194 testDir = new QTemporaryDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX"); 0195 0196 // use the helper to start the profile 0197 m_CaptureHelper->startEkosProfile(); 0198 0199 prepareTestCase(); 0200 } 0201 0202 void TestEkosAlign::cleanupTestCase() 0203 { 0204 // connect to the align process to count solving results 0205 disconnect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newSolverResults, this, 0206 &TestEkosAlign::solverResultReceived); 0207 // disconnect to the capture process to receive capture status changes 0208 disconnect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newImage, this, 0209 &TestEkosAlign::imageReceived); 0210 // disconnect to the alignment process to receive align status changes 0211 disconnect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus, this, 0212 &TestEkosAlign::alignStatusChanged); 0213 0214 m_CaptureHelper->cleanup(); 0215 QVERIFY(m_CaptureHelper->shutdownEkosProfile()); 0216 KTRY_CLOSE_EKOS(); 0217 KVERIFY_EKOS_IS_HIDDEN(); 0218 } 0219 0220 void TestEkosAlign::prepareTestCase() 0221 { 0222 Ekos::Manager * const ekos = Ekos::Manager::Instance(); 0223 0224 // set logging defaults for alignment 0225 Options::setVerboseLogging(false); 0226 Options::setAlignmentLogging(false); 0227 Options::setLogToFile(false); 0228 0229 // prepare optical trains for testing 0230 m_CaptureHelper->prepareOpticalTrains(); 0231 // prepare the mount module for testing 0232 m_CaptureHelper->prepareMountModule(); 0233 // prepare for focusing tests for those that use the focuser 0234 if (m_CaptureHelper->m_FocuserDevice != "") 0235 m_CaptureHelper->prepareFocusModule(); 0236 // prepare for alignment tests 0237 m_CaptureHelper->prepareAlignmentModule(); 0238 0239 m_CaptureHelper->init(); 0240 // close INDI window 0241 GUIManager::Instance()->close(); 0242 0243 // connect to the alignment process to receive align status changes 0244 connect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus, this, 0245 &TestEkosAlign::alignStatusChanged, Qt::UniqueConnection); 0246 0247 // connect to the mount process to receive status changes 0248 connect(Ekos::Manager::Instance()->mountModule(), &Ekos::Mount::newStatus, this, 0249 &TestEkosAlign::telescopeStatusChanged, Qt::UniqueConnection); 0250 0251 // connect to the align process to receive captured images 0252 connect(ekos->alignModule(), &Ekos::Align::newImage, this, &TestEkosAlign::imageReceived, 0253 Qt::UniqueConnection); 0254 0255 // connect to the align process to count solving results 0256 connect(ekos->alignModule(), &Ekos::Align::newSolverResults, this, &TestEkosAlign::solverResultReceived, 0257 Qt::UniqueConnection); 0258 } 0259 0260 void TestEkosAlign::init() 0261 { 0262 // reset counters 0263 solver_count = 0; 0264 image_count = 0; 0265 } 0266 0267 void TestEkosAlign::cleanup() 0268 { 0269 Ekos::Manager *ekos = Ekos::Manager::Instance(); 0270 Ekos::Scheduler *scheduler = ekos->schedulerModule(); 0271 // press stop button if running 0272 if (scheduler->status() == Ekos::SCHEDULER_STARTUP || scheduler->status() == Ekos::SCHEDULER_RUNNING) 0273 KTRY_CLICK(scheduler, startB); 0274 // in all cases, stop the scheduler and remove all jobs 0275 scheduler->stop(); 0276 scheduler->removeAllJobs(); 0277 } 0278 0279 bool TestEkosAlign::prepareMountModel(int points) 0280 { 0281 KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->alignModule(), 1000)); 0282 // open the mount model tool 0283 KTRY_CLICK_SUB(Ekos::Manager::Instance()->alignModule(), mountModelB); 0284 0285 // wait until the mount model is instantiated 0286 QTest::qWait(1000); 0287 Ekos::MountModel * mountModel = Ekos::Manager::Instance()->alignModule()->mountModel(); 0288 KVERIFY_SUB(mountModel != nullptr); 0289 0290 // clear alignment points 0291 if (mountModel->alignTable->rowCount() > 0) 0292 { 0293 QTimer::singleShot(2000, [&]() 0294 { 0295 QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget()); 0296 if (dialog != nullptr) 0297 QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Yes), Qt::LeftButton); 0298 }); 0299 mountModel->clearAllAlignB->click(); 0300 } 0301 KVERIFY_SUB(mountModel->alignTable->rowCount() == 0); 0302 // set number of alignment points 0303 mountModel->alignPtNum->setValue(points); 0304 KVERIFY_SUB(mountModel->alignPtNum->value() == points); 0305 // use named stars for alignment (this hopefully improves plate soving speed) 0306 mountModel->alignTypeBox->setCurrentIndex(Ekos::MountModel::OBJECT_NAMED_STAR); 0307 KVERIFY_SUB(mountModel->alignTypeBox->currentIndex() == Ekos::MountModel::OBJECT_NAMED_STAR); 0308 // create the alignment points 0309 KVERIFY_SUB(mountModel->wizardAlignB->isEnabled()); 0310 mountModel->wizardAlignB->click(); 0311 0312 // success 0313 return true; 0314 } 0315 0316 bool TestEkosAlign::runMountModelTool(int points, bool moveMount) 0317 { 0318 Ekos::MountModel * mountModel = Ekos::Manager::Instance()->alignModule()->mountModel(); 0319 0320 // start model creation 0321 KVERIFY_SUB(mountModel->startAlignB->isEnabled()); 0322 mountModel->startAlignB->click(); 0323 0324 // check alignment 0325 for (int i = 1; i <= points; i++) 0326 { 0327 KVERIFY_SUB(trackSingleAlignment(i == points, moveMount)); 0328 KWRAP_SUB(KTRY_CHECK_ALIGNMENTS(i)); 0329 } 0330 0331 // success 0332 return true; 0333 } 0334 0335 0336 bool TestEkosAlign::trackSingleAlignment(bool lastPoint, bool moveMount) 0337 { 0338 expectedTelescopeStates.append(ISD::Mount::MOUNT_TRACKING); 0339 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedTelescopeStates, 60000); 0340 0341 // wait one second ensuring that capturing starts 0342 QTest::qWait(1000); 0343 // determine current target position 0344 Ekos::Mount *mount = Ekos::Manager::Instance()->mountModule(); 0345 SkyPoint target = mount->currentTarget(); 0346 double ra = target.ra().Hours(); 0347 double dec = target.dec().Degrees(); 0348 // expect a suspend event if mount is moved 0349 if (moveMount) 0350 expectedAlignStates.append(Ekos::ALIGN_SUSPENDED); 0351 // expect a progress signal for start capturing and solving 0352 expectedAlignStates.append(Ekos::ALIGN_PROGRESS); 0353 // if last point is reached, expect a complete signal, else expect a slew 0354 if (lastPoint == true) 0355 expectedAlignStates.append(Ekos::ALIGN_COMPLETE); 0356 else 0357 expectedAlignStates.append(Ekos::ALIGN_SLEWING); 0358 if (moveMount) 0359 { 0360 // move the mount to interrupt alignment 0361 qCInfo(KSTARS_EKOS_TEST) << "Moving the mount to interrupt alignment..."; 0362 mount->slew(ra + 0.05, dec + 0.1); 0363 mount->slew(ra, dec); 0364 } 0365 // check if event sequence has been completed 0366 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedAlignStates, 180000); 0367 0368 // everything succeeded 0369 return true; 0370 } 0371 0372 bool TestEkosAlign::executeAlignment(SkyObject *targetObject) 0373 { 0374 // start alignment 0375 KVERIFY_SUB(m_CaptureHelper->startAligning(5.0)); 0376 // verify if alignment completes 0377 KTRY_VERIFY_WITH_TIMEOUT_SUB(m_CaptureHelper->getAlignStatus() == Ekos::ALIGN_COMPLETE, 60000); 0378 // check if the alignment target matches 0379 KVERIFY_SUB(verifyAlignmentTarget(targetObject)); 0380 // success 0381 return true; 0382 } 0383 0384 bool TestEkosAlign::verifyAlignmentTarget(SkyObject *targetObject) 0385 { 0386 QList<double> alignmentTarget = Ekos::Manager::Instance()->alignModule()->getTargetCoords(); 0387 KVERIFY2_SUB(std::abs(alignmentTarget[0] - targetObject->ra0().Hours()) < 0388 0.005, // difference small enough to capture JNow/J2000 errors 0389 QString("RA target J2000 deviation too big: %1 received, %2 expected.").arg(alignmentTarget[0]).arg( 0390 targetObject->ra0().Hours()).toLocal8Bit()); 0391 KVERIFY2_SUB(std::abs(alignmentTarget[1] - targetObject->dec0().Degrees()) < 0.005, 0392 QString("DEC target J2000 deviation too big: %1 received, %2 expected.").arg(alignmentTarget[1]).arg( 0393 targetObject->dec0().Degrees()).toLocal8Bit()); 0394 // success 0395 return true; 0396 } 0397 0398 bool TestEkosAlign::alignWithScheduler(SkyObject *targetObject, QString fitsTarget) 0399 { 0400 Ekos::Manager *ekos = Ekos::Manager::Instance(); 0401 Ekos::Scheduler *scheduler = ekos->schedulerModule(); 0402 // start ASAP 0403 TestEkosSchedulerHelper::StartupCondition startupCondition; 0404 startupCondition.type = Ekos::START_ASAP; 0405 TestEkosSchedulerHelper::CompletionCondition completionCondition; 0406 completionCondition.type = Ekos::FINISH_SEQUENCE; 0407 0408 // create the capture sequence file 0409 const QString sequenceFile = TestEkosSchedulerHelper::getDefaultEsqContent(); 0410 const QString esqFile = testDir->filePath(QString("test.esq")); 0411 // create the scheduler file 0412 const QString schedulerFile = TestEkosSchedulerHelper::getSchedulerFile(targetObject, startupCondition, 0413 completionCondition, {true, false, true, false}, false, false, 30, fitsTarget); 0414 const QString eslFile = testDir->filePath(QString("test.esl")); 0415 // write both files to the test directory 0416 KVERIFY_SUB(TestEkosSchedulerHelper::writeSimpleSequenceFiles(schedulerFile, eslFile, sequenceFile, esqFile)); 0417 // load the scheduler file 0418 KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(scheduler, 1000)); 0419 scheduler->loadScheduler(eslFile); 0420 0421 // start 0422 KTRY_CLICK_SUB(scheduler, startB); 0423 0424 // expect a slew 0425 expectedTelescopeStates.append(ISD::Mount::MOUNT_SLEWING); 0426 expectedTelescopeStates.append(ISD::Mount::MOUNT_TRACKING); 0427 // ensure that we wait until alignment completion 0428 expectedAlignStates.append(Ekos::ALIGN_PROGRESS); 0429 expectedAlignStates.append(Ekos::ALIGN_COMPLETE); 0430 0431 // wait until the slew has finished 0432 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedTelescopeStates, 60000); 0433 // wait until the alignment succeeds 0434 KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedAlignStates, 20000); 0435 0436 // check if the alignment target matches 0437 return verifyAlignmentTarget(targetObject); 0438 } 0439 0440 SkyObject *TestEkosAlign::findTargetByName(QString name) 0441 { 0442 SkyObject *targetObject = KStarsData::Instance()->skyComposite()->findByName(name); 0443 // update J2000 coordinates 0444 updateJ2000Coordinates(targetObject); 0445 0446 return targetObject; 0447 } 0448 0449 void TestEkosAlign::updateJ2000Coordinates(SkyPoint *target) 0450 { 0451 SkyPoint J2000Coord(target->ra(), target->dec()); 0452 J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd()); 0453 target->setRA0(J2000Coord.ra()); 0454 target->setDec0(J2000Coord.dec()); 0455 } 0456 0457 0458 0459 /* ********************************************************************************* 0460 * 0461 * Slots for catching state changes 0462 * 0463 * ********************************************************************************* */ 0464 0465 void TestEkosAlign::alignStatusChanged(Ekos::AlignState status) 0466 { 0467 m_AlignStatus = status; 0468 // check if the new state is the next one expected, then remove it from the stack 0469 if (!expectedAlignStates.isEmpty() && expectedAlignStates.head() == status) 0470 expectedAlignStates.dequeue(); 0471 } 0472 0473 void TestEkosAlign::telescopeStatusChanged(ISD::Mount::Status status) 0474 { 0475 m_TelescopeStatus = status; 0476 // check if the new state is the next one expected, then remove it from the stack 0477 if (!expectedTelescopeStates.isEmpty() && expectedTelescopeStates.head() == status) 0478 expectedTelescopeStates.dequeue(); 0479 } 0480 0481 void TestEkosAlign::captureStatusChanged(Ekos::CaptureState status) 0482 { 0483 m_CaptureStatus = status; 0484 // check if the new state is the next one expected, then remove it from the stack 0485 if (!expectedCaptureStates.isEmpty() && expectedCaptureStates.head() == status) 0486 expectedCaptureStates.dequeue(); 0487 } 0488 0489 void TestEkosAlign::imageReceived(const QSharedPointer<FITSView> &view) 0490 { 0491 Q_UNUSED(view); 0492 captureStatusChanged(Ekos::CAPTURE_COMPLETE); 0493 image_count++; 0494 } 0495 0496 void TestEkosAlign::solverResultReceived(double orientation, double ra, double dec, double pixscale) 0497 { 0498 Q_UNUSED(orientation); 0499 Q_UNUSED(ra); 0500 Q_UNUSED(dec); 0501 Q_UNUSED(pixscale); 0502 solver_count++; 0503 } 0504 0505 /* ********************************************************************************* 0506 * 0507 * Main function 0508 * 0509 * ********************************************************************************* */ 0510 0511 QTEST_KSTARS_MAIN(TestEkosAlign)