Instrument Neutral Distributed Interface INDI  2.0.2
celestrongps.cpp
Go to the documentation of this file.
1 #if 0
3 Copyright (C) 2003 - 2017 Jasem Mutlaq (mutlaqja@ikarustech.com)
4 
5 This library is free software;
6 you can redistribute it and / or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation;
9 either
10 version 2.1 of the License, or (at your option) any later version.
11 
12 This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY;
14 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;
20 if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA
22 
23 Version with experimental pulse guide support. GC 04.12.2015
24 
25 #endif
26 
27 #include "celestrongps.h"
28 
29 #include "indicom.h"
30 
31 #include <libnova/transform.h>
32 
33 #include <cmath>
34 #include <memory>
35 #include <cstring>
36 #include <unistd.h>
37 
38 #include <sys/stat.h>
39 
40 #include "indilogger.h"
41 #include "indiutility.h"
42 
43 //#include <time.h>
44 
45 // Simulation Parameters
46 #define GOTO_RATE 5 // slew rate, degrees/s
47 #define SLEW_RATE 0.5 // slew rate, degrees/s
48 #define FINE_SLEW_RATE 0.1 // slew rate, degrees/s
49 #define GOTO_LIMIT 5.5 // Move at GOTO_RATE until distance from target is GOTO_LIMIT degrees
50 #define SLEW_LIMIT 1 // Move at SLEW_LIMIT until distance from target is SLEW_LIMIT degrees
51 #define FINE_SLEW_LIMIT 0.5 // Move at FINE_SLEW_RATE until distance from target is FINE_SLEW_LIMIT degrees
52 
53 #define MOUNTINFO_TAB "Mount Info"
54 
55 static std::unique_ptr<CelestronGPS> telescope(new CelestronGPS());
56 
58 {
59  setVersion(3, 6); // update libindi/drivers.xml as well
60 
61 
62  fwInfo.Version = "Invalid";
63  fwInfo.controllerVersion = 0;
65  fwInfo.isGem = false;
66  fwInfo.hasFocuser = false;
67 
68  INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
69 
70  currentRA = 0;
71  currentDEC = 90;
72  currentAZ = 0;
73  currentALT = 0;
74  targetAZ = 0;
75  targetALT = 0;
76 
77  // focuser
79 
80  // Set minimum properties.
81  // ISGetProperties in INDI::Telescope checks for CanGOTO which must be set.
83 }
84 
85 bool CelestronGPS::checkMinVersion(double minVersion, const char *feature, bool debug)
86 {
87  if (((fwInfo.controllerVariant == ISSTARSENSE) &&
88  (fwInfo.controllerVersion < MINSTSENSVER)) ||
89  ((fwInfo.controllerVariant == ISNEXSTAR) &&
90  (fwInfo.controllerVersion < minVersion)))
91  {
92  if (debug)
93  LOGF_DEBUG("Firmware v%3.2f does not support %s. Minimum required version is %3.2f",
94  fwInfo.controllerVersion, feature, minVersion);
95  else
96  LOGF_WARN("Firmware v%3.2f does not support %s. Minimum required version is %3.2f",
97  fwInfo.controllerVersion, feature, minVersion);
98 
99  return false;
100  }
101  return true;
102 }
103 
105 {
106  return "Celestron GPS";
107 }
108 
110 {
113 
114  // Firmware
115  IUFillText(&FirmwareT[FW_MODEL], "Model", "", nullptr);
116  IUFillText(&FirmwareT[FW_VERSION], "HC Version", "", nullptr);
117  IUFillText(&FirmwareT[FW_RA], "Ra Version", "", nullptr);
118  IUFillText(&FirmwareT[FW_DEC], "Dec Version", "", nullptr);
119  IUFillText(&FirmwareT[FW_ISGEM], "Mount Type", "", nullptr);
120  IUFillText(&FirmwareT[FW_CAN_AUX], "Guide Method", "", nullptr);
121  IUFillText(&FirmwareT[FW_HAS_FOC], "Has Focuser", "", nullptr);
122  IUFillTextVector(&FirmwareTP, FirmwareT, 7, getDeviceName(), "Firmware Info", "", MOUNTINFO_TAB, IP_RO, 0,
123  IPS_IDLE);
124 
125  // Celestron Track Modes are Off, AltAz, EQ N, EQ S and Ra and Dec (StarSense only)
126  // off is not provided as these are used to set the track mode when tracking is enabled
127  // may be required for set up, value will be read from the mount if possible
128  IUFillSwitchVector(&CelestronTrackModeSP, CelestronTrackModeS, 4, getDeviceName(), "CELESTRON_TRACK_MODE", "Track Mode",
130  IUFillSwitch(&CelestronTrackModeS[0], "MODE_ALTAZ", "Alt Az", ISS_OFF);
131  IUFillSwitch(&CelestronTrackModeS[1], "MODE_EQ_N", "EQ N", ISS_ON);
132  IUFillSwitch(&CelestronTrackModeS[2], "MODE_EQ_S", "EQ S", ISS_OFF);
133  IUFillSwitch(&CelestronTrackModeS[3], "MODE_RA_DEC", "Ra and Dec", ISS_OFF);
134 
135  // INDI track modes are sidereal, solar and lunar
136  AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
137  AddTrackMode("TRACK_SOLAR", "Solar");
138  AddTrackMode("TRACK_LUNAR", "Lunar");
139 
140  IUFillSwitch(&UseHibernateS[0], "Enable", "", ISS_OFF);
141  IUFillSwitch(&UseHibernateS[1], "Disable", "", ISS_ON);
143  ISR_1OFMANY, 0, IPS_IDLE);
144 
145  //GUIDE Define "Use Pulse Cmd" property (Switch).
146  // IUFillSwitch(&UsePulseCmdS[0], "Off", "", ISS_OFF);
147  // IUFillSwitch(&UsePulseCmdS[1], "On", "", ISS_ON);
148  // IUFillSwitchVector(&UsePulseCmdSP, UsePulseCmdS, 2, getDeviceName(), "Use Pulse Cmd", "", MAIN_CONTROL_TAB, IP_RW,
149  // ISR_1OFMANY, 0, IPS_IDLE);
150 
151  // experimental last align control
152  IUFillSwitchVector(&LastAlignSP, LastAlignS, 1, getDeviceName(), "Align", "Align", MAIN_CONTROL_TAB,
153  IP_WO, ISR_1OFMANY, 0, IPS_IDLE);
154  IUFillSwitch(&LastAlignS[0], "Align", "Align", ISS_OFF);
155  // maybe a second switch which confirms the align
156 
158 
159  //GUIDE Initialize guiding properties.
161 
166  IUFillNumber(&GuideRateN[AXIS_RA], "GUIDE_RATE_WE", "W/E Rate", "%0.2f", 0, 1, 0.1, GuideRateN[AXIS_RA].value);
167  IUFillNumber(&GuideRateN[AXIS_DE], "GUIDE_RATE_NS", "N/S Rate", "%0.2f", 0, 1, 0.1, GuideRateN[AXIS_DE].value);
168  IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guide Rate x sidereal", GUIDE_TAB, IP_RW, 0,
169  IPS_IDLE);
170 
174 
175  IUFillSwitch(&PecControlS[PEC_Seek], "PEC_SEEK_INDEX", "Seek Index", ISS_OFF);
176  IUFillSwitch(&PecControlS[PEC_Stop], "PEC_STOP", "Stop", ISS_OFF);
177  IUFillSwitch(&PecControlS[PEC_Playback], "PEC_PLAYBACK", "Playback", ISS_OFF);
178  IUFillSwitch(&PecControlS[PEC_Record], "PEC_RECORD", "Record", ISS_OFF);
179  IUFillSwitchVector(&PecControlSP, PecControlS, 4, getDeviceName(), "PEC_CONTROL", "PEC Control", MOTION_TAB, IP_RW,
180  ISR_ATMOST1, 60, IPS_IDLE);
181 
182  IUFillText(&PecInfoT[0], "PEC_STATE", "Pec State", "undefined");
183  IUFillText(&PecInfoT[1], "PEC_INDEX", "Pec Index", " ");
184  IUFillTextVector(&PecInfoTP, PecInfoT, 2, getDeviceName(), "PEC_INFO", "Pec Info", MOTION_TAB, IP_RO, 60, IPS_IDLE);
185 
186  // load Pec data from file
187  IUFillText(&PecFileNameT[0], "PEC_FILE_NAME", "File Name", "");
188  IUFillTextVector(&PecFileNameTP, PecFileNameT, 1, getDeviceName(), "PEC_LOAD", "Load PEC", MOTION_TAB, IP_WO, 60, IPS_IDLE);
189 
193 
194  IUFillSwitch(&DSTSettingS[0], "DST_ENABLED", "Enabled", ISS_OFF);
195  IUFillSwitchVector(&DSTSettingSP, DSTSettingS, 1, getDeviceName(), "DST_STATE", "DST", SITE_TAB, IP_RW, ISR_NOFMANY, 60,
196  IPS_IDLE);
197 
198  addAuxControls();
199 
200  //GUIDE Set guider interface.
202 
203  //FocuserInterface
204  //Initial, these will be updated later.
205  FocusRelPosN[0].min = 0.;
206  FocusRelPosN[0].max = 30000.;
207  FocusRelPosN[0].value = 0;
208  FocusRelPosN[0].step = 1000;
209  FocusAbsPosN[0].min = 0.;
210  FocusAbsPosN[0].max = 60000.;
211  FocusAbsPosN[0].value = 0;
212  FocusAbsPosN[0].step = 1000;
213 
214  // Maximum Position Settings, will be read from the hardware
215  FocusMaxPosN[0].max = 60000;
216  FocusMaxPosN[0].min = 1000;
217  FocusMaxPosN[0].value = 60000;
219 
220  // Focuser backlash
221  // CR this is a value, positive or negative to define the direction. It is implemented
222  // in the driver.
223 
224  FocusBacklashN[0].min = -1000;
225  FocusBacklashN[0].max = 1000;
226  FocusBacklashN[0].step = 1;
227  FocusBacklashN[0].value = 0;
228  // IUFillNumber(&FocusBacklashN[0], "STEPS", "Steps", "%.f", -500., 500, 1., 0.);
229  // IUFillNumberVector(&FocusBacklashNP, FocusBacklashN, 1, getDeviceName(), "FOCUS_BACKLASH", "Backlash",
230  // FOCUS_TAB, IP_RW, 0, IPS_IDLE);
231 
232  // Focuser min limit, read from the hardware
233  IUFillNumber(&FocusMinPosN[0], "FOCUS_MIN_VALUE", "Steps", "%.f", 0, 40000., 1., 0.);
234  IUFillNumberVector(&FocusMinPosNP, FocusMinPosN, 1, getDeviceName(), "FOCUS_MIN", "Min. Position",
235  FOCUS_TAB, IP_RO, 0, IPS_IDLE);
236 
237  return true;
238 }
239 
240 void CelestronGPS::ISGetProperties(const char *dev)
241 {
242  static bool configLoaded = false;
243 
244  if (dev != nullptr && strcmp(dev, getDeviceName()) != 0)
245  return;
246 
248 
251  if (configLoaded == false)
252  {
253  configLoaded = true;
254  loadConfig(true, "Hibernate");
255  }
256 }
257 
259 {
260  if (isConnected())
261  {
262  uint32_t cap = TELESCOPE_CAN_GOTO | TELESCOPE_CAN_ABORT;
263 
264  if (driver.get_firmware(&fwInfo))
265  {
266  IUSaveText(&FirmwareT[FW_MODEL], fwInfo.Model.c_str());
267  IUSaveText(&FirmwareT[FW_VERSION], fwInfo.Version.c_str());
268  IUSaveText(&FirmwareT[FW_RA], fwInfo.RAFirmware.c_str());
269  IUSaveText(&FirmwareT[FW_DEC], fwInfo.DEFirmware.c_str());
270  IUSaveText(&FirmwareT[FW_ISGEM], fwInfo.isGem ? "GEM" : "Fork");
271  canAuxGuide = (atof(fwInfo.RAFirmware.c_str()) >= 6.12 && atof(fwInfo.DEFirmware.c_str()) >= 6.12);
272  IUSaveText(&FirmwareT[FW_CAN_AUX], canAuxGuide ? "Mount" : "Time Guide");
273  IUSaveText(&FirmwareT[FW_HAS_FOC], fwInfo.hasFocuser ? "True" : "False");
274 
275  usePreciseCoords = (checkMinVersion(2.2, "usePreciseCoords"));
276  // set the default switch index, will be updated from the mount if possible
278  }
279  else
280  {
281  fwInfo.Version = "Invalid";
282  LOG_WARN("Failed to retrieve firmware information.");
283  }
284 
285  // JM 2018-09-28: According to user reports in this thread:
286  // http://www.indilib.org/forum/mounts/2208-celestron-avx-mount-and-starsense.html
287  // Parking is also supported fine with StarSense
288  if (checkMinVersion(2.3, "park"))
289  cap |= TELESCOPE_CAN_PARK;
290 
291  if (checkMinVersion(4.1, "sync"))
292  cap |= TELESCOPE_CAN_SYNC;
293 
294  if (checkMinVersion(2.3, "updating time and location settings"))
295  {
297  }
298 
299  // changing track mode (aka rate) is only available for equatorial mounts
300 
301  // StarSense supports track mode
302  if (checkMinVersion(2.3, "track on/off"))
304  else
305  LOG_WARN("Mount firmware does not support track on off.");
306 
307  if (fwInfo.isGem && checkMinVersion(4.15, "Pier Side", true))
309  else
310  LOG_WARN("Mount firmware does not support getting pier side.");
311 
312  // Track Mode (t) is only supported for 2.3+
314  if (checkMinVersion(2.3, "track mode"))
315  {
316  if (isSimulation())
317  {
318  if (isParked())
319  driver.set_sim_track_mode(CTM_OFF);
320  else
321  driver.set_sim_track_mode(CTM_EQN);
322  }
323  if (driver.get_track_mode(&ctm))
324  {
325  if (ctm != CTM_OFF)
326  {
327  fwInfo.celestronTrackMode = ctm;
329  CelestronTrackModeS[ctm - 1].s = ISS_ON;
331 
332  saveConfig(true, "CELESTRON_TRACK_MODE");
333  LOGF_DEBUG("Celestron mount tracking, mode %s", CelestronTrackModeS[ctm - 1].label);
334  }
335  else
336  {
337  LOG_INFO("Mount tracking is off.");
339  }
340  }
341  else
342  {
343  LOG_DEBUG("get_track_mode failed");
345  }
346 
348  }
349 
350  if (fwInfo.celestronTrackMode != CTM_ALTAZ)
352  else
353  {
355  LOG_WARN("Mount firmware does not support track mode.");
356  }
357 
358  SetTelescopeCapability(cap, 9);
359 
361 
362  if (fwInfo.Version != "Invalid")
364 
365  if (InitPark())
366  {
367  // If loading parking data is successful, we just set the default parking values.
368  SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
370  }
371  else
372  {
373  // Otherwise, we set all parking data to default in case no parking data is found.
374  SetAxis1Park(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
376  SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
378  }
379 
380  // InitPark sets TrackState to IDLE or PARKED so this is the earliest we can
381  // update TrackState using the current mount properties
382  // Something seems to set IsParked to true, force the correct state if the
383  // mount is tracking
384  if (ctm != CTM_OFF)
385  {
386  SetParked(false);
388  }
389 
390  //GUIDE Update properties.
391  // check if the mount type and version supports guiding
392  // Only show the guide information for mounts that
393  // support guiding. That's GEMs and fork mounts in equatorial modes.
394  // well, anything in an equatorial mode
398  {
400  uint8_t rate;
401  if (driver.get_guide_rate(CELESTRON_AXIS::RA_AXIS, &rate))
402  {
403  GuideRateN[AXIS_RA].value = std::min(std::max(static_cast<double>(rate) / 255.0, 0.0), 1.0);
404  LOGF_DEBUG("Get Guide Rate: RA %f", GuideRateN[AXIS_RA].value);
405  if (driver.get_guide_rate(CELESTRON_AXIS::DEC_AXIS, &rate))
406  {
407  GuideRateN[AXIS_DE].value = std::min(std::max(static_cast<double>(rate) / 255.0, 0.0), 1.0);
408  IDSetNumber(&GuideRateNP, nullptr);
409  LOGF_DEBUG("Get Guide Rate: Dec %f", GuideRateN[AXIS_DE].value);
410  }
411  }
412  else
413  LOG_DEBUG("Unable to get guide rates from mount.");
414 
417 
418  LOG_INFO("Mount supports guiding.");
419  }
420  else
421  LOG_INFO("Mount does not support guiding. Tracking mode must be set in handset to either EQ-North or EQ-South.");
422 
423 
425 
426  // JM 2014-04-14: User (davidw) reported AVX mount serial communication times out issuing "h" command with firmware 5.28
427  // JM 2018-09-27: User (suramara) reports that it works with AVX mount with Star Sense firmware version 1.19
428  //if (fwInfo.controllerVersion >= 2.3 && fwInfo.Model != "AVX" && fwInfo.Model != "CGE Pro")
429  if (checkMinVersion(2.3, "date and time setting"))
430  {
431  double utc_offset;
432  int yy, dd, mm, hh, minute, ss;
433  bool dst;
434  // StarSense doesn't seems to handle the precise time commands
435  bool precise = fwInfo.controllerVersion >= 5.28;
436  if (driver.get_utc_date_time(&utc_offset, &yy, &mm, &dd, &hh, &minute, &ss, &dst, precise))
437  {
438  char isoDateTime[32];
439  char utcOffset[8];
440 
441  snprintf(isoDateTime, 32, "%04d-%02d-%02dT%02d:%02d:%02d", yy, mm, dd, hh, minute, ss);
442  snprintf(utcOffset, 8, "%4.2f", utc_offset);
443 
444  IUSaveText(IUFindText(&TimeTP, "UTC"), isoDateTime);
445  IUSaveText(IUFindText(&TimeTP, "OFFSET"), utcOffset);
446 
447  defineProperty(&DSTSettingSP);
448  DSTSettingS[0].s = dst ? ISS_ON : ISS_OFF;
449 
450  LOGF_INFO("Mount UTC offset: %s. UTC time: %s. DST: %s", utcOffset, isoDateTime, dst ? "On" : "Off");
451  //LOGF_DEBUG("Mount UTC offset is %s. UTC time is %s", utcOffset, isoDateTime);
452 
453  TimeTP.s = IPS_OK;
454  IDSetText(&TimeTP, nullptr);
455  IDSetSwitch(&DSTSettingSP, nullptr);
456  }
457  double longitude, latitude;
458  if (driver.get_location(&longitude, &latitude))
459  {
460  LocationNP.np[LOCATION_LATITUDE].value = latitude;
461  LocationNP.np[LOCATION_LONGITUDE].value = longitude;
462  LocationNP.np[LOCATION_ELEVATION].value = 0;
463  LocationNP.s = IPS_OK;
464  LOGF_DEBUG("Mount latitude %8.4f longitude %8.4f", latitude, longitude);
465  }
466  }
467  else
468  LOG_WARN("Mount does not support retrieval of date, time and location.");
469 
470  // last align is only available for mounts with switches that define the start index position
471  // At present that is only the CGX and CGX-L mounts so the control is only made available for them
472  // comment out this line and rebuild if you want to run with other mounts - at your own risk!
473  if (fwInfo.hasHomeIndex)
474  {
475  defineProperty(&LastAlignSP);
476  }
477 
478  // Sometimes users start their mount when it is NOT yet aligned and then try to proceed to use it
479  // So we check issue and issue error if not aligned.
480  checkAlignment();
481 
482  // PEC, must have PEC index and be equatorially mounted
483  if (fwInfo.canPec && CelestronTrackModeS[CTM_ALTAZ].s != ISS_OFF)
484  {
489  }
490 
491  // handle the focuser
492  if (fwInfo.hasFocuser)
493  {
494  //defineProperty(&FocusBacklashNP);
495  defineProperty(&FocusMinPosNP);
496  if (focusReadLimits())
497  {
499 
500  IDSetNumber(&FocusMaxPosNP, nullptr);
501  IDSetNumber(&FocusMinPosNP, nullptr);
502  // focuser move capability is only set if the focus limits are valid
505  syncDriverInfo();
506 
507  LOG_INFO("Auxiliary focuser is connected.");
508  }
509  if (!focuserIsCalibrated)
510  {
511  LOG_WARN("Focuser not calibrated, moves will not be allowed");
512  }
514  }
515  }
516  else // not connected
517  {
519 
521  deleteProperty(FocusMinPosNP.name);
522 
523  //GUIDE Delete properties.
526 
528 
529  deleteProperty(LastAlignSP.name);
531 
532  deleteProperty(DSTSettingSP.name);
533 
537 
538  if (fwInfo.Version != "Invalid")
540  }
541 
542  return true;
543 }
544 
545 bool CelestronGPS::Goto(double ra, double dec)
546 {
547  targetRA = ra;
548  targetDEC = dec;
549 
551  {
552  driver.abort();
553  // sleep for 500 mseconds
554  usleep(500000);
555  }
556 
557  if (driver.slew_radec(targetRA + SlewOffsetRa, targetDEC, usePreciseCoords) == false)
558  {
559  LOG_ERROR("Failed to slew telescope in RA/DEC.");
560  return false;
561  }
562 
564 
565  char RAStr[32], DecStr[32];
566  fs_sexa(RAStr, targetRA, 2, 3600);
567  fs_sexa(DecStr, targetDEC, 2, 3600);
568  LOGF_INFO("Slewing to JNOW RA %s - DEC %s SlewOffsetRa %4.1f arcsec", RAStr, DecStr, SlewOffsetRa * 3600 * 15);
569 
570  return true;
571 }
572 
573 bool CelestronGPS::Sync(double ra, double dec)
574 {
575  if (!checkMinVersion(4.1, "sync"))
576  return false;
577 
578  if (driver.sync(ra, dec, usePreciseCoords) == false)
579  {
580  LOG_ERROR("Sync failed.");
581  return false;
582  }
583 
584  currentRA = ra;
585  currentDEC = dec;
586 
587  char RAStr[32], DecStr[32];
588  fs_sexa(RAStr, targetRA, 2, 3600);
589  fs_sexa(DecStr, targetDEC, 2, 3600);
590  LOGF_INFO("Sync to %s, %s successful.", RAStr, DecStr);
591 
592  return true;
593 }
594 
595 /*
596 bool CelestronGPS::GotoAzAlt(double az, double alt)
597 {
598  if (isSimulation())
599  {
600  INDI::IHorizontalCoordinates horizontalPos;
601  // Libnova south = 0, west = 90, north = 180, east = 270
602  horizontalPos.az = az + 180;
603  if (horizontalPos.az >= 360)
604  horizontalPos.az -= 360;
605  horizontalPos.alt = alt;
606 
607  IGeographicCoordinates observer;
608 
609  observer.lat = LocationN[LOCATION_LATITUDE].value;
610  observer.lng = LocationN[LOCATION_LONGITUDE].value;
611 
612  if (observer.lng > 180)
613  observer.lng -= 360;
614 
615  INDI::IEquatorialCoordinates equatorialPos;
616  ln_get_equ_from_hrz(&horizontalPos, &observer, ln_get_julian_from_sys(), &equatorialPos);
617 
618  targetRA = equatorialPos.rightascension/15.0;
619  targetDEC = equatorialPos.dec;
620  }
621 
622  if (driver.slew_azalt(LocationN[LOCATION_LATITUDE].value, az, alt) == false)
623  {
624  LOG_ERROR("Failed to slew telescope in Az/Alt.");
625  return false;
626  }
627 
628  targetAZ = az;
629  targetALT= alt;
630 
631  TrackState = SCOPE_SLEWING;
632 
633  HorizontalCoordsNP.s = IPS_BUSY;
634 
635  char AZStr[16], ALTStr[16];
636  fs_sexa(AZStr, targetAZ, 3, 3600);
637  fs_sexa(ALTStr, targetALT, 2, 3600);
638  LOGF_INFO("Slewing to Az %s - Alt %s", AZStr, ALTStr);
639 
640  return true;
641 }
642 */
643 
645 {
646  CELESTRON_DIRECTION move;
647 
648  if (currentPierSide == PIER_WEST)
649  move = (dir == DIRECTION_NORTH) ? CELESTRON_N : CELESTRON_S;
650  else
651  move = (dir == DIRECTION_NORTH) ? CELESTRON_S : CELESTRON_N;
652 
654 
655  switch (command)
656  {
657  case MOTION_START:
658  if (driver.start_motion(move, rate) == false)
659  {
660  LOG_ERROR("Error setting N/S motion direction.");
661  return false;
662  }
663  else
664  LOGF_INFO("Moving toward %s.", (move == CELESTRON_N) ? "North" : "South");
665  break;
666 
667  case MOTION_STOP:
668  if (driver.stop_motion(move) == false)
669  {
670  LOG_ERROR("Error stopping N/S motion.");
671  return false;
672  }
673  else
674  LOGF_INFO("Movement toward %s halted.", (move == CELESTRON_N) ? "North" : "South");
675  break;
676  }
677 
678  return true;
679 }
680 
682 {
685 
686  switch (command)
687  {
688  case MOTION_START:
689  if (driver.start_motion(move, rate) == false)
690  {
691  LOG_ERROR("Error setting W/E motion direction.");
692  return false;
693  }
694  else
695  LOGF_INFO("Moving toward %s.", (move == CELESTRON_W) ? "West" : "East");
696  break;
697 
698  case MOTION_STOP:
699  if (driver.stop_motion(move) == false)
700  {
701  LOG_ERROR("Error stopping W/E motion.");
702  return false;
703  }
704  else
705  LOGF_INFO("Movement toward %s halted.", (move == CELESTRON_W) ? "West" : "East");
706  break;
707  }
708 
709  return true;
710 }
711 
713 {
715 
716  if (isSimulation())
717  mountSim();
718 
719  if (driver.get_radec(&currentRA, &currentDEC, usePreciseCoords) == false)
720  {
721  LOG_ERROR("Failed to read RA/DEC values.");
722  return false;
723  }
724 
725  if (HasPierSide())
726  {
727  // read the pier side close to reading the Radec so they should match
728  char sop;
729  char psc = 'u';
730  if (driver.get_pier_side(&sop))
731  {
732  // manage version and hemisphere nonsense
733  // HC versions less than 5.24 reverse the side of pier if the mount
734  // is in the Southern hemisphere. StarSense doesn't
735  if (LocationN[LOCATION_LATITUDE].value < 0)
736  {
737  if (fwInfo.controllerVersion <= 5.24 && fwInfo.controllerVariant != ISSTARSENSE)
738  {
739  // swap the char reported
740  if (sop == 'E')
741  sop = 'W';
742  else if (sop == 'W')
743  sop = 'E';
744  }
745  }
746  // The Celestron and INDI pointing states are opposite
747  if (sop == 'W')
748  {
749  pierSide = PIER_EAST;
750  psc = 'E';
751  }
752  else if (sop == 'E')
753  {
754  pierSide = PIER_WEST;
755  psc = 'W';
756  }
757  // pier side and Ha don't match at +-90 deg dec
758  if (currentDEC > 89.999 || currentDEC < -89.999)
759  {
760  pierSide = PIER_UNKNOWN;
761  psc = 'U';
762  }
763  }
764 
765  LOGF_DEBUG("latitude %g, sop %c, PierSide %c",
767  sop, psc);
768  }
769 
770 
771  // aligning
772  if (slewToIndex)
773  {
774  bool atIndex;
775  if (!driver.indexreached(&atIndex))
776  {
777  LOG_ERROR("IndexReached Failure");
778  slewToIndex = false;
779  return false;
780  }
781  if (atIndex)
782  {
783  slewToIndex = false;
784  // reached the index position.
785 
786  // do an alignment
787  if (!fwInfo.hasHomeIndex)
788  {
789  // put another dire warning here
790  LOG_WARN("This mount does not have index switches, the alignment assumes it is at the index position.");
791  }
792 
793  if (!driver.lastalign())
794  {
795  LOG_ERROR("LastAlign failed");
796  return false;
797  }
798 
799  LastAlignSP.s = IPS_IDLE;
800  IDSetSwitch(&LastAlignSP, "Align finished");
801 
802  bool isAligned;
803  if (!driver.check_aligned(&isAligned))
804  {
805  LOG_WARN("get Alignment Failed!");
806  }
807  else
808  {
809  if (isAligned)
810  LOG_INFO("Mount is aligned");
811  else
812  LOG_WARN("Alignment Failed!");
813  }
814 
815  return true;
816  }
817  }
818 
819  switch (TrackState)
820  {
821  case SCOPE_SLEWING:
822  // are we done?
823  bool slewing;
824  if (driver.is_slewing(&slewing) && !slewing)
825  {
826  LOG_INFO("Slew complete, tracking...");
827  SetTrackEnabled(true);
828  // update ra offset
829  double raoffset = targetRA - currentRA + SlewOffsetRa;
830  if (raoffset > 0.0 || raoffset < 10.0 / 3600.0)
831  {
832  // average last two values
833  SlewOffsetRa = SlewOffsetRa > 0 ? (SlewOffsetRa + raoffset) / 2 : raoffset;
834 
835  LOGF_DEBUG("raoffset %4.1f, SlewOffsetRa %4.1f arcsec", raoffset * 3600 * 15, SlewOffsetRa * 3600 * 15);
836  }
837  }
838  break;
839 
840  case SCOPE_PARKING:
841  // are we done?
842  if (driver.is_slewing(&slewing) && !slewing)
843  {
844  if (driver.set_track_mode(CTM_OFF))
845  LOG_DEBUG("Mount tracking is off.");
846 
847  SetParked(true);
848  saveConfig(true);
849 
850  // Check if we need to hibernate
851  if (UseHibernateS[0].s == ISS_ON)
852  {
853  LOG_INFO("Hibernating mount...");
854  if (driver.hibernate())
855  LOG_INFO("Mount hibernated. Please disconnect now and turn off your mount.");
856  else
857  LOG_ERROR("Hibernating mount failed!");
858  }
859  }
860  break;
861 
862  default:
863  break;
864  }
865 
866  // update pier side and RaDec close together to minimise the possibility of
867  // a mismatch causing an Ha limit error during a pier flip slew.
868  if (HasPierSide())
869  setPierSide(pierSide);
870  NewRaDec(currentRA, currentDEC);
871 
872  // is PEC Handling required
873  if (driver.pecState >= PEC_STATE::PEC_AVAILABLE)
874  {
875  static PEC_STATE lastPecState = PEC_STATE::NotKnown;
876  static size_t lastPecIndex = 1000;
877  static size_t numRecordPoints;
878 
879  if (driver.pecState >= PEC_STATE::PEC_INDEXED)
880  {
881  if (numPecBins < 88)
882  {
883  numPecBins = driver.getPecNumBins();
884  }
885  // get and show the current PEC index
886  size_t pecIndex = driver.pecIndex();
887 
888  if (pecIndex != lastPecIndex)
889  {
890  LOGF_DEBUG("PEC state %s, index %d", driver.PecStateStr(), pecIndex);
891  IUSaveText(&PecInfoT[1], std::to_string(pecIndex).c_str());
892  IDSetText(&PecInfoTP, nullptr);
893  lastPecIndex = pecIndex;
894 
895  // count the PEC records
896  if (driver.pecState == PEC_STATE::PEC_RECORDING)
897  numRecordPoints++;
898  else
899  numRecordPoints = 0;
900  }
901  }
902 
903  // update the PEC state
904  if (driver.updatePecState() != lastPecState)
905  {
906  // and handle the change, if there was one
907  LOGF_DEBUG("PEC last state %s, new State %s", driver.PecStateStr(lastPecState), driver.PecStateStr());
908 
909  // update the state string
910  IUSaveText(&PecInfoT[0], driver.PecStateStr());
911  IDSetText(&PecInfoTP, nullptr);
912 
913  // no need to check both current and last because they must be different
914  switch (lastPecState)
915  {
917  // finished seeking
920  IDSetSwitch(&PecControlSP, nullptr);
921  LOG_INFO("PEC index Seek completed.");
922  break;
924  // finished playback
927  IDSetSwitch(&PecControlSP, nullptr);
928  LOG_INFO("PEC playback finished");
929  break;
931  // finished recording
932  LOGF_DEBUG("PEC record stopped, %d records", numRecordPoints);
933 
934  if (numRecordPoints >= numPecBins)
935  {
936  savePecData();
937  }
938 
941  LOG_INFO("PEC record finished");
942  IDSetSwitch(&PecControlSP, nullptr);
943 
944  break;
945  default:
946  break;
947  }
948  lastPecState = driver.pecState;
949  }
950  }
951 
952  // focuser
953  if (fwInfo.hasFocuser)
954  {
955  // Check position
956  double lastPosition = FocusAbsPosN[0].value;
957 
958  int pos = driver.foc_position();
959  if (pos >= 0)
960  {
961  FocusAbsPosN[0].value = pos;
962  // Only update if there is actual change
963  if (fabs(lastPosition - FocusAbsPosN[0].value) > 1)
964  IDSetNumber(&FocusAbsPosNP, nullptr);
965  }
966 
968  {
969  // The backlash handling is done here, if the move state
970  // shows that a backlash move has been done then the final move needs to be started
971  // and the states left at IPS_BUSY
972 
973  if (!driver.foc_moving())
974  {
975  if (focusBacklashMove)
976  {
977  focusBacklashMove = false;
978  if (driver.foc_move(focusPosition))
979  LOGF_INFO("Focus final move %i", focusPosition);
980  else
981  LOG_INFO("Backlash move failed");
982  }
983  else
984  {
987  IDSetNumber(&FocusAbsPosNP, nullptr);
988  IDSetNumber(&FocusRelPosNP, nullptr);
989  LOG_INFO("Focuser reached requested position.");
990  }
991  }
992  }
993  }
994 
995  return true;
996 }
997 
999 {
1000  driver.stop_motion(CELESTRON_N);
1001  driver.stop_motion(CELESTRON_S);
1002  driver.stop_motion(CELESTRON_W);
1003  driver.stop_motion(CELESTRON_E);
1004 
1005  //GUIDE Abort guide operations.
1006  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
1007  {
1009  GuideNSN[0].value = GuideNSN[1].value = 0.0;
1010  GuideWEN[0].value = GuideWEN[1].value = 0.0;
1011 
1012  if (GuideNSTID)
1013  {
1015  GuideNSTID = 0;
1016  }
1017 
1018  if (GuideWETID)
1019  {
1021  GuideWETID = 0;
1022  }
1023 
1024  LOG_INFO("Guide aborted.");
1025  IDSetNumber(&GuideNSNP, nullptr);
1026  IDSetNumber(&GuideWENP, nullptr);
1027 
1028  return true;
1029  }
1030 
1031  return driver.abort();
1032 }
1033 
1035 {
1036  driver.set_device(getDeviceName());
1037  driver.set_port_fd(PortFD);
1038 
1039  if (isSimulation())
1040  {
1041  driver.set_simulation(true);
1042  driver.set_sim_slew_rate(SR_5);
1043  driver.set_sim_ra(0);
1044  driver.set_sim_dec(90);
1045  }
1046 
1047  if (driver.check_connection() == false)
1048  {
1049  LOG_ERROR("Failed to communicate with the mount, check the logs for details.");
1050  return false;
1051  }
1052 
1053  return true;
1054 }
1055 
1056 bool CelestronGPS::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1057 {
1058  if (dev && std::string(getDeviceName()) == dev)
1059  {
1060  // Enable/Disable hibernate
1061  if (name && std::string(name) == UseHibernateSP.name)
1062  {
1063  IUUpdateSwitch(&UseHibernateSP, states, names, n);
1064  if (fwInfo.controllerVersion > 0)
1065  {
1066  if (UseHibernateS[0].s == ISS_ON && (checkMinVersion(4.22, "hibernation", true) == false))
1067  {
1068  UseHibernateS[0].s = ISS_OFF;
1069  UseHibernateS[1].s = ISS_ON;
1071  }
1072  else
1074  }
1075  IDSetSwitch(&UseHibernateSP, nullptr);
1076  return true;
1077  }
1078 
1079  // start a last align
1080  // the process is:
1081  // start move to switch position
1082  // wait for the move to finish
1083  // set the time from the PC - maybe
1084  // send a Last Align command "Y"
1085 
1086 
1087  if (name && std::string(name) == LastAlignSP.name)
1088  {
1089  if (!fwInfo.hasHomeIndex)
1090  {
1091  // put the dire warning here
1092  LOG_WARN("This mount does not have index switches, make sure that it is at the index position.");
1093  }
1094  LOG_DEBUG("Start Align");
1095  // start move to switch positions
1096  if (!driver.startmovetoindex())
1097  {
1098  LastAlignSP.s = IPS_ALERT;
1099  return false;
1100  }
1101  // wait for the move to finish
1102  // done in ReadScopeStatus
1103  slewToIndex = true;
1104  LastAlignSP.s = IPS_BUSY;
1105  IDSetSwitch(&LastAlignSP, "Align in progress");
1106  return true;
1107  }
1108 
1109  // handle the PEC commands
1110  if (name && std::string(name) == PecControlSP.name)
1111  {
1112  IUUpdateSwitch(&PecControlSP, states, names, n);
1113  int idx = IUFindOnSwitchIndex(&PecControlSP);
1114 
1115  switch(idx)
1116  {
1117  case PEC_Stop:
1118  LOG_DEBUG(" stop PEC record or playback");
1119  bool playback;
1120  if ((playback = driver.pecState == PEC_PLAYBACK) || driver.pecState == PEC_RECORDING)
1121  {
1122  if (playback ? driver.PecPlayback(false) : driver.PecRecord(false))
1123  {
1125  }
1126  else
1127  {
1129  }
1130  }
1131  else
1132  {
1133  LOG_WARN("Incorrect state to stop PEC Playback or Record");
1135  }
1137  break;
1138  case PEC_Playback:
1139  LOG_DEBUG("start PEC Playback");
1140  if (driver.pecState == PEC_STATE::PEC_INDEXED)
1141  {
1142  // start playback
1143  if (driver.PecPlayback(true))
1144  {
1146  LOG_INFO("PEC Playback started");
1147  }
1148  else
1149  {
1151  return false;
1152  }
1153  }
1154  else
1155  {
1156  LOG_WARN("Incorrect state to start PEC Playback");
1157  }
1158  break;
1159  case PEC_Record:
1160  LOG_DEBUG("start PEC record");
1161  if (TrackState != TelescopeStatus::SCOPE_TRACKING)
1162  {
1163  LOG_WARN("Mount must be Tracking to record PEC");
1164  break;
1165  }
1166  if (driver.pecState == PEC_STATE::PEC_INDEXED)
1167  {
1168  if (driver.PecRecord(true))
1169  {
1171  LOG_INFO("PEC Record started");
1172  }
1173  else
1174  {
1176  return false;
1177  }
1178  }
1179  else
1180  {
1181  LOG_WARN("Incorrect state to start PEC Recording");
1182  }
1183  break;
1184  case PEC_Seek:
1185  LOG_DEBUG("Seek PEC Index");
1186  if (driver.isPecAtIndex(true))
1187  {
1188  LOG_INFO("PEC index already found");
1190  }
1191  else if (driver.pecState == PEC_STATE::PEC_AVAILABLE)
1192  {
1193  // start seek, moves up to 2 degrees in Ra
1194  if (driver.PecSeekIndex())
1195  {
1197  LOG_INFO("Seek PEC index started");
1198  }
1199  else
1200  {
1202  return false;
1203  }
1204  }
1205  break;
1206  }
1207  IDSetSwitch(&PecControlSP, nullptr);
1208  return true;
1209  }
1210 
1211  // Focuser
1212  if (strstr(name, "FOCUS"))
1213  {
1214  return FI::processSwitch(dev, name, states, names, n);
1215  }
1216  }
1217 
1218  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
1219 }
1220 
1221 bool CelestronGPS::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1222 {
1223  if (dev && std::string(dev) == getDeviceName())
1224  {
1225  // Guide Rate
1226  if (strcmp(name, "GUIDE_RATE") == 0)
1227  {
1228  IUUpdateNumber(&GuideRateNP, values, names, n);
1229  GuideRateNP.s = IPS_OK;
1230  IDSetNumber(&GuideRateNP, nullptr);
1231  uint8_t grRa = static_cast<uint8_t>(std::min(GuideRateN[AXIS_RA].value * 256.0, 255.0));
1232  uint8_t grDec = static_cast<uint8_t>(std::min(GuideRateN[AXIS_DE].value * 256.0, 255.0));
1233  //LOGF_DEBUG("Set Guide Rates (0-1x sidereal): Ra %f, Dec %f", GuideRateN[AXIS_RA].value, GuideRateN[AXIS_DE].value);
1234  //LOGF_DEBUG("Set Guide Rates (0-255): Ra %i, Dec %i", grRa, grDec);
1235  LOGF_DEBUG("Set Guide Rates: Ra %f, Dec %f", GuideRateN[AXIS_RA].value, GuideRateN[AXIS_DE].value);
1238  LOG_WARN("Changing guide rates may require recalibration of guiding.");
1239  return true;
1240  }
1241 
1242  //GUIDE process Guider properties.
1243  processGuiderProperties(name, values, names, n);
1244 
1245  if (strstr(name, "FOCUS_"))
1246  {
1247  return FI::processNumber(dev, name, values, names, n);
1248  }
1249  }
1250 
1251  INDI::Telescope::ISNewNumber(dev, name, values, names, n);
1252  return true;
1253 }
1254 
1255 bool CelestronGPS::ISNewText(const char *dev, const char *name, char **texts, char **names, int n)
1256 {
1257  // the idea is that pressing "Set" on the PEC_LOAD text will load the data in the file specified in the text
1258  if (dev && std::string(dev) == getDeviceName())
1259  {
1260  LOGF_DEBUG("ISNewText name %s, text %s, names %s, n %d", name, texts[0], names[0], n);
1261 
1262  if (name && std::string(name) == "PEC_LOAD")
1263  {
1264 
1265  IUUpdateText(&PecFileNameTP, texts, names, n);
1266  IDSetText(&PecFileNameTP, nullptr);
1267 
1268  LOGF_DEBUG("PEC Set %s", PecFileNameT[0].text);
1269 
1270  PecData pecData;
1271 
1272  // load from file
1273  if (!pecData.Load(PecFileNameT[0].text))
1274  {
1275  LOGF_WARN("File %s load failed", PecFileNameT[0].text);
1276  return false;
1277  }
1278  // save to mount
1279  if (!pecData.Save(&driver))
1280  {
1281  LOGF_WARN("PEC Data file %s save to mount failed", PecFileNameT[0].text);
1282  return false;
1283  }
1284  LOGF_INFO("PEC Data file %s sent to mount", PecFileNameT[0].text);
1285  }
1286  }
1287 
1288  INDI::Telescope::ISNewText(dev, name, texts, names, n);
1289  return true;
1290 }
1291 
1292 
1294 {
1295  // Just update the number
1296  INDI_UNUSED(steps);
1297  return true;
1298 }
1299 
1301 {
1302  static struct timeval ltv;
1303  struct timeval tv;
1304  double dt, dx, da_ra = 0, da_dec = 0;
1305  int nlocked;
1306 
1307  // update elapsed time since last poll, don't presume exactly POLLMS
1308  gettimeofday(&tv, nullptr);
1309 
1310  if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1311  ltv = tv;
1312 
1313  dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1314  ltv = tv;
1315 
1316  if (fabs(targetRA - currentRA) * 15. >= GOTO_LIMIT)
1317  da_ra = GOTO_RATE * dt;
1318  else if (fabs(targetRA - currentRA) * 15. >= SLEW_LIMIT)
1319  da_ra = SLEW_RATE * dt;
1320  else
1321  da_ra = FINE_SLEW_RATE * dt;
1322 
1323  if (fabs(targetDEC - currentDEC) >= GOTO_LIMIT)
1324  da_dec = GOTO_RATE * dt;
1325  else if (fabs(targetDEC - currentDEC) >= SLEW_LIMIT)
1326  da_dec = SLEW_RATE * dt;
1327  else
1328  da_dec = FINE_SLEW_RATE * dt;
1329 
1331  {
1332  int rate = IUFindOnSwitchIndex(&SlewRateSP);
1333 
1334  switch (rate)
1335  {
1336  case SLEW_GUIDE:
1337  da_ra = FINE_SLEW_RATE * dt * 0.05;
1338  da_dec = FINE_SLEW_RATE * dt * 0.05;
1339  break;
1340 
1341  case SLEW_CENTERING:
1342  da_ra = FINE_SLEW_RATE * dt * .1;
1343  da_dec = FINE_SLEW_RATE * dt * .1;
1344  break;
1345 
1346  case SLEW_FIND:
1347  da_ra = SLEW_RATE * dt;
1348  da_dec = SLEW_RATE * dt;
1349  break;
1350 
1351  default:
1352  da_ra = GOTO_RATE * dt;
1353  da_dec = GOTO_RATE * dt;
1354  break;
1355  }
1356 
1357  switch (MovementNSSP.s)
1358  {
1359  case IPS_BUSY:
1361  currentDEC += da_dec;
1362  else if (MovementNSS[DIRECTION_SOUTH].s == ISS_ON)
1363  currentDEC -= da_dec;
1364  break;
1365 
1366  default:
1367  break;
1368  }
1369 
1370  switch (MovementWESP.s)
1371  {
1372  case IPS_BUSY:
1373  if (MovementWES[DIRECTION_WEST].s == ISS_ON)
1374  currentRA += da_ra / 15.;
1375  else if (MovementWES[DIRECTION_EAST].s == ISS_ON)
1376  currentRA -= da_ra / 15.;
1377  break;
1378 
1379  default:
1380  break;
1381  }
1382 
1383  driver.set_sim_ra(currentRA);
1384  driver.set_sim_dec(currentDEC);
1385 
1387 
1388  return;
1389  }
1390 
1391  // Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly
1392  switch (TrackState)
1393  {
1394  case SCOPE_IDLE:
1395  currentRA = driver.get_sim_ra() + (TRACKRATE_SIDEREAL / 3600.0 * dt) / 15.0;
1397  break;
1398 
1399  case SCOPE_SLEWING:
1400  case SCOPE_PARKING:
1401  // slewing - nail it when both within one pulse @ SLEWRATE
1402  nlocked = 0;
1403 
1404  dx = targetRA - currentRA;
1405 
1406  // Take shortest path
1407  if (fabs(dx) > 12)
1408  dx *= -1;
1409 
1410  if (fabs(dx) <= da_ra)
1411  {
1412  currentRA = targetRA;
1413  nlocked++;
1414  }
1415  else if (dx > 0)
1416  currentRA += da_ra / 15.;
1417  else
1418  currentRA -= da_ra / 15.;
1419 
1420  if (currentRA < 0)
1421  currentRA += 24;
1422  else if (currentRA > 24)
1423  currentRA -= 24;
1424 
1425  dx = targetDEC - currentDEC;
1426  if (fabs(dx) <= da_dec)
1427  {
1428  currentDEC = targetDEC;
1429  nlocked++;
1430  }
1431  else if (dx > 0)
1432  currentDEC += da_dec;
1433  else
1434  currentDEC -= da_dec;
1435 
1436  if (nlocked == 2)
1437  {
1438  driver.set_sim_slewing(false);
1439  }
1440 
1441  break;
1442 
1443  default:
1444  break;
1445  }
1446 
1447  driver.set_sim_ra(currentRA);
1448  driver.set_sim_dec(currentDEC);
1449 }
1450 
1452 {
1453  driver.set_simulation(enable);
1454 }
1455 
1456 // Update Location and time are disabled if the mount is aligned. This is because
1457 // changing either will change the mount model because at least the local sidereal time
1458 // will be changed. StarSense will set the mount to unaligned but it isn't a good idea even
1459 // with the NexStar HCs
1460 
1461 bool CelestronGPS::updateLocation(double latitude, double longitude, double elevation)
1462 {
1463  if (!isConnected())
1464  {
1465  LOG_DEBUG("updateLocation called before we are connected");
1466  return false;
1467  }
1468 
1469  if (!checkMinVersion(2.3, "updating location"))
1470  return false;
1471 
1472  bool isAligned;
1473  if (!driver.check_aligned(&isAligned))
1474  {
1475  LOG_INFO("Update location - check_aligned failed");
1476  return false;
1477  }
1478 
1479  if (isAligned)
1480  {
1481  LOG_INFO("Updating location is not necessary since mount is already aligned.");
1482  return false;
1483  }
1484 
1485  LOGF_DEBUG("Update location %8.3f, %8.3f, %4.0f", latitude, longitude, elevation);
1486 
1487  return driver.set_location(longitude, latitude);
1488 }
1489 
1490 bool CelestronGPS::updateTime(ln_date *utc, double utc_offset)
1491 {
1492  if (!isConnected())
1493  {
1494  LOG_DEBUG("updateTime called before we are connected");
1495  return false;
1496  }
1497 
1498  if (!checkMinVersion(2.3, "updating time"))
1499  return false;
1500 
1501  // setting time on StarSense seems to make it not aligned
1502  bool isAligned;
1503  if (!driver.check_aligned(&isAligned))
1504  {
1505  LOG_INFO("UpdateTime - check_aligned failed");
1506  return false;
1507  }
1508  if (isAligned)
1509  {
1510  LOG_INFO("Updating time is not necessary since mount is already aligned.");
1511  return false;
1512  }
1513 
1514  // starsense HC doesn't seem to support the precise time setting
1515  bool precise = fwInfo.controllerVersion >= 5.28;
1516 
1517  bool dst = DSTSettingS[0].s == ISS_ON;
1518 
1519  LOGF_DEBUG("Update time: offset %f %s UTC %i-%02i-%02iT%02i:%02i:%02.0f", utc_offset, dst ? "DST" : "", utc->years,
1520  utc->months, utc->days,
1521  utc->hours, utc->minutes, utc->seconds);
1522 
1523  return (driver.set_datetime(utc, utc_offset, dst, precise));
1524 }
1525 
1527 {
1528  double parkAZ = GetAxis1Park();
1529  double parkAlt = GetAxis2Park();
1530 
1531  char AzStr[16], AltStr[16];
1532  fs_sexa(AzStr, parkAZ, 2, 3600);
1533  fs_sexa(AltStr, parkAlt, 2, 3600);
1534 
1535  // unsync is only for NS+ 5.29 or more and not StarSense
1536  if (fwInfo.controllerVersion >= 5.29 && !driver.unsync())
1537  return false;
1538 
1539  LOGF_DEBUG("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
1540 
1541  if (driver.slew_azalt(parkAZ, parkAlt, usePreciseCoords))
1542  {
1544  LOG_INFO("Parking is in progress...");
1545  return true;
1546  }
1547 
1548  return false;
1549 }
1550 
1552 {
1553  bool parkDataValid = (LoadParkData() == nullptr);
1554  // Check if we need to wake up IF:
1555  // 1. Park data exists in ParkData.xml
1556  // 2. Mount is currently parked
1557  // 3. Hibernate option is enabled
1558  if (parkDataValid && isParked() && UseHibernateS[0].s == ISS_ON)
1559  {
1560  LOG_INFO("Waking up mount...");
1561 
1562  if (!driver.wakeup())
1563  {
1564  LOG_ERROR("Waking up mount failed! Make sure mount is powered and connected. "
1565  "Hibernate requires firmware version >= 5.21");
1566  return false;
1567  }
1568  }
1569 
1570  // Set tracking mode to whatever it was stored before
1571  SetParked(false);
1572 
1573  //loadConfig(true, "TELESCOPE_TRACK_MODE");
1574  // Read Saved Track State from config file
1575  for (int i = 0; i < TrackStateSP.nsp; i++)
1577 
1578  // set the mount tracking state
1579  LOGF_DEBUG("track state %s", IUFindOnSwitch(&TrackStateSP)->label);
1581 
1582  // reinit PEC
1583  if (driver.pecState >= PEC_STATE::PEC_AVAILABLE)
1584  driver.pecState = PEC_AVAILABLE;
1585 
1586  return true;
1587 }
1588 
1590 {
1591  // The Goto Alt-Az and Get Alt-Az menu items have been renamed Goto Axis Postn and Get Axis Postn
1592  // where Postn is an abbreviation for Position. Since this feature doesn't actually refer
1593  // to altitude and azimuth when mounted on a wedge, the new designation is more accurate.
1594  // Source : NexStarHandControlVersion4UsersGuide.pdf
1595 
1596  if (driver.get_azalt(&currentAZ, &currentALT, usePreciseCoords) == false)
1597  {
1598  LOG_ERROR("Failed to read AZ/ALT values.");
1599  return false;
1600  }
1601 
1602  double parkAZ = currentAZ;
1603  double parkAlt = currentALT;
1604 
1605  char AzStr[16], AltStr[16];
1606  fs_sexa(AzStr, parkAZ, 2, 3600);
1607  fs_sexa(AltStr, parkAlt, 2, 3600);
1608 
1609  LOGF_DEBUG("Setting current parking position to coordinates Az (%s) Alt (%s)...", AzStr,
1610  AltStr);
1611 
1612  SetAxis1Park(parkAZ);
1613  SetAxis2Park(parkAlt);
1614 
1615  return true;
1616 }
1617 
1619 {
1620  // The Goto Alt-Az and Get Alt-Az menu items have been renamed Goto Axis Postn and Get Axis Postn
1621  // where Postn is an abbreviation for Position. Since this feature doesn't actually refer
1622  // to altitude and azimuth when mounted on a wedge, the new designation is more accurate.
1623  // Source : NexStarHandControlVersion4UsersGuide.pdf
1624 
1625  // By default azimuth 90° ( hemisphere doesn't matter)
1626  SetAxis1Park(90);
1627 
1628  // Altitude = 90° (latitude doesn't matter)
1629  SetAxis2Park(90);
1630 
1631  return true;
1632 }
1633 
1635 {
1637  FI::saveConfigItems(fp);
1638 
1641  IUSaveConfigSwitch(fp, &DSTSettingSP);
1642 
1643  IUSaveConfigNumber(fp, &FocusMinPosNP);
1644 
1645  return true;
1646 }
1647 
1648 bool CelestronGPS::setCelestronTrackMode(CELESTRON_TRACK_MODE mode)
1649 {
1650  if (driver.set_track_mode(mode))
1651  {
1652  TrackState = (mode == CTM_OFF) ? SCOPE_IDLE : SCOPE_TRACKING;
1653  LOGF_DEBUG("Tracking mode set to %i, %s.", mode, CelestronTrackModeS[mode - 1].label);
1654  return true;
1655  }
1656 
1657  return false;
1658 }
1659 
1660 //GUIDE Guiding functions.
1661 
1662 // There have been substantial changes at version 3.3, Nov 2019
1663 //
1664 // The mount controlled Aux Guide is used if it is available, this is
1665 // if the mount firmware version for both axes is 6.12 or better. Other
1666 // mounts use a timed guide method.
1667 // The mount Aux Guide command has a maximum vulue of 2.55 seconds but if
1668 // a longer guide is needed then multiple Aux Guide commands are sent.
1669 //
1670 // The start guide and stop guide functions use helper functions to avoid
1671 // code duplication.
1672 
1674 {
1676 }
1677 
1679 {
1681 }
1682 
1684 {
1686 }
1687 
1689 {
1691 }
1692 
1693 // common function to start guiding for all axes.
1695 {
1696  // set up direction properties
1697  char dc = 'x';
1699  ISwitch moveS = MovementNSS[0];
1700  int* guideTID = &GuideNSTID;
1701  int* ticks = &ticksNS;
1702  uint8_t rate = 50;
1703 
1704  // set up pointers to the various things needed
1705  switch (dirn)
1706  {
1707  case CELESTRON_N:
1708  dc = 'N';
1709  moveSP = &MovementNSSP;
1710  moveS = MovementNSS[0];
1711  guideTID = &GuideNSTID;
1712  ticks = &ticksNS;
1713  /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see CelestronDriver::send_pulse() */
1714  rate = guideRateDec = static_cast<uint8_t>(GuideRateN[AXIS_DE].value * 100.0);
1715  break;
1716  case CELESTRON_S:
1717  dc = 'S';
1718  moveSP = &MovementNSSP;
1719  moveS = MovementNSS[1];
1720  guideTID = &GuideNSTID;
1721  ticks = &ticksNS;
1722  /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see CelestronDriver::send_pulse() */
1723  rate = guideRateDec = static_cast<uint8_t>(GuideRateN[AXIS_DE].value * 100.0);
1724  break;
1725  case CELESTRON_E:
1726  dc = 'E';
1727  moveSP = &MovementWESP;
1728  moveS = MovementWES[1];
1729  guideTID = &GuideWETID;
1730  ticks = &ticksWE;
1731  /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see CelestronDriver::send_pulse() */
1732  rate = guideRateRa = static_cast<uint8_t>(GuideRateN[AXIS_RA].value * 100.0);
1733  break;
1734  case CELESTRON_W:
1735  dc = 'W';
1736  moveSP = &MovementWESP;
1737  moveS = MovementWES[0];
1738  guideTID = &GuideWETID;
1739  ticks = &ticksWE;
1740  /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see CelestronDriver::send_pulse() */
1741  rate = guideRateRa = static_cast<uint8_t>(GuideRateN[AXIS_RA].value * 100.0);
1742  break;
1743  }
1744 
1745  LOGF_DEBUG("GUIDE CMD: %c %u ms, %s guide", dc, ms, canAuxGuide ? "Aux" : "Time");
1746 
1747  if (!canAuxGuide && (MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY))
1748  {
1749  LOG_ERROR("Cannot guide while moving.");
1750  return IPS_ALERT;
1751  }
1752 
1753  // If already moving (no pulse command), then stop movement
1754  if (moveSP->s == IPS_BUSY)
1755  {
1756  LOG_DEBUG("Already moving - stop");
1757  driver.stop_motion(dirn);
1758  }
1759 
1760  if (*guideTID)
1761  {
1762  LOGF_DEBUG("Stop timer %c", dc);
1763  IERmTimer(*guideTID);
1764  *guideTID = 0;
1765  }
1766 
1767  if (canAuxGuide)
1768  {
1769  // get the number of 10ms hardware ticks
1770  *ticks = ms / 10;
1771 
1772  // send the first Aux Guide command,
1773  if (driver.send_pulse(dirn, rate, static_cast<char>(std::min(255, *ticks))) == 0)
1774  {
1775  LOGF_ERROR("send_pulse %c error", dc);
1776  return IPS_ALERT;
1777  }
1778  // decrease ticks and ms values
1779  *ticks -= 255;
1780  ms = ms > 2550 ? 2550 : ms;
1781  }
1782  else
1783  {
1784  moveS.s = ISS_ON;
1785  // start movement at HC button rate 1
1786  if (!driver.start_motion(dirn, CELESTRON_SLEW_RATE::SR_1))
1787  {
1788  LOGF_ERROR("StartMotion %c failed", dc);
1789  return IPS_ALERT;
1790  }
1791  *ticks = 0;
1792  }
1793 
1794  // Set slew to guiding
1797  IDSetSwitch(&SlewRateSP, nullptr);
1798  // start the guide timeout timer
1799  AddGuideTimer(dirn, static_cast<int>(ms));
1800  return IPS_BUSY;
1801 }
1802 
1803 //GUIDE The timer helper functions.
1805 {
1806  static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_N);
1807 }
1808 
1810 {
1811  static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_S);
1812 }
1813 
1815 {
1816  static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_W);
1817 }
1818 
1820 {
1821  static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_E);
1822 }
1823 
1824 //GUIDE The timer function
1825 
1826 /* Here I splitted the behaviour depending upon the direction
1827  * of the guide command which generates the timer; this was
1828  * done because the member variable "guide_direction" could
1829  * be modified by a pulse command on the other axis BEFORE
1830  * the calling pulse command is terminated.
1831  */
1832 
1834 {
1835  int* ticks = &ticksNS;
1836  uint8_t rate = 0;
1837 
1838  switch(dirn)
1839  {
1840  case CELESTRON_N:
1841  case CELESTRON_S:
1842  ticks = &ticksNS;
1843  rate = guideRateDec;
1844  break;
1845  case CELESTRON_E:
1846  case CELESTRON_W:
1847  ticks = &ticksWE;
1848  rate = guideRateRa;
1849  break;
1850  }
1851 
1852  LOGF_DEBUG("guideTimer dir %c, ticks %i, rate %i", "NSWE"[dirn], *ticks, rate);
1853 
1854  if (canAuxGuide)
1855  {
1856  if (driver.get_pulse_status(dirn))
1857  {
1858  // curent move not finished, add some more time
1859  AddGuideTimer(dirn, 100);
1860  return;
1861  }
1862  if (*ticks > 0)
1863  {
1864  // do some more guiding and set the timeout
1865  int dt = (*ticks > 255) ? 255 : *ticks;
1866  driver.send_pulse(dirn, rate, static_cast<uint8_t>(dt));
1867  AddGuideTimer(dirn, dt * 10);
1868  *ticks -= 255;
1869  return;
1870  }
1871  // we get here if the axis reports guiding finished and all the ticks have been done
1872  }
1873  else
1874  {
1875  if (!driver.stop_motion(dirn))
1876  LOGF_ERROR("StopMotion failed dir %c", "NSWE"[dirn]);
1877  }
1878 
1879  switch(dirn)
1880  {
1881  case CELESTRON_N:
1882  case CELESTRON_S:
1884  IDSetSwitch(&MovementNSSP, nullptr);
1885  GuideNSNP.np[0].value = 0;
1886  GuideNSNP.np[1].value = 0;
1887  GuideNSNP.s = IPS_IDLE;
1888  GuideNSTID = 0;
1889  IDSetNumber(&GuideNSNP, nullptr);
1890  break;
1891  case CELESTRON_E:
1892  case CELESTRON_W:
1894  IDSetSwitch(&MovementWESP, nullptr);
1895  GuideWENP.np[0].value = 0;
1896  GuideWENP.np[1].value = 0;
1897  GuideWENP.s = IPS_IDLE;
1898  GuideWETID = 0;
1899  IDSetNumber(&GuideWENP, nullptr);
1900  break;
1901  }
1902  LOGF_DEBUG("Guide %c finished", "NSWE"[dirn]);
1903 }
1904 
1906 {
1907  switch(dirn)
1908  {
1909  case CELESTRON_N:
1911  break;
1912  case CELESTRON_S:
1914  break;
1915  case CELESTRON_E:
1917  break;
1918  case CELESTRON_W:
1920  break;
1921  }
1922 }
1923 
1924 // end of guiding code
1925 
1926 // the INDI overload, expected to set the track rate
1927 // sidereal, solar or lunar and only if the mount is equatorial
1928 bool CelestronGPS::SetTrackMode(uint8_t mode)
1929 {
1930  CELESTRON_TRACK_RATE rate;
1931 
1932  switch (fwInfo.celestronTrackMode)
1933  {
1934  case CTM_OFF:
1935  case CTM_ALTAZ:
1936  case CTM_RADEC:
1937  return false;
1938  case CTM_EQN:
1939  case CTM_EQS:
1940  break;
1941  }
1942 
1943  switch (mode)
1944  {
1945  case 0:
1946  rate = CTR_SIDEREAL;
1947  break;
1948  case 1:
1949  rate = CTR_SOLAR;
1950  break;
1951  case 2:
1952  rate = CTR_LUNAR;
1953  break;
1954  default:
1955  return false;
1956  }
1957  return driver.set_track_rate(rate, fwInfo.celestronTrackMode);
1958 }
1959 
1961 {
1962  return setCelestronTrackMode(enabled ? fwInfo.celestronTrackMode : CTM_OFF);
1963  //return setTrackMode(enabled ? static_cast<CELESTRON_TRACK_MODE>(IUFindOnSwitchIndex(&TrackModeSP)+1) : TRACKING_OFF);
1964 }
1965 
1966 void CelestronGPS::checkAlignment()
1967 {
1968  ReadScopeStatus();
1969 
1970  bool isAligned;
1971  if (!driver.check_aligned(&isAligned) || !isAligned)
1972  LOG_WARN("Mount is NOT aligned. You must align the mount first before you can use it. Disconnect, align the mount, and reconnect again.");
1973 }
1974 
1975 bool CelestronGPS::savePecData()
1976 {
1977  // generate the file name:
1978  // ~/.indi/pec/yyyy-mm-dd/pecData_hh:mm.log
1979 
1980  char ts_date[32], ts_time[32];
1981  struct tm *tp;
1982  time_t t;
1983 
1984  time(&t);
1985  tp = gmtime(&t);
1986  strftime(ts_date, sizeof(ts_date), "%Y-%m-%d", tp);
1987  strftime(ts_time, sizeof(ts_time), "%H:%M", tp);
1988 
1989  char dir[MAXRBUF];
1990  snprintf(dir, MAXRBUF, "%s/PEC_Data/%s", getenv("HOME"), ts_date);
1991 
1992  if (INDI::mkpath(dir, 0755) == -1)
1993  {
1994  LOGF_ERROR("Error creating directory %s (%s)", dir, strerror(errno));
1995  return false;
1996  }
1997 
1998  char pecFileBuf[MAXRBUF];
1999  snprintf(pecFileBuf, MAXRBUF, "%s/pecData_%s.csv", dir, ts_time);
2000 
2001  // show the file name
2002  IUSaveText(&PecFileNameT[0], pecFileBuf);
2003  IDSetText(&PecFileNameTP, nullptr);
2004 
2005  // get the PEC data from the mount
2006  PecData pecdata;
2007 
2008  if (!pecdata.Load(&driver))
2009  {
2010  LOG_DEBUG("Load PEC from mount failed");
2011  return false;
2012  }
2013  pecdata.RemoveDrift();
2014  // and save it
2015  if (!pecdata.Save(pecFileBuf))
2016  {
2017  LOGF_DEBUG("Save PEC file %s failed", pecFileBuf);
2018  return false;
2019  }
2020  LOGF_INFO("PEC data saved to %s", pecFileBuf);
2021  return true;
2022 }
2023 
2024 // focus control
2026 {
2027  uint32_t position = targetTicks;
2028 
2029  if (!focuserIsCalibrated)
2030  {
2031  LOG_ERROR("Move is not allowed because the focuser is not calibrated");
2032  return IPS_ALERT;
2033  }
2034 
2035  // implement backlash
2036  int delta = static_cast<int>(targetTicks - FocusAbsPosN[0].value);
2037 
2038  if ((FocusBacklashN[0].value < 0 && delta > 0) ||
2039  (FocusBacklashN[0].value > 0 && delta < 0))
2040  {
2041  focusBacklashMove = true;
2042  focusPosition = position;
2043  position -= FocusBacklashN[0].value;
2044  }
2045 
2046  LOGF_INFO("Focus %s move %d", focusBacklashMove ? "backlash" : "direct", position);
2047 
2048  if(!driver.foc_move(position))
2049  return IPS_ALERT;
2050 
2051  return IPS_BUSY;
2052 }
2053 
2055 {
2056  uint32_t newPosition = 0;
2057 
2058  if (dir == FOCUS_INWARD)
2059  newPosition = static_cast<uint32_t>(FocusAbsPosN[0].value) - ticks;
2060  else
2061  newPosition = static_cast<uint32_t>(FocusAbsPosN[0].value) + ticks;
2062 
2063  // Clamp
2064  newPosition = std::min(static_cast<uint32_t>(FocusAbsPosN[0].max), newPosition);
2065  return MoveAbsFocuser(newPosition);
2066 }
2067 
2069 {
2070  return driver.foc_abort();
2071 }
2072 
2073 // read the focuser limits from the hardware
2074 bool CelestronGPS::focusReadLimits()
2075 {
2076  int low, high;
2077  bool valid = driver.foc_limits(&low, &high);
2078 
2079  FocusAbsPosN[0].max = high;
2080  FocusAbsPosN[0].min = low;
2083 
2084  FocusMaxPosN[0].value = high;
2086  IDSetNumber(&FocusMaxPosNP, nullptr);
2087 
2088  FocusMinPosN[0].value = low;
2089  FocusMinPosNP.s = IPS_OK;
2090  IDSetNumber(&FocusMinPosNP, nullptr);
2091 
2092  focuserIsCalibrated = valid;
2093 
2094  LOGF_INFO("Focus Limits: Maximum (%i) Minimum (%i) steps.", high, low);
2095  return valid;
2096 }
CELESTRON_DIRECTION
@ CELESTRON_W
@ CELESTRON_N
@ CELESTRON_S
@ CELESTRON_E
@ FW_CAN_AUX
@ FW_MODEL
@ FW_ISGEM
@ FW_VERSION
@ FW_DEC
@ FW_HAS_FOC
@ FW_RA
CELESTRON_SLEW_RATE
@ SR_1
@ SR_5
CELESTRON_TRACK_MODE
@ CTM_EQN
@ CTM_ALTAZ
@ CTM_EQS
@ CTM_OFF
@ CTM_RADEC
#define ISSTARSENSE
#define MINSTSENSVER
CELESTRON_TRACK_RATE
@ CTR_SIDEREAL
@ CTR_SOLAR
@ CTR_LUNAR
PEC_STATE
@ NotKnown
@ PEC_SEEKING
The PEC index is being searched for, goes to PEC_INDEXED when found
@ PEC_AVAILABLE
PEC is available but inactive, can seek index Seek index is only available command
@ PEC_PLAYBACK
PEC is being played back, stays in this state until stopped equivalent to TelescopePECState PEC_ON
@ PEC_RECORDING
PEC is being recorded, goes to PEC_INDEXED when completed
@ PEC_INDEXED
the PEC index has been found, can go to Playback or Recording this is equivalent to TelescopePECState...
#define ISNEXSTAR
#define SLEW_RATE
#define GOTO_RATE
#define FINE_SLEW_RATE
#define MOUNTINFO_TAB
#define SLEW_LIMIT
#define GOTO_LIMIT
bool slew_radec(double ra, double dec, bool precise)
bool get_azalt(double *az, double *alt, bool precise)
bool set_guide_rate(CELESTRON_AXIS axis, uint8_t rate)
void set_port_fd(int port_fd)
bool indexreached(bool *atIndex)
bool check_aligned(bool *isAligned)
bool set_datetime(struct ln_date *utc, double utc_offset, bool dst=false, bool precise=false)
bool set_location(double longitude, double latitude)
bool stop_motion(CELESTRON_DIRECTION dir)
const char * PecStateStr(PEC_STATE)
void set_sim_track_mode(CELESTRON_TRACK_MODE val)
bool get_firmware(FirmwareInfo *info)
bool sync(double ra, double dec, bool precise)
bool foc_limits(int *low, int *high)
bool PecPlayback(bool start)
size_t send_pulse(CELESTRON_DIRECTION direction, unsigned char rate, unsigned char duration_msec)
bool get_radec(double *ra, double *dec, bool precise)
bool get_track_mode(CELESTRON_TRACK_MODE *mode)
void set_device(const char *name)
void set_simulation(bool enable)
bool set_track_rate(CELESTRON_TRACK_RATE rate, CELESTRON_TRACK_MODE mode)
bool set_track_mode(CELESTRON_TRACK_MODE mode)
bool PecRecord(bool start)
bool isPecAtIndex(bool force=false)
bool get_location(double *longitude, double *latitude)
void set_sim_ra(double ra)
bool get_pulse_status(CELESTRON_DIRECTION direction)
void set_sim_slewing(bool isSlewing)
PEC_STATE updatePecState()
bool get_guide_rate(CELESTRON_AXIS axis, uint8_t *rate)
void set_sim_dec(double dec)
bool slew_azalt(double az, double alt, bool precise)
void set_sim_slew_rate(CELESTRON_SLEW_RATE val)
bool foc_move(uint32_t steps)
bool get_pier_side(char *sop)
bool is_slewing(bool *slewing)
bool get_utc_date_time(double *utc_hours, int *yy, int *mm, int *dd, int *hh, int *minute, int *ss, bool *dst, bool precise)
bool start_motion(CELESTRON_DIRECTION dir, CELESTRON_SLEW_RATE rate)
virtual bool Handshake() override
perform handshake with device to check communication
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
ITextVectorProperty PecFileNameTP
Definition: celestrongps.h:145
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
ISwitch UseHibernateS[2]
Definition: celestrongps.h:132
IText PecInfoT[2]
Definition: celestrongps.h:136
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
ISwitchVectorProperty PecControlSP
Definition: celestrongps.h:140
uint8_t guideRateRa
Definition: celestrongps.h:74
static void guideTimerHelperE(void *p)
static void guideTimerHelperS(void *p)
virtual bool UnPark() override
Unpark the telescope if already parked.
INumber GuideRateN[2]
Definition: celestrongps.h:71
ISwitch PecControlS[4]
Definition: celestrongps.h:139
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.
ITextVectorProperty FirmwareTP
Definition: celestrongps.h:115
void guideTimer(CELESTRON_DIRECTION dirn)
static void guideTimerHelperW(void *p)
IPState Guide(CELESTRON_DIRECTION dirn, uint32_t ms)
void AddGuideTimer(CELESTRON_DIRECTION dirn, int ms)
bool AbortFocuser() override
AbortFocuser all focus motion.
IText FirmwareT[7]
Definition: celestrongps.h:114
IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
INumberVectorProperty GuideRateNP
Definition: celestrongps.h:72
virtual const char * getDefaultName() override
ITextVectorProperty PecInfoTP
Definition: celestrongps.h:137
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool Park() override
Park the telescope to its home position.
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual void simulationTriggered(bool enable) override
Inform driver that the simulation option was triggered. This function is called after setSimulation i...
ISwitchVectorProperty CelestronTrackModeSP
Definition: celestrongps.h:124
uint8_t guideRateDec
Definition: celestrongps.h:75
IText PecFileNameT[1]
Definition: celestrongps.h:144
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
ISwitchVectorProperty UseHibernateSP
Definition: celestrongps.h:131
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
static void guideTimerHelperN(void *p)
ISwitch CelestronTrackModeS[4]
Definition: celestrongps.h:125
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
virtual bool ISNewText(const char *dev, const char *name, char **texts, char **names, int n) override
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
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...
Class to provide general functionality of a GPS device.
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.
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
void syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
Provides interface to implement focuser functionality.
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
void initProperties(const char *groupName)
Initilize focuser properties. It is recommended to call this function within initProperties() of your...
bool saveConfigItems(FILE *fp)
saveConfigItems save focuser properties defined in the interface in config file
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process focus number properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process focus switch properties.
INumberVectorProperty FocusMaxPosNP
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
ISwitchVectorProperty TrackStateSP
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
TelescopePierSide currentPierSide
ISwitchVectorProperty MovementNSSP
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
INumberVectorProperty LocationNP
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty TimeTP
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
double GetAxis1Park() const
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...
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty SlewRateSP
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
ISwitch MovementWES[2]
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
const char * LoadParkData()
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
ISwitch * TrackModeS
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
ISwitchVectorProperty MovementWESP
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
ISwitch TrackStateS[2]
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
void RemoveDrift()
bool Load(CelestronDriver *driver)
bool Save(const char *filename)
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.
const char * FOCUS_TAB
FOCUS_TAB Where all the properties for focuser are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
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
double max(void)
int errno
double min(void)
#define currentDEC
Definition: ieq45.cpp:48
#define currentRA
Definition: ieq45.cpp:47
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
@ IP_WO
Definition: indiapi.h:185
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_NOFMANY
Definition: indiapi.h:175
@ 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
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition: indicom.c:1235
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
Implementations for common driver routines.
#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
ISwitch * IUFindOnSwitch(const ISwitchVectorProperty *svp)
Returns the first ON switch it finds in the vector switch property.
Definition: indidevapi.c:108
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
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
IText * IUFindText(const ITextVectorProperty *tvp, const char *name)
Find an IText member in a vector text property.
Definition: indidevapi.c:56
#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 IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:1296
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
#define MAXRBUF
Definition: indiserver.cpp:102
#define RA_AXIS
#define DEC_AXIS
int mkpath(std::string s, mode_t mode)
Definition: indiutility.cpp:51
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
double controllerVersion
std::string Version
std::string RAFirmware
CELESTRON_TRACK_MODE celestronTrackMode
std::string Model
std::string DEFirmware
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
Definition: indilogger.cpp:339
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.
Definition: indilogger.cpp:72
One switch descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
Switch vector property descriptor.
Definition: indiapi.h:367
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250