Instrument Neutral Distributed Interface INDI  1.6.0
indidetector.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2010, 2017 Ilia Platone, Jasem Mutlaq. All rights reserved.
3 
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License version 2 as published by the Free Software Foundation.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 *******************************************************************************/
19 
20 #include "indidetector.h"
21 
22 #include "indicom.h"
23 
24 #include <fitsio.h>
25 
26 #include <libnova/julian_day.h>
27 #include <libnova/ln_types.h>
28 #include <libnova/precession.h>
29 
30 #include <regex>
31 
32 #include <dirent.h>
33 #include <cerrno>
34 #include <locale.h>
35 #include <cstdlib>
36 #include <zlib.h>
37 #include <sys/stat.h>
38 
39 
40 const char *CAPTURE_SETTINGS_TAB = "Capture Settings";
41 const char *CAPTURE_INFO_TAB = "Capture Info";
42 
43 // Create dir recursively
44 static int _det_mkdir(const char *dir, mode_t mode)
45 {
46  char tmp[PATH_MAX];
47  char *p = nullptr;
48  size_t len;
49 
50  snprintf(tmp, sizeof(tmp), "%s", dir);
51  len = strlen(tmp);
52  if (tmp[len - 1] == '/')
53  tmp[len - 1] = 0;
54  for (p = tmp + 1; *p; p++)
55  if (*p == '/')
56  {
57  *p = 0;
58  if (mkdir(tmp, mode) == -1 && errno != EEXIST)
59  return -1;
60  *p = '/';
61  }
62  if (mkdir(tmp, mode) == -1 && errno != EEXIST)
63  return -1;
64 
65  return 0;
66 }
67 
69 {
70  ContinuumBuffer = (uint8_t *)malloc(sizeof(uint8_t)); // Seed for realloc
71  ContinuumBufferSize = 0;
72  SpectrumBuffer = (double *)malloc(sizeof(double)); // Seed for realloc
73  SpectrumBufferSize = 0;
74 
75  BPS = 8;
76  NAxis = 2;
77 
78 
79  strncpy(captureExtention, "raw", MAXINDIBLOBFMT);
80 }
81 
83 {
84  free(ContinuumBuffer);
85  ContinuumBufferSize = 0;
86  ContinuumBuffer = nullptr;
87  free(SpectrumBuffer);
88  SpectrumBufferSize = 0;
89  SpectrumBuffer = nullptr;
90 }
91 
92 void DetectorDevice::setMinMaxStep(const char *property, const char *element, double min, double max, double step,
93  bool sendToClient)
94 {
95  INumberVectorProperty *vp = nullptr;
96 
97  if (!strcmp(property, FramedCaptureNP.name))
98  vp = &FramedCaptureNP;
99 
100  if (!strcmp(property, DetectorSettingsNP.name))
101  vp = &DetectorSettingsNP;
102 
103  INumber *np = IUFindNumber(vp, element);
104  if (np)
105  {
106  np->min = min;
107  np->max = max;
108  np->step = step;
109 
110  if (sendToClient)
111  IUUpdateMinMax(vp);
112  }
113 }
114 
116 {
117  samplerate = sr;
118 
119  DetectorSettingsN[DetectorDevice::DETECTOR_SAMPLERATE].value = sr;
120 
121  IDSetNumber(&DetectorSettingsNP, nullptr);
122 }
123 
125 {
126  Frequency = freq;
127 
128  DetectorSettingsN[DetectorDevice::DETECTOR_FREQUENCY].value = freq;
129 
130  IDSetNumber(&DetectorSettingsNP, nullptr);
131 }
132 
134 {
135  BPS = bbs;
136 
137  DetectorSettingsN[DetectorDevice::DETECTOR_BITSPERSAMPLE].value = BPS;
138 
139  IDSetNumber(&DetectorSettingsNP, nullptr);
140 }
141 
142 void DetectorDevice::setContinuumBufferSize(int nbuf, bool allocMem)
143 {
144  if (nbuf == ContinuumBufferSize)
145  return;
146 
147  ContinuumBufferSize = nbuf;
148 
149  if (allocMem == false)
150  return;
151 
152  ContinuumBuffer = (uint8_t *)realloc(ContinuumBuffer, nbuf * sizeof(uint8_t));
153 }
154 
155 void DetectorDevice::setSpectrumBufferSize(int nbuf, bool allocMem)
156 {
157  if (nbuf == SpectrumBufferSize)
158  return;
159 
160  SpectrumBufferSize = nbuf;
161 
162  if (allocMem == false)
163  return;
164 
165  SpectrumBuffer = (double *)realloc(SpectrumBuffer, nbuf * sizeof(double));
166 }
167 
168 void DetectorDevice::setCaptureLeft(double duration)
169 {
170  FramedCaptureN[0].value = duration;
171 
172  IDSetNumber(&FramedCaptureNP, nullptr);
173 }
174 
176 {
177  captureDuration = duration;
178  gettimeofday(&startCaptureTime, nullptr);
179 }
180 
182 {
183  static char ts[32];
184 
185  char iso8601[32];
186  struct tm *tp;
187  time_t t = (time_t)startCaptureTime.tv_sec;
188  int u = startCaptureTime.tv_usec / 1000.0;
189 
190  tp = gmtime(&t);
191  strftime(iso8601, sizeof(iso8601), "%Y-%m-%dT%H:%M:%S", tp);
192  snprintf(ts, 32, "%s.%03d", iso8601, u);
193  return (ts);
194 }
195 
197 {
198  FramedCaptureNP.s = IPS_ALERT;
199  IDSetNumber(&FramedCaptureNP, nullptr);
200 }
201 
203 {
204  return NAxis;
205 }
206 
208 {
209  NAxis = value;
210 }
211 
213 {
214  strncpy(captureExtention, ext, MAXINDIBLOBFMT);
215 }
216 
217 namespace INDI
218 {
219 
220 Detector::Detector()
221 {
222  //ctor
223  capability = 0;
224 
225  InCapture = false;
226 
227  AutoLoop = false;
228  SendCapture = false;
229  ShowMarker = false;
230 
231  CaptureTime = 0.0;
232  CurrentFilterSlot = -1;
233 
234  RA = -1000;
235  Dec = -1000;
236  MPSAS = -1000;
237  primaryAperture = primaryFocalLength - 1;
238 }
239 
240 Detector::~Detector()
241 {
242 }
243 
244 void Detector::SetDetectorCapability(uint32_t cap)
245 {
246  capability = cap;
247 
248  setDriverInterface(getDriverInterface());
249 }
250 
251 bool Detector::initProperties()
252 {
253  DefaultDevice::initProperties(); // let the base class flesh in what it wants
254 
255  // PrimaryDetector Temperature
256  IUFillNumber(&TemperatureN[0], "DETECTOR_TEMPERATURE_VALUE", "Temperature (C)", "%5.2f", -50.0, 50.0, 0., 0.);
257  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "DETECTOR_TEMPERATURE", "Temperature",
259 
260  /**********************************************/
261  /**************** Primary Device ****************/
262  /**********************************************/
263 
264  // PrimaryDetector Capture
265  IUFillNumber(&PrimaryDetector.FramedCaptureN[0], "DETECTOR_CAPTURE_VALUE", "Duration (s)", "%5.2f", 0.01, 3600, 1.0, 1.0);
266  IUFillNumberVector(&PrimaryDetector.FramedCaptureNP, PrimaryDetector.FramedCaptureN, 1, getDeviceName(), "DETECTOR_CAPTURE",
267  "Capture", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
268 
269  // PrimaryDetector Abort
270  IUFillSwitch(&PrimaryDetector.AbortCaptureS[0], "ABORT", "Abort", ISS_OFF);
271  IUFillSwitchVector(&PrimaryDetector.AbortCaptureSP, PrimaryDetector.AbortCaptureS, 1, getDeviceName(), "DETECTOR_ABORT_CAPTURE",
272  "Capture Abort", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
273 
274  // PrimaryDetector Info
275  IUFillNumber(&PrimaryDetector.DetectorSettingsN[DetectorDevice::DETECTOR_SAMPLERATE], "DETECTOR_SAMPLERATE", "Sample rate (SPS)", "%16.2f", 0.01, 1.0e+15, 0.01, 1.0e+6);
276  IUFillNumber(&PrimaryDetector.DetectorSettingsN[DetectorDevice::DETECTOR_FREQUENCY], "DETECTOR_FREQUENCY", "Center frequency (Hz)", "%16.2f", 0.01, 1.0e+15, 0.01, 1.42e+9);
277  IUFillNumber(&PrimaryDetector.DetectorSettingsN[DetectorDevice::DETECTOR_BITSPERSAMPLE], "DETECTOR_BITSPERSAMPLE", "Bits per sample", "%3.0f", 1, 64, 1, 8);
278  IUFillNumberVector(&PrimaryDetector.DetectorSettingsNP, PrimaryDetector.DetectorSettingsN, 3, getDeviceName(), "DETECTOR_SETTINGS", "Detector Settings", CAPTURE_SETTINGS_TAB, IP_RW, 60, IPS_IDLE);
279 
280  // PrimaryDetector Device Continuum Blob
281  IUFillBLOB(&PrimaryDetector.FitsB[0], "CONTINUUM", "Continuum", "");
282  IUFillBLOB(&PrimaryDetector.FitsB[1], "SPECTRUM", "Spectrum", "");
283  IUFillBLOBVector(&PrimaryDetector.FitsBP, PrimaryDetector.FitsB, 2, getDeviceName(), "DETECTOR", "Capture Data", CAPTURE_INFO_TAB,
284  IP_RO, 60, IPS_IDLE);
285 
286  /**********************************************/
287  /************** Upload Settings ***************/
288  /**********************************************/
289 
290  // Upload Mode
291  IUFillSwitch(&UploadS[0], "UPLOAD_CLIENT", "Client", ISS_ON);
292  IUFillSwitch(&UploadS[1], "UPLOAD_LOCAL", "Local", ISS_OFF);
293  IUFillSwitch(&UploadS[2], "UPLOAD_BOTH", "Both", ISS_OFF);
294  IUFillSwitchVector(&UploadSP, UploadS, 3, getDeviceName(), "UPLOAD_MODE", "Upload", OPTIONS_TAB, IP_RW, ISR_1OFMANY,
295  0, IPS_IDLE);
296 
297  // Upload Settings
298  IUFillText(&UploadSettingsT[UPLOAD_DIR], "UPLOAD_DIR", "Dir", "");
299  IUFillText(&UploadSettingsT[UPLOAD_PREFIX], "UPLOAD_PREFIX", "Prefix", "CAPTURE_XXX");
300  IUFillTextVector(&UploadSettingsTP, UploadSettingsT, 2, getDeviceName(), "UPLOAD_SETTINGS", "Upload Settings",
301  OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
302 
303  // Upload File Path
304  IUFillText(&FileNameT[0], "FILE_PATH", "Path", "");
305  IUFillTextVector(&FileNameTP, FileNameT, 1, getDeviceName(), "DETECTOR_FILE_PATH", "Filename", OPTIONS_TAB, IP_RO, 60,
306  IPS_IDLE);
307 
308  /**********************************************/
309  /****************** FITS Header****************/
310  /**********************************************/
311 
312  IUFillText(&FITSHeaderT[FITS_OBSERVER], "FITS_OBSERVER", "Observer", "Unknown");
313  IUFillText(&FITSHeaderT[FITS_OBJECT], "FITS_OBJECT", "Object", "Unknown");
314  IUFillTextVector(&FITSHeaderTP, FITSHeaderT, 2, getDeviceName(), "FITS_HEADER", "FITS Header", INFO_TAB, IP_RW, 60,
315  IPS_IDLE);
316 
317  /**********************************************/
318  /**************** Snooping ********************/
319  /**********************************************/
320 
321  // Snooped Devices
322  IUFillText(&ActiveDeviceT[0], "ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
323  IUFillText(&ActiveDeviceT[1], "ACTIVE_FOCUSER", "Focuser", "Focuser Simulator");
324  IUFillText(&ActiveDeviceT[2], "ACTIVE_FILTER", "Filter", "PrimaryDetector Simulator");
325  IUFillText(&ActiveDeviceT[3], "ACTIVE_SKYQUALITY", "Sky Quality", "SQM");
326  IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 4, getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB,
327  IP_RW, 60, IPS_IDLE);
328 
329  // Snooped RA/DEC Property
330  IUFillNumber(&EqN[0], "RA", "Ra (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
331  IUFillNumber(&EqN[1], "DEC", "Dec (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
332  IUFillNumberVector(&EqNP, EqN, 2, ActiveDeviceT[0].text, "EQUATORIAL_EOD_COORD", "EQ Coord", "Main Control", IP_RW,
333  60, IPS_IDLE);
334 
335  // Snoop properties of interest
336  IDSnoopDevice(ActiveDeviceT[0].text, "EQUATORIAL_EOD_COORD");
337  IDSnoopDevice(ActiveDeviceT[0].text, "TELESCOPE_INFO");
338  IDSnoopDevice(ActiveDeviceT[2].text, "FILTER_SLOT");
339  IDSnoopDevice(ActiveDeviceT[2].text, "FILTER_NAME");
340  IDSnoopDevice(ActiveDeviceT[3].text, "SKY_QUALITY");
341 
342  setDriverInterface(DETECTOR_INTERFACE);
343 
344  return true;
345 }
346 
347 void Detector::ISGetProperties(const char *dev)
348 {
350 
351  defineText(&ActiveDeviceTP);
352  loadConfig(true, "ACTIVE_DEVICES");
353 }
354 
355 bool Detector::updateProperties()
356 {
357  //IDLog("PrimaryDetector UpdateProperties isConnected returns %d %d\n",isConnected(),Connected);
358  if (isConnected())
359  {
360  defineNumber(&PrimaryDetector.FramedCaptureNP);
361 
362  if (CanAbort())
363  defineSwitch(&PrimaryDetector.AbortCaptureSP);
364 
365  defineText(&FITSHeaderTP);
366 
367  if (HasCooler())
368  defineNumber(&TemperatureNP);
369 
370  defineNumber(&PrimaryDetector.DetectorSettingsNP);
371  defineBLOB(&PrimaryDetector.FitsBP);
372 
373  defineSwitch(&TelescopeTypeSP);
374 
375  defineSwitch(&UploadSP);
376 
377  if (UploadSettingsT[UPLOAD_DIR].text == nullptr)
378  IUSaveText(&UploadSettingsT[UPLOAD_DIR], getenv("HOME"));
379  defineText(&UploadSettingsTP);
380  }
381  else
382  {
383  deleteProperty(PrimaryDetector.DetectorSettingsNP.name);
384 
385  deleteProperty(PrimaryDetector.FramedCaptureNP.name);
386  if (CanAbort())
387  deleteProperty(PrimaryDetector.AbortCaptureSP.name);
388  deleteProperty(PrimaryDetector.FitsBP.name);
389 
390  deleteProperty(FITSHeaderTP.name);
391 
392  if (HasCooler())
393  deleteProperty(TemperatureNP.name);
394 
395  deleteProperty(TelescopeTypeSP.name);
396 
397  deleteProperty(UploadSP.name);
398  deleteProperty(UploadSettingsTP.name);
399  }
400 
401  return true;
402 }
403 
405 {
406  XMLEle *ep = nullptr;
407  const char *propName = findXMLAttValu(root, "name");
408 
409  if (IUSnoopNumber(root, &EqNP) == 0)
410  {
411  float newra, newdec;
412  newra = EqN[0].value;
413  newdec = EqN[1].value;
414  if ((newra != RA) || (newdec != Dec))
415  {
416  //IDLog("RA %4.2f Dec %4.2f Snooped RA %4.2f Dec %4.2f\n",RA,Dec,newra,newdec);
417  RA = newra;
418  Dec = newdec;
419  }
420  }
421  else if (!strcmp(propName, "TELESCOPE_INFO"))
422  {
423  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
424  {
425  const char *name = findXMLAttValu(ep, "name");
426 
427  if (!strcmp(name, "TELESCOPE_APERTURE"))
428  {
429  primaryAperture = atof(pcdataXMLEle(ep));
430  }
431  else if (!strcmp(name, "TELESCOPE_FOCAL_LENGTH"))
432  {
433  primaryFocalLength = atof(pcdataXMLEle(ep));
434  }
435  }
436  }
437  else if (!strcmp(propName, "FILTER_NAME"))
438  {
439  FilterNames.clear();
440 
441  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
442  FilterNames.push_back(pcdataXMLEle(ep));
443  }
444  else if (!strcmp(propName, "FILTER_SLOT"))
445  {
446  CurrentFilterSlot = -1;
447  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
448  CurrentFilterSlot = atoi(pcdataXMLEle(ep));
449  }
450  else if (!strcmp(propName, "SKY_QUALITY"))
451  {
452  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
453  {
454  const char *name = findXMLAttValu(ep, "name");
455 
456  if (!strcmp(name, "SKY_BRIGHTNESS"))
457  {
458  MPSAS = atof(pcdataXMLEle(ep));
459  break;
460  }
461  }
462  }
463 
464  return DefaultDevice::ISSnoopDevice(root);
465 }
466 
467 bool Detector::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
468 {
469  // first check if it's for our device
470  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
471  {
472  // This is for our device
473  // Now lets see if it's something we process here
474  if (!strcmp(name, ActiveDeviceTP.name))
475  {
476  ActiveDeviceTP.s = IPS_OK;
477  IUUpdateText(&ActiveDeviceTP, texts, names, n);
478  IDSetText(&ActiveDeviceTP, nullptr);
479 
480  // Update the property name!
481  strncpy(EqNP.device, ActiveDeviceT[0].text, MAXINDIDEVICE);
482  IDSnoopDevice(ActiveDeviceT[0].text, "EQUATORIAL_EOD_COORD");
483  IDSnoopDevice(ActiveDeviceT[0].text, "TELESCOPE_INFO");
484  IDSnoopDevice(ActiveDeviceT[2].text, "FILTER_SLOT");
485  IDSnoopDevice(ActiveDeviceT[2].text, "FILTER_NAME");
486  IDSnoopDevice(ActiveDeviceT[3].text, "SKY_QUALITY");
487 
488  // Tell children active devices was updated.
489  activeDevicesUpdated();
490 
491  // We processed this one, so, tell the world we did it
492  return true;
493  }
494 
495  if (!strcmp(name, FITSHeaderTP.name))
496  {
497  IUUpdateText(&FITSHeaderTP, texts, names, n);
498  FITSHeaderTP.s = IPS_OK;
499  IDSetText(&FITSHeaderTP, nullptr);
500  return true;
501  }
502 
503  if (!strcmp(name, UploadSettingsTP.name))
504  {
505  IUUpdateText(&UploadSettingsTP, texts, names, n);
506  UploadSettingsTP.s = IPS_OK;
507  IDSetText(&UploadSettingsTP, nullptr);
508  return true;
509  }
510  }
511 
512  return DefaultDevice::ISNewText(dev, name, texts, names, n);
513 }
514 
515 bool Detector::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
516 {
517  // first check if it's for our device
518  //IDLog("Detector::ISNewNumber %s\n",name);
519  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
520  {
521  if (!strcmp(name, "DETECTOR_CAPTURE"))
522  {
523  if ((values[0] < PrimaryDetector.FramedCaptureN[0].min || values[0] > PrimaryDetector.FramedCaptureN[0].max))
524  {
525  DEBUGF(Logger::DBG_ERROR, "Requested capture value (%g) seconds out of bounds [%g,%g].",
526  values[0], PrimaryDetector.FramedCaptureN[0].min, PrimaryDetector.FramedCaptureN[0].max);
527  PrimaryDetector.FramedCaptureNP.s = IPS_ALERT;
528  IDSetNumber(&PrimaryDetector.FramedCaptureNP, nullptr);
529  return false;
530  }
531 
532  PrimaryDetector.FramedCaptureN[0].value = CaptureTime = values[0];
533 
534  if (PrimaryDetector.FramedCaptureNP.s == IPS_BUSY)
535  {
536  if (CanAbort() && AbortCapture() == false)
537  DEBUG(Logger::DBG_WARNING, "Warning: Aborting capture failed.");
538  }
539 
540  if (StartCapture(CaptureTime))
541  PrimaryDetector.FramedCaptureNP.s = IPS_BUSY;
542  else
543  PrimaryDetector.FramedCaptureNP.s = IPS_ALERT;
544  IDSetNumber(&PrimaryDetector.FramedCaptureNP, nullptr);
545  return true;
546  }
547 
548  // PrimaryDetector TEMPERATURE:
549  if (!strcmp(name, TemperatureNP.name))
550  {
551  if (values[0] < TemperatureN[0].min || values[0] > TemperatureN[0].max)
552  {
553  TemperatureNP.s = IPS_ALERT;
554  DEBUGF(Logger::DBG_ERROR, "Error: Bad temperature value! Range is [%.1f, %.1f] [C].",
555  TemperatureN[0].min, TemperatureN[0].max);
556  IDSetNumber(&TemperatureNP, nullptr);
557  return false;
558  }
559 
560  int rc = SetTemperature(values[0]);
561 
562  if (rc == 0)
563  TemperatureNP.s = IPS_BUSY;
564  else if (rc == 1)
565  TemperatureNP.s = IPS_OK;
566  else
567  TemperatureNP.s = IPS_ALERT;
568 
569  IDSetNumber(&TemperatureNP, nullptr);
570  return true;
571  }
572 
573  // PrimaryDetector Info
574  if (!strcmp(name, PrimaryDetector.DetectorSettingsNP.name))
575  {
576  IUUpdateNumber(&PrimaryDetector.DetectorSettingsNP, values, names, n);
577  PrimaryDetector.DetectorSettingsNP.s = IPS_OK;
578  SetDetectorParams(PrimaryDetector.DetectorSettingsNP.np[DetectorDevice::DETECTOR_SAMPLERATE].value,
579  PrimaryDetector.DetectorSettingsNP.np[DetectorDevice::DETECTOR_FREQUENCY].value,
580  PrimaryDetector.DetectorSettingsNP.np[DetectorDevice::DETECTOR_BITSPERSAMPLE].value);
581  IDSetNumber(&PrimaryDetector.DetectorSettingsNP, nullptr);
582  return true;
583  }
584  }
585 
586  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
587 }
588 
589 bool Detector::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
590 {
591  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
592  {
593  if (!strcmp(name, UploadSP.name))
594  {
595  int prevMode = IUFindOnSwitchIndex(&UploadSP);
596  IUUpdateSwitch(&UploadSP, states, names, n);
597  UploadSP.s = IPS_OK;
598  IDSetSwitch(&UploadSP, nullptr);
599 
600  if (UploadS[0].s == ISS_ON)
601  {
602  DEBUG(Logger::DBG_SESSION, "Upload settings set to client only.");
603  if (prevMode != 0)
604  deleteProperty(FileNameTP.name);
605  }
606  else if (UploadS[1].s == ISS_ON)
607  {
608  DEBUG(Logger::DBG_SESSION, "Upload settings set to local only.");
609  defineText(&FileNameTP);
610  }
611  else
612  {
613  DEBUG(Logger::DBG_SESSION, "Upload settings set to client and local.");
614  defineText(&FileNameTP);
615  }
616  return true;
617  }
618 
619  if (!strcmp(name, TelescopeTypeSP.name))
620  {
621  IUUpdateSwitch(&TelescopeTypeSP, states, names, n);
622  TelescopeTypeSP.s = IPS_OK;
623  IDSetSwitch(&TelescopeTypeSP, nullptr);
624  return true;
625  }
626 
627  // Primary Device Abort Expsoure
628  if (strcmp(name, PrimaryDetector.AbortCaptureSP.name) == 0)
629  {
630  IUResetSwitch(&PrimaryDetector.AbortCaptureSP);
631 
632  if (AbortCapture())
633  {
634  PrimaryDetector.AbortCaptureSP.s = IPS_OK;
635  PrimaryDetector.FramedCaptureNP.s = IPS_IDLE;
636  PrimaryDetector.FramedCaptureN[0].value = 0;
637  }
638  else
639  {
640  PrimaryDetector.AbortCaptureSP.s = IPS_ALERT;
641  PrimaryDetector.FramedCaptureNP.s = IPS_ALERT;
642  }
643 
644  IDSetSwitch(&PrimaryDetector.AbortCaptureSP, nullptr);
645  IDSetNumber(&PrimaryDetector.FramedCaptureNP, nullptr);
646 
647  return true;
648  }
649  }
650 
651  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
652 }
653 
654 int Detector::SetTemperature(double temperature)
655 {
656  INDI_UNUSED(temperature);
657  DEBUGF(Logger::DBG_WARNING, "Detector::SetTemperature %4.2f - Should never get here", temperature);
658  return -1;
659 }
660 
661 bool Detector::StartCapture(float duration)
662 {
663  INDI_UNUSED(duration);
664  DEBUGF(Logger::DBG_WARNING, "Detector::StartCapture %4.2f - Should never get here", duration);
665  return false;
666 }
667 
668 bool Detector::CaptureParamsUpdated(float sr, float freq, float bps)
669 {
670  INDI_UNUSED(sr);
671  INDI_UNUSED(freq);
672  INDI_UNUSED(bps);
673  DEBUGF(Logger::DBG_WARNING, "Detector::CaptureParamsUpdated %15.0f %15.0f %15.0f - Should never get here", sr, freq, bps);
674  return false;
675 }
676 
677 bool Detector::AbortCapture()
678 {
679  DEBUG(Logger::DBG_WARNING, "Detector::AbortCapture - Should never get here");
680  return false;
681 }
682 
683 void Detector::addFITSKeywords(fitsfile *fptr, DetectorDevice *targetDevice, int blobIndex)
684 {
685  INDI_UNUSED(blobIndex);
686  int status = 0;
687  char dev_name[32];
688  char exp_start[32];
689  double captureDuration;
690 
691  char *orig = setlocale(LC_NUMERIC, "C");
692 
693  char fitsString[MAXINDIDEVICE];
694 
695  // DETECTOR
696  strncpy(fitsString, getDeviceName(), MAXINDIDEVICE);
697  fits_update_key_s(fptr, TSTRING, "INSTRUME", fitsString, "PrimaryDetector Name", &status);
698 
699  // Telescope
700  strncpy(fitsString, ActiveDeviceT[0].text, MAXINDIDEVICE);
701  fits_update_key_s(fptr, TSTRING, "TELESCOP", fitsString, "Telescope name", &status);
702 
703  // Observer
704  strncpy(fitsString, FITSHeaderT[FITS_OBSERVER].text, MAXINDIDEVICE);
705  fits_update_key_s(fptr, TSTRING, "OBSERVER", fitsString, "Observer name", &status);
706 
707  // Object
708  strncpy(fitsString, FITSHeaderT[FITS_OBJECT].text, MAXINDIDEVICE);
709  fits_update_key_s(fptr, TSTRING, "OBJECT", fitsString, "Object name", &status);
710 
711  captureDuration = targetDevice->getCaptureDuration();
712 
713  strncpy(dev_name, getDeviceName(), 32);
714  strncpy(exp_start, targetDevice->getCaptureStartTime(), 32);
715 
716  fits_update_key_s(fptr, TDOUBLE, "EXPTIME", &(captureDuration), "Total Capture Time (s)", &status);
717 
718  if (HasCooler())
719  fits_update_key_s(fptr, TDOUBLE, "DETECTOR-TEMP", &(TemperatureN[0].value), "PrimaryDetector Temperature (Celsius)", &status);
720 
721  if (CurrentFilterSlot != -1 && CurrentFilterSlot <= (int)FilterNames.size())
722  {
723  char filter[32];
724  strncpy(filter, FilterNames.at(CurrentFilterSlot - 1).c_str(), 32);
725  fits_update_key_s(fptr, TSTRING, "FILTER", filter, "Filter", &status);
726  }
727 
728 #ifdef WITH_MINMAX
729  if (targetDevice->getNAxis() == 2)
730  {
731  double min_val, max_val;
733  {
734  getMinMax(&min_val, &max_val, targetDevice->getContinuumBuffer(), targetDevice->getContinuumBufferSize(), targetDevice->getBPS());
735  }
737  {
738  getMinMax(&min_val, &max_val, targetDevice->getSpectrumBuffer(), targetDevice->getSpectrumBufferSize(), sizeof(double) * 8);
739  }
740 
741  fits_update_key_s(fptr, TDOUBLE, "DATAMIN", &min_val, "Minimum value", &status);
742  fits_update_key_s(fptr, TDOUBLE, "DATAMAX", &max_val, "Maximum value", &status);
743  }
744 #endif
745 
746  if (primaryFocalLength != -1)
747  fits_update_key_s(fptr, TDOUBLE, "FOCALLEN", &primaryFocalLength, "Focal Length (mm)", &status);
748 
749  if (MPSAS != -1000)
750  {
751  fits_update_key_s(fptr, TDOUBLE, "MPSAS", &MPSAS, "Sky Quality (mag per arcsec^2)", &status);
752  }
753 
754  if (RA != -1000 && Dec != -1000)
755  {
756  ln_equ_posn epochPos { 0, 0 }, J2000Pos { 0, 0 };
757  epochPos.ra = RA * 15.0;
758  epochPos.dec = Dec;
759 
760  // Convert from JNow to J2000
761  //TODO use exp_start instead of julian from system
762  ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
763 
764  double raJ2000 = J2000Pos.ra / 15.0;
765  double decJ2000 = J2000Pos.dec;
766  char ra_str[32], de_str[32];
767 
768  fs_sexa(ra_str, raJ2000, 2, 360000);
769  fs_sexa(de_str, decJ2000, 2, 360000);
770 
771  char *raPtr = ra_str, *dePtr = de_str;
772  while (*raPtr != '\0')
773  {
774  if (*raPtr == ':')
775  *raPtr = ' ';
776  raPtr++;
777  }
778  while (*dePtr != '\0')
779  {
780  if (*dePtr == ':')
781  *dePtr = ' ';
782  dePtr++;
783  }
784 
785  fits_update_key_s(fptr, TSTRING, "OBJCTRA", ra_str, "Object RA", &status);
786  fits_update_key_s(fptr, TSTRING, "OBJCTDEC", de_str, "Object DEC", &status);
787 
788  int epoch = 2000;
789 
790  //fits_update_key_s(fptr, TINT, "EPOCH", &epoch, "Epoch", &status);
791  fits_update_key_s(fptr, TINT, "EQUINOX", &epoch, "Equinox", &status);
792  }
793 
794  fits_update_key_s(fptr, TSTRING, "DATE-OBS", exp_start, "UTC start date of observation", &status);
795  fits_write_comment(fptr, "Generated by INDI", &status);
796 
797  setlocale(LC_NUMERIC, orig);
798 }
799 
800 void Detector::fits_update_key_s(fitsfile *fptr, int type, std::string name, void *p, std::string explanation,
801  int *status)
802 {
803  // this function is for removing warnings about deprecated string conversion to char* (from arg 5)
804  fits_update_key(fptr, type, name.c_str(), p, const_cast<char *>(explanation.c_str()), status);
805 }
806 
807 bool Detector::CaptureComplete(DetectorDevice *targetDevice)
808 {
809  bool sendCapture = (UploadS[0].s == ISS_ON || UploadS[2].s == ISS_ON);
810  bool saveCapture = (UploadS[1].s == ISS_ON || UploadS[2].s == ISS_ON);
811  bool autoLoop = false;
812 
813  if (sendCapture || saveCapture)
814  {
815  if(HasContinuum())
816  {
817  if (!strcmp(targetDevice->getCaptureExtension(), "fits"))
818  {
819  void *memptr;
820  size_t memsize;
821  int img_type = 0;
822  int byte_type = 0;
823  int status = 0;
824  long naxis = 2;
825  long naxes[naxis];
826  int nelements = 0;
827  std::string bit_depth;
828  char error_status[MAXRBUF];
829 
830  fitsfile *fptr = nullptr;
831 
832  naxes[0] = targetDevice->getContinuumBufferSize() * 8 / targetDevice->getBPS();
833  naxes[0] = naxes[0] < 1 ? 1 : naxes[0];
834  naxes[1] = 1;
835 
836  switch (targetDevice->getBPS())
837  {
838  case 8:
839  byte_type = TBYTE;
840  img_type = BYTE_IMG;
841  bit_depth = "8 bits per sample";
842  break;
843 
844  case 16:
845  byte_type = TUSHORT;
846  img_type = USHORT_IMG;
847  bit_depth = "16 bits per sample";
848  break;
849 
850  case 32:
851  byte_type = TULONG;
852  img_type = ULONG_IMG;
853  bit_depth = "32 bits per sample";
854  break;
855 
856  default:
857  DEBUGF(Logger::DBG_ERROR, "Unsupported bits per sample value %d", targetDevice->getBPS());
858  return false;
859  break;
860  }
861 
862  nelements = naxes[0] * naxes[1];
863  if (naxis == 3)
864  {
865  nelements *= 3;
866  naxes[2] = 3;
867  }
868 
869  // Now we have to send fits format data to the client
870  memsize = 5760;
871  memptr = malloc(memsize);
872  if (!memptr)
873  {
874  DEBUGF(Logger::DBG_ERROR, "Error: failed to allocate memory: %lu", (unsigned long)memsize);
875  }
876 
877  fits_create_memfile(&fptr, &memptr, &memsize, 2880, realloc, &status);
878 
879  if (status)
880  {
881  fits_report_error(stderr, status); /* print out any error messages */
882  fits_get_errstatus(status, error_status);
883  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
884  return false;
885  }
886 
887  fits_create_img(fptr, img_type, naxis, naxes, &status);
888 
889  if (status)
890  {
891  fits_report_error(stderr, status); /* print out any error messages */
892  fits_get_errstatus(status, error_status);
893  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
894  return false;
895  }
896 
898 
899  fits_write_img(fptr, byte_type, 1, nelements, targetDevice->getContinuumBuffer(), &status);
900 
901  if (status)
902  {
903  fits_report_error(stderr, status); /* print out any error messages */
904  fits_get_errstatus(status, error_status);
905  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
906  return false;
907  }
908 
909  fits_close_file(fptr, &status);
910 
911  uploadFile(targetDevice, memptr, memsize, sendCapture, saveCapture, DetectorDevice::DETECTOR_BLOB_CONTINUUM);
912 
913  free(memptr);
914  }
915  else
916  {
917  uploadFile(targetDevice, targetDevice->getContinuumBuffer(), targetDevice->getContinuumBufferSize(), sendCapture,
919  }
920  }
921  if(HasSpectrum())
922  {
923  if (!strcmp(targetDevice->getCaptureExtension(), "fits"))
924  {
925  void *memptr;
926  size_t memsize;
927  int img_type = 0;
928  int byte_type = 0;
929  int status = 0;
930  long naxis = 2;
931  long naxes[naxis];
932  int nelements = 0;
933  std::string bit_depth;
934  char error_status[MAXRBUF];
935 
936  fitsfile *fptr = nullptr;
937 
938  naxes[0] = targetDevice->getSpectrumBufferSize() / sizeof(double);
939  naxes[1] = 1;
940 
941  byte_type = TDOUBLE;
942  img_type = DOUBLE_IMG;
943  bit_depth = "64 bits per sample";
944 
945  nelements = naxes[0] * naxes[1];
946 
947  // Now we have to send fits format data to the client
948  memsize = 5760;
949  memptr = malloc(memsize);
950  if (!memptr)
951  {
952  DEBUGF(Logger::DBG_ERROR, "Error: failed to allocate memory: %lu", (unsigned long)memsize);
953  }
954 
955  fits_create_memfile(&fptr, &memptr, &memsize, 2880, realloc, &status);
956 
957  if (status)
958  {
959  fits_report_error(stderr, status); /* print out any error messages */
960  fits_get_errstatus(status, error_status);
961  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
962  return false;
963  }
964 
965  fits_create_img(fptr, img_type, naxis, naxes, &status);
966 
967  if (status)
968  {
969  fits_report_error(stderr, status); /* print out any error messages */
970  fits_get_errstatus(status, error_status);
971  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
972  return false;
973  }
974 
976 
977  fits_write_img(fptr, byte_type, 1, nelements, targetDevice->getSpectrumBuffer(), &status);
978 
979  if (status)
980  {
981  fits_report_error(stderr, status); /* print out any error messages */
982  fits_get_errstatus(status, error_status);
983  DEBUGF(Logger::DBG_ERROR, "FITS Error: %s", error_status);
984  return false;
985  }
986 
987  fits_close_file(fptr, &status);
988 
989  uploadFile(targetDevice, memptr, memsize, sendCapture, saveCapture, DetectorDevice::DETECTOR_BLOB_SPECTRUM);
990 
991  free(memptr);
992  }
993  else
994  {
995  uploadFile(targetDevice, targetDevice->getSpectrumBuffer(), targetDevice->getSpectrumBufferSize() * sizeof(double), sendCapture,
997  }
998  }
999 
1000  if (sendCapture)
1001  IDSetBLOB(&targetDevice->FitsBP, nullptr);
1002 
1003  DEBUG(Logger::DBG_DEBUG, "Upload complete");
1004  }
1005 
1006  targetDevice->FramedCaptureNP.s = IPS_OK;
1007  IDSetNumber(&targetDevice->FramedCaptureNP, nullptr);
1008 
1009  if (autoLoop)
1010  {
1011  PrimaryDetector.FramedCaptureN[0].value = CaptureTime;
1012  PrimaryDetector.FramedCaptureNP.s = IPS_BUSY;
1013  if (StartCapture(CaptureTime))
1014  PrimaryDetector.FramedCaptureNP.s = IPS_BUSY;
1015  else
1016  {
1017  DEBUG(Logger::DBG_DEBUG, "Autoloop: PrimaryDetector Capture Error!");
1018  PrimaryDetector.FramedCaptureNP.s = IPS_ALERT;
1019  }
1020 
1021  IDSetNumber(&PrimaryDetector.FramedCaptureNP, nullptr);
1022  }
1023 
1024  return true;
1025 }
1026 
1027 bool Detector::uploadFile(DetectorDevice *targetDevice, const void *fitsData, size_t totalBytes, bool sendCapture,
1028  bool saveCapture, int blobIndex)
1029 {
1030 
1031  DEBUGF(Logger::DBG_DEBUG, "Uploading file. Ext: %s, Size: %d, sendCapture? %s, saveCapture? %s",
1032  targetDevice->getCaptureExtension(), totalBytes, sendCapture ? "Yes" : "No", saveCapture ? "Yes" : "No");
1033 
1034  if (saveCapture)
1035  {
1036  targetDevice->FitsB[blobIndex].blob = (unsigned char *)fitsData;
1037  targetDevice->FitsB[blobIndex].bloblen = totalBytes;
1038  snprintf(targetDevice->FitsB[blobIndex].format, MAXINDIBLOBFMT, ".%s", targetDevice->getCaptureExtension());
1039 
1040  FILE *fp = nullptr;
1041  char captureFileName[MAXRBUF];
1042 
1043  std::string prefix = UploadSettingsT[UPLOAD_PREFIX].text;
1044  int maxIndex = getFileIndex(UploadSettingsT[UPLOAD_DIR].text, UploadSettingsT[UPLOAD_PREFIX].text,
1045  targetDevice->FitsB[blobIndex].format);
1046 
1047  if (maxIndex < 0)
1048  {
1049  DEBUGF(Logger::DBG_ERROR, "Error iterating directory %s. %s", UploadSettingsT[0].text,
1050  strerror(errno));
1051  return false;
1052  }
1053 
1054  if (maxIndex > 0)
1055  {
1056  char ts[32];
1057  struct tm *tp;
1058  time_t t;
1059  time(&t);
1060  tp = localtime(&t);
1061  strftime(ts, sizeof(ts), "%Y-%m-%dT%H-%M-%S", tp);
1062  std::string filets(ts);
1063  prefix = std::regex_replace(prefix, std::regex("ISO8601"), filets);
1064 
1065  char indexString[8];
1066  snprintf(indexString, 8, "%03d", maxIndex);
1067  std::string prefixIndex = indexString;
1068  //prefix.replace(prefix.find("XXX"), std::string::npos, prefixIndex);
1069  prefix = std::regex_replace(prefix, std::regex("XXX"), prefixIndex);
1070  }
1071 
1072  snprintf(captureFileName, MAXRBUF, "%s/%s%s", UploadSettingsT[0].text, prefix.c_str(), targetDevice->FitsB[blobIndex].format);
1073 
1074  fp = fopen(captureFileName, "w");
1075  if (fp == nullptr)
1076  {
1077  DEBUGF(Logger::DBG_ERROR, "Unable to save capture file (%s). %s", captureFileName, strerror(errno));
1078  return false;
1079  }
1080 
1081  int n = 0;
1082  for (int nr = 0; nr < (int)targetDevice->FitsB[blobIndex].bloblen; nr += n)
1083  n = fwrite((static_cast<char *>(targetDevice->FitsB[blobIndex].blob) + nr), 1, targetDevice->FitsB[blobIndex].bloblen - nr, fp);
1084 
1085  fclose(fp);
1086 
1087  // Save capture file path
1088  IUSaveText(&FileNameT[0], captureFileName);
1089 
1090  DEBUGF(Logger::DBG_SESSION, "Capture saved to %s", captureFileName);
1091  FileNameTP.s = IPS_OK;
1092  IDSetText(&FileNameTP, nullptr);
1093  }
1094  targetDevice->FitsB[blobIndex].blob = (unsigned char *)fitsData;
1095  targetDevice->FitsB[blobIndex].bloblen = totalBytes;
1096  snprintf(targetDevice->FitsB[blobIndex].format, MAXINDIBLOBFMT, ".%s", targetDevice->getCaptureExtension());
1097 
1098  targetDevice->FitsB[blobIndex].size = totalBytes;
1099  targetDevice->FitsBP.s = IPS_OK;
1100 
1101  return true;
1102 }
1103 
1104 void Detector::SetDetectorParams(float samplerate, float freq, float bps)
1105 {
1106  PrimaryDetector.setSampleRate(samplerate);
1107  PrimaryDetector.setFrequency(freq);
1108  PrimaryDetector.setBPS(bps);
1109  CaptureParamsUpdated(samplerate, freq, bps);
1110 }
1111 
1112 bool Detector::saveConfigItems(FILE *fp)
1113 {
1114  DefaultDevice::saveConfigItems(fp);
1115 
1116  IUSaveConfigText(fp, &ActiveDeviceTP);
1117  IUSaveConfigSwitch(fp, &UploadSP);
1118  IUSaveConfigText(fp, &UploadSettingsTP);
1119  IUSaveConfigSwitch(fp, &TelescopeTypeSP);
1120 
1121  return true;
1122 }
1123 
1124 void Detector::getMinMax(double *min, double *max, uint8_t *buf, int len, int bpp)
1125 {
1126  int ind = 0, i, j;
1127  int captureHeight = 1;
1128  int captureWidth = abs(len * 8 / bpp);
1129  double lmin = 0, lmax = 0;
1130 
1131  switch (bpp)
1132  {
1133  case 8:
1134  {
1135  unsigned char *captureBuffer = (unsigned char *)buf;
1136  lmin = lmax = captureBuffer[0];
1137 
1138  for (i = 0; i < captureHeight; i++)
1139  for (j = 0; j < captureWidth; j++)
1140  {
1141  ind = (i * captureWidth) + j;
1142  if (captureBuffer[ind] < lmin)
1143  lmin = captureBuffer[ind];
1144  else if (captureBuffer[ind] > lmax)
1145  lmax = captureBuffer[ind];
1146  }
1147  }
1148  break;
1149 
1150  case 16:
1151  {
1152  unsigned short *captureBuffer = (unsigned short *)buf;
1153  lmin = lmax = captureBuffer[0];
1154 
1155  for (i = 0; i < captureHeight; i++)
1156  for (j = 0; j < captureWidth; j++)
1157  {
1158  ind = (i * captureWidth) + j;
1159  if (captureBuffer[ind] < lmin)
1160  lmin = captureBuffer[ind];
1161  else if (captureBuffer[ind] > lmax)
1162  lmax = captureBuffer[ind];
1163  }
1164  }
1165  break;
1166 
1167  case 32:
1168  {
1169  unsigned int *captureBuffer = (unsigned int *)buf;
1170  lmin = lmax = captureBuffer[0];
1171 
1172  for (i = 0; i < captureHeight; i++)
1173  for (j = 0; j < captureWidth; j++)
1174  {
1175  ind = (i * captureWidth) + j;
1176  if (captureBuffer[ind] < lmin)
1177  lmin = captureBuffer[ind];
1178  else if (captureBuffer[ind] > lmax)
1179  lmax = captureBuffer[ind];
1180  }
1181  }
1182  break;
1183 
1184  case 64:
1185  {
1186  double *captureBuffer = (double *)buf;
1187  lmin = lmax = captureBuffer[0];
1188 
1189  for (i = 0; i < captureHeight; i++)
1190  for (j = 0; j < captureWidth; j++)
1191  {
1192  ind = (i * captureWidth) + j;
1193  if (captureBuffer[ind] < lmin)
1194  lmin = captureBuffer[ind];
1195  else if (captureBuffer[ind] > lmax)
1196  lmax = captureBuffer[ind];
1197  }
1198  }
1199  break;
1200  }
1201  *min = lmin;
1202  *max = lmax;
1203 }
1204 
1205 std::string regex_replace_compat2(const std::string &input, const std::string &pattern, const std::string &replace)
1206 {
1207  std::stringstream s;
1208  std::regex_replace(std::ostreambuf_iterator<char>(s), input.begin(), input.end(), std::regex(pattern), replace);
1209  return s.str();
1210 }
1211 
1212 int Detector::getFileIndex(const char *dir, const char *prefix, const char *ext)
1213 {
1214  INDI_UNUSED(ext);
1215 
1216  DIR *dpdf = nullptr;
1217  struct dirent *epdf = nullptr;
1218  std::vector<std::string> files = std::vector<std::string>();
1219 
1220  std::string prefixIndex = prefix;
1221  prefixIndex = regex_replace_compat2(prefixIndex, "_ISO8601", "");
1222  prefixIndex = regex_replace_compat2(prefixIndex, "_XXX", "");
1223 
1224  // Create directory if does not exist
1225  struct stat st;
1226 
1227  if (stat(dir, &st) == -1)
1228  {
1229  DEBUGF(Logger::DBG_DEBUG, "Creating directory %s...", dir);
1230  if (_det_mkdir(dir, 0755) == -1)
1231  DEBUGF(Logger::DBG_ERROR, "Error creating directory %s (%s)", dir, strerror(errno));
1232  }
1233 
1234  dpdf = opendir(dir);
1235  if (dpdf != nullptr)
1236  {
1237  while ((epdf = readdir(dpdf)))
1238  {
1239  if (strstr(epdf->d_name, prefixIndex.c_str()))
1240  files.push_back(epdf->d_name);
1241  }
1242  }
1243  else
1244  return -1;
1245 
1246  int maxIndex = 0;
1247 
1248  for (int i = 0; i < (int)files.size(); i++)
1249  {
1250  int index = -1;
1251 
1252  std::string file = files.at(i);
1253  std::size_t start = file.find_last_of("_");
1254  std::size_t end = file.find_last_of(".");
1255  if (start != std::string::npos)
1256  {
1257  index = atoi(file.substr(start + 1, end).c_str());
1258  if (index > maxIndex)
1259  maxIndex = index;
1260  }
1261  }
1262 
1263  return (maxIndex + 1);
1264 }
1265 
1266 }
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidriver.c:1477
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indicom.c:1420
void setNAxis(int value)
setNAxis Set FITS number of axis
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indicom.c:1411
int getBPS()
getBPS Get Detector depth (bits per sample).
Definition: indidetector.h:58
std::string regex_replace_compat2(const std::string &input, const std::string &pattern, const std::string &replace)
void setContinuumBufferSize(int nbuf, bool allocMem=true)
setContinuumBufferSize Set desired continuum buffer size. The function will allocate memory according...
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:545
char format[MAXINDIBLOBFMT]
Definition: indiapi.h:430
#define MAXINDIDEVICE
Definition: indiapi.h:174
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.c:575
void IDSetText(const ITextVectorProperty *t, const char *msg,...)
Tell client to update an existing text vector property.
Definition: indidriver.c:1853
void IDSetSwitch(const ISwitchVectorProperty *s, const char *msg,...)
Tell client to update an existing switch vector property.
Definition: indidriver.c:1937
double step
Definition: indiapi.h:269
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:227
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:319
double * getSpectrumBuffer()
getSpectrumBuffer Get raw buffer of the spectrum of the Detector device.
Definition: indidetector.h:112
One number descriptor.
Definition: indiapi.h:256
int getNAxis() const
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:283
void setSampleRate(float sr)
setSampleRate Set depth of Detector device.
double max(void)
The DetectorDevice class provides functionality of a Detector Device within a Detector.
Definition: indidetector.h:37
#define MAXINDIBLOBFMT
Definition: indiapi.h:177
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
Definition: indiapi.h:165
void setFrequency(float freq)
setFrequency Set the frequency observed.
int size
Definition: indiapi.h:436
void ISSnoopDevice(XMLEle *root)
Function defined by Drivers that is called when another Driver it is snooping (by having previously c...
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:2095
#define MAXRBUF
Definition: indidriver.c:43
Definition: indiapi.h:167
const char * name
Definition: indiserver.c:113
const char * getCaptureStartTime()
getCaptureStartTime
void IDSetNumber(const INumberVectorProperty *n, const char *msg,...)
Tell client to update an existing number vector property.
Definition: indidriver.c:1895
Namespace to encapsulate INDI client, drivers, and mediator classes. Developers can subclass the base...
void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Update the value of an existing text vector property.
char name[MAXINDINAME]
Definition: indiapi.h:304
int bloblen
Definition: indiapi.h:434
void setCaptureFailed()
setCaptureFailed Alert the client that the Capture failed.
JsonIterator end(JsonValue)
Definition: gason.h:108
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
Definition: indidriver.c:634
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:445
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Update the value of an existing switch vector property.
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string.
Definition: indicom.c:117
void IUFillBLOB(IBLOB *bp, const char *name, const char *label, const char *format)
Assign attributes for a BLOB property. The BLOB&#39;s data and auxiliary elements will be set to NULL...
Definition: indidriver.c:468
int getContinuumBufferSize()
getContinuumBufferSize Get allocated continuum buffer size to hold the Detector captured stream...
Definition: indidetector.h:64
const char * CAPTURE_SETTINGS_TAB
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:384
#define DEBUGF(priority, msg,...)
Definition: indilogger.h:57
void * blob
Definition: indiapi.h:432
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:420
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidriver.c:1494
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indicom.c:1327
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition: indidriver.c:140
void ISGetProperties(const char *dev)
Get Device Properties.
int IUFindOnSwitchIndex(const ISwitchVectorProperty *sp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indicom.c:1389
Number vector property descriptor.
Definition: indiapi.h:299
void setCaptureExtension(const char *ext)
setCaptureExtension Set capture exntension
void IDSetBLOB(const IBLOBVectorProperty *b, const char *msg,...)
Tell client to update an existing BLOB vector property.
Definition: indidriver.c:2018
double min
Definition: indiapi.h:265
const char * CAPTURE_INFO_TAB
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver&#39;s options are located. Those may include auxiliary controls...
#define INDI_UNUSED(x)
Definition: indidevapi.h:797
void setMinMaxStep(const char *property, const char *element, double min, double max, double step, bool sendToClient=true)
setMinMaxStep for a number property element
void IUFillBLOBVector(IBLOBVectorProperty *bvp, IBLOB *bp, int nbp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a BLOB vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:597
void setCaptureLeft(double duration)
setCaptureLeft Update Capture time left. Inform the client of the new Capture time left value...
double getCaptureDuration()
getCaptureDuration Get requested Capture duration for the Detector device in seconds.
Definition: indidetector.h:94
void setSpectrumBufferSize(int nbuf, bool allocMem=true)
setSpectrumBufferSize Set desired spectrum buffer size. The function will allocate memory accordingly...
double max
Definition: indiapi.h:267
ISState
Switch state.
Definition: indiapi.h:134
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:494
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element&#39;s attribute value.
Definition: lilxml.c:613
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector&#39;s auxiliary elements will be set to NULL...
Definition: indidriver.c:571
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.c:524
char * getCaptureExtension()
Definition: indidetector.h:229
uint8_t * getContinuumBuffer()
getContinuumBuffer Get raw buffer of the continuum stream of the Detector device. ...
Definition: indidetector.h:106
double min(void)
int getSpectrumBufferSize()
getSpectrumBufferSize Get allocated spectrum buffer size to hold the Detector spectrum.
Definition: indidetector.h:70
int errno
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");...
Definition: indilogger.h:56
__le16 type
Definition: pwc-ioctl.h:53
void setCaptureDuration(double duration)
setCaptureDuration Set desired Detector frame Capture duration for next Capture. You must call this f...
Implementations for common driver routines.
void setBPS(int bps)
setBPP Set depth of Detector device.
void addFITSKeywords(fitsfile *fptr, IMAGE_INFO *image_info)
Definition: stv.c:1597
void uploadFile(const char *filename)
Definition: stv.c:1710
double value
Definition: indiapi.h:271