Instrument Neutral Distributed Interface INDI  2.0.2
eq500x.cpp
Go to the documentation of this file.
1 #if 0
2 LX200 - based Omegon EQ500X Equatorial Mount
3 
4 Copyright (C) 2019 Eric Dejouhanet (eric.dejouhanet@gmail.com)
5 
6 This library is free software;
7 you can redistribute it and / or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation;
10 either
11 version 2.1 of the License, or (at your option) any later version.
12 
13 This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY;
15 without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18 
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library;
21 if not, write to the Free Software
22 Foundation, 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 
41 typedef struct _simEQ500X
42 {
43  char MechanicalRAStr[16];
44  char MechanicalDECStr[16];
45  double MechanicalRA;
46  double MechanicalDEC;
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
65 static int const EQ500X_TIMEOUT = 5;
66 
67 // One degree, one arcminute, one arcsecond
68 static double constexpr ONEDEGREE = 1.0;
69 static double constexpr ARCMINUTE = ONEDEGREE / 60.0;
70 static double constexpr ARCSECOND = ONEDEGREE / 3600.0;
71 
72 // This is the minimum detectable movement in RA/DEC
73 static double /*constexpr*/ RA_GRANULARITY = std::lround((15.0 * ARCSECOND) * 3600.0) / 3600.0;
74 static 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
78 static 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 }
91 const 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 
138 {
140 }
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 
227 bool 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 = LocationN[LOCATION_LONGITUDE].value;
237  LocationN[LOCATION_LONGITUDE].value = 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  LocationN[LOCATION_LONGITUDE].value = 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  {
305  EqNP.s = IPS_ALERT;
306  IDSetNumber(&EqNP, "Error reading RA/DEC.");
307  return false;
308  }
309 
310  bool const ra_changed = currentRA != currentMechPosition.RAsky();
311  bool const dec_changed = currentDEC != currentMechPosition.DECsky();
312 
313  if (dec_changed)
314  currentDEC = currentMechPosition.DECsky();
315 
316  if (ra_changed)
317  {
318  currentRA = currentMechPosition.RAsky();
319 
320  // Update the side of pier - rangeHA is NOT suitable here
321  double HA = rangeHA(getLST() - currentRA);
322  while (+12 <= HA) HA -= 24;
323  while (HA <= -12) HA += 24;
324  switch (currentMechPosition.getPointingState())
325  {
328  break;
331  break;
332  }
333  LOGF_DEBUG("Mount HA=%lfh pointing %s on %s side", HA,
334  currentMechPosition.getPointingState() == MechanicalPoint::POINTING_NORMAL ? "normal" : "beyond pole",
335  getPierSide() == PIER_EAST ? "east" : "west");
336  }
337 
338  // If we are using the goto feature, check state
339  if (TrackState == SCOPE_SLEWING && _gotoEngaged)
340  {
341  if (EqN[AXIS_RA].value == currentRA && EqN[AXIS_DE].value == currentDEC)
342  {
343  _gotoEngaged = false;
344 
345  // Preliminary goto is complete, continue
346  if (!Goto(targetMechPosition.RAsky(), targetMechPosition.DECsky()))
347  goto slew_failure;
348  }
349  }
350 
351  // If we are adjusting, adjust movement and timer time to achieve arcsecond goto precision
352  if (TrackState == SCOPE_SLEWING && !_gotoEngaged)
353  {
354  // Compute RA/DEC deltas - keep in mind RA is in hours on the mount, with a granularity of 15 degrees
355  double const ra_delta = currentMechPosition.RA_degrees_to(targetMechPosition);
356  double const dec_delta = currentMechPosition.DEC_degrees_to(targetMechPosition);
357  double const abs_ra_delta = std::abs(ra_delta);
358  double const abs_dec_delta = std::abs(dec_delta);
359 
360  // If mount is not at target, adjust
361  if (RA_GRANULARITY <= abs_ra_delta || DEC_GRANULARITY <= abs_dec_delta)
362  {
363  // This will hold required adjustments in RA and DEC axes
364  struct _adjustment const *ra_adjust = nullptr, *dec_adjust = nullptr;
365 
366  // Choose slew rate for RA based on distance to target
367  for(size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]) && nullptr == ra_adjust; i++)
368  if (abs_ra_delta <= adjustments[i].distance)
369  ra_adjust = &adjustments[i];
370  assert(nullptr != ra_adjust);
371  LOGF_DEBUG("RA %lf-%lf = %+lf° under %lf° would require adjustment at %s until less than %lf°",
372  targetMechPosition.RAm() * 15.0, currentMechPosition.RAm() * 15.0, ra_delta, ra_adjust->distance, ra_adjust->slew_rate,
373  std::max(ra_adjust->epsilon, 15.0 / 3600.0));
374 
375  // Choose slew rate for DEC based on distance to target
376  for(size_t i = 0; i < sizeof(adjustments) / sizeof(adjustments[0]) && nullptr == dec_adjust; i++)
377  if (abs_dec_delta <= adjustments[i].distance)
378  dec_adjust = &adjustments[i];
379  assert(nullptr != dec_adjust);
380  LOGF_DEBUG("DEC %lf-%lf = %+lf° under %lf° would require adjustment at %s until less than %lf°",
381  targetMechPosition.DECm(), currentMechPosition.DECm(), dec_delta, dec_adjust->distance, dec_adjust->slew_rate,
382  dec_adjust->epsilon);
383 
384  // This will hold the command string to send to the mount, with move commands
385  char CmdString[32] = {0};
386 
387  // Previous alignment marker to spot when to change slew rate
388  static struct _adjustment const * previous_adjustment = nullptr;
389 
390  // We adjust the axis which has the faster slew rate first, eventually both axis at the same time if they have same speed
391  // 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
392  adjustment = ra_adjust < dec_adjust ? dec_adjust : ra_adjust;
393 
394  // If RA was moving but now would be moving at the wrong rate, stop it
395  if (ra_adjust != adjustment)
396  {
397  if (RAmIncrease)
398  {
399  strcat(CmdString, ":Qe#");
400  RAmIncrease = false;
401  }
402  if (RAmDecrease)
403  {
404  strcat(CmdString, ":Qw#");
405  RAmDecrease = false;
406  }
407  }
408 
409  // If DEC was moving but now would be moving at the wrong rate, stop it
410  if (dec_adjust != adjustment)
411  {
412  if (DECmDecrease)
413  {
414  strcat(CmdString, ":Qn#");
415  DECmDecrease = false;
416  }
417  if (DECmIncrease)
418  {
419  strcat(CmdString, ":Qs#");
420  DECmIncrease = false;
421  }
422  }
423 
424  // Prepare for the new rate
425  if (previous_adjustment != adjustment)
426  {
427  // Add the new slew rate
428  strcat(CmdString, adjustment->slew_rate);
429 
430  // If adjustment goes expectedly down, reset countdown
431  if (adjustment < previous_adjustment)
432  countdown = MAX_CONVERGENCE_LOOPS;
433 
434  // FIXME: wait for the mount to slow down to improve convergence?
435 
436  // Remember previous adjustment
437  previous_adjustment = adjustment;
438  }
439  LOGF_DEBUG("Current adjustment speed is %s", adjustment->slew_rate);
440 
441  // If RA is being adjusted, check delta against adjustment epsilon to enable or disable movement
442  // The smallest change detectable in RA is 1/3600 hours, or 15/3600 degrees
443  if (ra_adjust == adjustment)
444  {
445  // This is the lowest limit of this adjustment
446  double const ra_epsilon = std::max(adjustment->epsilon, RA_GRANULARITY);
447 
448  // Find requirement
449  bool const doDecrease = ra_epsilon <= ra_delta;
450  bool const doIncrease = ra_delta <= -ra_epsilon;
451  assert(!(doDecrease && doIncrease));
452 
453  // Stop movement if required - just stopping or going opposite
454  if (RAmIncrease && (!doDecrease || doIncrease))
455  {
456  strcat(CmdString, ":Qe#");
457  RAmIncrease = false;
458  }
459  if (RAmDecrease && (!doIncrease || doDecrease))
460  {
461  strcat(CmdString, ":Qw#");
462  RAmDecrease = false;
463  }
464 
465  // Initiate movement if required
466  if (doDecrease && !RAmIncrease)
467  {
468  strcat(CmdString, ":Me#");
469  RAmIncrease = true;
470  }
471  if (doIncrease && !RAmDecrease)
472  {
473  strcat(CmdString, ":Mw#");
474  RAmDecrease = true;
475  }
476  }
477 
478  // If DEC is being adjusted, check delta against adjustment epsilon to enable or disable movement
479  // The smallest change detectable in DEC is 1/3600 degrees
480  if (dec_adjust == adjustment)
481  {
482  // This is the lowest limit of this adjustment
483  double const dec_epsilon = std::max(adjustment->epsilon, DEC_GRANULARITY);
484 
485  // Find requirement
486  bool const doDecrease = dec_epsilon <= dec_delta;
487  bool const doIncrease = dec_delta <= -dec_epsilon;
488  assert(!(doDecrease && doIncrease));
489 
490  // Stop movement if required - just stopping or going opposite
491  if (DECmIncrease && (!doDecrease || doIncrease))
492  {
493  strcat(CmdString, ":Qn#");
494  DECmIncrease = false;
495  }
496  if (DECmDecrease && (!doIncrease || doDecrease))
497  {
498  strcat(CmdString, ":Qs#");
499  DECmDecrease = false;
500  }
501 
502  // Initiate movement if required
503  if (doDecrease && !DECmIncrease)
504  {
505  strcat(CmdString, ":Mn#");
506  DECmIncrease = true;
507  }
508  if (doIncrease && !DECmDecrease)
509  {
510  strcat(CmdString, ":Ms#");
511  DECmDecrease = true;
512  }
513  }
514 
515  // Basic algorithm sanitization on movement orientation: move one way or the other, or not at all
516  assert(!(RAmIncrease && RAmDecrease) && !(DECmDecrease && DECmIncrease));
517 
518  // This log shows target in Degrees/Degrees and delta in Degrees/Degrees
519  LOGF_DEBUG("Centering (%lf°,%lf°) delta (%lf°,%lf°) moving %c%c%c%c at %s until less than (%lf°,%lf°)",
520  targetMechPosition.RAm() * 15.0, targetMechPosition.DECm(), ra_delta, dec_delta, RAmDecrease ? 'W' : '.',
521  RAmIncrease ? 'E' : '.', DECmDecrease ? 'N' : '.', DECmIncrease ? 'S' : '.', adjustment->slew_rate,
522  std::max(adjustment->epsilon, RA_GRANULARITY), adjustment->epsilon);
523 
524  // If we have a command to run, issue it
525  if (CmdString[0] != '\0')
526  {
527  // Send command to mount
528  if (sendCmd(CmdString))
529  {
530  LOGF_ERROR("Error centering (%lf°,%lf°)", targetMechPosition.RAm() * 15.0, targetMechPosition.DECm());
531  slewError(-1);
532  return false;
533  }
534 
535  // Update slew rate
537  SlewRateS[adjustment->switch_index].s = ISS_ON;
538  IDSetSwitch(&SlewRateSP, nullptr);
539  }
540 
541  // If all movement flags are cleared, we are done adjusting
542  if (!RAmIncrease && !RAmDecrease && !DECmDecrease && !DECmIncrease)
543  {
544  LOGF_INFO("Centering delta (%lf,%lf) intermediate adjustment complete (%d loops)", ra_delta, dec_delta,
545  MAX_CONVERGENCE_LOOPS - countdown);
546  adjustment = nullptr;
547  }
548  // Else, if it has been too long since we started, maybe we have a convergence problem.
549  // The mount slows down when requested to stop under minimum distance, so we may miss the target.
550  // The behavior is improved by changing the slew rate while converging, but is still tricky to tune.
551  else if (--countdown <= 0)
552  {
553  LOGF_ERROR("Failed centering to (%lf,%lf) under loop limit, aborting...", targetMechPosition.RAm(),
554  targetMechPosition.DECm());
555  goto slew_failure;
556  }
557  // Else adjust poll timeout to adjustment speed and continue
558  else setCurrentPollingPeriod(static_cast <uint32_t> (adjustment->polling_interval));
559  }
560  // If we attained target position at one arcsecond precision, finish procedure and track target
561  else
562  {
563  LOG_INFO("Slew is complete. Tracking...");
564  sendCmd(":Q#");
565  updateSlewRate(savedSlewRateIndex);
566  adjustment = nullptr;
569  EqNP.s = IPS_OK;
570  IDSetNumber(&EqNP, "Mount is tracking");
571  }
572  }
573  else
574  {
575  // Force-reset markers in case we got aborted
576  if (DECmDecrease) DECmDecrease = false;
577  if (DECmIncrease) DECmIncrease = false;
578  if (RAmIncrease) RAmIncrease = false;
579  if (RAmDecrease) RAmDecrease = false;
580  adjustment = nullptr;
581  }
582 
583  // Update RA/DEC properties
584  if (ra_changed || dec_changed)
586 
587  return true;
588 
589 slew_failure:
590  // If we failed at some point, attempt to stop moving and update properties with error
591  sendCmd(":Q#");
592  updateSlewRate(savedSlewRateIndex);
593  adjustment = nullptr;
596  currentRA = currentMechPosition.RAsky();
597  currentDEC = currentMechPosition.DECsky();
599  slewError(-1);
600  return false;
601 }
602 
603 bool EQ500X::Goto(double ra, double dec)
604 {
605  // Check whether a meridian flip is required - rangeHA is NOT suitable here
606  double HA = getLST() - ra;
607  while (+12 <= HA) HA -= 24;
608  while (HA <= -12) HA += 24;
609 
610  // Deduce required orientation of mount in HA quadrants - set orientation BEFORE coordinates!
611  targetMechPosition.setPointingState((0 <= HA
613  targetMechPosition.RAsky(ra);
614  targetMechPosition.DECsky(dec);
615 
616  // If moving, let's stop it first.
617  if (EqNP.s == IPS_BUSY)
618  {
619  if (!Abort())
620  {
621  AbortSP.s = IPS_ALERT;
622  IDSetSwitch(&AbortSP, "Abort slew failed.");
623  return false;
624  }
625 
626  AbortSP.s = IPS_OK;
627  EqNP.s = IPS_IDLE;
628  IDSetSwitch(&AbortSP, "Slew aborted.");
629  IDSetNumber(&EqNP, nullptr);
630 
632  {
635  EqNP.s = IPS_IDLE;
638  IDSetSwitch(&MovementNSSP, nullptr);
639  IDSetSwitch(&MovementWESP, nullptr);
640  }
641 
642  // sleep for 100 mseconds
643  const struct timespec timeout = {0, 100000000L};
644  nanosleep(&timeout, nullptr);
645  }
646 
647  /* The goto feature is quite imprecise because it will always use full speed.
648  * By the time the mount stops, the position is off by 0-5 degrees, depending on the speed attained during the move.
649  * Additionally, a firmware limitation prevents the goto feature from slewing to close coordinates, and will cause uneeded axis rotation.
650  * Therefore, don't use the goto feature for a goto, and let ReadScope adjust the position by itself.
651  */
652 
653  // Set target position and adjust
654  if (setTargetMechanicalPosition(targetMechPosition))
655  {
656  EqNP.s = IPS_ALERT;
657  IDSetNumber(&EqNP, "Error setting RA/DEC.");
658  return false;
659  }
660  else
661  {
662  targetMechPosition.RAsky(targetRA = ra);
663  targetMechPosition.DECsky(targetDEC = dec);
664 
665  LOGF_INFO("Goto target (%lfh,%lf°) HA %lf, quadrant %s", ra, dec, HA,
666  targetMechPosition.getPointingState() == MechanicalPoint::POINTING_NORMAL ? "normal" : "beyond pole");
667  }
668 
669  // Limit the number of loops
670  countdown = MAX_CONVERGENCE_LOOPS;
671 
672  // Reset original adjustment
673  // Reset movement markers
674 
676  //EqNP.s = IPS_BUSY;
677 
678  // Remember current slew rate
679  savedSlewRateIndex = static_cast <enum TelescopeSlewRate> (IUFindOnSwitchIndex(&SlewRateSP));
680 
681  // Format RA/DEC for logs
682  char RAStr[16] = {0}, DecStr[16] = {0};
683  fs_sexa(RAStr, targetRA, 2, 3600);
684  fs_sexa(DecStr, targetDEC, 2, 3600);
685 
686  LOGF_INFO("Slewing to JNow RA: %s - DEC: %s", RAStr, DecStr);
687 
688  return true;
689 }
690 
691 bool EQ500X::Sync(double ra, double dec)
692 {
693  targetMechPosition.RAsky(targetRA = ra);
694  targetMechPosition.DECsky(targetDEC = dec);
695 
696  if(!setTargetMechanicalPosition(targetMechPosition))
697  {
698  if (!isSimulation())
699  {
700  char b[64/*RB_MAX_LEN*/] = {0};
701  tcflush(PortFD, TCIFLUSH);
702 
703  if (getCommandString(PortFD, b, ":CM#") < 0)
704  goto sync_error;
705  if (!strncmp("No name", b, sizeof(b)))
706  goto sync_error;
707  }
708  else
709  {
710  targetMechPosition.toStringRA(simEQ500X.MechanicalRAStr, sizeof(simEQ500X.MechanicalRAStr));
712  simEQ500X.MechanicalRA = targetMechPosition.RAm();
713  simEQ500X.MechanicalDEC = targetMechPosition.DECm();
714  }
715 
716  if (getCurrentMechanicalPosition(currentMechPosition))
717  goto sync_error;
718 
719  currentRA = currentMechPosition.RAsky();
720  currentDEC = currentMechPosition.DECsky();
722 
723  LOGF_INFO("Mount synced to target RA '%lf' DEC '%lf'", currentRA, currentDEC);
724  return true;
725  }
726 
727 sync_error:
728  EqNP.s = IPS_ALERT;
729  IDSetNumber(&EqNP, "Synchronization failed.");
730  LOGF_ERROR("Mount sync to target RA '%lf' DEC '%lf' failed", ra, dec);
731  return false;
732 }
733 
735 {
738  return LX200Telescope::Abort() && updateSlewRate(savedSlewRateIndex);
739 }
740 
742 {
743  INDI_UNUSED(side);
745  IDSetSwitch(&PierSideSP, "Not supported");
746 }
747 
749 {
750  // EQ500X has North/South directions inverted
751  int current_move = (dir == DIRECTION_NORTH) ? LX200_SOUTH : LX200_NORTH;
752 
753  switch (command)
754  {
755  case MOTION_START:
756  if (!isSimulation() && MoveTo(PortFD, current_move) < 0)
757  {
758  LOG_ERROR("Error setting N/S motion direction.");
759  return false;
760  }
761  else
762  LOGF_DEBUG("Moving toward %s.",
763  (current_move == LX200_NORTH) ? "North" : "South");
764  break;
765 
766  case MOTION_STOP:
767  if (!isSimulation() && HaltMovement(PortFD, current_move) < 0)
768  {
769  LOG_ERROR("Error stopping N/S motion.");
770  return false;
771  }
772  else
773  LOGF_DEBUG("Movement toward %s halted.",
774  (current_move == LX200_NORTH) ? "North" : "South");
775  break;
776  }
777 
778  return true;
779 }
780 
781 /**************************************************************************************
782 **
783 ***************************************************************************************/
784 
785 int EQ500X::sendCmd(char const *data)
786 {
787  LOGF_DEBUG("CMD <%s>", data);
788  if (!isSimulation())
789  {
790  int nbytes_write = 0;
791  return tty_write_string(PortFD, data, &nbytes_write);
792  }
793  return 0;
794 }
795 
796 int EQ500X::getReply(char *data, size_t const len)
797 {
798  if (!isSimulation())
799  {
800  int nbytes_read = 0;
801  int error_type = tty_read(PortFD, data, len, EQ500X_TIMEOUT, &nbytes_read);
802 
803  LOGF_DEBUG("RES <%.*s> (%d)", nbytes_read, data, error_type);
804  return error_type;
805  }
806  return 0;
807 }
808 
810 {
811  if (!isSimulation())
812  {
814  {
815  if (!sendCmd(":MS#"))
816  {
817  char buf[64/*RB_MAX_LEN*/] = {0};
818  if (!getReply(buf, 1))
819  return buf[0] != '0'; // 0 is valid for :MS
820  }
821  else return true;
822  }
823  else return true;
824  }
825  else
826  {
827  return !Sync(p.RAsky(), p.DECsky());
828  }
829 
830  return false;
831 }
832 
834 {
835  char b[64/*RB_MAX_LEN*/] = {0};
836  MechanicalPoint result = p;
837 
838  // Always read DEC first as it gives the side of pier the scope is on, and has an impact on RA
839 
840  if (isSimulation())
841  memcpy(b, simEQ500X.MechanicalDECStr, std::min(sizeof(b), sizeof(simEQ500X.MechanicalDECStr)));
842  else if (getCommandString(PortFD, b, ":GD#") < 0)
843  goto radec_error;
844 
845  if (result.parseStringDEC(b, 64))
846  goto radec_error;
847 
848  LOGF_DEBUG("Mount mechanical DEC reads '%s' as %lf.", b, result.DECm());
849 
850  if (isSimulation())
851  memcpy(b, simEQ500X.MechanicalRAStr, std::min(sizeof(b), sizeof(simEQ500X.MechanicalRAStr)));
852  else if (getCommandString(PortFD, b, ":GR#") < 0)
853  goto radec_error;
854 
855  if (result.parseStringRA(b, 64))
856  goto radec_error;
857 
858  LOGF_DEBUG("Mount mechanical RA reads '%s' as %lf.", b, result.RAm());
859 
860  p = result;
861  return false;
862 
863 radec_error:
864  return true;
865 }
866 
868 {
869  if (!isSimulation())
870  {
871  // Size string buffers appropriately
872  char CmdString[] = ":Sr" MechanicalPoint_RA_Format "#:Sd" MechanicalPoint_DEC_FormatW "#";
873  char bufRA[sizeof(MechanicalPoint_RA_Format)], bufDEC[sizeof(MechanicalPoint_DEC_FormatW)];
874 
875  // Write RA/DEC in placeholders
876  snprintf(CmdString, sizeof(CmdString), ":Sr%s#:Sd%s#", p.toStringRA(bufRA, sizeof(bufRA)), p.toStringDEC(bufDEC,
877  sizeof(bufDEC)));
878  LOGF_DEBUG("Target RA '%f' DEC '%f' converted to '%s'", static_cast <float> (p.RAm()), static_cast <float> (p.DECm()),
879  CmdString);
880 
881  char buf[64/*RB_MAX_LEN*/] = {0};
882 
883  if (!sendCmd(CmdString))
884  if (!getReply(buf, 2))
885  if (buf[0] == '1' && buf[1] == '1')
886  return false;
887  else LOGF_ERROR("Failed '%s', mount replied %c%c", CmdString, buf[0], buf[1]);
888  else LOGF_ERROR("Failed getting 2-byte reply to '%s'", CmdString);
889  else LOGF_ERROR("Failed '%s'", CmdString);
890 
891  return true;
892  }
893 
894  return false;
895 }
896 
897 /**************************************************************************************
898 **
899 ***************************************************************************************/
900 
902 {
903  RAm(ra);
904  DECm(dec);
905 }
906 
908 {
909  // Consider mount 0/0 is pole - no way to check if synced already
910  return _RAm == 0 && _DECm == 0;
911 }
912 
914 {
915  return static_cast <double> (_RAm) / 3600.0;
916 }
917 
919 {
920  return static_cast <double> (_DECm) / 3600.0;
921 }
922 
923 double EQ500X::MechanicalPoint::RAm(double const value)
924 {
925  _RAm = std::lround(std::fmod(value + 24.0, 24.0) * 3600.0);
926  return RAm();
927 }
928 
929 double EQ500X::MechanicalPoint::DECm(double const value)
930 {
931  // Should be inside [-180,+180] but mount supports a larger (not useful) interval
932  _DECm = std::lround(std::fmod(value, 256.0) * 3600.0);
933 
934  // Deduce pier side from mechanical DEC
935  if ((-256 * 3600 < _DECm && _DECm < -180 * 3600) || (0 <= _DECm && _DECm <= +180 * 3600))
936  _pointingState = POINTING_NORMAL;
937  else
938  _pointingState = POINTING_BEYOND_POLE;
939 
940  return DECm();
941 }
942 
944 {
945  switch (_pointingState)
946  {
947  case POINTING_BEYOND_POLE:
948  return static_cast <double> ((12 * 3600 + _RAm) % (24 * 3600)) / 3600.0;
949  case POINTING_NORMAL:
950  default:
951  return static_cast <double> ((24 * 3600 + _RAm) % (24 * 3600)) / 3600.0;
952  }
953 }
954 
956 {
957  // Convert to sky DEC inside [-90,90], allowing +/-90 values
958  long _DEC = 90 * 3600 - _DECm;
959  if (POINTING_BEYOND_POLE == _pointingState)
960  _DEC = 180 * 3600 - _DEC;
961  while (+90 * 3600 < _DEC) _DEC -= 180 * 3600;
962  while (_DEC < -90 * 3600) _DEC += 180 * 3600;
963  return static_cast <double> (_DEC) / 3600.0;
964 }
965 
966 double EQ500X::MechanicalPoint::RAsky(const double _RAsky)
967 {
968  switch (_pointingState)
969  {
970  case POINTING_BEYOND_POLE:
971  _RAm = static_cast <long> (std::fmod(12.0 + _RAsky, 24.0) * 3600.0);
972  break;
973  case POINTING_NORMAL:
974  _RAm = static_cast <long> (std::fmod(24.0 + _RAsky, 24.0) * 3600.0);
975  break;
976  }
977  return RAsky();
978 }
979 
980 double EQ500X::MechanicalPoint::DECsky(const double _DECsky)
981 {
982  switch (_pointingState)
983  {
984  case POINTING_BEYOND_POLE:
985  _DECm = 90 * 3600 - std::lround((180.0 - _DECsky) * 3600.0);
986  break;
987  case POINTING_NORMAL:
988  _DECm = 90 * 3600 - std::lround(_DECsky * 3600.0);
989  break;
990  }
991  return DECsky();
992 }
993 
994 char const * EQ500X::MechanicalPoint::toStringRA(char *buf, size_t buf_length) const
995 {
996  // See /test/test_eq500xdriver.cpp for description of RA conversion
997 
998  //int const hours = ((_pierSide == PIER_WEST ? 12 : 0) + 24 + static_cast <int> (_RAm/3600)) % 24;
999  int const hours = (24 + static_cast <int> (_RAm / 3600)) % 24;
1000  int const minutes = static_cast <int> (_RAm / 60) % 60;
1001  int const seconds = static_cast <int> (_RAm) % 60;
1002 
1003  int const written = snprintf(buf, buf_length, "%02d:%02d:%02d", hours, minutes, seconds);
1004 
1005  return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1006 }
1007 
1008 bool EQ500X::MechanicalPoint::parseStringRA(char const *buf, size_t buf_length)
1009 {
1010  if (buf_length < sizeof(MechanicalPoint_RA_Format) - 1)
1011  return true;
1012 
1013  // Mount replies to "#GR:" with "HH:MM:SS".
1014  // HH, MM and SS are respectively hours, minutes and seconds in [00:00:00,23:59:59].
1015  // FIXME: Sanitize.
1016 
1017  int hours = 0, minutes = 0, seconds = 0;
1018  if (3 == sscanf(buf, "%02d:%02d:%02d", &hours, &minutes, &seconds))
1019  {
1020  //_RAm = ( (_pierSide == PIER_WEST ? -12*3600 : +0) + 24*3600 +
1021  _RAm = (24 * 3600 + (hours % 24) * 3600 + minutes * 60 + seconds) % (24 * 3600);
1022  return false;
1023  }
1024 
1025  return true;
1026 }
1027 
1028 char const * EQ500X::MechanicalPoint::toStringDEC_Sim(char *buf, size_t buf_length) const
1029 {
1030  // See /test/test_eq500xdriver.cpp for description of DEC conversion
1031 
1032  int const degrees = static_cast <int> (_DECm / 3600) % 256;
1033  int const minutes = static_cast <int> (std::abs(_DECm) / 60) % 60;
1034  int const seconds = static_cast <int> (std::abs(_DECm)) % 60;
1035 
1036  if (degrees < -255 || +255 < degrees)
1037  return (char const*)0;
1038 
1039  char high_digit = '0';
1040  if (-100 < degrees && degrees < 100)
1041  {
1042  high_digit = '0' + (std::abs(degrees) / 10);
1043  }
1044  else switch (std::abs(degrees) / 10)
1045  {
1046  case 10:
1047  high_digit = ':';
1048  break;
1049  case 11:
1050  high_digit = ';';
1051  break;
1052  case 12:
1053  high_digit = '<';
1054  break;
1055  case 13:
1056  high_digit = '=';
1057  break;
1058  case 14:
1059  high_digit = '>';
1060  break;
1061  case 15:
1062  high_digit = '?';
1063  break;
1064  case 16:
1065  high_digit = '@';
1066  break;
1067  case 17:
1068  high_digit = 'A';
1069  break;
1070  case 18:
1071  high_digit = 'B';
1072  break;
1073  case 19:
1074  high_digit = 'C';
1075  break;
1076  case 20:
1077  high_digit = 'D';
1078  break;
1079  case 21:
1080  high_digit = 'E';
1081  break;
1082  case 22:
1083  high_digit = 'F';
1084  break;
1085  case 23:
1086  high_digit = 'G';
1087  break;
1088  case 24:
1089  high_digit = 'H';
1090  break;
1091  case 25:
1092  high_digit = 'I';
1093  break;
1094  default:
1095  return (char const*)0;
1096  break;
1097  }
1098 
1099  char const low_digit = '0' + (std::abs(degrees) % 10);
1100 
1101  int const written = snprintf(buf, buf_length, "%c%c%c:%02d:%02d", (0 <= degrees) ? '+' : '-', high_digit, low_digit,
1102  minutes, seconds);
1103 
1104  return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1105 }
1106 
1107 char const * EQ500X::MechanicalPoint::toStringDEC(char *buf, size_t buf_length) const
1108 {
1109  // See /test/test_eq500xdriver.cpp for description of DEC conversion
1110 
1111  int const degrees = static_cast <int> (_DECm / 3600) % 256;
1112  int const minutes = static_cast <int> (std::abs(_DECm) / 60) % 60;
1113  int const seconds = static_cast <int> (std::abs(_DECm)) % 60;
1114 
1115  if (degrees < -255 || +255 < degrees)
1116  return (char const*)0;
1117 
1118  int const written = snprintf(buf, buf_length, "%+03d:%02d:%02d", degrees, minutes, seconds);
1119 
1120  return (0 < written && written < (int) buf_length) ? buf : (char const*)0;
1121 }
1122 
1123 bool EQ500X::MechanicalPoint::parseStringDEC(char const *buf, size_t buf_length)
1124 {
1125  if (buf_length < sizeof(MechanicalPoint_DEC_FormatR) - 1)
1126  return true;
1127 
1128  char b[sizeof(MechanicalPoint_DEC_FormatR)] = {0};
1129  strncpy(b, buf, sizeof(b));
1130 
1131  // Mount replies to "#GD:" with "sDD:MM:SS".
1132  // s is in {+,-} and provides a sign.
1133  // DD are degrees, unit D spans '0' to '9' in [0,9] but high D spans '0' to 'I' in [0,25].
1134  // MM are minutes, SS are seconds in [00:00,59:59].
1135  // The whole reply is in [-255:59:59,+255:59:59].
1136 
1137  struct _DecValues
1138  {
1139  char degrees[4];
1140  char minutes[3];
1141  char seconds[3];
1142  } * const DecValues = (struct _DecValues*) b;
1143 
1144  if (DecValues->degrees[1] < '0' || 'I' < DecValues->degrees[1])
1145  return true;
1146 
1147  int const sgn = DecValues->degrees[0] == '-' ? -1 : +1;
1148 
1149  // Replace sign with hundredth, or space if lesser than 100
1150  switch (DecValues->degrees[1])
1151  {
1152  case ':':
1153  DecValues->degrees[0] = '1';
1154  DecValues->degrees[1] = '0';
1155  break;
1156  case ';':
1157  DecValues->degrees[0] = '1';
1158  DecValues->degrees[1] = '1';
1159  break;
1160  case '<':
1161  DecValues->degrees[0] = '1';
1162  DecValues->degrees[1] = '2';
1163  break;
1164  case '=':
1165  DecValues->degrees[0] = '1';
1166  DecValues->degrees[1] = '3';
1167  break;
1168  case '>':
1169  DecValues->degrees[0] = '1';
1170  DecValues->degrees[1] = '4';
1171  break;
1172  case '?':
1173  DecValues->degrees[0] = '1';
1174  DecValues->degrees[1] = '5';
1175  break;
1176  case '@':
1177  DecValues->degrees[0] = '1';
1178  DecValues->degrees[1] = '6';
1179  break;
1180  case 'A':
1181  DecValues->degrees[0] = '1';
1182  DecValues->degrees[1] = '7';
1183  break;
1184  case 'B':
1185  DecValues->degrees[0] = '1';
1186  DecValues->degrees[1] = '8';
1187  break;
1188  case 'C':
1189  DecValues->degrees[0] = '1';
1190  DecValues->degrees[1] = '9';
1191  break;
1192  case 'D':
1193  DecValues->degrees[0] = '2';
1194  DecValues->degrees[1] = '0';
1195  break;
1196  case 'E':
1197  DecValues->degrees[0] = '2';
1198  DecValues->degrees[1] = '1';
1199  break;
1200  case 'F':
1201  DecValues->degrees[0] = '2';
1202  DecValues->degrees[1] = '2';
1203  break;
1204  case 'G':
1205  DecValues->degrees[0] = '2';
1206  DecValues->degrees[1] = '3';
1207  break;
1208  case 'H':
1209  DecValues->degrees[0] = '2';
1210  DecValues->degrees[1] = '4';
1211  break;
1212  case 'I':
1213  DecValues->degrees[0] = '2';
1214  DecValues->degrees[1] = '5';
1215  break;
1216  default:
1217  DecValues->degrees[0] = '0';
1218  break;
1219  }
1220  DecValues->degrees[3] = DecValues->minutes[2] = DecValues->seconds[2] = '\0';
1221 
1222  _DECm = sgn * (
1223  atol(DecValues->degrees) * 3600 +
1224  atol(DecValues->minutes) * 60 +
1225  atol(DecValues->seconds) );
1226 
1227  // Deduce pointing state from mechanical DEC
1228  if ((-256 * 3600 < _DECm && _DECm <= -180 * 3600) || (0 <= _DECm && _DECm <= +180 * 3600))
1229  _pointingState = POINTING_NORMAL;
1230  else
1231  _pointingState = POINTING_BEYOND_POLE;
1232 
1233  return false;
1234 }
1235 
1237 {
1238  // RA is circular, DEC is not
1239  // We have hours and not degrees because that's what the mount is handling in terms of precision
1240  // We need to be cautious, as if we were to use real degrees, the RA movement would need to be 15 times more precise
1241  long delta = b._RAm - _RAm;
1242  if (delta > +12 * 3600) delta -= 24 * 3600;
1243  if (delta < -12 * 3600) delta += 24 * 3600;
1244  return static_cast <double> (delta * 15) / 3600.0;
1245 }
1246 
1248 {
1249  // RA is circular, DEC is not
1250  return static_cast <double> (b._DECm - _DECm) / 3600.0;
1251 }
1252 
1254 {
1255  double const ra_distance = RA_degrees_to(b);
1256  double const dec_distance = DEC_degrees_to(b);
1257  // FIXME: Incorrect distance calculation, but enough for our purpose
1258  return std::sqrt(ra_distance * ra_distance + dec_distance * dec_distance);
1259 }
1260 
1262 {
1263  return (_pointingState != b._pointingState) || (RA_GRANULARITY <= std::abs(RA_degrees_to(b)))
1264  || (DEC_GRANULARITY <= std::abs(DEC_degrees_to(b)));
1265 }
1266 
1268 {
1269  return !this->operator !=(b);
1270 }
1271 
1274 {
1275  return _pointingState = pointingState;
1276 }
1277 
1279 {
1280  return _pointingState;
1281 }
double DECsky() const
Definition: eq500x.cpp:955
enum PointingState setPointingState(enum PointingState)
Definition: eq500x.cpp:1272
bool operator==(MechanicalPoint const &) const
Definition: eq500x.cpp:1267
char const * toStringDEC_Sim(char *, size_t) const
Definition: eq500x.cpp:1028
double DEC_degrees_to(MechanicalPoint const &) const
Definition: eq500x.cpp:1247
bool operator!=(MechanicalPoint const &) const
Definition: eq500x.cpp:1261
bool atParkingPosition() const
Definition: eq500x.cpp:907
double DECm() const
Definition: eq500x.cpp:918
enum PointingState getPointingState() const
Definition: eq500x.cpp:1278
bool parseStringRA(char const *, size_t)
Definition: eq500x.cpp:1008
double RA_degrees_to(MechanicalPoint const &) const
Definition: eq500x.cpp:1236
double RAm() const
Definition: eq500x.cpp:913
char const * toStringDEC(char *, size_t) const
Definition: eq500x.cpp:1107
double RAsky() const
Definition: eq500x.cpp:943
double operator-(MechanicalPoint const &) const
Definition: eq500x.cpp:1253
bool parseStringDEC(char const *, size_t)
Definition: eq500x.cpp:1123
char const * toStringRA(char *, size_t) const
Definition: eq500x.cpp:994
Definition: eq500x.h:29
virtual bool updateLocation(double, double, double) override
Update telescope location settings.
Definition: eq500x.cpp:227
bool setTargetMechanicalPosition(MechanicalPoint const &)
Definition: eq500x.cpp:867
virtual bool MoveNS(INDI_DIR_NS, TelescopeMotionCommand) override
Start or Stop the telescope motion in the direction dir.
Definition: eq500x.cpp:748
virtual bool checkConnection() override
Definition: eq500x.cpp:165
bool gotoTargetPosition(MechanicalPoint const &)
Definition: eq500x.cpp:809
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
Definition: eq500x.cpp:603
const char * getDefautName()
Definition: eq500x.cpp:127
int getReply(char *, size_t const)
Definition: eq500x.cpp:796
EQ500X()
Definition: eq500x.cpp:102
virtual bool ReadScopeStatus() override
Read telescope status.
Definition: eq500x.cpp:249
int sendCmd(char const *)
Definition: eq500x.cpp:785
virtual bool Sync(double, double) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
Definition: eq500x.cpp:691
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition: eq500x.cpp:147
bool getCurrentMechanicalPosition(MechanicalPoint &)
Definition: eq500x.cpp:833
void resetSimulation()
Definition: eq500x.cpp:137
virtual void getBasicData() override
Definition: eq500x.cpp:160
virtual void setPierSide(TelescopePierSide)
Definition: eq500x.cpp:741
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
Definition: eq500x.cpp:734
virtual double getLST()
Definition: eq500x.cpp:132
bool isConnected() const
Definition: basedevice.cpp:520
bool operator!=(std::nullptr_t) const
Definition: basedevice.h:286
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
TelescopeStatus TrackState
ISwitchVectorProperty MovementNSSP
ISwitchVectorProperty AbortSP
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
ISwitchVectorProperty SlewRateSP
ISwitchVectorProperty PierSideSP
INumberVectorProperty EqNP
TelescopePierSide getPierSide()
INumber EqN[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
ISwitchVectorProperty MovementWESP
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
double max(void)
double min(void)
double ra
double dec
@ ISS_ON
Definition: indiapi.h:152
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
void tty_set_debug(int debug)
tty_set_debug Enable or disable debug which prints verbose information.
Definition: indicom.c:360
double rangeHA(double r)
rangeHA Limits the hour angle value to be between -12 —> 12
Definition: indicom.c:1225
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:474
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:141
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
#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)