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 = &it;
1037                     gainPerm = controlNP->getPermission();
1038                 }
1039                 else if (name == "offset" || label == "offset")
1040                 {
1041                     offsetN = &it;
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