Instrument Neutral Distributed Interface INDI  1.6.0
indiccd.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2010, 2011 Gerry Rozema, Jasem Mutlaq. All rights reserved.
3 
4  Rapid Guide support added by CloudMakers, s. r. o.
5  Copyright(c) 2013 CloudMakers, s. r. o. All rights reserved.
6 
7  Star detection algorithm is based on PHD Guiding by Craig Stark
8  Copyright (c) 2006-2010 Craig Stark. All rights reserved.
9 
10  This library is free software; you can redistribute it and/or
11  modify it under the terms of the GNU Library General Public
12  License version 2 as published by the Free Software Foundation.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Library General Public License for more details.
18 
19  You should have received a copy of the GNU Library General Public License
20  along with this library; see the file COPYING.LIB. If not, write to
21  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  Boston, MA 02110-1301, USA.
23 *******************************************************************************/
24 
25 #include "indiccd.h"
26 
27 #include "indicom.h"
28 #include "stream/streammanager.h"
29 #include "locale_compat.h"
30 
31 #include <fitsio.h>
32 
33 #include <libnova/julian_day.h>
34 #include <libnova/precession.h>
35 #include <libnova/airmass.h>
36 #include <libnova/transform.h>
37 #include <libnova/ln_types.h>
38 
39 #include <cmath>
40 #include <regex>
41 
42 #include <dirent.h>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <zlib.h>
46 #include <sys/stat.h>
47 
48 const char *IMAGE_SETTINGS_TAB = "Image Settings";
49 const char *IMAGE_INFO_TAB = "Image Info";
50 const char *GUIDE_HEAD_TAB = "Guider Head";
51 const char *GUIDE_CONTROL_TAB = "Guider Control";
52 const char *RAPIDGUIDE_TAB = "Rapid Guide";
53 const char *WCS_TAB = "WCS";
54 
55 // Create dir recursively
56 static int _ccd_mkdir(const char *dir, mode_t mode)
57 {
58  char tmp[PATH_MAX];
59  char *p = nullptr;
60  size_t len;
61 
62  snprintf(tmp, sizeof(tmp), "%s", dir);
63  len = strlen(tmp);
64  if (tmp[len - 1] == '/')
65  tmp[len - 1] = 0;
66  for (p = tmp + 1; *p; p++)
67  if (*p == '/')
68  {
69  *p = 0;
70  if (mkdir(tmp, mode) == -1 && errno != EEXIST)
71  return -1;
72  *p = '/';
73  }
74  if (mkdir(tmp, mode) == -1 && errno != EEXIST)
75  return -1;
76 
77  return 0;
78 }
79 
80 namespace INDI
81 {
82 
84 {
85  SendCompressed = false;
86  Interlaced = false;
87 
88  SubX = SubY = 0;
89  SubW = SubH = 1;
90  BPP = 8;
91  BinX = BinY = 1;
92  NAxis = 2;
93 
94  BinFrame = nullptr;
95 
96  strncpy(imageExtention, "fits", MAXINDIBLOBFMT);
97 
98  FrameType = LIGHT_FRAME;
99  lastRapidX = lastRapidY = -1;
100 }
101 
103 {
104  delete [] RawFrame;
105  delete[] BinFrame;
106 }
107 
109 {
110  FrameType = type;
111 }
112 
113 void CCDChip::setResolution(int x, int y)
114 {
115  XRes = x;
116  YRes = y;
117 
118  ImagePixelSizeN[0].value = x;
119  ImagePixelSizeN[1].value = y;
120 
121  IDSetNumber(&ImagePixelSizeNP, nullptr);
122 
123  ImageFrameN[FRAME_X].min = 0;
124  ImageFrameN[FRAME_X].max = x - 1;
125  ImageFrameN[FRAME_Y].min = 0;
126  ImageFrameN[FRAME_Y].max = y - 1;
127 
128  ImageFrameN[FRAME_W].min = 1;
129  ImageFrameN[FRAME_W].max = x;
130  ImageFrameN[FRAME_H].max = 1;
131  ImageFrameN[FRAME_H].max = y;
132  IUUpdateMinMax(&ImageFrameNP);
133 }
134 
135 void CCDChip::setFrame(int subx, int suby, int subw, int subh)
136 {
137  SubX = subx;
138  SubY = suby;
139  SubW = subw;
140  SubH = subh;
141 
142  ImageFrameN[FRAME_X].value = SubX;
143  ImageFrameN[FRAME_Y].value = SubY;
144  ImageFrameN[FRAME_W].value = SubW;
145  ImageFrameN[FRAME_H].value = SubH;
146 
147  IDSetNumber(&ImageFrameNP, nullptr);
148 }
149 
150 void CCDChip::setBin(int hor, int ver)
151 {
152  BinX = hor;
153  BinY = ver;
154 
155  ImageBinN[BIN_W].value = BinX;
156  ImageBinN[BIN_H].value = BinY;
157 
158  IDSetNumber(&ImageBinNP, nullptr);
159 }
160 
161 void CCDChip::setMinMaxStep(const char *property, const char *element, double min, double max, double step,
162  bool sendToClient)
163 {
164  INumberVectorProperty *nvp = nullptr;
165 
166  if (!strcmp(property, ImageExposureNP.name))
167  nvp = &ImageExposureNP;
168  else if (!strcmp(property, ImageFrameNP.name))
169  nvp = &ImageFrameNP;
170  else if (!strcmp(property, ImageBinNP.name))
171  nvp = &ImageBinNP;
172  else if (!strcmp(property, ImagePixelSizeNP.name))
173  nvp = &ImagePixelSizeNP;
174  else if (!strcmp(property, RapidGuideDataNP.name))
175  nvp = &RapidGuideDataNP;
176 
177  INumber *np = IUFindNumber(nvp, element);
178  if (np)
179  {
180  np->min = min;
181  np->max = max;
182  np->step = step;
183 
184  if (sendToClient)
185  IUUpdateMinMax(nvp);
186  }
187 }
188 
189 void CCDChip::setPixelSize(float x, float y)
190 {
191  PixelSizex = x;
192  PixelSizey = y;
193 
194  ImagePixelSizeN[2].value = x;
195  ImagePixelSizeN[3].value = x;
196  ImagePixelSizeN[4].value = y;
197 
198  IDSetNumber(&ImagePixelSizeNP, nullptr);
199 }
200 
201 void CCDChip::setBPP(int bbp)
202 {
203  BPP = bbp;
204 
205  ImagePixelSizeN[5].value = BPP;
206 
207  IDSetNumber(&ImagePixelSizeNP, nullptr);
208 }
209 
210 void CCDChip::setFrameBufferSize(int nbuf, bool allocMem)
211 {
212  if (nbuf == RawFrameSize)
213  return;
214 
215  RawFrameSize = nbuf;
216 
217  if (allocMem == false)
218  return;
219 
220  delete [] RawFrame;
221  RawFrame = new uint8_t[nbuf];
222 
223  if (BinFrame)
224  {
225  delete [] BinFrame;
226  BinFrame = new uint8_t[nbuf];
227  }
228 }
229 
230 void CCDChip::setExposureLeft(double duration)
231 {
232  ImageExposureN[0].value = duration;
233 
234  IDSetNumber(&ImageExposureNP, nullptr);
235 }
236 
237 void CCDChip::setExposureDuration(double duration)
238 {
239  exposureDuration = duration;
240  gettimeofday(&startExposureTime, nullptr);
241 }
242 
244 {
245  return FrameTypeS[fType].name;
246 }
247 
249 {
250  static char ts[32];
251 
252  char iso8601[32];
253  struct tm *tp;
254  time_t t = (time_t)startExposureTime.tv_sec;
255  int u = startExposureTime.tv_usec / 1000.0;
256 
257  tp = gmtime(&t);
258  strftime(iso8601, sizeof(iso8601), "%Y-%m-%dT%H:%M:%S", tp);
259  snprintf(ts, 32, "%s.%03d", iso8601, u);
260  return (ts);
261 }
262 
263 void CCDChip::setInterlaced(bool intr)
264 {
265  Interlaced = intr;
266 }
267 
269 {
270  ImageExposureNP.s = IPS_ALERT;
271  IDSetNumber(&ImageExposureNP, nullptr);
272 }
273 
274 int CCDChip::getNAxis() const
275 {
276  return NAxis;
277 }
278 
279 void CCDChip::setNAxis(int value)
280 {
281  NAxis = value;
282 }
283 
284 void CCDChip::setImageExtension(const char *ext)
285 {
286  strncpy(imageExtention, ext, MAXINDIBLOBFMT);
287 }
288 
290 {
291  if (BinX == 1)
292  return;
293 
294  // Jasem: Keep full frame shadow in memory to enhance performance and just swap frame pointers after operation is complete
295  if (BinFrame == nullptr)
296  BinFrame = new uint8_t[RawFrameSize];
297 
298  memset(BinFrame, 0, RawFrameSize);
299 
300  switch (getBPP())
301  {
302  case 8:
303  {
304  uint8_t *bin_buf = BinFrame;
305  // Try to average pixels since in 8bit they get saturated pretty quickly
306  double factor = (BinX * BinX) / 2;
307  double accumulator = 0;
308 
309  for (int i = 0; i < SubH; i += BinX)
310  for (int j = 0; j < SubW; j += BinX)
311  {
312  accumulator = 0;
313  for (int k = 0; k < BinX; k++)
314  {
315  for (int l = 0; l < BinX; l++)
316  {
317  accumulator += *(RawFrame + j + (i + k) * SubW + l);
318  }
319  }
320 
321  accumulator /= factor;
322  if (accumulator > UINT8_MAX)
323  *bin_buf = UINT8_MAX;
324  else
325  *bin_buf += static_cast<uint8_t>(accumulator);
326  bin_buf++;
327  }
328  }
329  break;
330 
331  case 16:
332  {
333  uint16_t *bin_buf = reinterpret_cast<uint16_t *>(BinFrame);
334  uint16_t *RawFrame16 = reinterpret_cast<uint16_t *>(RawFrame);
335  uint16_t val;
336  for (int i = 0; i < SubH; i += BinX)
337  for (int j = 0; j < SubW; j += BinX)
338  {
339  for (int k = 0; k < BinX; k++)
340  {
341  for (int l = 0; l < BinX; l++)
342  {
343  val = *(RawFrame16 + j + (i + k) * SubW + l);
344  if (val + *bin_buf > UINT16_MAX)
345  *bin_buf = UINT16_MAX;
346  else
347  *bin_buf += val;
348  }
349  }
350  bin_buf++;
351  }
352  }
353  break;
354 
355  default:
356  return;
357  }
358 
359  // Swap frame pointers
360  uint8_t *rawFramePointer = RawFrame;
361  RawFrame = BinFrame;
362  // We just memset it next time we use it
363  BinFrame = rawFramePointer;
364 }
365 
367 {
368  //ctor
369  capability = 0;
370 
371  InExposure = false;
372  InGuideExposure = false;
373  RapidGuideEnabled = false;
374  GuiderRapidGuideEnabled = false;
375  ValidCCDRotation = false;
376 
377  AutoLoop = false;
378  SendImage = false;
379  ShowMarker = false;
380  GuiderAutoLoop = false;
381  GuiderSendImage = false;
382  GuiderShowMarker = false;
383 
384  ExposureTime = 0.0;
385  GuiderExposureTime = 0.0;
386  CurrentFilterSlot = -1;
387 
388  RA = std::numeric_limits<double>::quiet_NaN();
389  Dec = std::numeric_limits<double>::quiet_NaN();
390  J2000RA = std::numeric_limits<double>::quiet_NaN();
391  J2000DE = std::numeric_limits<double>::quiet_NaN();
392  MPSAS = std::numeric_limits<double>::quiet_NaN();
393  RotatorAngle = std::numeric_limits<double>::quiet_NaN();
394  Airmass = std::numeric_limits<double>::quiet_NaN();
395  Latitude = std::numeric_limits<double>::quiet_NaN();
396  Longitude = std::numeric_limits<double>::quiet_NaN();
397  primaryAperture = primaryFocalLength = guiderAperture = guiderFocalLength - 1;
398 }
399 
401 {
402 }
403 
404 void CCD::SetCCDCapability(uint32_t cap)
405 {
406  capability = cap;
407 
408  if (HasST4Port())
409  setDriverInterface(getDriverInterface() | GUIDER_INTERFACE);
410  else
411  setDriverInterface(getDriverInterface() & ~GUIDER_INTERFACE);
412 
413  if (HasStreaming() && Streamer.get() == nullptr)
414  {
415  Streamer.reset(new StreamManager(this));
416  Streamer->initProperties();
417  }
418 }
419 
421 {
422  DefaultDevice::initProperties(); // let the base class flesh in what it wants
423 
424  // CCD Temperature
425  IUFillNumber(&TemperatureN[0], "CCD_TEMPERATURE_VALUE", "Temperature (C)", "%5.2f", -50.0, 50.0, 0., 0.);
426  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "CCD_TEMPERATURE", "Temperature",
428 
429  /**********************************************/
430  /**************** Primary Chip ****************/
431  /**********************************************/
432 
433  // Primary CCD Region-Of-Interest (ROI)
434  IUFillNumber(&PrimaryCCD.ImageFrameN[0], "X", "Left ", "%4.0f", 0, 0.0, 0, 0);
435  IUFillNumber(&PrimaryCCD.ImageFrameN[1], "Y", "Top", "%4.0f", 0, 0, 0, 0);
436  IUFillNumber(&PrimaryCCD.ImageFrameN[2], "WIDTH", "Width", "%4.0f", 0, 0.0, 0, 0.0);
437  IUFillNumber(&PrimaryCCD.ImageFrameN[3], "HEIGHT", "Height", "%4.0f", 0, 0, 0, 0.0);
438  IUFillNumberVector(&PrimaryCCD.ImageFrameNP, PrimaryCCD.ImageFrameN, 4, getDeviceName(), "CCD_FRAME", "Frame",
440 
441  // Primary CCD Frame Type
442  IUFillSwitch(&PrimaryCCD.FrameTypeS[0], "FRAME_LIGHT", "Light", ISS_ON);
443  IUFillSwitch(&PrimaryCCD.FrameTypeS[1], "FRAME_BIAS", "Bias", ISS_OFF);
444  IUFillSwitch(&PrimaryCCD.FrameTypeS[2], "FRAME_DARK", "Dark", ISS_OFF);
445  IUFillSwitch(&PrimaryCCD.FrameTypeS[3], "FRAME_FLAT", "Flat", ISS_OFF);
446  IUFillSwitchVector(&PrimaryCCD.FrameTypeSP, PrimaryCCD.FrameTypeS, 4, getDeviceName(), "CCD_FRAME_TYPE",
447  "Frame Type", IMAGE_SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
448 
449  // Primary CCD Exposure
450  IUFillNumber(&PrimaryCCD.ImageExposureN[0], "CCD_EXPOSURE_VALUE", "Duration (s)", "%5.2f", 0.01, 3600, 1.0, 1.0);
451  IUFillNumberVector(&PrimaryCCD.ImageExposureNP, PrimaryCCD.ImageExposureN, 1, getDeviceName(), "CCD_EXPOSURE",
452  "Expose", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
453 
454  // Primary CCD Abort
455  IUFillSwitch(&PrimaryCCD.AbortExposureS[0], "ABORT", "Abort", ISS_OFF);
456  IUFillSwitchVector(&PrimaryCCD.AbortExposureSP, PrimaryCCD.AbortExposureS, 1, getDeviceName(), "CCD_ABORT_EXPOSURE",
457  "Expose Abort", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
458 
459  // Primary CCD Binning
460  IUFillNumber(&PrimaryCCD.ImageBinN[0], "HOR_BIN", "X", "%2.0f", 1, 4, 1, 1);
461  IUFillNumber(&PrimaryCCD.ImageBinN[1], "VER_BIN", "Y", "%2.0f", 1, 4, 1, 1);
462  IUFillNumberVector(&PrimaryCCD.ImageBinNP, PrimaryCCD.ImageBinN, 2, getDeviceName(), "CCD_BINNING", "Binning",
464 
465  // Primary CCD Info
466  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_MAX_X], "CCD_MAX_X", "Max. Width", "%4.0f", 1, 16000, 0, 0);
467  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_MAX_Y], "CCD_MAX_Y", "Max. Height", "%4.0f", 1, 16000, 0, 0);
468  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE], "CCD_PIXEL_SIZE", "Pixel size (um)", "%5.2f", 1,
469  40, 0, 0);
470  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_X], "CCD_PIXEL_SIZE_X", "Pixel size X", "%5.2f", 1,
471  40, 0, 0);
472  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_Y], "CCD_PIXEL_SIZE_Y", "Pixel size Y", "%5.2f", 1,
473  40, 0, 0);
474  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_BITSPERPIXEL], "CCD_BITSPERPIXEL", "Bits per pixel", "%3.0f",
475  8, 64, 0, 0);
476  IUFillNumberVector(&PrimaryCCD.ImagePixelSizeNP, PrimaryCCD.ImagePixelSizeN, 6, getDeviceName(), "CCD_INFO",
477  "CCD Information", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE);
478 
479  // Primary CCD Compression Options
480  IUFillSwitch(&PrimaryCCD.CompressS[0], "CCD_COMPRESS", "Compress", ISS_OFF);
481  IUFillSwitch(&PrimaryCCD.CompressS[1], "CCD_RAW", "Raw", ISS_ON);
482  IUFillSwitchVector(&PrimaryCCD.CompressSP, PrimaryCCD.CompressS, 2, getDeviceName(), "CCD_COMPRESSION", "Image",
484  PrimaryCCD.SendCompressed = false;
485 
486  // Primary CCD Chip Data Blob
487  IUFillBLOB(&PrimaryCCD.FitsB, "CCD1", "Image", "");
488  IUFillBLOBVector(&PrimaryCCD.FitsBP, &PrimaryCCD.FitsB, 1, getDeviceName(), "CCD1", "Image Data", IMAGE_INFO_TAB,
489  IP_RO, 60, IPS_IDLE);
490 
491  // Bayer
492  IUFillText(&BayerT[0], "CFA_OFFSET_X", "X Offset", "0");
493  IUFillText(&BayerT[1], "CFA_OFFSET_Y", "Y Offset", "0");
494  IUFillText(&BayerT[2], "CFA_TYPE", "Filter", nullptr);
495  IUFillTextVector(&BayerTP, BayerT, 3, getDeviceName(), "CCD_CFA", "Bayer Info", IMAGE_INFO_TAB, IP_RW, 60,
496  IPS_IDLE);
497 
498  // Reset Frame Settings
499  IUFillSwitch(&PrimaryCCD.ResetS[0], "RESET", "Reset", ISS_OFF);
500  IUFillSwitchVector(&PrimaryCCD.ResetSP, PrimaryCCD.ResetS, 1, getDeviceName(), "CCD_FRAME_RESET", "Frame Values",
502 
503  /**********************************************/
504  /********* Primary Chip Rapid Guide **********/
505  /**********************************************/
506 
507  IUFillSwitch(&PrimaryCCD.RapidGuideS[0], "ENABLE", "Enable", ISS_OFF);
508  IUFillSwitch(&PrimaryCCD.RapidGuideS[1], "DISABLE", "Disable", ISS_ON);
509  IUFillSwitchVector(&PrimaryCCD.RapidGuideSP, PrimaryCCD.RapidGuideS, 2, getDeviceName(), "CCD_RAPID_GUIDE",
510  "Rapid Guide", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
511 
512  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[0], "AUTO_LOOP", "Auto loop", ISS_ON);
513  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[1], "SEND_IMAGE", "Send image", ISS_OFF);
514  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[2], "SHOW_MARKER", "Show marker", ISS_OFF);
515  IUFillSwitchVector(&PrimaryCCD.RapidGuideSetupSP, PrimaryCCD.RapidGuideSetupS, 3, getDeviceName(),
516  "CCD_RAPID_GUIDE_SETUP", "Rapid Guide Setup", RAPIDGUIDE_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
517 
518  IUFillNumber(&PrimaryCCD.RapidGuideDataN[0], "GUIDESTAR_X", "Guide star position X", "%5.2f", 0, 1024, 0, 0);
519  IUFillNumber(&PrimaryCCD.RapidGuideDataN[1], "GUIDESTAR_Y", "Guide star position Y", "%5.2f", 0, 1024, 0, 0);
520  IUFillNumber(&PrimaryCCD.RapidGuideDataN[2], "GUIDESTAR_FIT", "Guide star fit", "%5.2f", 0, 1024, 0, 0);
521  IUFillNumberVector(&PrimaryCCD.RapidGuideDataNP, PrimaryCCD.RapidGuideDataN, 3, getDeviceName(),
522  "CCD_RAPID_GUIDE_DATA", "Rapid Guide Data", RAPIDGUIDE_TAB, IP_RO, 60, IPS_IDLE);
523 
524  /**********************************************/
525  /***************** Guide Chip *****************/
526  /**********************************************/
527 
528  IUFillNumber(&GuideCCD.ImageFrameN[0], "X", "Left ", "%4.0f", 0, 0, 0, 0);
529  IUFillNumber(&GuideCCD.ImageFrameN[1], "Y", "Top", "%4.0f", 0, 0, 0, 0);
530  IUFillNumber(&GuideCCD.ImageFrameN[2], "WIDTH", "Width", "%4.0f", 0, 0, 0, 0);
531  IUFillNumber(&GuideCCD.ImageFrameN[3], "HEIGHT", "Height", "%4.0f", 0, 0, 0, 0);
532  IUFillNumberVector(&GuideCCD.ImageFrameNP, GuideCCD.ImageFrameN, 4, getDeviceName(), "GUIDER_FRAME", "Frame",
534 
535  IUFillNumber(&GuideCCD.ImageBinN[0], "HOR_BIN", "X", "%2.0f", 1, 4, 1, 1);
536  IUFillNumber(&GuideCCD.ImageBinN[1], "VER_BIN", "Y", "%2.0f", 1, 4, 1, 1);
537  IUFillNumberVector(&GuideCCD.ImageBinNP, GuideCCD.ImageBinN, 2, getDeviceName(), "GUIDER_BINNING", "Binning",
539 
540  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_MAX_X], "CCD_MAX_X", "Max. Width", "%4.0f", 1, 16000, 0, 0);
541  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_MAX_Y], "CCD_MAX_Y", "Max. Height", "%4.0f", 1, 16000, 0, 0);
542  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE], "CCD_PIXEL_SIZE", "Pixel size (um)", "%5.2f", 1,
543  40, 0, 0);
544  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_X], "CCD_PIXEL_SIZE_X", "Pixel size X", "%5.2f", 1,
545  40, 0, 0);
546  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_Y], "CCD_PIXEL_SIZE_Y", "Pixel size Y", "%5.2f", 1,
547  40, 0, 0);
548  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_BITSPERPIXEL], "CCD_BITSPERPIXEL", "Bits per pixel", "%3.0f", 8,
549  64, 0, 0);
550  IUFillNumberVector(&GuideCCD.ImagePixelSizeNP, GuideCCD.ImagePixelSizeN, 6, getDeviceName(), "GUIDER_INFO",
551  "Guide Info", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE);
552 
553  IUFillSwitch(&GuideCCD.FrameTypeS[0], "FRAME_LIGHT", "Light", ISS_ON);
554  IUFillSwitch(&GuideCCD.FrameTypeS[1], "FRAME_BIAS", "Bias", ISS_OFF);
555  IUFillSwitch(&GuideCCD.FrameTypeS[2], "FRAME_DARK", "Dark", ISS_OFF);
556  IUFillSwitch(&GuideCCD.FrameTypeS[3], "FRAME_FLAT", "Flat", ISS_OFF);
557  IUFillSwitchVector(&GuideCCD.FrameTypeSP, GuideCCD.FrameTypeS, 4, getDeviceName(), "GUIDER_FRAME_TYPE",
558  "Frame Type", GUIDE_HEAD_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
559 
560  IUFillNumber(&GuideCCD.ImageExposureN[0], "GUIDER_EXPOSURE_VALUE", "Duration (s)", "%5.2f", 0.01, 3600, 1.0, 1.0);
561  IUFillNumberVector(&GuideCCD.ImageExposureNP, GuideCCD.ImageExposureN, 1, getDeviceName(), "GUIDER_EXPOSURE",
562  "Guide Head", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
563 
564  IUFillSwitch(&GuideCCD.AbortExposureS[0], "ABORT", "Abort", ISS_OFF);
565  IUFillSwitchVector(&GuideCCD.AbortExposureSP, GuideCCD.AbortExposureS, 1, getDeviceName(), "GUIDER_ABORT_EXPOSURE",
566  "Guide Abort", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
567 
568  IUFillSwitch(&GuideCCD.CompressS[0], "GUIDER_COMPRESS", "Compress", ISS_OFF);
569  IUFillSwitch(&GuideCCD.CompressS[1], "GUIDER_RAW", "Raw", ISS_ON);
570  IUFillSwitchVector(&GuideCCD.CompressSP, GuideCCD.CompressS, 2, getDeviceName(), "GUIDER_COMPRESSION", "Image",
572  GuideCCD.SendCompressed = false;
573 
574  IUFillBLOB(&GuideCCD.FitsB, "CCD2", "Guider Image", "");
575  IUFillBLOBVector(&GuideCCD.FitsBP, &GuideCCD.FitsB, 1, getDeviceName(), "CCD2", "Image Data", IMAGE_INFO_TAB, IP_RO,
576  60, IPS_IDLE);
577 
578  /**********************************************/
579  /********* Guider Chip Rapid Guide ***********/
580  /**********************************************/
581 
582  IUFillSwitch(&GuideCCD.RapidGuideS[0], "ENABLE", "Enable", ISS_OFF);
583  IUFillSwitch(&GuideCCD.RapidGuideS[1], "DISABLE", "Disable", ISS_ON);
584  IUFillSwitchVector(&GuideCCD.RapidGuideSP, GuideCCD.RapidGuideS, 2, getDeviceName(), "GUIDER_RAPID_GUIDE",
585  "Guider Head Rapid Guide", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
586 
587  IUFillSwitch(&GuideCCD.RapidGuideSetupS[0], "AUTO_LOOP", "Auto loop", ISS_ON);
588  IUFillSwitch(&GuideCCD.RapidGuideSetupS[1], "SEND_IMAGE", "Send image", ISS_OFF);
589  IUFillSwitch(&GuideCCD.RapidGuideSetupS[2], "SHOW_MARKER", "Show marker", ISS_OFF);
590  IUFillSwitchVector(&GuideCCD.RapidGuideSetupSP, GuideCCD.RapidGuideSetupS, 3, getDeviceName(),
591  "GUIDER_RAPID_GUIDE_SETUP", "Rapid Guide Setup", RAPIDGUIDE_TAB, IP_RW, ISR_NOFMANY, 0,
592  IPS_IDLE);
593 
594  IUFillNumber(&GuideCCD.RapidGuideDataN[0], "GUIDESTAR_X", "Guide star position X", "%5.2f", 0, 1024, 0, 0);
595  IUFillNumber(&GuideCCD.RapidGuideDataN[1], "GUIDESTAR_Y", "Guide star position Y", "%5.2f", 0, 1024, 0, 0);
596  IUFillNumber(&GuideCCD.RapidGuideDataN[2], "GUIDESTAR_FIT", "Guide star fit", "%5.2f", 0, 1024, 0, 0);
597  IUFillNumberVector(&GuideCCD.RapidGuideDataNP, GuideCCD.RapidGuideDataN, 3, getDeviceName(),
598  "GUIDER_RAPID_GUIDE_DATA", "Rapid Guide Data", RAPIDGUIDE_TAB, IP_RO, 60, IPS_IDLE);
599 
600  /**********************************************/
601  /******************** WCS *********************/
602  /**********************************************/
603 
604  // WCS Enable/Disable
605  IUFillSwitch(&WorldCoordS[0], "WCS_ENABLE", "Enable", ISS_OFF);
606  IUFillSwitch(&WorldCoordS[1], "WCS_DISABLE", "Disable", ISS_ON);
607  IUFillSwitchVector(&WorldCoordSP, WorldCoordS, 2, getDeviceName(), "WCS_CONTROL", "WCS", WCS_TAB, IP_RW,
608  ISR_1OFMANY, 0, IPS_IDLE);
609 
610  IUFillNumber(&CCDRotationN[0], "CCD_ROTATION_VALUE", "Rotation", "%g", -360, 360, 1, 0);
611  IUFillNumberVector(&CCDRotationNP, CCDRotationN, 1, getDeviceName(), "CCD_ROTATION", "CCD FOV", WCS_TAB, IP_RW, 60,
612  IPS_IDLE);
613 
614  IUFillSwitch(&TelescopeTypeS[TELESCOPE_PRIMARY], "TELESCOPE_PRIMARY", "Primary", ISS_ON);
615  IUFillSwitch(&TelescopeTypeS[TELESCOPE_GUIDE], "TELESCOPE_GUIDE", "Guide", ISS_OFF);
616  IUFillSwitchVector(&TelescopeTypeSP, TelescopeTypeS, 2, getDeviceName(), "TELESCOPE_TYPE", "Telescope", OPTIONS_TAB,
617  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
618 
619  /**********************************************/
620  /************** Upload Settings ***************/
621  /**********************************************/
622 
623  // Upload Mode
624  IUFillSwitch(&UploadS[UPLOAD_CLIENT], "UPLOAD_CLIENT", "Client", ISS_ON);
625  IUFillSwitch(&UploadS[UPLOAD_LOCAL], "UPLOAD_LOCAL", "Local", ISS_OFF);
626  IUFillSwitch(&UploadS[UPLOAD_BOTH], "UPLOAD_BOTH", "Both", ISS_OFF);
627  IUFillSwitchVector(&UploadSP, UploadS, 3, getDeviceName(), "UPLOAD_MODE", "Upload", OPTIONS_TAB, IP_RW, ISR_1OFMANY,
628  0, IPS_IDLE);
629 
630  // Upload Settings
631  IUFillText(&UploadSettingsT[UPLOAD_DIR], "UPLOAD_DIR", "Dir", "");
632  IUFillText(&UploadSettingsT[UPLOAD_PREFIX], "UPLOAD_PREFIX", "Prefix", "IMAGE_XXX");
633  IUFillTextVector(&UploadSettingsTP, UploadSettingsT, 2, getDeviceName(), "UPLOAD_SETTINGS", "Upload Settings",
634  OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
635 
636  // Upload File Path
637  IUFillText(&FileNameT[0], "FILE_PATH", "Path", "");
638  IUFillTextVector(&FileNameTP, FileNameT, 1, getDeviceName(), "CCD_FILE_PATH", "Filename", IMAGE_INFO_TAB, IP_RO, 60,
639  IPS_IDLE);
640 
641  /**********************************************/
642  /****************** FITS Header****************/
643  /**********************************************/
644 
645  IUFillText(&FITSHeaderT[FITS_OBSERVER], "FITS_OBSERVER", "Observer", "Unknown");
646  IUFillText(&FITSHeaderT[FITS_OBJECT], "FITS_OBJECT", "Object", "Unknown");
647  IUFillTextVector(&FITSHeaderTP, FITSHeaderT, 2, getDeviceName(), "FITS_HEADER", "FITS Header", INFO_TAB, IP_RW, 60,
648  IPS_IDLE);
649 
650  /**********************************************/
651  /**************** Snooping ********************/
652  /**********************************************/
653 
654  // Snooped Devices
655  IUFillText(&ActiveDeviceT[0], "ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
656  IUFillText(&ActiveDeviceT[1], "ACTIVE_FOCUSER", "Focuser", "Focuser Simulator");
657  IUFillText(&ActiveDeviceT[2], "ACTIVE_FILTER", "Filter", "CCD Simulator");
658  IUFillText(&ActiveDeviceT[3], "ACTIVE_SKYQUALITY", "Sky Quality", "SQM");
659  IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 4, getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB,
660  IP_RW, 60, IPS_IDLE);
661 
662  // Snooped RA/DEC Property
663  IUFillNumber(&EqN[0], "RA", "Ra (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
664  IUFillNumber(&EqN[1], "DEC", "Dec (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
665  IUFillNumberVector(&EqNP, EqN, 2, ActiveDeviceT[0].text, "EQUATORIAL_EOD_COORD", "EQ Coord", "Main Control", IP_RW,
666  60, IPS_IDLE);
667 
668  // Snoop properties of interest
669 
670  // Snoop mount
671  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "EQUATORIAL_EOD_COORD");
672  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "TELESCOPE_INFO");
673  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "GEOGRAPHIC_COORD");
674 
675  // Snoop Rotator
676  IDSnoopDevice(ActiveDeviceT[SNOOP_ROTATOR].text, "ABS_ROTATOR_ANGLE");
677 
678  // Snoop Filter Wheel
679  IDSnoopDevice(ActiveDeviceT[SNOOP_FILTER_WHEEL].text, "FILTER_SLOT");
680  IDSnoopDevice(ActiveDeviceT[SNOOP_FILTER_WHEEL].text, "FILTER_NAME");
681 
682  // Snoop Sky Quality Meter
683  IDSnoopDevice(ActiveDeviceT[SNOOP_SQM].text, "SKY_QUALITY");
684 
685  // Guider Interface
686  initGuiderProperties(getDeviceName(), GUIDE_CONTROL_TAB);
687 
688  setDriverInterface(CCD_INTERFACE | GUIDER_INTERFACE);
689 
690  return true;
691 }
692 
693 void CCD::ISGetProperties(const char *dev)
694 {
696 
697  defineText(&ActiveDeviceTP);
698  loadConfig(true, "ACTIVE_DEVICES");
699 
700  if (HasStreaming())
701  Streamer->ISGetProperties(dev);
702 }
703 
705 {
706  //IDLog("CCD UpdateProperties isConnected returns %d %d\n",isConnected(),Connected);
707  if (isConnected())
708  {
709  defineNumber(&PrimaryCCD.ImageExposureNP);
710 
711  if (CanAbort())
712  defineSwitch(&PrimaryCCD.AbortExposureSP);
713  if (CanSubFrame() == false)
714  PrimaryCCD.ImageFrameNP.p = IP_RO;
715 
716  defineNumber(&PrimaryCCD.ImageFrameNP);
717  if (CanBin())
718  defineNumber(&PrimaryCCD.ImageBinNP);
719 
720  defineText(&FITSHeaderTP);
721 
722  if (HasGuideHead())
723  {
724  defineNumber(&GuideCCD.ImageExposureNP);
725  if (CanAbort())
726  defineSwitch(&GuideCCD.AbortExposureSP);
727  if (CanSubFrame() == false)
728  GuideCCD.ImageFrameNP.p = IP_RO;
729  defineNumber(&GuideCCD.ImageFrameNP);
730  }
731 
732  if (HasCooler())
733  defineNumber(&TemperatureNP);
734 
735  defineNumber(&PrimaryCCD.ImagePixelSizeNP);
736  if (HasGuideHead())
737  {
738  defineNumber(&GuideCCD.ImagePixelSizeNP);
739  if (CanBin())
740  defineNumber(&GuideCCD.ImageBinNP);
741  }
742  defineSwitch(&PrimaryCCD.CompressSP);
743  defineBLOB(&PrimaryCCD.FitsBP);
744  if (HasGuideHead())
745  {
746  defineSwitch(&GuideCCD.CompressSP);
747  defineBLOB(&GuideCCD.FitsBP);
748  }
749  if (HasST4Port())
750  {
751  defineNumber(&GuideNSNP);
752  defineNumber(&GuideWENP);
753  }
754  defineSwitch(&PrimaryCCD.FrameTypeSP);
755 
756  if (CanBin() || CanSubFrame())
757  defineSwitch(&PrimaryCCD.ResetSP);
758 
759  if (HasGuideHead())
760  defineSwitch(&GuideCCD.FrameTypeSP);
761 
762  if (HasBayer())
763  defineText(&BayerTP);
764 
765  defineSwitch(&PrimaryCCD.RapidGuideSP);
766 
767  if (HasGuideHead())
768  defineSwitch(&GuideCCD.RapidGuideSP);
769 
770  if (RapidGuideEnabled)
771  {
772  defineSwitch(&PrimaryCCD.RapidGuideSetupSP);
773  defineNumber(&PrimaryCCD.RapidGuideDataNP);
774  }
775  if (GuiderRapidGuideEnabled)
776  {
777  defineSwitch(&GuideCCD.RapidGuideSetupSP);
778  defineNumber(&GuideCCD.RapidGuideDataNP);
779  }
780  defineSwitch(&TelescopeTypeSP);
781 
782  defineSwitch(&WorldCoordSP);
783  defineSwitch(&UploadSP);
784 
785  if (UploadSettingsT[UPLOAD_DIR].text == nullptr)
786  IUSaveText(&UploadSettingsT[UPLOAD_DIR], getenv("HOME"));
787  defineText(&UploadSettingsTP);
788  }
789  else
790  {
791  deleteProperty(PrimaryCCD.ImageFrameNP.name);
792  deleteProperty(PrimaryCCD.ImagePixelSizeNP.name);
793 
794  if (CanBin())
795  deleteProperty(PrimaryCCD.ImageBinNP.name);
796 
797  deleteProperty(PrimaryCCD.ImageExposureNP.name);
798  if (CanAbort())
799  deleteProperty(PrimaryCCD.AbortExposureSP.name);
800  deleteProperty(PrimaryCCD.FitsBP.name);
801  deleteProperty(PrimaryCCD.CompressSP.name);
802  deleteProperty(PrimaryCCD.RapidGuideSP.name);
803  if (RapidGuideEnabled)
804  {
805  deleteProperty(PrimaryCCD.RapidGuideSetupSP.name);
806  deleteProperty(PrimaryCCD.RapidGuideDataNP.name);
807  }
808 
809  deleteProperty(FITSHeaderTP.name);
810 
811  if (HasGuideHead())
812  {
813  deleteProperty(GuideCCD.ImageExposureNP.name);
814  if (CanAbort())
815  deleteProperty(GuideCCD.AbortExposureSP.name);
816  deleteProperty(GuideCCD.ImageFrameNP.name);
817  deleteProperty(GuideCCD.ImagePixelSizeNP.name);
818 
819  deleteProperty(GuideCCD.FitsBP.name);
820  if (CanBin())
821  deleteProperty(GuideCCD.ImageBinNP.name);
822  deleteProperty(GuideCCD.CompressSP.name);
823  deleteProperty(GuideCCD.FrameTypeSP.name);
824  deleteProperty(GuideCCD.RapidGuideSP.name);
825  if (GuiderRapidGuideEnabled)
826  {
827  deleteProperty(GuideCCD.RapidGuideSetupSP.name);
828  deleteProperty(GuideCCD.RapidGuideDataNP.name);
829  }
830  }
831  if (HasCooler())
832  deleteProperty(TemperatureNP.name);
833  if (HasST4Port())
834  {
835  deleteProperty(GuideNSNP.name);
836  deleteProperty(GuideWENP.name);
837  }
838  deleteProperty(PrimaryCCD.FrameTypeSP.name);
839  if (CanBin() || CanSubFrame())
840  deleteProperty(PrimaryCCD.ResetSP.name);
841  if (HasBayer())
842  deleteProperty(BayerTP.name);
843  deleteProperty(TelescopeTypeSP.name);
844 
845  if (WorldCoordS[0].s == ISS_ON)
846  {
847  deleteProperty(CCDRotationNP.name);
848  }
849  deleteProperty(WorldCoordSP.name);
850  deleteProperty(UploadSP.name);
851  deleteProperty(UploadSettingsTP.name);
852  }
853 
854 // Streamer
855  if (HasStreaming())
856  Streamer->updateProperties();
857 
858  return true;
859 }
860 
862 {
863  XMLEle *ep = nullptr;
864  const char *propName = findXMLAttValu(root, "name");
865 
866  if (IUSnoopNumber(root, &EqNP) == 0)
867  {
868  float newra, newdec;
869  newra = EqN[0].value;
870  newdec = EqN[1].value;
871  if ((newra != RA) || (newdec != Dec))
872  {
873  //IDLog("RA %4.2f Dec %4.2f Snooped RA %4.2f Dec %4.2f\n",RA,Dec,newra,newdec);
874  RA = newra;
875  Dec = newdec;
876  }
877  }
878  else if (!strcmp(propName, "TELESCOPE_INFO"))
879  {
880  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
881  {
882  const char *name = findXMLAttValu(ep, "name");
883 
884  if (!strcmp(name, "TELESCOPE_APERTURE"))
885  {
886  primaryAperture = atof(pcdataXMLEle(ep));
887  }
888  else if (!strcmp(name, "TELESCOPE_FOCAL_LENGTH"))
889  {
890  primaryFocalLength = atof(pcdataXMLEle(ep));
891  }
892  else if (!strcmp(name, "GUIDER_APERTURE"))
893  {
894  guiderAperture = atof(pcdataXMLEle(ep));
895  }
896  else if (!strcmp(name, "GUIDER_FOCAL_LENGTH"))
897  {
898  guiderFocalLength = atof(pcdataXMLEle(ep));
899  }
900  }
901  }
902  else if (!strcmp(propName, "FILTER_NAME"))
903  {
904  FilterNames.clear();
905 
906  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
907  FilterNames.push_back(pcdataXMLEle(ep));
908  }
909  else if (!strcmp(propName, "FILTER_SLOT"))
910  {
911  CurrentFilterSlot = -1;
912  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
913  CurrentFilterSlot = atoi(pcdataXMLEle(ep));
914  }
915  else if (!strcmp(propName, "SKY_QUALITY"))
916  {
917  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
918  {
919  const char *name = findXMLAttValu(ep, "name");
920 
921  if (!strcmp(name, "SKY_BRIGHTNESS"))
922  {
923  MPSAS = atof(pcdataXMLEle(ep));
924  break;
925  }
926  }
927  }
928  else if (!strcmp(propName, "ABS_ROTATOR_ANGLE"))
929  {
930  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
931  {
932  const char *name = findXMLAttValu(ep, "name");
933 
934  if (!strcmp(name, "ANGLE"))
935  {
936  RotatorAngle = atof(pcdataXMLEle(ep));
937  break;
938  }
939  }
940  }
941  else if (!strcmp(propName, "GEOGRAPHIC_COORD"))
942  {
943  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
944  {
945  const char *name = findXMLAttValu(ep, "name");
946 
947  if (!strcmp(name, "LONG"))
948  {
949  Longitude = atof(pcdataXMLEle(ep));
950  if (Longitude > 180)
951  Longitude -= 360;
952  }
953  else if (!strcmp(name, "LAT"))
954  {
955  Latitude = atof(pcdataXMLEle(ep));
956  }
957  }
958  }
959 
960  return DefaultDevice::ISSnoopDevice(root);
961 }
962 
963 bool CCD::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
964 {
965  // first check if it's for our device
966  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
967  {
968  // This is for our device
969  // Now lets see if it's something we process here
970  if (!strcmp(name, ActiveDeviceTP.name))
971  {
972  ActiveDeviceTP.s = IPS_OK;
973  IUUpdateText(&ActiveDeviceTP, texts, names, n);
974  IDSetText(&ActiveDeviceTP, nullptr);
975 
976  // Update the property name!
977  strncpy(EqNP.device, ActiveDeviceT[SNOOP_MOUNT].text, MAXINDIDEVICE);
978  if (strlen(ActiveDeviceT[SNOOP_MOUNT].text) > 0)
979  {
980  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "EQUATORIAL_EOD_COORD");
981  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "TELESCOPE_INFO");
982  IDSnoopDevice(ActiveDeviceT[SNOOP_MOUNT].text, "GEOGRAPHIC_COORD");
983  }
984  else
985  {
986  RA = std::numeric_limits<double>::quiet_NaN();
987  Dec = std::numeric_limits<double>::quiet_NaN();
988  J2000RA = std::numeric_limits<double>::quiet_NaN();
989  J2000DE = std::numeric_limits<double>::quiet_NaN();
990  Latitude = std::numeric_limits<double>::quiet_NaN();
991  Longitude = std::numeric_limits<double>::quiet_NaN();
992  Airmass = std::numeric_limits<double>::quiet_NaN();
993  }
994 
995  if (strlen(ActiveDeviceT[SNOOP_ROTATOR].text) > 0)
996  IDSnoopDevice(ActiveDeviceT[SNOOP_ROTATOR].text, "ABS_ROTATOR_ANGLE");
997  else
998  MPSAS = std::numeric_limits<double>::quiet_NaN();
999 
1000  if (strlen(ActiveDeviceT[SNOOP_FILTER_WHEEL].text) > 0)
1001  {
1002  IDSnoopDevice(ActiveDeviceT[SNOOP_FILTER_WHEEL].text, "FILTER_SLOT");
1003  IDSnoopDevice(ActiveDeviceT[SNOOP_FILTER_WHEEL].text, "FILTER_NAME");
1004  }
1005  else
1006  {
1007  CurrentFilterSlot = -1;
1008  }
1009 
1010  IDSnoopDevice(ActiveDeviceT[SNOOP_SQM].text, "SKY_QUALITY");
1011 
1012  // Tell children active devices was updated.
1013  activeDevicesUpdated();
1014 
1015  // We processed this one, so, tell the world we did it
1016  return true;
1017  }
1018 
1019  if (!strcmp(name, BayerTP.name))
1020  {
1021  IUUpdateText(&BayerTP, texts, names, n);
1022  BayerTP.s = IPS_OK;
1023  IDSetText(&BayerTP, nullptr);
1024  return true;
1025  }
1026 
1027  if (!strcmp(name, FITSHeaderTP.name))
1028  {
1029  IUUpdateText(&FITSHeaderTP, texts, names, n);
1030  FITSHeaderTP.s = IPS_OK;
1031  IDSetText(&FITSHeaderTP, nullptr);
1032  return true;
1033  }
1034 
1035  if (!strcmp(name, UploadSettingsTP.name))
1036  {
1037  IUUpdateText(&UploadSettingsTP, texts, names, n);
1038  UploadSettingsTP.s = IPS_OK;
1039  IDSetText(&UploadSettingsTP, nullptr);
1040  return true;
1041  }
1042  }
1043 
1044 // Streamer
1045  if (HasStreaming())
1046  Streamer->ISNewText(dev, name, texts, names, n);
1047 
1048  return DefaultDevice::ISNewText(dev, name, texts, names, n);
1049 }
1050 
1051 bool CCD::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1052 {
1053  // first check if it's for our device
1054  //IDLog("CCD::ISNewNumber %s\n",name);
1055  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1056  {
1057  if (!strcmp(name, "CCD_EXPOSURE"))
1058  {
1059  if (PrimaryCCD.getFrameType() != CCDChip::BIAS_FRAME &&
1060  (values[0] < PrimaryCCD.ImageExposureN[0].min || values[0] > PrimaryCCD.ImageExposureN[0].max))
1061  {
1062  DEBUGF(Logger::DBG_ERROR, "Requested exposure value (%g) seconds out of bounds [%g,%g].",
1063  values[0], PrimaryCCD.ImageExposureN[0].min, PrimaryCCD.ImageExposureN[0].max);
1064  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1065  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1066  return false;
1067  }
1068 
1069  if (PrimaryCCD.getFrameType() == CCDChip::BIAS_FRAME)
1070  PrimaryCCD.ImageExposureN[0].value = ExposureTime = PrimaryCCD.ImageExposureN[0].min;
1071  else
1072  PrimaryCCD.ImageExposureN[0].value = ExposureTime = values[0];
1073 
1074  if (PrimaryCCD.ImageExposureNP.s == IPS_BUSY)
1075  {
1076  if (CanAbort() && AbortExposure() == false)
1077  DEBUG(Logger::DBG_WARNING, "Warning: Aborting exposure failed.");
1078  }
1079 
1080  if (StartExposure(ExposureTime))
1081  {
1082  if (PrimaryCCD.getFrameType() == CCDChip::LIGHT_FRAME && !std::isnan(RA) && !std::isnan(Dec))
1083  {
1084  ln_equ_posn epochPos { 0, 0 }, J2000Pos { 0, 0 };
1085  epochPos.ra = RA * 15.0;
1086  epochPos.dec = Dec;
1087 
1088  // Convert from JNow to J2000
1089  ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
1090 
1091  J2000RA = J2000Pos.ra / 15.0;
1092  J2000DE = J2000Pos.dec;
1093 
1094  if (!std::isnan(Latitude) && !std::isnan(Longitude))
1095  {
1096  // Horizontal Coords
1097  ln_hrz_posn horizontalPos;
1098  ln_lnlat_posn observer;
1099  observer.lat = Latitude;
1100  observer.lng = Longitude;
1101 
1102  ln_get_hrz_from_equ(&epochPos, &observer, ln_get_julian_from_sys(), &horizontalPos);
1103  Airmass = ln_get_airmass(horizontalPos.alt, 750);
1104  }
1105  }
1106 
1107  PrimaryCCD.ImageExposureNP.s = IPS_BUSY;
1108  }
1109  else
1110  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1111  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1112  return true;
1113  }
1114 
1115  if (!strcmp(name, "GUIDER_EXPOSURE"))
1116  {
1117  if (GuideCCD.getFrameType() != CCDChip::BIAS_FRAME &&
1118  (values[0] < GuideCCD.ImageExposureN[0].min || values[0] > GuideCCD.ImageExposureN[0].max))
1119  {
1120  DEBUGF(Logger::DBG_ERROR, "Requested guide exposure value (%g) seconds out of bounds [%g,%g].",
1121  values[0], GuideCCD.ImageExposureN[0].min, GuideCCD.ImageExposureN[0].max);
1122  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1123  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1124  return false;
1125  }
1126 
1127  if (GuideCCD.getFrameType() == CCDChip::BIAS_FRAME)
1128  GuideCCD.ImageExposureN[0].value = GuiderExposureTime = GuideCCD.ImageExposureN[0].min;
1129  else
1130  GuideCCD.ImageExposureN[0].value = GuiderExposureTime = values[0];
1131 
1132  GuideCCD.ImageExposureNP.s = IPS_BUSY;
1133  if (StartGuideExposure(GuiderExposureTime))
1134  GuideCCD.ImageExposureNP.s = IPS_BUSY;
1135  else
1136  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1137  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1138  return true;
1139  }
1140 
1141  if (!strcmp(name, "CCD_BINNING"))
1142  {
1143  // We are being asked to set camera binning
1144  INumber *np = IUFindNumber(&PrimaryCCD.ImageBinNP, names[0]);
1145  if (np == nullptr)
1146  {
1147  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1148  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1149  return false;
1150  }
1151 
1152  int binx, biny;
1153  if (!strcmp(np->name, "HOR_BIN"))
1154  {
1155  binx = values[0];
1156  biny = values[1];
1157  }
1158  else
1159  {
1160  binx = values[1];
1161  biny = values[0];
1162  }
1163 
1164  if (UpdateCCDBin(binx, biny))
1165  {
1166  IUUpdateNumber(&PrimaryCCD.ImageBinNP, values, names, n);
1167  PrimaryCCD.ImageBinNP.s = IPS_OK;
1168  }
1169  else
1170  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1171 
1172  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1173 
1174  return true;
1175  }
1176 
1177  if (!strcmp(name, "GUIDER_BINNING"))
1178  {
1179  // We are being asked to set camera binning
1180  INumber *np = IUFindNumber(&GuideCCD.ImageBinNP, names[0]);
1181  if (np == nullptr)
1182  {
1183  GuideCCD.ImageBinNP.s = IPS_ALERT;
1184  IDSetNumber(&GuideCCD.ImageBinNP, nullptr);
1185  return false;
1186  }
1187 
1188  int binx, biny;
1189  if (!strcmp(np->name, "HOR_BIN"))
1190  {
1191  binx = values[0];
1192  biny = values[1];
1193  }
1194  else
1195  {
1196  binx = values[1];
1197  biny = values[0];
1198  }
1199 
1200  if (UpdateGuiderBin(binx, biny))
1201  {
1202  IUUpdateNumber(&GuideCCD.ImageBinNP, values, names, n);
1203  GuideCCD.ImageBinNP.s = IPS_OK;
1204  }
1205  else
1206  GuideCCD.ImageBinNP.s = IPS_ALERT;
1207 
1208  IDSetNumber(&GuideCCD.ImageBinNP, nullptr);
1209 
1210  return true;
1211  }
1212 
1213  if (!strcmp(name, "CCD_FRAME"))
1214  {
1215  // We are being asked to set CCD Frame
1216  if (IUUpdateNumber(&PrimaryCCD.ImageFrameNP, values, names, n) < 0)
1217  return false;
1218 
1219  PrimaryCCD.ImageFrameNP.s = IPS_OK;
1220 
1221  DEBUGF(Logger::DBG_DEBUG, "Requested CCD Frame is (%3.0f,%3.0f) (%3.0f x %3.0f)", values[0], values[1],
1222  values[2], values[3]);
1223 
1224  if (UpdateCCDFrame(PrimaryCCD.ImageFrameN[0].value, PrimaryCCD.ImageFrameN[1].value,
1225  PrimaryCCD.ImageFrameN[2].value, PrimaryCCD.ImageFrameN[3].value) == false)
1226  PrimaryCCD.ImageFrameNP.s = IPS_ALERT;
1227 
1228  IDSetNumber(&PrimaryCCD.ImageFrameNP, nullptr);
1229  return true;
1230  }
1231 
1232  if (!strcmp(name, "GUIDER_FRAME"))
1233  {
1234  // We are being asked to set guide frame
1235  if (IUUpdateNumber(&GuideCCD.ImageFrameNP, values, names, n) < 0)
1236  return false;
1237 
1238  GuideCCD.ImageFrameNP.s = IPS_OK;
1239 
1240  DEBUGF(Logger::DBG_DEBUG, "Requested Guide Frame is %4.0f,%4.0f %4.0f x %4.0f", values[0], values[1],
1241  values[2], values[4]);
1242 
1243  if (UpdateGuiderFrame(GuideCCD.ImageFrameN[0].value, GuideCCD.ImageFrameN[1].value,
1244  GuideCCD.ImageFrameN[2].value, GuideCCD.ImageFrameN[3].value) == false)
1245  GuideCCD.ImageFrameNP.s = IPS_ALERT;
1246 
1247  IDSetNumber(&GuideCCD.ImageFrameNP, nullptr);
1248 
1249  return true;
1250  }
1251 
1252  if (!strcmp(name, "CCD_GUIDESTAR"))
1253  {
1254  PrimaryCCD.RapidGuideDataNP.s = IPS_OK;
1255  IUUpdateNumber(&PrimaryCCD.RapidGuideDataNP, values, names, n);
1256  IDSetNumber(&PrimaryCCD.RapidGuideDataNP, nullptr);
1257  return true;
1258  }
1259 
1260  if (!strcmp(name, "GUIDER_GUIDESTAR"))
1261  {
1262  GuideCCD.RapidGuideDataNP.s = IPS_OK;
1263  IUUpdateNumber(&GuideCCD.RapidGuideDataNP, values, names, n);
1264  IDSetNumber(&GuideCCD.RapidGuideDataNP, nullptr);
1265  return true;
1266  }
1267 
1268  if (!strcmp(name, GuideNSNP.name) || !strcmp(name, GuideWENP.name))
1269  {
1270  processGuiderProperties(name, values, names, n);
1271  return true;
1272  }
1273 
1274  // CCD TEMPERATURE:
1275  if (!strcmp(name, TemperatureNP.name))
1276  {
1277  if (values[0] < TemperatureN[0].min || values[0] > TemperatureN[0].max)
1278  {
1279  TemperatureNP.s = IPS_ALERT;
1280  DEBUGF(Logger::DBG_ERROR, "Error: Bad temperature value! Range is [%.1f, %.1f] [C].",
1281  TemperatureN[0].min, TemperatureN[0].max);
1282  IDSetNumber(&TemperatureNP, nullptr);
1283  return false;
1284  }
1285 
1286  int rc = SetTemperature(values[0]);
1287 
1288  if (rc == 0)
1289  TemperatureNP.s = IPS_BUSY;
1290  else if (rc == 1)
1291  TemperatureNP.s = IPS_OK;
1292  else
1293  TemperatureNP.s = IPS_ALERT;
1294 
1295  IDSetNumber(&TemperatureNP, nullptr);
1296  return true;
1297  }
1298 
1299  // Primary CCD Info
1300  if (!strcmp(name, PrimaryCCD.ImagePixelSizeNP.name))
1301  {
1302  IUUpdateNumber(&PrimaryCCD.ImagePixelSizeNP, values, names, n);
1303  PrimaryCCD.ImagePixelSizeNP.s = IPS_OK;
1304  SetCCDParams(PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_X].value,
1305  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_Y].value, PrimaryCCD.getBPP(),
1306  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_X].value,
1307  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_Y].value);
1308  IDSetNumber(&PrimaryCCD.ImagePixelSizeNP, nullptr);
1309  return true;
1310  }
1311 
1312  // Guide CCD Info
1313  if (!strcmp(name, GuideCCD.ImagePixelSizeNP.name))
1314  {
1315  IUUpdateNumber(&GuideCCD.ImagePixelSizeNP, values, names, n);
1316  GuideCCD.ImagePixelSizeNP.s = IPS_OK;
1317  SetGuiderParams(GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_X].value,
1318  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_Y].value, GuideCCD.getBPP(),
1319  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_X].value,
1320  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_Y].value);
1321  IDSetNumber(&GuideCCD.ImagePixelSizeNP, nullptr);
1322  return true;
1323  }
1324 
1325  // CCD Rotation
1326  if (!strcmp(name, CCDRotationNP.name))
1327  {
1328  IUUpdateNumber(&CCDRotationNP, values, names, n);
1329  CCDRotationNP.s = IPS_OK;
1330  IDSetNumber(&CCDRotationNP, nullptr);
1331  ValidCCDRotation = true;
1332 
1333  DEBUGF(Logger::DBG_SESSION, "CCD FOV rotation updated to %g degrees.", CCDRotationN[0].value);
1334 
1335  return true;
1336  }
1337  }
1338 
1339 // Streamer
1340  if (HasStreaming())
1341  Streamer->ISNewNumber(dev, name, values, names, n);
1342 
1343  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
1344 }
1345 
1346 bool CCD::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1347 {
1348  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1349  {
1350  // Upload Mode
1351  if (!strcmp(name, UploadSP.name))
1352  {
1353  int prevMode = IUFindOnSwitchIndex(&UploadSP);
1354  IUUpdateSwitch(&UploadSP, states, names, n);
1355 
1356  if (UpdateCCDUploadMode(static_cast<CCD_UPLOAD_MODE>(IUFindOnSwitchIndex(&UploadSP))))
1357  {
1358  if (UploadS[UPLOAD_CLIENT].s == ISS_ON)
1359  {
1360  DEBUG(Logger::DBG_SESSION, "Upload settings set to client only.");
1361  if (prevMode != 0)
1362  deleteProperty(FileNameTP.name);
1363  }
1364  else if (UploadS[UPLOAD_LOCAL].s == ISS_ON)
1365  {
1366  DEBUG(Logger::DBG_SESSION, "Upload settings set to local only.");
1367  defineText(&FileNameTP);
1368  }
1369  else
1370  {
1371  DEBUG(Logger::DBG_SESSION, "Upload settings set to client and local.");
1372  defineText(&FileNameTP);
1373  }
1374 
1375  UploadSP.s = IPS_OK;
1376  }
1377  else
1378  {
1379  IUResetSwitch(&UploadSP);
1380  UploadS[prevMode].s = ISS_ON;
1381  UploadSP.s = IPS_ALERT;
1382  }
1383 
1384  IDSetSwitch(&UploadSP, nullptr);
1385 
1386  return true;
1387  }
1388 
1389  if (!strcmp(name, TelescopeTypeSP.name))
1390  {
1391  IUUpdateSwitch(&TelescopeTypeSP, states, names, n);
1392  TelescopeTypeSP.s = IPS_OK;
1393  IDSetSwitch(&TelescopeTypeSP, nullptr);
1394  return true;
1395  }
1396 
1397  // WCS Enable/Disable
1398  if (!strcmp(name, WorldCoordSP.name))
1399  {
1400  IUUpdateSwitch(&WorldCoordSP, states, names, n);
1401  WorldCoordSP.s = IPS_OK;
1402 
1403  if (WorldCoordS[0].s == ISS_ON)
1404  {
1405  DEBUG(Logger::DBG_WARNING, "World Coordinate System is enabled. CCD rotation must be set either "
1406  "manually or by solving the image before proceeding to capture any "
1407  "frames, otherwise the WCS information may be invalid.");
1408  defineNumber(&CCDRotationNP);
1409  }
1410  else
1411  {
1412  deleteProperty(CCDRotationNP.name);
1413  }
1414 
1415  ValidCCDRotation = false;
1416  IDSetSwitch(&WorldCoordSP, nullptr);
1417  }
1418 
1419  // Primary Chip Frame Reset
1420  if (strcmp(name, PrimaryCCD.ResetSP.name) == 0)
1421  {
1422  IUResetSwitch(&PrimaryCCD.ResetSP);
1423  PrimaryCCD.ResetSP.s = IPS_OK;
1424  if (CanBin())
1425  UpdateCCDBin(1, 1);
1426  if (CanSubFrame())
1427  UpdateCCDFrame(0, 0, PrimaryCCD.getXRes(), PrimaryCCD.getYRes());
1428 
1429  IDSetSwitch(&PrimaryCCD.ResetSP, nullptr);
1430  return true;
1431  }
1432 
1433  // Primary Chip Abort Expsoure
1434  if (strcmp(name, PrimaryCCD.AbortExposureSP.name) == 0)
1435  {
1436  IUResetSwitch(&PrimaryCCD.AbortExposureSP);
1437 
1438  if (AbortExposure())
1439  {
1440  PrimaryCCD.AbortExposureSP.s = IPS_OK;
1441  PrimaryCCD.ImageExposureNP.s = IPS_IDLE;
1442  PrimaryCCD.ImageExposureN[0].value = 0;
1443  }
1444  else
1445  {
1446  PrimaryCCD.AbortExposureSP.s = IPS_ALERT;
1447  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1448  }
1449 
1450  IDSetSwitch(&PrimaryCCD.AbortExposureSP, nullptr);
1451  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1452 
1453  return true;
1454  }
1455 
1456  // Guide Chip Abort Exposure
1457  if (strcmp(name, GuideCCD.AbortExposureSP.name) == 0)
1458  {
1459  IUResetSwitch(&GuideCCD.AbortExposureSP);
1460 
1461  if (AbortGuideExposure())
1462  {
1463  GuideCCD.AbortExposureSP.s = IPS_OK;
1464  GuideCCD.ImageExposureNP.s = IPS_IDLE;
1465  GuideCCD.ImageExposureN[0].value = 0;
1466  }
1467  else
1468  {
1469  GuideCCD.AbortExposureSP.s = IPS_ALERT;
1470  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1471  }
1472 
1473  IDSetSwitch(&GuideCCD.AbortExposureSP, nullptr);
1474  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1475 
1476  return true;
1477  }
1478 
1479  // Primary Chip Compression
1480  if (strcmp(name, PrimaryCCD.CompressSP.name) == 0)
1481  {
1482  IUUpdateSwitch(&PrimaryCCD.CompressSP, states, names, n);
1483  PrimaryCCD.CompressSP.s = IPS_OK;
1484  IDSetSwitch(&PrimaryCCD.CompressSP, nullptr);
1485 
1486  if (PrimaryCCD.CompressS[0].s == ISS_ON)
1487  {
1488  PrimaryCCD.SendCompressed = true;
1489  }
1490  else
1491  {
1492  PrimaryCCD.SendCompressed = false;
1493  }
1494  return true;
1495  }
1496 
1497  // Guide Chip Compression
1498  if (strcmp(name, GuideCCD.CompressSP.name) == 0)
1499  {
1500  IUUpdateSwitch(&GuideCCD.CompressSP, states, names, n);
1501  GuideCCD.CompressSP.s = IPS_OK;
1502  IDSetSwitch(&GuideCCD.CompressSP, nullptr);
1503 
1504  if (GuideCCD.CompressS[0].s == ISS_ON)
1505  {
1506  GuideCCD.SendCompressed = true;
1507  }
1508  else
1509  {
1510  GuideCCD.SendCompressed = false;
1511  }
1512  return true;
1513  }
1514 
1515  // Primary Chip Frame Type
1516  if (strcmp(name, PrimaryCCD.FrameTypeSP.name) == 0)
1517  {
1518  IUUpdateSwitch(&PrimaryCCD.FrameTypeSP, states, names, n);
1519  PrimaryCCD.FrameTypeSP.s = IPS_OK;
1520  if (PrimaryCCD.FrameTypeS[0].s == ISS_ON)
1521  PrimaryCCD.setFrameType(CCDChip::LIGHT_FRAME);
1522  else if (PrimaryCCD.FrameTypeS[1].s == ISS_ON)
1523  {
1524  PrimaryCCD.setFrameType(CCDChip::BIAS_FRAME);
1525  if (HasShutter() == false)
1527  "The CCD does not have a shutter. Cover the camera in order to take a bias frame.");
1528  }
1529  else if (PrimaryCCD.FrameTypeS[2].s == ISS_ON)
1530  {
1531  PrimaryCCD.setFrameType(CCDChip::DARK_FRAME);
1532  if (HasShutter() == false)
1534  "The CCD does not have a shutter. Cover the camera in order to take a dark frame.");
1535  }
1536  else if (PrimaryCCD.FrameTypeS[3].s == ISS_ON)
1537  PrimaryCCD.setFrameType(CCDChip::FLAT_FRAME);
1538 
1539  if (UpdateCCDFrameType(PrimaryCCD.getFrameType()) == false)
1540  PrimaryCCD.FrameTypeSP.s = IPS_ALERT;
1541 
1542  IDSetSwitch(&PrimaryCCD.FrameTypeSP, nullptr);
1543 
1544  return true;
1545  }
1546 
1547  // Guide Chip Frame Type
1548  if (strcmp(name, GuideCCD.FrameTypeSP.name) == 0)
1549  {
1550  // Compression Update
1551  IUUpdateSwitch(&GuideCCD.FrameTypeSP, states, names, n);
1552  GuideCCD.FrameTypeSP.s = IPS_OK;
1553  if (GuideCCD.FrameTypeS[0].s == ISS_ON)
1554  GuideCCD.setFrameType(CCDChip::LIGHT_FRAME);
1555  else if (GuideCCD.FrameTypeS[1].s == ISS_ON)
1556  {
1557  GuideCCD.setFrameType(CCDChip::BIAS_FRAME);
1558  if (HasShutter() == false)
1560  "The CCD does not have a shutter. Cover the camera in order to take a bias frame.");
1561  }
1562  else if (GuideCCD.FrameTypeS[2].s == ISS_ON)
1563  {
1564  GuideCCD.setFrameType(CCDChip::DARK_FRAME);
1565  if (HasShutter() == false)
1567  "The CCD does not have a shutter. Cover the camera in order to take a dark frame.");
1568  }
1569  else if (GuideCCD.FrameTypeS[3].s == ISS_ON)
1570  GuideCCD.setFrameType(CCDChip::FLAT_FRAME);
1571 
1572  if (UpdateGuiderFrameType(GuideCCD.getFrameType()) == false)
1573  GuideCCD.FrameTypeSP.s = IPS_ALERT;
1574 
1575  IDSetSwitch(&GuideCCD.FrameTypeSP, nullptr);
1576 
1577  return true;
1578  }
1579 
1580  // Primary Chip Rapid Guide Enable/Disable
1581  if (strcmp(name, PrimaryCCD.RapidGuideSP.name) == 0)
1582  {
1583  IUUpdateSwitch(&PrimaryCCD.RapidGuideSP, states, names, n);
1584  PrimaryCCD.RapidGuideSP.s = IPS_OK;
1585  RapidGuideEnabled = (PrimaryCCD.RapidGuideS[0].s == ISS_ON);
1586 
1587  if (RapidGuideEnabled)
1588  {
1589  defineSwitch(&PrimaryCCD.RapidGuideSetupSP);
1590  defineNumber(&PrimaryCCD.RapidGuideDataNP);
1591  }
1592  else
1593  {
1594  deleteProperty(PrimaryCCD.RapidGuideSetupSP.name);
1595  deleteProperty(PrimaryCCD.RapidGuideDataNP.name);
1596  }
1597 
1598  IDSetSwitch(&PrimaryCCD.RapidGuideSP, nullptr);
1599  return true;
1600  }
1601 
1602  // Guide Chip Rapid Guide Enable/Disable
1603  if (strcmp(name, GuideCCD.RapidGuideSP.name) == 0)
1604  {
1605  IUUpdateSwitch(&GuideCCD.RapidGuideSP, states, names, n);
1606  GuideCCD.RapidGuideSP.s = IPS_OK;
1607  GuiderRapidGuideEnabled = (GuideCCD.RapidGuideS[0].s == ISS_ON);
1608 
1609  if (GuiderRapidGuideEnabled)
1610  {
1611  defineSwitch(&GuideCCD.RapidGuideSetupSP);
1612  defineNumber(&GuideCCD.RapidGuideDataNP);
1613  }
1614  else
1615  {
1616  deleteProperty(GuideCCD.RapidGuideSetupSP.name);
1617  deleteProperty(GuideCCD.RapidGuideDataNP.name);
1618  }
1619 
1620  IDSetSwitch(&GuideCCD.RapidGuideSP, nullptr);
1621  return true;
1622  }
1623 
1624  // Primary CCD Rapid Guide Setup
1625  if (strcmp(name, PrimaryCCD.RapidGuideSetupSP.name) == 0)
1626  {
1627  IUUpdateSwitch(&PrimaryCCD.RapidGuideSetupSP, states, names, n);
1628  PrimaryCCD.RapidGuideSetupSP.s = IPS_OK;
1629 
1630  AutoLoop = (PrimaryCCD.RapidGuideSetupS[0].s == ISS_ON);
1631  SendImage = (PrimaryCCD.RapidGuideSetupS[1].s == ISS_ON);
1632  ShowMarker = (PrimaryCCD.RapidGuideSetupS[2].s == ISS_ON);
1633 
1634  IDSetSwitch(&PrimaryCCD.RapidGuideSetupSP, nullptr);
1635  return true;
1636  }
1637 
1638  // Guide Chip Rapid Guide Setup
1639  if (strcmp(name, GuideCCD.RapidGuideSetupSP.name) == 0)
1640  {
1641  IUUpdateSwitch(&GuideCCD.RapidGuideSetupSP, states, names, n);
1642  GuideCCD.RapidGuideSetupSP.s = IPS_OK;
1643 
1644  GuiderAutoLoop = (GuideCCD.RapidGuideSetupS[0].s == ISS_ON);
1645  GuiderSendImage = (GuideCCD.RapidGuideSetupS[1].s == ISS_ON);
1646  GuiderShowMarker = (GuideCCD.RapidGuideSetupS[2].s == ISS_ON);
1647 
1648  IDSetSwitch(&GuideCCD.RapidGuideSetupSP, nullptr);
1649  return true;
1650  }
1651  }
1652 
1653  if (HasStreaming())
1654  Streamer->ISNewSwitch(dev, name, states, names, n);
1655 
1656  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1657 }
1658 
1659 int CCD::SetTemperature(double temperature)
1660 {
1661  INDI_UNUSED(temperature);
1662  DEBUGF(Logger::DBG_WARNING, "CCD::SetTemperature %4.2f - Should never get here", temperature);
1663  return -1;
1664 }
1665 
1666 bool CCD::StartExposure(float duration)
1667 {
1668  DEBUGF(Logger::DBG_WARNING, "CCD::StartExposure %4.2f - Should never get here", duration);
1669  return false;
1670 }
1671 
1672 bool CCD::StartGuideExposure(float duration)
1673 {
1674  DEBUGF(Logger::DBG_WARNING, "CCD::StartGuide Exposure %4.2f - Should never get here", duration);
1675  return false;
1676 }
1677 
1679 {
1680  DEBUG(Logger::DBG_WARNING, "CCD::AbortExposure - Should never get here");
1681  return false;
1682 }
1683 
1685 {
1686  DEBUG(Logger::DBG_WARNING, "CCD::AbortGuideExposure - Should never get here");
1687  return false;
1688 }
1689 
1690 bool CCD::UpdateCCDFrame(int x, int y, int w, int h)
1691 {
1692  // Just set value, unless HW layer overrides this and performs its own processing
1693  PrimaryCCD.setFrame(x, y, w, h);
1694  return true;
1695 }
1696 
1697 bool CCD::UpdateGuiderFrame(int x, int y, int w, int h)
1698 {
1699  GuideCCD.setFrame(x, y, w, h);
1700  return true;
1701 }
1702 
1703 bool CCD::UpdateCCDBin(int hor, int ver)
1704 {
1705  // Just set value, unless HW layer overrides this and performs its own processing
1706  PrimaryCCD.setBin(hor, ver);
1707  // Reset size
1708  if (HasStreaming())
1709  Streamer->setSize(PrimaryCCD.getSubW()/hor, PrimaryCCD.getSubH()/ver);
1710  return true;
1711 }
1712 
1713 bool CCD::UpdateGuiderBin(int hor, int ver)
1714 {
1715  // Just set value, unless HW layer overrides this and performs its own processing
1716  GuideCCD.setBin(hor, ver);
1717  return true;
1718 }
1719 
1721 {
1722  INDI_UNUSED(fType);
1723  // Child classes can override this
1724  return true;
1725 }
1726 
1728 {
1729  INDI_UNUSED(fType);
1730  // Child classes can override this
1731  return true;
1732 }
1733 
1734 void CCD::addFITSKeywords(fitsfile *fptr, CCDChip *targetChip)
1735 {
1736  int status = 0;
1737  char frame_s[32];
1738  char dev_name[32];
1739  char exp_start[32];
1740  double exposureDuration;
1741  float pixSize1, pixSize2;
1742  unsigned int xbin, ybin;
1743 
1744  AutoCNumeric locale;
1745 
1746  xbin = targetChip->getBinX();
1747  ybin = targetChip->getBinY();
1748 
1749  char fitsString[MAXINDIDEVICE];
1750 
1751  // CCD
1752  strncpy(fitsString, getDeviceName(), MAXINDIDEVICE);
1753  fits_update_key_s(fptr, TSTRING, "INSTRUME", fitsString, "CCD Name", &status);
1754 
1755  // Telescope
1756  if (strlen(ActiveDeviceT[0].text) > 0)
1757  {
1758  strncpy(fitsString, ActiveDeviceT[0].text, MAXINDIDEVICE);
1759  fits_update_key_s(fptr, TSTRING, "TELESCOP", fitsString, "Telescope name", &status);
1760  }
1761 
1762 
1763  // Observer
1764  strncpy(fitsString, FITSHeaderT[FITS_OBSERVER].text, MAXINDIDEVICE);
1765  fits_update_key_s(fptr, TSTRING, "OBSERVER", fitsString, "Observer name", &status);
1766 
1767  // Object
1768  strncpy(fitsString, FITSHeaderT[FITS_OBJECT].text, MAXINDIDEVICE);
1769  fits_update_key_s(fptr, TSTRING, "OBJECT", fitsString, "Object name", &status);
1770 
1771  switch (targetChip->getFrameType())
1772  {
1773  case CCDChip::LIGHT_FRAME:
1774  strcpy(frame_s, "Light");
1775  break;
1776  case CCDChip::BIAS_FRAME:
1777  strcpy(frame_s, "Bias");
1778  break;
1779  case CCDChip::FLAT_FRAME:
1780  strcpy(frame_s, "Flat Field");
1781  break;
1782  case CCDChip::DARK_FRAME:
1783  strcpy(frame_s, "Dark");
1784  break;
1785  }
1786 
1787  exposureDuration = targetChip->getExposureDuration();
1788 
1789  pixSize1 = targetChip->getPixelSizeX();
1790  pixSize2 = targetChip->getPixelSizeY();
1791 
1792  strncpy(dev_name, getDeviceName(), 32);
1793  strncpy(exp_start, targetChip->getExposureStartTime(), 32);
1794 
1795  fits_update_key_s(fptr, TDOUBLE, "EXPTIME", &(exposureDuration), "Total Exposure Time (s)", &status);
1796 
1797  if (targetChip->getFrameType() == CCDChip::DARK_FRAME)
1798  fits_update_key_s(fptr, TDOUBLE, "DARKTIME", &(exposureDuration), "Total Exposure Time (s)", &status);
1799 
1800  if (HasCooler())
1801  fits_update_key_s(fptr, TDOUBLE, "CCD-TEMP", &(TemperatureN[0].value), "CCD Temperature (Celsius)", &status);
1802 
1803  fits_update_key_s(fptr, TFLOAT, "PIXSIZE1", &(pixSize1), "Pixel Size 1 (microns)", &status);
1804  fits_update_key_s(fptr, TFLOAT, "PIXSIZE2", &(pixSize2), "Pixel Size 2 (microns)", &status);
1805  fits_update_key_s(fptr, TUINT, "XBINNING", &(xbin), "Binning factor in width", &status);
1806  fits_update_key_s(fptr, TUINT, "YBINNING", &(ybin), "Binning factor in height", &status);
1807  fits_update_key_s(fptr, TSTRING, "FRAME", frame_s, "Frame Type", &status);
1808  if (CurrentFilterSlot != -1 && CurrentFilterSlot <= (int)FilterNames.size())
1809  {
1810  char filter[32];
1811  strncpy(filter, FilterNames.at(CurrentFilterSlot - 1).c_str(), 32);
1812  fits_update_key_s(fptr, TSTRING, "FILTER", filter, "Filter", &status);
1813  }
1814 
1815 #ifdef WITH_MINMAX
1816  if (targetChip->getNAxis() == 2)
1817  {
1818  double min_val, max_val;
1819  getMinMax(&min_val, &max_val, targetChip);
1820 
1821  fits_update_key_s(fptr, TDOUBLE, "DATAMIN", &min_val, "Minimum value", &status);
1822  fits_update_key_s(fptr, TDOUBLE, "DATAMAX", &max_val, "Maximum value", &status);
1823  }
1824 #endif
1825 
1826  if (HasBayer() && targetChip->getNAxis() == 2)
1827  {
1828  unsigned int bayer_offset_x = atoi(BayerT[0].text);
1829  unsigned int bayer_offset_y = atoi(BayerT[1].text);
1830 
1831  fits_update_key_s(fptr, TUINT, "XBAYROFF", &bayer_offset_x, "X offset of Bayer array", &status);
1832  fits_update_key_s(fptr, TUINT, "YBAYROFF", &bayer_offset_y, "Y offset of Bayer array", &status);
1833  fits_update_key_s(fptr, TSTRING, "BAYERPAT", BayerT[2].text, "Bayer color pattern", &status);
1834  }
1835 
1836  if (TelescopeTypeS[TELESCOPE_PRIMARY].s == ISS_ON && primaryFocalLength != -1)
1837  fits_update_key_s(fptr, TDOUBLE, "FOCALLEN", &primaryFocalLength, "Focal Length (mm)", &status);
1838  else if (TelescopeTypeS[TELESCOPE_GUIDE].s == ISS_ON && guiderFocalLength != -1)
1839  fits_update_key_s(fptr, TDOUBLE, "FOCALLEN", &guiderFocalLength, "Focal Length (mm)", &status);
1840 
1841  if (!std::isnan(MPSAS))
1842  {
1843  fits_update_key_s(fptr, TDOUBLE, "MPSAS", &MPSAS, "Sky Quality (mag per arcsec^2)", &status);
1844  }
1845 
1846  if (!std::isnan(RotatorAngle))
1847  {
1848  fits_update_key_s(fptr, TDOUBLE, "ROTATANG", &MPSAS, "Rotator angle in degrees", &status);
1849  }
1850 
1851  if (targetChip->getFrameType() == CCDChip::LIGHT_FRAME && !std::isnan(J2000RA) && !std::isnan(J2000DE))
1852  {
1853  char ra_str[32], de_str[32];
1854 
1855  fs_sexa(ra_str, J2000RA, 2, 360000);
1856  fs_sexa(de_str, J2000DE, 2, 360000);
1857 
1858  char *raPtr = ra_str, *dePtr = de_str;
1859  while (*raPtr != '\0')
1860  {
1861  if (*raPtr == ':')
1862  *raPtr = ' ';
1863  raPtr++;
1864  }
1865  while (*dePtr != '\0')
1866  {
1867  if (*dePtr == ':')
1868  *dePtr = ' ';
1869  dePtr++;
1870  }
1871 
1872  if (!std::isnan(Airmass))
1873  fits_update_key_s(fptr, TDOUBLE, "AIRMASS", &Airmass, "Airmass", &status);
1874 
1875  fits_update_key_s(fptr, TSTRING, "OBJCTRA", ra_str, "Object RA", &status);
1876  fits_update_key_s(fptr, TSTRING, "OBJCTDEC", de_str, "Object DEC", &status);
1877 
1878  int epoch = 2000;
1879 
1880  //fits_update_key_s(fptr, TINT, "EPOCH", &epoch, "Epoch", &status);
1881  fits_update_key_s(fptr, TINT, "EQUINOX", &epoch, "Equinox", &status);
1882 
1883  // Add WCS Info
1884  if (WorldCoordS[0].s == ISS_ON && ValidCCDRotation && primaryFocalLength != -1)
1885  {
1886  double J2000RAHours = J2000RA * 15;
1887  fits_update_key_s(fptr, TDOUBLE, "CRVAL1", &J2000RAHours, "CRVAL1", &status);
1888  fits_update_key_s(fptr, TDOUBLE, "CRVAL2", &J2000DE, "CRVAL1", &status);
1889 
1890  char radecsys[8] = "FK5";
1891  char ctype1[16] = "RA---TAN";
1892  char ctype2[16] = "DEC--TAN";
1893 
1894  fits_update_key_s(fptr, TSTRING, "RADECSYS", radecsys, "RADECSYS", &status);
1895  fits_update_key_s(fptr, TSTRING, "CTYPE1", ctype1, "CTYPE1", &status);
1896  fits_update_key_s(fptr, TSTRING, "CTYPE2", ctype2, "CTYPE2", &status);
1897 
1898  double crpix1 = targetChip->getSubW() / targetChip->getBinX() / 2.0;
1899  double crpix2 = targetChip->getSubH() / targetChip->getBinY() / 2.0;
1900 
1901  fits_update_key_s(fptr, TDOUBLE, "CRPIX1", &crpix1, "CRPIX1", &status);
1902  fits_update_key_s(fptr, TDOUBLE, "CRPIX2", &crpix2, "CRPIX2", &status);
1903 
1904  double secpix1 = pixSize1 / primaryFocalLength * 206.3 * targetChip->getBinX();
1905  double secpix2 = pixSize2 / primaryFocalLength * 206.3 * targetChip->getBinY();
1906 
1907  //double secpix1 = pixSize1 / FocalLength * 206.3;
1908  //double secpix2 = pixSize2 / FocalLength * 206.3;
1909 
1910  fits_update_key_s(fptr, TDOUBLE, "SECPIX1", &secpix1, "SECPIX1", &status);
1911  fits_update_key_s(fptr, TDOUBLE, "SECPIX2", &secpix2, "SECPIX2", &status);
1912 
1913  double degpix1 = secpix1 / 3600.0;
1914  double degpix2 = secpix2 / 3600.0;
1915 
1916  fits_update_key_s(fptr, TDOUBLE, "CDELT1", &degpix1, "CDELT1", &status);
1917  fits_update_key_s(fptr, TDOUBLE, "CDELT2", &degpix2, "CDELT2", &status);
1918 
1919  // Rotation is CW, we need to convert it to CCW per CROTA1 definition
1920  double rotation = 360 - CCDRotationN[0].value;
1921  if (rotation > 360)
1922  rotation -= 360;
1923 
1924  fits_update_key_s(fptr, TDOUBLE, "CROTA1", &rotation, "CROTA1", &status);
1925  fits_update_key_s(fptr, TDOUBLE, "CROTA2", &rotation, "CROTA2", &status);
1926 
1927  /*double cd[4];
1928  cd[0] = degpix1;
1929  cd[1] = 0;
1930  cd[2] = 0;
1931  cd[3] = degpix2;
1932 
1933  fits_update_key_s(fptr, TDOUBLE, "CD1_1", &cd[0], "CD1_1", &status);
1934  fits_update_key_s(fptr, TDOUBLE, "CD1_2", &cd[1], "CD1_2", &status);
1935  fits_update_key_s(fptr, TDOUBLE, "CD2_1", &cd[2], "CD2_1", &status);
1936  fits_update_key_s(fptr, TDOUBLE, "CD2_2", &cd[3], "CD2_2", &status);*/
1937  }
1938  }
1939 
1940  fits_update_key_s(fptr, TSTRING, "DATE-OBS", exp_start, "UTC start date of observation", &status);
1941  fits_write_comment(fptr, "Generated by INDI", &status);
1942 }
1943 
1944 void CCD::fits_update_key_s(fitsfile *fptr, int type, std::string name, void *p, std::string explanation,
1945  int *status)
1946 {
1947  // this function is for removing warnings about deprecated string conversion to char* (from arg 5)
1948  fits_update_key(fptr, type, name.c_str(), p, const_cast<char *>(explanation.c_str()), status);
1949 }
1950 
1952 {
1953  bool sendImage = (UploadS[0].s == ISS_ON || UploadS[2].s == ISS_ON);
1954  bool saveImage = (UploadS[1].s == ISS_ON || UploadS[2].s == ISS_ON);
1955  //bool useSolver = (SolverS[0].s == ISS_ON);
1956  bool showMarker = false;
1957  bool autoLoop = false;
1958  bool sendData = false;
1959 
1960  if (RapidGuideEnabled && targetChip == &PrimaryCCD && (PrimaryCCD.getBPP() == 16 || PrimaryCCD.getBPP() == 8))
1961  {
1962  autoLoop = AutoLoop;
1963  sendImage = SendImage;
1964  showMarker = ShowMarker;
1965  sendData = true;
1966  saveImage = false;
1967  }
1968 
1969  if (GuiderRapidGuideEnabled && targetChip == &GuideCCD && (GuideCCD.getBPP() == 16 || PrimaryCCD.getBPP() == 8))
1970  {
1971  autoLoop = GuiderAutoLoop;
1972  sendImage = GuiderSendImage;
1973  showMarker = GuiderShowMarker;
1974  sendData = true;
1975  saveImage = false;
1976  }
1977 
1978  if (sendData)
1979  {
1980  static double P0 = 0.906, P1 = 0.584, P2 = 0.365, P3 = 0.117, P4 = 0.049, P5 = -0.05, P6 = -0.064, P7 = -0.074,
1981  P8 = -0.094;
1982  targetChip->RapidGuideDataNP.s = IPS_BUSY;
1983  int width = targetChip->getSubW() / targetChip->getBinX();
1984  int height = targetChip->getSubH() / targetChip->getBinY();
1985  void *src = (unsigned short *)targetChip->getFrameBuffer();
1986  int i0, i1, i2, i3, i4, i5, i6, i7, i8;
1987  int ix = 0, iy = 0;
1988  int xM4;
1989  double average, fit, bestFit = 0;
1990  int minx = 4;
1991  int maxx = width - 4;
1992  int miny = 4;
1993  int maxy = height - 4;
1994  if (targetChip->lastRapidX > 0 && targetChip->lastRapidY > 0)
1995  {
1996  minx = std::max(targetChip->lastRapidX - 20, 4);
1997  maxx = std::min(targetChip->lastRapidX + 20, width - 4);
1998  miny = std::max(targetChip->lastRapidY - 20, 4);
1999  maxy = std::min(targetChip->lastRapidY + 20, height - 4);
2000  }
2001  if (targetChip->getBPP() == 16)
2002  {
2003  unsigned short *p;
2004  for (int x = minx; x < maxx; x++)
2005  for (int y = miny; y < maxy; y++)
2006  {
2007  i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = 0;
2008  xM4 = x - 4;
2009  p = (unsigned short *)src + (y - 4) * width + xM4;
2010  i8 += *p++;
2011  i8 += *p++;
2012  i8 += *p++;
2013  i8 += *p++;
2014  i8 += *p++;
2015  i8 += *p++;
2016  i8 += *p++;
2017  i8 += *p++;
2018  i8 += *p++;
2019  p = (unsigned short *)src + (y - 3) * width + xM4;
2020  i8 += *p++;
2021  i8 += *p++;
2022  i8 += *p++;
2023  i7 += *p++;
2024  i6 += *p++;
2025  i7 += *p++;
2026  i8 += *p++;
2027  i8 += *p++;
2028  i8 += *p++;
2029  p = (unsigned short *)src + (y - 2) * width + xM4;
2030  i8 += *p++;
2031  i8 += *p++;
2032  i5 += *p++;
2033  i4 += *p++;
2034  i3 += *p++;
2035  i4 += *p++;
2036  i5 += *p++;
2037  i8 += *p++;
2038  i8 += *p++;
2039  p = (unsigned short *)src + (y - 1) * width + xM4;
2040  i8 += *p++;
2041  i7 += *p++;
2042  i4 += *p++;
2043  i2 += *p++;
2044  i1 += *p++;
2045  i2 += *p++;
2046  i4 += *p++;
2047  i8 += *p++;
2048  i8 += *p++;
2049  p = (unsigned short *)src + (y + 0) * width + xM4;
2050  i8 += *p++;
2051  i6 += *p++;
2052  i3 += *p++;
2053  i1 += *p++;
2054  i0 += *p++;
2055  i1 += *p++;
2056  i3 += *p++;
2057  i6 += *p++;
2058  i8 += *p++;
2059  p = (unsigned short *)src + (y + 1) * width + xM4;
2060  i8 += *p++;
2061  i7 += *p++;
2062  i4 += *p++;
2063  i2 += *p++;
2064  i1 += *p++;
2065  i2 += *p++;
2066  i4 += *p++;
2067  i8 += *p++;
2068  i8 += *p++;
2069  p = (unsigned short *)src + (y + 2) * width + xM4;
2070  i8 += *p++;
2071  i8 += *p++;
2072  i5 += *p++;
2073  i4 += *p++;
2074  i3 += *p++;
2075  i4 += *p++;
2076  i5 += *p++;
2077  i8 += *p++;
2078  i8 += *p++;
2079  p = (unsigned short *)src + (y + 3) * width + xM4;
2080  i8 += *p++;
2081  i8 += *p++;
2082  i8 += *p++;
2083  i7 += *p++;
2084  i6 += *p++;
2085  i7 += *p++;
2086  i8 += *p++;
2087  i8 += *p++;
2088  i8 += *p++;
2089  p = (unsigned short *)src + (y + 4) * width + xM4;
2090  i8 += *p++;
2091  i8 += *p++;
2092  i8 += *p++;
2093  i8 += *p++;
2094  i8 += *p++;
2095  i8 += *p++;
2096  i8 += *p++;
2097  i8 += *p++;
2098  i8 += *p++;
2099  average = (i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8) / 85.0;
2100  fit = P0 * (i0 - average) + P1 * (i1 - 4 * average) + P2 * (i2 - 4 * average) +
2101  P3 * (i3 - 4 * average) + P4 * (i4 - 8 * average) + P5 * (i5 - 4 * average) +
2102  P6 * (i6 - 4 * average) + P7 * (i7 - 8 * average) + P8 * (i8 - 48 * average);
2103  if (bestFit < fit)
2104  {
2105  bestFit = fit;
2106  ix = x;
2107  iy = y;
2108  }
2109  }
2110  }
2111  else
2112  {
2113  unsigned char *p;
2114  for (int x = minx; x < maxx; x++)
2115  for (int y = miny; y < maxy; y++)
2116  {
2117  i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = 0;
2118  xM4 = x - 4;
2119  p = (unsigned char *)src + (y - 4) * width + xM4;
2120  i8 += *p++;
2121  i8 += *p++;
2122  i8 += *p++;
2123  i8 += *p++;
2124  i8 += *p++;
2125  i8 += *p++;
2126  i8 += *p++;
2127  i8 += *p++;
2128  i8 += *p++;
2129  p = (unsigned char *)src + (y - 3) * width + xM4;
2130  i8 += *p++;
2131  i8 += *p++;
2132  i8 += *p++;
2133  i7 += *p++;
2134  i6 += *p++;
2135  i7 += *p++;
2136  i8 += *p++;
2137  i8 += *p++;
2138  i8 += *p++;
2139  p = (unsigned char *)src + (y - 2) * width + xM4;
2140  i8 += *p++;
2141  i8 += *p++;
2142  i5 += *p++;
2143  i4 += *p++;
2144  i3 += *p++;
2145  i4 += *p++;
2146  i5 += *p++;
2147  i8 += *p++;
2148  i8 += *p++;
2149  p = (unsigned char *)src + (y - 1) * width + xM4;
2150  i8 += *p++;
2151  i7 += *p++;
2152  i4 += *p++;
2153  i2 += *p++;
2154  i1 += *p++;
2155  i2 += *p++;
2156  i4 += *p++;
2157  i8 += *p++;
2158  i8 += *p++;
2159  p = (unsigned char *)src + (y + 0) * width + xM4;
2160  i8 += *p++;
2161  i6 += *p++;
2162  i3 += *p++;
2163  i1 += *p++;
2164  i0 += *p++;
2165  i1 += *p++;
2166  i3 += *p++;
2167  i6 += *p++;
2168  i8 += *p++;
2169  p = (unsigned char *)src + (y + 1) * width + xM4;
2170  i8 += *p++;
2171  i7 += *p++;
2172  i4 += *p++;
2173  i2 += *p++;
2174  i1 += *p++;
2175  i2 += *p++;
2176  i4 += *p++;
2177  i8 += *p++;
2178  i8 += *p++;
2179  p = (unsigned char *)src + (y + 2) * width + xM4;
2180  i8 += *p++;
2181  i8 += *p++;
2182  i5 += *p++;
2183  i4 += *p++;
2184  i3 += *p++;
2185  i4 += *p++;
2186  i5 += *p++;
2187  i8 += *p++;
2188  i8 += *p++;
2189  p = (unsigned char *)src + (y + 3) * width + xM4;
2190  i8 += *p++;
2191  i8 += *p++;
2192  i8 += *p++;
2193  i7 += *p++;
2194  i6 += *p++;
2195  i7 += *p++;
2196  i8 += *p++;
2197  i8 += *p++;
2198  i8 += *p++;
2199  p = (unsigned char *)src + (y + 4) * width + xM4;
2200  i8 += *p++;
2201  i8 += *p++;
2202  i8 += *p++;
2203  i8 += *p++;
2204  i8 += *p++;
2205  i8 += *p++;
2206  i8 += *p++;
2207  i8 += *p++;
2208  i8 += *p++;
2209  average = (i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8) / 85.0;
2210  fit = P0 * (i0 - average) + P1 * (i1 - 4 * average) + P2 * (i2 - 4 * average) +
2211  P3 * (i3 - 4 * average) + P4 * (i4 - 8 * average) + P5 * (i5 - 4 * average) +
2212  P6 * (i6 - 4 * average) + P7 * (i7 - 8 * average) + P8 * (i8 - 48 * average);
2213  if (bestFit < fit)
2214  {
2215  bestFit = fit;
2216  ix = x;
2217  iy = y;
2218  }
2219  }
2220  }
2221 
2222  targetChip->RapidGuideDataN[0].value = ix;
2223  targetChip->RapidGuideDataN[1].value = iy;
2224  targetChip->RapidGuideDataN[2].value = bestFit;
2225  targetChip->lastRapidX = ix;
2226  targetChip->lastRapidY = iy;
2227  if (bestFit > 50)
2228  {
2229  int sumX = 0;
2230  int sumY = 0;
2231  int total = 0;
2232  int max = 0;
2233  int noiseThreshold = 0;
2234 
2235  if (targetChip->getBPP() == 16)
2236  {
2237  unsigned short *p;
2238  for (int y = iy - 4; y <= iy + 4; y++)
2239  {
2240  p = (unsigned short *)src + y * width + ix - 4;
2241  for (int x = ix - 4; x <= ix + 4; x++)
2242  {
2243  int w = *p++;
2244  noiseThreshold += w;
2245  if (w > max)
2246  max = w;
2247  }
2248  }
2249  noiseThreshold = (noiseThreshold / 81 + max) / 2; // set threshold between peak and average
2250  for (int y = iy - 4; y <= iy + 4; y++)
2251  {
2252  p = (unsigned short *)src + y * width + ix - 4;
2253  for (int x = ix - 4; x <= ix + 4; x++)
2254  {
2255  int w = *p++;
2256  if (w < noiseThreshold)
2257  w = 0;
2258  sumX += x * w;
2259  sumY += y * w;
2260  total += w;
2261  }
2262  }
2263  }
2264  else
2265  {
2266  unsigned char *p;
2267  for (int y = iy - 4; y <= iy + 4; y++)
2268  {
2269  p = (unsigned char *)src + y * width + ix - 4;
2270  for (int x = ix - 4; x <= ix + 4; x++)
2271  {
2272  int w = *p++;
2273  noiseThreshold += w;
2274  if (w > max)
2275  max = w;
2276  }
2277  }
2278  noiseThreshold = (noiseThreshold / 81 + max) / 2; // set threshold between peak and average
2279  for (int y = iy - 4; y <= iy + 4; y++)
2280  {
2281  p = (unsigned char *)src + y * width + ix - 4;
2282  for (int x = ix - 4; x <= ix + 4; x++)
2283  {
2284  int w = *p++;
2285  if (w < noiseThreshold)
2286  w = 0;
2287  sumX += x * w;
2288  sumY += y * w;
2289  total += w;
2290  }
2291  }
2292  }
2293 
2294  if (total > 0)
2295  {
2296  targetChip->RapidGuideDataN[0].value = ((double)sumX) / total;
2297  targetChip->RapidGuideDataN[1].value = ((double)sumY) / total;
2298  targetChip->RapidGuideDataNP.s = IPS_OK;
2299 
2300  DEBUGF(Logger::DBG_DEBUG, "Guide Star X: %g Y: %g FIT: %g", targetChip->RapidGuideDataN[0].value,
2301  targetChip->RapidGuideDataN[1].value, targetChip->RapidGuideDataN[2].value);
2302  }
2303  else
2304  {
2305  targetChip->RapidGuideDataNP.s = IPS_ALERT;
2306  targetChip->lastRapidX = targetChip->lastRapidY = -1;
2307  }
2308  }
2309  else
2310  {
2311  targetChip->RapidGuideDataNP.s = IPS_ALERT;
2312  targetChip->lastRapidX = targetChip->lastRapidY = -1;
2313  }
2314  IDSetNumber(&targetChip->RapidGuideDataNP, nullptr);
2315 
2316  if (showMarker)
2317  {
2318  int xmin = std::max(ix - 10, 0);
2319  int xmax = std::min(ix + 10, width - 1);
2320  int ymin = std::max(iy - 10, 0);
2321  int ymax = std::min(iy + 10, height - 1);
2322 
2323  //fprintf(stderr, "%d %d %d %d\n", xmin, xmax, ymin, ymax);
2324 
2325  if (targetChip->getBPP() == 16)
2326  {
2327  unsigned short *p;
2328  if (ymin > 0)
2329  {
2330  p = (unsigned short *)src + ymin * width + xmin;
2331  for (int x = xmin; x <= xmax; x++)
2332  *p++ = 50000;
2333  }
2334 
2335  if (xmin > 0)
2336  {
2337  for (int y = ymin; y <= ymax; y++)
2338  {
2339  *((unsigned short *)src + y * width + xmin) = 50000;
2340  }
2341  }
2342 
2343  if (xmax < width - 1)
2344  {
2345  for (int y = ymin; y <= ymax; y++)
2346  {
2347  *((unsigned short *)src + y * width + xmax) = 50000;
2348  }
2349  }
2350 
2351  if (ymax < height - 1)
2352  {
2353  p = (unsigned short *)src + ymax * width + xmin;
2354  for (int x = xmin; x <= xmax; x++)
2355  *p++ = 50000;
2356  }
2357  }
2358  else
2359  {
2360  unsigned char *p;
2361  if (ymin > 0)
2362  {
2363  p = (unsigned char *)src + ymin * width + xmin;
2364  for (int x = xmin; x <= xmax; x++)
2365  *p++ = 255;
2366  }
2367 
2368  if (xmin > 0)
2369  {
2370  for (int y = ymin; y <= ymax; y++)
2371  {
2372  *((unsigned char *)src + y * width + xmin) = 255;
2373  }
2374  }
2375 
2376  if (xmax < width - 1)
2377  {
2378  for (int y = ymin; y <= ymax; y++)
2379  {
2380  *((unsigned char *)src + y * width + xmax) = 255;
2381  }
2382  }
2383 
2384  if (ymax < height - 1)
2385  {
2386  p = (unsigned char *)src + ymax * width + xmin;
2387  for (int x = xmin; x <= xmax; x++)
2388  *p++ = 255;
2389  }
2390  }
2391  }
2392  }
2393 
2394  if (sendImage || saveImage /* || useSolver*/)
2395  {
2396  if (!strcmp(targetChip->getImageExtension(), "fits"))
2397  {
2398  void *memptr;
2399  size_t memsize;
2400  int img_type = 0;
2401  int byte_type = 0;
2402  int status = 0;
2403  long naxis = targetChip->getNAxis();
2404  long naxes[naxis];
2405  int nelements = 0;
2406  std::string bit_depth;
2407  char error_status[MAXRBUF];
2408 
2409  fitsfile *fptr = nullptr;
2410 
2411  naxes[0] = targetChip->getSubW() / targetChip->getBinX();
2412  naxes[1] = targetChip->getSubH() / targetChip->getBinY();
2413 
2414  switch (targetChip->getBPP())
2415  {
2416  case 8:
2417  byte_type = TBYTE;
2418  img_type = BYTE_IMG;
2419  bit_depth = "8 bits per pixel";
2420  break;
2421 
2422  case 16:
2423  byte_type = TUSHORT;
2424  img_type = USHORT_IMG;
2425  bit_depth = "16 bits per pixel";
2426  break;
2427 
2428  case 32:
2429  byte_type = TULONG;
2430  img_type = ULONG_IMG;
2431  bit_depth = "32 bits per pixel";
2432  break;
2433 
2434  default:
2435  DEBUGF(Logger::DBG_ERROR, "Unsupported bits per pixel value %d", targetChip->getBPP());
2436  return false;
2437  break;
2438  }
2439 
2440  nelements = naxes[0] * naxes[1];
2441  if (naxis == 3)
2442  {
2443  nelements *= 3;
2444  naxes[2] = 3;
2445  }
2446 
2447  /*DEBUGF(Logger::DBG_DEBUG, "Exposure complete. Image Depth: %s. Width: %d Height: %d nelements: %d", bit_depth.c_str(), naxes[0],
2448  naxes[1], nelements);*/
2449 
2450  // Now we have to send fits format data to the client
2451  memsize = 5760;
2452  memptr = malloc(memsize);
2453  if (!memptr)
2454  {
2455  DEBUGF(Logger::DBG_ERROR, "Error: failed to allocate memory: %lu", (unsigned long)memsize);
2456  }
2457 
2458  fits_create_memfile(&fptr, &memptr, &memsize, 2880, realloc, &status);
2459 
2460  if (status)
2461  {
2462  fits_report_error(stderr, status); /* print out any error messages */
2463  fits_get_errstatus(status, error_status);
2464  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
2465  return false;
2466  }
2467 
2468  fits_create_img(fptr, img_type, naxis, naxes, &status);
2469 
2470  if (status)
2471  {
2472  fits_report_error(stderr, status); /* print out any error messages */
2473  fits_get_errstatus(status, error_status);
2474  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
2475  return false;
2476  }
2477 
2478  addFITSKeywords(fptr, targetChip);
2479 
2480  fits_write_img(fptr, byte_type, 1, nelements, targetChip->getFrameBuffer(), &status);
2481 
2482  if (status)
2483  {
2484  fits_report_error(stderr, status); /* print out any error messages */
2485  fits_get_errstatus(status, error_status);
2486  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
2487  return false;
2488  }
2489 
2490  fits_close_file(fptr, &status);
2491 
2492  bool rc = uploadFile(targetChip, memptr, memsize, sendImage, saveImage /*, useSolver*/);
2493 
2494  free(memptr);
2495 
2496  if (rc == false)
2497  {
2498  targetChip->setExposureFailed();
2499  return false;
2500  }
2501  }
2502  else
2503  {
2504  bool rc = uploadFile(targetChip, targetChip->getFrameBuffer(), targetChip->getFrameBufferSize(), sendImage,
2505  saveImage);
2506 
2507  if (rc == false)
2508  {
2509  targetChip->setExposureFailed();
2510  return false;
2511  }
2512  }
2513  }
2514 
2515  targetChip->ImageExposureNP.s = IPS_OK;
2516  IDSetNumber(&targetChip->ImageExposureNP, nullptr);
2517 
2518  if (autoLoop)
2519  {
2520  if (targetChip == &PrimaryCCD)
2521  {
2522  PrimaryCCD.ImageExposureN[0].value = ExposureTime;
2523  if (StartExposure(ExposureTime))
2524  {
2525  // Record information required later in creation of FITS header
2526  if (targetChip->getFrameType() == CCDChip::LIGHT_FRAME && !std::isnan(RA) && !std::isnan(Dec))
2527  {
2528  ln_equ_posn epochPos { 0, 0 }, J2000Pos { 0, 0 };
2529  epochPos.ra = RA * 15.0;
2530  epochPos.dec = Dec;
2531 
2532  // Convert from JNow to J2000
2533  ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
2534 
2535  J2000RA = J2000Pos.ra / 15.0;
2536  J2000DE = J2000Pos.dec;
2537 
2538  if (!std::isnan(Latitude) && !std::isnan(Longitude))
2539  {
2540  // Horizontal Coords
2541  ln_hrz_posn horizontalPos;
2542  ln_lnlat_posn observer;
2543  observer.lat = Latitude;
2544  observer.lng = Longitude;
2545 
2546  ln_get_hrz_from_equ(&epochPos, &observer, ln_get_julian_from_sys(), &horizontalPos);
2547  Airmass = ln_get_airmass(horizontalPos.alt, 750);
2548  }
2549  }
2550 
2551  PrimaryCCD.ImageExposureNP.s = IPS_BUSY;
2552  }
2553  else
2554  {
2555  DEBUG(Logger::DBG_DEBUG, "Autoloop: Primary CCD Exposure Error!");
2556  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
2557  }
2558 
2559  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
2560  }
2561  else
2562  {
2563  GuideCCD.ImageExposureN[0].value = GuiderExposureTime;
2564  GuideCCD.ImageExposureNP.s = IPS_BUSY;
2565  if (StartGuideExposure(GuiderExposureTime))
2566  GuideCCD.ImageExposureNP.s = IPS_BUSY;
2567  else
2568  {
2569  DEBUG(Logger::DBG_DEBUG, "Autoloop: Guide CCD Exposure Error!");
2570  GuideCCD.ImageExposureNP.s = IPS_ALERT;
2571  }
2572 
2573  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
2574  }
2575  }
2576 
2577  return true;
2578 }
2579 
2580 bool CCD::uploadFile(CCDChip *targetChip, const void *fitsData, size_t totalBytes, bool sendImage,
2581  bool saveImage /*, bool useSolver*/)
2582 {
2583  unsigned char *compressedData = nullptr;
2584  uLongf compressedBytes = 0;
2585 
2586  DEBUGF(Logger::DBG_DEBUG, "Uploading file. Ext: %s, Size: %d, sendImage? %s, saveImage? %s",
2587  targetChip->getImageExtension(), totalBytes, sendImage ? "Yes" : "No", saveImage ? "Yes" : "No");
2588 
2589  if (saveImage)
2590  {
2591  targetChip->FitsB.blob = (unsigned char *)fitsData;
2592  targetChip->FitsB.bloblen = totalBytes;
2593  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s", targetChip->getImageExtension());
2594 
2595  FILE *fp = nullptr;
2596  char imageFileName[MAXRBUF];
2597 
2598  std::string prefix = UploadSettingsT[UPLOAD_PREFIX].text;
2599  int maxIndex = getFileIndex(UploadSettingsT[UPLOAD_DIR].text, UploadSettingsT[UPLOAD_PREFIX].text,
2600  targetChip->FitsB.format);
2601 
2602  if (maxIndex < 0)
2603  {
2604  DEBUGF(Logger::DBG_ERROR, "Error iterating directory %s. %s", UploadSettingsT[0].text,
2605  strerror(errno));
2606  return false;
2607  }
2608 
2609  if (maxIndex > 0)
2610  {
2611  char ts[32];
2612  struct tm *tp;
2613  time_t t;
2614  time(&t);
2615  tp = localtime(&t);
2616  strftime(ts, sizeof(ts), "%Y-%m-%dT%H-%M-%S", tp);
2617  std::string filets(ts);
2618  prefix = std::regex_replace(prefix, std::regex("ISO8601"), filets);
2619 
2620  char indexString[8];
2621  snprintf(indexString, 8, "%03d", maxIndex);
2622  std::string prefixIndex = indexString;
2623  //prefix.replace(prefix.find("XXX"), std::string::npos, prefixIndex);
2624  prefix = std::regex_replace(prefix, std::regex("XXX"), prefixIndex);
2625  }
2626 
2627  snprintf(imageFileName, MAXRBUF, "%s/%s%s", UploadSettingsT[0].text, prefix.c_str(), targetChip->FitsB.format);
2628 
2629  fp = fopen(imageFileName, "w");
2630  if (fp == nullptr)
2631  {
2632  DEBUGF(Logger::DBG_ERROR, "Unable to save image file (%s). %s", imageFileName, strerror(errno));
2633  return false;
2634  }
2635 
2636  int n = 0;
2637  for (int nr = 0; nr < (int)targetChip->FitsB.bloblen; nr += n)
2638  n = fwrite((static_cast<char *>(targetChip->FitsB.blob) + nr), 1, targetChip->FitsB.bloblen - nr, fp);
2639 
2640  fclose(fp);
2641 
2642  // Save image file path
2643  IUSaveText(&FileNameT[0], imageFileName);
2644 
2645  DEBUGF(Logger::DBG_SESSION, "Image saved to %s", imageFileName);
2646  FileNameTP.s = IPS_OK;
2647  IDSetText(&FileNameTP, nullptr);
2648  }
2649 
2650  if (targetChip->SendCompressed)
2651  {
2652  compressedBytes = sizeof(char) * totalBytes + totalBytes / 64 + 16 + 3;
2653  compressedData = new uint8_t[compressedBytes];
2654 
2655  if (fitsData == nullptr || compressedData == nullptr)
2656  {
2657  if (compressedData)
2658  delete [] compressedData;
2659  DEBUG(Logger::DBG_ERROR, "Error: Ran out of memory compressing image");
2660  return false;
2661  }
2662 
2663  int r = compress2(compressedData, &compressedBytes, (const Bytef *)fitsData, totalBytes, 9);
2664  if (r != Z_OK)
2665  {
2666  /* this should NEVER happen */
2667  DEBUG(Logger::DBG_ERROR, "Error: Failed to compress image");
2668  delete [] compressedData;
2669  return false;
2670  }
2671 
2672  targetChip->FitsB.blob = compressedData;
2673  targetChip->FitsB.bloblen = compressedBytes;
2674  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s.z", targetChip->getImageExtension());
2675  }
2676  else
2677  {
2678  targetChip->FitsB.blob = const_cast<void *>(fitsData);
2679  targetChip->FitsB.bloblen = totalBytes;
2680  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s", targetChip->getImageExtension());
2681  }
2682 
2683  targetChip->FitsB.size = totalBytes;
2684  targetChip->FitsBP.s = IPS_OK;
2685 
2686  if (sendImage)
2687  IDSetBLOB(&targetChip->FitsBP, nullptr);
2688 
2689  if (compressedData)
2690  delete [] compressedData;
2691 
2692  DEBUG(Logger::DBG_DEBUG, "Upload complete");
2693 
2694  return true;
2695 }
2696 
2697 void CCD::SetCCDParams(int x, int y, int bpp, float xf, float yf)
2698 {
2699  PrimaryCCD.setResolution(x, y);
2700  PrimaryCCD.setFrame(0, 0, x, y);
2701  if (CanBin())
2702  PrimaryCCD.setBin(1, 1);
2703  PrimaryCCD.setPixelSize(xf, yf);
2704  PrimaryCCD.setBPP(bpp);
2705 }
2706 
2707 void CCD::SetGuiderParams(int x, int y, int bpp, float xf, float yf)
2708 {
2709  capability |= CCD_HAS_GUIDE_HEAD;
2710 
2711  GuideCCD.setResolution(x, y);
2712  GuideCCD.setFrame(0, 0, x, y);
2713  GuideCCD.setPixelSize(xf, yf);
2714  GuideCCD.setBPP(bpp);
2715 }
2716 
2717 bool CCD::saveConfigItems(FILE *fp)
2718 {
2720 
2721  IUSaveConfigText(fp, &ActiveDeviceTP);
2722  IUSaveConfigSwitch(fp, &UploadSP);
2723  IUSaveConfigText(fp, &UploadSettingsTP);
2724  IUSaveConfigSwitch(fp, &TelescopeTypeSP);
2725 
2726  IUSaveConfigSwitch(fp, &PrimaryCCD.CompressSP);
2727 
2728  if (HasGuideHead())
2729  IUSaveConfigSwitch(fp, &GuideCCD.CompressSP);
2730 
2731  if (CanSubFrame())
2732  IUSaveConfigNumber(fp, &PrimaryCCD.ImageFrameNP);
2733 
2734  if (CanBin())
2735  IUSaveConfigNumber(fp, &PrimaryCCD.ImageBinNP);
2736 
2737  if (HasBayer())
2738  IUSaveConfigText(fp, &BayerTP);
2739 
2740  if (HasStreaming())
2741  Streamer->saveConfigItems(fp);
2742 
2743  return true;
2744 }
2745 
2747 {
2748  INDI_UNUSED(ms);
2749  DEBUG(Logger::DBG_ERROR, "The CCD does not support guiding.");
2750  return IPS_ALERT;
2751 }
2752 
2754 {
2755  INDI_UNUSED(ms);
2756  DEBUG(Logger::DBG_ERROR, "The CCD does not support guiding.");
2757  return IPS_ALERT;
2758 }
2759 
2761 {
2762  INDI_UNUSED(ms);
2763  DEBUG(Logger::DBG_ERROR, "The CCD does not support guiding.");
2764  return IPS_ALERT;
2765 }
2766 
2768 {
2769  INDI_UNUSED(ms);
2770  DEBUG(Logger::DBG_ERROR, "The CCD does not support guiding.");
2771  return IPS_ALERT;
2772 }
2773 
2774 void CCD::getMinMax(double *min, double *max, CCDChip *targetChip)
2775 {
2776  int ind = 0, i, j;
2777  int imageHeight = targetChip->getSubH() / targetChip->getBinY();
2778  int imageWidth = targetChip->getSubW() / targetChip->getBinX();
2779  double lmin = 0, lmax = 0;
2780 
2781  switch (targetChip->getBPP())
2782  {
2783  case 8:
2784  {
2785  unsigned char *imageBuffer = (unsigned char *)targetChip->getFrameBuffer();
2786  lmin = lmax = imageBuffer[0];
2787 
2788  for (i = 0; i < imageHeight; i++)
2789  for (j = 0; j < imageWidth; j++)
2790  {
2791  ind = (i * imageWidth) + j;
2792  if (imageBuffer[ind] < lmin)
2793  lmin = imageBuffer[ind];
2794  else if (imageBuffer[ind] > lmax)
2795  lmax = imageBuffer[ind];
2796  }
2797  }
2798  break;
2799 
2800  case 16:
2801  {
2802  unsigned short *imageBuffer = (unsigned short *)targetChip->getFrameBuffer();
2803  lmin = lmax = imageBuffer[0];
2804 
2805  for (i = 0; i < imageHeight; i++)
2806  for (j = 0; j < imageWidth; j++)
2807  {
2808  ind = (i * imageWidth) + j;
2809  if (imageBuffer[ind] < lmin)
2810  lmin = imageBuffer[ind];
2811  else if (imageBuffer[ind] > lmax)
2812  lmax = imageBuffer[ind];
2813  }
2814  }
2815  break;
2816 
2817  case 32:
2818  {
2819  unsigned int *imageBuffer = (unsigned int *)targetChip->getFrameBuffer();
2820  lmin = lmax = imageBuffer[0];
2821 
2822  for (i = 0; i < imageHeight; i++)
2823  for (j = 0; j < imageWidth; j++)
2824  {
2825  ind = (i * imageWidth) + j;
2826  if (imageBuffer[ind] < lmin)
2827  lmin = imageBuffer[ind];
2828  else if (imageBuffer[ind] > lmax)
2829  lmax = imageBuffer[ind];
2830  }
2831  }
2832  break;
2833  }
2834  *min = lmin;
2835  *max = lmax;
2836 }
2837 
2838 std::string regex_replace_compat(const std::string &input, const std::string &pattern, const std::string &replace)
2839 {
2840  std::stringstream s;
2841  std::regex_replace(std::ostreambuf_iterator<char>(s), input.begin(), input.end(), std::regex(pattern), replace);
2842  return s.str();
2843 }
2844 
2845 int CCD::getFileIndex(const char *dir, const char *prefix, const char *ext)
2846 {
2847  INDI_UNUSED(ext);
2848 
2849  DIR *dpdf = nullptr;
2850  struct dirent *epdf = nullptr;
2851  std::vector<std::string> files = std::vector<std::string>();
2852 
2853  std::string prefixIndex = prefix;
2854  prefixIndex = regex_replace_compat(prefixIndex, "_ISO8601", "");
2855  prefixIndex = regex_replace_compat(prefixIndex, "_XXX", "");
2856 
2857  // Create directory if does not exist
2858  struct stat st;
2859 
2860  if (stat(dir, &st) == -1)
2861  {
2862  DEBUGF(Logger::DBG_DEBUG, "Creating directory %s...", dir);
2863  if (_ccd_mkdir(dir, 0755) == -1)
2864  DEBUGF(Logger::DBG_ERROR, "Error creating directory %s (%s)", dir, strerror(errno));
2865  }
2866 
2867  dpdf = opendir(dir);
2868  if (dpdf != nullptr)
2869  {
2870  while ((epdf = readdir(dpdf)))
2871  {
2872  if (strstr(epdf->d_name, prefixIndex.c_str()))
2873  files.push_back(epdf->d_name);
2874  }
2875  }
2876  else
2877  return -1;
2878 
2879  int maxIndex = 0;
2880 
2881  for (int i = 0; i < (int)files.size(); i++)
2882  {
2883  int index = -1;
2884 
2885  std::string file = files.at(i);
2886  std::size_t start = file.find_last_of("_");
2887  std::size_t end = file.find_last_of(".");
2888  if (start != std::string::npos)
2889  {
2890  index = atoi(file.substr(start + 1, end).c_str());
2891  if (index > maxIndex)
2892  maxIndex = index;
2893  }
2894  }
2895 
2896  return (maxIndex + 1);
2897 }
2898 
2900 {
2902 }
2903 
2905 {
2906  DEBUG(Logger::DBG_ERROR, "Streaming is not supported.");
2907  return false;
2908 }
2909 
2911 {
2912  DEBUG(Logger::DBG_ERROR, "Streaming is not supported.");
2913  return false;
2914 }
2915 
2916 }
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver...
Definition: indiccd.cpp:704
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidriver.c:1477
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indicom.c:1420
const char * IMAGE_INFO_TAB
Definition: indiccd.cpp:49
int getSubW()
getSubW Get the width of the frame
Definition: indiccd.h:97
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indicom.c:1411
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void setExposureLeft(double duration)
setExposureLeft Update exposure time left. Inform the client of the new exposure time left value...
Definition: indiccd.cpp:230
void setImageExtension(const char *ext)
setImageExtension Set image exntension
Definition: indiccd.cpp:284
virtual void addFITSKeywords(fitsfile *fptr, CCDChip *targetChip)
Add FITS keywords to a fits file.
Definition: indiccd.cpp:1734
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
Definition: indiccd.cpp:1051
void setMinMaxStep(const char *property, const char *element, double min, double max, double step, bool sendToClient=true)
setMinMaxStep for a number property element
Definition: indiccd.cpp:161
virtual bool UpdateCCDFrame(int x, int y, int w, int h)
CCD calls this function when CCD Frame dimension needs to be updated in the hardware. Derived classes should implement this function.
Definition: indiccd.cpp:1690
void setFrameType(CCD_FRAME type)
setFrameType Set desired frame type for next exposure.
Definition: indiccd.cpp:108
The CCDChip class provides functionality of a CCD Chip within a CCD.
Definition: indiccd.h:51
virtual bool StartExposure(float duration)
Start exposing primary CCD chip.
Definition: indiccd.cpp:1666
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:545
virtual void ISGetProperties(const char *dev)
define the driver&#39;s properties to the client. Usually, only a minimum set of properties are defined t...
char format[MAXINDIBLOBFMT]
Definition: indiapi.h:430
#define MAXINDIDEVICE
Definition: indiapi.h:174
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.c:575
void IDSetText(const ITextVectorProperty *t, const char *msg,...)
Tell client to update an existing text vector property.
Definition: indidriver.c:1853
void IDSetSwitch(const ISwitchVectorProperty *s, const char *msg,...)
Tell client to update an existing switch vector property.
Definition: indidriver.c:1937
double step
Definition: indiapi.h:269
IPState
Property state.
Definition: indiapi.h:143
virtual IPState GuideEast(float ms)
Guide easward for ms milliseconds.
Definition: indiccd.cpp:2760
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
int getNAxis() const
Definition: indiccd.cpp:274
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:227
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:319
const char * getExposureStartTime()
getExposureStartTime
Definition: indiccd.cpp:248
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
One number descriptor.
Definition: indiapi.h:256
int getBPP()
getBPP Get CCD Chip depth (bits per pixel).
Definition: indiccd.h:133
void setInterlaced(bool intr)
setInterlaced Set whether the CCD chip is interlaced or not?
Definition: indiccd.cpp:263
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:283
virtual bool StartGuideExposure(float duration)
Start exposing guide CCD chip.
Definition: indiccd.cpp:1672
double max(void)
Definition: indiapi.h:166
std::string regex_replace_compat(const std::string &input, const std::string &pattern, const std::string &replace)
Definition: indiccd.cpp:2838
#define MAXINDIBLOBFMT
Definition: indiapi.h:177
void setNAxis(int value)
setNAxis Set FITS number of axis
Definition: indiccd.cpp:279
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
Definition: indiapi.h:165
int size
Definition: indiapi.h:436
const char * getFrameTypeName(CCD_FRAME fType)
getFrameTypeName returns CCD Frame type name
Definition: indiccd.cpp:243
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:2095
#define MAXRBUF
Definition: indidriver.c:43
Definition: indiapi.h:167
const char * name
Definition: indiserver.c:113
virtual int SetTemperature(double temperature)
Set CCD temperature.
Definition: indiccd.cpp:1659
int getBinX()
getBinX Get horizontal binning of the CCD chip.
Definition: indiccd.h:109
int getFrameBufferSize()
getFrameBufferSize Get allocated frame buffer size to hold the CCD image frame.
Definition: indiccd.h:139
virtual bool UpdateGuiderFrameType(CCDChip::CCD_FRAME fType)
CCD calls this function when Guide frame type is updated by the client.
Definition: indiccd.cpp:1727
void IDSetNumber(const INumberVectorProperty *n, const char *msg,...)
Tell client to update an existing number vector property.
Definition: indidriver.c:1895
virtual void SetCCDParams(int x, int y, int bpp, float xf, float yf)
Setup CCD paramters for primary CCD. Child classes call this function to update CCD parameters...
Definition: indiccd.cpp:2697
Namespace to encapsulate INDI client, drivers, and mediator classes. Developers can subclass the base...
int getSubH()
getSubH Get the height of the frame
Definition: indiccd.h:103
char name[MAXINDINAME]
Definition: indiapi.h:304
int bloblen
Definition: indiapi.h:434
JsonIterator end(JsonValue)
Definition: gason.h:108
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
Definition: indidriver.c:634
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:445
void setExposureDuration(double duration)
setExposureDuration Set desired CCD frame exposure duration for next exposure. You must call this fun...
Definition: indiccd.cpp:237
const char * GUIDE_HEAD_TAB
Definition: indiccd.cpp:50
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string.
Definition: indicom.c:117
void IUFillBLOB(IBLOB *bp, const char *name, const char *label, const char *format)
Assign attributes for a BLOB property. The BLOB&#39;s data and auxiliary elements will be set to NULL...
Definition: indidriver.c:468
virtual bool initProperties()
Initilize properties initial state and value. The child class must implement this function...
Definition: indiccd.cpp:420
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidriver.c:1458
virtual bool UpdateCCDFrameType(CCDChip::CCD_FRAME fType)
CCD calls this function when CCD frame type needs to be updated in the hardware.
Definition: indiccd.cpp:1720
void setExposureFailed()
setExposureFailed Alert the client that the exposure failed.
Definition: indiccd.cpp:268
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:384
char name[MAXINDINAME]
Definition: indiapi.h:259
#define DEBUGF(priority, msg,...)
Definition: indilogger.h:57
void setFrameBufferSize(int nbuf, bool allocMem=true)
setFrameBufferSize Set desired frame buffer size. The function will allocate memory accordingly...
Definition: indiccd.cpp:210
void * blob
Definition: indiapi.h:432
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:420
virtual bool StopStreaming()
StopStreaming Stop live video streaming.
Definition: indiccd.cpp:2910
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidriver.c:1494
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indicom.c:1327
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition: indidriver.c:140
const char * GUIDE_CONTROL_TAB
Definition: indiccd.cpp:51
int IUFindOnSwitchIndex(const ISwitchVectorProperty *sp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indicom.c:1389
const char * WCS_TAB
Definition: indiccd.cpp:53
CCD_FRAME getFrameType()
getFrameType
Definition: indiccd.h:191
const char * IMAGE_SETTINGS_TAB
Definition: indiccd.cpp:48
Number vector property descriptor.
Definition: indiapi.h:299
double getExposureDuration()
getExposureDuration Get requested exposure duration for the CCD chip in seconds.
Definition: indiccd.h:151
void IDSetBLOB(const IBLOBVectorProperty *b, const char *msg,...)
Tell client to update an existing BLOB vector property.
Definition: indidriver.c:2018
void setFrame(int subx, int suby, int subw, int subh)
setFrame Set desired frame resolutoin for an exposure.
Definition: indiccd.cpp:135
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
Definition: indiccd.h:163
double min
Definition: indiapi.h:265
const char * RAPIDGUIDE_TAB
Definition: indiccd.cpp:52
INDI_EQ_AXIS
Definition: indibasetypes.h:32
virtual void SetGuiderParams(int x, int y, int bpp, float xf, float yf)
Setup CCD paramters for guide head CCD. Child classes call this function to update CCD parameters...
Definition: indiccd.cpp:2707
virtual bool UpdateCCDBin(int hor, int ver)
CCD calls this function when CCD Binning needs to be updated in the hardware. Derived classes should ...
Definition: indiccd.cpp:1703
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver&#39;s options are located. Those may include auxiliary controls...
virtual IPState GuideWest(float ms)
Guide westward for ms milliseconds.
Definition: indiccd.cpp:2767
#define INDI_UNUSED(x)
Definition: indidevapi.h:797
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void IUFillBLOBVector(IBLOBVectorProperty *bvp, IBLOB *bp, int nbp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a BLOB vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:597
virtual bool StartStreaming()
StartStreaming Start live video streaming.
Definition: indiccd.cpp:2904
double max
Definition: indiapi.h:267
virtual bool initProperties()
Initilize properties initial state and value. The child class must implement this function...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition: indiccd.cpp:861
ISState
Switch state.
Definition: indiapi.h:134
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:494
void setBPP(int bpp)
setBPP Set depth of CCD chip.
Definition: indiccd.cpp:201
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element&#39;s attribute value.
Definition: lilxml.c:613
virtual bool AbortExposure()
Abort ongoing exposure.
Definition: indiccd.cpp:1678
void GuideComplete(INDI_EQ_AXIS axis)
Call GuideComplete once the guiding pulse is complete.
Definition: indiccd.cpp:2899
void setBin(int hor, int ver)
setBin Set CCD Chip binnig
Definition: indiccd.cpp:150
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:571
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.c:524
virtual void GuideComplete(INDI_EQ_AXIS axis)
Call GuideComplete once the guiding pulse is complete.
double min(void)
void setResolution(int x, int y)
setResolution set CCD Chip resolution
Definition: indiccd.cpp:113
float getPixelSizeY()
getPixelSizeY Get vertical pixel size in microns.
Definition: indiccd.h:127
void fits_update_key_s(fitsfile *fptr, int type, std::string name, void *p, std::string explanation, int *status)
Definition: indiccd.cpp:1944
virtual IPState GuideSouth(float ms)
Guide southward for ms milliseconds.
Definition: indiccd.cpp:2753
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save configuration items in XML file.
Definition: indiccd.cpp:2717
int errno
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");...
Definition: indilogger.h:56
virtual ~CCD()
Definition: indiccd.cpp:400
virtual IPState GuideNorth(float ms)
Guide northward for ms milliseconds.
Definition: indiccd.cpp:2746
__le16 type
Definition: pwc-ioctl.h:53
virtual bool AbortGuideExposure()
Abort ongoing exposure.
Definition: indiccd.cpp:1684
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
Definition: indiccd.cpp:963
int getBinY()
getBinY Get vertical binning of the CCD chip.
Definition: indiccd.h:115
virtual bool UpdateGuiderBin(int hor, int ver)
CCD calls this function when Guide head binning is updated by the client. Derived classes should impl...
Definition: indiccd.cpp:1713
char name[MAXINDINAME]
Definition: indiapi.h:332
char * getImageExtension()
Definition: indiccd.h:322
float getPixelSizeX()
getPixelSizeX Get horizontal pixel size in microns.
Definition: indiccd.h:121
virtual void ISGetProperties(const char *dev)
define the driver&#39;s properties to the client. Usually, only a minimum set of properties are defined t...
Definition: indiccd.cpp:693
void binFrame()
binFrame Perform softwre binning on the CCD frame. Only use this function if hardware binning is not ...
Definition: indiccd.cpp:289
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
Definition: indiccd.cpp:1346
virtual bool UpdateGuiderFrame(int x, int y, int w, int h)
CCD calls this function when Guide head frame dimension is updated by the client. Derived classes sho...
Definition: indiccd.cpp:1697
Implementations for common driver routines.
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. Al fields must be initilized.
Definition: indiccd.cpp:404
void addFITSKeywords(fitsfile *fptr, IMAGE_INFO *image_info)
Definition: stv.c:1597
void uploadFile(const char *filename)
Definition: stv.c:1710
void setPixelSize(float x, float y)
setPixelSize Set CCD Chip pixel size
Definition: indiccd.cpp:189
virtual bool ExposureComplete(CCDChip *targetChip)
Uploads target Chip exposed buffer as FITS to the client. Dervied classes should class this function ...
Definition: indiccd.cpp:1951
double value
Definition: indiapi.h:271