1 /*
2 
3 LX200_TeenAstro
4 
5 Based on LX200_OnStep and others
6 François Desvallées https://github.com/fdesvallees
7 
8 Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com)
9 
10 This library is free software;
11 you can redistribute it and / or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation;
14 either
15 version 2.1 of the License, or (at your option) any later version.
16 
17 This library is distributed in the hope that it will be useful,
18      but WITHOUT ANY WARRANTY;
19 without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 Lesser General Public License for more details.
22 
23 You should have received a copy of the GNU Lesser General Public
24 License along with this library;
25 if not, write to the Free Software
26 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110 - 1301  USA
27 
28 */
29 
30 #include "lx200_TeenAstro.h"
31 #include <libnova/sidereal_time.h>
32 
33 #include <unistd.h>
34 #include <termios.h>
35 #include <stdlib.h>
36 #include <cerrno>
37 
38 #include <cmath>
39 #include <memory>
40 #include <cstring>
41 #include <mutex>
42 
43 
44 /* Simulation Parameters */
45 #define SLEWRATE 1        /* slew rate, degrees/s */
46 #define SIDRATE  0.004178 /* sidereal rate, degrees/s */
47 
48 #define FIRMWARE_TAB "Firmware data"
49 
50 #define ONSTEP_TIMEOUT  3
51 
52 // Our telescope auto pointer
53 static std::unique_ptr<LX200_TeenAstro> teenAstro(new LX200_TeenAstro());
54 extern std::mutex lx200CommsLock;
55 
56 /*
57  * LX200 TeenAstro constructor
58  */
LX200_TeenAstro()59 LX200_TeenAstro::LX200_TeenAstro()
60 {
61     setVersion(1, 2);           // don't forget to update drivers.xml
62 
63     DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
64 
65     SetTelescopeCapability(
66         TELESCOPE_CAN_GOTO | TELESCOPE_CAN_SYNC | TELESCOPE_CAN_PARK | TELESCOPE_CAN_ABORT |
67         TELESCOPE_HAS_TIME | TELESCOPE_HAS_LOCATION | TELESCOPE_HAS_PIER_SIDE |
68         TELESCOPE_HAS_TRACK_MODE  | TELESCOPE_CAN_CONTROL_TRACK);
69 
70     LOG_DEBUG("Initializing from LX200 TeenAstro device...");
71 }
72 
debugTriggered(bool enable)73 void LX200_TeenAstro::debugTriggered(bool enable)
74 {
75     INDI_UNUSED(enable);
76     setLX200Debug(getDeviceName(), DBG_SCOPE);
77 }
78 
getDriverName()79 const char *LX200_TeenAstro::getDriverName()
80 {
81     return getDefaultName();
82 }
83 
getDefaultName()84 const char *LX200_TeenAstro::getDefaultName()
85 {
86     return "LX200 TeenAstro";
87 }
88 
initProperties()89 bool LX200_TeenAstro::initProperties()
90 {
91     /* Make sure to init parent properties first */
92     INDI::Telescope::initProperties();
93 
94     SetParkDataType(PARK_RA_DEC);
95 
96     // ============== MAIN_CONTROL_TAB
97 
98     // Tracking Mode
99     AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
100     AddTrackMode("TRACK_SOLAR", "Solar");
101     AddTrackMode("TRACK_LUNAR", "Lunar");
102 
103     // Error Status
104     IUFillText(&ErrorStatusT[0], "Error code", "", "");
105     IUFillTextVector(&ErrorStatusTP, ErrorStatusT, 1, getDeviceName(), "Mount Status", "", MAIN_CONTROL_TAB, IP_RO, 0,
106                      IPS_IDLE);
107 
108 
109     // ============== MOTION_TAB
110     // Motion speed of axis when pressing NSWE buttons
111     IUFillSwitch(&SlewRateS[0], "Guide", "Guide Speed", ISS_OFF);
112     IUFillSwitch(&SlewRateS[1], "Slow", "Slow", ISS_OFF);
113     IUFillSwitch(&SlewRateS[2], "Medium", "Medium", ISS_OFF);
114     IUFillSwitch(&SlewRateS[3], "Fast", "Fast", ISS_ON);
115     IUFillSwitch(&SlewRateS[4], "Max", "Max", ISS_OFF);
116     IUFillSwitchVector(&SlewRateSP, SlewRateS, 5, getDeviceName(), "TELESCOPE_SLEW_RATE", "Centering Rate",
117                        MOTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
118 
119     // ============== GUIDE_TAB
120     // Motion speed of axis when guiding
121     IUFillSwitch(&GuideRateS[0], "25", "0.25x", ISS_OFF);
122     IUFillSwitch(&GuideRateS[1], "50", "0.5x", ISS_ON);
123     IUFillSwitch(&GuideRateS[2], "100", "1.0x", ISS_OFF);
124     IUFillSwitchVector(&GuideRateSP, GuideRateS, 3, getDeviceName(), "TELESCOPE_GUIDE_RATE", "Guide Rate", GUIDE_TAB, IP_RW,
125                        ISR_1OFMANY, 0, IPS_IDLE);
126     initGuiderProperties(getDeviceName(), GUIDE_TAB);
127 
128     // ============== OPTIONS_TAB
129     // Slew threshold
130     IUFillNumber(&SlewAccuracyN[0], "SlewRA", "RA (arcmin)", "%10.6m", 0., 60., 1., 3.0);   // min,max,step,current
131     IUFillNumber(&SlewAccuracyN[1], "SlewDEC", "Dec (arcmin)", "%10.6m", 0., 60., 1., 3.0);
132     IUFillNumberVector(&SlewAccuracyNP, SlewAccuracyN, NARRAY(SlewAccuracyN), getDeviceName(), "Slew Accuracy", "",
133                        OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
134 
135     // ============== SITE_TAB
136     IUFillSwitch(&SiteS[0], "Site 1", "", ISS_OFF);
137     IUFillSwitch(&SiteS[1], "Site 2", "", ISS_OFF);
138     IUFillSwitch(&SiteS[2], "Site 3", "", ISS_OFF);
139     IUFillSwitch(&SiteS[3], "Site 4", "", ISS_OFF);
140     IUFillSwitchVector(&SiteSP, SiteS, 4, getDeviceName(), "Sites", "", SITE_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
141 
142     IUFillText(&SiteNameT[0], "Name", "", "");
143     IUFillTextVector(&SiteNameTP, SiteNameT, 1, getDeviceName(), "Site Name", "", SITE_TAB, IP_RO, 0, IPS_IDLE);
144 
145 
146     // ============== FIRMWARE_TAB
147     IUFillText(&VersionT[0], "Date", "", "");
148     IUFillText(&VersionT[1], "Time", "", "");
149     IUFillText(&VersionT[2], "Number", "", "");
150     IUFillText(&VersionT[3], "Name", "", "");
151     IUFillTextVector(&VersionTP, VersionT, 4, getDeviceName(), "Firmware Info", "", FIRMWARE_TAB, IP_RO, 0, IPS_IDLE);
152 
153     addAuxControls();
154     setDriverInterface(getDriverInterface() | GUIDER_INTERFACE);
155 
156     return true;
157 }
ISGetProperties(const char * dev)158 void LX200_TeenAstro::ISGetProperties(const char *dev)
159 {
160     if (dev != nullptr && strcmp(dev, getDeviceName()) != 0)
161         return;
162 
163     INDI::Telescope::ISGetProperties(dev);
164 }
165 
updateProperties()166 bool LX200_TeenAstro::updateProperties()
167 {
168     INDI::Telescope::updateProperties();
169 
170     if (isConnected())
171     {
172         // Delete inherited controls - too confusing
173         deleteProperty("USEJOYSTICK");
174         deleteProperty("ACTIVE_DEVICES");
175         deleteProperty("DOME_POLICY");
176         deleteProperty("TELESCOPE_HAS_TRACK_RATE");
177         // Main Control
178         defineProperty(&SlewAccuracyNP);
179         defineProperty(&ErrorStatusTP);
180         // Connection
181         // Options
182         // Motion Control
183         defineProperty(&SlewRateSP);
184         defineProperty(&GuideRateSP);
185 
186         // Site Management
187         defineProperty(&ParkOptionSP);
188         defineProperty(&SetHomeSP);
189 
190         defineProperty(&SiteSP);
191         defineProperty(&SiteNameTP);
192 
193         // Guide
194         defineProperty(&GuideNSNP);
195         defineProperty(&GuideWENP);
196 
197         // Firmware Data
198         defineProperty(&VersionTP);
199         getBasicData();
200     }
201     else
202     {
203         // Main Control
204         deleteProperty(SlewAccuracyNP.name);
205         deleteProperty(ErrorStatusTP.name);
206         // Connection
207         // Options
208         // Motion Control
209         deleteProperty(SlewRateSP.name);
210         deleteProperty(GuideRateSP.name);
211         deleteProperty(SiteSP.name);
212         deleteProperty(SiteNameTP.name);
213 
214         // Site Management
215         deleteProperty(ParkOptionSP.name);
216         deleteProperty(SetHomeSP.name);
217         // Guide
218         deleteProperty(GuideNSNP.name);
219         deleteProperty(GuideWENP.name);
220         // Firmware Data
221         deleteProperty(VersionTP.name);
222     }
223     return true;
224 }
225 
226 
Handshake()227 bool LX200_TeenAstro::Handshake()
228 {
229     if (isSimulation())
230     {
231         LOG_INFO("Simulated Connection.");
232         return true;
233     }
234 
235     if (getLX200RA(PortFD, &currentRA) != 0)
236     {
237         LOG_ERROR("Error communicating with telescope.");
238         return false;
239     }
240     LOG_INFO("TeenAstro is Connected");
241     return true;
242 }
243 
244 /*
245  * ReadScopeStatus
246  * Called when polling the mount about once per second
247  */
ReadScopeStatus()248 bool LX200_TeenAstro::ReadScopeStatus()
249 {
250     if (isSimulation())
251     {
252         mountSim();
253         return true;
254     }
255     if (!isConnected())
256         return false;
257 
258     if (getLX200RA(PortFD, &currentRA) < 0 || getLX200DEC(PortFD, &currentDEC) < 0)
259     {
260         EqNP.s = IPS_ALERT;
261         return false;
262     }
263 
264     if (TrackState == SCOPE_SLEWING)
265     {
266         // Check if LX200 is done slewing
267         if (isSlewComplete())
268         {
269             TrackState = SCOPE_TRACKING;
270             LOG_INFO("Slew is complete. Tracking...");
271         }
272     }
273     else if (TrackState == SCOPE_PARKING)
274     {
275         LOG_INFO("Parking");
276     }
277 
278     // update mount status
279     getCommandString(PortFD, OSStat, statusCommand);       // returns a string containg controller status
280     if (OSStat[15] != '0')
281     {
282         updateMountStatus(OSStat[15]);              // error
283     }
284     if (strcmp(OSStat, OldOSStat) != 0)             // if status changed
285     {
286         handleStatusChange();
287         snprintf(OldOSStat, sizeof(OldOSStat), "%s", OSStat);
288     }
289 
290     NewRaDec(currentRA, currentDEC);
291 
292     return true;
293 }
294 
295 /*
296  * Use OSStat to detect status change - handle each byte separately
297  * called by ReadScopeStatus()
298  */
handleStatusChange(void)299 void LX200_TeenAstro::handleStatusChange(void)
300 {
301     LOGF_DEBUG ("Status Change: %s", OSStat);
302 
303     if (OSStat[0] != OldOSStat[0])
304     {
305         if (OSStat[0] == '0')
306         {
307             TrackState = SCOPE_IDLE;
308         }
309         else if (OSStat[0] == '1')
310         {
311             TrackState = SCOPE_TRACKING;
312         }
313         else if (OSStat[0] == '2' || OSStat[0] == '3' )
314         {
315             TrackState = SCOPE_SLEWING;
316         }
317     }
318 
319 
320     // Byte 2 is park status
321     if (OSStat[2] != OldOSStat[2])
322     {
323         if (OSStat[2] == 'P')
324         {
325             SetParked(true);            // defaults to TrackState=SCOPE_PARKED
326         }
327         else
328         {
329             SetParked(false);
330             //            SetTrackEnabled(false);     //disable since TeenAstro enables it by default
331         }
332     }
333     // Byte 13 is pier side
334     if (OSStat[13] != OldOSStat[13])
335     {
336         setPierSide(OSStat[13] == 'W' ? INDI::Telescope::PIER_WEST : INDI::Telescope::PIER_EAST);
337     }
338     // Byte 15 is the error status
339     if (OSStat[15] != OldOSStat[15])
340     {
341         updateMountStatus(OSStat[15]);
342     }
343 }
344 
345 /*
346  * Mount Error status
347  * 0:ERR_NONE,  1: ERR_MOTOR_FAULT, 2: ERR_ALT, 3: ERR_LIMIT_SENSE
348  * 4: ERR_AXIS2,5: ERR_AZM, 6: ERR_UNDER_POLE, 7: ERR_MERIDIAN, 8: ERR_SYNC
349  */
updateMountStatus(char status)350 void LX200_TeenAstro::updateMountStatus(char status)
351 {
352     static const char *errCodes[9] = {"ERR_NONE",  "ERR_MOTOR_FAULT", "ERR_ALT", "ERR_LIMIT_SENSE",
353                                       "ERR_AXIS2", "ERR_AZM", "ERR_UNDER_POLE", "ERR_MERIDIAN", "ERR_SYNC"
354                                      };
355 
356     if (status < '0' || status > '9')
357     {
358         return;
359     }
360     if (status == '0')
361     {
362         ErrorStatusTP.s = IPS_OK;
363     }
364     else
365     {
366         ErrorStatusTP.s = IPS_ALERT;
367         TrackState = SCOPE_IDLE;     // Tell Ekos mount is not tracking anymore
368     }
369     IUSaveText(&ErrorStatusT[0], errCodes[status - '0']);
370     IDSetText(&ErrorStatusTP, nullptr);
371 }
372 
373 /*
374  *  Goto target
375  *  Use standard lx200driver command (:Sr   #)
376  *  Set state to slewing
377  */
Goto(double r,double d)378 bool LX200_TeenAstro::Goto(double r, double d)
379 {
380     targetRA  = r;
381     targetDEC = d;
382     char RAStr[64] = {0}, DecStr[64] = {0};
383 
384     fs_sexa(RAStr, targetRA, 2, 3600);
385     fs_sexa(DecStr, targetDEC, 2, 3600);
386 
387     // If moving, let's stop it first.
388     if (EqNP.s == IPS_BUSY)
389     {
390         if (!isSimulation() && abortSlew(PortFD) < 0)
391         {
392             AbortSP.s = IPS_ALERT;
393             IDSetSwitch(&AbortSP, "Abort slew failed.");
394             return false;
395         }
396 
397         AbortSP.s = IPS_OK;
398         EqNP.s    = IPS_IDLE;
399         IDSetSwitch(&AbortSP, "Slew aborted.");
400         IDSetNumber(&EqNP, nullptr);
401 
402         // sleep for 100 mseconds
403         usleep(100000);
404     }
405 
406     if (!isSimulation())
407     {
408         if (setObjectRA(PortFD, targetRA) < 0 || (setObjectDEC(PortFD, targetDEC)) < 0)  // standard LX200 command
409         {
410             EqNP.s = IPS_ALERT;
411             IDSetNumber(&EqNP, "Error setting RA/DEC.");
412             return false;
413         }
414 
415         int err = 0;
416 
417         /* Slew reads the '0', that is not the end of the slew */
418         if ((err = Slew(PortFD)))
419         {
420             EqNP.s = IPS_ALERT;
421             IDSetNumber(&EqNP, "Error Slewing to JNow RA %s - DEC %s\n", RAStr, DecStr);
422             slewError(err);
423             return false;
424         }
425     }
426 
427     TrackState = SCOPE_SLEWING;
428     EqNP.s     = IPS_BUSY;
429 
430     LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
431     return true;
432 }
433 
isSlewComplete()434 bool LX200_TeenAstro::isSlewComplete()
435 {
436     const double dx = targetRA - currentRA;
437     const double dy = targetDEC - currentDEC;
438     return fabs(dx) <= (SlewAccuracyN[0].value / (900.0)) && fabs(dy) <= (SlewAccuracyN[1].value / 60.0);
439 }
440 
SetTrackMode(uint8_t mode)441 bool LX200_TeenAstro::SetTrackMode(uint8_t mode)
442 {
443     if (isSimulation())
444         return true;
445 
446     bool rc = (selectTrackingMode(PortFD, mode) == 0);
447 
448     return rc;
449 }
450 
451 /*
452  * Sync - synchronizes the telescope with its currently selected database object coordinates
453  */
Sync(double ra,double dec)454 bool LX200_TeenAstro::Sync(double ra, double dec)
455 {
456     char syncString[256] = {0};
457 
458     // goto target
459     if (!isSimulation() && (setObjectRA(PortFD, ra) < 0 || (setObjectDEC(PortFD, dec)) < 0))
460     {
461         EqNP.s = IPS_ALERT;
462         IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
463         return false;
464     }
465 
466     // Use the parent Sync() function (lx200driver.cpp)
467     if (!isSimulation() && ::Sync(PortFD, syncString) < 0)
468     {
469         EqNP.s = IPS_ALERT;
470         IDSetNumber(&EqNP, "Synchronization failed.");
471         return false;
472     }
473 
474     currentRA  = ra;
475     currentDEC = dec;
476 
477     LOG_INFO("Synchronization successful.");
478     EqNP.s     = IPS_OK;
479     NewRaDec(currentRA, currentDEC);
480 
481     return true;
482 }
483 
484 
485 
486 //======================== Parking =======================
SetCurrentPark()487 bool LX200_TeenAstro::SetCurrentPark()
488 {
489     char response[RB_MAX_LEN];
490 
491     if (isSimulation())
492     {
493         LOG_DEBUG("SetCurrentPark: CMD <:hQ>");
494         return true;
495     }
496 
497     if (getCommandString(PortFD, response, ":hQ#") < 0)
498     {
499         LOGF_WARN("===CMD==> Set Park Pos %s", response);
500         return false;
501     }
502     SetAxis1Park(currentRA);
503     SetAxis2Park(currentDEC);
504     LOG_WARN("Park Value set to current postion");
505     return true;
506 }
507 
UnPark()508 bool LX200_TeenAstro::UnPark()
509 {
510     char response[RB_MAX_LEN];
511 
512     if (isSimulation())
513     {
514         LOG_DEBUG("UnPark: CMD <:hR>");
515         TrackState = SCOPE_IDLE;
516         EqNP.s    = IPS_OK;
517         return true;
518     }
519     if (getCommandString(PortFD, response, ":hR#") < 0)
520     {
521         return false;
522     }
523     SetParked(false);
524 
525     return true;
526 }
527 
Park()528 bool LX200_TeenAstro::Park()
529 {
530     if (isSimulation())
531     {
532         LOG_DEBUG("SlewToPark: CMD <:hP>");
533         TrackState = SCOPE_PARKED;
534         EqNP.s    = IPS_OK;
535         return true;
536     }
537 
538     // If scope is moving, let's stop it first.
539     if (EqNP.s == IPS_BUSY)
540     {
541         if (abortSlew(PortFD) < 0)
542         {
543             Telescope::AbortSP.s = IPS_ALERT;
544             IDSetSwitch(&(Telescope::AbortSP), "Abort slew failed.");
545             return false;
546         }
547         Telescope::AbortSP.s = IPS_OK;
548         EqNP.s    = IPS_IDLE;
549         IDSetSwitch(&(Telescope::AbortSP), "Slew aborted.");
550         IDSetNumber(&EqNP, nullptr);
551 
552         if (MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY)
553         {
554             MovementNSSP.s = IPS_IDLE;
555             MovementWESP.s = IPS_IDLE;
556             EqNP.s = IPS_IDLE;
557             IUResetSwitch(&MovementNSSP);
558             IUResetSwitch(&MovementWESP);
559 
560             IDSetSwitch(&MovementNSSP, nullptr);
561             IDSetSwitch(&MovementWESP, nullptr);
562         }
563     }
564     if (slewToPark(PortFD) < 0)  // slewToPark is a macro (lx200driver.h)
565     {
566         ParkSP.s = IPS_ALERT;
567         IDSetSwitch(&ParkSP, "Parking Failed.");
568         return false;
569     }
570     ParkSP.s   = IPS_BUSY;
571     TrackState = SCOPE_PARKING;
572     LOG_INFO("Parking is in progress...");
573 
574     return true;
575 }
576 
577 
578 /*
579  *  updateLocation: not used - use hand controller to update
580  */
updateLocation(double latitude,double longitude,double elevation)581 bool LX200_TeenAstro::updateLocation(double latitude, double longitude, double elevation)
582 {
583     INDI_UNUSED(latitude);
584     INDI_UNUSED(longitude);
585     INDI_UNUSED(elevation);
586 
587     return true;
588 }
589 
590 /*
591  *  getBasicData: standard LX200 commands
592  */
getBasicData()593 void LX200_TeenAstro::getBasicData()
594 {
595     int currentSiteIndex, slewRateIndex;
596 
597     if (!isSimulation())
598     {
599         checkLX200EquatorialFormat(PortFD);
600         char buffer[128];
601         getVersionDate(PortFD, buffer);
602         IUSaveText(&VersionT[0], buffer);
603         getVersionTime(PortFD, buffer);
604         IUSaveText(&VersionT[1], buffer);
605         getVersionNumber(PortFD, buffer);
606         statusCommand = ":GXI#";
607         guideSpeedCommand = ":SXR0:%s#";
608         IUSaveText(&VersionT[2], buffer);
609         getProductName(PortFD, buffer);
610         IUSaveText(&VersionT[3], buffer);
611 
612         IDSetText(&VersionTP, nullptr);
613         SiteNameT[0].text = new char[64];
614         sendScopeTime();
615 
616         if (getSiteIndex(&currentSiteIndex))
617         {
618             SiteS[currentSiteIndex].s = ISS_ON;
619             currentSiteNum = currentSiteIndex + 1;
620             LOGF_INFO("Site number %d", currentSiteNum);
621             getSiteName(PortFD, SiteNameTP.tp[0].text, currentSiteNum);
622             SiteNameTP.s = IPS_OK;
623             SiteSP.s = IPS_OK;
624             IDSetText(&SiteNameTP, nullptr);
625             IDSetSwitch(&SiteSP, nullptr);
626             getLocation();                  // read site from TeenAstro
627         }
628         else
629         {
630             LOG_ERROR("Error reading current site number");
631         }
632 
633         // Get initial state and set switches
634         for (unsigned i = 0; i < sizeof(OldOSStat); i++)
635             OldOSStat[i] = 'x';                         // reset old OS stat to force re-evaluation
636         getCommandString(PortFD, OSStat, statusCommand);       // returns a string containing controller status
637         handleStatusChange();
638         LOGF_INFO("Initial Status: %s", OSStat);
639 
640         // get current slew rate
641         if (getSlewRate(&slewRateIndex))
642         {
643             LOGF_INFO("current slew rate : %d", slewRateIndex);
644             SlewRateS[slewRateIndex].s = ISS_ON;
645             SlewRateSP.s = IPS_OK;
646             IDSetSwitch(&SlewRateSP, nullptr);
647         }
648         else
649         {
650             LOG_ERROR("Error reading current slew rate");
651         }
652 
653         // Turn off tracking. (too much interaction with telescope.cpp if we try to keep the mount's current track state)
654         if (TrackState != SCOPE_TRACKING)
655         {
656             SetTrackEnabled(false);
657         }
658 
659 
660         if (InitPark())
661         {
662             // If loading parking data is successful, we just set the default parking values.
663             LOG_INFO("=============== Parkdata loaded");
664         }
665         else
666         {
667             // Otherwise, we set all parking data to default in case no parking data is found.
668             LOG_INFO("=============== Parkdata Load Failed");
669         }
670     }
671 }
672 
673 /*
674  * ISNewNumber: callback from user interface
675  *
676  * when user has entered a number, handle it to store corresponding driver parameter
677  *
678  */
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)679 bool LX200_TeenAstro::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
680 {
681     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
682     {
683         if (!strcmp(name, SlewAccuracyNP.name))
684         {
685             if (IUUpdateNumber(&SlewAccuracyNP, values, names, n) < 0)
686                 return false;
687 
688             SlewAccuracyNP.s = IPS_OK;
689 
690             if (SlewAccuracyN[0].value < 3 || SlewAccuracyN[1].value < 3)
691                 IDSetNumber(&SlewAccuracyNP, "Warning: Setting the slew accuracy too low may result in a dead lock");
692 
693             IDSetNumber(&SlewAccuracyNP, nullptr);
694             return true;
695         }
696 
697         // GUIDE process Guider properties.
698         processGuiderProperties(name, values, names, n);
699     }
700 
701     return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
702 }
703 
704 
705 
706 /*
707  * ISNewSwitch: callback from user interface
708  *
709  * when user has entered a switch, handle it to store corresponding driver parameter
710  *
711  */
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)712 bool LX200_TeenAstro::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
713 {
714 
715     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
716     {
717         // Slew button speed
718         if (!strcmp(name, SlewRateSP.name))
719         {
720             IUUpdateSwitch(&SlewRateSP, states, names, n);
721             int slewRate = IUFindOnSwitchIndex(&SlewRateSP);
722 
723             if (!selectSlewRate(slewRate))
724             {
725                 LOGF_ERROR("Error setting move to rate %d.", slewRate);
726                 return false;
727             }
728 
729             SlewRateSP.s = IPS_OK;
730             IDSetSwitch(&SlewRateSP, nullptr);
731             return true;
732         }
733 
734         if (!strcmp(name, GuideRateSP.name))
735         {
736             IUUpdateSwitch(&GuideRateSP, states, names, n);
737             int index = IUFindOnSwitchIndex(&GuideRateSP);
738             GuideRateSP.s = IPS_OK;
739             SetGuideRate(index);
740             IDSetSwitch(&GuideRateSP, nullptr);
741         }
742         // Sites
743         if (!strcmp(name, SiteSP.name))
744         {
745             if (IUUpdateSwitch(&SiteSP, states, names, n) < 0)
746                 return false;
747 
748             currentSiteNum = IUFindOnSwitchIndex(&SiteSP) + 1;
749             LOGF_DEBUG("currentSiteNum: %d", currentSiteNum);
750             if (!isSimulation() && (!setSite(currentSiteNum)))
751             {
752                 SiteSP.s = IPS_ALERT;
753                 IDSetSwitch(&SiteSP, "Error selecting sites.");
754                 return false;
755             }
756 
757             if (isSimulation())
758                 IUSaveText(&SiteNameTP.tp[0], "Sample Site");
759             else
760             {
761                 LOGF_DEBUG("Site name %s", SiteNameTP.tp[0].text);
762                 getSiteName(PortFD, SiteNameTP.tp[0].text, currentSiteNum);
763             }
764 
765             // When user selects a new site, read it from TeenAstro
766             getLocation();
767 
768             LOGF_INFO("Setting site number %d", currentSiteNum);
769             SiteS[currentSiteNum - 1].s = ISS_ON;
770             SiteNameTP.s = IPS_OK;
771             SiteSP.s = IPS_OK;
772 
773             IDSetText(&SiteNameTP, nullptr);
774             IDSetSwitch(&SiteSP, nullptr);
775 
776             return false;
777         }
778     }
779 
780     return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
781 }
782 
ISNewText(const char * dev,const char * name,char * texts[],char * names[],int n)783 bool LX200_TeenAstro::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
784 {
785     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
786     {
787         if (!strcmp(name, SiteNameTP.name))
788         {
789             if (!isSimulation() && setSiteName(PortFD, texts[0], currentSiteNum) < 0)
790             {
791                 SiteNameTP.s = IPS_ALERT;
792                 IDSetText(&SiteNameTP, nullptr);
793                 return false;
794             }
795 
796             SiteNameTP.s = IPS_OK;
797             IText *tp    = IUFindText(&SiteNameTP, names[0]);
798             IUSaveText(tp, texts[0]);
799             IDSetText(&SiteNameTP, nullptr);
800             return true;
801         }
802     }
803 
804     return INDI::Telescope::ISNewText(dev, name, texts, names, n);
805 }
806 
807 
808 
809 /*
810  * getLocalDate() to sendScopeTime() are copied from lx200telescope.cpp
811  */
getLocalDate(char * dateString)812 bool LX200_TeenAstro::getLocalDate(char *dateString)
813 {
814     if (isSimulation())
815     {
816         time_t now = time(nullptr);
817         strftime(dateString, MAXINDINAME, "%F", localtime(&now));
818     }
819     else
820     {
821         getCalendarDate(PortFD, dateString);
822     }
823 
824     return true;
825 }
826 
setLocalDate(uint8_t days,uint8_t months,uint16_t years)827 bool LX200_TeenAstro::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
828 {
829     return (setCalenderDate(PortFD, days, months, years) == 0);
830 }
831 
getLocalTime(char * timeString)832 bool LX200_TeenAstro::getLocalTime(char *timeString)
833 {
834     if (isSimulation())
835     {
836         time_t now = time(nullptr);
837         strftime(timeString, MAXINDINAME, "%T", localtime(&now));
838     }
839     else
840     {
841         double ctime = 0;
842         int h, m, s;
843         getLocalTime24(PortFD, &ctime);
844         getSexComponents(ctime, &h, &m, &s);
845         snprintf(timeString, MAXINDINAME, "%02d:%02d:%02d", h, m, s);
846     }
847 
848     return true;
849 }
850 
851 
getUTFOffset(double * offset)852 bool LX200_TeenAstro::getUTFOffset(double *offset)
853 {
854     if (isSimulation())
855     {
856         *offset = 3;
857         return true;
858     }
859 
860     int lx200_utc_offset = 0;
861     getUTCOffset(PortFD, &lx200_utc_offset);
862 
863     // LX200 TimeT Offset is defined at the number of hours added to LOCAL TIME to get TimeT. This is contrary to the normal definition.
864     *offset = lx200_utc_offset * -1;
865     return true;
866 }
867 
sendScopeTime()868 bool LX200_TeenAstro::sendScopeTime()
869 {
870     char cdate[MAXINDINAME] = {0};
871     char ctime[MAXINDINAME] = {0};
872     struct tm ltm;
873     struct tm utm;
874     time_t time_epoch;
875 
876     double offset = 0;
877 
878     if (getUTFOffset(&offset))
879     {
880         char utcStr[8] = {0};
881         snprintf(utcStr, sizeof(utcStr), "%.2f", offset);
882         IUSaveText(&TimeT[1], utcStr);
883     }
884     else
885     {
886         LOG_WARN("Could not obtain UTC offset from mount!");
887         return false;
888     }
889 
890     if (getLocalTime(ctime) == false)
891     {
892         LOG_WARN("Could not obtain local time from mount!");
893         return false;
894     }
895 
896     if (getLocalDate(cdate) == false)
897     {
898         LOG_WARN("Could not obtain local date from mount!");
899         return false;
900     }
901 
902     // To ISO 8601 format in LOCAL TIME!
903     char datetime[MAXINDINAME] = {0};
904     snprintf(datetime, MAXINDINAME, "%sT%s", cdate, ctime);
905 
906     // Now that date+time are combined, let's get tm representation of it.
907     if (strptime(datetime, "%FT%T", &ltm) == nullptr)
908     {
909         LOGF_WARN("Could not process mount date and time: %s", datetime);
910         return false;
911     }
912 
913     // Get local time epoch in UNIX seconds
914     time_epoch = mktime(&ltm);
915 
916     // LOCAL to UTC by subtracting offset.
917     time_epoch -= static_cast<int>(offset * 3600.0);
918 
919     // Get UTC (we're using localtime_r, but since we shifted time_epoch above by UTCOffset, we should be getting the real UTC time)
920     localtime_r(&time_epoch, &utm);
921 
922     // Format it into the final UTC ISO 8601
923     strftime(cdate, MAXINDINAME, "%Y-%m-%dT%H:%M:%S", &utm);
924     IUSaveText(&TimeT[0], cdate);
925 
926     LOGF_DEBUG("Mount controller UTC Time: %s", TimeT[0].text);
927     LOGF_DEBUG("Mount controller UTC Offset: %s", TimeT[1].text);
928 
929     // Let's send everything to the client
930     TimeTP.s = IPS_OK;
931     IDSetText(&TimeTP, nullptr);
932 
933     return true;
934 }
935 /*
936  * Called by INDI - not sure when
937  */
938 
sendScopeLocation()939 bool LX200_TeenAstro::sendScopeLocation()
940 {
941     int dd = 0, mm = 0, elev = 0;
942     double ssf = 0.0;
943 
944     LOG_INFO("Send location");
945     return true;
946 
947     if (isSimulation())
948     {
949         LocationNP.np[LOCATION_LATITUDE].value = 29.5;  // Kuwait - Jasem's home!
950         LocationNP.np[LOCATION_LONGITUDE].value = 48.0;
951         LocationNP.np[LOCATION_ELEVATION].value = 10;
952         LocationNP.s           = IPS_OK;
953         IDSetNumber(&LocationNP, nullptr);
954         return true;
955     }
956 
957     if (getSiteLatitude(PortFD, &dd, &mm, &ssf) < 0)
958     {
959         LOG_WARN("Failed to get site latitude from device.");
960         return false;
961     }
962     else
963     {
964         if (dd > 0)
965             LocationNP.np[LOCATION_LATITUDE].value = dd + mm / 60.0;
966         else
967             LocationNP.np[LOCATION_LATITUDE].value = dd - mm / 60.0;
968     }
969     if (getSiteLongitude(PortFD, &dd, &mm, &ssf) < 0)
970     {
971         LOG_WARN("Failed to get site longitude from device.");
972         return false;
973     }
974     else
975     {
976         if (dd > 0)
977             LocationNP.np[LOCATION_LONGITUDE].value = 360.0 - (dd + mm / 60.0);
978         else
979             LocationNP.np[LOCATION_LONGITUDE].value = (dd - mm / 60.0) * -1.0;
980     }
981     LOGF_DEBUG("Mount Controller Latitude: %g Longitude: %g", LocationN[LOCATION_LATITUDE].value,
982                LocationN[LOCATION_LONGITUDE].value);
983 
984     if (getSiteElevation(&elev))
985     {
986         LocationNP.np[LOCATION_ELEVATION].value = elev;
987     }
988     else
989     {
990         LOG_ERROR("Error getting site elevation");
991     }
992     IDSetNumber(&LocationNP, nullptr);
993     saveConfig(true, "GEOGRAPHIC_COORD");
994 
995     return true;
996 }
997 
998 /*
999  * getSiteElevation - not in Meade standard
1000  */
getSiteElevation(int * elevationP)1001 bool LX200_TeenAstro::getSiteElevation(int *elevationP)
1002 {
1003     if (getCommandInt(PortFD, elevationP, ":Ge#") != 0)
1004         return false;
1005     return true;
1006 }
1007 
1008 /*
1009  * getSiteIndex - not in Meade standard
1010  */
getSiteIndex(int * ndxP)1011 bool LX200_TeenAstro::getSiteIndex(int *ndxP)
1012 {
1013     if (getCommandInt(PortFD, ndxP, ":W?#") != 0)
1014         return false;
1015     return true;
1016 }
1017 
1018 
1019 /*
1020  * getSlewRate - not in Meade standard
1021  */
getSlewRate(int * ndxP)1022 bool LX200_TeenAstro::getSlewRate(int *ndxP)
1023 {
1024     if (getCommandInt(PortFD, ndxP, ":GXRD#") != 0)
1025         return false;
1026     return true;
1027 }
1028 
1029 /*
1030  * setSite - not in Meade standard
1031  * argument is the site number (1 to 4)
1032  * TeenAstro handles numbers 0 to 3
1033  */
setSite(int num)1034 bool LX200_TeenAstro::setSite(int num)
1035 {
1036     char buf[10];
1037     snprintf (buf, sizeof(buf), ":W%d#", num - 1);
1038     sendCommand(buf);
1039     return true;
1040 }
1041 
1042 /*
1043  * setSiteElevation - not in Meade standard
1044  */
setSiteElevation(double elevation)1045 bool LX200_TeenAstro::setSiteElevation(double elevation)
1046 {
1047     char buf[20];
1048     snprintf (buf, sizeof(buf), ":Se%+4d#", static_cast<int>(elevation));
1049     sendCommand(buf);
1050     return true;
1051 }
1052 
1053 
1054 /*
1055  * getLocation
1056  * retrieve from scope, set into user interface
1057  */
getLocation()1058 bool LX200_TeenAstro::getLocation()
1059 {
1060     int dd = 0, mm = 0, elev = 0;
1061     double ssf = 0.0;
1062 
1063     if (getSiteLatitude(PortFD, &dd, &mm, &ssf) < 0)
1064     {
1065         LOG_WARN("Failed to get site latitude from device.");
1066         return false;
1067     }
1068     else
1069     {
1070         if (dd > 0)
1071             LocationNP.np[LOCATION_LATITUDE].value = dd + mm / 60.0;
1072         else
1073             LocationNP.np[LOCATION_LATITUDE].value = dd - mm / 60.0;
1074     }
1075 
1076     if (getSiteLongitude(PortFD, &dd, &mm, &ssf) < 0)
1077     {
1078         LOG_WARN("Failed to get site longitude from device.");
1079         return false;
1080     }
1081     else
1082     {
1083         if (dd > 0)
1084             LocationNP.np[LOCATION_LONGITUDE].value = 360.0 - (dd + mm / 60.0);
1085         else
1086             LocationNP.np[LOCATION_LONGITUDE].value = (dd - mm / 60.0) * -1.0;
1087     }
1088     if (getSiteElevation(&elev))
1089     {
1090         LocationNP.np[LOCATION_ELEVATION].value = elev;
1091     }
1092     else
1093     {
1094         LOG_ERROR("Error getting site elevation");
1095     }
1096 
1097     IDSetNumber(&LocationNP, nullptr);
1098     return true;
1099 }
1100 
1101 /*
1102  * Set Guide Rate -  :SXR0:dddd# (v1.2 and above) where ddd is guide rate * 100
1103  */
SetGuideRate(int index)1104 bool LX200_TeenAstro::SetGuideRate(int index)
1105 {
1106     char cmdString[20];
1107 
1108     snprintf (cmdString, sizeof(cmdString), guideSpeedCommand, GuideRateS[index].name);  // GuideRateS is {25,50,100}
1109     sendCommand(cmdString);
1110 
1111     return true;
1112 }
1113 
1114 
1115 /*
1116  *  Guidexxx - use SendPulseCmd function from lx200driver.cpp
1117  */
GuideNorth(uint32_t ms)1118 IPState LX200_TeenAstro::GuideNorth(uint32_t ms)
1119 {
1120     SendPulseCmd(LX200_NORTH, ms);
1121     return IPS_OK;
1122 }
1123 
GuideSouth(uint32_t ms)1124 IPState LX200_TeenAstro::GuideSouth(uint32_t ms)
1125 {
1126     SendPulseCmd(LX200_SOUTH, ms);
1127     return IPS_OK;
1128 }
1129 
GuideEast(uint32_t ms)1130 IPState LX200_TeenAstro::GuideEast(uint32_t ms)
1131 {
1132     SendPulseCmd(LX200_EAST, ms);
1133     return IPS_OK;
1134 }
1135 
GuideWest(uint32_t ms)1136 IPState LX200_TeenAstro::GuideWest(uint32_t ms)
1137 {
1138     SendPulseCmd(LX200_WEST, ms);
1139     return IPS_OK;
1140 }
1141 
SendPulseCmd(int8_t direction,uint32_t duration_msec)1142 void LX200_TeenAstro::SendPulseCmd(int8_t direction, uint32_t duration_msec)
1143 {
1144     ::SendPulseCmd(PortFD, direction, duration_msec);
1145 }
1146 
1147 
1148 /*
1149  * Abort() calls standard lx200driver command (:Q#)
1150  */
Abort()1151 bool LX200_TeenAstro::Abort()
1152 {
1153     if (!isSimulation() && abortSlew(PortFD) < 0)
1154     {
1155         LOG_ERROR("Failed to abort slew.");
1156         return false;
1157     }
1158 
1159     EqNP.s     = IPS_IDLE;
1160     TrackState = SCOPE_IDLE;
1161     IDSetNumber(&EqNP, nullptr);
1162 
1163     LOG_INFO("Slew aborted.");
1164     return true;
1165 }
1166 
1167 /*
1168  * MoveNS and MoveWE call lx200telescope functions
1169  */
MoveNS(INDI_DIR_NS dirns,TelescopeMotionCommand cmd)1170 bool LX200_TeenAstro::MoveNS(INDI_DIR_NS dirns, TelescopeMotionCommand cmd)
1171 {
1172     if (dirns == DIRECTION_NORTH)
1173         return Move(LX200_NORTH, cmd);
1174     else
1175         return Move(LX200_SOUTH, cmd);
1176 }
MoveWE(INDI_DIR_WE dirwe,TelescopeMotionCommand cmd)1177 bool LX200_TeenAstro::MoveWE(INDI_DIR_WE dirwe, TelescopeMotionCommand cmd)
1178 {
1179     if (dirwe == DIRECTION_WEST)
1180         return Move(LX200_WEST, cmd);
1181     else
1182         return Move(LX200_EAST, cmd);
1183 }
1184 
1185 /*
1186  * Single function for move - use LX200 functions
1187  */
Move(TDirection dir,TelescopeMotionCommand cmd)1188 bool LX200_TeenAstro::Move(TDirection dir, TelescopeMotionCommand cmd)
1189 {
1190     switch (cmd)
1191     {
1192         case MOTION_START:
1193             MoveTo(PortFD, dir);
1194             break;
1195         case MOTION_STOP:
1196             HaltMovement(PortFD, dir);
1197             break;
1198     }
1199     return true;
1200 }
1201 
1202 /*
1203  * Override default config saving
1204  */
saveConfigItems(FILE * fp)1205 bool LX200_TeenAstro::saveConfigItems(FILE *fp)
1206 {
1207     IUSaveConfigSwitch(fp, &SlewRateSP);
1208     IUSaveConfigSwitch(fp, &GuideRateSP);
1209 
1210     return INDI::Telescope::saveConfigItems(fp);
1211 }
1212 
1213 
1214 /*
1215  * Mount simulation
1216  */
mountSim()1217 void LX200_TeenAstro::mountSim()
1218 {
1219     static struct timeval ltv;
1220     struct timeval tv;
1221     double dt, da, dx;
1222     int nlocked;
1223 
1224     /* update elapsed time since last poll, don't presume exactly POLLMS */
1225     gettimeofday(&tv, nullptr);
1226 
1227     if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1228         ltv = tv;
1229 
1230     dt  = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1231     ltv = tv;
1232     da  = SLEWRATE * dt;
1233 
1234     /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */
1235     switch (TrackState)
1236     {
1237         case SCOPE_TRACKING:
1238             /* RA moves at sidereal, Dec stands still */
1239             currentRA += (SIDRATE * dt / 15.);
1240             break;
1241 
1242         case SCOPE_SLEWING:
1243             /* slewing - nail it when both within one pulse @ SLEWRATE */
1244             nlocked = 0;
1245 
1246             dx = targetRA - currentRA;
1247 
1248             if (fabs(dx) <= da)
1249             {
1250                 currentRA = targetRA;
1251                 nlocked++;
1252             }
1253             else if (dx > 0)
1254                 currentRA += da / 15.;
1255             else
1256                 currentRA -= da / 15.;
1257 
1258             dx = targetDEC - currentDEC;
1259             if (fabs(dx) <= da)
1260             {
1261                 currentDEC = targetDEC;
1262                 nlocked++;
1263             }
1264             else if (dx > 0)
1265                 currentDEC += da;
1266             else
1267                 currentDEC -= da;
1268 
1269             if (nlocked == 2)
1270             {
1271                 TrackState = SCOPE_TRACKING;
1272             }
1273 
1274             break;
1275 
1276         default:
1277             break;
1278     }
1279 
1280     NewRaDec(currentRA, currentDEC);
1281 }
1282 
slewError(int slewCode)1283 void LX200_TeenAstro::slewError(int slewCode)
1284 {
1285     EqNP.s = IPS_ALERT;
1286 
1287     if (slewCode == 1)
1288         IDSetNumber(&EqNP, "Object below horizon.");
1289     else if (slewCode == 2)
1290         IDSetNumber(&EqNP, "Object below the minimum elevation limit.");
1291     else
1292         IDSetNumber(&EqNP, "Slew failed.");
1293 }
1294 /*
1295  *  Enable or disable sidereal tracking (events handled by inditelescope)
1296  */
SetTrackEnabled(bool enabled)1297 bool LX200_TeenAstro::SetTrackEnabled(bool enabled)
1298 {
1299     LOGF_INFO("TrackEnable %d", enabled);
1300 
1301     if (enabled)
1302     {
1303         sendCommand(":Te#");
1304     }
1305     else
1306     {
1307         sendCommand(":Td#");
1308     }
1309     return true;
1310 }
1311 
1312 /*
1313  * selectSlewrate - select among TeenAstro's 5 predefined rates
1314  */
selectSlewRate(int index)1315 bool LX200_TeenAstro::selectSlewRate(int index)
1316 {
1317     char cmd[20];
1318 
1319     snprintf(cmd, sizeof(cmd), ":SXRD:%d#", index);
1320     sendCommand(cmd);
1321     return true;
1322 }
1323 
1324 
1325 /*
1326  * Used instead of getCommandString when response is not terminated with '#'
1327  *
1328  */
sendCommand(const char * cmd)1329 void LX200_TeenAstro::sendCommand(const char *cmd)
1330 {
1331     char resp;
1332     int nbytes_read;
1333     std::unique_lock<std::mutex> guard(lx200CommsLock);
1334     LOGF_INFO("sendCommand %s", cmd);
1335     int rc = write(PortFD, cmd, strlen(cmd));
1336     rc = tty_read(PortFD, &resp, 1, ONSTEP_TIMEOUT, &nbytes_read);
1337     INDI_UNUSED(rc);
1338 }
1339 
1340 
1341 
1342