Instrument Neutral Distributed Interface INDI 2.2.5
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
39void AXISSTATUS::SetSlewing(bool forward, bool highspeed)
40{
41 FullStop = SlewingTo = false;
42 Slewing = true;
43
44 SlewingForward = forward;
45 HighSpeed = highspeed;
46}
47
48void AXISSTATUS::SetSlewingTo(bool forward, bool highspeed)
49{
50 FullStop = Slewing = false;
51 SlewingTo = true;
52
53 SlewingForward = forward;
54 HighSpeed = highspeed;
55}
56
57const 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 WAVE_150I:
96 return "Wave 150i";
97 case WAVE_100I:
98 return "Wave 100i";
99 case GT:
100 return "GT";
101 case MF:
102 return "MF";
103 case _114GT:
104 return "114 GT";
105 case DOB:
106 return "Dob";
107 case AZGTE:
108 return "AZ-GTe";
109 case AZGTI:
110 return "AZ-GTi";
111 default:
112 return "Unknown";
113 }
114}
115
130
131unsigned long SkywatcherAPI::BCDstr2long(std::string &String)
132{
133 if (String.size() != 6)
134 {
135 return 0;
136 }
137 unsigned long value = 0;
138
139#define HEX(c) (((c) < 'A') ? ((c) - '0') : ((c) - 'A') + 10)
140
141 value = HEX(String[4]);
142 value <<= 4;
143 value |= HEX(String[5]);
144 value <<= 4;
145 value |= HEX(String[2]);
146 value <<= 4;
147 value |= HEX(String[3]);
148 value <<= 4;
149 value |= HEX(String[0]);
150 value <<= 4;
151 value |= HEX(String[1]);
152
153#undef HEX
154
155 return value;
156}
157
158unsigned long SkywatcherAPI::Highstr2long(std::string &String)
159{
160 if (String.size() < 2)
161 {
162 return 0;
163 }
164 unsigned long res = 0;
165
166#define HEX(c) (((c) < 'A') ? ((c) - '0') : ((c) - 'A') + 10)
167
168 res = HEX(String[0]);
169 res <<= 4;
170 res |= HEX(String[1]);
171
172#undef HEX
173
174 return res;
175}
176
178{
179 MYDEBUG(DBG_SCOPE, "CheckIfDCMotor");
180 // Flush the tty read buffer
181 char input[20];
182 int rc;
183 int nbytes;
184
185 while (true)
186 {
187 rc = tty_read(MyPortFD, input, 20, 1, &nbytes);
188 if (TTY_TIME_OUT == rc)
189 break;
190 if (TTY_OK != rc)
191 return false;
192 }
193
194 if (TTY_OK != tty_write(MyPortFD, ":", 1, &nbytes))
195 return false;
196
197 rc = tty_read(MyPortFD, input, 1, 1, &nbytes);
198
199 if ((TTY_OK == rc) && (1 == nbytes) && (':' == input[0]))
200 {
201 IsDCMotor = true;
202 return true;
203 }
204 if (TTY_TIME_OUT == rc)
205 {
206 IsDCMotor = false;
207 return true;
208 }
209
210 return false;
211}
212
213// bool SkywatcherAPI::IsMerlinMount() const
214// {
215// return MountCode >= 0x80 && MountCode < 0x90;
216// }
217
219{
220 double MicrostepsPerSecond = DegreesPerSecond * MicrostepsPerDegree[Axis];
221
222 return long((double(StepperClockFrequency[Axis]) / MicrostepsPerSecond));
223}
224
226{
227 return (long)(AngleInDegrees * MicrostepsPerDegree[(int)Axis]);
228}
229
231{
232 // MYDEBUG(DBG_SCOPE, "GetEncoder");
233 std::string Parameters, Response;
234 if (!TalkWithAxis(Axis, GetAxisPosition, Parameters, Response))
235 return false;
236
237 long Microsteps = BCDstr2long(Response);
238 // Only accept valid data
239 if (Microsteps > 0)
240 CurrentEncoders[Axis] = Microsteps;
241 return true;
242}
243
245{
246 MYDEBUG(DBG_SCOPE, "GetHighSpeedRatio");
247 std::string Parameters, Response;
248
249 if (!TalkWithAxis(Axis, InquireHighSpeedRatio, Parameters, Response))
250 return false;
251
252 unsigned long highSpeedRatio = Highstr2long(Response);
253
254 if (highSpeedRatio == 0)
255 {
256 MYDEBUG(INDI::Logger::DBG_ERROR, "Invalid highspeed ratio value from mount. Cycle power and reconnect again.");
257 return false;
258 }
259
260 HighSpeedRatio[Axis] = highSpeedRatio;
261
262 return true;
263}
264
266{
267 MYDEBUG(DBG_SCOPE, "GetMicrostepsPerRevolution");
268 std::string Parameters, Response;
269
270 if (!TalkWithAxis(Axis, InquireGridPerRevolution, Parameters, Response))
271 return false;
272
273 long tmpMicrostepsPerRevolution = BCDstr2long(Response);
274
275 if (tmpMicrostepsPerRevolution == 0)
276 {
277 MYDEBUG(INDI::Logger::DBG_ERROR, "Invalid microstep value from mount. Cycle power and reconnect again.");
278 return false;
279 }
280
281 if (MountCode == _114GT)
282 tmpMicrostepsPerRevolution = 0x205318; // for 114GT mount
283
284 // if (IsMerlinMount())
285 // tmpMicrostepsPerRevolution = (long)((double)tmpMicrostepsPerRevolution * 0.655);
286
287 MicrostepsPerRevolution[(int)Axis] = tmpMicrostepsPerRevolution;
288
289 MicrostepsPerRadian[(int)Axis] = tmpMicrostepsPerRevolution / (2 * M_PI);
290 RadiansPerMicrostep[(int)Axis] = 2 * M_PI / tmpMicrostepsPerRevolution;
291 MicrostepsPerDegree[(int)Axis] = tmpMicrostepsPerRevolution / 360.0;
292 DegreesPerMicrostep[(int)Axis] = 360.0 / tmpMicrostepsPerRevolution;
293
294 MYDEBUGF(DBG_SCOPE, "Axis %d: %lf microsteps/degree, %lf microsteps/arcsec", Axis,
295 (double)tmpMicrostepsPerRevolution / 360.0, (double)tmpMicrostepsPerRevolution / 360.0 / 60 / 60);
296
297 return true;
298}
299
301{
302 MYDEBUG(DBG_SCOPE, "GetMicrostepsPerWormRevolution");
303 std::string Parameters, Response;
304
305 if (!TalkWithAxis(Axis, InquirePECPeriod, Parameters, Response))
306 return false;
307
308 uint32_t value = BCDstr2long(Response);
309 if (value == 0)
310 MYDEBUGF(INDI::Logger::DBG_WARNING, "Zero Microsteps per worm revolution for Axis %d. Possible corrupted data.", Axis);
311
313
314 return true;
315}
316
318{
319 MYDEBUG(DBG_SCOPE, "GetMotorBoardVersion");
320 std::string Parameters, Response;
321
322 if (!TalkWithAxis(Axis, InquireMotorBoardVersion, Parameters, Response))
323 return false;
324
325 unsigned long tmpMCVersion = BCDstr2long(Response);
326
327 MCVersion = ((tmpMCVersion & 0xFF) << 16) | ((tmpMCVersion & 0xFF00)) | ((tmpMCVersion & 0xFF0000) >> 16);
328
329 MYDEBUGF(INDI::Logger::DBG_DEBUG, "Motor Board Version: %#X", MCVersion);
330
331 return true;
332}
333
342
344{
345 MYDEBUG(DBG_SCOPE, "GetStepperClockFrequency");
346 std::string Parameters, Response;
347
348 if (!TalkWithAxis(Axis, InquireTimerInterruptFreq, Parameters, Response))
349 return false;
350
351 uint32_t value = BCDstr2long(Response);
352
353 if (value == 0)
354 {
356 "Invalid Stepper Clock Frequency value from mount. Cycle power and reconnect again.");
357 return false;
358 }
359
361
362 return true;
363}
364
366{
367 // MYDEBUG(DBG_SCOPE, "GetStatus");
368 std::string Parameters, Response;
369
370 if (!TalkWithAxis(Axis, GetAxisStatus, Parameters, Response))
371 return false;
372
373 if ((Response[1] & 0x01) != 0)
374 {
375 // Axis is running
376 AxesStatus[(int)Axis].FullStop = false;
377 if ((Response[0] & 0x01) != 0)
378 {
379 AxesStatus[(int)Axis].Slewing = true; // Axis in slewing(AstroMisc speed) mode.
380 AxesStatus[(int)Axis].SlewingTo = false;
381 }
382 else
383 {
384 AxesStatus[(int)Axis].SlewingTo = true; // Axis in SlewingTo mode.
385 AxesStatus[(int)Axis].Slewing = false;
386 }
387 }
388 else
389 {
390 // SlewTo Debugging
391 if (AxesStatus[(int)Axis].SlewingTo)
392 {
393 // If the mount was doing a slew to
395 // MYDEBUGF(INDI::Logger::DBG_SESSION,
396 // "Axis %s SlewTo complete - offset to target %ld microsteps %lf arc seconds "
397 // "LastSlewToTarget %ld CurrentEncoder %ld",
398 // Axis == AXIS1 ? "AXIS1" : "AXIS2", LastSlewToTarget[Axis] - CurrentEncoders[Axis],
399 // MicrostepsToDegrees(Axis, LastSlewToTarget[Axis] - CurrentEncoders[Axis]) * 3600,
400 // LastSlewToTarget[Axis], CurrentEncoders[Axis]);
401 }
402
403 AxesStatus[(int)Axis].FullStop = true; // FullStop = 1; // Axis is fully stop.
404 AxesStatus[(int)Axis].Slewing = false;
405 AxesStatus[(int)Axis].SlewingTo = false;
406 }
407
408 if ((Response[0] & 0x02) == 0)
409 AxesStatus[(int)Axis].SlewingForward = true; // Angle increase = 1;
410 else
411 AxesStatus[(int)Axis].SlewingForward = false;
412
413 if ((Response[0] & 0x04) != 0)
414 AxesStatus[(int)Axis].HighSpeed = true; // HighSpeed running mode = 1;
415 else
416 AxesStatus[(int)Axis].HighSpeed = false;
417
418 if ((Response[2] & 1) == 0)
419 AxesStatus[(int)Axis].NotInitialized = true; // MC is not initialized.
420 else
421 AxesStatus[(int)Axis].NotInitialized = false;
422
423 return true;
424}
425
426// Set initialization done ":F3", where '3'= Both CH1 and CH2.
428{
429 MYDEBUG(DBG_SCOPE, "InitializeMC");
430 std::string Parameters, Response;
431
432 if (!TalkWithAxis(AXIS1, Initialize, Parameters, Response))
433 return false;
434 if (!TalkWithAxis(AXIS2, Initialize, Parameters, Response))
435 return false;
436 return true;
437}
438
440{
441 std::string Parameters, Response;
442 uint32_t rafeatures = 0, defeatures = 0;
443
444 Long2BCDstr(GET_FEATURES_CMD, Parameters);
445 if (!TalkWithAxis(AXIS1, GetFeatureCmd, Parameters, Response))
446 return false;
447 else
448 rafeatures = BCDstr2long(Response);
449
450 Long2BCDstr(GET_FEATURES_CMD, Parameters);
451 if (!TalkWithAxis(AXIS2, GetFeatureCmd, Parameters, Response))
452 return false;
453 else
454 defeatures = BCDstr2long(Response);
455
456 if ((rafeatures & 0x000000F0) != (defeatures & 0x000000F0))
457 {
458 MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found different features for RA (%d) and DEC (%d)", __FUNCTION__,
459 rafeatures, defeatures);
460 }
461 if (rafeatures & 0x00000010)
462 {
463 MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found RA PPEC training on", __FUNCTION__);
464 }
465 if (defeatures & 0x00000010)
466 {
467 MYDEBUGF(INDI::Logger::DBG_WARNING, "%s(): Found DE PPEC training on", __FUNCTION__);
468 }
469
470 // RA
471 AxisFeatures[AXIS1].inPPECTraining = rafeatures & 0x00000010;
472 AxisFeatures[AXIS1].inPPEC = rafeatures & 0x00000020;
473 AxisFeatures[AXIS1].hasEncoder = rafeatures & 0x00000001;
474 AxisFeatures[AXIS1].hasPPEC = rafeatures & 0x00000002;
475 AxisFeatures[AXIS1].hasHomeIndexer = rafeatures & 0x00000004;
476 AxisFeatures[AXIS1].isAZEQ = rafeatures & 0x00000008;
477 AxisFeatures[AXIS1].hasPolarLed = rafeatures & 0x00001000;
478 AxisFeatures[AXIS1].hasCommonSlewStart = rafeatures & 0x00002000; // supports :J3
479 AxisFeatures[AXIS1].hasHalfCurrentTracking = rafeatures & 0x00004000;
480 AxisFeatures[AXIS1].hasWifi = rafeatures & 0x00008000;
481
482 // DE
483 AxisFeatures[AXIS2].inPPECTraining = defeatures & 0x00000010;
484 AxisFeatures[AXIS2].inPPEC = defeatures & 0x00000020;
485 AxisFeatures[AXIS2].hasEncoder = defeatures & 0x00000001;
486 AxisFeatures[AXIS2].hasPPEC = defeatures & 0x00000002;
487 AxisFeatures[AXIS2].hasHomeIndexer = defeatures & 0x00000004;
488 AxisFeatures[AXIS2].isAZEQ = defeatures & 0x00000008;
489 AxisFeatures[AXIS2].hasPolarLed = defeatures & 0x00001000;
490 AxisFeatures[AXIS2].hasCommonSlewStart = defeatures & 0x00002000; // supports :J3
491 AxisFeatures[AXIS2].hasHalfCurrentTracking = defeatures & 0x00004000;
492 AxisFeatures[AXIS2].hasWifi = defeatures & 0x00008000;
493
494 return true;
495}
496
498{
499 //MYDEBUG(DBG_SCOPE, "InitMount");
500
501 if (!CheckIfDCMotor())
502 return false;
503
505 return false;
506
507 MountCode = MCVersion & 0xFF;
508
509 MYDEBUGF(DBG_SCOPE, "Mount Code: %d (%s)", MountCode, mountTypeToString(MountCode));
510
511 // Disable EQ mounts
512 // 0x22 is code for AZEQ6 which is added as an exception as proposed by Dirk Tetzlaff
514 {
515 MYDEBUGF(DBG_SCOPE, "Mount type not supported. %d", MountCode);
516 return false;
517 }
518
519 // Inquire Features
521
522 // Inquire Gear Rate
524 return false;
526 return false;
527
528 // Get stepper clock frequency
530 return false;
532 return false;
533
534 // Inquire motor high speed ratio
536 return false;
538 return false;
539
540 // Inquire PEC period
541 // DC motor controller does not support PEC
542 if (!IsDCMotor)
543 {
546 }
547
550
551 // In case not init, let's do that
552 if (AxesStatus[AXIS1].NotInitialized && AxesStatus[AXIS2].NotInitialized)
553 {
554 // Inquire Axis Position
555 if (!GetEncoder(AXIS1))
556 return false;
557 if (!GetEncoder(AXIS2))
558 return false;
559 MYDEBUGF(DBG_SCOPE, "Encoders before init AXIS1 %ld AXIS2 %ld", CurrentEncoders[AXIS1], CurrentEncoders[AXIS2]);
560
565
566 if (!InitializeMC())
567 return false;
568
569 }
570 // Mount already initialized
571 else
572 {
573 PolarisPositionEncoders[AXIS1] = 0x800000;
574 PolarisPositionEncoders[AXIS2] = 0x800000;
577 }
578
579 // These two LowSpeedGotoMargin are calculate from slewing for 5 seconds in 128x sidereal rate
580 LowSpeedGotoMargin[(int)AXIS1] = (long)(640 * SIDEREALRATE * MicrostepsPerRadian[(int)AXIS1]);
581 LowSpeedGotoMargin[(int)AXIS2] = (long)(640 * SIDEREALRATE * MicrostepsPerRadian[(int)AXIS2]);
582
583 return true;
584}
585
587{
588 // Request a slow stop
589 MYDEBUG(DBG_SCOPE, "InstantStop");
590 std::string Parameters, Response;
591 if (!TalkWithAxis(Axis, InstantAxisStop, Parameters, Response))
592 return false;
593 AxesStatus[(int)Axis].SetFullStop();
594 return true;
595}
596
597void SkywatcherAPI::Long2BCDstr(long Number, std::string &String)
598{
599 std::stringstream Temp;
600 Temp << std::hex << std::setfill('0') << std::uppercase << std::setw(2) << (Number & 0xff) << std::setw(2)
601 << ((Number & 0xff00) >> 8) << std::setw(2) << ((Number & 0xff0000) >> 16);
602 String = Temp.str();
603}
604
606{
607 return Microsteps * DegreesPerMicrostep[(int)Axis];
608}
609
611{
612 return Microsteps * RadiansPerMicrostep[(int)Axis];
613}
614
616{
617 // Update the axis status
618 if (!GetStatus(Axis))
619 return;
620
621 if (!AxesStatus[Axis].FullStop)
622 {
623 // Axis is running
624 if ((AxesStatus[Axis].SlewingTo) // slew to (GOTO) in progress
625 || (AxesStatus[Axis].HighSpeed) // currently high speed slewing
626 || (std::abs(Speed) >= LOW_SPEED_MARGIN) // I am about to request high speed
627 || ((AxesStatus[Axis].SlewingForward) && (Speed < 0)) // Direction change
628 || (!(AxesStatus[Axis].SlewingForward) && (Speed > 0))) // Direction change
629 {
630 // I need to stop the axis first
631 SlowStop(Axis);
632 }
633 else
634 return; // NO need change motion mode
635
636 // Horrible bit A POLLING LOOP !!!!!!!!!!
637 while (true)
638 {
639 // Update status
641
642 if (AxesStatus[Axis].FullStop)
643 break;
644
645 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep for 1/10 second
646 }
647 }
648
649 char Direction;
650 if (Speed > 0.0)
651 Direction = '0';
652 else
653 {
654 Direction = '1';
655 Speed = -Speed;
656 }
657
658 if (Speed > LOW_SPEED_MARGIN)
660 else
662}
663
665{
666 double MicrostepsPerSecond = RadiansPerSecond * MicrostepsPerRadian[Axis];
667
668 return long((double(StepperClockFrequency[Axis]) / MicrostepsPerSecond));
669}
670
672{
673 return (long)(AngleInRadians * MicrostepsPerRadian[(int)Axis]);
674}
675
677{
678 MYDEBUG(DBG_SCOPE, "SetEncoder");
679 std::string Parameters, Response;
680
681 Long2BCDstr(Microsteps, Parameters);
682
683 return TalkWithAxis(Axis, SetAxisPositionCmd, Parameters, Response);
684}
685
686bool SkywatcherAPI::SetGotoTargetOffset(AXISID Axis, long OffsetInMicrosteps)
687{
688 // MYDEBUG(DBG_SCOPE, "SetGotoTargetOffset");
689 std::string Parameters, Response;
690
691 Long2BCDstr(OffsetInMicrosteps, Parameters);
692
693 return TalkWithAxis(Axis, SetGotoTargetIncrement, Parameters, Response);
694}
695
701{
702 // MYDEBUG(DBG_SCOPE, "SetMotionMode");
703 std::string Parameters, Response;
704
705 Parameters.push_back(Func);
706 Parameters.push_back(Direction);
707
708 return TalkWithAxis(Axis, SetMotionMode, Parameters, Response);
709}
710
711bool SkywatcherAPI::SetClockTicksPerMicrostep(AXISID Axis, long ClockTicksPerMicrostep)
712{
713 //MYDEBUG(DBG_SCOPE, "SetClockTicksPerMicrostep");
714 std::string Parameters, Response;
715
716 Long2BCDstr(ClockTicksPerMicrostep, Parameters);
717
718 return TalkWithAxis(Axis, SetStepPeriod, Parameters, Response);
719}
720
722{
723 // MYDEBUG(DBG_SCOPE, "SetSlewModeDeccelerationRampLength");
724 std::string Parameters, Response;
725
726 Long2BCDstr(Microsteps, Parameters);
727
728 return TalkWithAxis(Axis, SetBreakStep, Parameters, Response);
729}
730
732{
733 // MYDEBUG(DBG_SCOPE, "SetSlewToModeDeccelerationRampLength");
734 std::string Parameters, Response;
735
736 Long2BCDstr(Microsteps, Parameters);
737
738 return TalkWithAxis(Axis, SetBreakPointIncrement, Parameters, Response);
739}
740
742{
743 std::string Response;
744 std::string Parameters = enabled ? "1" : "0";
745 return TalkWithAxis(AXIS1, SetSnapPort, Parameters, Response);
746}
747
748void SkywatcherAPI::Slew(AXISID Axis, double SpeedInRadiansPerSecond, bool IgnoreSilentMode)
749{
750 MYDEBUGF(DBG_SCOPE, "Slew axis: %d speed: %1.6f", Axis, SpeedInRadiansPerSecond);
751 // Clamp to MAX_SPEED
752 if (SpeedInRadiansPerSecond > MAX_SPEED)
753 SpeedInRadiansPerSecond = MAX_SPEED;
754 else if (SpeedInRadiansPerSecond < -MAX_SPEED)
755 SpeedInRadiansPerSecond = -MAX_SPEED;
756
757 double InternalSpeed = SpeedInRadiansPerSecond;
758
759 if (std::abs(InternalSpeed) <= SIDEREALRATE / 1000.0)
760 {
761 SlowStop(Axis);
762 return;
763 }
764
765 // Stop motor and set motion mode if necessary
766 PrepareForSlewing(Axis, InternalSpeed);
767
768 bool Forward;
769 if (InternalSpeed > 0.0)
770 Forward = true;
771 else
772 {
773 InternalSpeed = -InternalSpeed;
774 Forward = false;
775 }
776
777 bool HighSpeed = false;
778
779 if (InternalSpeed > LOW_SPEED_MARGIN && (IgnoreSilentMode || !SilentSlewMode))
780 {
781 InternalSpeed = InternalSpeed / (double)HighSpeedRatio[Axis];
782 HighSpeed = true;
783 }
784 long SpeedInt = RadiansPerSecondToClocksTicksPerMicrostep(Axis, InternalSpeed);
785 if ((MCVersion == 0x010600) || (MCVersion == 0x0010601)) // Cribbed from Mount_Skywatcher.cs
786 SpeedInt -= 3;
787 if (SpeedInt < 6)
788 SpeedInt = 6;
790
792
793 AxesStatus[Axis].SetSlewing(Forward, HighSpeed);
794 SlewingSpeed[Axis] = SpeedInRadiansPerSecond;
795}
796
797void SkywatcherAPI::SlewTo(AXISID Axis, long OffsetInMicrosteps, bool verbose)
798{
799 // Nothing to do
800 if (0 == OffsetInMicrosteps)
801 return;
802
803 // Debugging
804 LastSlewToTarget[Axis] = CurrentEncoders[Axis] + OffsetInMicrosteps;
805 if (verbose)
806 {
807 MYDEBUGF(INDI::Logger::DBG_DEBUG, "SlewTo Axis %d Offset %ld CurrentEncoder %ld SlewToTarget %ld", Axis,
808 OffsetInMicrosteps, CurrentEncoders[Axis], LastSlewToTarget[Axis]);
809 }
810
811 char Direction = '0';
812 bool Forward = true;
813
814 if (OffsetInMicrosteps < 0)
815 {
816 Forward = false;
817 Direction = '1';
818 OffsetInMicrosteps = -OffsetInMicrosteps;
819 }
820
821 bool HighSpeed = (OffsetInMicrosteps > LowSpeedGotoMargin[Axis] && !SilentSlewMode);
822
823 if (!GetStatus(Axis))
824 return;
825
826 if (!AxesStatus[Axis].FullStop)
827 {
828 // Axis is running
829 if ((AxesStatus[Axis].SlewingTo) // slew to (GOTO) in progress
830 || (AxesStatus[Axis].HighSpeed) // currently high speed slewing
831 || HighSpeed // I am about to request high speed
832 || ((AxesStatus[Axis].SlewingForward) && !Forward) // Direction change
833 || (!(AxesStatus[Axis].SlewingForward) && Forward)) // Direction change
834 {
835 // I need to stop the axis first
836 SlowStop(Axis);
837 // Horrible bit A POLLING LOOP !!!!!!!!!!
838 while (true)
839 {
840 // Update status
842
843 if (AxesStatus[Axis].FullStop)
844 break;
845
846 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep for 1/10 second
847 }
848 }
849 }
850
851 if (HighSpeed)
853 else
855
856 SetGotoTargetOffset(Axis, OffsetInMicrosteps);
857
858 if (HighSpeed)
859 SetSlewToModeDeccelerationRampLength(Axis, OffsetInMicrosteps > 3200 ? 3200 : OffsetInMicrosteps);
860 else
861 SetSlewToModeDeccelerationRampLength(Axis, OffsetInMicrosteps > 200 ? 200 : OffsetInMicrosteps);
862
864
865 AxesStatus[Axis].SetSlewingTo(Forward, HighSpeed);
866}
867
869{
870 // Request a slow stop
871 std::string Parameters, Response;
872
873 return TalkWithAxis(Axis, NotInstantAxisStop, Parameters, Response);
874}
875
877{
878 std::string Parameters, Response;
879
880 return TalkWithAxis(Axis, StartMotion, Parameters, Response);
881}
882
883bool SkywatcherAPI::TalkWithAxis(AXISID Axis, SkywatcherCommand Command, std::string &cmdDataStr, std::string &responseStr)
884{
885 int bytesWritten = 0;
886 int bytesRead = 0;
887 int errorCode = 0;
888 char command[SKYWATCHER_MAX_CMD] = {0};
889 char response[SKYWATCHER_MAX_CMD] = {0};
890
891 snprintf(command, SKYWATCHER_MAX_CMD, ":%c%c%s", Command, Axis == AXIS1 ? '1' : '2', cmdDataStr.c_str());
892
893 MYDEBUGF(DBG_SCOPE, "CMD <%s>", command + 1);
894
895 // Now add the trailing 0xD
896 command[strlen(command)] = 0xD;
897
898 for (int retries = 0; retries < SKYWATCHER_MAX_RETRTY; retries++)
899 {
900 tcflush(MyPortFD, TCIOFLUSH);
901 if ( (errorCode = tty_write_string(MyPortFD, command, &bytesWritten)) != TTY_OK)
902 {
903 if (retries == SKYWATCHER_MAX_RETRTY - 1)
904 {
905 char errorMessage[MAXRBUF] = {0};
906 tty_error_msg(errorCode, errorMessage, MAXRBUF);
907 MYDEBUGF(INDI::Logger::DBG_ERROR, "Communication error: %s", errorMessage);
908 return false;
909 }
910 else
911 {
912 std::this_thread::sleep_for(std::chrono::milliseconds(100));
913 continue;
914 }
915 }
916
917 bool isResponseReceived = false;
918 while (true)
919 {
920 memset(response, '\0', SKYWATCHER_MAX_CMD);
921 // If we get less than 2 bytes then it must be an error (=\r is a valid response).
922 if ( (errorCode = tty_read_section_expanded(MyPortFD, response, 0x0D, SKYWATCHER_TIMEOUT_S, SKYWATCHER_TIMEOUT_US, &bytesRead)) != TTY_OK
923 || bytesRead < 2)
924 {
925 if (retries == SKYWATCHER_MAX_RETRTY - 1)
926 {
927 char errorMessage[MAXRBUF] = {0};
928 tty_error_msg(errorCode, errorMessage, MAXRBUF);
929 if (bytesRead < 2)
930 return false;
931 else
932 MYDEBUGF(INDI::Logger::DBG_ERROR, "Communication error: %s", errorMessage);
933 return false;
934 }
935 else
936 {
937 std::this_thread::sleep_for(std::chrono::milliseconds(100));
938 break;
939 }
940 }
941 else
942 {
943 // Remove CR (0x0D)
944 response[bytesRead - 1] = '\0';
945 if(response[0] == '=' || response[0] == '!')
946 {
947 isResponseReceived = true;
948 break;
949 }
950 else
951 {
952 //Skip invalid response
953 }
954 }
955 }
956 if(isResponseReceived) break;
957 }
958
959 // If it is not empty, log it.
960 if (response[1] != 0)
961 MYDEBUGF(DBG_SCOPE, "RES <%s>", response + 1);
962 // Skip first = or !
963 responseStr = response + 1;
964
965 if (response[0] == '!')
966 {
967 // char to int
968 uint8_t code = response[1] - 0x30;
969 if (errorCodes.count(code) > 0)
970 MYDEBUGF(INDI::Logger::DBG_ERROR, "Mount error: %s", errorCodes.at(code).c_str());
971 return false;
972 }
973
974 // Response starting to ! is abnormal response, while = is OK.
975 return true;
976}
977
979{
980 MYDEBUG(DBG_SCOPE, "IsInMotion");
981
982 return AxesStatus[(int)Axis].Slewing || AxesStatus[(int)Axis].SlewingTo;
983}
984
986{
987 return (AxisFeatures[AXIS1].hasHomeIndexer) && (AxisFeatures[AXIS2].hasHomeIndexer);
988}
989
991{
992 return (AxisFeatures[AXIS1].hasEncoder) && (AxisFeatures[AXIS2].hasEncoder);
993}
994
996{
997 return AxisFeatures[AXIS1].hasPPEC;
998}
999
1001{
1002 return MountCode == 0x04 || MountCode == 0x05 || MountCode == 0x06 || MountCode == 0x0A || MountCode == 0x23
1003 || MountCode == 0xA5;
1004}
1005
1007{
1008 return MountCode == 0x06;
1009}
1010
1012{
1013 return (AxisFeatures[AXIS1].hasPolarLed) && (AxisFeatures[AXIS2].hasPolarLed);
1014}
1015
1017{
1018 uint32_t command;
1019 if (on)
1020 command = ENCODER_ON_CMD;
1021 else
1022 command = ENCODER_OFF_CMD;
1023 SetFeature(axis, command);
1024}
1025
1027{
1028 TurnEncoder(AXIS1, on);
1029}
1030
1032{
1033 TurnEncoder(AXIS2, on);
1034}
1035
1036void SkywatcherAPI::SetFeature(AXISID axis, uint32_t command)
1037{
1038 std::string cmd, response;
1039 Long2BCDstr(command, cmd);
1040 TalkWithAxis(axis, SetFeatureCmd, cmd, response);
1041}
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.
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)
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 a Virtuoso (AltAz)
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.
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 previously selected mode.
bool InitMount()
Initialize the communication to the mount.
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]
#define M_PI
Definition dsp.h:52
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_read_section_expanded(int fd, char *buf, char stop_char, long timeout_seconds, long timeout_microseconds, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:658
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
@ TTY_TIME_OUT
Definition indicom.h:58
const char * Direction[]
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int verbose
Definition indidriver.c:49
#define MAXRBUF
Definition indidriver.c:52
uint16_t type
Definition pwc-ioctl.h:0
uint8_t cmd[4]
Definition pwc-ioctl.h:2
#define HEX(c)
#define MYDEBUG(priority, msg)
#define MYDEBUGF(priority, msg,...)
bool NotInitialized
void SetSlewingTo(bool forward, bool highspeed)
void SetFullStop()
bool SlewingForward
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.
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.