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 | |
30 | namespace Ekos |
31 | { |
32 | |
33 | Guide::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 | |
81 | Guide::~Guide() |
82 | { |
83 | delete guider; |
84 | delete calibration; |
85 | delete pmath; |
86 | } |
87 | |
88 | void 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 | |
105 | void Guide::setTelescope(ISD::GDInterface *newTelescope) |
106 | { |
107 | currentTelescope = (ISD::Telescope*) newTelescope; |
108 | |
109 | syncTelescopeInfo(); |
110 | |
111 | } |
112 | |
113 | void 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 | |
129 | void 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 | |
159 | void 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 | |
191 | void 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 | |
221 | void 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 | |
238 | void Guide::setAO(ISD::ST4 *newAO) |
239 | { |
240 | AODriver = newAO; |
241 | guider->set_ao(true); |
242 | } |
243 | |
244 | bool 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 | } |
295 | void 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 | |
391 | void 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 | |
399 | void Guide::clearLog() |
400 | { |
401 | logText.clear(); |
402 | emit newLog(); |
403 | } |
404 | |
405 | void Guide::setDECSwap(bool enable) |
406 | { |
407 | if (ST4Driver == NULL) |
408 | return; |
409 | |
410 | ST4Driver->setDECSwap(enable); |
411 | } |
412 | |
413 | bool 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 | |
424 | bool 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 | |
436 | void 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 | |
446 | double Guide::getReticleAngle() |
447 | { |
448 | return calibration->getReticleAngle(); |
449 | } |
450 | |
451 | void Guide::viewerClosed() |
452 | { |
453 | pmath->set_image(NULL); |
454 | guider->set_image(NULL); |
455 | calibration->set_image(NULL); |
456 | } |
457 | |
458 | void 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 | |
507 | void 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 | |
526 | void 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 | |
543 | void Guide::dither() |
544 | { |
545 | if (guider->is_dithering() == false) |
546 | guider->dither(); |
547 | } |
548 | |
549 | void 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 | |
575 | void Guide::stopGuiding() |
576 | { |
577 | guider->abort(); |
578 | } |
579 | |
580 | void 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 | |