Instrument Neutral Distributed Interface INDI  2.0.2
lx200pulsar2.cpp
Go to the documentation of this file.
1 /*
2  Pulsar2 INDI driver
3 
4  Copyright (C) 2016, 2017 Jasem Mutlaq and Camiel Severijns
5  Minor Changes (C) 2021 James Lancaster
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Lesser General Public
9  License as published by the Free Software Foundation; 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; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License along with this library; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21 
22 #include "lx200pulsar2.h"
23 
24 #include "indicom.h"
25 #include "lx200driver.h"
26 
27 #include <cmath>
28 #include <cerrno>
29 #include <cstring>
30 #include <termios.h>
31 #include <unistd.h>
32 #include <mutex>
33 
34 extern char lx200Name[MAXINDIDEVICE];
35 extern unsigned int DBG_SCOPE;
36 
37 
38 namespace PulsarTX
39 {
40 
41 namespace
42 {
43 // We re-implement some low-level tty commands to solve intermittent
44 // problems with tcflush() calls on the input stream. The following
45 // methods send to and parse input from the Pulsar controller.
46 
47 static constexpr char Termination = '#';
48 static constexpr int TimeOut = 1; // tenths of a second
49 static constexpr int MaxAttempts = 5;
50 
51 // The following indicates whether the input and output on the port
52 // needs to be resynchronized due to a timeout error.
53 static bool resynchronize_needed = false;
54 
55 static std::mutex dev_mtx;
56 
57 static char lastCmd[40]; // used only for verbose logging
58 
59 // --- --- --- --- --- --- --- ---
60 // "private" namespace methods
61 // --- --- --- --- --- --- --- ---
62 const char * getDeviceName() // a local implementation meant only to satisfy logging macros such as LOG_INFO
63 {
64  return static_cast<const char *>("Pulsar2");
65 }
66 
67 // The following was a re-work of two previous elegantly-constructed functions,
68 // in order to allow the insertion of some debug commands to try to figure out
69 // what was going on with the controller. We just leave it this way for now.
70 bool _sendReceiveACK_(const int fd, char *received_char)
71 {
72  const char ackbuf[1] = { '\006' };
73  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "ACK CMD: <%02X>", ackbuf[0]);
74 
75  char response[8];
76  response[0] = LX200Pulsar2::Null; // oversized, just in case
77  int nbytes_read = 0;
78  bool success = (write(fd, ackbuf, sizeof(ackbuf)) > 0);
79  if (success)
80  {
81  int error_type = tty_read(fd, response, 1, TimeOut, &nbytes_read);
82  success = (error_type == TTY_OK && nbytes_read == 1);
83  if (success)
84  {
85  *received_char = response[0];
86  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "ACK RESPONSE: <%c>", *received_char);
87  }
88  else
89  {
90  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Error reading ACK: %s", strerror(errno));
91  }
92  }
93  else
94  {
95  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Error sending ACK: %s", strerror(errno));
96  }
97 
98  return success;
99 }
100 
101 inline bool _isValidACKResponse_(char test_char)
102 {
103  bool success;
104  switch (test_char)
105  {
106  case 'P':
107  case 'A':
108  case 'L':
109  success = true;
110  break;
111  default:
112  success = false;
113  break;
114  }
115  return success;
116 }
117 
118 void _resynchronize_(const int fd)
119 {
120  DEBUGDEVICE(lx200Name, DBG_SCOPE, "RESYNC");
121  const int ack_maxtries = 10;
122  int ack_try_cntr = 0;
123 
124  char lead_ACK = LX200Pulsar2::Null;
125  char follow_ACK = LX200Pulsar2::Null;
126  tcflush(fd, TCIOFLUSH);
127  while (resynchronize_needed && ack_try_cntr++ < ack_maxtries)
128  {
129  if (_isValidACKResponse_(lead_ACK) || (_sendReceiveACK_(fd, &lead_ACK) && _isValidACKResponse_(lead_ACK)))
130  {
131  if (_isValidACKResponse_(follow_ACK) || (_sendReceiveACK_(fd, &follow_ACK) && _isValidACKResponse_(follow_ACK)))
132  {
133  if (follow_ACK == lead_ACK)
134  {
135  resynchronize_needed = false;
136  }
137  else
138  {
139  lead_ACK = follow_ACK;
140  follow_ACK = LX200Pulsar2::Null;
141  }
142  }
143  else
144  {
145  lead_ACK = LX200Pulsar2::Null;
146  follow_ACK = LX200Pulsar2::Null;
147  tcflush(fd, TCIFLUSH);
148  }
149  }
150  else
151  {
152  lead_ACK = LX200Pulsar2::Null;
153  follow_ACK = LX200Pulsar2::Null;
154  tcflush(fd, TCIFLUSH);
155  }
156  }
157 
158 
159  if (resynchronize_needed)
160  {
161  resynchronize_needed = false; // whether we succeeded or failed
162  DEBUGDEVICE(lx200Name, DBG_SCOPE, "RESYNC error");
163  if (LX200Pulsar2::verboseLogging) LOG_INFO("tty resynchronize failed");
164  }
165  else
166  {
167  DEBUGDEVICE(lx200Name, DBG_SCOPE, "RESYNC complete");
168  if (LX200Pulsar2::verboseLogging) LOG_INFO("tty resynchronize complete");
169  }
170 
171 }
172 
173 // Send a command string without waiting for any response from the Pulsar controller
174 bool _send_(const int fd, const char *cmd)
175 {
176  if (resynchronize_needed) _resynchronize_(fd);
177  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd);
178  const int nbytes = strlen(cmd);
179  int nbytes_written = 0;
180  do
181  {
182  const int errcode = tty_write(fd, &cmd[nbytes_written], nbytes - nbytes_written, &nbytes_written);
183  if (errcode != TTY_OK)
184  {
185  char errmsg[MAXRBUF];
186  tty_error_msg(errcode, errmsg, MAXRBUF);
187  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Error: %s (%s)", errmsg, strerror(errno));
188  return false;
189  }
190  }
191  while (nbytes_written < nbytes); // Ensure that all characters have been sent
192 
193  return true;
194 }
195 
196 // Receive a terminated response string
197 bool _receive_(const int fd, char response[], const char *cmd)
198 {
199  const struct timespec nanosleeptime = {0, 100000000L}; // 1/10th second
200  response[0] = LX200Pulsar2::Null;
201  bool done = false;
202  int nbytes_read_total = 0;
203  int attempt;
204  for (attempt = 0; !done; ++attempt)
205  {
206  int nbytes_read = 0;
207  const int errcode = tty_read_section(fd, response + nbytes_read_total, Termination, TimeOut, &nbytes_read);
208  if (errcode != TTY_OK)
209  {
210  char errmsg[MAXRBUF];
211  tty_error_msg(errcode, errmsg, MAXRBUF);
212  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Error: %s (%s, attempt %d)", errmsg, strerror(errno), attempt);
213  nbytes_read_total +=
214  nbytes_read; // Keep track of how many characters have been read successfully despite the error
215  if (attempt == MaxAttempts - 1)
216  {
217  resynchronize_needed = (errcode == TTY_TIME_OUT);
218  response[nbytes_read_total] = LX200Pulsar2::Null;
219  if (LX200Pulsar2::verboseLogging) LOGF_INFO("receive: resynchronize_needed flag set for cmd: %s, previous cmd was: %s", cmd,
220  lastCmd);
221  return false;
222  }
223  else
224  {
225  nanosleep(&nanosleeptime, nullptr);
226  }
227  }
228  else
229  {
230  // Skip response strings consisting of a single termination character
231  if (nbytes_read_total == 0 && response[0] == Termination)
232  response[0] = LX200Pulsar2::Null;
233  else
234  {
235  nbytes_read_total += nbytes_read;
236  done = true;
237  }
238  }
239  }
240  response[nbytes_read_total - 1] = LX200Pulsar2::Null; // Remove the termination character
241  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s> (attempt %d)", response, attempt);
242 
243  if (LX200Pulsar2::verboseLogging) strncpy(lastCmd, cmd, 39);
244 
245  return true;
246 }
247 
248 } // end anonymous namespace
249 
250 
251 
252 // --- --- --- --- --- --- --- ---
253 // "public" namespace methods
254 // --- --- --- --- --- --- --- ---
255 
256 // send a command to the controller, without expectation of a return value.
257 bool sendOnly(const int fd, const char *cmd)
258 {
259  const std::lock_guard<std::mutex> lock(dev_mtx);
260  return _send_(fd, cmd);
261 }
262 
263 
264 // Send a command string and wait for a single character response indicating
265 // success or failure. Ignore leading # characters.
266 bool confirmed(const int fd, const char *cmd, char &response)
267 {
268  response = Termination;
269  const std::lock_guard<std::mutex> lock(dev_mtx);
270  if (_send_(fd, cmd))
271  {
272  for (int attempt = 0; response == Termination; ++attempt)
273  {
274  int nbytes_read = 0;
275  const int errcode = tty_read(fd, &response, sizeof(response), TimeOut, &nbytes_read);
276  if (errcode != TTY_OK)
277  {
278  char errmsg[MAXRBUF];
279  tty_error_msg(errcode, errmsg, MAXRBUF);
280  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Error: %s (%s, attempt %d)", errmsg, strerror(errno), attempt);
281  if (attempt == MaxAttempts - 1)
282  {
283  resynchronize_needed = true;
284  if (LX200Pulsar2::verboseLogging) LOGF_INFO("confirmed: resynchronize_needed flag set for cmd: %s", cmd);
285  return false; // early exit
286  }
287  }
288  else // tty_read was successful and nbytes_read should be 1
289  {
290  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%c> (attempt %d)", response, attempt);
291  }
292  }
293  }
294  return true;
295 }
296 
297 
298 // send a command to the controller, expect a terminated response
299 bool sendReceive(const int fd, const char *cmd, char response[])
300 {
301  const std::lock_guard<std::mutex> lock(dev_mtx);
302  bool success = _send_(fd, cmd);
303  if (success)
304  {
305  success = _receive_(fd, response, cmd);
306  }
307  return success;
308 }
309 
310 // send a command to the controller, expect (up to) two terminated responses
311 bool sendReceive2(const int fd, const char *cmd, char response1[], char response2[])
312 {
313  const std::lock_guard<std::mutex> lock(dev_mtx);
314  bool success = _send_(fd, cmd);
315  if (success)
316  {
317  success = _receive_(fd, response1, cmd);
318  if (success && response1[1] != Termination) // questionable
319  {
320  success = _receive_(fd, response2, cmd);
321  }
322  else
323  {
324  *response2 = LX200Pulsar2::Null;
325  }
326  }
327  return success;
328 }
329 
330 // send a command to the controller, expect an integral response
331 bool sendReceiveInt(const int fd, const char *cmd, int *value)
332 {
333  char response[16];
334  response[15] = LX200Pulsar2::Null;
335  bool success = sendReceive(fd, cmd, response);
336 
337  if (!success)
338  {
339  unsigned long rlen = strlen(response);
340  if (LX200Pulsar2::verboseLogging) LOGF_INFO("sendReceiveInt() Failed cmd is: %s, response len is: %lu ", cmd, rlen);
341  }
342 
343  if (success)
344  {
345  success = (sscanf(response, "%d", value) == 1);
346  if (success)
347  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%d]", *value);
348  else
349  DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response for integer value");
350  }
351  return success;
352 }
353 
354 // go through the tty "resynchronize" protocol
355 void resyncTTY(const int fd)
356 {
357  const std::lock_guard<std::mutex> lock(dev_mtx);
358  resynchronize_needed = true;
359  _resynchronize_(fd);
360 }
361 
362 }; // end namespace PulsarTX
363 
364 
365 
367 {
368 // --- --- --- --- --- --- --- ---
369 // enums and static data members
370 // --- --- --- --- --- --- --- ---
372 {
374  PECorrectionOn = 1
375 };
376 
378 {
380  RCorrectionOn = 1
381 };
382 
384 {
392  RateNone = 99
393 };
394 
396 {
397  German = 0,
398  Fork = 1,
399  AltAz = 2,
401 };
402 
404 {
408 };
409 
411 {
413  PoleCrossingOn = 1
414 };
415 
417 {
419  RotationOne = 1
420 };
421 
423 {
424  SlewMax = 0,
429 };
430 
432 {
433  North = 0,
438 };
439 
440 
441 // state flags
442 static OTASideOfPier currentOTASideOfPier = OTASideOfPier::InvalidSideOfPier; // polling will handle this correctly
443 static int site_location_initialized = 0;
444 static bool check_ota_side_of_pier = false; // flip-flop
445 static bool speedsExtended = false; // may change according to firware version
446 
447 // static codes and labels
448 static const char *DirectionName[Pulsar2Commands::NumDirections] = { "North", "East", "South", "West" };
449 static const char DirectionCode[Pulsar2Commands::NumDirections] = {'n', 'e', 's', 'w' };
450 static char nonGuideSpeedUnit[] = "1x Sidereal";
451 static char nonGuideSpeedExtendedUnit[] = "1/6x Sidereal";
452 
453 
454 // --- --- --- --- --- --- --- ---
455 // local namespace methods
456 // --- --- --- --- --- --- --- ---
457 
458 const char * getDeviceName() // a local implementation meant only to satisfy logging macros such as LOG_INFO
459 {
460  return static_cast<const char *>("Pulsar2");
461 }
462 
463 
464 // (was inline)
465 bool getVersion(const int fd, char response[])
466 {
467  return PulsarTX::sendReceive(fd, ":YV#", response);
468 }
469 
470 bool getPECorrection(const int fd, PECorrection *PECra, PECorrection *PECdec)
471 {
472  char response[8];
473  bool success = PulsarTX::sendReceive(fd, "#:YGP#", response);
474  if (success)
475  {
476  success = (sscanf(response, "%1d,%1d", reinterpret_cast<int *>(PECra), reinterpret_cast<int *>(PECdec)) == 2);
477  }
478  return success;
479 }
480 
481 bool getRCorrection(const int fd, RCorrection *Rra, RCorrection *Rdec)
482 {
483  char response[8];
484  bool success = PulsarTX::sendReceive(fd, "#:YGR#", response);
485  if (success)
486  {
487  success = (sscanf(response, "%1d,%1d", reinterpret_cast<int *>(Rra), reinterpret_cast<int *>(Rdec)) == 2);
488  }
489  return success;
490 }
491 
493 {
494  TrackingRateInd result = Pulsar2Commands::RateNone; // start off pessimistic
495  char response[16];
496  response[15] = LX200Pulsar2::Null;
497  if (PulsarTX::sendReceive(fd, "#:YGS#", response))
498  {
499  int ra_tri, dec_tri;
500  if (sscanf(response, "%1d,%1d", &ra_tri, &dec_tri) == 2)
501  {
502  result = static_cast<TrackingRateInd>(ra_tri == 0 ? (LX200Pulsar2::numPulsarTrackingRates - 1) : --ra_tri);
503  }
504  }
505  return result;
506 }
507 
508 
510 {
511  MountType result = Pulsar2Commands::German; // the overwhelming default
512  char response[16];
513  response[15] = LX200Pulsar2::Null;
514  if (PulsarTX::sendReceive(fd, "#:YGM#", response))
515  {
516  int itype;
517  if (sscanf(response, "%d", &itype) == 1)
518  {
519  switch (itype)
520  {
521  case 1:
522  result = Pulsar2Commands::German;
523  break;
524  case 2:
525  result = Pulsar2Commands::Fork;
526  break;
527  case 3:
528  result = Pulsar2Commands::AltAz;
529  break;
530  default:
531  break; // paranoid
532  }
533  }
534  }
535  return result;
536 }
537 
538 int getSpeedInd(const int fd, const char *cmd)
539 {
540  int result = 0; // start off pessimistic (zero is a non-valid value)
541  char response[16];
542  response[15] = LX200Pulsar2::Null;
543  if (PulsarTX::sendReceive(fd, cmd, response))
544  {
545  int dec_dummy;
546  if (sscanf(response, "%d,%d", &result, &dec_dummy) != 2) result = 0;
547  }
548  return result;
549 }
550 
551 int getGuideSpeedInd(const int fd)
552 {
553  return getSpeedInd(fd, "#:YGA#");
554 }
555 
556 int getCenterSpeedInd(const int fd)
557 {
558  return getSpeedInd(fd, "#:YGB#");
559 }
560 
561 int getFindSpeedInd(const int fd)
562 {
563  return getSpeedInd(fd, "#:YGC#");
564 }
565 
566 int getSlewSpeedInd(const int fd)
567 {
568  return getSpeedInd(fd, "#:YGD#");
569 }
570 
571 int getGoToSpeedInd(const int fd)
572 {
573  return getSpeedInd(fd, "#:YGE#");
574 }
575 
576 
577 bool getSwapTubeDelay(const int fd, int *delay_value) // unknown so far
578 {
579  bool success = PulsarTX::sendReceiveInt(fd, "", delay_value);
580  return success;
581 }
582 
583 bool getPoleCrossingDirection(const int fd, int *direction) // unknown so far
584 {
585  bool success = PulsarTX::sendReceiveInt(fd, "", direction);
586  return success;
587 }
588 
589 
590 bool getRamp(const int fd, int *ra_ramp, int *dec_ramp)
591 {
592  char response[16];
593  response[15] = LX200Pulsar2::Null;
594  bool success = PulsarTX::sendReceive(fd, "#:YGp#", response);
595  if (success)
596  {
597  success = (sscanf(response, "%u,%u", ra_ramp, dec_ramp) == 2);
598  }
599  return success;
600 }
601 
602 bool setRamp(const int fd, int ra_ramp, int dec_ramp)
603 {
604  char cmd[16];
605  cmd[15] = LX200Pulsar2::Null;
606  int safe_ra_ramp = std::min(std::max(1, ra_ramp), 10);
607  int safe_dec_ramp = std::min(std::max(1, dec_ramp), 10);
608  sprintf(cmd, "#:YSp%d,%d#", safe_ra_ramp, safe_dec_ramp);
609  char response = LX200Pulsar2::Null;
610  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
611 }
612 
613 bool getReduction(const int fd, int *red_ra, int *red_dec)
614 {
615  char response[20];
616  response[19] = LX200Pulsar2::Null;
617  bool success = PulsarTX::sendReceive(fd, "#:YGr#", response);
618  if (success)
619  {
620  success = (sscanf(response, "%u,%u", red_ra, red_dec) == 2);
621  }
622  return success;
623 }
624 
625 bool setReduction(const int fd, int red_ra, int red_dec)
626 {
627  char cmd[20];
628  cmd[19] = LX200Pulsar2::Null;
629  int safe_red_ra = std::min(std::max(100, red_ra), 6000);
630  int safe_red_dec = std::min(std::max(100, red_dec), 6000);
631  sprintf(cmd, "#:YSr%d,%d#", safe_red_ra, safe_red_dec);
632  char response = LX200Pulsar2::Null;
633  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
634 }
635 
636 
637 bool getMaingear(const int fd, int *mg_ra, int *mg_dec)
638 {
639  char response[20];
640  response[19] = LX200Pulsar2::Null;
641  bool success = PulsarTX::sendReceive(fd, "#:YGm#", response);
642  if (success)
643  {
644  success = (sscanf(response, "%u,%u", mg_ra, mg_dec) == 2);
645  }
646  return success;
647 }
648 
649 bool setMaingear(const int fd, int mg_ra, int mg_dec)
650 {
651  char cmd[20];
652  cmd[19] = LX200Pulsar2::Null;
653  int safe_mg_ra = std::min(std::max(100, mg_ra), 6000);
654  int safe_mg_dec = std::min(std::max(100, mg_dec), 6000);
655  sprintf(cmd, "#:YSm%d,%d#", safe_mg_ra, safe_mg_dec);
656  char response = LX200Pulsar2::Null;
657  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
658 }
659 
660 bool getBacklash(const int fd, int *bl_min, int *bl_sec)
661 {
662  char response[20];
663  response[19] = LX200Pulsar2::Null;
664  bool success = PulsarTX::sendReceive(fd, "#:YGb#", response);
665  if (success)
666  {
667  success = (sscanf(response, "%u:%u", bl_min, bl_sec) == 2);
668  }
669  return success;
670 }
671 
672 bool setBacklash(const int fd, int bl_min, int bl_sec)
673 {
674  char cmd[20];
675  cmd[19] = LX200Pulsar2::Null;
676  int safe_bl_min = std::min(std::max(0, bl_min), 9);
677  int safe_bl_sec = std::min(std::max(0, bl_sec), 59);
678  sprintf(cmd, "#:YSb%d,%02d#", safe_bl_min, safe_bl_sec);
679  char response = LX200Pulsar2::Null;
680  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
681 }
682 
683 bool getHomePosition(const int fd, double *hp_alt, double *hp_az)
684 {
685  char response[30];
686  response[18] = LX200Pulsar2::Null;
687  response[29] = LX200Pulsar2::Null;
688  bool success = PulsarTX::sendReceive(fd, "#:YGX#", response);
689  if (success)
690  {
691  success = (sscanf(response, "%lf,%lf", hp_alt, hp_az) == 2);
692  }
693  return success;
694 }
695 
696 bool setHomePosition(const int fd, double hp_alt, double hp_az)
697 {
698  char cmd[30];
699  cmd[29] = LX200Pulsar2::Null;
700  double safe_hp_alt = std::min(std::max(0.0, hp_alt), 90.0);
701  // There are odd limits for azimuth because the controller rounds
702  // strangely, and defaults to a 180-degree value if it sees a number
703  // as out-of-bounds. The min value here (0.0004) will be intepreted
704  // as zero, max (359.9994) as 360.
705  double safe_hp_az = std::min(std::max(0.0004, hp_az), 359.9994);
706  sprintf(cmd, "#:YSX%+08.4lf,%08.4lf#", safe_hp_alt, safe_hp_az);
707  char response = LX200Pulsar2::Null;
708  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
709 }
710 
711 // note that the following has not been verified to work correctly
712 bool getUserRate(const int fd, int usr_ind, double *ur_ra, double *ur_dec)
713 {
714  char response[30];
715  response[22] = LX200Pulsar2::Null;
716  response[29] = LX200Pulsar2::Null;
717  if (usr_ind < 1 || usr_ind > 3) return false; // paranoid, early exit
718  char cmd[] = "#:YGZ_#";
719  cmd[5] = usr_ind + '0';
720  bool success = PulsarTX::sendReceive(fd, cmd, response);
721  if (success)
722  {
723  // debug
724  //LOGF_INFO("getUserRate(%d) for cmd: %s returns: %s", usr_ind, cmd, response);
725  // end debug
726  success = (sscanf(response, "%lf,%lf", ur_ra, ur_dec) == 2);
727  }
728  return success;
729 }
730 
731 
732 bool getUserRate1(const int fd, double *u1_ra, double *u1_dec)
733 {
734  return getUserRate(fd, 1, u1_ra, u1_dec);
735 }
736 
737 bool getUserRate2(const int fd, double *u2_ra, double *u2_dec)
738 {
739  return getUserRate(fd, 2, u2_ra, u2_dec);
740 }
741 
742 bool getUserRate3(const int fd, double *u3_ra, double *u3_dec)
743 {
744  return getUserRate(fd, 3, u3_ra, u3_dec);
745 }
746 
747 
748 // note that the following has not been verified to work correctly
749 bool setUserRate(const int fd, int usr_ind, double ur_ra, double ur_dec)
750 {
751  if (usr_ind < 1 || usr_ind > 3) return false; // paranoid, early exit
752  char cmd[36];
753  cmd[35] = LX200Pulsar2::Null;
754  double safe_ur_ra = std::min(std::max(-4.1887902, ur_ra), 4.1887902);
755  double safe_ur_dec = std::min(std::max(-4.1887902, ur_dec), 4.1887902);
756  sprintf(cmd, "#:YSZ%1c%+09.7lf,%+09.7lf#", usr_ind + '0', safe_ur_ra, safe_ur_dec);
757  char response = LX200Pulsar2::Null;
758  // debug
759  //LOGF_INFO("setUserRate(%d) sending command: %s", usr_ind, cmd);
760  // end debug
761  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
762 }
763 
764 bool setUserRate1(const int fd, double ur_ra, double ur_dec)
765 {
766  return setUserRate(fd, 1, ur_ra, ur_dec);
767 }
768 
769 bool setUserRate2(const int fd, double ur_ra, double ur_dec)
770 {
771  return setUserRate(fd, 2, ur_ra, ur_dec);
772 }
773 
774 bool setUserRate3(const int fd, double ur_ra, double ur_dec)
775 {
776  return setUserRate(fd, 3, ur_ra, ur_dec);
777 }
778 
779 
780 
781 int getCurrentValue(const int fd, const char *cmd)
782 {
783  int result = 0; // start off pessimistic (zero is a non-valid value)
784  char response[16];
785  response[15] = LX200Pulsar2::Null;
786  if (PulsarTX::sendReceive(fd, cmd, response))
787  {
788  int dec_dummy;
789  if (sscanf(response, "%d,%d", &result, &dec_dummy) != 2) result = 0;
790  }
791  return result;
792 }
793 
794 int getTrackingCurrent(const int fd) // return is mA
795 {
796  return getCurrentValue(fd, "#:YGt#");
797 }
798 
799 int getStopCurrent(const int fd) // return is mA
800 {
801  return getCurrentValue(fd, "#:YGs#");
802 }
803 
804 int getGoToCurrent(const int fd) // return is mA
805 {
806  return getCurrentValue(fd, "#:YGg#");
807 }
808 
810 {
811  char cmd[] = "#:YSM_#";
812  cmd[5] = '0' + (char)((int)mtype + 1);
813  char response = LX200Pulsar2::Null;
814  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
815 }
816 
817 bool setCurrentValue(const int fd, const char *partialCmd, const int mA, const int maxmA) // input is mA
818 {
819  char cmd[20];
820  cmd[19] = LX200Pulsar2::Null;
821  char response;
822  int actualCur = std::min(std::max(mA, 100), maxmA); // reasonable limits
823  sprintf(cmd, "%s%04d,%04d#", partialCmd, actualCur, actualCur);
824  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
825 }
826 
827 bool setTrackingCurrent(const int fd, const int mA)
828 {
829  return setCurrentValue(fd, "#:YSt", mA, 2000);
830 }
831 
832 bool setStopCurrent(const int fd, const int mA)
833 {
834  return setCurrentValue(fd, "#:YSs", mA, 2000);
835 }
836 
837 bool setGoToCurrent(const int fd, const int mA)
838 {
839  return setCurrentValue(fd, "#:YSg", mA, 2000);
840 }
841 
842 bool setSpeedInd(const int fd, const char *partialCmd, const int speedInd, const int maxInd)
843 {
844  char cmd[20];
845  cmd[19] = LX200Pulsar2::Null;
846  char response;
847  int actualInd = std::min(std::max(speedInd, 1), maxInd);
848  sprintf(cmd, "%s%04d,%04d#", partialCmd, actualInd, actualInd);
849  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
850 }
851 
852 bool setGuideSpeedInd(const int fd, const int speedInd)
853 {
854  return setSpeedInd(fd, "#:YSA", speedInd, 9);
855 }
856 
857 bool setCenterSpeedInd(const int fd, const int speedInd)
858 {
859  int maxVal = speedsExtended ? 9999 : 999;
860  return setSpeedInd(fd, "#:YSB", speedInd, maxVal);
861 }
862 
863 bool setFindSpeedInd(const int fd, const int speedInd)
864 {
865  int maxVal = speedsExtended ? 9999 : 999;
866  return setSpeedInd(fd, "#:YSC", speedInd, maxVal);
867 }
868 
869 bool setSlewSpeedInd(const int fd, const int speedInd)
870 {
871  int maxVal = speedsExtended ? 9999 : 999;
872  return setSpeedInd(fd, "#:YSD", speedInd, maxVal);
873 }
874 
875 bool setGoToSpeedInd(const int fd, const int speedInd)
876 {
877  int maxVal = speedsExtended ? 9999 : 999;
878  return setSpeedInd(fd, "#:YSE", speedInd, maxVal);
879 }
880 
881 
882 bool getSideOfPier(const int fd, OTASideOfPier *ota_side_of_pier)
883 {
884  *ota_side_of_pier = Pulsar2Commands::OTASideOfPier::EastOfPier; // effectively a fail-safe default
885  int ival;
886  if (!PulsarTX::sendReceiveInt(fd, "#:YGN#", &ival)) return false;
887  if (ival == 1) *ota_side_of_pier = Pulsar2Commands::OTASideOfPier::WestOfPier;
888  return true;
889 }
890 
891 
892 bool getPoleCrossing(const int fd, PoleCrossing *pole_crossing)
893 {
894  return PulsarTX::sendReceiveInt(fd, "#:YGQ#", reinterpret_cast<int *>(pole_crossing));
895 }
896 
897 
898 bool getRotation(const int fd, Rotation *rot_ra, Rotation *rot_dec)
899 {
900  char response[8];
901  response[7] = LX200Pulsar2::Null;
902  bool success = PulsarTX::sendReceive(fd, "#:YGn#", response);
903  if (success)
904  {
905  success = (sscanf(response, "%1d,%1d", reinterpret_cast<int *>(rot_ra), reinterpret_cast<int *>(rot_dec)) == 2);
906  }
907  return success;
908 }
909 
910 
911 bool getSexa(const int fd, const char *cmd, double *value)
912 {
913  char response[16];
914  response[15] = LX200Pulsar2::Null;
915  bool success = PulsarTX::sendReceive(fd, cmd, response);
916  if (success)
917  {
918  success = (f_scansexa(response, value) == 0);
919  if (success)
920  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%g]", *value);
921  else
922  DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response");
923  }
924  return success;
925 }
926 
927 
928 bool getObjectRADec(const int fd, double *ra, double *dec)
929 {
930  return (getSexa(fd, "#:GR#", ra) && getSexa(fd, "#:GD#", dec));
931 }
932 
933 // --- --- --- --- --- --- --- --- --- --- ---
934 // Older-style geographic coordinate handling
935 //bool getDegreesMinutes(const int fd, const char *cmd, int *d, int *m)
936 //{
937 // *d = *m = 0;
938 // char response[16];
939 // bool success = sendReceive(fd, cmd, response);
940 // if (success)
941 // {
942 // success = (sscanf(response, "%d%*c%d", d, m) == 2);
943 // if (success)
944 // DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%+03d:%02d]", *d, *m);
945 // else
946 // DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response");
947 // }
948 // return success;
949 //}
950 
951 //inline bool getSiteLatitude(const int fd, int *d, int *m)
952 //{
953 // return getDegreesMinutes(fd, "#:Gt#", d, m);
954 //}
955 
956 //inline bool getSiteLongitude(const int fd, int *d, int *m)
957 //{
958 // return getDegreesMinutes(fd, "#:Gg#", d, m);
959 //}
960 // --- --- --- --- --- --- --- --- --- --- ---
961 
962 // Newer-style latitude-longitude in a single call, with correction to
963 // make west negative, rather than east (as the controller returns)
964 // (was inline)
965 bool getSiteLatitudeLongitude(const int fd, double *lat, double *lon)
966 {
967  *lat = 0.0;
968  *lon = 0.0;
969  char response[32];
970  response[31] = LX200Pulsar2::Null;
971 
972  bool success = PulsarTX::sendReceive(fd, "#:YGl#", response);
973  if (success)
974  {
975  success = (sscanf(response, "%lf,%lf", lat, lon) == 2);
976  if (success)
977  {
978  *lon = (*lon) * (-1.0);
979  }
980  else
981  {
982  DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse latitude-longitude response");
983  }
984  }
985  return success;
986 }
987 
988 bool getUTCDate(const int fd, int *m, int *d, int *y)
989 {
990  char response[12];
991  bool success = PulsarTX::sendReceive(fd, "#:GC#", response);
992  if (success)
993  {
994  success = (sscanf(response, "%2d%*c%2d%*c%2d", m, d, y) == 3);
995  if (success)
996  {
997  *y += (*y < 50 ? 2000 : 1900);
998  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%02d/%02d/%04d]", *m, *d, *y);
999  }
1000  else
1001  DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse date string");
1002  }
1003  return success;
1004 }
1005 
1006 bool getUTCTime(const int fd, int *h, int *m, int *s)
1007 {
1008  char response[12];
1009  bool success = PulsarTX::sendReceive(fd, "#:GL#", response);
1010  if (success)
1011  {
1012  success = (sscanf(response, "%2d%*c%2d%*c%2d", h, m, s) == 3);
1013  if (success)
1014  DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%02d:%02d:%02d]", *h, *m, *s);
1015  else
1016  DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse time string");
1017  }
1018  return success;
1019 }
1020 
1021 bool setDegreesMinutes(const int fd, const char *partialCmd, const double value)
1022 {
1023  int degrees, minutes, seconds;
1024  getSexComponents(value, &degrees, &minutes, &seconds);
1025  char full_cmd[32];
1026  snprintf(full_cmd, sizeof(full_cmd), "#:%s %03d:%02d#", partialCmd, degrees, minutes);
1027  char response;
1028  return (PulsarTX::confirmed(fd, full_cmd, response) && response == '1');
1029 }
1030 
1031 
1032 bool setSite(const int fd, const double longitude, const double latitude)
1033 {
1034  return (setDegreesMinutes(fd, "Sl", 360.0 - longitude) && setDegreesMinutes(fd, "St", latitude));
1035 }
1036 
1037 bool setSlewMode(const int fd, const SlewMode slewMode)
1038 {
1039  static const char *commands[NumSlewRates] { "#:RS#", "#:RM#", "#:RC#", "#:RG#" };
1040  return PulsarTX::sendOnly(fd, commands[slewMode]);
1041 }
1042 
1043 bool moveTo(const int fd, const Direction direction)
1044 {
1045  static const char *commands[NumDirections] = { "#:Mn#", "#:Me#", "#:Ms#", "#:Mw#" };
1046  return PulsarTX::sendOnly(fd, commands[direction]);
1047 }
1048 
1049 bool haltMovement(const int fd, const Direction direction)
1050 {
1051  static const char *commands[NumDirections] = { "#:Qn#", "#:Qe#", "#:Qs#", "#:Qw#" };
1052  return PulsarTX::sendOnly(fd, commands[direction]);
1053 }
1054 
1055 
1056 bool startSlew(const int fd)
1057 {
1058  char response[4];
1059  const bool success = (PulsarTX::sendReceive(fd, "#:MS#", response) && response[0] == '0');
1060  return success;
1061 }
1062 
1063 
1064 bool abortSlew(const int fd)
1065 {
1066  return PulsarTX::sendOnly(fd, "#:Q#");
1067 }
1068 
1069 // Pulse guide commands are only supported by the Pulsar2 controller, and NOT the older Pulsar controller
1070 bool pulseGuide(const int fd, const Direction direction, uint32_t ms)
1071 {
1072  // make sure our pulse length is in a reasonable range
1073  int safePulseLen = std::min(std::max(1, static_cast<int>(ms)), 9990);
1074 
1075  bool success = true;
1076  if (safePulseLen > 4) // otherwise send no guide pulse -- 10ms is our minimum (5+ will round upward)
1077  {
1078  // our own little rounding method, so as not to call slower library rounding routines
1079  int splm10 = safePulseLen % 10;
1080  if (splm10 != 0) // worth the test, since it happens frequently
1081  {
1082  safePulseLen = (splm10 > 4) ? (safePulseLen - splm10) + 10 : safePulseLen - splm10;
1083  }
1084 
1085  char cmd[16];
1086  snprintf(cmd, sizeof(cmd), "#:Mg%c%04d#", Pulsar2Commands::DirectionCode[direction], safePulseLen);
1087  success = PulsarTX::sendOnly(fd, cmd);
1089  {
1090  if (success)
1091  LOGF_INFO("Pulse guide sent, direction %c, len: %d ms, cmd: %s", Pulsar2Commands::DirectionCode[direction], safePulseLen,
1092  cmd);
1093  else
1094  LOGF_INFO("Pulse guide FAILED direction %c, len: %d ms, cmd: %s", Pulsar2Commands::DirectionCode[direction], safePulseLen,
1095  cmd);
1096  }
1097  }
1098  return success;
1099 }
1100 
1101 bool setTime(const int fd, const int h, const int m, const int s)
1102 {
1103  char full_cmd[32];
1104  snprintf(full_cmd, sizeof(full_cmd), "#:SL %02d:%02d:%02d#", h, m, s);
1105  char response;
1106  return (PulsarTX::confirmed(fd, full_cmd, response) && response == '1');
1107 }
1108 
1109 bool setDate(const int fd, const int dd, const int mm, const int yy)
1110 {
1111  char response1[64]; // only first character consulted
1112  char response2[64]; // not used
1113  char cmd[32];
1114  snprintf(cmd, sizeof(cmd), ":SC %02d/%02d/%02d#", mm, dd, (yy % 100));
1115  bool success = (PulsarTX::sendReceive2(fd, cmd, response1, response2) && (*response1 == '1'));
1116  return success;
1117 }
1118 
1119 bool ensureLongFormat(const int fd)
1120 {
1121  char response[16] = { 0 };
1122  bool success = PulsarTX::sendReceive(fd, "#:GR#", response);
1123  if (success)
1124  {
1125  if (response[5] == '.')
1126  {
1127  // In case of short format, set long format
1128  success = (PulsarTX::confirmed(fd, "#:U#", response[0]) && response[0] == '1');
1129  }
1130  }
1131  return success;
1132 }
1133 
1134 bool setObjectRA(const int fd, const double ra)
1135 {
1136  int h, m, s;
1137  getSexComponents(ra, &h, &m, &s);
1138  char full_cmd[32];
1139  snprintf(full_cmd, sizeof(full_cmd), "#:Sr %02d:%02d:%02d#", h, m, s);
1140  char response;
1141  return (PulsarTX::confirmed(fd, full_cmd, response) && response == '1');
1142 }
1143 
1144 bool setObjectDEC(const int fd, const double dec)
1145 {
1146  int d, m, s;
1147  getSexComponents(dec, &d, &m, &s);
1148  char full_cmd[32];
1149  snprintf(full_cmd, sizeof(full_cmd), "#:Sd %c%02d:%02d:%02d#", ( dec < 0.0 ? '-' : '+' ), abs(d), m, s);
1150  char response;
1151  return (PulsarTX::confirmed(fd, full_cmd, response) && response == '1');
1152 }
1153 
1154 
1155 bool setObjectRADec(const int fd, const double ra, const double dec)
1156 {
1157  return (setObjectRA(fd, ra) && setObjectDEC(fd, dec));
1158 }
1159 
1160 
1161 bool park(const int fd)
1162 {
1163  int success = 0;
1164  return (PulsarTX::sendReceiveInt(fd, "#:YH#", &success) && success == 1);
1165 }
1166 
1167 
1168 bool unpark(const int fd)
1169 {
1170  int result = 0;
1171  if (!PulsarTX::sendReceiveInt(fd, "#:YL#", &result))
1172  {
1173  // retry
1174  if (LX200Pulsar2::verboseLogging) LOG_INFO("Unpark retry compensating for failed unpark return value...");
1175  if (!PulsarTX::sendReceiveInt(fd, "#:YL#", &result))
1176  result = 0;
1177  }
1178  return (result == 1);
1179 }
1180 
1181 
1182 bool sync(const int fd)
1183 {
1184  return PulsarTX::sendOnly(fd, "#:CM#");
1185 }
1186 
1187 static const char ZeroOneChar[2] = { '0', '1' };
1188 
1189 bool setSideOfPier(const int fd, const OTASideOfPier ota_side_of_pier)
1190 {
1191  char cmd[] = "#:YSN_#";
1192  cmd[5] = ZeroOneChar[(int)ota_side_of_pier];
1193  char response;
1194  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1195 }
1196 
1197 bool setTrackingRateInd(const int fd, const TrackingRateInd tri)
1198 {
1199  char cmd[16];
1200  cmd[15] = LX200Pulsar2::Null;
1201  char response;
1202  unsigned int trii = static_cast<unsigned int>(tri);
1203  trii = (trii == (LX200Pulsar2::numPulsarTrackingRates - 1)) ? 0 : (trii + 1);
1204  sprintf(cmd, "#:YSS%u,%u#", trii, 0);
1205  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1206 }
1207 
1208 bool setPECorrection(const int fd, const PECorrection pec_ra, const PECorrection pec_dec)
1209 {
1210  char cmd[] = "#:YSP_,_#";
1211  cmd[5] = ZeroOneChar[(int)pec_ra];
1212  cmd[7] = ZeroOneChar[(int)pec_dec];
1213  char response;
1214  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1215 }
1216 
1217 bool setPoleCrossing(const int fd, const PoleCrossing pole_crossing)
1218 {
1219  char cmd[] = "#:YSQ_#";
1220  cmd[5] = ZeroOneChar[(int)pole_crossing];
1221  char response;
1222  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1223 }
1224 
1225 bool setRCorrection(const int fd, const RCorrection rc_ra, const RCorrection rc_dec)
1226 {
1227  char cmd[] = "#:YSR_,_#";
1228  cmd[5] = ZeroOneChar[(int)rc_ra];
1229  cmd[7] = ZeroOneChar[(int)rc_dec];
1230  char response;
1231  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1232 }
1233 
1234 
1235 bool setRotation(const int fd, const Rotation rot_ra, const Rotation rot_dec)
1236 {
1237  char cmd[] = "#:YSn_,_#";
1238  cmd[5] = ZeroOneChar[(int)rot_ra];
1239  cmd[7] = ZeroOneChar[(int)rot_dec];
1240  char response;
1241  return (PulsarTX::confirmed(fd, cmd, response) && response == '1');
1242 }
1243 
1244 // - - - - - - - - - - - - - - - - - - -
1245 // Predicates
1246 // - - - - - - - - - - - - - - - - - - -
1247 
1248 bool isHomeSet(const int fd)
1249 {
1250  int is_home_set = -1;
1251  return (PulsarTX::sendReceiveInt(fd, "#:YGh#", &is_home_set) && is_home_set == 1);
1252 }
1253 
1254 
1255 bool isParked(const int fd)
1256 {
1257  int is_parked = -1;
1258  return (PulsarTX::sendReceiveInt(fd, "#:YGk#", &is_parked) && is_parked == 1);
1259 }
1260 
1261 
1262 bool isParking(const int fd)
1263 {
1264  int is_parking = -1;
1265  return (PulsarTX::sendReceiveInt(fd, "#:YGj#", &is_parking) && is_parking == 1);
1266 }
1267 
1268 }; // end Pulsar2Commands namespace
1269 
1270 
1271 // ----------------------------------------------------
1272 // LX200Pulsar2 implementation
1273 // ----------------------------------------------------
1274 
1275 // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1276 // constructor
1277 // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1278 LX200Pulsar2::LX200Pulsar2() : LX200Generic(), just_started_slewing(false)
1279 {
1280  setVersion(1, 3);
1281  //setLX200Capability(0);
1283 
1284  // Note that we do not have TELESCOPE_PIER_SIDE indicated here, since we re-implement it --
1285  // there is just too much confusion surrounding that value, so we preempt it.
1288 
1289  PulsarTX::lastCmd[0] = LX200Pulsar2::Null; // paranoid
1290 }
1291 
1292 // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1293 // Overrides
1294 // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1295 
1297 {
1298  return static_cast<const char *>("Pulsar2");
1299 }
1300 
1302 {
1303  const bool success = INDI::Telescope::Connect(); // takes care of hardware connection
1304  if (success)
1305  {
1307  {
1308  LOGF_DEBUG("%s", "Trying to wake up the mount.");
1309  UnPark();
1310  }
1311  else
1312  {
1313  LOGF_DEBUG("%s", "The mount was awake on connection.");
1314  // the following assumes we are tracking, since there is no "idle" state for Pulsar2
1316  ParkS[1].s = ISS_ON; // Unparked
1317  IDSetSwitch(&ParkSP, nullptr);
1318  }
1319  }
1320 
1321  return success;
1322 }
1323 
1325 {
1326  // TODO: set tracking state (?)
1328 
1329  return true;
1330 }
1331 
1333 {
1334  // Anything needs to be done besides this? INDI::Telescope would call ReadScopeStatus but
1335  // maybe we need to UnPark() before ReadScopeStatus() can return valid results?
1336  return true;
1337 }
1338 
1339 // The following function is called at the configured polling interval
1341 {
1342  bool success = isConnected();
1343 
1344  if (success)
1345  {
1346  success = isSimulation();
1347  if (success)
1348  mountSim();
1349  else
1350  {
1351  if (this->initialization_complete)
1352  {
1353  // set track state for slewing and parking
1354  switch (TrackState)
1355  {
1356  case SCOPE_SLEWING:
1357  // Check if LX200 is done slewing
1358  if (isSlewComplete())
1359  {
1360  // Set slew mode to "Centering"
1363  IDSetSwitch(&SlewRateSP, nullptr);
1365  IDMessage(getDeviceName(), "Slew is complete. Tracking...");
1366  }
1367  break;
1368 
1369  case SCOPE_PARKING:
1370  if (isSlewComplete() && !Pulsar2Commands::isParking(PortFD)) // !isParking() is experimental
1371  SetParked(true);
1372  break;
1373 
1374  default:
1375  break;
1376  }
1377 
1378  // read RA/Dec
1380  if (success)
1382  else
1383  {
1384  EqNP.s = IPS_ALERT;
1385  IDSetNumber(&EqNP, "Error reading RA/DEC.");
1386  }
1387 
1388  // check side of pier -- note that this is done only every other polling cycle
1389  Pulsar2Commands::check_ota_side_of_pier = !Pulsar2Commands::check_ota_side_of_pier; // set flip-flop
1390  if (Pulsar2Commands::check_ota_side_of_pier)
1391  {
1392  Pulsar2Commands::OTASideOfPier ota_side_of_pier;
1393  if (Pulsar2Commands::getSideOfPier(PortFD, &ota_side_of_pier))
1394  {
1395  if (ota_side_of_pier != Pulsar2Commands::currentOTASideOfPier) // init, or something changed
1396  {
1398  ota_side_of_pier == Pulsar2Commands::EastOfPier ? ISS_ON : ISS_OFF;
1400  ota_side_of_pier == Pulsar2Commands::WestOfPier ? ISS_ON : ISS_OFF;
1401  IDSetSwitch(&PierSideSP, nullptr);
1402  Pulsar2Commands::currentOTASideOfPier = ota_side_of_pier; // not thread-safe
1403  }
1404  }
1405  else
1406  {
1408  IDSetSwitch(&PierSideSP, "Could not read OTA side of pier from controller");
1409  if (LX200Pulsar2::verboseLogging) LOG_INFO("Could not read OTA side of pier from controller");
1410  }
1411  } // side of pier check
1412  } // init complete
1413  } // not a simulation
1414  }
1415 
1416  return success;
1417 }
1418 
1419 void LX200Pulsar2::ISGetProperties(const char *dev)
1420 {
1421  if (dev != nullptr && strcmp(dev, getDeviceName()) != 0)
1422  return;
1423  // just pass this to the parent -- it will eventually call the grandparent,
1424  // which will (nearly) first thing call initProperties()
1426 }
1427 
1428 // this function is called only once by DefaultDevice::ISGetProperties()
1430 {
1431  const bool result = LX200Generic::initProperties();
1432  if (result) // pretty much always true
1433  {
1434  IUFillSwitch(&TrackingRateIndS[0], "RATE_SIDEREAL", "Sidereal", ISS_ON);
1435  IUFillSwitch(&TrackingRateIndS[1], "RATE_LUNAR", "Lunar", ISS_OFF);
1436  IUFillSwitch(&TrackingRateIndS[2], "RATE_SOLAR", "Solar", ISS_OFF);
1437  IUFillSwitch(&TrackingRateIndS[3], "RATE_USER1", "User1", ISS_OFF);
1438  IUFillSwitch(&TrackingRateIndS[4], "RATE_USER2", "User2", ISS_OFF);
1439  IUFillSwitch(&TrackingRateIndS[5], "RATE_USER3", "User3", ISS_OFF);
1440  IUFillSwitch(&TrackingRateIndS[6], "RATE_STILL", "Still", ISS_OFF);
1442  "TRACKING_RATE_IND", "Tracking Rate", MOTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1443 
1444 
1445  IUFillNumber(&GuideSpeedIndN[0], "GUIDE_SPEED_IND", "0.1x Sidereal", "%.0f", 1, 9, 1, 0.0);
1446  IUFillNumberVector(&GuideSpeedIndNP, GuideSpeedIndN, 1, getDeviceName(), "GUIDE_SPEED_IND", "Guide Speed",
1447  MOTION_TAB, IP_RW, 0, IPS_IDLE);
1448 
1449  // Note that the following three values may be modified dynamically in getBasicData
1450  int nonGuideSpeedMax = Pulsar2Commands::speedsExtended ? 9999 : 999;
1451  int nonGuideSpeedStep = Pulsar2Commands::speedsExtended ? 100 : 10;
1452  const char *nonGuideSpeedLabel = Pulsar2Commands::speedsExtended ? "1/6x Sidereal" : "1x Sidereal";
1453 
1454  IUFillNumber(&CenterSpeedIndN[0], "CENTER_SPEED_IND", nonGuideSpeedLabel, "%.0f", 1, nonGuideSpeedMax, nonGuideSpeedStep,
1455  0.0);
1456  IUFillNumberVector(&CenterSpeedIndNP, CenterSpeedIndN, 1, getDeviceName(), "CENTER_SPEED_IND", "Center Speed",
1457  MOTION_TAB, IP_RW, 0, IPS_IDLE);
1458 
1459  IUFillNumber(&FindSpeedIndN[0], "FIND_SPEED_IND", nonGuideSpeedLabel, "%.0f", 1, nonGuideSpeedMax, nonGuideSpeedStep, 0.0);
1460  IUFillNumberVector(&FindSpeedIndNP, FindSpeedIndN, 1, getDeviceName(), "FIND_SPEED_IND", "Find Speed",
1461  MOTION_TAB, IP_RW, 0, IPS_IDLE);
1462 
1463  IUFillNumber(&SlewSpeedIndN[0], "SLEW_SPEED_IND", nonGuideSpeedLabel, "%.0f", 1, nonGuideSpeedMax, nonGuideSpeedStep, 0.0);
1464  IUFillNumberVector(&SlewSpeedIndNP, SlewSpeedIndN, 1, getDeviceName(), "SLEW_SPEED_IND", "Slew Speed",
1465  MOTION_TAB, IP_RW, 0, IPS_IDLE);
1466 
1467  IUFillNumber(&GoToSpeedIndN[0], "GOTO_SPEED_IND", nonGuideSpeedLabel, "%.0f", 1, nonGuideSpeedMax, nonGuideSpeedStep, 0.0);
1468  IUFillNumberVector(&GoToSpeedIndNP, GoToSpeedIndN, 1, getDeviceName(), "GOTO_SPEED_IND", "GoTo Speed",
1469  MOTION_TAB, IP_RW, 0, IPS_IDLE);
1470 
1471  // ramp
1472  IUFillNumber(&RampN[0], "RAMP_RA", "RA Ramp", "%.0f", 1, 10, 1, 0.0);
1473  IUFillNumber(&RampN[1], "RAMP_DEC", "Dec Ramp", "%.0f", 1, 10, 1, 0.0);
1474  IUFillNumberVector(&RampNP, RampN, 2, getDeviceName(), "RAMP", "Ramp",
1476 
1477  // reduction
1478  IUFillNumber(&ReductionN[0], "REDUCTION_RA", "RA Reduction", "%.2f", 100, 6000, 100, 0.0);
1479  IUFillNumber(&ReductionN[1], "REDUCTION_DEC", "Dec Reduction", "%.2f", 100, 6000, 100, 0.0);
1480  IUFillNumberVector(&ReductionNP, ReductionN, 2, getDeviceName(), "REDUCTION", "Reduction",
1482 
1483  // maingear
1484  IUFillNumber(&MaingearN[0], "MAINGEAR_RA", "RA Maingear", "%.2f", 100, 6000, 100, 0.0);
1485  IUFillNumber(&MaingearN[1], "MAINGEAR_DEC", "Dec Maingear", "%.2f", 100, 6000, 100, 0.0);
1486  IUFillNumberVector(&MaingearNP, MaingearN, 2, getDeviceName(), "MAINGEAR", "Maingear",
1488 
1489  // backlash
1490  IUFillNumber(&BacklashN[0], "BACKLASH_MIN", "Dec Backlash Minutes", "%.0f", 0, 9, 1, 0.0);
1491  IUFillNumber(&BacklashN[1], "BACKLASH_SEC", "Dec Backlash Seconds", "%.0f", 0, 59, 1, 0.0);
1492  IUFillNumberVector(&BacklashNP, BacklashN, 2, getDeviceName(), "BACKLASH", "Backlash",
1494 
1495  // user rate 1
1496  IUFillNumber(&UserRate1N[0], "USERRATE1_RA", "RA (radians/min)", "%.7f", -4.1887902, 4.1887902, 0, 0.0);
1497  IUFillNumber(&UserRate1N[1], "USERRATE1_DEC", "Dec (radians/min)", "%.7f", -4.1887902, 4.1887902, 0, 0.0);
1498  IUFillNumberVector(&UserRate1NP, UserRate1N, 2, getDeviceName(), "USERRATE1", "UserRate1",
1500 
1501  // home position
1502  IUFillNumber(&HomePositionN[0], "HOME_POSITION_ALT", "Altitude (0 to +90 deg.)", "%.4f", 0, 90, 0, 0.0);
1503  IUFillNumber(&HomePositionN[1], "HOME_POSITION_AZ", "Azimuth (0 to 360 deg.)", "%.4f", 0, 360, 0, 0.0);
1504  IUFillNumberVector(&HomePositionNP, HomePositionN, 2, getDeviceName(), "HOME_POSITION", "Home Pos.",
1505  SITE_TAB, IP_RW, 0, IPS_IDLE);
1506 
1507  // mount type
1508  IUFillSwitch(&MountTypeS[(int)Pulsar2Commands::German], "MOUNT_TYPE_GERMAN", "German", ISS_OFF); // no default
1509  IUFillSwitch(&MountTypeS[(int)Pulsar2Commands::Fork], "MOUNT_TYPE_FORK", "Fork", ISS_OFF); // no default
1510  IUFillSwitch(&MountTypeS[(int)Pulsar2Commands::AltAz], "MOUNT_TYPE_ALTAZ", "AltAz", ISS_OFF); // no default
1511  IUFillSwitchVector(&MountTypeSP, MountTypeS, 3, getDeviceName(), "MOUNT_TYPE", "Mount Type",
1513 
1514  // pier side (indicator)
1515  IUFillSwitch(&PierSideS[Pulsar2Commands::EastOfPier], "PIER_EAST", "OTA on East side (-> west)", ISS_OFF); // no default
1516  IUFillSwitch(&PierSideS[Pulsar2Commands::WestOfPier], "PIER_WEST", "OTA on West side (-> east)", ISS_OFF); // no default
1517  IUFillSwitchVector(&PierSideSP, PierSideS, 2, getDeviceName(), "TELESCOPE_PIER_SIDE", "Pier Side Ind",
1519  // pier side (toggle)
1520  IUFillSwitch(&PierSideToggleS[0], "PIER_SIDE_TOGGLE", "Toggle OTA Pier Side (init only)", ISS_OFF);
1521  IUFillSwitchVector(&PierSideToggleSP, PierSideToggleS, 1, getDeviceName(), "PIER_SIDE_TOGGLE", "Pier Side Switch",
1523 
1524  // PEC on/off
1525  IUFillSwitch(&PeriodicErrorCorrectionS[0], "PEC_OFF", "Off", ISS_OFF);
1526  IUFillSwitch(&PeriodicErrorCorrectionS[1], "PEC_ON", "On", ISS_ON); // default
1528  "P.E. Correction", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1529 
1530  // pole crossing on/off
1531  IUFillSwitch(&PoleCrossingS[0], "POLE_CROSS_OFF", "Off", ISS_OFF);
1532  IUFillSwitch(&PoleCrossingS[1], "POLE_CROSS_ON", "On", ISS_ON); // default
1533  IUFillSwitchVector(&PoleCrossingSP, PoleCrossingS, 2, getDeviceName(), "POLE_CROSSING", "Pole Crossing",
1535 
1536  // refraction correction
1537  IUFillSwitch(&RefractionCorrectionS[0], "REFR_CORR_OFF", "Off", ISS_OFF);
1538  IUFillSwitch(&RefractionCorrectionS[1], "REFR_CORR_ON", "On", ISS_ON); // default
1540  "Refraction Corr.", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1541 
1542  // rotation (RA)
1543  IUFillSwitch(&RotationRAS[0], "ROT_RA_ZERO", "CW (Right)", ISS_OFF);
1544  IUFillSwitch(&RotationRAS[1], "ROT_RA_ONE", "CCW (Left)", ISS_OFF);
1546  "RA Rotation", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1547  // rotation (Dec)
1548  IUFillSwitch(&RotationDecS[0], "ROT_DEC_ZERO", "CW", ISS_OFF);
1549  IUFillSwitch(&RotationDecS[1], "ROT_DEC_ONE", "CCW", ISS_OFF);
1551  "Dec Rotation", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1552 
1553  // tracking current
1554  IUFillNumber(&TrackingCurrentN[0], "TRACKING_CURRENT", "mA", "%.0f", 200, 2000, 200, 0.0); // min, max, step, value
1555  IUFillNumberVector(&TrackingCurrentNP, TrackingCurrentN, 1, getDeviceName(), "TRACKING_CURRENT", "Tracking Current",
1557  // stop current
1558  IUFillNumber(&StopCurrentN[0], "STOP_CURRENT", "mA", "%.0f", 200, 2000, 200, 0.0); // min, max, step, value
1559  IUFillNumberVector(&StopCurrentNP, StopCurrentN, 1, getDeviceName(), "STOP_CURRENT", "Stop Current",
1561  // goto current
1562  IUFillNumber(&GoToCurrentN[0], "GOTO_CURRENT", "mA", "%.0f", 200, 2000, 200, 0.0); // min, max, step, value
1563  IUFillNumberVector(&GoToCurrentNP, GoToCurrentN, 1, getDeviceName(), "GOTO_CURRENT", "GoTo Current",
1565  }
1566  return result;
1567 }
1568 
1570 {
1571  if (isConnected())
1572  {
1573  if (!this->local_properties_updated)
1574  {
1575  // note that there are several other "defines" embedded within getBasicData()
1578 
1582 
1586 
1587  this->local_properties_updated = true;
1588  }
1589  }
1590  else
1591  {
1614  //deleteProperty(UserRate1NP.name); // user rates are not working correctly in the controller
1615  local_properties_updated = false;
1616  }
1617 
1618  LX200Generic::updateProperties(); // calls great-grandparent updateProperties() (which for connections calls getBasicData())
1619 
1620  if (isConnected())
1621  {
1622  storeScopeLocation();
1623  sendScopeTime();
1624  // for good measure, resynchronize the tty
1626  LOG_INFO("Initial tty resync complete.");
1627  }
1628 
1629  // allow polling to proceed (or not) for this instance of the driver
1630  this->initialization_complete = isConnected();
1631 
1632  return true;
1633 }
1634 
1635 
1636 bool LX200Pulsar2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1637 {
1638  // first, make sure that the incoming message is for our device
1639  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1640  {
1642  // Guide Speed
1644  if (!strcmp(name, GuideSpeedIndNP.name))
1645  {
1646  int ival = static_cast<int>(round(values[0]));
1647  if (ival > 0 && ival < 10) // paranoid
1648  {
1649  if (!isSimulation())
1650  {
1652  {
1654  IDSetNumber(&GuideSpeedIndNP, "Unable to set guide speed indicator to mount controller");
1655  return false; // early exit
1656  }
1657  }
1658  IUUpdateNumber(&GuideSpeedIndNP, values, names, n);
1660  IDSetNumber(&GuideSpeedIndNP, nullptr);
1661  }
1662  else
1663  {
1665  IDSetNumber(&GuideSpeedIndNP, "Value out of bounds for guide speed indicator");
1666  return false; // early exit
1667  }
1668  return true; // early exit
1669  }
1671  // Center Speed
1673  if (!strcmp(name, CenterSpeedIndNP.name))
1674  {
1675  int ival = static_cast<int>(round(values[0]));
1676  if (ival > 0 && ival < (Pulsar2Commands::speedsExtended ? 10000 : 1000)) // paranoid at this point
1677  {
1678  if (!isSimulation())
1679  {
1681  {
1683  IDSetNumber(&CenterSpeedIndNP, "Unable to set center speed indicator to mount controller");
1684  return false; // early exit
1685  }
1686  }
1687  IUUpdateNumber(&CenterSpeedIndNP, values, names, n);
1689  IDSetNumber(&CenterSpeedIndNP, nullptr);
1690  }
1691  else
1692  {
1694  IDSetNumber(&CenterSpeedIndNP, "Value out of bounds for center speed indicator");
1695  return false; // early exit
1696  }
1697  return true; // early exit
1698  }
1700  // Find Speed
1702  if (!strcmp(name, FindSpeedIndNP.name))
1703  {
1704  int ival = static_cast<int>(round(values[0]));
1705  if (ival > 0 && ival < (Pulsar2Commands::speedsExtended ? 10000 : 1000)) // paranoid at this point
1706  {
1707  if (!isSimulation())
1708  {
1710  {
1712  IDSetNumber(&FindSpeedIndNP, "Unable to set find speed indicator to mount controller");
1713  return false; // early exit
1714  }
1715  }
1716  IUUpdateNumber(&FindSpeedIndNP, values, names, n);
1718  IDSetNumber(&FindSpeedIndNP, nullptr);
1719  }
1720  else
1721  {
1723  IDSetNumber(&FindSpeedIndNP, "Value out of bounds for find speed indicator");
1724  return false; // early exit
1725  }
1726  return true; // early exit
1727  }
1729  // Slew Speed
1731  if (!strcmp(name, SlewSpeedIndNP.name))
1732  {
1733  int ival = static_cast<int>(round(values[0]));
1734  if (ival > 0 && ival < (Pulsar2Commands::speedsExtended ? 10000 : 1000)) // paranoid at this point
1735  {
1736  if (!isSimulation())
1737  {
1739  {
1741  IDSetNumber(&SlewSpeedIndNP, "Unable to set slew speed indicator to mount controller");
1742  return false; // early exit
1743  }
1744  }
1745  IUUpdateNumber(&SlewSpeedIndNP, values, names, n);
1747  IDSetNumber(&SlewSpeedIndNP, nullptr);
1748  }
1749  else
1750  {
1752  IDSetNumber(&SlewSpeedIndNP, "Value out of bounds for slew speed indicator");
1753  return false; // early exit
1754  }
1755  return true; // early exit
1756  }
1758  // GoTo Speed
1760  if (!strcmp(name, GoToSpeedIndNP.name))
1761  {
1762  int ival = static_cast<int>(round(values[0]));
1763  if (ival > 0 && ival < (Pulsar2Commands::speedsExtended ? 10000 : 1000)) // paranoid at this point
1764  {
1765  if (!isSimulation())
1766  {
1768  {
1770  IDSetNumber(&GoToSpeedIndNP, "Unable to set goto speed indicator to mount controller");
1771  return false; // early exit
1772  }
1773  }
1774  IUUpdateNumber(&GoToSpeedIndNP, values, names, n);
1776  IDSetNumber(&GoToSpeedIndNP, nullptr);
1777  }
1778  else
1779  {
1781  IDSetNumber(&GoToSpeedIndNP, "Value out of bounds for goto speed indicator");
1782  return false; // early exit
1783  }
1784  return true; // early exit
1785  }
1786 
1788  // Ramp
1790  if (!strcmp(name, RampNP.name))
1791  {
1792  int ra_ramp_val = static_cast<int>(round(values[0]));
1793  int dec_ramp_val = static_cast<int>(round(values[1]));
1794  if (ra_ramp_val >= 1 && ra_ramp_val <= 10 && dec_ramp_val >= 1 && dec_ramp_val <= 10) // paranoid
1795  {
1796  if (!isSimulation())
1797  {
1798  if (!Pulsar2Commands::setRamp(PortFD, ra_ramp_val, dec_ramp_val))
1799  {
1800  RampNP.s = IPS_ALERT;
1801  IDSetNumber(&RampNP, "Unable to set ramp to mount controller");
1802  return false; // early exit
1803  }
1804  }
1805  IUUpdateNumber(&RampNP, values, names, n);
1806  RampNP.s = IPS_OK;
1807  IDSetNumber(&RampNP, nullptr);
1808  }
1809  else
1810  {
1811  RampNP.s = IPS_ALERT;
1812  IDSetNumber(&RampNP, "Value(s) out of bounds for ramp");
1813  return false; // early exit
1814  }
1815  return true; // early exit
1816  }
1817 
1819  // Reduction
1821  if (!strcmp(name, ReductionNP.name))
1822  {
1823  int red_ra_val = static_cast<int>(round(values[0]));
1824  int red_dec_val = static_cast<int>(round(values[1]));
1825  if (red_ra_val >= 100 && red_ra_val <= 6000 && red_dec_val >= 100 && red_dec_val <= 6000) // paranoid
1826  {
1827  if (!isSimulation())
1828  {
1829  if (!Pulsar2Commands::setReduction(PortFD, red_ra_val, red_dec_val))
1830  {
1832  IDSetNumber(&ReductionNP, "Unable to set reduction values in mount controller");
1833  return false; // early exit
1834  }
1835  }
1836  IUUpdateNumber(&ReductionNP, values, names, n);
1837  ReductionNP.s = IPS_OK;
1838  IDSetNumber(&ReductionNP, nullptr);
1839  }
1840  else
1841  {
1843  IDSetNumber(&ReductionNP, "Value(s) out of bounds for reduction");
1844  return false; // early exit
1845  }
1846  return true; // early exit
1847  }
1848 
1850  // Maingear
1852  if (!strcmp(name, MaingearNP.name))
1853  {
1854  int mg_ra_val = static_cast<int>(round(values[0]));
1855  int mg_dec_val = static_cast<int>(round(values[1]));
1856  if (mg_ra_val >= 100 && mg_ra_val <= 6000 && mg_dec_val >= 100 && mg_dec_val <= 6000) // paranoid
1857  {
1858  if (!isSimulation())
1859  {
1860  if (!Pulsar2Commands::setMaingear(PortFD, mg_ra_val, mg_dec_val))
1861  {
1863  IDSetNumber(&MaingearNP, "Unable to set maingear values in mount controller");
1864  return false; // early exit
1865  }
1866  }
1867  IUUpdateNumber(&MaingearNP, values, names, n);
1868  MaingearNP.s = IPS_OK;
1869  IDSetNumber(&MaingearNP, nullptr);
1870  }
1871  else
1872  {
1874  IDSetNumber(&MaingearNP, "Value(s) out of bounds for maingear");
1875  return false; // early exit
1876  }
1877  return true; // early exit
1878  }
1879 
1881  // Backlash
1883  if (!strcmp(name, BacklashNP.name))
1884  {
1885  int bl_min_val = static_cast<int>(round(values[0]));
1886  int bl_sec_val = static_cast<int>(round(values[1]));
1887  if (bl_min_val >= 0 && bl_min_val <= 9 && bl_sec_val >= 0 && bl_sec_val <= 59) // paranoid
1888  {
1889  if (!isSimulation())
1890  {
1891  if (!Pulsar2Commands::setBacklash(PortFD, bl_min_val, bl_sec_val))
1892  {
1894  IDSetNumber(&BacklashNP, "Unable to set backlash values in mount controller");
1895  return false; // early exit
1896  }
1897  else
1898  {
1899  // we have to re-get the values from the controller, because
1900  // it sets this value according to some unknown rounding algorithm
1901  if (Pulsar2Commands::getBacklash(PortFD, &bl_min_val, &bl_sec_val))
1902  {
1903  values[0] = bl_min_val;
1904  values[1] = bl_sec_val;
1905  }
1906  }
1907  }
1908  IUUpdateNumber(&BacklashNP, values, names, n);
1909  BacklashNP.s = IPS_OK;
1910  IDSetNumber(&BacklashNP, nullptr);
1911  }
1912  else
1913  {
1915  IDSetNumber(&BacklashNP, "Value(s) out of bounds for backlash");
1916  return false; // early exit
1917  }
1918  return true; // early exit
1919  }
1920 
1921 
1923  // Home Position
1925  if (!strcmp(name, HomePositionNP.name))
1926  {
1927  double hp_alt = values[0];
1928  double hp_az = values[1];
1929  if (hp_alt >= -90.0 && hp_alt <= 90.0 && hp_az >= 0.0 && hp_az <= 360.0) // paranoid
1930  {
1931  if (!isSimulation())
1932  {
1933  if (!Pulsar2Commands::setHomePosition(PortFD, hp_alt, hp_az))
1934  {
1936  IDSetNumber(&HomePositionNP, "Unable to set home position values in mount controller");
1937  return false; // early exit
1938  }
1939  else
1940  {
1941  // we have to re-get the values from the controller, because
1942  // it does flaky things with floating point rounding and
1943  // 180/360 degree calculations
1944  if (Pulsar2Commands::getHomePosition(PortFD, &hp_alt, &hp_az))
1945  {
1946  values[0] = hp_alt;
1947  values[1] = hp_az;
1948  }
1949  }
1950  }
1951  IUUpdateNumber(&HomePositionNP, values, names, n);
1953  IDSetNumber(&HomePositionNP, nullptr);
1954  }
1955  else
1956  {
1958  IDSetNumber(&HomePositionNP, "Value(s) out of bounds for home position");
1959  return false; // early exit
1960  }
1961  return true; // early exit
1962  }
1963 
1965  // User Rate 1
1967  // note that the following has not been verified to work correctly
1968  if (!strcmp(name, UserRate1NP.name))
1969  {
1970  if (!Pulsar2Commands::speedsExtended) // a way to check the firmware version
1971  {
1972  double ur1_ra = values[0];
1973  double ur1_dec = values[1];
1974  if (ur1_ra >= -4.1887902 && ur1_ra <= 4.1887902 && ur1_dec >= -4.1887902 && ur1_dec <= 4.1887902) // paranoid
1975  {
1976  if (!isSimulation())
1977  {
1978  if (!Pulsar2Commands::setUserRate1(PortFD, ur1_ra, ur1_dec))
1979  {
1981  IDSetNumber(&UserRate1NP, "Unable to set user rate 1 values in mount controller");
1982  return false; // early exit
1983  }
1984  else
1985  {
1986  // we have to re-get the values from the controller, because
1987  // it does flaky things with floating point rounding
1988  if (Pulsar2Commands::getUserRate1(PortFD, &ur1_ra, &ur1_dec))
1989  {
1990  values[0] = ur1_ra;
1991  values[1] = ur1_dec;
1992  }
1993  }
1994  }
1995  IUUpdateNumber(&UserRate1NP, values, names, n);
1996  UserRate1NP.s = IPS_OK;
1997  IDSetNumber(&UserRate1NP, nullptr);
1998  }
1999  }
2000  return true; // early exit
2001  }
2002 
2004  // Tracking Current
2006  if (!strcmp(name, TrackingCurrentNP.name))
2007  {
2008  int ival = static_cast<int>(round(values[0]));
2009  if (ival >= 200 && ival <= 2000) // paranoid
2010  {
2011  if (!isSimulation())
2012  {
2014  {
2016  IDSetNumber(&TrackingCurrentNP, "Unable to set tracking current to mount controller");
2017  return false; // early exit
2018  }
2019  }
2020  IUUpdateNumber(&TrackingCurrentNP, values, names, n);
2022  IDSetNumber(&TrackingCurrentNP, nullptr);
2023  }
2024  else
2025  {
2027  IDSetNumber(&TrackingCurrentNP, "Value out of bounds for tracking current");
2028  return false; // early exit
2029  }
2030  return true; // early exit
2031  }
2032 
2034  // Stop Current
2036  if (!strcmp(name, StopCurrentNP.name))
2037  {
2038  int ival = static_cast<int>(round(values[0]));
2039  if (ival >= 200 && ival <= 2000) // paranoid
2040  {
2041  if (!isSimulation())
2042  {
2044  {
2046  IDSetNumber(&StopCurrentNP, "Unable to set stop current to mount controller");
2047  return false; // early exit
2048  }
2049  }
2050  IUUpdateNumber(&StopCurrentNP, values, names, n);
2052  IDSetNumber(&StopCurrentNP, nullptr);
2053  }
2054  else
2055  {
2057  IDSetNumber(&StopCurrentNP, "Value out of bounds for stop current");
2058  return false; // early exit
2059  }
2060  return true; // early exit
2061  }
2062 
2064  // GoTo Current
2066  if (!strcmp(name, GoToCurrentNP.name))
2067  {
2068  int ival = static_cast<int>(round(values[0]));
2069  if (ival >= 200 && ival <= 2000) // paranoid
2070  {
2071  if (!isSimulation())
2072  {
2074  {
2076  IDSetNumber(&GoToCurrentNP, "Unable to set goto current to mount controller");
2077  return false; // early exit
2078  }
2079  }
2080  IUUpdateNumber(&GoToCurrentNP, values, names, n);
2082  IDSetNumber(&GoToCurrentNP, nullptr);
2083  }
2084  else
2085  {
2087  IDSetNumber(&GoToCurrentNP, "Value out of bounds for goto current");
2088  return false; // early exit
2089  }
2090  return true; // early exit
2091  }
2092 
2094  // Geographic Coords
2096  if (strcmp(name, "GEOGRAPHIC_COORD") == 0)
2097  {
2098  if (!isSimulation())
2099  {
2100  // first two rounds are local, so are trapped here -- after that,
2101  // pass it on to the parent. This ugly hack is due to the fact
2102  // that sendScopeLocation() (renamed in this file to storeScopeLocation)
2103  // is not virtual/overridable
2104  if (Pulsar2Commands::site_location_initialized < 2)
2105  {
2106  Pulsar2Commands::site_location_initialized++;
2107  return true; // early exit
2108  }
2109  }
2110  }
2111 
2112  } // check for our device
2113 
2114  // If we got here, the input name has not been processed, so pass it to the parent
2115  return LX200Generic::ISNewNumber(dev, name, values, names, n);
2116 }
2117 
2118 
2119 bool LX200Pulsar2::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
2120 {
2121  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
2122  {
2123  // Sites (copied from lx200telescope.cpp, due to call to sendScopeLocation() which is not virtual)
2124  if (!strcmp(name, SiteSP.name))
2125  {
2126  if (IUUpdateSwitch(&SiteSP, states, names, n) < 0)
2127  return false;
2128 
2130 
2131  if (!isSimulation() && selectSite(PortFD, currentSiteNum) < 0)
2132  {
2133  SiteSP.s = IPS_ALERT;
2134  IDSetSwitch(&SiteSP, "Error selecting sites.");
2135  return false;
2136  }
2137 
2138  char siteName[64] = {0};
2139 
2140  if (isSimulation())
2141  {
2142  IUSaveText(&SiteNameTP.tp[0], "Sample Site");
2143  }
2144  else
2145  {
2146  getSiteName(PortFD, siteName, currentSiteNum);
2147  IUSaveText(&SiteNameT[0], siteName);
2148  }
2150  storeScopeLocation();
2151 
2152  SiteNameTP.s = IPS_OK;
2153  SiteSP.s = IPS_OK;
2154 
2155  IDSetText(&SiteNameTP, nullptr);
2156  IDSetSwitch(&SiteSP, nullptr);
2157 
2158  return false;
2159  }
2160  // end Sites copy
2161 
2162  // mount type
2163  if (strcmp(name, MountTypeSP.name) == 0)
2164  {
2165  if (IUUpdateSwitch(&MountTypeSP, states, names, n) < 0)
2166  return false;
2167 
2168  if (!isSimulation())
2169  {
2170  bool success = false; // start out pessimistic
2171  for (int idx = 0; idx < MountTypeSP.nsp; idx++)
2172  {
2173  if (MountTypeS[idx].s == ISS_ON)
2174  {
2175  success = setMountType(PortFD, (Pulsar2Commands::MountType)(idx));
2176  break;
2177  }
2178  }
2179  if (success)
2180  {
2181  MountTypeSP.s = IPS_OK;
2182  IDSetSwitch(&MountTypeSP, nullptr);
2183  }
2184  else
2185  {
2187  IDSetSwitch(&MountTypeSP, "Could not determine or change the mount type");
2188  }
2189  }
2190  }
2191 
2192  // pier side toggle -- the sync command requires that the pier side be known.
2193  // This is *not* related to a meridian flip, but rather, to the OTA orientation.
2194  if (strcmp(name, PierSideToggleSP.name) == 0)
2195  {
2196  if (IUUpdateSwitch(&PierSideToggleSP, states, names, n) < 0)
2197  return false;
2198 
2199  if (!isSimulation())
2200  {
2201  if (Pulsar2Commands::currentOTASideOfPier != Pulsar2Commands::InvalidSideOfPier) // paranoid
2202  {
2203  Pulsar2Commands::OTASideOfPier requested_side_of_pier =
2204  Pulsar2Commands::currentOTASideOfPier == Pulsar2Commands::EastOfPier ? Pulsar2Commands::WestOfPier :
2206  bool success = Pulsar2Commands::setSideOfPier(PortFD, requested_side_of_pier);
2207  // always turn it off
2208  PierSideToggleS[0].s = ISS_OFF;
2209  if (success)
2210  {
2212  IDSetSwitch(&PierSideToggleSP, nullptr);
2213  }
2214  else
2215  {
2217  IDSetSwitch(&PierSideToggleSP, "Could not change the OTA side of pier");
2218  }
2219  }
2220  return true; // always signal success
2221  }
2222  }
2223 
2224  // periodic error correction
2225  if (strcmp(name, PeriodicErrorCorrectionSP.name) == 0)
2226  {
2227  if (IUUpdateSwitch(&PeriodicErrorCorrectionSP, states, names, n) < 0)
2228  return false;
2229 
2230  if (!isSimulation())
2231  {
2232  // Only control PEC in RA; PEC in Declination doesn't seem useful
2233  const bool success = Pulsar2Commands::setPECorrection(PortFD,
2234  (PeriodicErrorCorrectionS[1].s == ISS_ON ?
2238  if (success)
2239  {
2242  }
2243  else
2244  {
2246  IDSetSwitch(&PeriodicErrorCorrectionSP, "Could not change the periodic error correction");
2247  }
2248  return success;
2249  }
2250  }
2251 
2252  // pole crossing
2253  if (strcmp(name, PoleCrossingSP.name) == 0)
2254  {
2255  if (IUUpdateSwitch(&PoleCrossingSP, states, names, n) < 0)
2256  return false;
2257 
2258  if (!isSimulation())
2259  {
2260  const bool success = Pulsar2Commands::setPoleCrossing(PortFD, (PoleCrossingS[1].s == ISS_ON ?
2263  if (success)
2264  {
2266  IDSetSwitch(&PoleCrossingSP, nullptr);
2267  }
2268  else
2269  {
2271  IDSetSwitch(&PoleCrossingSP, "Could not change the pole crossing");
2272  }
2273  return success;
2274  }
2275  }
2276 
2277  // refraction correction
2278  if (strcmp(name, RefractionCorrectionSP.name) == 0)
2279  {
2280  if (IUUpdateSwitch(&RefractionCorrectionSP, states, names, n) < 0)
2281  return false;
2282 
2283  if (!isSimulation())
2284  {
2285  // Control refraction correction in both RA and decl.
2286  const Pulsar2Commands::RCorrection rc =
2289  const bool success = Pulsar2Commands::setRCorrection(PortFD, rc, rc);
2290  if (success)
2291  {
2294  }
2295  else
2296  {
2298  IDSetSwitch(&RefractionCorrectionSP, "Could not change the refraction correction");
2299  }
2300  return success;
2301  }
2302  }
2303 
2304  // rotation RA
2305  if (strcmp(name, RotationRASP.name) == 0)
2306  {
2307  if (IUUpdateSwitch(&RotationRASP, states, names, n) < 0)
2308  return false;
2309 
2310  if (!isSimulation())
2311  {
2312  // Control rotation of RA
2313  Pulsar2Commands::Rotation rot_ra, rot_dec;
2314  bool success = Pulsar2Commands::getRotation(PortFD, &rot_ra, &rot_dec);
2315  if (success)
2316  {
2318  success = Pulsar2Commands::setRotation(PortFD, rot_ra, rot_dec);
2319  if (success)
2320  {
2321  RotationRASP.s = IPS_OK;
2322  IDSetSwitch(&RotationRASP, nullptr);
2323  }
2324  else
2325  {
2327  IDSetSwitch(&RotationRASP, "Could not change RA rotation direction");
2328  }
2329  }
2330  return success;
2331  }
2332  }
2333 
2334  // rotation Dec
2335  if (strcmp(name, RotationDecSP.name) == 0)
2336  {
2337  if (IUUpdateSwitch(&RotationDecSP, states, names, n) < 0)
2338  return false;
2339 
2340  if (!isSimulation())
2341  {
2342  // Control rotation of Dec
2343  Pulsar2Commands::Rotation rot_ra, rot_dec;
2344  bool success = Pulsar2Commands::getRotation(PortFD, &rot_ra, &rot_dec);
2345  if (success)
2346  {
2348  success = Pulsar2Commands::setRotation(PortFD, rot_ra, rot_dec);
2349  if (success)
2350  {
2352  IDSetSwitch(&RotationDecSP, nullptr);
2353  }
2354  else
2355  {
2357  IDSetSwitch(&RotationDecSP, "Could not change Dec rotation direction");
2358  }
2359  }
2360  return success;
2361  }
2362  }
2363 
2364 
2365  // tracking rate indicator
2366  if (strcmp(name, TrackingRateIndSP.name) == 0)
2367  {
2368  if (IUUpdateSwitch(&TrackingRateIndSP, states, names, n) < 0)
2369  return false;
2370 
2371  if (!isSimulation())
2372  {
2373  int idx = 0;
2374  for (; idx < static_cast<int>(LX200Pulsar2::numPulsarTrackingRates); idx++)
2375  {
2376  if (TrackingRateIndS[idx].s == ISS_ON) break;
2377  }
2378 
2380  if (success)
2381  {
2383  IDSetSwitch(&TrackingRateIndSP, nullptr);
2384  }
2385  else
2386  {
2388  IDSetSwitch(&TrackingRateIndSP, "Could not change the tracking rate");
2389  }
2390  return success;
2391  }
2392  }
2393 
2394  } // dev is ok
2395 
2396  // Nobody has claimed this, so pass it to the parent
2397  return LX200Generic::ISNewSwitch(dev, name, states, names, n);
2398 }
2399 
2400 bool LX200Pulsar2::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
2401 {
2402  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
2403  {
2404  // Nothing to do yet
2405  }
2406  return LX200Generic::ISNewText(dev, name, texts, names, n);
2407 }
2408 
2410 {
2411  // Convert index from Meade format
2412  index = 3 - index;
2413  const bool success =
2415  if (success)
2416  {
2417  SlewRateSP.s = IPS_OK;
2418  IDSetSwitch(&SlewRateSP, nullptr);
2419  }
2420  else
2421  {
2423  IDSetSwitch(&SlewRateSP, "Error setting slew rate");
2424  }
2425  return success;
2426 }
2427 
2429 {
2430  Pulsar2Commands::Direction motionDirection;
2431  switch (dir) // map INDI directions to Pulsar2 directions
2432  {
2433  case DIRECTION_NORTH:
2434  motionDirection = Pulsar2Commands::North;
2435  break;
2436  case DIRECTION_SOUTH:
2437  motionDirection = Pulsar2Commands::South;
2438  break;
2439  default:
2440  LOG_INFO("Attempt to move neither North nor South using MoveNS()");
2441  return false;
2442  }
2443 
2444  bool success = true;
2445  switch (motionCommand)
2446  {
2447  case MOTION_START:
2448  last_ns_motion = dir; // globals such as this are not advisable
2449  success = (isSimulation() || Pulsar2Commands::moveTo(PortFD, motionDirection));
2450  if (success)
2451  LOGF_INFO("Moving toward %s.", Pulsar2Commands::DirectionName[motionDirection]);
2452  else
2453  LOG_ERROR("Error starting N/S motion.");
2454  break;
2455  case MOTION_STOP:
2456  success = (isSimulation() || Pulsar2Commands::haltMovement(PortFD, motionDirection));
2457  if (success)
2458  LOGF_INFO("Movement toward %s halted.",
2459  Pulsar2Commands::DirectionName[motionDirection]);
2460  else
2461  LOG_ERROR("Error stopping N/S motion.");
2462  break;
2463  }
2464  return success;
2465 }
2466 
2468 {
2469  Pulsar2Commands::Direction motionDirection;
2470  switch (dir) // map INDI directions to Pulsar2 directions
2471  {
2472  case DIRECTION_WEST:
2473  motionDirection = Pulsar2Commands::West;
2474  break;
2475  case DIRECTION_EAST:
2476  motionDirection = Pulsar2Commands::East;
2477  break;
2478  default:
2479  LOG_INFO("Attempt to move neither West nor East using MoveWE()");
2480  return false;
2481  }
2482 
2483  bool success = true;
2484  switch (command)
2485  {
2486  case MOTION_START:
2487  last_we_motion = dir; // globals such as this are not advisable
2488  success = (isSimulation() || Pulsar2Commands::moveTo(PortFD, motionDirection));
2489  if (success)
2490  LOGF_INFO("Moving toward %s.", Pulsar2Commands::DirectionName[motionDirection]);
2491  else
2492  LOG_ERROR("Error starting W/E motion.");
2493  break;
2494  case MOTION_STOP:
2495  success = (isSimulation() || Pulsar2Commands::haltMovement(PortFD, motionDirection));
2496  if (success)
2497  LOGF_INFO("Movement toward %s halted.",
2498  Pulsar2Commands::DirectionName[motionDirection]);
2499  else
2500  LOG_ERROR("Error stopping W/E motion.");
2501  break;
2502  }
2503  return success;
2504 }
2505 
2507 {
2508  const bool success = (isSimulation() || Pulsar2Commands::abortSlew(PortFD));
2509  if (success)
2510  {
2511  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
2512  {
2514  GuideNSN[0].value = GuideNSN[1].value = 0.0;
2515  GuideWEN[0].value = GuideWEN[1].value = 0.0;
2516  if (GuideNSTID)
2517  {
2519  GuideNSTID = 0;
2520  }
2521  if (GuideWETID)
2522  {
2524  GuideNSTID = 0;
2525  }
2526  IDMessage(getDeviceName(), "Guide aborted.");
2527  IDSetNumber(&GuideNSNP, nullptr);
2528  IDSetNumber(&GuideWENP, nullptr);
2529  }
2530  }
2531  else
2532  LOG_ERROR("Failed to abort slew!");
2533  return success;
2534 }
2535 
2536 
2538 {
2540  {
2541  LOG_ERROR("Cannot guide while moving.");
2542  return IPS_ALERT;
2543  }
2544  // If already moving (no pulse command), then stop movement
2545  if (MovementNSSP.s == IPS_BUSY)
2546  {
2547  const int dir = IUFindOnSwitchIndex(&MovementNSSP);
2549  }
2550  if (GuideNSTID)
2551  {
2553  GuideNSTID = 0;
2554  }
2555  if (usePulseCommand)
2556  {
2558  }
2559  else
2560  {
2562  {
2564  IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
2565  return IPS_ALERT;
2566  }
2567  MovementNSS[0].s = ISS_ON;
2569  }
2570 
2571  // Set switched slew rate to "guide"
2574  IDSetSwitch(&SlewRateSP, nullptr);
2577  return IPS_BUSY;
2578 }
2579 
2581 {
2583  {
2584  LOG_ERROR("Cannot guide while moving.");
2585  return IPS_ALERT;
2586  }
2587  // If already moving (no pulse command), then stop movement
2588  if (MovementNSSP.s == IPS_BUSY)
2589  {
2590  const int dir = IUFindOnSwitchIndex(&MovementNSSP);
2592  }
2593  if (GuideNSTID)
2594  {
2596  GuideNSTID = 0;
2597  }
2598  if (usePulseCommand)
2600  else
2601  {
2603  {
2605  IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
2606  return IPS_ALERT;
2607  }
2608  MovementNSS[1].s = ISS_ON;
2610  }
2611 
2612  // Set switch slew rate to "guide"
2615  IDSetSwitch(&SlewRateSP, nullptr);
2618  return IPS_BUSY;
2619 }
2620 
2622 {
2624  {
2625  LOG_ERROR("Cannot guide while moving.");
2626  return IPS_ALERT;
2627  }
2628  // If already moving (no pulse command), then stop movement
2629  if (MovementWESP.s == IPS_BUSY)
2630  {
2631  const int dir = IUFindOnSwitchIndex(&MovementWESP);
2633  }
2634  if (GuideWETID)
2635  {
2637  GuideWETID = 0;
2638  }
2639  if (usePulseCommand)
2641  else
2642  {
2644  {
2646  IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
2647  return IPS_ALERT;
2648  }
2649  MovementWES[1].s = ISS_ON;
2651  }
2652 
2653  // Set switched slew rate to "guide"
2656  IDSetSwitch(&SlewRateSP, nullptr);
2659  return IPS_BUSY;
2660 }
2661 
2663 {
2665  {
2666  LOG_ERROR("Cannot guide while moving.");
2667  return IPS_ALERT;
2668  }
2669  // If already moving (no pulse command), then stop movement
2670  if (MovementWESP.s == IPS_BUSY)
2671  {
2672  const int dir = IUFindOnSwitchIndex(&MovementWESP);
2674  }
2675  if (GuideWETID)
2676  {
2678  GuideWETID = 0;
2679  }
2680  if (usePulseCommand)
2682  else
2683  {
2685  {
2687  IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
2688  return IPS_ALERT;
2689  }
2690  MovementWES[0].s = ISS_ON;
2692  }
2693  // Set switched slew to "guide"
2696  IDSetSwitch(&SlewRateSP, nullptr);
2699  return IPS_BUSY;
2700 }
2701 
2702 
2703 bool LX200Pulsar2::updateTime(ln_date *utc, double utc_offset)
2704 {
2705  INDI_UNUSED(utc_offset);
2706  bool success = true;
2707  if (!isSimulation())
2708  {
2709  struct ln_zonedate ltm;
2710  ln_date_to_zonedate(utc, &ltm, 0.0); // One should use only UTC with Pulsar!
2711  JD = ln_get_julian_day(utc);
2712  LOGF_DEBUG("New JD is %f", static_cast<float>(JD));
2713  success = Pulsar2Commands::setTime(PortFD, ltm.hours, ltm.minutes, ltm.seconds);
2714  if (success)
2715  {
2716  success = Pulsar2Commands::setDate(PortFD, ltm.days, ltm.months, ltm.years);
2717  if (success)
2718  LOG_INFO("UTC date-time is set.");
2719  else
2720  LOG_ERROR("Error setting UTC date/time.");
2721  }
2722  else
2723  LOG_ERROR("Error setting UTC time.");
2724  // Pulsar cannot set UTC offset (?)
2725  }
2726 
2727  return success;
2728 }
2729 
2730 bool LX200Pulsar2::updateLocation(double latitude, double longitude, double elevation)
2731 {
2732  INDI_UNUSED(elevation);
2733  bool success = true;
2734  if (!isSimulation())
2735  {
2736  success = Pulsar2Commands::setSite(PortFD, longitude, latitude);
2737  if (success)
2738  {
2739  char l[32], L[32];
2740  fs_sexa(l, latitude, 3, 3600);
2741  fs_sexa(L, longitude, 4, 3600);
2742  IDMessage(getDeviceName(), "Site coordinates updated to Lat %.32s - Long %.32s", l, L);
2743  LOGF_INFO("Site coordinates updated to lat: %+f, lon: %+f", latitude, longitude);
2744  }
2745  else
2746  LOG_ERROR("Error setting site coordinates");
2747  }
2748  return success;
2749 }
2750 
2751 
2752 bool LX200Pulsar2::Goto(double r, double d)
2753 {
2754  const struct timespec timeout = {0, 100000000L}; // 1/10 second
2755  char RAStr[64], DecStr[64];
2756  fs_sexa(RAStr, targetRA = r, 2, 3600);
2757  fs_sexa(DecStr, targetDEC = d, 2, 3600);
2758 
2759  // If moving, let's stop it first.
2760  if (EqNP.s == IPS_BUSY)
2761  {
2763  {
2764  AbortSP.s = IPS_ALERT;
2765  IDSetSwitch(&AbortSP, "Abort slew failed.");
2766  return false;
2767  }
2768 
2769  AbortSP.s = IPS_OK;
2770  EqNP.s = IPS_IDLE;
2771  IDSetSwitch(&AbortSP, "Slew aborted.");
2772  IDSetNumber(&EqNP, nullptr);
2773 
2775  {
2778  EqNP.s = IPS_IDLE;
2781  IDSetSwitch(&MovementNSSP, nullptr);
2782  IDSetSwitch(&MovementWESP, nullptr);
2783  }
2784  nanosleep(&timeout, nullptr);
2785  }
2786 
2787  if (!isSimulation())
2788  {
2790  {
2791  EqNP.s = IPS_ALERT;
2792  IDSetNumber(&EqNP, "Error setting RA/DEC.");
2793  return false;
2794  }
2796  {
2797  EqNP.s = IPS_ALERT;
2798  IDSetNumber(&EqNP, "Error Slewing to JNow RA %s - DEC %s\n", RAStr, DecStr);
2799  slewError(3);
2800  return false;
2801  }
2802  just_started_slewing = true;
2803  }
2804 
2806  //EqNP.s = IPS_BUSY;
2807  LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
2808  return true;
2809 }
2810 
2812 {
2813  const struct timespec timeout = {0, 100000000L}; // 1/10th second
2814 
2815  if (!isSimulation())
2816  {
2818  {
2819  ParkSP.s = IPS_ALERT;
2820  IDSetSwitch(&ParkSP, "No parking position defined.");
2821  return false;
2822  }
2824  {
2825  ParkSP.s = IPS_ALERT;
2826  IDSetSwitch(&ParkSP, "Scope has already been parked.");
2827  return false;
2828  }
2829  }
2830 
2831  // If scope is moving, let's stop it first.
2832  if (EqNP.s == IPS_BUSY)
2833  {
2835  {
2836  AbortSP.s = IPS_ALERT;
2837  IDSetSwitch(&AbortSP, "Abort slew failed.");
2838  return false;
2839  }
2840 
2841  AbortSP.s = IPS_OK;
2842  EqNP.s = IPS_IDLE;
2843  IDSetSwitch(&AbortSP, "Slew aborted.");
2844  IDSetNumber(&EqNP, nullptr);
2845 
2847  {
2850  EqNP.s = IPS_IDLE;
2853 
2854  IDSetSwitch(&MovementNSSP, nullptr);
2855  IDSetSwitch(&MovementWESP, nullptr);
2856  }
2857  nanosleep(&timeout, nullptr);
2858  }
2859 
2861  {
2862  ParkSP.s = IPS_ALERT;
2863  IDSetSwitch(&ParkSP, "Parking Failed.");
2864  return false;
2865  }
2866 
2867  ParkSP.s = IPS_BUSY;
2869  IDMessage(getDeviceName(), "Parking telescope in progress...");
2870  return true;
2871 }
2872 
2873 
2874 bool LX200Pulsar2::Sync(double ra, double dec)
2875 {
2876  const struct timespec timeout = {0, 300000000L}; // 3/10 seconds
2877  bool success = true;
2878  if (!isSimulation())
2879  {
2880  if (!isSlewing())
2881  {
2883  nanosleep(&timeout, nullptr); // This seems to be necessary (why?)
2884  if (!success)
2885  {
2886  EqNP.s = IPS_ALERT;
2887  IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
2888  }
2889  else
2890  {
2891  char RAresponse[32];
2892  memset(RAresponse, '\0', 32); // currently just for debug
2893  char DECresponse[32];
2894  memset(DECresponse, '\0', 32); // currently just for debug
2895  success = PulsarTX::sendReceive2(PortFD, "#:CM#", RAresponse, DECresponse);
2896  if (success)
2897  {
2898  // Pulsar returns coordinates separated/terminated by # characters (<RA>#<Dec>#).
2899  // Currently, we don't check that the received coordinates match the sent coordinates.
2900  LOGF_DEBUG("Sync RAresponse: %s, DECresponse: %s", RAresponse, DECresponse);
2901  currentRA = ra;
2902  currentDEC = dec;
2903  EqNP.s = IPS_OK;
2905  LOG_INFO("Synchronization successful.");
2906  }
2907  else
2908  {
2909  EqNP.s = IPS_ALERT;
2910  IDSetNumber(&EqNP, "Synchronization failed.");
2911  LOG_INFO("Synchronization failed.");
2912  }
2913  }
2914  }
2915  else
2916  {
2917  success = false;
2918  LOG_INFO("Cannot sync while slewing");
2919  }
2920  }
2921 
2922  return success;
2923 }
2924 
2925 
2927 {
2928  if (!isSimulation())
2929  {
2931  {
2932  ParkSP.s = IPS_ALERT;
2933  IDSetSwitch(&ParkSP, "Mount is not parked.");
2934  LOG_INFO("Mount is not parked, so cannot unpark.");
2935  return false; // early exit
2936  }
2938  {
2939  ParkSP.s = IPS_ALERT;
2940  IDSetSwitch(&ParkSP, "Unparking failed.");
2941  LOG_INFO("Unparking failed.");
2942  return false; // early exit
2943  }
2944  }
2945  ParkSP.s = IPS_OK;
2947  SetParked(false);
2948  IDMessage(getDeviceName(), "Telescope has been unparked.");
2949 
2950  // the following assumes we are tracking, since there is no truly
2951  // "idle" state for Pulsar2
2952  LOG_INFO("Telescope has been unparked.");
2954  IDSetSwitch(&ParkSP, nullptr);
2955 
2956  return true;
2957 }
2958 
2960 {
2961  bool result = false;
2962  switch (TrackState)
2963  {
2964  case SCOPE_SLEWING:
2965  result = !isSlewing();
2966  break;
2967  case SCOPE_PARKING:
2969  break;
2970  default:
2971  break;
2972  }
2973  return result;
2974 }
2975 
2977 {
2978  if (isSimulation())
2979  return true; // early exit
2980 
2981  return LX200Generic::checkConnection(); // a reduced form of resynchronize()
2982 }
2983 
2984 
2985 // Note that several "definitions" are also included in the following
2986 // functions, so we can dynamically modify some input fields
2988 {
2989  if (!isConnected()) return; // early exit
2990 
2991  if (!isSimulation())
2992  {
2993  // first do the parent's data gathering
2995 
2996  // ensure long format
2998  {
2999  LOG_DEBUG("Failed to ensure that long format coordinates are used.");
3000  }
3001 
3002  // Determine which Pulsar firmware version we are connected to.
3003  // We expect a response something like: 'PULSAR V2.66aR ,2008.12.10. #'
3004  const struct timespec getVersionSleepTime = {0, 50000000L}; // 1/20th second
3005  char versionResponse[40];
3006  memset(versionResponse, '\0', 40);
3007  if (Pulsar2Commands::getVersion(PortFD, versionResponse))
3008  {
3009  char *vp = strstr(versionResponse, "PULSAR V");
3010  if (*vp != LX200Pulsar2::Null)
3011  {
3012  char versionString[20];
3013  memset(versionString, '\0', 20);
3014  vp += 8;
3015  char *vs = versionString;
3016  for (int i = 0; i < 19 && (isalnum(*vp) || *vp == '.'); * (vs++) = *(vp++), i++);
3017  if (strcmp(versionString, "5.7") < 0) Pulsar2Commands::speedsExtended = true;
3018  LOGF_INFO("Pulsar firmware Version: %s", versionString);
3019  // The following commented out, as it may not always work
3020  //(void)sscanf(response, "PULSAR V%8s ,%4d.%2d.%2d. ", versionString, &versionYear, &versionMonth, &versionDay);
3021  //LOGF_INFO("%s version %s dated %04d.%02d.%02d",
3022  // (Pulsar2Commands::versionString[0] > '2' ? "Pulsar2" : "Pulsar"), Pulsar2Commands::versionString, Pulsar2Commands::versionYear, Pulsar2Commands::versionMonth, Pulsar2Commands::versionDay);
3023  }
3024  else
3025  {
3026  LOG_INFO("Could not determine valid firmware version.");
3027  }
3028  }
3029  nanosleep(&getVersionSleepTime, nullptr);
3030 
3031  int nonGuideSpeedMax = Pulsar2Commands::speedsExtended ? 9999 : 999;
3032  int nonGuideSpeedStep = Pulsar2Commands::speedsExtended ? 100 : 10;
3033  const char *nonGuideSpeedLabel = Pulsar2Commands::speedsExtended ? Pulsar2Commands::nonGuideSpeedExtendedUnit :
3034  Pulsar2Commands::nonGuideSpeedUnit;
3035 
3036  // mount type
3038  MountTypeS[(int)mount_type].s = ISS_ON;
3039  IDSetSwitch(&MountTypeSP, nullptr);
3040 
3041  // PE correction (one value used for both RA and Dec)
3044  if (Pulsar2Commands::getPECorrection(PortFD, &pec_ra, &pec_dec))
3045  {
3049  }
3050  else
3051  {
3053  IDSetSwitch(&PeriodicErrorCorrectionSP, "Can't check whether PEC is enabled.");
3054  }
3055 
3056  // pole crossing
3058  if (Pulsar2Commands::getPoleCrossing(PortFD, &pole_crossing))
3059  {
3060  PoleCrossingS[0].s = (pole_crossing == Pulsar2Commands::PoleCrossingOn ? ISS_OFF : ISS_ON);
3061  PoleCrossingS[1].s = (pole_crossing == Pulsar2Commands::PoleCrossingOn ? ISS_ON : ISS_OFF);
3062  IDSetSwitch(&PoleCrossingSP, nullptr);
3063  }
3064  else
3065  {
3067  IDSetSwitch(&PoleCrossingSP, "Can't check whether pole crossing is enabled.");
3068  }
3069 
3070  // refraction correction (one value used for both RA and Dec)
3072  if (Pulsar2Commands::getRCorrection(PortFD, &rc_ra, &rc_dec))
3073  {
3077  }
3078  else
3079  {
3081  IDSetSwitch(&RefractionCorrectionSP, "Can't check whether refraction correction is enabled.");
3082  }
3083 
3084  // rotation
3085  Pulsar2Commands::Rotation rot_ra, rot_dec;
3086  if (Pulsar2Commands::getRotation(PortFD, &rot_ra, &rot_dec))
3087  {
3089  RotationRAS[1].s = (rot_ra == Pulsar2Commands::RotationOne ? ISS_ON : ISS_OFF);
3090  IDSetSwitch(&RotationRASP, nullptr);
3091  RotationDecS[0].s = (rot_dec == Pulsar2Commands::RotationZero ? ISS_ON : ISS_OFF);
3092  RotationDecS[1].s = (rot_dec == Pulsar2Commands::RotationOne ? ISS_ON : ISS_OFF);
3093  IDSetSwitch(&RotationDecSP, nullptr);
3094  }
3095 
3096 
3097  // - - - - - - - - - - - - - - - - - -
3098  // Motion Control Tab
3099  // - - - - - - - - - - - - - - - - - -
3100 
3101  // tracking rate indicator
3103  for (int i = 0; i < static_cast<int>(LX200Pulsar2::numPulsarTrackingRates); i++) TrackingRateIndS[i].s = ISS_OFF;
3104  if (tracking_rate_ind != Pulsar2Commands::RateNone)
3105  {
3106  TrackingRateIndS[static_cast<int>(tracking_rate_ind)].s = ISS_ON;
3107  IDSetSwitch(&TrackingRateIndSP, nullptr);
3108  }
3109  else
3110  {
3112  IDSetSwitch(&TrackingRateIndSP, "Can't get the tracking rate indicator.");
3113  }
3114  defineProperty(&TrackingRateIndSP); // defined here for consistency
3115 
3116  // guide speed indicator
3117  int guide_speed_ind = Pulsar2Commands::getGuideSpeedInd(PortFD);
3118  if (guide_speed_ind > 0)
3119  {
3120  double guide_speed_ind_d = static_cast<double>(guide_speed_ind);
3121  GuideSpeedIndN[0].value = guide_speed_ind_d;
3122  IDSetNumber(&GuideSpeedIndNP, nullptr);
3123  }
3124  defineProperty(&GuideSpeedIndNP); // defined here, in order to match input value with controller value
3125 
3126  // center speed indicator
3127  int center_speed_ind = Pulsar2Commands::getCenterSpeedInd(PortFD);
3128  if (center_speed_ind > 0)
3129  {
3130  double center_speed_ind_d = static_cast<double>(center_speed_ind);
3131  CenterSpeedIndN[0].value = center_speed_ind_d;
3132  CenterSpeedIndN[0].max = nonGuideSpeedMax;
3133  CenterSpeedIndN[0].step = nonGuideSpeedStep;
3134  strcpy(CenterSpeedIndN[0].label, nonGuideSpeedLabel);
3135  IDSetNumber(&CenterSpeedIndNP, nullptr);
3136  }
3137  defineProperty(&CenterSpeedIndNP); // defined here, in order to match input value with controller value
3138 
3139  // find speed indicator
3140  int find_speed_ind = Pulsar2Commands::getFindSpeedInd(PortFD);
3141  if (find_speed_ind > 0)
3142  {
3143  double find_speed_ind_d = static_cast<double>(find_speed_ind);
3144  FindSpeedIndN[0].value = find_speed_ind_d;
3145  FindSpeedIndN[0].max = nonGuideSpeedMax;
3146  FindSpeedIndN[0].step = nonGuideSpeedStep;
3147  strcpy(FindSpeedIndN[0].label, nonGuideSpeedLabel);
3148  IDSetNumber(&FindSpeedIndNP, nullptr);
3149  }
3150  defineProperty(&FindSpeedIndNP); // defined here, in order to match input value with controller value
3151 
3152  // slew speed indicator
3153  int slew_speed_ind = Pulsar2Commands::getSlewSpeedInd(PortFD);
3154  if (slew_speed_ind > 0)
3155  {
3156  double slew_speed_ind_d = static_cast<double>(slew_speed_ind);
3157  SlewSpeedIndN[0].value = slew_speed_ind_d;
3158  SlewSpeedIndN[0].max = nonGuideSpeedMax;
3159  SlewSpeedIndN[0].step = nonGuideSpeedStep;
3160  strcpy(SlewSpeedIndN[0].label, nonGuideSpeedLabel);
3161  IDSetNumber(&SlewSpeedIndNP, nullptr);
3162  }
3163  defineProperty(&SlewSpeedIndNP); // defined here, in order to match input value with controller value
3164 
3165  // goto speed indicator
3166  int goto_speed_ind = Pulsar2Commands::getGoToSpeedInd(PortFD);
3167  if (goto_speed_ind > 0)
3168  {
3169  double goto_speed_ind_d = static_cast<double>(goto_speed_ind);
3170  GoToSpeedIndN[0].value = goto_speed_ind_d;
3171  GoToSpeedIndN[0].max = nonGuideSpeedMax;
3172  GoToSpeedIndN[0].step = nonGuideSpeedStep;
3173  strcpy(GoToSpeedIndN[0].label, nonGuideSpeedLabel);
3174  IDSetNumber(&GoToSpeedIndNP, nullptr);
3175  }
3176  defineProperty(&GoToSpeedIndNP); // defined here, in order to match input value with controller value
3177 
3178 
3179  // - - - - - - - - - - - - - - - - - -
3180  // Site Management Tab
3181  // - - - - - - - - - - - - - - - - - -
3182 
3183  // home position
3184  double hp_alt, hp_az;
3185  if (Pulsar2Commands::getHomePosition(PortFD, &hp_alt, &hp_az))
3186  {
3187  HomePositionN[0].value = hp_alt;
3188  HomePositionN[1].value = hp_az;
3189  IDSetNumber(&HomePositionNP, nullptr);
3190  }
3191  else
3192  {
3194  IDSetNumber(&HomePositionNP, "Unable to get home position values from controller.");
3195  }
3196  defineProperty(&HomePositionNP); // defined here, in order to match input value with controller value
3197 
3198 
3199  // - - - - - - - - - - - - - - - - - -
3200  // Advanced Setup Tab
3201  // - - - - - - - - - - - - - - - - - -
3202 
3203  // tracking current
3204  int tracking_current = Pulsar2Commands::getTrackingCurrent(PortFD);
3205  if (tracking_current > 0)
3206  {
3207  double tracking_current_d = static_cast<double>(tracking_current);
3208  TrackingCurrentN[0].value = tracking_current_d;
3209  IDSetNumber(&TrackingCurrentNP, nullptr);
3210  }
3211  else
3212  {
3214  IDSetNumber(&TrackingCurrentNP, "Can't get tracking current value");
3215  }
3216  defineProperty(&TrackingCurrentNP); // defined here, in order to match input value with controller value
3217 
3218  // stop current
3219  int stop_current = Pulsar2Commands::getStopCurrent(PortFD);
3220  if (stop_current > 0)
3221  {
3222  double stop_current_d = static_cast<double>(stop_current);
3223  StopCurrentN[0].value = stop_current_d;
3224  IDSetNumber(&StopCurrentNP, nullptr);
3225  }
3226  else
3227  {
3229  IDSetNumber(&StopCurrentNP, "Can't get stop current value");
3230  }
3231  defineProperty(&StopCurrentNP); // defined here, in order to match input value with controller value
3232 
3233  // goto current
3234  int goto_current = Pulsar2Commands::getGoToCurrent(PortFD);
3235  if (goto_current > 0)
3236  {
3237  double goto_current_d = static_cast<double>(goto_current);
3238  GoToCurrentN[0].value = goto_current_d;
3239  IDSetNumber(&GoToCurrentNP, nullptr);
3240  }
3241  else
3242  {
3244  IDSetNumber(&GoToCurrentNP, "Can't get goto current value");
3245  }
3246  defineProperty(&GoToCurrentNP); // defined here, in order to match input value with controller value
3247 
3248  // ramp
3249  int ra_ramp, dec_ramp;
3250  if (Pulsar2Commands::getRamp(PortFD, &ra_ramp, &dec_ramp))
3251  {
3252  double ra_ramp_d = static_cast<double>(ra_ramp);
3253  double dec_ramp_d = static_cast<double>(dec_ramp);
3254  RampN[0].value = ra_ramp_d;
3255  RampN[1].value = dec_ramp_d;
3256  IDSetNumber(&RampNP, nullptr);
3257  }
3258  else
3259  {
3260  RampNP.s = IPS_ALERT;
3261  IDSetNumber(&RampNP, "Unable to get ramp values from controller.");
3262  }
3263  defineProperty(&RampNP); // defined here, in order to match input value with controller value
3264 
3265  // reduction
3266  int red_ra, red_dec;
3267  if (Pulsar2Commands::getReduction(PortFD, &red_ra, &red_dec))
3268  {
3269  double ra_red_d = static_cast<double>(red_ra);
3270  double dec_red_d = static_cast<double>(red_dec);
3271  ReductionN[0].value = ra_red_d;
3272  ReductionN[1].value = dec_red_d;
3273  IDSetNumber(&ReductionNP, nullptr);
3274  }
3275  else
3276  {
3278  IDSetNumber(&ReductionNP, "Unable to get reduction values from controller.");
3279  }
3280  defineProperty(&ReductionNP); // defined here, in order to match input value with controller value
3281 
3282  // maingear
3283  int mg_ra, mg_dec;
3284  if (Pulsar2Commands::getMaingear(PortFD, &mg_ra, &mg_dec))
3285  {
3286  double mg_ra_d = static_cast<double>(mg_ra);
3287  double mg_dec_d = static_cast<double>(mg_dec);
3288  MaingearN[0].value = mg_ra_d;
3289  MaingearN[1].value = mg_dec_d;
3290  IDSetNumber(&MaingearNP, nullptr);
3291  }
3292  else
3293  {
3295  IDSetNumber(&MaingearNP, "Unable to get maingear values from controller.");
3296  }
3297  defineProperty(&MaingearNP); // defined here, in order to match input value with controller value
3298 
3299  // backlash
3300  int bl_min, bl_sec;
3301  if (Pulsar2Commands::getBacklash(PortFD, &bl_min, &bl_sec))
3302  {
3303  double bl_min_d = static_cast<double>(bl_min);
3304  double bl_sec_d = static_cast<double>(bl_sec);
3305  BacklashN[0].value = bl_min_d;
3306  BacklashN[1].value = bl_sec_d;
3307  IDSetNumber(&BacklashNP, nullptr);
3308  }
3309  else
3310  {
3312  IDSetNumber(&BacklashNP, "Unable to get backlash values from controller.");
3313  }
3314  defineProperty(&BacklashNP); // defined here, in order to match input value with controller value
3315 
3316 
3317  // user rate 1
3318  // note that the following has not been verified to work correctly,
3319  // and perhaps not at all for earlier firmware versions
3320  if (!Pulsar2Commands::speedsExtended) // a way to check for a firmware version
3321  {
3322  double ur1_ra, ur1_dec;
3323  if (Pulsar2Commands::getUserRate1(PortFD, &ur1_ra, &ur1_dec))
3324  {
3325  UserRate1N[0].value = ur1_ra;
3326  UserRate1N[1].value = ur1_dec;
3327  IDSetNumber(&UserRate1NP, nullptr);
3328  }
3329  else
3330  {
3332  IDSetNumber(&UserRate1NP, "Unable to get user rate 1 values from controller.");
3333  }
3334  //defineProperty(&UserRate1NP); // user rates are not working correctly in the controller
3335  }
3336 
3337  } // not a simulation
3338 
3339 }
3340 
3341 // -- -- -- -- -- -- -- -- -- -- -- -- -- --
3342 // Other methods
3343 // -- -- -- -- -- -- -- -- -- -- -- -- -- --
3344 
3345 bool LX200Pulsar2::storeScopeLocation()
3346 {
3347  LocationNP.s = IPS_OK;
3348  double lat = 29.5; // simulation default
3349  double lon = 48.0; // simulation default
3350 
3352  {
3353  LocationNP.np[0].value = lat;
3354  double stdLon = (lon < 0 ? 360.0 + lon : lon);
3355  LocationNP.np[1].value = stdLon;
3356 
3357  LOGF_DEBUG("Mount Controller Latitude: %g Longitude: %g", LocationN[LOCATION_LATITUDE].value,
3358  LocationN[LOCATION_LONGITUDE].value);
3359 
3360  IDSetNumber(&LocationNP, nullptr);
3361  saveConfig(true, "GEOGRAPHIC_COORD");
3362  if (LX200Pulsar2::verboseLogging) LOGF_INFO("Controller location read and stored; lat: %+f, lon: %+f", lat, stdLon);
3363  }
3364  else
3365  {
3367  IDMessage(getDeviceName(), "Failed to get site lat/lon from Pulsar controller.");
3368  return false;
3369  }
3370 
3371  return true;
3372 }
3373 
3374 // Old-style individual latitude/longitude retrieval
3375 //void LX200Pulsar2::sendScopeLocation()
3376 //{
3377 // LocationNP.s = IPS_OK;
3378 // int dd = 29, mm = 30;
3379 // if (isSimulation() || Pulsar2Commands::getSiteLatitude(PortFD, &dd, &mm))
3380 // {
3381 // LocationNP.np[0].value = (dd < 0 ? -1 : 1) * (abs(dd) + mm / 60.0);
3382 // LOGF_DEBUG("Pulsar latitude: %d:%d", dd, mm);
3383 // }
3384 // else
3385 // {
3386 // IDMessage(getDeviceName(), "Failed to get site latitude from Pulsar controller.");
3387 // LocationNP.s = IPS_ALERT;
3388 // }
3389 // dd = 48;
3390 // mm = 0;
3391 // if (isSimulation() || Pulsar2Commands::getSiteLongitude(PortFD, &dd, &mm))
3392 // {
3393 // LocationNP.np[1].value = (dd > 0 ? 360.0 - (dd + mm / 60.0) : -(dd - mm / 60.0));
3394 // LOGF_DEBUG("Pulsar longitude: %d:%d", dd, mm);
3395 //
3396 // saveConfig(true, "GEOGRAPHIC_COORD");
3397 // }
3398 // else
3399 // {
3400 // IDMessage(getDeviceName(), "Failed to get site longitude from Pulsar controller.");
3401 // LocationNP.s = IPS_ALERT;
3402 // }
3403 // IDSetNumber(&LocationNP, nullptr);
3404 //}
3405 
3406 bool LX200Pulsar2::sendScopeTime()
3407 {
3408  struct tm ltm;
3409  memset(&ltm, 0, sizeof(ltm));
3410  if (isSimulation())
3411  {
3412  const time_t t = time(nullptr);
3413  if (gmtime_r(&t, &ltm) == nullptr)
3414  return true;
3415  return false;
3416  }
3417  else
3418  {
3419  if (!Pulsar2Commands::getUTCTime(PortFD, &ltm.tm_hour, &ltm.tm_min, &ltm.tm_sec) ||
3420  !Pulsar2Commands::getUTCDate(PortFD, &ltm.tm_mon, &ltm.tm_mday, &ltm.tm_year))
3421  return false;
3422  ltm.tm_mon -= 1;
3423  ltm.tm_year -= 1900;
3424  }
3425 
3426  ltm.tm_isdst = 0;
3427  // Get time epoch and convert to TimeT
3428  const time_t time_epoch = mktime(&ltm);
3429  struct tm utm;
3430  localtime_r(&time_epoch, &utm);
3431 
3432  // Format it into ISO 8601
3433  char cdate[32];
3434  strftime(cdate, sizeof(cdate), "%Y-%m-%dT%H:%M:%S", &utm);
3435 
3436  IUSaveText(&TimeT[0], cdate);
3437  IUSaveText(&TimeT[1], "0"); // Pulsar maintains time in UTC only
3438  if (isDebug())
3439  {
3440  IDLog("Telescope Local Time: %02d:%02d:%02d\n", ltm.tm_hour, ltm.tm_min, ltm.tm_sec);
3441  IDLog("Telescope TimeT Offset: %s\n", TimeT[1].text);
3442  IDLog("Telescope UTC Time: %s\n", TimeT[0].text);
3443  }
3444  // Let's send everything to the client
3445  TimeTP.s = IPS_OK;
3446  IDSetText(&TimeTP, nullptr);
3447 
3448  return true;
3449 }
3450 
3451 bool LX200Pulsar2::isSlewing()
3452 {
3453  // A problem with the Pulsar controller is that the :YGi# command starts
3454  // returning the value 1 as long as a few seconds after a slew has been
3455  // started. This means that a (short) slew can end before this happens.
3456  auto mount_is_off_target = [this](void)
3457  {
3458  return (fabs(currentRA - targetRA) > 1.0 / 3600.0 || fabs(currentDEC - targetDEC) > 5.0 / 3600.0);
3459  };
3460  // Detect the end of a short slew
3461  bool result = (just_started_slewing ? mount_is_off_target() : true);
3462  if (result)
3463  {
3464  int is_slewing = -1;
3465  if (PulsarTX::sendReceiveInt(PortFD, "#:YGi#", &is_slewing))
3466  {
3467  if (is_slewing == 1) // We can rely on the Pulsar "is slewing" indicator from here on
3468  {
3469  just_started_slewing = false;
3470  result = true;
3471  }
3472  else // ... otherwise we have to rely on the value of the attribute "just_started_slewing"
3473  result = just_started_slewing;
3474  }
3475  else // Fallback in case of error
3476  result = mount_is_off_target();
3477  }
3478  // Make sure that "just_started_slewing" is reset at the end of a slew
3479  if (!result)
3480  just_started_slewing = false;
3481  return result;
3482 }
3483 
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool Disconnect()
Disconnect from device.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
INumberVectorProperty GuideNSNP
INumberVectorProperty GuideWENP
TelescopeStatus TrackState
ISwitchVectorProperty MovementNSSP
ISwitchVectorProperty AbortSP
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
INumberVectorProperty LocationNP
ITextVectorProperty TimeTP
ISwitchVectorProperty SlewRateSP
ISwitch MovementWES[2]
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
ISwitchVectorProperty ParkSP
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
ISwitch * SlewRateS
ISwitch ParkS[2]
ISwitchVectorProperty MovementWESP
static constexpr char const * ADVANCED_TAB
Definition: lx200pulsar2.h:32
INumber TrackingCurrentN[1]
Definition: lx200pulsar2.h:157
virtual bool isSlewComplete() override
INumberVectorProperty TrackingCurrentNP
Definition: lx200pulsar2.h:156
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ISwitchVectorProperty MountTypeSP
Definition: lx200pulsar2.h:128
INumber GoToCurrentN[1]
Definition: lx200pulsar2.h:163
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
ISwitch TrackingRateIndS[numPulsarTrackingRates]
Definition: lx200pulsar2.h:85
ISwitchVectorProperty PoleCrossingSP
Definition: lx200pulsar2.h:136
INumber GoToSpeedIndN[1]
Definition: lx200pulsar2.h:101
virtual void getBasicData() override
INumber MaingearN[2]
Definition: lx200pulsar2.h:113
INumberVectorProperty SlewSpeedIndNP
Definition: lx200pulsar2.h:97
INumberVectorProperty GoToSpeedIndNP
Definition: lx200pulsar2.h:100
INumber RampN[2]
Definition: lx200pulsar2.h:105
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool UnPark() override
Unpark the telescope if already parked.
ISwitchVectorProperty PierSideSP
Definition: lx200pulsar2.h:79
INumber ReductionN[2]
Definition: lx200pulsar2.h:109
INumber UserRate1N[2]
Definition: lx200pulsar2.h:152
INumberVectorProperty HomePositionNP
Definition: lx200pulsar2.h:120
virtual bool Handshake() override
perform handshake with device to check communication
ISwitch RotationDecS[2]
Definition: lx200pulsar2.h:148
INumberVectorProperty GoToCurrentNP
Definition: lx200pulsar2.h:162
INumberVectorProperty BacklashNP
Definition: lx200pulsar2.h:116
ISwitch PoleCrossingS[2]
Definition: lx200pulsar2.h:137
INumber SlewSpeedIndN[1]
Definition: lx200pulsar2.h:98
ISwitchVectorProperty RefractionCorrectionSP
Definition: lx200pulsar2.h:140
INumber FindSpeedIndN[1]
Definition: lx200pulsar2.h:95
ISwitchVectorProperty RotationDecSP
Definition: lx200pulsar2.h:147
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool Disconnect() override
Disconnect from device.
ISwitchVectorProperty RotationRASP
Definition: lx200pulsar2.h:144
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
ISwitchVectorProperty PierSideToggleSP
Definition: lx200pulsar2.h:81
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
INumberVectorProperty GuideSpeedIndNP
Definition: lx200pulsar2.h:88
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
INumber StopCurrentN[1]
Definition: lx200pulsar2.h:160
virtual const char * getDefaultName() override
static constexpr char Null
Definition: lx200pulsar2.h:34
INumber BacklashN[2]
Definition: lx200pulsar2.h:117
INumberVectorProperty ReductionNP
Definition: lx200pulsar2.h:108
INumberVectorProperty RampNP
Definition: lx200pulsar2.h:104
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
ISwitch RefractionCorrectionS[2]
Definition: lx200pulsar2.h:141
ISwitch MountTypeS[3]
Definition: lx200pulsar2.h:129
ISwitchVectorProperty TrackingRateIndSP
Definition: lx200pulsar2.h:84
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
ISwitch PeriodicErrorCorrectionS[2]
Definition: lx200pulsar2.h:133
INumber GuideSpeedIndN[1]
Definition: lx200pulsar2.h:89
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
INumberVectorProperty UserRate1NP
Definition: lx200pulsar2.h:151
INumber HomePositionN[2]
Definition: lx200pulsar2.h:121
INumber CenterSpeedIndN[1]
Definition: lx200pulsar2.h:92
virtual bool checkConnection() override
INumberVectorProperty StopCurrentNP
Definition: lx200pulsar2.h:159
virtual bool Park() override
Park the telescope to its home position.
static constexpr bool verboseLogging
Definition: lx200pulsar2.h:33
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
INumberVectorProperty FindSpeedIndNP
Definition: lx200pulsar2.h:94
ISwitch PierSideToggleS[1]
Definition: lx200pulsar2.h:80
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual bool ReadScopeStatus() override
Read telescope status.
static const unsigned int numPulsarTrackingRates
Definition: lx200pulsar2.h:50
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
INumberVectorProperty CenterSpeedIndNP
Definition: lx200pulsar2.h:91
ISwitch RotationRAS[2]
Definition: lx200pulsar2.h:145
ISwitchVectorProperty PeriodicErrorCorrectionSP
Definition: lx200pulsar2.h:132
ISwitch PierSideS[2]
Definition: lx200pulsar2.h:78
INumberVectorProperty MaingearNP
Definition: lx200pulsar2.h:112
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
int8_t guide_direction_we
static void guideTimeoutHelperNS(void *p)
ITextVectorProperty SiteNameTP
virtual void slewError(int slewCode)
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
static void guideTimeoutHelperWE(void *p)
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool checkConnection()
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual void getBasicData()
IText SiteNameT[1]
void setLX200Capability(uint32_t cap)
ISwitchVectorProperty SiteSP
int8_t guide_direction_ns
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:602
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition: eventloop.c:582
double max(void)
int errno
double min(void)
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define MAXINDIDEVICE
Definition: indiapi.h:193
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_ATMOST1
Definition: indiapi.h:174
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_EAST
Definition: indibasetypes.h:57
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_SOUTH
Definition: indibasetypes.h:50
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
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 IDLog(const char *fmt,...)
Definition: indicom.c:316
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
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
@ TTY_TIME_OUT
Definition: indicom.h:154
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
void IDMessage(const char *dev, const char *fmt,...)
Definition: indidriver.c:960
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define DEBUGDEVICE(device, priority, msg)
Definition: indilogger.h:60
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define DEBUGFDEVICE(device, priority, msg,...)
Definition: indilogger.h:61
#define MAXRBUF
Definition: indiserver.cpp:102
int fd
Definition: intelliscope.c:43
int getSiteName(int fd, char *siteName, int siteNum)
int selectSite(int fd, int siteNum)
@ LX200_WEST
Definition: lx200driver.h:42
@ LX200_SOUTH
Definition: lx200driver.h:44
@ LX200_NORTH
Definition: lx200driver.h:41
@ LX200_EAST
Definition: lx200driver.h:43
char lx200Name[MAXINDIDEVICE]
Definition: lx200driver.cpp:51
unsigned int DBG_SCOPE
Definition: lx200driver.cpp:53
bool setRamp(const int fd, int ra_ramp, int dec_ramp)
bool getHomePosition(const int fd, double *hp_alt, double *hp_az)
bool abortSlew(const int fd)
bool setTrackingCurrent(const int fd, const int mA)
bool getUserRate1(const int fd, double *u1_ra, double *u1_dec)
int getTrackingCurrent(const int fd)
int getCurrentValue(const int fd, const char *cmd)
int getGoToCurrent(const int fd)
bool getPoleCrossingDirection(const int fd, int *direction)
bool setTime(const int fd, const int h, const int m, const int s)
bool setDate(const int fd, const int dd, const int mm, const int yy)
bool setCurrentValue(const int fd, const char *partialCmd, const int mA, const int maxmA)
bool setObjectRA(const int fd, const double ra)
bool getRamp(const int fd, int *ra_ramp, int *dec_ramp)
bool pulseGuide(const int fd, const Direction direction, uint32_t ms)
bool setSlewSpeedInd(const int fd, const int speedInd)
bool getVersion(const int fd, char response[])
bool park(const int fd)
bool setRCorrection(const int fd, const RCorrection rc_ra, const RCorrection rc_dec)
bool getPECorrection(const int fd, PECorrection *PECra, PECorrection *PECdec)
bool ensureLongFormat(const int fd)
int getSpeedInd(const int fd, const char *cmd)
bool getMaingear(const int fd, int *mg_ra, int *mg_dec)
MountType getMountType(const int fd)
int getCenterSpeedInd(const int fd)
bool getRCorrection(const int fd, RCorrection *Rra, RCorrection *Rdec)
bool setCenterSpeedInd(const int fd, const int speedInd)
bool setReduction(const int fd, int red_ra, int red_dec)
bool getBacklash(const int fd, int *bl_min, int *bl_sec)
bool setSite(const int fd, const double longitude, const double latitude)
bool setUserRate(const int fd, int usr_ind, double ur_ra, double ur_dec)
bool setMountType(const int fd, Pulsar2Commands::MountType mtype)
bool setRotation(const int fd, const Rotation rot_ra, const Rotation rot_dec)
bool setPoleCrossing(const int fd, const PoleCrossing pole_crossing)
bool setHomePosition(const int fd, double hp_alt, double hp_az)
bool getObjectRADec(const int fd, double *ra, double *dec)
bool isParking(const int fd)
bool getRotation(const int fd, Rotation *rot_ra, Rotation *rot_dec)
bool setSideOfPier(const int fd, const OTASideOfPier ota_side_of_pier)
bool getUTCDate(const int fd, int *m, int *d, int *y)
bool getUserRate(const int fd, int usr_ind, double *ur_ra, double *ur_dec)
bool setSlewMode(const int fd, const SlewMode slewMode)
bool setGoToCurrent(const int fd, const int mA)
TrackingRateInd getTrackingRateInd(const int fd)
int getGuideSpeedInd(const int fd)
bool unpark(const int fd)
bool getPoleCrossing(const int fd, PoleCrossing *pole_crossing)
bool setBacklash(const int fd, int bl_min, int bl_sec)
bool setGuideSpeedInd(const int fd, const int speedInd)
bool setFindSpeedInd(const int fd, const int speedInd)
bool setPECorrection(const int fd, const PECorrection pec_ra, const PECorrection pec_dec)
bool isHomeSet(const int fd)
bool setTrackingRateInd(const int fd, const TrackingRateInd tri)
bool setUserRate2(const int fd, double ur_ra, double ur_dec)
bool moveTo(const int fd, const Direction direction)
int getGoToSpeedInd(const int fd)
bool setUserRate3(const int fd, double ur_ra, double ur_dec)
bool setSpeedInd(const int fd, const char *partialCmd, const int speedInd, const int maxInd)
bool sync(const int fd)
bool getSideOfPier(const int fd, OTASideOfPier *ota_side_of_pier)
bool setUserRate1(const int fd, double ur_ra, double ur_dec)
int getStopCurrent(const int fd)
bool getUserRate2(const int fd, double *u2_ra, double *u2_dec)
bool getUTCTime(const int fd, int *h, int *m, int *s)
bool getReduction(const int fd, int *red_ra, int *red_dec)
bool getSexa(const int fd, const char *cmd, double *value)
bool setObjectRADec(const int fd, const double ra, const double dec)
bool haltMovement(const int fd, const Direction direction)
bool setDegreesMinutes(const int fd, const char *partialCmd, const double value)
bool startSlew(const int fd)
int getFindSpeedInd(const int fd)
bool setObjectDEC(const int fd, const double dec)
const char * getDeviceName()
bool getSiteLatitudeLongitude(const int fd, double *lat, double *lon)
bool isParked(const int fd)
bool setStopCurrent(const int fd, const int mA)
bool setMaingear(const int fd, int mg_ra, int mg_dec)
bool setGoToSpeedInd(const int fd, const int speedInd)
int getSlewSpeedInd(const int fd)
bool getUserRate3(const int fd, double *u3_ra, double *u3_dec)
bool getSwapTubeDelay(const int fd, int *delay_value)
bool sendOnly(const int fd, const char *cmd)
void resyncTTY(const int fd)
bool sendReceive2(const int fd, const char *cmd, char response1[], char response2[])
bool confirmed(const int fd, const char *cmd, char &response)
bool sendReceiveInt(const int fd, const char *cmd, int *value)
bool sendReceive(const int fd, const char *cmd, char response[])
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
double round(double value, int decimal_places)