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