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