1 /*
2     Astro-Physics INDI driver
3 
4     Copyright (C) 2014 Jasem Mutlaq
5 
6     Based on INDI Astrophysics Driver by Markus Wildi
7 
8     This library is free software; you can redistribute it and/or
9     modify it under the terms of the GNU Lesser General Public
10     License as published by the Free Software Foundation; either
11     version 2.1 of the License, or (at your option) any later version.
12 
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Lesser General Public License for more details.
17 
18     You should have received a copy of the GNU Lesser General Public
19     License along with this library; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 */
22 
23 #include "lx200ap.h"
24 
25 #include "indicom.h"
26 #include "lx200driver.h"
27 #include "lx200apdriver.h"
28 
29 #include <libnova/transform.h>
30 
31 #include <cmath>
32 #include <cstring>
33 #include <unistd.h>
34 #include <termios.h>
35 
36 #define MOUNT_TAB    "Mount"
37 
38 /* Constructor */
LX200AstroPhysics()39 LX200AstroPhysics::LX200AstroPhysics() : LX200Generic()
40 {
41     setLX200Capability(LX200_HAS_PULSE_GUIDING);
42     SetTelescopeCapability(GetTelescopeCapability() | TELESCOPE_HAS_PIER_SIDE | TELESCOPE_HAS_PEC | TELESCOPE_CAN_CONTROL_TRACK
43                            | TELESCOPE_HAS_TRACK_RATE, 4);
44 
45     sendLocationOnStartup = false;
46     sendTimeOnStartup = false;
47 }
48 
getDefaultName()49 const char *LX200AstroPhysics::getDefaultName()
50 {
51     return (const char *)"AstroPhysics";
52 }
53 
initProperties()54 bool LX200AstroPhysics::initProperties()
55 {
56     LX200Generic::initProperties();
57 
58     timeFormat = LX200_24;
59 
60     IUFillSwitch(&StartUpS[0], "COLD", "Cold", ISS_OFF);
61     IUFillSwitch(&StartUpS[1], "WARM", "Warm", ISS_OFF);
62     IUFillSwitchVector(&StartUpSP, StartUpS, 2, getDeviceName(), "STARTUP", "Mount init.", MAIN_CONTROL_TAB, IP_RW,
63                        ISR_1OFMANY, 0, IPS_IDLE);
64 
65     IUFillNumber(&HourangleCoordsN[0], "HA", "HA H:M:S", "%10.6m", 0., 24., 0., 0.);
66     IUFillNumber(&HourangleCoordsN[1], "DEC", "Dec D:M:S", "%10.6m", -90.0, 90.0, 0., 0.);
67     IUFillNumberVector(&HourangleCoordsNP, HourangleCoordsN, 2, getDeviceName(), "HOURANGLE_COORD", "Hourangle Coords",
68                        MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
69 
70     IUFillNumber(&HorizontalCoordsN[0], "AZ", "Az D:M:S", "%10.6m", 0., 360., 0., 0.);
71     IUFillNumber(&HorizontalCoordsN[1], "ALT", "Alt D:M:S", "%10.6m", -90., 90., 0., 0.);
72     IUFillNumberVector(&HorizontalCoordsNP, HorizontalCoordsN, 2, getDeviceName(), "HORIZONTAL_COORD",
73                        "Horizontal Coords", MAIN_CONTROL_TAB, IP_RW, 120, IPS_IDLE);
74 
75 
76     // Max rate is 999.99999X for the GTOCP4.
77     // Using :RR998.9999#  just to be safe. 15.041067*998.99999 = 15026.02578
78     TrackRateN[AXIS_RA].min = -15026.0258;
79     TrackRateN[AXIS_RA].max = 15026.0258;
80     TrackRateN[AXIS_DE].min = -998.9999;
81     TrackRateN[AXIS_DE].max = 998.9999;
82 
83     // Motion speed of axis when pressing NSWE buttons
84     IUFillSwitch(&SlewRateS[0], "12", "12x", ISS_OFF);
85     IUFillSwitch(&SlewRateS[1], "64", "64x", ISS_ON);
86     IUFillSwitch(&SlewRateS[2], "600", "600x", ISS_OFF);
87     IUFillSwitch(&SlewRateS[3], "1200", "1200x", ISS_OFF);
88     IUFillSwitchVector(&SlewRateSP, SlewRateS, 4, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB, IP_RW,
89                        ISR_1OFMANY, 0, IPS_IDLE);
90 
91     // Slew speed when performing regular GOTO
92     IUFillSwitch(&APSlewSpeedS[0], "600", "600x", ISS_ON);
93     IUFillSwitch(&APSlewSpeedS[1], "900", "900x", ISS_OFF);
94     IUFillSwitch(&APSlewSpeedS[2], "1200", "1200x", ISS_OFF);
95     IUFillSwitchVector(&APSlewSpeedSP, APSlewSpeedS, 3, getDeviceName(), "GOTO Rate", "", MOTION_TAB, IP_RW, ISR_1OFMANY,
96                        0, IPS_IDLE);
97 
98     IUFillSwitch(&SwapS[0], "NS", "North/South", ISS_OFF);
99     IUFillSwitch(&SwapS[1], "EW", "East/West", ISS_OFF);
100     IUFillSwitchVector(&SwapSP, SwapS, 2, getDeviceName(), "SWAP", "Swap buttons", MOTION_TAB, IP_RW, ISR_1OFMANY, 0,
101                        IPS_IDLE);
102 
103     IUFillSwitch(&SyncCMRS[USE_REGULAR_SYNC], ":CM#", ":CM#", ISS_OFF);
104     IUFillSwitch(&SyncCMRS[USE_CMR_SYNC], ":CMR#", ":CMR#", ISS_ON);
105     IUFillSwitchVector(&SyncCMRSP, SyncCMRS, 2, getDeviceName(), "SYNCCMR", "Sync", MOTION_TAB, IP_RW, ISR_1OFMANY, 0,
106                        IPS_IDLE);
107 
108     // guide speed
109     IUFillSwitch(&APGuideSpeedS[0], "0.25", "0.25x", ISS_OFF);
110     IUFillSwitch(&APGuideSpeedS[1], "0.5", "0.50x", ISS_OFF);
111     IUFillSwitch(&APGuideSpeedS[2], "1.0", "1.0x", ISS_ON);
112     IUFillSwitchVector(&APGuideSpeedSP, APGuideSpeedS, 3, getDeviceName(), "Guide Rate", "", GUIDE_TAB, IP_RW, ISR_1OFMANY,
113                        0, IPS_IDLE);
114 
115     IUFillText(&VersionT[0], "Number", "", nullptr);
116     IUFillTextVector(&VersionTP, VersionT, 1, getDeviceName(), "Firmware Info", "", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
117 
118     IUFillText(&DeclinationAxisT[0], "RELHA", "rel. to HA", "undefined");
119     IUFillTextVector(&DeclinationAxisTP, DeclinationAxisT, 1, getDeviceName(), "DECLINATIONAXIS", "Declination axis",
120                      MOUNT_TAB, IP_RO, 0, IPS_IDLE);
121 
122     // Slew threshold
123     IUFillNumber(&SlewAccuracyN[0], "SlewRA", "RA (arcmin)", "%10.6m", 0., 60., 1., 3.0);
124     IUFillNumber(&SlewAccuracyN[1], "SlewDEC", "Dec (arcmin)", "%10.6m", 0., 60., 1., 3.0);
125     IUFillNumberVector(&SlewAccuracyNP, SlewAccuracyN, 2, getDeviceName(), "Slew Accuracy", "", MOUNT_TAB, IP_RW, 0,
126                        IPS_IDLE);
127 
128     SetParkDataType(PARK_AZ_ALT);
129 
130     return true;
131 }
132 
ISGetProperties(const char * dev)133 void LX200AstroPhysics::ISGetProperties(const char *dev)
134 {
135     LX200Generic::ISGetProperties(dev);
136 
137     /*
138     if (isConnected())
139     {
140         defineProperty(&StartUpSP);
141         defineProperty(&VersionTP);
142 
143         //defineProperty(&DeclinationAxisTP);
144 
145         // Motion group
146         defineProperty(&APSlewSpeedSP);
147         defineProperty(&SwapSP);
148         defineProperty(&SyncCMRSP);
149         defineProperty(&APGuideSpeedSP);
150         defineProperty(&SlewAccuracyNP);
151 
152         LOG_INFO("Please initialize the mount before issuing any command.");
153     }
154     */
155 }
156 
updateProperties()157 bool LX200AstroPhysics::updateProperties()
158 {
159     LX200Generic::updateProperties();
160 
161     if (isConnected())
162     {
163         defineProperty(&StartUpSP);
164         defineProperty(&VersionTP);
165 
166         //defineProperty(&DeclinationAxisTP);
167 
168         /* Motion group */
169         defineProperty(&APSlewSpeedSP);
170         defineProperty(&SwapSP);
171         defineProperty(&SyncCMRSP);
172         defineProperty(&APGuideSpeedSP);
173         defineProperty(&SlewAccuracyNP);
174 
175         LOG_INFO("Please initialize the mount before issuing any command.");
176     }
177     else
178     {
179         deleteProperty(StartUpSP.name);
180         deleteProperty(VersionTP.name);
181         //deleteProperty(DeclinationAxisTP.name);
182         deleteProperty(APSlewSpeedSP.name);
183         deleteProperty(SwapSP.name);
184         deleteProperty(SyncCMRSP.name);
185         deleteProperty(APGuideSpeedSP.name);
186         deleteProperty(SlewAccuracyNP.name);
187     }
188 
189     return true;
190 }
191 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)192 bool LX200AstroPhysics::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
193 {
194     int err = 0;
195 
196     // ignore if not ours //
197     if (strcmp(getDeviceName(), dev))
198         return false;
199 
200     // ============================================================
201     // Satisfy AP mount initialization, see AP key pad manual p. 76
202     // ============================================================
203     if (!strcmp(name, StartUpSP.name))
204     {
205         int switch_nr;
206 
207         IUUpdateSwitch(&StartUpSP, states, names, n);
208 
209         if (initStatus == MOUNTNOTINITIALIZED)
210         {
211             if (timeUpdated == false || locationUpdated == false)
212             {
213                 StartUpSP.s = IPS_ALERT;
214                 LOG_ERROR("Time and location must be set before mount initialization is invoked.");
215                 IDSetSwitch(&StartUpSP, nullptr);
216                 return false;
217             }
218 
219             if (StartUpSP.sp[0].s == ISS_ON) // do it only in case a power on (cold start)
220             {
221                 if (setBasicDataPart1() == false)
222                 {
223                     StartUpSP.s = IPS_ALERT;
224                     IDSetSwitch(&StartUpSP, "Cold mount initialization failed.");
225                     return false;
226                 }
227             }
228 
229             initStatus = MOUNTINITIALIZED;
230 
231             if (isSimulation())
232             {
233                 SlewRateSP.s = IPS_OK;
234                 IDSetSwitch(&SlewRateSP, nullptr);
235 
236                 APSlewSpeedSP.s = IPS_OK;
237                 IDSetSwitch(&APSlewSpeedSP, nullptr);
238 
239                 IUSaveText(&VersionT[0], "1.0");
240                 VersionTP.s = IPS_OK;
241                 IDSetText(&VersionTP, nullptr);
242 
243                 StartUpSP.s = IPS_OK;
244                 IDSetSwitch(&StartUpSP, "Mount initialized.");
245 
246                 //currentRA  = 0;
247                 //currentDEC = 90;
248             }
249             else
250             {
251                 // Make sure that the mount is setup according to the properties
252                 switch_nr = IUFindOnSwitchIndex(&TrackModeSP);
253 
254                 if ( (err = selectAPTrackingMode(PortFD, switch_nr)) < 0)
255                 {
256                     LOGF_ERROR("StartUpSP: Error setting tracking mode (%d).", err);
257                     return false;
258                 }
259 
260                 TrackState = (switch_nr != AP_TRACKING_OFF) ? SCOPE_TRACKING : SCOPE_IDLE;
261 
262                 // On most mounts SlewRateS defines the MoveTo AND Slew (GOTO) speeds
263                 // lx200ap is different - some of the MoveTo speeds are not VALID
264                 // Slew speeds so we have to keep two lists.
265                 //
266                 // SlewRateS is used as the MoveTo speed
267                 switch_nr = IUFindOnSwitchIndex(&SlewRateSP);
268                 if ( (err = selectAPMoveToRate(PortFD, switch_nr)) < 0)
269                 {
270                     LOGF_ERROR("StartUpSP: Error setting move rate (%d).", err);
271                     return false;
272                 }
273 
274                 SlewRateSP.s = IPS_OK;
275                 IDSetSwitch(&SlewRateSP, nullptr);
276 
277                 // APSlewSpeedsS defines the Slew (GOTO) speeds valid on the AP mounts
278                 switch_nr = IUFindOnSwitchIndex(&APSlewSpeedSP);
279                 if ( (err = selectAPSlewRate(PortFD, switch_nr)) < 0)
280                 {
281                     LOGF_ERROR("StartUpSP: Error setting slew to rate (%d).", err);
282                     return false;
283                 }
284                 APSlewSpeedSP.s = IPS_OK;
285                 IDSetSwitch(&APSlewSpeedSP, nullptr);
286 
287                 getLX200RA(PortFD, &currentRA);
288                 getLX200DEC(PortFD, &currentDEC);
289 
290                 // make a IDSet in order the dome controller is aware of the initial values
291                 targetRA  = currentRA;
292                 targetDEC = currentDEC;
293 
294                 NewRaDec(currentRA, currentDEC);
295 
296                 char versionString[64];
297                 getAPVersionNumber(PortFD, versionString);
298                 VersionTP.s = IPS_OK;
299                 IUSaveText(&VersionT[0], versionString);
300                 IDSetText(&VersionTP, nullptr);
301 
302                 // TODO check controller type here
303                 INDI_UNUSED(controllerType);
304                 INDI_UNUSED(servoType);
305                 //controllerType = ...;
306 
307                 StartUpSP.s = IPS_OK;
308                 IDSetSwitch(&StartUpSP, "Mount initialized.");
309 
310             }
311         }
312         else
313         {
314             StartUpSP.s = IPS_OK;
315             IDSetSwitch(&StartUpSP, "Mount is already initialized.");
316         }
317         return true;
318     }
319 
320     // =======================================
321     // Swap Buttons
322     // =======================================
323     if (!strcmp(name, SwapSP.name))
324     {
325         int currentSwap;
326 
327         IUResetSwitch(&SwapSP);
328         IUUpdateSwitch(&SwapSP, states, names, n);
329         currentSwap = IUFindOnSwitchIndex(&SwapSP);
330 
331         if ((!isSimulation() && (err = swapAPButtons(PortFD, currentSwap)) < 0))
332         {
333             LOGF_ERROR("Error swapping buttons (%d).", err);
334             return false;
335         }
336 
337         SwapS[0].s = ISS_OFF;
338         SwapS[1].s = ISS_OFF;
339         SwapSP.s   = IPS_OK;
340         IDSetSwitch(&SwapSP, nullptr);
341         return true;
342     }
343 
344     // ===========================================================
345     // GOTO ("slew") Speed.
346     // ===========================================================
347     if (!strcmp(name, APSlewSpeedSP.name))
348     {
349         IUUpdateSwitch(&APSlewSpeedSP, states, names, n);
350         int slewRate = IUFindOnSwitchIndex(&APSlewSpeedSP);
351 
352         if (!isSimulation() && (err = selectAPSlewRate(PortFD, slewRate) < 0))
353         {
354             LOGF_ERROR("Error setting move to rate (%d).", err);
355             return false;
356         }
357 
358         APSlewSpeedSP.s = IPS_OK;
359         IDSetSwitch(&APSlewSpeedSP, nullptr);
360         return true;
361     }
362 
363     // ===========================================================
364     // Guide Speed.
365     // ===========================================================
366     if (!strcmp(name, APGuideSpeedSP.name))
367     {
368         IUUpdateSwitch(&APGuideSpeedSP, states, names, n);
369         int guideRate = IUFindOnSwitchIndex(&APGuideSpeedSP);
370 
371         if (!isSimulation() && (err = selectAPGuideRate(PortFD, guideRate) < 0))
372         {
373             LOGF_ERROR("Error setting guiding to rate (%d).", err);
374             return false;
375         }
376 
377         APGuideSpeedSP.s = IPS_OK;
378         IDSetSwitch(&APGuideSpeedSP, nullptr);
379         return true;
380     }
381 
382     // =======================================
383     // Choose the appropriate sync command
384     // =======================================
385     if (!strcmp(name, SyncCMRSP.name))
386     {
387         IUResetSwitch(&SyncCMRSP);
388         IUUpdateSwitch(&SyncCMRSP, states, names, n);
389         IUFindOnSwitchIndex(&SyncCMRSP);
390         SyncCMRSP.s = IPS_OK;
391         IDSetSwitch(&SyncCMRSP, nullptr);
392         return true;
393     }
394 
395     // =======================================
396     // Choose the PEC playback mode
397     // =======================================
398     if (!strcmp(name, PECStateSP.name))
399     {
400         IUResetSwitch(&PECStateSP);
401         IUUpdateSwitch(&PECStateSP, states, names, n);
402         IUFindOnSwitchIndex(&PECStateSP);
403 
404         int pecstate = IUFindOnSwitchIndex(&PECStateSP);
405 
406         if (!isSimulation() && (err = selectAPPECState(PortFD, pecstate) < 0))
407         {
408             LOGF_ERROR("Error setting PEC state (%d).", err);
409             return false;
410         }
411 
412         PECStateSP.s = IPS_OK;
413         IDSetSwitch(&PECStateSP, nullptr);
414 
415         return true;
416     }
417 
418     return LX200Generic::ISNewSwitch(dev, name, states, names, n);
419 }
420 
421 /**************************************************************************************
422 **
423 ***************************************************************************************/
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)424 bool LX200AstroPhysics::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
425 {
426     if (strcmp(getDeviceName(), dev))
427         return false;
428 
429     // Update slew precision limit
430     if (!strcmp(name, SlewAccuracyNP.name))
431     {
432         if (IUUpdateNumber(&SlewAccuracyNP, values, names, n) < 0)
433             return false;
434 
435         SlewAccuracyNP.s = IPS_OK;
436 
437         if (SlewAccuracyN[0].value < 3 || SlewAccuracyN[1].value < 3)
438             IDSetNumber(&SlewAccuracyNP, "Warning: Setting the slew accuracy too low may result in a dead lock");
439 
440         IDSetNumber(&SlewAccuracyNP, nullptr);
441         return true;
442     }
443 
444     return LX200Generic::ISNewNumber(dev, name, values, names, n);
445 }
446 
isMountInit()447 bool LX200AstroPhysics::isMountInit()
448 {
449     return (StartUpSP.s != IPS_IDLE);
450 }
451 
ReadScopeStatus()452 bool LX200AstroPhysics::ReadScopeStatus()
453 {
454     if (!isMountInit())
455         return false;
456 
457     if (isSimulation())
458     {
459         mountSim();
460         return true;
461     }
462 
463     if (getLX200RA(PortFD, &currentRA) < 0 || getLX200DEC(PortFD, &currentDEC) < 0)
464     {
465         EqNP.s = IPS_ALERT;
466         IDSetNumber(&EqNP, "Error reading RA/DEC.");
467         return false;
468     }
469 
470     if (TrackState == SCOPE_SLEWING)
471     {
472         double dx = targetRA - currentRA;
473         double dy = targetDEC - currentDEC;
474 
475         // Wait until acknowledged or within threshold
476         if (fabs(dx) <= (SlewAccuracyN[0].value / (900.0)) && fabs(dy) <= (SlewAccuracyN[1].value / 60.0))
477         {
478             TrackState = SCOPE_TRACKING;
479             LOG_INFO("Slew is complete. Tracking...");
480         }
481     }
482     else if (TrackState == SCOPE_PARKING)
483     {
484         double currentAlt, currentAz;
485         if (getLX200Az(PortFD, &currentAz) < 0 || getLX200Alt(PortFD, &currentAlt) < 0)
486         {
487             EqNP.s = IPS_ALERT;
488             IDSetNumber(&EqNP, "Error reading Az/Alt.");
489             return false;
490         }
491 
492         double dx = GetAxis1Park() - currentAz;
493         double dy = GetAxis2Park() - currentAlt;
494 
495         LOGF_DEBUG("Parking... targetAz: %g currentAz: %g dx: %g targetAlt: %g currentAlt: %g dy: %g", GetAxis1Park(),
496                    currentAz, dx, GetAxis2Park(), currentAlt, dy);
497 
498         if (fabs(dx) <= (SlewAccuracyN[0].value / (60.0)) && fabs(dy) <= (SlewAccuracyN[1].value / 60.0))
499         {
500             LOG_DEBUG("Parking slew is complete. Asking astrophysics mount to park...");
501 
502             if (!isSimulation() && setAPPark(PortFD) < 0)
503             {
504                 LOG_ERROR("Parking Failed.");
505                 return false;
506             }
507 
508             SetParked(true);
509         }
510     }
511 
512     NewRaDec(currentRA, currentDEC);
513 
514     syncSideOfPier();
515 
516     return true;
517 }
518 
setBasicDataPart0()519 bool LX200AstroPhysics::setBasicDataPart0()
520 {
521     int err;
522     //struct ln_date utm;
523     //struct ln_zonedate ltm;
524 
525     if (isSimulation() == true)
526     {
527         LOG_INFO("setBasicDataPart0 simulation complete.");
528         return true;
529     }
530 
531     if ((err = setAPClearBuffer(PortFD)) < 0)
532     {
533         LOGF_ERROR("Error clearing the buffer (%d): %s", err, strerror(err));
534         return false;
535     }
536 
537     if ((err = setAPLongFormat(PortFD)) < 0)
538     {
539         LOGF_ERROR("Error setting long format failed (%d): %s", err, strerror(err));
540         return false;
541     }
542 
543     if ((err = setAPBackLashCompensation(PortFD, 0, 0, 0)) < 0)
544     {
545         // It seems we need to send it twice before it works!
546         if ((err = setAPBackLashCompensation(PortFD, 0, 0, 0)) < 0)
547         {
548             LOGF_ERROR("Error setting back lash compensation (%d): %s.", err, strerror(err));
549             return false;
550         }
551     }
552 
553     // Detect and set fomat. It should be LONG.
554     checkLX200EquatorialFormat(PortFD);
555 
556     return true;
557 }
558 
setBasicDataPart1()559 bool LX200AstroPhysics::setBasicDataPart1()
560 {
561     int err = 0;
562 
563     if (InitPark())
564     {
565         // If loading parking data is successful, we just set the default parking values.
566         SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
567         SetAxis2ParkDefault(LocationN[LOCATION_LATITUDE].value);
568     }
569     else
570     {
571         // Otherwise, we set all parking data to default in case no parking data is found.
572         SetAxis1Park(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
573         SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value);
574 
575         SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
576         SetAxis2ParkDefault(LocationN[LOCATION_LATITUDE].value);
577     }
578 
579     // Unpark
580     UnPark();
581 
582     // Stop
583     if (!isSimulation() && (err = setAPMotionStop(PortFD)) < 0)
584     {
585         LOGF_ERROR("Stop motion (:Q#) failed, check the mount (%d): %s", err, strerror(err));
586         return false;
587     }
588 
589     // AP always track after unpark? Must check
590     TrackState = SCOPE_TRACKING;
591 
592     return true;
593 }
594 
Goto(double r,double d)595 bool LX200AstroPhysics::Goto(double r, double d)
596 {
597     const struct timespec timeout = {0, 100000000L};
598 
599     targetRA  = r;
600     targetDEC = d;
601     char RAStr[64], DecStr[64];
602 
603     fs_sexa(RAStr, targetRA, 2, 3600);
604     fs_sexa(DecStr, targetDEC, 2, 3600);
605 
606     // If moving, let's stop it first.
607     if (EqNP.s == IPS_BUSY)
608     {
609         if (!isSimulation() && abortSlew(PortFD) < 0)
610         {
611             AbortSP.s = IPS_ALERT;
612             IDSetSwitch(&AbortSP, "Abort slew failed.");
613             return false;
614         }
615 
616         AbortSP.s = IPS_OK;
617         EqNP.s    = IPS_IDLE;
618         IDSetSwitch(&AbortSP, "Slew aborted.");
619         IDSetNumber(&EqNP, nullptr);
620 
621         if (MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY)
622         {
623             MovementNSSP.s = IPS_IDLE;
624             MovementWESP.s = IPS_IDLE;
625             EqNP.s = IPS_IDLE;
626             IUResetSwitch(&MovementNSSP);
627             IUResetSwitch(&MovementWESP);
628             IDSetSwitch(&MovementNSSP, nullptr);
629             IDSetSwitch(&MovementWESP, nullptr);
630         }
631 
632         // sleep for 100 mseconds
633         nanosleep(&timeout, nullptr);
634     }
635 
636     if (!isSimulation())
637     {
638         if (setAPObjectRA(PortFD, targetRA) < 0 || (setAPObjectDEC(PortFD, targetDEC)) < 0)
639         {
640             EqNP.s = IPS_ALERT;
641             IDSetNumber(&EqNP, "Error setting RA/DEC.");
642             return false;
643         }
644 
645         int err = 0;
646 
647         /* Slew reads the '0', that is not the end of the slew */
648         if ((err = Slew(PortFD)))
649         {
650             EqNP.s = IPS_ALERT;
651             IDSetNumber(&EqNP, "Error Slewing to JNow RA %s - DEC %s\n", RAStr, DecStr);
652             slewError(err);
653             return false;
654         }
655     }
656 
657     TrackState = SCOPE_SLEWING;
658     //EqNP.s     = IPS_BUSY;
659 
660     LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
661     return true;
662 }
663 
664 
SendPulseCmd(int8_t direction,uint32_t duration_msec)665 int LX200AstroPhysics::SendPulseCmd(int8_t direction, uint32_t duration_msec)
666 {
667     return APSendPulseCmd(PortFD, direction, duration_msec);
668 }
669 
670 
Handshake()671 bool LX200AstroPhysics::Handshake()
672 {
673     return setBasicDataPart0();
674 }
675 
Disconnect()676 bool LX200AstroPhysics::Disconnect()
677 {
678     timeUpdated     = false;
679     locationUpdated = false;
680 
681     return LX200Generic::Disconnect();
682 }
683 
Sync(double ra,double dec)684 bool LX200AstroPhysics::Sync(double ra, double dec)
685 {
686     char syncString[256];
687 
688     int syncType = IUFindOnSwitchIndex(&SyncCMRSP);
689 
690     if (!isSimulation())
691     {
692         if (setAPObjectRA(PortFD, ra) < 0 || setAPObjectDEC(PortFD, dec) < 0)
693         {
694             EqNP.s = IPS_ALERT;
695             IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
696             return false;
697         }
698 
699         bool syncOK = true;
700 
701         switch (syncType)
702         {
703             case USE_REGULAR_SYNC:
704                 if (::Sync(PortFD, syncString) < 0)
705                     syncOK = false;
706                 break;
707 
708             case USE_CMR_SYNC:
709                 if (APSyncCMR(PortFD, syncString) < 0)
710                     syncOK = false;
711                 break;
712 
713             default:
714                 break;
715         }
716 
717         if (syncOK == false)
718         {
719             EqNP.s = IPS_ALERT;
720             IDSetNumber(&EqNP, "Synchronization failed.");
721             return false;
722         }
723 
724     }
725 
726     currentRA  = ra;
727     currentDEC = dec;
728 
729     LOGF_DEBUG("%s Synchronization successful %s", (syncType == USE_REGULAR_SYNC ? "CM" : "CMR"), syncString);
730     LOG_INFO("Synchronization successful.");
731 
732     EqNP.s     = IPS_OK;
733 
734     NewRaDec(currentRA, currentDEC);
735 
736     return true;
737 }
738 
updateTime(ln_date * utc,double utc_offset)739 bool LX200AstroPhysics::updateTime(ln_date *utc, double utc_offset)
740 {
741     struct ln_zonedate ltm;
742 
743     if (isSimulation())
744     {
745         timeUpdated = true;
746         return true;
747     }
748 
749     ln_date_to_zonedate(utc, &ltm, utc_offset * 3600.0);
750 
751     JD = ln_get_julian_day(utc);
752 
753     LOGF_DEBUG("New JD is %.2f", JD);
754 
755     // Set Local Time
756     if (setLocalTime(PortFD, ltm.hours, ltm.minutes, (int)ltm.seconds) < 0)
757     {
758         LOG_ERROR("Error setting local time.");
759         return false;
760     }
761 
762     LOGF_DEBUG("Set Local Time %02d:%02d:%02d is successful.", ltm.hours, ltm.minutes,
763                (int)ltm.seconds);
764 
765     if (setCalenderDate(PortFD, ltm.days, ltm.months, ltm.years) < 0)
766     {
767         LOG_ERROR("Error setting local date.");
768         return false;
769     }
770 
771     LOGF_DEBUG("Set Local Date %02d/%02d/%02d is successful.", ltm.days, ltm.months, ltm.years);
772 
773     if (setAPUTCOffset(PortFD, fabs(utc_offset)) < 0)
774     {
775         LOG_ERROR("Error setting UTC Offset.");
776         return false;
777     }
778 
779     LOGF_DEBUG("Set UTC Offset %g (always positive for AP) is successful.", fabs(utc_offset));
780 
781     LOG_INFO("Time updated.");
782 
783     timeUpdated = true;
784 
785     return true;
786 }
787 
updateLocation(double latitude,double longitude,double elevation)788 bool LX200AstroPhysics::updateLocation(double latitude, double longitude, double elevation)
789 {
790     INDI_UNUSED(elevation);
791 
792     if (isSimulation())
793     {
794         locationUpdated = true;
795         return true;
796     }
797 
798     if (!isSimulation() && setAPSiteLongitude(PortFD, 360.0 - longitude) < 0)
799     {
800         LOG_ERROR("Error setting site longitude coordinates");
801         return false;
802     }
803 
804     if (!isSimulation() && setAPSiteLatitude(PortFD, latitude) < 0)
805     {
806         LOG_ERROR("Error setting site latitude coordinates");
807         return false;
808     }
809 
810     char l[32], L[32];
811     fs_sexa(l, latitude, 3, 3600);
812     fs_sexa(L, longitude, 4, 3600);
813 
814     LOGF_INFO("Site location updated to Lat %.32s - Long %.32s", l, L);
815 
816     locationUpdated = true;
817 
818     return true;
819 }
820 
debugTriggered(bool enable)821 void LX200AstroPhysics::debugTriggered(bool enable)
822 {
823     INDI_UNUSED(enable);
824     LX200Generic::debugTriggered(enable);
825     set_lx200ap_name(getDeviceName(), DBG_SCOPE);
826 }
827 
828 
829 // For most mounts the SetSlewRate() method sets both the MoveTo and Slew (GOTO) speeds.
830 // For AP mounts these two speeds are handled separately - so SetSlewRate() actually sets the MoveTo speed for AP mounts - confusing!
831 // ApSetSlew
SetSlewRate(int index)832 bool LX200AstroPhysics::SetSlewRate(int index)
833 {
834     if (!isSimulation() && selectAPMoveToRate(PortFD, index) < 0)
835     {
836         SlewRateSP.s = IPS_ALERT;
837         IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
838         return false;
839     }
840 
841     SlewRateSP.s = IPS_OK;
842     IDSetSwitch(&SlewRateSP, nullptr);
843     return true;
844 }
845 
Park()846 bool LX200AstroPhysics::Park()
847 {
848     if (initStatus == MOUNTNOTINITIALIZED)
849     {
850         LOG_WARN("You must initialize the mount before parking.");
851         return false;
852     }
853 
854     double parkAz  = GetAxis1Park();
855     double parkAlt = GetAxis2Park();
856 
857     char AzStr[16], AltStr[16];
858     fs_sexa(AzStr, parkAz, 2, 3600);
859     fs_sexa(AltStr, parkAlt, 2, 3600);
860     LOGF_DEBUG("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
861 
862     if (isSimulation())
863     {
864         INDI::IEquatorialCoordinates equatorialCoords {0, 0};
865         INDI::IHorizontalCoordinates horizontalCoords {parkAz, parkAlt};
866         INDI::HorizontalToEquatorial(&horizontalCoords, &m_Location, ln_get_julian_from_sys(), &equatorialCoords);
867         Goto(equatorialCoords.rightascension, equatorialCoords.declination);
868     }
869     else
870     {
871         if (setAPObjectAZ(PortFD, parkAz) < 0 || setAPObjectAlt(PortFD, parkAlt) < 0)
872         {
873             LOG_ERROR("Error setting Az/Alt.");
874             return false;
875         }
876 
877         int err = 0;
878 
879         /* Slew reads the '0', that is not the end of the slew */
880         if ((err = Slew(PortFD)))
881         {
882             LOGF_ERROR("Error Slewing to Az %s - Alt %s", AzStr, AltStr);
883             slewError(err);
884             return false;
885         }
886     }
887 
888     EqNP.s     = IPS_BUSY;
889     TrackState = SCOPE_PARKING;
890     LOG_INFO("Parking is in progress...");
891 
892     return true;
893 }
894 
UnPark()895 bool LX200AstroPhysics::UnPark()
896 {
897     // First we unpark astrophysics
898     if (isSimulation() == false)
899     {
900         if (setAPUnPark(PortFD) < 0)
901         {
902             LOG_ERROR("UnParking Failed.");
903             return false;
904         }
905     }
906 
907     // Then we sync with to our last stored position
908     double parkAz  = GetAxis1Park();
909     double parkAlt = GetAxis2Park();
910 
911     char AzStr[16], AltStr[16];
912     fs_sexa(AzStr, parkAz, 2, 3600);
913     fs_sexa(AltStr, parkAlt, 2, 3600);
914     LOGF_DEBUG("Syncing to parked coordinates Az (%s) Alt (%s)...", AzStr, AltStr);
915 
916     if (isSimulation())
917     {
918         INDI::IEquatorialCoordinates equatorialCoords {0, 0};
919         INDI::IHorizontalCoordinates horizontalCoords {parkAz, parkAlt};
920         INDI::HorizontalToEquatorial(&horizontalCoords, &m_Location, ln_get_julian_from_sys(), &equatorialCoords);
921 
922         currentRA = equatorialCoords.rightascension;
923         currentDEC = equatorialCoords.declination;
924     }
925     else
926     {
927         if (setAPObjectAZ(PortFD, parkAz) < 0 || (setAPObjectAlt(PortFD, parkAlt)) < 0)
928         {
929             LOG_ERROR("Error setting Az/Alt.");
930             return false;
931         }
932 
933         char syncString[256];
934         if (APSyncCM(PortFD, syncString) < 0)
935         {
936             LOG_WARN("Sync failed.");
937             return false;
938         }
939     }
940 
941     SetParked(false);
942     return true;
943 }
944 
SetCurrentPark()945 bool LX200AstroPhysics::SetCurrentPark()
946 {
947     INDI::IEquatorialCoordinates equatorialCoords {currentRA, currentDEC};
948     INDI::IHorizontalCoordinates horizontalCoords {0, 0};
949     INDI::EquatorialToHorizontal(&equatorialCoords, &m_Location, ln_get_julian_from_sys(), &horizontalCoords);
950     double parkAZ = horizontalCoords.azimuth;
951     double parkAlt = horizontalCoords.altitude;
952 
953     char AzStr[16], AltStr[16];
954     fs_sexa(AzStr, parkAZ, 2, 3600);
955     fs_sexa(AltStr, parkAlt, 2, 3600);
956 
957     LOGF_DEBUG("Setting current parking position to coordinates Az (%s) Alt (%s)...", AzStr,
958                AltStr);
959 
960     SetAxis1Park(parkAZ);
961     SetAxis2Park(parkAlt);
962 
963     return true;
964 }
965 
SetDefaultPark()966 bool LX200AstroPhysics::SetDefaultPark()
967 {
968     // Az = 0 for North hemisphere
969     SetAxis1Park(LocationN[LOCATION_LATITUDE].value > 0 ? 0 : 180);
970 
971     // Alt = Latitude
972     SetAxis2Park(LocationN[LOCATION_LATITUDE].value);
973 
974     return true;
975 }
976 
syncSideOfPier()977 void LX200AstroPhysics::syncSideOfPier()
978 {
979     const char *cmd = ":pS#";
980     // Response
981     char response[16] = { 0 };
982     int rc = 0, nbytes_read = 0, nbytes_written = 0;
983 
984     LOGF_DEBUG("CMD: <%s>", cmd);
985 
986     tcflush(PortFD, TCIOFLUSH);
987 
988     if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
989     {
990         char errmsg[256];
991         tty_error_msg(rc, errmsg, 256);
992         LOGF_ERROR("Error writing to device %s (%d)", errmsg, rc);
993         return;
994     }
995 
996     // Read Side
997     if ((rc = tty_read_section(PortFD, response, '#', 3, &nbytes_read)) != TTY_OK)
998     {
999         char errmsg[256];
1000         tty_error_msg(rc, errmsg, 256);
1001         LOGF_ERROR("Error reading from device %s (%d)", errmsg, rc);
1002         return;
1003     }
1004 
1005     response[nbytes_read - 1] = '\0';
1006 
1007     tcflush(PortFD, TCIOFLUSH);
1008 
1009     LOGF_DEBUG("RES: <%s>", response);
1010 
1011     if (!strcmp(response, "East"))
1012         setPierSide(INDI::Telescope::PIER_EAST);
1013     else if (!strcmp(response, "West"))
1014         setPierSide(INDI::Telescope::PIER_WEST);
1015     else
1016         LOGF_ERROR("Invalid pier side response from device-> %s", response);
1017 
1018 }
1019 
saveConfigItems(FILE * fp)1020 bool LX200AstroPhysics::saveConfigItems(FILE *fp)
1021 {
1022     LX200Generic::saveConfigItems(fp);
1023 
1024     IUSaveConfigSwitch(fp, &SyncCMRSP);
1025     IUSaveConfigSwitch(fp, &APSlewSpeedSP);
1026     IUSaveConfigSwitch(fp, &APGuideSpeedSP);
1027 
1028     return true;
1029 }
1030 
SetTrackMode(uint8_t mode)1031 bool LX200AstroPhysics::SetTrackMode(uint8_t mode)
1032 {
1033     int err = 0;
1034 
1035     if (mode == TRACK_CUSTOM)
1036     {
1037         if (!isSimulation() && (err = selectAPTrackingMode(PortFD, AP_TRACKING_SIDEREAL)) < 0)
1038         {
1039             LOGF_ERROR("Error setting tracking mode (%d).", err);
1040             return false;
1041         }
1042 
1043         return SetTrackRate(TrackRateN[AXIS_RA].value, TrackRateN[AXIS_DE].value);
1044     }
1045 
1046     if (!isSimulation() && (err = selectAPTrackingMode(PortFD, mode)) < 0)
1047     {
1048         LOGF_ERROR("Error setting tracking mode (%d).", err);
1049         return false;
1050     }
1051 
1052     return true;
1053 }
1054 
SetTrackEnabled(bool enabled)1055 bool LX200AstroPhysics::SetTrackEnabled(bool enabled)
1056 {
1057     return SetTrackMode(enabled ? IUFindOnSwitchIndex(&TrackModeSP) : AP_TRACKING_OFF);
1058 }
1059 
SetTrackRate(double raRate,double deRate)1060 bool LX200AstroPhysics::SetTrackRate(double raRate, double deRate)
1061 {
1062     // Convert to arcsecs/s to AP sidereal multiplier
1063     /*
1064     :RR0.0000#      =       normal sidereal tracking in RA - similar to  :RT2#
1065     :RR+1.0000#     =       1 + normal sidereal     =       2X sidereal
1066     :RR+9.0000#     =       9 + normal sidereal     =       10X sidereal
1067     :RR-1.0000#     =       normal sidereal - 1     =       0 or Stop - similar to  :RT9#
1068     :RR-11.0000#    =       normal sidereal - 11    =       -10X sidereal (East at 10X)
1069 
1070     :RD0.0000#      =       normal zero rate for Dec.
1071     :RD5.0000#      =       5 + normal zero rate    =       5X sidereal clockwise from above - equivalent to South
1072     :RD-5.0000#     =       normal zero rate - 5    =       5X sidereal counter-clockwise from above - equivalent to North
1073     */
1074 
1075     double APRARate = (raRate - TRACKRATE_SIDEREAL) / TRACKRATE_SIDEREAL;
1076     double APDERate = deRate / TRACKRATE_SIDEREAL;
1077 
1078     if (!isSimulation())
1079     {
1080         if (setAPRATrackRate(PortFD, APRARate) < 0 || setAPDETrackRate(PortFD, APDERate) < 0)
1081             return false;
1082     }
1083 
1084     return true;
1085 }
1086 
getUTFOffset(double * offset)1087 bool LX200AstroPhysics::getUTFOffset(double *offset)
1088 {
1089     if (isSimulation())
1090     {
1091         *offset = 3;
1092         return true;
1093     }
1094 
1095     return (getAPUTCOffset(PortFD, offset) == 0);
1096 }
1097