Instrument Neutral Distributed Interface INDI  2.0.2
skywatcherAPI.cpp
Go to the documentation of this file.
1 
24 #include "skywatcherAPI.h"
25 #include "indicom.h"
26 
27 #include <cmath>
28 #include <iomanip>
29 #include <memory>
30 #include <thread>
31 #include <termios.h>
32 
34 {
35  FullStop = true;
36  SlewingTo = Slewing = false;
37 }
38 
39 void AXISSTATUS::SetSlewing(bool forward, bool highspeed)
40 {
41  FullStop = SlewingTo = false;
42  Slewing = true;
43 
44  SlewingForward = forward;
45  HighSpeed = highspeed;
46 }
47 
48 void AXISSTATUS::SetSlewingTo(bool forward, bool highspeed)
49 {
50  FullStop = Slewing = false;
51  SlewingTo = true;
52 
53  SlewingForward = forward;
54  HighSpeed = highspeed;
55 }
56 
57 const std::map<int, std::string> SkywatcherAPI::errorCodes
58 {
59  {0, "Unknown command"},
60  {1, "Command length error"},
61  {2, "Motor not stopped"},
62  {3, "Invalid character"},
63  {4, "Not initialized"},
64  {5, "Driver sleeping"}
65 };
66 
68 {
69  switch(type)
70  {
71  case EQ6:
72  return "EQ6";
73  case HEQ5:
74  return "HEQ5";
75  case EQ5:
76  return "EQ5";
77  case EQ3:
78  return "EQ3";
79  case EQ8:
80  return "EQ8";
81  case AZEQ6:
82  return "AZ-EQ6";
83  case AZEQ5:
84  return "AZ-EQ5";
85  case STAR_ADVENTURER:
86  return "Star Adventurer";
87  case EQ8R_PRO:
88  return "EQ8R Pro";
89  case AZEQ6_PRO:
90  return "AZ-EQ6 Pro";
91  case EQ6_PRO:
92  return "EQ6 Pro";
93  case EQ5_PRO:
94  return "EQ5 Pro";
95  case GT:
96  return "GT";
97  case MF:
98  return "MF";
99  case _114GT:
100  return "114 GT";
101  case DOB:
102  return "Dob";
103  case AZGTE:
104  return "AZ-GTe";
105  case AZGTI:
106  return "AZ-GTi";
107  default:
108  return "Unknown";
109  }
110 }
111 
113 {
114  // I add an additional debug level so I can log verbose scope status
115  DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
116 
125 }
126 
127 unsigned long SkywatcherAPI::BCDstr2long(std::string &String)
128 {
129  if (String.size() != 6)
130  {
131  return 0;
132  }
133  unsigned long value = 0;
134 
135 #define HEX(c) (((c) < 'A') ? ((c) - '0') : ((c) - 'A') + 10)
136 
137  value = HEX(String[4]);
138  value <<= 4;
139  value |= HEX(String[5]);
140  value <<= 4;
141  value |= HEX(String[2]);
142  value <<= 4;
143  value |= HEX(String[3]);
144  value <<= 4;
145  value |= HEX(String[0]);
146  value <<= 4;
147  value |= HEX(String[1]);
148 
149 #undef HEX
150 
151  return value;
152 }
153 
154 unsigned long SkywatcherAPI::Highstr2long(std::string &String)
155 {
156  if (String.size() < 2)
157  {
158  return 0;
159  }
160  unsigned long res = 0;
161 
162 #define HEX(c) (((c) < 'A') ? ((c) - '0') : ((c) - 'A') + 10)
163 
164  res = HEX(String[0]);
165  res <<= 4;
166  res |= HEX(String[1]);
167 
168 #undef HEX
169 
170  return res;
171 }
172 
174 {
175  MYDEBUG(DBG_SCOPE, "CheckIfDCMotor");
176  // Flush the tty read buffer
177  char input[20];
178  int rc;
179  int nbytes;
180 
181  while (true)
182  {
183  rc = tty_read(MyPortFD, input, 20, 1, &nbytes);
184  if (TTY_TIME_OUT == rc)
185  break;
186  if (TTY_OK != rc)
187  return false;
188  }
189 
190  if (TTY_OK != tty_write(MyPortFD, ":", 1, &nbytes))
191  return false;
192 
193  rc = tty_read(MyPortFD, input, 1, 1, &nbytes);
194 
195  if ((TTY_OK == rc) && (1 == nbytes) && (':' == input[0]))
196  {
197  IsDCMotor = true;
198  return true;
199  }
200  if (TTY_TIME_OUT == rc)
201  {
202  IsDCMotor = false;
203  return true;
204  }
205 
206  return false;
207 }
208 
210 {
211  return MountCode >= 0x80 && MountCode < 0x90;
212 }
213 
215 {
216  double MicrostepsPerSecond = DegreesPerSecond * MicrostepsPerDegree[Axis];
217 
218  return long((double(StepperClockFrequency[Axis]) / MicrostepsPerSecond));
219 }
220 
221 long SkywatcherAPI::DegreesToMicrosteps(AXISID Axis, double AngleInDegrees)
222 {
223  return (long)(AngleInDegrees * MicrostepsPerDegree[(int)Axis]);
224 }
225 
227 {
228  // MYDEBUG(DBG_SCOPE, "GetEncoder");
229  std::string Parameters, Response;
230  if (!TalkWithAxis(Axis, GetAxisPosition, Parameters, Response))
231  return false;
232 
233  long Microsteps = BCDstr2long(Response);
234  // Only accept valid data
235  if (Microsteps > 0)
236  CurrentEncoders[Axis] = Microsteps;
237  return true;
238 }
239 
241 {
242  MYDEBUG(DBG_SCOPE, "GetHighSpeedRatio");
243  std::string Parameters, Response;
244 
245  if (!TalkWithAxis(Axis, InquireHighSpeedRatio, Parameters, Response))
246  return false;
247 
248  unsigned long highSpeedRatio = Highstr2long(Response);
249 
250  if (highSpeedRatio == 0)
251  {
252  MYDEBUG(INDI::Logger::DBG_ERROR, "Invalid highspeed ratio value from mount. Cycle power and reconnect again.");
253  return false;
254  }
255 
256  HighSpeedRatio[Axis] = highSpeedRatio;
257 
258  return true;
259 }
260 
262 {
263  MYDEBUG(DBG_SCOPE, "GetMicrostepsPerRevolution");
264  std::string Parameters, Response;
265 
266  if (!TalkWithAxis(Axis, InquireGridPerRevolution, Parameters, Response))
267  return false;
268 
269  long tmpMicrostepsPerRevolution = BCDstr2long(Response);
270 
271  if (tmpMicrostepsPerRevolution == 0)
272  {
273  MYDEBUG(INDI::Logger::DBG_ERROR, "Invalid microstep value from mount. Cycle power and reconnect again.");
274  return false;
275  }
276 
277  if (MountCode == _114GT)
278  tmpMicrostepsPerRevolution = 0x205318; // for 114GT mount
279 
280  if (IsMerlinMount())
281  tmpMicrostepsPerRevolution = (long)((double)tmpMicrostepsPerRevolution * 0.655);
282 
283  MicrostepsPerRevolution[(int)Axis] = tmpMicrostepsPerRevolution;
284 
285  MicrostepsPerRadian[(int)Axis] = tmpMicrostepsPerRevolution / (2 * M_PI);
286  RadiansPerMicrostep[(int)Axis] = 2 * M_PI / tmpMicrostepsPerRevolution;
287  MicrostepsPerDegree[(int)Axis] = tmpMicrostepsPerRevolution / 360.0;
288  DegreesPerMicrostep[(int)Axis] = 360.0 / tmpMicrostepsPerRevolution;
289 
290  MYDEBUGF(DBG_SCOPE, "Axis %d: %lf microsteps/degree, %lf microsteps/arcsec", Axis,
291  (double)tmpMicrostepsPerRevolution / 360.0, (double)tmpMicrostepsPerRevolution / 360.0 / 60 / 60);
292 
293  return true;
294 }
295 
297 {
298  MYDEBUG(DBG_SCOPE, "GetMicrostepsPerWormRevolution");
299  std::string Parameters, Response;
300 
301  if (!TalkWithAxis(Axis, InquirePECPeriod, Parameters, Response))
302  return false;
303 
304  uint32_t value = BCDstr2long(Response);
305  if (value == 0)
306  MYDEBUGF(INDI::Logger::DBG_WARNING, "Zero Microsteps per worm revolution for Axis %d. Possible corrupted data.", Axis);
307 
309 
310  return true;
311 }
312 
314 {
315  MYDEBUG(DBG_SCOPE, "GetMotorBoardVersion");
316  std::string Parameters, Response;
317 
318  if (!TalkWithAxis(Axis, InquireMotorBoardVersion, Parameters, Response))
319  return false;
320 
321  unsigned long tmpMCVersion = BCDstr2long(Response);
322 
323  MCVersion = ((tmpMCVersion & 0xFF) << 16) | ((tmpMCVersion & 0xFF00)) | ((tmpMCVersion & 0xFF0000) >> 16);
324 
325  MYDEBUGF(INDI::Logger::DBG_DEBUG, "Motor Board Version: %#X", MCVersion);
326 
327  return true;
328 }
329 
331 {
332  INDI_UNUSED(Axis);
333  if (MountCode == _114GT)
334  return CLOCKWISE;
335 
336  return ANTICLOCKWISE;
337 }
338 
340 {
341  MYDEBUG(DBG_SCOPE, "GetStepperClockFrequency");
342  std::string Parameters, Response;
343 
344  if (!TalkWithAxis(Axis, InquireTimerInterruptFreq, Parameters, Response))
345  return false;
346 
347  uint32_t value = BCDstr2long(Response);
348 
349  if (value == 0)
350  {
352  "Invalid Stepper Clock Frequency value from mount. Cycle power and reconnect again.");
353  return false;
354  }
355 
356  StepperClockFrequency[Axis] = value;
357 
358  return true;
359 }
360 
362 {
363  // MYDEBUG(DBG_SCOPE, "GetStatus");
364  std::string Parameters, Response;
365 
366  if (!TalkWithAxis(Axis, GetAxisStatus, Parameters, Response))
367  return false;
368 
369  if ((Response[1] & 0x01) != 0)
370  {
371  // Axis is running
372  AxesStatus[(int)Axis].FullStop = false;
373  if ((Response[0] & 0x01) != 0)
374  {
375  AxesStatus[(int)Axis].Slewing = true; // Axis in slewing(AstroMisc speed) mode.
376  AxesStatus[(int)Axis].SlewingTo = false;
377  }
378  else
379  {
380  AxesStatus[(int)Axis].SlewingTo = true; // Axis in SlewingTo mode.
381  AxesStatus[(int)Axis].Slewing = false;
382  }
383  }
384  else
385  {
386  // SlewTo Debugging
387  if (AxesStatus[(int)Axis].SlewingTo)
388  {
389  // If the mount was doing a slew to
390  GetEncoder(Axis);
391  // MYDEBUGF(INDI::Logger::DBG_SESSION,
392  // "Axis %s SlewTo complete - offset to target %ld microsteps %lf arc seconds "
393  // "LastSlewToTarget %ld CurrentEncoder %ld",
394  // Axis == AXIS1 ? "AXIS1" : "AXIS2", LastSlewToTarget[Axis] - CurrentEncoders[Axis],
395  // MicrostepsToDegrees(Axis, LastSlewToTarget[Axis] - CurrentEncoders[Axis]) * 3600,
396  // LastSlewToTarget[Axis], CurrentEncoders[Axis]);
397  }
398 
399  AxesStatus[(int)Axis].FullStop = true; // FullStop = 1; // Axis is fully stop.
400  AxesStatus[(int)Axis].Slewing = false;
401  AxesStatus[(int)Axis].SlewingTo = false;
402  }
403 
404  if ((Response[0] & 0x02) == 0)
405  AxesStatus[(int)Axis].SlewingForward = true; // Angle increase = 1;
406  else
407  AxesStatus[(int)Axis].SlewingForward = false;
408 
409  if ((Response[0] & 0x04) != 0)
410  AxesStatus[(int)Axis].HighSpeed = true; // HighSpeed running mode = 1;
411  else
412  AxesStatus[(int)Axis].HighSpeed = false;
413 
414  if ((Response[2] & 1) == 0)
415  AxesStatus[(int)Axis].NotInitialized = true; // MC is not initialized.
416  else
417  AxesStatus[(int)Axis].NotInitialized = false;
418 
419  return true;
420 }
421 
422 // Set initialization done ":F3", where '3'= Both CH1 and CH2.
424 {
425  MYDEBUG(DBG_SCOPE, "InitializeMC");
426  std::string Parameters, Response;
427 
428  if (!TalkWithAxis(AXIS1, Initialize, Parameters, Response))
429  return false;
430  if (!TalkWithAxis(AXIS2, Initialize, Parameters, Response))
431  return false;
432  return true;
433 }
434 
436 {
437  std::string Parameters, Response;
438  uint32_t rafeatures = 0, defeatures = 0;
439 
440  Long2BCDstr(GET_FEATURES_CMD, Parameters);
441  if (!TalkWithAxis(AXIS1, GetFeatureCmd, Parameters, Response))
442  return false;
443  else
444  rafeatures = BCDstr2long(Response);
445 
446  Long2BCDstr(GET_FEATURES_CMD, Parameters);
447  if (!TalkWithAxis(AXIS2, GetFeatureCmd, Parameters, Response))
448  return false;
449  else
450  defeatures = BCDstr2long(Response);
451 
452  if ((rafeatures & 0x000000F0) != (defeatures & 0x000000F0))
453  {
454  MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found different features for RA (%d) and DEC (%d)", __FUNCTION__,
455  rafeatures, defeatures);
456  }
457  if (rafeatures & 0x00000010)
458  {
459  MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found RA PPEC training on", __FUNCTION__);
460  }
461  if (defeatures & 0x00000010)
462  {
463  MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found DE PPEC training on", __FUNCTION__);
464  }
465 
466  // RA
467  AxisFeatures[AXIS1].inPPECTraining = rafeatures & 0x00000010;
468  AxisFeatures[AXIS1].inPPEC = rafeatures & 0x00000020;
469  AxisFeatures[AXIS1].hasEncoder = rafeatures & 0x00000001;
470  AxisFeatures[AXIS1].hasPPEC = rafeatures & 0x00000002;
471  AxisFeatures[AXIS1].hasHomeIndexer = rafeatures & 0x00000004;
472  AxisFeatures[AXIS1].isAZEQ = rafeatures & 0x00000008;
473  AxisFeatures[AXIS1].hasPolarLed = rafeatures & 0x00001000;
474  AxisFeatures[AXIS1].hasCommonSlewStart = rafeatures & 0x00002000; // supports :J3
475  AxisFeatures[AXIS1].hasHalfCurrentTracking = rafeatures & 0x00004000;
476  AxisFeatures[AXIS1].hasWifi = rafeatures & 0x00008000;
477 
478  // DE
479  AxisFeatures[AXIS2].inPPECTraining = defeatures & 0x00000010;
480  AxisFeatures[AXIS2].inPPEC = defeatures & 0x00000020;
481  AxisFeatures[AXIS2].hasEncoder = defeatures & 0x00000001;
482  AxisFeatures[AXIS2].hasPPEC = defeatures & 0x00000002;
483  AxisFeatures[AXIS2].hasHomeIndexer = defeatures & 0x00000004;
484  AxisFeatures[AXIS2].isAZEQ = defeatures & 0x00000008;
485  AxisFeatures[AXIS2].hasPolarLed = defeatures & 0x00001000;
486  AxisFeatures[AXIS2].hasCommonSlewStart = defeatures & 0x00002000; // supports :J3
487  AxisFeatures[AXIS2].hasHalfCurrentTracking = defeatures & 0x00004000;
488  AxisFeatures[AXIS2].hasWifi = defeatures & 0x00008000;
489 
490  return true;
491 }
492 
494 {
495  //MYDEBUG(DBG_SCOPE, "InitMount");
496 
497  if (!CheckIfDCMotor())
498  return false;
499 
501  return false;
502 
503  MountCode = MCVersion & 0xFF;
504 
505  MYDEBUGF(DBG_SCOPE, "Mount Code: %d (%s)", MountCode, mountTypeToString(MountCode));
506 
507  // Disable EQ mounts
508  // 0x22 is code for AZEQ6 which is added as an exception as proposed by Dirk Tetzlaff
509  if (MountCode < 0x80 && MountCode != AZEQ6 && MountCode != AZEQ5 && MountCode != AZEQ6_PRO)
510  {
511  MYDEBUGF(DBG_SCOPE, "Mount type not supported. %d", MountCode);
512  return false;
513  }
514 
515  // Inquire Features
516  InquireFeatures();
517 
518  // Inquire Gear Rate
520  return false;
522  return false;
523 
524  // Get stepper clock frequency
526  return false;
528  return false;
529 
530  // Inquire motor high speed ratio
531  if (!GetHighSpeedRatio(AXIS1))
532  return false;
533  if (!GetHighSpeedRatio(AXIS2))
534  return false;
535 
536  // Inquire PEC period
537  // DC motor controller does not support PEC
538  if (!IsDCMotor)
539  {
542  }
543 
544  GetStatus(AXIS1);
545  GetStatus(AXIS2);
546 
547  // In case not init, let's do that
548  if (AxesStatus[AXIS1].NotInitialized && AxesStatus[AXIS2].NotInitialized)
549  {
550  // Inquire Axis Position
551  if (!GetEncoder(AXIS1))
552  return false;
553  if (!GetEncoder(AXIS2))
554  return false;
555  MYDEBUGF(DBG_SCOPE, "Encoders before init AXIS1 %ld AXIS2 %ld", CurrentEncoders[AXIS1], CurrentEncoders[AXIS2]);
556 
561 
562  if (!InitializeMC())
563  return false;
564 
565  }
566  // Mount already initialized
567  else
568  {
569  PolarisPositionEncoders[AXIS1] = 0x800000;
570  PolarisPositionEncoders[AXIS2] = 0x800000;
573  }
574 
575  // These two LowSpeedGotoMargin are calculate from slewing for 5 seconds in 128x sidereal rate
576  LowSpeedGotoMargin[(int)AXIS1] = (long)(640 * SIDEREALRATE * MicrostepsPerRadian[(int)AXIS1]);
577  LowSpeedGotoMargin[(int)AXIS2] = (long)(640 * SIDEREALRATE * MicrostepsPerRadian[(int)AXIS2]);
578 
579  return true;
580 }
581 
583 {
584  // Request a slow stop
585  MYDEBUG(DBG_SCOPE, "InstantStop");
586  std::string Parameters, Response;
587  if (!TalkWithAxis(Axis, InstantAxisStop, Parameters, Response))
588  return false;
589  AxesStatus[(int)Axis].SetFullStop();
590  return true;
591 }
592 
593 void SkywatcherAPI::Long2BCDstr(long Number, std::string &String)
594 {
595  std::stringstream Temp;
596  Temp << std::hex << std::setfill('0') << std::uppercase << std::setw(2) << (Number & 0xff) << std::setw(2)
597  << ((Number & 0xff00) >> 8) << std::setw(2) << ((Number & 0xff0000) >> 16);
598  String = Temp.str();
599 }
600 
602 {
603  return Microsteps * DegreesPerMicrostep[(int)Axis];
604 }
605 
607 {
608  return Microsteps * RadiansPerMicrostep[(int)Axis];
609 }
610 
612 {
613  // Update the axis status
614  if (!GetStatus(Axis))
615  return;
616 
617  if (!AxesStatus[Axis].FullStop)
618  {
619  // Axis is running
620  if ((AxesStatus[Axis].SlewingTo) // slew to (GOTO) in progress
621  || (AxesStatus[Axis].HighSpeed) // currently high speed slewing
622  || (std::abs(Speed) >= LOW_SPEED_MARGIN) // I am about to request high speed
623  || ((AxesStatus[Axis].SlewingForward) && (Speed < 0)) // Direction change
624  || (!(AxesStatus[Axis].SlewingForward) && (Speed > 0))) // Direction change
625  {
626  // I need to stop the axis first
627  SlowStop(Axis);
628  }
629  else
630  return; // NO need change motion mode
631 
632  // Horrible bit A POLLING LOOP !!!!!!!!!!
633  while (true)
634  {
635  // Update status
636  GetStatus(Axis);
637 
638  if (AxesStatus[Axis].FullStop)
639  break;
640 
641  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep for 1/10 second
642  }
643  }
644 
645  char Direction;
646  if (Speed > 0.0)
647  Direction = '0';
648  else
649  {
650  Direction = '1';
651  Speed = -Speed;
652  }
653 
654  if (Speed > LOW_SPEED_MARGIN)
656  else
658 }
659 
661 {
662  double MicrostepsPerSecond = RadiansPerSecond * MicrostepsPerRadian[Axis];
663 
664  return long((double(StepperClockFrequency[Axis]) / MicrostepsPerSecond));
665 }
666 
667 long SkywatcherAPI::RadiansToMicrosteps(AXISID Axis, double AngleInRadians)
668 {
669  return (long)(AngleInRadians * MicrostepsPerRadian[(int)Axis]);
670 }
671 
672 bool SkywatcherAPI::SetEncoder(AXISID Axis, long Microsteps)
673 {
674  MYDEBUG(DBG_SCOPE, "SetEncoder");
675  std::string Parameters, Response;
676 
677  Long2BCDstr(Microsteps, Parameters);
678 
679  return TalkWithAxis(Axis, SetAxisPositionCmd, Parameters, Response);
680 }
681 
682 bool SkywatcherAPI::SetGotoTargetOffset(AXISID Axis, long OffsetInMicrosteps)
683 {
684  // MYDEBUG(DBG_SCOPE, "SetGotoTargetOffset");
685  std::string Parameters, Response;
686 
687  Long2BCDstr(OffsetInMicrosteps, Parameters);
688 
689  return TalkWithAxis(Axis, SetGotoTargetIncrement, Parameters, Response);
690 }
691 
697 {
698  // MYDEBUG(DBG_SCOPE, "SetMotionMode");
699  std::string Parameters, Response;
700 
701  Parameters.push_back(Func);
702  Parameters.push_back(Direction);
703 
704  return TalkWithAxis(Axis, SetMotionMode, Parameters, Response);
705 }
706 
707 bool SkywatcherAPI::SetClockTicksPerMicrostep(AXISID Axis, long ClockTicksPerMicrostep)
708 {
709  //MYDEBUG(DBG_SCOPE, "SetClockTicksPerMicrostep");
710  std::string Parameters, Response;
711 
712  Long2BCDstr(ClockTicksPerMicrostep, Parameters);
713 
714  return TalkWithAxis(Axis, SetStepPeriod, Parameters, Response);
715 }
716 
718 {
719  // MYDEBUG(DBG_SCOPE, "SetSlewModeDeccelerationRampLength");
720  std::string Parameters, Response;
721 
722  Long2BCDstr(Microsteps, Parameters);
723 
724  return TalkWithAxis(Axis, SetBreakStep, Parameters, Response);
725 }
726 
728 {
729  // MYDEBUG(DBG_SCOPE, "SetSlewToModeDeccelerationRampLength");
730  std::string Parameters, Response;
731 
732  Long2BCDstr(Microsteps, Parameters);
733 
734  return TalkWithAxis(Axis, SetBreakPointIncrement, Parameters, Response);
735 }
736 
738 {
739  std::string Response;
740  std::string Parameters = enabled ? "1" : "0";
741  return TalkWithAxis(AXIS1, SetSnapPort, Parameters, Response);
742 }
743 
744 void SkywatcherAPI::Slew(AXISID Axis, double SpeedInRadiansPerSecond, bool IgnoreSilentMode)
745 {
746  MYDEBUGF(DBG_SCOPE, "Slew axis: %d speed: %1.6f", Axis, SpeedInRadiansPerSecond);
747  // Clamp to MAX_SPEED
748  if (SpeedInRadiansPerSecond > MAX_SPEED)
749  SpeedInRadiansPerSecond = MAX_SPEED;
750  else if (SpeedInRadiansPerSecond < -MAX_SPEED)
751  SpeedInRadiansPerSecond = -MAX_SPEED;
752 
753  double InternalSpeed = SpeedInRadiansPerSecond;
754 
755  if (std::abs(InternalSpeed) <= SIDEREALRATE / 1000.0)
756  {
757  SlowStop(Axis);
758  return;
759  }
760 
761  // Stop motor and set motion mode if necessary
762  PrepareForSlewing(Axis, InternalSpeed);
763 
764  bool Forward;
765  if (InternalSpeed > 0.0)
766  Forward = true;
767  else
768  {
769  InternalSpeed = -InternalSpeed;
770  Forward = false;
771  }
772 
773  bool HighSpeed = false;
774 
775  if (InternalSpeed > LOW_SPEED_MARGIN && (IgnoreSilentMode || !SilentSlewMode))
776  {
777  InternalSpeed = InternalSpeed / (double)HighSpeedRatio[Axis];
778  HighSpeed = true;
779  }
780  long SpeedInt = RadiansPerSecondToClocksTicksPerMicrostep(Axis, InternalSpeed);
781  if ((MCVersion == 0x010600) || (MCVersion == 0x0010601)) // Cribbed from Mount_Skywatcher.cs
782  SpeedInt -= 3;
783  if (SpeedInt < 6)
784  SpeedInt = 6;
785  SetClockTicksPerMicrostep(Axis, SpeedInt);
786 
788 
789  AxesStatus[Axis].SetSlewing(Forward, HighSpeed);
790  SlewingSpeed[Axis] = SpeedInRadiansPerSecond;
791 }
792 
793 void SkywatcherAPI::SlewTo(AXISID Axis, long OffsetInMicrosteps, bool verbose)
794 {
795  // Nothing to do
796  if (0 == OffsetInMicrosteps)
797  return;
798 
799  // Debugging
800  LastSlewToTarget[Axis] = CurrentEncoders[Axis] + OffsetInMicrosteps;
801  if (verbose)
802  {
803  MYDEBUGF(INDI::Logger::DBG_DEBUG, "SlewTo Axis %d Offset %ld CurrentEncoder %ld SlewToTarget %ld", Axis,
804  OffsetInMicrosteps, CurrentEncoders[Axis], LastSlewToTarget[Axis]);
805  }
806 
807  char Direction = '0';
808  bool Forward = true;
809 
810  if (OffsetInMicrosteps < 0)
811  {
812  Forward = false;
813  Direction = '1';
814  OffsetInMicrosteps = -OffsetInMicrosteps;
815  }
816 
817  bool HighSpeed = (OffsetInMicrosteps > LowSpeedGotoMargin[Axis] && !SilentSlewMode);
818 
819  if (!GetStatus(Axis))
820  return;
821 
822  if (!AxesStatus[Axis].FullStop)
823  {
824  // Axis is running
825  if ((AxesStatus[Axis].SlewingTo) // slew to (GOTO) in progress
826  || (AxesStatus[Axis].HighSpeed) // currently high speed slewing
827  || HighSpeed // I am about to request high speed
828  || ((AxesStatus[Axis].SlewingForward) && !Forward) // Direction change
829  || (!(AxesStatus[Axis].SlewingForward) && Forward)) // Direction change
830  {
831  // I need to stop the axis first
832  SlowStop(Axis);
833  // Horrible bit A POLLING LOOP !!!!!!!!!!
834  while (true)
835  {
836  // Update status
837  GetStatus(Axis);
838 
839  if (AxesStatus[Axis].FullStop)
840  break;
841 
842  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep for 1/10 second
843  }
844  }
845  }
846 
847  if (HighSpeed)
849  else
851 
852  SetGotoTargetOffset(Axis, OffsetInMicrosteps);
853 
854  if (HighSpeed)
855  SetSlewToModeDeccelerationRampLength(Axis, OffsetInMicrosteps > 3200 ? 3200 : OffsetInMicrosteps);
856  else
857  SetSlewToModeDeccelerationRampLength(Axis, OffsetInMicrosteps > 200 ? 200 : OffsetInMicrosteps);
858 
860 
861  AxesStatus[Axis].SetSlewingTo(Forward, HighSpeed);
862 }
863 
865 {
866  // Request a slow stop
867  std::string Parameters, Response;
868 
869  return TalkWithAxis(Axis, NotInstantAxisStop, Parameters, Response);
870 }
871 
873 {
874  std::string Parameters, Response;
875 
876  return TalkWithAxis(Axis, StartMotion, Parameters, Response);
877 }
878 
879 bool SkywatcherAPI::TalkWithAxis(AXISID Axis, SkywatcherCommand Command, std::string &cmdDataStr, std::string &responseStr)
880 {
881  int bytesWritten = 0;
882  int bytesRead = 0;
883  int errorCode = 0;
884  char command[SKYWATCHER_MAX_CMD] = {0};
885  char response[SKYWATCHER_MAX_CMD] = {0};
886 
887  snprintf(command, SKYWATCHER_MAX_CMD, ":%c%c%s", Command, Axis == AXIS1 ? '1' : '2', cmdDataStr.c_str());
888 
889  MYDEBUGF(DBG_SCOPE, "CMD <%s>", command + 1);
890 
891  // Now add the trailing 0xD
892  command[strlen(command)] = 0xD;
893 
894  for (int retries = 0; retries < SKYWATCHER_MAX_RETRTY; retries++)
895  {
896  tcflush(MyPortFD, TCIOFLUSH);
897  if ( (errorCode = tty_write_string(MyPortFD, command, &bytesWritten)) != TTY_OK)
898  {
899  if (retries == SKYWATCHER_MAX_RETRTY - 1)
900  {
901  char errorMessage[MAXRBUF] = {0};
902  tty_error_msg(errorCode, errorMessage, MAXRBUF);
903  MYDEBUGF(INDI::Logger::DBG_ERROR, "Communication error: %s", errorMessage);
904  return false;
905  }
906  else
907  {
908  std::this_thread::sleep_for(std::chrono::milliseconds(100));
909  continue;
910  }
911  }
912 
913  bool isResponseReceived = false;
914  while (true)
915  {
916  memset(response, '\0', SKYWATCHER_MAX_CMD);
917  // If we get less than 2 bytes then it must be an error (=\r is a valid response).
918  if ( (errorCode = tty_read_section(MyPortFD, response, 0x0D, SKYWATCHER_TIMEOUT, &bytesRead)) != TTY_OK
919  || bytesRead < 2)
920  {
921  if (retries == SKYWATCHER_MAX_RETRTY - 1)
922  {
923  char errorMessage[MAXRBUF] = {0};
924  tty_error_msg(errorCode, errorMessage, MAXRBUF);
925  if (bytesRead < 2)
926  return false;
927  else
928  MYDEBUGF(INDI::Logger::DBG_ERROR, "Communication error: %s", errorMessage);
929  return false;
930  }
931  else
932  {
933  std::this_thread::sleep_for(std::chrono::milliseconds(100));
934  break;
935  }
936  }
937  else
938  {
939  // Remove CR (0x0D)
940  response[bytesRead - 1] = '\0';
941  if(response[0] == '=' || response[0] == '!')
942  {
943  isResponseReceived = true;
944  break;
945  }
946  else
947  {
948  //Skip invalid response
949  }
950  }
951  }
952  if(isResponseReceived) break;
953  }
954 
955  // If it is not empty, log it.
956  if (response[1] != 0)
957  MYDEBUGF(DBG_SCOPE, "RES <%s>", response + 1);
958  // Skip first = or !
959  responseStr = response + 1;
960 
961  if (response[0] == '!')
962  {
963  // char to int
964  uint8_t code = response[1] - 0x30;
965  if (errorCodes.count(code) > 0)
966  MYDEBUGF(INDI::Logger::DBG_ERROR, "Mount error: %s", errorCodes.at(code).c_str());
967  return false;
968  }
969 
970  // Response starting to ! is abnormal response, while = is OK.
971  return true;
972 }
973 
975 {
976  MYDEBUG(DBG_SCOPE, "IsInMotion");
977 
978  return AxesStatus[(int)Axis].Slewing || AxesStatus[(int)Axis].SlewingTo;
979 }
980 
982 {
983  return (AxisFeatures[AXIS1].hasHomeIndexer) && (AxisFeatures[AXIS2].hasHomeIndexer);
984 }
985 
987 {
988  return (AxisFeatures[AXIS1].hasEncoder) && (AxisFeatures[AXIS2].hasEncoder);
989 }
990 
992 {
993  return AxisFeatures[AXIS1].hasPPEC;
994 }
995 
997 {
998  return MountCode == 0x04 || MountCode == 0x05 || MountCode == 0x06 || MountCode == 0x0A || MountCode == 0x23
999  || MountCode == 0xA5;
1000 }
1001 
1003 {
1004  return MountCode == 0x06;
1005 }
1006 
1008 {
1009  return (AxisFeatures[AXIS1].hasPolarLed) && (AxisFeatures[AXIS2].hasPolarLed);
1010 }
1011 
1013 {
1014  uint32_t command;
1015  if (on)
1016  command = ENCODER_ON_CMD;
1017  else
1018  command = ENCODER_OFF_CMD;
1019  SetFeature(axis, command);
1020 }
1021 
1023 {
1024  TurnEncoder(AXIS1, on);
1025 }
1026 
1028 {
1029  TurnEncoder(AXIS2, on);
1030 }
1031 
1032 void SkywatcherAPI::SetFeature(AXISID axis, uint32_t command)
1033 {
1034  std::string cmd, response;
1035  Long2BCDstr(command, cmd);
1036  TalkWithAxis(axis, SetFeatureCmd, cmd, response);
1037 }
The Axis class Implements a generic Axis which can be used for equatorial or AltAz mounts for both ax...
bool SetGotoTargetOffset(AXISID Axis, long OffsetInMicrosteps)
Set the goto target offset per the specified axis.
double MicrostepsToRadians(AXISID Axis, long Microsteps)
Convert microsteps to angle in radians.
bool GetHighSpeedRatio(AXISID Axis)
Set the HighSpeedRatio status variable to the ratio between high and low speed stepping modes.
bool IsMerlinMount() const
Check if the current mount is a Virtuoso (AltAz)
unsigned long BCDstr2long(std::string &String)
SkyWatcherFeatures AxisFeatures[2]
void TurnRAEncoder(bool on)
unsigned long Highstr2long(std::string &String)
long LastSlewToTarget[2]
long LowSpeedGotoMargin[2]
bool SetSlewModeDeccelerationRampLength(AXISID Axis, long Microsteps)
Set the length of the deccelaration ramp for Slew mode.
PositiveRotationSense_t GetPositiveRotationDirection(AXISID Axis)
Returns the rotation direction for a positive step on the designated axis.
static constexpr double SIDEREALRATE
bool GetMotorBoardVersion(AXISID Axis)
static constexpr double LOW_SPEED_MARGIN
double MicrostepsToDegrees(AXISID Axis, long Microsteps)
Convert microsteps to angle in degrees.
unsigned long MountCode
bool GetStepperClockFrequency(AXISID Axis)
Set the StepperClockFrequency status variable to fixed PIC timer interrupt frequency (ticks per secon...
void TurnEncoder(AXISID axis, bool on)
bool InquireFeatures()
void SlewTo(AXISID Axis, long OffsetInMicrosteps, bool verbose=true)
Slew to the given offset and stop.
void SetFeature(AXISID axis, uint32_t command)
long ZeroPositionEncoders[2]
Zero position encoder values (microsteps).
double MicrostepsPerRadian[2]
unsigned int DBG_SCOPE
long CurrentEncoders[2]
Current encoder values (microsteps).
bool GetStatus(AXISID Axis)
bool GetMicrostepsPerRevolution(AXISID Axis)
Set the MicrostepsPerRevolution status variable to the number of microsteps for a 360 degree revoluti...
unsigned long MCVersion
long RadiansToMicrosteps(AXISID Axis, double AngleInRadians)
Convert angle in radians to microsteps.
bool GetMicrostepsPerWormRevolution(AXISID Axis)
Set the MicrostepsPermWormRevolution status variable to the number of microsteps for a 360 degree rev...
double RadiansPerMicrostep[2]
bool SetEncoder(AXISID Axis, long Microsteps)
Set axis encoder to the specified value.
void PrepareForSlewing(AXISID Axis, double Speed)
long DegreesPerSecondToClocksTicksPerMicrostep(AXISID Axis, double DegreesPerSecond)
Check if the current mount is AZ GTi.
bool InstantStop(AXISID Axis)
Bring the axis to an immediate halt. N.B. This command could cause damage to the mount or telescope a...
void TurnDEEncoder(bool on)
long PolarisPositionEncoders[2]
Polaris position (initial) encoder values (microsteps).
bool GetEncoder(AXISID Axis)
Set the CurrentEncoders status variable to the current encoder value in microsteps for the specified ...
long MicrostepsPerWormRevolution[2]
long HighSpeedRatio[2]
double SlewingSpeed[2]
double MicrostepsPerDegree[2]
long DegreesToMicrosteps(AXISID Axis, double AngleInDegrees)
Convert angle in degrees to microsteps.
@ InquireTimerInterruptFreq
Definition: skywatcherAPI.h:68
@ InquireGridPerRevolution
Definition: skywatcherAPI.h:67
@ InquireMotorBoardVersion
Definition: skywatcherAPI.h:66
double DegreesPerMicrostep[2]
bool SlowStop(AXISID Axis)
Bring the axis to slow stop in the distance specified by SetSlewModeDeccelerationRampLength.
bool StartAxisMotion(AXISID Axis)
Start the axis slewing in the prevously selected mode.
bool InitMount()
Initialize the communication to the mount.
bool HasHomeIndexers()
bool SetSlewToModeDeccelerationRampLength(AXISID Axis, long Microsteps)
Set the length of the deccelaration ramp for SlewTo mode.
long RadiansPerSecondToClocksTicksPerMicrostep(AXISID Axis, double RadiansPerSecond)
Convert a slewing rate in radians per second into the required clock ticks per microstep setting.
void Slew(AXISID Axis, double SpeedInRadiansPerSecond, bool IgnoreSilentMode=true)
Start the axis slewing at the given rate.
bool TalkWithAxis(AXISID Axis, SkywatcherCommand Command, std::string &cmdDataStr, std::string &responseStr)
static constexpr double MAX_SPEED
bool SetAxisMotionMode(AXISID Axis, char Func, char Direction)
Set the motion mode per the specified axis.
long MicrostepsPerRevolution[2]
bool IsInMotion(AXISID Axis)
Check if an axis is moving.
long StepperClockFrequency[2]
bool toggleSnapPort(bool enabled)
Toggle the snap port on or off.
bool SetClockTicksPerMicrostep(AXISID Axis, long ClockTicksPerMicrostep)
Set the PIC internal divider variable which determines how many clock interrupts have to occur betwee...
static const char * mountTypeToString(uint8_t type)
void Long2BCDstr(long Number, std::string &String)
AXISSTATUS AxesStatus[2]
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:566
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
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
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
@ TTY_TIME_OUT
Definition: indicom.h:154
const char * Direction[]
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int verbose
Definition: indidriver.c:49
#define MAXRBUF
Definition: indiserver.cpp:102
Command
The Command enum includes all the command types sent to the various devices (motor,...
__le16 type
Definition: pwc-ioctl.h:0
__u8 cmd[4]
Definition: pwc-ioctl.h:2
#define HEX(c)
#define MYDEBUG(priority, msg)
Definition: skywatcherAPI.h:25
#define MYDEBUGF(priority, msg,...)
Definition: skywatcherAPI.h:27
bool NotInitialized
Definition: skywatcherAPI.h:46
void SetSlewingTo(bool forward, bool highspeed)
bool SlewingTo
Definition: skywatcherAPI.h:43
bool HighSpeed
Definition: skywatcherAPI.h:45
void SetFullStop()
bool SlewingForward
Definition: skywatcherAPI.h:44
void SetSlewing(bool forward, bool highspeed)
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
Definition: indilogger.cpp:339
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.
Definition: indilogger.cpp:72