Instrument Neutral Distributed Interface INDI 2.2.5
lx200apdriver.cpp
Go to the documentation of this file.
1#if 0
2LX200 Astro - Physics Driver
3Copyright (C) 2007 Markus Wildi
4
5This library is free software;
6you can redistribute it and / or
7modify it under the terms of the GNU Lesser General Public
8License as published by the Free Software Foundation;
9either
10version 2.1 of the License, or (at your option) any later version.
11
12This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY;
14without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16Lesser General Public License for more details.
17
18You should have received a copy of the GNU Lesser General Public
19License along with this library;
20if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA
22
23#endif
24
25#include <cmath>
26#include "lx200apdriver.h"
27
28#include "indicom.h"
29#include "indilogger.h"
30#include "lx200driver.h"
31
32#include <cstring>
33#include <unistd.h>
34
35#ifndef _WIN32
36#include <termios.h>
37#endif
38
39#define LX200_TIMEOUT 5 /* FD timeout in seconds */
40
41// maximum guide pulse request to send to controller
42#define MAX_LX200AP_PULSE_LEN 999
43
45unsigned int AP_DBG_SCOPE;
46
47void set_lx200ap_name(const char *deviceName, unsigned int debug_level)
48{
49 snprintf(lx200ap_name, MAXINDIDEVICE, "%s", deviceName);
50 AP_DBG_SCOPE = debug_level;
51}
52
54{
55 const struct timespec timeout = {0, 50000000L};
56 int i = 0;
57 char temp_string[256];
58 int error_type;
59 int nbytes_write = 0;
60 int nbytes_read = 0;
61
62 DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "Testing telescope's connection using #:GG#...");
63
64 if (fd <= 0)
65 {
67 "check_lx200ap_connection: not a valid file descriptor received");
68
69 return -1;
70 }
71 for (i = 0; i < 2; i++)
72 {
73 // This is the command to get the UTC offset. Used as a connection test.
74 if ((error_type = tty_write_string(fd, "#:GG#", &nbytes_write)) != TTY_OK)
75 {
77 "check_lx200ap_connection: unsuccessful write to telescope, %d", nbytes_write);
78
79 return error_type;
80 }
81 tty_read_section(fd, temp_string, '#', LX200_TIMEOUT, &nbytes_read);
82 tcflush(fd, TCIFLUSH);
83 if (nbytes_read > 1)
84 {
85 temp_string[nbytes_read - 1] = '\0';
86
87 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "check_lx200ap_connection: received bytes %d, [%s]",
88 nbytes_write, temp_string);
89
90 return 0;
91 }
92 nanosleep(&timeout, nullptr);
93 }
94
95 DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "check_lx200ap_connection: wrote, but nothing received.");
96
97 return -1;
98}
99
100// get UTC offset.
101int getAPUTCOffset(int fd, double *value)
102{
103 int error_type;
104 int nbytes_write = 0;
105 int nbytes_read = 0;
106
107 char temp_string[256];
108 temp_string[0] = 0;
109 temp_string[1] = 0;
110
111 const char *cmd = "#:GG#";
113
114 if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
115 return error_type;
116
117 if ((error_type = tty_read_section(fd, temp_string, '#', LX200_TIMEOUT, &nbytes_read)) != TTY_OK)
118 {
119 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPUTCOffset: saying good bye %d, %d", error_type,
120 nbytes_read);
121 return error_type;
122 }
123
124 tcflush(fd, TCIFLUSH);
125
126 DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "RES <%s>", temp_string);
127
128 /* Negative offsets, see AP keypad manual p. 77 */
129 if ((temp_string[0] == 'A') || ((temp_string[0] == '0') && (temp_string[1] == '0')) || (temp_string[0] == '@'))
130 {
131 int i;
132 for (i = nbytes_read; i > 0; i--)
133 {
134 temp_string[i] = temp_string[i - 1];
135 }
136 temp_string[0] = '-';
137 temp_string[nbytes_read + 1] = '\0';
138
139 if (temp_string[1] == 'A')
140 {
141 temp_string[1] = '0';
142 switch (temp_string[2])
143 {
144 case '5':
145
146 temp_string[2] = '1';
147 break;
148 case '4':
149
150 temp_string[2] = '2';
151 break;
152 case '3':
153
154 temp_string[2] = '3';
155 break;
156 case '2':
157
158 temp_string[2] = '4';
159 break;
160 case '1':
161
162 temp_string[2] = '5';
163 break;
164 default:
165 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPUTCOffset: string not handled %s",
166 temp_string);
167 return -1;
168 break;
169 }
170 }
171 else if (temp_string[1] == '0')
172 {
173 temp_string[1] = '0';
174 temp_string[2] = '6';
175 }
176 else if (temp_string[1] == '@')
177 {
178 temp_string[1] = '0';
179 switch (temp_string[2])
180 {
181 case '9':
182
183 temp_string[2] = '7';
184 break;
185 case '8':
186
187 temp_string[2] = '8';
188 break;
189 case '7':
190
191 temp_string[2] = '9';
192 break;
193 case '6':
194
195 temp_string[2] = '0';
196 break;
197 case '5':
198 temp_string[1] = '1';
199 temp_string[2] = '1';
200 break;
201 case '4':
202
203 temp_string[1] = '1';
204 temp_string[2] = '2';
205 break;
206 default:
207 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPUTCOffset: string not handled %s",
208 temp_string);
209 return -1;
210 break;
211 }
212 }
213 else
214 {
215 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPUTCOffset: string not handled %s", temp_string);
216 }
217 }
218 else
219 {
220 temp_string[nbytes_read - 1] = '\0';
221 }
222
223 if (f_scansexa(temp_string, value))
224 {
225 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPUTCOffset: unable to process %s", temp_string);
226 return -1;
227 }
228 return 0;
229}
230
231int setAPObjectAZ(int fd, double az)
232{
233 int h, m, s;
234 char cmd[256];
235
236 // The azimuth should be 0-360.
237 while (az < 0) az += 360.0;
238 while (az > 360.0) az -= 360.0;
239
240 getSexComponents(az, &h, &m, &s);
241
242 snprintf(cmd, sizeof(cmd), "#:Sz %03d*%02d:%02d#", h, m, s);
243
245
246 return (setStandardProcedure(fd, cmd));
247}
248
249/* wildi Valid set Values are positive, add error condition */
250
251int setAPObjectAlt(int fd, double alt)
252{
253 int d, m, s;
254 char cmd[256];
255
256 getSexComponents(alt, &d, &m, &s);
257 if (d < 0) d = -d;
258 snprintf(cmd, sizeof(cmd), "#:Sa %s%02d*%02d:%02d#",
259 alt >= 0 ? "+" : "-", d, m, s);
260
262
263 return (setStandardProcedure(fd, cmd));
264}
265
266// Set the UTC offset.
267// Previously this only set positive offsets.
268// Added the sign according to the doc in https://astro-physics.info/tech_support/mounts/protocol-cp3-cp4.pdf
269int setAPUTCOffset(int fd, double hours)
270{
271 int h, m, s;
272
273 char cmd[256];
274
275 getSexComponents(hours, &h, &m, &s);
276 if (h < 0) h = -h;
277 snprintf(cmd, sizeof(cmd), "#:SG %s%02d:%02d:%02d#",
278 hours >= 0 ? "+" : "-", h, m, s);
279
281
282 return (setStandardProcedure(fd, cmd));
283}
284int APSyncCM(int fd, char *matchedObject)
285{
286 const struct timespec timeout = {0, 10000000L};
287 int error_type;
288 int nbytes_write = 0;
289 int nbytes_read = 0;
290
291 const char *cmd = "#:CM#";
293
294 if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
295 return error_type;
296
297 if ((error_type = tty_read_section(fd, matchedObject, '#', LX200_TIMEOUT, &nbytes_read)) != TTY_OK)
298 return error_type;
299
300 matchedObject[nbytes_read - 1] = '\0';
301
302 DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "RES <%s>", matchedObject);
303
304 /* Sleep 10ms before flushing. This solves some issues with LX200 compatible devices. */
305 nanosleep(&timeout, nullptr);
306
307 tcflush(fd, TCIFLUSH);
308
309 return 0;
310}
311
312int APSyncCMR(int fd, char *matchedObject)
313{
314 const struct timespec timeout = {0, 10000000L};
315 int error_type;
316 int nbytes_write = 0;
317 int nbytes_read = 0;
318
319 const char *cmd = "#:CMR#";
321
322 if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
323 return error_type;
324
325 if ((error_type = tty_read_section(fd, matchedObject, '#', LX200_TIMEOUT, &nbytes_read)) != TTY_OK)
326 return error_type;
327
328 matchedObject[nbytes_read - 1] = '\0';
329
330 DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "RES <%s>", matchedObject);
331
332 /* Sleep 10ms before flushing. This solves some issues with LX200 compatible devices. */
333 nanosleep(&timeout, nullptr);
334
335 tcflush(fd, TCIFLUSH);
336
337 return 0;
338}
339
340
341int sendAPCommand(int fd, const char *cmd, const char *comment)
342{
343 int error_type;
344 int nbytes_write = 0;
345
348
349 if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
350 return error_type;
351 return 0;
352}
353
354int selectAPPECState(int fd, int pecstate)
355{
356 switch (pecstate)
357 {
358 case AP_PEC_OFF:
359 return sendAPCommand(fd, "#:p#", "selectAPPECState: Setting PEC OFF");
360 case AP_PEC_ON:
361 return sendAPCommand(fd, "#:pP#", "selectAPPECState: Setting PEC ON");
362 case AP_PEC_RECORD:
363 return sendAPCommand(fd, "#:pR#", "selectAPPECState: Enabling PEC RECORD");
364 default:
365 return -1;
366 }
367
368 return 0;
369}
370
371
372// Should return a number between 0 and 969 inclusive.
373// It is a "normalized worm position", normalized to the number of PEM datapoints per revolution.
374// It is based on the gear angle and remembered through a power cycle.
375int getAPWormPosition(int fd, int *position)
376{
377 int nbytes_read = 0;
378 char response[128];
379
380 int res = sendAPCommand(fd, "#:Gp#", "getAWormPosition");
381 if (res != TTY_OK)
382 {
383 DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPWormPosition: write failed.");
384 return res;
385 }
386
387 res = tty_read_section(fd, response, '#', LX200_TIMEOUT, &nbytes_read);
388 if (res != TTY_OK)
389 {
390 // This does happen occasionally, not sure why, but isn't critical.
391 // DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPWormPosition: read failed.");
392 return res;
393 }
394
395 tcflush(fd, TCIFLUSH);
396 if (nbytes_read > 1)
397 {
398 response[nbytes_read - 1] = '\0';
399 response[3] = '\0';
400 sscanf(response, "%d", position);
401 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "getAPWormPosition: response: %d", *position);
402 return TTY_OK;
403 }
404 DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getAPWormPosition: wrote, but bad response.");
405 return -1;
406}
407
408int selectAPMoveToRate(int fd, int moveToIndex)
409{
410 switch (moveToIndex)
411 {
412 case 0:
413 return sendAPCommand(fd, "#:RC0#", "selectAPMoveToRate: Setting move to rate to 12x");
414 case 1:
415 return sendAPCommand(fd, "#:RC1#", "selectAPMoveToRate: Setting move to rate to 64x");
416 case 2:
417 return sendAPCommand(fd, "#:RC2#", "selectAPMoveToRate: Setting move to rate to 600x");
418 case 3:
419 return sendAPCommand(fd, "#:RC3#", "selectAPMoveToRate: Setting move to rate to 1200x");
420 default:
421 return -1;
422 }
423 return 0;
424}
425
426int selectAPSlewRate(int fd, int slewIndex)
427{
428 switch (slewIndex)
429 {
430 case 0:
431 return sendAPCommand(fd, "#:RS0#", "selectAPSlewRate: Setting slew to rate to index 0");
432 case 1:
433 return sendAPCommand(fd, "#:RS1#", "selectAPSlewRate: Setting slew to rate to index 1");
434 case 2:
435 return sendAPCommand(fd, "#:RS2#", "selectAPSlewRate: Setting slew to rate to index 2");
436 default:
437 return -1;
438 }
439 return 0;
440}
441
442int selectAPTrackingMode(int fd, int trackMode)
443{
444 switch (trackMode)
445 {
447 return sendAPCommand(fd, "#:RT2#", "selectAPTrackingMode: Setting tracking mode to sidereal.");
449 return sendAPCommand(fd, "#:RT1#", "selectAPTrackingMode: Setting tracking mode to solar.");
451 return sendAPCommand(fd, "#:RT0#", "selectAPTrackingMode: Setting tracking mode to lunar.");
454 "selectAPTrackingMode: Setting tracking mode to Custom -- NOT IMPLEMENTED!.");
455 break;
456 case AP_TRACKING_OFF:
457 return sendAPCommand(fd, "#:RT9#", "selectAPTrackingMode: Setting tracking mode to Zero.");
458 default:
459 return -1;
460 }
461 return 0;
462}
463
464int selectAPGuideRate(int fd, int guideRate)
465{
466 switch (guideRate)
467 {
468 case 0:
469 return sendAPCommand(fd, "#:RG0#", "selectAPGuideRate: Setting guide to rate to 0.25x");
470 case 1:
471 return sendAPCommand(fd, "#:RG1#", "selectAPGuideRate: Setting guide to rate to 0.50x");
472 case 2:
473 return sendAPCommand(fd, "#:RG2#", "selectAPGuideRate: Setting guide to rate to 1.00x");
474 default:
475 return -1;
476 }
477 return 0;
478}
479
480int swapAPButtons(int fd, int currentSwap)
481{
482 switch (currentSwap)
483 {
484 case 0:
485 return sendAPCommand(fd, "#:NS#", "swapAPButtons: Swapping NS.");
486 case 1:
487 return sendAPCommand(fd, "#:EW#", "swapAPButtons: Swapping EW.");
488 default:
489 return -1;
490 }
491 return 0;
492}
493
494int setAPObjectRA(int fd, double ra)
495{
496 /*ToDo AP accepts "#:Sr %02d:%02d:%02d.%1d#"*/
497 int h, m, s;
498 char cmd[256];
499
500 // Make sure RA is 0-24.
501 while (ra < 0) ra += 24.0;
502 while (ra > 24.0) ra -= 24.0;
503
504 getSexComponents(ra, &h, &m, &s);
505
506 snprintf(cmd, sizeof(cmd), "#:Sr %02d:%02d:%02d#", h, m, s);
507
509
510 return (setStandardProcedure(fd, cmd));
511}
512
513int setAPObjectDEC(int fd, double dec)
514{
515 int d, m, s;
516 char cmd[256];
517
518 getSexComponents(dec, &d, &m, &s);
519 if (d < 0) d = -d;
520 snprintf(cmd, sizeof(cmd), "#:Sd %s%02d*%02d:%02d#",
521 dec >= 0 ? "+" : "-", d, m, s);
522
524
525 return (setStandardProcedure(fd, cmd));
526}
527
528// Set the longitude.
529int setAPSiteLongitude(int fd, double Long)
530{
531 int d, m, s;
532 char cmd[256];
533
534 // Make sure longitude is 0-360.
535 while (Long < 0) Long += 360.0;
536 while (Long > 360.0) Long -= 360.0;
537
538 getSexComponents(Long, &d, &m, &s);
539 snprintf(cmd, sizeof(cmd), "#:Sg %03d*%02d:%02d#", d, m, s);
540
542
543 return (setStandardProcedure(fd, cmd));
544}
545
546// Set the latitude.
547int setAPSiteLatitude(int fd, double Lat)
548{
549 int d, m, s;
550 char cmd[256];
551
552 getSexComponents(Lat, &d, &m, &s);
553 if (d < 0) d = -d;
554 snprintf(cmd, sizeof(cmd), "#:St %s%02d*%02d:%02d#",
555 Lat >= 0 ? "+" : "-", d, m, s);
556
558
559 return (setStandardProcedure(fd, cmd));
560}
561
562int setAPRATrackRate(int fd, double rate)
563{
564 char cmd[16];
565 char sign;
566 int errcode = 0;
567 char errmsg[MAXRBUF];
568 char response[8];
569 int nbytes_read = 0;
570 int nbytes_written = 0;
571
572 if (rate < 0)
573 sign = '-';
574 else
575 sign = '+';
576
577 snprintf(cmd, 16, "#:RR%c%03.4f#", sign, fabs(rate));
578
580
581 tcflush(fd, TCIFLUSH);
582
583 if ((errcode = tty_write(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
584 {
585 tty_error_msg(errcode, errmsg, MAXRBUF);
587 return errcode;
588 }
589
590 if ((errcode = tty_read(fd, response, 1, LX200_TIMEOUT, &nbytes_read)))
591 {
592 tty_error_msg(errcode, errmsg, MAXRBUF);
594 return errcode;
595 }
596
597 if (nbytes_read > 0)
598 {
599 response[nbytes_read] = '\0';
600 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "RES (%s)", response);
601
602 tcflush(fd, TCIFLUSH);
603 return 0;
604 }
605
606 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 1.", nbytes_read);
607 return -1;
608}
609
610int setAPDETrackRate(int fd, double rate)
611{
612 char cmd[16];
613 char sign;
614 int errcode = 0;
615 char errmsg[MAXRBUF];
616 char response[8];
617 int nbytes_read = 0;
618 int nbytes_written = 0;
619
620 if (rate < 0)
621 sign = '-';
622 else
623 sign = '+';
624
625 snprintf(cmd, 16, "#:RD%c%03.4f#", sign, fabs(rate));
626
628
629
630 tcflush(fd, TCIFLUSH);
631
632 if ((errcode = tty_write(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
633 {
634 tty_error_msg(errcode, errmsg, MAXRBUF);
636 return errcode;
637 }
638
639 if ((errcode = tty_read(fd, response, 1, LX200_TIMEOUT, &nbytes_read)))
640 {
641 tty_error_msg(errcode, errmsg, MAXRBUF);
643 return errcode;
644 }
645
646 if (nbytes_read > 0)
647 {
648 response[nbytes_read] = '\0';
649 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "RES (%s)", response);
650
651 tcflush(fd, TCIFLUSH);
652 return 0;
653 }
654
655 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 1.", nbytes_read);
656 return -1;
657}
658
659int APSendPulseCmd(int fd, int direction, int duration_msec)
660{
661 char cmd[20];
662
663 // GTOCP3 supports 3 digits for msec duration
664 if (duration_msec > MAX_LX200AP_PULSE_LEN)
665 {
666 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "APSendPulseCmd requested %d msec limited to 999 msec!", duration_msec);
667 duration_msec = 999;
668 }
669
670 switch (direction)
671 {
672 case LX200_NORTH:
673 sprintf(cmd, "#:Mn%03d#", duration_msec);
674 break;
675 case LX200_SOUTH:
676 sprintf(cmd, "#:Ms%03d#", duration_msec);
677 break;
678 case LX200_EAST:
679 sprintf(cmd, "#:Me%03d#", duration_msec);
680 break;
681 case LX200_WEST:
682 sprintf(cmd, "#:Mw%03d#", duration_msec);
683 break;
684 default:
685 return 1;
686 }
687
688 int res = sendAPCommand(fd, cmd, "APSendPulseCmd: Sending pulse command.");
689 tcflush(fd, TCIFLUSH);
690 return res;
691}
692
694{
695 return sendAPCommand(fd, "#:KA#", "APParkMount: Sending park command.");
696}
697
698
700{
701 return sendAPCommand(fd, "#:PO#", "APUnParkMount: Sending unpark command.");
702}
703
704// This is a modified version of selectAPMoveRate() from lx200apdriver.cpp
705// This version allows changing the rate to GUIDE as well as 12x/64x/600x/1200x
706// and is required some the experimental AP driver properly handles
707// pulse guide requests over 999ms by simulated it by setting the move rate
708// to GUIDE and then starting and halting a move of the correct duration.
709int selectAPCenterRate(int fd, int centerIndex)
710{
711 switch (centerIndex)
712 {
713 case 0:
714 return sendAPCommand(fd, "#:RG#", "selectAPMoveToRate: Setting move to rate to GUIDE");
715 case 1:
716 return sendAPCommand(fd, "#:RC0#", "selectAPMoveToRate: Setting move to rate to 12x");
717 case 2:
718 return sendAPCommand(fd, "#:RC1#", "selectAPMoveToRate: Setting move to rate to 64x");
719 case 3:
720 return sendAPCommand(fd, "#:RC2#", "selectAPMoveToRate: Setting move to rate to 600x");
721 case 4:
722 return sendAPCommand(fd, "#:RC3#", "selectAPMoveToRate: Setting move to rate to 1200x");
723 default:
724 return -1;
725 }
726 return 0;
727}
728
729int selectAPV2CenterRate(int fd, int centerIndex, APRateTableState rateTable)
730{
731 if (rateTable == AP_RATE_TABLE_DEFAULT) // If no rate table, do as we always have
732 return selectAPCenterRate(fd, centerIndex);
733
734 else
735 {
736 switch (centerIndex)
737 {
738 case 0:
739 return sendAPCommand(fd, "#:RC5#", "selectAPMoveToRate: Setting center rate to 0.25x");
740 case 1:
741 return sendAPCommand(fd, "#:RC6#", "selectAPMoveToRate: Setting center rate to 0.5x");
742 case 2:
743 return sendAPCommand(fd, "#:RC7#", "selectAPMoveToRate: Setting center rate to 1.0x");
744 case 3:
745 return sendAPCommand(fd, "#:RC0#", "selectAPMoveToRate: Setting center rate to 12");
746 case 4:
747 return sendAPCommand(fd, "#:RC1#", "selectAPMoveToRate: Setting center rate to 64x");
748 case 5:
749 return sendAPCommand(fd, "#:RC2#", "selectAPMoveToRate: Setting center rate to 200x");
750 case 6:
751 return sendAPCommand(fd, "#:RC3#", "selectAPMoveToRate: Setting center rate to index 3");
752 case 7:
753 return sendAPCommand(fd, "#:RC4#", "selectAPMoveToRate: Setting center rate to index 4");
754 default:
755 return -1;
756 }
757 }
758 return 0;
759}
760
761// Doc for the :GOS command fom A-P:
762//
763// Response for GTOCP3 Rev “T” through GTOCP4 Rev VCP4-P01-14 is a 13 character string: ABCDEFGHIJKLM.
764//
765// Note the addition of the last two characters: L & M. (Rev “S” had the 1st 11 characters)
766// 14th character “N” added in VCPx-P02-xx and later
767//
768// Possible values for each variable are as follows (Please note the difference between “0” and “O” in the responses)
769// Note the differences starting with the GTOCP4, especially with P02-01 and later!!
770//
771// A: Park Status 'P' parked, '0' not parked, '1' auto-park - ON
772// B: RA Tracking Status '0'=Lunar Rate, '1'=Solar Rate, '2'=Sidereal, '9'=Tracking Stopped,
773// 'C'=Custom RA Tracking Rate (read specific value with :Rr# command)
774// C: Dec Tracking Status '9'=No Motion (to mimic RA Tracking), 'C'=Custom DEC Tracking Rate
775// (read specific value with :Rd# command)
776// D: Slewing Status (GOTO Slews) 'S'=Slewing, '0'=Not slewing
777// E: Moving RA Axis 'E'=Moving East, 'W'=Moving West, '0'=Not Moving
778// (via a Move command/Slew/ST4 Port signal)
779// F: Moving Dec Axis 'N'=Moving North (counter-clockwise), 'S'=Moving South (clockwise), '0'=Not Moving
780// (via a Move command/Slew/ST4 Port signal):
781// G: Guide Rate '0'=0.25x, '1'=0.50x, '2'=1.00x
782// H: Center/Move Rate < P02-xx '0'=12x, '1'=64x, '2'=600x, '3'=1200x, 'C'=Custom Rate
783// NOTE: Divide rates for '2' and '3' by 2 for 3600GTO
784// Read “C” rate with :Rc# command. Do not divide by 2 for 3600GTO
785// H: Center/Move Rate > P02-xx '0'=12x, '1'=64x, '2'=200x, '3'= 400x - 600x see table,
786// '4'= 600x - 1200x see table, '5'=0.25x, '6'=0.5x, '7'=1.0x, 'C'=Custom Rate
787// I: Slew Rate '0'=600x Slow, '1'=900x Medium, '2'=1200x Fast, 'C'=Custom Rate
788// NOTE: Rates are scaled for “0”, “1” & “2” by different amounts for 3600GTOs
789// and for some 400GTO and 600EGTO mounts.
790// Read 'C' rate with :Rs# command. Do not scale custom rates.
791// J: PEM 'O'=Off, 'P'=Playback, 'R'=Recording, 'E'=Encoder
792// K: Mount Status '0'=Normal, '1'=Stalled, '2'=Low Power Supply,
793// '4'=Servo fault / number problem, '8'=Reserved (CP3 only)
794// K: Mount Status '0'=Normal 'Z'=Stalled, 'Y'=Low Power Supply, 'X'=Servo fault / number problem,
795// VCP4-P02-01 and later 'N'=CCW Internal Declination Limit or AE Limit, 'S'=CW Internal Declination Limit or AE Limit,
796// 'E'=East Internal RA Limit or AE Limit, 'W'=West Internal RA Limit or AE Limit,
797// 'z'=Kill Function has been issued
798// L: E – W button reversal '0'=Normal E = E and W = W, '1'=Reversed E = W and W = E
799// M: N – S button reversal '0'=Normal N = CCW and S = CW, '1'=Reversed N = CW and S = CCW
800// N: Button / Slew Rate Table '0'=Normal, '1'= ~75% speed reduction, '2'= ~50% speed reduction, '3'= High Speed for Mach2GTO
801// VCP4-P02-01 and later
802//
803
804int getApStatusStringInternal(int fd, char *statusString, bool complain)
805{
806 int nbytes_write = 0;
807 int nbytes_read = 0;
808
809 if (fd <= 0)
810 {
812 "getApStatusString: not a valid file descriptor received");
813 return -1;
814 }
815
816 int res = sendAPCommand(fd, "#:GOS#", "getApStatusString");
817 if (res != TTY_OK)
818 {
820 "getApStatusString: unsuccessful write to telescope, %d", nbytes_write);
821 return res;
822 }
823
824 tty_read_section(fd, statusString, '#', LX200_TIMEOUT, &nbytes_read);
825 tcflush(fd, TCIFLUSH);
826 if (nbytes_read > 3)
827 {
828 statusString[nbytes_read - 1] = '\0';
829
830 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "getApStatusString: received bytes %d, [%s]",
831 nbytes_write, statusString);
832
833 return 0;
834 }
835
836 if (complain) DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getApStatusString: wrote, but nothing received.");
837
838 return -1;
839}
840
841int getApStatusString(int fd, char *statusString)
842{
843 // I seem to get intermittent failures.
844 // Try again on these after a 50ms delay, and the 250ms delay.
845 if (getApStatusStringInternal(fd, statusString, false) != TTY_OK)
846 {
847 const struct timespec timeout50ms = {0, 50000000L};
848 nanosleep(&timeout50ms, nullptr);
849 if (getApStatusStringInternal(fd, statusString, true) == TTY_OK)
850 return TTY_OK;
851 else
852 {
853 const struct timespec timeout250ms = {0, 250000000L};
854 nanosleep(&timeout250ms, nullptr);
855 return getApStatusStringInternal(fd, statusString, true);
856 }
857 }
858 return TTY_OK;
859}
860
861int check_lx200ap_status(int fd, char *parkStatus, char *slewStatus)
862{
863 char status_string[256];
864 int res = getApStatusString(fd, status_string);
865 if (res != TTY_OK) return res;
866
867 *parkStatus = status_string[0];
868 *slewStatus = status_string[3];
869 return TTY_OK;
870}
871
872// See above for the full list.
873// K: Mount Status '0'=Normal, '1'=Stalled, '2'=Low Power Supply,
874// '4'=Servo fault / number problem, '8'=Reserved (CP3 only)
875// K: Mount Status '0'=Normal 'Z'=Stalled, 'Y'=Low Power Supply, 'X'=Servo fault / number problem,
876// VCP4-P02-01 and later 'N'=CCW Internal Declination Limit or AE Limit, 'S'=CW Internal Declination Limit or AE Limit,
877// 'E'=East Internal RA Limit or AE Limit, 'W'=West Internal RA Limit or AE Limit,
878// 'z'=Kill Function has been issued
879const char *apMountStatus(const char *statusString)
880{
881 if (strlen(statusString) < 11)
882 return "????";
883 const char statusChar = statusString[10];
884 switch (statusChar)
885 {
886 case '0':
887 return "Normal";
888 case '1':
889 case 'Z':
890 return "Stalled";
891 case '2':
892 case 'Y':
893 return "Low Power Supply";
894 case '4':
895 case 'X':
896 return "Servo Fault";
897 case 'N':
898 return "CCW DEC or AE Limit";
899 case 'S':
900 return "CW DEC or AE Limit";
901 case 'E':
902 return "East RA or AE Limit";
903 case 'W':
904 return "West RA or AE Limit";
905 case 'z':
906 return "Kill Function issued";
907 case '8':
908 default:
909 return "";
910 }
911}
912
913bool apStatusParked(const char *statusString)
914{
915 return statusString[0] == 'P';
916}
917
918bool apStatusSlewing(const char *statusString)
919{
920 return statusString[3] != '0';
921}
922
923// The 14th character in the status string "N" tells up about the rate table.
924APRateTableState apRateTable(const char *statusString)
925{
926 if (strlen(statusString) >= 14)
927 {
928 switch (statusString[13])
929 {
930 case '0':
931 return AP_RATE_TABLE_0;
932 break;
933 case '1':
934 return AP_RATE_TABLE_1;
935 break;
936 case '2':
937 return AP_RATE_TABLE_2;
938 break;
939 case '3':
940 return AP_RATE_TABLE_3;
941 break;
942 default:
944 break;
945 }
946 }
948}
949
950
951// Doc for the :G_E command fom A-P:
952// Note that for CP3, must send G control-E but CP4 and CP5 will also accept G_E.
953// This function just sends the G control-E which should work for all three controllers.
954//
955// Get Mount Features
956// Command: :G<cntl>E#
957// Response: xxxx#
958// History: All firmware versions
959// Gets the bit mask associated with mount features.
960// Bit Weighting Meaning
961// 0 1 Mount Type: 0 = Equatorial Mount, 1 = Fork Mount
962// 1 2 0 = Normal Speed Range, 2 = Slew Scaling on Standard Rates ( >= 600x)
963// This function has been eliminated beginning P02-01, in favor of the rate tables.
964// 2 4 0 = Encoders not Supported, 4 = Encoders Supported
965// 3-5 Bit encoded indication of what encoder types are supported
966// 6 64 Motor Type: 0 = Servo Motors, 64 = Stepper Motors
967// 7 128 Encoder Reference: 0 = Clutch Dependent, 128 = Clutch Independent (ex. Mach2GTO)
968// This bit is only meaningful if bit 2 is set.
969// 8 256 0 = Modeling not Enabled, 256 = Modeling Enabled,
970// This bit is only meaningful in the GTOCP4, as modeling is always enabled in the GTOCP5,
971// and isn’t available for the GTOCP1-3
972// 9-31 (reserved for future use)
973int getApMountFeatures(int fd, bool *hasEncoder, bool *clutchAware)
974{
975 bool complain = false;
976 int nbytes_write = 0;
977 int nbytes_read = 0;
978 constexpr int RB_MAX_LEN = 256;
979 char readBuffer[RB_MAX_LEN];
980 *hasEncoder = false;
981 *clutchAware = false;
982
983 if (fd <= 0)
984 {
986 "getApStatusString: not a valid file descriptor received");
987 return TTY_READ_ERROR;
988 }
989
990 int res = sendAPCommand(fd, "#:G\005#", "getApStatusString");
991 if (res != TTY_OK)
992 {
994 "getApMountFeatures: unsuccessful write to telescope, %d", nbytes_write);
995 return res;
996 }
997
998 tty_read_section(fd, readBuffer, '#', LX200_TIMEOUT, &nbytes_read);
999 tcflush(fd, TCIFLUSH);
1000 if (nbytes_read > 1)
1001 {
1002 readBuffer[nbytes_read - 1] = '\0';
1003
1004 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "getApMountFeatures: received bytes %d, [%s]",
1005 nbytes_write, readBuffer);
1006 int value;
1007 if (sscanf(readBuffer, "%d", &value) > 0)
1008 {
1009 *hasEncoder = value & 4;
1010 *clutchAware = value & 128;
1011 }
1012
1013 return TTY_OK;
1014 }
1015
1016 if (complain) DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_ERROR, "getApReadBuffer: wrote, but nothing received.");
1017
1018 return TTY_READ_ERROR;
1019}
1020
1021bool apCanHome(int fd)
1022{
1023 bool hasEncoder = false;
1024 bool clutchAware = false;
1025 return (getApMountFeatures(fd, &hasEncoder, &clutchAware) == TTY_OK) &&
1026 hasEncoder && clutchAware;
1027}
1028
1029// This would just work with a clutch-aware encoder mount running a CP5. Currently only Mach2.
1031{
1032 return sendAPCommand(fd, "#$HA#", "AP Home and Sync");
1033}
1034
1035int isAPInitialized(int fd, bool *isInitialized)
1036{
1037 constexpr int RB_MAX_LEN = 256;
1038 char readBuffer[RB_MAX_LEN];
1039 int error_type;
1040 int nbytes_write = 0;
1041 int nbytes_read = 0;
1042
1043 DEBUGDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "Check initialized...");
1044
1045 if (fd <= 0)
1046 {
1048 "isAPInitialized: not a valid file descriptor received");
1049
1050 return -1;
1051 }
1052
1053 if ((error_type = tty_write_string(fd, "#:GR#", &nbytes_write)) != TTY_OK)
1054 {
1056 "isAPInitialized: unsuccessful write to telescope, %d", nbytes_write);
1057
1058 return error_type;
1059 }
1060
1061 error_type = tty_nread_section(fd, readBuffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read);
1062
1063
1064 if (nbytes_read < 1)
1065 {
1066 DEBUGFDEVICE(lx200ap_name, INDI::Logger::DBG_DEBUG, "RES ERROR <%d>", error_type);
1067 return error_type;
1068 }
1069
1070 readBuffer[nbytes_read - 1] = '\0';
1071
1072 if (!strcmp("00:00.0", readBuffer))
1073 *isInitialized = false;
1074 else if (!strcmp("00:00:00.0", readBuffer))
1075 *isInitialized = false; // not sure about this one--high precision 0.
1076 else
1077 *isInitialized = true; // Should I test further????
1078
1079 tcflush(fd, TCIFLUSH);
1080 return 0;
1081}
double ra
double dec
#define MAXINDIDEVICE
Definition indiapi.h:191
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:653
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition indicom.c:194
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void getSexComponents(double value, int *d, int *m, int *s)
Definition indicom.c:243
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
@ TTY_READ_ERROR
Definition indicom.h:55
#define MAXRBUF
Definition indidriver.c:52
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
int fd
#define RB_MAX_LEN
int selectAPMoveToRate(int fd, int moveToIndex)
char lx200ap_name[MAXINDIDEVICE]
bool apStatusParked(const char *statusString)
int check_lx200ap_connection(int fd)
int setAPUTCOffset(int fd, double hours)
int setAPSiteLatitude(int fd, double Lat)
int getAPWormPosition(int fd, int *position)
void set_lx200ap_name(const char *deviceName, unsigned int debug_level)
int setAPRATrackRate(int fd, double rate)
int apHomeAndSync(int fd)
int setAPObjectRA(int fd, double ra)
int APSyncCMR(int fd, char *matchedObject)
int setAPObjectAlt(int fd, double alt)
int swapAPButtons(int fd, int currentSwap)
int setAPObjectDEC(int fd, double dec)
int isAPInitialized(int fd, bool *isInitialized)
int APSendPulseCmd(int fd, int direction, int duration_msec)
int selectAPCenterRate(int fd, int centerIndex)
int APParkMount(int fd)
unsigned int AP_DBG_SCOPE
int selectAPSlewRate(int fd, int slewIndex)
const char * apMountStatus(const char *statusString)
int getApStatusString(int fd, char *statusString)
int check_lx200ap_status(int fd, char *parkStatus, char *slewStatus)
int APUnParkMount(int fd)
bool apCanHome(int fd)
int selectAPPECState(int fd, int pecstate)
int selectAPGuideRate(int fd, int guideRate)
APRateTableState apRateTable(const char *statusString)
int getApStatusStringInternal(int fd, char *statusString, bool complain)
int setAPObjectAZ(int fd, double az)
int APSyncCM(int fd, char *matchedObject)
int getApMountFeatures(int fd, bool *hasEncoder, bool *clutchAware)
#define MAX_LX200AP_PULSE_LEN
int getAPUTCOffset(int fd, double *value)
int setAPDETrackRate(int fd, double rate)
#define LX200_TIMEOUT
bool apStatusSlewing(const char *statusString)
int setAPSiteLongitude(int fd, double Long)
int selectAPV2CenterRate(int fd, int centerIndex, APRateTableState rateTable)
int sendAPCommand(int fd, const char *cmd, const char *comment)
int selectAPTrackingMode(int fd, int trackMode)
APRateTableState
@ AP_RATE_TABLE_1
@ AP_RATE_TABLE_2
@ AP_RATE_TABLE_DEFAULT
@ AP_RATE_TABLE_3
@ AP_RATE_TABLE_0
#define AP_PEC_OFF
#define AP_PEC_ON
#define AP_TRACKING_LUNAR
#define AP_TRACKING_OFF
#define AP_PEC_RECORD
#define AP_TRACKING_CUSTOM
#define AP_TRACKING_SIDEREAL
#define AP_TRACKING_SOLAR
int setStandardProcedure(int fd, const char *data)
@ LX200_WEST
Definition lx200driver.h:42
@ LX200_SOUTH
Definition lx200driver.h:44
@ LX200_NORTH
Definition lx200driver.h:41
@ LX200_EAST
Definition lx200driver.h:43
uint8_t cmd[4]
Definition pwc-ioctl.h:2