Instrument Neutral Distributed Interface INDI  2.0.2
lx200ap_v2.cpp
Go to the documentation of this file.
1 /*
2  Astro-Physics INDI driver
3 
4  Copyright (C) 2014 Jasem Mutlaq, Mike Fulbright
5  Copyright (C) 2020 indilib.org, by Markus Wildi
6  Copyright (C) 2022 Hy Murveit
7 
8  Based on INDI Astrophysics Driver by Markus Wildi
9 
10  This library is free software; you can redistribute it and/or
11  modify it under the terms of the GNU Lesser General Public
12  License as published by the Free Software Foundation; either
13  version 2.1 of the License, or (at your option) any later version.
14 
15  This library is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  Lesser General Public License for more details.
19 
20  You should have received a copy of the GNU Lesser General Public
21  License along with this library; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24 /*
25 2020-08-07, ToDo --wildi
26 AP commands not yet implemented for revision >= G
27 
28 Sets the centering rate for the N-S-E-W buttons to xxx Rcxxx#
29 Default command for an equatorial fork mount, which eliminates the meridian flip :FM#
30 Default command for A German equatorial mount that includes the meridian flip :EM#
31 Horizon check during slewing functions :ho# and :hq#
32 */
33 
34 
35 /***********************************************************************
36  * This file was copied an modified from lx200ap.cpp in Jan 2022.
37  *
38  * This is an update of the Wildi and Fulbright A-P drivers.
39  * It is currently being tested.
40  * You should not use this unless part of the test group.
41 ***********************************************************************/
42 
43 #include "lx200ap_v2.h"
44 
45 #include "indicom.h"
46 #include "lx200driver.h"
47 #include "lx200apdriver.h"
49 
50 #include <libnova/transform.h>
51 
52 #include <cmath>
53 #include <cstring>
54 #include <unistd.h>
55 #include <termios.h>
56 #include <regex>
57 
58 // PEC Recording values
60 {
63 };
64 
65 // maximum guide pulse request to send to controller
66 #define MAX_LX200AP_PULSE_LEN 999
67 
68 // The workaround for long pulses does't work!
69 // #define DONT_SIMULATE_LONG_PULSES true
70 // This didn't work. The driver simply doesn't send pulse
71 // commands longer than 999ms since CP3 controllers don't support that.
72 
74 {
76  // The 5 means there are 5 slew rates.
79 
80  majorVersion = 0;
81  minorVersion = 0;
82  setVersion(1, 1);
83 }
84 
86 {
87  return "AstroPhysics V2";
88 }
89 
91 {
92  Connection::Interface *activeConnection = getActiveConnection();
93  if (!activeConnection->name().compare("CONNECTION_TCP"))
94  {
95  // When using a tcp connection, the GTOCP4 adds trailing LF to response.
96  // this small hack will get rid of them as they are not expected in the driver. and generated
97  // lot of communication errors.
99  }
100 
101  // If ApInitialize fails, probably have to turn some buttons red. Verify!
102  return LX200Generic::Connect() && ApInitialize();
103 }
104 
106 {
108 
110 
111  IUFillNumber(&HourangleCoordsN[0], "HA", "HA H:M:S", "%10.6m", -24., 24., 0., 0.);
112  IUFillNumber(&HourangleCoordsN[1], "DEC", "Dec D:M:S", "%10.6m", -90.0, 90.0, 0., 0.);
113  IUFillNumberVector(&HourangleCoordsNP, HourangleCoordsN, 2, getDeviceName(), "HOURANGLE_COORD", "Hourangle Coords",
115 
116  IUFillNumber(&HorizontalCoordsN[0], "AZ", "Az D:M:S", "%10.6m", 0., 360., 0., 0.);
117  IUFillNumber(&HorizontalCoordsN[1], "ALT", "Alt D:M:S", "%10.6m", -90., 90., 0., 0.);
119  "Horizontal Coords", MAIN_CONTROL_TAB, IP_RW, 120, IPS_IDLE);
120 
121  // Max rate is 999.99999X for the GTOCP4.
122  // Using :RR998.9999# just to be safe. 15.041067*998.99999 = 15026.02578
123  TrackRateN[AXIS_RA].min = -15026.0258;
124  TrackRateN[AXIS_RA].max = 15026.0258;
125  TrackRateN[AXIS_DE].min = -998.9999;
126  TrackRateN[AXIS_DE].max = 998.9999;
127 
128  // Rates populated in a different routine since they can change after connect:
129  initRateLabels();
130 
131  // Home button for clutch aware mounts with encoders.
132  IUFillSwitch(&HomeAndReSyncS[0], "Home and ReSync", "Home and ReSync", ISS_OFF);
134  "HomeAndReSync", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
135 
136  // Manual-set-mount-to-parked button for recovering from issues.
137  IUFillSwitch(&ManualSetParkedS[0], "MANUAL SET PARKED", "Manual Set Parked", ISS_OFF);
139  "ManualSetParked", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
140 
141  IUFillSwitch(&SwapS[0], "NS", "North/South", ISS_OFF);
142  IUFillSwitch(&SwapS[1], "EW", "East/West", ISS_OFF);
143  IUFillSwitchVector(&SwapSP, SwapS, 2, getDeviceName(), "SWAP", "Swap buttons", MOTION_TAB, IP_RW, ISR_1OFMANY, 0,
144  IPS_IDLE);
145 
146  // guide speed
147  IUFillSwitch(&APGuideSpeedS[0], "0.25", "0.25x", ISS_OFF);
148  IUFillSwitch(&APGuideSpeedS[1], "0.5", "0.50x", ISS_OFF);
149  IUFillSwitch(&APGuideSpeedS[2], "1.0", "1.0x", ISS_ON);
151  0, IPS_IDLE);
152 
153  // Unpark from?
154  // Order should be the same as the ParkPosition enum.
155  IUFillSwitch(&UnparkFromS[0], "Last", "Last Parked--recommended!", ISS_ON);
156  IUFillSwitch(&UnparkFromS[1], "Park1", "Park1", ISS_OFF);
157  IUFillSwitch(&UnparkFromS[2], "Park2", "Park2", ISS_OFF);
158  IUFillSwitch(&UnparkFromS[3], "Park3", "Park3", ISS_OFF);
159  IUFillSwitch(&UnparkFromS[4], "Park4", "Park4", ISS_OFF);
160  IUFillSwitchVector(&UnparkFromSP, UnparkFromS, 5, getDeviceName(), "UNPARK_FROM", "Unpark From?", MAIN_CONTROL_TAB, IP_RW,
161  ISR_1OFMANY, 0, IPS_IDLE);
162 
163  // park presets
164  // Order should be the same as the ParkPosition enum.
165  IUFillSwitch(&ParkToS[0], "Custom", "Custom--not implemented", ISS_OFF);
166  IUFillSwitch(&ParkToS[1], "Park1", "Park1", ISS_OFF);
167  IUFillSwitch(&ParkToS[2], "Park2", "Park2", ISS_OFF);
168  IUFillSwitch(&ParkToS[3], "Park3", "Park3", ISS_ON);
169  IUFillSwitch(&ParkToS[4], "Park4", "Park4", ISS_OFF);
170  IUFillSwitch(&ParkToS[5], "Current", "Current Position--not implemented", ISS_OFF);
172  IPS_IDLE);
173 
174  IUFillText(&VersionT[0], "Version", "Version", "");
175  IUFillTextVector(&VersionTP, VersionT, 1, getDeviceName(), "Firmware", "Firmware", SITE_TAB, IP_RO, 0, IPS_IDLE);
176 
177  // UTC offset
178  IUFillNumber(&APUTCOffsetN[0], "APUTC_OFFSET", "AP UTC offset", "%8.5f", 0.0, 24.0, 0.0, 0.);
179  IUFillNumberVector(&APUTCOffsetNP, APUTCOffsetN, 1, getDeviceName(), "APUTC_OFFSET", "AP UTC offset", SITE_TAB,
180  IP_RW, 60, IPS_OK);
181  // sidereal time, ToDO move define where it belongs to
182  IUFillNumber(&APSiderealTimeN[0], "AP_SIDEREAL_TIME", "AP sidereal time", "%10.6m", 0.0, 24.0, 0.0, 0.0);
183  IUFillNumberVector(&APSiderealTimeNP, APSiderealTimeN, 1, getDeviceName(), "AP_SIDEREAL_TIME", "ap sidereal time", SITE_TAB,
184  IP_RO, 60, IPS_OK);
185 
186  // Worm position
187  IUFillNumber(&APWormPositionN[0], "APWormPosition", "AP Worm Position", "%3.0f", 0, 1000, 1, 0);
188  IUFillNumberVector(&APWormPositionNP, APWormPositionN, 1, getDeviceName(), "APWormPosition", "AP Worm Position",
189  MOTION_TAB, IP_RO, 0, IPS_IDLE);
190 
191  // PEC State
192  IUFillText(&APPECStateT[0], "APPECState", "AP PEC State", "");
193  IUFillTextVector(&APPECStateTP, APPECStateT, 1, getDeviceName(), "APPECState", "AP PEC State",
194  MOTION_TAB, IP_RO, 0, IPS_IDLE);
195 
196  IUFillText(&APMountStatusT[0], "APMountStatus", "AP Mount Status", "");
197  IUFillTextVector(&APMountStatusTP, APMountStatusT, 1, getDeviceName(), "APMountStatus", "AP Mount Status",
198  MOTION_TAB, IP_RO, 0, IPS_IDLE);
199 
200  // PEC Record button.
201  IUFillSwitch(&APPECRecordS[AP_PEC_RECORD_OFF], "APPECRecordOFF", "Off", ISS_ON);
202  IUFillSwitch(&APPECRecordS[AP_PEC_RECORD_ON], "APPECRecordON", "Record", ISS_OFF);
203  IUFillSwitchVector(&APPECRecordSP, APPECRecordS, 2, getDeviceName(), "APPECRecord", "Record PEC", MOTION_TAB,
204  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
205 
206  // Without below, it will not write the ParkData.xml file.
207  // However, ParkData.xml is not used.
208  // SetParkDataType(PARK_AZ_ALT);
209 
210  return true;
211 }
212 
213 void LX200AstroPhysicsV2::initRateLabels()
214 {
215  if (rateTable == AP_RATE_TABLE_DEFAULT) // Legacy, pre P02-01
216  {
217  // Motion speed of axis when pressing NSWE buttons
218  IUFillSwitch(&SlewRateS[0], "1", "Guide", ISS_OFF);
219  IUFillSwitch(&SlewRateS[1], "12", "12x", ISS_OFF);
220  IUFillSwitch(&SlewRateS[2], "64", "64x", ISS_ON);
221  IUFillSwitch(&SlewRateS[3], "600", "600x", ISS_OFF);
222  IUFillSwitch(&SlewRateS[4], "1200", "1200x", ISS_OFF);
223  IUFillSwitchVector(&SlewRateSP, SlewRateS, 5, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB, IP_RW,
224  ISR_1OFMANY, 0, IPS_IDLE);
225 
226  // Slew speed when performing regular GOTO
227  IUFillSwitch(&APSlewSpeedS[0], "600", "600x", ISS_ON);
228  IUFillSwitch(&APSlewSpeedS[1], "900", "900x", ISS_OFF);
229  IUFillSwitch(&APSlewSpeedS[2], "1200", "1200x", ISS_OFF);
231  0, IPS_IDLE);
232  }
233  else
234  {
235  // This is the rate table straight out of the CPx source. First two numbers
236  // are the highest two center/button rates, and the next three numbers are the
237  // three 'goto' rates. There are 4 sets of rates for 4 different types of mounts.
238  std::string standard_rates[4][5] =
239  {
240  { "600", "1200", "600", "900", "1200" },
241  { "500", "900", "400", "650", "900" },
242  { "400", "600", "300", "450", "600" },
243  { "600", "1200", "600", "1000", "1800" }
244  };
245 
246  // The 8 means there are 8 slew/center rates.
249  int i = rateTable;
250  IUFillSwitch(&SlewRateS[0], "0.25", "0.25x", ISS_OFF);
251  IUFillSwitch(&SlewRateS[1], "0.5", "0.5x", ISS_OFF);
252  IUFillSwitch(&SlewRateS[2], "1.0", "1.0x", ISS_OFF);
253  IUFillSwitch(&SlewRateS[3], "12", "12x", ISS_OFF);
254  IUFillSwitch(&SlewRateS[4], "64", "64x", ISS_ON);
255  IUFillSwitch(&SlewRateS[5], "200", "200x", ISS_OFF);
256  IUFillSwitch(&SlewRateS[6], standard_rates[i][0].c_str(), standard_rates[i][0].append("x").c_str(), ISS_OFF);
257  IUFillSwitch(&SlewRateS[7], standard_rates[i][1].c_str(), standard_rates[i][1].append("x").c_str(), ISS_OFF);
258 
259  // Slew speed when performing regular GOTO
260  IUFillSwitch(&APSlewSpeedS[0], standard_rates[i][2].c_str(), standard_rates[i][2].append("x").c_str(), ISS_ON);
261  IUFillSwitch(&APSlewSpeedS[1], standard_rates[i][3].c_str(), standard_rates[i][3].append("x").c_str(), ISS_OFF);
262  IUFillSwitch(&APSlewSpeedS[2], standard_rates[i][4].c_str(), standard_rates[i][4].append("x").c_str(), ISS_OFF);
263  }
264 }
265 
267 {
269 
273 
274  if (isConnected())
275  {
286  }
287  else
288  {
289  LOG_INFO("ISGetProperties: Not Connected");
290  }
291 }
292 
294 {
296 
300 
301  if (isConnected())
302  {
305  /* Motion group */
316  }
317  else
318  {
331  }
332 
333  return true;
334 }
335 
336 bool LX200AstroPhysicsV2::getWormPosition()
337 {
338  int position;
339  if (isSimulation())
340  position = 0;
341  else
342  {
343  int res = getAPWormPosition(PortFD, &position);
344  if (res != TTY_OK)
345  {
346  APWormPositionNP.np[0].value = 0;
348  IDSetNumber(&APWormPositionNP, nullptr);
349  return false;
350  }
351  }
352  APWormPositionNP.np[0].value = position;
354  IDSetNumber(&APWormPositionNP, nullptr);
355  return true;
356 }
357 
358 void LX200AstroPhysicsV2::processMountStatus(const char *statusString)
359 {
360  const char *mountStatusString = apMountStatus(statusString);
361  IUSaveText(&APMountStatusT[0], mountStatusString);
362  IDSetText(&APMountStatusTP, nullptr);
364 }
365 
366 bool LX200AstroPhysicsV2::getPECState(const char *statusString)
367 {
368  int pecState;
369  if (isSimulation())
370  pecState = AP_PEC_OFF;
371  else
372  {
373  if (strlen(statusString) < 10)
374  return false;
375  const char pecChar = statusString[9];
376  switch (pecChar)
377  {
378  case 'O':
379  pecState = AP_PEC_OFF;
380  break;
381  case 'P':
382  pecState = AP_PEC_ON;
383  break;
384  case 'R':
385  pecState = AP_PEC_RECORD;
386  break;
387  case 'E':
388  pecState = AP_PEC_ENCODER;
389  break;
390  default:
391  IUSaveText(&APPECStateT[0], "");
392  IDSetText(&APPECStateTP, nullptr);
394  return false;
395  }
396  }
397  // Set the text status display based on the info from the mount.
398  // Also set the PEC buttons: playback on/off & recording on/off.
399  switch (pecState)
400  {
401  case AP_PEC_OFF:
402  IUSaveText(&APPECStateT[0], "Off");
403 
407  IDSetSwitch(&APPECRecordSP, nullptr);
408 
410 
411  break;
412  case AP_PEC_ON:
413  IUSaveText(&APPECStateT[0], "On");
414 
418  IDSetSwitch(&APPECRecordSP, nullptr);
419 
421 
422  break;
423  case AP_PEC_RECORD:
424  IUSaveText(&APPECStateT[0], "Recording");
425 
429  IDSetSwitch(&APPECRecordSP, nullptr);
430 
432 
433  break;
434  case AP_PEC_ENCODER:
435  IUSaveText(&APPECStateT[0], "Encoder");
436 
440  IDSetSwitch(&APPECRecordSP, nullptr);
441 
443 
444  break;
445  }
446  IDSetText(&APPECStateTP, nullptr);
448 
449  return true;
450 }
451 
452 // The version string should be formatted as VCP4-$MAJOR-$MINOR.
453 // Could be VCP5 as well. For instance: VCP4-P02-12
454 void LX200AstroPhysicsV2::setMajorMinorVersions(char *version)
455 {
456  majorVersion = 0;
457  minorVersion = 0;
458 
459  const std::string v = version;
460  std::regex rgx(".*-(\\w+)-(\\w+)");
461  std::smatch match;
462 
463  if (std::regex_search(v.begin(), v.end(), match, rgx))
464  {
465  std::string major = match.str(1);
466  std::string minor = match.str(2);
467 
468  std::string majorStripped = std::regex_replace(major, std::regex(R"([\D])"), "");
469  std::string minorStripped = std::regex_replace(minor, std::regex(R"([\D])"), "");
470 
471  if (majorStripped.size() > 0)
472  majorVersion = stoi(majorStripped);
473  if (minorStripped.size() > 0)
474  minorVersion = stoi(minorStripped);
475  }
476 }
477 
478 bool LX200AstroPhysicsV2::getFirmwareVersion()
479 {
480  bool success;
481  char rev[8];
482  char versionString[128];
483  majorVersion = 0;
484  minorVersion = 0;
485 
486  success = false;
487 
488  if (isSimulation())
489  strncpy(versionString, "VCP4-P01-01", 128);
490  else
491  getAPVersionNumber(PortFD, versionString);
492 
493  VersionTP.s = IPS_OK;
494  IUSaveText(&VersionT[0], versionString);
495  IDSetText(&VersionTP, nullptr);
496 
497  if (strstr(versionString, "VCP4"))
498  {
499  firmwareVersion = MCV_V;
500  servoType = GTOCP4;
501  strcpy(rev, "V");
502  success = true;
503  setMajorMinorVersions(versionString);
504  }
505  else if (strstr(versionString, "VCP5"))
506 
507  {
508  firmwareVersion = MCV_V;
509  servoType = GTOCP5;
510  strcpy(rev, "V");
511  setMajorMinorVersions(versionString);
512  success = true;
513  }
514  else if (strlen(versionString) == 1 || strlen(versionString) == 2)
515  {
516  // Check earlier versions
517  // FIXME could probably use better range checking in case we get a letter like 'Z' that doesn't map to anything!
518  int typeIndex = VersionT[0].text[0] - 'D';
519  if (typeIndex >= 0)
520  {
521  firmwareVersion = static_cast<ControllerVersion>(typeIndex);
522  LOGF_DEBUG("Firmware version index: %d", typeIndex);
523  if (firmwareVersion < MCV_G)
524  servoType = GTOCP2;
525  else
526  servoType = GTOCP3;
527 
528  strncpy(rev, versionString, 8);
529 
530  success = true;
531  }
532  else
533  {
534  LOGF_WARN("unknown AP controller version %s", VersionT[0].text);
535  }
536  }
537 
538  if (success)
539  {
540  LOGF_INFO("Servo Box Controller: GTOCP%d.", servoType);
541  LOGF_INFO("Firmware Version: '%s'", versionString);
542  if (majorVersion && minorVersion)
543  LOGF_INFO("Firmware Major Version: %d Minor Version %d", majorVersion, minorVersion);
544  }
545 
546  return success;
547 }
548 
549 /**************************************************************************************
550 **
551 ***************************************************************************************/
552 bool LX200AstroPhysicsV2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
553 {
554  if (strcmp(getDeviceName(), dev))
555  return false;
556 
557  if (!strcmp(name, APUTCOffsetNP.name))
558  {
559  if (IUUpdateNumber(&APUTCOffsetNP, values, names, n) < 0)
560  return false;
561 
562  float mdelay;
563  int err;
564 
565  mdelay = APUTCOffsetN[0].value;
566 
567  if (!isSimulation() && (err = setAPUTCOffset(PortFD, mdelay) < 0))
568  {
569  LOGF_ERROR("Error setting UTC offset (%d).", err);
570  return false;
571  }
572 
574  IDSetNumber(&APUTCOffsetNP, nullptr);
575 
576  return true;
577  }
578  if (!strcmp(name, HourangleCoordsNP.name))
579  {
580 
581  if (IUUpdateNumber(&HourangleCoordsNP, values, names, n) < 0)
582  return false;
583 
584  double lng = LocationN[LOCATION_LONGITUDE].value;
585  double lst = get_local_sidereal_time(lng);
586  double ra = lst - HourangleCoordsN[0].value;
587  double dec = HourangleCoordsN[1].value;
588  bool success = false;
589  if ((ISS_ON == IUFindSwitch(&CoordSP, "TRACK")->s) || (ISS_ON == IUFindSwitch(&CoordSP, "SLEW")->s))
590  {
591  success = Goto(ra, dec);
592  }
593  else
594  {
595  success = APSync(ra, dec, true);
596  }
597  if (success)
598  {
600  }
601  else
602  {
604  }
605  IDSetNumber(&HourangleCoordsNP, nullptr);
606  return true;
607  }
608 
609  return LX200Generic::ISNewNumber(dev, name, values, names, n);
610 }
611 
612 bool LX200AstroPhysicsV2::ApInitialize()
613 {
614  if ((firmwareVersion == MCV_UNKNOWN) || (firmwareVersion < MCV_T))
615  {
616  // Can't use this driver
617  LOG_ERROR("This driver requires at least version T firmware");
618  return false;
619  }
620 
622  char status_string[256];
623  if (getApStatusString(PortFD, status_string) == TTY_OK)
624  {
625  rateTable = apRateTable(status_string);
626  if ((rateTable >= 0) && (rateTable <= 3))
627  LOGF_INFO("Using Rate Table: %d", rateTable);
628  else
630  }
631  initRateLabels();
632 
634 
635  // Set location up every time we connect.
636  double longitude = -1000, latitude = -1000;
637  // Get value from config file if it exists.
638  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LONG", &longitude);
639  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LAT", &latitude);
640  if (longitude != -1000 && latitude != -1000)
641  updateAPLocation(latitude, longitude, 0);
642 
643  bool isAPParked = false;
644  if (!IsMountParked(&isAPParked))
645  return false;
646  if (isAPParked)
647  {
648  if (!loadConfig(true, UnparkFromSP.name))
649  LOG_DEBUG("could not load config data for UnparkFromSP.name");
650  if (!loadConfig(true, ParkToSP.name))
651  LOG_DEBUG("could not load config data for ParkTo.name");
652  if(UnparkFromS[PARK_LAST].s == ISS_ON)
653  LOG_INFO("Driver's config 'Unpark From ?' is set to Last Parked");
654  // forcing mount being parked from INDI's perspective
655  LOG_INFO("ApInitialize, parked.");
656  SetParked(true);
657  }
658  else
659  {
660  LOG_INFO("ApInitialize, not parked.");
661  SetParked(false);
662  }
663 
664  if (isSimulation())
665  {
666  SlewRateSP.s = IPS_OK;
667  IDSetSwitch(&SlewRateSP, nullptr);
668 
670  IDSetSwitch(&APSlewSpeedSP, nullptr);
671 
672  IUSaveText(&VersionT[0], "1.0");
673  VersionTP.s = IPS_OK;
674  IDSetText(&VersionTP, nullptr);
675 
676  return true;
677 
678  }
679  // Make sure that the mount is setup according to the properties
680  int switch_nr = IUFindOnSwitchIndex(&TrackModeSP);
681 
682  int err = 0;
683  if ( (err = selectAPTrackingMode(PortFD, switch_nr)) < 0)
684  {
685  LOGF_ERROR("ApInitialize: Error setting tracking mode (%d).", err);
686  return false;
687  }
688 
689  // On most mounts SlewRateS defines the MoveTo AND Slew (GOTO) speeds
690  // lx200ap is different - some of the MoveTo speeds are not VALID
691  // Slew speeds so we have to keep two lists.
692  //
693  // SlewRateS is used as the MoveTo speed
694  switch_nr = IUFindOnSwitchIndex(&SlewRateSP);
695  if ((err = selectAPV2CenterRate(PortFD, switch_nr, rateTable)) < 0)
696  {
697  LOGF_ERROR("ApInitialize: Error setting move rate (%d).", err);
698  return false;
699  }
700 
701  SlewRateSP.s = IPS_OK;
702  IDSetSwitch(&SlewRateSP, nullptr);
703 
704  // APSlewSpeedsS defines the Slew (GOTO) speeds valid on the AP mounts
705  switch_nr = IUFindOnSwitchIndex(&APSlewSpeedSP);
706  if ( (err = selectAPSlewRate(PortFD, switch_nr)) < 0)
707  {
708  LOGF_ERROR("ApInitialize: Error setting slew to rate (%d).", err);
709  return false;
710  }
712  IDSetSwitch(&APSlewSpeedSP, nullptr);
713 
716 
717  // make a IDSet in order the dome controller is aware of the initial values
720 
722 
723  char versionString[64];
724  getAPVersionNumber(PortFD, versionString);
725  VersionTP.s = IPS_OK;
726  IUSaveText(&VersionT[0], versionString);
727  IDSetText(&VersionTP, nullptr);
728 
729  return true;
730 }
731 
732 bool LX200AstroPhysicsV2::ISNewSwitch(const char *dev, const char *name, ISState * states, char *names[], int n)
733 {
734  int err = 0;
735  if (strcmp(getDeviceName(), dev))
736  return false;
737 
738  // =======================================
739  // Swap Buttons
740  // =======================================
741  if (!strcmp(name, SwapSP.name))
742  {
743  int currentSwap;
744 
746  IUUpdateSwitch(&SwapSP, states, names, n);
747  currentSwap = IUFindOnSwitchIndex(&SwapSP);
748 
749  if ((!isSimulation() && (err = swapAPButtons(PortFD, currentSwap)) < 0))
750  {
751  LOGF_ERROR("Error swapping buttons (%d).", err);
752  return false;
753  }
754 
755  SwapS[0].s = ISS_OFF;
756  SwapS[1].s = ISS_OFF;
757  SwapSP.s = IPS_OK;
758  IDSetSwitch(&SwapSP, nullptr);
759  return true;
760  }
761 
762  // ===========================================================
763  // GOTO ("slew") Speed.
764  // ===========================================================
765  if (!strcmp(name, APSlewSpeedSP.name))
766  {
767  IUUpdateSwitch(&APSlewSpeedSP, states, names, n);
768  int slewRate = IUFindOnSwitchIndex(&APSlewSpeedSP);
769 
770  if (!isSimulation() && (err = selectAPSlewRate(PortFD, slewRate) < 0))
771  {
772  LOGF_ERROR("Error setting move to rate (%d).", err);
773  return false;
774  }
775 
777  IDSetSwitch(&APSlewSpeedSP, nullptr);
778  return true;
779  }
780 
781  // ===========================================================
782  // Guide Speed.
783  // ===========================================================
784  if (!strcmp(name, APGuideSpeedSP.name))
785  {
786  IUUpdateSwitch(&APGuideSpeedSP, states, names, n);
787  int guideRate = IUFindOnSwitchIndex(&APGuideSpeedSP);
788 
789  if (!isSimulation() && (err = selectAPGuideRate(PortFD, guideRate) < 0))
790  {
791  LOGF_ERROR("Error setting guiding to rate (%d).", err);
792  return false;
793  }
794 
796  IDSetSwitch(&APGuideSpeedSP, nullptr);
797  return true;
798  }
799 
800  // =======================================
801  // Choose the PEC playback mode
802  // =======================================
803  if (!strcmp(name, PECStateSP.name))
804  {
806  IUUpdateSwitch(&PECStateSP, states, names, n);
808 
809  int pecstate = IUFindOnSwitchIndex(&PECStateSP);
810 
811  if (!isSimulation() && (err = selectAPPECState(PortFD, pecstate) < 0))
812  {
813  LOGF_ERROR("Error setting PEC state (%d).", err);
814  return false;
815  }
816 
817  PECStateSP.s = IPS_OK;
818  IDSetSwitch(&PECStateSP, nullptr);
819 
820  return true;
821  }
822 
823  if (strcmp(name, APPECRecordSP.name) == 0)
824  {
826  IUUpdateSwitch(&APPECRecordSP, states, names, n);
828 
829  int recordState = IUFindOnSwitchIndex(&APPECRecordSP);
830 
831  // Can't turn recording off.
832  if (recordState == AP_PEC_RECORD_ON)
833  {
835  if (!isSimulation() && (err != 0))
836  {
837  LOGF_ERROR("Error setting PEC state RECORD (%d).", err);
838  return false;
839  }
840  LOG_INFO("Recording PEC");
842  IDSetSwitch(&PECStateSP, nullptr);
843  }
844  return true;
845  }
846 
847  // ===========================================================
848  // Unpark from positions
849  // ===========================================================
850  if (!strcmp(name, UnparkFromSP.name))
851  {
852  IUUpdateSwitch(&UnparkFromSP, states, names, n);
853  ParkPosition unparkPos = static_cast<ParkPosition>(IUFindOnSwitchIndex(&UnparkFromSP));
854 
856  if( unparkPos != PARK_LAST)
857  {
858  double unparkAlt, unparkAz;
859  if (!calcParkPosition(unparkPos, &unparkAlt, &unparkAz))
860  {
861  LOG_WARN("Error calculating unpark position!");
863  }
864  else
865  {
866  // 2020-06-01, wildi, UnPark() relies on it
867  saveConfig(true);
868  }
869  }
870  IDSetSwitch(&UnparkFromSP, nullptr);
871  return true;
872  }
873 
874  // ===========================================================
875  // Switch Park(ed), Unpark(ed)
876  // ===========================================================
877 
878  if (!strcmp(name, ParkSP.name))
879  {
880  }
881 
882  // ===========================================================
883  // Park To positions
884  // ===========================================================
885  if (!strcmp(name, ParkToSP.name))
886  {
887  IUUpdateSwitch(&ParkToSP, states, names, n);
888  ParkPosition parkPos = static_cast<ParkPosition>(IUFindOnSwitchIndex(&ParkToSP));
889  if (parkPos != PARK_CUSTOM && parkPos != PARK_CURRENT)
890  {
891  double parkAz, parkAlt;
892  if (calcParkPosition(parkPos, &parkAlt, &parkAz))
893  {
894  LOGF_INFO("Set predefined park position %d to az=%f alt=%f", parkPos, parkAz, parkAlt);
895  }
896  else
897  {
898  LOGF_ERROR("Unable to set predefined park position %d!!", parkPos);
899  }
900  }
901  else
902  {
903  LOG_WARN("ISNewSwitch: park custom/current not supported");
905  ParkToSP.s = IPS_ALERT;
906  IDSetSwitch(&ParkToSP, nullptr);
907  return false;
908  }
910  ParkToS[(int)parkPos].s = ISS_ON;
911  ParkToSP.s = IPS_OK;
912  IDSetSwitch(&ParkToSP, nullptr);
913  return true;
914  }
915 
916  if (strcmp(name, ManualSetParkedSP.name) == 0)
917  {
918  // Force the mount to be parked where it is and disconnect.
920  bool alreadyConnected = isConnected();
921  if (!alreadyConnected)
922  {
924  Connection::Interface *activeConnection = getActiveConnection();
925  if (!activeConnection->name().compare("CONNECTION_TCP"))
926  {
927  // When using a tcp connection, the GTOCP4 adds trailing LF to response.
928  // this small hack will get rid of them as they are not expected in the driver. and generated
929  // lot of communication errors.
931  }
932  if (!LX200Generic::Connect())
933  {
934  LOG_ERROR("Connect failed for Manual Park");
935  return true;
936  }
937  }
938 
939  if (parkInternal())
940  saveConfig(true);
941  else
942  LOG_ERROR("ParkInternal failed for Manual Park");
943 
944  if (!alreadyConnected)
945  {
946  if (!Disconnect())
947  LOG_ERROR("Disconnect failed for Manual Park");
948  }
949  IDSetSwitch(&ManualSetParkedSP, nullptr);
950  return true;
951  }
952 
953  // ===========================================================
954  // Home and ReSync mount
955  // ===========================================================
956  if (strcmp(name, HomeAndReSyncSP.name) == 0)
957  {
960  IDSetSwitch(&HomeAndReSyncSP, nullptr);
961  return true;
962  }
963 
964  return LX200Generic::ISNewSwitch(dev, name, states, names, n);
965 }
966 
968 {
969  switch(state)
970  {
972  static char idleStr[] = "Idle";
973  return idleStr;
975  static char slewStr[] = "Slewing";
976  return slewStr;
978  static char trackStr[] = "Tracking";
979  return trackStr;
981  static char parkingStr[] = "Parking";
982  return parkingStr;
984  static char parkStr[] = "Parked";
985  return parkStr;
986  }
987  static char emptyStr[] = "???";
988  return emptyStr;
989 }
990 
992 {
993  if (!isAPReady())
994  {
995  LOGF_DEBUG("APStatus: Not ready--Checked %s Initialized %s Time updated %s Location Updated %s",
996  apInitializationChecked ? "Y" : "N", apIsInitialized ? "Y" : "N",
997  apTimeInitialized ? "Y" : "N", apLocationInitialized ? "Y" : "N");
998 
999  // hope this return doen't delay the time & location. If it does return true?
1000  return false;
1001  }
1002  double lng = LocationN[LOCATION_LONGITUDE].value;
1003  double lst = get_local_sidereal_time(lng);
1004  double val = lst;
1005  if ((!isSimulation()) && (getSDTime(PortFD, &val) < 0))
1006  {
1007  LOG_ERROR("Reading sidereal time failed");
1008  return false;
1009  }
1010  std::string sTimeStr;
1011  if (val >= 0 && val <= 24.0)
1012  sTimeStr = std::to_string(val);
1013  else
1014  {
1015  val = 0;
1016  sTimeStr = "????";
1017  }
1018 
1019  APSiderealTimeNP.np[0].value = lst;
1021  IDSetNumber(&APSiderealTimeNP, nullptr);
1022 
1023  if (isSimulation())
1024  {
1025  mountSim();
1026  return true;
1027  }
1028  if (getLX200RA(PortFD, &currentRA) < 0 || getLX200DEC(PortFD, &currentDEC) < 0)
1029  {
1030  EqNP.s = IPS_ALERT;
1031  IDSetNumber(&EqNP, "Error reading RA/DEC.");
1032  return false;
1033  }
1034 
1035  char apStatusString[256];
1036  if (getApStatusString(PortFD, apStatusString) != TTY_OK)
1037  {
1038  LOG_ERROR("Reading AP status failed");
1039  return false;
1040  }
1041 
1042  getWormPosition();
1043  getPECState(apStatusString);
1044  processMountStatus(apStatusString);
1045 
1046  const bool apParked = apStatusParked(apStatusString);
1047  if (!apParked)
1048  {
1049  double ha = get_local_hour_angle(lst, currentRA);
1050 
1051  // No need to spam log until we have some actual changes.
1052  if (std::fabs(HourangleCoordsN[0].value - ha) > 0.00001 ||
1053  std::fabs(HourangleCoordsN[1].value - currentDEC) > 0.00001)
1054  {
1055  // in case of simulation, the coordinates are set on parking
1056  HourangleCoordsN[0].value = ha;
1057  HourangleCoordsN[1].value = currentDEC;
1059  IDSetNumber(&HourangleCoordsNP, nullptr );
1060  }
1061  }
1062  LOGF_DEBUG("APStatus: %s %s stime: %s RA/DEC: %.3f %.3f",
1063  trackStateString(TrackState), apParked ? "Parked" : "Unparked", sTimeStr.c_str(), currentRA, currentDEC);
1064 
1065  if (TrackState == SCOPE_SLEWING)
1066  {
1067  const double dx = fabs(lastRA - currentRA);
1068  const double dy = fabs(lastDE - currentDEC);
1069 
1070  LOGF_DEBUG("Slewing... currentRA: %.3f dx: %g currentDE: %.3f dy: %g", currentRA, dx, currentDEC, dy);
1071 
1072  // Note, RA won't hit 0 if it's not tracking, becuase the RA changes when still.
1073  // Dec might, though.
1074  // 0 might work now that I "fixed" slewing...perhaps not when tracking is off.
1075  if (dx < 1e-3 && dy < 1e-3)
1076  {
1078  LOG_INFO("Slew is complete. Tracking...");
1079  }
1080 
1081  // Keep try of last values to determine if the mount settled.
1082  lastRA = currentRA;
1083  lastDE = currentDEC;
1084  }
1085  else if (TrackState == SCOPE_PARKING)
1086  {
1087  bool slewcomplete = false;
1088  double PARKTHRES = 0.1; // max difference from parked position to consider mount PARKED
1089 
1090  if (!apStatusSlewing(apStatusString))
1091  slewcomplete = true;
1092 
1093  // old way
1094  if (getLX200Az(PortFD, &currentAz) < 0 || getLX200Alt(PortFD, &currentAlt) < 0)
1095  {
1096  EqNP.s = IPS_ALERT;
1097  IDSetNumber(&EqNP, "Error reading Az/Alt.");
1098  return false;
1099  }
1100 
1101  const double dx = fabs(lastAZ - currentAz);
1102  const double dy = fabs(lastAL - currentAlt);
1103  LOGF_DEBUG("Parking... currentAz: %g dx: %g currentAlt: %g dy: %g", currentAz, dx, currentAlt, dy);
1104 
1105  // if for some reason we check slew status BEFORE park motion starts make sure we dont consider park
1106  // action complete too early by checking how far from park position we are!
1107 
1108  if (slewcomplete && (dx > PARKTHRES || dy > PARKTHRES))
1109  {
1110  LOG_WARN("Parking... slew status indicates mount stopped by dx/dy too far from mount - continuing!");
1111  slewcomplete = false;
1112  }
1113 
1114  // Not sure why it hedged previously. Require slewcomplete for now. Verify!
1115  // if (slewcomplete || (dx <= PARKTHRES && dy <= PARKTHRES))
1116  if (slewcomplete)
1117  {
1118  LOG_DEBUG("Parking slew is complete. Asking astrophysics mount to park...");
1119  if (!parkInternal())
1120  {
1121  return false;
1122  }
1123  saveConfig(true);
1124  }
1125 
1126  lastAZ = currentAz;
1127  lastAL = currentAlt;
1128  }
1129 
1131 
1132  syncSideOfPier();
1133 
1134  return true;
1135 }
1136 
1137 bool LX200AstroPhysicsV2::parkInternal()
1138 {
1139  if (APParkMount(PortFD) < 0)
1140  {
1141  LOG_ERROR("Parking Failed.");
1142  return false;
1143  }
1144 
1145  SetTrackEnabled(false);
1146  SetParked(true);
1147  return true;
1148 }
1149 
1150 bool LX200AstroPhysicsV2::IsMountParked(bool * isAPParked)
1151 {
1152  if (isSimulation())
1153  {
1154  // 2030-05-30, if Unparked is selected, this condition is not met
1155  *isAPParked = (ParkS[0].s == ISS_ON);
1156  return true;
1157  }
1158 
1159  char parkStatus;
1160  char slewStatus;
1161  if (check_lx200ap_status(PortFD, &parkStatus, &slewStatus) == 0)
1162  {
1163  LOGF_DEBUG("parkStatus: %c", parkStatus);
1164 
1165  *isAPParked = (parkStatus == 'P');
1166  return true;
1167  }
1168  return false;
1169 }
1170 
1171 bool LX200AstroPhysicsV2::Goto(double r, double d)
1172 {
1173  if (!isAPReady())
1174  return false;
1175 
1176  const struct timespec timeout = {0, 100000000L};
1177 
1178  targetRA = r;
1179  targetDEC = d;
1180 
1181  char RAStr[64], DecStr[64];
1182  fs_sexa(RAStr, targetRA, 2, 3600);
1183  fs_sexa(DecStr, targetDEC, 2, 3600);
1184 
1185  // If moving, let's stop it first.
1186  if (EqNP.s == IPS_BUSY)
1187  {
1188  if (!isSimulation() && abortSlew(PortFD) < 0)
1189  {
1190  AbortSP.s = IPS_ALERT;
1191  IDSetSwitch(&AbortSP, "Abort slew failed.");
1192  return false;
1193  }
1194 
1195  AbortSP.s = IPS_OK;
1196  EqNP.s = IPS_IDLE;
1197  IDSetSwitch(&AbortSP, "Slew aborted.");
1198  IDSetNumber(&EqNP, nullptr);
1199 
1201  {
1204  EqNP.s = IPS_IDLE;
1207  IDSetSwitch(&MovementNSSP, nullptr);
1208  IDSetSwitch(&MovementWESP, nullptr);
1209  }
1210 
1211  // sleep for 100 mseconds
1212  nanosleep(&timeout, nullptr);
1213  }
1214 
1215  if (!isSimulation())
1216  {
1218  {
1219  EqNP.s = IPS_ALERT;
1220  IDSetNumber(&EqNP, "Error setting RA/DEC.");
1221  return false;
1222  }
1223 
1224  int err = 0;
1225 
1226  /* Slew reads the '0', that is not the end of the slew */
1227  if ((err = Slew(PortFD)))
1228  {
1229  EqNP.s = IPS_ALERT;
1230  IDSetNumber(&EqNP, "Error Slewing to JNow RA %s - DEC %s\n", RAStr, DecStr);
1231  slewError(err);
1232  return false;
1233  }
1234  lastRA = targetRA;
1235  lastDE = targetDEC;
1236  }
1237 
1239  //EqNP.s = IPS_BUSY;
1240 
1241  LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
1242  return true;
1243 }
1244 
1245 // AP mounts handle guide commands differently enough from the "generic" LX200 we need to override some
1246 // functions related to the GuiderInterface
1247 
1249 {
1250  if (!isAPReady())
1251  return IPS_ALERT;
1252 
1253  // If we're using pulse command, then MovementXXX should NOT be active at all.
1255  {
1256  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1257  return IPS_ALERT;
1258  }
1259 
1260  if (GuideNSTID)
1261  {
1263  GuideNSTID = 0;
1264  }
1265 
1266  if (ms > MAX_LX200AP_PULSE_LEN)
1267  {
1268  LOGF_DEBUG("GuideNorth truncating %dms pulse to %dms", ms, MAX_LX200AP_PULSE_LEN);
1269  ms = MAX_LX200AP_PULSE_LEN;
1270  }
1271  if (usePulseCommand)
1272  {
1274  GuideNSTID = IEAddTimer(static_cast<int>(ms), pulseGuideTimeoutHelperNS, this);
1275  }
1276 
1277  return IPS_BUSY;
1278 }
1279 
1281 {
1282  if (!isAPReady())
1283  return IPS_ALERT;
1284 
1285  // If we're using pulse command, then MovementXXX should NOT be active at all.
1287  {
1288  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1289  return IPS_ALERT;
1290  }
1291 
1292  if (GuideNSTID)
1293  {
1295  GuideNSTID = 0;
1296  }
1297 
1298  if (ms > MAX_LX200AP_PULSE_LEN)
1299  {
1300  LOGF_DEBUG("GuideSouth truncating %dms pulse to %dms", ms, MAX_LX200AP_PULSE_LEN);
1301  ms = MAX_LX200AP_PULSE_LEN;
1302  }
1303  if (usePulseCommand)
1304  {
1306  GuideNSTID = IEAddTimer(static_cast<int>(ms), pulseGuideTimeoutHelperNS, this);
1307  }
1308 
1309  return IPS_BUSY;
1310 }
1311 
1313 {
1314  if (!isAPReady())
1315  return IPS_ALERT;
1316 
1317  // If we're using pulse command, then MovementXXX should NOT be active at all.
1319  {
1320  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1321  return IPS_ALERT;
1322  }
1323 
1324  if (GuideWETID)
1325  {
1327  GuideWETID = 0;
1328  }
1329 
1330  if (ms > MAX_LX200AP_PULSE_LEN)
1331  {
1332  LOGF_DEBUG("GuideEast truncating %dms pulse to %dms", ms, MAX_LX200AP_PULSE_LEN);
1333  ms = MAX_LX200AP_PULSE_LEN;
1334  }
1335  if (usePulseCommand)
1336  {
1338  GuideWETID = IEAddTimer(static_cast<int>(ms), pulseGuideTimeoutHelperWE, this);
1339  }
1340 
1341  return IPS_BUSY;
1342 }
1343 
1345 {
1346  if (!isAPReady())
1347  return IPS_ALERT;
1348 
1349  // If we're using pulse command, then MovementXXX should NOT be active at all.
1351  {
1352  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1353  return IPS_ALERT;
1354  }
1355 
1356  if (GuideWETID)
1357  {
1359  GuideWETID = 0;
1360  }
1361 
1362  if (ms > MAX_LX200AP_PULSE_LEN)
1363  {
1364  LOGF_DEBUG("GuideWest truncating %dms pulse to %dms", ms, MAX_LX200AP_PULSE_LEN);
1365  ms = MAX_LX200AP_PULSE_LEN;
1366  }
1367  if (usePulseCommand)
1368  {
1370  GuideWETID = IEAddTimer(static_cast<int>(ms), pulseGuideTimeoutHelperWE, this);
1371  }
1372 
1373  return IPS_BUSY;
1374 }
1375 
1377 {
1378  static_cast<LX200AstroPhysicsV2 *>(p)->AstroPhysicsGuideTimeoutNS(false);
1379 }
1380 
1382 {
1383  static_cast<LX200AstroPhysicsV2 *>(p)->AstroPhysicsGuideTimeoutWE(false);
1384 }
1385 
1387 {
1388  static_cast<LX200AstroPhysicsV2 *>(p)->AstroPhysicsGuideTimeoutNS(true);
1389 }
1390 
1392 {
1393  static_cast<LX200AstroPhysicsV2 *>(p)->AstroPhysicsGuideTimeoutWE(true);
1394 }
1395 
1397 {
1398  LOGF_DEBUG("AstroPhysicsGuideTimeoutWE() pulse guide simul = %d", simul);
1399 
1400  if (simul == true)
1401  {
1402  ISState states[] = { ISS_OFF, ISS_OFF };
1403  const char *names[] = { MovementWES[DIRECTION_WEST].name, MovementWES[DIRECTION_EAST].name};
1404  ISNewSwitch(MovementWESP.device, MovementWESP.name, states, const_cast<char **>(names), 2);
1405  }
1406 
1407  GuideWENP.np[DIRECTION_WEST].value = 0;
1408  GuideWENP.np[DIRECTION_EAST].value = 0;
1409  GuideWENP.s = IPS_IDLE;
1410  GuideWETID = 0;
1411  IDSetNumber(&GuideWENP, nullptr);
1412 }
1413 
1415 {
1416  LOGF_DEBUG("AstroPhysicsGuideTimeoutNS() pulse guide simul = %d", simul);
1417 
1418  if (simul == true)
1419  {
1420  ISState states[] = { ISS_OFF, ISS_OFF };
1421  const char *names[] = { MovementNSS[DIRECTION_NORTH].name, MovementNSS[DIRECTION_SOUTH].name};
1422  ISNewSwitch(MovementNSSP.device, MovementNSSP.name, states, const_cast<char **>(names), 2);
1423  }
1424 
1425  GuideNSNP.np[0].value = 0;
1426  GuideNSNP.np[1].value = 0;
1427  GuideNSNP.s = IPS_IDLE;
1428  GuideNSTID = 0;
1429  IDSetNumber(&GuideNSNP, nullptr);
1430 }
1431 
1432 int LX200AstroPhysicsV2::SendPulseCmd(int8_t direction, uint32_t duration_msec)
1433 {
1434  return APSendPulseCmd(PortFD, direction, duration_msec);
1435 }
1436 
1438 {
1439  if (isSimulation())
1440  {
1441  LOG_INFO("Simulated Astrophysics is online. Retrieving basic data...");
1442  getFirmwareVersion();
1443  return true;
1444  }
1445 
1446  int err = 0;
1447 
1448  if ((err = setAPClearBuffer(PortFD)) < 0)
1449  {
1450  LOGF_ERROR("Error clearing the buffer (%d): %s", err, strerror(err));
1451  return false;
1452  }
1453  if (!getActiveConnection()->name().compare("CONNECTION_TCP"))
1454  {
1455  LOG_INFO("Setting generic udp format (1)");
1457  }
1458  if (setAPBackLashCompensation(PortFD, 0, 0, 0) < 0)
1459  {
1460  // It seems we need to send it twice before it works!
1461  if ((err = setAPBackLashCompensation(PortFD, 0, 0, 0)) < 0)
1462  {
1463  LOGF_ERROR("Error setting backlash compensation (%d): %s.", err, strerror(err));
1464  }
1465  }
1466 
1467  // get firmware version
1468  bool rc = false;
1469 
1470  rc = getFirmwareVersion();
1471 
1472  if (!rc || (firmwareVersion == MCV_UNKNOWN) || (firmwareVersion < MCV_T))
1473  {
1474  // Can't use this driver at < version T. No way to test.
1475  LOG_ERROR("Firmware detection failed or is unknown. This driver requires at least version T firmware");
1476  return false;
1477  }
1478 
1479  // Do not track until mount is umparked
1480  if ((err = selectAPTrackingMode(PortFD, AP_TRACKING_OFF)) < 0)
1481  {
1482  LOGF_ERROR("Handshake: Error setting tracking mode to zero (%d).", err);
1483  return false;
1484  }
1485  else
1486  {
1487  LOG_INFO("Stopped tracking");
1488  }
1489 
1490  // Check to see if the mount is initialized during handshake.
1491  // If it isn't, we'll later need to set things up, and make sure
1492  // that location and time were sent to it.
1493  apIsInitialized = false;
1494  apInitializationChecked = false;
1495  apLocationInitialized = false;
1496  apTimeInitialized = false;
1497 
1498  // Let it fail twice before failing
1499  if ((isAPInitialized(PortFD, &apIsInitialized) != TTY_OK) ||
1500  (isAPInitialized(PortFD, &apIsInitialized) != TTY_OK))
1501  {
1502  return false;
1503  }
1504  apInitializationChecked = true;
1505 
1506  // Detect and set format. It should be LONG.
1507  return (checkLX200EquatorialFormat(PortFD) == 0);
1508 }
1509 
1510 bool LX200AstroPhysicsV2::isAPReady()
1511 {
1512  if (!apInitializationChecked)
1513  return false;
1514 
1515  // AP has passed the initialization check.
1516  if (apIsInitialized)
1517  return true;
1518 
1519  // Below is implementing the LastParked Scheme.
1520  // I don't require that PARK_LAST is the unparkFrom scheme.
1521  // If the mount is uninitialized, then trust the mount's PARK_LAST data.
1522  if (apLocationInitialized && apTimeInitialized)
1523  {
1524  bool commWorked = true;
1525  char statusString[256];
1526  if (getApStatusString(PortFD, statusString) != TTY_OK)
1527  {
1528  // Try again
1529  commWorked = getApStatusString(PortFD, statusString) == TTY_OK;
1530  }
1531  if (commWorked)
1532  {
1533  bool isAPParked = apStatusParked(statusString);
1534 
1535  // A-P came up unitialized, but we can now fix.
1536  if (APUnParkMount(PortFD) != TTY_OK)
1537  // Try again if we had a comm failure.
1538  commWorked = APUnParkMount(PortFD) == TTY_OK;
1539 
1540  if (commWorked)
1541  {
1542  // The mount should now be "calibrated" and have a correct RA/DEC,
1543  // based on its LastParked position.
1544  bool mountOk = false;
1545  if (isAPInitialized(PortFD, &mountOk) != TTY_OK)
1546  {
1547  // try one more time
1548  commWorked = isAPInitialized(PortFD, &mountOk) == TTY_OK;
1549  }
1550  if (commWorked && mountOk)
1551  {
1552  apIsInitialized = true;
1553 
1554  // Put it back into the state we found it.
1555  if (isAPParked)
1556  parkInternal();
1557  else
1558  SetParked(false);
1559  return true;
1560  }
1561  }
1562  }
1563  // If we arrive here, we tried but were unable to initialized the mount.
1564  // If unparkFrom is set to one of the park positions, and we're parked,
1565  // then don't fail, as we will recover on the unpark.
1566  ISState lastParkState;
1567  IUGetConfigSwitch(getDeviceName(), "UNPARK_FROM", "Last", &lastParkState);
1568  if (commWorked && apStatusParked(statusString) && (lastParkState != ISS_ON))
1569  {
1570  apIsInitialized = true;
1571  return true;
1572  }
1573  else
1574  {
1575  LOG_ERROR("Could not initialize mount.");
1576  Disconnect(); // Not sure about this....
1577  return false;
1578  }
1579  }
1580  // Not initialized, but not ready to give up either.
1581  return false;
1582 }
1583 
1585 {
1586  apIsInitialized = false;
1587  apLocationInitialized = false;
1588  apTimeInitialized = false;
1589  apInitializationChecked = false;
1590 
1591  return LX200Generic::Disconnect();
1592 }
1593 
1594 bool LX200AstroPhysicsV2::APSync(double ra, double dec, bool recalibrate)
1595 {
1596  char syncString[256] = ""; // simulation needs UTF-8
1597 
1598  if (!isSimulation())
1599  {
1600  if (setAPObjectRA(PortFD, ra) < 0 || setAPObjectDEC(PortFD, dec) < 0)
1601  {
1602  EqNP.s = IPS_ALERT;
1603  IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
1604  return false;
1605  }
1606  bool syncOK = true;
1607 
1608  if (recalibrate)
1609  {
1610  if (APSyncCMR(PortFD, syncString) < 0)
1611  syncOK = false;
1612  }
1613  else
1614  {
1615  if (APSyncCM(PortFD, syncString) < 0)
1616  syncOK = false;
1617  }
1618 
1619  if (!syncOK)
1620  {
1621  EqNP.s = IPS_ALERT;
1622  IDSetNumber(&EqNP, "Synchronization failed.");
1623  return false;
1624  }
1625 
1626  }
1627 
1628  currentRA = ra;
1629  currentDEC = dec;
1630  LOGF_DEBUG("%s Synchronization successful %s", (recalibrate ? "CMR" : "CM"), syncString);
1631 
1632  EqNP.s = IPS_OK;
1633 
1635 
1636  return true;
1637 }
1638 
1639 bool LX200AstroPhysicsV2::Sync(double ra, double dec)
1640 {
1641  // The default sync is a "CMR" / "Recalibrate" sync.
1642  return APSync(ra, dec, true);
1643 }
1644 
1645 bool LX200AstroPhysicsV2::updateTime(ln_date * utc, double utc_offset)
1646 {
1647  // 2020-06-02, wildi, ToDo, time obtained from KStars differs up to a couple
1648  // of 5 seconds from system time.
1649  struct ln_zonedate ltm;
1650 
1651  ln_date_to_zonedate(utc, &ltm, utc_offset * 3600.0);
1652  JD = ln_get_julian_day(utc);
1653  LOGF_DEBUG("New JD is %f, local time: %d, %d, %d, utc offset: %f", JD, ltm.hours, ltm.minutes, (int)ltm.seconds,
1654  utc_offset);
1655 
1656  // Set Local Time
1657  if (isSimulation() == false && setLocalTime(PortFD, ltm.hours, ltm.minutes, (int)ltm.seconds) < 0)
1658  {
1659  LOG_ERROR("Error setting local time.");
1660  return false;
1661  }
1662  LOGF_DEBUG("Set Local Time %02d:%02d:%02d is successful.", ltm.hours, ltm.minutes,
1663  (int)ltm.seconds);
1664 
1665  if (isSimulation() == false && setCalenderDate(PortFD, ltm.days, ltm.months, ltm.years) < 0)
1666  {
1667  LOG_ERROR("Error setting local date.");
1668  return false;
1669  }
1670  LOGF_DEBUG("Set Local Date %02d/%02d/%02d is successful.", ltm.days, ltm.months, ltm.years);
1671 
1672  // 2020-05-30, wildi, after a very long journey
1673  // AP: TZ (0,12): West, East (-12.,-0), (>12,24)
1674  // Peru, Lima:
1675  //(TX=':Gg#'), RX='+77*01:42#
1676  //(TX=':SG05:00:00#'), RX='1'
1677  // Linux/Windows TZ values: West: -12,0, East 0,12
1678  // AP GTOCPX accepts a converted float including 24.
1679  double ap_utc_offset = - utc_offset;
1680  if (!isSimulation() && setAPUTCOffset(PortFD, ap_utc_offset) < 0)
1681  {
1682  LOG_ERROR("Error setting UTC Offset.");
1683  return false;
1684  }
1685  APUTCOffsetN[0].value = ap_utc_offset ;
1687  IDSetNumber(&APUTCOffsetNP, nullptr);
1688 
1689  LOGF_DEBUG("Set UTC Offset %g as AP UTC Offset %g is successful.", utc_offset, ap_utc_offset);
1690  apTimeInitialized = true;
1691  return true;
1692 }
1693 
1694 bool LX200AstroPhysicsV2::updateAPLocation(double latitude, double longitude, double elevation)
1695 {
1696  INDI_UNUSED(elevation);
1697  LOG_DEBUG("LX200AstroPhysicsV2::updateLocation entry");
1698 
1699  if ((latitude == 0.) && (longitude == 0.))
1700  {
1701  LOG_DEBUG("updateLocation: latitude, longitude both zero");
1702  return false;
1703  }
1704 
1705  // Why is it 360-longitude? Verify!
1706  double apLongitude = 360 - longitude ;
1707  while (apLongitude < 0)
1708  apLongitude += 360;
1709  while (apLongitude > 360)
1710  apLongitude -= 360;
1711 
1712  LOGF_DEBUG("Setting site longitude coordinates, %f %f", longitude, apLongitude);
1713 
1714  if (!isSimulation() && setAPSiteLongitude(PortFD, apLongitude) < 0)
1715  {
1716  LOG_ERROR("Error setting site longitude coordinates");
1717  return false;
1718  }
1719 
1720  if (!isSimulation() && setAPSiteLatitude(PortFD, latitude) < 0)
1721  {
1722  LOG_ERROR("Error setting site latitude coordinates");
1723  return false;
1724  }
1725 
1726  char l[32], L[32];
1727  fs_sexa(l, latitude, 3, 3600);
1728  fs_sexa(L, longitude, 4, 3600);
1729 
1730  LOGF_DEBUG("Site location updated to Lat %.32s - Long %.32s, deg: %f, %f", l, L, latitude, longitude);
1731  apLocationInitialized = true;
1732  return true;
1733 }
1734 
1735 bool LX200AstroPhysicsV2::updateLocation(double latitude, double longitude, double elevation)
1736 {
1737  if ((latitude == 0.) && (longitude == 0.))
1738  {
1739  LOG_DEBUG("updateLocation: latitude, longitude both zero");
1740  return false;
1741  }
1742  if (!isConnected())
1743  return true;
1744  return updateAPLocation(latitude, longitude, elevation);
1745 }
1746 
1748 {
1750 
1751  // we use routines from legacy AP driver routines and newer experimental driver routines
1753 
1754 }
1755 
1756 // For most mounts the SetSlewRate() method sets both the MoveTo and Slew (GOTO) speeds.
1757 // For AP mounts these two speeds are handled separately - so SetSlewRate() actually sets the MoveTo speed for AP mounts - confusing!
1758 // ApSetSlew
1760 {
1761  if (!isSimulation() && selectAPV2CenterRate(PortFD, index, rateTable) < 0)
1762  {
1763  LOG_ERROR("Error setting slew mode.");
1764  return false;
1765  }
1766 
1767  return true;
1768 }
1769 
1771 {
1772  ParkPosition parkPos = static_cast<ParkPosition>(IUFindOnSwitchIndex(&ParkToSP));
1773  double parkAz {90}, parkAlt {0};
1774  if (parkPos == PARK_CURRENT)
1775  {
1776  LOG_DEBUG("PARK_CURRENT not implemented");
1777  }
1778  else if (calcParkPosition(parkPos, &parkAlt, &parkAz))
1779  {
1780  LOGF_DEBUG("Set park position %d to az=%f alt=%f", parkPos, parkAz, parkAlt);
1781  }
1782  else
1783  {
1784  LOGF_ERROR("Unable to set park position %d!!", parkPos);
1785  }
1786 
1787  char AzStr[16] = {0}, AltStr[16] = {0};
1788  fs_sexa(AzStr, parkAz, 2, 3600);
1789  fs_sexa(AltStr, parkAlt, 2, 3600);
1790  LOGF_INFO("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
1791 
1792  INDI::IEquatorialCoordinates equatorialCoords {0, 0};
1793  INDI::IHorizontalCoordinates horizontalCoords {parkAz, parkAlt};
1794  INDI::HorizontalToEquatorial(&horizontalCoords, &m_Location, ln_get_julian_from_sys(), &equatorialCoords);
1796  double ha = get_local_hour_angle(lst, equatorialCoords.rightascension);
1797 
1799  HourangleCoordsN[0].value = ha;
1800  HourangleCoordsN[1].value = equatorialCoords.declination;
1801  IDSetNumber(&HourangleCoordsNP, nullptr);
1802 
1803  if (isSimulation())
1804  {
1805  Goto(equatorialCoords.rightascension, equatorialCoords.declination);
1806  }
1807  else
1808  {
1809  if (setAPObjectAZ(PortFD, parkAz) < 0 || setAPObjectAlt(PortFD, parkAlt) < 0)
1810  {
1811  LOG_ERROR("Error setting Az/Alt.");
1812  return false;
1813  }
1814 
1815  int err = 0;
1816 
1817  /* Slew reads the '0', that is not the end of the slew */
1818  if ((err = Slew(PortFD)))
1819  {
1820  LOGF_ERROR("Error Slewing to Az %s - Alt %s", AzStr, AltStr);
1821  slewError(err);
1822  return false;
1823  }
1824  lastAZ = parkAz;
1825  lastAL = parkAlt;
1826  }
1827 
1828  EqNP.s = IPS_BUSY;
1830  LOG_INFO("Parking is in progress...");
1831 
1832  return true;
1833 }
1834 
1835 bool LX200AstroPhysicsV2::calcParkPosition(ParkPosition pos, double * parkAlt, double * parkAz)
1836 {
1837  switch (pos)
1838  {
1839  // last unparked and park custom share enum 0
1840  case PARK_CUSTOM:
1841  LOG_ERROR("Called calcParkPosition with PARK_CUSTOM or PARK_LAST!");
1842  return false;
1843  break;
1844 
1845  case PARK_CURRENT:
1846  LOG_ERROR("Called calcParkPosition with PARK_CURRENT!");
1847  return false;
1848  break;
1849 
1850  // Park 1
1851  // Northern Hemisphere should be pointing at ALT=0 AZ=0 with scope on WEST side of pier
1852  // Southern Hemisphere should be pointing at ALT=0 AZ=180 with scope on WEST side of pier
1853  case PARK_PARK1:
1854  LOG_INFO("Computing PARK1 position...");
1855  *parkAlt = 0;
1856  *parkAz = LocationN[LOCATION_LATITUDE].value > 0 ? 359.1 : 180.1;
1857  break;
1858 
1859  // Park 2
1860  // Northern Hemisphere should be pointing at ALT=0 AZ=90 with scope pointing EAST
1861  // Southern Hemisphere should be pointing at ALT=0 AZ=90 with scope pointing EAST
1862  case PARK_PARK2:
1863  LOG_INFO("Computing PARK2 position...");
1864  *parkAlt = 0;
1865  *parkAz = 90;
1866  break;
1867 
1868  // Park 3
1869  // Northern Hemisphere should be pointing at ALT=LAT AZ=0 with scope pointing NORTH with CW down
1870  // Southern Hemisphere should be pointing at ALT=LAT AZ=180 with scope pointing SOUTH with CW down
1871  // wildi: the hour angle is undefined if AZ = 0,180 and ALT=LAT is chosen, adding .1 to Az sets PARK3
1872  // as close as possible to to HA = -6 hours (CW down), valid for both hemispheres.
1873  case PARK_PARK3:
1874  *parkAlt = fabs(LocationN[LOCATION_LATITUDE].value);
1875  *parkAz = LocationN[LOCATION_LATITUDE].value > 0 ? 0.1 : 179.9;
1876  LOG_INFO("Computing PARK3 position");
1877  break;
1878 
1879  // Park 4
1880  // Northern Hemisphere should be pointing at ALT=0 AZ=180 with scope on EAST side of pier
1881  // Southern Hemisphere should be pointing at ALT=0 AZ=0 with scope on EAST side of pier
1882  case PARK_PARK4:
1883  LOG_INFO("Computing PARK4 position...");
1884  *parkAlt = 0;
1885  *parkAz = LocationN[LOCATION_LATITUDE].value > 0 ? 180.1 : 359.1;
1886  break;
1887 
1888  default:
1889  LOG_ERROR("Unknown park position!");
1890  return false;
1891  break;
1892  }
1893 
1894  LOGF_DEBUG("calcParkPosition: parkPos=%d parkAlt=%f parkAz=%f", pos, *parkAlt, *parkAz);
1895 
1896  return true;
1897 
1898 }
1899 
1901 {
1902  bool unpark_from_last_config = (PARK_LAST == IUFindOnSwitchIndex(&UnparkFromSP));
1903  double unparkAlt = 0, unparkAz = 0;
1904 
1905  if (!unpark_from_last_config)
1906  {
1907  ParkPosition unparkfromPos = static_cast<ParkPosition>(IUFindOnSwitchIndex(&UnparkFromSP));
1908  LOGF_DEBUG("UnPark: park position = %d from current driver", unparkfromPos);
1909  if (!calcParkPosition(unparkfromPos, &unparkAlt, &unparkAz))
1910  {
1911  LOG_ERROR("UnPark: Error calculating unpark position!");
1913  IDSetSwitch(&UnparkFromSP, nullptr);
1914  return false;
1915  }
1916  LOGF_DEBUG("UnPark: parkPos=%d parkAlt=%f parkAz=%f", unparkfromPos, unparkAlt, unparkAz);
1917  }
1918 
1919  bool isAPParked = true;
1920  if(!isSimulation())
1921  {
1922  if (!IsMountParked(&isAPParked))
1923  {
1924  LOG_WARN("UnPark:could not determine AP park status");
1926  IDSetSwitch(&UnparkFromSP, nullptr);
1927  return false;
1928  }
1929 
1930  if(!isAPParked)
1931  {
1932  LOG_WARN("UnPark: AP mount status: unparked, park first");
1934  IDSetSwitch(&UnparkFromSP, nullptr);
1935  return false;
1936  }
1937 
1938  if (APUnParkMount(PortFD) < 0)
1939  {
1941  ParkS[0].s = ISS_ON;
1942  ParkSP.s = IPS_ALERT;
1943  IDSetSwitch(&ParkSP, nullptr);
1944  LOG_ERROR("UnParking AP mount failed.");
1945  return false;
1946  }
1947 
1948  SetParked(false);
1949  // Stop :Q#
1950  if ( abortSlew(PortFD) < 0)
1951  {
1953  ParkS[0].s = ISS_ON;
1954  ParkSP.s = IPS_ALERT;
1955  IDSetSwitch(&ParkSP, nullptr);
1956  LOG_WARN("Abort motion Failed");
1957  return false;
1958  }
1959  SetTrackEnabled(true);
1961  }
1962  else
1963  {
1964  SetParked(false);
1965  SetTrackEnabled(false);
1967  }
1968 
1969  if (!unpark_from_last_config)
1970  {
1971  INDI::IEquatorialCoordinates equatorialCoords {0, 0};
1972  INDI::IHorizontalCoordinates horizontalCoords {unparkAz, unparkAlt};
1973  INDI::HorizontalToEquatorial(&horizontalCoords, &m_Location, ln_get_julian_from_sys(), &equatorialCoords);
1974 
1975  char AzStr[16], AltStr[16];
1976  fs_sexa(AzStr, unparkAz, 2, 3600);
1977  fs_sexa(AltStr, unparkAlt, 2, 3600);
1978  char RaStr[16], DecStr[16];
1979  fs_sexa(RaStr, equatorialCoords.rightascension, 2, 3600);
1980  fs_sexa(DecStr, equatorialCoords.declination, 2, 3600);
1981 
1983  double ha = get_local_hour_angle(lst, equatorialCoords.rightascension);
1984  char HaStr[16];
1985  fs_sexa(HaStr, ha, 2, 3600);
1986  LOGF_INFO("UnPark: Current parking position Az (%s) Alt (%s), HA (%s) RA (%s) Dec (%s)", AzStr, AltStr, HaStr,
1987  RaStr, DecStr);
1988 
1990  HourangleCoordsN[0].value = ha;
1991  HourangleCoordsN[1].value = equatorialCoords.declination;
1992  IDSetNumber(&HourangleCoordsNP, nullptr);
1993 
1994 
1995  // If we are not using PARK_LAST, then we're unparking from a pre-defined position, and this is the
1996  // only time we should use the full :CM "Fully Calibrate" sync command.
1997  bool success = APSync(equatorialCoords.rightascension, equatorialCoords.declination, false);
1998  if(!success)
1999  {
2000  LOG_WARN("Could not sync mount");
2001  return false;
2002  }
2003  else
2004  {
2005  LOGF_INFO("UnPark: Sync'd (:CM) to RA/DEC: %f %f", equatorialCoords.rightascension, equatorialCoords.declination);
2006  }
2007  }
2008 
2010  {
2013  EqNP.s = IPS_IDLE;
2016  IDSetSwitch(&MovementNSSP, nullptr);
2017  IDSetSwitch(&MovementWESP, nullptr);
2018  }
2019 
2020  UnparkFromSP.s = IPS_OK;
2021  IDSetSwitch(&UnparkFromSP, nullptr);
2022  // SlewRateS is used as the MoveTo speed
2023  int err;
2024  int switch_nr = IUFindOnSwitchIndex(&SlewRateSP);
2025  if (!isSimulation() && (err = selectAPV2CenterRate(PortFD, switch_nr, rateTable)) < 0)
2026  {
2027  LOGF_ERROR("Error setting center (MoveTo) rate (%d).", err);
2028  return false;
2029  }
2030 
2031  SlewRateSP.s = IPS_OK;
2032  IDSetSwitch(&SlewRateSP, nullptr);
2033 
2034  // APSlewSpeedsS defines the Slew (GOTO) speeds valid on the AP mounts
2035  switch_nr = IUFindOnSwitchIndex(&APSlewSpeedSP);
2036  if (!isSimulation() && (err = selectAPSlewRate(PortFD, switch_nr)) < 0)
2037  {
2038  LOGF_ERROR("Error setting slew to rate (%d).", err);
2039  return false;
2040  }
2041 
2043  IDSetSwitch(&APSlewSpeedSP, nullptr);
2044 
2045  LOG_DEBUG("UnPark: Mount unparked successfully");
2046 
2047  return true;
2048 }
2049 
2051 {
2052  return true;
2053 }
2054 
2056 {
2057  return true;
2058 }
2059 
2060 void LX200AstroPhysicsV2::syncSideOfPier()
2061 {
2062  const char *cmd = "#:pS#";
2063  // Response
2064  char response[16] = { 0 };
2065  int rc = 0, nbytes_read = 0, nbytes_written = 0;
2066 
2067  LOGF_DEBUG("CMD: <%s>", cmd);
2068 
2069  tcflush(PortFD, TCIOFLUSH);
2070 
2071  if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
2072  {
2073  char errmsg[256];
2074  tty_error_msg(rc, errmsg, 256);
2075  LOGF_ERROR("Error writing to device %s (%d)", errmsg, rc);
2076  return;
2077  }
2078 
2079  // Read Side
2080  if ((rc = tty_read_section(PortFD, response, '#', 3, &nbytes_read)) != TTY_OK)
2081  {
2082  char errmsg[256];
2083  tty_error_msg(rc, errmsg, 256);
2084  LOGF_ERROR("Error reading from device %s (%d)", errmsg, rc);
2085  return;
2086  }
2087 
2088  response[nbytes_read - 1] = '\0';
2089 
2090  tcflush(PortFD, TCIOFLUSH);
2091 
2092  LOGF_DEBUG("RES: <%s>", response);
2093 
2094  if (!strcmp(response, "East"))
2096  else if (!strcmp(response, "West"))
2098  else
2099  LOGF_ERROR("Invalid pier side response from device-> %s", response);
2100 }
2101 
2103 {
2105 
2111 
2112  return true;
2113 }
2114 
2116 {
2117  int err = 0;
2118 
2119  LOGF_DEBUG("LX200AstroPhysicsV2::SetTrackMode(%d)", mode);
2120 
2121  if (mode == TRACK_CUSTOM)
2122  {
2124  {
2125  LOGF_ERROR("Error setting tracking mode (%d).", err);
2126  return false;
2127  }
2128 
2129  return SetTrackRate(TrackRateN[AXIS_RA].value, TrackRateN[AXIS_DE].value);
2130  }
2131 
2132  if (!isSimulation() && (err = selectAPTrackingMode(PortFD, mode)) < 0)
2133  {
2134  LOGF_ERROR("Error setting tracking mode (%d).", err);
2135  return false;
2136  }
2137 
2138  return true;
2139 }
2140 
2142 {
2143  bool rc;
2144 
2145  LOGF_DEBUG("LX200AstroPhysicsV2::SetTrackEnabled(%d)", enabled);
2146 
2148 
2149  LOGF_DEBUG("LX200AstroPhysicsV2::SetTrackMode() returned %d", rc);
2150 
2151  return rc;
2152 }
2153 
2154 bool LX200AstroPhysicsV2::SetTrackRate(double raRate, double deRate)
2155 {
2156  // Convert to arcsecs/s to AP sidereal multiplier
2157  /*
2158  :RR0.0000# = normal sidereal tracking in RA - similar to :RT2#
2159  :RR+1.0000# = 1 + normal sidereal = 2X sidereal
2160  :RR+9.0000# = 9 + normal sidereal = 10X sidereal
2161  :RR-1.0000# = normal sidereal - 1 = 0 or Stop - similar to :RT9#
2162  :RR-11.0000# = normal sidereal - 11 = -10X sidereal (East at 10X)
2163 
2164  :RD0.0000# = normal zero rate for Dec.
2165  :RD5.0000# = 5 + normal zero rate = 5X sidereal clockwise from above - equivalent to South
2166  :RD-5.0000# = normal zero rate - 5 = 5X sidereal counter-clockwise from above - equivalent to North
2167  */
2168 
2169  double APRARate = (raRate - TRACKRATE_SIDEREAL) / TRACKRATE_SIDEREAL;
2170  double APDERate = deRate / TRACKRATE_SIDEREAL;
2171 
2172  if (!isSimulation())
2173  {
2174  if (setAPRATrackRate(PortFD, APRARate) < 0 || setAPDETrackRate(PortFD, APDERate) < 0)
2175  return false;
2176  }
2177 
2178  return true;
2179 }
2180 
2182 {
2183  if (isSimulation())
2184  {
2185  *offset = 3;
2186  return true;
2187  }
2188 
2189  return (getAPUTCOffset(PortFD, offset) == 0);
2190 }
2191 
2193 {
2194  // If we are not guiding and we need to restore slew rate, then let's restore it.
2195  if (command == MOTION_START && GuideNSTID == 0 && rememberSlewRate >= 0)
2196  {
2197  ISState states[] = { ISS_OFF, ISS_OFF, ISS_OFF, ISS_OFF };
2198  states[rememberSlewRate] = ISS_ON;
2199  const char *names[] = { SlewRateS[0].name, SlewRateS[1].name,
2200  SlewRateS[2].name, SlewRateS[3].name
2201  };
2202  ISNewSwitch(SlewRateSP.device, SlewRateSP.name, states, const_cast<char **>(names), 4);
2203  rememberSlewRate = -1;
2204  }
2205 
2206  bool rc = LX200Generic::MoveNS(dir, command);
2207  return rc;
2208 }
2209 
2211 {
2212  // If we are not guiding and we need to restore slew rate, then let's restore it.
2213  if (command == MOTION_START && GuideWETID == 0 && rememberSlewRate >= 0)
2214  {
2215  ISState states[] = { ISS_OFF, ISS_OFF, ISS_OFF, ISS_OFF };
2216  states[rememberSlewRate] = ISS_ON;
2217  const char *names[] = { SlewRateS[0].name, SlewRateS[1].name,
2218  SlewRateS[2].name, SlewRateS[3].name
2219  };
2220  ISNewSwitch(SlewRateSP.device, SlewRateSP.name, states, const_cast<char **>(names), 4);
2221  rememberSlewRate = -1;
2222  }
2223 
2224  bool rc = LX200Generic::MoveWE(dir, command);
2225  return rc;
2226 }
2227 
2229 {
2230  if (!isAPReady())
2231  return false;
2232 
2233  // restore guide rate
2235 
2236  bool rc = LX200Generic::MoveNS(dir, command);
2237  return rc;
2238 }
2239 
2241 {
2242  if (!isAPReady())
2243  return false;
2244 
2245  // restore guide rate
2247 
2248  bool rc = LX200Generic::MoveWE(dir, command);
2249  return rc;
2250 }
2251 
The Interface class is the base class for all INDI connection plugins.
virtual std::string name()=0
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool Disconnect()
Disconnect from device.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
Connection::Interface * getActiveConnection()
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
INumberVectorProperty GuideNSNP
INumberVectorProperty GuideWENP
TelescopeStatus TrackState
ISwitchVectorProperty TrackStateSP
ISwitchVectorProperty MovementNSSP
ISwitchVectorProperty AbortSP
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
ISwitchVectorProperty CoordSP
ISwitchVectorProperty PECStateSP
ISwitchVectorProperty TrackModeSP
ISwitchVectorProperty SlewRateSP
ISwitch MovementWES[2]
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
void setPECState(TelescopePECState state)
ISwitchVectorProperty ParkSP
INumber TrackRateN[2]
IGeographicCoordinates m_Location
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
ISwitch ParkS[2]
ISwitchVectorProperty MovementWESP
TelescopePECState getPECState()
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: lx200ap_v2.cpp:732
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition: lx200ap_v2.cpp:105
ITextVectorProperty APMountStatusTP
Definition: lx200ap_v2.h:162
ISwitch ParkToS[6]
Definition: lx200ap_v2.h:138
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
ISwitchVectorProperty APSlewSpeedSP
Definition: lx200ap_v2.h:123
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
INumber HorizontalCoordsN[2]
Definition: lx200ap_v2.h:119
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
ISwitch UnparkFromS[5]
Definition: lx200ap_v2.h:135
virtual bool Handshake() override
perform handshake with device to check communication
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
INumberVectorProperty HourangleCoordsNP
Definition: lx200ap_v2.h:115
ISwitchVectorProperty ParkToSP
Definition: lx200ap_v2.h:139
ISwitch APSlewSpeedS[3]
Definition: lx200ap_v2.h:122
void AstroPhysicsGuideTimeoutNS(bool simul)
ITextVectorProperty APPECStateTP
Definition: lx200ap_v2.h:159
ITextVectorProperty VersionTP
Definition: lx200ap_v2.h:145
ISwitch APPECRecordS[2]
Definition: lx200ap_v2.h:165
INumberVectorProperty HorizontalCoordsNP
Definition: lx200ap_v2.h:120
virtual bool GuideNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
ISwitch HomeAndReSyncS[1]
Definition: lx200ap_v2.h:151
virtual bool Park() override
Park the telescope to its home position.
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
Definition: lx200ap_v2.cpp:293
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual void debugTriggered(bool enable) override
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
static void pulseGuideTimeoutHelperWE(void *p)
INumberVectorProperty APSiderealTimeNP
Definition: lx200ap_v2.h:117
virtual bool getUTFOffset(double *offset) override
INumberVectorProperty APUTCOffsetNP
Definition: lx200ap_v2.h:141
INumber APSiderealTimeN[1]
Definition: lx200ap_v2.h:116
INumber APUTCOffsetN[1]
Definition: lx200ap_v2.h:142
ISwitch SwapS[2]
Definition: lx200ap_v2.h:125
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
static void pulseGuideTimeoutHelperNS(void *p)
ISwitch APGuideSpeedS[3]
Definition: lx200ap_v2.h:132
virtual int SendPulseCmd(int8_t direction, uint32_t duration_msec) override
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
ISwitchVectorProperty SwapSP
Definition: lx200ap_v2.h:126
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: lx200ap_v2.cpp:552
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
ISwitchVectorProperty APGuideSpeedSP
Definition: lx200ap_v2.h:133
IText APMountStatusT[1]
Definition: lx200ap_v2.h:161
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool SetTrackRate(double raRate, double deRate) override
SetTrackRate Set custom tracking rates.
static void simulGuideTimeoutHelperNS(void *p)
ISwitchVectorProperty ManualSetParkedSP
Definition: lx200ap_v2.h:147
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
APRateTableState rateTable
Definition: lx200ap_v2.h:153
ISwitchVectorProperty APPECRecordSP
Definition: lx200ap_v2.h:164
ISwitchVectorProperty UnparkFromSP
Definition: lx200ap_v2.h:136
virtual bool ReadScopeStatus() override
Read telescope status.
Definition: lx200ap_v2.cpp:991
ISwitchVectorProperty HomeAndReSyncSP
Definition: lx200ap_v2.h:150
IText APPECStateT[1]
Definition: lx200ap_v2.h:158
virtual bool Disconnect() override
Disconnect from device.
void AstroPhysicsGuideTimeoutWE(bool simul)
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition: lx200ap_v2.cpp:266
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
INumber HourangleCoordsN[2]
Definition: lx200ap_v2.h:114
INumberVectorProperty APWormPositionNP
Definition: lx200ap_v2.h:156
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
Definition: lx200ap_v2.cpp:90
virtual bool GuideWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
static void simulGuideTimeoutHelperWE(void *p)
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
ISwitch ManualSetParkedS[1]
Definition: lx200ap_v2.h:148
INumber APWormPositionN[1]
Definition: lx200ap_v2.h:155
virtual const char * getDefaultName() override
Definition: lx200ap_v2.cpp:85
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual void slewError(int slewCode)
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual void debugTriggered(bool enable) override
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void setLX200Capability(uint32_t cap)
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:602
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition: eventloop.c:582
#define setLocalTime(fd, x, y, z)
Definition: ieq45driver.h:141
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_ATMOST1
Definition: indiapi.h:174
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_EAST
Definition: indibasetypes.h:57
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_SOUTH
Definition: indibasetypes.h:50
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
void tty_set_generic_udp_format(int enabled)
Definition: indicom.c:370
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:566
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
void tty_clr_trailing_read_lf(int enabled)
Definition: indicom.c:375
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition: indicom.c:1293
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
#define TRACKRATE_SIDEREAL
Definition: indicom.h:55
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
ISwitch * IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
Find an ISwitch member in a vector switch property.
Definition: indidevapi.c:76
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUGetConfigNumber(const char *dev, const char *property, const char *member, double *value)
IUGetConfigNumber Opens configuration file and reads single number property.
Definition: indidriver.c:831
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition: indidriver.c:653
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
APPECRecordingState
Definition: lx200ap_v2.cpp:60
@ AP_PEC_RECORD_ON
Definition: lx200ap_v2.cpp:62
@ AP_PEC_RECORD_OFF
Definition: lx200ap_v2.cpp:61
char * trackStateString(INDI::Telescope::TelescopeStatus state)
Definition: lx200ap_v2.cpp:967
#define MAX_LX200AP_PULSE_LEN
Definition: lx200ap_v2.cpp:66
bool apStatusParked(const char *statusString)
int setAPUTCOffset(int fd, double hours)
int setAPSiteLatitude(int fd, double Lat)
int getAPWormPosition(int fd, int *position)
void set_lx200ap_name(const char *deviceName, unsigned int debug_level)
int setAPRATrackRate(int fd, double rate)
int apHomeAndSync(int fd)
int setAPObjectRA(int fd, double ra)
int APSyncCMR(int fd, char *matchedObject)
int setAPObjectAlt(int fd, double alt)
int swapAPButtons(int fd, int currentSwap)
int setAPObjectDEC(int fd, double dec)
int isAPInitialized(int fd, bool *isInitialized)
int APSendPulseCmd(int fd, int direction, int duration_msec)
const char * apMountStatus(const char *statusString)
int APParkMount(int fd)
int selectAPSlewRate(int fd, int slewIndex)
int getApStatusString(int fd, char *statusString)
int check_lx200ap_status(int fd, char *parkStatus, char *slewStatus)
int APUnParkMount(int fd)
bool apCanHome(int fd)
int selectAPPECState(int fd, int pecstate)
int selectAPGuideRate(int fd, int guideRate)
APRateTableState apRateTable(const char *statusString)
int setAPObjectAZ(int fd, double az)
int APSyncCM(int fd, char *matchedObject)
int getAPUTCOffset(int fd, double *value)
int setAPDETrackRate(int fd, double rate)
bool apStatusSlewing(const char *statusString)
int setAPSiteLongitude(int fd, double Long)
int selectAPV2CenterRate(int fd, int centerIndex, APRateTableState rateTable)
int selectAPTrackingMode(int fd, int trackMode)
#define setAPBackLashCompensation(fd, x, y, z)
Definition: lx200apdriver.h:30
@ AP_RATE_TABLE_DEFAULT
Definition: lx200apdriver.h:63
#define getAPVersionNumber(fd, x)
Definition: lx200apdriver.h:28
#define setAPClearBuffer(fd)
Definition: lx200apdriver.h:29
#define AP_PEC_OFF
Definition: lx200apdriver.h:38
#define AP_PEC_ON
Definition: lx200apdriver.h:39
#define AP_PEC_ENCODER
Definition: lx200apdriver.h:41
#define AP_TRACKING_OFF
Definition: lx200apdriver.h:36
#define AP_PEC_RECORD
Definition: lx200apdriver.h:40
#define AP_TRACKING_SIDEREAL
Definition: lx200apdriver.h:32
int checkLX200EquatorialFormat(int fd)
int abortSlew(int fd)
int setCalenderDate(int fd, int dd, int mm, int yy, bool addSpace)
int Slew(int fd)
#define getLX200DEC(fd, x)
Definition: lx200driver.h:118
@ LX200_24
Definition: lx200driver.h:64
#define getLX200RA(fd, x)
Definition: lx200driver.h:117
#define getLX200Alt(fd, x)
Definition: lx200driver.h:124
@ LX200_WEST
Definition: lx200driver.h:42
@ LX200_SOUTH
Definition: lx200driver.h:44
@ LX200_NORTH
Definition: lx200driver.h:41
@ LX200_EAST
Definition: lx200driver.h:43
#define getLX200Az(fd, x)
Definition: lx200driver.h:125
#define getSDTime(fd, x)
Definition: lx200driver.h:123
void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, IEquatorialCoordinates *position)
HorizontalToEquatorial Calculate Equatorial EOD Coordinates from horizontal coordinates.
Definition: libastro.cpp:156
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:23613
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char device[MAXINDIDEVICE]
Definition: indiapi.h:369
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250