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, ¤tRA);
288 getLX200DEC(PortFD, ¤tDEC);
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, ¤tRA) < 0 || getLX200DEC(PortFD, ¤tDEC) < 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, ¤tAz) < 0 || getLX200Alt(PortFD, ¤tAlt) < 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, <m, 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