Instrument Neutral Distributed Interface INDI 2.2.5
eq500x.cpp
Go to the documentation of this file.
1#if 0
2LX200 - based Omegon EQ500X Equatorial Mount
3
4Copyright (C) 2019 Eric Dejouhanet ([email protected])
5
6This library is free software;
7you can redistribute it and / or
8modify it under the terms of the GNU Lesser General Public
9License as published by the Free Software Foundation;
10either
11version 2.1 of the License, or (at your option) any later version.
12
13This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY;
15without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17Lesser General Public License for more details.
18
19You should have received a copy of the GNU Lesser General Public
20License along with this library;
21if not, write to the Free Software
22Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA
23#endif
24
25#include <cmath>
26#include <memory>
27#include <cstring>
28#include <termios.h>
29#include <unistd.h>
30#include <cassert>
31
32#include <libnova/sidereal_time.h>
33#include <libnova/transform.h>
34
35#include "lx200generic.h"
36#include "eq500x.h"
37
38#include "indicom.h"
39#include "lx200driver.h"
40
41typedef struct _simEQ500X
42{
47 clock_t last_sim;
49
51{
52 "00:00:00",
53 "+00*00'00",
54 0.0, 0.0,
55 0,
56};
57
59
60#define MechanicalPoint_DEC_FormatR "+DD:MM:SS"
61#define MechanicalPoint_DEC_FormatW "+DDD:MM:SS"
62#define MechanicalPoint_RA_Format "HH:MM:SS"
63
64// This is the duration the serial port waits for while expecting replies
65static int const EQ500X_TIMEOUT = 5;
66
67// One degree, one arcminute, one arcsecond
68static double constexpr ONEDEGREE = 1.0;
69static double constexpr ARCMINUTE = ONEDEGREE / 60.0;
70static double constexpr ARCSECOND = ONEDEGREE / 3600.0;
71
72// This is the minimum detectable movement in RA/DEC
73static double /*constexpr*/ RA_GRANULARITY = std::lround((15.0 * ARCSECOND) * 3600.0) / 3600.0;
74static double /*constexpr*/ DEC_GRANULARITY = std::lround((1.0 * ARCSECOND) * 3600.0) / 3600.0;
75
76// This is the number of loops expected to achieve convergence on each slew rate
77// A full rotation at 5deg/s would take 360/5=72s to complete at RS speed, checking position twice per second
78static int MAX_CONVERGENCE_LOOPS = 144;
79
80// Hardcoded adjustment intervals
81// RA/DEC deltas are adjusted at specific 'slew_rate' down to 'epsilon' degrees when smaller than 'distance' degrees
82// The greater adjustment requirement drives the slew rate (one single command for both axis)
84{
85 char const * slew_rate;
87 double epsilon;
88 double distance;
90}
91const adjustments[] =
92{
93 {":RG#", 0, 1 * ARCSECOND, 0.7 * ARCMINUTE, 100 }, // Guiding speed
94 {":RC#", 1, 0.7 * ARCMINUTE, 10 * ARCMINUTE, 200 }, // Centering speed
95 {":RM#", 2, 10 * ARCMINUTE, 5 * ONEDEGREE, 500 }, // Finding speed
96 {":RS#", 3, 5 * ONEDEGREE, 360 * ONEDEGREE, 1000 }
97}; // Slew speed
98
99/**************************************************************************************
100** EQ500X Constructor
101***************************************************************************************/
103{
104 setVersion(1, 1);
105
106 // Sanitize constants: epsilon of a slew rate must be smaller than distance of its smaller sibling
107 for (size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]) - 1; i++)
108 assert(adjustments[i + 1].epsilon <= adjustments[i].distance);
109
110 // Sanitize constants: epsilon of slew rates must be smaller than distance
111 for (size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]); i++)
112 assert(adjustments[i].epsilon <= adjustments[i].distance);
113
114 // No pulse guiding (mount doesn't support Mgx commands) nor tracking frequency nor nothing generic has actually
116
117 // Sync, goto, abort, location and 4 slew rates, no guiding rates and no park position
120
121 LOG_DEBUG("Initializing from EQ500X device...");
122}
123
124/**************************************************************************************
125**
126***************************************************************************************/
128{
129 return "EQ500X";
130}
131
133{
135}
136
141
142/**************************************************************************************
143**
144***************************************************************************************/
145
146
148{
150
151 // Mount tracks as soon as turned on
153
154 return true;
155}
156
157/**************************************************************************************
158**
159***************************************************************************************/
161{
162 /* */
163}
164
166{
167 if (!isSimulation())
168 {
169 if (PortFD <= 0)
170 return false;
171
172 LOG_DEBUG("Testing telescope connection using GR...");
173 tty_set_debug(1);
174
175 LOG_DEBUG("Clearing input...");
176 tcflush(PortFD, TCIFLUSH);
177 }
178
179 for (int i = 0; i < 2; i++)
180 {
181 if (ReadScopeStatus())
182 {
183 if(1 <= i)
184 {
185 LOG_DEBUG("Failure. Telescope is not responding to GR/GD!");
186 return false;
187 }
188 }
189 else break;
190
191 const struct timespec timeout = {0, 50000000L};
192 nanosleep(&timeout, nullptr);
193 }
194
195 /* Blink the control pad */
196#if 0
197 const struct timespec timeout = {0, 250000000L};
198 sendCmd(":RG#");
199 nanosleep(&timeout, nullptr);
200 sendCmd(":RC#");
201 nanosleep(&timeout, nullptr);
202 sendCmd(":RM#");
203 nanosleep(&timeout, nullptr);
204 sendCmd(":RS#");
205 nanosleep(&timeout, nullptr);
206 sendCmd(":RC#");
207 nanosleep(&timeout, nullptr);
208 sendCmd(":RM#");
209 nanosleep(&timeout, nullptr);
210 sendCmd(":RS#");
211 nanosleep(&timeout, nullptr);
212 sendCmd(":RC#");
213 nanosleep(&timeout, nullptr);
214 sendCmd(":RM#");
215 nanosleep(&timeout, nullptr);
216 sendCmd(":RS#");
217 nanosleep(&timeout, nullptr);
218 sendCmd(":RG#");
219#endif
220
221 LOG_DEBUG("Connection check successful!");
222 if (!isSimulation())
223 tty_set_debug(0);
224 return true;
225}
226
227bool EQ500X::updateLocation(double latitude, double longitude, double elevation)
228{
229 INDI_UNUSED(elevation);
230 LOGF_INFO("Location updated: Longitude (%g) Latitude (%g)", longitude, latitude);
231
232 // Only update LST if the mount is connected and "parked" looking at the pole
233 if (isConnected() && !getCurrentMechanicalPosition(currentMechPosition) && currentMechPosition.atParkingPosition())
234 {
235 // HACK: Longitude used by getLST is updated after this function returns, so hack a new longitude first
236 double const prevLongitude = LocationNP[LOCATION_LONGITUDE].getValue();
237 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
238
239 double const LST = getLST();
240 Sync(LST - 6, currentMechPosition.DECsky());
241 LOGF_INFO("Location updated: mount considered parked, synced to LST %gh.", LST);
242
243 LocationNP[LOCATION_LONGITUDE].setValue(prevLongitude);
244 }
245
246 return true;
247}
248
250{
251 if (!isConnected())
252 return false;
253
254 // Movement markers, adjustment is done when no movement is required and all flags are cleared
255 //static bool east = false, west = false, north = false, south = false;
256
257 // Current adjustment rate
258 //static struct _adjustment const * adjustment = nullptr;
259
260 // If simulating, do simulate rates - in that case currentPosition is driven by currentRA/currentDEC
261 if (isSimulation())
262 {
263 // These are the simulated rates
264 double const rates[sizeof(adjustments)] =
265 {
266 /*RG*/5 * ARCSECOND,
267 /*RC*/5 * ARCMINUTE,
268 /*RM*/20 * ARCMINUTE,
269 /*RS*/5 * ONEDEGREE
270 };
271
272 // Calculate elapsed time since last status read
273 struct timespec clock = {0, 0};
274 clock_gettime(CLOCK_MONOTONIC, &clock);
275 long const now = clock.tv_sec * 1000 + static_cast <long> (round(clock.tv_nsec / 1000000.0));
276 double const delta = simEQ500X.last_sim ? static_cast <double> (now - simEQ500X.last_sim) / 1000.0 : 0.0;
277 simEQ500X.last_sim = now;
278
279 // Simulate movement if needed
280 if (nullptr != adjustment)
281 {
282 // Use currentRA/currentDEC to store smaller-than-one-arcsecond values
283 if (RAmDecrease) simEQ500X.MechanicalRA = std::fmod(simEQ500X.MechanicalRA - rates[adjustment - adjustments] * delta / 15.0
284 + 24.0, 24.0);
285 if (RAmIncrease) simEQ500X.MechanicalRA = std::fmod(simEQ500X.MechanicalRA + rates[adjustment - adjustments] * delta / 15.0
286 + 24.0, 24.0);
287 if (DECmDecrease) simEQ500X.MechanicalDEC -= rates[adjustment - adjustments] * delta;
288 if (DECmIncrease) simEQ500X.MechanicalDEC += rates[adjustment - adjustments] * delta;
289
290 // Update current position and rewrite simulated mechanical positions
294
295 LOGF_DEBUG("New mechanical RA/DEC simulated as %lf°/%lf° (%+lf°,%+lf°), stored as %lfh/%lf° = %s/%s",
296 simEQ500X.MechanicalRA * 15.0, simEQ500X.MechanicalDEC, (RAmDecrease
297 || RAmIncrease) ? rates[adjustment - adjustments]*delta : 0, (DECmDecrease
298 || DECmIncrease) ? rates[adjustment - adjustments]*delta : 0, p.RAm(), p.DECm(), simEQ500X.MechanicalRAStr,
300 }
301 }
302
303 if (getCurrentMechanicalPosition(currentMechPosition))
304 {
306 LOG_ERROR("Error reading RA/DEC.");
307 EqNP.apply();
308 return false;
309 }
310
311 bool const ra_changed = currentRA != currentMechPosition.RAsky();
312 bool const dec_changed = currentDEC != currentMechPosition.DECsky();
313
314 if (dec_changed)
315 currentDEC = currentMechPosition.DECsky();
316
317 if (ra_changed)
318 {
319 currentRA = currentMechPosition.RAsky();
320
321 // Update the side of pier - rangeHA is NOT suitable here
322 double HA = rangeHA(getLST() - currentRA);
323 while (+12 <= HA) HA -= 24;
324 while (HA <= -12) HA += 24;
325 switch (currentMechPosition.getPointingState())
326 {
329 break;
332 break;
333 }
334 LOGF_DEBUG("Mount HA=%lfh pointing %s on %s side", HA,
335 currentMechPosition.getPointingState() == MechanicalPoint::POINTING_NORMAL ? "normal" : "beyond pole",
336 getPierSide() == PIER_EAST ? "east" : "west");
337 }
338
339 // If we are using the goto feature, check state
340 if (TrackState == SCOPE_SLEWING && _gotoEngaged)
341 {
342 if (EqNP[AXIS_RA].getValue() == currentRA && EqNP[AXIS_DE].getValue() == currentDEC)
343 {
344 _gotoEngaged = false;
345
346 // Preliminary goto is complete, continue
347 if (!Goto(targetMechPosition.RAsky(), targetMechPosition.DECsky()))
348 goto slew_failure;
349 }
350 }
351
352 // If we are adjusting, adjust movement and timer time to achieve arcsecond goto precision
353 if (TrackState == SCOPE_SLEWING && !_gotoEngaged)
354 {
355 // Compute RA/DEC deltas - keep in mind RA is in hours on the mount, with a granularity of 15 degrees
356 double const ra_delta = currentMechPosition.RA_degrees_to(targetMechPosition);
357 double const dec_delta = currentMechPosition.DEC_degrees_to(targetMechPosition);
358 double const abs_ra_delta = std::abs(ra_delta);
359 double const abs_dec_delta = std::abs(dec_delta);
360
361 // If mount is not at target, adjust
362 if (RA_GRANULARITY <= abs_ra_delta || DEC_GRANULARITY <= abs_dec_delta)
363 {
364 // This will hold required adjustments in RA and DEC axes
365 struct _adjustment const *ra_adjust = nullptr, *dec_adjust = nullptr;
366
367 // Choose slew rate for RA based on distance to target
368 for(size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]) && nullptr == ra_adjust; i++)
369 if (abs_ra_delta <= adjustments[i].distance)
370 ra_adjust = &adjustments[i];
371 assert(nullptr != ra_adjust);
372 LOGF_DEBUG("RA %lf-%lf = %+lf° under %lf° would require adjustment at %s until less than %lf°",
373 targetMechPosition.RAm() * 15.0, currentMechPosition.RAm() * 15.0, ra_delta, ra_adjust->distance, ra_adjust->slew_rate,
374 std::max(ra_adjust->epsilon, 15.0 / 3600.0));
375
376 // Choose slew rate for DEC based on distance to target
377 for(size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]) && nullptr == dec_adjust; i++)
378 if (abs_dec_delta <= adjustments[i].distance)
379 dec_adjust = &adjustments[i];
380 assert(nullptr != dec_adjust);
381 LOGF_DEBUG("DEC %lf-%lf = %+lf° under %lf° would require adjustment at %s until less than %lf°",
382 targetMechPosition.DECm(), currentMechPosition.DECm(), dec_delta, dec_adjust->distance, dec_adjust->slew_rate,
383 dec_adjust->epsilon);
384
385 // This will hold the command string to send to the mount, with move commands
386 char CmdString[32] = {0};
387
388 // Previous alignment marker to spot when to change slew rate
389 static struct _adjustment const * previous_adjustment = nullptr;
390
391 // We adjust the axis which has the faster slew rate first, eventually both axis at the same time if they have same speed
392 // Because we have only one rate for both axes, we need to choose the fastest rate and control the axis (eventually both) which requires that rate
393 adjustment = ra_adjust < dec_adjust ? dec_adjust : ra_adjust;
394
395 // If RA was moving but now would be moving at the wrong rate, stop it
396 if (ra_adjust != adjustment)
397 {
398 if (RAmIncrease)
399 {
400 strcat(CmdString, ":Qe#");
401 RAmIncrease = false;
402 }
403 if (RAmDecrease)
404 {
405 strcat(CmdString, ":Qw#");
406 RAmDecrease = false;
407 }
408 }
409
410 // If DEC was moving but now would be moving at the wrong rate, stop it
411 if (dec_adjust != adjustment)
412 {
413 if (DECmDecrease)
414 {
415 strcat(CmdString, ":Qn#");
416 DECmDecrease = false;
417 }
418 if (DECmIncrease)
419 {
420 strcat(CmdString, ":Qs#");
421 DECmIncrease = false;
422 }
423 }
424
425 // Prepare for the new rate
426 if (previous_adjustment != adjustment)
427 {
428 // Add the new slew rate
429 strcat(CmdString, adjustment->slew_rate);
430
431 // If adjustment goes expectedly down, reset countdown
432 if (adjustment < previous_adjustment)
433 countdown = MAX_CONVERGENCE_LOOPS;
434
435 // FIXME: wait for the mount to slow down to improve convergence?
436
437 // Remember previous adjustment
438 previous_adjustment = adjustment;
439 }
440 LOGF_DEBUG("Current adjustment speed is %s", adjustment->slew_rate);
441
442 // If RA is being adjusted, check delta against adjustment epsilon to enable or disable movement
443 // The smallest change detectable in RA is 1/3600 hours, or 15/3600 degrees
444 if (ra_adjust == adjustment)
445 {
446 // This is the lowest limit of this adjustment
447 double const ra_epsilon = std::max(adjustment->epsilon, RA_GRANULARITY);
448
449 // Find requirement
450 bool const doDecrease = ra_epsilon <= ra_delta;
451 bool const doIncrease = ra_delta <= -ra_epsilon;
452 assert(!(doDecrease && doIncrease));
453
454 // Stop movement if required - just stopping or going opposite
455 if (RAmIncrease && (!doDecrease || doIncrease))
456 {
457 strcat(CmdString, ":Qe#");
458 RAmIncrease = false;
459 }
460 if (RAmDecrease && (!doIncrease || doDecrease))
461 {
462 strcat(CmdString, ":Qw#");
463 RAmDecrease = false;
464 }
465
466 // Initiate movement if required
467 if (doDecrease && !RAmIncrease)
468 {
469 strcat(CmdString, ":Me#");
470 RAmIncrease = true;
471 }
472 if (doIncrease && !RAmDecrease)
473 {
474 strcat(CmdString, ":Mw#");
475 RAmDecrease = true;
476 }
477 }
478
479 // If DEC is being adjusted, check delta against adjustment epsilon to enable or disable movement
480 // The smallest change detectable in DEC is 1/3600 degrees
481 if (dec_adjust == adjustment)
482 {
483 // This is the lowest limit of this adjustment
484 double const dec_epsilon = std::max(adjustment->epsilon, DEC_GRANULARITY);
485
486 // Find requirement
487 bool const doDecrease = dec_epsilon <= dec_delta;
488 bool const doIncrease = dec_delta <= -dec_epsilon;
489 assert(!(doDecrease && doIncrease));
490
491 // Stop movement if required - just stopping or going opposite
492 if (DECmIncrease && (!doDecrease || doIncrease))
493 {
494 strcat(CmdString, ":Qn#");
495 DECmIncrease = false;
496 }
497 if (DECmDecrease && (!doIncrease || doDecrease))
498 {
499 strcat(CmdString, ":Qs#");
500 DECmDecrease = false;
501 }
502
503 // Initiate movement if required
504 if (doDecrease && !DECmIncrease)
505 {
506 strcat(CmdString, ":Mn#");
507 DECmIncrease = true;
508 }
509 if (doIncrease && !DECmDecrease)
510 {
511 strcat(CmdString, ":Ms#");
512 DECmDecrease = true;
513 }
514 }
515
516 // Basic algorithm sanitization on movement orientation: move one way or the other, or not at all
517 assert(!(RAmIncrease && RAmDecrease) && !(DECmDecrease && DECmIncrease));
518
519 // This log shows target in Degrees/Degrees and delta in Degrees/Degrees
520 LOGF_DEBUG("Centering (%lf°,%lf°) delta (%lf°,%lf°) moving %c%c%c%c at %s until less than (%lf°,%lf°)",
521 targetMechPosition.RAm() * 15.0, targetMechPosition.DECm(), ra_delta, dec_delta, RAmDecrease ? 'W' : '.',
522 RAmIncrease ? 'E' : '.', DECmDecrease ? 'N' : '.', DECmIncrease ? 'S' : '.', adjustment->slew_rate,
523 std::max(adjustment->epsilon, RA_GRANULARITY), adjustment->epsilon);
524
525 // If we have a command to run, issue it
526 if (CmdString[0] != '\0')
527 {
528 // Send command to mount
529 if (sendCmd(CmdString))
530 {
531 LOGF_ERROR("Error centering (%lf°,%lf°)", targetMechPosition.RAm() * 15.0, targetMechPosition.DECm());
532 slewError(-1);
533 return false;
534 }
535
536 // Update slew rate
540 }
541
542 // If all movement flags are cleared, we are done adjusting
543 if (!RAmIncrease && !RAmDecrease && !DECmDecrease && !DECmIncrease)
544 {
545 LOGF_INFO("Centering delta (%lf,%lf) intermediate adjustment complete (%d loops)", ra_delta, dec_delta,
546 MAX_CONVERGENCE_LOOPS - countdown);
547 adjustment = nullptr;
548 }
549 // Else, if it has been too long since we started, maybe we have a convergence problem.
550 // The mount slows down when requested to stop under minimum distance, so we may miss the target.
551 // The behavior is improved by changing the slew rate while converging, but is still tricky to tune.
552 else if (--countdown <= 0)
553 {
554 LOGF_ERROR("Failed centering to (%lf,%lf) under loop limit, aborting...", targetMechPosition.RAm(),
555 targetMechPosition.DECm());
556 goto slew_failure;
557 }
558 // Else adjust poll timeout to adjustment speed and continue
559 else setCurrentPollingPeriod(static_cast <uint32_t> (adjustment->polling_interval));
560 }
561 // If we attained target position at one arcsecond precision, finish procedure and track target
562 else
563 {
564 LOG_INFO("Slew is complete. Tracking...");
565 sendCmd(":Q#");
566 updateSlewRate(savedSlewRateIndex);
567 adjustment = nullptr;
571 LOG_INFO("Mount is tracking");
572 EqNP.apply();
573 }
574 }
575 else
576 {
577 // Force-reset markers in case we got aborted
578 if (DECmDecrease) DECmDecrease = false;
579 if (DECmIncrease) DECmIncrease = false;
580 if (RAmIncrease) RAmIncrease = false;
581 if (RAmDecrease) RAmDecrease = false;
582 adjustment = nullptr;
583 }
584
585 // Update RA/DEC properties
586 if (ra_changed || dec_changed)
588
589 return true;
590
591slew_failure:
592 // If we failed at some point, attempt to stop moving and update properties with error
593 sendCmd(":Q#");
594 updateSlewRate(savedSlewRateIndex);
595 adjustment = nullptr;
598 currentRA = currentMechPosition.RAsky();
599 currentDEC = currentMechPosition.DECsky();
601 slewError(-1);
602 return false;
603}
604
605bool EQ500X::Goto(double ra, double dec)
606{
607 // Check whether a meridian flip is required - rangeHA is NOT suitable here
608 double HA = getLST() - ra;
609 while (+12 <= HA) HA -= 24;
610 while (HA <= -12) HA += 24;
611
612 // Deduce required orientation of mount in HA quadrants - set orientation BEFORE coordinates!
613 targetMechPosition.setPointingState((0 <= HA
615 targetMechPosition.RAsky(ra);
616 targetMechPosition.DECsky(dec);
617
618 // If moving, let's stop it first.
619 if (EqNP.getState() == IPS_BUSY)
620 {
621 if (!Abort())
622 {
624 LOG_ERROR("Abort slew failed.");
625 Telescope::AbortSP.apply();
626 return false;
627 }
628
631 LOG_ERROR("Slew aborted.");
632 Telescope::AbortSP.apply();
633 EqNP.apply();
634
636 {
644 }
645
646 // sleep for 100 mseconds
647 const struct timespec timeout = {0, 100000000L};
648 nanosleep(&timeout, nullptr);
649 }
650
651 /* The goto feature is quite imprecise because it will always use full speed.
652 * By the time the mount stops, the position is off by 0-5 degrees, depending on the speed attained during the move.
653 * Additionally, a firmware limitation prevents the goto feature from slewing to close coordinates, and will cause uneeded axis rotation.
654 * Therefore, don't use the goto feature for a goto, and let ReadScope adjust the position by itself.
655 */
656
657 // Set target position and adjust
658 if (setTargetMechanicalPosition(targetMechPosition))
659 {
661 LOG_ERROR("Error setting RA/DEC.");
662 EqNP.apply();
663 return false;
664 }
665 else
666 {
667 targetMechPosition.RAsky(targetRA = ra);
668 targetMechPosition.DECsky(targetDEC = dec);
669
670 LOGF_INFO("Goto target (%lfh,%lf°) HA %lf, quadrant %s", ra, dec, HA,
671 targetMechPosition.getPointingState() == MechanicalPoint::POINTING_NORMAL ? "normal" : "beyond pole");
672 }
673
674 // Limit the number of loops
675 countdown = MAX_CONVERGENCE_LOOPS;
676
677 // Reset original adjustment
678 // Reset movement markers
679
681 //EqNP.s = IPS_BUSY;
682
683 // Remember current slew rate
684 savedSlewRateIndex = static_cast <enum TelescopeSlewRate> (SlewRateSP.findOnSwitchIndex());
685
686 // Format RA/DEC for logs
687 char RAStr[16] = {0}, DecStr[16] = {0};
688 fs_sexa(RAStr, targetRA, 2, 3600);
689 fs_sexa(DecStr, targetDEC, 2, 3600);
690
691 LOGF_INFO("Slewing to JNow RA: %s - DEC: %s", RAStr, DecStr);
692
693 return true;
694}
695
696bool EQ500X::Sync(double ra, double dec)
697{
698 targetMechPosition.RAsky(targetRA = ra);
699 targetMechPosition.DECsky(targetDEC = dec);
700
701 if(!setTargetMechanicalPosition(targetMechPosition))
702 {
703 if (!isSimulation())
704 {
705 char b[64/*RB_MAX_LEN*/] = {0};
706 tcflush(PortFD, TCIFLUSH);
707
708 if (getCommandString(PortFD, b, ":CM#") < 0)
709 goto sync_error;
710 if (!strncmp("No name", b, sizeof(b)))
711 goto sync_error;
712 }
713 else
714 {
715 targetMechPosition.toStringRA(simEQ500X.MechanicalRAStr, sizeof(simEQ500X.MechanicalRAStr));
717 simEQ500X.MechanicalRA = targetMechPosition.RAm();
718 simEQ500X.MechanicalDEC = targetMechPosition.DECm();
719 }
720
721 if (getCurrentMechanicalPosition(currentMechPosition))
722 goto sync_error;
723
724 currentRA = currentMechPosition.RAsky();
725 currentDEC = currentMechPosition.DECsky();
727
728 LOGF_INFO("Mount synced to target RA '%lf' DEC '%lf'", currentRA, currentDEC);
729 return true;
730 }
731
732sync_error:
734 LOG_ERROR("Synchronization failed.");
735 EqNP.apply();
736 LOGF_ERROR("Mount sync to target RA '%lf' DEC '%lf' failed", ra, dec);
737 return false;
738}
739
741{
744 return LX200Telescope::Abort() && updateSlewRate(savedSlewRateIndex);
745}
746
748{
749 INDI_UNUSED(side);
751 LOG_ERROR("Not supported");
753}
754
756{
757 // EQ500X has North/South directions inverted
758 int current_move = (dir == DIRECTION_NORTH) ? LX200_SOUTH : LX200_NORTH;
759
760 switch (command)
761 {
762 case MOTION_START:
763 if (!isSimulation() && MoveTo(PortFD, current_move) < 0)
764 {
765 LOG_ERROR("Error setting N/S motion direction.");
766 return false;
767 }
768 else
769 LOGF_DEBUG("Moving toward %s.",
770 (current_move == LX200_NORTH) ? "North" : "South");
771 break;
772
773 case MOTION_STOP:
774 if (!isSimulation() && HaltMovement(PortFD, current_move) < 0)
775 {
776 LOG_ERROR("Error stopping N/S motion.");
777 return false;
778 }
779 else
780 LOGF_DEBUG("Movement toward %s halted.",
781 (current_move == LX200_NORTH) ? "North" : "South");
782 break;
783 }
784
785 return true;
786}
787
788/**************************************************************************************
789**
790***************************************************************************************/
791
792int EQ500X::sendCmd(char const *data)
793{
794 LOGF_DEBUG("CMD <%s>", data);
795 if (!isSimulation())
796 {
797 int nbytes_write = 0;
798 return tty_write_string(PortFD, data, &nbytes_write);
799 }
800 return 0;
801}
802
803int EQ500X::getReply(char *data, size_t const len)
804{
805 if (!isSimulation())
806 {
807 int nbytes_read = 0;
808 int error_type = tty_read(PortFD, data, len, EQ500X_TIMEOUT, &nbytes_read);
809
810 LOGF_DEBUG("RES <%.*s> (%d)", nbytes_read, data, error_type);
811 return error_type;
812 }
813 return 0;
814}
815
817{
818 if (!isSimulation())
819 {
821 {
822 if (!sendCmd(":MS#"))
823 {
824 char buf[64/*RB_MAX_LEN*/] = {0};
825 if (!getReply(buf, 1))
826 return buf[0] != '0'; // 0 is valid for :MS
827 }
828 else return true;
829 }
830 else return true;
831 }
832 else
833 {
834 return !Sync(p.RAsky(), p.DECsky());
835 }
836
837 return false;
838}
839
841{
842 char b[64/*RB_MAX_LEN*/] = {0};
843 MechanicalPoint result = p;
844
845 // Always read DEC first as it gives the side of pier the scope is on, and has an impact on RA
846
847 if (isSimulation())
848 memcpy(b, simEQ500X.MechanicalDECStr, std::min(sizeof(b), sizeof(simEQ500X.MechanicalDECStr)));
849 else if (getCommandString(PortFD, b, ":GD#") < 0)
850 goto radec_error;
851
852 if (result.parseStringDEC(b, 64))
853 goto radec_error;
854
855 LOGF_DEBUG("Mount mechanical DEC reads '%s' as %lf.", b, result.DECm());
856
857 if (isSimulation())
858 memcpy(b, simEQ500X.MechanicalRAStr, std::min(sizeof(b), sizeof(simEQ500X.MechanicalRAStr)));
859 else if (getCommandString(PortFD, b, ":GR#") < 0)
860 goto radec_error;
861
862 if (result.parseStringRA(b, 64))
863 goto radec_error;
864
865 LOGF_DEBUG("Mount mechanical RA reads '%s' as %lf.", b, result.RAm());
866
867 p = result;
868 return false;
869
870radec_error:
871 return true;
872}
873
875{
876 if (!isSimulation())
877 {
878 // Size string buffers appropriately
879 char CmdString[] = ":Sr" MechanicalPoint_RA_Format "#:Sd" MechanicalPoint_DEC_FormatW "#";
880 char bufRA[sizeof(MechanicalPoint_RA_Format)], bufDEC[sizeof(MechanicalPoint_DEC_FormatW)];
881
882 // Write RA/DEC in placeholders
883 snprintf(CmdString, sizeof(CmdString), ":Sr%s#:Sd%s#", p.toStringRA(bufRA, sizeof(bufRA)), p.toStringDEC(bufDEC,
884 sizeof(bufDEC)));
885 LOGF_DEBUG("Target RA '%f' DEC '%f' converted to '%s'", static_cast <float> (p.RAm()), static_cast <float> (p.DECm()),
886 CmdString);
887
888 char buf[64/*RB_MAX_LEN*/] = {0};
889
890 if (!sendCmd(CmdString))
891 if (!getReply(buf, 2))
892 if (buf[0] == '1' && buf[1] == '1')
893 return false;
894 else LOGF_ERROR("Failed '%s', mount replied %c%c", CmdString, buf[0], buf[1]);
895 else LOGF_ERROR("Failed getting 2-byte reply to '%s'", CmdString);
896 else LOGF_ERROR("Failed '%s'", CmdString);
897
898 return true;
899 }
900
901 return false;
902}
903
904/**************************************************************************************
905**
906***************************************************************************************/
907
909{
910 RAm(ra);
911 DECm(dec);
912}
913
915{
916 // Consider mount 0/0 is pole - no way to check if synced already
917 return _RAm == 0 && _DECm == 0;
918}
919
921{
922 return static_cast <double> (_RAm) / 3600.0;
923}
924
926{
927 return static_cast <double> (_DECm) / 3600.0;
928}
929
930double EQ500X::MechanicalPoint::RAm(double const value)
931{
932 _RAm = std::lround(std::fmod(value + 24.0, 24.0) * 3600.0);
933 return RAm();
934}
935
936double EQ500X::MechanicalPoint::DECm(double const value)
937{
938 // Should be inside [-180,+180] but mount supports a larger (not useful) interval
939 _DECm = std::lround(std::fmod(value, 256.0) * 3600.0);
940
941 // Deduce pier side from mechanical DEC
942 if ((-256 * 3600 < _DECm && _DECm < -180 * 3600) || (0 <= _DECm && _DECm <= +180 * 3600))
943 _pointingState = POINTING_NORMAL;
944 else
945 _pointingState = POINTING_BEYOND_POLE;
946
947 return DECm();
948}
949
951{
952 switch (_pointingState)
953{
954 case POINTING_BEYOND_POLE:
955 return static_cast <double> ((12 * 3600 + _RAm) % (24 * 3600)) / 3600.0;
956 case POINTING_NORMAL:
957 default:
958 return static_cast <double> ((24 * 3600 + _RAm) % (24 * 3600)) / 3600.0;
959 }
960}
961
963{
964 // Convert to sky DEC inside [-90,90], allowing +/-90 values
965 long _DEC = 90 * 3600 - _DECm;
966 if (POINTING_BEYOND_POLE == _pointingState)
967 _DEC = 180 * 3600 - _DEC;
968 while (+90 * 3600 < _DEC) _DEC -= 180 * 3600;
969 while (_DEC < -90 * 3600) _DEC += 180 * 3600;
970 return static_cast <double> (_DEC) / 3600.0;
971}
972
973double EQ500X::MechanicalPoint::RAsky(const double _RAsky)
974{
975 switch (_pointingState)
976 {
977 case POINTING_BEYOND_POLE:
978 _RAm = static_cast <long> (std::fmod(12.0 + _RAsky, 24.0) * 3600.0);
979 break;
980 case POINTING_NORMAL:
981 _RAm = static_cast <long> (std::fmod(24.0 + _RAsky, 24.0) * 3600.0);
982 break;
983 }
984 return RAsky();
985}
986
987double EQ500X::MechanicalPoint::DECsky(const double _DECsky)
988{
989 switch (_pointingState)
990 {
991 case POINTING_BEYOND_POLE:
992 _DECm = 90 * 3600 - std::lround((180.0 - _DECsky) * 3600.0);
993 break;
994 case POINTING_NORMAL:
995 _DECm = 90 * 3600 - std::lround(_DECsky * 3600.0);
996 break;
997 }
998 return DECsky();
999}
1000
1001char const * EQ500X::MechanicalPoint::toStringRA(char *buf, size_t buf_length) const
1002{
1003 // See /test/test_eq500xdriver.cpp for description of RA conversion
1004
1005 //int const hours = ((_pierSide == PIER_WEST ? 12 : 0) + 24 + static_cast <int> (_RAm/3600)) % 24;
1006 int const hours = (24 + static_cast <int> (_RAm / 3600)) % 24;
1007 int const minutes = static_cast <int> (_RAm / 60) % 60;
1008 int const seconds = static_cast <int> (_RAm) % 60;
1009
1010 int const written = snprintf(buf, buf_length, "%02d:%02d:%02d", hours, minutes, seconds);
1011
1012 return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1013}
1014
1015bool EQ500X::MechanicalPoint::parseStringRA(char const *buf, size_t buf_length)
1016{
1017 if (buf_length < sizeof(MechanicalPoint_RA_Format) - 1)
1018 return true;
1019
1020 // Mount replies to "#GR:" with "HH:MM:SS".
1021 // HH, MM and SS are respectively hours, minutes and seconds in [00:00:00,23:59:59].
1022 // FIXME: Sanitize.
1023
1024 int hours = 0, minutes = 0, seconds = 0;
1025 if (3 == sscanf(buf, "%02d:%02d:%02d", &hours, &minutes, &seconds))
1026 {
1027 //_RAm = ( (_pierSide == PIER_WEST ? -12*3600 : +0) + 24*3600 +
1028 _RAm = (24 * 3600 + (hours % 24) * 3600 + minutes * 60 + seconds) % (24 * 3600);
1029 return false;
1030 }
1031
1032 return true;
1033}
1034
1035char const * EQ500X::MechanicalPoint::toStringDEC_Sim(char *buf, size_t buf_length) const
1036{
1037 // See /test/test_eq500xdriver.cpp for description of DEC conversion
1038
1039 int const degrees = static_cast <int> (_DECm / 3600) % 256;
1040 int const minutes = static_cast <int> (std::abs(_DECm) / 60) % 60;
1041 int const seconds = static_cast <int> (std::abs(_DECm)) % 60;
1042
1043 if (degrees < -255 || +255 < degrees)
1044 return (char const * )0;
1045
1046 char high_digit = '0';
1047 if (-100 < degrees && degrees < 100)
1048 {
1049 high_digit = '0' + (std::abs(degrees) / 10);
1050 }
1051 else switch (std::abs(degrees) / 10)
1052 {
1053 case 10:
1054 high_digit = ':';
1055 break;
1056 case 11:
1057 high_digit = ';';
1058 break;
1059 case 12:
1060 high_digit = '<';
1061 break;
1062 case 13:
1063 high_digit = '=';
1064 break;
1065 case 14:
1066 high_digit = '>';
1067 break;
1068 case 15:
1069 high_digit = '?';
1070 break;
1071 case 16:
1072 high_digit = '@';
1073 break;
1074 case 17:
1075 high_digit = 'A';
1076 break;
1077 case 18:
1078 high_digit = 'B';
1079 break;
1080 case 19:
1081 high_digit = 'C';
1082 break;
1083 case 20:
1084 high_digit = 'D';
1085 break;
1086 case 21:
1087 high_digit = 'E';
1088 break;
1089 case 22:
1090 high_digit = 'F';
1091 break;
1092 case 23:
1093 high_digit = 'G';
1094 break;
1095 case 24:
1096 high_digit = 'H';
1097 break;
1098 case 25:
1099 high_digit = 'I';
1100 break;
1101 default:
1102 return (char const*)0;
1103 break;
1104 }
1105
1106 char const low_digit = '0' + (std::abs(degrees) % 10);
1107
1108 int const written = snprintf(buf, buf_length, "%c%c%c:%02d:%02d", (0 <= degrees) ? '+' : '-', high_digit, low_digit,
1109 minutes, seconds);
1110
1111 return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1112}
1113
1114char const * EQ500X::MechanicalPoint::toStringDEC(char *buf, size_t buf_length) const
1115{
1116 // See /test/test_eq500xdriver.cpp for description of DEC conversion
1117
1118 int const degrees = static_cast <int> (_DECm / 3600) % 256;
1119 int const minutes = static_cast <int> (std::abs(_DECm) / 60) % 60;
1120 int const seconds = static_cast <int> (std::abs(_DECm)) % 60;
1121
1122 if (degrees < -255 || +255 < degrees)
1123 return (char const * )0;
1124
1125 int const written = snprintf(buf, buf_length, "%+03d:%02d:%02d", degrees, minutes, seconds);
1126
1127 return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1128}
1129
1130bool EQ500X::MechanicalPoint::parseStringDEC(char const *buf, size_t buf_length)
1131{
1132 if (buf_length < sizeof(MechanicalPoint_DEC_FormatR) - 1)
1133 return true;
1134
1135 char b[sizeof(MechanicalPoint_DEC_FormatR)] = {0};
1136 snprintf(b, sizeof(b), "%s", buf);
1137
1138 // Mount replies to "#GD:" with "sDD:MM:SS".
1139 // s is in {+,-} and provides a sign.
1140 // DD are degrees, unit D spans '0' to '9' in [0,9] but high D spans '0' to 'I' in [0,25].
1141 // MM are minutes, SS are seconds in [00:00,59:59].
1142 // The whole reply is in [-255:59:59,+255:59:59].
1143
1144 struct _DecValues
1145 {
1146 char degrees[4];
1147 char minutes[3];
1148 char seconds[3];
1149 } * const DecValues = (struct _DecValues*) b;
1150
1151 if (DecValues->degrees[1] < '0' || 'I' < DecValues->degrees[1])
1152 return true;
1153
1154 int const sgn = DecValues->degrees[0] == '-' ? -1 : +1;
1155
1156 // Replace sign with hundredth, or space if lesser than 100
1157 switch (DecValues->degrees[1])
1158 {
1159 case ':':
1160 DecValues->degrees[0] = '1';
1161 DecValues->degrees[1] = '0';
1162 break;
1163 case ';':
1164 DecValues->degrees[0] = '1';
1165 DecValues->degrees[1] = '1';
1166 break;
1167 case '<':
1168 DecValues->degrees[0] = '1';
1169 DecValues->degrees[1] = '2';
1170 break;
1171 case '=':
1172 DecValues->degrees[0] = '1';
1173 DecValues->degrees[1] = '3';
1174 break;
1175 case '>':
1176 DecValues->degrees[0] = '1';
1177 DecValues->degrees[1] = '4';
1178 break;
1179 case '?':
1180 DecValues->degrees[0] = '1';
1181 DecValues->degrees[1] = '5';
1182 break;
1183 case '@':
1184 DecValues->degrees[0] = '1';
1185 DecValues->degrees[1] = '6';
1186 break;
1187 case 'A':
1188 DecValues->degrees[0] = '1';
1189 DecValues->degrees[1] = '7';
1190 break;
1191 case 'B':
1192 DecValues->degrees[0] = '1';
1193 DecValues->degrees[1] = '8';
1194 break;
1195 case 'C':
1196 DecValues->degrees[0] = '1';
1197 DecValues->degrees[1] = '9';
1198 break;
1199 case 'D':
1200 DecValues->degrees[0] = '2';
1201 DecValues->degrees[1] = '0';
1202 break;
1203 case 'E':
1204 DecValues->degrees[0] = '2';
1205 DecValues->degrees[1] = '1';
1206 break;
1207 case 'F':
1208 DecValues->degrees[0] = '2';
1209 DecValues->degrees[1] = '2';
1210 break;
1211 case 'G':
1212 DecValues->degrees[0] = '2';
1213 DecValues->degrees[1] = '3';
1214 break;
1215 case 'H':
1216 DecValues->degrees[0] = '2';
1217 DecValues->degrees[1] = '4';
1218 break;
1219 case 'I':
1220 DecValues->degrees[0] = '2';
1221 DecValues->degrees[1] = '5';
1222 break;
1223 default:
1224 DecValues->degrees[0] = '0';
1225 break;
1226 }
1227 DecValues->degrees[3] = DecValues->minutes[2] = DecValues->seconds[2] = '\0';
1228
1229 _DECm = sgn * (
1230 atol(DecValues->degrees) * 3600 +
1231 atol(DecValues->minutes) * 60 +
1232 atol(DecValues->seconds) );
1233
1234 // Deduce pointing state from mechanical DEC
1235 if ((-256 * 3600 < _DECm && _DECm <= -180 * 3600) || (0 <= _DECm && _DECm <= +180 * 3600))
1236 _pointingState = POINTING_NORMAL;
1237 else
1238 _pointingState = POINTING_BEYOND_POLE;
1239
1240 return false;
1241}
1242
1244{
1245 // RA is circular, DEC is not
1246 // We have hours and not degrees because that's what the mount is handling in terms of precision
1247 // We need to be cautious, as if we were to use real degrees, the RA movement would need to be 15 times more precise
1248 long delta = b._RAm - _RAm;
1249 if (delta > +12 * 3600) delta -= 24 * 3600;
1250 if (delta < -12 * 3600) delta += 24 * 3600;
1251 return static_cast <double> (delta * 15) / 3600.0;
1252}
1253
1255{
1256 // RA is circular, DEC is not
1257 return static_cast <double> (b._DECm - _DECm) / 3600.0;
1258}
1259
1261{
1262 double const ra_distance = RA_degrees_to(b);
1263 double const dec_distance = DEC_degrees_to(b);
1264 // FIXME: Incorrect distance calculation, but enough for our purpose
1265 return std::sqrt(ra_distance * ra_distance + dec_distance * dec_distance);
1266}
1267
1269{
1270 return (_pointingState != b._pointingState) || (RA_GRANULARITY <= std::abs(RA_degrees_to(b)))
1271 || (DEC_GRANULARITY <= std::abs(DEC_degrees_to(b)));
1272}
1273
1275{
1276 return !this->operator !=(b);
1277}
1278
1281{
1282 return _pointingState = pointingState;
1283}
1284
double DECsky() const
Definition eq500x.cpp:962
enum PointingState setPointingState(enum PointingState)
Definition eq500x.cpp:1279
bool operator==(MechanicalPoint const &) const
Definition eq500x.cpp:1274
char const * toStringDEC_Sim(char *, size_t) const
Definition eq500x.cpp:1035
double DEC_degrees_to(MechanicalPoint const &) const
Definition eq500x.cpp:1254
bool operator!=(MechanicalPoint const &) const
Definition eq500x.cpp:1268
bool atParkingPosition() const
Definition eq500x.cpp:914
double DECm() const
Definition eq500x.cpp:925
enum PointingState getPointingState() const
Definition eq500x.cpp:1285
bool parseStringRA(char const *, size_t)
Definition eq500x.cpp:1015
double RA_degrees_to(MechanicalPoint const &) const
Definition eq500x.cpp:1243
double RAm() const
Definition eq500x.cpp:920
char const * toStringDEC(char *, size_t) const
Definition eq500x.cpp:1114
double RAsky() const
Definition eq500x.cpp:950
double operator-(MechanicalPoint const &) const
Definition eq500x.cpp:1260
bool parseStringDEC(char const *, size_t)
Definition eq500x.cpp:1130
char const * toStringRA(char *, size_t) const
Definition eq500x.cpp:1001
virtual bool updateLocation(double, double, double) override
Update telescope location settings.
Definition eq500x.cpp:227
bool setTargetMechanicalPosition(MechanicalPoint const &)
Definition eq500x.cpp:874
virtual bool MoveNS(INDI_DIR_NS, TelescopeMotionCommand) override
Start or Stop the telescope motion in the direction dir.
Definition eq500x.cpp:755
virtual bool checkConnection() override
Definition eq500x.cpp:165
bool gotoTargetPosition(MechanicalPoint const &)
Definition eq500x.cpp:816
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
Definition eq500x.cpp:605
const char * getDefautName()
Definition eq500x.cpp:127
int getReply(char *, size_t const)
Definition eq500x.cpp:803
EQ500X()
Definition eq500x.cpp:102
virtual bool ReadScopeStatus() override
Read telescope status.
Definition eq500x.cpp:249
int sendCmd(char const *)
Definition eq500x.cpp:792
virtual bool Sync(double, double) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
Definition eq500x.cpp:696
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition eq500x.cpp:147
bool getCurrentMechanicalPosition(MechanicalPoint &)
Definition eq500x.cpp:840
void resetSimulation()
Definition eq500x.cpp:137
virtual void getBasicData() override
Definition eq500x.cpp:160
virtual void setPierSide(TelescopePierSide)
Definition eq500x.cpp:747
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
Definition eq500x.cpp:740
virtual double getLST()
Definition eq500x.cpp:132
bool isConnected() const
bool operator!=(std::nullptr_t) const
Definition basedevice.h:297
void setCurrentPollingPeriod(uint32_t msec)
setCurrentPollingPeriod Change the current polling period to call TimerHit() function in the driver.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
bool isSimulation() const
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
TelescopeStatus TrackState
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
TelescopePierSide getPierSide()
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual void slewError(int slewCode)
bool updateSlewRate(int index)
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
void setLX200Capability(uint32_t cap)
struct _adjustment adjustments[]
struct _simEQ500X simEQ500X_t
#define MechanicalPoint_DEC_FormatW
Definition eq500x.cpp:61
#define MechanicalPoint_RA_Format
Definition eq500x.cpp:62
#define MechanicalPoint_DEC_FormatR
Definition eq500x.cpp:60
simEQ500X_t simEQ500X_zero
Definition eq500x.cpp:50
simEQ500X_t simEQ500X
Definition eq500x.cpp:58
INDI::PropertySwitch AbortSP
INDI::PropertySwitch MovementNSSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch PierSideSP
INDI::PropertySwitch MovementWESP
INDI::PropertyNumber LocationNP
double ra
double dec
@ ISS_ON
Definition indiapi.h:150
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ AXIS_DE
@ AXIS_RA
INDI_DIR_NS
@ DIRECTION_NORTH
void tty_set_debug(int debug)
tty_set_debug Enable or disable debug which prints verbose information.
Definition indicom.c:349
double rangeHA(double r)
rangeHA Limits the hour angle value to be between -12 —> 12
Definition indicom.c:1301
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#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
int MoveTo(int fd, int direction)
int HaltMovement(int fd, int direction)
int getCommandString(int fd, char *data, const char *cmd)
@ LX200_SOUTH
Definition lx200driver.h:44
@ LX200_NORTH
Definition lx200driver.h:41
int polling_interval
Definition eq500x.cpp:89
char const * slew_rate
Definition eq500x.cpp:85
double epsilon
Definition eq500x.cpp:87
double distance
Definition eq500x.cpp:88
int switch_index
Definition eq500x.cpp:86
clock_t last_sim
Definition eq500x.cpp:47
char MechanicalDECStr[16]
Definition eq500x.cpp:44
double MechanicalRA
Definition eq500x.cpp:45
double MechanicalDEC
Definition eq500x.cpp:46
char MechanicalRAStr[16]
Definition eq500x.cpp:43
double round(double value, int decimal_places)