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(¤tRA, ¤tDEC, 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(¤tAZ, ¤tALT, 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