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