Instrument Neutral Distributed Interface INDI 2.2.5
guide_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
20/************************************************************************************
21This simulator is useful for simulating the images sent to a guiding program such as the
22Ekos guider or PHD2.
23
24There are many adjustments to the image needing comments. An important one is:
25
26 Seeing: make this reasonably large so that single pixels stars aren't generated and the
27 guider can track sub-pixel. 5 is a good start.
28
29Use the below INDI properties to simulate imperfections in real-world mounts.
30
31 RA drift: Simulates a drift in arcseconds/second of the RA angle, e.g. due to bad tracking
32 or refraction
33 DEC drift: Similar, useful to simulate polar alignment error, etc.
34
35 Periodic error period (secs):
36 Periodic error maxval (arcsecs): These add a sinusoid of the given period, going from
37 -maxval to maxval arcseconds onto the RA.
38
39 Max random RA add (arcsecs):
40 Max random DEC add (arcsecs): These add random RA or DEC offsets each frame to the RA or
41 dec values. The random values are linearly distributed between -value to + value.
42
43Another interesting guide hardware simulation is found in the telescope simulator,
44which can simulate backlash to the guiding pulses. See its Dec Backlash parameter.
45************************************************************************************/
46
47#include "guide_simulator.h"
48#include "indicom.h"
50
51#include <libnova/julian_day.h>
52#include <libastro.h>
53
54#include <cmath>
55#include <unistd.h>
56
57static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
58static pthread_mutex_t condMutex = PTHREAD_MUTEX_INITIALIZER;
59
60constexpr double DEGREES_TO_RADIANS = 0.0174532925;
61
62// We declare an auto pointer to GuideSim.
63static std::unique_ptr<GuideSim> ccd(new GuideSim());
64
66{
69
70 m_StreamPredicate = 0;
71 m_TerminateThread = false;
72}
73
75{
76 int nbuf;
77 SetCCDParams(SimulatorSettingsNP[SIM_XRES].getValue(), SimulatorSettingsNP[SIM_YRES].getValue(), 16,
78 SimulatorSettingsNP[SIM_XSIZE].getValue(),
79 SimulatorSettingsNP[SIM_YSIZE].getValue());
80
81 // Random number added to each pixel up to this value.
82 m_MaxNoise = SimulatorSettingsNP[SIM_NOISE].getValue();
83 // A "glow" added to all frames, stronger at the center and less so further from the center.
84 m_SkyGlow = SimulatorSettingsNP[SIM_SKYGLOW].getValue();
85 // Clipping ADU value. Nothing is allowed to get brighter.
86 m_MaxVal = SimulatorSettingsNP[SIM_MAXVAL].getValue();
87 // Fixed bias added to each pixel. Useful when negative and half of m_MaxNoise.
88 // This only gets added if m_MaxNoise is > 0.
89 m_Bias = SimulatorSettingsNP[SIM_BIAS].getValue();
90 // A saturation mag star saturates in one second
91 // and a limiting mag produces a one adu level in one second
92 m_LimitingMag = SimulatorSettingsNP[SIM_LIMITINGMAG].getValue();
93 m_SaturationMag = SimulatorSettingsNP[SIM_SATURATION].getValue();
94 // offset the dec (in minutes) by the guide head offset
95 m_OAGoffset = SimulatorSettingsNP[SIM_OAGOFFSET].getValue();
96 // Doesn't make much sense to me.
97 // The dec is offset by (m_PolarError * polarDrift * cos(dec)) / 3.81
98 // This (locally at least) is a constant offset to dec, so won't show
99 // up much in guiding error.
100 m_PolarError = SimulatorSettingsNP[SIM_POLAR].getValue();
101 m_PolarDrift = SimulatorSettingsNP[SIM_POLARDRIFT].getValue();
102 // Kwiq++
103 m_KingGamma = SimulatorSettingsNP[SIM_KING_GAMMA].getValue() * DEGREES_TO_RADIANS;
104 m_KingTheta = SimulatorSettingsNP[SIM_KING_THETA].getValue() * DEGREES_TO_RADIANS;
105 // Reduce the simulator "wait time" for exposures by this factor.
106 // That is, we can say exposure duration = 10s, and m_TimeFactor = 0.05
107 // and the system will simulate a 10s exposure, but it will only take 0.5 seconds.
108 m_TimeFactor = SimulatorSettingsNP[SIM_TIME_FACTOR].getValue();
109 // This is the rotation offset of the simulated camera respective to North.
110 // Because the simulated star field is calculated with their RA/DEC-coordinates
111 // (see DrawCcdFrame()) the origin angle of star field points north. So this value
112 // for EQ mounts normally simulate a certain camera offset and is a constant.
113 // For ALTAZ-mount this variable is altered consecutively by the value of the parallactic
114 // angle (transfered through a signal from KStars/skymapdrawabstract.cpp) and this way used
115 // to simulate the deviation of the camera orientation from N.
116 m_RotationOffset = SimulatorSettingsNP[SIM_ROTATION].getValue();
117 m_Seeing = SimulatorSettingsNP[SIM_SEEING].getValue();
118 m_RaTimeDrift = SimulatorSettingsNP[SIM_RA_DRIFT].getValue();
119 m_DecTimeDrift = SimulatorSettingsNP[SIM_DEC_DRIFT].getValue();
120 m_RaRand = SimulatorSettingsNP[SIM_RA_RAND].getValue();
121 m_DecRand = SimulatorSettingsNP[SIM_DEC_RAND].getValue();
122 m_PEPeriod = SimulatorSettingsNP[SIM_PE_PERIOD].getValue();
123 m_PEMax = SimulatorSettingsNP[SIM_PE_MAX].getValue();
124 m_TemperatureRequest = SimulatorSettingsNP[SIM_TEMPERATURE].getValue();
125 TemperatureNP[0].setValue(m_TemperatureRequest);
126
128 //nbuf += 512;
130
131 Streamer->setPixelFormat(INDI_MONO, 16);
133
134 return true;
135}
136
138{
139 m_StreamPredicate = 0;
140 m_TerminateThread = false;
141 pthread_create(&m_PrimaryThread, nullptr, &streamVideoHelper, this);
143 return true;
144}
145
147{
148 pthread_mutex_lock(&condMutex);
149 m_StreamPredicate = 1;
150 m_TerminateThread = true;
151 pthread_cond_signal(&cv);
152 pthread_mutex_unlock(&condMutex);
153
154 return true;
155}
156
158{
159 return "Guide Simulator";
160}
161
163{
164 // Most hardware layers wont actually have indi properties defined
165 // but the simulators are a special case
167
168 CaptureFormat format = {"INDI_MONO", "Mono", 16, true};
169 addCaptureFormat(format);
170
171 SimulatorSettingsNP[SIM_XRES].fill("SIM_XRES", "X resolution", "%4.0f", 0, 8192, 0, 1280);
172 SimulatorSettingsNP[SIM_YRES].fill("SIM_YRES", "Y resolution", "%4.0f", 0, 8192, 0, 1024);
173 SimulatorSettingsNP[SIM_XSIZE].fill("SIM_XSIZE", "X Pixel Size", "%4.2f", 0, 60, 0, 2.4);
174 SimulatorSettingsNP[SIM_YSIZE].fill("SIM_YSIZE", "Y Pixel Size", "%4.2f", 0, 60, 0, 2.4);
175 SimulatorSettingsNP[SIM_MAXVAL].fill("SIM_MAXVAL", "Maximum ADU", "%4.0f", 0, 65000, 0, 65000);
176 SimulatorSettingsNP[SIM_BIAS].fill("SIM_BIAS", "Bias", "%4.0f", 0, 6000, 0, 10);
177 SimulatorSettingsNP[SIM_SATURATION].fill("SIM_SATURATION", "Saturation Mag", "%4.1f", 0, 20, 0, 1.0);
178 SimulatorSettingsNP[SIM_LIMITINGMAG].fill("SIM_LIMITINGMAG", "Limiting Mag", "%4.1f", 0, 20, 0, 17.0);
179 SimulatorSettingsNP[SIM_NOISE].fill("SIM_NOISE", "Noise", "%4.0f", 0, 6000, 0, 10);
180 SimulatorSettingsNP[SIM_SKYGLOW].fill("SIM_SKYGLOW", "Sky Glow (magnitudes)", "%4.1f", 0, 6000, 0, 19.5);
181 SimulatorSettingsNP[SIM_OAGOFFSET].fill("SIM_OAGOFFSET", "Oag Offset (arcminutes)", "%4.1f", 0, 6000, 0, 0);
182 SimulatorSettingsNP[SIM_POLAR].fill("SIM_POLAR", "PAE (arcminutes)", "%4.3f", -600, 600, 0,
183 0); /* PAE = Polar Alignment Error */
184 SimulatorSettingsNP[SIM_POLARDRIFT].fill("SIM_POLARDRIFT", "PAE Drift (minutes)", "%4.3f", 0, 6000, 0, 0);
185 SimulatorSettingsNP[SIM_ROTATION].fill("SIM_ROTATION", "Rotation Offset", "%4.1f", -180, 180, 0, 0);
186 SimulatorSettingsNP[SIM_KING_GAMMA].fill("SIM_KING_GAMMA", "(CP,TCP), deg", "%4.1f", 0, 10, 0, 0);
187 SimulatorSettingsNP[SIM_KING_THETA].fill("SIM_KING_THETA", "hour hangle, deg", "%4.1f", 0, 360, 0, 0);
188 SimulatorSettingsNP[SIM_TIME_FACTOR].fill("SIM_TIME_FACTOR", "Time Factor (x)", "%.2f", 0.01, 100, 0, 1);
189
190 SimulatorSettingsNP[SIM_SEEING].fill("SIM_SEEING", "Seeing (a-s)", "%4.1f", 0, 20, 0, 6);
191 SimulatorSettingsNP[SIM_RA_DRIFT].fill("SIM_RA_DRIFT", "RA drift (a-s/second)", "%5.3f", -2, 2, 0, 0.05);
192 SimulatorSettingsNP[SIM_DEC_DRIFT].fill("SIM_DEC_DRIFT", "DEC drift (a-s/second)", "%5.3f", -2, 2, 0, -0.05);
193 SimulatorSettingsNP[SIM_RA_RAND].fill("SIM_RA_RAND", "Max random RA add (a-s)", "%5.3f", -2, 2, 0, 0.2);
194 SimulatorSettingsNP[SIM_DEC_RAND].fill("SIM_DEC_RAND", "Max random DEC add (a-s)", "%5.3f", -2, 2, 0,
195 0.3);
196 SimulatorSettingsNP[SIM_PE_PERIOD].fill("SIM_PE_PERIOD", "Periodic error period (secs)", "%4.1f", 0, 1000, 0, 120);
197 SimulatorSettingsNP[SIM_PE_MAX].fill("SIM_PE_MAX", "Periodic error maxval (a-s)", "%4.1f", 0, 100, 0, 3);
198 SimulatorSettingsNP[SIM_TEMPERATURE].fill("SIM_TEMPERATURE", "Temperature (°C)", "%4.1f", -100, 100, 0, 25);
199
200 SimulatorSettingsNP.fill(getDeviceName(), "SIMULATOR_SETTINGS",
201 "Config", SIMULATOR_TAB, IP_RW, 60, IPS_IDLE);
202 // load() is important to fill all editfields with saved values also, so ISNewNumber() of one field
203 // doesn't update the other fields of the group with the "old" contents.
204 SimulatorSettingsNP.load();
205 // Sim Tests
206 SimTestsSP[SIM_TESTS_RGB].fill("RGB", "RGB", ISS_OFF);
207 SimTestsSP[SIM_TESTS_CRASH].fill("CRASH", "Crash", ISS_OFF);
208 SimTestsSP[SIM_TESTS_TIMEOUT].fill("TIMEOUT", "Timeout", ISS_OFF);
209 SimTestsSP.fill(getDeviceName(), "SIM_TESTS", "Sim Tests", SIMULATOR_TAB, IP_RW,
210 ISR_NOFMANY, 60, IPS_IDLE);
211
212 // CCD Gain
213 GainNP[0].fill("GAIN", "Gain", "%.f", 0, 100, 10, 50);
214 GainNP.fill(getDeviceName(), "CCD_GAIN", "Gain", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
215
216 EqPENP[RA_PE].fill("RA_PE", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
217 EqPENP[DEC_PE].fill("DEC_PE", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
218 EqPENP.fill(getDeviceName(), "EQUATORIAL_PE", "EQ PE", SIMULATOR_TAB, IP_RW, 60,
219 IPS_IDLE);
220
221#ifdef USE_EQUATORIAL_PE
222 IDSnoopDevice(ActiveDeviceTP[0].getText(), "EQUATORIAL_PE");
223#else
224 IDSnoopDevice(ActiveDeviceTP[ACTIVE_TELESCOPE].getText(), "EQUATORIAL_EOD_COORD");
225#endif
226
228 TemperatureNP[0].setValue(25);
229
230 uint32_t cap = 0;
231
232 cap |= CCD_CAN_ABORT;
233 cap |= CCD_CAN_BIN;
234 cap |= CCD_CAN_SUBFRAME;
235 cap |= CCD_HAS_SHUTTER;
236 cap |= CCD_HAS_ST4_PORT;
237 cap |= CCD_HAS_STREAMING;
238
239 SetCCDCapability(cap);
240
241 // This should be called after the initial SetCCDCapability (above)
242 // as it modifies the capabilities.
243 setRGB(m_SimulateRGB);
244
246
248
249 return true;
250}
251
252void GuideSim::setRGB(bool onOff)
253{
254 if (onOff)
255 {
257 BayerTP[CFA_OFFSET_X].setText("0");
258 BayerTP[CFA_OFFSET_Y].setText("0");
259 BayerTP[CFA_TYPE].setText("RGGB");
260 }
261 else
262 {
264 }
265}
266
267void GuideSim::ISGetProperties(const char * dev)
268{
270
271 defineProperty(SimulatorSettingsNP);
272 defineProperty(EqPENP);
273 defineProperty(SimTestsSP);
274}
275
277{
279
280 if (isConnected())
281 {
283 defineProperty(GainNP);
284
285 SetupParms();
286
287 if (HasGuideHead())
288 {
289 SetGuiderParams(500, 290, 16, 9.8, 12.6);
291 }
292 }
293 else
294 {
296 deleteProperty(GainNP);
297 }
298
299 return true;
300}
301
302bool GuideSim::StartExposure(float duration)
303{
304 // for the simulator, we can just draw the frame now
305 // and it will get returned at the right time
306 // by the timer routines
307 m_AbortPrimaryFrame = false;
308 m_ExposureRequest = duration;
309
311 gettimeofday(&m_ExpStart, nullptr);
312 // Leave the proper time showing for the draw routines
314 // Now compress the actual wait time
315 m_ExposureRequest = duration * m_TimeFactor;
316 InExposure = true;
317
318 return true;
319}
320
322{
323 if (!InExposure)
324 return true;
325
326 m_AbortPrimaryFrame = true;
327
328 return true;
329}
330
332{
333 uint32_t nextTimer = getCurrentPollingPeriod();
334
335 // No need to reset timer if we are not connected anymore
336 if (!isConnected())
337 return;
338
339 if (InExposure && !m_SimulateTimeout)
340 {
341 if (m_AbortPrimaryFrame)
342 {
343 InExposure = false;
344 m_AbortPrimaryFrame = false;
345 }
346 else
347 {
348 float timeleft;
349 timeleft = CalcTimeLeft(m_ExpStart, m_ExposureRequest);
350
351 //IDLog("CCD Exposure left: %g - Request: %g\n", timeleft, m_ExposureRequest);
352 if (timeleft < 0)
353 timeleft = 0;
354
355 PrimaryCCD.setExposureLeft(timeleft);
356
357 if (timeleft < 1.0)
358 {
359 if (timeleft <= 0.001)
360 {
361 InExposure = false;
364 }
365 else
366 {
367 // set a shorter timer
368 nextTimer = timeleft * 1000;
369 }
370 }
371 }
372 }
373
374 SetTimer(nextTimer);
375}
376
378{
379 double exposure_time;
380
381 uint16_t * ptr = reinterpret_cast<uint16_t *>(targetChip->getFrameBuffer());
382
383 if (Streamer->isStreaming())
384 exposure_time = (m_ExposureRequest < 1) ? (m_ExposureRequest * 100) : m_ExposureRequest * 2;
385 else
386 exposure_time = m_ExposureRequest;
387
388 exposure_time *= (1 + sqrt(GainNP[0].getValue()));
389
390 auto targetFocalLength = ScopeInfoNP[FOCAL_LENGTH].getValue() > 0 ? ScopeInfoNP[FOCAL_LENGTH].getValue() :
392
393 if (m_ShowStarField)
394 {
395 double PEOffset = 0;
396 double rad; // telescope RA in degrees
397 double rar; // telescope RA in radians
398 double decr; // telescope Dec in radians
399
400 time_t now;
401 time(&now);
402 if (!m_RunStartInitialized || difftime(now, m_LastSim) > 30)
403 {
404 m_RunStartInitialized = true;
405 time(&m_RunStart);
406 }
407 m_LastSim = now;
408
409 const double timesince = difftime(now, m_RunStart);
410
411 if (m_PEPeriod != 0 && m_PEMax != 0)
412 {
413 const double PESpot = 2.0 * 3.14159 * timesince / m_PEPeriod;
414 PEOffset = m_PEMax * std::sin(PESpot) / 3600.0;
415 }
416
417 // Camera rotation: manual offset + snooped rotator + pier flip
418 m_RotationOffset = SimulatorSettingsNP[SIM_ROTATION].getValue();
419 double theta = m_RotationOffset;
420 if (!std::isnan(RotatorAngle))
421 theta += RotatorAngle;
422 if (pierSide == 1)
423 theta -= 180;
424 theta = range360(theta);
425 LOGF_DEBUG("Rotator Angle: %f, Camera Rotation: %f", RotatorAngle, theta);
426
427#ifdef USE_EQUATORIAL_PE
428 if (m_UsePE)
429 {
432 }
433 else
434 {
435 static bool s_warnedNoSnoop = false;
436 if (!s_warnedNoSnoop)
437 {
438 LOG_WARN("EQUATORIAL_PE not yet snooped -- rendering at raw EQUATORIAL_EOD_COORD (mount errors not active)");
439 s_warnedNoSnoop = true;
440 }
441#endif
442 m_CurrentRA = RA;
444
445 if (std::isnan(m_CurrentRA))
446 {
447 m_CurrentRA = 0;
448 m_CurrentDEC = 0;
449 }
450
451 INDI::IEquatorialCoordinates epochPos { m_CurrentRA, m_CurrentDEC }, J2000Pos { 0, 0 };
452 INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
453 m_CurrentRA = J2000Pos.rightascension;
454 m_CurrentDEC = J2000Pos.declination;
457#ifdef USE_EQUATORIAL_PE
458 }
459#endif
460
461 // Linear drift in degrees (drift rate in arcsec/sec * elapsed seconds / 3600)
462 const float raTDrift = timesince * m_RaTimeDrift / 3600.0f;
463 const float decTDrift = timesince * m_DecTimeDrift / 3600.0f;
464
465 // Random offsets for RA and DEC in degrees
466 double raRandomDrift = 0;
467 double decRandomDrift = 0;
468 constexpr int scale = 1000000;
469 if (m_RaRand > 0)
470 {
471 const int raScale = scale * m_RaRand;
472 raRandomDrift = ((random() % (2 * raScale)) - raScale) / (3600.0 * scale);
473 }
474 if (m_DecRand > 0)
475 {
476 const int decScale = scale * m_DecRand;
477 decRandomDrift = ((random() % (2 * decScale)) - decScale) / (3600.0 * scale);
478 }
479
480 rad = m_CurrentRA * 15.0 + PEOffset + raTDrift + raRandomDrift;
481 rar = rad * DEGREES_TO_RADIANS;
482
483 float cameradec = m_CurrentDEC + m_OAGoffset / 60.0f;
484 decr = cameradec * DEGREES_TO_RADIANS;
485
486 const double decDrift = (m_PolarDrift * m_PolarError * cos(decr)) / 3.81;
487 decr += (decRandomDrift + decTDrift + decDrift / 3600.0) * DEGREES_TO_RADIANS;
488 cameradec = static_cast<float>(decr / DEGREES_TO_RADIANS);
489
490 if (m_KingGamma > 0.)
491 {
492 // King cone-error transform -- see E.S. King (1902AnHar..41..153K)
493 // Differential correction to RA and Dec for alt-az polar alignment error.
494 double J2decr = m_CurrentDEC * DEGREES_TO_RADIANS;
495 double sid = get_local_sidereal_time(this->Longitude);
496 double JnHAr = get_local_hour_angle(sid, RA) * 15.0 * DEGREES_TO_RADIANS;
497
498 double J2_mnt_d_rar = m_KingGamma * sin(J2decr) * sin(JnHAr - m_KingTheta) / cos(J2decr);
499 double J2_mnt_rar = rar - J2_mnt_d_rar;
500
501 double J2_mnt_d_decr = m_KingGamma * cos(JnHAr - m_KingTheta);
502 double J2_mnt_decr = decr + J2_mnt_d_decr;
503
504 if (J2_mnt_decr > M_PI / 2.)
505 {
506 J2_mnt_decr = M_PI / 2. - (J2_mnt_decr - M_PI / 2.);
507 J2_mnt_rar -= M_PI;
508 }
509 J2_mnt_rar = fmod(J2_mnt_rar, 2. * M_PI);
510
511 PEOffset = 0.;
512 rar = J2_mnt_rar;
513 rad = rar / DEGREES_TO_RADIANS;
514 decr = J2_mnt_decr;
515 cameradec = static_cast<float>(decr / DEGREES_TO_RADIANS);
516 }
517
518 // King mode needs a wider GSC search to ensure the plate solver has enough stars.
519 double const kingMinRadius = (m_KingGamma > 0.) ? 60.0 : 0.0;
520
521 INDI::CCDChip::CCD_FRAME ftype = targetChip->getFrameType();
522 bool const isLight = (ftype == INDI::CCDChip::LIGHT_FRAME);
523 bool const isFlat = (ftype == INDI::CCDChip::FLAT_FRAME);
524
525 RenderConfig cfg;
526 cfg.maxVal = m_MaxVal;
527 cfg.limitingMag = m_LimitingMag;
528 cfg.saturationMag = m_SaturationMag;
529 cfg.seeing = m_Seeing;
530 // Guide sim sky glow: no extra boost for light frames (guide images are typically short)
531 cfg.skyGlow = isLight ? m_SkyGlow : m_SkyGlow / 10.0f;
532 m_Renderer.setConfig(cfg);
533
534 std::unique_lock<std::mutex> guard(ccdBufferLock);
535
536 if (isLight || isFlat)
537 {
538 int drawn = m_Renderer.renderFrame(targetChip, rad, cameradec,
539 targetFocalLength, theta,
540 static_cast<float>(exposure_time), isLight,
541 kingMinRadius);
542 if (isLight && drawn < 0)
543 LOG_ERROR("Error launching gsc, is it installed with appropriate environment variables set?");
544 else if (isLight && drawn == 0)
545 LOG_WARN("No stars found in field -- check gsc catalog coverage for this region.");
546 }
547 else
548 {
549 memset(targetChip->getFrameBuffer(), 0, targetChip->getFrameBufferSize());
550 }
551
552 m_Renderer.applyReadoutNoise(targetChip, m_Bias, m_MaxNoise);
553 }
554 else
555 {
556 m_TestValue++;
557 if (m_TestValue > 255)
558 m_TestValue = 0;
559 uint16_t val = m_TestValue;
560
561 int nbuf = targetChip->getSubW() * targetChip->getSubH();
562
563 for (int x = 0; x < nbuf; x++)
564 {
565 *ptr = val++;
566 ptr++;
567 }
568 }
569 return 0;
570}
571
572float GuideSim::CalcTimeLeft(timeval start, float req)
573{
574 struct timeval now
575 {
576 0, 0
577 };
578 gettimeofday(&now, nullptr);
579 double const timesince = (now.tv_sec * 1000.0 + now.tv_usec / 1000.0)
580 - (start.tv_sec * 1000.0 + start.tv_usec / 1000.0);
581 return static_cast<float>(req - timesince / 1000.0);
582}
583
585{
586 m_GuideNSOffset += v / 1000.0 * m_GuideRate / 3600;
587 return IPS_OK;
588}
589
591{
592 m_GuideNSOffset += v / -1000.0 * m_GuideRate / 3600;
593 return IPS_OK;
594}
595
597{
598 float c = v / 1000.0 * m_GuideRate;
599 c = c / 3600.0 / 15.0;
600 c = c / (cos(m_CurrentDEC * DEGREES_TO_RADIANS));
601
602 m_GuideWEOffset += c;
603
604 return IPS_OK;
605}
606
608{
609 float c = v / -1000.0 * m_GuideRate;
610 c = c / 3600.0 / 15.0;
611 c = c / (cos(m_CurrentDEC * DEGREES_TO_RADIANS));
612
613 m_GuideWEOffset += c;
614
615 return IPS_OK;
616}
617
618bool GuideSim::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
619{
620 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
621 {
622
623 if (GainNP.isNameMatch(name))
624 {
625 GainNP.update(values, names, n);
626 GainNP.setState(IPS_OK);
627 GainNP.apply();
628 return true;
629 }
630
631 if (strcmp(name, "SIMULATOR_SETTINGS") == 0)
632 {
633 SimulatorSettingsNP.update(values, names, n);
634 SimulatorSettingsNP.setState(IPS_OK);
635
636 // Reset our parameters now
637 SetupParms();
638 SimulatorSettingsNP.apply();
639
640 m_MaxNoise = SimulatorSettingsNP[SIM_NOISE].getValue();
641 m_SkyGlow = SimulatorSettingsNP[SIM_SKYGLOW].getValue();
642 m_MaxVal = SimulatorSettingsNP[SIM_MAXVAL].getValue();
643 m_Bias = SimulatorSettingsNP[SIM_BIAS].getValue();
644 m_LimitingMag = SimulatorSettingsNP[SIM_LIMITINGMAG].getValue();
645 m_SaturationMag = SimulatorSettingsNP[SIM_SATURATION].getValue();
646 m_OAGoffset = SimulatorSettingsNP[SIM_OAGOFFSET].getValue();
647 m_PolarError = SimulatorSettingsNP[SIM_POLAR].getValue();
648 m_PolarDrift = SimulatorSettingsNP[SIM_POLARDRIFT].getValue();
649 m_RotationOffset = SimulatorSettingsNP[SIM_ROTATION].getValue();
650 // Kwiq++
651 m_KingGamma = SimulatorSettingsNP[SIM_KING_GAMMA].getValue() * DEGREES_TO_RADIANS;
652 m_KingTheta = SimulatorSettingsNP[SIM_KING_THETA].getValue() * DEGREES_TO_RADIANS;
653 m_TimeFactor = SimulatorSettingsNP[SIM_TIME_FACTOR].getValue();
654
655 m_Seeing = SimulatorSettingsNP[SIM_SEEING].getValue();
656 m_RaTimeDrift = SimulatorSettingsNP[SIM_RA_DRIFT].getValue();
657 m_DecTimeDrift = SimulatorSettingsNP[SIM_DEC_DRIFT].getValue();
658 m_RaRand = SimulatorSettingsNP[SIM_RA_RAND].getValue();
659 m_DecRand = SimulatorSettingsNP[SIM_DEC_RAND].getValue();
660 m_PEPeriod = SimulatorSettingsNP[SIM_PE_PERIOD].getValue();
661 m_PEMax = SimulatorSettingsNP[SIM_PE_MAX].getValue();
662 m_TemperatureRequest = SimulatorSettingsNP[SIM_TEMPERATURE].getValue();
663 TemperatureNP[0].setValue(m_TemperatureRequest);
665
666 return true;
667 }
668
669 // Record PE EQ to simulate different position in the sky than actual mount coordinate
670 // This can be useful to simulate Periodic Error or cone error or any arbitrary error.
671 if (EqPENP.isNameMatch(name))
672 {
673 EqPENP.update(values, names, n);
674 EqPENP.setState(IPS_OK);
675
676 INDI::IEquatorialCoordinates epochPos { EqPENP[AXIS_RA].getValue(), EqPENP[AXIS_DE].getValue() }, J2000Pos { 0, 0 };
677 INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
678 m_CurrentRA = J2000Pos.rightascension;
679 m_CurrentDEC = J2000Pos.declination;
680 m_UsePE = true;
681 EqPENP.apply();
682 return true;
683 }
684 }
685
686 return INDI::CCD::ISNewNumber(dev, name, values, names, n);
687}
688
689bool GuideSim::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
690{
691 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
692 {
693
694 if (SimTestsSP.isNameMatch(name))
695 {
696 SimTestsSP.update(states, names, n);
697
698 // RGB
699 if (SimTestsSP[SIM_TESTS_RGB].getState() == ISS_ON)
700 {
701 m_SimulateRGB = true;
702 SimTestsSP[SIM_TESTS_RGB].setState(ISS_ON);
703 }
704 else
705 {
706 m_SimulateRGB = false;
707 SimTestsSP[SIM_TESTS_RGB].setState(ISS_OFF);
708 }
709 setRGB(m_SimulateRGB);
710
711 // Crash
712 if (SimTestsSP[SIM_TESTS_CRASH].getState() == ISS_ON)
713 {
714 abort();
715 }
716
717 // Timeout
718 if (SimTestsSP[SIM_TESTS_TIMEOUT].getState() == ISS_ON)
719 {
720 m_SimulateTimeout = true;
721 SimTestsSP[SIM_TESTS_TIMEOUT].setState(ISS_ON);
722 }
723 else
724 {
725 m_SimulateTimeout = false;
726 SimTestsSP[SIM_TESTS_TIMEOUT].setState(ISS_OFF);
727 }
728
729 SimTestsSP.setState(IPS_OK);
730 SimTestsSP.apply();
731 return true;
732 }
733 }
734
735 // Nobody has claimed this, so, ignore it
736 return INDI::CCD::ISNewSwitch(dev, name, states, names, n);
737}
738
740{
741#ifdef USE_EQUATORIAL_PE
743#endif
744 IDSnoopDevice(ActiveDeviceTP[ACTIVE_TELESCOPE].getText(), "EQUATORIAL_EOD_COORD");
745}
746
748{
749 // We try to snoop EQUATORIAL_PE first (true pointing with mount errors injected);
750 // if not found we fall through to the regular EQUATORIAL_EOD_COORD snoop below.
751#ifdef USE_EQUATORIAL_PE
752 const char * propName = findXMLAttValu(root, "name");
753 if (!strcmp(propName, "EQUATORIAL_PE"))
754 {
755 XMLEle * ep = nullptr;
756 int rc_ra = -1, rc_de = -1;
757 double newra = 0, newdec = 0;
758
759 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
760 {
761 const char * elemName = findXMLAttValu(ep, "name");
762
763 if (!strcmp(elemName, "RA_PE"))
764 rc_ra = f_scansexa(pcdataXMLEle(ep), &newra);
765 else if (!strcmp(elemName, "DEC_PE"))
766 rc_de = f_scansexa(pcdataXMLEle(ep), &newdec);
767 }
768
769 if (rc_ra == 0 && rc_de == 0)
770 {
771 INDI::IEquatorialCoordinates epochPos { newra, newdec }, J2000Pos { 0, 0 };
772 INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
773 raPE = J2000Pos.rightascension;
774 decPE = J2000Pos.declination;
775
776 // Only activate PE rendering once the telescope reports a valid (Ok) state.
777 // The initial defNumberVector arrives with state Idle and values 0/0.
778 const char * state = findXMLAttValu(root, "state");
779 if (!m_UsePE && strcmp(state, "Ok") == 0)
780 {
781 LOGF_INFO("EQUATORIAL_PE snoop active: JNow RA %.4f Dec %.4f -> J2000 RA %.4f Dec %.4f", newra, newdec, raPE, decPE);
782 m_UsePE = true;
783 }
784
785 EqPENP[AXIS_RA].setValue(newra);
786 EqPENP[AXIS_DE].setValue(newdec);
787 EqPENP.apply();
788
789 LOGF_DEBUG("Snooped EQUATORIAL_PE JNow RA %g Dec %g -> J2000 RA %g Dec %g", newra, newdec, raPE, decPE);
790
791 return true;
792 }
793 }
794#endif
795
796 return INDI::CCD::ISSnoopDevice(root);
797}
798
800{
801 // Save CCD Config
803
804
805 // Save CCD Simulator Config
806 SimulatorSettingsNP.save(fp);
807
808 // Gain
809 GainNP.save(fp);
810
811
812 return true;
813}
814
816{
817 m_ExposureRequest = 1.0 / Streamer->getTargetExposure();
818 pthread_mutex_lock(&condMutex);
819 m_StreamPredicate = 1;
820 pthread_mutex_unlock(&condMutex);
821 pthread_cond_signal(&cv);
822
823 return true;
824}
825
827{
828 pthread_mutex_lock(&condMutex);
829 m_StreamPredicate = 0;
830 pthread_mutex_unlock(&condMutex);
831 pthread_cond_signal(&cv);
832
833 return true;
834}
835
836bool GuideSim::UpdateCCDFrame(int x, int y, int w, int h)
837{
838 long bin_width = w / PrimaryCCD.getBinX();
839 long bin_height = h / PrimaryCCD.getBinY();
840
841 bin_width = bin_width - (bin_width % 2);
842 bin_height = bin_height - (bin_height % 2);
843
844 Streamer->setSize(bin_width, bin_height);
845
846 return INDI::CCD::UpdateCCDFrame(x, y, w, h);
847}
848
849bool GuideSim::UpdateCCDBin(int hor, int ver)
850{
851 if (hor == 3 || ver == 3)
852 {
853 LOG_ERROR("3x3 binning is not supported.");
854 return false;
855 }
856
857 long bin_width = PrimaryCCD.getSubW() / hor;
858 long bin_height = PrimaryCCD.getSubH() / ver;
859
860 bin_width = bin_width - (bin_width % 2);
861 bin_height = bin_height - (bin_height % 2);
862
863 Streamer->setSize(bin_width, bin_height);
864
865 return INDI::CCD::UpdateCCDBin(hor, ver);
866}
867
868void * GuideSim::streamVideoHelper(void * context)
869{
870 return static_cast<GuideSim *>(context)->streamVideo();
871}
872
874{
875 auto start = std::chrono::high_resolution_clock::now();
876 auto finish = std::chrono::high_resolution_clock::now();
877
878 while (true)
879 {
880 pthread_mutex_lock(&condMutex);
881
882 while (m_StreamPredicate == 0)
883 {
884 pthread_cond_wait(&cv, &condMutex);
885 m_ExposureRequest = Streamer->getTargetExposure();
886 }
887
888 if (m_TerminateThread)
889 break;
890
891 // release condMutex
892 pthread_mutex_unlock(&condMutex);
893
894
895 // 16 bit
897
899
900 finish = std::chrono::high_resolution_clock::now();
901 std::chrono::duration<double> elapsed = finish - start;
902
903 if (elapsed.count() < m_ExposureRequest)
904 usleep(fabs(m_ExposureRequest - elapsed.count()) * 1e6);
905
907 Streamer->newFrame(PrimaryCCD.getFrameBuffer(), size);
908
909 start = std::chrono::high_resolution_clock::now();
910 }
911
912 pthread_mutex_unlock(&condMutex);
913 return nullptr;
914}
915
916void GuideSim::addFITSKeywords(INDI::CCDChip *targetChip, std::vector<INDI::FITSRecord> &fitsKeywords)
917{
918 INDI::CCD::addFITSKeywords(targetChip, fitsKeywords);
919
920 fitsKeywords.push_back({"GAIN", GainNP[0].getValue(), 3, "Gain"});
921}
The GuideSim class provides an advanced simulator for a CCD that includes a dedicated on-board guide ...
double m_CurrentDEC
virtual IPState GuideNorth(uint32_t) override
Guide northward for ms milliseconds.
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....
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
virtual IPState GuideEast(uint32_t) override
Guide easward for ms milliseconds.
float m_GuideRate
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
float m_GuideNSOffset
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 DrawCcdFrame(INDI::CCDChip *targetChip)
double m_CurrentRA
virtual void addFITSKeywords(INDI::CCDChip *targetChip, std::vector< INDI::FITSRecord > &fitsKeywords) override
Generate FITS keywords that will be added to FIST/XISF file.
const char * getDefaultName() override
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
bool Disconnect() override
Disconnect from device.
float m_GuideWEOffset
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
bool AbortExposure() override
Abort ongoing exposure.
virtual bool StartStreaming() override
StartStreaming Start live video streaming.
virtual bool StopStreaming() override
StopStreaming Stop live video streaming.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void * streamVideo()
virtual IPState GuideSouth(uint32_t) override
Guide southward for ms milliseconds.
static void * streamVideoHelper(void *context)
bool StartExposure(float duration) override
Start exposing primary CCD chip.
virtual void activeDevicesUpdated() override
activeDevicesUpdated Inform children that ActiveDevices property was updated so they can snoop on the...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual IPState GuideWest(uint32_t) override
Guide westward for ms milliseconds.
bool SetupParms()
bool isConnected() const
INDI::PropertyText getText(const char *name) const
The CCDChip class provides functionality of a CCD Chip within a CCD.
Definition indiccdchip.h:39
void 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
int getBPP() const
getBPP Get CCD Chip depth (bits per pixel).
CCD_FRAME getFrameType() const
isInterlaced
int getBinY() const
getBinY Get vertical binning of the CCD chip.
int getXRes() const
getXRes Get the horizontal resolution in pixels of the CCD Chip.
Definition indiccdchip.h:81
int getBinX() const
getBinX Get horizontal binning of the CCD chip.
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
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
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.
int getYRes() const
Get the vertical resolution in pixels of the CCD Chip.
Definition indiccdchip.h:90
void binFrame()
binFrame Perform software binning on the CCD frame. Only use this function if hardware binning is not...
double Longitude
Definition indiccd.h:597
CCDChip PrimaryCCD
Definition indiccd.h:621
uint32_t GetCCDCapability() const
GetCCDCapability returns the CCD capabilities.
Definition indiccd.h:161
bool HasGuideHead()
Definition indiccd.h:199
std::unique_ptr< StreamManager > Streamer
Definition indiccd.h:619
bool InExposure
Definition indiccd.h:566
@ FOCAL_LENGTH
Definition indiccd.h:723
@ CFA_OFFSET_Y
Definition indiccd.h:688
@ CFA_OFFSET_X
Definition indiccd.h:687
INDI::PropertyText ActiveDeviceTP
ActiveDeviceTP defines 4 devices the camera driver can listen to (snoop) for properties of interest s...
Definition indiccd.h:656
virtual bool UpdateCCDFrame(int x, int y, int w, int h)
CCD calls this function when CCD Frame dimension needs to be updated in the hardware....
Definition indiccd.cpp:1988
virtual void addFITSKeywords(CCDChip *targetChip, std::vector< FITSRecord > &fitsKeywords)
Generate FITS keywords that will be added to FIST/XISF file.
Definition indiccd.cpp:2055
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition indiccd.cpp:1554
@ CCD_CAN_SUBFRAME
Definition indiccd.h:116
@ CCD_CAN_ABORT
Definition indiccd.h:117
@ CCD_HAS_SHUTTER
Definition indiccd.h:120
@ CCD_HAS_BAYER
Definition indiccd.h:122
@ CCD_CAN_BIN
Definition indiccd.h:115
@ CCD_HAS_STREAMING
Definition indiccd.h:123
@ CCD_HAS_ST4_PORT
Definition indiccd.h:119
virtual bool ExposureComplete(CCDChip *targetChip)
Uploads target Chip exposed buffer as FITS to the client. Derived classes should class this function ...
Definition indiccd.cpp:2361
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition indiccd.cpp:525
INDI::PropertyNumber TemperatureNP
TemperatureNP Camera Temperature in Celcius.
Definition indiccd.h:669
std::mutex ccdBufferLock
Definition indiccd.h:612
INDI::PropertyNumber ScopeInfoNP
Definition indiccd.h:720
virtual bool UpdateCCDBin(int hor, int ver)
CCD calls this function when CCD Binning needs to be updated in the hardware. Derived classes should ...
Definition indiccd.cpp:2007
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition indiccd.cpp:157
int pierSide
Definition indiccd.h:554
INDI::PropertyText BayerTP
BayerTP Bayer pattern offset and type.
Definition indiccd.h:684
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition indiccd.cpp:540
virtual void addCaptureFormat(const CaptureFormat &format)
addCaptureFormat Add a supported camera native capture format (e.g. Mono, Bayer8.....
Definition indiccd.cpp:3264
double RotatorAngle
Definition indiccd.h:585
virtual void SetGuiderParams(int x, int y, int bpp, float xf, float yf)
Setup CCD parameters for guide head CCD. Child classes call this function to update CCD parameters.
Definition indiccd.cpp:2901
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
Definition indiccd.cpp:2914
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition indiccd.cpp:729
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. All fields must be initialized.
Definition indiccd.cpp:140
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition indiccd.cpp:1143
@ ACTIVE_TELESCOPE
Definition indiccd.h:659
CCDChip GuideCCD
Definition indiccd.h:622
double RA
Definition indiccd.h:551
virtual void SetCCDParams(int x, int y, int bpp, float xf, float yf)
Setup CCD parameters for primary CCD. Child classes call this function to update CCD parameters.
Definition indiccd.cpp:2888
double snoopedFocalLength
Definition indiccd.h:565
double Dec
Definition indiccd.h:551
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
uint32_t getDriverInterface() const
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
void setPermission(IPerm permission)
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
int renderFrame(INDI::CCDChip *chip, double ra_j2000_deg, double dec_j2000_deg, double focal_length_mm, double rotation_deg, float exposure_s, bool renderStars=true, double minSearchRadiusArcmin=0)
void applyReadoutNoise(INDI::CCDChip *chip, int bias, int maxNoise)
void setConfig(const RenderConfig &cfg)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
#define M_PI
Definition dsp.h:52
constexpr double DEGREES_TO_RADIANS
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_NOFMANY
Definition indiapi.h:173
@ AXIS_DE
@ AXIS_RA
@ INDI_MONO
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:194
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition indicom.c:1321
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition indicom.c:1379
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
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
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define getDeviceName
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos)
ObservedToJ2000 converts an observed position to a J2000 catalogue position removes aberration,...
Definition libastro.cpp:51
float limitingMag
Definition sky_renderer.h:9
float saturationMag