1 /*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "indiccd.h"
8
9 #include "config-kstars.h"
10
11 #include "indi_debug.h"
12
13 #include "clientmanager.h"
14 #include "driverinfo.h"
15 #include "guimanager.h"
16 #include "kspaths.h"
17 #include "kstars.h"
18 #include "kstarsdata.h"
19 #include "Options.h"
20 #include "streamwg.h"
21 //#include "ekos/manager.h"
22 #ifdef HAVE_CFITSIO
23 #include "fitsviewer/fitsdata.h"
24 #endif
25
26 #include <KNotifications/KNotification>
27 #include "auxiliary/ksmessagebox.h"
28 #include "ksnotification.h"
29 #include <QImageReader>
30 #include <QFileInfo>
31 #include <QStatusBar>
32 #include <QtConcurrent>
33
34 #include <basedevice.h>
35
36 const QStringList RAWFormats = { "cr2", "cr3", "crw", "nef", "raf", "dng", "arw", "orf" };
37
getFITSModeStringString(FITSMode mode)38 const QString &getFITSModeStringString(FITSMode mode)
39 {
40 return FITSModes[mode];
41 }
42
43 namespace ISD
44 {
CCDChip(ISD::CCD * ccd,ChipType cType)45 CCDChip::CCDChip(ISD::CCD *ccd, ChipType cType)
46 {
47 baseDevice = ccd->getBaseDevice();
48 clientManager = ccd->getDriverInfo()->getClientManager();
49 parentCCD = ccd;
50 type = cType;
51 }
52
getImageView(FITSMode imageType)53 FITSView *CCDChip::getImageView(FITSMode imageType)
54 {
55 switch (imageType)
56 {
57 case FITS_NORMAL:
58 return normalImage;
59
60 case FITS_FOCUS:
61 return focusImage;
62
63 case FITS_GUIDE:
64 return guideImage;
65
66 case FITS_CALIBRATE:
67 return calibrationImage;
68
69 case FITS_ALIGN:
70 return alignImage;
71 }
72
73 return nullptr;
74 }
75
setImageView(FITSView * image,FITSMode imageType)76 void CCDChip::setImageView(FITSView *image, FITSMode imageType)
77 {
78 switch (imageType)
79 {
80 case FITS_NORMAL:
81 normalImage = image;
82 break;
83
84 case FITS_FOCUS:
85 focusImage = image;
86 break;
87
88 case FITS_GUIDE:
89 guideImage = image;
90 break;
91
92 case FITS_CALIBRATE:
93 calibrationImage = image;
94 break;
95
96 case FITS_ALIGN:
97 alignImage = image;
98 break;
99 }
100
101 if (image)
102 imageData = image->imageData();
103 }
104
getFrameMinMax(int * minX,int * maxX,int * minY,int * maxY,int * minW,int * maxW,int * minH,int * maxH)105 bool CCDChip::getFrameMinMax(int *minX, int *maxX, int *minY, int *maxY, int *minW, int *maxW, int *minH, int *maxH)
106 {
107 INumberVectorProperty *frameProp = nullptr;
108
109 switch (type)
110 {
111 case PRIMARY_CCD:
112 frameProp = baseDevice->getNumber("CCD_FRAME");
113 break;
114
115 case GUIDE_CCD:
116 frameProp = baseDevice->getNumber("GUIDER_FRAME");
117 break;
118 }
119
120 if (frameProp == nullptr)
121 return false;
122
123 INumber *arg = IUFindNumber(frameProp, "X");
124
125 if (arg == nullptr)
126 return false;
127
128 if (minX)
129 *minX = arg->min;
130 if (maxX)
131 *maxX = arg->max;
132
133 arg = IUFindNumber(frameProp, "Y");
134
135 if (arg == nullptr)
136 return false;
137
138 if (minY)
139 *minY = arg->min;
140 if (maxY)
141 *maxY = arg->max;
142
143 arg = IUFindNumber(frameProp, "WIDTH");
144
145 if (arg == nullptr)
146 return false;
147
148 if (minW)
149 *minW = arg->min;
150 if (maxW)
151 *maxW = arg->max;
152
153 arg = IUFindNumber(frameProp, "HEIGHT");
154
155 if (arg == nullptr)
156 return false;
157
158 if (minH)
159 *minH = arg->min;
160 if (maxH)
161 *maxH = arg->max;
162
163 return true;
164 }
165
setImageInfo(uint16_t width,uint16_t height,double pixelX,double pixelY,uint8_t bitdepth)166 bool CCDChip::setImageInfo(uint16_t width, uint16_t height, double pixelX, double pixelY, uint8_t bitdepth)
167 {
168 INumberVectorProperty *ccdInfoProp = nullptr;
169
170 switch (type)
171 {
172 case PRIMARY_CCD:
173 ccdInfoProp = baseDevice->getNumber("CCD_INFO");
174 break;
175
176 case GUIDE_CCD:
177 ccdInfoProp = baseDevice->getNumber("GUIDER_INFO");
178 break;
179 }
180
181 if (ccdInfoProp == nullptr)
182 return false;
183
184 ccdInfoProp->np[0].value = width;
185 ccdInfoProp->np[1].value = height;
186 ccdInfoProp->np[2].value = std::hypotf(pixelX, pixelY);
187 ccdInfoProp->np[3].value = pixelX;
188 ccdInfoProp->np[4].value = pixelY;
189 ccdInfoProp->np[5].value = bitdepth;
190
191 clientManager->sendNewNumber(ccdInfoProp);
192
193 return true;
194 }
195
getImageInfo(uint16_t & width,uint16_t & height,double & pixelX,double & pixelY,uint8_t & bitdepth)196 bool CCDChip::getImageInfo(uint16_t &width, uint16_t &height, double &pixelX, double &pixelY, uint8_t &bitdepth)
197 {
198 INumberVectorProperty *ccdInfoProp = nullptr;
199
200 switch (type)
201 {
202 case PRIMARY_CCD:
203 ccdInfoProp = baseDevice->getNumber("CCD_INFO");
204 break;
205
206 case GUIDE_CCD:
207 ccdInfoProp = baseDevice->getNumber("GUIDER_INFO");
208 break;
209 }
210
211 if (ccdInfoProp == nullptr)
212 return false;
213
214 width = ccdInfoProp->np[0].value;
215 height = ccdInfoProp->np[1].value;
216 pixelX = ccdInfoProp->np[2].value;
217 pixelY = ccdInfoProp->np[3].value;
218 bitdepth = ccdInfoProp->np[5].value;
219
220 return true;
221 }
222
getBayerInfo(uint16_t & offsetX,uint16_t & offsetY,QString & pattern)223 bool CCDChip::getBayerInfo(uint16_t &offsetX, uint16_t &offsetY, QString &pattern)
224 {
225 ITextVectorProperty * bayerTP = baseDevice->getText("CCD_CFA");
226 if (!bayerTP)
227 return false;
228
229 offsetX = QString(bayerTP->tp[0].text).toInt();
230 offsetY = QString(bayerTP->tp[1].text).toInt();
231 pattern = QString(bayerTP->tp[2].text);
232
233 return true;
234 }
235
getFrame(int * x,int * y,int * w,int * h)236 bool CCDChip::getFrame(int *x, int *y, int *w, int *h)
237 {
238 INumberVectorProperty *frameProp = nullptr;
239
240 switch (type)
241 {
242 case PRIMARY_CCD:
243 frameProp = baseDevice->getNumber("CCD_FRAME");
244 break;
245
246 case GUIDE_CCD:
247 frameProp = baseDevice->getNumber("GUIDER_FRAME");
248 break;
249 }
250
251 if (frameProp == nullptr)
252 return false;
253
254 INumber *arg = IUFindNumber(frameProp, "X");
255
256 if (arg == nullptr)
257 return false;
258
259 *x = arg->value;
260
261 arg = IUFindNumber(frameProp, "Y");
262 if (arg == nullptr)
263 return false;
264
265 *y = arg->value;
266
267 arg = IUFindNumber(frameProp, "WIDTH");
268 if (arg == nullptr)
269 return false;
270
271 *w = arg->value;
272
273 arg = IUFindNumber(frameProp, "HEIGHT");
274 if (arg == nullptr)
275 return false;
276
277 *h = arg->value;
278
279 return true;
280 }
281
resetFrame()282 bool CCDChip::resetFrame()
283 {
284 INumberVectorProperty *frameProp = nullptr;
285
286 switch (type)
287 {
288 case PRIMARY_CCD:
289 frameProp = baseDevice->getNumber("CCD_FRAME");
290 break;
291
292 case GUIDE_CCD:
293 frameProp = baseDevice->getNumber("GUIDER_FRAME");
294 break;
295 }
296
297 if (frameProp == nullptr)
298 return false;
299
300 INumber *xarg = IUFindNumber(frameProp, "X");
301 INumber *yarg = IUFindNumber(frameProp, "Y");
302 INumber *warg = IUFindNumber(frameProp, "WIDTH");
303 INumber *harg = IUFindNumber(frameProp, "HEIGHT");
304
305 if (xarg && yarg && warg && harg)
306 {
307 if (!std::fabs(xarg->value - xarg->min) &&
308 !std::fabs(yarg->value - yarg->min) &&
309 !std::fabs(warg->value - warg->max) &&
310 !std::fabs(harg->value - harg->max))
311 return false;
312
313 xarg->value = xarg->min;
314 yarg->value = yarg->min;
315 warg->value = warg->max;
316 harg->value = harg->max;
317
318 clientManager->sendNewNumber(frameProp);
319 return true;
320 }
321
322 return false;
323 }
324
setFrame(int x,int y,int w,int h,bool force)325 bool CCDChip::setFrame(int x, int y, int w, int h, bool force)
326 {
327 INumberVectorProperty *frameProp = nullptr;
328
329 switch (type)
330 {
331 case PRIMARY_CCD:
332 frameProp = baseDevice->getNumber("CCD_FRAME");
333 break;
334
335 case GUIDE_CCD:
336 frameProp = baseDevice->getNumber("GUIDER_FRAME");
337 break;
338 }
339
340 if (frameProp == nullptr)
341 return false;
342
343 INumber *xarg = IUFindNumber(frameProp, "X");
344 INumber *yarg = IUFindNumber(frameProp, "Y");
345 INumber *warg = IUFindNumber(frameProp, "WIDTH");
346 INumber *harg = IUFindNumber(frameProp, "HEIGHT");
347
348 if (xarg && yarg && warg && harg)
349 {
350 if (!force &&
351 !std::fabs(xarg->value - x) &&
352 !std::fabs(yarg->value - y) &&
353 !std::fabs(warg->value - w) &&
354 !std::fabs(harg->value - h))
355 return true;
356
357 xarg->value = x;
358 yarg->value = y;
359 warg->value = w;
360 harg->value = h;
361
362 clientManager->sendNewNumber(frameProp);
363 return true;
364 }
365
366 return false;
367 }
368
capture(double exposure)369 bool CCDChip::capture(double exposure)
370 {
371 //qCDebug(KSTARS_INDI) << "IndiCCD: capture()" << (type==PRIMARY_CCD?"CCD":"Guide");
372 INumberVectorProperty *expProp = nullptr;
373
374 switch (type)
375 {
376 case PRIMARY_CCD:
377 expProp = baseDevice->getNumber("CCD_EXPOSURE");
378 break;
379
380 case GUIDE_CCD:
381 expProp = baseDevice->getNumber("GUIDER_EXPOSURE");
382 break;
383 }
384
385 if (expProp == nullptr)
386 return false;
387
388 // If we have exposure presets, let's limit the exposure value
389 // to the preset values if it falls within their range of max/min
390 if (Options::forceDSLRPresets())
391 {
392 QMap<QString, double> exposurePresets = parentCCD->getExposurePresets();
393 if (!exposurePresets.isEmpty())
394 {
395 double min, max;
396 QPair<double, double> minmax = parentCCD->getExposurePresetsMinMax();
397 min = minmax.first;
398 max = minmax.second;
399 if (exposure > min && exposure < max)
400 {
401 double diff = 1e6;
402 double closestMatch = exposure;
403 for (const auto &oneValue : exposurePresets.values())
404 {
405 double newDiff = std::fabs(exposure - oneValue);
406 if (newDiff < diff)
407 {
408 closestMatch = oneValue;
409 diff = newDiff;
410 }
411 }
412
413 qCDebug(KSTARS_INDI) << "Requested exposure" << exposure << "closes match is" << closestMatch;
414 exposure = closestMatch;
415 }
416 }
417 }
418
419 // clone the INumberVectorProperty, to avoid modifications to the same
420 // property from two threads
421 INumber n;
422 strcpy(n.name, expProp->np[0].name);
423 n.value = exposure;
424
425 std::unique_ptr<INumberVectorProperty> newExpProp(new INumberVectorProperty());
426 strncpy(newExpProp->device, expProp->device, MAXINDIDEVICE);
427 strncpy(newExpProp->name, expProp->name, MAXINDINAME);
428 strncpy(newExpProp->label, expProp->label, MAXINDILABEL);
429 newExpProp->np = &n;
430 newExpProp->nnp = 1;
431
432 clientManager->sendNewNumber(newExpProp.get());
433
434 return true;
435 }
436
abortExposure()437 bool CCDChip::abortExposure()
438 {
439 ISwitchVectorProperty *abortProp = nullptr;
440
441 switch (type)
442 {
443 case PRIMARY_CCD:
444 abortProp = baseDevice->getSwitch("CCD_ABORT_EXPOSURE");
445 break;
446
447 case GUIDE_CCD:
448 abortProp = baseDevice->getSwitch("GUIDER_ABORT_EXPOSURE");
449 break;
450 }
451
452 if (abortProp == nullptr)
453 return false;
454
455 ISwitch *abort = IUFindSwitch(abortProp, "ABORT");
456
457 if (abort == nullptr)
458 return false;
459
460 abort->s = ISS_ON;
461
462 clientManager->sendNewSwitch(abortProp);
463
464 return true;
465 }
canBin() const466 bool CCDChip::canBin() const
467 {
468 return CanBin;
469 }
470
setCanBin(bool value)471 void CCDChip::setCanBin(bool value)
472 {
473 CanBin = value;
474 }
canSubframe() const475 bool CCDChip::canSubframe() const
476 {
477 return CanSubframe;
478 }
479
setCanSubframe(bool value)480 void CCDChip::setCanSubframe(bool value)
481 {
482 CanSubframe = value;
483 }
canAbort() const484 bool CCDChip::canAbort() const
485 {
486 return CanAbort;
487 }
488
setCanAbort(bool value)489 void CCDChip::setCanAbort(bool value)
490 {
491 CanAbort = value;
492 }
493
getImageData() const494 const QSharedPointer<FITSData> &CCDChip::getImageData() const
495 {
496 return imageData;
497 }
498
getISOIndex() const499 int CCDChip::getISOIndex() const
500 {
501 auto isoProp = baseDevice->getSwitch("CCD_ISO");
502
503 if (!isoProp)
504 return -1;
505
506 return isoProp->findOnSwitchIndex();
507 }
508
setISOIndex(int value)509 bool CCDChip::setISOIndex(int value)
510 {
511 auto isoProp = baseDevice->getSwitch("CCD_ISO");
512
513 if (!isoProp)
514 return false;
515
516 isoProp->reset();
517 isoProp->at(value)->setState(ISS_ON);
518
519 clientManager->sendNewSwitch(isoProp);
520
521 return true;
522 }
523
getISOList() const524 QStringList CCDChip::getISOList() const
525 {
526 QStringList isoList;
527
528 auto isoProp = baseDevice->getSwitch("CCD_ISO");
529
530 if (!isoProp)
531 return isoList;
532
533 for (const auto &it : *isoProp)
534 isoList << it.getLabel();
535
536 return isoList;
537 }
538
isCapturing()539 bool CCDChip::isCapturing()
540 {
541 INumberVectorProperty *expProp = nullptr;
542
543 switch (type)
544 {
545 case PRIMARY_CCD:
546 expProp = baseDevice->getNumber("CCD_EXPOSURE");
547 break;
548
549 case GUIDE_CCD:
550 expProp = baseDevice->getNumber("GUIDER_EXPOSURE");
551 break;
552 }
553
554 if (expProp == nullptr)
555 return false;
556
557 return (expProp->s == IPS_BUSY);
558 }
559
setFrameType(const QString & name)560 bool CCDChip::setFrameType(const QString &name)
561 {
562 CCDFrameType fType = FRAME_LIGHT;
563
564 if (name == "FRAME_LIGHT" || name == "Light")
565 fType = FRAME_LIGHT;
566 else if (name == "FRAME_DARK" || name == "Dark")
567 fType = FRAME_DARK;
568 else if (name == "FRAME_BIAS" || name == "Bias")
569 fType = FRAME_BIAS;
570 else if (name == "FRAME_FLAT" || name == "Flat")
571 fType = FRAME_FLAT;
572 else
573 {
574 qCWarning(KSTARS_INDI) << name << " frame type is unknown." ;
575 return false;
576 }
577
578 return setFrameType(fType);
579 }
580
setFrameType(CCDFrameType fType)581 bool CCDChip::setFrameType(CCDFrameType fType)
582 {
583 ISwitchVectorProperty *frameProp = nullptr;
584
585 if (type == PRIMARY_CCD)
586 frameProp = baseDevice->getSwitch("CCD_FRAME_TYPE");
587 else
588 frameProp = baseDevice->getSwitch("GUIDER_FRAME_TYPE");
589 if (frameProp == nullptr)
590 return false;
591
592 ISwitch *ccdFrame = nullptr;
593
594 if (fType == FRAME_LIGHT)
595 ccdFrame = IUFindSwitch(frameProp, "FRAME_LIGHT");
596 else if (fType == FRAME_DARK)
597 ccdFrame = IUFindSwitch(frameProp, "FRAME_DARK");
598 else if (fType == FRAME_BIAS)
599 ccdFrame = IUFindSwitch(frameProp, "FRAME_BIAS");
600 else if (fType == FRAME_FLAT)
601 ccdFrame = IUFindSwitch(frameProp, "FRAME_FLAT");
602
603 if (ccdFrame == nullptr)
604 return false;
605
606 if (ccdFrame->s == ISS_ON)
607 return true;
608
609 if (fType != FRAME_LIGHT)
610 captureMode = FITS_CALIBRATE;
611
612 IUResetSwitch(frameProp);
613 ccdFrame->s = ISS_ON;
614
615 clientManager->sendNewSwitch(frameProp);
616
617 return true;
618 }
619
getFrameType()620 CCDFrameType CCDChip::getFrameType()
621 {
622 CCDFrameType fType = FRAME_LIGHT;
623 ISwitchVectorProperty *frameProp = nullptr;
624
625 if (type == PRIMARY_CCD)
626 frameProp = baseDevice->getSwitch("CCD_FRAME_TYPE");
627 else
628 frameProp = baseDevice->getSwitch("GUIDER_FRAME_TYPE");
629
630 if (frameProp == nullptr)
631 return fType;
632
633 ISwitch *ccdFrame = nullptr;
634
635 ccdFrame = IUFindOnSwitch(frameProp);
636
637 if (ccdFrame == nullptr)
638 {
639 qCWarning(KSTARS_INDI) << "ISD:CCD Cannot find active frame in CCD!";
640 return fType;
641 }
642
643 if (!strcmp(ccdFrame->name, "FRAME_LIGHT"))
644 fType = FRAME_LIGHT;
645 else if (!strcmp(ccdFrame->name, "FRAME_DARK"))
646 fType = FRAME_DARK;
647 else if (!strcmp(ccdFrame->name, "FRAME_FLAT"))
648 fType = FRAME_FLAT;
649 else if (!strcmp(ccdFrame->name, "FRAME_BIAS"))
650 fType = FRAME_BIAS;
651
652 return fType;
653 }
654
setBinning(CCDBinType binType)655 bool CCDChip::setBinning(CCDBinType binType)
656 {
657 switch (binType)
658 {
659 case SINGLE_BIN:
660 return setBinning(1, 1);
661 case DOUBLE_BIN:
662 return setBinning(2, 2);
663 case TRIPLE_BIN:
664 return setBinning(3, 3);
665 case QUADRAPLE_BIN:
666 return setBinning(4, 4);
667 }
668
669 return false;
670 }
671
getBinning()672 CCDBinType CCDChip::getBinning()
673 {
674 CCDBinType binType = SINGLE_BIN;
675 INumberVectorProperty *binProp = nullptr;
676
677 switch (type)
678 {
679 case PRIMARY_CCD:
680 binProp = baseDevice->getNumber("CCD_BINNING");
681 break;
682
683 case GUIDE_CCD:
684 binProp = baseDevice->getNumber("GUIDER_BINNING");
685 break;
686 }
687
688 if (binProp == nullptr)
689 return binType;
690
691 INumber *horBin = nullptr, *verBin = nullptr;
692
693 horBin = IUFindNumber(binProp, "HOR_BIN");
694 verBin = IUFindNumber(binProp, "VER_BIN");
695
696 if (!horBin || !verBin)
697 return binType;
698
699 switch (static_cast<int>(horBin->value))
700 {
701 case 2:
702 binType = DOUBLE_BIN;
703 break;
704
705 case 3:
706 binType = TRIPLE_BIN;
707 break;
708
709 case 4:
710 binType = QUADRAPLE_BIN;
711 break;
712
713 default:
714 break;
715 }
716
717 return binType;
718 }
719
getBinning(int * bin_x,int * bin_y)720 bool CCDChip::getBinning(int *bin_x, int *bin_y)
721 {
722 INumberVectorProperty *binProp = nullptr;
723 *bin_x = *bin_y = 1;
724
725 switch (type)
726 {
727 case PRIMARY_CCD:
728 binProp = baseDevice->getNumber("CCD_BINNING");
729 break;
730
731 case GUIDE_CCD:
732 binProp = baseDevice->getNumber("GUIDER_BINNING");
733 break;
734 }
735
736 if (binProp == nullptr)
737 return false;
738
739 INumber *horBin = nullptr, *verBin = nullptr;
740
741 horBin = IUFindNumber(binProp, "HOR_BIN");
742 verBin = IUFindNumber(binProp, "VER_BIN");
743
744 if (!horBin || !verBin)
745 return false;
746
747 *bin_x = horBin->value;
748 *bin_y = verBin->value;
749
750 return true;
751 }
752
getMaxBin(int * max_xbin,int * max_ybin)753 bool CCDChip::getMaxBin(int *max_xbin, int *max_ybin)
754 {
755 if (!max_xbin || !max_ybin)
756 return false;
757
758 INumberVectorProperty *binProp = nullptr;
759
760 *max_xbin = *max_ybin = 1;
761
762 switch (type)
763 {
764 case PRIMARY_CCD:
765 binProp = baseDevice->getNumber("CCD_BINNING");
766 break;
767
768 case GUIDE_CCD:
769 binProp = baseDevice->getNumber("GUIDER_BINNING");
770 break;
771 }
772
773 if (binProp == nullptr)
774 return false;
775
776 INumber *horBin = nullptr, *verBin = nullptr;
777
778 horBin = IUFindNumber(binProp, "HOR_BIN");
779 verBin = IUFindNumber(binProp, "VER_BIN");
780
781 if (!horBin || !verBin)
782 return false;
783
784 *max_xbin = horBin->max;
785 *max_ybin = verBin->max;
786
787 return true;
788 }
789
setBinning(int bin_x,int bin_y)790 bool CCDChip::setBinning(int bin_x, int bin_y)
791 {
792 INumberVectorProperty *binProp = nullptr;
793
794 switch (type)
795 {
796 case PRIMARY_CCD:
797 binProp = baseDevice->getNumber("CCD_BINNING");
798 break;
799
800 case GUIDE_CCD:
801 binProp = baseDevice->getNumber("GUIDER_BINNING");
802 break;
803 }
804
805 if (binProp == nullptr)
806 return false;
807
808 INumber *horBin = IUFindNumber(binProp, "HOR_BIN");
809 INumber *verBin = IUFindNumber(binProp, "VER_BIN");
810
811 if (!horBin || !verBin)
812 return false;
813
814 if (!std::fabs(horBin->value - bin_x) && !std::fabs(verBin->value - bin_y))
815 return true;
816
817 if (bin_x > horBin->max || bin_y > verBin->max)
818 return false;
819
820 horBin->value = bin_x;
821 verBin->value = bin_y;
822
823 clientManager->sendNewNumber(binProp);
824
825 return true;
826 }
827
CCD(GDInterface * iPtr)828 CCD::CCD(GDInterface *iPtr) : DeviceDecorator(iPtr)
829 {
830 dType = KSTARS_CCD;
831 primaryChip.reset(new CCDChip(this, CCDChip::PRIMARY_CCD));
832
833 readyTimer.reset(new QTimer());
834 readyTimer.get()->setInterval(250);
835 readyTimer.get()->setSingleShot(true);
836 connect(readyTimer.get(), &QTimer::timeout, this, &CCD::ready);
837
838 m_Media.reset(new WSMedia(this));
839 connect(m_Media.get(), &WSMedia::newFile, this, &CCD::setWSBLOB);
840
841 connect(clientManager, &ClientManager::newBLOBManager, this, &CCD::setBLOBManager, Qt::UniqueConnection);
842 m_LastNotificationTS = QDateTime::currentDateTime();
843 }
844
~CCD()845 CCD::~CCD()
846 {
847 if (m_ImageViewerWindow)
848 m_ImageViewerWindow->close();
849 if (fileWriteThread.isRunning())
850 fileWriteThread.waitForFinished();
851 if (fileWriteBuffer != nullptr)
852 delete [] fileWriteBuffer;
853 }
854
setBLOBManager(const char * device,INDI::Property prop)855 void CCD::setBLOBManager(const char *device, INDI::Property prop)
856 {
857 if (!prop->getRegistered())
858 return;
859
860 if (device == getDeviceName())
861 emit newBLOBManager(prop);
862 }
863
registerProperty(INDI::Property prop)864 void CCD::registerProperty(INDI::Property prop)
865 {
866 if (isConnected())
867 readyTimer.get()->start();
868
869 if (prop->isNameMatch("GUIDER_EXPOSURE"))
870 {
871 HasGuideHead = true;
872 guideChip.reset(new CCDChip(this, CCDChip::GUIDE_CCD));
873 }
874 else if (prop->isNameMatch("CCD_FRAME_TYPE"))
875 {
876 auto ccdFrame = prop->getSwitch();
877
878 primaryChip->clearFrameTypes();
879
880 for (const auto &it : *ccdFrame)
881 primaryChip->addFrameLabel(it.getLabel());
882 }
883 else if (prop->isNameMatch("CCD_FRAME"))
884 {
885 auto np = prop->getNumber();
886 if (np && np->getPermission() != IP_RO)
887 primaryChip->setCanSubframe(true);
888 }
889 else if (prop->isNameMatch("GUIDER_FRAME"))
890 {
891 auto np = prop->getNumber();
892 if (np && np->getPermission() != IP_RO)
893 guideChip->setCanSubframe(true);
894 }
895 else if (prop->isNameMatch("CCD_BINNING"))
896 {
897 auto np = prop->getNumber();
898 if (np && np->getPermission() != IP_RO)
899 primaryChip->setCanBin(true);
900 }
901 else if (prop->isNameMatch("GUIDER_BINNING"))
902 {
903 auto np = prop->getNumber();
904 if (np && np->getPermission() != IP_RO)
905 guideChip->setCanBin(true);
906 }
907 else if (prop->isNameMatch("CCD_ABORT_EXPOSURE"))
908 {
909 auto sp = prop->getSwitch();
910 if (sp && sp->getPermission() != IP_RO)
911 primaryChip->setCanAbort(true);
912 }
913 else if (prop->isNameMatch("GUIDER_ABORT_EXPOSURE"))
914 {
915 auto sp = prop->getSwitch();
916 if (sp && sp->getPermission() != IP_RO)
917 guideChip->setCanAbort(true);
918 }
919 else if (prop->isNameMatch("CCD_TEMPERATURE"))
920 {
921 auto np = prop->getNumber();
922 HasCooler = true;
923 CanCool = (np->getPermission() != IP_RO);
924 if (np)
925 emit newTemperatureValue(np->at(0)->getValue());
926 }
927 else if (prop->isNameMatch("CCD_COOLER"))
928 {
929 // Can turn cooling on/off
930 HasCoolerControl = true;
931 }
932 else if (prop->isNameMatch("CCD_VIDEO_STREAM"))
933 {
934 // Has Video Stream
935 HasVideoStream = true;
936 }
937 else if (prop->isNameMatch("CCD_TRANSFER_FORMAT"))
938 {
939 auto sp = prop->getSwitch();
940 if (sp)
941 {
942 auto format = sp->findWidgetByName("FORMAT_NATIVE");
943 if (format && format->getState() == ISS_ON)
944 transferFormat = FORMAT_NATIVE;
945 else
946 transferFormat = FORMAT_FITS;
947 }
948 }
949 else if (prop->isNameMatch("CCD_EXPOSURE_PRESETS"))
950 {
951 auto svp = prop->getSwitch();
952 if (svp)
953 {
954 bool ok = false;
955 for (const auto &it : *svp)
956 {
957 QString key = QString(it.getLabel());
958 double value = key.toDouble(&ok);
959 if (!ok)
960 {
961 QStringList parts = key.split("/");
962 if (parts.count() == 2)
963 {
964 bool numOk = false, denOk = false;
965 double numerator = parts[0].toDouble(&numOk);
966 double denominator = parts[1].toDouble(&denOk);
967 if (numOk && denOk && denominator > 0)
968 {
969 ok = true;
970 value = numerator / denominator;
971 }
972 }
973 }
974 if (ok)
975 m_ExposurePresets.insert(key, value);
976
977 double min = 1e6, max = 1e-6;
978 for (auto oneValue : m_ExposurePresets.values())
979 {
980 if (oneValue < min)
981 min = oneValue;
982 if (oneValue > max)
983 max = oneValue;
984 }
985 m_ExposurePresetsMinMax = qMakePair<double, double>(min, max);
986 }
987 }
988 }
989 else if (prop->isNameMatch("CCD_FAST_TOGGLE"))
990 {
991 auto sp = prop->getSwitch();
992 if (sp)
993 m_FastExposureEnabled = sp->findOnSwitchIndex() == 0;
994 else
995 m_FastExposureEnabled = false;
996 }
997 else if (prop->isNameMatch("TELESCOPE_TYPE"))
998 {
999 auto sp = prop->getSwitch();
1000 if (sp)
1001 {
1002 auto format = sp->findWidgetByName("TELESCOPE_PRIMARY");
1003 if (format && format->getState() == ISS_ON)
1004 telescopeType = TELESCOPE_PRIMARY;
1005 else
1006 telescopeType = TELESCOPE_GUIDE;
1007 }
1008 }
1009 else if (prop->isNameMatch("CCD_WEBSOCKET_SETTINGS"))
1010 {
1011 auto np = prop->getNumber();
1012 m_Media->setURL(QUrl(QString("ws://%1:%2").arg(clientManager->getHost()).arg(np->at(0)->getValue())));
1013 m_Media->connectServer();
1014 }
1015 else if (prop->isNameMatch("CCD1"))
1016 {
1017 IBLOBVectorProperty *bp = prop->getBLOB();
1018 primaryCCDBLOB = bp->bp;
1019 primaryCCDBLOB->bvp = bp;
1020 }
1021 // try to find gain and/or offset property, if any
1022 else if ( (gainN == nullptr || offsetN == nullptr) && prop->getType() == INDI_NUMBER)
1023 {
1024 // Since gain is spread among multiple property depending on the camera providing it
1025 // we need to search in all possible number properties
1026 auto controlNP = prop->getNumber();
1027 if (controlNP)
1028 {
1029 for (auto &it : *controlNP)
1030 {
1031 QString name = QString(it.getName()).toLower();
1032 QString label = QString(it.getLabel()).toLower();
1033
1034 if (name == "gain" || label == "gain")
1035 {
1036 gainN = ⁢
1037 gainPerm = controlNP->getPermission();
1038 }
1039 else if (name == "offset" || label == "offset")
1040 {
1041 offsetN = ⁢
1042 offsetPerm = controlNP->getPermission();
1043 }
1044 }
1045 }
1046 }
1047
1048 DeviceDecorator::registerProperty(prop);
1049 }
1050
removeProperty(const QString & name)1051 void CCD::removeProperty(const QString &name)
1052 {
1053 if (name == "CCD_WEBSOCKET_SETTINGS")
1054 {
1055 m_Media->disconnectServer();
1056 }
1057
1058 DeviceDecorator::removeProperty(name);
1059 }
1060
processLight(ILightVectorProperty * lvp)1061 void CCD::processLight(ILightVectorProperty *lvp)
1062 {
1063 DeviceDecorator::processLight(lvp);
1064 }
1065
processNumber(INumberVectorProperty * nvp)1066 void CCD::processNumber(INumberVectorProperty *nvp)
1067 {
1068 if (!strcmp(nvp->name, "CCD_EXPOSURE"))
1069 {
1070 INumber *np = IUFindNumber(nvp, "CCD_EXPOSURE_VALUE");
1071 if (np)
1072 emit newExposureValue(primaryChip.get(), np->value, nvp->s);
1073 if (nvp->s == IPS_ALERT)
1074 emit error(ERROR_CAPTURE);
1075 }
1076 else if (!strcmp(nvp->name, "CCD_TEMPERATURE"))
1077 {
1078 HasCooler = true;
1079 INumber *np = IUFindNumber(nvp, "CCD_TEMPERATURE_VALUE");
1080 if (np)
1081 emit newTemperatureValue(np->value);
1082 }
1083 else if (!strcmp(nvp->name, "GUIDER_EXPOSURE"))
1084 {
1085 INumber *np = IUFindNumber(nvp, "GUIDER_EXPOSURE_VALUE");
1086 if (np)
1087 emit newExposureValue(guideChip.get(), np->value, nvp->s);
1088 }
1089 else if (!strcmp(nvp->name, "FPS"))
1090 {
1091 emit newFPS(nvp->np[0].value, nvp->np[1].value);
1092 }
1093 else if (!strcmp(nvp->name, "CCD_RAPID_GUIDE_DATA"))
1094 {
1095 double dx = -1, dy = -1, fit = -1;
1096 INumber *np = nullptr;
1097
1098 if (nvp->s == IPS_ALERT)
1099 {
1100 emit newGuideStarData(primaryChip.get(), -1, -1, -1);
1101 }
1102 else
1103 {
1104 np = IUFindNumber(nvp, "GUIDESTAR_X");
1105 if (np)
1106 dx = np->value;
1107 np = IUFindNumber(nvp, "GUIDESTAR_Y");
1108 if (np)
1109 dy = np->value;
1110 np = IUFindNumber(nvp, "GUIDESTAR_FIT");
1111 if (np)
1112 fit = np->value;
1113
1114 if (dx >= 0 && dy >= 0 && fit >= 0)
1115 emit newGuideStarData(primaryChip.get(), dx, dy, fit);
1116 }
1117 }
1118 else if (!strcmp(nvp->name, "GUIDER_RAPID_GUIDE_DATA"))
1119 {
1120 double dx = -1, dy = -1, fit = -1;
1121 INumber *np = nullptr;
1122
1123 if (nvp->s == IPS_ALERT)
1124 {
1125 emit newGuideStarData(guideChip.get(), -1, -1, -1);
1126 }
1127 else
1128 {
1129 np = IUFindNumber(nvp, "GUIDESTAR_X");
1130 if (np)
1131 dx = np->value;
1132 np = IUFindNumber(nvp, "GUIDESTAR_Y");
1133 if (np)
1134 dy = np->value;
1135 np = IUFindNumber(nvp, "GUIDESTAR_FIT");
1136 if (np)
1137 fit = np->value;
1138
1139 if (dx >= 0 && dy >= 0 && fit >= 0)
1140 emit newGuideStarData(guideChip.get(), dx, dy, fit);
1141 }
1142 }
1143
1144 DeviceDecorator::processNumber(nvp);
1145 }
1146
processSwitch(ISwitchVectorProperty * svp)1147 void CCD::processSwitch(ISwitchVectorProperty *svp)
1148 {
1149 if (!strcmp(svp->name, "CCD_COOLER"))
1150 {
1151 // Can turn cooling on/off
1152 HasCoolerControl = true;
1153 emit coolerToggled(svp->sp[0].s == ISS_ON);
1154 }
1155 else if (QString(svp->name).endsWith("VIDEO_STREAM"))
1156 {
1157 // If BLOB is not enabled for this camera, then ignore all VIDEO_STREAM calls.
1158 if (isBLOBEnabled() == false)
1159 return;
1160
1161 HasVideoStream = true;
1162
1163 if (!streamWindow && svp->sp[0].s == ISS_ON)
1164 {
1165 streamWindow.reset(new StreamWG(this));
1166
1167 INumberVectorProperty *streamFrame = baseDevice->getNumber("CCD_STREAM_FRAME");
1168 INumber *w = nullptr, *h = nullptr;
1169
1170 if (streamFrame)
1171 {
1172 w = IUFindNumber(streamFrame, "WIDTH");
1173 h = IUFindNumber(streamFrame, "HEIGHT");
1174 }
1175
1176 if (w && h)
1177 {
1178 streamW = w->value;
1179 streamH = h->value;
1180 }
1181 else
1182 {
1183 // Only use CCD dimensions if we are receiving raw stream and not stream of images (i.e. mjpeg..etc)
1184 auto rawBP = baseDevice->getBLOB("CCD1");
1185 if (rawBP)
1186 {
1187 int x = 0, y = 0, w = 0, h = 0;
1188 int binx = 0, biny = 0;
1189
1190 primaryChip->getFrame(&x, &y, &w, &h);
1191 primaryChip->getBinning(&binx, &biny);
1192 streamW = w / binx;
1193 streamH = h / biny;
1194 }
1195 }
1196
1197 streamWindow->setSize(streamW, streamH);
1198 }
1199
1200 if (streamWindow)
1201 {
1202 connect(streamWindow.get(), &StreamWG::hidden, this, &CCD::StreamWindowHidden, Qt::UniqueConnection);
1203 connect(streamWindow.get(), &StreamWG::imageChanged, this, &CCD::newVideoFrame, Qt::UniqueConnection);
1204
1205 streamWindow->enableStream(svp->sp[0].s == ISS_ON);
1206 emit videoStreamToggled(svp->sp[0].s == ISS_ON);
1207 }
1208 }
1209 else if (!strcmp(svp->name, "CCD_TRANSFER_FORMAT"))
1210 {
1211 ISwitch *format = IUFindSwitch(svp, "FORMAT_NATIVE");
1212
1213 if (format && format->s == ISS_ON)
1214 transferFormat = FORMAT_NATIVE;
1215 else
1216 transferFormat = FORMAT_FITS;
1217 }
1218 else if (!strcmp(svp->name, "RECORD_STREAM"))
1219 {
1220 ISwitch *recordOFF = IUFindSwitch(svp, "RECORD_OFF");
1221
1222 if (recordOFF && recordOFF->s == ISS_ON)
1223 {
1224 emit videoRecordToggled(false);
1225 KSNotification::event(QLatin1String("IndiServerMessage"), i18n("Video Recording Stopped"), KSNotification::EVENT_INFO);
1226 }
1227 else
1228 {
1229 emit videoRecordToggled(true);
1230 KSNotification::event(QLatin1String("IndiServerMessage"), i18n("Video Recording Started"), KSNotification::EVENT_INFO);
1231 }
1232 }
1233 else if (!strcmp(svp->name, "TELESCOPE_TYPE"))
1234 {
1235 ISwitch *format = IUFindSwitch(svp, "TELESCOPE_PRIMARY");
1236 if (format && format->s == ISS_ON)
1237 telescopeType = TELESCOPE_PRIMARY;
1238 else
1239 telescopeType = TELESCOPE_GUIDE;
1240 }
1241 else if (!strcmp(svp->name, "CCD_FAST_TOGGLE"))
1242 {
1243 m_FastExposureEnabled = IUFindOnSwitchIndex(svp) == 0;
1244 }
1245 else if (streamWindow && !strcmp(svp->name, "CONNECTION"))
1246 {
1247 ISwitch *dSwitch = IUFindSwitch(svp, "DISCONNECT");
1248
1249 if (dSwitch && dSwitch->s == ISS_ON)
1250 {
1251 streamWindow->enableStream(false);
1252 emit videoStreamToggled(false);
1253 streamWindow->close();
1254 streamWindow.reset();
1255 }
1256
1257 //emit switchUpdated(svp);
1258 //return;
1259 }
1260
1261 DeviceDecorator::processSwitch(svp);
1262 }
1263
processText(ITextVectorProperty * tvp)1264 void CCD::processText(ITextVectorProperty *tvp)
1265 {
1266 if (!strcmp(tvp->name, "CCD_FILE_PATH"))
1267 {
1268 IText *filepath = IUFindText(tvp, "FILE_PATH");
1269 if (filepath)
1270 emit newRemoteFile(QString(filepath->text));
1271 }
1272
1273 DeviceDecorator::processText(tvp);
1274 }
1275
setWSBLOB(const QByteArray & message,const QString & extension)1276 void CCD::setWSBLOB(const QByteArray &message, const QString &extension)
1277 {
1278 if (!primaryCCDBLOB)
1279 return;
1280
1281 primaryCCDBLOB->blob = const_cast<char *>(message.data());
1282 primaryCCDBLOB->size = message.size();
1283 strncpy(primaryCCDBLOB->format, extension.toLatin1().constData(), MAXINDIFORMAT - 1);
1284 processBLOB(primaryCCDBLOB);
1285
1286 // Disassociate
1287 primaryCCDBLOB->blob = nullptr;
1288 }
1289
processStream(IBLOB * bp)1290 void CCD::processStream(IBLOB *bp)
1291 {
1292 if (!streamWindow || streamWindow->isStreamEnabled() == false)
1293 return;
1294
1295 INumberVectorProperty *streamFrame = baseDevice->getNumber("CCD_STREAM_FRAME");
1296 INumber *w = nullptr, *h = nullptr;
1297
1298 if (streamFrame)
1299 {
1300 w = IUFindNumber(streamFrame, "WIDTH");
1301 h = IUFindNumber(streamFrame, "HEIGHT");
1302 }
1303
1304 if (w && h)
1305 {
1306 streamW = w->value;
1307 streamH = h->value;
1308 }
1309 else
1310 {
1311 int x = 0, y = 0, w = 0, h = 0;
1312 int binx = 1, biny = 1;
1313
1314 primaryChip->getFrame(&x, &y, &w, &h);
1315 primaryChip->getBinning(&binx, &biny);
1316 streamW = w / binx;
1317 streamH = h / biny;
1318 }
1319
1320 streamWindow->setSize(streamW, streamH);
1321
1322 streamWindow->show();
1323 streamWindow->newFrame(bp);
1324 }
1325
generateFilename(bool batch_mode,const QString & extension,QString * filename)1326 bool CCD::generateFilename(bool batch_mode, const QString &extension, QString *filename)
1327 {
1328
1329 placeholderPath.generateFilename("%p1/%t/%T/%F/%t_%T_%F_%e_%D_%s3", ISOMode,
1330 batch_mode, nextSequenceID, extension, filename);
1331
1332 QDir currentDir = QFileInfo(*filename).dir();
1333 if (currentDir.exists() == false)
1334 QDir().mkpath(currentDir.path());
1335
1336 QFile test_file(*filename);
1337 if (!test_file.open(QIODevice::WriteOnly))
1338 return false;
1339 test_file.flush();
1340 test_file.close();
1341 return true;
1342 }
1343
writeImageFile(const QString & filename,IBLOB * bp,bool is_fits)1344 bool CCD::writeImageFile(const QString &filename, IBLOB *bp, bool is_fits)
1345 {
1346 // TODO: Not yet threading the writes for non-fits files.
1347 // Would need to deal with the raw conversion, etc.
1348 if (is_fits)
1349 {
1350 // Check if the last write is still ongoing, and if so wait.
1351 // It is using the fileWriteBuffer.
1352 if (fileWriteThread.isRunning())
1353 {
1354 fileWriteThread.waitForFinished();
1355 }
1356
1357 // Wait until the file is written before overwritting the filename.
1358 fileWriteFilename = filename;
1359
1360 // Will write blob data in a separate thread, and can't depend on the blob
1361 // memory, so copy it first.
1362
1363 // Check buffer size.
1364 if (fileWriteBufferSize != bp->size)
1365 {
1366 if (fileWriteBuffer != nullptr)
1367 delete [] fileWriteBuffer;
1368 fileWriteBufferSize = bp->size;
1369 fileWriteBuffer = new char[fileWriteBufferSize];
1370 }
1371
1372 // Copy memory, and write file on a separate thread.
1373 // Probably too late to return an error if the file couldn't write.
1374 memcpy(fileWriteBuffer, bp->blob, bp->size);
1375 fileWriteThread = QtConcurrent::run(this, &ISD::CCD::WriteImageFileInternal, fileWriteFilename, fileWriteBuffer, bp->size);
1376 }
1377 else
1378 {
1379 if (!WriteImageFileInternal(filename, static_cast<char*>(bp->blob), bp->size))
1380 return false;
1381 }
1382 return true;
1383 }
1384
1385 // Get or Create FITSViewer if we are using FITSViewer
1386 // or if capture mode is calibrate since for now we are forced to open the file in the viewer
1387 // this should be fixed in the future and should only use FITSData
getFITSViewer()1388 QPointer<FITSViewer> CCD::getFITSViewer()
1389 {
1390 // if the FITS viewer exists, return it
1391 if (m_FITSViewerWindow != nullptr && ! m_FITSViewerWindow.isNull())
1392 return m_FITSViewerWindow;
1393
1394 // otherwise, create it
1395 normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1;
1396
1397 m_FITSViewerWindow = KStars::Instance()->createFITSViewer();
1398
1399 // Check if ONE tab of the viewer was closed.
1400 connect(m_FITSViewerWindow, &FITSViewer::closed, this, [this](int tabIndex)
1401 {
1402 if (tabIndex == normalTabID)
1403 normalTabID = -1;
1404 else if (tabIndex == calibrationTabID)
1405 calibrationTabID = -1;
1406 else if (tabIndex == focusTabID)
1407 focusTabID = -1;
1408 else if (tabIndex == guideTabID)
1409 guideTabID = -1;
1410 else if (tabIndex == alignTabID)
1411 alignTabID = -1;
1412 });
1413
1414 // If FITS viewer was completed closed. Reset everything
1415 connect(m_FITSViewerWindow, &FITSViewer::destroyed, this, [this]()
1416 {
1417 normalTabID = -1;
1418 calibrationTabID = -1;
1419 focusTabID = -1;
1420 guideTabID = -1;
1421 alignTabID = -1;
1422 m_FITSViewerWindow.clear();
1423 });
1424
1425 return m_FITSViewerWindow;
1426 }
1427
processBLOB(IBLOB * bp)1428 void CCD::processBLOB(IBLOB *bp)
1429 {
1430 // Ignore write-only BLOBs since we only receive it for state-change
1431 if (bp->bvp->p == IP_WO || bp->size == 0)
1432 return;
1433
1434 BType = BLOB_OTHER;
1435
1436 QString format = QString(bp->format).toLower();
1437
1438 // If stream, process it first
1439 if (format.contains("stream") && streamWindow)
1440 {
1441 processStream(bp);
1442 return;
1443 }
1444
1445 // Format without leading . (.jpg --> jpg)
1446 QString shortFormat = format.mid(1);
1447
1448 // If it's not FITS or an image, don't process it.
1449 if ((QImageReader::supportedImageFormats().contains(shortFormat.toLatin1())))
1450 BType = BLOB_IMAGE;
1451 else if (format.contains("fits"))
1452 BType = BLOB_FITS;
1453 else if (RAWFormats.contains(shortFormat))
1454 BType = BLOB_RAW;
1455
1456 if (BType == BLOB_OTHER)
1457 {
1458 DeviceDecorator::processBLOB(bp);
1459 return;
1460 }
1461
1462 CCDChip *targetChip = nullptr;
1463
1464 if (!strcmp(bp->name, "CCD2"))
1465 targetChip = guideChip.get();
1466 else
1467 {
1468 targetChip = primaryChip.get();
1469 qCDebug(KSTARS_INDI) << "Image received. Mode:" << getFITSModeStringString(targetChip->getCaptureMode()) << "Size:" <<
1470 bp->size;
1471 }
1472
1473 // Create temporary name if ANY of the following conditions are met:
1474 // 1. file is preview or batch mode is not enabled
1475 // 2. file type is not FITS_NORMAL (focus, guide..etc)
1476 QString filename;
1477 #if 0
1478
1479 if (targetChip->isBatchMode() == false || targetChip->getCaptureMode() != FITS_NORMAL)
1480 {
1481 if (!writeTempImageFile(format, static_cast<char *>(bp->blob), bp->size, &filename))
1482 {
1483 emit BLOBUpdated(nullptr);
1484 return;
1485 }
1486 if (BType == BLOB_FITS)
1487 addFITSKeywords(filename, filter);
1488
1489 }
1490 #endif
1491 // Create file name for sequences.
1492 if (targetChip->isBatchMode())
1493 {
1494 // If either generating file name or writing the image file fails
1495 // then return
1496 if (!generateFilename(targetChip->isBatchMode(), format, &filename) ||
1497 !writeImageFile(filename, bp, BType == BLOB_FITS))
1498 {
1499 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [ = ]()
1500 {
1501 KSMessageBox::Instance()->disconnect(this);
1502 emit error(ERROR_SAVE);
1503 });
1504 KSMessageBox::Instance()->error(i18n("Failed writing image to %1\nPlease check folder, filename & permissions.",
1505 filename),
1506 i18n("Image Write Failed"), 30);
1507
1508 emit BLOBUpdated(nullptr);
1509 return;
1510 }
1511 }
1512 else
1513 filename = QDir::tempPath() + QDir::separator() + "image" + format;
1514
1515 if (targetChip->getCaptureMode() == FITS_NORMAL && targetChip->isBatchMode() == true)
1516 {
1517 KStars::Instance()->statusBar()->showMessage(i18n("%1 file saved to %2", shortFormat.toUpper(), filename), 0);
1518 qCInfo(KSTARS_INDI) << shortFormat.toUpper() << "file saved to" << filename;
1519 }
1520
1521 // Don't spam, just one notification per 3 seconds
1522 if (QDateTime::currentDateTime().secsTo(m_LastNotificationTS) <= -3)
1523 {
1524 KNotification::event(QLatin1String("FITSReceived"), i18n("Image file is received"));
1525 m_LastNotificationTS = QDateTime::currentDateTime();
1526 }
1527
1528 // Check if we need to process RAW or regular image. Anything but FITS.
1529 #if 0
1530 if (BType == BLOB_IMAGE || BType == BLOB_RAW)
1531 {
1532 bool useFITSViewer = Options::autoImageToFITS() &&
1533 (Options::useFITSViewer() || (Options::useDSLRImageViewer() == false && targetChip->isBatchMode() == false));
1534 bool useDSLRViewer = (Options::useDSLRImageViewer() || targetChip->isBatchMode() == false);
1535 // For raw image, we only process them to JPG if we need to open them in the image viewer
1536 if (BType == BLOB_RAW && (useFITSViewer || useDSLRViewer))
1537 {
1538 QString rawFileName = filename;
1539 rawFileName = rawFileName.remove(0, rawFileName.lastIndexOf(QLatin1String("/")));
1540
1541 QString templateName = QString("%1/%2.XXXXXX").arg(QDir::tempPath(), rawFileName);
1542 QTemporaryFile imgPreview(templateName);
1543
1544 imgPreview.setAutoRemove(false);
1545 imgPreview.open();
1546 imgPreview.close();
1547 QString preview_filename = imgPreview.fileName();
1548 QString errorMessage;
1549
1550 if (KSUtils::RAWToJPEG(filename, preview_filename, errorMessage) == false)
1551 {
1552 KStars::Instance()->statusBar()->showMessage(errorMessage);
1553 emit BLOBUpdated(bp);
1554 return;
1555 }
1556
1557 // Remove tempeorary CR2 files
1558 if (targetChip->isBatchMode() == false)
1559 QFile::remove(filename);
1560
1561 filename = preview_filename;
1562 format = ".jpg";
1563 shortFormat = "jpg";
1564 }
1565
1566 // Convert to FITS if checked.
1567 QString output;
1568 if (useFITSViewer && (FITSData::ImageToFITS(filename, shortFormat, output)))
1569 {
1570 if (BType == BLOB_RAW || targetChip->isBatchMode() == false)
1571 QFile::remove(filename);
1572
1573 filename = output;
1574 BType = BLOB_FITS;
1575
1576 emit previewFITSGenerated(output);
1577
1578 FITSData *blob_fits_data = new FITSData(targetChip->getCaptureMode());
1579
1580 QFuture<bool> fitsloader = blob_fits_data->loadFromFile(filename, false);
1581 fitsloader.waitForFinished();
1582 if (!fitsloader.result())
1583 {
1584 // If reading the blob fails, we treat it the same as exposure failure
1585 // and recapture again if possible
1586 delete (blob_fits_data);
1587 qCCritical(KSTARS_INDI) << "failed reading FITS memory buffer";
1588 emit newExposureValue(targetChip, 0, IPS_ALERT);
1589 return;
1590 }
1591 displayFits(targetChip, filename, bp, blob_fits_data);
1592 return;
1593 }
1594 else if (useDSLRViewer)
1595 {
1596 if (m_ImageViewerWindow.isNull())
1597 m_ImageViewerWindow = new ImageViewer(getDeviceName(), KStars::Instance());
1598
1599 m_ImageViewerWindow->loadImage(filename);
1600
1601 emit previewJPEGGenerated(filename, m_ImageViewerWindow->metadata());
1602 }
1603 }
1604 #endif
1605
1606 // Load FITS if either:
1607 // #1 FITS Viewer is set to enabled.
1608 // #2 This is a preview, so we MUST open FITS Viewer even if disabled.
1609 // if (BType == BLOB_FITS)
1610 // {
1611 // Don't display image if the following conditions are met:
1612 // 1. Mode is NORMAL or CALIBRATE; and
1613 // 2. FITS Viewer is disabled; and
1614 // 3. Batch mode is enabled.
1615 // 4. Summary view is false.
1616 if ((targetChip->getCaptureMode() == FITS_NORMAL || targetChip->getCaptureMode() == FITS_CALIBRATE) &&
1617 Options::useFITSViewer() == false &&
1618 Options::useSummaryPreview() == false &&
1619 targetChip->isBatchMode())
1620 {
1621 emit BLOBUpdated(bp);
1622 emit newImage(nullptr);
1623 return;
1624 }
1625
1626 QSharedPointer<FITSData> blob_data;
1627 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(bp->blob), bp->size);
1628 blob_data.reset(new FITSData(targetChip->getCaptureMode()), &QObject::deleteLater);
1629 if (!blob_data->loadFromBuffer(buffer, shortFormat, filename, false))
1630 {
1631 // If reading the blob fails, we treat it the same as exposure failure
1632 // and recapture again if possible
1633 qCCritical(KSTARS_INDI) << "failed reading FITS memory buffer";
1634 emit error(ERROR_LOAD);
1635 return;
1636 }
1637
1638 handleImage(targetChip, filename, bp, blob_data);
1639 // else
1640 // emit BLOBUpdated(bp);
1641 }
1642
handleImage(CCDChip * targetChip,const QString & filename,IBLOB * bp,QSharedPointer<FITSData> data)1643 void CCD::handleImage(CCDChip *targetChip, const QString &filename, IBLOB *bp, QSharedPointer<FITSData> data)
1644 {
1645 FITSMode captureMode = targetChip->getCaptureMode();
1646
1647 // Add metadata
1648 data->setProperty("device", getDeviceName());
1649 data->setProperty("blobVector", bp->bvp->name);
1650 data->setProperty("blobElement", bp->name);
1651 data->setProperty("chip", targetChip->getType());
1652
1653 switch (captureMode)
1654 {
1655 case FITS_NORMAL:
1656 case FITS_CALIBRATE:
1657 {
1658 if (Options::useFITSViewer() || targetChip->isBatchMode() == false)
1659 {
1660 // No need to wait until the image is loaded in the view, but emit AFTER checking
1661 // batch mode, since newImage() may change it
1662 emit BLOBUpdated(bp);
1663 emit newImage(data);
1664
1665 bool success = false;
1666 int tabIndex = -1;
1667 int *tabID = (captureMode == FITS_NORMAL) ? &normalTabID : &calibrationTabID;
1668 QUrl fileURL = QUrl::fromLocalFile(filename);
1669 FITSScale captureFilter = targetChip->getCaptureFilter();
1670 if (*tabID == -1 || Options::singlePreviewFITS() == false)
1671 {
1672 // If image is preview and we should display all captured images in a
1673 // single tab called "Preview", then set the title to "Preview",
1674 // Otherwise, the title will be the captured image name
1675 QString previewTitle;
1676 if (targetChip->isBatchMode() == false && Options::singlePreviewFITS())
1677 {
1678 // If we are displaying all images from all cameras in a single FITS
1679 // Viewer window, then we prefix the camera name to the "Preview" string
1680 if (Options::singleWindowCapturedFITS())
1681 previewTitle = i18n("%1 Preview", getDeviceName());
1682 else
1683 // Otherwise, just use "Preview"
1684 previewTitle = i18n("Preview");
1685 }
1686
1687 success = getFITSViewer()->loadData(data, fileURL, &tabIndex, captureMode, captureFilter, previewTitle);
1688 }
1689 else
1690 success = getFITSViewer()->updateData(data, fileURL, *tabID, &tabIndex, captureFilter);
1691
1692 if (!success)
1693 {
1694 // If opening file fails, we treat it the same as exposure failure
1695 // and recapture again if possible
1696 qCCritical(KSTARS_INDI) << "error adding/updating FITS";
1697 emit error(ERROR_VIEWER);
1698 return;
1699 }
1700 *tabID = tabIndex;
1701 targetChip->setImageView(getFITSViewer()->getView(tabIndex), captureMode);
1702 if (Options::focusFITSOnNewImage())
1703 getFITSViewer()->raise();
1704 }
1705 else
1706 {
1707 emit BLOBUpdated(bp);
1708 emit newImage(data);
1709 }
1710 }
1711 break;
1712
1713 case FITS_FOCUS:
1714 case FITS_GUIDE:
1715 case FITS_ALIGN:
1716 loadImageInView(targetChip, data);
1717 emit BLOBUpdated(bp);
1718 emit newImage(data);
1719 break;
1720 }
1721 }
1722
loadImageInView(ISD::CCDChip * targetChip,const QSharedPointer<FITSData> & data)1723 void CCD::loadImageInView(ISD::CCDChip *targetChip, const QSharedPointer<FITSData> &data)
1724 {
1725 FITSMode mode = targetChip->getCaptureMode();
1726 FITSView *view = targetChip->getImageView(mode);
1727 //QString filename = QString(static_cast<const char *>(bp->aux2));
1728
1729 if (view)
1730 {
1731 view->setFilter(targetChip->getCaptureFilter());
1732 //if (!view->loadFITSFromData(data, filename))
1733 if (!view->loadData(data))
1734 {
1735 emit error(ERROR_LOAD);
1736 return;
1737
1738 }
1739 // FITSViewer is shown if:
1740 // Image in preview mode, or useFITSViewer is true; AND
1741 // Image type is either NORMAL or CALIBRATION since the rest have their dedicated windows.
1742 // NORMAL is used for raw INDI drivers without Ekos.
1743 if ( (Options::useFITSViewer() || targetChip->isBatchMode() == false) &&
1744 (mode == FITS_NORMAL || mode == FITS_CALIBRATE))
1745 getFITSViewer()->show();
1746 }
1747 }
1748
getTargetTransferFormat() const1749 CCD::TransferFormat CCD::getTargetTransferFormat() const
1750 {
1751 return targetTransferFormat;
1752 }
1753
setTargetTransferFormat(const TransferFormat & value)1754 void CCD::setTargetTransferFormat(const TransferFormat &value)
1755 {
1756 targetTransferFormat = value;
1757 }
1758
1759 //void CCD::FITSViewerDestroyed()
1760 //{
1761 // normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1;
1762 //}
1763
StreamWindowHidden()1764 void CCD::StreamWindowHidden()
1765 {
1766 if (baseDevice->isConnected())
1767 {
1768 // We can have more than one *_VIDEO_STREAM property active so disable them all
1769 auto streamSP = baseDevice->getSwitch("CCD_VIDEO_STREAM");
1770 if (streamSP)
1771 {
1772 streamSP->reset();
1773 streamSP->at(0)->setState(ISS_OFF);
1774 streamSP->at(1)->setState(ISS_ON);
1775 streamSP->setState(IPS_IDLE);
1776 clientManager->sendNewSwitch(streamSP);
1777 }
1778
1779 streamSP = baseDevice->getSwitch("VIDEO_STREAM");
1780 if (streamSP)
1781 {
1782 streamSP->reset();
1783 streamSP->at(0)->setState(ISS_OFF);
1784 streamSP->at(1)->setState(ISS_ON);
1785 streamSP->setState(IPS_IDLE);
1786 clientManager->sendNewSwitch(streamSP);
1787 }
1788
1789 streamSP = baseDevice->getSwitch("AUX_VIDEO_STREAM");
1790 if (streamSP)
1791 {
1792 streamSP->reset();
1793 streamSP->at(0)->setState(ISS_OFF);
1794 streamSP->at(1)->setState(ISS_ON);
1795 streamSP->setState(IPS_IDLE);
1796 clientManager->sendNewSwitch(streamSP);
1797 }
1798 }
1799
1800 if (streamWindow)
1801 streamWindow->disconnect();
1802 }
1803
hasGuideHead()1804 bool CCD::hasGuideHead()
1805 {
1806 return HasGuideHead;
1807 }
1808
hasCooler()1809 bool CCD::hasCooler()
1810 {
1811 return HasCooler;
1812 }
1813
hasCoolerControl()1814 bool CCD::hasCoolerControl()
1815 {
1816 return HasCoolerControl;
1817 }
1818
setCoolerControl(bool enable)1819 bool CCD::setCoolerControl(bool enable)
1820 {
1821 if (HasCoolerControl == false)
1822 return false;
1823
1824 auto coolerSP = baseDevice->getSwitch("CCD_COOLER");
1825
1826 if (!coolerSP)
1827 return false;
1828
1829 // Cooler ON/OFF
1830 auto coolerON = coolerSP->findWidgetByName("COOLER_ON");
1831 auto coolerOFF = coolerSP->findWidgetByName("COOLER_OFF");
1832 if (!coolerON || !coolerOFF)
1833 return false;
1834
1835 coolerON->setState(enable ? ISS_ON : ISS_OFF);
1836 coolerOFF->setState(enable ? ISS_OFF : ISS_ON);
1837 clientManager->sendNewSwitch(coolerSP);
1838
1839 return true;
1840 }
1841
getChip(CCDChip::ChipType cType)1842 CCDChip *CCD::getChip(CCDChip::ChipType cType)
1843 {
1844 switch (cType)
1845 {
1846 case CCDChip::PRIMARY_CCD:
1847 return primaryChip.get();
1848
1849 case CCDChip::GUIDE_CCD:
1850 return guideChip.get();
1851 }
1852
1853 return nullptr;
1854 }
1855
setRapidGuide(CCDChip * targetChip,bool enable)1856 bool CCD::setRapidGuide(CCDChip *targetChip, bool enable)
1857 {
1858 ISwitchVectorProperty *rapidSP = nullptr;
1859 ISwitch *enableS = nullptr;
1860
1861 if (targetChip == primaryChip.get())
1862 rapidSP = baseDevice->getSwitch("CCD_RAPID_GUIDE");
1863 else
1864 rapidSP = baseDevice->getSwitch("GUIDER_RAPID_GUIDE");
1865
1866 if (rapidSP == nullptr)
1867 return false;
1868
1869 enableS = IUFindSwitch(rapidSP, "ENABLE");
1870
1871 if (enableS == nullptr)
1872 return false;
1873
1874 // Already updated, return OK
1875 if ((enable && enableS->s == ISS_ON) || (!enable && enableS->s == ISS_OFF))
1876 return true;
1877
1878 IUResetSwitch(rapidSP);
1879 rapidSP->sp[0].s = enable ? ISS_ON : ISS_OFF;
1880 rapidSP->sp[1].s = enable ? ISS_OFF : ISS_ON;
1881
1882 clientManager->sendNewSwitch(rapidSP);
1883
1884 return true;
1885 }
1886
configureRapidGuide(CCDChip * targetChip,bool autoLoop,bool sendImage,bool showMarker)1887 bool CCD::configureRapidGuide(CCDChip *targetChip, bool autoLoop, bool sendImage, bool showMarker)
1888 {
1889 ISwitchVectorProperty *rapidSP = nullptr;
1890 ISwitch *autoLoopS = nullptr, *sendImageS = nullptr, *showMarkerS = nullptr;
1891
1892 if (targetChip == primaryChip.get())
1893 rapidSP = baseDevice->getSwitch("CCD_RAPID_GUIDE_SETUP");
1894 else
1895 rapidSP = baseDevice->getSwitch("GUIDER_RAPID_GUIDE_SETUP");
1896
1897 if (rapidSP == nullptr)
1898 return false;
1899
1900 autoLoopS = IUFindSwitch(rapidSP, "AUTO_LOOP");
1901 sendImageS = IUFindSwitch(rapidSP, "SEND_IMAGE");
1902 showMarkerS = IUFindSwitch(rapidSP, "SHOW_MARKER");
1903
1904 if (!autoLoopS || !sendImageS || !showMarkerS)
1905 return false;
1906
1907 // If everything is already set, let's return.
1908 if (((autoLoop && autoLoopS->s == ISS_ON) || (!autoLoop && autoLoopS->s == ISS_OFF)) &&
1909 ((sendImage && sendImageS->s == ISS_ON) || (!sendImage && sendImageS->s == ISS_OFF)) &&
1910 ((showMarker && showMarkerS->s == ISS_ON) || (!showMarker && showMarkerS->s == ISS_OFF)))
1911 return true;
1912
1913 autoLoopS->s = autoLoop ? ISS_ON : ISS_OFF;
1914 sendImageS->s = sendImage ? ISS_ON : ISS_OFF;
1915 showMarkerS->s = showMarker ? ISS_ON : ISS_OFF;
1916
1917 clientManager->sendNewSwitch(rapidSP);
1918
1919 return true;
1920 }
1921
updateUploadSettings(const QString & remoteDir)1922 void CCD::updateUploadSettings(const QString &remoteDir)
1923 {
1924 QString filename = seqPrefix + (seqPrefix.isEmpty() ? "" : "_") + QString("XXX");
1925
1926 ITextVectorProperty *uploadSettingsTP = nullptr;
1927 IText *uploadT = nullptr;
1928
1929 uploadSettingsTP = baseDevice->getText("UPLOAD_SETTINGS");
1930 if (uploadSettingsTP)
1931 {
1932 uploadT = IUFindText(uploadSettingsTP, "UPLOAD_DIR");
1933 if (uploadT && remoteDir.isEmpty() == false)
1934 IUSaveText(uploadT, remoteDir.toLatin1().constData());
1935
1936 uploadT = IUFindText(uploadSettingsTP, "UPLOAD_PREFIX");
1937 if (uploadT)
1938 IUSaveText(uploadT, filename.toLatin1().constData());
1939
1940 clientManager->sendNewText(uploadSettingsTP);
1941 }
1942 }
1943
getUploadMode()1944 CCD::UploadMode CCD::getUploadMode()
1945 {
1946 ISwitchVectorProperty *uploadModeSP = nullptr;
1947
1948 uploadModeSP = baseDevice->getSwitch("UPLOAD_MODE");
1949
1950 if (uploadModeSP == nullptr)
1951 {
1952 qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver.";
1953 return UPLOAD_CLIENT;
1954 }
1955
1956 if (uploadModeSP)
1957 {
1958 ISwitch *modeS = nullptr;
1959
1960 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_CLIENT");
1961 if (modeS && modeS->s == ISS_ON)
1962 return UPLOAD_CLIENT;
1963 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_LOCAL");
1964 if (modeS && modeS->s == ISS_ON)
1965 return UPLOAD_LOCAL;
1966 modeS = IUFindSwitch(uploadModeSP, "UPLOAD_BOTH");
1967 if (modeS && modeS->s == ISS_ON)
1968 return UPLOAD_BOTH;
1969 }
1970
1971 // Default
1972 return UPLOAD_CLIENT;
1973 }
1974
setUploadMode(UploadMode mode)1975 bool CCD::setUploadMode(UploadMode mode)
1976 {
1977 ISwitch *modeS = nullptr;
1978
1979 auto uploadModeSP = baseDevice->getSwitch("UPLOAD_MODE");
1980
1981 if (!uploadModeSP)
1982 {
1983 qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver.";
1984 return false;
1985 }
1986
1987 switch (mode)
1988 {
1989 case UPLOAD_CLIENT:
1990 modeS = uploadModeSP->findWidgetByName("UPLOAD_CLIENT");
1991 if (!modeS)
1992 return false;
1993 if (modeS->s == ISS_ON)
1994 return true;
1995 break;
1996
1997 case UPLOAD_BOTH:
1998 modeS = uploadModeSP->findWidgetByName("UPLOAD_BOTH");
1999 if (!modeS)
2000 return false;
2001 if (modeS->s == ISS_ON)
2002 return true;
2003 break;
2004
2005 case UPLOAD_LOCAL:
2006 modeS = uploadModeSP->findWidgetByName("UPLOAD_LOCAL");
2007 if (!modeS)
2008 return false;
2009 if (modeS->s == ISS_ON)
2010 return true;
2011 break;
2012 }
2013
2014 uploadModeSP->reset();
2015 modeS->s = ISS_ON;
2016
2017 clientManager->sendNewSwitch(uploadModeSP);
2018
2019 return true;
2020 }
2021
getTemperature(double * value)2022 bool CCD::getTemperature(double *value)
2023 {
2024 if (HasCooler == false)
2025 return false;
2026
2027 auto temperatureNP = baseDevice->getNumber("CCD_TEMPERATURE");
2028
2029 if (!temperatureNP)
2030 return false;
2031
2032 *value = temperatureNP->at(0)->getValue();
2033
2034 return true;
2035 }
2036
setTemperature(double value)2037 bool CCD::setTemperature(double value)
2038 {
2039 auto nvp = baseDevice->getNumber("CCD_TEMPERATURE");
2040
2041 if (!nvp)
2042 return false;
2043
2044 auto np = nvp->findWidgetByName("CCD_TEMPERATURE_VALUE");
2045
2046 if (!np)
2047 return false;
2048
2049 np->setValue(value);
2050
2051 clientManager->sendNewNumber(nvp);
2052
2053 return true;
2054 }
2055
setTransformFormat(CCD::TransferFormat format)2056 bool CCD::setTransformFormat(CCD::TransferFormat format)
2057 {
2058 if (format == transferFormat)
2059 return true;
2060
2061 auto svp = baseDevice->getSwitch("CCD_TRANSFER_FORMAT");
2062
2063 if (!svp)
2064 return false;
2065
2066 auto formatFITS = svp->findWidgetByName("FORMAT_FITS");
2067 auto formatNative = svp->findWidgetByName("FORMAT_NATIVE");
2068
2069 if (!formatFITS || !formatNative)
2070 return false;
2071
2072 transferFormat = format;
2073
2074 formatFITS->setState(transferFormat == FORMAT_FITS ? ISS_ON : ISS_OFF);
2075 formatNative->setState(transferFormat == FORMAT_FITS ? ISS_OFF : ISS_ON);
2076
2077 clientManager->sendNewSwitch(svp);
2078
2079 return true;
2080 }
2081
setTelescopeType(TelescopeType type)2082 bool CCD::setTelescopeType(TelescopeType type)
2083 {
2084 if (type == telescopeType)
2085 return true;
2086
2087 auto svp = baseDevice->getSwitch("TELESCOPE_TYPE");
2088
2089 if (!svp)
2090 return false;
2091
2092 auto typePrimary = svp->findWidgetByName("TELESCOPE_PRIMARY");
2093 auto typeGuide = svp->findWidgetByName("TELESCOPE_GUIDE");
2094
2095 if (!typePrimary || !typeGuide)
2096 return false;
2097
2098 telescopeType = type;
2099
2100 if ( (telescopeType == TELESCOPE_PRIMARY && typePrimary->getState() == ISS_OFF) ||
2101 (telescopeType == TELESCOPE_GUIDE && typeGuide->getState() == ISS_OFF))
2102 {
2103 typePrimary->setState(telescopeType == TELESCOPE_PRIMARY ? ISS_ON : ISS_OFF);
2104 typeGuide->setState(telescopeType == TELESCOPE_PRIMARY ? ISS_OFF : ISS_ON);
2105 clientManager->sendNewSwitch(svp);
2106 setConfig(SAVE_CONFIG);
2107 }
2108
2109 return true;
2110 }
2111
setVideoStreamEnabled(bool enable)2112 bool CCD::setVideoStreamEnabled(bool enable)
2113 {
2114 if (HasVideoStream == false)
2115 return false;
2116
2117 auto svp = baseDevice->getSwitch("CCD_VIDEO_STREAM");
2118
2119 if (!svp)
2120 return false;
2121
2122 // If already on and enable is set or vice versa no need to change anything we return true
2123 if ((enable && svp->at(0)->getState() == ISS_ON) || (!enable && svp->at(1)->getState() == ISS_ON))
2124 return true;
2125
2126 svp->at(0)->setState(enable ? ISS_ON : ISS_OFF);
2127 svp->at(1)->setState(enable ? ISS_OFF : ISS_ON);
2128
2129 clientManager->sendNewSwitch(svp);
2130
2131 return true;
2132 }
2133
resetStreamingFrame()2134 bool CCD::resetStreamingFrame()
2135 {
2136 auto frameProp = baseDevice->getNumber("CCD_STREAM_FRAME");
2137
2138 if (!frameProp)
2139 return false;
2140
2141 auto xarg = frameProp->findWidgetByName("X");
2142 auto yarg = frameProp->findWidgetByName("Y");
2143 auto warg = frameProp->findWidgetByName("WIDTH");
2144 auto harg = frameProp->findWidgetByName("HEIGHT");
2145
2146 if (xarg && yarg && warg && harg)
2147 {
2148 if (!std::fabs(xarg->getValue() - xarg->getMin()) &&
2149 !std::fabs(yarg->getValue() - yarg->getMin()) &&
2150 !std::fabs(warg->getValue() - warg->getMax()) &&
2151 !std::fabs(harg->getValue() - harg->getMax()))
2152 return false;
2153
2154 xarg->setValue(xarg->getMin());
2155 yarg->setValue(yarg->getMin());
2156 warg->setValue(warg->getMax());
2157 harg->setValue(harg->getMax());
2158
2159 clientManager->sendNewNumber(frameProp);
2160 return true;
2161 }
2162
2163 return false;
2164 }
2165
setStreamLimits(uint16_t maxBufferSize,uint16_t maxPreviewFPS)2166 bool CCD::setStreamLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS)
2167 {
2168 auto limitsProp = baseDevice->getNumber("LIMITS");
2169
2170 if (!limitsProp)
2171 return false;
2172
2173 auto bufferMax = limitsProp->findWidgetByName("LIMITS_BUFFER_MAX");
2174 auto previewFPS = limitsProp->findWidgetByName("LIMITS_PREVIEW_FPS");
2175
2176 if (bufferMax && previewFPS)
2177 {
2178 if(std::fabs(bufferMax->getValue() - maxBufferSize) > 0 || std::fabs(previewFPS->getValue() - maxPreviewFPS) > 0)
2179 {
2180 bufferMax->setValue(maxBufferSize);
2181 previewFPS->setValue(maxPreviewFPS);
2182 clientManager->sendNewNumber(limitsProp);
2183 }
2184
2185 return true;
2186 }
2187
2188 return false;
2189 }
2190
setStreamingFrame(int x,int y,int w,int h)2191 bool CCD::setStreamingFrame(int x, int y, int w, int h)
2192 {
2193 auto frameProp = baseDevice->getNumber("CCD_STREAM_FRAME");
2194
2195 if (!frameProp)
2196 return false;
2197
2198 auto xarg = frameProp->findWidgetByName("X");
2199 auto yarg = frameProp->findWidgetByName("Y");
2200 auto warg = frameProp->findWidgetByName("WIDTH");
2201 auto harg = frameProp->findWidgetByName("HEIGHT");
2202
2203 if (xarg && yarg && warg && harg)
2204 {
2205 if (!std::fabs(xarg->getValue() - x) &&
2206 !std::fabs(yarg->getValue() - y) &&
2207 !std::fabs(warg->getValue() - w) &&
2208 !std::fabs(harg->getValue() - h))
2209 return true;
2210
2211 // N.B. We add offset since the X, Y are relative to whatever streaming frame is currently active
2212 xarg->value = qBound(xarg->getMin(), static_cast<double>(x) + xarg->getValue(), xarg->getMax());
2213 yarg->value = qBound(yarg->getMin(), static_cast<double>(y) + yarg->getValue(), yarg->getMax());
2214 warg->value = qBound(warg->getMin(), static_cast<double>(w), warg->getMax());
2215 harg->value = qBound(harg->getMin(), static_cast<double>(h), harg->getMax());
2216
2217 clientManager->sendNewNumber(frameProp);
2218 return true;
2219 }
2220
2221 return false;
2222 }
2223
isStreamingEnabled()2224 bool CCD::isStreamingEnabled()
2225 {
2226 if (HasVideoStream == false || !streamWindow)
2227 return false;
2228
2229 return streamWindow->isStreamEnabled();
2230 }
2231
setSERNameDirectory(const QString & filename,const QString & directory)2232 bool CCD::setSERNameDirectory(const QString &filename, const QString &directory)
2233 {
2234 auto tvp = baseDevice->getText("RECORD_FILE");
2235
2236 if (!tvp)
2237 return false;
2238
2239 auto filenameT = tvp->findWidgetByName("RECORD_FILE_NAME");
2240 auto dirT = tvp->findWidgetByName("RECORD_FILE_DIR");
2241
2242 if (!filenameT || !dirT)
2243 return false;
2244
2245 filenameT->setText(filename.toLatin1().data());
2246 dirT->setText(directory.toLatin1().data());
2247
2248 clientManager->sendNewText(tvp);
2249
2250 return true;
2251 }
2252
getSERNameDirectory(QString & filename,QString & directory)2253 bool CCD::getSERNameDirectory(QString &filename, QString &directory)
2254 {
2255 auto tvp = baseDevice->getText("RECORD_FILE");
2256
2257 if (!tvp)
2258 return false;
2259
2260 auto filenameT = tvp->findWidgetByName("RECORD_FILE_NAME");
2261 auto dirT = tvp->findWidgetByName("RECORD_FILE_DIR");
2262
2263 if (!filenameT || !dirT)
2264 return false;
2265
2266 filename = QString(filenameT->getText());
2267 directory = QString(dirT->getText());
2268
2269 return true;
2270 }
2271
startRecording()2272 bool CCD::startRecording()
2273 {
2274 auto svp = baseDevice->getSwitch("RECORD_STREAM");
2275
2276 if (!svp)
2277 return false;
2278
2279 auto recordON = svp->findWidgetByName("RECORD_ON");
2280
2281 if (!recordON)
2282 return false;
2283
2284 if (recordON->getState() == ISS_ON)
2285 return true;
2286
2287 svp->reset();
2288 recordON->setState(ISS_ON);
2289
2290 clientManager->sendNewSwitch(svp);
2291
2292 return true;
2293 }
2294
startDurationRecording(double duration)2295 bool CCD::startDurationRecording(double duration)
2296 {
2297 auto nvp = baseDevice->getNumber("RECORD_OPTIONS");
2298
2299 if (!nvp)
2300 return false;
2301
2302 auto durationN = nvp->findWidgetByName("RECORD_DURATION");
2303
2304 if (!durationN)
2305 return false;
2306
2307 auto svp = baseDevice->getSwitch("RECORD_STREAM");
2308
2309 if (!svp)
2310 return false;
2311
2312 auto recordON = svp->findWidgetByName("RECORD_DURATION_ON");
2313
2314 if (!recordON)
2315 return false;
2316
2317 if (recordON->getState() == ISS_ON)
2318 return true;
2319
2320 durationN->setValue(duration);
2321 clientManager->sendNewNumber(nvp);
2322
2323 svp->reset();
2324 recordON->setState(ISS_ON);
2325
2326 clientManager->sendNewSwitch(svp);
2327
2328 return true;
2329 }
2330
startFramesRecording(uint32_t frames)2331 bool CCD::startFramesRecording(uint32_t frames)
2332 {
2333 auto nvp = baseDevice->getNumber("RECORD_OPTIONS");
2334
2335 if (!nvp)
2336 return false;
2337
2338 auto frameN = nvp->findWidgetByName("RECORD_FRAME_TOTAL");
2339 auto svp = baseDevice->getSwitch("RECORD_STREAM");
2340
2341 if (!frameN || !svp)
2342 return false;
2343
2344 auto recordON = svp->findWidgetByName("RECORD_FRAME_ON");
2345
2346 if (!recordON)
2347 return false;
2348
2349 if (recordON->getState() == ISS_ON)
2350 return true;
2351
2352 frameN->setValue(frames);
2353 clientManager->sendNewNumber(nvp);
2354
2355 svp->reset();
2356 recordON->setState(ISS_ON);
2357
2358 clientManager->sendNewSwitch(svp);
2359
2360 return true;
2361 }
2362
stopRecording()2363 bool CCD::stopRecording()
2364 {
2365 auto svp = baseDevice->getSwitch("RECORD_STREAM");
2366
2367 if (!svp)
2368 return false;
2369
2370 auto recordOFF = svp->findWidgetByName("RECORD_OFF");
2371
2372 if (!recordOFF)
2373 return false;
2374
2375 // If already set
2376 if (recordOFF->getState() == ISS_ON)
2377 return true;
2378
2379 svp->reset();
2380 recordOFF->setState(ISS_ON);
2381
2382 clientManager->sendNewSwitch(svp);
2383
2384 return true;
2385 }
2386
setFITSHeader(const QMap<QString,QString> & values)2387 bool CCD::setFITSHeader(const QMap<QString, QString> &values)
2388 {
2389 auto tvp = baseDevice->getText("FITS_HEADER");
2390
2391 if (!tvp)
2392 return false;
2393
2394 QMapIterator<QString, QString> i(values);
2395
2396 while (i.hasNext())
2397 {
2398 i.next();
2399
2400 auto headerT = tvp->findWidgetByName(i.key().toLatin1().data());
2401
2402 if (!headerT)
2403 continue;
2404
2405 headerT->setText(i.value().toLatin1().data());
2406 }
2407
2408 clientManager->sendNewText(tvp);
2409
2410 return true;
2411 }
2412
setGain(double value)2413 bool CCD::setGain(double value)
2414 {
2415 if (!gainN)
2416 return false;
2417
2418 gainN->value = value;
2419 clientManager->sendNewNumber(gainN->nvp);
2420 return true;
2421 }
2422
getGain(double * value)2423 bool CCD::getGain(double *value)
2424 {
2425 if (!gainN)
2426 return false;
2427
2428 *value = gainN->value;
2429
2430 return true;
2431 }
2432
getGainMinMaxStep(double * min,double * max,double * step)2433 bool CCD::getGainMinMaxStep(double *min, double *max, double *step)
2434 {
2435 if (!gainN)
2436 return false;
2437
2438 *min = gainN->min;
2439 *max = gainN->max;
2440 *step = gainN->step;
2441
2442 return true;
2443 }
2444
setOffset(double value)2445 bool CCD::setOffset(double value)
2446 {
2447 if (!offsetN)
2448 return false;
2449
2450 offsetN->value = value;
2451 clientManager->sendNewNumber(offsetN->nvp);
2452 return true;
2453 }
2454
getOffset(double * value)2455 bool CCD::getOffset(double *value)
2456 {
2457 if (!offsetN)
2458 return false;
2459
2460 *value = offsetN->value;
2461
2462 return true;
2463 }
2464
getOffsetMinMaxStep(double * min,double * max,double * step)2465 bool CCD::getOffsetMinMaxStep(double *min, double *max, double *step)
2466 {
2467 if (!offsetN)
2468 return false;
2469
2470 *min = offsetN->min;
2471 *max = offsetN->max;
2472 *step = offsetN->step;
2473
2474 return true;
2475 }
2476
isBLOBEnabled()2477 bool CCD::isBLOBEnabled()
2478 {
2479 return (clientManager->isBLOBEnabled(getDeviceName(), "CCD1"));
2480 }
2481
setBLOBEnabled(bool enable,const QString & prop)2482 bool CCD::setBLOBEnabled(bool enable, const QString &prop)
2483 {
2484 clientManager->setBLOBEnabled(enable, getDeviceName(), prop);
2485
2486 return true;
2487 }
2488
setFastExposureEnabled(bool enable)2489 bool CCD::setFastExposureEnabled(bool enable)
2490 {
2491 // Set value immediately
2492 m_FastExposureEnabled = enable;
2493
2494 auto svp = baseDevice->getSwitch("CCD_FAST_TOGGLE");
2495
2496 if (!svp)
2497 return false;
2498
2499 svp->at(0)->setState(enable ? ISS_ON : ISS_OFF);
2500 svp->at(1)->setState(enable ? ISS_OFF : ISS_ON);
2501 clientManager->sendNewSwitch(svp);
2502
2503 return true;
2504 }
2505
setFastCount(uint32_t count)2506 bool CCD::setFastCount(uint32_t count)
2507 {
2508 auto nvp = baseDevice->getNumber("CCD_FAST_COUNT");
2509
2510 if (!nvp)
2511 return false;
2512
2513 nvp->at(0)->setValue(count);
2514
2515 clientManager->sendNewNumber(nvp);
2516
2517 return true;
2518 }
2519
setStreamExposure(double duration)2520 bool CCD::setStreamExposure(double duration)
2521 {
2522 auto nvp = baseDevice->getNumber("STREAMING_EXPOSURE");
2523
2524 if (!nvp)
2525 return false;
2526
2527 nvp->at(0)->setValue(duration);
2528
2529 clientManager->sendNewNumber(nvp);
2530
2531 return true;
2532 }
2533
getStreamExposure(double * duration)2534 bool CCD::getStreamExposure(double *duration)
2535 {
2536 auto nvp = baseDevice->getNumber("STREAMING_EXPOSURE");
2537
2538 if (!nvp)
2539 return false;
2540
2541 *duration = nvp->at(0)->getValue();
2542
2543 return true;
2544 }
2545
isCoolerOn()2546 bool CCD::isCoolerOn()
2547 {
2548 auto svp = baseDevice->getSwitch("CCD_COOLER");
2549
2550 if (!svp)
2551 return false;
2552
2553 return (svp->at(0)->getState() == ISS_ON);
2554 }
2555
getTemperatureRegulation(double & ramp,double & threshold)2556 bool CCD::getTemperatureRegulation(double &ramp, double &threshold)
2557 {
2558 auto regulation = baseDevice->getProperty("CCD_TEMP_RAMP");
2559 if (!regulation->isValid())
2560 return false;
2561
2562 ramp = regulation.getNumber()->at(0)->getValue();
2563 threshold = regulation.getNumber()->at(1)->getValue();
2564 return true;
2565 }
2566
setTemperatureRegulation(double ramp,double threshold)2567 bool CCD::setTemperatureRegulation(double ramp, double threshold)
2568 {
2569 auto regulation = baseDevice->getProperty("CCD_TEMP_RAMP");
2570 if (!regulation->isValid())
2571 return false;
2572
2573 regulation.getNumber()->at(0)->setValue(ramp);
2574 regulation.getNumber()->at(1)->setValue(threshold);
2575 clientManager->sendNewNumber(regulation->getNumber());
2576 return true;
2577 }
2578
2579 // Internal function to write an image blob to disk.
WriteImageFileInternal(const QString & filename,char * buffer,const size_t size)2580 bool CCD::WriteImageFileInternal(const QString &filename, char *buffer, const size_t size)
2581 {
2582 QFile file(filename);
2583 if (!file.open(QIODevice::WriteOnly))
2584 {
2585 qCCritical(KSTARS_INDI) << "ISD:CCD Error: Unable to open write file: " <<
2586 filename;
2587 return false;
2588 }
2589 int n = 0;
2590 QDataStream out(&file);
2591 bool ok = true;
2592 for (size_t nr = 0; nr < size; nr += n)
2593 {
2594 n = out.writeRawData(buffer + nr, size - nr);
2595 if (n < 0)
2596 {
2597 ok = false;
2598 break;
2599 }
2600 }
2601 ok = file.flush() && ok;
2602 file.close();
2603 file.setPermissions(QFileDevice::ReadUser |
2604 QFileDevice::WriteUser |
2605 QFileDevice::ReadGroup |
2606 QFileDevice::ReadOther);
2607 return ok;
2608 }
2609 }
2610