1 #if 0
2 Celestron GPS
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 
CelestronGPS()57 CelestronGPS::CelestronGPS() : FI(this)
58 {
59     setVersion(3, 6); // update libindi/drivers.xml as well
60 
61 
62     fwInfo.Version           = "Invalid";
63     fwInfo.controllerVersion = 0;
64     fwInfo.controllerVariant = ISNEXSTAR;
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
78     FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_HAS_BACKLASH);
79 
80     // Set minimum properties.
81     // ISGetProperties in INDI::Telescope checks for CanGOTO which must be set.
82     SetTelescopeCapability(TELESCOPE_CAN_GOTO | TELESCOPE_CAN_ABORT, 9);
83 }
84 
checkMinVersion(double minVersion,const char * feature,bool debug)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 
getDefaultName()104 const char *CelestronGPS::getDefaultName()
105 {
106     return "Celestron GPS";
107 }
108 
initProperties()109 bool CelestronGPS::initProperties()
110 {
111     INDI::Telescope::initProperties();
112     FI::initProperties(FOCUS_TAB);
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",
129                        MOUNTINFO_TAB, IP_RO, ISR_1OFMANY, 0, IPS_IDLE);
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);
142     IUFillSwitchVector(&UseHibernateSP, UseHibernateS, 2, getDeviceName(), "Hibernate", "", OPTIONS_TAB, IP_RW,
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 
157     SetParkDataType(PARK_AZ_ALT);
158 
159     //GUIDE Initialize guiding properties.
160     initGuiderProperties(getDeviceName(), GUIDE_TAB);
161 
162     //////////////////////////////////////////////////////////////////////////////////////////////////
163     /// Guide Rate; units and min/max as specified in the INDI Standard Properties SLEW_GUIDE
164     /// https://indilib.org/developers/developer-manual/101-standard-properties.html#h3-telescopes
165     //////////////////////////////////////////////////////////////////////////////////////////////////
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 
171     ////////////////////////////////////////////////////////////////////////////////////////
172     /// PEC
173     /////////////////////////////////////////////////////////////////////////////////////////
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 
190     /////////////////////////////
191     /// DST setting
192     /////////////////////////////
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.
201     setDriverInterface(getDriverInterface() | 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;
218     FocusMaxPosNP.p = IP_RO;
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 
ISGetProperties(const char * dev)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 
247     INDI::Telescope::ISGetProperties(dev);
248 
249     defineProperty(&UseHibernateSP);
250     defineProperty(&CelestronTrackModeSP);
251     if (configLoaded == false)
252     {
253         configLoaded = true;
254         loadConfig(true, "Hibernate");
255     }
256 }
257 
updateProperties()258 bool CelestronGPS::updateProperties()
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
277             fwInfo.celestronTrackMode = static_cast<CELESTRON_TRACK_MODE>(IUFindOnSwitchIndex(&CelestronTrackModeSP) + 1);
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         {
296             cap |= TELESCOPE_HAS_TIME | TELESCOPE_HAS_LOCATION;
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"))
303             cap |= TELESCOPE_CAN_CONTROL_TRACK;
304         else
305             LOG_WARN("Mount firmware does not support track on off.");
306 
307         if (fwInfo.isGem  && checkMinVersion(4.15, "Pier Side", true))
308             cap |= TELESCOPE_HAS_PIER_SIDE;
309         else
310             LOG_WARN("Mount firmware does not support getting pier side.");
311 
312         // Track Mode (t) is only supported for 2.3+
313         CELESTRON_TRACK_MODE ctm = CTM_OFF;
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;
328                     IUResetSwitch(&CelestronTrackModeSP);
329                     CelestronTrackModeS[ctm - 1].s = ISS_ON;
330                     CelestronTrackModeSP.s      = IPS_OK;
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.");
338                     TrackState = isParked() ? SCOPE_PARKED : SCOPE_IDLE;
339                 }
340             }
341             else
342             {
343                 LOG_DEBUG("get_track_mode failed");
344                 CelestronTrackModeSP.s = IPS_ALERT;
345             }
346 
347             IDSetSwitch(&CelestronTrackModeSP, nullptr);
348         }
349 
350         if (fwInfo.celestronTrackMode != CTM_ALTAZ)
351             cap |= TELESCOPE_HAS_TRACK_MODE;
352         else
353         {
354             TrackModeS[TRACK_SIDEREAL].s = ISS_ON;
355             LOG_WARN("Mount firmware does not support track mode.");
356         }
357 
358         SetTelescopeCapability(cap, 9);
359 
360         INDI::Telescope::updateProperties();
361 
362         if (fwInfo.Version != "Invalid")
363             defineProperty(&FirmwareTP);
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);
369             SetAxis2ParkDefault(LocationN[LOCATION_LATITUDE].value);
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);
375             SetAxis2Park(LocationN[LOCATION_LATITUDE].value);
376             SetAxis1ParkDefault(LocationN[LOCATION_LATITUDE].value >= 0 ? 0 : 180);
377             SetAxis2ParkDefault(LocationN[LOCATION_LATITUDE].value);
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);
387             TrackState = SCOPE_TRACKING;
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
395         if (fwInfo.celestronTrackMode == CELESTRON_TRACK_MODE::CTM_EQN ||
396                 fwInfo.celestronTrackMode == CELESTRON_TRACK_MODE::CTM_EQS ||
397                 fwInfo.celestronTrackMode == CELESTRON_TRACK_MODE::CTM_RADEC)
398         {
399             defineProperty(&GuideRateNP);
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 
415             defineProperty(&GuideNSNP);
416             defineProperty(&GuideWENP);
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 
424         defineProperty(&CelestronTrackModeSP);
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         {
485             driver.pecState = PEC_STATE::PEC_AVAILABLE;
486             defineProperty(&PecControlSP);
487             defineProperty(&PecInfoTP);
488             defineProperty(&PecFileNameTP);
489         }
490 
491         //  handle the focuser
492         if (fwInfo.hasFocuser)
493         {
494             LOG_INFO("update focuser properties");
495             //defineProperty(&FocusBacklashNP);
496             defineProperty(&FocusMinPosNP);
497             if (focusReadLimits())
498             {
499                 IUUpdateMinMax(&FocusAbsPosNP);
500 
501                 IDSetNumber(&FocusMaxPosNP, nullptr);
502                 IDSetNumber(&FocusMinPosNP, nullptr);
503                 // focuser move capability is only set if the focus limits are valid
504                 FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT);
505             }
506             if (!focuserIsCalibrated)
507             {
508                 LOG_WARN("Focuser not calibrated, moves will not be allowed");
509             }
510             FI::updateProperties();
511         }
512     }
513     else    // not connected
514     {
515         INDI::Telescope::updateProperties();
516 
517         FI::updateProperties();
518         //deleteProperty(FocusBacklashNP.name);
519         deleteProperty(FocusMinPosNP.name);
520 
521         //GUIDE Delete properties.
522         deleteProperty(GuideNSNP.name);
523         deleteProperty(GuideWENP.name);
524 
525         deleteProperty(GuideRateNP.name);
526 
527         deleteProperty(LastAlignSP.name);
528         deleteProperty(CelestronTrackModeSP.name);
529 
530         deleteProperty(DSTSettingSP.name);
531 
532         deleteProperty(PecInfoTP.name);
533         deleteProperty(PecControlSP.name);
534         deleteProperty(PecFileNameTP.name);
535 
536         if (fwInfo.Version != "Invalid")
537             deleteProperty(FirmwareTP.name);
538     }
539 
540     return true;
541 }
542 
Goto(double ra,double dec)543 bool CelestronGPS::Goto(double ra, double dec)
544 {
545     targetRA  = ra;
546     targetDEC = dec;
547 
548     if (EqNP.s == IPS_BUSY || MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY)
549     {
550         driver.abort();
551         // sleep for 500 mseconds
552         usleep(500000);
553     }
554 
555     if (driver.slew_radec(targetRA + SlewOffsetRa, targetDEC, usePreciseCoords) == false)
556     {
557         LOG_ERROR("Failed to slew telescope in RA/DEC.");
558         return false;
559     }
560 
561     TrackState = SCOPE_SLEWING;
562 
563     char RAStr[32], DecStr[32];
564     fs_sexa(RAStr, targetRA, 2, 3600);
565     fs_sexa(DecStr, targetDEC, 2, 3600);
566     LOGF_INFO("Slewing to JNOW RA %s - DEC %s SlewOffsetRa %4.1f arcsec", RAStr, DecStr, SlewOffsetRa * 3600 * 15);
567 
568     return true;
569 }
570 
Sync(double ra,double dec)571 bool CelestronGPS::Sync(double ra, double dec)
572 {
573     if (!checkMinVersion(4.1, "sync"))
574         return false;
575 
576     if (driver.sync(ra, dec, usePreciseCoords) == false)
577     {
578         LOG_ERROR("Sync failed.");
579         return false;
580     }
581 
582     currentRA  = ra;
583     currentDEC = dec;
584 
585     char RAStr[32], DecStr[32];
586     fs_sexa(RAStr, targetRA, 2, 3600);
587     fs_sexa(DecStr, targetDEC, 2, 3600);
588     LOGF_INFO("Sync to %s, %s successful.", RAStr, DecStr);
589 
590     return true;
591 }
592 
593 /*
594 bool CelestronGPS::GotoAzAlt(double az, double alt)
595 {
596     if (isSimulation())
597     {
598         INDI::IHorizontalCoordinates horizontalPos;
599         // Libnova south = 0, west = 90, north = 180, east = 270
600         horizontalPos.az = az + 180;
601         if (horizontalPos.az >= 360)
602              horizontalPos.az -= 360;
603         horizontalPos.alt = alt;
604 
605         IGeographicCoordinates observer;
606 
607         observer.lat = LocationN[LOCATION_LATITUDE].value;
608         observer.lng = LocationN[LOCATION_LONGITUDE].value;
609 
610         if (observer.lng > 180)
611             observer.lng -= 360;
612 
613         INDI::IEquatorialCoordinates equatorialPos;
614         ln_get_equ_from_hrz(&horizontalPos, &observer, ln_get_julian_from_sys(), &equatorialPos);
615 
616         targetRA  = equatorialPos.rightascension/15.0;
617         targetDEC = equatorialPos.dec;
618     }
619 
620     if (driver.slew_azalt(LocationN[LOCATION_LATITUDE].value, az, alt) == false)
621     {
622         LOG_ERROR("Failed to slew telescope in Az/Alt.");
623         return false;
624     }
625 
626     targetAZ = az;
627     targetALT= alt;
628 
629     TrackState = SCOPE_SLEWING;
630 
631     HorizontalCoordsNP.s = IPS_BUSY;
632 
633     char AZStr[16], ALTStr[16];
634     fs_sexa(AZStr, targetAZ, 3, 3600);
635     fs_sexa(ALTStr, targetALT, 2, 3600);
636     LOGF_INFO("Slewing to Az %s - Alt %s", AZStr, ALTStr);
637 
638     return true;
639 }
640 */
641 
MoveNS(INDI_DIR_NS dir,TelescopeMotionCommand command)642 bool CelestronGPS::MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
643 {
644     CELESTRON_DIRECTION move;
645 
646     if (currentPierSide == PIER_WEST)
647         move = (dir == DIRECTION_NORTH) ? CELESTRON_N : CELESTRON_S;
648     else
649         move = (dir == DIRECTION_NORTH) ? CELESTRON_S : CELESTRON_N;
650 
651     CELESTRON_SLEW_RATE rate = static_cast<CELESTRON_SLEW_RATE>(IUFindOnSwitchIndex(&SlewRateSP));
652 
653     switch (command)
654     {
655         case MOTION_START:
656             if (driver.start_motion(move, rate) == false)
657             {
658                 LOG_ERROR("Error setting N/S motion direction.");
659                 return false;
660             }
661             else
662                 LOGF_INFO("Moving toward %s.", (move == CELESTRON_N) ? "North" : "South");
663             break;
664 
665         case MOTION_STOP:
666             if (driver.stop_motion(move) == false)
667             {
668                 LOG_ERROR("Error stopping N/S motion.");
669                 return false;
670             }
671             else
672                 LOGF_INFO("Movement toward %s halted.", (move == CELESTRON_N) ? "North" : "South");
673             break;
674     }
675 
676     return true;
677 }
678 
MoveWE(INDI_DIR_WE dir,TelescopeMotionCommand command)679 bool CelestronGPS::MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
680 {
681     CELESTRON_DIRECTION move = (dir == DIRECTION_WEST) ? CELESTRON_W : CELESTRON_E;
682     CELESTRON_SLEW_RATE rate = static_cast<CELESTRON_SLEW_RATE>(IUFindOnSwitchIndex(&SlewRateSP));
683 
684     switch (command)
685     {
686         case MOTION_START:
687             if (driver.start_motion(move, rate) == false)
688             {
689                 LOG_ERROR("Error setting W/E motion direction.");
690                 return false;
691             }
692             else
693                 LOGF_INFO("Moving toward %s.", (move == CELESTRON_W) ? "West" : "East");
694             break;
695 
696         case MOTION_STOP:
697             if (driver.stop_motion(move) == false)
698             {
699                 LOG_ERROR("Error stopping W/E motion.");
700                 return false;
701             }
702             else
703                 LOGF_INFO("Movement toward %s halted.", (move == CELESTRON_W) ? "West" : "East");
704             break;
705     }
706 
707     return true;
708 }
709 
ReadScopeStatus()710 bool CelestronGPS::ReadScopeStatus()
711 {
712     INDI::Telescope::TelescopePierSide pierSide = PIER_UNKNOWN;
713 
714     if (isSimulation())
715         mountSim();
716 
717     if (driver.get_radec(&currentRA, &currentDEC, usePreciseCoords) == false)
718     {
719         LOG_ERROR("Failed to read RA/DEC values.");
720         return false;
721     }
722 
723     if (HasPierSide())
724     {
725         // read the pier side close to reading the Radec so they should match
726         char sop;
727         char psc = 'u';
728         if (driver.get_pier_side(&sop))
729         {
730             // manage version and hemisphere nonsense
731             // HC versions less than 5.24 reverse the side of pier if the mount
732             // is in the Southern hemisphere.  StarSense doesn't
733             if (LocationN[LOCATION_LATITUDE].value < 0)
734             {
735                 if (fwInfo.controllerVersion <= 5.24 && fwInfo.controllerVariant != ISSTARSENSE)
736                 {
737                     // swap the char reported
738                     if (sop == 'E')
739                         sop = 'W';
740                     else if (sop == 'W')
741                         sop = 'E';
742                 }
743             }
744             // The Celestron and INDI pointing states are opposite
745             if (sop == 'W')
746             {
747                 pierSide = PIER_EAST;
748                 psc = 'E';
749             }
750             else if (sop == 'E')
751             {
752                 pierSide = PIER_WEST;
753                 psc = 'W';
754             }
755             // pier side and Ha don't match at +-90 deg dec
756             if (currentDEC > 89.999 || currentDEC < -89.999)
757             {
758                 pierSide = PIER_UNKNOWN;
759                 psc = 'U';
760             }
761         }
762 
763         LOGF_DEBUG("latitude %g, sop %c, PierSide %c",
764                    LocationN[LOCATION_LATITUDE].value,
765                    sop, psc);
766     }
767 
768 
769     // aligning
770     if (slewToIndex)
771     {
772         bool atIndex;
773         if (!driver.indexreached(&atIndex))
774         {
775             LOG_ERROR("IndexReached Failure");
776             slewToIndex = false;
777             return false;
778         }
779         if (atIndex)
780         {
781             slewToIndex = false;
782             // reached the index position.
783 
784             // do an alignment
785             if (!fwInfo.hasHomeIndex)
786             {
787                 // put another dire warning here
788                 LOG_WARN("This mount does not have index switches, the alignment assumes it is at the index position.");
789             }
790 
791             if (!driver.lastalign())
792             {
793                 LOG_ERROR("LastAlign failed");
794                 return false;
795             }
796 
797             LastAlignSP.s = IPS_IDLE;
798             IDSetSwitch(&LastAlignSP, "Align finished");
799 
800             bool isAligned;
801             if (!driver.check_aligned(&isAligned))
802             {
803                 LOG_WARN("get Alignment Failed!");
804             }
805             else
806             {
807                 if (isAligned)
808                     LOG_INFO("Mount is aligned");
809                 else
810                     LOG_WARN("Alignment Failed!");
811             }
812 
813             return true;
814         }
815     }
816 
817     switch (TrackState)
818     {
819         case SCOPE_SLEWING:
820             // are we done?
821             bool slewing;
822             if (driver.is_slewing(&slewing) && !slewing)
823             {
824                 LOG_INFO("Slew complete, tracking...");
825                 TrackState = SCOPE_TRACKING;
826                 // update ra offset
827                 double raoffset = targetRA - currentRA + SlewOffsetRa;
828                 if (raoffset > 0.0 || raoffset < 10.0 / 3600.0)
829                 {
830                     // average last two values
831                     SlewOffsetRa = SlewOffsetRa > 0 ? (SlewOffsetRa + raoffset) / 2 : raoffset;
832 
833                     LOGF_DEBUG("raoffset %4.1f, SlewOffsetRa %4.1f arcsec", raoffset * 3600 * 15, SlewOffsetRa * 3600 * 15);
834                 }
835             }
836             break;
837 
838         case SCOPE_PARKING:
839             // are we done?
840             if (driver.is_slewing(&slewing) && !slewing)
841             {
842                 if (driver.set_track_mode(CTM_OFF))
843                     LOG_DEBUG("Mount tracking is off.");
844 
845                 SetParked(true);
846 
847                 saveConfig(true);
848 
849                 // Check if we need to hibernate
850                 if (UseHibernateS[0].s == ISS_ON)
851                 {
852                     LOG_INFO("Hibernating mount...");
853                     if (driver.hibernate())
854                         LOG_INFO("Mount hibernated. Please disconnect now and turn off your mount.");
855                     else
856                         LOG_ERROR("Hibernating mount failed!");
857                 }
858             }
859             break;
860 
861         default:
862             break;
863     }
864 
865     // update pier side and RaDec close together to minimise the possibility of
866     // a mismatch causing an Ha limit error during a pier flip slew.
867     if (HasPierSide())
868         setPierSide(pierSide);
869     NewRaDec(currentRA, currentDEC);
870 
871     // is PEC Handling required
872     if (driver.pecState >= PEC_STATE::PEC_AVAILABLE)
873     {
874         static PEC_STATE lastPecState = PEC_STATE::NotKnown;
875         static size_t lastPecIndex = 1000;
876         static size_t numRecordPoints;
877 
878         if (driver.pecState >= PEC_STATE::PEC_INDEXED)
879         {
880             if (numPecBins < 88)
881             {
882                 numPecBins = driver.getPecNumBins();
883             }
884             // get and show the current PEC index
885             size_t pecIndex = driver.pecIndex();
886 
887             if (pecIndex != lastPecIndex)
888             {
889                 LOGF_DEBUG("PEC state %s, index %d", driver.PecStateStr(), pecIndex);
890                 IUSaveText(&PecInfoT[1], std::to_string(pecIndex).c_str());
891                 IDSetText(&PecInfoTP, nullptr);
892                 lastPecIndex = pecIndex;
893 
894                 // count the PEC records
895                 if (driver.pecState == PEC_STATE::PEC_RECORDING)
896                     numRecordPoints++;
897                 else
898                     numRecordPoints = 0;
899             }
900         }
901 
902         // update the PEC state
903         if (driver.updatePecState() != lastPecState)
904         {
905             // and handle the change, if there was one
906             LOGF_DEBUG("PEC last state %s, new State %s", driver.PecStateStr(lastPecState), driver.PecStateStr());
907 
908             // update the state string
909             IUSaveText(&PecInfoT[0], driver.PecStateStr());
910             IDSetText(&PecInfoTP, nullptr);
911 
912             // no need to check both current and last because they must be different
913             switch (lastPecState)
914             {
915                 case PEC_STATE::PEC_SEEKING:
916                     // finished seeking
917                     PecControlS[PEC_Seek].s = ISS_OFF;
918                     PecControlSP.s = IPS_IDLE;
919                     IDSetSwitch(&PecControlSP, nullptr);
920                     LOG_INFO("PEC index Seek completed.");
921                     break;
922                 case PEC_STATE::PEC_PLAYBACK:
923                     // finished playback
924                     PecControlS[PEC_Playback].s = ISS_OFF;
925                     PecControlSP.s = IPS_IDLE;
926                     IDSetSwitch(&PecControlSP, nullptr);
927                     LOG_INFO("PEC playback finished");
928                     break;
929                 case PEC_STATE::PEC_RECORDING:
930                     // finished recording
931                     LOGF_DEBUG("PEC record stopped, %d records", numRecordPoints);
932 
933                     if (numRecordPoints >= numPecBins)
934                     {
935                         savePecData();
936                     }
937 
938                     PecControlS[PEC_Record].s = ISS_OFF;
939                     PecControlSP.s = IPS_IDLE;
940                     LOG_INFO("PEC record finished");
941                     IDSetSwitch(&PecControlSP, nullptr);
942 
943                     break;
944                 default:
945                     break;
946             }
947             lastPecState = driver.pecState;
948         }
949     }
950 
951     // focuser
952     if (fwInfo.hasFocuser)
953     {
954         // Check position
955         double lastPosition = FocusAbsPosN[0].value;
956 
957         int pos = driver.foc_position();
958         if (pos >= 0)
959         {
960             FocusAbsPosN[0].value = pos;
961             // Only update if there is actual change
962             if (fabs(lastPosition - FocusAbsPosN[0].value) > 1)
963                 IDSetNumber(&FocusAbsPosNP, nullptr);
964         }
965 
966         if (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
967         {
968             // The backlash handling is done here, if the move state
969             // shows that a backlash move has been done then the final move needs to be started
970             // and the states left at IPS_BUSY
971 
972             if (!driver.foc_moving())
973             {
974                 if (focusBacklashMove)
975                 {
976                     focusBacklashMove = false;
977                     if (driver.foc_move(focusPosition))
978                         LOGF_INFO("Focus final move %i", focusPosition);
979                     else
980                         LOG_INFO("Backlash move failed");
981                 }
982                 else
983                 {
984                     FocusAbsPosNP.s = IPS_OK;
985                     FocusRelPosNP.s = IPS_OK;
986                     IDSetNumber(&FocusAbsPosNP, nullptr);
987                     IDSetNumber(&FocusRelPosNP, nullptr);
988                     LOG_INFO("Focuser reached requested position.");
989                 }
990             }
991         }
992     }
993 
994     return true;
995 }
996 
Abort()997 bool CelestronGPS::Abort()
998 {
999     driver.stop_motion(CELESTRON_N);
1000     driver.stop_motion(CELESTRON_S);
1001     driver.stop_motion(CELESTRON_W);
1002     driver.stop_motion(CELESTRON_E);
1003 
1004     //GUIDE Abort guide operations.
1005     if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
1006     {
1007         GuideNSNP.s = GuideWENP.s = IPS_IDLE;
1008         GuideNSN[0].value = GuideNSN[1].value = 0.0;
1009         GuideWEN[0].value = GuideWEN[1].value = 0.0;
1010 
1011         if (GuideNSTID)
1012         {
1013             IERmTimer(GuideNSTID);
1014             GuideNSTID = 0;
1015         }
1016 
1017         if (GuideWETID)
1018         {
1019             IERmTimer(GuideWETID);
1020             GuideWETID = 0;
1021         }
1022 
1023         LOG_INFO("Guide aborted.");
1024         IDSetNumber(&GuideNSNP, nullptr);
1025         IDSetNumber(&GuideWENP, nullptr);
1026 
1027         return true;
1028     }
1029 
1030     return driver.abort();
1031 }
1032 
Handshake()1033 bool CelestronGPS::Handshake()
1034 {
1035     driver.set_device(getDeviceName());
1036     driver.set_port_fd(PortFD);
1037 
1038     if (isSimulation())
1039     {
1040         driver.set_simulation(true);
1041         driver.set_sim_slew_rate(SR_5);
1042         driver.set_sim_ra(0);
1043         driver.set_sim_dec(90);
1044     }
1045 
1046     if (driver.check_connection() == false)
1047     {
1048         LOG_ERROR("Failed to communicate with the mount, check the logs for details.");
1049         return false;
1050     }
1051 
1052     return true;
1053 }
1054 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)1055 bool CelestronGPS::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1056 {
1057     if (dev && std::string(getDeviceName()) == dev)
1058     {
1059         // Enable/Disable hibernate
1060         if (name && std::string(name) == UseHibernateSP.name)
1061         {
1062             IUUpdateSwitch(&UseHibernateSP, states, names, n);
1063             if (fwInfo.controllerVersion > 0)
1064             {
1065                 if (UseHibernateS[0].s == ISS_ON && (checkMinVersion(4.22, "hibernation", true) == false))
1066                 {
1067                     UseHibernateS[0].s = ISS_OFF;
1068                     UseHibernateS[1].s = ISS_ON;
1069                     UseHibernateSP.s = IPS_ALERT;
1070                 }
1071                 else
1072                     UseHibernateSP.s = IPS_OK;
1073             }
1074             IDSetSwitch(&UseHibernateSP, nullptr);
1075             return true;
1076         }
1077 
1078         // start a last align
1079         // the process is:
1080         //  start move to switch position
1081         //  wait for the move to finish
1082         //  set the time from the PC - maybe
1083         //  send a Last Align command "Y"
1084 
1085 
1086         if (name && std::string(name) == LastAlignSP.name)
1087         {
1088             if (!fwInfo.hasHomeIndex)
1089             {
1090                 // put the dire warning here
1091                 LOG_WARN("This mount does not have index switches, make sure that it is at the index position.");
1092             }
1093             LOG_DEBUG("Start Align");
1094             // start move to switch positions
1095             if (!driver.startmovetoindex())
1096             {
1097                 LastAlignSP.s = IPS_ALERT;
1098                 return false;
1099             }
1100             // wait for the move to finish
1101             // done in ReadScopeStatus
1102             slewToIndex = true;
1103             LastAlignSP.s = IPS_BUSY;
1104             IDSetSwitch(&LastAlignSP, "Align in progress");
1105             return true;
1106         }
1107 
1108         // handle the PEC commands
1109         if (name && std::string(name) == PecControlSP.name)
1110         {
1111             IUUpdateSwitch(&PecControlSP, states, names, n);
1112             int idx = IUFindOnSwitchIndex(&PecControlSP);
1113 
1114             switch(idx)
1115             {
1116                 case PEC_Stop:
1117                     LOG_DEBUG(" stop PEC record or playback");
1118                     bool playback;
1119                     if ((playback = driver.pecState == PEC_PLAYBACK) || driver.pecState == PEC_RECORDING)
1120                     {
1121                         if (playback ? driver.PecPlayback(false) : driver.PecRecord(false))
1122                         {
1123                             PecControlSP.s = IPS_IDLE;
1124                         }
1125                         else
1126                         {
1127                             PecControlSP.s = IPS_ALERT;
1128                         }
1129                     }
1130                     else
1131                     {
1132                         LOG_WARN("Incorrect state to stop PEC Playback or Record");
1133                         PecControlSP.s = IPS_ALERT;
1134                     }
1135                     IUResetSwitch(&PecControlSP);
1136                     break;
1137                 case PEC_Playback:
1138                     LOG_DEBUG("start PEC Playback");
1139                     if (driver.pecState == PEC_STATE::PEC_INDEXED)
1140                     {
1141                         // start playback
1142                         if (driver.PecPlayback(true))
1143                         {
1144                             PecControlSP.s = IPS_BUSY;
1145                             LOG_INFO("PEC Playback started");
1146                         }
1147                         else
1148                         {
1149                             PecControlSP.s = IPS_ALERT;
1150                             return false;
1151                         }
1152                     }
1153                     else
1154                     {
1155                         LOG_WARN("Incorrect state to start PEC Playback");
1156                     }
1157                     break;
1158                 case PEC_Record:
1159                     LOG_DEBUG("start PEC record");
1160                     if (TrackState != TelescopeStatus::SCOPE_TRACKING)
1161                     {
1162                         LOG_WARN("Mount must be Tracking to record PEC");
1163                         break;
1164                     }
1165                     if (driver.pecState == PEC_STATE::PEC_INDEXED)
1166                     {
1167                         if (driver.PecRecord(true))
1168                         {
1169                             PecControlSP.s = IPS_BUSY;
1170                             LOG_INFO("PEC Record started");
1171                         }
1172                         else
1173                         {
1174                             PecControlSP.s = IPS_ALERT;
1175                             return false;
1176                         }
1177                     }
1178                     else
1179                     {
1180                         LOG_WARN("Incorrect state to start PEC Recording");
1181                     }
1182                     break;
1183                 case PEC_Seek:
1184                     LOG_DEBUG("Seek PEC Index");
1185                     if (driver.isPecAtIndex(true))
1186                     {
1187                         LOG_INFO("PEC index already found");
1188                         PecControlS[PEC_Seek].s = ISS_OFF;
1189                     }
1190                     else if (driver.pecState == PEC_STATE::PEC_AVAILABLE)
1191                     {
1192                         // start seek, moves up to 2 degrees in Ra
1193                         if (driver.PecSeekIndex())
1194                         {
1195                             PecControlSP.s = IPS_BUSY;
1196                             LOG_INFO("Seek PEC index started");
1197                         }
1198                         else
1199                         {
1200                             PecControlSP.s = IPS_ALERT;
1201                             return false;
1202                         }
1203                     }
1204                     break;
1205             }
1206             IDSetSwitch(&PecControlSP, nullptr);
1207             return true;
1208         }
1209 
1210         // Focuser
1211         if (strstr(name, "FOCUS"))
1212         {
1213             return FI::processSwitch(dev, name, states, names, n);
1214         }
1215     }
1216 
1217     return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
1218 }
1219 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)1220 bool CelestronGPS::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
1221 {
1222     if (dev && std::string(dev) == getDeviceName())
1223     {
1224         // Guide Rate
1225         if (strcmp(name, "GUIDE_RATE") == 0)
1226         {
1227             IUUpdateNumber(&GuideRateNP, values, names, n);
1228             GuideRateNP.s = IPS_OK;
1229             IDSetNumber(&GuideRateNP, nullptr);
1230             uint8_t grRa  = static_cast<uint8_t>(std::min(GuideRateN[AXIS_RA].value * 256.0, 255.0));
1231             uint8_t grDec = static_cast<uint8_t>(std::min(GuideRateN[AXIS_DE].value * 256.0, 255.0));
1232             //LOGF_DEBUG("Set Guide Rates (0-1x sidereal): Ra %f, Dec %f", GuideRateN[AXIS_RA].value, GuideRateN[AXIS_DE].value);
1233             //LOGF_DEBUG("Set Guide Rates         (0-255): Ra %i, Dec %i", grRa, grDec);
1234             LOGF_DEBUG("Set Guide Rates: Ra %f, Dec %f", GuideRateN[AXIS_RA].value, GuideRateN[AXIS_DE].value);
1235             driver.set_guide_rate(CELESTRON_AXIS::RA_AXIS, grRa);
1236             driver.set_guide_rate(CELESTRON_AXIS::DEC_AXIS, grDec);
1237             LOG_WARN("Changing guide rates may require recalibration of guiding.");
1238             return true;
1239         }
1240 
1241         //GUIDE process Guider properties.
1242         processGuiderProperties(name, values, names, n);
1243 
1244         if (strstr(name, "FOCUS_"))
1245         {
1246             return FI::processNumber(dev, name, values, names, n);
1247         }
1248     }
1249 
1250     INDI::Telescope::ISNewNumber(dev, name, values, names, n);
1251     return true;
1252 }
1253 
ISNewText(const char * dev,const char * name,char ** texts,char ** names,int n)1254 bool CelestronGPS::ISNewText(const char *dev, const char *name, char **texts, char **names, int n)
1255 {
1256     // the idea is that pressing "Set" on the PEC_LOAD text will load the data in the file specified in the text
1257     if (dev && std::string(dev) == getDeviceName())
1258     {
1259         LOGF_DEBUG("ISNewText name %s, text %s, names %s, n %d", name, texts[0], names[0], n);
1260 
1261         if (name && std::string(name) == "PEC_LOAD")
1262         {
1263 
1264             IUUpdateText(&PecFileNameTP, texts, names, n);
1265             IDSetText(&PecFileNameTP, nullptr);
1266 
1267             LOGF_DEBUG("PEC Set %s", PecFileNameT[0].text);
1268 
1269             PecData pecData;
1270 
1271             // load from file
1272             if (!pecData.Load(PecFileNameT[0].text))
1273             {
1274                 LOGF_WARN("File %s load failed", PecFileNameT[0].text);
1275                 return false;
1276             }
1277             // save to mount
1278             if (!pecData.Save(&driver))
1279             {
1280                 LOGF_WARN("PEC Data file %s save to mount failed", PecFileNameT[0].text);
1281                 return false;
1282             }
1283             LOGF_INFO("PEC Data file %s sent to mount", PecFileNameT[0].text);
1284         }
1285     }
1286 
1287     INDI::Telescope::ISNewText(dev, name, texts, names, n);
1288     return true;
1289 }
1290 
1291 
SetFocuserBacklash(int32_t steps)1292 bool CelestronGPS::SetFocuserBacklash(int32_t steps)
1293 {
1294     // Just update the number
1295     INDI_UNUSED(steps);
1296     return true;
1297 }
1298 
mountSim()1299 void CelestronGPS::mountSim()
1300 {
1301     static struct timeval ltv;
1302     struct timeval tv;
1303     double dt, dx, da_ra = 0, da_dec = 0;
1304     int nlocked;
1305 
1306     // update elapsed time since last poll, don't presume exactly POLLMS
1307     gettimeofday(&tv, nullptr);
1308 
1309     if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1310         ltv = tv;
1311 
1312     dt  = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1313     ltv = tv;
1314 
1315     if (fabs(targetRA - currentRA) * 15. >= GOTO_LIMIT)
1316         da_ra = GOTO_RATE * dt;
1317     else if (fabs(targetRA - currentRA) * 15. >= SLEW_LIMIT)
1318         da_ra = SLEW_RATE * dt;
1319     else
1320         da_ra = FINE_SLEW_RATE * dt;
1321 
1322     if (fabs(targetDEC - currentDEC) >= GOTO_LIMIT)
1323         da_dec = GOTO_RATE * dt;
1324     else if (fabs(targetDEC - currentDEC) >= SLEW_LIMIT)
1325         da_dec = SLEW_RATE * dt;
1326     else
1327         da_dec = FINE_SLEW_RATE * dt;
1328 
1329     if (MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY)
1330     {
1331         int rate = IUFindOnSwitchIndex(&SlewRateSP);
1332 
1333         switch (rate)
1334         {
1335             case SLEW_GUIDE:
1336                 da_ra  = FINE_SLEW_RATE * dt * 0.05;
1337                 da_dec = FINE_SLEW_RATE * dt * 0.05;
1338                 break;
1339 
1340             case SLEW_CENTERING:
1341                 da_ra  = FINE_SLEW_RATE * dt * .1;
1342                 da_dec = FINE_SLEW_RATE * dt * .1;
1343                 break;
1344 
1345             case SLEW_FIND:
1346                 da_ra  = SLEW_RATE * dt;
1347                 da_dec = SLEW_RATE * dt;
1348                 break;
1349 
1350             default:
1351                 da_ra  = GOTO_RATE * dt;
1352                 da_dec = GOTO_RATE * dt;
1353                 break;
1354         }
1355 
1356         switch (MovementNSSP.s)
1357         {
1358             case IPS_BUSY:
1359                 if (MovementNSS[DIRECTION_NORTH].s == ISS_ON)
1360                     currentDEC += da_dec;
1361                 else if (MovementNSS[DIRECTION_SOUTH].s == ISS_ON)
1362                     currentDEC -= da_dec;
1363                 break;
1364 
1365             default:
1366                 break;
1367         }
1368 
1369         switch (MovementWESP.s)
1370         {
1371             case IPS_BUSY:
1372                 if (MovementWES[DIRECTION_WEST].s == ISS_ON)
1373                     currentRA += da_ra / 15.;
1374                 else if (MovementWES[DIRECTION_EAST].s == ISS_ON)
1375                     currentRA -= da_ra / 15.;
1376                 break;
1377 
1378             default:
1379                 break;
1380         }
1381 
1382         driver.set_sim_ra(currentRA);
1383         driver.set_sim_dec(currentDEC);
1384 
1385         NewRaDec(currentRA, currentDEC);
1386 
1387         return;
1388     }
1389 
1390     // Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly
1391     switch (TrackState)
1392     {
1393         case SCOPE_IDLE:
1394             currentRA = driver.get_sim_ra() + (TRACKRATE_SIDEREAL / 3600.0 * dt) / 15.0;
1395             currentRA = range24(currentRA);
1396             break;
1397 
1398         case SCOPE_SLEWING:
1399         case SCOPE_PARKING:
1400             // slewing - nail it when both within one pulse @ SLEWRATE
1401             nlocked = 0;
1402 
1403             dx = targetRA - currentRA;
1404 
1405             // Take shortest path
1406             if (fabs(dx) > 12)
1407                 dx *= -1;
1408 
1409             if (fabs(dx) <= da_ra)
1410             {
1411                 currentRA = targetRA;
1412                 nlocked++;
1413             }
1414             else if (dx > 0)
1415                 currentRA += da_ra / 15.;
1416             else
1417                 currentRA -= da_ra / 15.;
1418 
1419             if (currentRA < 0)
1420                 currentRA += 24;
1421             else if (currentRA > 24)
1422                 currentRA -= 24;
1423 
1424             dx = targetDEC - currentDEC;
1425             if (fabs(dx) <= da_dec)
1426             {
1427                 currentDEC = targetDEC;
1428                 nlocked++;
1429             }
1430             else if (dx > 0)
1431                 currentDEC += da_dec;
1432             else
1433                 currentDEC -= da_dec;
1434 
1435             if (nlocked == 2)
1436             {
1437                 driver.set_sim_slewing(false);
1438             }
1439 
1440             break;
1441 
1442         default:
1443             break;
1444     }
1445 
1446     driver.set_sim_ra(currentRA);
1447     driver.set_sim_dec(currentDEC);
1448 }
1449 
simulationTriggered(bool enable)1450 void CelestronGPS::simulationTriggered(bool enable)
1451 {
1452     driver.set_simulation(enable);
1453 }
1454 
1455 // Update Location and time are disabled if the mount is aligned.  This is because
1456 // changing either will change the mount model because at least the local sidereal time
1457 // will be changed. StarSense will set the mount to unaligned but it isn't a good idea even
1458 // with the NexStar HCs
1459 
updateLocation(double latitude,double longitude,double elevation)1460 bool CelestronGPS::updateLocation(double latitude, double longitude, double elevation)
1461 {
1462     if (!isConnected())
1463     {
1464         LOG_DEBUG("updateLocation called before we are connected");
1465         return false;
1466     }
1467 
1468     if (!checkMinVersion(2.3, "updating location"))
1469         return false;
1470 
1471     bool isAligned;
1472     if (!driver.check_aligned(&isAligned))
1473     {
1474         LOG_INFO("Update location - check_aligned failed");
1475         return false;
1476     }
1477 
1478     if (isAligned)
1479     {
1480         LOG_INFO("Updating location is not necessary since mount is already aligned.");
1481         return false;
1482     }
1483 
1484     LOGF_DEBUG("Update location %8.3f, %8.3f, %4.0f", latitude, longitude, elevation);
1485 
1486     return driver.set_location(longitude, latitude);
1487 }
1488 
updateTime(ln_date * utc,double utc_offset)1489 bool CelestronGPS::updateTime(ln_date *utc, double utc_offset)
1490 {
1491     if (!isConnected())
1492     {
1493         LOG_DEBUG("updateTime called before we are connected");
1494         return false;
1495     }
1496 
1497     if (!checkMinVersion(2.3, "updating time"))
1498         return false;
1499 
1500     // setting time on StarSense seems to make it not aligned
1501     bool isAligned;
1502     if (!driver.check_aligned(&isAligned))
1503     {
1504         LOG_INFO("UpdateTime - check_aligned failed");
1505         return false;
1506     }
1507     if (isAligned)
1508     {
1509         LOG_INFO("Updating time is not necessary since mount is already aligned.");
1510         return false;
1511     }
1512 
1513     // starsense HC doesn't seem to support the precise time setting
1514     bool precise = fwInfo.controllerVersion >= 5.28;
1515 
1516     bool dst = DSTSettingS[0].s == ISS_ON;
1517 
1518     LOGF_DEBUG("Update time: offset %f %s UTC %i-%02i-%02iT%02i:%02i:%02.0f", utc_offset, dst ? "DST" : "", utc->years,
1519                utc->months, utc->days,
1520                utc->hours, utc->minutes, utc->seconds);
1521 
1522     return (driver.set_datetime(utc, utc_offset, dst, precise));
1523 }
1524 
Park()1525 bool CelestronGPS::Park()
1526 {
1527     double parkAZ  = GetAxis1Park();
1528     double parkAlt = GetAxis2Park();
1529 
1530     char AzStr[16], AltStr[16];
1531     fs_sexa(AzStr, parkAZ, 2, 3600);
1532     fs_sexa(AltStr, parkAlt, 2, 3600);
1533 
1534     // unsync is only for NS+ 5.29 or more and not StarSense
1535     if (fwInfo.controllerVersion >= 5.29 && !driver.unsync())
1536         return false;
1537 
1538     LOGF_DEBUG("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
1539 
1540     if (driver.slew_azalt(parkAZ, parkAlt, usePreciseCoords))
1541     {
1542         TrackState = SCOPE_PARKING;
1543         LOG_INFO("Parking is in progress...");
1544         return true;
1545     }
1546 
1547     return false;
1548 }
1549 
UnPark()1550 bool CelestronGPS::UnPark()
1551 {
1552     bool parkDataValid = (LoadParkData() == nullptr);
1553     // Check if we need to wake up IF:
1554     // 1. Park data exists in ParkData.xml
1555     // 2. Mount is currently parked
1556     // 3. Hibernate option is enabled
1557     if (parkDataValid && isParked() && UseHibernateS[0].s == ISS_ON)
1558     {
1559         LOG_INFO("Waking up mount...");
1560 
1561         if (!driver.wakeup())
1562         {
1563             LOG_ERROR("Waking up mount failed! Make sure mount is powered and connected. "
1564                       "Hibernate requires firmware version >= 5.21");
1565             return false;
1566         }
1567     }
1568 
1569     // Set tracking mode to whatever it was stored before
1570     SetParked(false);
1571 
1572     //loadConfig(true, "TELESCOPE_TRACK_MODE");
1573     // Read Saved Track State from config file
1574     for (int i = 0; i < TrackStateSP.nsp; i++)
1575         IUGetConfigSwitch(getDeviceName(), TrackStateSP.name, TrackStateS[i].name, &(TrackStateS[i].s));
1576 
1577     // set the mount tracking state
1578     LOGF_DEBUG("track state %s", IUFindOnSwitch(&TrackStateSP)->label);
1579     SetTrackEnabled(IUFindOnSwitchIndex(&TrackStateSP) == TRACK_ON);
1580 
1581     // reinit PEC
1582     if (driver.pecState >= PEC_STATE::PEC_AVAILABLE)
1583         driver.pecState = PEC_AVAILABLE;
1584 
1585     return true;
1586 }
1587 
SetCurrentPark()1588 bool CelestronGPS::SetCurrentPark()
1589 {
1590     // The Goto Alt-Az and Get Alt-Az menu items have been renamed Goto Axis Postn and Get Axis Postn
1591     // where Postn is an abbreviation for Position. Since this feature doesn't actually refer
1592     // to altitude and azimuth when mounted on a wedge, the new designation is more accurate.
1593     // Source  : NexStarHandControlVersion4UsersGuide.pdf
1594 
1595     if (driver.get_azalt(&currentAZ, &currentALT, usePreciseCoords) == false)
1596     {
1597         LOG_ERROR("Failed to read AZ/ALT values.");
1598         return false;
1599     }
1600 
1601     double parkAZ = currentAZ;
1602     double parkAlt = currentALT;
1603 
1604     char AzStr[16], AltStr[16];
1605     fs_sexa(AzStr, parkAZ, 2, 3600);
1606     fs_sexa(AltStr, parkAlt, 2, 3600);
1607 
1608     LOGF_DEBUG("Setting current parking position to coordinates Az (%s) Alt (%s)...", AzStr,
1609                AltStr);
1610 
1611     SetAxis1Park(parkAZ);
1612     SetAxis2Park(parkAlt);
1613 
1614     return true;
1615 }
1616 
SetDefaultPark()1617 bool CelestronGPS::SetDefaultPark()
1618 {
1619     // The Goto Alt-Az and Get Alt-Az menu items have been renamed Goto Axis Postn and Get Axis Postn
1620     // where Postn is an abbreviation for Position. Since this feature doesn't actually refer
1621     // to altitude and azimuth when mounted on a wedge, the new designation is more accurate.
1622     // Source  : NexStarHandControlVersion4UsersGuide.pdf
1623 
1624     // By default azimuth 90° ( hemisphere doesn't matter)
1625     SetAxis1Park(90);
1626 
1627     // Altitude = 90° (latitude doesn't matter)
1628     SetAxis2Park(90);
1629 
1630     return true;
1631 }
1632 
saveConfigItems(FILE * fp)1633 bool CelestronGPS::saveConfigItems(FILE *fp)
1634 {
1635     INDI::Telescope::saveConfigItems(fp);
1636     FI::saveConfigItems(fp);
1637 
1638     IUSaveConfigSwitch(fp, &UseHibernateSP);
1639     IUSaveConfigSwitch(fp, &CelestronTrackModeSP);
1640     IUSaveConfigSwitch(fp, &DSTSettingSP);
1641 
1642     IUSaveConfigNumber(fp, &FocusMinPosNP);
1643 
1644     return true;
1645 }
1646 
setCelestronTrackMode(CELESTRON_TRACK_MODE mode)1647 bool CelestronGPS::setCelestronTrackMode(CELESTRON_TRACK_MODE mode)
1648 {
1649     if (driver.set_track_mode(mode))
1650     {
1651         TrackState = (mode == CTM_OFF) ? SCOPE_IDLE : SCOPE_TRACKING;
1652         LOGF_DEBUG("Tracking mode set to %i, %s.", mode, CelestronTrackModeS[mode - 1].label);
1653         return true;
1654     }
1655 
1656     return false;
1657 }
1658 
1659 //GUIDE Guiding functions.
1660 
1661 // There have been substantial changes at version 3.3, Nov 2019
1662 //
1663 // The mount controlled Aux Guide is used if it is available, this is
1664 // if the mount firmware version for both axes is 6.12 or better.  Other
1665 // mounts use a timed guide method.
1666 // The mount Aux Guide command has a maximum vulue of 2.55 seconds but if
1667 // a longer guide is needed then multiple Aux Guide commands are sent.
1668 //
1669 // The start guide and stop guide functions use helper functions to avoid
1670 // code duplication.
1671 
GuideNorth(uint32_t ms)1672 IPState CelestronGPS::GuideNorth(uint32_t ms)
1673 {
1674     return Guide(CELESTRON_DIRECTION::CELESTRON_N, ms);
1675 }
1676 
GuideSouth(uint32_t ms)1677 IPState CelestronGPS::GuideSouth(uint32_t ms)
1678 {
1679     return Guide(CELESTRON_DIRECTION::CELESTRON_S, ms);
1680 }
1681 
GuideEast(uint32_t ms)1682 IPState CelestronGPS::GuideEast(uint32_t ms)
1683 {
1684     return Guide(CELESTRON_DIRECTION::CELESTRON_E, ms);
1685 }
1686 
GuideWest(uint32_t ms)1687 IPState CelestronGPS::GuideWest(uint32_t ms)
1688 {
1689     return Guide(CELESTRON_DIRECTION::CELESTRON_W, ms);
1690 }
1691 
1692 // common function to start guiding for all axes.
Guide(CELESTRON_DIRECTION dirn,uint32_t ms)1693 IPState CelestronGPS::Guide(CELESTRON_DIRECTION dirn, uint32_t ms)
1694 {
1695     // set up direction properties
1696     char dc = 'x';
1697     ISwitchVectorProperty *moveSP = &MovementNSSP;
1698     ISwitch moveS = MovementNSS[0];
1699     int* guideTID = &GuideNSTID;
1700     int* ticks = &ticksNS;
1701     uint8_t rate = 50;
1702 
1703     // set up pointers to the various things needed
1704     switch (dirn)
1705     {
1706         case CELESTRON_N:
1707             dc = 'N';
1708             moveSP = &MovementNSSP;
1709             moveS = MovementNSS[0];
1710             guideTID = &GuideNSTID;
1711             ticks = &ticksNS;
1712             /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see  CelestronDriver::send_pulse() */
1713             rate = guideRateDec = static_cast<uint8_t>(GuideRateN[AXIS_DE].value * 100.0);
1714             break;
1715         case CELESTRON_S:
1716             dc = 'S';
1717             moveSP = &MovementNSSP;
1718             moveS = MovementNSS[1];
1719             guideTID = &GuideNSTID;
1720             ticks = &ticksNS;
1721             /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see  CelestronDriver::send_pulse() */
1722             rate = guideRateDec = static_cast<uint8_t>(GuideRateN[AXIS_DE].value * 100.0);
1723             break;
1724         case CELESTRON_E:
1725             dc = 'E';
1726             moveSP = &MovementWESP;
1727             moveS = MovementWES[1];
1728             guideTID = &GuideWETID;
1729             ticks = &ticksWE;
1730             /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see  CelestronDriver::send_pulse() */
1731             rate = guideRateRa = static_cast<uint8_t>(GuideRateN[AXIS_RA].value * 100.0);
1732             break;
1733         case CELESTRON_W:
1734             dc = 'W';
1735             moveSP = &MovementWESP;
1736             moveS = MovementWES[0];
1737             guideTID = &GuideWETID;
1738             ticks = &ticksWE;
1739             /* Scale guide rates to uint8 in [0..100] for sending to telescopoe, see  CelestronDriver::send_pulse() */
1740             rate = guideRateRa = static_cast<uint8_t>(GuideRateN[AXIS_RA].value * 100.0);
1741             break;
1742     }
1743 
1744     LOGF_DEBUG("GUIDE CMD: %c %u ms, %s guide", dc, ms, canAuxGuide ? "Aux" : "Time");
1745 
1746     if (!canAuxGuide && (MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY))
1747     {
1748         LOG_ERROR("Cannot guide while moving.");
1749         return IPS_ALERT;
1750     }
1751 
1752     // If already moving (no pulse command), then stop movement
1753     if (moveSP->s == IPS_BUSY)
1754     {
1755         LOG_DEBUG("Already moving - stop");
1756         driver.stop_motion(dirn);
1757     }
1758 
1759     if (*guideTID)
1760     {
1761         LOGF_DEBUG("Stop timer %c", dc);
1762         IERmTimer(*guideTID);
1763         *guideTID = 0;
1764     }
1765 
1766     if (canAuxGuide)
1767     {
1768         // get the number of 10ms hardware ticks
1769         *ticks = ms / 10;
1770 
1771         // send the first Aux Guide command,
1772         if (driver.send_pulse(dirn, rate, static_cast<char>(std::min(255, *ticks))) == 0)
1773         {
1774             LOGF_ERROR("send_pulse %c error", dc);
1775             return IPS_ALERT;
1776         }
1777         // decrease ticks and ms values
1778         *ticks -= 255;
1779         ms = ms > 2550 ? 2550 : ms;
1780     }
1781     else
1782     {
1783         moveS.s = ISS_ON;
1784         // start movement at HC button rate 1
1785         if (!driver.start_motion(dirn, CELESTRON_SLEW_RATE::SR_1))
1786         {
1787             LOGF_ERROR("StartMotion %c failed", dc);
1788             return IPS_ALERT;
1789         }
1790         *ticks = 0;
1791     }
1792 
1793     // Set slew to guiding
1794     IUResetSwitch(&SlewRateSP);
1795     SlewRateS[SLEW_GUIDE].s = ISS_ON;
1796     IDSetSwitch(&SlewRateSP, nullptr);
1797     // start the guide timeout timer
1798     AddGuideTimer(dirn, static_cast<int>(ms));
1799     return IPS_BUSY;
1800 }
1801 
1802 //GUIDE The timer helper functions.
guideTimerHelperN(void * p)1803 void CelestronGPS::guideTimerHelperN(void *p)
1804 {
1805     static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_N);
1806 }
1807 
guideTimerHelperS(void * p)1808 void CelestronGPS::guideTimerHelperS(void *p)
1809 {
1810     static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_S);
1811 }
1812 
guideTimerHelperW(void * p)1813 void CelestronGPS::guideTimerHelperW(void *p)
1814 {
1815     static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_W);
1816 }
1817 
guideTimerHelperE(void * p)1818 void CelestronGPS::guideTimerHelperE(void *p)
1819 {
1820     static_cast<CelestronGPS *>(p)->guideTimer(CELESTRON_E);
1821 }
1822 
1823 //GUIDE The timer function
1824 
1825 /* Here I splitted the behaviour depending upon the direction
1826  * of the  guide command which generates the timer;  this was
1827  * done because the  member variable "guide_direction"  could
1828  * be  modified by a pulse  command on the  other axis BEFORE
1829  * the calling pulse command is terminated.
1830  */
1831 
guideTimer(CELESTRON_DIRECTION dirn)1832 void CelestronGPS::guideTimer(CELESTRON_DIRECTION dirn)
1833 {
1834     int* ticks = &ticksNS;
1835     uint8_t rate = 0;
1836 
1837     switch(dirn)
1838     {
1839         case CELESTRON_N:
1840         case CELESTRON_S:
1841             ticks = &ticksNS;
1842             rate = guideRateDec;
1843             break;
1844         case CELESTRON_E:
1845         case CELESTRON_W:
1846             ticks = &ticksWE;
1847             rate = guideRateRa;
1848             break;
1849     }
1850 
1851     LOGF_DEBUG("guideTimer dir %c, ticks %i, rate %i", "NSWE"[dirn], *ticks, rate);
1852 
1853     if (canAuxGuide)
1854     {
1855         if (driver.get_pulse_status(dirn))
1856         {
1857             // curent move not finished, add some more time
1858             AddGuideTimer(dirn, 100);
1859             return;
1860         }
1861         if (*ticks > 0)
1862         {
1863             // do some more guiding and set the timeout
1864             int dt =  (*ticks > 255) ? 255 : *ticks;
1865             driver.send_pulse(dirn, rate, static_cast<uint8_t>(dt));
1866             AddGuideTimer(dirn, dt * 10);
1867             *ticks -= 255;
1868             return;
1869         }
1870         // we get here if the axis reports guiding finished and all the ticks have been done
1871     }
1872     else
1873     {
1874         if (!driver.stop_motion(dirn))
1875             LOGF_ERROR("StopMotion failed dir %c", "NSWE"[dirn]);
1876     }
1877 
1878     switch(dirn)
1879     {
1880         case CELESTRON_N:
1881         case CELESTRON_S:
1882             IUResetSwitch(&MovementNSSP);
1883             IDSetSwitch(&MovementNSSP, nullptr);
1884             GuideNSNP.np[0].value = 0;
1885             GuideNSNP.np[1].value = 0;
1886             GuideNSNP.s           = IPS_IDLE;
1887             GuideNSTID            = 0;
1888             IDSetNumber(&GuideNSNP, nullptr);
1889             break;
1890         case CELESTRON_E:
1891         case CELESTRON_W:
1892             IUResetSwitch(&MovementWESP);
1893             IDSetSwitch(&MovementWESP, nullptr);
1894             GuideWENP.np[0].value = 0;
1895             GuideWENP.np[1].value = 0;
1896             GuideWENP.s           = IPS_IDLE;
1897             GuideWETID            = 0;
1898             IDSetNumber(&GuideWENP, nullptr);
1899             break;
1900     }
1901     LOGF_DEBUG("Guide %c finished", "NSWE"[dirn]);
1902 }
1903 
AddGuideTimer(CELESTRON_DIRECTION dirn,int ms)1904 void CelestronGPS::AddGuideTimer(CELESTRON_DIRECTION dirn, int ms)
1905 {
1906     switch(dirn)
1907     {
1908         case CELESTRON_N:
1909             GuideNSTID = IEAddTimer(ms, guideTimerHelperN, this);
1910             break;
1911         case CELESTRON_S:
1912             GuideNSTID = IEAddTimer(ms, guideTimerHelperS, this);
1913             break;
1914         case CELESTRON_E:
1915             GuideWETID = IEAddTimer(ms, guideTimerHelperE, this);
1916             break;
1917         case CELESTRON_W:
1918             GuideWETID = IEAddTimer(ms, guideTimerHelperW, this);
1919             break;
1920     }
1921 }
1922 
1923 // end of guiding code
1924 
1925 // the INDI overload, expected to set the track rate
1926 // sidereal, solar or lunar and only if the mount is equatorial
SetTrackMode(uint8_t mode)1927 bool CelestronGPS::SetTrackMode(uint8_t mode)
1928 {
1929     CELESTRON_TRACK_RATE rate;
1930 
1931     switch (fwInfo.celestronTrackMode)
1932     {
1933         case CTM_OFF:
1934         case CTM_ALTAZ:
1935         case CTM_RADEC:
1936             return false;
1937         case CTM_EQN:
1938         case CTM_EQS:
1939             break;
1940     }
1941 
1942     switch (mode)
1943     {
1944         case 0:
1945             rate = CTR_SIDEREAL;
1946             break;
1947         case 1:
1948             rate = CTR_SOLAR;
1949             break;
1950         case 2:
1951             rate = CTR_LUNAR;
1952             break;
1953         default:
1954             return false;
1955     }
1956     return driver.set_track_rate(rate, fwInfo.celestronTrackMode);
1957 }
1958 
SetTrackEnabled(bool enabled)1959 bool CelestronGPS::SetTrackEnabled(bool enabled)
1960 {
1961     return setCelestronTrackMode(enabled ? fwInfo.celestronTrackMode : CTM_OFF);
1962     //return setTrackMode(enabled ? static_cast<CELESTRON_TRACK_MODE>(IUFindOnSwitchIndex(&TrackModeSP)+1) : TRACKING_OFF);
1963 }
1964 
checkAlignment()1965 void CelestronGPS::checkAlignment()
1966 {
1967     ReadScopeStatus();
1968 
1969     bool isAligned;
1970     if (!driver.check_aligned(&isAligned) || !isAligned)
1971         LOG_WARN("Mount is NOT aligned. You must align the mount first before you can use it. Disconnect, align the mount, and reconnect again.");
1972 }
1973 
savePecData()1974 bool CelestronGPS::savePecData()
1975 {
1976     // generate the file name:
1977     // ~/.indi/pec/yyyy-mm-dd/pecData_hh:mm.log
1978 
1979     char ts_date[32], ts_time[32];
1980     struct tm *tp;
1981     time_t t;
1982 
1983     time(&t);
1984     tp = gmtime(&t);
1985     strftime(ts_date, sizeof(ts_date), "%Y-%m-%d", tp);
1986     strftime(ts_time, sizeof(ts_time), "%H:%M", tp);
1987 
1988     char dir[MAXRBUF];
1989     snprintf(dir, MAXRBUF, "%s/PEC_Data/%s", getenv("HOME"), ts_date);
1990 
1991     if (INDI::mkpath(dir, 0755) == -1)
1992     {
1993         LOGF_ERROR("Error creating directory %s (%s)", dir, strerror(errno));
1994         return false;
1995     }
1996 
1997     char pecFileBuf[MAXRBUF];
1998     snprintf(pecFileBuf, MAXRBUF, "%s/pecData_%s.csv", dir, ts_time);
1999 
2000     // show the file name
2001     IUSaveText(&PecFileNameT[0], pecFileBuf);
2002     IDSetText(&PecFileNameTP, nullptr);
2003 
2004     // get the PEC data from the mount
2005     PecData pecdata;
2006 
2007     if (!pecdata.Load(&driver))
2008     {
2009         LOG_DEBUG("Load PEC from mount failed");
2010         return false;
2011     }
2012     pecdata.RemoveDrift();
2013     // and save it
2014     if (!pecdata.Save(pecFileBuf))
2015     {
2016         LOGF_DEBUG("Save PEC file %s failed", pecFileBuf);
2017         return false;
2018     }
2019     LOGF_INFO("PEC data saved to %s", pecFileBuf);
2020     return true;
2021 }
2022 
2023 // focus control
MoveAbsFocuser(uint32_t targetTicks)2024 IPState CelestronGPS::MoveAbsFocuser(uint32_t targetTicks)
2025 {
2026     uint32_t position = targetTicks;
2027 
2028     if (!focuserIsCalibrated)
2029     {
2030         LOG_ERROR("Move is not allowed because the focuser is not calibrated");
2031         return IPS_ALERT;
2032     }
2033 
2034     // implement backlash
2035     int delta = static_cast<int>(targetTicks - FocusAbsPosN[0].value);
2036 
2037     if ((FocusBacklashN[0].value < 0 && delta > 0) ||
2038             (FocusBacklashN[0].value > 0 && delta < 0))
2039     {
2040         focusBacklashMove = true;
2041         focusPosition = position;
2042         position -= FocusBacklashN[0].value;
2043     }
2044 
2045     LOGF_INFO("Focus %s move %d", focusBacklashMove ? "backlash" : "direct", position);
2046 
2047     if(!driver.foc_move(position))
2048         return IPS_ALERT;
2049 
2050     return IPS_BUSY;
2051 }
2052 
MoveRelFocuser(INDI::FocuserInterface::FocusDirection dir,uint32_t ticks)2053 IPState CelestronGPS::MoveRelFocuser(INDI::FocuserInterface::FocusDirection dir, uint32_t ticks)
2054 {
2055     uint32_t newPosition = 0;
2056 
2057     if (dir == FOCUS_INWARD)
2058         newPosition = static_cast<uint32_t>(FocusAbsPosN[0].value) - ticks;
2059     else
2060         newPosition = static_cast<uint32_t>(FocusAbsPosN[0].value) + ticks;
2061 
2062     // Clamp
2063     newPosition = std::min(static_cast<uint32_t>(FocusAbsPosN[0].max), newPosition);
2064     return MoveAbsFocuser(newPosition);
2065 }
2066 
AbortFocuser()2067 bool CelestronGPS::AbortFocuser()
2068 {
2069     return driver.foc_abort();
2070 }
2071 
2072 // read the focuser limits from the hardware
focusReadLimits()2073 bool CelestronGPS::focusReadLimits()
2074 {
2075     int low, high;
2076     bool valid = driver.foc_limits(&low, &high);
2077 
2078     FocusAbsPosN[0].max = high;
2079     FocusAbsPosN[0].min = low;
2080     FocusAbsPosNP.s = IPS_OK;
2081     IUUpdateMinMax(&FocusAbsPosNP);
2082 
2083     FocusMaxPosN[0].value = high;
2084     FocusMaxPosNP.s = IPS_OK;
2085     IDSetNumber(&FocusMaxPosNP, nullptr);
2086 
2087     FocusMinPosN[0].value = low;
2088     FocusMinPosNP.s = IPS_OK;
2089     IDSetNumber(&FocusMinPosNP, nullptr);
2090 
2091     focuserIsCalibrated = valid;
2092 
2093     LOGF_INFO("Focus Limits: Maximum (%i) Minimum (%i) steps.", high, low);
2094     return valid;
2095 }
2096