1 /*  KStars UI tests
2     SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 
8 #include "test_ekos_focus.h"
9 
10 #if defined(HAVE_INDI)
11 
12 #include "kstars_ui_tests.h"
13 #include "test_ekos.h"
14 #include "test_ekos_simulator.h"
15 #include "test_ekos_mount.h"
16 #include "Options.h"
17 
18 class KFocusProcedureSteps: public QObject
19 {
20 public:
21     QMetaObject::Connection starting;
22     QMetaObject::Connection aborting;
23     QMetaObject::Connection completing;
24     QMetaObject::Connection notguiding;
25     QMetaObject::Connection guiding;
26     QMetaObject::Connection quantifying;
27 
28 public:
29     bool started { false };
30     bool aborted { false };
31     bool complete { false };
32     bool unguided { false };
33     double hfr { -2 };
34 
35 public:
KFocusProcedureSteps()36     KFocusProcedureSteps():
37         starting (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::autofocusStarting, this, [&]() { started = true; }, Qt::UniqueConnection)),
__anon130cd9be0202() 38         aborting (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::autofocusAborted, this, [&]() { started = false; aborted = true; }, Qt::UniqueConnection)),
__anon130cd9be0302() 39         completing (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::autofocusComplete, this, [&]() { started = false; complete = true; }, Qt::UniqueConnection)),
__anon130cd9be0402() 40         notguiding (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::suspendGuiding, this, [&]() { unguided = true; }, Qt::UniqueConnection)),
__anon130cd9be0502() 41         guiding (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::resumeGuiding, this, [&]() { unguided = false; }, Qt::UniqueConnection)),
__anon130cd9be0602(double _hfr) 42         quantifying (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::newHFR, this, [&](double _hfr) { hfr = _hfr; }, Qt::UniqueConnection))
43     {};
~KFocusProcedureSteps()44     virtual ~KFocusProcedureSteps() {
45         disconnect(starting);
46         disconnect(aborting);
47         disconnect(completing);
48         disconnect(notguiding);
49         disconnect(guiding);
50         disconnect(quantifying);
51     };
52 };
53 
54 class KFocusStateList: public QObject, public QList <Ekos::FocusState>
55 {
56 public:
57     QMetaObject::Connection handler;
58 
59 public:
KFocusStateList()60     KFocusStateList():
61         handler (connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::newStatus, this, [&](Ekos::FocusState s) { append(s); }, Qt::UniqueConnection))
62     {};
~KFocusStateList()63     virtual ~KFocusStateList() {};
64 };
65 
TestEkosFocus(QObject * parent)66 TestEkosFocus::TestEkosFocus(QObject *parent) : QObject(parent)
67 {
68 }
69 
initTestCase()70 void TestEkosFocus::initTestCase()
71 {
72     KVERIFY_EKOS_IS_HIDDEN();
73     KTRY_OPEN_EKOS();
74     KVERIFY_EKOS_IS_OPENED();
75     KTRY_EKOS_START_SIMULATORS();
76 
77     // We can't use this here because of the meridian flip test
78     // HACK: Reset clock to initial conditions
79     // KHACK_RESET_EKOS_TIME();
80 
81     KTELL_BEGIN();
82 }
83 
cleanupTestCase()84 void TestEkosFocus::cleanupTestCase()
85 {
86     KTELL_END();
87     KTRY_EKOS_STOP_SIMULATORS();
88     KTRY_CLOSE_EKOS();
89     KVERIFY_EKOS_IS_HIDDEN();
90 }
91 
init()92 void TestEkosFocus::init()
93 {
94 }
95 
cleanup()96 void TestEkosFocus::cleanup()
97 {
98     if (Ekos::Manager::Instance())
99         if (Ekos::Manager::Instance()->focusModule())
100             Ekos::Manager::Instance()->focusModule()->abort();
101     KTELL_HIDE();
102 }
103 
testCaptureStates()104 void TestEkosFocus::testCaptureStates()
105 {
106     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.");
107     KTRY_MOUNT_SYNC(60.0, true, -1);
108 
109     // Prepare to detect state change
110     KFocusStateList state_list;
111     QVERIFY(state_list.handler);
112 
113     KTELL("Configure fields.\nCapture a frame.\nExpect PROGRESS, IDLE.");
114     KTRY_FOCUS_MOVETO(40000);
115     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3.0);
116     KTRY_FOCUS_DETECT(2, 1, 99);
117     QTRY_COMPARE_WITH_TIMEOUT(state_list.count(), 2, 5000);
118     QCOMPARE(state_list[0], Ekos::FocusState::FOCUS_PROGRESS);
119     QCOMPARE(state_list[1], Ekos::FocusState::FOCUS_IDLE);
120     state_list.clear();
121 
122     KTELL("Move focuser.\nExpect no capture triggered.");
123     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3.0);
124     KTRY_FOCUS_MOVETO(43210);
125     QTest::qWait(1000);
126     QCOMPARE(state_list.count(), 0);
127 
128     KTRY_FOCUS_GADGET(QPushButton, startLoopB);
129     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
130 
131     KTELL("Loop captures.\nAbort loop.\nExpect FRAMING, PROGRESS, ABORTED, ABORTED.");
132     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3.0);
133     KTRY_FOCUS_CLICK(startLoopB);
134     QTRY_VERIFY_WITH_TIMEOUT(state_list.count() >= 1, 5000);
135     KTRY_FOCUS_CLICK(stopFocusB);
136     QTRY_VERIFY_WITH_TIMEOUT(state_list.count() >= 4, 5000);
137     QCOMPARE((int)state_list[0], (int)Ekos::FocusState::FOCUS_FRAMING);
138     QCOMPARE((int)state_list[1], (int)Ekos::FocusState::FOCUS_PROGRESS);
139     QCOMPARE((int)state_list[2], (int)Ekos::FocusState::FOCUS_ABORTED);
140     QCOMPARE((int)state_list[3], (int)Ekos::FocusState::FOCUS_ABORTED);
141     state_list.clear();
142 
143     KTRY_FOCUS_GADGET(QCheckBox, useAutoStar);
144     KTRY_FOCUS_GADGET(QPushButton, resetFrameB);
145 
146     QWARN("This test does not wait for the hardcoded timeout to select a star.");
147 
148     KTELL("Use a successful automatic star selection (not full-field).\nCapture a frame.\nExpect PROGRESS, IDLE\nCheck star selection.");
149     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 0.0, 3.0);
150     useAutoStar->setCheckState(Qt::CheckState::Checked);
151     KTRY_FOCUS_DETECT(2, 1, 99);
152     QTRY_VERIFY_WITH_TIMEOUT(state_list.count() >= 2, 5000);
153     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
154     KTRY_FOCUS_CLICK(resetFrameB);
155     QCOMPARE(state_list[0], Ekos::FocusState::FOCUS_PROGRESS);
156     QCOMPARE(state_list[1], Ekos::FocusState::FOCUS_IDLE);
157     useAutoStar->setCheckState(Qt::CheckState::Unchecked);
158     state_list.clear();
159 
160     KTELL("Use an unsuccessful automatic star selection (not full-field).\nCapture a frame\nExpect PROGRESS, WAITING.\nCheck star selection.");
161     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 0.0, 3.0);
162     useAutoStar->setCheckState(Qt::CheckState::Checked);
163     KTRY_FOCUS_DETECT(0.01, 1, 1);
164     QTRY_VERIFY_WITH_TIMEOUT(state_list.count() >= 2, 5000);
165     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
166     KTRY_FOCUS_CLICK(resetFrameB);
167     QCOMPARE(state_list[0], Ekos::FocusState::FOCUS_PROGRESS);
168     QCOMPARE(state_list[1], Ekos::FocusState::FOCUS_WAITING);
169     useAutoStar->setCheckState(Qt::CheckState::Unchecked);
170     state_list.clear();
171 }
172 
testDuplicateFocusRequest()173 void TestEkosFocus::testDuplicateFocusRequest()
174 {
175     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure a fast autofocus.");
176     KTRY_FOCUS_SHOW();
177     KTRY_MOUNT_SYNC(60.0, true, -1);
178     KTRY_FOCUS_MOVETO(35000);
179     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 0.0, 30);
180     KTRY_FOCUS_EXPOSURE(3, 99);
181 
182     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
183     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
184     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
185     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
186 
187     // Prepare to detect the beginning of the autofocus_procedure
188     KFocusProcedureSteps autofocus;
189     QVERIFY(autofocus.starting);
190 
191     KTELL("Click the autofocus button\nExpect a signal that the procedure starts.\nExpect state change and disabled button.");
192     KTRY_FOCUS_CLICK(startFocusB);
193     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
194     QVERIFY(Ekos::Manager::Instance()->focusModule()->status() != Ekos::FOCUS_IDLE);
195     QVERIFY(!startFocusB->isEnabled());
196     QVERIFY(stopFocusB->isEnabled());
197 
198     KTELL("Issue a few autofocus commands at that point through the d-bus entry point\nExpect no parallel procedure start.");
199     for (int i = 0; i < 5; i++)
200     {
201         autofocus.started = false;
202         Ekos::Manager::Instance()->focusModule()->start();
203         QTest::qWait(500);
204         QVERIFY(!autofocus.started);
205     }
206 
207     KTELL("Stop the running autofocus.");
208     KTRY_FOCUS_CLICK(stopFocusB);
209     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 5000);
210 }
211 
testAutofocusSignalEmission()212 void TestEkosFocus::testAutofocusSignalEmission()
213 {
214     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure fast autofocus.");
215     KTRY_FOCUS_SHOW();
216     KTRY_MOUNT_SYNC(60.0,true, -1);
217 
218     KTRY_FOCUS_MOVETO(35000);
219     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 30);
220     KTRY_FOCUS_EXPOSURE(3, 99);
221 
222     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
223     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
224     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
225     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
226 
227     // Prepare to detect the beginning of the autofocus_procedure
228     KFocusProcedureSteps autofocus;
229     QVERIFY(autofocus.starting);
230 
231     KTELL("Configure to restart autofocus when it finishes, like Scheduler does.");
232     volatile bool ran_once = false;
233     autofocus.completing = connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::autofocusComplete, &autofocus, [&]() {
234         autofocus.complete = true;
235         autofocus.started = false;
236         if (!ran_once)
237         {
238             Ekos::Manager::Instance()->focusModule()->start();
239             ran_once = true;
240         }
241     }, Qt::UniqueConnection);
242     QVERIFY(autofocus.completing);
243 
244     KTELL("Run autofocus, wait for completion.\nHandler restarts a second one immediately.");
245     QVERIFY(!autofocus.started);
246     QVERIFY(!autofocus.complete);
247     KTRY_FOCUS_CLICK(startFocusB);
248     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
249     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 30000);
250 
251     KTELL("Wait for the second run to finish.\nNo other autofocus started.");
252     autofocus.complete = false;
253     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 30000);
254     QVERIFY(!autofocus.started);
255 }
256 
testFocusAbort()257 void TestEkosFocus::testFocusAbort()
258 {
259     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure fast autofocus.");
260     KTRY_FOCUS_SHOW();
261     KTRY_MOUNT_SYNC(60.0, true, -1);
262     KTRY_FOCUS_MOVETO(35000);
263     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 30);
264     KTRY_FOCUS_EXPOSURE(3, 99);
265 
266     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
267     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
268     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
269     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
270 
271     // Prepare to detect the beginning of the autofocus_procedure
272     KFocusProcedureSteps autofocus;
273     QVERIFY(autofocus.starting);
274     QVERIFY(autofocus.completing);
275 
276     KTELL("Configure to restart autofocus when it finishes, like Scheduler does.");
277     volatile bool ran_once = false;
278     autofocus.aborting = connect(Ekos::Manager::Instance()->focusModule(), &Ekos::Focus::autofocusAborted, this, [&]() {
279         autofocus.aborted = true;
280         autofocus.started = false;
281         if (!ran_once)
282         {
283             Ekos::Manager::Instance()->focusModule()->start();
284             ran_once = true;
285         }
286     }, Qt::UniqueConnection);
287     QVERIFY(autofocus.aborting);
288 
289     KTELL("Run autofocus, don't wait for the completion signal and abort it.\nHandler restarts a second one immediately.");
290     QVERIFY(!autofocus.started);
291     QVERIFY(!autofocus.aborted);
292     QVERIFY(!autofocus.complete);
293     KTRY_FOCUS_CLICK(startFocusB);
294     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
295     KTRY_FOCUS_CLICK(stopFocusB);
296     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 1000);
297 
298     KTELL("Wait for the second run to finish.\nNo other autofocus started.");
299     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
300     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 30000);
301     QVERIFY(!autofocus.started);
302 }
303 
testGuidingSuspendWhileFocusing()304 void TestEkosFocus::testGuidingSuspendWhileFocusing()
305 {
306     KTELL("Sync high on meridian to avoid jitter in CCD Simulator\nConfigure a fast autofocus.");
307     KTRY_FOCUS_SHOW();
308     KTRY_MOUNT_SYNC(60.0, true, -1);
309     KTRY_FOCUS_MOVETO(35000);
310     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 30);
311     KTRY_FOCUS_EXPOSURE(3, 99);
312 
313     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
314     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
315     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
316     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
317 
318     // Prepare to detect the beginning of the autofocus_procedure
319     KFocusProcedureSteps autofocus;
320     QVERIFY(autofocus.starting);
321     QVERIFY(autofocus.aborting);
322     QVERIFY(autofocus.completing);
323     QVERIFY(autofocus.notguiding);
324     QVERIFY(autofocus.guiding);
325 
326     KTRY_FOCUS_GADGET(QCheckBox, suspendGuideCheck);
327 
328     KTELL("Abort the autofocus with guiding set to suspend\nGuiding required to suspend, then required to resume");
329     suspendGuideCheck->setCheckState(Qt::CheckState::Checked);
330     QVERIFY(!autofocus.started);
331     QVERIFY(!autofocus.aborted);
332     QVERIFY(!autofocus.complete);
333     QVERIFY(!autofocus.unguided);
334     KTRY_FOCUS_CLICK(startFocusB);
335     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
336     QVERIFY(autofocus.unguided);
337     Ekos::Manager::Instance()->focusModule()->abort();
338     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 5000);
339     QVERIFY(!autofocus.unguided);
340 
341     KTELL("Run the autofocus to completion with guiding set to suspend\nGuiding required to suspend, then required to resume\nNo other autofocus started.");
342     autofocus.started = autofocus.aborted = false;
343     KTRY_FOCUS_CLICK(startFocusB);
344     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
345     QVERIFY(autofocus.unguided);
346     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 30000);
347     QVERIFY(!autofocus.unguided);
348     QVERIFY(!autofocus.started);
349 
350     KTELL("Abort the autofocus with guiding set to continue\nNo guiding signal emitted");
351     suspendGuideCheck->setCheckState(Qt::CheckState::Unchecked);
352     autofocus.started = autofocus.aborted = autofocus.complete = autofocus.unguided = false;
353     KTRY_FOCUS_CLICK(startFocusB);
354     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
355     QVERIFY(!autofocus.unguided);
356     Ekos::Manager::Instance()->focusModule()->abort();
357     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 5000);
358     QVERIFY(!autofocus.unguided);
359 
360     KTELL("Run the autofocus to completion with guiding set to continue\nNo guiding signal emitted\nNo other autofocus started.");
361     autofocus.started = autofocus.aborted = false;
362     KTRY_FOCUS_CLICK(startFocusB);
363     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
364     QVERIFY(!autofocus.unguided);
365     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 30000);
366     QVERIFY(!autofocus.unguided);
367     QVERIFY(!autofocus.started);
368 }
369 
testFocusWhenMountFlips()370 void TestEkosFocus::testFocusWhenMountFlips()
371 {
372     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure a fast autofocus.");
373     KTRY_FOCUS_SHOW();
374     KTRY_MOUNT_SYNC(60.0, true, +10.0/3600);
375     KTRY_FOCUS_MOVETO(35000);
376     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 5);
377     KTRY_FOCUS_EXPOSURE(3, 99);
378 
379     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
380     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
381     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
382     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
383 
384     // Prepare to detect the states of the autofocus_procedure
385     KFocusProcedureSteps autofocus;
386     QVERIFY(autofocus.starting);
387     QVERIFY(autofocus.aborting);
388     QVERIFY(autofocus.completing);
389 
390     KTELL("Ensure flip is enabled on meridian.\n.Start a standard autofocus.");
391     Ekos::Manager::Instance()->mountModule()->setMeridianFlipValues(true, 0);
392     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->meridianFlipEnabled(), 1000);
393     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->meridianFlipValue() == 0, 1000);
394     QVERIFY(!autofocus.started);
395     QVERIFY(!autofocus.aborted);
396     QVERIFY(!autofocus.complete);
397     KTRY_FOCUS_CLICK(startFocusB);
398     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 5000);
399 
400     KTELL("Wait for the meridian flip to occur.\nCheck procedure aborts while flipping.");
401     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->status() == ISD::Telescope::MOUNT_SLEWING, 15000);
402     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 5000);
403 
404     KTELL("Wait for the flip to end.");
405     QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->mountModule()->status() == ISD::Telescope::MOUNT_TRACKING, 120000);
406 
407     KTELL("Start the procedure again.\nExpect the procedure to succeed.");
408     autofocus.started = false;
409     autofocus.aborted = false;
410     autofocus.complete = false;
411     KTRY_FOCUS_CLICK(startFocusB);
412     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 60000);
413 }
414 
testFocusWhenHFRChecking()415 void TestEkosFocus::testFocusWhenHFRChecking()
416 {
417     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure a fast autofocus.");
418     KTRY_FOCUS_SHOW();
419     KTRY_MOUNT_SYNC(60.0, true, -1);
420     int initialFocusPosition = 35000;
421     KTRY_FOCUS_MOVETO(initialFocusPosition);
422     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 50);
423     KTRY_FOCUS_EXPOSURE(3, 99);
424 
425     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
426     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
427     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
428     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
429 
430     // Prepare to detect the beginning of the autofocus_procedure
431     KFocusProcedureSteps autofocus;
432     QVERIFY(autofocus.starting);
433     QVERIFY(autofocus.aborting);
434     QVERIFY(autofocus.completing);
435 
436     KTELL("Run a standard autofocus.\nRun a HFR check.\nExpect no effect on the procedure.");
437     QVERIFY(!autofocus.started);
438     QVERIFY(!autofocus.aborted);
439     QVERIFY(!autofocus.complete);
440     KTRY_FOCUS_CLICK(startFocusB);
441     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 10000);
442 
443     KTELL("Wait a little, run a first HFR check while the procedure runs.");
444     QTest::qWait(3000);
445     Ekos::Manager::Instance()->focusModule()->checkFocus(0.1);
446 
447     KTELL("Expect procedure to succeed nonetheless.");
448     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 60000);
449 
450     KTELL("Run a second HFR check that would start an autofocus.");
451     autofocus.complete = false;
452     Ekos::Manager::Instance()->focusModule()->checkFocus(0.1);
453 
454     KTELL("Expect procedure to start properly.\nAbort the procedure manually.\nRun a third HFR check.");
455     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 10000);
456     KTRY_FOCUS_CLICK(stopFocusB);
457     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 10000);
458 
459     KTELL("Expect autofocus to start properly.\nChange settings so that the procedure fails now.\nExpect a failure.");
460     autofocus.aborted = autofocus.complete = false;
461     Ekos::Manager::Instance()->focusModule()->checkFocus(0.1);
462     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 10000);
463     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 0.1, 0.1);
464     KTRY_FOCUS_EXPOSURE(0.1, 1);
465     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 90000);
466     KTRY_FOCUS_CHECK_POSITION_WITH_TIMEOUT(initialFocusPosition, 5000);
467 
468     KTELL("Run a fourth HFR check.\nExpect autofocus to complete.");
469     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 50);
470     KTRY_FOCUS_EXPOSURE(3, 99);
471     autofocus.aborted = autofocus.complete = false;
472     Ekos::Manager::Instance()->focusModule()->checkFocus(0.1);
473     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 10000);
474     QTRY_VERIFY_WITH_TIMEOUT(autofocus.complete, 60000);
475 }
476 
testFocusFailure()477 void TestEkosFocus::testFocusFailure()
478 {
479     KTELL("Sync high on meridian to avoid jitter in CCD Simulator");
480     KTRY_FOCUS_SHOW();
481     KTRY_MOUNT_SYNC(60.0, true, -1);
482 
483     KTELL("Configure an autofocus that cannot see any star, so that the initial setup fails.");
484     KTRY_FOCUS_MOVETO(10000);
485     KTRY_FOCUS_CONFIGURE("SEP", "Polynomial", 0.0, 1.0, 0.1);
486     KTRY_FOCUS_EXPOSURE(0.01, 1);
487 
488     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
489     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
490     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
491     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
492 
493     // Prepare to detect the beginning of the autofocus_procedure
494     KFocusProcedureSteps autofocus;
495     QVERIFY(autofocus.starting);
496     QVERIFY(autofocus.aborting);
497     QVERIFY(autofocus.completing);
498 
499     KTELL("Run the autofocus, wait for the completion signal.\nExpect no further autofocus started as we are not running a sequence.");
500     QVERIFY(!autofocus.started);
501     QVERIFY(!autofocus.aborted);
502     QVERIFY(!autofocus.complete);
503     KTRY_FOCUS_CLICK(startFocusB);
504     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
505     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 30000);
506     QVERIFY(!autofocus.started);
507 
508     QSKIP("Skipping abort test for device limits, focus algorithms are too sensitive to CCD Sim noise.");
509 
510     KTELL("Configure an autofocus that can see stars but is too far off and cannot achieve focus, so that the procedure fails.");
511     KTRY_FOCUS_MOVETO(25000);
512     QWARN("Iterative and Polynomial are too easily successful for this test.");
513     KTRY_FOCUS_CONFIGURE("SEP", "Linear", 0.0, 100.0, 1.0);
514     KTRY_FOCUS_EXPOSURE(5, 99);
515     KTRY_FOCUS_GADGET(QDoubleSpinBox, maxTravelIN);
516     maxTravelIN->setValue(2000);
517 
518     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
519     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
520     autofocus.started = false;
521     autofocus.aborted = false;
522     autofocus.complete = false;
523 
524     KTELL("Run the autofocus, wait for the completion signal.\nNo further autofocus started as we are not running a sequence.");
525     QVERIFY(!autofocus.started);
526     QVERIFY(!autofocus.aborted);
527     QVERIFY(!autofocus.complete);
528     KTRY_FOCUS_CLICK(startFocusB);
529     QTRY_VERIFY_WITH_TIMEOUT(autofocus.started, 500);
530     QTRY_VERIFY_WITH_TIMEOUT(autofocus.aborted, 240000);
531     QVERIFY(!autofocus.started);
532 }
533 
testFocusOptions()534 void TestEkosFocus::testFocusOptions()
535 {
536     KTELL("Sync high on meridian to avoid jitter in CCD Simulator.\nConfigure a standard autofocus.");
537     KTRY_FOCUS_SHOW();
538     KTRY_MOUNT_SYNC(60.0, true, -1);
539     KTRY_FOCUS_MOVETO(40000);
540     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3);
541     KTRY_FOCUS_EXPOSURE(1, 99);
542 
543     // Prepare to detect a new HFR
544     KFocusProcedureSteps autofocus;
545     QVERIFY(autofocus.quantifying);
546 
547     KTRY_FOCUS_GADGET(QPushButton, captureB);
548 
549     KTELL("Validate filter to apply to frame after capture.");
550     // This option is tricky: it follows the FITSViewer::filterTypes filter list, but
551     // is used as a list of filter that may be applied in the FITS view.
552     // In the Focus view, it also requires the "--" item as no-op filter, present in the
553     // combobox stating which filter to apply to frames before analysing them.
554     {
555         // Validate the default Ekos option is recognised as a filter
556         int const fe = Options::focusEffect();
557         QVERIFY(0 <= fe && fe < FITSViewer::filterTypes.count() + 1);
558         QWARN(qPrintable(QString("Default filtering option is %1/%2").arg(fe).arg(FITSViewer::filterTypes.value(fe - 1, "--"))));
559 
560         // Validate the UI changes the Ekos option
561         for (int i = 0; i < FITSViewer::filterTypes.count(); i++)
562         {
563             QTRY_VERIFY_WITH_TIMEOUT(captureB->isEnabled(), 5000);
564 
565             QString const & filterType = FITSViewer::filterTypes.value(i, "Unknown image filter");
566             QWARN(qPrintable(QString("Testing filtering option %1/%2").arg(i + 1).arg(filterType)));
567 
568             // Set filter to apply in the UI, verify impact on Ekos option
569             Options::setFocusEffect(fe);
570             KTRY_FOCUS_COMBO_SET(filterCombo, filterType);
571             QTRY_COMPARE_WITH_TIMEOUT(Options::focusEffect(), (uint) i + 1, 1000);
572 
573             // Set filter to apply with the d-bus entry point, verify impact on Ekos option
574             Options::setFocusEffect(fe);
575             Ekos::Manager::Instance()->focusModule()->setImageFilter(filterType);
576             QTRY_COMPARE_WITH_TIMEOUT(Options::focusEffect(), (uint) i + 1, 1000);
577 
578             // Run a capture with detection for coverage
579             autofocus.hfr = -2;
580             KTRY_FOCUS_CLICK(captureB);
581             QTRY_VERIFY_WITH_TIMEOUT(-1 <= autofocus.hfr, 5000);
582         }
583 
584         // Set no-op filter to apply in the UI, verify impact on Ekos option
585         Options::setFocusEffect(0);
586         KTRY_FOCUS_COMBO_SET(filterCombo, "--");
587         QTRY_COMPARE_WITH_TIMEOUT(Options::focusEffect(), (uint) 0, 1000);
588 
589         // Set no-op filter to apply with the d-bus entry point, verify impact on Ekos option
590         Options::setFocusEffect(0);
591         Ekos::Manager::Instance()->focusModule()->setImageFilter("--");
592         QTRY_COMPARE_WITH_TIMEOUT(Options::focusEffect(), (uint) 0, 1000);
593 
594         // Restore the original Ekos option
595         KTRY_FOCUS_COMBO_SET(filterCombo, FITSViewer::filterTypes.value(fe - 1, "--"));
596         QTRY_COMPARE_WITH_TIMEOUT(Options::focusEffect(), (uint) fe, 1000);
597     }
598 }
599 
testStarDetection_data()600 void TestEkosFocus::testStarDetection_data()
601 {
602 #if QT_VERSION < 0x050900
603     QSKIP("Skipping fixture-based test on old QT version.");
604 #else
605     QTest::addColumn<QString>("NAME");
606     QTest::addColumn<QString>("RA");
607     QTest::addColumn<QString>("DEC");
608 
609     // Altitude computation taken from SchedulerJob::findAltitude
610     GeoLocation * const geo = KStarsData::Instance()->geo();
611     KStarsDateTime const now(KStarsData::Instance()->lt());
612     KSNumbers const numbers(now.djd());
613     CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(now).gst());
614 
615     std::list<char const *> Objects = { "Polaris", "Mizar", "M 51", "M 13", "M 47", "Vega", "NGC 2238", "M 81" };
616     size_t count = 0;
617 
618     foreach (char const *name, Objects)
619     {
620         SkyObject const * const so = KStars::Instance()->data()->objectNamed(name);
621         if (so != nullptr)
622         {
623             SkyObject o(*so);
624             o.updateCoordsNow(&numbers);
625             o.EquatorialToHorizontal(&LST, geo->lat());
626             if (10.0 < o.alt().Degrees())
627             {
628                 QTest::addRow("%s", name)
629                         << name
630                         << o.ra().toHMSString()
631                         << o.dec().toDMSString();
632                 count++;
633             }
634             else QWARN(QString("Fixture '%1' altitude is '%2' degrees, discarding.").arg(name).arg(so->alt().Degrees()).toStdString().c_str());
635         }
636     }
637 
638     if (!count)
639         QSKIP("No usable fixture objects, bypassing test.");
640 #endif
641 }
642 
testStarDetection()643 void TestEkosFocus::testStarDetection()
644 {
645 
646 #if QT_VERSION < 0x050900
647     QSKIP("Skipping fixture-based test on old QT version.");
648 #else
649     Ekos::Manager * const ekos = Ekos::Manager::Instance();
650     QVERIFY(ekos);
651 
652     QFETCH(QString, NAME);
653     QFETCH(QString, RA);
654     QFETCH(QString, DEC);
655 
656     KTELL(QString(NAME+"\nSync to %1/%2 to make the mount teleport to the object.").arg(qPrintable(RA)).arg(qPrintable(DEC)));
657     QTRY_VERIFY_WITH_TIMEOUT(ekos->mountModule() != nullptr, 5000);
658     ekos->mountModule()->setMeridianFlipValues(false, 0);
659     QVERIFY(ekos->mountModule()->sync(RA, DEC));
660     ekos->mountModule()->setTrackEnabled(true);
661 
662     KTELL(NAME+"\nWait for Focus to come up\nSwitch to Focus tab.");
663     KTRY_FOCUS_SHOW();
664 
665     KTRY_FOCUS_GADGET(QPushButton, startFocusB);
666     KTRY_FOCUS_GADGET(QPushButton, stopFocusB);
667     QTRY_VERIFY_WITH_TIMEOUT(startFocusB->isEnabled(), 1000);
668     QTRY_VERIFY_WITH_TIMEOUT(!stopFocusB->isEnabled(), 1000);
669 
670     KTRY_FOCUS_GADGET(QLineEdit, starsOut);
671 
672     KTELL(NAME+"\nMove focuser to see stars.");
673     KTRY_FOCUS_MOVETO(35000);
674 
675     KTELL(NAME+"\nRun the detection with SEP.");
676     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3.0);
677     KTRY_FOCUS_DETECT(1, 3, 99);
678     QTRY_VERIFY_WITH_TIMEOUT(starsOut->text().toInt() >= 1, 5000);
679 
680     KTELL(NAME+"\nRun the detection with Centroid.");
681     KTRY_FOCUS_CONFIGURE("Centroid", "Iterative", 0.0, 100.0, 3.0);
682     KTRY_FOCUS_DETECT(1, 3, 99);
683     QTRY_VERIFY_WITH_TIMEOUT(starsOut->text().toInt() >= 1, 5000);
684 
685     KTELL(NAME+"\nRun the detection with Threshold (no full-field).");
686     KTRY_FOCUS_CONFIGURE("Threshold", "Iterative", 0.0, 0.0, 3.0);
687     KTRY_FOCUS_DETECT(1, 3, 99);
688     QTRY_VERIFY_WITH_TIMEOUT(starsOut->text().toInt() >= 1, 5000);
689 
690     KTELL(NAME+"\nRun the detection with Gradient (no full-field).");
691     KTRY_FOCUS_CONFIGURE("Gradient", "Iterative", 0.0, 0.0, 3.0);
692     KTRY_FOCUS_DETECT(1, 3, 99);
693     QTRY_VERIFY_WITH_TIMEOUT(starsOut->text().toInt() >= 1, 5000);
694 
695     KTELL(NAME+"\nRun the detection with SEP (8s capture).");
696     KTRY_FOCUS_CONFIGURE("SEP", "Iterative", 0.0, 100.0, 3.0);
697     KTRY_FOCUS_DETECT(8, 1, 99);
698     QTRY_VERIFY_WITH_TIMEOUT(starsOut->text().toInt() >= 1, 5000);
699 
700     KTELL(NAME+"\nRun the detection with SEP\nFull-field with various values\nHFR averaged on 3 frames.");
701     for (double inner = 0.0; inner < 100.0; inner += 43.0)
702     {
703         for (double outer = 100.0; inner < outer; outer -= 42.0)
704         {
705             KTRY_FOCUS_CONFIGURE("SEP", "Iterative", inner, outer, 3.0);
706             KTRY_FOCUS_DETECT(1, 2, 99);
707         }
708     }
709 
710     KTELL(NAME+"\nRun the detection with Threshold, full-field.");
711     for (double threshold = 80.0; threshold < 99.0; threshold += 13.3)
712     {
713         KTRY_FOCUS_GADGET(QDoubleSpinBox, thresholdSpin);
714         thresholdSpin->setValue(threshold);
715         KTRY_FOCUS_CONFIGURE("Threshold", "Iterative", 0, 0.0, 3.0);
716         KTRY_FOCUS_DETECT(1, 1, 99);
717     }
718 #endif
719 }
720 
721 QTEST_KSTARS_MAIN(TestEkosFocus)
722 
723 #endif // HAVE_INDI
724