Instrument Neutral Distributed Interface INDI  2.0.2
pmc8driver.cpp
Go to the documentation of this file.
1 /*
2  INDI Explore Scientific PMC8 driver
3 
4  Copyright (C) 2017 Michael Fulbright
5  Additional contributors:
6  Thomas Olson, Copyright (C) 2019
7  Karl Rees, Copyright (C) 2019-2023
8  Martin Ruiz, Copyright (C) 2023
9 
10  Based on IEQPro driver.
11 
12  This library is free software; you can redistribute it and/or
13  modify it under the terms of the GNU Lesser General Public
14  License as published by the Free Software Foundation; either
15  version 2.1 of the License, or (at your option) any later version.
16 
17  This library is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20  Lesser General Public License for more details.
21 
22  You should have received a copy of the GNU Lesser General Public
23  License along with this library; if not, write to the Free Software
24  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 */
26 
27 #include "pmc8driver.h"
28 
29 #include "indicom.h"
30 #include "indilogger.h"
31 #include "inditelescope.h"
32 
33 #include <libnova/julian_day.h>
34 #include <libnova/sidereal_time.h>
35 
36 #include <math.h>
37 #include <string.h>
38 #include <termios.h>
39 #include <unistd.h>
40 
41 #include <sys/ioctl.h>
42 
43 // only used for test timing of pulse guiding
44 #include <sys/time.h>
45 
46 #define PMC8_TIMEOUT 5 /* FD timeout in seconds */
47 
48 #define PMC8_SIMUL_VERSION_RESP "ESGvES06B9T9"
49 
50 // MOUNT_G11
51 #define PMC8_G11_AXIS0_SCALE 4608000.0
52 #define PMC8_G11_AXIS1_SCALE 4608000.0
53 // MOUNT_EXOS2
54 #define PMC8_EXOS2_AXIS0_SCALE 4147200.0
55 #define PMC8_EXOS2_AXIS1_SCALE 4147200.0
56 // MOUNT_iEXOS100
57 #define PMC8_iEXOS100_AXIS0_SCALE 4147200.0
58 #define PMC8_iEXOS100_AXIS1_SCALE 4147200.0
59 
60 // Need to initialize to some value, or certain clients (e.g., KStars Lite) freak out
63 
64 #define ARCSEC_IN_CIRCLE 1296000.0
65 
66 // Reference says 2621.44 counts, which then needs to be multiplied by 25 (so actually 16^4-1)
67 // However, on Exos2 62500 (F424) is reported when slewing
68 #define PMC8_MAX_PRECISE_MOTOR_RATE 62500
69 
70 // any guide pulses less than this are ignored as it will not result in any actual motor motion
71 #define PMC8_PULSE_GUIDE_MIN_MS 20
72 
73 // guide pulses longer than this require using a timer
74 #define PMC8_PULSE_GUIDE_MAX_NOTIMER 250
75 
76 #define PMC8_MAX_RETRIES 3 /*number of times to retry reading a response */
77 #define PMC8_RETRY_DELAY 30000 /* how long to wait before retrying i/o */
78 #define PMC8_MAX_IO_ERROR_THRESHOLD 2 /* how many consecutive read timeouts before trying to reset the connection */
79 
80 #define PMC8_RATE_SIDEREAL 15.000
81 #define PMC8_RATE_LUNAR 14.451
82 #define PMC8_RATE_SOLAR 14.959
83 #define PMC8_RATE_KING 14.996
84 
86 bool pmc8_debug = false;
87 bool pmc8_simulation = false;
88 bool pmc8_isRev2Compliant = false;
89 bool pmc8_reconnect_flag = false;
90 bool pmc8_goto_resume = true;
92 char pmc8_device[MAXINDIDEVICE] = "PMC8";
93 double pmc8_latitude = 0; // must be kept updated by pmc8.cpp when it is changed!
94 double pmc8_longitude = 0; // must be kept updated by pmc8.cpp when it is changed!
97 int pmc8_east_dir = 1; // 1 is for northern hemisphere, switch to 0 for southern
99 
100 // state variable for driver based pulse guiding
101 typedef struct PulseGuideState
102 {
103  bool pulseguideactive = false;
104  bool fakepulse = false;
105  int ms;
106  long long pulse_start_us;
107  double cur_rate;
108  int cur_dir;
109  double new_rate;
110  int new_dir;
112 
113 // need one for NS and EW pulses which may be simultaneous
115 
116 struct
117 {
118  double ra;
119  double dec;
122  double trackRate;
123  double moveRate;
124  double guide_rate;
126 
127 // convert mount count to 6 character two complement hex string
128 void convert_motor_counts_to_hex(int val, char *hex)
129 {
130  unsigned tmp;
131  char h[16];
132 
133  if (val < 0)
134  {
135  tmp = abs(val);
136  tmp = ~tmp;
137  tmp++;
138  }
139  else
140  {
141  tmp = val;
142  }
143 
144  sprintf(h, "%08X", tmp);
145 
146  strcpy(hex, h + 2);
147 
148  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_motor_counts_to_hex val=%d, h=%s, hex=%s", val, h, hex);
149 }
150 
151 // convert rate in arcsec/sidereal_second to internal PMC8 precise motor rate for RA axis tracking ONLY
152 bool convert_precise_rate_to_motor(double rate, int *mrate)
153 {
154 
155  *mrate = round(25 * rate * (PMC8_AXIS0_SCALE / ARCSEC_IN_CIRCLE));
156 
157  if (*mrate > PMC8_MAX_PRECISE_MOTOR_RATE)
158  {
159  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "requested tracking motor rate %d exceeds maximum, using %d", *mrate,
162  }
163  else if (*mrate < -PMC8_MAX_PRECISE_MOTOR_RATE)
164  {
165  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "requested tracking motor rate %d exceeds maximum, using %d", *mrate,
167  *mrate = -PMC8_MAX_PRECISE_MOTOR_RATE;
168  }
169 
170  return true;
171 }
172 
173 // convert rate in arcsec/sidereal_second to internal PMC8 precise motor rate for RA axis tracking ONLY
174 bool convert_precise_motor_to_rate(int mrate, double *rate)
175 {
176  *rate = ((double)mrate) * (ARCSEC_IN_CIRCLE / PMC8_AXIS0_SCALE) / 25;
177 
178  return true;
179 }
180 
181 // convert rate in arcsec/sidereal_second to internal PMC8 motor rate for move action (not slewing)
182 bool convert_move_rate_to_motor(float rate, int *mrate)
183 {
184 
185  float capped_move_rate = rate;
186  if (rate > PMC8_MAX_MOVE_RATE)
187  capped_move_rate = PMC8_MAX_MOVE_RATE;
188  else if (rate < -PMC8_MAX_MOVE_RATE)
189  capped_move_rate = -PMC8_MAX_MOVE_RATE;
190 
191  *mrate = (int)(capped_move_rate * (PMC8_AXIS0_SCALE / ARCSEC_IN_CIRCLE));
192 
193  return true;
194 }
195 
196 // convert rate internal PMC8 motor rate to arcsec/sec for move action (not slewing)
197 bool convert_motor_rate_to_move_rate(int mrate, double *rate)
198 {
199  *rate = ((double)mrate) * ARCSEC_IN_CIRCLE / PMC8_AXIS0_SCALE;
200 
201  return true;
202 }
203 
205 {
206  switch(index)
207  {
208  case 0: // LosMandy G11
211  break;
212  case 1: // EXOS2
215  break;
216  case 2: // iEXOS100
219  break;
220  default:
221  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need To Select a Mount");
222  break;
223  }
224 }
225 
226 void set_pmc8_debug(bool enable)
227 {
228  pmc8_debug = enable;
229 }
230 
231 void set_pmc8_simulation(bool enable)
232 {
233  pmc8_simulation = enable;
234  if (enable)
235  simPMC8Data.guide_rate = 0.5;
236 }
237 
238 void set_pmc8_device(const char *name)
239 {
240  strncpy(pmc8_device, name, MAXINDIDEVICE);
241 }
242 
243 void set_pmc8_location(double latitude, double longitude)
244 {
245  pmc8_latitude = latitude;
246  pmc8_longitude = longitude;
247 
248  pmc8_east_dir = (latitude < 0) ? 0 : 1;
249 
250  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Set PMC8 'lowlevel' lat:%f long:%f", pmc8_latitude, pmc8_longitude);
251 }
252 
254 {
255  simPMC8Info.systemStatus = value;
256 
257  if (value == ST_PARKED)
258  {
259  double lst;
260  double ra;
261 
263 
264  ra = lst + 6;
265  if (ra > 24)
266  ra -= 24;
267 
269  if (pmc8_latitude <0)
270  set_pmc8_sim_dec(-90.0);
271  else
272  set_pmc8_sim_dec(90.0);
273 
274  }
275 }
276 
278 {
279  simPMC8Data.trackRate = value;
280 }
281 
282 void set_pmc8_sim_move_rate(int value)
283 {
284  simPMC8Data.moveRate = value;
285 }
286 
287 void set_pmc8_sim_ra(double ra)
288 {
289  simPMC8Data.ra = ra;
290 }
291 
292 void set_pmc8_sim_dec(double dec)
293 {
294  simPMC8Data.dec = dec;
295 }
296 
298 {
299  pmc8_connection = connection;
300 
301  if (connection == PMC8_ETHERNET)
302  {
303  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Connecting to PMC8 via Ethernet.");
304  }
305  else
306  {
308  "Connecting to PMC8 via standard Serial cable. Please wait 15 seconds for mount to reset.");
310  "Connecting to PMC8 via Serial. Autodecting cable type. This could take up to 30 seconds.");
311  else DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Connecting to PMC8 via inverted Serial.");
312  }
313 
314  if (connection != PMC8_SERIAL_STANDARD)
315  {
316  for (int i = 0; i < 2; i++)
317  {
318  if (i) DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Retrying...");
319 
320  if (detect_pmc8(fd)) return true;
321  usleep(PMC8_RETRY_DELAY);
322  }
323  }
324 
325  if ((connection == PMC8_SERIAL_STANDARD) || (connection == PMC8_SERIAL_AUTO))
326  {
327  // If they're not using a custom-configured cable, we need to clear DTR for serial to start working
328  // But this resets the PMC8, so only do it after we've already checked for connection
329  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Attempting to clear DTR for standard cable.");
330  int serial = TIOCM_DTR;
331  ioctl(fd, TIOCMBIC, &serial);
332 
333  // when we clear DTR, the PMC8 will respond with initialization screen, so may need read several times
334  for (int i = 0; i < 2; i++)
335  {
336  if (i) DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_SESSION, "Retrying...");
337 
338  if (detect_pmc8(fd))
339  {
340  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_WARNING, "Connected to PMC8 using a standard-configured FTDI cable."
341  "Your mount will reset and lose its position anytime you disconnect and reconnect."
342  "See http://indilib.org/devices/telescopes/explore-scientific-g11-pmc-eight/ ");
343  return true;
344  }
345  usleep(PMC8_RETRY_DELAY);
346  }
347  }
348 
350  "check_pmc8_connection(): Error connecting. Check power and connection settings.");
351 
352  return false;
353 }
354 
355 bool detect_pmc8(int fd)
356 {
357  char initCMD[] = "ESGv!";
358  int errcode = 0;
359  char errmsg[MAXRBUF];
360  char response[64];
361  int nbytes_read = 0;
362  int nbytes_written = 0;
363 
364  if (pmc8_simulation)
365  {
366  strcpy(response, PMC8_SIMUL_VERSION_RESP);
367  nbytes_read = strlen(response);
368  }
369  else
370  {
371  if ((errcode = send_pmc8_command(fd, initCMD, strlen(initCMD), &nbytes_written)) != TTY_OK)
372  {
373  tty_error_msg(errcode, errmsg, MAXRBUF);
374  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error connecting on write: %s", errmsg);
375  return false;
376  }
377 
378  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGv")))
379  {
380  tty_error_msg(errcode, errmsg, MAXRBUF);
381  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Error connecting on read: %s", errmsg);
382  return false;
383  }
384  }
385 
386  // return true if valid firmware response
387  return (!strncmp(response, "ESGvES", 6));
388 }
389 
391 {
392  // Only one model for now
393  info->Model.assign("PMC-Eight");
394 
395  // Set the mount type from firmware if we can (instead of relying on interface)
396  // older firmware has type in firmware string
398  {
399  INDI_UNUSED(fd);
400 
401  if (strstr(info->MainBoardFirmware.c_str(), "G11"))
402  {
403  info->MountType = MOUNT_G11;
404  }
405  else if (strstr(info->MainBoardFirmware.c_str(), "EXOS2"))
406  {
407  info->MountType = MOUNT_EXOS2;
408  }
409  else if (strstr(info->MainBoardFirmware.c_str(), "ES1A"))
410  {
411  info->MountType = MOUNT_iEXOS100;
412  }
413  }
414  else
415  {
416  //for newer firmware, need to use ESGi to get mount type
417  char cmd[] = "ESGi!";
418  int errcode = 0;
419  char errmsg[MAXRBUF];
420  char response[64];
421  int nbytes_read = 0;
422  int nbytes_written = 0;
423 
424  if (pmc8_simulation)
425  {
426  strcpy(response, PMC8_SIMUL_VERSION_RESP);
427  nbytes_read = strlen(response);
428  }
429  else
430  {
431  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
432  {
433  tty_error_msg(errcode, errmsg, MAXRBUF);
435  return false;
436  }
437 
438  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGi")))
439  {
440  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_main_firmware(): Error reading response.");
441  return false;
442  }
443 
444  //ESGi response should be 31 characters
445  if (nbytes_read >= 31)
446  {
447  //locate P9 code in response
448  char num_str[3] = {0};
449  strncat(num_str, response + 20, 2);
450  int p9 = (int)strtol(num_str, nullptr, 10);
451 
452  // Set mount type based on P9 code
453  if (p9 <= 1) info->MountType = MOUNT_iEXOS100;
454  // these codes are reserved. I'm assuming for something like iExos100, so let's go with that
455  else if (p9 <= 3)
456  {
457  info->MountType = MOUNT_iEXOS100;
458  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Unrecognized device code #%d. Treating as iEXOS100.", p9);
459  }
460  else if (p9 <= 7) info->MountType = MOUNT_G11;
461  else if (p9 <= 11) info->MountType = MOUNT_EXOS2;
462  // unrecognized code. Just going to guess and treat as iExos100.
463  else
464  {
465  info->MountType = MOUNT_iEXOS100;
466  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Unrecognized device code #%d. Treating as iEXOS100.", p9);
467  }
468 
469  }
470  else
471  {
473  "Could not detect device type. Only received #%d bytes, expected at least 31.", nbytes_read);
474  return false;
475  }
476 
477  tcflush(fd, TCIFLUSH);
478  }
479  }
480  // update mount parameters
482  return true;
483 }
484 
486 {
487  char cmd[] = "ESGv!";
488  char board[64];
489  int errcode = 0;
490  char errmsg[MAXRBUF];
491  char response[64];
492  int nbytes_read = 0;
493  int nbytes_written = 0;
494 
495  if (pmc8_simulation)
496  {
497  strcpy(response, PMC8_SIMUL_VERSION_RESP);
498  nbytes_read = strlen(response);
499  }
500  else
501  {
502  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
503  {
504  tty_error_msg(errcode, errmsg, MAXRBUF);
506  return false;
507  }
508 
509  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGv")))
510  {
511  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_main_firmware(): Error reading response.");
512  return false;
513  }
514  }
515 
516  // prior to v2, minimum size firmware string is 12 (for iExos100), 14 for others, but can be up to 20
517  // post v2, can be 50+
518  if (nbytes_read >= 12)
519  {
520 
521  // strip ESGvES from string when getting firmware version
522  strncpy(board, response + 6, nbytes_read - 7);
523  info->MainBoardFirmware.assign(board, nbytes_read - 7);
524 
525  // Assuming version strings longer than 24 must be version 2.0 and up
526  if (nbytes_read > 24) info->IsRev2Compliant = pmc8_isRev2Compliant = true;
527 
528  tcflush(fd, TCIFLUSH);
529 
530  return true;
531  }
532 
534  "Could not read firmware. Only received #%d bytes, expected at least 12.", nbytes_read);
535  return false;
536 }
537 
539 {
540  bool rc = false;
541 
542  rc = get_pmc8_main_firmware(fd, info);
543 
544  if (rc == false)
545  return rc;
546 
547  rc = get_pmc8_model(fd, info);
548 
549  return rc;
550 }
551 
552 // return move rate in arcsec / sec
553 bool get_pmc8_move_rate_axis(int fd, PMC8_AXIS axis, double &rate)
554 {
555  char cmd[32];
556  int errcode = 0;
557  char errmsg[MAXRBUF];
558  char response[16];
559  int nbytes_read = 0;
560  int nbytes_written = 0;
561 
562  snprintf(cmd, sizeof(cmd), "ESGr%d!", axis);
563 
564  if (pmc8_simulation)
565  {
566  if (axis == PMC8_AXIS_RA)
567  rate = simPMC8Data.trackRate;
568  else if (axis == PMC8_AXIS_DEC)
569  rate = 0; // DEC tracking not supported yet
570  else
571  return false;
572 
573  return true;
574  }
575 
576  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
577  {
578  tty_error_msg(errcode, errmsg, MAXRBUF);
580  return false;
581  }
582 
583  cmd[5] = '\0';
584 
585  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
586  {
587  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting Move Rate");
588  return false;
589  }
590 
591  if (nbytes_read != 10)
592  {
593  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get move rate cmd response incorrect");
594  return false;
595  }
596 
597  char num_str[16] = {0};
598 
599  strcpy(num_str, "0X");
600  strncat(num_str, response + 5, 6);
601 
602  int mrate = (int)strtol(num_str, nullptr, 0);
603 
604  convert_motor_rate_to_move_rate(mrate, &rate);
605 
606  return true;
607 }
608 
609 bool get_pmc8_direction_axis(int fd, PMC8_AXIS axis, int &dir)
610 {
611  char cmd[32];
612  int errcode = 0;
613  char errmsg[MAXRBUF];
614  char response[16];
615  int nbytes_read = 0;
616  int nbytes_written = 0;
617 
618  snprintf(cmd, sizeof(cmd), "ESGd%d!", axis);
619 
620  if (pmc8_simulation)
621  {
622  if (axis == PMC8_AXIS_RA)
623  dir = simPMC8Data.raDirection;
624  else if (axis == PMC8_AXIS_DEC)
625  dir = simPMC8Data.decDirection;
626  else
627  return false;
628 
629  return true;
630  }
631 
632  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
633  {
634  tty_error_msg(errcode, errmsg, MAXRBUF);
636  return false;
637  }
638 
639  cmd[5] = '\0';
640 
641  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
642  {
643  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting direction axis");
644  return false;
645  }
646 
647  if (nbytes_read != 7)
648  {
649  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get dir cmd response incorrect");
650  return false;
651  }
652 
653  char num_str[16] = {0};
654 
655  strncat(num_str, response + 5, 2);
656 
657  dir = (int)strtol(num_str, nullptr, 0);
658 
659  return true;
660 }
661 
662 // if fast is true dont wait on response! Used for psuedo-pulse guide
663 // NOTE that this will possibly mean the response will be read by a following command if it is called before
664 // response comes from controller, since next command will flush before data is in buffer!
665 bool set_pmc8_direction_axis(int fd, PMC8_AXIS axis, int dir, bool fast)
666 {
667 
668  char cmd[32], expresp[32];
669  int errcode = 0;
670  char errmsg[MAXRBUF];
671  char response[16];
672  int nbytes_read = 0;
673  int nbytes_written = 0;
674 
675  snprintf(cmd, sizeof(cmd), "ESSd%d%d!", axis, dir);
676 
677  if (pmc8_simulation)
678  {
679  if (axis == PMC8_AXIS_RA)
680  simPMC8Data.raDirection = (PMC8_DIRECTION) dir;
681  else if (axis == PMC8_AXIS_DEC)
682  simPMC8Data.decDirection = (PMC8_DIRECTION) dir;
683  else
684  return false;
685 
686  return true;
687  }
688 
689  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
690  {
691  tty_error_msg(errcode, errmsg, MAXRBUF);
693  return false;
694  }
695 
696  if (fast)
697  {
698  return true;
699  }
700 
701  snprintf(expresp, sizeof(expresp), "ESGd%d%d!", axis, dir);
702 
703  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
704  {
705  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis get dir cmd response incorrect: expected=%s", expresp);
706  return false;
707  }
708 
709  return true;
710 }
711 
712 bool get_pmc8_is_scope_slewing(int fd, bool &isslew)
713 {
714  double rarate;
715  double decrate;
716  bool rc;
717 
718  rc = get_pmc8_move_rate_axis(fd, PMC8_AXIS_RA, rarate);
719  if (!rc)
720  {
721  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_is_scope_slewing(): Error reading RA move rate");
722  return false;
723  }
724 
725  rc = get_pmc8_move_rate_axis(fd, PMC8_AXIS_DEC, decrate);
726  if (!rc)
727  {
728  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "get_pmc8_is_scope_slewing(): Error reading DEC move rate");
729  return false;
730  }
731 
732  if (pmc8_simulation)
733  {
734  isslew = (simPMC8Info.systemStatus == ST_SLEWING);
735  }
736  else
737  {
738  isslew = ((rarate > PMC8_MAX_TRACK_RATE) || (decrate >= PMC8_MAX_TRACK_RATE));
739  }
740 
741  return true;
742 }
743 
744 // set move speed in terms of how many times sidereal
745 bool set_pmc8_move_rate_axis(int fd, PMC8_DIRECTION dir, int reqrate)
746 {
747  int rate = reqrate;
748 
749  if (rate > PMC8_MAX_MOVE_RATE)
750  rate = PMC8_MAX_MOVE_RATE;
751  else if (rate < -PMC8_MAX_MOVE_RATE)
752  rate = -PMC8_MAX_MOVE_RATE;
753 
754  switch (dir)
755  {
756  case PMC8_S:
757  rate = -rate;
758  [[fallthrough]];
759  case PMC8_N:
760  return set_pmc8_custom_dec_move_rate(fd, rate);
761  case PMC8_E:
762  rate = -rate;
763  [[fallthrough]];
764  case PMC8_W:
765  return set_pmc8_custom_ra_move_rate(fd, rate);
766  }
767 
768  return false;
769 }
770 
772 {
773  bool rc;
774 
775  // stop tracking
777  if (!rc)
778  {
779  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping RA axis!");
780  return false;
781  }
782 
783  return true;
784 }
785 
786 // get current (precise) tracking rate in arcsec/sec
787 bool get_pmc8_track_rate(int fd, double &rate)
788 {
789  char cmd[32];
790  int errcode = 0;
791  char errmsg[MAXRBUF];
792  char response[16];
793  int nbytes_read = 0;
794  int nbytes_written = 0;
795 
796  snprintf(cmd, sizeof(cmd), "ESGx!");
797 
798  if (pmc8_simulation)
799  {
800  rate = simPMC8Data.trackRate;
801  return true;
802  }
803 
804  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
805  {
806  tty_error_msg(errcode, errmsg, MAXRBUF);
808  return false;
809  }
810 
811  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGx")))
812  {
813  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting Tracking Rate");
814  return false;
815  }
816 
817  if (nbytes_read != 9)
818  {
819  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Get track rate cmd response incorrect");
820  return false;
821  }
822 
823  char num_str[16] = {0};
824 
825  strcpy(num_str, "0X");
826  strncat(num_str, response + 4, 4);
827 
828  int mrate = (int)strtol(num_str, nullptr, 0);
829  convert_precise_motor_to_rate(mrate, &rate);
830 
831  return true;
832 }
833 
834 bool get_pmc8_tracking_data(int fd, double &rate, uint8_t &mode)
835 {
836  if (!get_pmc8_track_rate(fd, rate)) return false;
838  return true;
839 }
840 
841 
843 {
844  int tmotor, refmotor;
845  uint8_t mode;
846 
847  //get precise motor rate
848  convert_precise_rate_to_motor(rate, &tmotor);
849 
850  //now check what sidereal would be
852  if (tmotor == refmotor) mode = PMC8_TRACK_SIDEREAL;
853  else
854  {
855 
856  //now check lunar
858  if (tmotor == refmotor) mode = PMC8_TRACK_LUNAR;
859  else
860  {
861 
862  //now check solar
864  if (tmotor == refmotor) mode = PMC8_TRACK_SOLAR;
865  else
866  {
867 
868  //now check king
870  if (tmotor == refmotor) mode = PMC8_TRACK_KING;
871  // must be custom
872  else mode = PMC8_TRACK_CUSTOM;
873  }
874  }
875  }
876  return mode;
877 }
878 
879 
880 // set speed for move action (MoveNS/MoveWE) NOT slews! This version DOESNT handle direction and expects a motor rate!
881 // if fast is true dont wait on response! Used for psuedo-pulse guide
882 // NOTE that this will possibly mean the response will be read by a following command if it is called before
883 // response comes from controller, since next command will flush before data is in buffer!
884 bool set_pmc8_axis_motor_rate(int fd, PMC8_AXIS axis, int mrate, bool fast)
885 {
886  char cmd[24];
887  int errcode = 0;
888  char errmsg[MAXRBUF];
889  char response[24];
890  int nbytes_read = 0;
891  int nbytes_written = 0;
892 
893  snprintf(cmd, sizeof(cmd), "ESSr%d%04X!", axis, mrate);
894 
895  if (pmc8_simulation)
896  {
897  return true;
898  }
899 
900  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
901  {
902  tty_error_msg(errcode, errmsg, MAXRBUF);
904  return false;
905  }
906 
907  if (fast)
908  {
909  return true;
910  }
911 
912  snprintf(cmd, sizeof(cmd), "ESGr%d", axis);
913 
914  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
915  {
916  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting axis motor rate");
917  return false;
918  }
919 
920  if (nbytes_read == 10)
921  {
922  tcflush(fd, TCIFLUSH);
923  return true;
924  }
925 
926  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 10.", nbytes_read);
927  return false;
928 }
929 
930 // set speed for move action (MoveNS/MoveWE) NOT slews! This version accepts arcsec/sec as rate.
931 // also handles direction
932 bool set_pmc8_axis_move_rate(int fd, PMC8_AXIS axis, float rate)
933 {
934  bool rc;
935  int motor_rate;
936 
937  // set direction
938  if (rate < 0)
939  rc = set_pmc8_direction_axis(fd, axis, 0, false);
940  else
941  rc = set_pmc8_direction_axis(fd, axis, 1, false);
942 
943  if (!rc)
944  return rc;
945 
946  if (!convert_move_rate_to_motor(fabs(rate), &motor_rate))
947  {
948  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", rate);
949  return false;
950  }
951 
952  rc = set_pmc8_axis_motor_rate(fd, axis, motor_rate, false);
953 
954  if (pmc8_simulation)
955  {
956  simPMC8Data.moveRate = rate;
957  return true;
958  }
959 
960  return rc;
961 }
962 
963 #if 0
964 bool set_pmc8_track_enabled(int fd, bool enabled)
965 {
966  char cmd[32];
967  int errcode = 0;
968  char errmsg[MAXRBUF];
969  char response[8];
970  int nbytes_read = 0;
971  int nbytes_written = 0;
972 
973  snprintf(cmd, 32, ":ST%d#", enabled ? 1 : 0);
974 
976 
977  if (pmc8_simulation)
978  {
979  // FIXME - need to implement pmc8 track enabled sim
980  // simPMC8Info.systemStatus = enabled ? ST_TRACKING_PEC_ON : ST_STOPPED;
981  // strcpy(response, "1");
982  // nbytes_read = strlen(response);
983 
984  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement pmc8 track enabled sim");
985  return false;
986  }
987  else
988  {
989  // determine current tracking mode
990 
991  // return SetTrackMode(enabled ? IUFindOnSwitchIndex(&TrackModeSP) : AP_TRACKING_OFF);
992  }
993 }
994 #endif
995 
996 bool set_pmc8_track_mode(int fd, uint8_t mode)
997 {
998  float ratereal = 0;
999 
1000  switch (mode)
1001  {
1002  case PMC8_TRACK_SIDEREAL:
1003  ratereal = PMC8_RATE_SIDEREAL;
1004  break;
1005  case PMC8_TRACK_LUNAR:
1006  ratereal = PMC8_RATE_LUNAR;
1007  break;
1008  case PMC8_TRACK_SOLAR:
1009  ratereal = PMC8_RATE_SOLAR;
1010  break;
1011  case PMC8_TRACK_KING:
1012  ratereal = PMC8_RATE_KING;
1013  break;
1014  default:
1015  return false;
1016  break;
1017  }
1018 
1019  if (!set_pmc8_direction_axis(fd, PMC8_AXIS_RA, pmc8_east_dir, false)) return false;
1020  return set_pmc8_custom_ra_track_rate(fd, ratereal);
1021 }
1022 
1023 // start tracking at a precision track rate
1024 bool set_pmc8_ra_tracking(int fd, double rate)
1025 {
1026  //set right direction
1027  int direction = pmc8_east_dir;
1028  if (rate < 0) direction = !direction;
1029  if (!set_pmc8_direction_axis(fd, PMC8_AXIS_RA, direction, false)) return false;
1030 
1031  //then set rate
1032  return (set_pmc8_custom_ra_track_rate(fd, fabs(rate)));
1033 }
1034 
1035 // just set the precision track rate - for when we've already set tracking direction
1036 bool set_pmc8_custom_ra_track_rate(int fd, double rate)
1037 {
1038  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_ra_track_rate() called rate=%f ", rate);
1039 
1040  char cmd[24];
1041  int errcode = 0;
1042  char errmsg[MAXRBUF];
1043  char response[24];
1044  int nbytes_read = 0;
1045  int nbytes_written = 0;
1046  int rateval;
1047 
1048  if (!convert_precise_rate_to_motor(rate, &rateval))
1049  {
1050  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", rate);
1051  return false;
1052  }
1053 
1054  snprintf(cmd, sizeof(cmd), "ESTr%04X!", rateval);
1055 
1056  if (pmc8_simulation)
1057  {
1058  simPMC8Data.trackRate = rate;
1059  return true;
1060  }
1061  else
1062  {
1063  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1064  {
1065  tty_error_msg(errcode, errmsg, MAXRBUF);
1067  return false;
1068  }
1069 
1070  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, "ESGx")))
1071  {
1072  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting custom RA track rate");
1073  return false;
1074  }
1075  }
1076 
1077  if (nbytes_read != 9)
1078  {
1079  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Only received #%d bytes, expected 9.", nbytes_read);
1080  return false;
1081  }
1082 
1083  tcflush(fd, TCIFLUSH);
1084 
1085  // set direction to 1
1086  // return set_pmc8_direction_axis(fd, PMC8_AXIS_RA, 1, false);
1087  return true;
1088 }
1089 
1090 #if 0
1091 bool set_pmc8_custom_dec_track_rate(int fd, double rate)
1092 {
1093  bool rc;
1094 
1095  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_dec_track_rate() called rate=%f ", rate);
1096 
1097  if (pmc8_simulation)
1098  {
1099  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_track_rate simulation not implemented");
1100 
1101  rc = false;
1102  }
1103  else
1104  {
1105  rc = set_pmc8_axis_rate(fd, PMC8_AXIS_DEC, rate);
1106  }
1107 
1108  return rc;
1109 }
1110 #else
1111 bool set_pmc8_custom_dec_track_rate(int fd, double rate)
1112 {
1113  INDI_UNUSED(fd);
1114  INDI_UNUSED(rate);
1115 
1116  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_track_rate not implemented!");
1117  return false;
1118 }
1119 #endif
1120 
1121 bool set_pmc8_custom_ra_move_rate(int fd, double rate)
1122 {
1123  bool rc;
1124 
1125  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_ra move_rate() called rate=%f ", rate);
1126 
1127  // safe guard for now - only all use to STOP slewing or MOVE commands with this
1128  if (fabs(rate) > PMC8_MAX_MOVE_RATE)
1129  {
1130  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_ra_move rate only supports low rates currently");
1131 
1132  return false;
1133  }
1134 
1136 
1137  return rc;
1138 }
1139 
1140 bool set_pmc8_custom_dec_move_rate(int fd, double rate)
1141 {
1142  bool rc;
1143 
1144  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_custom_dec_move_rate() called rate=%f ", rate);
1145 
1146  // safe guard for now - only all use to STOP slewing with this
1147  if (fabs(rate) > PMC8_MAX_MOVE_RATE)
1148  {
1149  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_custom_dec_move_rate only supports low rates currently");
1150  return false;
1151  }
1152 
1154 
1155  return rc;
1156 }
1157 
1158 // rate is fraction of sidereal
1159 bool set_pmc8_guide_rate(int fd, PMC8_AXIS axis, double rate)
1160 {
1161  if (pmc8_simulation)
1162  {
1163  simPMC8Data.guide_rate = rate;
1164  return true;
1165  }
1166 
1167  // set driver values
1168  if (axis == PMC8_AXIS_RA)
1169  {
1171  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_guide_rate: ra guide rate set to %f", rate);
1172  }
1173  if ((axis == PMC8_AXIS_DEC) || !pmc8_isRev2Compliant)
1174  {
1176  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "set_pmc8_guide_rate: dec guide rate set to %f", rate);
1177  }
1178 
1180  {
1181  // now write to mount to sync ST4 rates
1182  char cmd[32], expresp[32];
1183  int errcode = 0;
1184  char errmsg[MAXRBUF];
1185  char response[16];
1186  int nbytes_read = 0;
1187  int nbytes_written = 0;
1188 
1189  snprintf(cmd, sizeof(cmd), "ESSf%d%02X!", axis, int(rate * 100));
1190 
1191  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1192  {
1193  tty_error_msg(errcode, errmsg, MAXRBUF);
1195  return false;
1196  }
1197 
1198  snprintf(expresp, sizeof(expresp), "ESGf%d%02X!", axis, int(rate * 100));
1199 
1200  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1201  {
1202  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "SRF set cmd response incorrect: expected=%s", expresp);
1203  return false;
1204  }
1205  }
1206 
1207  return true;
1208 }
1209 
1210 // get SRF value for axis
1211 bool get_pmc8_guide_rate(int fd, PMC8_AXIS axis, double &rate)
1212 {
1213  if (pmc8_simulation)
1214  {
1215  rate = simPMC8Data.guide_rate;
1216  return true;
1217  }
1218 
1219  // read from mount
1220  char cmd[32];
1221  int errcode = 0;
1222  char errmsg[MAXRBUF];
1223  char response[16];
1224  int nbytes_read = 0;
1225  int nbytes_written = 0;
1226 
1227  snprintf(cmd, sizeof(cmd), "ESGf%d!", axis);
1228 
1229  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1230  {
1231  tty_error_msg(errcode, errmsg, MAXRBUF);
1233  return false;
1234  }
1235 
1236  cmd[5] = '\0';
1237 
1238  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
1239  {
1240  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting SRF rate");
1241  return false;
1242  }
1243 
1244  if (nbytes_read != 8)
1245  {
1246  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "SRF Get rate cmd response incorrect");
1247  return false;
1248  }
1249 
1250  char num_str[16] = {0};
1251 
1252  strcpy(num_str, "0X");
1253  strncat(num_str, response + 5, 2);
1254  int tint = strtol(num_str, nullptr, 0);
1255 
1256  rate = ((double)tint) / 100;
1257 
1258  // set driver values
1259  if (axis == PMC8_AXIS_RA)
1260  {
1262  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "get_pmc8_guide_rate: ra guide rate set to %f", rate);
1263  }
1264  else
1265  {
1267  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "get_pmc8_guide_rate: dec guide rate set to %f", rate);
1268  }
1269 
1270  return true;
1271 }
1272 
1274 {
1275 
1276  switch (gdir)
1277  {
1278  case PMC8_N:
1279  case PMC8_S:
1280  *pstate = &NS_PulseGuideState;
1281  break;
1282 
1283  case PMC8_W:
1284  case PMC8_E:
1285  *pstate = &EW_PulseGuideState;
1286  break;
1287 
1288  default:
1289  return false;
1290  break;
1291  }
1292  return true;
1293 }
1294 
1295 // if return value is true then timetaken will return how much pulse time has already occurred
1296 bool start_pmc8_guide(int fd, PMC8_DIRECTION gdir, int ms, long &timetaken_us, double ratehint)
1297 {
1298  bool rc;
1299  double cur_rate = 0;
1300  int cur_dir = -1;
1301 
1302  // used to test timing
1303  struct timeval tp;
1304  long long pulse_start_us;
1305  long long pulse_sofar_us;
1306 
1307  PulseGuideState *pstate;
1308 
1309  if (!get_pmc8_guide_state(gdir, &pstate))
1310  {
1311  return false;
1312  }
1313 
1314  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): pulse dir=%d dur=%d ms", gdir, ms);
1315 
1316  if (pstate->pulseguideactive)
1317  {
1318  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): already executing a pulse guide!");
1319  return false;
1320  }
1321 
1322  // ignore short pulses - they do nothing
1323  if (ms < PMC8_PULSE_GUIDE_MIN_MS)
1324  {
1325  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): ignore short pulse ms=%d ms", ms);
1326  timetaken_us = ms * 1000;
1327  pstate->pulseguideactive = true;
1328  pstate->fakepulse = true;
1329  return true;
1330  }
1331 
1332  // get precise tracking rate if in RA
1333  if ((gdir == PMC8_E) || (gdir == PMC8_W))
1334  {
1335 
1336  // use rate provided by interface if valid rather than querying for it
1337  if (ratehint <= 0)
1338  {
1339  rc = get_pmc8_track_rate(fd, cur_rate);
1340  if (!rc)
1341  {
1342  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): error reading current RA rate!");
1343  return rc;
1344  }
1345  }
1346  else
1347  cur_rate = ratehint;
1348  }
1349  // we could get slew rate if in DEC, but driver doesn't currently support DEC tracking
1350  // and we shouldn't get here if we're slewing, so for now we assume interface is always correct and avoid delay from unnecessary calls to mount
1351  else
1352  {
1353  cur_rate = ratehint;
1354  }
1355 
1356  // if slewing abort
1357  // shouldn't get here if slewing, but doesn't hurt to check
1358  if (cur_rate > PMC8_MAX_TRACK_RATE)
1359  {
1361  "pmc8_start_guide(): Cannot send guide correction while slewing! rate=%d dir=%d",
1362  cur_rate, gdir);
1363  return rc;
1364  }
1365 
1366  double new_rate = cur_rate;
1367  int new_dir = 0;
1368 
1369  // RA guiding routine just changes the precision tracking call
1370  if ((gdir == PMC8_E) || (gdir == PMC8_W))
1371  {
1373 
1374  if (gdir == PMC8_E) new_rate -= guide_rate;
1375  else new_rate += guide_rate;
1376 
1377  if (new_rate < 0)
1378  {
1380  "pmc8_start_guide(): with current tracking rate of %f, requested guide rate of %f would flip RA motor in opposite direction, so pausing motor instead.",
1381  cur_rate, new_rate);
1382  new_rate = 0;
1383  }
1384 
1385  // measure time when we start pulse
1386  gettimeofday(&tp, nullptr);
1387  pulse_start_us = tp.tv_sec * 1000000 + tp.tv_usec;
1388 
1389  if (!set_pmc8_custom_ra_track_rate(fd, new_rate))
1390  {
1391  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_start_guide(): error settings new_rate to %f", new_rate);
1392  return false;
1393  }
1394 
1395  }
1396  // DEC guiding routine needs to set a DEC move rate and possibly a new direction
1397  else if ((gdir == PMC8_N) || (gdir == PMC8_S))
1398  {
1400 
1401  if (gdir == PMC8_S) new_rate -= guide_rate;
1402  else new_rate += guide_rate;
1403 
1404  if (new_rate < 0) new_dir = 1;
1405 
1406  int mrate;
1407  if (!convert_move_rate_to_motor(fabs(new_rate), &mrate))
1408  {
1409  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", new_rate);
1410  return false;
1411  }
1412 
1413  // we should flip direction first so that we decrease the distance we could be going in the wrong direction
1414  // this is of course obvious with dec assumed to be 0, but just a reminder in case we ever support dec tracking
1415 
1416  // ideally, we would set direction only if needed
1417  // but based on our current assumptions, that could cost us an extra call to find out the current direction
1418  // so for now we'll always end up setting the direction
1419  if (cur_dir != new_dir)
1420  if (!set_pmc8_direction_axis(fd, PMC8_AXIS_DEC, new_dir, false))
1421  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): error setting new_dec_dir");
1422 
1423  // measure time when we start pulse
1424  gettimeofday(&tp, nullptr);
1425  pulse_start_us = tp.tv_sec * 1000000 + tp.tv_usec;
1426 
1427  if (!set_pmc8_axis_motor_rate(fd, PMC8_AXIS_DEC, mrate, false))
1428  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): error setting new_dec_rate");
1429 
1430  }
1431  // if for some reason the gdir is non-sensical ...
1432  else
1433  {
1434  return false;
1435  }
1436 
1437  // store state
1438  pstate->pulseguideactive = true;
1439  pstate->fakepulse = false;
1440  pstate->ms = ms;
1441  pstate->pulse_start_us = pulse_start_us;
1442  pstate->cur_rate = cur_rate;
1443  pstate->cur_dir = cur_dir;
1444  pstate->new_rate = new_rate;
1445  pstate->new_dir = new_dir;
1446 
1447  // see how long we've waited
1448  gettimeofday(&tp, nullptr);
1449  pulse_sofar_us = (tp.tv_sec * 1000000 + tp.tv_usec) - pulse_start_us;
1450 
1451  timetaken_us = pulse_sofar_us;
1452 
1453  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_start_guide(): timetaken_us=%d us", timetaken_us);
1454 
1455  return true;
1456 }
1457 
1459 {
1460  struct timeval tp;
1461  long long pulse_end_us;
1462 
1463  PulseGuideState *pstate;
1464 
1465  if (!get_pmc8_guide_state(gdir, &pstate))
1466  {
1467  return false;
1468  }
1469 
1470  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): pulse dir=%d dur=%d ms", gdir, pstate->ms);
1471 
1472  if (!pstate->pulseguideactive)
1473  {
1474  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_stop_guide(): pulse guide not active!!");
1475  return false;
1476  }
1477 
1478  // flush any responses to commands we ignored above!
1479  tcflush(fd, TCIFLUSH);
1480 
1481  // "fake pulse" - it was so short we would have overshot its length AND the motors wouldn't have moved anyways
1482  if (pstate->fakepulse)
1483  {
1484 
1485  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): fake pulse done");
1486  pstate->pulseguideactive = false;
1487  return true;
1488  }
1489 
1490  gettimeofday(&tp, nullptr);
1491  pulse_end_us = tp.tv_sec * 1000000 + tp.tv_usec;
1492 
1493  if ((gdir == PMC8_E) || (gdir == PMC8_W))
1494  {
1495  if (!set_pmc8_custom_ra_track_rate(fd, pstate->cur_rate))
1496  {
1497  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "pmc8_stop_guide(): error restoring tracking_rate to %f",
1498  pstate->cur_rate);
1499  return false;
1500  }
1501  }
1502  else if ((gdir == PMC8_N) || (gdir == PMC8_S))
1503  {
1504  int mrate;
1505 
1506  if (!convert_move_rate_to_motor(fabs(pstate->cur_rate), &mrate))
1507  {
1508  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error converting rate %f", pstate->cur_rate);
1509  return false;
1510  }
1511 
1512  // under assumption of no dec tracking, all we need to do is stop motion
1513  // but if dec tracking is ever supported, need to fix direction, and it may be better to do that first if cur_rate > new_rate
1514  if (!set_pmc8_axis_motor_rate(fd, PMC8_AXIS_DEC, mrate, false))
1515  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): error returning to old move rate");
1516  // only change direction if needed
1517  if ((pstate->cur_rate != 0) && (pstate->cur_dir != pstate->new_dir))
1518  if (!set_pmc8_direction_axis(fd, PMC8_AXIS_DEC, pstate->cur_dir, false))
1519  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): error returning to old direction");
1520  }
1521  else
1522  {
1523  return false;
1524  }
1525 
1526  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "pmc8_stop_guide(): requested = %d ms, actual = %f ms",
1527  pstate->ms, (pulse_end_us - pstate->pulse_start_us) / 1000.0);
1528 
1529  // flush any responses to commands we ignored above!
1530  tcflush(fd, TCIFLUSH);
1531 
1532  // mark pulse done
1533  pstate->pulseguideactive = false;
1534 
1535  return true;
1536 }
1537 
1538 // convert from axis position returned by controller to motor counts used in conversion to RA/DEC
1540 {
1541  int r;
1542 
1543  if (axispos > 8388608)
1544  r = 0 - (16777216 - axispos);
1545  else
1546  r = axispos;
1547 
1548  return r;
1549 }
1550 
1552 {
1553  double motor_angle;
1554  double hour_angle;
1555  double lst;
1556 
1557  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - ra=%f sop=%d", ra, sop);
1558 
1560 
1561  hour_angle = lst - ra;
1562 
1563  // limit values to +/- 12 hours
1564  if (hour_angle > 12)
1565  hour_angle = hour_angle - 24;
1566  else if (hour_angle <= -12)
1567  hour_angle = hour_angle + 24;
1568 
1569  // Northern Hemisphere
1570  if (pmc8_east_dir)
1571  {
1572  if (sop == INDI::Telescope::PIER_EAST)
1573  motor_angle = hour_angle - 6;
1574  else if (sop == INDI::Telescope::PIER_WEST)
1575  motor_angle = hour_angle + 6;
1576  else
1577  return false;
1578  }
1579  // Southern Hemisphere
1580  else
1581  {
1582  if (sop == INDI::Telescope::PIER_EAST)
1583  motor_angle = -(hour_angle + 6);
1584  else if (sop == INDI::Telescope::PIER_WEST)
1585  motor_angle = -(hour_angle - 6);
1586  else
1587  return false;
1588  }
1589 
1590 
1591  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - lst = %f hour_angle=%f", lst, hour_angle);
1592 
1593  *mcounts = motor_angle * PMC8_AXIS0_SCALE / 24;
1594 
1595  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_ra_to_motor - motor_angle=%f *mcounts=%d", motor_angle, *mcounts);
1596 
1597 
1598  return true;
1599 }
1600 
1601 bool convert_motor_to_radec(int racounts, int deccounts, double &ra_value, double &dec_value)
1602 {
1603  double motor_angle;
1604  double hour_angle;
1605 
1606  double lst;
1607 
1609 
1610  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "lst = %f", lst);
1611 
1612  motor_angle = (24.0 * racounts) / PMC8_AXIS0_SCALE;
1613 
1614  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "racounts = %d motor_angle = %f", racounts, motor_angle);
1615 
1616  // Northern Hemisphere
1617  if (pmc8_east_dir)
1618  {
1619  if (deccounts < 0)
1620  hour_angle = motor_angle + 6;
1621  else
1622  hour_angle = motor_angle - 6;
1623  }
1624  // Southern Hemisphere
1625  else
1626  {
1627  if (deccounts < 0)
1628  hour_angle = -(motor_angle + 6);
1629  else
1630  hour_angle = -(motor_angle - 6);
1631  }
1632 
1633  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "hour_angle = %f", hour_angle);
1634 
1635  ra_value = lst - hour_angle;
1636 
1637  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra_value = %f", ra_value);
1638 
1639  if (ra_value >= 24.0)
1640  ra_value = ra_value - 24.0;
1641  else if (ra_value < 0.0)
1642  ra_value = ra_value + 24.0;
1643 
1644  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra_value (final) = %f", ra_value);
1645 
1646  motor_angle = (360.0 * deccounts) / PMC8_AXIS1_SCALE;
1647 
1648  // Northern Hemisphere
1649  if (pmc8_east_dir)
1650  {
1651  if (motor_angle >= 0)
1652  dec_value = 90 - motor_angle;
1653  else
1654  dec_value = 90 + motor_angle;
1655  }
1656  // Sourthern Hemisphere
1657  else
1658  {
1659  if (motor_angle >= 0)
1660  dec_value = -90 + motor_angle;
1661  else
1662  dec_value = -90 - motor_angle;
1663  }
1664 
1665  return true;
1666 }
1667 
1669 {
1670  double motor_angle;
1671 
1672  // Northern Hemisphere
1673  if (pmc8_east_dir)
1674  {
1675  if (sop == INDI::Telescope::PIER_EAST)
1676  motor_angle = (dec - 90.0);
1677  else if (sop == INDI::Telescope::PIER_WEST)
1678  motor_angle = -(dec - 90.0);
1679  else
1680  return false;
1681  }
1682  // Southern Hemisphere
1683  else
1684  {
1685  if (sop == INDI::Telescope::PIER_EAST)
1686  motor_angle = -(dec + 90.0);
1687  else if (sop == INDI::Telescope::PIER_WEST)
1688  motor_angle = (dec + 90.0);
1689  else
1690  return false;
1691  }
1692 
1693  *mcounts = (motor_angle / 360.0) * PMC8_AXIS1_SCALE;
1694 
1695  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_dec_to_motor dec = %f, sop = %d", dec, sop);
1696  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "convert_dec_to_motor motor_angle = %f, motor_counts= %d", motor_angle, *mcounts);
1697 
1698  return true;
1699 }
1700 
1701 bool set_pmc8_target_position_axis(int fd, PMC8_AXIS axis, int point)
1702 {
1703 
1704  char cmd[32];
1705  char expresp[32];
1706  char hexpt[16];
1707  int errcode = 0;
1708  char errmsg[MAXRBUF];
1709  char response[16];
1710  int nbytes_read = 0;
1711  int nbytes_written = 0;
1712 
1713  convert_motor_counts_to_hex(point, hexpt);
1714 
1715  // for v2+ firmware, use axis 2 if we don't want to track after the slew
1716  int naxis = axis;
1717  if (pmc8_isRev2Compliant && !axis && !pmc8_goto_resume) naxis = 2;
1718  snprintf(cmd, sizeof(cmd), "ESPt%d%s!", naxis, hexpt);
1719 
1720  if (!pmc8_simulation)
1721  {
1722 
1723  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1724  {
1725  tty_error_msg(errcode, errmsg, MAXRBUF);
1727  return false;
1728  }
1729 
1730  snprintf(expresp, sizeof(expresp), "ESGt%d%s!", naxis, hexpt);
1731 
1732  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1733  {
1734  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Set Point cmd response incorrect: %s - expected %s", response,
1735  expresp);
1736  return false;
1737  }
1738  }
1739 
1740  return true;
1741 }
1742 
1743 bool set_pmc8_target_position(int fd, int rapoint, int decpoint)
1744 {
1745  bool rc;
1746 
1748 
1749  if (!rc)
1750  return rc;
1751 
1753 
1754  return rc;
1755 }
1756 
1757 
1758 bool set_pmc8_position_axis(int fd, PMC8_AXIS axis, int point)
1759 {
1760 
1761  char cmd[32];
1762  char expresp[32];
1763  char hexpt[16];
1764  int errcode = 0;
1765  char errmsg[MAXRBUF];
1766  char response[16];
1767  int nbytes_read = 0;
1768  int nbytes_written = 0;
1769 
1770  if (pmc8_simulation)
1771  {
1772  // FIXME - need to implement simulation code for setting point position
1773  return true;
1774  }
1775 
1776  convert_motor_counts_to_hex(point, hexpt);
1777  snprintf(cmd, sizeof(cmd), "ESSp%d%s!", axis, hexpt);
1778 
1779  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1780  {
1781  tty_error_msg(errcode, errmsg, MAXRBUF);
1783  return false;
1784  }
1785 
1786  snprintf(expresp, sizeof(expresp), "ESGp%d%s!", axis, hexpt);
1787 
1788  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1789  {
1790  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Set Point cmd response incorrect: %s - expected %s", response,
1791  expresp);
1792  return false;
1793  }
1794 
1795  return true;
1796 }
1797 
1798 
1799 bool set_pmc8_position(int fd, int rapoint, int decpoint)
1800 {
1801  bool rc;
1802 
1803  rc = set_pmc8_position_axis(fd, PMC8_AXIS_RA, rapoint);
1804 
1805  if (!rc)
1806  return rc;
1807 
1808  rc = set_pmc8_position_axis(fd, PMC8_AXIS_DEC, decpoint);
1809 
1810  return rc;
1811 }
1812 
1813 
1814 bool get_pmc8_position_axis(int fd, PMC8_AXIS axis, int &point)
1815 {
1816 
1817  char cmd[32];
1818  int errcode = 0;
1819  char errmsg[MAXRBUF];
1820  char response[16];
1821  int nbytes_read = 0;
1822  int nbytes_written = 0;
1823 
1824  if (pmc8_simulation)
1825  {
1826  // FIXME - need to implement simulation code for setting point position
1827  return true;
1828  }
1829 
1830  snprintf(cmd, sizeof(cmd), "ESGp%d!", axis);
1831 
1832  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1833  {
1834  tty_error_msg(errcode, errmsg, MAXRBUF);
1836  return false;
1837  }
1838 
1839  cmd[5] = '\0';
1840 
1841  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, cmd)))
1842  {
1843  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error getting position axis");
1844  return false;
1845  }
1846 
1847  if (nbytes_read != 12)
1848  {
1849  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Axis Get Point cmd response incorrect");
1850  return false;
1851  }
1852 
1853  char num_str[16] = {0};
1854 
1855  strcpy(num_str, "0X");
1856  strncat(num_str, response + 5, 6);
1857 
1858  point = (int)strtol(num_str, nullptr, 0);
1859 
1860  return true;
1861 }
1862 
1863 
1864 bool get_pmc8_position(int fd, int &rapoint, int &decpoint)
1865 {
1866  bool rc;
1867  int axis_ra_pos, axis_dec_pos;
1868 
1869  rc = get_pmc8_position_axis(fd, PMC8_AXIS_RA, axis_ra_pos);
1870 
1871  if (!rc)
1872  return rc;
1873 
1874  rc = get_pmc8_position_axis(fd, PMC8_AXIS_DEC, axis_dec_pos);
1875 
1876  if (!rc)
1877  return rc;
1878 
1879  // convert from axis position to motor counts
1880  rapoint = convert_axispos_to_motor(axis_ra_pos);
1881  decpoint = convert_axispos_to_motor(axis_dec_pos);
1882 
1883  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra axis pos = 0x%x motor_counts=%d", axis_ra_pos, rapoint);
1884  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "dec axis pos = 0x%x motor_counts=%d", axis_dec_pos, decpoint);
1885 
1886  return rc;
1887 }
1888 
1889 
1890 bool park_pmc8(int fd)
1891 {
1892 
1893  bool rc;
1894 
1895  rc = set_pmc8_target_position(fd, 0, 0);
1896 
1897  // FIXME - Need to add code to handle simulation and also setting any scope state values
1898 
1899  return rc;
1900 }
1901 
1902 
1903 bool unpark_pmc8(int fd)
1904 {
1905  INDI_UNUSED(fd);
1906 
1907  // nothing really to do for PMC8 there is no unpark command
1908 
1909  if (pmc8_simulation)
1910  {
1912  return true;
1913  }
1914 
1915 
1916  // FIXME - probably need to set a state variable to show we're unparked
1917  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "PMC8 unparked");
1918 
1919  return true;
1920 }
1921 
1922 bool abort_pmc8(int fd)
1923 {
1924  bool rc;
1925 
1926  if (pmc8_simulation)
1927  {
1928  // FIXME - need to do something to represent mount has stopped slewing
1929  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "PMC8 slew stopped in simulation - need to add more code?");
1930  return true;
1931  }
1932 
1933  // stop move/slew rates
1935  if (!rc)
1936  {
1937  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping RA axis!");
1938  return false;
1939  }
1940 
1942  if (!rc)
1943  {
1944  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error stopping DEC axis!");
1945  return false;
1946  }
1947 
1948  return true;
1949 }
1950 
1952 {
1953  char cmd[32];
1954  char expresp[32];
1955  int errcode = 0;
1956  char errmsg[MAXRBUF];
1957  char response[16];
1958  int nbytes_read = 0;
1959  int nbytes_written = 0;
1960 
1961  snprintf(cmd, sizeof(cmd), "ESPt300000!");
1962 
1963  if (!pmc8_simulation)
1964  {
1965 
1966  if ((errcode = send_pmc8_command(fd, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1967  {
1968  tty_error_msg(errcode, errmsg, MAXRBUF);
1970  return false;
1971  }
1972 
1973  snprintf(expresp, sizeof(expresp), "ESGt3!");
1974 
1975  if ((errcode = get_pmc8_response(fd, response, &nbytes_read, expresp)))
1976  {
1977  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Abort Goto cmd response incorrect: %s - expected %s", response, expresp);
1978  return false;
1979  }
1980  }
1981 
1982  return true;
1983 }
1984 
1985 // "slew" on PMC8 is instantaneous once you set the target ra/dec
1986 // no concept of setting target and then starting a slew operation as two steps
1987 bool slew_pmc8(int fd, double ra, double dec)
1988 {
1989  bool rc;
1990  int racounts, deccounts;
1992 
1993  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "slew_pmc8: ra=%f dec=%f", ra, dec);
1994 
1995  sop = destSideOfPier(ra, dec);
1996 
1997  rc = convert_ra_to_motor(ra, sop, &racounts);
1998  if (!rc)
1999  {
2000  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "slew_pmc8: error converting RA to motor counts");
2001  return false;
2002  }
2003 
2004  rc = convert_dec_to_motor(dec, sop, &deccounts);
2005  if (!rc)
2006  {
2007  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "slew_pmc8: error converting DEC to motor counts");
2008  return false;
2009  }
2010 
2011  rc = set_pmc8_target_position(fd, racounts, deccounts);
2012 
2013  if (!rc)
2014  {
2015  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error slewing PMC8");
2016  return false;
2017  }
2018 
2019  if (pmc8_simulation)
2020  {
2022  }
2023 
2024  return true;
2025 }
2026 
2028 {
2029  double hour_angle;
2030  double lst;
2031 
2032  INDI_UNUSED(dec);
2033 
2035 
2036  hour_angle = lst - ra;
2037 
2038  // limit values to +/- 12 hours
2039  if (hour_angle > 12)
2040  hour_angle = hour_angle - 24;
2041  else if (hour_angle <= -12)
2042  hour_angle = hour_angle + 24;
2043 
2044  // Northern Hemisphere
2045  if (pmc8_east_dir)
2046  {
2047  if (hour_angle < 0.0)
2049  else
2051  }
2052  //Southern Hemisphere
2053  else
2054  {
2055  if (hour_angle < 0.0)
2057  else
2059  }
2060 }
2061 
2062 bool sync_pmc8(int fd, double ra, double dec)
2063 {
2064  bool rc;
2065  int racounts, deccounts;
2067 
2068  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "sync_pmc8: ra=%f dec=%f", ra, dec);
2069 
2070  sop = destSideOfPier(ra, dec);
2071 
2072  rc = convert_ra_to_motor(ra, sop, &racounts);
2073  if (!rc)
2074  {
2075  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "sync_pmc8: error converting RA to motor counts");
2076  return false;
2077  }
2078 
2079  rc = convert_dec_to_motor(dec, sop, &deccounts);
2080  if (!rc)
2081  {
2082  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "sync_pmc8: error converting DEC to motor counts");
2083  return false;
2084  }
2085 
2086  if (pmc8_simulation)
2087  {
2088  // FIXME - need to implement pmc8 sync sim
2089  // strcpy(response, "1");
2090  // nbytes_read = strlen(response);
2091  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement PMC8 sync simulation");
2092  return false;
2093  }
2094  else
2095  {
2096  rc = set_pmc8_position(fd, racounts, deccounts);
2097  }
2098 
2099  if (!rc)
2100  {
2101  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting pmc8 position");
2102  return false;
2103  }
2104 
2105  return true;
2106 }
2107 
2108 bool set_pmc8_radec(int fd, double ra, double dec)
2109 {
2110  bool rc;
2111  int racounts, deccounts;
2113 
2114 
2115  sop = destSideOfPier(ra, dec);
2116 
2117  rc = convert_ra_to_motor(ra, sop, &racounts);
2118  if (!rc)
2119  {
2120  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_radec: error converting RA to motor counts");
2121  return false;
2122  }
2123 
2124  rc = convert_dec_to_motor(ra, sop, &deccounts);
2125  if (!rc)
2126  {
2127  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "set_pmc8_radec: error converting DEC to motor counts");
2128  return false;
2129  }
2130 
2131  if (pmc8_simulation)
2132  {
2133  // FIXME - need to implement pmc8 sync sim
2134  // strcpy(response, "1");
2135  // nbytes_read = strlen(response);
2136  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Need to implement PMC8 sync simulation");
2137  return false;
2138  }
2139  else
2140  {
2141 
2142  rc = set_pmc8_target_position(fd, racounts, deccounts);
2143  }
2144 
2145  if (!rc)
2146  {
2147  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Error setting target position");
2148  return false;
2149  }
2150 
2151  return true;
2152 }
2153 
2154 bool get_pmc8_coords(int fd, double &ra, double &dec)
2155 {
2156  int racounts, deccounts;
2157  bool rc;
2158 
2159  if (pmc8_simulation)
2160  {
2161  // sortof silly but convert simulated RA/DEC to counts so we can then convert
2162  // back to RA/DEC to test that conversion code
2164 
2165  sop = destSideOfPier(simPMC8Data.ra, simPMC8Data.dec);
2166 
2167  rc = convert_ra_to_motor(simPMC8Data.ra, sop, &racounts);
2168 
2169  if (!rc)
2170  return rc;
2171 
2172  rc = convert_dec_to_motor(simPMC8Data.dec, sop, &deccounts);
2173 
2174  if (!rc)
2175  return rc;
2176  }
2177  else
2178  {
2179  rc = get_pmc8_position(fd, racounts, deccounts);
2180  }
2181 
2182  if (!rc)
2183  {
2184  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Error getting PMC8 motor position");
2185  return false;
2186  }
2187 
2188  // convert motor counts to ra/dec
2189  convert_motor_to_radec(racounts, deccounts, ra, dec);
2190 
2191  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "ra motor_counts=%d RA = %f", racounts, ra);
2192  // DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "dec motor_counts=%d DEC = %f", deccounts, dec);
2193 
2194  return rc;
2195 }
2196 
2197 // wrap read commands to PMC8
2198 bool get_pmc8_response(int fd, char* buf, int *nbytes_read, const char* expected = NULL )
2199 {
2200  int err_code = 1;
2201  int cnt = 0;
2202 
2203  //repeat a few times, after that, let's assume we're not getting a response
2204  while ((err_code) && (cnt++ < PMC8_MAX_RETRIES))
2205  {
2206  //Read until exclamation point to get response
2207  if ((err_code = tty_read_section(fd, buf, '!', PMC8_TIMEOUT, nbytes_read)))
2208  {
2209 
2210  char errmsg[MAXRBUF];
2211  tty_error_msg(err_code, errmsg, MAXRBUF);
2212 
2213  // if we see connection timed out, exit out of here and try to reconnect
2214  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Read error: %s", errmsg);
2215  if (strstr(errmsg, "Connection timed out") || strstr(errmsg, "Bad"))
2216  {
2218  return err_code;
2219  }
2220  }
2221  if (*nbytes_read > 0)
2222  {
2223  buf[*nbytes_read] = '\0';
2224  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "RES %d bytes (%s)", *nbytes_read, buf);
2225 
2226  //PMC8 connection is not entirely reliable when using Ethernet instead of Serial connection.
2227  //So, try to compensate for common problems
2229  {
2230  //One problem is we get the string *HELLO* when we connect or disconnect, so discard that
2231  if (buf[0] == '*')
2232  {
2233  strcpy(buf, buf + 7);
2234  *nbytes_read = *nbytes_read - 7;
2235  }
2236  //Another problem is we sometimes get the string AT when we reconnect, so discard that
2237  if (strncmp(buf, "AT", 2) == 0)
2238  {
2239  strcpy(buf, buf + 2);
2240  *nbytes_read = *nbytes_read - 2;
2241  }
2242  //Another problem is random extraneous ESGp! reponses during slew, so when we see those, drop them and try again
2243  if (strncmp(buf, "ESGp!", 5) == 0)
2244  {
2245  err_code = 1;
2246  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_DEBUG, "Invalid response ESGp!");
2247  }
2248  }
2249  //If a particular response was expected, make sure we got it
2250  if (expected)
2251  {
2252  if (strncmp(buf, expected, strlen(expected)) != 0)
2253  {
2254  err_code = 1;
2255  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_EXTRA_1, "No Match for %s", expected);
2256  }
2257  else
2258  {
2259  DEBUGFDEVICE(pmc8_device, INDI::Logger::DBG_EXTRA_1, "Matches %s", expected);
2260  // On rare occasions, there may have been a read error even though it's the response we want, so set err_code explicitly
2261  err_code = 0;
2262  }
2263  }
2264  }
2265  else
2266  {
2268  err_code = 1;
2269  }
2270  }
2271  // if this is our nth consecutive read error, try to reconnect
2272  if (err_code)
2273  {
2275  }
2276  else
2277  {
2278  pmc8_io_error_ctr = 0;
2279  }
2280  return err_code;
2281 }
2282 
2283 //wrap write commands to pmc8
2284 bool send_pmc8_command(int fd, const char *buf, int nbytes, int *nbytes_written)
2285 {
2286 
2288 
2289  tcflush(fd, TCIFLUSH);
2290 
2291  int err_code = 1;
2292  //try to reconnect if we see broken pipe error
2293  if ((err_code = tty_write(fd, buf, nbytes, nbytes_written)))
2294  {
2295  char errmsg[MAXRBUF];
2296  tty_error_msg(err_code, errmsg, MAXRBUF);
2297  if (strstr(errmsg, "Broken pipe") || strstr(errmsg, "Bad"))
2298  {
2300  return err_code;
2301  }
2302  }
2303  return err_code;
2304 }
2305 
2307 {
2308  DEBUGDEVICE(pmc8_device, INDI::Logger::DBG_ERROR, "Bad connection. Trying to reconnect.");
2309  pmc8_reconnect_flag = true;
2310 }
2311 
2313 {
2314  if (pmc8_reconnect_flag)
2315  {
2316  pmc8_reconnect_flag = false;
2317  return true;
2318  }
2319  return false;
2320 }
2321 
2322 void set_pmc8_goto_resume(bool resume)
2323 {
2324  pmc8_goto_resume = resume;
2325 }
#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 tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
#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
@ ST_SLEWING
Definition: ieqdriverbase.h:35
@ ST_STOPPED
Definition: ieqdriverbase.h:33
@ ST_PARKED
Definition: ieqdriverbase.h:39
bool convert_motor_rate_to_move_rate(int mrate, double *rate)
Definition: pmc8driver.cpp:197
bool get_pmc8_model(int fd, FirmwareInfo *info)
Definition: pmc8driver.cpp:390
bool get_pmc8_guide_rate(int fd, PMC8_AXIS axis, double &rate)
PMC8Info simPMC8Info
Definition: pmc8driver.cpp:98
PulseGuideState NS_PulseGuideState
Definition: pmc8driver.cpp:114
void set_pmc8_location(double latitude, double longitude)
Definition: pmc8driver.cpp:243
#define PMC8_MAX_IO_ERROR_THRESHOLD
Definition: pmc8driver.cpp:78
#define PMC8_iEXOS100_AXIS0_SCALE
Definition: pmc8driver.cpp:57
#define PMC8_RATE_KING
Definition: pmc8driver.cpp:83
bool set_pmc8_custom_ra_track_rate(int fd, double rate)
double PMC8_AXIS0_SCALE
Definition: pmc8driver.cpp:61
#define PMC8_RATE_SIDEREAL
Definition: pmc8driver.cpp:80
uint8_t get_pmc8_tracking_mode_from_rate(double rate)
Definition: pmc8driver.cpp:842
double pmc8_sidereal_rate_fraction_ra
Definition: pmc8driver.cpp:95
int raDirection
Definition: pmc8driver.cpp:120
bool start_pmc8_guide(int fd, PMC8_DIRECTION gdir, int ms, long &timetaken_us, double ratehint)
#define PMC8_RATE_SOLAR
Definition: pmc8driver.cpp:82
int pmc8_east_dir
Definition: pmc8driver.cpp:97
bool set_pmc8_target_position_axis(int fd, PMC8_AXIS axis, int point)
bool pmc8_reconnect_flag
Definition: pmc8driver.cpp:89
#define PMC8_SIMUL_VERSION_RESP
Definition: pmc8driver.cpp:48
void set_pmc8_sim_system_status(PMC8_SYSTEM_STATUS value)
Definition: pmc8driver.cpp:253
struct @187 simPMC8Data
bool set_pmc8_custom_dec_track_rate(int fd, double rate)
bool convert_precise_motor_to_rate(int mrate, double *rate)
Definition: pmc8driver.cpp:174
void set_pmc8_device(const char *name)
Definition: pmc8driver.cpp:238
bool set_pmc8_axis_move_rate(int fd, PMC8_AXIS axis, float rate)
Definition: pmc8driver.cpp:932
bool detect_pmc8(int fd)
Definition: pmc8driver.cpp:355
#define PMC8_RETRY_DELAY
Definition: pmc8driver.cpp:77
#define PMC8_EXOS2_AXIS0_SCALE
Definition: pmc8driver.cpp:54
bool park_pmc8(int fd)
bool stop_pmc8_guide(int fd, PMC8_DIRECTION gdir)
double pmc8_longitude
Definition: pmc8driver.cpp:94
void set_pmc8_debug(bool enable)
Definition: pmc8driver.cpp:226
bool sync_pmc8(int fd, double ra, double dec)
#define ARCSEC_IN_CIRCLE
Definition: pmc8driver.cpp:64
void convert_motor_counts_to_hex(int val, char *hex)
Definition: pmc8driver.cpp:128
bool abort_pmc8_goto(int fd)
double pmc8_sidereal_rate_fraction_de
Definition: pmc8driver.cpp:96
bool convert_precise_rate_to_motor(double rate, int *mrate)
Definition: pmc8driver.cpp:152
int convert_axispos_to_motor(int axispos)
bool slew_pmc8(int fd, double ra, double dec)
#define PMC8_PULSE_GUIDE_MIN_MS
Definition: pmc8driver.cpp:71
double trackRate
Definition: pmc8driver.cpp:122
bool convert_ra_to_motor(double ra, INDI::Telescope::TelescopePierSide sop, int *mcounts)
bool get_pmc8_response(int fd, char *buf, int *nbytes_read, const char *expected=NULL)
#define PMC8_EXOS2_AXIS1_SCALE
Definition: pmc8driver.cpp:55
bool get_pmc8_is_scope_slewing(int fd, bool &isslew)
Definition: pmc8driver.cpp:712
PulseGuideState EW_PulseGuideState
Definition: pmc8driver.cpp:114
double ra
Definition: pmc8driver.cpp:118
bool send_pmc8_command(int fd, const char *buf, int nbytes, int *nbytes_written)
bool set_pmc8_track_mode(int fd, uint8_t mode)
Definition: pmc8driver.cpp:996
bool pmc8_simulation
Definition: pmc8driver.cpp:87
bool convert_dec_to_motor(double dec, INDI::Telescope::TelescopePierSide sop, int *mcounts)
bool pmc8_debug
Definition: pmc8driver.cpp:86
char pmc8_device[MAXINDIDEVICE]
Definition: pmc8driver.cpp:92
bool get_pmc8_position_axis(int fd, PMC8_AXIS axis, int &point)
bool convert_move_rate_to_motor(float rate, int *mrate)
Definition: pmc8driver.cpp:182
#define PMC8_iEXOS100_AXIS1_SCALE
Definition: pmc8driver.cpp:58
bool set_pmc8_radec(int fd, double ra, double dec)
bool pmc8_isRev2Compliant
Definition: pmc8driver.cpp:88
bool set_pmc8_target_position(int fd, int rapoint, int decpoint)
void set_pmc8_mountParameters(int index)
Definition: pmc8driver.cpp:204
bool pmc8_goto_resume
Definition: pmc8driver.cpp:90
double PMC8_AXIS1_SCALE
Definition: pmc8driver.cpp:62
double pmc8_latitude
Definition: pmc8driver.cpp:93
bool set_pmc8_guide_rate(int fd, PMC8_AXIS axis, double rate)
INDI::Telescope::TelescopePierSide destSideOfPier(double ra, double dec)
#define PMC8_G11_AXIS0_SCALE
Definition: pmc8driver.cpp:51
bool set_pmc8_axis_motor_rate(int fd, PMC8_AXIS axis, int mrate, bool fast)
Definition: pmc8driver.cpp:884
void set_pmc8_simulation(bool enable)
Definition: pmc8driver.cpp:231
#define PMC8_G11_AXIS1_SCALE
Definition: pmc8driver.cpp:52
void set_pmc8_goto_resume(bool resume)
bool get_pmc8_firmware(int fd, FirmwareInfo *info)
Definition: pmc8driver.cpp:538
#define PMC8_MAX_RETRIES
Definition: pmc8driver.cpp:76
bool get_pmc8_main_firmware(int fd, FirmwareInfo *info)
Definition: pmc8driver.cpp:485
#define PMC8_RATE_LUNAR
Definition: pmc8driver.cpp:81
bool get_pmc8_coords(int fd, double &ra, double &dec)
#define PMC8_TIMEOUT
Definition: pmc8driver.cpp:46
bool abort_pmc8(int fd)
bool set_pmc8_position(int fd, int rapoint, int decpoint)
bool set_pmc8_custom_ra_move_rate(int fd, double rate)
bool get_pmc8_reconnect_flag()
bool get_pmc8_guide_state(PMC8_DIRECTION gdir, PulseGuideState **pstate)
PMC8_CONNECTION_TYPE pmc8_connection
Definition: pmc8driver.cpp:85
bool get_pmc8_tracking_data(int fd, double &rate, uint8_t &mode)
Definition: pmc8driver.cpp:834
bool check_pmc8_connection(int fd, PMC8_CONNECTION_TYPE connection)
Definition: pmc8driver.cpp:297
bool convert_motor_to_radec(int racounts, int deccounts, double &ra_value, double &dec_value)
bool set_pmc8_custom_dec_move_rate(int fd, double rate)
bool set_pmc8_direction_axis(int fd, PMC8_AXIS axis, int dir, bool fast)
Definition: pmc8driver.cpp:665
double guide_rate
Definition: pmc8driver.cpp:124
bool unpark_pmc8(int fd)
int decDirection
Definition: pmc8driver.cpp:121
double dec
Definition: pmc8driver.cpp:119
void set_pmc8_sim_move_rate(int value)
Definition: pmc8driver.cpp:282
bool set_pmc8_position_axis(int fd, PMC8_AXIS axis, int point)
bool stop_pmc8_tracking_motion(int fd)
Definition: pmc8driver.cpp:771
void set_pmc8_sim_track_rate(PMC8_TRACK_RATE value)
Definition: pmc8driver.cpp:277
bool get_pmc8_direction_axis(int fd, PMC8_AXIS axis, int &dir)
Definition: pmc8driver.cpp:609
bool get_pmc8_position(int fd, int &rapoint, int &decpoint)
void set_pmc8_sim_ra(double ra)
Definition: pmc8driver.cpp:287
int pmc8_io_error_ctr
Definition: pmc8driver.cpp:91
void set_pmc8_reconnect_flag()
double moveRate
Definition: pmc8driver.cpp:123
bool set_pmc8_ra_tracking(int fd, double rate)
struct PulseGuideState PulseGuideState
void set_pmc8_sim_dec(double dec)
Definition: pmc8driver.cpp:292
bool set_pmc8_move_rate_axis(int fd, PMC8_DIRECTION dir, int reqrate)
Definition: pmc8driver.cpp:745
#define PMC8_MAX_PRECISE_MOTOR_RATE
Definition: pmc8driver.cpp:68
bool get_pmc8_track_rate(int fd, double &rate)
Definition: pmc8driver.cpp:787
bool get_pmc8_move_rate_axis(int fd, PMC8_AXIS axis, double &rate)
Definition: pmc8driver.cpp:553
PMC8_CONNECTION_TYPE
Definition: pmc8driver.h:64
@ PMC8_SERIAL_AUTO
Definition: pmc8driver.h:64
@ PMC8_ETHERNET
Definition: pmc8driver.h:64
@ PMC8_SERIAL_STANDARD
Definition: pmc8driver.h:64
#define PMC8_MAX_MOVE_RATE
Definition: pmc8driver.h:40
@ MOUNT_G11
Definition: pmc8driver.h:62
@ MOUNT_iEXOS100
Definition: pmc8driver.h:62
@ MOUNT_EXOS2
Definition: pmc8driver.h:62
PMC8_TRACK_RATE
Definition: pmc8driver.h:55
@ PMC8_TRACK_KING
Definition: pmc8driver.h:55
@ PMC8_TRACK_CUSTOM
Definition: pmc8driver.h:55
@ PMC8_TRACK_SIDEREAL
Definition: pmc8driver.h:55
@ PMC8_TRACK_LUNAR
Definition: pmc8driver.h:55
@ PMC8_TRACK_SOLAR
Definition: pmc8driver.h:55
PMC8_SYSTEM_STATUS
Definition: pmc8driver.h:43
PMC8_AXIS
Definition: pmc8driver.h:59
@ PMC8_AXIS_DEC
Definition: pmc8driver.h:59
@ PMC8_AXIS_RA
Definition: pmc8driver.h:59
PMC8_DIRECTION
Definition: pmc8driver.h:60
@ PMC8_N
Definition: pmc8driver.h:60
@ PMC8_W
Definition: pmc8driver.h:60
@ PMC8_S
Definition: pmc8driver.h:60
@ PMC8_E
Definition: pmc8driver.h:60
#define PMC8_MAX_TRACK_RATE
Definition: pmc8driver.h:37
__u8 cmd[4]
Definition: pwc-ioctl.h:2
std::string Model
PMC8_MOUNT_TYPES MountType
Definition: pmc8driver.h:77
std::string MainBoardFirmware
bool IsRev2Compliant
Definition: pmc8driver.h:78
PMC8_SYSTEM_STATUS systemStatus
Definition: pmc8driver.h:68
long long pulse_start_us
Definition: pmc8driver.cpp:106
double round(double value, int decimal_places)