Instrument Neutral Distributed Interface INDI  2.0.2
ccd_simulator.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3  Copyright(c) 2010 Gerry Rozema. All rights reserved.
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7  .
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12  .
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #include "ccd_simulator.h"
20 #include "indicom.h"
21 #include "stream/streammanager.h"
22 
23 #include "locale_compat.h"
24 
25 #include <libnova/julian_day.h>
26 #include <libastro.h>
27 
28 #include <cmath>
29 #include <unistd.h>
30 #include <sys/types.h>
31 #include <dirent.h>
32 #include <sys/stat.h>
33 #include <algorithm>
34 
35 static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
36 static pthread_mutex_t condMutex = PTHREAD_MUTEX_INITIALIZER;
37 
38 static std::unique_ptr<CCDSim> ccdsim(new CCDSim());
39 
41 {
42  currentRA = RA;
43  currentDE = Dec;
44 
45  terminateThread = false;
46 
47  time(&RunStart);
48 
49  // Filter stuff
50  FilterSlotN[0].min = 1;
51  FilterSlotN[0].max = 8;
52 }
53 
55 {
58  16,
61 
65  m_Bias = OffsetN[0].value;
68  // An oag is offset this much from center of scope position (arcminutes);
76 
77  uint32_t nbuf = PrimaryCCD.getXRes() * PrimaryCCD.getYRes() * PrimaryCCD.getBPP() / 8;
79 
80  Streamer->setPixelFormat(INDI_MONO, 16);
82 
83  return true;
84 }
85 
87 {
88  streamPredicate = 0;
89  terminateThread = false;
90  pthread_create(&primary_thread, nullptr, &streamVideoHelper, this);
92  return true;
93 }
94 
96 {
97  pthread_mutex_lock(&condMutex);
98  streamPredicate = 1;
99  terminateThread = true;
100  pthread_cond_signal(&cv);
101  pthread_mutex_unlock(&condMutex);
102 
103  return true;
104 }
105 
107 {
108  return "CCD Simulator";
109 }
110 
112 {
114 
115  CaptureFormat format = {"INDI_MONO", "Mono", 16, true};
116  addCaptureFormat(format);
117 
118  IUFillNumber(&SimulatorSettingsN[SIM_XRES], "SIM_XRES", "CCD X resolution", "%4.0f", 512, 8192, 512, 1280);
119  IUFillNumber(&SimulatorSettingsN[SIM_YRES], "SIM_YRES", "CCD Y resolution", "%4.0f", 512, 8192, 512, 1024);
120  IUFillNumber(&SimulatorSettingsN[SIM_XSIZE], "SIM_XSIZE", "CCD X Pixel Size", "%4.2f", 1, 30, 5, 5.2);
121  IUFillNumber(&SimulatorSettingsN[SIM_YSIZE], "SIM_YSIZE", "CCD Y Pixel Size", "%4.2f", 1, 30, 5, 5.2);
122  IUFillNumber(&SimulatorSettingsN[SIM_MAXVAL], "SIM_MAXVAL", "CCD Maximum ADU", "%4.0f", 255, 65000, 1000, 65000);
123  IUFillNumber(&SimulatorSettingsN[SIM_SATURATION], "SIM_SATURATION", "Saturation Mag", "%4.1f", 0, 20, 1, 1.0);
124  IUFillNumber(&SimulatorSettingsN[SIM_LIMITINGMAG], "SIM_LIMITINGMAG", "Limiting Mag", "%4.1f", 0, 20, 1, 17.0);
125  IUFillNumber(&SimulatorSettingsN[SIM_NOISE], "SIM_NOISE", "CCD Noise", "%4.0f", 0, 6000, 500, 10);
126  IUFillNumber(&SimulatorSettingsN[SIM_SKYGLOW], "SIM_SKYGLOW", "Sky Glow (magnitudes)", "%4.1f", 0, 6000, 500, 19.5);
127  IUFillNumber(&SimulatorSettingsN[SIM_OAGOFFSET], "SIM_OAGOFFSET", "Oag Offset (arcminutes)", "%4.1f", 0, 6000, 500, 0);
128  IUFillNumber(&SimulatorSettingsN[SIM_POLAR], "SIM_POLAR", "PAE (arcminutes)", "%4.1f", -600, 600, 100, 0);
129  IUFillNumber(&SimulatorSettingsN[SIM_POLARDRIFT], "SIM_POLARDRIFT", "PAE Drift (minutes)", "%4.1f", 0, 60, 5, 0);
130  IUFillNumber(&SimulatorSettingsN[SIM_PE_PERIOD], "SIM_PEPERIOD", "PE Period (seconds)", "%4.1f", 0, 60, 5, 0);
131  IUFillNumber(&SimulatorSettingsN[SIM_PE_MAX], "SIM_PEMAX", "PE Max (arcsec)", "%4.1f", 0, 6000, 500, 0);
132  IUFillNumber(&SimulatorSettingsN[SIM_TIME_FACTOR], "SIM_TIME_FACTOR", "Time Factor (x)", "%.2f", 0.01, 100, 10, 1);
133  IUFillNumber(&SimulatorSettingsN[SIM_ROTATION], "SIM_ROTATION", "CCD Rotation", "%.2f", 0, 360, 10, 0);
134 
136  "Settings", SIMULATOR_TAB, IP_RW, 60, IPS_IDLE);
137 
138  // RGB Simulation
139  IUFillSwitch(&SimulateBayerS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
140  IUFillSwitch(&SimulateBayerS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_ON);
142  ISR_1OFMANY, 60, IPS_IDLE);
143 
144  // Simulate focusing
145  IUFillNumber(&FocusSimulationN[0], "SIM_FOCUS_POSITION", "Focus", "%.f", 0.0, 100000.0, 1.0, 36700.0);
146  IUFillNumber(&FocusSimulationN[1], "SIM_FOCUS_MAX", "Max. Position", "%.f", 0.0, 100000.0, 1.0, 100000.0);
147  IUFillNumber(&FocusSimulationN[2], "SIM_SEEING", "Seeing (arcsec)", "%4.2f", 0, 60, 0, 3.5);
148  IUFillNumberVector(&FocusSimulationNP, FocusSimulationN, 3, getDeviceName(), "SIM_FOCUSING", "Focus Simulation",
150 
151  // Simulate Crash
152  IUFillSwitch(&CrashS[0], "CRASH", "Crash driver", ISS_OFF);
153  IUFillSwitchVector(&CrashSP, CrashS, 1, getDeviceName(), "CCD_SIMULATE_CRASH", "Crash", SIMULATOR_TAB, IP_WO,
154  ISR_ATMOST1, 0, IPS_IDLE);
155 
156  // Periodic Error
157  IUFillNumber(&EqPEN[AXIS_RA], "RA_PE", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
158  IUFillNumber(&EqPEN[AXIS_DE], "DEC_PE", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
159  IUFillNumberVector(&EqPENP, EqPEN, 2, getDeviceName(), "EQUATORIAL_PE", "EQ PE", SIMULATOR_TAB, IP_RW, 60,
160  IPS_IDLE);
161 
162  // FWHM
163  IUFillNumber(&FWHMN[0], "SIM_FWHM", "FWHM (arcseconds)", "%4.2f", 0, 60, 0, 7.5);
165 
166  // Cooler
167  IUFillSwitch(&CoolerS[INDI_ENABLED], "COOLER_ON", "ON", ISS_OFF);
168  IUFillSwitch(&CoolerS[INDI_DISABLED], "COOLER_OFF", "OFF", ISS_ON);
169  IUFillSwitchVector(&CoolerSP, CoolerS, 2, getDeviceName(), "CCD_COOLER", "Cooler", MAIN_CONTROL_TAB, IP_WO,
170  ISR_1OFMANY, 0, IPS_IDLE);
171 
172  // Gain
173  IUFillNumber(&GainN[0], "GAIN", "value", "%.f", 0, 100, 10, 90);
174  IUFillNumberVector(&GainNP, GainN, 1, getDeviceName(), "CCD_GAIN", "Gain", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
175 
176  // Offset
177  IUFillNumber(&OffsetN[0], "OFFSET", "value", "%.f", 0, 6000, 500, 0);
178  IUFillNumberVector(&OffsetNP, OffsetN, 1, getDeviceName(), "CCD_OFFSET", "Offset", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
179 
180  // Directory to read images from. This is useful to test real images captured by camera
181  // For each capture, one file is read (sorted by name) and is sent to client.
182  IUFillText(&DirectoryT[0], "LOCATION", "Location", getenv("HOME"));
183  IUFillTextVector(&DirectoryTP, DirectoryT, 1, getDeviceName(), "CCD_DIRECTORY_LOCATION", "Directory", SIMULATOR_TAB, IP_RW,
184  60, IPS_IDLE);
185 
186  // Toggle Directory Reading. If enabled. The simulator will just read images from the directory and not generate them.
187  IUFillSwitch(&DirectoryS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_OFF);
188  IUFillSwitch(&DirectoryS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_ON);
189  IUFillSwitchVector(&DirectorySP, DirectoryS, 2, getDeviceName(), "CCD_DIRECTORY_TOGGLE", "Use Dir.", SIMULATOR_TAB, IP_RW,
190  ISR_1OFMANY, 60, IPS_IDLE);
191 
192 #ifdef USE_EQUATORIAL_PE
193  IDSnoopDevice(ActiveDeviceT[0].text, "EQUATORIAL_PE");
194 #else
195  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_EOD_COORD");
196 #endif
197 
198 
200 
201  uint32_t cap = 0;
202 
203  cap |= CCD_CAN_ABORT;
204  cap |= CCD_CAN_BIN;
205  cap |= CCD_CAN_SUBFRAME;
206  cap |= CCD_HAS_COOLER;
207  cap |= CCD_HAS_GUIDE_HEAD;
208  cap |= CCD_HAS_SHUTTER;
209  cap |= CCD_HAS_ST4_PORT;
210  cap |= CCD_HAS_STREAMING;
211  cap |= CCD_HAS_DSP;
212 
213 #ifdef HAVE_WEBSOCKET
214  cap |= CCD_HAS_WEB_SOCKET;
215 #endif
216 
217  SetCCDCapability(cap);
218 
219  // This should be called after the initial SetCCDCapability (above)
220  // as it modifies the capabilities.
222 
224 
225  FilterSlotN[0].min = 1;
226  FilterSlotN[0].max = 8;
227 
228  addDebugControl();
229 
231 
232  return true;
233 }
234 
235 void CCDSim::setBayerEnabled(bool onOff)
236 {
237  if (onOff)
238  {
240  IUSaveText(&BayerT[0], "0");
241  IUSaveText(&BayerT[1], "0");
242  IUSaveText(&BayerT[2], "RGGB");
243  }
244  else
245  {
247  }
248 }
249 
250 void CCDSim::ISGetProperties(const char * dev)
251 {
253 
259 }
260 
262 {
264 
265  if (isConnected())
266  {
267  if (HasCooler())
269 
272 
275 
276  setupParameters();
277 
278  if (HasGuideHead())
279  {
280  SetGuiderParams(500, 290, 16, 9.8, 12.6);
282  }
283 
284  // Define the Filter Slot and name properties
286  }
287  else
288  {
289  if (HasCooler())
291 
296 
298  }
299 
300  return true;
301 }
302 
303 int CCDSim::SetTemperature(double temperature)
304 {
305  TemperatureRequest = temperature;
306  if (fabs(temperature - TemperatureN[0].value) < 0.1)
307  {
308  TemperatureN[0].value = temperature;
309  return 1;
310  }
311 
312  CoolerS[0].s = ISS_ON;
313  CoolerS[1].s = ISS_OFF;
314  CoolerSP.s = IPS_BUSY;
315  IDSetSwitch(&CoolerSP, nullptr);
316  return 0;
317 }
318 
319 bool CCDSim::StartExposure(float duration)
320 {
321  // for the simulator, we can just draw the frame now
322  // and it will get returned at the right time
323  // by the timer routines
324  AbortPrimaryFrame = false;
325  ExposureRequest = duration;
326 
328  gettimeofday(&ExpStart, nullptr);
329  // Leave the proper time showing for the draw routines
331  {
332  if (loadNextImage() == false)
333  return false;
334  }
335  else
337  // Now compress the actual wait time
338  ExposureRequest = duration * m_TimeFactor;
339  InExposure = true;
340 
341  return true;
342 }
343 
345 {
347  AbortGuideFrame = false;
350  gettimeofday(&GuideExpStart, nullptr);
351  InGuideExposure = true;
352  return true;
353 }
354 
356 {
357  if (!InExposure)
358  return true;
359 
360  AbortPrimaryFrame = true;
361 
362  return true;
363 }
364 
366 {
367  //IDLog("Enter AbortGuideExposure\n");
368  if (!InGuideExposure)
369  return true; // no need to abort if we aren't doing one
370  AbortGuideFrame = true;
371  return true;
372 }
373 
374 float CCDSim::CalcTimeLeft(timeval start, float req)
375 {
376  double timesince;
377  double timeleft;
378  struct timeval now
379  {
380  0, 0
381  };
382  gettimeofday(&now, nullptr);
383 
384  timesince =
385  (double)(now.tv_sec * 1000.0 + now.tv_usec / 1000) - (double)(start.tv_sec * 1000.0 + start.tv_usec / 1000);
386  timesince = timesince / 1000;
387  timeleft = req - timesince;
388  return timeleft;
389 }
390 
392 {
393  uint32_t nextTimer = getCurrentPollingPeriod();
394 
395  // No need to reset timer if we are not connected anymore
396  if (!isConnected())
397  return;
398 
399  if (InExposure)
400  {
401  if (AbortPrimaryFrame)
402  {
403  InExposure = false;
404  AbortPrimaryFrame = false;
405  }
406  else
407  {
408  float timeleft;
410 
411  //IDLog("CCD Exposure left: %g - Requset: %g\n", timeleft, ExposureRequest);
412  if (timeleft < 0)
413  timeleft = 0;
414 
415  PrimaryCCD.setExposureLeft(timeleft);
416 
417  if (timeleft < 1.0)
418  {
419  if (timeleft <= 0.001)
420  {
421  InExposure = false;
422  // We don't bin for raw images.
423  if (DirectoryS[INDI_DISABLED].s == ISS_ON)
426  }
427  else
428  {
429  // set a shorter timer
430  nextTimer = timeleft * 1000;
431  }
432  }
433  }
434  }
435 
436  if (InGuideExposure)
437  {
438  double timeleft = CalcTimeLeft(GuideExpStart, GuideExposureRequest);
439  if (timeleft < 0)
440  timeleft = 0;
441 
442  GuideCCD.setExposureLeft(timeleft);
443 
444  if (timeleft < 1.0)
445  {
446  if (timeleft <= 0.001)
447  {
448  InGuideExposure = false;
449  if (!AbortGuideFrame)
450  {
451  GuideCCD.binFrame();
453  if (InGuideExposure)
454  {
455  // the call to complete triggered another exposure
457  if (timeleft < 1.0)
458  {
459  nextTimer = timeleft * 1000;
460  }
461  }
462  }
463  else
464  {
465  //IDLog("Not sending guide frame cuz of abort\n");
466  }
467  AbortGuideFrame = false;
468  }
469  else
470  {
471  nextTimer = timeleft * 1000; // set a shorter timer
472  }
473  }
474  }
475 
476  if (TemperatureNP.s == IPS_BUSY)
477  {
478  if (TemperatureRequest < TemperatureN[0].value)
479  TemperatureN[0].value = std::max(TemperatureRequest, TemperatureN[0].value - 0.5);
480  else
481  TemperatureN[0].value = std::min(TemperatureRequest, TemperatureN[0].value + 0.5);
482 
483  if (std::abs(TemperatureN[0].value - m_LastTemperature) > 0.1)
484  {
485  m_LastTemperature = TemperatureN[0].value;
486  IDSetNumber(&TemperatureNP, nullptr);
487  }
488 
489  // Above 20, cooler is off
490  if (TemperatureN[0].value >= 20)
491  {
492  CoolerS[0].s = ISS_OFF;
493  CoolerS[1].s = ISS_ON;
494  CoolerSP.s = IPS_IDLE;
495  IDSetSwitch(&CoolerSP, nullptr);
496  }
497  }
498 
499 
500  SetTimer(nextTimer);
501 }
502 
503 double CCDSim::flux(double mag) const
504 {
505  // The limiting magnitude provides zero ADU whatever the exposure
506  // The saturation magnitude provides max ADU in one second
507  double const z = m_LimitingMag;
508  double const k = 2.5 * log10(m_MaxVal) / (m_LimitingMag - m_SaturationMag);
509  return pow(10, (z - mag) * k / 2.5);
510 }
511 
513 {
514  // CCD frame is 16 bit data
515  float exposure_time;
516 
517  uint16_t * ptr = reinterpret_cast<uint16_t *>(targetChip->getFrameBuffer());
518 
519  if (targetChip->getXRes() == 500)
520  exposure_time = GuideExposureRequest * 4;
521  else if (Streamer->isStreaming())
522  exposure_time = (ExposureRequest < 1) ? (ExposureRequest * 100) : ExposureRequest * 2;
523  else
524  exposure_time = ExposureRequest;
525 
526  exposure_time *= (1 + sqrt(GainN[0].value));
527 
528  auto targetFocalLength = ScopeInfoNP[FocalLength].getValue() > 0 ? ScopeInfoNP[FocalLength].getValue() : snoopedFocalLength;
529 
530  if (ShowStarField)
531  {
532  float PEOffset {0};
533  float decDrift {0};
534  // telescope ra in degrees
535  double rad {0};
536  // telescope ra in radians
537  double rar {0};
538  // telescope dec in radians;
539  double decr {0};
540  int nwidth = 0, nheight = 0;
541 
542  if (m_PEPeriod > 0)
543  {
544  double timesince;
545  time_t now;
546  time(&now);
547 
548  // Lets figure out where we are on the pe curve
549  timesince = difftime(now, RunStart);
550  // This is our spot in the curve
551  double PESpot = timesince / m_PEPeriod;
552  // Now convert to radians
553  PESpot = PESpot * 2.0 * 3.14159;
554 
555  PEOffset = m_PEMax * std::sin(PESpot);
556  // convert to degrees
557  PEOffset = PEOffset / 3600;
558  }
559 
560  // Spin up a set of plate constants that will relate
561  // ra/dec of stars, to our fictitious ccd layout
562 
563  // to account for various rotations etc
564  // we should spin up some plate constants here
565  // then we can use these constants to rotate and offset
566  // the standard co-ordinates on each star for drawing
567  // a ccd frame;
568  double pa, pb, pc, pd, pe, pf;
569  // Pixels per radian
570  double pprx, ppry;
571  // Scale in arcsecs per pixel
572  double Scalex;
573  double Scaley;
574  // CCD width in pixels
575  double ccdW = targetChip->getXRes();
576 
577  // Pixels per radian
578  pprx = targetFocalLength / targetChip->getPixelSizeX() * 1000;
579  ppry = targetFocalLength / targetChip->getPixelSizeY() * 1000;
580 
581  // we do a simple scale for x and y locations
582  // based on the focal length and pixel size
583  // focal length in mm, pixels in microns
584  // JM: 2015-03-17: Using a simpler formula, Scalex and Scaley are in arcsecs/pixel
585  Scalex = (targetChip->getPixelSizeX() / targetFocalLength) * 206.3;
586  Scaley = (targetChip->getPixelSizeY() / targetFocalLength) * 206.3;
587 
588 #if 0
589  DEBUGF(
591  "pprx: %g pixels per radian ppry: %g pixels per radian ScaleX: %g arcsecs/pixel ScaleY: %g arcsecs/pixel",
592  pprx, ppry, Scalex, Scaley);
593 #endif
594 
595  double theta = 270;
596  if (!std::isnan(RotatorAngle))
597  theta += RotatorAngle;
598  if (pierSide == 1)
599  theta -= 180; // rotate 180 if on East
600  theta = range360(theta);
601 
602  // JM: 2015-03-17: Next we do a rotation assuming CW for angle theta
603  pa = pprx * cos(theta * M_PI / 180.0);
604  pb = ppry * sin(theta * M_PI / 180.0);
605 
606  pd = pprx * -sin(theta * M_PI / 180.0);
607  pe = ppry * cos(theta * M_PI / 180.0);
608 
609  nwidth = targetChip->getXRes();
610  pc = nwidth / 2;
611 
612  nheight = targetChip->getYRes();
613  pf = nheight / 2;
614 
615  ImageScalex = Scalex;
616  ImageScaley = Scaley;
617 
618 #ifdef USE_EQUATORIAL_PE
619  if (!usePE)
620  {
621 #endif
622  currentRA = RA;
623  currentDE = Dec;
624 
625  if (std::isnan(currentRA))
626  {
627  currentRA = 0;
628  currentDE = 0;
629  }
630 
631  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
632 
633  double jd = ln_get_julian_from_sys();
634 
635  epochPos.rightascension = currentRA;
636  epochPos.declination = currentDE;
637 
638  // Convert from JNow to J2000
639  INDI::ObservedToJ2000(&epochPos, jd, &J2000Pos);
640 
641  currentRA = J2000Pos.rightascension;
642  currentDE = J2000Pos.declination;
643 
644  //LOGF_DEBUG("DrawCcdFrame JNow %f, %f J2000 %f, %f", epochPos.ra, epochPos.dec, J2000Pos.ra, J2000Pos.dec);
645  //INDI::IEquatorialCoordinates jnpos;
646  //INDI::J2000toObserved(&J2000Pos, jd, &jnpos);
647  //LOGF_DEBUG("J2000toObserved JNow %f, %f J2000 %f, %f", jnpos.ra, jnpos.dec, J2000Pos.ra, J2000Pos.dec);
648 
651 #ifdef USE_EQUATORIAL_PE
652  }
653 #endif
654 
655  // calc this now, we will use it a lot later
656  rad = currentRA * 15.0 + PEOffset;
657  rar = rad * 0.0174532925;
658  // offsetting the dec by the guide head offset
659  float cameradec;
660  cameradec = currentDE + m_OAGOffset / 60;
661  decr = cameradec * 0.0174532925;
662 
663  decDrift = (m_PolarDrift * m_PolarError * cos(decr)) / 3.81;
664 
665  // Add declination drift, if any.
666  decr += decDrift / 3600.0 * 0.0174532925;
667 
668  // now lets calculate the radius we need to fetch
669  double radius = sqrt((Scalex * Scalex * targetChip->getXRes() / 2.0 * targetChip->getXRes() / 2.0) +
670  (Scaley * Scaley * targetChip->getYRes() / 2.0 * targetChip->getYRes() / 2.0));
671  // we have radius in arcseconds now
672  // convert to arcminutes
673  radius = radius / 60;
674 #if 0
675  LOGF_DEBUG("Lookup radius %4.2f", radius);
676 #endif
677 
678  // A saturationmag star saturates in one second
679  // and a limitingmag produces a one adu level in one second
680  // solve for zero point and system gain
681 
682  //k = (m_SaturationMag - m_LimitingMag) / ((-2.5 * log(m_MaxVal)) - (-2.5 * log(1.0 / 2.0)));
683  //z = m_SaturationMag - k * (-2.5 * log(m_MaxVal));
684 
685  // Should probably do some math here to figure out the dimmest
686  // star we can see on this exposure
687  // and only fetch to that magnitude
688  // for now, just use the limiting mag number with some room to spare
689  double lookuplimit = m_LimitingMag;
690 
691  if (radius > 60)
692  lookuplimit = 11;
693 
694  // if this is a light frame, we need a star field drawn
695  INDI::CCDChip::CCD_FRAME ftype = targetChip->getFrameType();
696 
697  std::unique_lock<std::mutex> guard(ccdBufferLock);
698 
699  // Start by clearing the frame buffer
700  memset(targetChip->getFrameBuffer(), 0, targetChip->getFrameBufferSize());
701 
702  if (ftype == INDI::CCDChip::LIGHT_FRAME)
703  {
704  AutoCNumeric locale;
705  char gsccmd[250];
706  FILE * pp;
707  int drawn = 0;
708 
709  sprintf(gsccmd, "gsc -c %8.6f %+8.6f -r %4.1f -m 0 %4.2f -n 3000",
710  range360(rad),
711  rangeDec(cameradec),
712  radius,
713  lookuplimit);
714 
715  pp = popen(gsccmd, "r");
716  if (pp != nullptr)
717  {
718  char line[256];
719  int stars = 0;
720  int lines = 0;
721 
722  while (fgets(line, 256, pp) != nullptr)
723  {
724  // ok, lets parse this line for specifcs we want
725  char id[20];
726  char plate[6];
727  char ob[6];
728  float mag;
729  float mage;
730  float ra;
731  float dec;
732  float pose;
733  int band;
734  float dist;
735  int dir;
736  int c;
737 
738  int rc = sscanf(line, "%10s %f %f %f %f %f %d %d %4s %2s %f %d", id, &ra, &dec, &pose, &mag, &mage,
739  &band, &c, plate, ob, &dist, &dir);
740  if (rc == 12)
741  {
742  lines++;
743  stars++;
744 
745  // Convert the ra/dec to standard co-ordinates
746  double sx; // standard co-ords
747  double sy; //
748  double srar; // star ra in radians
749  double sdecr; // star dec in radians;
750  double ccdx;
751  double ccdy;
752 
753  srar = ra * 0.0174532925;
754  sdecr = dec * 0.0174532925;
755 
756  // Handbook of astronomical image processing
757  // page 253
758  // equations 9.1 and 9.2
759  // convert ra/dec to standard co-ordinates
760 
761  sx = cos(sdecr) * sin(srar - rar) /
762  (cos(decr) * cos(sdecr) * cos(srar - rar) + sin(decr) * sin(sdecr));
763  sy = (sin(decr) * cos(sdecr) * cos(srar - rar) - cos(decr) * sin(sdecr)) /
764  (cos(decr) * cos(sdecr) * cos(srar - rar) + sin(decr) * sin(sdecr));
765 
766  // now convert to pixels
767  ccdx = pa * sx + pb * sy + pc;
768  ccdy = pd * sx + pe * sy + pf;
769 
770  // Invert horizontally
771  ccdx = ccdW - ccdx;
772 
773  rc = DrawImageStar(targetChip, mag, ccdx, ccdy, exposure_time);
774  drawn += rc;
775 #ifdef __DEV__
776  if (rc == 1)
777  {
778  LOGF_DEBUG("star %s scope %6.4f %6.4f star %6.4f %6.4f ccd %6.2f %6.2f", id, rad, decPE, ra, dec, ccdx, ccdy);
779  LOGF_DEBUG("star %s ccd %6.2f %6.2f", id, ccdx, ccdy);
780  }
781 #endif
782  }
783  }
784  pclose(pp);
785  }
786  else
787  {
788  LOG_ERROR("Error looking up stars, is gsc installed with appropriate environment variables set ??");
789  }
790  if (drawn == 0)
791  {
792  LOG_ERROR("Got no stars, is gsc installed with appropriate environment variables set ??");
793  }
794  }
795 
796  // now we need to add background sky glow, with vignetting
797  // this is essentially the same math as drawing a dim star with
798  // fwhm equivalent to the full field of view
799 
800  if (ftype == INDI::CCDChip::LIGHT_FRAME || ftype == INDI::CCDChip::FLAT_FRAME)
801  {
802  // calculate flux from our zero point and gain values
803  float glow = m_SkyGlow * 1.3;
804 
805  if (ftype == INDI::CCDChip::FLAT_FRAME)
806  {
807  // Assume flats are done with a diffuser
808  // in broad daylight, so, the sky magnitude
809  // is much brighter than at night
810  glow = m_SkyGlow / 10;
811  }
812 
813  // Flux represents one second, scale up linearly for exposure time
814  float const skyflux = flux(glow) * exposure_time;
815 
816  uint16_t * pt = reinterpret_cast<uint16_t *>(targetChip->getFrameBuffer());
817 
818  nheight = targetChip->getSubH();
819  nwidth = targetChip->getSubW();
820 
821  for (int y = 0; y < nheight; y++)
822  {
823  float const sy = nheight / 2 - y;
824 
825  for (int x = 0; x < nwidth; x++)
826  {
827  float const sx = nwidth / 2 - x;
828 
829  // Vignetting parameter in arcsec
830  float const vig = std::min(nwidth, nheight) * ImageScalex;
831 
832  // Squared distance to center in arcsec (need to make this account for actual pixel size)
833  float const dc2 = sx * sx * ImageScalex * ImageScalex + sy * sy * ImageScaley * ImageScaley;
834 
835  // Gaussian falloff to the edges of the frame
836  float const fa = exp(-2.0 * 0.7 * dc2 / (vig * vig));
837 
838  // Get the current value of the pixel, add the sky glow and scale for vignetting
839  float fp = (pt[0] + skyflux) * fa;
840 
841  // Clamp to limits, store minmax
842  if (fp > m_MaxVal) fp = m_MaxVal;
843  if (fp < pt[0]) fp = pt[0];
844  if (fp > maxpix) maxpix = fp;
845  if (fp < minpix) minpix = fp;
846 
847  // And put it back
848  pt[0] = fp;
849  pt++;
850  }
851  }
852  }
853 
854  // Now we add some bias and read noise
855  int subX = targetChip->getSubX();
856  int subY = targetChip->getSubY();
857  int subW = targetChip->getSubW() + subX;
858  int subH = targetChip->getSubH() + subY;
859 
860  if (m_MaxNoise > 0)
861  {
862  for (int x = subX; x < subW; x++)
863  {
864  for (int y = subY; y < subH; y++)
865  {
866  int noise;
867 
868  noise = random();
869  noise = noise % m_MaxNoise; //
870 
871  AddToPixel(targetChip, x, y, m_Bias + noise);
872  }
873  }
874  }
875  }
876  else
877  {
878  testvalue++;
879  if (testvalue > 255)
880  testvalue = 0;
881  uint16_t val = testvalue;
882 
883  int nbuf = targetChip->getSubW() * targetChip->getSubH();
884 
885  for (int x = 0; x < nbuf; x++)
886  {
887  *ptr = val++;
888  ptr++;
889  }
890  }
891  return 0;
892 }
893 
894 int CCDSim::DrawImageStar(INDI::CCDChip * targetChip, float mag, float x, float y, float exposure_time)
895 {
896  int sx, sy;
897  int drew = 0;
898  //int boxsizex = 5;
899  int boxsizey = 5;
900  float flux;
901 
902  int subX = targetChip->getSubX();
903  int subY = targetChip->getSubY();
904  int subW = targetChip->getSubW() + subX;
905  int subH = targetChip->getSubH() + subY;
906 
907  if ((x < subX) || (x > subW || (y < subY) || (y > subH)))
908  {
909  // this star is not on the ccd frame anyways
910  return 0;
911  }
912 
913  // calculate flux from our zero point and gain values
914  flux = this->flux(mag);
915 
916  // ok, flux represents one second now
917  // scale up linearly for exposure time
918  flux = flux * exposure_time;
919 
920  float qx;
921  // we need a box size that gives a radius at least 3 times fwhm
922  qx = seeing / ImageScalex;
923  qx = qx * 3;
924  //boxsizex = (int)qx;
925  //boxsizex++;
926  qx = seeing / ImageScaley;
927  qx = qx * 3;
928  boxsizey = static_cast<int>(qx);
929  boxsizey++;
930 
931  //IDLog("BoxSize %d %d\n",boxsizex,boxsizey);
932 
933  for (sy = -boxsizey; sy <= boxsizey; sy++)
934  {
935  for (sx = -boxsizey; sx <= boxsizey; sx++)
936  {
937  int rc;
938 
939  // Squared distance to center in arcsec (need to make this account for actual pixel size)
940  float const dc2 = sx * sx * ImageScalex * ImageScalex + sy * sy * ImageScaley * ImageScaley;
941 
942  // Use a gaussian of unitary integral, scale it with the source flux
943  // f(x) = 1/(sqrt(2*pi)*sigma) * exp( -x² / (2*sigma²) )
944  // FWHM = 2*sqrt(2*log(2))*sigma => sigma = seeing/(2*sqrt(2*log(2)))
945  float const sigma = seeing / ( 2 * sqrt(2 * log(2)));
946  float const fa = 1 / (sigma * sqrt(2 * 3.1416)) * exp( -dc2 / (2 * sigma * sigma));
947 
948  // The source contribution is the gaussian value, stretched by seeing/FWHM
949  float fp = fa * flux;
950 
951  if (fp < 0)
952  fp = 0;
953 
954  rc = AddToPixel(targetChip, x + sx, y + sy, fp);
955  if (rc != 0)
956  drew = 1;
957  }
958  }
959  return drew;
960 }
961 
962 int CCDSim::AddToPixel(INDI::CCDChip * targetChip, int x, int y, int val)
963 {
964  int nwidth = targetChip->getSubW();
965  int nheight = targetChip->getSubH();
966 
967  x -= targetChip->getSubX();
968  y -= targetChip->getSubY();
969 
970  int drew = 0;
971  if (x >= 0)
972  {
973  if (x < nwidth)
974  {
975  if (y >= 0)
976  {
977  if (y < nheight)
978  {
979  unsigned short * pt;
980  int newval;
981  drew++;
982 
983  pt = reinterpret_cast<uint16_t *>(targetChip->getFrameBuffer());
984 
985  pt += (y * nwidth);
986  pt += x;
987  newval = pt[0];
988  newval += val;
989  if (newval > m_MaxVal)
990  newval = m_MaxVal;
991  if (newval > maxpix)
992  maxpix = newval;
993  if (newval < minpix)
994  minpix = newval;
995  pt[0] = newval;
996  }
997  }
998  }
999  }
1000  return drew;
1001 }
1002 
1004 {
1005  guideNSOffset += v / 1000.0 * GuideRate / 3600;
1006  return IPS_OK;
1007 }
1008 
1010 {
1011  guideNSOffset += v / -1000.0 * GuideRate / 3600;
1012  return IPS_OK;
1013 }
1014 
1016 {
1017  float c = v / 1000.0 * GuideRate;
1018  c = c / 3600.0 / 15.0;
1019  c = c / (cos(currentDE * 0.0174532925));
1020 
1021  guideWEOffset += c;
1022 
1023  return IPS_OK;
1024 }
1025 
1027 {
1028  float c = v / -1000.0 * GuideRate;
1029  c = c / 3600.0 / 15.0;
1030  c = c / (cos(currentDE * 0.0174532925));
1031 
1032  guideWEOffset += c;
1033 
1034  return IPS_OK;
1035 }
1036 
1037 bool CCDSim::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
1038 {
1039  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1040  {
1041  // This is for our device
1042  // Now lets see if it's something we process here
1043  if (strcmp(name, FilterNameTP->name) == 0)
1044  {
1045  INDI::FilterInterface::processText(dev, name, texts, names, n);
1046  return true;
1047  }
1048  else if (!strcmp(DirectoryTP.name, name))
1049  {
1050  IUUpdateText(&DirectoryTP, texts, names, n);
1051  DirectoryTP.s = IPS_OK;
1052  IDSetText(&DirectoryTP, nullptr);
1053  return true;
1054  }
1055 
1056  }
1057 
1058  return INDI::CCD::ISNewText(dev, name, texts, names, n);
1059 }
1060 
1061 bool CCDSim::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
1062 {
1063  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1064  {
1065 
1066  if (!strcmp(name, GainNP.name))
1067  {
1068  IUUpdateNumber(&GainNP, values, names, n);
1069  GainNP.s = IPS_OK;
1070  IDSetNumber(&GainNP, nullptr);
1071  return true;
1072  }
1073  if (!strcmp(name, OffsetNP.name))
1074  {
1075  IUUpdateNumber(&OffsetNP, values, names, n);
1076  OffsetNP.s = IPS_OK;
1077  IDSetNumber(&OffsetNP, nullptr);
1078  m_Bias = OffsetN[0].value;
1079  return true;
1080  }
1081  else if (!strcmp(name, SimulatorSettingsNP.name))
1082  {
1083  IUUpdateNumber(&SimulatorSettingsNP, values, names, n);
1085 
1086  // Reset our parameters now
1087  setupParameters();
1088  IDSetNumber(&SimulatorSettingsNP, nullptr);
1090  return true;
1091  }
1092  // Record PE EQ to simulate different position in the sky than actual mount coordinate
1093  // This can be useful to simulate Periodic Error or cone error or any arbitrary error.
1094  else if (!strcmp(name, EqPENP.name))
1095  {
1096  IUUpdateNumber(&EqPENP, values, names, n);
1097  EqPENP.s = IPS_OK;
1098 
1099  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1100  epochPos.rightascension = EqPEN[AXIS_RA].value;
1101  epochPos.declination = EqPEN[AXIS_DE].value;
1102 
1103  RA = EqPEN[AXIS_RA].value;
1104  Dec = EqPEN[AXIS_DE].value;
1105 
1106  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
1107  currentRA = J2000Pos.rightascension;
1108  currentDE = J2000Pos.declination;
1109  usePE = true;
1110 
1111  IDSetNumber(&EqPENP, nullptr);
1112  return true;
1113  }
1114  else if (!strcmp(name, FilterSlotNP.name))
1115  {
1116  INDI::FilterInterface::processNumber(dev, name, values, names, n);
1117  return true;
1118  }
1119  else if (!strcmp(name, FocusSimulationNP.name))
1120  {
1121  // update focus simulation parameters
1122  IUUpdateNumber(&FocusSimulationNP, values, names, n);
1124  IDSetNumber(&FocusSimulationNP, nullptr);
1125  }
1126  }
1127 
1128  return INDI::CCD::ISNewNumber(dev, name, values, names, n);
1129 }
1130 
1131 bool CCDSim::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
1132 {
1133  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1134  {
1135  // Simulate RGB
1136  if (!strcmp(name, SimulateBayerSP.name))
1137  {
1138  IUUpdateSwitch(&SimulateBayerSP, states, names, n);
1139  int index = IUFindOnSwitchIndex(&SimulateBayerSP);
1140  if (index == -1)
1141  {
1143  LOG_INFO("Cannot determine whether RGB simulation should be switched on or off.");
1144  IDSetSwitch(&SimulateBayerSP, nullptr);
1145  return false;
1146  }
1147 
1148  m_SimulateBayer = index == 0;
1150 
1154  IDSetSwitch(&SimulateBayerSP, nullptr);
1155 
1156  return true;
1157  }
1158  else if (strcmp(name, CoolerSP.name) == 0)
1159  {
1160  IUUpdateSwitch(&CoolerSP, states, names, n);
1161 
1162  if (CoolerS[0].s == ISS_ON)
1163  CoolerSP.s = IPS_BUSY;
1164  else
1165  {
1166  CoolerSP.s = IPS_IDLE;
1167  m_TargetTemperature = 20;
1171  }
1172 
1173  IDSetSwitch(&CoolerSP, nullptr);
1174 
1175  return true;
1176  }
1177  else if (!strcmp(DirectorySP.name, name))
1178  {
1179  IUUpdateSwitch(&DirectorySP, states, names, n);
1180  m_AllFiles.clear();
1181  m_RemainingFiles.clear();
1182  if (DirectoryS[INDI_ENABLED].s == ISS_ON)
1183  {
1184  DIR* dirp = opendir(DirectoryT[0].text);
1185  if (dirp == nullptr)
1186  {
1190  LOGF_ERROR("Cannot monitor invalid directory %s", DirectoryT[0].text);
1191  IDSetSwitch(&DirectorySP, nullptr);
1192  return true;
1193  }
1194  struct dirent * dp;
1195  std::string d_dir = std::string(DirectoryT[0].text);
1196  if (DirectoryT[0].text[strlen(DirectoryT[0].text) - 1] != '/')
1197  d_dir += "/";
1198  while ((dp = readdir(dirp)) != NULL)
1199  {
1200 
1201  // For now, just FITS.
1202  if (strstr(dp->d_name, ".fits"))
1203  m_AllFiles.push_back(d_dir + dp->d_name);
1204  }
1205  closedir(dirp);
1206 
1207  if (m_AllFiles.empty())
1208  {
1212  LOGF_ERROR("No FITS files found in directory %s", DirectoryT[0].text);
1213  IDSetSwitch(&DirectorySP, nullptr);
1214  }
1215  else
1216  {
1217  DirectorySP.s = IPS_OK;
1218  std::sort(m_AllFiles.begin(), m_AllFiles.end());
1220  LOGF_INFO("Directory-based images are enabled. Subsequent exposures will be loaded from directory %s", DirectoryT[0].text);
1221  }
1222  }
1223  else
1224  {
1225  m_RemainingFiles.clear();
1226  DirectorySP.s = IPS_OK;
1228  LOG_INFO("Directory-based images are disabled.");
1229  }
1230  IDSetSwitch(&DirectorySP, nullptr);
1231  return true;
1232  }
1233  else if (strcmp(name, CrashSP.name) == 0)
1234  {
1235  abort();
1236  }
1237  }
1238 
1239  // Nobody has claimed this, so, ignore it
1240  return INDI::CCD::ISNewSwitch(dev, name, states, names, n);
1241 }
1242 
1244 {
1245 #ifdef USE_EQUATORIAL_PE
1246  IDSnoopDevice(ActiveDeviceT[0].text, "EQUATORIAL_PE");
1247 #else
1248  IDSnoopDevice(ActiveDeviceT[ACTIVE_TELESCOPE].text, "EQUATORIAL_EOD_COORD");
1249 #endif
1251 
1253 }
1254 
1256 {
1257  if (IUSnoopNumber(root, &FWHMNP) == 0)
1258  {
1259  // we calculate the FWHM and do not snoop it from the focus simulator
1260  // seeing = FWHMNP.np[0].value;
1261  return true;
1262  }
1263 
1264  XMLEle * ep = nullptr;
1265  const char * propName = findXMLAttValu(root, "name");
1266 
1267  if (!strcmp(propName, "ABS_FOCUS_POSITION"))
1268  {
1269  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1270  {
1271  const char * name = findXMLAttValu(ep, "name");
1272 
1273  if (!strcmp(name, "FOCUS_ABSOLUTE_POSITION"))
1274  {
1275  FocuserPos = atol(pcdataXMLEle(ep));
1276 
1277  // calculate FWHM
1278  double focus = FocusSimulationN[0].value;
1279  double max = FocusSimulationN[1].value;
1280  double optimalFWHM = FocusSimulationN[2].value;
1281 
1282  // limit to +/- 10
1283  double ticks = 20 * (FocuserPos - focus) / max;
1284 
1285  seeing = 0.5625 * ticks * ticks + optimalFWHM;
1286  return true;
1287  }
1288  }
1289  }
1290  // We try to snoop EQPEC first, if not found, we snoop regular EQNP
1291 #ifdef USE_EQUATORIAL_PE
1292  const char * propName = findXMLAttValu(root, "name");
1293  if (!strcmp(propName, EqPENP.name))
1294  {
1295  XMLEle * ep = nullptr;
1296  int rc_ra = -1, rc_de = -1;
1297  double newra = 0, newdec = 0;
1298 
1299  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1300  {
1301  const char * elemName = findXMLAttValu(ep, "name");
1302 
1303  if (!strcmp(elemName, "RA_PE"))
1304  rc_ra = f_scansexa(pcdataXMLEle(ep), &newra);
1305  else if (!strcmp(elemName, "DEC_PE"))
1306  rc_de = f_scansexa(pcdataXMLEle(ep), &newdec);
1307  }
1308 
1309  if (rc_ra == 0 && rc_de == 0 && ((newra != raPE) || (newdec != decPE)))
1310  {
1311  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1312  epochPos.ra = newra * 15.0;
1313  epochPos.dec = newdec;
1314  ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
1315  raPE = J2000Pos.ra / 15.0;
1316  decPE = J2000Pos.dec;
1317  usePE = true;
1318 
1319  EqPEN[AXIS_RA].value = newra;
1320  EqPEN[AXIS_DE].value = newdec;
1321  IDSetNumber(&EqPENP, nullptr);
1322 
1323  LOGF_DEBUG("raPE %g decPE %g Snooped raPE %g decPE %g", raPE, decPE, newra, newdec);
1324 
1325  return true;
1326  }
1327  }
1328 #endif
1329 
1330  return INDI::CCD::ISSnoopDevice(root);
1331 }
1332 
1334 {
1335  // Save CCD Config
1337 
1338  // Save Filter Wheel Config
1340 
1341  // Save CCD Simulator Config
1343 
1344  // Gain
1345  IUSaveConfigNumber(fp, &GainNP);
1347 
1348  // Directory
1350 
1351  // Bayer
1353 
1354  // Focus simulation
1356 
1357  return true;
1358 }
1359 
1361 {
1362  CurrentFilter = f;
1363  SelectFilterDone(f);
1364  return true;
1365 }
1366 
1368 {
1369  return CurrentFilter;
1370 }
1371 
1373 {
1374  ExposureRequest = 1.0 / Streamer->getTargetExposure();
1375  pthread_mutex_lock(&condMutex);
1376  streamPredicate = 1;
1377  pthread_mutex_unlock(&condMutex);
1378  pthread_cond_signal(&cv);
1379 
1380  return true;
1381 }
1382 
1384 {
1385  pthread_mutex_lock(&condMutex);
1386  streamPredicate = 0;
1387  pthread_mutex_unlock(&condMutex);
1388  pthread_cond_signal(&cv);
1389 
1390  return true;
1391 }
1392 
1393 bool CCDSim::UpdateCCDFrame(int x, int y, int w, int h)
1394 {
1395  long bin_width = w / PrimaryCCD.getBinX();
1396  long bin_height = h / PrimaryCCD.getBinY();
1397 
1398  bin_width = bin_width - (bin_width % 2);
1399  bin_height = bin_height - (bin_height % 2);
1400 
1401  Streamer->setSize(bin_width, bin_height);
1402 
1403  return INDI::CCD::UpdateCCDFrame(x, y, w, h);
1404 }
1405 
1406 bool CCDSim::UpdateCCDBin(int hor, int ver)
1407 {
1408  if (PrimaryCCD.getSubW() % hor != 0 || PrimaryCCD.getSubH() % ver != 0)
1409  {
1410  LOGF_ERROR("%dx%d binning is not supported.", hor, ver);
1411  return false;
1412  }
1413 
1414  uint32_t bin_width = PrimaryCCD.getSubW() / hor;
1415  uint32_t bin_height = PrimaryCCD.getSubH() / ver;
1416  Streamer->setSize(bin_width, bin_height);
1417 
1418  return INDI::CCD::UpdateCCDBin(hor, ver);
1419 }
1420 
1421 bool CCDSim::UpdateGuiderBin(int hor, int ver)
1422 {
1423  if (GuideCCD.getSubW() % hor != 0 || GuideCCD.getSubH() % ver != 0)
1424  {
1425  LOGF_ERROR("%dx%d binning is not supported.", hor, ver);
1426  return false;
1427  }
1428 
1429  return INDI::CCD::UpdateGuiderBin(hor, ver);
1430 }
1431 
1432 void * CCDSim::streamVideoHelper(void * context)
1433 {
1434  return static_cast<CCDSim *>(context)->streamVideo();
1435 }
1436 
1438 {
1439  auto start = std::chrono::high_resolution_clock::now();
1440 
1441  while (true)
1442  {
1443  pthread_mutex_lock(&condMutex);
1444 
1445  while (streamPredicate == 0)
1446  {
1447  pthread_cond_wait(&cv, &condMutex);
1448  ExposureRequest = Streamer->getTargetExposure();
1449  }
1450 
1451  if (terminateThread)
1452  break;
1453 
1454  // release condMutex
1455  pthread_mutex_unlock(&condMutex);
1456 
1457 
1458  // 16 bit
1460 
1461  PrimaryCCD.binFrame();
1462 
1463  auto finish = std::chrono::high_resolution_clock::now();
1464  std::chrono::duration<double> elapsed = finish - start;
1465 
1466  if (elapsed.count() < ExposureRequest)
1467  usleep(fabs(ExposureRequest - elapsed.count()) * 1e6);
1468 
1469  uint32_t size = PrimaryCCD.getFrameBufferSize() / (PrimaryCCD.getBinX() * PrimaryCCD.getBinY());
1470  Streamer->newFrame(PrimaryCCD.getFrameBuffer(), size);
1471 
1472  start = std::chrono::high_resolution_clock::now();
1473  }
1474 
1475  pthread_mutex_unlock(&condMutex);
1476  return nullptr;
1477 }
1478 
1479 void CCDSim::addFITSKeywords(INDI::CCDChip *targetChip, std::vector<INDI::FITSRecord> &fitsKeyword)
1480 {
1481  INDI::CCD::addFITSKeywords(targetChip, fitsKeyword);
1482 
1483  fitsKeyword.push_back({"GAIN", GainN[0].value, 3, "Gain"});
1484 }
1485 
1487 {
1488  if (m_RemainingFiles.empty())
1490  const std::string filename = m_RemainingFiles[0];
1491  m_RemainingFiles.pop_front();
1492  char comment[512] = {0}, bayer_pattern[16] = {0};
1493  int status = 0, anynull = 0;
1494  double pixel_size = 5.2;
1495  int ndim {2}, bitpix {8};
1496  long naxes[3];
1497  fitsfile *fptr = nullptr;
1498 
1499  if (fits_open_diskfile(&fptr, filename.c_str(), READONLY, &status))
1500  {
1501  char error_status[512] = {0};
1502  fits_get_errstatus(status, error_status);
1503  LOGF_WARN("Error opening file %s due to error %s", filename.c_str(), error_status);
1504  return false;
1505  }
1506 
1507  fits_get_img_param(fptr, 3, &bitpix, &ndim, naxes, &status);
1508 
1509  if (ndim < 3)
1510  naxes[2] = 1;
1511  else
1512  PrimaryCCD.setNAxis(3);
1513  int samples_per_channel = naxes[0] * naxes[1];
1514  int channels = naxes[2];
1515  int elements = samples_per_channel * channels;
1516  int size = elements * bitpix / 8;
1518 
1519  if (fits_read_img(fptr, bitpix == 8 ? TBYTE : TUSHORT, 1, elements, 0, PrimaryCCD.getFrameBuffer(), &anynull, &status))
1520  {
1521  char error_status[512] = {0};
1522  fits_get_errstatus(status, error_status);
1523  LOGF_WARN("Error reading file %s due to error %s", filename.c_str(), error_status);
1524  return false;
1525  }
1526 
1527  if (fits_read_key_dbl(fptr, "PIXSIZE1", &pixel_size, comment, &status))
1528  {
1529  char error_status[512] = {0};
1530  fits_get_errstatus(status, error_status);
1531  LOGF_WARN("Error reading file %s due to error %s", filename.c_str(), error_status);
1532  return false;
1533  }
1534 
1535  if (fits_read_key_str(fptr, "BAYERPAT", bayer_pattern, comment, &status))
1536  {
1537  char error_status[512] = {0};
1538  fits_get_errstatus(status, error_status);
1539  LOGF_DEBUG("No BAYERPAT keyword found in %s (%s)", filename.c_str(), error_status);
1540  }
1541  SetCCDParams(naxes[0], naxes[1], bitpix, pixel_size, pixel_size);
1542 
1543  // Check if MONO or Bayer
1544  if (channels == 1 && strlen(bayer_pattern) == 4)
1545  {
1547  IUSaveText(&BayerT[0], "0");
1548  IUSaveText(&BayerT[1], "0");
1549  IUSaveText(&BayerT[2], bayer_pattern);
1550  }
1551  else
1552  {
1554  }
1555 
1556  fits_close_file(fptr, &status);
1557  return true;
1558 }
1559 
1560 bool CCDSim::SetCaptureFormat(uint8_t index)
1561 {
1562  INDI_UNUSED(index);
1563  return true;
1564 }
The CCDSim class provides an advanced simulator for a CCD that includes a dedicated on-board guide ch...
Definition: ccd_simulator.h:43
virtual bool UpdateCCDFrame(int x, int y, int w, int h) override
CCD calls this function when CCD Frame dimension needs to be updated in the hardware....
@ SIM_PE_MAX
Definition: ccd_simulator.h:61
@ SIM_SATURATION
Definition: ccd_simulator.h:53
@ SIM_LIMITINGMAG
Definition: ccd_simulator.h:54
@ SIM_MAXVAL
Definition: ccd_simulator.h:52
@ SIM_SKYGLOW
Definition: ccd_simulator.h:56
@ SIM_ROTATION
Definition: ccd_simulator.h:63
@ SIM_TIME_FACTOR
Definition: ccd_simulator.h:62
@ SIM_PE_PERIOD
Definition: ccd_simulator.h:60
@ SIM_OAGOFFSET
Definition: ccd_simulator.h:57
@ SIM_POLARDRIFT
Definition: ccd_simulator.h:59
int maxpix
bool m_SimulateBayer
double m_LastTemperature
ISwitch CoolerS[2]
ISwitchVectorProperty CoolerSP
bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
float guideWEOffset
void setBayerEnabled(bool onOff)
bool StartExposure(float duration) override
Start exposing primary CCD chip.
virtual IPState GuideSouth(uint32_t) override
Guide southward for ms milliseconds.
INumberVectorProperty OffsetNP
float ImageScalex
bool ShowStarField
int m_Bias
float GuideRate
Guide rate is 7 arcseconds per second.
bool AbortGuideFrame
bool Disconnect() override
Disconnect from device.
float m_SkyGlow
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void * streamVideo()
float seeing
float guideNSOffset
bool StartGuideExposure(float) override
Start exposing guide CCD chip.
virtual bool SetCaptureFormat(uint8_t index) override
SetCaptureFormat Set Active Capture format.
float m_PolarDrift
pthread_t primary_thread
INumber EqPEN[2]
INumber FocusSimulationN[3]
int AddToPixel(INDI::CCDChip *targetChip, int, int, int)
int DrawImageStar(INDI::CCDChip *targetChip, float, float, float, float ExposureTime)
int m_MaxVal
float m_PEMax
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual int SetTemperature(double temperature) override
Set CCD temperature.
const char * getDefaultName() override
std::deque< std::string > m_AllFiles
virtual bool StartStreaming() override
StartStreaming Start live video streaming.
static void * streamVideoHelper(void *context)
ISwitch CrashS[1]
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
int minpix
float m_PolarError
bool setupParameters()
IText DirectoryT[1]
virtual IPState GuideEast(uint32_t) override
Guide easward for ms milliseconds.
ISwitchVectorProperty CrashSP
INumberVectorProperty FocusSimulationNP
time_t RunStart
double flux(double magnitude) const
virtual IPState GuideWest(uint32_t) override
Guide westward for ms milliseconds.
float m_OAGOffset
ISwitchVectorProperty SimulateBayerSP
int QueryFilter() override
Return current filter position.
INumberVectorProperty SimulatorSettingsNP
INumber FWHMN[1]
virtual bool StopStreaming() override
StopStreaming Stop live video streaming.
float m_PEPeriod
PEPeriod in minutes.
bool SelectFilter(int) override
Select a new filter position.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
double currentDE
bool loadNextImage()
bool AbortPrimaryFrame
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool UpdateCCDBin(int hor, int ver) override
CCD calls this function when CCD Binning needs to be updated in the hardware. Derived classes should ...
int testvalue
ITextVectorProperty DirectoryTP
float m_LimitingMag
INumber GainN[1]
INumberVectorProperty FWHMNP
virtual IPState GuideNorth(uint32_t) override
Guide northward for ms milliseconds.
float CalcTimeLeft(timeval, float)
bool AbortExposure() override
Abort ongoing exposure.
float m_TimeFactor
float m_SaturationMag
ISwitch SimulateBayerS[2]
ISwitch DirectoryS[2]
int streamPredicate
INumber SimulatorSettingsN[SIM_N]
ISwitchVectorProperty DirectorySP
double TemperatureRequest
INumber OffsetN[1]
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual void activeDevicesUpdated() override
activeDevicesUpdated Inform children that ActiveDevices property was updated so they can snoop on the...
double currentRA
bool usePE
float ExposureRequest
int m_MaxNoise
std::deque< std::string > m_RemainingFiles
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool terminateThread
INumberVectorProperty GainNP
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
virtual bool UpdateGuiderBin(int hor, int ver) override
CCD calls this function when Guide head binning is updated by the client. Derived classes should impl...
static constexpr const char * SIMULATOR_TAB
INumberVectorProperty EqPENP
float GuideExposureRequest
virtual void addFITSKeywords(INDI::CCDChip *targetChip, std::vector< INDI::FITSRecord > &fitsKeyword) override
Generate FITS keywords that will be added to FIST/XISF file.
bool AbortGuideExposure() override
Abort ongoing exposure.
int DrawCcdFrame(INDI::CCDChip *targetChip)
float ImageScaley
Provides interface to implement Filter Wheel functionality.
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
The CCDChip class provides functionality of a CCD Chip within a CCD.
Definition: indiccdchip.h:35
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
Definition: indiccdchip.h:209
int getSubX() const
getSubX Get the starting left coordinates (X) of the frame.
Definition: indiccdchip.h:95
int getSubY() const
getSubY Get the starting top coordinates (Y) of the frame.
Definition: indiccdchip.h:104
float getPixelSizeY() const
getPixelSizeY Get vertical pixel size in microns.
Definition: indiccdchip.h:158
void setExposureDuration(double duration)
setExposureDuration Set desired CCD frame exposure duration for next exposure. You must call this fun...
int getSubH() const
getSubH Get the height of the frame
Definition: indiccdchip.h:122
int getBPP() const
getBPP Get CCD Chip depth (bits per pixel).
Definition: indiccdchip.h:167
CCD_FRAME getFrameType() const
isInterlaced
Definition: indiccdchip.h:249
int getBinY() const
getBinY Get vertical binning of the CCD chip.
Definition: indiccdchip.h:140
float getPixelSizeX() const
getPixelSizeX Get horizontal pixel size in microns.
Definition: indiccdchip.h:149
int getXRes() const
getXRes Get the horizontal resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:77
int getBinX() const
getBinX Get horizontal binning of the CCD chip.
Definition: indiccdchip.h:131
void setExposureLeft(double duration)
setExposureLeft Update exposure time left. Inform the client of the new exposure time left value.
int getSubW() const
getSubW Get the width of the frame
Definition: indiccdchip.h:113
void setFrameBufferSize(uint32_t nbuf, bool allocMem=true)
setFrameBufferSize Set desired frame buffer size. The function will allocate memory accordingly....
int getFrameBufferSize() const
getFrameBufferSize Get allocated frame buffer size to hold the CCD image frame.
Definition: indiccdchip.h:176
int getYRes() const
Get the vertical resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:86
void binFrame()
binFrame Perform software binning on the CCD frame. Only use this function if hardware binning is not...
void setNAxis(int value)
setNAxis Set FITS number of axis
bool InGuideExposure
Definition: indiccd.h:583
CCDChip PrimaryCCD
Definition: indiccd.h:629
uint32_t GetCCDCapability() const
GetCCDCapability returns the CCD capabilities.
Definition: indiccd.h:170
@ CCD_CAN_SUBFRAME
Definition: indiccd.h:124
@ CCD_HAS_DSP
Definition: indiccd.h:133
@ CCD_HAS_WEB_SOCKET
Definition: indiccd.h:132
@ CCD_CAN_ABORT
Definition: indiccd.h:125
@ CCD_HAS_GUIDE_HEAD
Definition: indiccd.h:126
@ CCD_HAS_SHUTTER
Definition: indiccd.h:128
@ CCD_HAS_BAYER
Definition: indiccd.h:130
@ CCD_CAN_BIN
Definition: indiccd.h:123
@ CCD_HAS_STREAMING
Definition: indiccd.h:131
@ CCD_HAS_ST4_PORT
Definition: indiccd.h:127
@ CCD_HAS_COOLER
Definition: indiccd.h:129
bool HasGuideHead()
Definition: indiccd.h:208
std::unique_ptr< StreamManager > Streamer
Definition: indiccd.h:627
@ ACTIVE_TELESCOPE
Definition: indiccd.h:667
@ ACTIVE_FOCUSER
Definition: indiccd.h:669
bool HasCooler()
Definition: indiccd.h:232
IText BayerT[3]
Definition: indiccd.h:694
bool InExposure
Definition: indiccd.h:582
INDI::Timer m_TemperatureCheckTimer
Definition: indiccd.h:616
virtual bool UpdateCCDFrame(int x, int y, int w, int h)
CCD calls this function when CCD Frame dimension needs to be updated in the hardware....
Definition: indiccd.cpp:1891
virtual void addFITSKeywords(CCDChip *targetChip, std::vector< FITSRecord > &fitsKeywords)
Generate FITS keywords that will be added to FIST/XISF file.
Definition: indiccd.cpp:1940
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: indiccd.cpp:1451
virtual bool ExposureComplete(CCDChip *targetChip)
Uploads target Chip exposed buffer as FITS to the client. Dervied classes should class this function ...
Definition: indiccd.cpp:2228
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition: indiccd.cpp:516
std::mutex ccdBufferLock
Definition: indiccd.h:620
INDI::PropertyNumber ScopeInfoNP
Definition: indiccd.h:728
INumber TemperatureN[1]
Definition: indiccd.h:678
virtual bool UpdateCCDBin(int hor, int ver)
CCD calls this function when CCD Binning needs to be updated in the hardware. Derived classes should ...
Definition: indiccd.cpp:1904
long FocuserPos
Definition: indiccd.h:604
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indiccd.cpp:152
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition: indiccd.cpp:884
int pierSide
Definition: indiccd.h:570
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indiccd.cpp:528
virtual void addCaptureFormat(const CaptureFormat &format)
addCaptureFormat Add a supported camera native capture format (e.g. Mono, Bayer8.....
Definition: indiccd.cpp:3033
double RotatorAngle
Definition: indiccd.h:601
virtual void SetGuiderParams(int x, int y, int bpp, float xf, float yf)
Setup CCD paramters for guide head CCD. Child classes call this function to update CCD parameters.
Definition: indiccd.cpp:2752
INDI::ElapsedTimer m_TemperatureElapsedTimer
Definition: indiccd.h:617
virtual bool UpdateGuiderBin(int hor, int ver)
CCD calls this function when Guide head binning is updated by the client. Derived classes should impl...
Definition: indiccd.cpp:1919
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
Definition: indiccd.cpp:2762
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition: indiccd.cpp:728
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. Al fields must be initialized.
Definition: indiccd.cpp:138
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: indiccd.cpp:1070
INumberVectorProperty TemperatureNP
TemperatureNP Camera Temperature in Celcius.
Definition: indiccd.h:677
IText ActiveDeviceT[5]
Definition: indiccd.h:664
CCDChip GuideCCD
Definition: indiccd.h:630
double RA
Definition: indiccd.h:567
virtual void SetCCDParams(int x, int y, int bpp, float xf, float yf)
Setup CCD paramters for primary CCD. Child classes call this function to update CCD parameters.
Definition: indiccd.cpp:2742
@ FocalLength
Definition: indiccd.h:731
double snoopedFocalLength
Definition: indiccd.h:581
double m_TargetTemperature
Definition: indiccd.h:615
double Dec
Definition: indiccd.h:567
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
uint16_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
void start()
Starts this timer. Once started, a timer value can be checked with elapsed().
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process text properties.
ITextVectorProperty * FilterNameTP
INumberVectorProperty FilterSlotNP
bool updateProperties()
updateProperties Defines or Delete proprties based on default device connection status
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process number properties.
void SelectFilterDone(int newpos)
The child class calls this function when the hardware successfully finished selecting a new filter wh...
void initProperties(const char *groupName)
Initilize filter wheel properties. It is recommended to call this function within initProperties() of...
bool saveConfigItems(FILE *fp)
saveConfigItems save Filter Names in config file
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition: inditimer.cpp:82
const char * FILTER_TAB
FILTER_TAB Where all the properties for filter wheels are located.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
double max(void)
double min(void)
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define MAXINDIDEVICE
Definition: indiapi.h:193
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ IP_WO
Definition: indiapi.h:185
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_ATMOST1
Definition: indiapi.h:174
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
@ INDI_MONO
Definition: indibasetypes.h:71
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition: indicom.c:205
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition: indicom.c:1245
double rangeDec(double decdegrees)
rangeDec Limits declination value to be in -90 to 90 range.
Definition: indicom.c:1255
Implementations for common driver routines.
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp)
Update a snooped number vector property from the given XML root element.
Definition: indidevapi.c:337
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition: indidriver.c:143
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#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
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
Namespace to encapsulate INDI client, drivers, and mediator classes.
void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos)
ObservedToJ2000 converts an observed position to a J2000 catalogue position removes aberration,...
Definition: libastro.cpp:50
char device[MAXINDIDEVICE]
Definition: indiapi.h:321
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250