Instrument Neutral Distributed Interface INDI  2.0.2
lx200apdriver.cpp
Go to the documentation of this file.
1 #if 0
2 LX200 Astro - Physics Driver
3 Copyright (C) 2007 Markus Wildi
4 
5 This library is free software;
6 you can redistribute it and / or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation;
9 either
10 version 2.1 of the License, or (at your option) any later version.
11 
12 This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY;
14 without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17 
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library;
20 if not, write to the Free Software
21 Foundation, 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 
45 unsigned int AP_DBG_SCOPE;
46 
47 void set_lx200ap_name(const char *deviceName, unsigned int debug_level)
48 {
49  strncpy(lx200ap_name, deviceName, MAXINDIDEVICE);
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.
101 int 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#";
112  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
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 
231 int 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 
244  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
245 
246  return (setStandardProcedure(fd, cmd));
247 }
248 
249 /* wildi Valid set Values are positive, add error condition */
250 
251 int 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 
261  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
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
269 int 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 
280  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
281 
282  return (setStandardProcedure(fd, cmd));
283 }
284 int 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#";
292  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
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 
312 int 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#";
320  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
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 
341 int sendAPCommand(int fd, const char *cmd, const char *comment)
342 {
343  int error_type;
344  int nbytes_write = 0;
345 
347  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
348 
349  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
350  return error_type;
351  return 0;
352 }
353 
354 int 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.
375 int 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 
408 int 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 
426 int 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 
442 int selectAPTrackingMode(int fd, int trackMode)
443 {
444  switch (trackMode)
445  {
447  return sendAPCommand(fd, "#:RT2#", "selectAPTrackingMode: Setting tracking mode to sidereal.");
448  case AP_TRACKING_SOLAR:
449  return sendAPCommand(fd, "#:RT1#", "selectAPTrackingMode: Setting tracking mode to solar.");
450  case AP_TRACKING_LUNAR:
451  return sendAPCommand(fd, "#:RT0#", "selectAPTrackingMode: Setting tracking mode to lunar.");
452  case AP_TRACKING_CUSTOM:
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 
464 int 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 
480 int 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 
494 int 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 
508  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
509 
510  return (setStandardProcedure(fd, cmd));
511 }
512 
513 int 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 
523  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
524 
525  return (setStandardProcedure(fd, cmd));
526 }
527 
528 // Set the longitude.
529 int 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 
541  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
542 
543  return (setStandardProcedure(fd, cmd));
544 }
545 
546 // Set the latitude.
547 int 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 
557  DEBUGFDEVICE(lx200ap_name, AP_DBG_SCOPE, "CMD <%s>", cmd);
558 
559  return (setStandardProcedure(fd, cmd));
560 }
561 
562 int 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 
610 int 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 
659 int 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 
693 int APParkMount(int fd)
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.
709 int 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 
729 int 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 Satus (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 
804 int 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 
841 int getApStatusString(int fd, char *statusString)
842 {
843  // I seem to get intermittant 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 
861 int 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
879 const 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 
913 bool apStatusParked(const char *statusString)
914 {
915  return statusString[0] == 'P';
916 }
917 
918 bool 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.
924 APRateTableState 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:
943  return AP_RATE_TABLE_DEFAULT;
944  break;
945  }
946  }
947  return AP_RATE_TABLE_DEFAULT;
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)
973 int 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 
1021 bool 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 
1035 int 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:193
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 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:205
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void getSexComponents(double value, int *d, int *m, int *s)
Definition: indicom.c:254
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
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:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
@ TTY_READ_ERROR
Definition: indicom.h:151
#define DEBUGDEVICE(device, priority, msg)
Definition: indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition: indilogger.h:61
#define MAXRBUF
Definition: indiserver.cpp:102
int fd
Definition: intelliscope.c:43
#define RB_MAX_LEN
Definition: lx200_OnStep.h:147
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)
const char * apMountStatus(const char *statusString)
int selectAPCenterRate(int fd, int centerIndex)
int APParkMount(int fd)
unsigned int AP_DBG_SCOPE
int selectAPSlewRate(int fd, int slewIndex)
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
Definition: lx200apdriver.h:58
@ AP_RATE_TABLE_1
Definition: lx200apdriver.h:60
@ AP_RATE_TABLE_2
Definition: lx200apdriver.h:61
@ AP_RATE_TABLE_DEFAULT
Definition: lx200apdriver.h:63
@ AP_RATE_TABLE_3
Definition: lx200apdriver.h:62
@ AP_RATE_TABLE_0
Definition: lx200apdriver.h:59
#define AP_PEC_OFF
Definition: lx200apdriver.h:38
#define AP_PEC_ON
Definition: lx200apdriver.h:39
#define AP_TRACKING_LUNAR
Definition: lx200apdriver.h:34
#define AP_TRACKING_OFF
Definition: lx200apdriver.h:36
#define AP_PEC_RECORD
Definition: lx200apdriver.h:40
#define AP_TRACKING_CUSTOM
Definition: lx200apdriver.h:35
#define AP_TRACKING_SIDEREAL
Definition: lx200apdriver.h:32
#define AP_TRACKING_SOLAR
Definition: lx200apdriver.h:33
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
__u8 cmd[4]
Definition: pwc-ioctl.h:2