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, ¤tRA) != 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, ¤tRA) < 0 || getLX200DEC(PortFD, ¤tDEC) < 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(¤tSiteIndex))
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", <m) == 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(<m);
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