Instrument Neutral Distributed Interface INDI  2.0.2
indiccd.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2010-2018 Jasem Mutlaq. All rights reserved.
3 
4  Copyright(c) 2010, 2011 Gerry Rozema. All rights reserved.
5 
6  Rapid Guide support added by CloudMakers, s. r. o.
7  Copyright(c) 2013 CloudMakers, s. r. o. All rights reserved.
8 
9  Star detection algorithm is based on PHD Guiding by Craig Stark
10  Copyright (c) 2006-2010 Craig Stark. All rights reserved.
11 
12  This library is free software; you can redistribute it and/or
13  modify it under the terms of the GNU Library General Public
14  License version 2 as published by the Free Software Foundation.
15 
16  This library is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  Library General Public License for more details.
20 
21  You should have received a copy of the GNU Library General Public License
22  along with this library; see the file COPYING.LIB. If not, write to
23  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  Boston, MA 02110-1301, USA.
25 *******************************************************************************/
26 
27 // use 64-bit values when calling stat()
28 #define _FILE_OFFSET_BITS 64
29 
30 #include "indiccd.h"
31 
32 #include "fpack/fpack.h"
33 #include "indicom.h"
34 #include "locale_compat.h"
35 #include "indiutility.h"
36 
37 #ifdef HAVE_XISF
38 #include <libxisf.h>
39 #endif
40 
41 #include <fitsio.h>
42 
43 #include <libnova/julian_day.h>
44 #include <libnova/precession.h>
45 #include <libnova/airmass.h>
46 #include <libnova/transform.h>
47 #include <libnova/ln_types.h>
48 #include <libastro.h>
49 
50 #include <cmath>
51 #include <regex>
52 #include <iterator>
53 #include <variant>
54 
55 #include <dirent.h>
56 #include <cerrno>
57 #include <cstdlib>
58 #include <zlib.h>
59 #include <sys/stat.h>
60 
61 const char * IMAGE_SETTINGS_TAB = "Image Settings";
62 const char * IMAGE_INFO_TAB = "Image Info";
63 const char * GUIDE_HEAD_TAB = "Guider Head";
64 //const char * RAPIDGUIDE_TAB = "Rapid Guide";
65 
66 #ifdef HAVE_WEBSOCKET
67 uint16_t INDIWSServer::m_global_port = 11623;
68 #endif
69 
70 std::string join(std::vector<std::string> const &strings, std::string delim)
71 {
72  std::stringstream ss;
73  std::copy(strings.begin(), strings.end(),
74  std::ostream_iterator<std::string>(ss, delim.c_str()));
75  return ss.str();
76 }
77 
78 namespace INDI
79 {
80 
82 {
83  //ctor
84  capability = 0;
85 
86  InExposure = false;
87  InGuideExposure = false;
88  //RapidGuideEnabled = false;
89  //GuiderRapidGuideEnabled = false;
90  m_ValidCCDRotation = false;
91 
92  AutoLoop = false;
93  SendImage = false;
94  ShowMarker = false;
95  GuiderAutoLoop = false;
96  GuiderSendImage = false;
97  GuiderShowMarker = false;
98 
99  ExposureTime = 0.0;
100  GuiderExposureTime = 0.0;
101  CurrentFilterSlot = -1;
102 
103  RA = std::numeric_limits<double>::quiet_NaN();
104  Dec = std::numeric_limits<double>::quiet_NaN();
105  pierSide = -1;
106  J2000RA = std::numeric_limits<double>::quiet_NaN();
107  J2000DE = std::numeric_limits<double>::quiet_NaN();
108  J2000Valid = false;
109  MPSAS = std::numeric_limits<double>::quiet_NaN();
110  RotatorAngle = std::numeric_limits<double>::quiet_NaN();
111  // JJ ed 2019-12-10
112  FocuserPos = -1;
113  FocuserTemp = std::numeric_limits<double>::quiet_NaN();
114 
115  Airmass = std::numeric_limits<double>::quiet_NaN();
116  Latitude = std::numeric_limits<double>::quiet_NaN();
117  Longitude = std::numeric_limits<double>::quiet_NaN();
118  Azimuth = std::numeric_limits<double>::quiet_NaN();
119  Altitude = std::numeric_limits<double>::quiet_NaN();
120  snoopedAperture = std::numeric_limits<double>::quiet_NaN();
121  snoopedFocalLength = std::numeric_limits<double>::quiet_NaN();
122 
123  // Check temperature every 5 seconds.
126 
127  exposureStartTime[0] = 0;
128  exposureDuration = 0.0;
129 }
130 
132 {
133  // Only update if index is different.
134  if (m_ConfigFastExposureIndex != IUFindOnSwitchIndex(&FastExposureToggleSP))
136 }
137 
138 void CCD::SetCCDCapability(uint32_t cap)
139 {
140  capability = cap;
141 
142  if (HasST4Port())
144  else
146 
147  syncDriverInfo();
148  HasStreaming();
149  HasDSP();
150 }
151 
153 {
155 
156  // CCD Temperature
157  IUFillNumber(&TemperatureN[0], "CCD_TEMPERATURE_VALUE", "Temperature (C)", "%5.2f", -50.0, 50.0, 0., 0.);
158  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "CCD_TEMPERATURE", "Temperature",
160 
161  // Camera temperature ramp
162  TemperatureRampNP[RAMP_SLOPE].fill("RAMP_SLOPE", "Max. dT (C/min)", "%.f", 0, 30, 1, 0);
163  TemperatureRampNP[RAMP_THRESHOLD].fill("RAMP_THRESHOLD", "Threshold (C)", "%.1f", 0.1, 2, 0.1, 0.2);
164  TemperatureRampNP.fill(getDeviceName(), "CCD_TEMP_RAMP", "Temp. Ramp", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
165 
166  /**********************************************/
167  /**************** Primary Chip ****************/
168  /**********************************************/
169 
170  // Primary CCD Region-Of-Interest (ROI)
171  IUFillNumber(&PrimaryCCD.ImageFrameN[CCDChip::FRAME_X], "X", "Left ", "%4.0f", 0, 0.0, 0, 0);
172  IUFillNumber(&PrimaryCCD.ImageFrameN[CCDChip::FRAME_Y], "Y", "Top", "%4.0f", 0, 0, 0, 0);
173  IUFillNumber(&PrimaryCCD.ImageFrameN[CCDChip::FRAME_W], "WIDTH", "Width", "%4.0f", 0, 0.0, 0, 0.0);
174  IUFillNumber(&PrimaryCCD.ImageFrameN[CCDChip::FRAME_H], "HEIGHT", "Height", "%4.0f", 0, 0, 0, 0.0);
175  IUFillNumberVector(&PrimaryCCD.ImageFrameNP, PrimaryCCD.ImageFrameN, 4, getDeviceName(), "CCD_FRAME", "Frame",
177 
178  // Primary CCD Frame Type
179  IUFillSwitch(&PrimaryCCD.FrameTypeS[CCDChip::LIGHT_FRAME], "FRAME_LIGHT", "Light", ISS_ON);
180  IUFillSwitch(&PrimaryCCD.FrameTypeS[CCDChip::BIAS_FRAME], "FRAME_BIAS", "Bias", ISS_OFF);
181  IUFillSwitch(&PrimaryCCD.FrameTypeS[CCDChip::DARK_FRAME], "FRAME_DARK", "Dark", ISS_OFF);
182  IUFillSwitch(&PrimaryCCD.FrameTypeS[CCDChip::FLAT_FRAME], "FRAME_FLAT", "Flat", ISS_OFF);
183  IUFillSwitchVector(&PrimaryCCD.FrameTypeSP, PrimaryCCD.FrameTypeS, 4, getDeviceName(), "CCD_FRAME_TYPE",
185 
186  // Primary CCD Exposure
187  IUFillNumber(&PrimaryCCD.ImageExposureN[0], "CCD_EXPOSURE_VALUE", "Duration (s)", "%5.2f", 0.01, 3600, 1.0, 1.0);
188  IUFillNumberVector(&PrimaryCCD.ImageExposureNP, PrimaryCCD.ImageExposureN, 1, getDeviceName(), "CCD_EXPOSURE",
189  "Expose", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
190 
191  // Primary CCD Abort
192  IUFillSwitch(&PrimaryCCD.AbortExposureS[0], "ABORT", "Abort", ISS_OFF);
193  IUFillSwitchVector(&PrimaryCCD.AbortExposureSP, PrimaryCCD.AbortExposureS, 1, getDeviceName(), "CCD_ABORT_EXPOSURE",
194  "Abort", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
195 
196  // Primary CCD Binning
197  IUFillNumber(&PrimaryCCD.ImageBinN[0], "HOR_BIN", "X", "%2.0f", 1, 4, 1, 1);
198  IUFillNumber(&PrimaryCCD.ImageBinN[1], "VER_BIN", "Y", "%2.0f", 1, 4, 1, 1);
199  IUFillNumberVector(&PrimaryCCD.ImageBinNP, PrimaryCCD.ImageBinN, 2, getDeviceName(), "CCD_BINNING", "Binning",
201 
202  // Primary CCD Info
203  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_MAX_X], "CCD_MAX_X", "Max. Width", "%.f", 1, 16000, 0, 0);
204  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_MAX_Y], "CCD_MAX_Y", "Max. Height", "%.f", 1, 16000, 0, 0);
205  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE], "CCD_PIXEL_SIZE", "Pixel size (um)", "%.2f", 1,
206  40, 0, 0);
207  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_X], "CCD_PIXEL_SIZE_X", "Pixel size X", "%.2f", 1,
208  40, 0, 0);
209  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_Y], "CCD_PIXEL_SIZE_Y", "Pixel size Y", "%.2f", 1,
210  40, 0, 0);
211  IUFillNumber(&PrimaryCCD.ImagePixelSizeN[CCDChip::CCD_BITSPERPIXEL], "CCD_BITSPERPIXEL", "Bits per pixel", "%.f",
212  8, 64, 0, 0);
213  IUFillNumberVector(&PrimaryCCD.ImagePixelSizeNP, PrimaryCCD.ImagePixelSizeN, 6, getDeviceName(), "CCD_INFO",
214  "CCD Information", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE);
215 
216  // Primary CCD Compression Options
217  IUFillSwitch(&PrimaryCCD.CompressS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
218  IUFillSwitch(&PrimaryCCD.CompressS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_ON);
219  IUFillSwitchVector(&PrimaryCCD.CompressSP, PrimaryCCD.CompressS, 2, getDeviceName(), "CCD_COMPRESSION", "Compression",
221  PrimaryCCD.SendCompressed = false;
222 
223  // Primary CCD Chip Data Blob
224  IUFillBLOB(&PrimaryCCD.FitsB, "CCD1", "Image", "");
225  IUFillBLOBVector(&PrimaryCCD.FitsBP, &PrimaryCCD.FitsB, 1, getDeviceName(), "CCD1", "Image Data", IMAGE_INFO_TAB,
226  IP_RO, 60, IPS_IDLE);
227 
228  // Bayer
229  IUFillText(&BayerT[0], "CFA_OFFSET_X", "X Offset", "0");
230  IUFillText(&BayerT[1], "CFA_OFFSET_Y", "Y Offset", "0");
231  IUFillText(&BayerT[2], "CFA_TYPE", "Filter", nullptr);
232  IUFillTextVector(&BayerTP, BayerT, 3, getDeviceName(), "CCD_CFA", "Bayer Info", IMAGE_INFO_TAB, IP_RW, 60,
233  IPS_IDLE);
234 
235  // Reset Frame Settings
236  IUFillSwitch(&PrimaryCCD.ResetS[0], "RESET", "Reset", ISS_OFF);
237  IUFillSwitchVector(&PrimaryCCD.ResetSP, PrimaryCCD.ResetS, 1, getDeviceName(), "CCD_FRAME_RESET", "Frame Values",
239 
240  /**********************************************/
241  /********* Primary Chip Rapid Guide **********/
242  /**********************************************/
243 #if 0
244  IUFillSwitch(&PrimaryCCD.RapidGuideS[0], "ENABLE", "Enable", ISS_OFF);
245  IUFillSwitch(&PrimaryCCD.RapidGuideS[1], "DISABLE", "Disable", ISS_ON);
246  IUFillSwitchVector(&PrimaryCCD.RapidGuideSP, PrimaryCCD.RapidGuideS, 2, getDeviceName(), "CCD_RAPID_GUIDE",
247  "Rapid Guide", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
248 
249  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[0], "AUTO_LOOP", "Auto loop", ISS_ON);
250  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[1], "SEND_IMAGE", "Send image", ISS_OFF);
251  IUFillSwitch(&PrimaryCCD.RapidGuideSetupS[2], "SHOW_MARKER", "Show marker", ISS_OFF);
252  IUFillSwitchVector(&PrimaryCCD.RapidGuideSetupSP, PrimaryCCD.RapidGuideSetupS, 3, getDeviceName(),
253  "CCD_RAPID_GUIDE_SETUP", "Rapid Guide Setup", RAPIDGUIDE_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
254 
255  IUFillNumber(&PrimaryCCD.RapidGuideDataN[0], "GUIDESTAR_X", "Guide star position X", "%5.2f", 0, 1024, 0, 0);
256  IUFillNumber(&PrimaryCCD.RapidGuideDataN[1], "GUIDESTAR_Y", "Guide star position Y", "%5.2f", 0, 1024, 0, 0);
257  IUFillNumber(&PrimaryCCD.RapidGuideDataN[2], "GUIDESTAR_FIT", "Guide star fit", "%5.2f", 0, 1024, 0, 0);
258  IUFillNumberVector(&PrimaryCCD.RapidGuideDataNP, PrimaryCCD.RapidGuideDataN, 3, getDeviceName(),
259  "CCD_RAPID_GUIDE_DATA", "Rapid Guide Data", RAPIDGUIDE_TAB, IP_RO, 60, IPS_IDLE);
260 #endif
261 
262  /**********************************************/
263  /***************** Guide Chip *****************/
264  /**********************************************/
265 
266  IUFillNumber(&GuideCCD.ImageFrameN[CCDChip::FRAME_X], "X", "Left ", "%4.0f", 0, 0, 0, 0);
267  IUFillNumber(&GuideCCD.ImageFrameN[CCDChip::FRAME_Y], "Y", "Top", "%4.0f", 0, 0, 0, 0);
268  IUFillNumber(&GuideCCD.ImageFrameN[CCDChip::FRAME_W], "WIDTH", "Width", "%4.0f", 0, 0, 0, 0);
269  IUFillNumber(&GuideCCD.ImageFrameN[CCDChip::FRAME_H], "HEIGHT", "Height", "%4.0f", 0, 0, 0, 0);
270  IUFillNumberVector(&GuideCCD.ImageFrameNP, GuideCCD.ImageFrameN, 4, getDeviceName(), "GUIDER_FRAME", "Frame",
272 
273  IUFillNumber(&GuideCCD.ImageBinN[0], "HOR_BIN", "X", "%2.0f", 1, 4, 1, 1);
274  IUFillNumber(&GuideCCD.ImageBinN[1], "VER_BIN", "Y", "%2.0f", 1, 4, 1, 1);
275  IUFillNumberVector(&GuideCCD.ImageBinNP, GuideCCD.ImageBinN, 2, getDeviceName(), "GUIDER_BINNING", "Binning",
277 
278  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_MAX_X], "CCD_MAX_X", "Max. Width", "%4.0f", 1, 16000, 0, 0);
279  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_MAX_Y], "CCD_MAX_Y", "Max. Height", "%4.0f", 1, 16000, 0, 0);
280  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE], "CCD_PIXEL_SIZE", "Pixel size (um)", "%5.2f", 1,
281  40, 0, 0);
282  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_X], "CCD_PIXEL_SIZE_X", "Pixel size X", "%5.2f", 1,
283  40, 0, 0);
284  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_PIXEL_SIZE_Y], "CCD_PIXEL_SIZE_Y", "Pixel size Y", "%5.2f", 1,
285  40, 0, 0);
286  IUFillNumber(&GuideCCD.ImagePixelSizeN[CCDChip::CCD_BITSPERPIXEL], "CCD_BITSPERPIXEL", "Bits per pixel", "%3.0f", 8,
287  64, 0, 0);
288  IUFillNumberVector(&GuideCCD.ImagePixelSizeNP, GuideCCD.ImagePixelSizeN, 6, getDeviceName(), "GUIDER_INFO",
289  "Info", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE);
290 
291  IUFillSwitch(&GuideCCD.FrameTypeS[0], "FRAME_LIGHT", "Light", ISS_ON);
292  IUFillSwitch(&GuideCCD.FrameTypeS[1], "FRAME_BIAS", "Bias", ISS_OFF);
293  IUFillSwitch(&GuideCCD.FrameTypeS[2], "FRAME_DARK", "Dark", ISS_OFF);
294  IUFillSwitch(&GuideCCD.FrameTypeS[3], "FRAME_FLAT", "Flat", ISS_OFF);
295  IUFillSwitchVector(&GuideCCD.FrameTypeSP, GuideCCD.FrameTypeS, 4, getDeviceName(), "GUIDER_FRAME_TYPE",
296  "Type", GUIDE_HEAD_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
297 
298  IUFillNumber(&GuideCCD.ImageExposureN[0], "GUIDER_EXPOSURE_VALUE", "Duration (s)", "%5.2f", 0.01, 3600, 1.0, 1.0);
299  IUFillNumberVector(&GuideCCD.ImageExposureNP, GuideCCD.ImageExposureN, 1, getDeviceName(), "GUIDER_EXPOSURE",
300  "Guide Head", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
301 
302  IUFillSwitch(&GuideCCD.AbortExposureS[0], "ABORT", "Abort", ISS_OFF);
303  IUFillSwitchVector(&GuideCCD.AbortExposureSP, GuideCCD.AbortExposureS, 1, getDeviceName(), "GUIDER_ABORT_EXPOSURE",
304  "Abort", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
305 
306  IUFillSwitch(&GuideCCD.CompressS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
307  IUFillSwitch(&GuideCCD.CompressS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_ON);
308  IUFillSwitchVector(&GuideCCD.CompressSP, GuideCCD.CompressS, 2, getDeviceName(), "GUIDER_COMPRESSION", "Compression",
310  GuideCCD.SendCompressed = false;
311 
312  IUFillBLOB(&GuideCCD.FitsB, "CCD2", "Guider Image", "");
313  IUFillBLOBVector(&GuideCCD.FitsBP, &GuideCCD.FitsB, 1, getDeviceName(), "CCD2", "Image Data", IMAGE_INFO_TAB, IP_RO,
314  60, IPS_IDLE);
315 
316  /**********************************************/
317  /********* Guider Chip Rapid Guide ***********/
318  /**********************************************/
319 
320 #if 0
321  IUFillSwitch(&GuideCCD.RapidGuideS[0], "ENABLE", "Enable", ISS_OFF);
322  IUFillSwitch(&GuideCCD.RapidGuideS[1], "DISABLE", "Disable", ISS_ON);
323  IUFillSwitchVector(&GuideCCD.RapidGuideSP, GuideCCD.RapidGuideS, 2, getDeviceName(), "GUIDER_RAPID_GUIDE",
324  "Guider Head Rapid Guide", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
325 
326  IUFillSwitch(&GuideCCD.RapidGuideSetupS[0], "AUTO_LOOP", "Auto loop", ISS_ON);
327  IUFillSwitch(&GuideCCD.RapidGuideSetupS[1], "SEND_IMAGE", "Send image", ISS_OFF);
328  IUFillSwitch(&GuideCCD.RapidGuideSetupS[2], "SHOW_MARKER", "Show marker", ISS_OFF);
329  IUFillSwitchVector(&GuideCCD.RapidGuideSetupSP, GuideCCD.RapidGuideSetupS, 3, getDeviceName(),
330  "GUIDER_RAPID_GUIDE_SETUP", "Rapid Guide Setup", RAPIDGUIDE_TAB, IP_RW, ISR_NOFMANY, 0,
331  IPS_IDLE);
332 
333  IUFillNumber(&GuideCCD.RapidGuideDataN[0], "GUIDESTAR_X", "Guide star position X", "%5.2f", 0, 1024, 0, 0);
334  IUFillNumber(&GuideCCD.RapidGuideDataN[1], "GUIDESTAR_Y", "Guide star position Y", "%5.2f", 0, 1024, 0, 0);
335  IUFillNumber(&GuideCCD.RapidGuideDataN[2], "GUIDESTAR_FIT", "Guide star fit", "%5.2f", 0, 1024, 0, 0);
336  IUFillNumberVector(&GuideCCD.RapidGuideDataNP, GuideCCD.RapidGuideDataN, 3, getDeviceName(),
337  "GUIDER_RAPID_GUIDE_DATA", "Rapid Guide Data", RAPIDGUIDE_TAB, IP_RO, 60, IPS_IDLE);
338 
339 #endif
340 
341  /**********************************************/
342  /******************** WCS *********************/
343  /**********************************************/
344 
345  // WCS Enable/Disable
346  IUFillSwitch(&WorldCoordS[0], "WCS_ENABLE", "Enable", ISS_OFF);
347  IUFillSwitch(&WorldCoordS[1], "WCS_DISABLE", "Disable", ISS_ON);
348  IUFillSwitchVector(&WorldCoordSP, WorldCoordS, 2, getDeviceName(), "WCS_CONTROL", "WCS", WCS_TAB, IP_RW,
349  ISR_1OFMANY, 0, IPS_IDLE);
350 
351  IUFillNumber(&CCDRotationN[0], "CCD_ROTATION_VALUE", "Rotation", "%g", -360, 360, 1, 0);
352  IUFillNumberVector(&CCDRotationNP, CCDRotationN, 1, getDeviceName(), "CCD_ROTATION", "CCD FOV", WCS_TAB, IP_RW, 60,
353  IPS_IDLE);
354 
355  ScopeInfoNP[FocalLength].fill("FOCAL_LENGTH", "Focal Length (mm)", "%.2f", 10, 10000, 100, 0);
356  ScopeInfoNP[Aperture].fill("APERTURE", "Aperture (mm)", "%.2f", 10, 3000, 100, 0);
357  ScopeInfoNP.fill(getDeviceName(), "SCOPE_INFO", "Scope", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
358 
359  /**********************************************/
360  /************** Capture Format ***************/
361  /**********************************************/
362  char configName[64] = {0};
363  if (IUGetConfigOnSwitchName(getDeviceName(), "CCD_CAPTURE_FORMAT", configName, MAXINDINAME) == 0)
364  m_ConfigCaptureFormatName = configName;
365  CaptureFormatSP.fill(getDeviceName(), "CCD_CAPTURE_FORMAT", "Format", IMAGE_SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60,
366  IPS_IDLE);
367 
368  m_ConfigEncodeFormatIndex = FORMAT_FITS;
369  IUGetConfigOnSwitchIndex(getDeviceName(), "CCD_TRANSFER_FORMAT", &m_ConfigEncodeFormatIndex);
370  EncodeFormatSP[FORMAT_FITS].fill("FORMAT_FITS", "FITS",
371  m_ConfigEncodeFormatIndex == FORMAT_FITS ? ISS_ON : ISS_OFF);
372  EncodeFormatSP[FORMAT_NATIVE].fill("FORMAT_NATIVE", "Native",
373  m_ConfigEncodeFormatIndex == FORMAT_NATIVE ? ISS_ON : ISS_OFF);
374 #ifdef HAVE_XISF
375  EncodeFormatSP[FORMAT_XISF].fill("FORMAT_XISF", "XISF",
376  m_ConfigEncodeFormatIndex == FORMAT_XISF ? ISS_ON : ISS_OFF);
377 #endif
378  EncodeFormatSP.fill(getDeviceName(), "CCD_TRANSFER_FORMAT", "Encode", IMAGE_SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60,
379  IPS_IDLE);
380 
381  /**********************************************/
382  /************** Upload Settings ***************/
383  /**********************************************/
384 
385  // Upload Mode
386  IUFillSwitch(&UploadS[UPLOAD_CLIENT], "UPLOAD_CLIENT", "Client", ISS_ON);
387  IUFillSwitch(&UploadS[UPLOAD_LOCAL], "UPLOAD_LOCAL", "Local", ISS_OFF);
388  IUFillSwitch(&UploadS[UPLOAD_BOTH], "UPLOAD_BOTH", "Both", ISS_OFF);
389  IUFillSwitchVector(&UploadSP, UploadS, 3, getDeviceName(), "UPLOAD_MODE", "Upload", OPTIONS_TAB, IP_RW, ISR_1OFMANY,
390  0, IPS_IDLE);
391 
392  // Upload Settings
393  IUFillText(&UploadSettingsT[UPLOAD_DIR], "UPLOAD_DIR", "Dir", "");
394  IUFillText(&UploadSettingsT[UPLOAD_PREFIX], "UPLOAD_PREFIX", "Prefix", "IMAGE_XXX");
395  IUFillTextVector(&UploadSettingsTP, UploadSettingsT, 2, getDeviceName(), "UPLOAD_SETTINGS", "Upload Settings",
396  OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
397 
398  // Upload File Path
399  IUFillText(&FileNameT[0], "FILE_PATH", "Path", "");
400  IUFillTextVector(&FileNameTP, FileNameT, 1, getDeviceName(), "CCD_FILE_PATH", "Filename", IMAGE_INFO_TAB, IP_RO, 60,
401  IPS_IDLE);
402 
403  /**********************************************/
404  /****************** FITS Header****************/
405  /**********************************************/
406 
407  FITSHeaderTP[KEYWORD_NAME].fill("KEYWORD_NAME", "Name", nullptr);
408  FITSHeaderTP[KEYWORD_VALUE].fill("KEYWORD_VALUE", "Value", nullptr);
409  FITSHeaderTP[KEYWORD_COMMENT].fill("KEYWORD_COMMENT", "Comment", nullptr);
410  FITSHeaderTP.fill(getDeviceName(), "FITS_HEADER", "FITS Header", INFO_TAB, IP_WO, 60, IPS_IDLE);
411 
412  /**********************************************/
413  /****************** Exposure Looping **********/
414  /***************** Primary CCD Only ***********/
415  IUGetConfigOnSwitchIndex(getDeviceName(), FastExposureToggleSP.name, &m_ConfigFastExposureIndex);
416  IUFillSwitch(&FastExposureToggleS[INDI_ENABLED], "INDI_ENABLED", "Enabled",
417  m_ConfigFastExposureIndex == INDI_ENABLED ? ISS_ON : ISS_OFF);
418  IUFillSwitch(&FastExposureToggleS[INDI_DISABLED], "INDI_DISABLED", "Disabled",
419  m_ConfigFastExposureIndex == INDI_DISABLED ? ISS_ON : ISS_OFF);
420  IUFillSwitchVector(&FastExposureToggleSP, FastExposureToggleS, 2, getDeviceName(), "CCD_FAST_TOGGLE", "Fast Exposure",
422 
423  // CCD Should loop until the number of frames specified in this property is completed
424  IUFillNumber(&FastExposureCountN[0], "FRAMES", "Frames", "%.f", 0, 100000, 1, 1);
425  IUFillNumberVector(&FastExposureCountNP, FastExposureCountN, 1, getDeviceName(), "CCD_FAST_COUNT", "Fast Count",
426  OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
427 
428  /**********************************************/
429  /**************** Web Socket ******************/
430  /**********************************************/
431  IUFillSwitch(&WebSocketS[WEBSOCKET_ENABLED], "WEBSOCKET_ENABLED", "Enabled", ISS_OFF);
432  IUFillSwitch(&WebSocketS[WEBSOCKET_DISABLED], "WEBSOCKET_DISABLED", "Disabled", ISS_ON);
433  IUFillSwitchVector(&WebSocketSP, WebSocketS, 2, getDeviceName(), "CCD_WEBSOCKET", "Websocket", OPTIONS_TAB,
434  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
435 
436  IUFillNumber(&WebSocketSettingsN[WS_SETTINGS_PORT], "WS_SETTINGS_PORT", "Port", "%.f", 0, 50000, 0, 0);
437  IUFillNumberVector(&WebSocketSettingsNP, WebSocketSettingsN, 1, getDeviceName(), "CCD_WEBSOCKET_SETTINGS", "WS Settings",
439  60, IPS_IDLE);
440 
441  /**********************************************/
442  /**************** Snooping ********************/
443  /**********************************************/
444 
445  // Snooped Devices
446 
447  // Load from config
448  char telescope[MAXINDIDEVICE] = {"Telescope Simulator"};
449  IUGetConfigText(getDeviceName(), "ACTIVE_DEVICES", "ACTIVE_TELESCOPE", telescope, MAXINDIDEVICE);
450  char rotator[MAXINDIDEVICE] = {"Rotator Simulator"};
451  IUGetConfigText(getDeviceName(), "ACTIVE_DEVICES", "ACTIVE_ROTATOR", rotator, MAXINDIDEVICE);
452  char focuser[MAXINDIDEVICE] = {"Focuser Simulator"};
453  IUGetConfigText(getDeviceName(), "ACTIVE_DEVICES", "ACTIVE_FOCUSER", focuser, MAXINDIDEVICE);
454  char filter[MAXINDIDEVICE] = {"CCD Simulator"};
455  IUGetConfigText(getDeviceName(), "ACTIVE_DEVICES", "ACTIVE_FILTER", filter, MAXINDIDEVICE);
456  char skyquality[MAXINDIDEVICE] = {"SQM"};
457  IUGetConfigText(getDeviceName(), "ACTIVE_DEVICES", "ACTIVE_SKYQUALITY", skyquality, MAXINDIDEVICE);
458 
459  IUFillText(&ActiveDeviceT[ACTIVE_TELESCOPE], "ACTIVE_TELESCOPE", "Telescope", telescope);
460  IUFillText(&ActiveDeviceT[ACTIVE_ROTATOR], "ACTIVE_ROTATOR", "Rotator", rotator);
461  IUFillText(&ActiveDeviceT[ACTIVE_FOCUSER], "ACTIVE_FOCUSER", "Focuser", focuser);
462  IUFillText(&ActiveDeviceT[ACTIVE_FILTER], "ACTIVE_FILTER", "Filter", filter);
463  IUFillText(&ActiveDeviceT[ACTIVE_SKYQUALITY], "ACTIVE_SKYQUALITY", "Sky Quality", skyquality);
464  IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 5, getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB,
465  IP_RW, 60, IPS_IDLE);
466 
467  // Snooped RA/DEC Property
468  IUFillNumber(&EqN[0], "RA", "Ra (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
469  IUFillNumber(&EqN[1], "DEC", "Dec (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
470  IUFillNumberVector(&EqNP, EqN, 2, ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_EOD_COORD", "EQ Coord", "Main Control",
471  IP_RW,
472  60, IPS_IDLE);
473 
474  // Snooped J2000 RA/DEC Property
475  IUFillNumber(&J2000EqN[0], "RA", "Ra (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
476  IUFillNumber(&J2000EqN[1], "DEC", "Dec (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
477  IUFillNumberVector(&J2000EqNP, J2000EqN, 2, ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_COORD", "J2000 EQ Coord",
478  "Main Control", IP_RW,
479  60, IPS_IDLE);
480 
481  // Snoop properties of interest
482 
483  // Snoop mount
484  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_EOD_COORD");
485  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_COORD");
486  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_INFO");
487  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "GEOGRAPHIC_COORD");
488  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_PIER_SIDE");
489 
490  // Snoop Rotator
491  IDSnoopDevice(ActiveDeviceT[ACTIVE_ROTATOR].text, "ABS_ROTATOR_ANGLE");
492 
493  // JJ ed 2019-12-10
494  // Snoop Focuser
495  IDSnoopDevice(ActiveDeviceT[ACTIVE_FOCUSER].text, "ABS_FOCUS_POSITION");
496  IDSnoopDevice(ActiveDeviceT[ACTIVE_FOCUSER].text, "FOCUS_TEMPERATURE");
497  //
498 
499  // Snoop Filter Wheel
500  IDSnoopDevice(ActiveDeviceT[ACTIVE_FILTER].text, "FILTER_SLOT");
501  IDSnoopDevice(ActiveDeviceT[ACTIVE_FILTER].text, "FILTER_NAME");
502 
503  // Snoop Sky Quality Meter
504  IDSnoopDevice(ActiveDeviceT[ACTIVE_SKYQUALITY].text, "SKY_QUALITY");
505 
506  // Guider Interface
508 
510 
512 
513  return true;
514 }
515 
516 void CCD::ISGetProperties(const char * dev)
517 {
520 
521  if (HasStreaming())
522  Streamer->ISGetProperties(dev);
523 
524  if (HasDSP())
525  DSP->ISGetProperties(dev);
526 }
527 
529 {
530  //IDLog("CCD UpdateProperties isConnected returns %d %d\n",isConnected(),Connected);
531  if (isConnected())
532  {
533  defineProperty(&PrimaryCCD.ImageExposureNP);
534 
535  if (CanAbort())
536  defineProperty(&PrimaryCCD.AbortExposureSP);
537  if (CanSubFrame() == false)
538  PrimaryCCD.ImageFrameNP.p = IP_RO;
539 
540  defineProperty(&PrimaryCCD.ImageFrameNP);
541  if (CanBin() || CanSubFrame())
542  defineProperty(&PrimaryCCD.ResetSP);
543 
544  if (CanBin())
545  defineProperty(&PrimaryCCD.ImageBinNP);
546 
548 
549  if (HasGuideHead())
550  {
551  defineProperty(&GuideCCD.ImageExposureNP);
552  if (CanAbort())
553  defineProperty(&GuideCCD.AbortExposureSP);
554  if (CanSubFrame() == false)
555  GuideCCD.ImageFrameNP.p = IP_RO;
556  defineProperty(&GuideCCD.ImageFrameNP);
557  }
558 
559  if (HasCooler())
560  {
563  }
564 
567 
568  defineProperty(&PrimaryCCD.ImagePixelSizeNP);
569  if (HasGuideHead())
570  {
571  defineProperty(&GuideCCD.ImagePixelSizeNP);
572  if (CanBin())
573  defineProperty(&GuideCCD.ImageBinNP);
574  }
575  defineProperty(&PrimaryCCD.CompressSP);
576  defineProperty(&PrimaryCCD.FitsBP);
577  if (HasGuideHead())
578  {
579  defineProperty(&GuideCCD.CompressSP);
580  defineProperty(&GuideCCD.FitsBP);
581  }
582  if (HasST4Port())
583  {
586  }
587  defineProperty(&PrimaryCCD.FrameTypeSP);
588 
589  if (HasGuideHead())
590  defineProperty(&GuideCCD.FrameTypeSP);
591 
592  if (HasBayer())
594 
595 #if 0
596  defineProperty(&PrimaryCCD.RapidGuideSP);
597 
598  if (HasGuideHead())
599  defineProperty(&GuideCCD.RapidGuideSP);
600 
601  if (RapidGuideEnabled)
602  {
603  defineProperty(&PrimaryCCD.RapidGuideSetupSP);
604  defineProperty(&PrimaryCCD.RapidGuideDataNP);
605  }
606  if (GuiderRapidGuideEnabled)
607  {
608  defineProperty(&GuideCCD.RapidGuideSetupSP);
609  defineProperty(&GuideCCD.RapidGuideDataNP);
610  }
611 #endif
613 
616 
617  if (UploadSettingsT[UPLOAD_DIR].text == nullptr)
618  IUSaveText(&UploadSettingsT[UPLOAD_DIR], getenv("HOME"));
620 
621 #ifdef HAVE_WEBSOCKET
622  if (HasWebSocket())
624 #endif
625 
628  }
629  else
630  {
631  deleteProperty(PrimaryCCD.ImageFrameNP.name);
632  if (CanBin() || CanSubFrame())
633  deleteProperty(PrimaryCCD.ResetSP.name);
634 
635  deleteProperty(PrimaryCCD.ImagePixelSizeNP.name);
636 
639 
640  if (CanBin())
641  deleteProperty(PrimaryCCD.ImageBinNP.name);
642 
643  deleteProperty(PrimaryCCD.ImageExposureNP.name);
644  if (CanAbort())
645  deleteProperty(PrimaryCCD.AbortExposureSP.name);
647  deleteProperty(PrimaryCCD.CompressSP.name);
648 
649 #if 0
650  deleteProperty(PrimaryCCD.RapidGuideSP.name);
651  if (RapidGuideEnabled)
652  {
653  deleteProperty(PrimaryCCD.RapidGuideSetupSP.name);
654  deleteProperty(PrimaryCCD.RapidGuideDataNP.name);
655  }
656 #endif
657 
659 
660  if (HasGuideHead())
661  {
662  deleteProperty(GuideCCD.ImageExposureNP.name);
663  if (CanAbort())
664  deleteProperty(GuideCCD.AbortExposureSP.name);
665  deleteProperty(GuideCCD.ImageFrameNP.name);
666  deleteProperty(GuideCCD.ImagePixelSizeNP.name);
667 
668  deleteProperty(GuideCCD.FitsBP.name);
669  if (CanBin())
670  deleteProperty(GuideCCD.ImageBinNP.name);
671  deleteProperty(GuideCCD.CompressSP.name);
672  deleteProperty(GuideCCD.FrameTypeSP.name);
673 
674 #if 0
675  deleteProperty(GuideCCD.RapidGuideSP.name);
676  if (GuiderRapidGuideEnabled)
677  {
678  deleteProperty(GuideCCD.RapidGuideSetupSP.name);
679  deleteProperty(GuideCCD.RapidGuideDataNP.name);
680  }
681 #endif
682  }
683  if (HasCooler())
684  {
687  }
688  if (HasST4Port())
689  {
692  }
693  deleteProperty(PrimaryCCD.FrameTypeSP.name);
694  if (HasBayer())
697 
698  if (WorldCoordS[0].s == ISS_ON)
699  {
701  }
705 
706 #ifdef HAVE_WEBSOCKET
707  if (HasWebSocket())
708  {
711  }
712 #endif
715  }
716 
717  // Streamer
718  if (HasStreaming())
719  Streamer->updateProperties();
720 
721  // DSP
722  if (HasDSP())
723  DSP->updateProperties();
724 
725  return true;
726 }
727 
729 {
730  XMLEle * ep = nullptr;
731  const char * propName = findXMLAttValu(root, "name");
732 
733  if (IUSnoopNumber(root, &EqNP) == 0)
734  {
735  double newra, newdec;
736  newra = EqN[0].value;
737  newdec = EqN[1].value;
738  if ((newra != RA) || (newdec != Dec))
739  {
740  //IDLog("RA %4.2f Dec %4.2f Snooped RA %4.2f Dec %4.2f\n",RA,Dec,newra,newdec);
741  RA = newra;
742  Dec = newdec;
743  }
744  }
745  else if (IUSnoopNumber(root, &J2000EqNP) == 0)
746  {
747  float newra, newdec;
748  newra = J2000EqN[0].value;
749  newdec = J2000EqN[1].value;
750  if ((newra != J2000RA) || (newdec != J2000DE))
751  {
752  // IDLog("J2000 RA %4.2f Dec %4.2f Snooped RA %4.2f Dec %4.2f\n",J2000RA,J2000DE,newra,newdec);
753  J2000RA = newra;
754  J2000DE = newdec;
755  }
756  J2000Valid = true;
757  }
758  else if (!strcmp("TELESCOPE_PIER_SIDE", propName))
759  {
760  // set default to say we have no valid information from mount
761  pierSide = -1;
762  // crack the message
763  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
764  {
765  const char * elemName = findXMLAttValu(ep, "name");
766 
767  if (!strcmp(elemName, "PIER_EAST") && !strcmp(pcdataXMLEle(ep), "On"))
768  pierSide = 1;
769  else if (!strcmp(elemName, "PIER_WEST") && !strcmp(pcdataXMLEle(ep), "On"))
770  pierSide = 0;
771  }
772  }
773  else if (!strcmp(propName, "TELESCOPE_INFO"))
774  {
775  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
776  {
777  const char * name = findXMLAttValu(ep, "name");
778 
779  if (!strcmp(name, "TELESCOPE_APERTURE"))
780  {
781  snoopedAperture = atof(pcdataXMLEle(ep));
782  }
783  else if (!strcmp(name, "TELESCOPE_FOCAL_LENGTH"))
784  {
785  snoopedFocalLength = atof(pcdataXMLEle(ep));
786  }
787  }
788  }
789  else if (!strcmp(propName, "FILTER_NAME"))
790  {
791  LOG_DEBUG("SNOOP: FILTER_NAME update...");
792  FilterNames.clear();
793  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
794  FilterNames.push_back(pcdataXMLEle(ep));
795  LOGF_DEBUG("SNOOP: FILTER_NAME -> %s", join(FilterNames, ", ").c_str());
796 
797  }
798  else if (!strcmp(propName, "FILTER_SLOT"))
799  {
800  LOG_DEBUG("SNOOP: FILTER_SLOT update...");
801  CurrentFilterSlot = -1;
802  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
803  CurrentFilterSlot = atoi(pcdataXMLEle(ep));
804  LOGF_DEBUG("SNOOP: FILTER_SLOT is %d", CurrentFilterSlot);
805  }
806  else if (!strcmp(propName, "SKY_QUALITY"))
807  {
808  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
809  {
810  const char * name = findXMLAttValu(ep, "name");
811 
812  if (!strcmp(name, "SKY_BRIGHTNESS"))
813  {
814  MPSAS = atof(pcdataXMLEle(ep));
815  break;
816  }
817  }
818  }
819  else if (!strcmp(propName, "ABS_ROTATOR_ANGLE"))
820  {
821  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
822  {
823  const char * name = findXMLAttValu(ep, "name");
824 
825  if (!strcmp(name, "ANGLE"))
826  {
827  RotatorAngle = atof(pcdataXMLEle(ep));
828  break;
829  }
830  }
831  }
832 
833  // JJ ed 2019-12-10
834  else if (!strcmp(propName, "ABS_FOCUS_POSITION"))
835  {
836  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
837  {
838  const char * name = findXMLAttValu(ep, "name");
839 
840  if (!strcmp(name, "FOCUS_ABSOLUTE_POSITION"))
841  {
842  FocuserPos = atol(pcdataXMLEle(ep));
843  break;
844  }
845  }
846  }
847  else if (!strcmp(propName, "FOCUS_TEMPERATURE"))
848  {
849  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
850  {
851  const char * name = findXMLAttValu(ep, "name");
852 
853  if (!strcmp(name, "TEMPERATURE"))
854  {
855  FocuserTemp = atof(pcdataXMLEle(ep));
856  break;
857  }
858  }
859  }
860  //
861 
862  else if (!strcmp(propName, "GEOGRAPHIC_COORD"))
863  {
864  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
865  {
866  const char * name = findXMLAttValu(ep, "name");
867 
868  if (!strcmp(name, "LONG"))
869  {
870  Longitude = atof(pcdataXMLEle(ep));
871  if (Longitude > 180)
872  Longitude -= 360;
873  }
874  else if (!strcmp(name, "LAT"))
875  {
876  Latitude = atof(pcdataXMLEle(ep));
877  }
878  }
879  }
880 
881  return DefaultDevice::ISSnoopDevice(root);
882 }
883 
884 bool CCD::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
885 {
886  // first check if it's for our device
887  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
888  {
889  // This is for our device
890  // Now lets see if it's something we process here
891  if (!strcmp(name, ActiveDeviceTP.name))
892  {
894  IUUpdateText(&ActiveDeviceTP, texts, names, n);
895  IDSetText(&ActiveDeviceTP, nullptr);
896 
897  // Update the property name!
900  if (strlen(ActiveDeviceT[ACTIVE_TELESCOPE].text) > 0)
901  {
902  LOGF_DEBUG("Snopping on Mount %s", ActiveDeviceT[ACTIVE_TELESCOPE].text);
903  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_EOD_COORD");
904  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_COORD");
905  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "TELESCOPE_INFO");
906  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "GEOGRAPHIC_COORD");
907  }
908  else
909  {
910  LOG_DEBUG("No mount is set. Clearing all mount watchers.");
911  RA = std::numeric_limits<double>::quiet_NaN();
912  Dec = std::numeric_limits<double>::quiet_NaN();
913  J2000RA = std::numeric_limits<double>::quiet_NaN();
914  J2000DE = std::numeric_limits<double>::quiet_NaN();
915  Latitude = std::numeric_limits<double>::quiet_NaN();
916  Longitude = std::numeric_limits<double>::quiet_NaN();
917  Airmass = std::numeric_limits<double>::quiet_NaN();
918  Azimuth = std::numeric_limits<double>::quiet_NaN();
919  Altitude = std::numeric_limits<double>::quiet_NaN();
920  }
921 
922  if (strlen(ActiveDeviceT[ACTIVE_ROTATOR].text) > 0)
923  {
924  LOGF_DEBUG("Snopping on Rotator %s", ActiveDeviceT[ACTIVE_ROTATOR].text);
925  IDSnoopDevice(ActiveDeviceT[ACTIVE_ROTATOR].text, "ABS_ROTATOR_ANGLE");
926  }
927  else
928  {
929  LOG_DEBUG("No rotator is set. Clearing all rotator watchers.");
930  MPSAS = std::numeric_limits<double>::quiet_NaN();
931  }
932 
933  // JJ ed 2019-12-10
934  if (strlen(ActiveDeviceT[ACTIVE_FOCUSER].text) > 0)
935  {
936  LOGF_DEBUG("Snopping on Focuser %s", ActiveDeviceT[ACTIVE_FOCUSER].text);
937  IDSnoopDevice(ActiveDeviceT[ACTIVE_FOCUSER].text, "ABS_FOCUS_POSITION");
938  IDSnoopDevice(ActiveDeviceT[ACTIVE_FOCUSER].text, "FOCUS_TEMPERATURE");
939  }
940  else
941  {
942  LOG_DEBUG("No focuser is set. Clearing all focuser watchers.");
943  FocuserPos = -1;
944  FocuserTemp = std::numeric_limits<double>::quiet_NaN();
945  }
946 
947 
948  if (strlen(ActiveDeviceT[ACTIVE_FILTER].text) > 0)
949  {
950  LOGF_DEBUG("Snopping on Filter Wheel %s", ActiveDeviceT[ACTIVE_FILTER].text);
951  IDSnoopDevice(ActiveDeviceT[ACTIVE_FILTER].text, "FILTER_SLOT");
952  IDSnoopDevice(ActiveDeviceT[ACTIVE_FILTER].text, "FILTER_NAME");
953  }
954  else
955  {
956  LOG_DEBUG("No filter wheel is set. Clearing All filter wheel watchers.");
957  CurrentFilterSlot = -1;
958  }
959 
960  IDSnoopDevice(ActiveDeviceT[ACTIVE_SKYQUALITY].text, "SKY_QUALITY");
961 
962  // Tell children active devices was updated.
964 
965  // We processed this one, so, tell the world we did it
966  return true;
967  }
968 
969  if (!strcmp(name, BayerTP.name))
970  {
971  IUUpdateText(&BayerTP, texts, names, n);
972  BayerTP.s = IPS_OK;
973  IDSetText(&BayerTP, nullptr);
974  return true;
975  }
976 
977  // FITS Header
978  if (FITSHeaderTP.isNameMatch(name))
979  {
980  FITSHeaderTP.update(texts, names, n);
981 
982  std::string name = FITSHeaderTP[KEYWORD_NAME].getText();
983  std::string value = FITSHeaderTP[KEYWORD_VALUE].getText();
984  std::string comment = FITSHeaderTP[KEYWORD_COMMENT].getText();
985 
986  if (name.empty() && value.empty() && comment.empty())
987  {
988  LOG_ERROR("Cannot add an empty FITS record.");
990  }
991  else
992  {
994  // Specical keyword
995  if (name == "INDI_CLEAR")
996  {
997  m_CustomFITSKeywords.clear();
998  LOG_INFO("Custom FITS headers cleared.");
999  }
1000  else if (name.empty() == false && value.empty() == false)
1001  {
1002  // Double regex
1003  std::regex checkDouble("^[-+]?([0-9]*?[.,][0-9]+|[0-9]+)$");
1004  // Integer regex
1005  std::regex checkInteger("^[-+]?([0-9]*)$");
1006 
1007  try
1008  {
1009  // Try long
1010  if (std::regex_match(value, checkInteger))
1011  {
1012  auto lValue = std::stol(value);
1013  FITSRecord record(name.c_str(), lValue, comment.c_str());
1014  m_CustomFITSKeywords[name.c_str()] = record;
1015  }
1016  // Try double
1017  else if (std::regex_match(value, checkDouble))
1018  {
1019  auto dValue = std::stod(value);
1020  FITSRecord record(name.c_str(), dValue, 6, comment.c_str());
1021  m_CustomFITSKeywords[name.c_str()] = record;
1022  }
1023  // Store as text
1024  else
1025  {
1026  // String
1027  FITSRecord record(name.c_str(), value.c_str(), comment.c_str());
1028  m_CustomFITSKeywords[name.c_str()] = record;
1029  }
1030  }
1031  // In case conversion fails
1032  catch (std::exception &e)
1033  {
1034  // String
1035  FITSRecord record(name.c_str(), value.c_str(), comment.c_str());
1036  m_CustomFITSKeywords[name.c_str()] = record;
1037  }
1038  }
1039  else if (comment.empty() == false)
1040  {
1041  FITSRecord record(comment.c_str());
1042  m_CustomFITSKeywords[comment.c_str()] = record;
1043  }
1044  }
1045 
1046  FITSHeaderTP.apply();
1047  return true;
1048  }
1049 
1050  if (!strcmp(name, UploadSettingsTP.name))
1051  {
1052  IUUpdateText(&UploadSettingsTP, texts, names, n);
1054  IDSetText(&UploadSettingsTP, nullptr);
1055  return true;
1056  }
1057  }
1058 
1059  // Streamer
1060  if (HasStreaming())
1061  Streamer->ISNewText(dev, name, texts, names, n);
1062 
1063  // DSP
1064  if (HasDSP())
1065  DSP->ISNewText(dev, name, texts, names, n);
1066 
1067  return DefaultDevice::ISNewText(dev, name, texts, names, n);
1068 }
1069 
1070 bool CCD::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
1071 {
1072  // first check if it's for our device
1073  //IDLog("CCD::ISNewNumber %s\n",name);
1074  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1075  {
1076  if (!strcmp(name, "CCD_EXPOSURE"))
1077  {
1079  (values[0] < PrimaryCCD.ImageExposureN[0].min || values[0] > PrimaryCCD.ImageExposureN[0].max))
1080  {
1081  LOGF_ERROR("Requested exposure value (%g) seconds out of bounds [%g,%g].",
1082  values[0], PrimaryCCD.ImageExposureN[0].min, PrimaryCCD.ImageExposureN[0].max);
1083  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1084  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1085  return false;
1086  }
1087 
1089  PrimaryCCD.ImageExposureN[0].value = ExposureTime = PrimaryCCD.ImageExposureN[0].min;
1090  else
1091  PrimaryCCD.ImageExposureN[0].value = ExposureTime = values[0];
1092 
1093  // Only abort when busy if we are not already in an exposure loops
1094  //if (PrimaryCCD.ImageExposureNP.s == IPS_BUSY && FastExposureToggleS[INDI_DISABLED].s == ISS_ON)
1095  if (PrimaryCCD.ImageExposureNP.s == IPS_BUSY)
1096  {
1097  if (CanAbort() && AbortExposure() == false)
1098  DEBUG(Logger::DBG_WARNING, "Warning: Aborting exposure failed.");
1099  }
1100 
1102  {
1103  PrimaryCCD.ImageExposureNP.s = IPS_BUSY;
1104  if (ExposureTime * 1000 < getCurrentPollingPeriod())
1106  }
1107  else
1108  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1109  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1110  return true;
1111  }
1112 
1113  if (!strcmp(name, "GUIDER_EXPOSURE"))
1114  {
1116  (values[0] < GuideCCD.ImageExposureN[0].min || values[0] > GuideCCD.ImageExposureN[0].max))
1117  {
1118  LOGF_ERROR("Requested guide exposure value (%g) seconds out of bounds [%g,%g].",
1119  values[0], GuideCCD.ImageExposureN[0].min, GuideCCD.ImageExposureN[0].max);
1120  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1121  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1122  return false;
1123  }
1124 
1126  GuideCCD.ImageExposureN[0].value = GuiderExposureTime = GuideCCD.ImageExposureN[0].min;
1127  else
1128  GuideCCD.ImageExposureN[0].value = GuiderExposureTime = values[0];
1129 
1130  GuideCCD.ImageExposureNP.s = IPS_BUSY;
1132  GuideCCD.ImageExposureNP.s = IPS_BUSY;
1133  else
1134  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1135  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1136  return true;
1137  }
1138 
1139  if (!strcmp(name, "CCD_BINNING"))
1140  {
1141  // We are being asked to set camera binning
1142  INumber * np = IUFindNumber(&PrimaryCCD.ImageBinNP, names[0]);
1143  if (np == nullptr)
1144  {
1145  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1146  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1147  return false;
1148  }
1149  else if (values[0] == 0 || values[1] == 0)
1150  {
1151  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1152  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1153  LOGF_ERROR("%.fx%.f binning is invalid.", values[0], values[1]);
1154  return false;
1155  }
1156 
1157  int binx, biny;
1158  if (!strcmp(np->name, "HOR_BIN"))
1159  {
1160  binx = values[0];
1161  biny = values[1];
1162  }
1163  else
1164  {
1165  binx = values[1];
1166  biny = values[0];
1167  }
1168 
1169  if (UpdateCCDBin(binx, biny))
1170  {
1171  IUUpdateNumber(&PrimaryCCD.ImageBinNP, values, names, n);
1172  PrimaryCCD.ImageBinNP.s = IPS_OK;
1173  }
1174  else
1175  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1176 
1177  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1178 
1179  return true;
1180  }
1181 
1182  if (!strcmp(name, "GUIDER_BINNING"))
1183  {
1184  // We are being asked to set camera binning
1185  INumber * np = IUFindNumber(&GuideCCD.ImageBinNP, names[0]);
1186  if (np == nullptr)
1187  {
1188  GuideCCD.ImageBinNP.s = IPS_ALERT;
1189  IDSetNumber(&GuideCCD.ImageBinNP, nullptr);
1190  return false;
1191  }
1192  else if (values[0] == 0 || values[1] == 0)
1193  {
1194  PrimaryCCD.ImageBinNP.s = IPS_ALERT;
1195  IDSetNumber(&PrimaryCCD.ImageBinNP, nullptr);
1196  LOGF_ERROR("%.fx%.f binning is invalid.", values[0], values[1]);
1197  return false;
1198  }
1199 
1200  int binx, biny;
1201  if (!strcmp(np->name, "HOR_BIN"))
1202  {
1203  binx = values[0];
1204  biny = values[1];
1205  }
1206  else
1207  {
1208  binx = values[1];
1209  biny = values[0];
1210  }
1211 
1212  if (UpdateGuiderBin(binx, biny))
1213  {
1214  IUUpdateNumber(&GuideCCD.ImageBinNP, values, names, n);
1215  GuideCCD.ImageBinNP.s = IPS_OK;
1216  }
1217  else
1218  GuideCCD.ImageBinNP.s = IPS_ALERT;
1219 
1220  IDSetNumber(&GuideCCD.ImageBinNP, nullptr);
1221 
1222  return true;
1223  }
1224 
1225  // Scope Information
1226  if (ScopeInfoNP.isNameMatch(name))
1227  {
1228  ScopeInfoNP.update(values, names, n);
1230  ScopeInfoNP.apply();
1231  return true;
1232  }
1233 
1234  if (!strcmp(name, "CCD_FRAME"))
1235  {
1236  int x = -1, y = -1, w = -1, h = -1;
1237  for (int i = 0; i < n; i++)
1238  {
1239  if (!strcmp(names[i], "X"))
1240  x = values[i];
1241  else if (!strcmp(names[i], "Y"))
1242  y = values[i];
1243  else if (!strcmp(names[i], "WIDTH"))
1244  w = values[i];
1245  else if (!strcmp(names[i], "HEIGHT"))
1246  h = values[i];
1247  }
1248 
1249  DEBUGF(Logger::DBG_DEBUG, "Requested CCD Frame is (%d,%d) (%d x %d)", x, y, w, h);
1250 
1251  if (x < 0 || y < 0 || w <= 0 || h <= 0)
1252  {
1253  LOGF_ERROR("Invalid frame requested (%d,%d) (%d x %d)", x, y, w, h);
1254  PrimaryCCD.ImageFrameNP.s = IPS_ALERT;
1255  IDSetNumber(&PrimaryCCD.ImageFrameNP, nullptr);
1256  return true;
1257  }
1258 
1259  if (UpdateCCDFrame(x, y, w, h))
1260  {
1261  PrimaryCCD.ImageFrameNP.s = IPS_OK;
1262  IUUpdateNumber(&PrimaryCCD.ImageFrameNP, values, names, n);
1263  }
1264  else
1265  PrimaryCCD.ImageFrameNP.s = IPS_ALERT;
1266 
1267  IDSetNumber(&PrimaryCCD.ImageFrameNP, nullptr);
1268  return true;
1269  }
1270 
1271  if (!strcmp(name, "GUIDER_FRAME"))
1272  {
1273  // We are being asked to set guide frame
1274  if (IUUpdateNumber(&GuideCCD.ImageFrameNP, values, names, n) < 0)
1275  return false;
1276 
1277  GuideCCD.ImageFrameNP.s = IPS_OK;
1278 
1279  DEBUGF(Logger::DBG_DEBUG, "Requested Guide Frame is %4.0f,%4.0f %4.0f x %4.0f", values[0], values[1],
1280  values[2], values[4]);
1281 
1282  if (UpdateGuiderFrame(GuideCCD.ImageFrameN[0].value, GuideCCD.ImageFrameN[1].value,
1283  GuideCCD.ImageFrameN[2].value, GuideCCD.ImageFrameN[3].value) == false)
1284  GuideCCD.ImageFrameNP.s = IPS_ALERT;
1285 
1286  IDSetNumber(&GuideCCD.ImageFrameNP, nullptr);
1287 
1288  return true;
1289  }
1290 
1291 #if 0
1292  if (!strcmp(name, "CCD_GUIDESTAR"))
1293  {
1294  PrimaryCCD.RapidGuideDataNP.s = IPS_OK;
1295  IUUpdateNumber(&PrimaryCCD.RapidGuideDataNP, values, names, n);
1296  IDSetNumber(&PrimaryCCD.RapidGuideDataNP, nullptr);
1297  return true;
1298  }
1299 
1300  if (!strcmp(name, "GUIDER_GUIDESTAR"))
1301  {
1302  GuideCCD.RapidGuideDataNP.s = IPS_OK;
1303  IUUpdateNumber(&GuideCCD.RapidGuideDataNP, values, names, n);
1304  IDSetNumber(&GuideCCD.RapidGuideDataNP, nullptr);
1305  return true;
1306  }
1307 #endif
1308 
1309  if (!strcmp(name, GuideNSNP.name) || !strcmp(name, GuideWENP.name))
1310  {
1311  processGuiderProperties(name, values, names, n);
1312  return true;
1313  }
1314 
1315  // Fast Exposure Count
1316  if (!strcmp(name, FastExposureCountNP.name))
1317  {
1318  IUUpdateNumber(&FastExposureCountNP, values, names, n);
1320  IDSetNumber(&FastExposureCountNP, nullptr);
1321  return true;
1322  }
1323 
1324  // CCD TEMPERATURE
1325  if (!strcmp(name, TemperatureNP.name))
1326  {
1327  if (values[0] < TemperatureN[0].min || values[0] > TemperatureN[0].max)
1328  {
1330  LOGF_ERROR("Error: Bad temperature value! Range is [%.1f, %.1f] [C].",
1331  TemperatureN[0].min, TemperatureN[0].max);
1332  IDSetNumber(&TemperatureNP, nullptr);
1333  return false;
1334  }
1335 
1336  double nextTemperature = values[0];
1337  // If temperature ramp is enabled, find
1338  if (TemperatureRampNP[RAMP_SLOPE].getValue() != 0)
1339  {
1340  if (values[0] < TemperatureN[0].value)
1341  {
1342  nextTemperature = std::max(values[0], TemperatureN[0].value - TemperatureRampNP[RAMP_SLOPE].getValue());
1343  }
1344  // Going up
1345  else
1346  {
1347  nextTemperature = std::min(values[0], TemperatureN[0].value + TemperatureRampNP[RAMP_SLOPE].getValue());
1348  }
1349  }
1350 
1351  int rc = SetTemperature(nextTemperature);
1352 
1353  if (rc == 0)
1354  {
1355  if (TemperatureRampNP[RAMP_SLOPE].getValue() != 0)
1357 
1358  m_TargetTemperature = values[0];
1361  }
1362  else if (rc == 1)
1364  else
1366 
1367  IDSetNumber(&TemperatureNP, nullptr);
1368  return true;
1369  }
1370 
1371  // Camera Temperature Ramp
1372  if (!strcmp(name, TemperatureRampNP.getName()))
1373  {
1374  double previousSlope = TemperatureRampNP[RAMP_SLOPE].getValue();
1375  double previousThreshold = TemperatureRampNP[RAMP_THRESHOLD].getValue();
1376  TemperatureRampNP.update(values, names, n);
1379  if (TemperatureRampNP[0].getValue() == 0)
1380  LOG_INFO("Temperature ramp is disabled.");
1381  else
1382  LOGF_INFO("Temperature ramp is enabled. Gradual cooling and warming is regulated at %.f Celsius per minute.",
1383  TemperatureRampNP[0].getValue());
1384 
1385  // Save config if there is a change
1386  if (std::abs(previousSlope - TemperatureRampNP[RAMP_SLOPE].getValue()) > 0 ||
1387  std::abs(previousThreshold - TemperatureRampNP[RAMP_THRESHOLD].getValue()) > 0.01)
1389  return true;
1390  }
1391 
1392  // Primary CCD Info
1393  if (!strcmp(name, PrimaryCCD.ImagePixelSizeNP.name))
1394  {
1395  if (IUUpdateNumber(&PrimaryCCD.ImagePixelSizeNP, values, names, n) == 0)
1396  {
1397  PrimaryCCD.ImagePixelSizeNP.s = IPS_OK;
1398  SetCCDParams(PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_X].value,
1399  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_Y].value,
1400  PrimaryCCD.getBPP(),
1401  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_X].value,
1402  PrimaryCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_Y].value);
1403  saveConfig(true, PrimaryCCD.ImagePixelSizeNP.name);
1404  }
1405  else
1406  PrimaryCCD.ImagePixelSizeNP.s = IPS_ALERT;
1407 
1408  IDSetNumber(&PrimaryCCD.ImagePixelSizeNP, nullptr);
1409  return true;
1410  }
1411 
1412  // Guide CCD Info
1413  if (!strcmp(name, GuideCCD.ImagePixelSizeNP.name))
1414  {
1415  IUUpdateNumber(&GuideCCD.ImagePixelSizeNP, values, names, n);
1416  GuideCCD.ImagePixelSizeNP.s = IPS_OK;
1417  SetGuiderParams(GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_X].value,
1418  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_MAX_Y].value, GuideCCD.getBPP(),
1419  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_X].value,
1420  GuideCCD.ImagePixelSizeNP.np[CCDChip::CCD_PIXEL_SIZE_Y].value);
1421  IDSetNumber(&GuideCCD.ImagePixelSizeNP, nullptr);
1422  saveConfig(true);
1423  return true;
1424  }
1425 
1426  // CCD Rotation
1427  if (!strcmp(name, CCDRotationNP.name))
1428  {
1429  IUUpdateNumber(&CCDRotationNP, values, names, n);
1431  IDSetNumber(&CCDRotationNP, nullptr);
1432  m_ValidCCDRotation = true;
1433 
1434  DEBUGF(Logger::DBG_SESSION, "CCD FOV rotation updated to %g degrees.", CCDRotationN[0].value);
1435 
1436  return true;
1437  }
1438  }
1439 
1440  // Streamer
1441  if (HasStreaming())
1442  Streamer->ISNewNumber(dev, name, values, names, n);
1443 
1444  // DSP
1445  if (HasDSP())
1446  DSP->ISNewNumber(dev, name, values, names, n);
1447 
1448  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
1449 }
1450 
1451 bool CCD::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
1452 {
1453  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1454  {
1455  // Upload Mode
1456  if (!strcmp(name, UploadSP.name))
1457  {
1458  int prevMode = IUFindOnSwitchIndex(&UploadSP);
1459  IUUpdateSwitch(&UploadSP, states, names, n);
1460 
1462  {
1463  if (UploadS[UPLOAD_CLIENT].s == ISS_ON)
1464  {
1465  DEBUG(Logger::DBG_SESSION, "Upload settings set to client only.");
1466  if (prevMode != 0)
1468  }
1469  else if (UploadS[UPLOAD_LOCAL].s == ISS_ON)
1470  {
1471  DEBUG(Logger::DBG_SESSION, "Upload settings set to local only.");
1473  }
1474  else
1475  {
1476  DEBUG(Logger::DBG_SESSION, "Upload settings set to client and local.");
1478  }
1479 
1480  UploadSP.s = IPS_OK;
1481  }
1482  else
1483  {
1485  UploadS[prevMode].s = ISS_ON;
1486  UploadSP.s = IPS_ALERT;
1487  }
1488 
1489  IDSetSwitch(&UploadSP, nullptr);
1490 
1491  return true;
1492  }
1493 
1494  // Fast Exposure Toggle
1495  if (!strcmp(name, FastExposureToggleSP.name))
1496  {
1497  IUUpdateSwitch(&FastExposureToggleSP, states, names, n);
1498 
1499  // Only display warning for the first time this is enabled.
1501  LOG_WARN("Experimental Feature: After a frame is downloaded, the next frame capture immediately starts to avoid any delays.");
1502 
1504  {
1506  IDSetNumber(&FastExposureCountNP, nullptr);
1507  m_UploadTime = 0;
1508  if (PrimaryCCD.isExposing())
1509  AbortExposure();
1510  }
1511 
1513  IDSetSwitch(&FastExposureToggleSP, nullptr);
1514  return true;
1515  }
1516 
1517 
1518 #ifdef HAVE_WEBSOCKET
1519  // Websocket Enable/Disable
1520  if (!strcmp(name, WebSocketSP.name))
1521  {
1522  IUUpdateSwitch(&WebSocketSP, states, names, n);
1523  WebSocketSP.s = IPS_OK;
1524 
1526  {
1527  wsThread = std::thread(&wsThreadHelper, this);
1528  WebSocketSettingsN[WS_SETTINGS_PORT].value = wsServer.generatePort();
1531  }
1532  else if (wsServer.is_running())
1533  {
1534  wsServer.stop();
1535  wsThread.join();
1537  }
1538 
1539  IDSetSwitch(&WebSocketSP, nullptr);
1540  return true;
1541  }
1542 #endif
1543 
1544  // WCS Enable/Disable
1545  if (!strcmp(name, WorldCoordSP.name))
1546  {
1547  IUUpdateSwitch(&WorldCoordSP, states, names, n);
1548  WorldCoordSP.s = IPS_OK;
1549 
1550  if (WorldCoordS[0].s == ISS_ON)
1551  {
1552  LOG_INFO("World Coordinate System is enabled.");
1554  }
1555  else
1556  {
1557  LOG_INFO("World Coordinate System is disabled.");
1559  }
1560 
1561  m_ValidCCDRotation = false;
1562  IDSetSwitch(&WorldCoordSP, nullptr);
1563  }
1564 
1565  // Primary Chip Frame Reset
1566  if (strcmp(name, PrimaryCCD.ResetSP.name) == 0)
1567  {
1568  IUResetSwitch(&PrimaryCCD.ResetSP);
1569  PrimaryCCD.ResetSP.s = IPS_OK;
1570  if (CanBin())
1571  UpdateCCDBin(1, 1);
1572  if (CanSubFrame())
1574 
1575  IDSetSwitch(&PrimaryCCD.ResetSP, nullptr);
1576  return true;
1577  }
1578 
1579  // Primary Chip Abort Expsoure
1580  if (strcmp(name, PrimaryCCD.AbortExposureSP.name) == 0)
1581  {
1582  IUResetSwitch(&PrimaryCCD.AbortExposureSP);
1583 
1584  if (AbortExposure())
1585  {
1586  PrimaryCCD.AbortExposureSP.s = IPS_OK;
1587  PrimaryCCD.ImageExposureNP.s = IPS_IDLE;
1588  PrimaryCCD.ImageExposureN[0].value = 0;
1589  }
1590  else
1591  {
1592  PrimaryCCD.AbortExposureSP.s = IPS_ALERT;
1593  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
1594  }
1595 
1597 
1598  // Fast Exposure Count
1600  {
1601  m_UploadTime = 0;
1603  FastExposureCountN[0].value = 1;
1604  IDSetNumber(&FastExposureCountNP, nullptr);
1605  }
1606 
1607  IDSetSwitch(&PrimaryCCD.AbortExposureSP, nullptr);
1608  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
1609 
1610  return true;
1611  }
1612 
1613  // Guide Chip Abort Exposure
1614  if (strcmp(name, GuideCCD.AbortExposureSP.name) == 0)
1615  {
1616  IUResetSwitch(&GuideCCD.AbortExposureSP);
1617 
1618  if (AbortGuideExposure())
1619  {
1620  GuideCCD.AbortExposureSP.s = IPS_OK;
1621  GuideCCD.ImageExposureNP.s = IPS_IDLE;
1622  GuideCCD.ImageExposureN[0].value = 0;
1623  }
1624  else
1625  {
1626  GuideCCD.AbortExposureSP.s = IPS_ALERT;
1627  GuideCCD.ImageExposureNP.s = IPS_ALERT;
1628  }
1629 
1630  IDSetSwitch(&GuideCCD.AbortExposureSP, nullptr);
1631  IDSetNumber(&GuideCCD.ImageExposureNP, nullptr);
1632 
1633  return true;
1634  }
1635 
1636  // Primary Chip Compression
1637  if (strcmp(name, PrimaryCCD.CompressSP.name) == 0)
1638  {
1639  IUUpdateSwitch(&PrimaryCCD.CompressSP, states, names, n);
1640  PrimaryCCD.CompressSP.s = IPS_OK;
1641  IDSetSwitch(&PrimaryCCD.CompressSP, nullptr);
1642  PrimaryCCD.SendCompressed = PrimaryCCD.CompressS[INDI_ENABLED].s == ISS_ON;
1643  return true;
1644  }
1645 
1646  // Guide Chip Compression
1647  if (strcmp(name, GuideCCD.CompressSP.name) == 0)
1648  {
1649  IUUpdateSwitch(&GuideCCD.CompressSP, states, names, n);
1650  GuideCCD.CompressSP.s = IPS_OK;
1651  IDSetSwitch(&GuideCCD.CompressSP, nullptr);
1652  GuideCCD.SendCompressed = GuideCCD.CompressS[INDI_ENABLED].s == ISS_ON;
1653  return true;
1654  }
1655 
1656  // Primary Chip Frame Type
1657  if (strcmp(name, PrimaryCCD.FrameTypeSP.name) == 0)
1658  {
1659  IUUpdateSwitch(&PrimaryCCD.FrameTypeSP, states, names, n);
1660  PrimaryCCD.FrameTypeSP.s = IPS_OK;
1661  if (PrimaryCCD.FrameTypeS[0].s == ISS_ON)
1663  else if (PrimaryCCD.FrameTypeS[1].s == ISS_ON)
1664  {
1666  if (HasShutter() == false)
1668  "The CCD does not have a shutter. Cover the camera in order to take a bias frame.");
1669  }
1670  else if (PrimaryCCD.FrameTypeS[2].s == ISS_ON)
1671  {
1673  if (HasShutter() == false)
1675  "The CCD does not have a shutter. Cover the camera in order to take a dark frame.");
1676  }
1677  else if (PrimaryCCD.FrameTypeS[3].s == ISS_ON)
1679 
1680  if (UpdateCCDFrameType(PrimaryCCD.getFrameType()) == false)
1681  PrimaryCCD.FrameTypeSP.s = IPS_ALERT;
1682 
1683  IDSetSwitch(&PrimaryCCD.FrameTypeSP, nullptr);
1684 
1685  return true;
1686  }
1687 
1688  // Guide Chip Frame Type
1689  if (strcmp(name, GuideCCD.FrameTypeSP.name) == 0)
1690  {
1691  // Compression Update
1692  IUUpdateSwitch(&GuideCCD.FrameTypeSP, states, names, n);
1693  GuideCCD.FrameTypeSP.s = IPS_OK;
1694  if (GuideCCD.FrameTypeS[0].s == ISS_ON)
1696  else if (GuideCCD.FrameTypeS[1].s == ISS_ON)
1697  {
1699  if (HasShutter() == false)
1701  "The CCD does not have a shutter. Cover the camera in order to take a bias frame.");
1702  }
1703  else if (GuideCCD.FrameTypeS[2].s == ISS_ON)
1704  {
1706  if (HasShutter() == false)
1708  "The CCD does not have a shutter. Cover the camera in order to take a dark frame.");
1709  }
1710  else if (GuideCCD.FrameTypeS[3].s == ISS_ON)
1712 
1714  GuideCCD.FrameTypeSP.s = IPS_ALERT;
1715 
1716  IDSetSwitch(&GuideCCD.FrameTypeSP, nullptr);
1717 
1718  return true;
1719  }
1720 
1721  // Capture Format
1722  if (CaptureFormatSP.isNameMatch(name))
1723  {
1724  int previousIndex = CaptureFormatSP.findOnSwitchIndex();
1725  CaptureFormatSP.update(states, names, n);
1726 
1729  else
1730  {
1731  if (previousIndex >= 0)
1732  {
1734  CaptureFormatSP[previousIndex].setState(ISS_ON);
1735  }
1737  }
1739 
1740  if (m_ConfigCaptureFormatName != CaptureFormatSP.findOnSwitch()->getName())
1741  {
1742  m_ConfigCaptureFormatName = CaptureFormatSP.findOnSwitch()->getName();
1744  }
1745 
1746  return true;
1747  }
1748 
1749  // Encode Format
1750  if (EncodeFormatSP.isNameMatch(name))
1751  {
1752  EncodeFormatSP.update(states, names, n);
1755 
1756  if (m_ConfigEncodeFormatIndex != EncodeFormatSP.findOnSwitchIndex())
1757  {
1758  m_ConfigEncodeFormatIndex = EncodeFormatSP.findOnSwitchIndex();
1760  }
1761 
1762  return true;
1763  }
1764 
1765 #if 0
1766  // Primary Chip Rapid Guide Enable/Disable
1767  if (strcmp(name, PrimaryCCD.RapidGuideSP.name) == 0)
1768  {
1769  IUUpdateSwitch(&PrimaryCCD.RapidGuideSP, states, names, n);
1770  PrimaryCCD.RapidGuideSP.s = IPS_OK;
1771  RapidGuideEnabled = (PrimaryCCD.RapidGuideS[0].s == ISS_ON);
1772 
1773  if (RapidGuideEnabled)
1774  {
1775  defineProperty(&PrimaryCCD.RapidGuideSetupSP);
1776  defineProperty(&PrimaryCCD.RapidGuideDataNP);
1777  }
1778  else
1779  {
1780  deleteProperty(PrimaryCCD.RapidGuideSetupSP.name);
1781  deleteProperty(PrimaryCCD.RapidGuideDataNP.name);
1782  }
1783 
1784  IDSetSwitch(&PrimaryCCD.RapidGuideSP, nullptr);
1785  return true;
1786  }
1787 
1788  // Guide Chip Rapid Guide Enable/Disable
1789  if (strcmp(name, GuideCCD.RapidGuideSP.name) == 0)
1790  {
1791  IUUpdateSwitch(&GuideCCD.RapidGuideSP, states, names, n);
1792  GuideCCD.RapidGuideSP.s = IPS_OK;
1793  GuiderRapidGuideEnabled = (GuideCCD.RapidGuideS[0].s == ISS_ON);
1794 
1795  if (GuiderRapidGuideEnabled)
1796  {
1797  defineProperty(&GuideCCD.RapidGuideSetupSP);
1798  defineProperty(&GuideCCD.RapidGuideDataNP);
1799  }
1800  else
1801  {
1802  deleteProperty(GuideCCD.RapidGuideSetupSP.name);
1803  deleteProperty(GuideCCD.RapidGuideDataNP.name);
1804  }
1805 
1806  IDSetSwitch(&GuideCCD.RapidGuideSP, nullptr);
1807  return true;
1808  }
1809 
1810  // Primary CCD Rapid Guide Setup
1811  if (strcmp(name, PrimaryCCD.RapidGuideSetupSP.name) == 0)
1812  {
1813  IUUpdateSwitch(&PrimaryCCD.RapidGuideSetupSP, states, names, n);
1814  PrimaryCCD.RapidGuideSetupSP.s = IPS_OK;
1815 
1816  AutoLoop = (PrimaryCCD.RapidGuideSetupS[0].s == ISS_ON);
1817  SendImage = (PrimaryCCD.RapidGuideSetupS[1].s == ISS_ON);
1818  ShowMarker = (PrimaryCCD.RapidGuideSetupS[2].s == ISS_ON);
1819 
1820  IDSetSwitch(&PrimaryCCD.RapidGuideSetupSP, nullptr);
1821  return true;
1822  }
1823 
1824  // Guide Chip Rapid Guide Setup
1825  if (strcmp(name, GuideCCD.RapidGuideSetupSP.name) == 0)
1826  {
1827  IUUpdateSwitch(&GuideCCD.RapidGuideSetupSP, states, names, n);
1828  GuideCCD.RapidGuideSetupSP.s = IPS_OK;
1829 
1830  GuiderAutoLoop = (GuideCCD.RapidGuideSetupS[0].s == ISS_ON);
1831  GuiderSendImage = (GuideCCD.RapidGuideSetupS[1].s == ISS_ON);
1832  GuiderShowMarker = (GuideCCD.RapidGuideSetupS[2].s == ISS_ON);
1833 
1834  IDSetSwitch(&GuideCCD.RapidGuideSetupSP, nullptr);
1835  return true;
1836  }
1837 #endif
1838  }
1839 
1840  if (HasStreaming())
1841  Streamer->ISNewSwitch(dev, name, states, names, n);
1842 
1843  // DSP
1844  if (HasDSP())
1845  DSP->ISNewSwitch(dev, name, states, names, n);
1846 
1847  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1848 }
1849 
1850 bool CCD::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
1851  char *formats[], char *names[], int n)
1852 {
1853  // DSP
1854  if (HasDSP())
1855  DSP->ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
1856 
1857  return DefaultDevice::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
1858 }
1859 
1860 int CCD::SetTemperature(double temperature)
1861 {
1862  INDI_UNUSED(temperature);
1863  DEBUGF(Logger::DBG_WARNING, "CCD::SetTemperature %4.2f - Should never get here", temperature);
1864  return -1;
1865 }
1866 
1867 bool CCD::StartExposure(float duration)
1868 {
1869  DEBUGF(Logger::DBG_WARNING, "CCD::StartExposure %4.2f - Should never get here", duration);
1870  return false;
1871 }
1872 
1873 bool CCD::StartGuideExposure(float duration)
1874 {
1875  DEBUGF(Logger::DBG_WARNING, "CCD::StartGuide Exposure %4.2f - Should never get here", duration);
1876  return false;
1877 }
1878 
1880 {
1881  DEBUG(Logger::DBG_WARNING, "CCD::AbortExposure - Should never get here");
1882  return false;
1883 }
1884 
1886 {
1887  DEBUG(Logger::DBG_WARNING, "CCD::AbortGuideExposure - Should never get here");
1888  return false;
1889 }
1890 
1891 bool CCD::UpdateCCDFrame(int x, int y, int w, int h)
1892 {
1893  // Just set value, unless HW layer overrides this and performs its own processing
1894  PrimaryCCD.setFrame(x, y, w, h);
1895  return true;
1896 }
1897 
1898 bool CCD::UpdateGuiderFrame(int x, int y, int w, int h)
1899 {
1900  GuideCCD.setFrame(x, y, w, h);
1901  return true;
1902 }
1903 
1904 bool CCD::UpdateCCDBin(int hor, int ver)
1905 {
1906  // Just set value, unless HW layer overrides this and performs its own processing
1907  PrimaryCCD.setBin(hor, ver);
1908  // Reset size
1909  if (HasStreaming())
1910  Streamer->setSize(PrimaryCCD.getSubW() / hor, PrimaryCCD.getSubH() / ver);
1911 
1912  // DSP
1913  if (HasDSP())
1914  DSP->setSizes(2, new int[2] { PrimaryCCD.getSubW() / hor, PrimaryCCD.getSubH() / ver });
1915 
1916  return true;
1917 }
1918 
1919 bool CCD::UpdateGuiderBin(int hor, int ver)
1920 {
1921  // Just set value, unless HW layer overrides this and performs its own processing
1922  GuideCCD.setBin(hor, ver);
1923  return true;
1924 }
1925 
1927 {
1928  INDI_UNUSED(fType);
1929  // Child classes can override this
1930  return true;
1931 }
1932 
1934 {
1935  INDI_UNUSED(fType);
1936  // Child classes can override this
1937  return true;
1938 }
1939 
1940 void CCD::addFITSKeywords(CCDChip * targetChip, std::vector<FITSRecord> &fitsKeywords)
1941 {
1942  char dev_name[MAXINDINAME] = {0};
1943  double effectiveFocalLength = std::numeric_limits<double>::quiet_NaN();
1944  double effectiveAperture = std::numeric_limits<double>::quiet_NaN();
1945 
1946 
1947  AutoCNumeric locale;
1948  fitsKeywords.push_back({"ROWORDER", "TOP-DOWN", "Row Order"});
1949  fitsKeywords.push_back({"INSTRUME", getDeviceName(), "CCD Name"});
1950 
1951  // Telescope
1952  if (strlen(ActiveDeviceT[ACTIVE_TELESCOPE].text) > 0)
1953  {
1954  fitsKeywords.push_back({"TELESCOP", ActiveDeviceT[0].text, "Telescope name"});
1955  }
1956 
1957  // Which scope is in effect
1958  // Prefer Scope Info over snooped property which should be deprecated.
1959  effectiveFocalLength = ScopeInfoNP[FocalLength].getValue() > 0 ? ScopeInfoNP[FocalLength].getValue() : snoopedFocalLength;
1960  effectiveAperture = ScopeInfoNP[Aperture].getValue() > 0 ? ScopeInfoNP[Aperture].getValue() : snoopedAperture;
1961 
1962  if (std::isnan(effectiveFocalLength))
1963  LOG_WARN("Telescope focal length is missing.");
1964  if (std::isnan(effectiveAperture))
1965  LOG_WARN("Telescope aperture is missing.");
1966 
1967  double subPixSize1 = static_cast<double>(targetChip->getPixelSizeX());
1968  double subPixSize2 = static_cast<double>(targetChip->getPixelSizeY());
1969  uint32_t subW = targetChip->getSubW();
1970  uint32_t subH = targetChip->getSubH();
1971  uint32_t subBinX = targetChip->getBinX();
1972  uint32_t subBinY = targetChip->getBinY();
1973 
1974  strncpy(dev_name, getDeviceName(), MAXINDINAME);
1975 
1976  fitsKeywords.push_back({"EXPTIME", exposureDuration, 6, "Total Exposure Time (s)"});
1977 
1978  if (targetChip->getFrameType() == CCDChip::DARK_FRAME)
1979  fitsKeywords.push_back({"DARKTIME", exposureDuration, 6, "Total Dark Exposure Time (s)"});
1980 
1981  // If the camera has a cooler OR if the temperature permission was explicitly set to Read-Only, then record the temperature
1982  if (HasCooler() || TemperatureNP.p == IP_RO)
1983  fitsKeywords.push_back({"CCD-TEMP", TemperatureN[0].value, 3, "CCD Temperature (Celsius)"});
1984 
1985  fitsKeywords.push_back({"PIXSIZE1", subPixSize1, 6, "Pixel Size 1 (microns)"});
1986  fitsKeywords.push_back({"PIXSIZE2", subPixSize2, 6, "Pixel Size 2 (microns)"});
1987  fitsKeywords.push_back({"XBINNING", targetChip->getBinX(), "Binning factor in width"});
1988  fitsKeywords.push_back({"YBINNING", targetChip->getBinY(), "Binning factor in height"});
1989  // XPIXSZ and YPIXSZ are logical sizes including the binning factor
1990  double xpixsz = subPixSize1 * subBinX;
1991  double ypixsz = subPixSize2 * subBinY;
1992  fitsKeywords.push_back({"XPIXSZ", xpixsz, 6, "X binned pixel size in microns"});
1993  fitsKeywords.push_back({"YPIXSZ", ypixsz, 6, "Y binned pixel size in microns"});
1994 
1995  switch (targetChip->getFrameType())
1996  {
1997  case CCDChip::LIGHT_FRAME:
1998  fitsKeywords.push_back({"FRAME", "Light", "Frame Type"});
1999  fitsKeywords.push_back({"IMAGETYP", "Light Frame", "Frame Type"});
2000  break;
2001  case CCDChip::BIAS_FRAME:
2002  fitsKeywords.push_back({"FRAME", "Bias", "Frame Type"});
2003  fitsKeywords.push_back({"IMAGETYP", "Bias Frame", "Frame Type"});
2004  break;
2005  case CCDChip::FLAT_FRAME:
2006  fitsKeywords.push_back({"FRAME", "Flat", "Frame Type"});
2007  fitsKeywords.push_back({"IMAGETYP", "Flat Frame", "Frame Type"});
2008  break;
2009  case CCDChip::DARK_FRAME:
2010  fitsKeywords.push_back({"FRAME", "Dark", "Frame Type"});
2011  fitsKeywords.push_back({"IMAGETYP", "Dark Frame", "Frame Type"});
2012  break;
2013  }
2014 
2015  if (CurrentFilterSlot != -1 && CurrentFilterSlot <= static_cast<int>(FilterNames.size()))
2016  {
2017  fitsKeywords.push_back({"FILTER", FilterNames.at(CurrentFilterSlot - 1).c_str(), "Filter"});
2018  }
2019 
2020 #ifdef WITH_MINMAX
2021  if (targetChip->getNAxis() == 2)
2022  {
2023  double min_val, max_val;
2024  getMinMax(&min_val, &max_val, targetChip);
2025 
2026  fitsKeywords.push_back({"DATAMIN", min_val, 6, "Minimum value"});
2027  fitsKeywords.push_back({"DATAMAX", max_val, 6, "Maximum value"});
2028  }
2029 #endif
2030 
2031  if (HasBayer() && targetChip->getNAxis() == 2)
2032  {
2033  fitsKeywords.push_back({"XBAYROFF", atoi(BayerT[0].text), "X offset of Bayer array"});
2034  fitsKeywords.push_back({"YBAYROFF", atoi(BayerT[1].text), "Y offset of Bayer array"});
2035  fitsKeywords.push_back({"BAYERPAT", BayerT[2].text, "Bayer color pattern"});
2036  }
2037 
2038  if (!std::isnan(effectiveFocalLength))
2039  fitsKeywords.push_back({"FOCALLEN", effectiveFocalLength, 3, "Focal Length (mm)"});
2040 
2041  if (!std::isnan(effectiveAperture))
2042  fitsKeywords.push_back({"APTDIA", effectiveAperture, 3, "Telescope diameter (mm)"});
2043 
2044  if (!std::isnan(MPSAS))
2045  {
2046  fitsKeywords.push_back({"MPSAS", MPSAS, 6, "Sky Quality (mag per arcsec^2)"});
2047  }
2048 
2049  if (!std::isnan(RotatorAngle))
2050  {
2051  fitsKeywords.push_back({"ROTATANG", RotatorAngle, 3, "Rotator angle in degrees"});
2052  }
2053 
2054  // JJ ed 2020-03-28
2055  // If the focus position or temperature is set, add the information to the FITS header
2056  if (FocuserPos != -1)
2057  {
2058  fitsKeywords.push_back({"FOCUSPOS", FocuserPos, "Focus position in steps"});
2059  }
2060  if (!std::isnan(FocuserTemp))
2061  {
2062  fitsKeywords.push_back({"FOCUSTEM", FocuserTemp, 3, "Focuser temperature in degrees C"});
2063  }
2064 
2065  // SCALE assuming square-pixels
2066  if (!std::isnan(effectiveFocalLength))
2067  {
2068  double pixScale = subPixSize1 / effectiveFocalLength * 206.3 * subBinX;
2069  fitsKeywords.push_back({"SCALE", pixScale, 6, "arcsecs per pixel"});
2070  }
2071 
2072 
2073  if ( targetChip->getFrameType() == CCDChip::LIGHT_FRAME && !std::isnan(RA) && !std::isnan(Dec) && (std::isnan(J2000RA)
2074  || std::isnan(J2000DE) || !J2000Valid) )
2075  {
2076  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
2077  epochPos.rightascension = RA;
2078  epochPos.declination = Dec;
2079 
2080  // Convert from JNow to J2000
2081  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
2082 
2083  J2000RA = J2000Pos.rightascension;
2084  J2000DE = J2000Pos.declination;
2085  }
2086  J2000Valid = false; // enforce usage of EOD position if we receive no new epoch position
2087 
2088  if ( targetChip->getFrameType() == CCDChip::LIGHT_FRAME && !std::isnan(J2000RA) && !std::isnan(J2000DE) )
2089  {
2090  if (!std::isnan(Latitude) && !std::isnan(Longitude))
2091  {
2092  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
2093 
2094  J2000Pos.rightascension = J2000RA;
2095  J2000Pos.declination = J2000DE;
2096 
2097  // Convert from JNow to J2000
2098  INDI::J2000toObserved(&J2000Pos, ln_get_julian_from_sys(), &epochPos);
2099 
2100  // Horizontal Coords
2101  INDI::IHorizontalCoordinates horizontalPos;
2102  IGeographicCoordinates observer;
2103  observer.latitude = Latitude;
2104  observer.longitude = Longitude;
2105 
2106  EquatorialToHorizontal(&epochPos, &observer, ln_get_julian_from_sys(), &horizontalPos);
2107  Azimuth = horizontalPos.azimuth;
2108  Altitude = horizontalPos.altitude;
2109  Airmass = ln_get_airmass(Altitude, 750);
2110  }
2111 
2112  char ra_str[32] = {0}, de_str[32] = {0};
2113 
2114  fs_sexa(ra_str, J2000RA, 2, 360000);
2115  fs_sexa(de_str, J2000DE, 2, 360000);
2116 
2117  char * raPtr = ra_str, *dePtr = de_str;
2118  while (*raPtr != '\0')
2119  {
2120  if (*raPtr == ':')
2121  *raPtr = ' ';
2122  raPtr++;
2123  }
2124  while (*dePtr != '\0')
2125  {
2126  if (*dePtr == ':')
2127  *dePtr = ' ';
2128  dePtr++;
2129  }
2130 
2131  if (!std::isnan(Latitude) && !std::isnan(Longitude))
2132  {
2133  fitsKeywords.push_back({"SITELAT", Latitude, 6, "Latitude of the imaging site in degrees"});
2134  fitsKeywords.push_back({"SITELONG", Longitude, 6, "Longitude of the imaging site in degrees"});
2135  }
2136  if (!std::isnan(Airmass))
2137  {
2138  //fits_update_key_s(fptr, TDOUBLE, "AIRMASS", &Airmass, "Airmass", &status);
2139  fitsKeywords.push_back({"AIRMASS", Airmass, 6, "Airmass"});
2140  fitsKeywords.push_back({"OBJCTAZ", Azimuth, 6, "Azimuth of center of image in Degrees"});
2141  fitsKeywords.push_back({"OBJCTALT", Altitude, 6, "Altitude of center of image in Degrees"});
2142  }
2143  fitsKeywords.push_back({"OBJCTRA", ra_str, "Object J2000 RA in Hours"});
2144  fitsKeywords.push_back({"OBJCTDEC", de_str, "Object J2000 DEC in Degrees"});
2145 
2146  fitsKeywords.push_back({"RA", J2000RA * 15, 6, "Object J2000 RA in Degrees"});
2147  fitsKeywords.push_back({"DEC", J2000DE, 6, "Object J2000 DEC in Degrees"});
2148 
2149  // pier side
2150  switch (pierSide)
2151  {
2152  case 0:
2153  fitsKeywords.push_back({"PIERSIDE", "WEST", "West, looking East"});
2154  break;
2155  case 1:
2156  fitsKeywords.push_back({"PIERSIDE", "EAST", "East, looking West"});
2157  break;
2158  }
2159 
2160  //fits_update_key_s(fptr, TINT, "EPOCH", &epoch, "Epoch", &status);
2161  fitsKeywords.push_back({"EQUINOX", 2000, "Equinox"});
2162 
2163  // Add WCS Info
2164  if (WorldCoordS[0].s == ISS_ON && m_ValidCCDRotation && !std::isnan(effectiveFocalLength))
2165  {
2166  double J2000RAHours = J2000RA * 15;
2167  fitsKeywords.push_back({"CRVAL1", J2000RAHours, 10, "CRVAL1"});
2168  fitsKeywords.push_back({"CRVAL2", J2000DE, 10, "CRVAL1"});
2169 
2170  char radecsys[8] = "FK5";
2171  char ctype1[16] = "RA---TAN";
2172  char ctype2[16] = "DEC--TAN";
2173 
2174  fitsKeywords.push_back({"RADECSYS", radecsys, "RADECSYS"});
2175  fitsKeywords.push_back({"CTYPE1", ctype1, "CTYPE1"});
2176  fitsKeywords.push_back({"CTYPE2", ctype2, "CTYPE2"});
2177 
2178  double crpix1 = subW / subBinX / 2.0;
2179  double crpix2 = subH / subBinY / 2.0;
2180 
2181  fitsKeywords.push_back({"CRPIX1", crpix1, 10, "CRPIX1"});
2182  fitsKeywords.push_back({"CRPIX2", crpix2, 10, "CRPIX2"});
2183 
2184  double secpix1 = subPixSize1 / effectiveFocalLength * 206.3 * subBinX;
2185  double secpix2 = subPixSize2 / effectiveFocalLength * 206.3 * subBinY;
2186 
2187  fitsKeywords.push_back({"SECPIX1", secpix1, 10, "SECPIX1"});
2188  fitsKeywords.push_back({"SECPIX2", secpix2, 10, "SECPIX2"});
2189 
2190  double degpix1 = secpix1 / 3600.0;
2191  double degpix2 = secpix2 / 3600.0;
2192 
2193  fitsKeywords.push_back({"CDELT1", degpix1, 10, "CDELT1"});
2194  fitsKeywords.push_back({"CDELT2", degpix2, 10, "CDELT2"});
2195 
2196  // Rotation is CW, we need to convert it to CCW per CROTA1 definition
2197  double rotation = 360 - CCDRotationN[0].value;
2198  if (rotation > 360)
2199  rotation -= 360;
2200 
2201  fitsKeywords.push_back({"CROTA1", rotation, 10, "CROTA1"});
2202  fitsKeywords.push_back({"CROTA2", rotation, 10, "CROTA2"});
2203 
2204  /*double cd[4];
2205  cd[0] = degpix1;
2206  cd[1] = 0;
2207  cd[2] = 0;
2208  cd[3] = degpix2;
2209 
2210  fits_update_key_s(fptr, TDOUBLE, "CD1_1", &cd[0], "CD1_1", &status);
2211  fits_update_key_s(fptr, TDOUBLE, "CD1_2", &cd[1], "CD1_2", &status);
2212  fits_update_key_s(fptr, TDOUBLE, "CD2_1", &cd[2], "CD2_1", &status);
2213  fits_update_key_s(fptr, TDOUBLE, "CD2_2", &cd[3], "CD2_2", &status);*/
2214  }
2215  }
2216 
2217  fitsKeywords.push_back({"DATE-OBS", exposureStartTime, "UTC start date of observation"});
2218  fitsKeywords.push_back(FITSRecord("Generated by INDI"));
2219 }
2220 
2221 void CCD::fits_update_key_s(fitsfile * fptr, int type, std::string name, void * p, std::string explanation,
2222  int * status)
2223 {
2224  // this function is for removing warnings about deprecated string conversion to char* (from arg 5)
2225  fits_update_key(fptr, type, name.c_str(), p, const_cast<char *>(explanation.c_str()), status);
2226 }
2227 
2228 bool CCD::ExposureComplete(CCDChip * targetChip)
2229 {
2230  // Reset POLLMS to default value
2231  setCurrentPollingPeriod(getPollingPeriod());
2232 
2233  // Run async
2234  std::thread(&CCD::ExposureCompletePrivate, this, targetChip).detach();
2235 
2236  return true;
2237 }
2238 
2239 bool CCD::ExposureCompletePrivate(CCDChip * targetChip)
2240 {
2241  LOG_DEBUG("Exposure complete");
2242 
2243  // save information used for the fits header
2244  exposureDuration = targetChip->getExposureDuration();
2245  strncpy(exposureStartTime, targetChip->getExposureStartTime(), MAXINDINAME);
2246 
2247  if(HasDSP())
2248  {
2249  uint8_t* buf = static_cast<uint8_t*>(malloc(targetChip->getFrameBufferSize()));
2250  memcpy(buf, targetChip->getFrameBuffer(), targetChip->getFrameBufferSize());
2251  DSP->processBLOB(buf, 2, new int[2] { targetChip->getXRes() / targetChip->getBinX(), targetChip->getYRes() / targetChip->getBinY() },
2252  targetChip->getBPP());
2253  free(buf);
2254  }
2255 
2256  if (processFastExposure(targetChip) == false)
2257  return false;
2258 
2259  bool sendImage = (UploadS[UPLOAD_CLIENT].s == ISS_ON || UploadS[UPLOAD_BOTH].s == ISS_ON);
2260  bool saveImage = (UploadS[UPLOAD_LOCAL].s == ISS_ON || UploadS[UPLOAD_BOTH].s == ISS_ON);
2261 
2262  // Do not send or save an empty image.
2263  if (targetChip->getFrameBufferSize() == 0)
2264  sendImage = saveImage = false;
2265 
2266  if (sendImage || saveImage)
2267  {
2268  if (EncodeFormatSP[FORMAT_FITS].getState() == ISS_ON)
2269  {
2270  targetChip->setImageExtension("fits");
2271 
2272  int img_type = 0;
2273  int byte_type = 0;
2274  int status = 0;
2275  long naxis = targetChip->getNAxis();
2276  long naxes[3];
2277  int nelements = 0;
2278  char error_status[MAXRBUF];
2279 
2280  naxes[0] = targetChip->getSubW() / targetChip->getBinX();
2281  naxes[1] = targetChip->getSubH() / targetChip->getBinY();
2282 
2283  switch (targetChip->getBPP())
2284  {
2285  case 8:
2286  byte_type = TBYTE;
2287  img_type = BYTE_IMG;
2288  break;
2289 
2290  case 16:
2291  byte_type = TUSHORT;
2292  img_type = USHORT_IMG;
2293  break;
2294 
2295  case 32:
2296  byte_type = TULONG;
2297  img_type = ULONG_IMG;
2298  break;
2299 
2300  default:
2301  LOGF_ERROR("Unsupported bits per pixel value %d", targetChip->getBPP());
2302  return false;
2303  }
2304 
2305  nelements = naxes[0] * naxes[1];
2306  if (naxis == 3)
2307  {
2308  nelements *= 3;
2309  naxes[2] = 3;
2310  }
2311 
2312  /*DEBUGF(Logger::DBG_DEBUG, "Exposure complete. Image Depth: %s. Width: %d Height: %d nelements: %d", bit_depth.c_str(), naxes[0],
2313  naxes[1], nelements);*/
2314 
2315  std::unique_lock<std::mutex> guard(ccdBufferLock);
2316 
2317  // 8640 = 2880 * 3 which is sufficient for most cases.
2318  uint32_t size = 8640 + nelements * (targetChip->getBPP() / 8);
2319  // Initialize FITS file.
2320  if (targetChip->openFITSFile(size, status) == false)
2321  {
2322  fits_report_error(stderr, status); /* print out any error messages */
2323  fits_get_errstatus(status, error_status);
2324  LOGF_ERROR("FITS Error: %s", error_status);
2325  return false;
2326  }
2327 
2328  auto fptr = *targetChip->fitsFilePointer();
2329 
2330  fits_create_img(fptr, img_type, naxis, naxes, &status);
2331 
2332  if (status)
2333  {
2334  fits_report_error(stderr, status); /* print out any error messages */
2335  fits_get_errstatus(status, error_status);
2336  LOGF_ERROR("FITS Error: %s", error_status);
2337  targetChip->closeFITSFile();
2338  return false;
2339  }
2340 
2341  std::vector<FITSRecord> fitsKeywords;
2342 
2343  addFITSKeywords(targetChip, fitsKeywords);
2344 
2345  // Add all custom keywords next
2346  for (auto &record : m_CustomFITSKeywords)
2347  fitsKeywords.push_back(record.second);
2348 
2349  for (auto &keyword : fitsKeywords)
2350  {
2351  int key_status = 0;
2352  switch(keyword.type())
2353  {
2355  break;
2357  fits_write_comment(fptr, keyword.comment().c_str(), &key_status);
2358  break;
2360  fits_update_key_str(fptr, keyword.key().c_str(), keyword.valueString().c_str(), keyword.comment().c_str(), &key_status);
2361  break;
2363  fits_update_key_lng(fptr, keyword.key().c_str(), keyword.valueInt(), keyword.comment().c_str(), &key_status);
2364  break;
2366  fits_update_key_dbl(fptr, keyword.key().c_str(), keyword.valueDouble(), keyword.decimal(), keyword.comment().c_str(),
2367  &key_status);
2368  break;
2369  }
2370  if (key_status)
2371  {
2372  fits_get_errstatus(key_status, error_status);
2373  LOGF_ERROR("FITS key %s Error: %s", keyword.key().c_str(), error_status);
2374  }
2375  }
2376 
2377  fits_write_img(fptr, byte_type, 1, nelements, targetChip->getFrameBuffer(), &status);
2378  targetChip->finishFITSFile(status);
2379  if (status)
2380  {
2381  fits_report_error(stderr, status); /* print out any error messages */
2382  fits_get_errstatus(status, error_status);
2383  LOGF_ERROR("FITS Error: %s", error_status);
2384  targetChip->closeFITSFile();
2385  return false;
2386  }
2387 
2388 
2389  bool rc = uploadFile(targetChip, *(targetChip->fitsMemoryBlockPointer()), *(targetChip->fitsMemorySizePointer()), sendImage,
2390  saveImage);
2391 
2392  targetChip->closeFITSFile();
2393 
2394  guard.unlock();
2395 
2396  if (rc == false)
2397  {
2398  targetChip->setExposureFailed();
2399  return false;
2400  }
2401  }
2402 #ifdef HAVE_XISF
2403  else if (EncodeFormatSP[FORMAT_XISF].getState() == ISS_ON)
2404  {
2405  std::vector<FITSRecord> fitsKeywords;
2406  addFITSKeywords(targetChip, fitsKeywords);
2407  targetChip->setImageExtension("xisf");
2408 
2409  try
2410  {
2411  AutoCNumeric locale;
2412  LibXISF::Image image;
2413  LibXISF::XISFWriter xisfWriter;
2414 
2415  for (auto &keyword : fitsKeywords)
2416  {
2417  image.addFITSKeyword({keyword.key().c_str(), keyword.valueString().c_str(), keyword.comment().c_str()});
2418  image.addFITSKeywordAsProperty(keyword.key().c_str(), keyword.valueString());
2419  }
2420 
2421  image.setGeometry(targetChip->getSubW() / targetChip->getBinX(),
2422  targetChip->getSubH() / targetChip->getBinY(),
2423  targetChip->getNAxis() == 2 ? 1 : 3);
2424  switch(targetChip->getBPP())
2425  {
2426  case 8:
2427  image.setSampleFormat(LibXISF::Image::UInt8);
2428  break;
2429  case 16:
2430  image.setSampleFormat(LibXISF::Image::UInt16);
2431  break;
2432  case 32:
2433  image.setSampleFormat(LibXISF::Image::UInt32);
2434  break;
2435  default:
2436  LOGF_ERROR("Unsupported bits per pixel value %d", targetChip->getBPP());
2437  return false;
2438  }
2439 
2440  switch(targetChip->getFrameType())
2441  {
2442  case CCDChip::LIGHT_FRAME:
2443  image.setImageType(LibXISF::Image::Light);
2444  break;
2445  case CCDChip::BIAS_FRAME:
2446  image.setImageType(LibXISF::Image::Bias);
2447  break;
2448  case CCDChip::DARK_FRAME:
2449  image.setImageType(LibXISF::Image::Dark);
2450  break;
2451  case CCDChip::FLAT_FRAME:
2452  image.setImageType(LibXISF::Image::Flat);
2453  break;
2454  }
2455 
2456  if (targetChip->SendCompressed)
2457  {
2458  image.setCompression(LibXISF::DataBlock::LZ4);
2459  image.setByteshuffling(targetChip->getBPP() / 8);
2460  }
2461 
2462  if (HasBayer())
2463  image.setColorFilterArray({2, 2, BayerT[2].text});
2464 
2465  if (targetChip->getNAxis() == 3)
2466  {
2467  image.setColorSpace(LibXISF::Image::RGB);
2468  }
2469 
2470  std::unique_lock<std::mutex> guard(ccdBufferLock);
2471  std::memcpy(image.imageData(), targetChip->getFrameBuffer(), image.imageDataSize());
2472  xisfWriter.writeImage(image);
2473 
2474  LibXISF::ByteArray xisfFile;
2475  xisfWriter.save(xisfFile);
2476  bool rc = uploadFile(targetChip, xisfFile.data(), xisfFile.size(), sendImage, saveImage);
2477  if (rc == false)
2478  {
2479  targetChip->setExposureFailed();
2480  return false;
2481  }
2482  }
2483  catch (LibXISF::Error &error)
2484  {
2485  LOGF_ERROR("XISF Error: %s", error.what());
2486  return false;
2487  }
2488  }
2489 #endif
2490  else
2491  {
2492  // If image extension was set to fits (default), change if bin if not already set to another format by the driver.
2493  if (!strcmp(targetChip->getImageExtension(), "fits"))
2494  targetChip->setImageExtension("bin");
2495  std::unique_lock<std::mutex> guard(ccdBufferLock);
2496  bool rc = uploadFile(targetChip, targetChip->getFrameBuffer(), targetChip->getFrameBufferSize(), sendImage,
2497  saveImage);
2498  guard.unlock();
2499 
2500  if (rc == false)
2501  {
2502  targetChip->setExposureFailed();
2503  return false;
2504  }
2505  }
2506  }
2507 
2508  if (FastExposureToggleS[INDI_ENABLED].s != ISS_ON)
2509  targetChip->setExposureComplete();
2510 
2511  UploadComplete(targetChip);
2512  return true;
2513 }
2514 
2515 bool CCD::uploadFile(CCDChip * targetChip, const void * fitsData, size_t totalBytes, bool sendImage,
2516  bool saveImage)
2517 {
2518  uint8_t * compressedData = nullptr;
2519 
2520  DEBUGF(Logger::DBG_DEBUG, "Uploading file. Ext: %s, Size: %d, sendImage? %s, saveImage? %s",
2521  targetChip->getImageExtension(), totalBytes, sendImage ? "Yes" : "No", saveImage ? "Yes" : "No");
2522 
2523  if (saveImage)
2524  {
2525  targetChip->FitsB.blob = const_cast<void *>(fitsData);
2526  targetChip->FitsB.bloblen = totalBytes;
2527  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s", targetChip->getImageExtension());
2528 
2529  FILE * fp = nullptr;
2530  char imageFileName[MAXRBUF];
2531 
2532  std::string prefix = UploadSettingsT[UPLOAD_PREFIX].text;
2533  int maxIndex = getFileIndex(UploadSettingsT[UPLOAD_DIR].text, UploadSettingsT[UPLOAD_PREFIX].text,
2534  targetChip->FitsB.format);
2535 
2536  if (maxIndex < 0)
2537  {
2538  LOGF_ERROR("Error iterating directory %s. %s", UploadSettingsT[0].text,
2539  strerror(errno));
2540  return false;
2541  }
2542 
2543  if (maxIndex > 0)
2544  {
2545  char ts[32];
2546  struct tm * tp;
2547  time_t t;
2548  time(&t);
2549  tp = localtime(&t);
2550  strftime(ts, sizeof(ts), "%Y-%m-%dT%H-%M-%S", tp);
2551  std::string filets(ts);
2552  prefix = std::regex_replace(prefix, std::regex("ISO8601"), filets);
2553 
2554  char indexString[8];
2555  snprintf(indexString, 8, "%03d", maxIndex);
2556  std::string prefixIndex = indexString;
2557  //prefix.replace(prefix.find("XXX"), std::string::npos, prefixIndex);
2558  prefix = std::regex_replace(prefix, std::regex("XXX"), prefixIndex);
2559  }
2560 
2561  snprintf(imageFileName, MAXRBUF, "%s/%s%s", UploadSettingsT[0].text, prefix.c_str(), targetChip->FitsB.format);
2562 
2563  fp = fopen(imageFileName, "w");
2564  if (fp == nullptr)
2565  {
2566  LOGF_ERROR("Unable to save image file (%s). %s", imageFileName, strerror(errno));
2567  return false;
2568  }
2569 
2570  int n = 0;
2571  for (int nr = 0; nr < targetChip->FitsB.bloblen; nr += n)
2572  n = fwrite((static_cast<char *>(targetChip->FitsB.blob) + nr), 1, targetChip->FitsB.bloblen - nr, fp);
2573 
2574  fclose(fp);
2575 
2576  // Save image file path
2577  IUSaveText(&FileNameT[0], imageFileName);
2578 
2579  DEBUGF(Logger::DBG_SESSION, "Image saved to %s", imageFileName);
2580  FileNameTP.s = IPS_OK;
2581  IDSetText(&FileNameTP, nullptr);
2582  }
2583 
2584  if (targetChip->SendCompressed && EncodeFormatSP[FORMAT_XISF].getState() != ISS_ON)
2585  {
2586  if (EncodeFormatSP[FORMAT_FITS].getState() == ISS_ON && !strcmp(targetChip->getImageExtension(), "fits"))
2587  {
2588  fpstate fpvar;
2589  fp_init (&fpvar);
2590  size_t compressedBytes = 0;
2591  int islossless = 0;
2592  if (fp_pack_data_to_data(reinterpret_cast<const char *>(fitsData), totalBytes, &compressedData, &compressedBytes, fpvar,
2593  &islossless) < 0)
2594  {
2595  free(compressedData);
2596  LOG_ERROR("Error: Ran out of memory compressing image");
2597  return false;
2598  }
2599 
2600  targetChip->FitsB.blob = compressedData;
2601  targetChip->FitsB.bloblen = compressedBytes;
2602  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s.fz", targetChip->getImageExtension());
2603  }
2604  else
2605  {
2606  uLong compressedBytes = sizeof(char) * totalBytes + totalBytes / 64 + 16 + 3;
2607  compressedData = new uint8_t[compressedBytes];
2608 
2609  if (fitsData == nullptr || compressedData == nullptr)
2610  {
2611  if (compressedData)
2612  delete [] compressedData;
2613  LOG_ERROR("Error: Ran out of memory compressing image");
2614  return false;
2615  }
2616 
2617  int r = compress2(compressedData, &compressedBytes, (const Bytef *)fitsData, totalBytes, 9);
2618  if (r != Z_OK)
2619  {
2620  /* this should NEVER happen */
2621  LOG_ERROR("Error: Failed to compress image");
2622  delete [] compressedData;
2623  return false;
2624  }
2625 
2626  targetChip->FitsB.blob = compressedData;
2627  targetChip->FitsB.bloblen = compressedBytes;
2628  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s.z", targetChip->getImageExtension());
2629  }
2630  }
2631  else
2632  {
2633  targetChip->FitsB.blob = const_cast<void *>(fitsData);
2634  targetChip->FitsB.bloblen = totalBytes;
2635  snprintf(targetChip->FitsB.format, MAXINDIBLOBFMT, ".%s", targetChip->getImageExtension());
2636  }
2637 
2638  targetChip->FitsB.size = totalBytes;
2639  targetChip->FitsBP.s = IPS_OK;
2640 
2641  if (sendImage)
2642  {
2643 #ifdef HAVE_WEBSOCKET
2644  if (HasWebSocket() && WebSocketS[WEBSOCKET_ENABLED].s == ISS_ON)
2645  {
2646  auto start = std::chrono::high_resolution_clock::now();
2647 
2648  // Send format/size/..etc first later
2649  wsServer.send_text(std::string(targetChip->FitsB.format));
2650  wsServer.send_binary(targetChip->FitsB.blob, targetChip->FitsB.bloblen);
2651 
2652  auto end = std::chrono::high_resolution_clock::now();
2653  std::chrono::duration<double> diff = end - start;
2654  LOGF_DEBUG("Websocket transfer took %g seconds", diff.count());
2655  }
2656  else
2657 #endif
2658  {
2659  auto start = std::chrono::high_resolution_clock::now();
2660  IDSetBLOB(&targetChip->FitsBP, nullptr);
2661  auto end = std::chrono::high_resolution_clock::now();
2662  std::chrono::duration<double> diff = end - start;
2663  LOGF_DEBUG("BLOB transfer took %g seconds", diff.count());
2664  }
2665  }
2666 
2667  if (compressedData)
2668  delete [] compressedData;
2669 
2670  DEBUG(Logger::DBG_DEBUG, "Upload complete");
2671 
2672  return true;
2673 }
2674 
2675 bool CCD::processFastExposure(CCDChip * targetChip)
2676 {
2677  // If fast exposure is on, let's immediately take another capture
2678  if (FastExposureToggleS[INDI_ENABLED].s == ISS_ON)
2679  {
2680  targetChip->setExposureComplete();
2681  double duration = targetChip->getExposureDuration();
2682 
2683  // Check fast exposure count
2684  if (FastExposureCountN[0].value > 1)
2685  {
2686  if (UploadS[UPLOAD_LOCAL].s != ISS_ON)
2687  {
2688  if (FastExposureCountNP.s != IPS_BUSY)
2689  {
2690  FastExposureToggleStartup = std::chrono::system_clock::now();
2691  }
2692  else
2693  {
2694  auto end = std::chrono::system_clock::now();
2695 
2696  m_UploadTime = (std::chrono::duration_cast<std::chrono::milliseconds>(end - FastExposureToggleStartup)).count() / 1000.0 -
2697  duration;
2698  LOGF_DEBUG("Image download and upload/save took %.3f seconds.", m_UploadTime);
2699 
2700  FastExposureToggleStartup = end;
2701  }
2702  }
2703 
2704  FastExposureCountNP.s = IPS_BUSY;
2705  FastExposureCountN[0].value--;
2706  IDSetNumber(&FastExposureCountNP, nullptr);
2707 
2708  if (UploadS[UPLOAD_LOCAL].s == ISS_ON || m_UploadTime < duration)
2709  {
2710  if (StartExposure(duration))
2711  PrimaryCCD.ImageExposureNP.s = IPS_BUSY;
2712  else
2713  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
2714  if (duration * 1000 < getCurrentPollingPeriod())
2715  setCurrentPollingPeriod(duration * 950);
2716  }
2717  else
2718  {
2719  LOGF_ERROR("Rapid exposure not possible since upload time is %.2f seconds while exposure time is %.2f seconds.",
2720  m_UploadTime,
2721  duration);
2722  PrimaryCCD.ImageExposureNP.s = IPS_ALERT;
2723  IDSetNumber(&PrimaryCCD.ImageExposureNP, nullptr);
2724  FastExposureCountN[0].value = 1;
2725  FastExposureCountNP.s = IPS_IDLE;
2726  IDSetNumber(&FastExposureCountNP, nullptr);
2727  m_UploadTime = 0;
2728  return false;
2729  }
2730  }
2731  else
2732  {
2733  m_UploadTime = 0;
2734  FastExposureCountNP.s = IPS_IDLE;
2735  IDSetNumber(&FastExposureCountNP, nullptr);
2736  }
2737  }
2738 
2739  return true;
2740 }
2741 
2742 void CCD::SetCCDParams(int x, int y, int bpp, float xf, float yf)
2743 {
2744  PrimaryCCD.setResolution(x, y);
2745  PrimaryCCD.setFrame(0, 0, x, y);
2746  if (CanBin())
2747  PrimaryCCD.setBin(1, 1);
2748  PrimaryCCD.setPixelSize(xf, yf);
2749  PrimaryCCD.setBPP(bpp);
2750 }
2751 
2752 void CCD::SetGuiderParams(int x, int y, int bpp, float xf, float yf)
2753 {
2754  capability |= CCD_HAS_GUIDE_HEAD;
2755 
2756  GuideCCD.setResolution(x, y);
2757  GuideCCD.setFrame(0, 0, x, y);
2758  GuideCCD.setPixelSize(xf, yf);
2759  GuideCCD.setBPP(bpp);
2760 }
2761 
2762 bool CCD::saveConfigItems(FILE * fp)
2763 {
2764  DefaultDevice::saveConfigItems(fp);
2765 
2766  IUSaveConfigText(fp, &ActiveDeviceTP);
2767  IUSaveConfigSwitch(fp, &UploadSP);
2768  IUSaveConfigText(fp, &UploadSettingsTP);
2769  IUSaveConfigSwitch(fp, &FastExposureToggleSP);
2770 
2771  IUSaveConfigSwitch(fp, &PrimaryCCD.CompressSP);
2772 
2773  if (PrimaryCCD.getCCDInfo()->p != IP_RO)
2774  IUSaveConfigNumber(fp, PrimaryCCD.getCCDInfo());
2775 
2776  CaptureFormatSP.save(fp);
2777  EncodeFormatSP.save(fp);
2778 
2779  if (HasCooler())
2780  TemperatureRampNP.save(fp);
2781 
2782  if (HasGuideHead())
2783  {
2784  IUSaveConfigSwitch(fp, &GuideCCD.CompressSP);
2785  IUSaveConfigNumber(fp, &GuideCCD.ImageBinNP);
2786  }
2787 
2788  if (CanSubFrame() && PrimaryCCD.ImageFrameN[2].value > 0)
2789  IUSaveConfigNumber(fp, &PrimaryCCD.ImageFrameNP);
2790 
2791  if (CanBin())
2792  IUSaveConfigNumber(fp, &PrimaryCCD.ImageBinNP);
2793 
2794  if (HasBayer())
2795  IUSaveConfigText(fp, &BayerTP);
2796 
2797  if (HasStreaming())
2798  Streamer->saveConfigItems(fp);
2799 
2800  if (HasDSP())
2801  DSP->saveConfigItems(fp);
2802 
2803  return true;
2804 }
2805 
2806 IPState CCD::GuideNorth(uint32_t ms)
2807 {
2808  INDI_UNUSED(ms);
2809  LOG_ERROR("The CCD does not support guiding.");
2810  return IPS_ALERT;
2811 }
2812 
2813 IPState CCD::GuideSouth(uint32_t ms)
2814 {
2815  INDI_UNUSED(ms);
2816  LOG_ERROR("The CCD does not support guiding.");
2817  return IPS_ALERT;
2818 }
2819 
2820 IPState CCD::GuideEast(uint32_t ms)
2821 {
2822  INDI_UNUSED(ms);
2823  LOG_ERROR("The CCD does not support guiding.");
2824  return IPS_ALERT;
2825 }
2826 
2827 IPState CCD::GuideWest(uint32_t ms)
2828 {
2829  INDI_UNUSED(ms);
2830  LOG_ERROR("The CCD does not support guiding.");
2831  return IPS_ALERT;
2832 }
2833 
2834 void CCD::getMinMax(double * min, double * max, CCDChip * targetChip)
2835 {
2836  int ind = 0, i, j;
2837  int imageHeight = targetChip->getSubH() / targetChip->getBinY();
2838  int imageWidth = targetChip->getSubW() / targetChip->getBinX();
2839  double lmin = 0, lmax = 0;
2840 
2841  switch (targetChip->getBPP())
2842  {
2843  case 8:
2844  {
2845  uint8_t * imageBuffer = targetChip->getFrameBuffer();
2846  lmin = lmax = imageBuffer[0];
2847 
2848  for (i = 0; i < imageHeight; i++)
2849  for (j = 0; j < imageWidth; j++)
2850  {
2851  ind = (i * imageWidth) + j;
2852  if (imageBuffer[ind] < lmin)
2853  lmin = imageBuffer[ind];
2854  else if (imageBuffer[ind] > lmax)
2855  lmax = imageBuffer[ind];
2856  }
2857  }
2858  break;
2859 
2860  case 16:
2861  {
2862  uint16_t * imageBuffer = reinterpret_cast<uint16_t*>(targetChip->getFrameBuffer());
2863  lmin = lmax = imageBuffer[0];
2864 
2865  for (i = 0; i < imageHeight; i++)
2866  for (j = 0; j < imageWidth; j++)
2867  {
2868  ind = (i * imageWidth) + j;
2869  if (imageBuffer[ind] < lmin)
2870  lmin = imageBuffer[ind];
2871  else if (imageBuffer[ind] > lmax)
2872  lmax = imageBuffer[ind];
2873  }
2874  }
2875  break;
2876 
2877  case 32:
2878  {
2879  uint32_t * imageBuffer = reinterpret_cast<uint32_t*>(targetChip->getFrameBuffer());
2880  lmin = lmax = imageBuffer[0];
2881 
2882  for (i = 0; i < imageHeight; i++)
2883  for (j = 0; j < imageWidth; j++)
2884  {
2885  ind = (i * imageWidth) + j;
2886  if (imageBuffer[ind] < lmin)
2887  lmin = imageBuffer[ind];
2888  else if (imageBuffer[ind] > lmax)
2889  lmax = imageBuffer[ind];
2890  }
2891  }
2892  break;
2893  }
2894  *min = lmin;
2895  *max = lmax;
2896 }
2897 
2898 std::string regex_replace_compat(const std::string &input, const std::string &pattern, const std::string &replace)
2899 {
2900  std::stringstream s;
2901  std::regex_replace(std::ostreambuf_iterator<char>(s), input.begin(), input.end(), std::regex(pattern), replace);
2902  return s.str();
2903 }
2904 
2905 int CCD::getFileIndex(const char * dir, const char * prefix, const char * ext)
2906 {
2907  INDI_UNUSED(ext);
2908 
2909  DIR * dpdf = nullptr;
2910  struct dirent * epdf = nullptr;
2911  std::vector<std::string> files = std::vector<std::string>();
2912 
2913  std::string prefixIndex = prefix;
2914  prefixIndex = regex_replace_compat(prefixIndex, "_ISO8601", "");
2915  prefixIndex = regex_replace_compat(prefixIndex, "_XXX", "");
2916 
2917  // Create directory if does not exist
2918  struct stat st;
2919 
2920  if (stat(dir, &st) == -1)
2921  {
2922  if (errno == ENOENT)
2923  {
2924  DEBUGF(Logger::DBG_DEBUG, "Creating directory %s...", dir);
2925  if (INDI::mkpath(dir, 0755) == -1)
2926  LOGF_ERROR("Error creating directory %s (%s)", dir, strerror(errno));
2927  }
2928  else
2929  {
2930  LOGF_ERROR("Couldn't stat directory %s: %s", dir, strerror(errno));
2931  return -1;
2932  }
2933  }
2934 
2935  dpdf = opendir(dir);
2936  if (dpdf != nullptr)
2937  {
2938  while ((epdf = readdir(dpdf)))
2939  {
2940  if (strstr(epdf->d_name, prefixIndex.c_str()))
2941  files.push_back(epdf->d_name);
2942  }
2943  }
2944  else
2945  {
2946  closedir(dpdf);
2947  return -1;
2948  }
2949  int maxIndex = 0;
2950 
2951  for (uint32_t i = 0; i < files.size(); i++)
2952  {
2953  int index = -1;
2954 
2955  std::string file = files.at(i);
2956  std::size_t start = file.find_last_of("_");
2957  std::size_t end = file.find_last_of(".");
2958  if (start != std::string::npos)
2959  {
2960  index = atoi(file.substr(start + 1, end).c_str());
2961  if (index > maxIndex)
2962  maxIndex = index;
2963  }
2964  }
2965 
2966  closedir(dpdf);
2967  return (maxIndex + 1);
2968 }
2969 
2970 void CCD::GuideComplete(INDI_EQ_AXIS axis)
2971 {
2972  GuiderInterface::GuideComplete(axis);
2973 }
2974 
2975 bool CCD::StartStreaming()
2976 {
2977  LOG_ERROR("Streaming is not supported.");
2978  return false;
2979 }
2980 
2981 bool CCD::StopStreaming()
2982 {
2983  LOG_ERROR("Streaming is not supported.");
2984  return false;
2985 }
2986 
2987 #ifdef HAVE_WEBSOCKET
2988 void CCD::wsThreadHelper(void * context)
2989 {
2990  static_cast<CCD *>(context)->wsThreadEntry();
2991 }
2992 
2993 void CCD::wsThreadEntry()
2994 {
2995  wsServer.run();
2996 }
2997 #endif
2998 
3002 void CCD::checkTemperatureTarget()
3003 {
3004  if (TemperatureNP.s == IPS_BUSY)
3005  {
3006  if (std::abs(m_TargetTemperature - TemperatureN[0].value) <= TemperatureRampNP[RAMP_THRESHOLD].getValue())
3007  {
3008  TemperatureNP.s = IPS_OK;
3009  m_TemperatureCheckTimer.stop();
3010  IDSetNumber(&TemperatureNP, nullptr);
3011  }
3012  // If we are beyond a minute, check for next step
3013  else if (TemperatureRampNP[RAMP_SLOPE].getValue() > 0 && m_TemperatureElapsedTimer.elapsed() >= 60000)
3014  {
3015  double nextTemperature = 0;
3016  // Going down
3017  if (m_TargetTemperature < TemperatureN[0].value)
3018  {
3019  nextTemperature = std::max(m_TargetTemperature, TemperatureN[0].value - TemperatureRampNP[RAMP_SLOPE].getValue());
3020  }
3021  // Going up
3022  else
3023  {
3024  nextTemperature = std::min(m_TargetTemperature, TemperatureN[0].value + TemperatureRampNP[RAMP_SLOPE].getValue());
3025  }
3026 
3027  m_TemperatureElapsedTimer.restart();
3028  SetTemperature(nextTemperature);
3029  }
3030  }
3031 }
3032 
3033 void CCD::addCaptureFormat(const CaptureFormat &format)
3034 {
3035  // Avoid duplicates.
3036  auto pos = std::find_if(m_CaptureFormats.begin(), m_CaptureFormats.end(), [format](auto & oneFormat)
3037  {
3038  return format.name == oneFormat.name;
3039  });
3040  if (pos != m_CaptureFormats.end())
3041  return;
3042 
3043  // Add NEW format.
3044  auto count = CaptureFormatSP.size();
3045  CaptureFormatSP.resize(count + 1);
3046  // Format is ON if the label matches the configuration label OR if there is no configuration saved and isDefault is true.
3047  const bool isOn = (format.name == m_ConfigCaptureFormatName) || (m_ConfigCaptureFormatName.empty() && format.isDefault);
3048  CaptureFormatSP[count].fill(format.name.c_str(), format.label.c_str(), isOn ? ISS_ON : ISS_OFF);
3049  m_CaptureFormats.push_back(format);
3050 }
3051 
3052 bool CCD::SetCaptureFormat(uint8_t index)
3053 {
3054  INDI_UNUSED(index);
3055  return true;
3056 }
3057 
3058 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
The CCDChip class provides functionality of a CCD Chip within a CCD.
Definition: indiccdchip.h:35
void setExposureComplete()
setExposureComplete Mark exposure as complete by setting ImageExposure property to IPS_OK
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
Definition: indiccdchip.h:209
bool finishFITSFile(int &status)
Finish any pending write to fits file.
Definition: indiccdchip.cpp:61
float getPixelSizeY() const
getPixelSizeY Get vertical pixel size in microns.
Definition: indiccdchip.h:158
size_t * fitsMemorySizePointer()
Definition: indiccdchip.h:421
int getSubH() const
getSubH Get the height of the frame
Definition: indiccdchip.h:122
double getExposureDuration() const
getExposureDuration Get requested exposure duration for the CCD chip in seconds.
Definition: indiccdchip.h:194
int getBPP() const
getBPP Get CCD Chip depth (bits per pixel).
Definition: indiccdchip.h:167
const char * getExposureStartTime()
getExposureStartTime
char * getImageExtension()
Definition: indiccdchip.h:391
CCD_FRAME getFrameType() const
isInterlaced
Definition: indiccdchip.h:249
int getBinY() const
getBinY Get vertical binning of the CCD chip.
Definition: indiccdchip.h:140
void closeFITSFile()
closeFITSFile Close the in-memory FITS File.
Definition: indiccdchip.cpp:72
float getPixelSizeX() const
getPixelSizeX Get horizontal pixel size in microns.
Definition: indiccdchip.h:149
void setFrame(uint32_t subx, uint32_t suby, uint32_t subw, uint32_t subh)
setFrame Set desired frame resolutoin for an exposure.
int getXRes() const
getXRes Get the horizontal resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:77
bool isExposing() const
Definition: indiccdchip.h:399
int getBinX() const
getBinX Get horizontal binning of the CCD chip.
Definition: indiccdchip.h:131
void setFrameType(CCD_FRAME type)
setFrameType Set desired frame type for next exposure.
Definition: indiccdchip.cpp:85
void setImageExtension(const char *ext)
setImageExtension Set image exntension
void ** fitsMemoryBlockPointer()
Definition: indiccdchip.h:426
void setExposureFailed()
setExposureFailed Alert the client that the exposure failed.
int getSubW() const
getSubW Get the width of the frame
Definition: indiccdchip.h:113
fitsfile ** fitsFilePointer()
Definition: indiccdchip.h:416
int getFrameBufferSize() const
getFrameBufferSize Get allocated frame buffer size to hold the CCD image frame.
Definition: indiccdchip.h:176
void setBin(uint8_t hor, uint8_t ver)
setBin Set CCD Chip binnig
int getNAxis() const
int getYRes() const
Get the vertical resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:86
bool openFITSFile(uint32_t size, int &status)
openFITSFile Allocate memory buffer for internal FITS file structure and open
Definition: indiccdchip.cpp:40
Class to provide general functionality of CCD cameras with a single CCD sensor, or a primary CCD sens...
Definition: indiccd.h:116
INumberVectorProperty EqNP
Properties.
Definition: indiccd.h:642
bool InGuideExposure
Definition: indiccd.h:583
ISwitchVectorProperty UploadSP
Definition: indiccd.h:717
static constexpr const char * WCS_TAB
Definition: indiccd.h:163
virtual bool StartExposure(float duration)
Start exposing primary CCD chip.
Definition: indiccd.cpp:1867
double Longitude
Definition: indiccd.h:610
ITextVectorProperty ActiveDeviceTP
ActiveDeviceTP defines 4 devices the camera driver can listen to (snoop) for properties of interest s...
Definition: indiccd.h:661
CCDChip PrimaryCCD
Definition: indiccd.h:629
ISwitch WorldCoordS[2]
Definition: indiccd.h:754
INumberVectorProperty CCDRotationNP
Definition: indiccd.h:759
bool HasGuideHead()
Definition: indiccd.h:208
INumber J2000EqN[2]
Definition: indiccd.h:651
std::unique_ptr< StreamManager > Streamer
Definition: indiccd.h:627
@ ACTIVE_TELESCOPE
Definition: indiccd.h:667
@ ACTIVE_FILTER
Definition: indiccd.h:670
@ ACTIVE_FOCUSER
Definition: indiccd.h:669
@ ACTIVE_SKYQUALITY
Definition: indiccd.h:671
@ ACTIVE_ROTATOR
Definition: indiccd.h:668
bool HasCooler()
Definition: indiccd.h:232
IText BayerT[3]
Definition: indiccd.h:694
double J2000RA
Definition: indiccd.h:573
bool InExposure
Definition: indiccd.h:582
double GuiderExposureTime
Definition: indiccd.h:595
double Airmass
Definition: indiccd.h:608
virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n) override
Process the client newBLOB command.
Definition: indiccd.cpp:1850
ISwitchVectorProperty WebSocketSP
Definition: indiccd.h:737
bool CanAbort()
Definition: indiccd.h:184
ISwitch WebSocketS[2]
Definition: indiccd.h:736
INDI::Timer m_TemperatureCheckTimer
Definition: indiccd.h:616
static constexpr const char * GUIDE_CONTROL_TAB
Group Names.
Definition: indiccd.h:162
bool HasST4Port()
Definition: indiccd.h:224
bool HasWebSocket()
Definition: indiccd.h:265
@ UPLOAD_DIR
Definition: indiccd.h:723
@ UPLOAD_PREFIX
Definition: indiccd.h:724
@ KEYWORD_VALUE
Definition: indiccd.h:775
@ KEYWORD_NAME
Definition: indiccd.h:774
@ KEYWORD_COMMENT
Definition: indiccd.h:776
ITextVectorProperty FileNameTP
FileNameTP File name of locally-saved images. By default, images are uploaded to the client but when ...
Definition: indiccd.h:701
bool CanBin()
Definition: indiccd.h:192
virtual void activeDevicesUpdated()
activeDevicesUpdated Inform children that ActiveDevices property was updated so they can snoop on the...
Definition: indiccd.h:519
ISwitchVectorProperty FastExposureToggleSP
Definition: indiccd.h:763
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....
Definition: indiccd.cpp:1891
virtual void addFITSKeywords(CCDChip *targetChip, std::vector< FITSRecord > &fitsKeywords)
Generate FITS keywords that will be added to FIST/XISF file.
Definition: indiccd.cpp:1940
ITextVectorProperty BayerTP
BayerTP Bayer pattern offset and type.
Definition: indiccd.h:693
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: indiccd.cpp:1451
INDI::PropertyNumber TemperatureRampNP
Temperature Ramp in C/Min with configurable threshold.
Definition: indiccd.h:683
bool SendImage
Definition: indiccd.h:589
ITextVectorProperty UploadSettingsTP
Definition: indiccd.h:720
int CurrentFilterSlot
Definition: indiccd.h:623
bool ShowMarker
Definition: indiccd.h:591
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition: indiccd.cpp:516
virtual int SetTemperature(double temperature)
Set CCD temperature.
Definition: indiccd.cpp:1860
INDI::PropertyNumber ScopeInfoNP
Definition: indiccd.h:728
INumber TemperatureN[1]
Definition: indiccd.h:678
INumber WebSocketSettingsN[1]
Definition: indiccd.h:746
INDI::PropertyText FITSHeaderTP
Definition: indiccd.h:771
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:1898
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:1904
INumber CCDRotationN[1]
Definition: indiccd.h:758
INDI::PropertySwitch EncodeFormatSP
Specifies Driver image encoding format (FITS, Native, JPG, ..etc)
Definition: indiccd.h:708
double exposureDuration
Definition: indiccd.h:579
virtual bool SetCaptureFormat(uint8_t index)
SetCaptureFormat Set Active Capture format.
Definition: indiccd.cpp:3052
INumberVectorProperty J2000EqNP
J200EqNP Snoop property to read the equatorial J2000 coordinates of the mount. ActiveDeviceTP defines...
Definition: indiccd.h:650
bool GuiderAutoLoop
Definition: indiccd.h:588
@ WEBSOCKET_DISABLED
Definition: indiccd.h:741
@ WEBSOCKET_ENABLED
Definition: indiccd.h:740
long FocuserPos
Definition: indiccd.h:604
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indiccd.cpp:152
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: indiccd.cpp:884
double snoopedAperture
Definition: indiccd.h:581
virtual bool StartGuideExposure(float duration)
Start exposing guide CCD chip.
Definition: indiccd.cpp:1873
double J2000DE
Definition: indiccd.h:574
virtual bool AbortExposure()
Abort ongoing exposure.
Definition: indiccd.cpp:1879
bool GuiderSendImage
Definition: indiccd.h:590
CCD_UPLOAD_MODE
Definition: indiccd.h:136
@ UPLOAD_BOTH
Definition: indiccd.h:136
@ UPLOAD_LOCAL
Definition: indiccd.h:136
@ UPLOAD_CLIENT
Definition: indiccd.h:136
int pierSide
Definition: indiccd.h:570
char exposureStartTime[MAXINDINAME]
Definition: indiccd.h:578
virtual bool UpdateGuiderFrameType(CCDChip::CCD_FRAME fType)
CCD calls this function when Guide frame type is updated by the client.
Definition: indiccd.cpp:1933
INumberVectorProperty FastExposureCountNP
Definition: indiccd.h:767
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indiccd.cpp:528
ISwitch FastExposureToggleS[2]
Definition: indiccd.h:762
INumberVectorProperty WebSocketSettingsNP
Definition: indiccd.h:747
double Azimuth
Definition: indiccd.h:611
virtual void checkTemperatureTarget()
checkTemperatureTarget Checks the current temperature against target temperature and calculates the n...
Definition: indiccd.cpp:3002
bool J2000Valid
Definition: indiccd.h:575
double RotatorAngle
Definition: indiccd.h:601
std::vector< std::string > FilterNames
Definition: indiccd.h:622
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:2752
ISwitch UploadS[3]
Definition: indiccd.h:716
ISwitchVectorProperty WorldCoordSP
Definition: indiccd.h:755
@ WS_SETTINGS_PORT
Definition: indiccd.h:750
INDI::ElapsedTimer m_TemperatureElapsedTimer
Definition: indiccd.h:617
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:1919
bool HasDSP()
Definition: indiccd.h:273
bool CanSubFrame()
Definition: indiccd.h:200
INumber FastExposureCountN[1]
Definition: indiccd.h:766
bool HasStreaming()
Definition: indiccd.h:248
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition: indiccd.cpp:728
bool GuiderShowMarker
Definition: indiccd.h:592
INDI::PropertySwitch CaptureFormatSP
Specifies Camera NATIVE capture format (e.g. Mono, RGB, RAW8..etc).
Definition: indiccd.h:705
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. Al fields must be initialized.
Definition: indiccd.cpp:138
IText FileNameT[1]
Definition: indiccd.h:702
bool AutoLoop
Definition: indiccd.h:587
bool HasBayer()
Definition: indiccd.h:240
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: indiccd.cpp:1070
INumberVectorProperty TemperatureNP
TemperatureNP Camera Temperature in Celcius.
Definition: indiccd.h:677
IText ActiveDeviceT[5]
Definition: indiccd.h:664
double Latitude
Definition: indiccd.h:609
bool HasShutter()
Definition: indiccd.h:216
@ RAMP_THRESHOLD
Definition: indiccd.h:687
@ RAMP_SLOPE
Definition: indiccd.h:686
IText UploadSettingsT[2]
Definition: indiccd.h:719
double MPSAS
Definition: indiccd.h:598
CCDChip GuideCCD
Definition: indiccd.h:630
@ FORMAT_NATIVE
Definition: indiccd.h:712
@ FORMAT_XISF
Definition: indiccd.h:713
@ FORMAT_FITS
Definition: indiccd.h:711
static void wsThreadHelper(void *context)
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:1926
double RA
Definition: indiccd.h:567
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:2742
@ Aperture
Definition: indiccd.h:732
@ FocalLength
Definition: indiccd.h:731
virtual bool UpdateCCDUploadMode(CCD_UPLOAD_MODE mode)
CCD calls this function when client upload mode switch is updated.
Definition: indiccd.h:400
virtual ~CCD()
Definition: indiccd.cpp:131
double snoopedFocalLength
Definition: indiccd.h:581
double FocuserTemp
Definition: indiccd.h:605
double m_UploadTime
Definition: indiccd.h:768
double m_TargetTemperature
Definition: indiccd.h:615
INumber EqN[2]
Definition: indiccd.h:643
virtual bool AbortGuideExposure()
Abort ongoing exposure.
Definition: indiccd.cpp:1885
double Altitude
Definition: indiccd.h:612
double Dec
Definition: indiccd.h:567
double ExposureTime
Definition: indiccd.h:594
void addPollPeriodControl()
Add Polling period control to the driver.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setCurrentPollingPeriod(uint32_t msec)
setCurrentPollingPeriod Change the current polling period to call TimerHit() function in the driver.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initilize properties initial state and value. The child class must implement this function.
void syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Process the client newBLOB command.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void start()
Starts this timer. Once started, a timer value can be checked with elapsed().
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
void callOnTimeout(const std::function< void()> &callback)
Definition: inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition: inditimer.cpp:82
void setInterval(int msec)
Set the timeout interval in milliseconds.
Definition: inditimer.cpp:103
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
double max(void)
int errno
double min(void)
int fp_pack_data_to_data(const char *inputBuffer, size_t inputBufferSize, unsigned char **outputBuffer, size_t *outputBufferSize, fpstate fpvar, int *islossless)
Definition: fpackutil.c:1031
int fp_init(fpstate *fpptr)
Definition: fpackutil.c:159
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define MAXINDIBLOBFMT
Definition: indiapi.h:196
#define MAXINDIDEVICE
Definition: indiapi.h:193
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ IP_WO
Definition: indiapi.h:185
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_NOFMANY
Definition: indiapi.h:175
@ ISR_ATMOST1
Definition: indiapi.h:174
#define MAXINDINAME
Definition: indiapi.h:191
INDI_EQ_AXIS
Definition: indibasetypes.h:34
const char * IMAGE_SETTINGS_TAB
Definition: indiccd.cpp:61
std::string join(std::vector< std::string > const &strings, std::string delim)
Definition: indiccd.cpp:70
const char * IMAGE_INFO_TAB
Definition: indiccd.cpp:62
const char * GUIDE_HEAD_TAB
Definition: indiccd.cpp:63
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
Implementations for common driver routines.
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
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's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indidevapi.c:66
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
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's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
Definition: indidevapi.c:337
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
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's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
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's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
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's auxiliary elements will be set to NULL.
Definition: indidevapi.c:310
void IUFillBLOB(IBLOB *bp, const char *name, const char *label, const char *format)
Assign attributes for a BLOB property. The BLOB's data and auxiliary elements will be set to NULL.
Definition: indidevapi.c:216
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
int IUGetConfigOnSwitchName(const char *dev, const char *property, char *name, size_t size)
IUGetConfigOnSwitchLabel Opens configuration file and returns the name of the ON switch property,...
Definition: indidriver.c:771
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition: indidriver.c:889
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:143
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
int IUGetConfigOnSwitchIndex(const char *dev, const char *property, int *index)
IUGetConfigOnSwitchIndex Opens configuration file and reads single switch property to find ON switch ...
Definition: indidriver.c:711
void IDSetBLOB(const IBLOBVectorProperty *bvp, const char *fmt,...)
Definition: indidriver.c:1287
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition: indilogger.h:56
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition: indilogger.h:57
#define MAXRBUF
Definition: indiserver.cpp:102
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.cpp:644
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
The DSP Namespace adds signal processing to INDI drivers. Primarily written for sensors and detectors...
Definition: convolution.cpp:40
Namespace to encapsulate INDI client, drivers, and mediator classes.
void J2000toObserved(IEquatorialCoordinates *J2000pos, double jd, IEquatorialCoordinates *observed)
*J2000toObserved converts catalogue to observed
Definition: libastro.cpp:80
void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, IHorizontalCoordinates *position)
EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates.
Definition: libastro.cpp:140
void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos)
ObservedToJ2000 converts an observed position to a J2000 catalogue position removes aberration,...
Definition: libastro.cpp:50
std::string regex_replace_compat(const std::string &input, const std::string &pattern, const std::string &replace)
Definition: indiccd.cpp:2898
int mkpath(std::string s, mode_t mode)
Definition: indiutility.cpp:51
@ error
throw a parse_error exception in case of a tag
__le16 type
Definition: pwc-ioctl.h:0
const char * getName() const
One number descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:475
char device[MAXINDIDEVICE]
Definition: indiapi.h:321
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250
Definition: fpack.h:100
@ DARK_FRAME
Definition: stv.c:82
@ LIGHT_FRAME
Definition: stv.c:80
@ FLAT_FRAME
Definition: stv.c:83
@ BIAS_FRAME
Definition: stv.c:81
void uploadFile(const char *filename)
Definition: stv.c:1710
void addFITSKeywords(fitsfile *fptr, IMAGE_INFO *image_info)
Definition: stv.c:1597