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