1/* Ekos
2 Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 This application is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8 */
9
10#include "guide.h"
11
12#include <QDateTime>
13
14#include "guide/gmath.h"
15#include "guide/guider.h"
16#include "Options.h"
17
18
19#include <KMessageBox>
20#include <KLed>
21
22#include "indi/driverinfo.h"
23#include "fitsviewer/fitsviewer.h"
24#include "fitsviewer/fitsview.h"
25
26#include "guide/rcalibration.h"
27
28#include <basedevice.h>
29
30namespace Ekos
31{
32
33Guide::Guide() : QWidget()
34{
35 setupUi(this);
36
37 currentCCD = NULL;
38 currentTelescope = NULL;
39 ccd_hor_pixel = ccd_ver_pixel = focal_length = aperture = -1;
40 useGuideHead = false;
41 useDarkFrame = false;
42 rapidGuideReticleSet = false;
43 isSuspended = false;
44 darkExposure = 0;
45 darkImage = NULL;
46 AODriver= NULL;
47 GuideDriver=NULL;
48
49 tabWidget = new QTabWidget(this);
50
51 tabLayout->addWidget(tabWidget);
52
53 guiderStage = CALIBRATION_STAGE;
54
55 pmath = new cgmath();
56
57 connect(pmath, SIGNAL(newAxisDelta(double,double)), this, SIGNAL(newAxisDelta(double,double)));
58 connect(pmath, SIGNAL(newAxisDelta(double,double)), this, SLOT(updateGuideDriver(double,double)));
59
60 calibration = new rcalibration(this);
61 calibration->set_math(pmath);
62
63 guider = new rguider(this);
64 guider->set_math(pmath);
65
66 connect(guider, SIGNAL(ditherToggled(bool)), this, SIGNAL(ditherToggled(bool)));
67 connect(guider, SIGNAL(autoGuidingToggled(bool,bool)), this, SIGNAL(autoGuidingToggled(bool,bool)));
68 connect(guider, SIGNAL(ditherComplete()), this, SIGNAL(ditherComplete()));
69
70 tabWidget->addTab(calibration, calibration->windowTitle());
71 tabWidget->addTab(guider, guider->windowTitle());
72 tabWidget->setTabEnabled(1, false);
73
74 connect(ST4Combo, SIGNAL(currentIndexChanged(int)), this, SLOT(newST4(int)));
75
76 foreach(QString filter, FITSViewer::filterTypes)
77 filterCombo->addItem(filter);
78
79}
80
81Guide::~Guide()
82{
83 delete guider;
84 delete calibration;
85 delete pmath;
86}
87
88void Guide::setCCD(ISD::GDInterface *newCCD)
89{
90 currentCCD = (ISD::CCD *) newCCD;
91
92 guiderCombo->addItem(currentCCD->getDeviceName());
93
94 if (currentCCD->hasGuideHead())
95 addGuideHead(newCCD);
96
97 connect(currentCCD, SIGNAL(FITSViewerClosed()), this, SLOT(viewerClosed()));
98
99 syncCCDInfo();
100
101 //qDebug() << "SetCCD: ccd_pix_w " << ccd_hor_pixel << " - ccd_pix_h " << ccd_ver_pixel << " - focal length " << focal_length << " aperture " << aperture << endl;
102
103}
104
105void Guide::setTelescope(ISD::GDInterface *newTelescope)
106{
107 currentTelescope = (ISD::Telescope*) newTelescope;
108
109 syncTelescopeInfo();
110
111}
112
113void Guide::addGuideHead(ISD::GDInterface *ccd)
114{
115 if (currentCCD == NULL)
116 currentCCD = (ISD::CCD *) ccd;
117
118 // Let's just make sure
119 if (currentCCD->hasGuideHead())
120 {
121 guiderCombo->clear();
122 guiderCombo->addItem(currentCCD->getDeviceName() + QString(" Guider"));
123 useGuideHead = true;
124 syncCCDInfo();
125 }
126
127}
128
129void Guide::syncCCDInfo()
130{
131 INumberVectorProperty * nvp = NULL;
132
133 if (currentCCD == NULL)
134 return;
135
136 if (useGuideHead)
137 nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO");
138 else
139 nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO");
140
141 if (nvp)
142 {
143 INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X");
144 if (np)
145 ccd_hor_pixel = np->value;
146
147 np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
148 if (np)
149 ccd_ver_pixel = np->value;
150
151 np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
152 if (np)
153 ccd_ver_pixel = np->value;
154 }
155
156 updateGuideParams();
157}
158
159void Guide::syncTelescopeInfo()
160{
161 INumberVectorProperty * nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO");
162
163 if (nvp)
164 {
165 INumber *np = IUFindNumber(nvp, "GUIDER_APERTURE");
166
167 if (np && np->value != 0)
168 aperture = np->value;
169 else
170 {
171 np = IUFindNumber(nvp, "TELESCOPE_APERTURE");
172 if (np)
173 aperture = np->value;
174 }
175
176 np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH");
177 if (np && np->value != 0)
178 focal_length = np->value;
179 else
180 {
181 np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH");
182 if (np)
183 focal_length = np->value;
184 }
185 }
186
187 updateGuideParams();
188
189}
190
191void Guide::updateGuideParams()
192{
193 if (ccd_hor_pixel != -1 && ccd_ver_pixel != -1 && focal_length != -1 && aperture != -1)
194 {
195 pmath->set_guider_params(ccd_hor_pixel, ccd_ver_pixel, aperture, focal_length);
196 int x,y,w,h;
197
198 if (currentCCD->hasGuideHead() == false)
199 useGuideHead = false;
200
201 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
202
203 if (targetChip == NULL)
204 {
205 appendLogText(i18n("Connection to the guide CCD is lost."));
206 return;
207 }
208
209 emit guideChipUpdated(targetChip);
210
211 guider->set_target_chip(targetChip);
212
213 if (targetChip->getFrame(&x,&y,&w,&h))
214 pmath->set_video_params(w, h);
215
216 guider->fill_interface();
217
218 }
219}
220
221void Guide::addST4(ISD::ST4 *newST4)
222{
223 foreach(ISD::ST4 *guidePort, ST4List)
224 {
225 if (guidePort == newST4)
226 return;
227 }
228
229 ST4Combo->addItem(newST4->getDeviceName());
230 ST4List.append(newST4);
231
232 ST4Driver = ST4List.at(0);
233 GuideDriver = ST4Driver;
234 ST4Combo->setCurrentIndex(0);
235
236}
237
238void Guide::setAO(ISD::ST4 *newAO)
239{
240 AODriver = newAO;
241 guider->set_ao(true);
242}
243
244bool Guide::capture()
245{
246 if (currentCCD == NULL)
247 return false;
248
249 double seqExpose = exposureSpin->value();
250
251 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
252
253 CCDFrameType ccdFrame = FRAME_LIGHT;
254
255 if (currentCCD->isConnected() == false)
256 {
257 appendLogText(i18n("Error: Lost connection to CCD."));
258 return false;
259 }
260
261 // Exposure changed, take a new dark
262 if (useDarkFrame && darkExposure != seqExpose)
263 {
264 darkExposure = seqExpose;
265 targetChip->setFrameType(FRAME_DARK);
266
267 KMessageBox::information(NULL, i18n("If the guider camera if not equipped with a shutter, cover the telescope or camera in order to take a dark exposure."), i18n("Dark Exposure"), "dark_exposure_dialog_notification");
268
269 connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
270 targetChip->capture(seqExpose);
271
272 appendLogText(i18n("Taking a dark frame. "));
273
274 return true;
275 }
276
277 targetChip->setCaptureMode(FITS_GUIDE);
278 targetChip->setFrameType(ccdFrame);
279
280 if (guider->is_guiding())
281 {
282 if (guider->isRapidGuide() == false)
283 connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
284
285 targetChip->capture(seqExpose);
286 return true;
287 }
288
289 connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
290 targetChip->capture(seqExpose);
291
292 return true;
293
294}
295void Guide::newFITS(IBLOB *bp)
296{
297 INDI_UNUSED(bp);
298
299 FITSViewer *fv = currentCCD->getViewer();
300
301 disconnect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
302
303 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
304
305 // Received a dark calibration frame
306 if (targetChip->getFrameType() == FRAME_DARK)
307 {
308 FITSView *targetImage = targetChip->getImage(FITS_CALIBRATE);
309 if (targetImage)
310 {
311 darkImage = targetImage->getImageData();
312 capture();
313 }
314 else
315 appendLogText(i18n("Dark frame processing failed."));
316
317 return;
318 }
319
320 FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
321
322 if (targetImage == NULL)
323 {
324 pmath->set_image(NULL);
325 guider->set_image(NULL);
326 calibration->set_image(NULL);
327 return;
328 }
329
330 FITSImage *image_data = targetImage->getImageData();
331
332 if (image_data == NULL)
333 return;
334
335 if (darkImage)
336 image_data->subtract(darkImage->getImageBuffer());
337
338 if (filterCombo->currentIndex() != -1)
339 {
340 image_data->applyFilter((FITSScale) filterCombo->currentIndex());
341 targetImage->rescale(ZOOM_KEEP_LEVEL);
342 targetImage->updateFrame();
343 }
344
345 pmath->set_image(targetImage);
346 guider->set_image(targetImage);
347 calibration->set_image(targetImage);
348
349 fv->show();
350
351 if (isSuspended)
352 {
353 //capture();
354 return;
355 }
356
357 if (guider->is_dithering())
358 {
359 pmath->do_processing();
360 if (guider->dither() == false)
361 {
362 appendLogText(i18n("Dithering failed. Autoguiding aborted."));
363 guider->abort();
364 emit ditherFailed();
365 }
366 }
367 else if (guider->is_guiding())
368 {
369 guider->guide();
370
371 if (guider->is_guiding())
372 capture();
373 }
374 else if (calibration->is_calibrating())
375 {
376 GuideDriver = ST4Driver;
377 pmath->do_processing();
378 calibration->process_calibration();
379
380 if (calibration->is_finished())
381 {
382 guider->set_ready(true);
383 tabWidget->setTabEnabled(1, true);
384 emit guideReady();
385 }
386 }
387
388}
389
390
391void Guide::appendLogText(const QString &text)
392{
393
394 logText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text));
395
396 emit newLog();
397}
398
399void Guide::clearLog()
400{
401 logText.clear();
402 emit newLog();
403}
404
405void Guide::setDECSwap(bool enable)
406{
407 if (ST4Driver == NULL)
408 return;
409
410 ST4Driver->setDECSwap(enable);
411}
412
413bool Guide::do_pulse( GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs )
414{
415 if (GuideDriver == NULL || (ra_dir == NO_DIR && dec_dir == NO_DIR))
416 return false;
417
418 if (calibration->is_calibrating())
419 QTimer::singleShot( (ra_msecs > dec_msecs ? ra_msecs : dec_msecs) + 100, this, SLOT(capture()));
420
421 return GuideDriver->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
422}
423
424bool Guide::do_pulse( GuideDirection dir, int msecs )
425{
426 if (GuideDriver == NULL || dir==NO_DIR)
427 return false;
428
429 if (calibration->is_calibrating())
430 QTimer::singleShot(msecs+100, this, SLOT(capture()));
431
432 return GuideDriver->doPulse(dir, msecs);
433
434}
435
436void Guide::newST4(int index)
437{
438 if (ST4List.empty() || index >= ST4List.count())
439 return;
440
441 ST4Driver = ST4List.at(index);
442 GuideDriver = ST4Driver;
443
444}
445
446double Guide::getReticleAngle()
447{
448 return calibration->getReticleAngle();
449}
450
451void Guide::viewerClosed()
452{
453 pmath->set_image(NULL);
454 guider->set_image(NULL);
455 calibration->set_image(NULL);
456}
457
458void Guide::processRapidStarData(ISD::CCDChip *targetChip, double dx, double dy, double fit)
459{
460 // Check if guide star is lost
461 if (dx == -1 && dy == -1 && fit == -1)
462 {
463 KMessageBox::error(NULL, i18n("Lost track of the guide star. Rapid guide aborted."));
464 guider->abort();
465 return;
466 }
467
468 FITSView *targetImage = targetChip->getImage(FITS_GUIDE);
469
470 if (targetImage == NULL)
471 {
472 pmath->set_image(NULL);
473 guider->set_image(NULL);
474 calibration->set_image(NULL);
475 }
476
477 if (rapidGuideReticleSet == false)
478 {
479 // Let's set reticle parameter on first capture to those of the star, then we check if there
480 // is any deviation
481 double x,y,angle;
482 pmath->get_reticle_params(&x, &y, &angle);
483 pmath->set_reticle_params(dx, dy, angle);
484 rapidGuideReticleSet = true;
485 }
486
487 pmath->setRapidStarData(dx, dy);
488
489 if (guider->is_dithering())
490 {
491 pmath->do_processing();
492 if (guider->dither() == false)
493 {
494 appendLogText(i18n("Dithering failed. Autoguiding aborted."));
495 guider->abort();
496 emit ditherFailed();
497 }
498 }
499 else
500 {
501 guider->guide();
502 capture();
503 }
504
505}
506
507void Guide::startRapidGuide()
508{
509 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
510
511 if (currentCCD->setRapidGuide(targetChip, true) == false)
512 {
513 appendLogText(i18n("The CCD does not support Rapid Guiding. Aborting..."));
514 guider->abort();
515 return;
516 }
517
518 rapidGuideReticleSet = false;
519
520 pmath->setRapidGuide(true);
521 currentCCD->configureRapidGuide(targetChip, true);
522 connect(currentCCD, SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double)), this, SLOT(processRapidStarData(ISD::CCDChip*,double,double,double)));
523
524}
525
526void Guide::stopRapidGuide()
527{
528 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
529
530 pmath->setRapidGuide(false);
531
532 rapidGuideReticleSet = false;
533
534 currentCCD->disconnect(SIGNAL(newGuideStarData(ISD::CCDChip*,double,double,double)));
535
536 currentCCD->configureRapidGuide(targetChip, false, false, false);
537
538 currentCCD->setRapidGuide(targetChip, false);
539
540}
541
542
543void Guide::dither()
544{
545 if (guider->is_dithering() == false)
546 guider->dither();
547}
548
549void Guide::updateGuideDriver(double delta_ra, double delta_dec)
550{
551 if (guider->is_guiding() == false)
552 return;
553
554 if (guider->is_dithering())
555 {
556 GuideDriver = ST4Driver;
557 return;
558 }
559
560 // Guide via AO only if guiding deviation is below AO limit
561 if (AODriver != NULL && (fabs(delta_ra) < guider->get_ao_limit()) && (fabs(delta_dec) < guider->get_ao_limit()))
562 {
563 if (AODriver != GuideDriver)
564 appendLogText(i18n("Using %1 to correct for guiding errors.", AODriver->getDeviceName()));
565 GuideDriver = AODriver;
566 return;
567 }
568
569 if (GuideDriver != ST4Driver)
570 appendLogText(i18n("Using %1 to correct for guiding errors.", ST4Driver->getDeviceName()));
571
572 GuideDriver = ST4Driver;
573}
574
575void Guide::stopGuiding()
576{
577 guider->abort();
578}
579
580void Guide::setSuspended(bool enable)
581{
582 if (enable == isSuspended)
583 return;
584
585 isSuspended = enable;
586
587 if (isSuspended == false)
588 capture();
589
590 if (isSuspended)
591 appendLogText(i18n("Guiding suspended."));
592 else
593 appendLogText(i18n("Guiding resumed."));
594}
595
596
597}
598
599#include "guide.moc"
600