Instrument Neutral Distributed Interface INDI 2.2.5
inditelescope.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2011 Gerry Rozema, Jasem Mutlaq. All rights reserved.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*******************************************************************************/
18
19#include "inditelescope.h"
20
21#include "indicom.h"
22#include "indicontroller.h"
25
26#include <libnova/sidereal_time.h>
27#include <libnova/transform.h>
28
29#include <cmath>
30#include <cerrno>
31#include <pwd.h>
32#include <cstdlib>
33#include <cstring>
34#include <ctime>
35#include <unistd.h>
36#include <wordexp.h>
37#include <limits>
38
39namespace INDI
40{
41
43 : DefaultDevice(), ParkDataFileName(GetHomeDirectory() + "/.indi/ParkData.xml")
44{
45 controller = new Controller(this);
47 controller->setAxisCallback(axisHelper);
49
52
55}
56
58{
59 if (ParkdataXmlRoot)
60 delXMLEle(ParkdataXmlRoot);
61
62 delete (controller);
63}
64
66{
68
69 // Since this property was created in setTelescopeCapability before a device name is defined.
70 // Then we need to set the device name here
72
73 // Active Devices
74 // @INDI_STANDARD_PROPERTY@
75 ActiveDeviceTP[ACTIVE_GPS].fill("ACTIVE_GPS", "GPS", "GPS Simulator");
76 ActiveDeviceTP[ACTIVE_DOME].fill("ACTIVE_DOME", "DOME", "Dome Simulator");
77 ActiveDeviceTP.fill(getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
79
80 // Use locking if dome is closed (and or) park scope if dome is closing
81 // @INDI_STANDARD_PROPERTY@
82 DomePolicySP[DOME_IGNORED].fill("DOME_IGNORED", "Dome ignored", ISS_ON);
83 DomePolicySP[DOME_LOCKS].fill("DOME_LOCKS", "Dome locks", ISS_OFF);
84 DomePolicySP.fill(getDeviceName(), "DOME_POLICY", "Dome Policy", OPTIONS_TAB, IP_RW,
86
87 // @INDI_STANDARD_PROPERTY@
88 EqNP[AXIS_RA].fill("RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
89 EqNP[AXIS_DE].fill("DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
90 EqNP.fill(getDeviceName(), "EQUATORIAL_EOD_COORD", "Eq. Coordinates", MAIN_CONTROL_TAB,
91 IP_RW, 60, IPS_IDLE);
92 lastEqState = IPS_IDLE;
93
94 // @INDI_STANDARD_PROPERTY@
95 TargetNP[AXIS_RA].fill("RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
96 TargetNP[AXIS_DE].fill("DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
97 TargetNP.fill(getDeviceName(), "TARGET_EOD_COORD", "Slew Target", MOTION_TAB, IP_RO, 60,
98 IPS_IDLE);
99
100 // @INDI_STANDARD_PROPERTY@
101 ParkOptionSP[PARK_CURRENT].fill("PARK_CURRENT", "Current", ISS_OFF);
102 ParkOptionSP[PARK_DEFAULT].fill("PARK_DEFAULT", "Default", ISS_OFF);
103 ParkOptionSP[PARK_WRITE_DATA].fill("PARK_WRITE_DATA", "Write Data", ISS_OFF);
104 ParkOptionSP[PARK_PURGE_DATA].fill("PARK_PURGE_DATA", "Purge Data", ISS_OFF);
105 ParkOptionSP.fill(getDeviceName(), "TELESCOPE_PARK_OPTION", "Park Options", SITE_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
106
107 // @INDI_STANDARD_PROPERTY@
108 TimeTP[UTC].fill("UTC", "UTC Time", nullptr);
109 TimeTP[OFFSET].fill("OFFSET", "UTC Offset", nullptr);
110 TimeTP.fill(getDeviceName(), "TIME_UTC", "UTC", SITE_TAB, IP_RW, 60, IPS_IDLE);
111
112 // @INDI_STANDARD_PROPERTY@
113 LocationNP[LOCATION_LATITUDE].fill("LAT", "Lat (dd:mm:ss.s)", "%012.8m", -90, 90, 0, 0.0);
114 LocationNP[LOCATION_LONGITUDE].fill("LONG", "Lon (dd:mm:ss.s)", "%012.8m", 0, 360, 0, 0.0);
115 LocationNP[LOCATION_ELEVATION].fill("ELEV", "Elevation (m)", "%g", -200, 10000, 0, 0);
116 LocationNP.fill(getDeviceName(), "GEOGRAPHIC_COORD", "Scope Location", SITE_TAB,
117 IP_RW, 60, IPS_IDLE);
118
119 // Pier Side
120 // @INDI_STANDARD_PROPERTY@
121 PierSideSP[PIER_WEST].fill("PIER_WEST", "West (pointing east)", ISS_OFF);
122 PierSideSP[PIER_EAST].fill("PIER_EAST", "East (pointing west)", ISS_OFF);
123 PierSideSP.fill(getDeviceName(), "TELESCOPE_PIER_SIDE", "Pier Side", MAIN_CONTROL_TAB,
125
126 // Pier Side Simulation
127 // @INDI_STANDARD_PROPERTY@
128 SimulatePierSideSP[SIMULATE_YES].fill("SIMULATE_YES", "Yes", ISS_OFF);
129 SimulatePierSideSP[SIMULATE_NO].fill("SIMULATE_NO", "No", ISS_ON);
130 SimulatePierSideSP.fill(getDeviceName(), "SIMULATE_PIER_SIDE", "Simulate Pier Side",
133
134 // PEC State
135 // @INDI_STANDARD_PROPERTY@
136 PECStateSP[PEC_OFF].fill("PEC OFF", "PEC OFF", ISS_ON);
137 PECStateSP[PEC_ON].fill("PEC ON", "PEC ON", ISS_OFF);
138 PECStateSP.fill(getDeviceName(), "PEC", "PEC Playback", MOTION_TAB, IP_RW, ISR_1OFMANY, 0,
139 IPS_IDLE);
140
141 // Mount Type
142 // @INDI_STANDARD_PROPERTY@
143 MountTypeSP[MOUNT_ALTAZ].fill("ALTAZ", "ALTAZ", ISS_OFF);
144 MountTypeSP[MOUNT_EQ_FORK].fill("EQ_FORK", "Fork (Eq)", ISS_OFF);
145 MountTypeSP[MOUNT_EQ_GEM].fill("EQ_GEM", "GEM", ISS_ON);
146 MountTypeSP.fill(getDeviceName(), "TELESCOPE_MOUNT_TYPE", "Mount Type", MOTION_TAB, IP_RO, ISR_1OFMANY, 60, IPS_IDLE);
147
148 // Track Mode. Child class must call AddTrackMode to add members
149 // @INDI_STANDARD_PROPERTY@
150 TrackModeSP.fill(getDeviceName(), "TELESCOPE_TRACK_MODE", "Track Mode", MAIN_CONTROL_TAB,
152
153 // Track State
154 // @INDI_STANDARD_PROPERTY@
155 TrackStateSP[TRACK_ON].fill("TRACK_ON", "On", ISS_OFF);
156 TrackStateSP[TRACK_OFF].fill("TRACK_OFF", "Off", ISS_ON);
157 TrackStateSP.fill(getDeviceName(), "TELESCOPE_TRACK_STATE", "Tracking", MAIN_CONTROL_TAB,
158 IP_RW, ISR_1OFMANY, 0,
159 IPS_IDLE);
160
161 // Track Rate
162 // @INDI_STANDARD_PROPERTY@
163 TrackRateNP[AXIS_RA].fill("TRACK_RATE_RA", "RA (arcsecs/s)", "%.6f", -16384.0, 16384.0, 0.000001,
165 TrackRateNP[AXIS_DE].fill("TRACK_RATE_DE", "DE (arcsecs/s)", "%.6f", -16384.0, 16384.0, 0.000001, 0.0);
166 TrackRateNP.fill(getDeviceName(), "TELESCOPE_TRACK_RATE", "Track Rates", MAIN_CONTROL_TAB,
167 IP_RW, 60, IPS_IDLE);
168
169 // On Coord Set
170 // @INDI_STANDARD_PROPERTY@
171 // Note: Member elements can be TRACK, SLEW, SYNC, and FLIP
172 CoordSP.fill(getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
173 generateCoordSet();
174
175 // @INDI_STANDARD_PROPERTY@
176 if (nSlewRate >= 4)
177 SlewRateSP.fill(getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
179
180 if (CanTrackSatellite())
181 {
182 // @INDI_STANDARD_PROPERTY@
183 TLEtoTrackTP[0].fill("TLE", "TLE", "");
184 TLEtoTrackTP.fill(getDeviceName(), "SAT_TLE_TEXT", "Orbit Params", SATELLITE_TAB,
185 IP_RW, 60, IPS_IDLE);
186
187 char curTime[32] = {0};
188 std::time_t t = std::time(nullptr);
189 struct std::tm *utctimeinfo = std::gmtime(&t);
190 strftime(curTime, sizeof(curTime), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
191
192 // @INDI_STANDARD_PROPERTY@
193 SatPassWindowTP[SAT_PASS_WINDOW_END].fill("SAT_PASS_WINDOW_END", "End UTC", curTime);
194 SatPassWindowTP[SAT_PASS_WINDOW_START].fill("SAT_PASS_WINDOW_START", "Start UTC", curTime);
195 SatPassWindowTP.fill(getDeviceName(), "SAT_PASS_WINDOW", "Pass Window", SATELLITE_TAB, IP_RW, 60, IPS_IDLE);
196
197 // @INDI_STANDARD_PROPERTY@
198 TrackSatSP[SAT_TRACK].fill("SAT_TRACK", "Track", ISS_OFF);
199 TrackSatSP[SAT_HALT].fill("SAT_HALT", "Halt", ISS_ON);
200 TrackSatSP.fill(getDeviceName(), "SAT_TRACKING_STAT",
201 "Sat tracking", SATELLITE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
202 }
203
204 // @INDI_STANDARD_PROPERTY@
205 ParkSP[PARK].fill("PARK", "Park(ed)", ISS_OFF);
206 ParkSP[UNPARK].fill("UNPARK", "UnPark(ed)", ISS_OFF);
207 ParkSP.fill(getDeviceName(), "TELESCOPE_PARK", "Parking", MAIN_CONTROL_TAB, IP_RW,
208 ISR_1OFMANY, 60, IPS_IDLE);
209
210 // @INDI_STANDARD_PROPERTY@
211 AbortSP[0].fill("ABORT", "Abort", ISS_OFF);
212 AbortSP.fill(getDeviceName(), "TELESCOPE_ABORT_MOTION", "Abort Motion", MAIN_CONTROL_TAB,
214
215 // @INDI_STANDARD_PROPERTY@
216 MovementNSSP[DIRECTION_NORTH].fill("MOTION_NORTH", "North", ISS_OFF);
217 MovementNSSP[DIRECTION_SOUTH].fill("MOTION_SOUTH", "South", ISS_OFF);
218 MovementNSSP.fill(getDeviceName(), "TELESCOPE_MOTION_NS", "Motion N/S", MOTION_TAB,
220
221 // @INDI_STANDARD_PROPERTY@
222 MovementWESP[DIRECTION_WEST].fill("MOTION_WEST", "West", ISS_OFF);
223 MovementWESP[DIRECTION_EAST].fill("MOTION_EAST", "East", ISS_OFF);
224 MovementWESP.fill(getDeviceName(), "TELESCOPE_MOTION_WE", "Motion W/E", MOTION_TAB,
226
227 // Reverse NS or WE
228 // @INDI_STANDARD_PROPERTY@
229 ReverseMovementSP[REVERSE_NS].fill("REVERSE_NS", "North/South", ISS_OFF);
230 ReverseMovementSP[REVERSE_WE].fill("REVERSE_WE", "West/East", ISS_OFF);
231 ReverseMovementSP.fill(getDeviceName(), "TELESCOPE_REVERSE_MOTION", "Reverse", MOTION_TAB, IP_RW, ISR_NOFMANY, 60,
232 IPS_IDLE);
233
234 controller->initProperties();
235
236 // Joystick motion control
237 // @INDI_STANDARD_PROPERTY@
238 MotionControlModeTP[MOTION_CONTROL_MODE_JOYSTICK].fill("MOTION_CONTROL_MODE_JOYSTICK", "4-Way Joystick", ISS_ON);
239 MotionControlModeTP[MOTION_CONTROL_MODE_AXES].fill("MOTION_CONTROL_MODE_AXES", "Two Separate Axes", ISS_OFF);
240 MotionControlModeTP.fill(getDeviceName(), "MOTION_CONTROL_MODE", "Motion Control",
241 "Joystick", IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
242
243 // Lock Axis
244 // @INDI_STANDARD_PROPERTY@
245 LockAxisSP[LOCK_AXIS_1].fill("LOCK_AXIS_1", "West/East", ISS_OFF);
246 LockAxisSP[LOCK_AXIS_2].fill("LOCK_AXIS_2", "North/South", ISS_OFF);
247 LockAxisSP.fill(getDeviceName(), "JOYSTICK_LOCK_AXIS", "Lock Axis", "Joystick", IP_RW,
248 ISR_ATMOST1, 60, IPS_IDLE);
249
250 // Joystick reverse motion and swapping (joystick only)
251 JoystickReverseSP[JOYSTICK_REVERSE_NS].fill("JOYSTICK_REVERSE_NS", "N/S", ISS_OFF);
252 JoystickReverseSP[JOYSTICK_REVERSE_WE].fill("JOYSTICK_REVERSE_WE", "W/E", ISS_OFF);
253 JoystickReverseSP[JOYSTICK_REVERSE_SWAP].fill("JOYSTICK_REVERSE_SWAP", "Swap N/S<->W/E", ISS_OFF);
254 JoystickReverseSP.fill(getDeviceName(), "TELESCOPE_JOYSTICK_REVERSE", "Axis Reverse", "Joystick", IP_RW, ISR_NOFMANY, 60,
255 IPS_IDLE);
256
258
260
261 if (telescopeConnection & CONNECTION_SERIAL)
262 {
265 {
266 return callHandshake();
267 });
269 }
270
271 if (telescopeConnection & CONNECTION_TCP)
272 {
273 tcpConnection = new Connection::TCP(this);
275 {
276 return callHandshake();
277 });
278
280 }
281
282 IDSnoopDevice(ActiveDeviceTP[ACTIVE_GPS].getText(), "GEOGRAPHIC_COORD");
284
287
289
290 double longitude = 0, latitude = 0, elevation = 0;
291 // Get value from config file if it exists.
293 {
294 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
295 m_Location.longitude = longitude;
296 }
298 {
299 LocationNP[LOCATION_LATITUDE].setValue(latitude);
300 m_Location.latitude = latitude;
301 }
303 {
304 LocationNP[LOCATION_ELEVATION].setValue(elevation);
305 m_Location.elevation = elevation;
306 }
307
308 return true;
309}
310
311void Telescope::ISGetProperties(const char *dev)
312{
314
315 if (CanGOTO())
316 {
318
319 ISState isDomeIgnored = ISS_OFF;
321 {
322 DomePolicySP[DOME_IGNORED].setState(isDomeIgnored);
323 DomePolicySP[DOME_LOCKS].setState((isDomeIgnored == ISS_ON) ? ISS_OFF : ISS_ON);
324 }
326 }
327
328 if (CanGOTO())
329 controller->ISGetProperties(dev);
330}
331
333{
334 if (isConnected())
335 {
336 controller->mapController("MOTIONDIR", "N/S/W/E Control", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_1");
337 controller->mapController("MOTIONDIRNS", "N/S Control", Controller::CONTROLLER_AXIS, "AXIS_8");
338 controller->mapController("MOTIONDIRWE", "W/E Control", Controller::CONTROLLER_AXIS, "AXIS_7");
339
340 if (nSlewRate >= 4)
341 {
342 controller->mapController("SLEWPRESET", "Slew Rate", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_2");
343 controller->mapController("SLEWPRESETUP", "Slew Rate Up", Controller::CONTROLLER_BUTTON, "BUTTON_5");
344 controller->mapController("SLEWPRESETDOWN", "Slew Rate Down", Controller::CONTROLLER_BUTTON,
345 "BUTTON_6");
346 }
347 if (CanAbort())
348 controller->mapController("ABORTBUTTON", "Abort", Controller::CONTROLLER_BUTTON, "BUTTON_1");
349 if (CanPark())
350 {
351 controller->mapController("PARKBUTTON", "Park", Controller::CONTROLLER_BUTTON, "BUTTON_2");
352 controller->mapController("UNPARKBUTTON", "UnPark", Controller::CONTROLLER_BUTTON, "BUTTON_3");
353 }
354
355 // Now we add our telescope specific stuff
356 if (CanGOTO() || CanSync())
359 if (CanAbort())
361
362 if (HasTrackMode() && TrackModeSP)
364 if (CanControlTrack())
366 if (HasTrackRate())
368 if (CanHomeFind() || CanHomeSet() || CanHomeGo())
370
371 if (CanGOTO())
372 {
376 if (nSlewRate >= 4)
379 }
380
382
383 if (HasTime())
385 if (HasLocation())
387 if (CanPark())
388 {
390 if (parkDataType != PARK_NONE)
391 {
394 }
395 }
396
397 if (HasPierSide())
399
401 {
403 ISState value;
404 if (IUGetConfigSwitch(getDefaultName(), "SIMULATE_PIER_SIDE", "SIMULATE_YES", &value) )
405 setSimulatePierSide(value == ISS_ON);
406 }
407
408 if (CanTrackSatellite())
409 {
413 }
414
415 if (HasPECState())
417 }
418 else
419 {
420 if (CanGOTO() || CanSync())
423 if (CanAbort())
425 if (HasTrackMode() && TrackModeSP)
427 if (HasTrackRate())
429 if (CanControlTrack())
431 if (CanHomeFind() || CanHomeSet() || CanHomeGo())
433
434 if (CanGOTO())
435 {
439 if (nSlewRate >= 4)
442 }
443
445
446 if (HasTime())
448 if (HasLocation())
450
451 if (CanPark())
452 {
454 if (parkDataType != PARK_NONE)
455 {
458 }
459 }
460
461 if (HasPierSide())
463
465 {
467 if (getSimulatePierSide() == true)
469 }
470
471 if (CanTrackSatellite())
472 {
476 }
477
478 if (HasPECState())
480 }
481
482 if (CanGOTO())
483 {
484 controller->updateProperties();
485
486 auto useJoystick = getSwitch("USEJOYSTICK");
487 if (useJoystick)
488 {
489 if (isConnected())
490 {
491 if (useJoystick[0].getState() == ISS_ON)
492 {
496
500 }
501 else
502 {
506 }
507 }
508 else
509 {
513 }
514 }
515 }
516
517 return true;
518}
519
521{
522 controller->ISSnoopDevice(root);
523
524 XMLEle *ep = nullptr;
525 const char *propName = findXMLAttValu(root, "name");
526 auto deviceName = std::string(findXMLAttValu(root, "device"));
527
528 if (isConnected())
529 {
530 if (HasLocation() && !strcmp(propName, "GEOGRAPHIC_COORD") && deviceName == ActiveDeviceTP[ACTIVE_GPS].getText())
531 {
532 // Only accept IPS_OK state
533 if (strcmp(findXMLAttValu(root, "state"), "Ok"))
534 return false;
535
536 double longitude = -1, latitude = -1, elevation = -1;
537
538 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
539 {
540 const char *elemName = findXMLAttValu(ep, "name");
541
542 if (!strcmp(elemName, "LAT"))
543 latitude = atof(pcdataXMLEle(ep));
544 else if (!strcmp(elemName, "LONG"))
545 longitude = atof(pcdataXMLEle(ep));
546 else if (!strcmp(elemName, "ELEV"))
547 elevation = atof(pcdataXMLEle(ep));
548 }
549
550 return processLocationInfo(latitude, longitude, elevation);
551 }
552 else if (HasTime() && !strcmp(propName, "TIME_UTC") && deviceName == ActiveDeviceTP[ACTIVE_GPS].getText())
553 {
554 // Only accept IPS_OK state
555 if (strcmp(findXMLAttValu(root, "state"), "Ok"))
556 return false;
557
558 char utc[MAXINDITSTAMP], offset[MAXINDITSTAMP];
559
560 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
561 {
562 const char *elemName = findXMLAttValu(ep, "name");
563
564 if (!strcmp(elemName, "UTC"))
565 snprintf(utc, MAXINDITSTAMP, "%s", pcdataXMLEle(ep));
566 else if (!strcmp(elemName, "OFFSET"))
567 snprintf(offset, MAXINDITSTAMP, "%s", pcdataXMLEle(ep));
568 }
569
570 return processTimeInfo(utc, offset);
571 }
572 else if (!strcmp(propName, "DOME_PARK") && deviceName == ActiveDeviceTP[ACTIVE_DOME].getText())
573 {
574 // This is handled by Watchdog driver.
575 // Mount shouldn't park due to dome closing in INDI::Telescope
576#if 0
577 if (strcmp(findXMLAttValu(root, "state"), "Ok"))
578 {
579 // Dome options is dome parks or both and dome is parking.
580 if ((DomeClosedLockT[2].s == ISS_ON || DomeClosedLockT[3].s == ISS_ON) && !IsLocked && !IsParked)
581 {
582 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
583 {
584 const char * elemName = findXMLAttValu(ep, "name");
585 if (( (!strcmp(elemName, "SHUTTER_CLOSE") || !strcmp(elemName, "PARK"))
586 && !strcmp(pcdataXMLEle(ep), "On")))
587 {
589 Park();
590 LOG_INFO("Dome is closing, parking mount...");
591 }
592 }
593 }
594 } // Dome is changing state and Dome options is lock or both. d
595 else
596#endif
597 if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
598 {
599 bool prevState = IsLocked;
600 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
601 {
602 const char *elemName = findXMLAttValu(ep, "name");
603
604 if (!IsLocked && (!strcmp(elemName, "PARK")) && !strcmp(pcdataXMLEle(ep), "On"))
605 IsLocked = true;
606 else if (IsLocked && (!strcmp(elemName, "UNPARK")) && !strcmp(pcdataXMLEle(ep), "On"))
607 IsLocked = false;
608 }
609 if (prevState != IsLocked && (DomePolicySP[DOME_LOCKS].getState() == ISS_ON))
610 LOGF_INFO("Dome status changed. Lock is set to: %s", IsLocked ? "locked" : "unlock");
611 }
612 return true;
613 }
614 }
615
616 return DefaultDevice::ISSnoopDevice(root);
617}
618
619void Telescope::triggerSnoop(const char *driverName, const char *snoopedProp)
620{
621 LOGF_DEBUG("Active Snoop, driver: %s, property: %s", driverName, snoopedProp);
622 IDSnoopDevice(driverName, snoopedProp);
623}
624
626{
627 return telescopeConnection;
628}
629
630void Telescope::setTelescopeConnection(const uint8_t &value)
631{
633
634 if (value == 0 || (mask & value) == 0)
635 {
636 DEBUGF(Logger::DBG_ERROR, "Invalid connection mode %d", value);
637 return;
638 }
639
640 telescopeConnection = value;
641}
642
644{
646
648 DomePolicySP.save(fp);
649
650 // Ensure that we only save valid locations
651 if (HasLocation() && (LocationNP[LOCATION_LONGITUDE].getValue() != 0 || LocationNP[LOCATION_LATITUDE].getValue() != 0))
652 LocationNP.save(fp);
653
654 if (CanGOTO())
656
657 if (SlewRateSP.isValid())
658 SlewRateSP.save(fp);
659 if (HasPECState())
660 PECStateSP.save(fp);
661 if (HasTrackMode())
662 TrackModeSP.save(fp);
663 if (HasTrackRate())
664 TrackRateNP.save(fp);
665
666 controller->saveConfigItems(fp);
668 LockAxisSP.save(fp);
671
672 return true;
673}
674
675void Telescope::NewRaDec(double ra, double dec)
676{
677 switch (TrackState)
678 {
679 case SCOPE_PARKED:
680 case SCOPE_IDLE:
682 break;
683
684 case SCOPE_SLEWING:
685 case SCOPE_PARKING:
687 break;
688
689 case SCOPE_TRACKING:
691 break;
692 }
693
695 {
700 }
701 else if (TrackState == SCOPE_TRACKING && CanControlTrack() && TrackStateSP[TRACK_OFF].getState() == ISS_ON)
702 {
707 }
708
709 // RA is in hours, so change the arc-second threshold accordingly.
710 constexpr double RA_NOTIFY_THRESHOLD = EQ_NOTIFY_THRESHOLD / 15.0;
711 if (std::abs(EqNP[AXIS_RA].getValue() - ra) > RA_NOTIFY_THRESHOLD ||
712 std::abs(EqNP[AXIS_DE].getValue() - dec) > EQ_NOTIFY_THRESHOLD ||
713 EqNP.getState() != lastEqState)
714 {
715 EqNP[AXIS_RA].setValue(ra);
716 EqNP[AXIS_DE].setValue(dec);
717 lastEqState = EqNP.getState();
718 EqNP.apply();
719 }
720}
721
722bool Telescope::Sync(double ra, double dec)
723{
726 // if we get here, our mount doesn't support sync
727 DEBUG(Logger::DBG_ERROR, "Telescope does not support Sync.");
728 return false;
729}
730
732{
733 INDI_UNUSED(dir);
734 INDI_UNUSED(command);
735 DEBUG(Logger::DBG_ERROR, "Telescope does not support North/South motion.");
739 return false;
740}
741
743{
744 INDI_UNUSED(dir);
745 INDI_UNUSED(command);
746 DEBUG(Logger::DBG_ERROR, "Telescope does not support West/East motion.");
750 return false;
751}
752
753/**************************************************************************************
754** Process Text properties
755***************************************************************************************/
756bool Telescope::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
757{
758 // first check if it's for our device
759 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
760 {
761 if (TimeTP.isNameMatch(name))
762 {
763 int utcindex = IUFindIndex("UTC", names, n);
764 int offsetindex = IUFindIndex("OFFSET", names, n);
765
766 return processTimeInfo(texts[utcindex], texts[offsetindex]);
767 }
768
769 if (ActiveDeviceTP.isNameMatch(name))
770 {
772 ActiveDeviceTP.update(texts, names, n);
773 // Update client display
775
776 IDSnoopDevice(ActiveDeviceTP[ACTIVE_GPS].getText(), "GEOGRAPHIC_COORD");
778
781
783 return true;
784 }
785 }
786
787 controller->ISNewText(dev, name, texts, names, n);
788
789 return DefaultDevice::ISNewText(dev, name, texts, names, n);
790}
791
792/**************************************************************************************
793**
794***************************************************************************************/
795bool Telescope::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
796{
797 // first check if it's for our device
798 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
799 {
801 // Goto & Sync for Equatorial Coords
803 if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
804 {
805 // this is for us, and it is a goto
806 bool rc = false;
807 double ra = -1;
808 double dec = -100;
809
810 for (int x = 0; x < n; x++)
811 {
812 if (EqNP[AXIS_RA].isNameMatch(names[x]))
813 ra = values[x];
814 else if (EqNP[AXIS_DE].isNameMatch(names[x]))
815 dec = values[x];
816 }
817 if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
818 {
819 // Check if it is already parked.
820 if (CanPark())
821 {
822 if (isParked())
823 {
825 "Please unpark the mount before issuing any motion/sync commands.");
826 EqNP.setState(lastEqState = IPS_IDLE);
827 EqNP.apply();
828 return false;
829 }
830 }
831
832 // Check if it can sync
833 if (CanSync())
834 {
835 if (CoordSP.isSwitchOn("SYNC"))
836 {
837 rc = Sync(ra, dec);
838 if (rc)
839 EqNP.setState(lastEqState = IPS_OK);
840 else
841 EqNP.setState(lastEqState = IPS_ALERT);
842 EqNP.apply();
843 return rc;
844 }
845 }
846
847 bool doFlip = false;
848 if (CanFlip())
849 {
850 doFlip = CoordSP.isSwitchOn("FLIP");
851 }
852
853 // Remember Track State
855 // Issue GOTO/Flip
856 if(doFlip)
857 {
858 rc = Flip(ra, dec);
859 }
860 else
861 {
862 rc = Goto(ra, dec);
863 }
864 if (rc)
865 {
866 EqNP.setState(lastEqState = IPS_BUSY);
867 // Now fill in target co-ords, so domes can start turning
868 TargetNP[AXIS_RA].setValue(ra);
869 TargetNP[AXIS_DE].setValue(dec);
870 TargetNP.apply();
871 }
872 else
873 {
874 EqNP.setState(lastEqState = IPS_ALERT);
875 }
876 EqNP.apply();
877 }
878 return rc;
879 }
880
882 // Geographic Coords
884 if (LocationNP.isNameMatch(name))
885 {
886 int latindex = IUFindIndex("LAT", names, n);
887 int longindex = IUFindIndex("LONG", names, n);
888 int elevationindex = IUFindIndex("ELEV", names, n);
889
890 if (latindex == -1 || longindex == -1 || elevationindex == -1)
891 {
893 LOG_ERROR("Location data missing or corrupted.");
895 }
896
897 double targetLat = values[latindex];
898 double targetLong = values[longindex];
899 double targetElev = values[elevationindex];
900
901 return processLocationInfo(targetLat, targetLong, targetElev);
902 }
903
905 // Park Position
907 if (ParkPositionNP.isNameMatch(name))
908 {
909 double axis1 = std::numeric_limits<double>::quiet_NaN(), axis2 = std::numeric_limits<double>::quiet_NaN();
910 for (int x = 0; x < n; x++)
911 {
912 if (ParkPositionNP[AXIS_RA].isNameMatch(names[x]))
913 {
914 axis1 = values[x];
915 }
916 else if (ParkPositionNP[AXIS_DE].isNameMatch(names[x]))
917 {
918 axis2 = values[x];
919 }
920 }
921
922 if (std::isnan(axis1) == false && std::isnan(axis2) == false)
923 {
924 bool rc = false;
925
926 rc = SetParkPosition(axis1, axis2);
927
928 if (rc)
929 {
930 ParkPositionNP.update(values, names, n);
931 Axis1ParkPosition = ParkPositionNP[AXIS_RA].getValue();
932 Axis2ParkPosition = ParkPositionNP[AXIS_DE].getValue();
933 }
934
936 }
937 else
939
941 return true;
942 }
943
945 // Track Rate
947 if (TrackRateNP.isNameMatch(name))
948 {
949 double preAxis1 = TrackRateNP[AXIS_RA].getValue(), preAxis2 = TrackRateNP[AXIS_DE].getValue();
950 auto currentTrackingMode = TrackModeSP.findOnSwitch();
951 if (TrackRateNP.update(values, names, n) == false || currentTrackingMode == nullptr)
952 {
955 return false;
956 }
957
958 if (TrackState == SCOPE_TRACKING && currentTrackingMode->isNameMatch("TRACK_CUSTOM"))
959 {
960 // Check that we do not abruptly change positive tracking rates to negative ones.
961 // tracking must be stopped first.
962 // Give warning is tracking sign would cause a reverse in direction
963 if ( (preAxis1 * TrackRateNP[AXIS_RA].getValue() < 0) || (preAxis2 * TrackRateNP[AXIS_DE].getValue() < 0) )
964 {
965 LOG_ERROR("Cannot reverse tracking while tracking is engaged. Disengage tracking then try again.");
966 return false;
967 }
968
969 // All is fine, ask mount to change tracking rate
970 if (SetTrackRate(TrackRateNP[AXIS_RA].getValue(), TrackRateNP[AXIS_DE].getValue()) == false)
971 {
972 TrackRateNP[AXIS_RA].setValue(preAxis1);
973 TrackRateNP[AXIS_DE].setValue(preAxis2);
975 }
976 else
978 }
979
980 // If we are already tracking but tracking mode is NOT custom
981 // We just inform the user that it must be set to custom for these values to take
982 // effect.
983 if (TrackState == SCOPE_TRACKING && !currentTrackingMode->isNameMatch("TRACK_CUSTOM"))
984 {
985 LOG_INFO("Custom tracking rates set. Tracking mode must be set to Custom for these rates to take effect.");
986 }
987
988 // If mount is NOT tracking, we simply accept whatever valid values for use when mount tracking is engaged.
990 return true;
991 }
992 }
993
994 return DefaultDevice::ISNewNumber(dev, name, values, names, n);
995}
996
997/**************************************************************************************
998**
999***************************************************************************************/
1000bool Telescope::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1001{
1002 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1003 {
1004 // This one is for us
1005 if (CoordSP.isNameMatch(name))
1006 {
1007 // client is telling us what to do with co-ordinate requests
1009 CoordSP.update(states, names, n);
1010 // Update client display
1011 CoordSP.apply();
1012 return true;
1013 }
1014
1016 // Slew Rate
1018 if (SlewRateSP.isNameMatch(name))
1019 {
1020 int preIndex = SlewRateSP.findOnSwitchIndex();
1021 SlewRateSP.update(states, names, n);
1022 int nowIndex = SlewRateSP.findOnSwitchIndex();
1023 if (SetSlewRate(nowIndex) == false)
1024 {
1025 SlewRateSP.reset();
1026 SlewRateSP[preIndex].setState(ISS_ON);
1028 }
1029 else
1031 SlewRateSP.apply();
1032 return true;
1033 }
1034
1036 // Parking
1038 if (ParkSP.isNameMatch(name))
1039 {
1041 {
1042 ParkSP.reset();
1044 Abort();
1045 LOG_INFO("Parking/Unparking aborted.");
1046 ParkSP.apply();
1047 return true;
1048 }
1049
1050 int preIndex = ParkSP.findOnSwitchIndex();
1051 ParkSP.update(states, names, n);
1052
1053 bool toPark = (ParkSP[PARK].getState() == ISS_ON);
1054
1055 if (toPark == false && TrackState != SCOPE_PARKED)
1056 {
1057 ParkSP.reset();
1060 LOG_INFO("Telescope already unparked.");
1061 IsParked = false;
1062 ParkSP.apply();
1063 return true;
1064 }
1065
1066 if (toPark == false && isLocked())
1067 {
1068 ParkSP.reset();
1071 LOG_WARN("Cannot unpark mount when dome is locking. See: Dome Policy in options tab.");
1072 IsParked = true;
1073 ParkSP.apply();
1074 return true;
1075 }
1076
1077 if (toPark && TrackState == SCOPE_PARKED)
1078 {
1079 ParkSP.reset();
1082 LOG_INFO("Telescope already parked.");
1083 ParkSP.apply();
1084 return true;
1085 }
1086
1088
1089 ParkSP.reset();
1090 bool rc = toPark ? Park() : UnPark();
1091 if (rc)
1092 {
1094 {
1095 ParkSP[PARK].setState(toPark ? ISS_ON : ISS_OFF);
1096 ParkSP[UNPARK].setState(toPark ? ISS_OFF : ISS_ON);
1098 }
1099 else
1100 {
1101 ParkSP[PARK].setState(toPark ? ISS_ON : ISS_OFF);
1102 ParkSP[UNPARK].setState(toPark ? ISS_OFF : ISS_ON);
1104 }
1105 }
1106 else
1107 {
1108 ParkSP[preIndex].setState(ISS_ON);
1110 }
1111
1112 ParkSP.apply();
1113 return true;
1114 }
1115
1117 // NS Motion
1119 if (MovementNSSP.isNameMatch(name))
1120 {
1121 // Check if it is already parked.
1122 if (CanPark())
1123 {
1124 if (isParked())
1125 {
1127 "Please unpark the mount before issuing any motion/sync commands.");
1130 return false;
1131 }
1132 }
1133
1134 MovementNSSP.update(states, names, n);
1135
1136 int current_motion = MovementNSSP.findOnSwitchIndex();
1137
1138 // if same move requested, return
1139 if (MovementNSSP.getState() == IPS_BUSY && current_motion == last_ns_motion)
1140 return true;
1141
1142 // Time to stop motion
1143 if (current_motion == -1 || (last_ns_motion != -1 && current_motion != last_ns_motion))
1144 {
1145 auto targetDirection = last_ns_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1146 if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1147 targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1148
1149 if (MoveNS(targetDirection, MOTION_STOP))
1150 {
1153 last_ns_motion = -1;
1154 // Update Target when stopped so that domes can track
1155 TargetNP[AXIS_RA].setValue(EqNP[AXIS_RA].getValue());
1156 TargetNP[AXIS_DE].setValue(EqNP[AXIS_DE].getValue());
1157 TargetNP.apply();
1158 }
1159 else
1161 }
1162 else
1163 {
1166
1167 auto targetDirection = current_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1168 if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1169 targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1170
1171 if (MoveNS(targetDirection, MOTION_START))
1172 {
1174 last_ns_motion = targetDirection;
1175 }
1176 else
1177 {
1180 last_ns_motion = -1;
1181 }
1182 }
1183
1185
1186 return true;
1187 }
1188
1190 // WE Motion
1192 if (MovementWESP.isNameMatch(name))
1193 {
1194 // Check if it is already parked.
1195 if (CanPark())
1196 {
1197 if (isParked())
1198 {
1200 "Please unpark the mount before issuing any motion/sync commands.");
1203 return false;
1204 }
1205 }
1206
1207 MovementWESP.update(states, names, n);
1208
1209 int current_motion = MovementWESP.findOnSwitchIndex();
1210
1211 // if same move requested, return
1212 if (MovementWESP.getState() == IPS_BUSY && current_motion == last_we_motion)
1213 return true;
1214
1215 // Time to stop motion
1216 if (current_motion == -1 || (last_we_motion != -1 && current_motion != last_we_motion))
1217 {
1218 auto targetDirection = last_we_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1219 if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1220 targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1221 if (MoveWE(targetDirection, MOTION_STOP))
1222 {
1225 last_we_motion = -1;
1226 // Update Target when stopped so that domes can track
1227 TargetNP[AXIS_RA].setValue(EqNP[AXIS_RA].getValue());
1228 TargetNP[AXIS_DE].setValue(EqNP[AXIS_DE].getValue());
1229 TargetNP.apply();
1230 }
1231 else
1233 }
1234 else
1235 {
1238
1239 auto targetDirection = current_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1240 if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1241 targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1242
1243 if (MoveWE(targetDirection, MOTION_START))
1244 {
1246 last_we_motion = targetDirection;
1247 }
1248 else
1249 {
1252 last_we_motion = -1;
1253 }
1254 }
1255
1257
1258 return true;
1259 }
1260
1262 // WE or NS Reverse Motion
1265 {
1266 ReverseMovementSP.update(states, names, n);
1270 return true;
1271 }
1272
1274 // Homing
1276 if (HomeSP.isNameMatch(name))
1277 {
1278 auto onSwitchName = IUFindOnSwitchName(states, names, n);
1279 // No switch selected. Cancel whatever action if any and return
1280 if (onSwitchName == NULL)
1281 {
1282 if (HomeSP.getState() == IPS_BUSY)
1283 Abort();
1284 HomeSP.reset();
1286 HomeSP.apply();
1287 return true;
1288 }
1289 auto actionString = std::string(onSwitchName);
1290 auto action = HOME_FIND;
1291 if (actionString == "SET")
1292 action = HOME_SET;
1293 else if (actionString == "GO")
1294 action = HOME_GO;
1295
1296 if (isParked())
1297 {
1299 HomeSP.reset();
1300 HomeSP.apply();
1301 LOG_ERROR("Cannot home while parked.");
1302 return true;
1303 }
1304
1305 auto state = ExecuteHomeAction(action);
1306 HomeSP.setState(state);
1307 if (state == IPS_BUSY)
1308 HomeSP.update(states, names, n);
1309 HomeSP.apply();
1310 return true;
1311 }
1312
1314 // Abort Motion
1316 if (AbortSP.isNameMatch(name))
1317 {
1318 AbortSP.reset();
1319
1320 if (Abort())
1321 {
1323
1324 if (ParkSP.getState() == IPS_BUSY)
1325 {
1326 ParkSP.reset();
1328 ParkSP.apply();
1329
1330 LOG_INFO("Parking aborted.");
1331 }
1332 if (EqNP.getState() == IPS_BUSY)
1333 {
1334 EqNP.setState(lastEqState = IPS_IDLE);
1335 EqNP.apply();
1336 LOG_INFO("Slew/Track aborted.");
1337 }
1339 {
1343 }
1345 {
1349 }
1350
1352
1353 // JM 2017-07-28: Abort shouldn't affect tracking state. It should affect motion and that's it.
1354 //if (TrackState != SCOPE_PARKED)
1355 //TrackState = SCOPE_IDLE;
1356 // For Idle, Tracking, Parked state, we do not change its status, it should remain as is.
1357 // For Slewing & Parking, state should go back to last remembered state.
1359 {
1361 }
1362 }
1363 else
1365
1366 AbortSP.apply();
1367
1368 return true;
1369 }
1370
1372 // Track Mode
1374 if (TrackModeSP.isNameMatch(name))
1375 {
1376 int prevIndex = TrackModeSP.findOnSwitchIndex();
1377 TrackModeSP.update(states, names, n);
1378 int currIndex = TrackModeSP.findOnSwitchIndex();
1379 // If same as previous index, or if scope is already idle, then just update switch and return. No commands are sent to the mount.
1380 if (prevIndex == currIndex || TrackState == SCOPE_IDLE)
1381 {
1383 TrackModeSP.apply(nullptr);
1384 return true;
1385 }
1386
1387 if (TrackState == SCOPE_PARKED)
1388 {
1389 DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before changing track mode.");
1390 return false;
1391 }
1392
1393 if (SetTrackMode(currIndex))
1395 else
1396 {
1398 TrackModeSP[prevIndex].setState(ISS_ON);
1400 }
1402 return false;
1403 }
1404
1406 // Track State
1408 if (TrackStateSP.isNameMatch(name))
1409 {
1410 int previousState = TrackStateSP.findOnSwitchIndex();
1411 TrackStateSP.update(states, names, n);
1412 int targetState = TrackStateSP.findOnSwitchIndex();
1413
1414 if (previousState == targetState)
1415 {
1417 return true;
1418 }
1419
1420 if (TrackState == SCOPE_PARKED)
1421 {
1422 DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before tracking.");
1423 return false;
1424 }
1425
1426 if (SetTrackEnabled((targetState == TRACK_ON) ? true : false))
1427 {
1428 TrackState = (targetState == TRACK_ON) ? SCOPE_TRACKING : SCOPE_IDLE;
1429 TrackStateSP.setState((targetState == TRACK_ON) ? IPS_BUSY : IPS_IDLE);
1430 TrackStateSP[TRACK_ON].setState((targetState == TRACK_ON) ? ISS_ON : ISS_OFF);
1431 TrackStateSP[TRACK_OFF].setState((targetState == TRACK_ON) ? ISS_OFF : ISS_ON);
1432 }
1433 else
1434 {
1437 TrackStateSP[previousState].setState(ISS_ON);
1438 }
1439
1441 return true;
1442 }
1443
1445 // Park Options
1447 if (ParkOptionSP.isNameMatch(name))
1448 {
1449 ParkOptionSP.update(states, names, n);
1450 auto index = ParkOptionSP.findOnSwitchIndex();
1451 if (index == -1)
1452 return false;
1453
1455
1456 bool rc = false;
1457
1460 {
1461 LOG_INFO("Can not change park position while slewing or already parked.");
1464 return false;
1465 }
1466
1467 switch (index)
1468 {
1469 case PARK_CURRENT:
1470 rc = SetCurrentPark();
1471 break;
1472 case PARK_DEFAULT:
1473 rc = SetDefaultPark();
1474 break;
1475 case PARK_WRITE_DATA:
1476 rc = WriteParkData();
1477 if (rc)
1478 LOG_INFO("Saved Park Status/Position.");
1479 else
1480 DEBUG(Logger::DBG_WARNING, "Can not save Park Status/Position.");
1481 break;
1482 case PARK_PURGE_DATA:
1483 rc = PurgeParkData();
1484 if (rc)
1485 LOG_INFO("Park data purged.");
1486 else
1487 DEBUG(Logger::DBG_WARNING, "Can not purge Park Status/Position.");
1488 break;
1489 }
1490
1493 return true;
1494 }
1495
1497 // Parking Dome Policy
1499 if (DomePolicySP.isNameMatch(name))
1500 {
1501 DomePolicySP.update(states, names, n);
1502 if (DomePolicySP[DOME_IGNORED].getState() == ISS_ON)
1503 LOG_INFO("Dome Policy set to: Dome ignored. Mount can park or unpark regardless of dome parking state.");
1504 else
1505 LOG_WARN("Dome Policy set to: Dome locks. This prevents the mount from unparking when dome is parked.");
1506#if 0
1507 else if (!strcmp(names[0], DomeClosedLockT[2].name))
1508 LOG_INFO("Warning: Dome parking policy set to: Dome parks. This tells "
1509 "scope to park if dome is parking. This will disable the locking "
1510 "for dome parking, EVEN IF MOUNT PARKING FAILS");
1511 else if (!strcmp(names[0], DomeClosedLockT[3].name))
1512 LOG_INFO("Warning: Dome parking policy set to: Both. This disallows the "
1513 "scope from unparking when dome is parked, and tells scope to "
1514 "park if dome is parking. This will disable the locking for dome "
1515 "parking, EVEN IF MOUNT PARKING FAILS.");
1516#endif
1519 triggerSnoop(ActiveDeviceTP[ACTIVE_DOME].getText(), "DOME_PARK");
1520 return true;
1521 }
1522
1524 // Simulate Pier Side
1525 // This ia a major change to the design of the simulated scope, it might not handle changes on the fly
1528 {
1529 SimulatePierSideSP.update(states, names, n);
1531 if (index == -1)
1532 {
1534 LOG_INFO("Cannot determine whether pier side simulation should be switched on or off.");
1536 return false;
1537 }
1538
1539 bool pierSideEnabled = index == 0;
1540
1541 LOGF_INFO("Simulating Pier Side %s.", (pierSideEnabled ? "enabled" : "disabled"));
1542
1543 setSimulatePierSide(pierSideEnabled);
1544 if (pierSideEnabled)
1545 {
1546 // set the pier side from the current Ra
1547 // assumes we haven't tracked across the meridian
1549 }
1550 return true;
1551 }
1552
1554 // Joystick Motion Control Mode
1557 {
1558 MotionControlModeTP.update(states, names, n);
1562 LOG_INFO("Motion control is set to 4-way joystick.");
1563 else if (MotionControlModeTP[MOTION_CONTROL_MODE_AXES].getState() == ISS_ON)
1564 LOG_INFO("Motion control is set to 2 separate axes.");
1565 else
1566 DEBUGF(Logger::DBG_WARNING, "Motion control is set to unknown value %d!", n);
1567 return true;
1568 }
1569
1571 // Joystick Lock Axis
1573 if (LockAxisSP.isNameMatch(name))
1574 {
1575 LockAxisSP.update(states, names, n);
1577 LockAxisSP.apply();
1578 if (LockAxisSP[AXIS_RA].getState() == ISS_ON)
1579 LOG_INFO("Joystick motion is locked to West/East axis only.");
1580 else if (LockAxisSP[AXIS_DE].getState() == ISS_ON)
1581 LOG_INFO("Joystick motion is locked to North/South axis only.");
1582 else
1583 LOG_INFO("Joystick motion is unlocked.");
1584 return true;
1585 }
1586
1588 // Joystick Reverse/Swap Motion
1591 {
1592 JoystickReverseSP.update(states, names, n);
1596 return true;
1597 }
1598 }
1599
1600 bool rc = controller->ISNewSwitch(dev, name, states, names, n);
1601 if (rc)
1602 {
1603 auto useJoystick = getSwitch("USEJOYSTICK");
1604 if (useJoystick && useJoystick[0].getState() == ISS_ON)
1605 {
1609
1610 LockAxisSP.load();
1613 }
1614 else
1615 {
1619 }
1620
1621 }
1622
1623 // Nobody has claimed this, so, ignore it
1624 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1625}
1626
1628{
1629 if (telescopeConnection > 0)
1630 {
1633 else if (getActiveConnection() == tcpConnection)
1635 }
1636
1637 return Handshake();
1638}
1639
1641{
1642 /* Test connection */
1643 return ReadScopeStatus();
1644}
1645
1647{
1648 if (isConnected())
1649 {
1650 bool rc;
1651
1652 rc = ReadScopeStatus();
1653
1654 if (!rc)
1655 {
1656 // read was not good
1657 EqNP.setState(lastEqState = IPS_ALERT);
1658 EqNP.apply();
1659 }
1660
1662 }
1663}
1664
1665bool Telescope::Flip(double ra, double dec)
1666{
1667 INDI_UNUSED(ra);
1669
1670 DEBUG(Logger::DBG_WARNING, "Flip is not supported.");
1671 return false;
1672}
1673
1674bool Telescope::Goto(double ra, double dec)
1675{
1676 INDI_UNUSED(ra);
1678
1679 DEBUG(Logger::DBG_WARNING, "GOTO is not supported.");
1680 return false;
1681}
1682
1684{
1685 DEBUG(Logger::DBG_WARNING, "Abort is not supported.");
1686 return false;
1687}
1688
1690{
1691 DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1692 return false;
1693}
1694
1696{
1697 DEBUG(Logger::DBG_WARNING, "UnParking is not supported.");
1698 return false;
1699}
1700
1701bool Telescope::SetTrackMode(uint8_t mode)
1702{
1703 INDI_UNUSED(mode);
1704 DEBUG(Logger::DBG_WARNING, "Tracking mode is not supported.");
1705 return false;
1706}
1707
1708bool Telescope::SetTrackRate(double raRate, double deRate)
1709{
1710 INDI_UNUSED(raRate);
1711 INDI_UNUSED(deRate);
1712 DEBUG(Logger::DBG_WARNING, "Custom tracking rates is not supported.");
1713 return false;
1714}
1715
1717{
1718 INDI_UNUSED(enabled);
1719 DEBUG(Logger::DBG_WARNING, "Tracking state is not supported.");
1720 return false;
1721}
1722
1723int Telescope::AddTrackMode(const char *name, const char *label, bool isDefault)
1724{
1725 INDI::WidgetSwitch node;
1726 node.fill(name, label, isDefault ? ISS_ON : ISS_OFF);
1727 TrackModeSP.push(std::move(node));
1728 return (TrackModeSP.count() - 1);
1729}
1730
1732{
1733 DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1734 return false;
1735}
1736
1738{
1739 DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1740 return false;
1741}
1742
1743bool Telescope::processTimeInfo(const char *utc, const char *offset)
1744{
1745 struct ln_date utc_date;
1746 double utc_offset = 0;
1747
1748 if (extractISOTime(utc, &utc_date) == -1)
1749 {
1751 LOGF_ERROR("Date/Time is invalid: %s.", utc);
1752 TimeTP.apply();
1753 return false;
1754 }
1755
1756 utc_offset = atof(offset);
1757
1758 if (updateTime(&utc_date, utc_offset))
1759 {
1760 TimeTP[UTC].setText(utc);
1761 TimeTP[OFFSET].setText(offset);
1763 TimeTP.apply();
1764 return true;
1765 }
1766 else
1767 {
1769 TimeTP.apply();
1770 return false;
1771 }
1772}
1773
1774bool Telescope::processLocationInfo(double latitude, double longitude, double elevation)
1775{
1776 // Do not update if not necessary
1777 // JM 2021-05-26: This can sometimes be problematic. Let child driver deals with duplicate requests.
1778#if 0
1779 if (latitude == LocationN[LOCATION_LATITUDE].value && longitude == LocationN[LOCATION_LONGITUDE].value &&
1780 elevation == LocationN[LOCATION_ELEVATION].value)
1781 {
1782 LocationNP.s = IPS_OK;
1783 IDSetNumber(&LocationNP, nullptr);
1784 return true;
1785 }
1786 else
1787#endif
1788 if (latitude == 0 && longitude == 0)
1789 {
1790 LOG_DEBUG("Silently ignoring invalid latitude and longitude.");
1792 LocationNP.apply();
1793 return false;
1794 }
1795
1796 if (updateLocation(latitude, longitude, elevation))
1797 {
1799 LocationNP[LOCATION_LATITUDE].setValue(latitude);
1800 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
1801 LocationNP[LOCATION_ELEVATION].setValue(elevation);
1802 // Update client display
1803 LocationNP.apply();
1804
1805 // Always save geographic coord config immediately.
1806 saveConfig(true, "GEOGRAPHIC_COORD");
1807
1808 updateObserverLocation(latitude, longitude, elevation);
1809
1810 return true;
1811 }
1812 else
1813 {
1815 // Update client display
1816 LocationNP.apply();
1817
1818 return false;
1819 }
1820}
1821
1822bool Telescope::updateTime(ln_date *utc, double utc_offset)
1823{
1824 INDI_UNUSED(utc);
1825 INDI_UNUSED(utc_offset);
1826 return true;
1827}
1828
1829bool Telescope::updateLocation(double latitude, double longitude, double elevation)
1830{
1831 INDI_UNUSED(latitude);
1832 INDI_UNUSED(longitude);
1833 INDI_UNUSED(elevation);
1834 return true;
1835}
1836
1842
1843void Telescope::updateObserverLocation(double latitude, double longitude, double elevation)
1844{
1845 m_Location.longitude = longitude;
1846 m_Location.latitude = latitude;
1847 m_Location.elevation = elevation;
1848 char lat_str[MAXINDIFORMAT] = {0}, lng_str[MAXINDIFORMAT] = {0};
1849
1850 // Make display longitude to be in the standard 0 to +180 East, and 0 to -180 West.
1851 // No need to confuse new users with INDI format.
1852 double display_longitude = longitude > 180 ? longitude - 360 : longitude;
1853 fs_sexa(lat_str, m_Location.latitude, 2, 36000);
1854 fs_sexa(lng_str, display_longitude, 2, 36000);
1855 // Choose WGS 84, also known as EPSG:4326 for latitude/longitude ordering
1856 LOGF_INFO("Observer location updated: Latitude %.12s (%.2f) Longitude %.12s (%.2f)", lat_str, m_Location.latitude, lng_str,
1857 display_longitude);
1858}
1859
1860bool Telescope::SetParkPosition(double Axis1Value, double Axis2Value)
1861{
1862 INDI_UNUSED(Axis1Value);
1863 INDI_UNUSED(Axis2Value);
1864 return true;
1865}
1866
1867void Telescope::generateCoordSet()
1868{
1869 CoordSP.resize(0);
1870
1871 INDI::WidgetSwitch node;
1872 node.fill("TRACK", "Track", ISS_ON);
1873 CoordSP.push(std::move(node));
1874
1875 if(CanGOTO())
1876 {
1877 INDI::WidgetSwitch node;
1878 node.fill("SLEW", "Slew", ISS_OFF);
1879 CoordSP.push(std::move(node));
1880 }
1881
1882 if(CanSync())
1883 {
1884 INDI::WidgetSwitch node;
1885 node.fill("SYNC", "Sync", ISS_OFF);
1886 CoordSP.push(std::move(node));
1887
1888 }
1889
1890 if(CanFlip())
1891 {
1892 INDI::WidgetSwitch node;
1893 node.fill("FLIP", "Flip", ISS_OFF);
1894 CoordSP.push(std::move(node));
1895 }
1896}
1897
1898void Telescope::SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
1899{
1900 capability = cap;
1901 nSlewRate = slewRateCount;
1902
1903 generateCoordSet();
1904
1905 if (nSlewRate >= 4)
1906 {
1907 SlewRateSP.resize(0);
1908 INDI::WidgetSwitch node;
1909 //int step = nSlewRate / 4;
1910 for (int i = 0; i < nSlewRate; i++)
1911 {
1912 // char name[4];
1913 auto name = std::to_string(i + 1) + "x";
1914 // snprintf(name, 4, "%dx", i + 1);
1915 // IUFillSwitch(SlewRateS + i, name, name, ISS_OFF);
1916 node.fill(name, name, ISS_OFF);
1917 SlewRateSP.push(std::move(node));
1918 }
1919
1920 // If number of slew rate is EXACTLY 4, then let's use common labels
1921 if (nSlewRate == 4)
1922 {
1923 // strncpy((SlewRateS + (0))->label, "Guide", MAXINDILABEL);
1924 SlewRateSP[0].setLabel("Guide");
1925 SlewRateSP[1].setLabel("Centering");
1926 SlewRateSP[2].setLabel("Find");
1927 SlewRateSP[3].setLabel("Max");
1928 }
1929
1930 // By Default we set current Slew Rate to 0.5 of max
1931 SlewRateSP[nSlewRate / 2].setState(ISS_ON);
1932
1933 SlewRateSP.fill(getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
1935 }
1936
1937 if (CanHomeFind() || CanHomeSet() || CanHomeGo())
1938 {
1939 HomeSP.resize(0);
1940 if (CanHomeFind())
1941 {
1942 INDI::WidgetSwitch node;
1943 node.fill("FIND", "Find", ISS_OFF);
1944 HomeSP.push(std::move(node));
1945 }
1946
1947 if (CanHomeSet())
1948 {
1949 INDI::WidgetSwitch node;
1950 node.fill("SET", "Set", ISS_OFF);
1951 HomeSP.push(std::move(node));
1952 }
1953
1954 if (CanHomeGo())
1955 {
1956 INDI::WidgetSwitch node;
1957 node.fill("GO", "Go", ISS_OFF);
1958 HomeSP.push(std::move(node));
1959 }
1960
1962 HomeSP.fill(getDeviceName(), "TELESCOPE_HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
1963 }
1964}
1965
1967{
1969
1971 {
1972 switch (parkDataType)
1973 {
1974 case PARK_RA_DEC:
1975 ParkPositionNP[AXIS_RA].fill("PARK_RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
1976 ParkPositionNP[AXIS_DE].fill("PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1977 break;
1978
1979 case PARK_HA_DEC:
1980 ParkPositionNP[AXIS_RA].fill("PARK_HA", "HA (hh:mm:ss)", "%010.6m", -12, 12, 0, 0);
1981 ParkPositionNP[AXIS_DE].fill("PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1982 break;
1983
1984 case PARK_AZ_ALT:
1985 ParkPositionNP[AXIS_AZ].fill("PARK_AZ", "AZ D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
1986 ParkPositionNP[AXIS_ALT].fill("PARK_ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
1987 break;
1988
1990 ParkPositionNP[AXIS_RA].fill("PARK_RA", "RA Encoder", "%.0f", 0, 16777215, 1, 0);
1991 ParkPositionNP[AXIS_DE].fill("PARK_DEC", "DEC Encoder", "%.0f", 0, 16777215, 1, 0);
1992 break;
1993
1995 ParkPositionNP[AXIS_RA].fill("PARK_AZ", "AZ Encoder", "%.0f", 0, 16777215, 1, 0);
1996 ParkPositionNP[AXIS_DE].fill("PARK_ALT", "ALT Encoder", "%.0f", 0, 16777215, 1, 0);
1997 break;
1998
1999 default:
2000 break;
2001 }
2002
2003 ParkPositionNP.fill(getDeviceName(), "TELESCOPE_PARK_POSITION", "Park Position", SITE_TAB, IP_RW, 60, IPS_IDLE);
2004 }
2005}
2006
2007void Telescope::SyncParkStatus(bool isparked)
2008{
2009 IsParked = isparked;
2010 ParkSP.reset();
2012
2013 if (IsParked)
2014 {
2017 LOG_INFO("Mount is parked.");
2018 }
2019 else
2020 {
2023 LOG_INFO("Mount is unparked.");
2024 }
2025
2026 ParkSP.apply();
2027}
2028
2029void Telescope::SetParked(bool isparked)
2030{
2031 SyncParkStatus(isparked);
2032
2033 if (parkDataType != PARK_NONE)
2034 WriteParkData();
2035}
2036
2038{
2039 return IsParked;
2040}
2041
2043{
2044 const char *loadres = LoadParkData();
2045 if (loadres)
2046 {
2047 LOGF_INFO("InitPark: No Park data in file %s: %s", ParkDataFileName.c_str(), loadres);
2048 SyncParkStatus(false);
2049 return false;
2050 }
2051
2053
2055 {
2056 LOGF_DEBUG("InitPark Axis1 %.2f Axis2 %.2f", Axis1ParkPosition, Axis2ParkPosition);
2057 ParkPositionNP[AXIS_RA].setValue(Axis1ParkPosition);
2058 ParkPositionNP[AXIS_DE].setValue(Axis2ParkPosition);
2060 }
2061
2062 return true;
2063}
2064
2065const char *Telescope::LoadParkXML()
2066{
2067 wordexp_t wexp;
2068 FILE *fp = nullptr;
2069 LilXML *lp = nullptr;
2070 static char errmsg[XML_ERROR_SIZE];
2071
2072 XMLEle *parkxml = nullptr;
2073 XMLAtt *ap = nullptr;
2074 bool devicefound = false;
2075
2076 ParkDeviceName = getDeviceName();
2077 ParkstatusXml = nullptr;
2078 ParkdeviceXml = nullptr;
2079 ParkpositionXml = nullptr;
2080 ParkpositionAxis1Xml = nullptr;
2081 ParkpositionAxis2Xml = nullptr;
2082
2083 if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2084 {
2085 wordfree(&wexp);
2086 return "Badly formed filename.";
2087 }
2088
2089 if (!(fp = fopen(wexp.we_wordv[0], "r")))
2090 {
2091 wordfree(&wexp);
2092 return strerror(errno);
2093 }
2094 wordfree(&wexp);
2095
2096 lp = newLilXML();
2097
2098 if (ParkdataXmlRoot)
2099 delXMLEle(ParkdataXmlRoot);
2100
2101 ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2102 fclose(fp);
2103
2104 delLilXML(lp);
2105 if (!ParkdataXmlRoot)
2106 return errmsg;
2107
2108 parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2109
2110 if (!parkxml)
2111 return "Empty park file.";
2112
2113 if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2114 {
2115 delXMLEle(parkxml);
2116 return "Not a park data file";
2117 }
2118
2119 while (parkxml)
2120 {
2121 if (strcmp(tagXMLEle(parkxml), "device"))
2122 {
2123 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2124 continue;
2125 }
2126 ap = findXMLAtt(parkxml, "name");
2127 if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2128 {
2129 devicefound = true;
2130 break;
2131 }
2132 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2133 }
2134
2135 if (!devicefound)
2136 {
2137 delXMLEle(parkxml);
2138 return "No park data found for this device";
2139 }
2140
2141 ParkdeviceXml = parkxml;
2142 ParkstatusXml = findXMLEle(parkxml, "parkstatus");
2143
2145 {
2146 ParkpositionXml = findXMLEle(parkxml, "parkposition");
2147 if (ParkpositionXml)
2148 ParkpositionAxis1Xml = findXMLEle(ParkpositionXml, "axis1position");
2149 if (ParkpositionXml)
2150 ParkpositionAxis2Xml = findXMLEle(ParkpositionXml, "axis2position");
2151
2152 if (ParkstatusXml == nullptr || ParkpositionAxis1Xml == nullptr || ParkpositionAxis2Xml == nullptr)
2153 {
2154 return "Park data invalid or missing.";
2155 }
2156 }
2157 else if (ParkstatusXml == nullptr)
2158 {
2159 return "Park data invalid or missing.";
2160 }
2161
2162 return nullptr;
2163}
2164
2166{
2167 IsParked = false;
2168
2169 const char *result = LoadParkXML();
2170 if (result != nullptr)
2171 return result;
2172
2173 if (!strcmp(pcdataXMLEle(ParkstatusXml), "true"))
2174 IsParked = true;
2175
2177 {
2178 double axis1Pos = std::numeric_limits<double>::quiet_NaN();
2179 double axis2Pos = std::numeric_limits<double>::quiet_NaN();
2180
2181 int rc = sscanf(pcdataXMLEle(ParkpositionAxis1Xml), "%lf", &axis1Pos);
2182 if (rc != 1)
2183 {
2184 return "Unable to parse Park Position Axis 1.";
2185 }
2186 rc = sscanf(pcdataXMLEle(ParkpositionAxis2Xml), "%lf", &axis2Pos);
2187 if (rc != 1)
2188 {
2189 return "Unable to parse Park Position Axis 2.";
2190 }
2191
2192 if (std::isnan(axis1Pos) == false && std::isnan(axis2Pos) == false)
2193 {
2194 Axis1ParkPosition = axis1Pos;
2195 Axis2ParkPosition = axis2Pos;
2196 return nullptr;
2197 }
2198
2199 return "Failed to parse Park Position.";
2200 }
2201
2202 return nullptr;
2203}
2204
2206{
2207 // We need to refresh parking data in case other devices parking states were updated since we
2208 // read the data the first time.
2209 if (LoadParkXML() != nullptr)
2210 LOG_DEBUG("Failed to refresh parking data.");
2211
2212 wordexp_t wexp;
2213 FILE *fp = nullptr;
2214 LilXML *lp = nullptr;
2215 static char errmsg[XML_ERROR_SIZE];
2216
2217 XMLEle *parkxml = nullptr;
2218 XMLAtt *ap = nullptr;
2219 bool devicefound = false;
2220
2221 ParkDeviceName = getDeviceName();
2222
2223 if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2224 {
2225 wordfree(&wexp);
2226 return false;
2227 }
2228
2229 if (!(fp = fopen(wexp.we_wordv[0], "r")))
2230 {
2231 wordfree(&wexp);
2232 LOGF_ERROR("Failed to purge park data: %s", strerror(errno));
2233 return false;
2234 }
2235 wordfree(&wexp);
2236
2237 lp = newLilXML();
2238
2239 if (ParkdataXmlRoot)
2240 delXMLEle(ParkdataXmlRoot);
2241
2242 ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2243 fclose(fp);
2244
2245 delLilXML(lp);
2246 if (!ParkdataXmlRoot)
2247 return false;
2248
2249 parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2250
2251 if (!parkxml)
2252 return false;
2253
2254 if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2255 {
2256 delXMLEle(parkxml);
2257 return false;
2258 }
2259
2260 while (parkxml)
2261 {
2262 if (strcmp(tagXMLEle(parkxml), "device"))
2263 {
2264 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2265 continue;
2266 }
2267 ap = findXMLAtt(parkxml, "name");
2268 if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2269 {
2270 devicefound = true;
2271 break;
2272 }
2273 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2274 }
2275
2276 if (!devicefound)
2277 return false;
2278
2279 delXMLEle(parkxml);
2280
2281 ParkstatusXml = nullptr;
2282 ParkdeviceXml = nullptr;
2283 ParkpositionXml = nullptr;
2284 ParkpositionAxis1Xml = nullptr;
2285 ParkpositionAxis2Xml = nullptr;
2286
2287 wordexp(ParkDataFileName.c_str(), &wexp, 0);
2288 if (!(fp = fopen(wexp.we_wordv[0], "w")))
2289 {
2290 wordfree(&wexp);
2291 LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(), strerror(errno));
2292 return false;
2293 }
2294 prXMLEle(fp, ParkdataXmlRoot, 0);
2295 fclose(fp);
2296 wordfree(&wexp);
2297
2298 return true;
2299}
2300
2302{
2303 // We need to refresh parking data in case other devices parking states were updated since we
2304 // read the data the first time.
2305 if (LoadParkXML() != nullptr)
2306 LOG_DEBUG("Failed to refresh parking data.");
2307
2308 wordexp_t wexp;
2309 FILE *fp;
2310 char pcdata[30];
2311 ParkDeviceName = getDeviceName();
2312
2313 if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2314 {
2315 wordfree(&wexp);
2316 LOGF_INFO("WriteParkData: can not write file %s: Badly formed filename.",
2317 ParkDataFileName.c_str());
2318 return false;
2319 }
2320
2321 if (!(fp = fopen(wexp.we_wordv[0], "w")))
2322 {
2323 wordfree(&wexp);
2324 LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(),
2325 strerror(errno));
2326 return false;
2327 }
2328
2329 if (!ParkdataXmlRoot)
2330 ParkdataXmlRoot = addXMLEle(nullptr, "parkdata");
2331
2332 if (!ParkdeviceXml)
2333 {
2334 ParkdeviceXml = addXMLEle(ParkdataXmlRoot, "device");
2335 addXMLAtt(ParkdeviceXml, "name", ParkDeviceName);
2336 }
2337
2338 if (!ParkstatusXml)
2339 ParkstatusXml = addXMLEle(ParkdeviceXml, "parkstatus");
2340 editXMLEle(ParkstatusXml, (IsParked ? "true" : "false"));
2341
2343 {
2344 if (!ParkpositionXml)
2345 ParkpositionXml = addXMLEle(ParkdeviceXml, "parkposition");
2346 if (!ParkpositionAxis1Xml)
2347 ParkpositionAxis1Xml = addXMLEle(ParkpositionXml, "axis1position");
2348 if (!ParkpositionAxis2Xml)
2349 ParkpositionAxis2Xml = addXMLEle(ParkpositionXml, "axis2position");
2350
2351 snprintf(pcdata, sizeof(pcdata), "%lf", Axis1ParkPosition);
2352 editXMLEle(ParkpositionAxis1Xml, pcdata);
2353 snprintf(pcdata, sizeof(pcdata), "%lf", Axis2ParkPosition);
2354 editXMLEle(ParkpositionAxis2Xml, pcdata);
2355 }
2356
2357 prXMLEle(fp, ParkdataXmlRoot, 0);
2358 fclose(fp);
2359 wordfree(&wexp);
2360
2361 return true;
2362}
2363
2365{
2366 return Axis1ParkPosition;
2367}
2369{
2370 return Axis1DefaultParkPosition;
2371}
2373{
2374 return Axis2ParkPosition;
2375}
2377{
2378 return Axis2DefaultParkPosition;
2379}
2380
2381void Telescope::SetAxis1Park(double value)
2382{
2383 LOGF_DEBUG("Setting Park Axis1 to %.2f", value);
2384 Axis1ParkPosition = value;
2385 ParkPositionNP[AXIS_RA].setValue(value);
2387}
2388
2390{
2391 LOGF_DEBUG("Setting Default Park Axis1 to %.2f", value);
2392 Axis1DefaultParkPosition = value;
2393}
2394
2395void Telescope::SetAxis2Park(double value)
2396{
2397 LOGF_DEBUG("Setting Park Axis2 to %.2f", value);
2398 Axis2ParkPosition = value;
2399 ParkPositionNP[AXIS_DE].setValue(value);
2401}
2402
2404{
2405 LOGF_DEBUG("Setting Default Park Axis2 to %.2f", value);
2406 Axis2DefaultParkPosition = value;
2407}
2408
2410{
2411 return DomePolicySP[DOME_LOCKS].getState() == ISS_ON && IsLocked;
2412}
2413
2415{
2416 INDI_UNUSED(index);
2417 return true;
2418}
2419
2420void Telescope::processButton(const char *button_n, ISState state)
2421{
2422 //ignore OFF
2423 if (state == ISS_OFF)
2424 return;
2425
2426 if (!strcmp(button_n, "ABORTBUTTON"))
2427 {
2428 auto trackSW = getSwitch("TELESCOPE_TRACK_MODE");
2429 // Only abort if we have some sort of motion going on
2431 || EqNP.getState() == IPS_BUSY ||
2432 (trackSW && trackSW.getState() == IPS_BUSY))
2433 {
2434 // Invoke parent processing so that Telescope takes care of abort cross-check
2435 ISState states[1] = { ISS_ON };
2436 const char *names[1] = { AbortSP[0].getName() };
2437 ISNewSwitch(getDeviceName(), AbortSP.getName(), states, const_cast<char **>(names), 1);
2438 }
2439 }
2440 else if (!strcmp(button_n, "PARKBUTTON"))
2441 {
2442 ISState states[2] = { ISS_ON, ISS_OFF };
2443 const char *names[2] = { ParkSP[PARK].getName(), ParkSP[UNPARK].getName() };
2444 ISNewSwitch(getDeviceName(), ParkSP.getName(), states, const_cast<char **>(names), 2);
2445 }
2446 else if (!strcmp(button_n, "UNPARKBUTTON"))
2447 {
2448 ISState states[2] = { ISS_OFF, ISS_ON };
2449 const char *names[2] = { ParkSP[PARK].getName(), ParkSP[UNPARK].getName() };
2450 ISNewSwitch(getDeviceName(), ParkSP.getName(), states, const_cast<char **>(names), 2);
2451 }
2452 else if (!strcmp(button_n, "SLEWPRESETUP"))
2453 {
2454 // Re-arm the one-shot gate — button events don't pass through the
2455 // mag < 0.5 re-arm path that analog joystick pairs use.
2456 m_slewPresetArmed = true;
2457 // angle=90 (RIGHT) falls in the (45°,225°] "increase-index" branch
2458 processSlewPresets(1, 90);
2459 }
2460 else if (!strcmp(button_n, "SLEWPRESETDOWN"))
2461 {
2462 // Re-arm the one-shot gate — same reason as above.
2463 m_slewPresetArmed = true;
2464 // angle=270 (LEFT) falls in the "decrease-index" branch
2465 processSlewPresets(1, 270);
2466 }
2467}
2468
2469void Telescope::processJoystick(const char *joystick_n, double mag, double angle)
2470{
2471 if (MotionControlModeTP[MOTION_CONTROL_MODE_JOYSTICK].getState() == ISS_ON && !strcmp(joystick_n, "MOTIONDIR"))
2472 {
2474 {
2475 DEBUG(Logger::DBG_WARNING, "Can not slew while mount is parking/parked.");
2476 return;
2477 }
2478
2479 processNSWE(mag, angle);
2480 }
2481 else if (!strcmp(joystick_n, "SLEWPRESET"))
2482 processSlewPresets(mag, angle);
2483}
2484
2485void Telescope::processAxis(const char *axis_n, double value)
2486{
2488 {
2489 if (!strcmp(axis_n, "MOTIONDIRNS") || !strcmp(axis_n, "MOTIONDIRWE"))
2490 {
2492 {
2493 LOG_WARN("Cannot slew while mount is parking/parked.");
2494 return;
2495 }
2496
2497 if (!strcmp(axis_n, "MOTIONDIRNS"))
2498 {
2499 // South
2500 if (value > 0)
2501 {
2502 motionDirNSValue = -1;
2503 }
2504 // North
2505 else if (value < 0)
2506 {
2507 motionDirNSValue = 1;
2508 }
2509 else
2510 {
2511 motionDirNSValue = 0;
2512 }
2513 }
2514 else if (!strcmp(axis_n, "MOTIONDIRWE"))
2515 {
2516 // East
2517 if (value > 0)
2518 {
2519 motionDirWEValue = 1;
2520 }
2521 // West
2522 else if (value < 0)
2523 {
2524 motionDirWEValue = -1;
2525 }
2526 else
2527 {
2528 motionDirWEValue = 0;
2529 }
2530 }
2531
2532 float x = motionDirWEValue * sqrt(1 - pow(motionDirNSValue, 2) / 2.0f);
2533 float y = motionDirNSValue * sqrt(1 - pow(motionDirWEValue, 2) / 2.0f);
2534 float angle = atan2(y, x) * (180.0 / 3.141592653589);
2535 float mag = sqrt(pow(y, 2) + pow(x, 2));
2536 while (angle < 0)
2537 {
2538 angle += 360;
2539 }
2540 if (mag == 0)
2541 {
2542 angle = 0;
2543 }
2544
2545 processNSWE(mag, angle);
2546 }
2547 }
2548}
2549
2550void Telescope::processNSWE(double mag, double angle)
2551{
2552 if (mag < 0.5)
2553 {
2554 // Moving in the same direction will make it stop
2556 {
2558 {
2562 }
2563 else
2564 {
2567 }
2568 }
2569
2571 {
2573 {
2577 }
2578 else
2579 {
2582 }
2583 }
2584 }
2585 // Put high threshold
2586 else if (mag > 0.9)
2587 {
2588 // Only one axis can move at a time
2589 if (LockAxisSP[AXIS_RA].getState() == ISS_ON)
2590 {
2591 // West
2592 if (angle >= 90 && angle <= 270)
2593 angle = 180;
2594 // East
2595 else
2596 angle = 0;
2597 }
2598 else if (LockAxisSP[AXIS_DE].getState() == ISS_ON)
2599 {
2600 // North
2601 if (angle >= 0 && angle <= 180)
2602 angle = 90;
2603 // South
2604 else
2605 angle = 270;
2606 }
2607
2608 // Snap angle to x or y direction if close to corresponding axis (i.e. deviation < 15°)
2609 if (angle > 75 && angle < 105)
2610 {
2611 angle = 90;
2612 }
2613 if (angle > 165 && angle < 195)
2614 {
2615 angle = 180;
2616 }
2617 if (angle > 255 && angle < 285)
2618 {
2619 angle = 270;
2620 }
2621 if (angle > 345 || angle < 15)
2622 {
2623 angle = 0;
2624 }
2625
2626 // Apply joystick reverse/swap transforms
2628 angle = fmod(angle + 90.0, 360.0);
2629 if (JoystickReverseSP[JOYSTICK_REVERSE_NS].getState() == ISS_ON)
2630 angle = fmod(360.0 - angle, 360.0);
2631 if (JoystickReverseSP[JOYSTICK_REVERSE_WE].getState() == ISS_ON)
2632 angle = fmod(540.0 - angle, 360.0);
2633
2634 // North
2635 if (angle > 0 && angle < 180)
2636 {
2637 // Don't try to move if you're busy and moving in the same direction
2640
2645 }
2646 // South
2647 if (angle > 180 && angle < 360)
2648 {
2649 // Don't try to move if you're busy and moving in the same direction
2652
2657 }
2658 // East
2659 if (angle < 90 || angle > 270)
2660 {
2661 // Don't try to move if you're busy and moving in the same direction
2664
2669 }
2670
2671 // West
2672 if (angle > 90 && angle < 270)
2673 {
2674 // Don't try to move if you're busy and moving in the same direction
2677
2682 }
2683 }
2684}
2685
2686void Telescope::processSlewPresets(double mag, double angle)
2687{
2688 // mag >= 0.9 → joystick fully deflected → eligible to fire once
2689 // mag < 0.5 → joystick near centre → re-arm for next press
2690 // 0.5 ≤ mag < 0.9 → transitional zone → do nothing
2691 if (mag < 0.9)
2692 {
2693 if (mag < 0.5 && !m_slewPresetArmed)
2694 m_slewPresetArmed = true;
2695 return;
2696 }
2697
2698 // Already fired once for this press – ignore jitter until released.
2699 if (!m_slewPresetArmed)
2700 return;
2701
2702 m_slewPresetArmed = false;
2703
2704 size_t currentIndex = SlewRateSP.findOnSwitchIndex();
2705
2706 // Direction convention (compass angles, N=0°):
2707 // RIGHT (90°) / DOWN (180°) → angle in (45°, 225°] → increase index
2708 // UP (0°) / LEFT (270°) → angle outside that range → decrease index
2709 //
2710 // This matches the standard GUI list convention where RIGHT/DOWN moves to
2711 // the next (higher-index) item and UP/LEFT moves to the previous item.
2712 if (angle > 45 && angle <= 225)
2713 {
2714 // Increase index → next (faster) slew rate
2715 if (currentIndex >= SlewRateSP.count() - 1)
2716 return;
2717 SlewRateSP.reset();
2718 SlewRateSP[currentIndex + 1].setState(ISS_ON);
2719 SetSlewRate(currentIndex + 1);
2720 }
2721 else
2722 {
2723 // Decrease index → previous (slower) slew rate
2724 if (currentIndex <= 0)
2725 return;
2726 SlewRateSP.reset();
2727 SlewRateSP[currentIndex - 1].setState(ISS_ON);
2728 SetSlewRate(currentIndex - 1);
2729 }
2730
2731 SlewRateSP.apply();
2732}
2733
2734void Telescope::joystickHelper(const char *joystick_n, double mag, double angle, void *context)
2735{
2736 static_cast<Telescope *>(context)->processJoystick(joystick_n, mag, angle);
2737}
2738
2739void Telescope::axisHelper(const char *axis_n, double value, void *context)
2740{
2741 static_cast<Telescope *>(context)->processAxis(axis_n, value);
2742}
2743
2744void Telescope::buttonHelper(const char *button_n, ISState state, void *context)
2745{
2746 static_cast<Telescope *>(context)->processButton(button_n, state);
2747}
2748
2750{
2751 // ensure that the scope knows it's pier side or the pier side is simulated
2752 if (HasPierSide() == false && getSimulatePierSide() == false)
2753 return;
2754
2755 currentPierSide = side;
2756
2758 {
2762 PierSideSP.apply();
2763
2765 }
2766}
2767
2774{
2775 // return unknown if the mount does not have pier side, this will be the case for a fork mount
2776 // where a pier flip is not required.
2779
2780 // calculate the hour angle and derive the pier side
2782 double hourAngle = get_local_hour_angle(lst, ra);
2783
2785}
2786
2788{
2789 currentPECState = state;
2790
2792 {
2793
2795 PECStateSP[PEC_ON].setState((state == PEC_ON) ? ISS_ON : ISS_OFF);
2797 PECStateSP.apply();
2798
2800 }
2801}
2802
2804{
2805 // Check first the HOME environmental variable
2806 const char *HomeDir = getenv("HOME");
2807
2808 // ...otherwise get the home directory of the current user.
2809 if (!HomeDir)
2810 {
2811 HomeDir = getpwuid(getuid())->pw_dir;
2812 }
2813 return (HomeDir ? std::string(HomeDir) : "");
2814}
2815
2816bool Telescope::CheckFile(const std::string &file_name, bool writable) const
2817{
2818 FILE *FilePtr = fopen(file_name.c_str(), (writable ? "a" : "r"));
2819
2820 if (FilePtr)
2821 {
2822 fclose(FilePtr);
2823 return true;
2824 }
2825 return false;
2826}
2827
2829{
2830 char ts[32] = {0};
2831
2832 std::time_t t = std::time(nullptr);
2833 struct std::tm *utctimeinfo = std::gmtime(&t);
2834
2835 strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
2836 TimeTP[UTC].setText(ts);
2837
2838 struct std::tm *localtimeinfo = std::localtime(&t);
2839 snprintf(ts, sizeof(ts), "%4.2f", (localtimeinfo->tm_gmtoff / 3600.0));
2840 TimeTP[OFFSET].setText(ts);
2841
2843
2844 TimeTP.apply();
2845}
2846
2848{
2849 return m_simulatePierSide;
2850}
2851
2853{
2854 switch (ps)
2855 {
2856 case PIER_WEST:
2857 return "PIER_WEST";
2858 case PIER_EAST:
2859 return "PIER_EAST";
2860 default:
2861 return "PIER_UNKNOWN";
2862 }
2863}
2864
2866{
2872
2873 if (simulate)
2874 {
2877 }
2878 else
2879 {
2880 capability &= static_cast<uint32_t>(~TELESCOPE_HAS_PIER_SIDE);
2882 }
2883
2884 m_simulatePierSide = simulate;
2885}
2886
2887}
int SetSlewRate(void)
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
int getPortFD() const
bool isConnected() const
const char * getDeviceName() const
INDI::PropertySwitch getSwitch(const char *name) const
INDI::PropertyText getText(const char *name) const
The Controller class provides functionality to access a controller (e.g. joystick) input and send it ...
void setButtonCallback(buttonFunc buttonCallback)
setButtonCallback Sets the callback function when a new button input is detected.
virtual bool ISSnoopDevice(XMLEle *root)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
void mapController(const char *propertyName, const char *propertyLabel, ControllerType type, const char *initialValue)
mapController adds a new property to the joystick's settings.
virtual bool initProperties()
void setAxisCallback(axisFunc axisCallback)
setAxisCallback Sets the callback function when a new axis input is detected.
virtual bool saveConfigItems(FILE *fp)
virtual void ISGetProperties(const char *dev)
virtual bool updateProperties()
void setJoystickCallback(joystickFunc joystickCallback)
setJoystickCallback Sets the callback function when a new joystick input is detected.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
void addPollPeriodControl()
Add Polling period control to the driver.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual const char * getDefaultName()=0
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
Connection::Interface * getActiveConnection()
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
void setDeviceName(const char *name)
void push(WidgetView< T > &&item)
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void resize(size_t size)
void setLabel(const char *label)
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool isSwitchOn(const std::string &name) const
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool isValid() const
TelescopeStatus TrackState
bool isLocked() const
isLocked is mount currently locked?
virtual bool SetTrackMode(uint8_t mode)
SetTrackMode Set active tracking mode. Do not change track state.
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
bool HasPierSideSimulation()
virtual bool Goto(double ra, double dec)
Move the scope to the supplied RA and DEC coordinates.
void processButton(const char *button_n, ISState state)
static void axisHelper(const char *axis_n, double value, void *context)
void processAxis(const char *axis_n, double value)
bool CheckFile(const std::string &file_name, bool writable) const
Check if a file exists and it is readable.
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
virtual bool ReadScopeStatus()=0
Read telescope status.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool Park()
Park the telescope to its home position.
virtual bool SetCurrentPark()
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
TelescopeParkData parkDataType
std::string GetHomeDirectory() const
Validate a file name.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual void TimerHit() override
Called when setTimer() time is up.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool SetTrackRate(double raRate, double deRate)
SetTrackRate Set custom tracking rates.
double GetAxis1Park() const
virtual bool UnPark()
Unpark the telescope if already parked.
virtual bool SetSlewRate(int index)
SetSlewRate Set desired slew rate index.
virtual bool SetDefaultPark()
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
void setTelescopeConnection(const uint8_t &value)
setTelescopeConnection Set telescope connection mode. Child class should call this in the constructor...
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
void processNSWE(double mag, double angle)
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
static void joystickHelper(const char *joystick_n, double mag, double angle, void *context)
void updateObserverLocation(double latitude, double longitude, double elevation)
Update location settings of the observer.
double GetAxis1ParkDefault() const
virtual bool Handshake()
perform handshake with device to check communication
virtual bool SetTrackEnabled(bool enabled)
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
virtual bool updateTime(ln_date *utc, double utc_offset)
Update telescope time, date, and UTC offset.
Connection::TCP * tcpConnection
static void buttonHelper(const char *button_n, ISState state, void *context)
uint8_t getTelescopeConnection() const
const char * LoadParkData()
void setPECState(TelescopePECState state)
virtual IPState ExecuteHomeAction(TelescopeHomeAction action)
Execute a homing action.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateLocation(double latitude, double longitude, double elevation)
Update telescope location settings.
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual bool SetParkPosition(double Axis1Value, double Axis2Value)
SetParkPosition Set desired parking position to the supplied value. This ONLY sets the desired park p...
IGeographicCoordinates m_Location
void processJoystick(const char *joystick_n, double mag, double angle)
bool callHandshake()
callHandshake Helper function that sets the port file descriptor before calling the actual Handshake ...
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
virtual void SyncParkStatus(bool isparked)
SyncParkStatus Update the state and switches for parking.
void setPierSide(TelescopePierSide side)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
Move the telescope in the direction dir.
TelescopeStatus RememberTrackState
RememberTrackState Remember last state of Track State to fall back to in case of errors or aborts.
virtual bool Abort()
Abort any telescope motion including tracking if possible.
double GetAxis2ParkDefault() const
TelescopePECState currentPECState
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
Start or Stop the telescope motion in the direction dir.
virtual bool Flip(double ra, double dec)
Move and flip the scope to the supplied RA and DEC coordinates.
void processSlewPresets(double mag, double angle)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
TelescopePECState lastPECState
TelescopePierSide expectedPierSide(double ra)
Calculate the expected pier side for scopes that do not report this property themselves.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool Sync(double ra, double dec)
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * SATELLITE_TAB
SATELLITE_TAB.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
int errno
INDI::PropertySwitch ParkSP
INDI::PropertySwitch SimulatePierSideSP
TelescopePierSide currentPierSide
INDI::PropertySwitch AbortSP
INDI::PropertySwitch DomePolicySP
INDI::PropertyText ActiveDeviceTP
INDI::PropertyText TLEtoTrackTP
Text Vector property defining the orbital elements of an artificial satellite (TLE)....
INDI::PropertyNumber TrackRateNP
INDI::PropertySwitch MovementNSSP
INDI::PropertyText SatPassWindowTP
Text Vector property defining the start and end of a satellite pass (window contains pass)....
TelescopePierSide lastPierSide
INDI::PropertyNumber EqNP
INDI::PropertySwitch TrackStateSP
bool getSimulatePierSide() const
INDI::PropertySwitch PECStateSP
INDI::PropertySwitch HomeSP
INDI::PropertySwitch MotionControlModeTP
INDI::PropertySwitch TrackSatSP
Switch Vector property defining the state of the satellite tracking of the mount. Example implementat...
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertyText TimeTP
INDI::PropertySwitch MountTypeSP
INDI::PropertySwitch PierSideSP
INDI::PropertySwitch LockAxisSP
INDI::PropertySwitch ReverseMovementSP
INDI::PropertySwitch MovementWESP
INDI::PropertySwitch JoystickReverseSP
INDI::PropertySwitch ParkOptionSP
INDI::PropertySwitch CoordSP
void setSimulatePierSide(bool value)
INDI::PropertyNumber LocationNP
INDI::PropertyNumber ParkPositionNP
const char * getPierSideStr(TelescopePierSide ps)
INDI::PropertyNumber TargetNP
@ SAT_PASS_WINDOW_END
Index for end of the window.
@ SAT_PASS_WINDOW_START
Index for start of the window.
@ SAT_TRACK
Track signal.
@ SAT_HALT
Halt signal (abort)
double ra
double dec
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
#define MAXINDIFORMAT
Definition indiapi.h:193
#define MAXINDITSTAMP
Definition indiapi.h:195
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
@ AXIS_DE
@ AXIS_RA
@ AXIS_AZ
@ AXIS_ALT
INDI_DIR_WE
@ DIRECTION_EAST
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_SOUTH
@ DIRECTION_NORTH
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition indicom.c:1379
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
int extractISOTime(const char *timestr, struct ln_date *iso_date)
Extract ISO 8601 time and store it in a tm struct.
int IUFindIndex(const char *needle, char **hay, unsigned int n)
Returns the index of the string in a string array.
Definition indidevapi.c:326
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
Definition indidevapi.c:355
#define INDI_UNUSED(x)
Definition indidevapi.h:132
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition indidriver.c:143
int IUGetConfigNumber(const char *dev, const char *property, const char *member, double *value)
IUGetConfigNumber Opens configuration file and reads single number property.
Definition indidriver.c:846
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition indidriver.c:657
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition indilogger.h:57
#define TRACKRATE_SIDEREAL
Definition indimacros.h:39
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
XMLAtt * findXMLAtt(XMLEle *ep, const char *name)
Find an XML attribute within an XML element.
Definition lilxml.cpp:524
LilXML * newLilXML()
Create a new lilxml parser.
Definition lilxml.cpp:150
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition lilxml.cpp:600
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
Definition lilxml.cpp:670
XMLEle * findXMLEle(XMLEle *ep, const char *tag)
Find an XML element within an XML element.
Definition lilxml.cpp:537
char * valuXMLAtt(XMLAtt *ap)
Return the value of an XML attribute.
Definition lilxml.cpp:624
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
Definition lilxml.cpp:698
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition lilxml.cpp:844
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition lilxml.cpp:159
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition lilxml.cpp:706
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
Definition lilxml.cpp:653
#define XML_ERROR_SIZE
Definition lilxml.h:73
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint16_t type
Definition pwc-ioctl.h:0
void fill(const char *name, const char *label, ISState state=ISS_OFF)