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