Instrument Neutral Distributed Interface INDI  2.0.0
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 
39 namespace INDI
40 {
41 
43  : DefaultDevice(), ScopeConfigFileName(GetHomeDirectory() + "/.indi/ScopeConfig.xml"),
44  ParkDataFileName(GetHomeDirectory() + "/.indi/ParkData.xml")
45 {
46  controller = new Controller(this);
48  controller->setAxisCallback(axisHelper);
49  controller->setButtonCallback(buttonHelper);
50 
53 
56 }
57 
59 {
60  if (ParkdataXmlRoot)
61  delXMLEle(ParkdataXmlRoot);
62 
63  delete (controller);
64 }
65 
67 {
69 
70  // Active Devices
71  IUFillText(&ActiveDeviceT[0], "ACTIVE_GPS", "GPS", "GPS Simulator");
72  IUFillText(&ActiveDeviceT[1], "ACTIVE_DOME", "DOME", "Dome Simulator");
73  IUFillTextVector(&ActiveDeviceTP, ActiveDeviceT, 2, getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB,
74  IP_RW, 60, IPS_IDLE);
75 
76  // Use locking if dome is closed (and or) park scope if dome is closing
77  IUFillSwitch(&DomePolicyS[DOME_IGNORED], "DOME_IGNORED", "Dome ignored", ISS_ON);
78  IUFillSwitch(&DomePolicyS[DOME_LOCKS], "DOME_LOCKS", "Dome locks", ISS_OFF);
79  // IUFillSwitch(&DomeClosedLockT[2], "FORCE_CLOSE", "Dome parks", ISS_OFF);
80  // IUFillSwitch(&DomeClosedLockT[3], "LOCK_AND_FORCE", "Both", ISS_OFF);
81  IUFillSwitchVector(&DomePolicySP, DomePolicyS, 2, getDeviceName(), "DOME_POLICY", "Dome Policy", OPTIONS_TAB, IP_RW,
82  ISR_1OFMANY, 60, IPS_IDLE);
83 
84  IUFillNumber(&EqN[AXIS_RA], "RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
85  IUFillNumber(&EqN[AXIS_DE], "DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
86  IUFillNumberVector(&EqNP, EqN, 2, getDeviceName(), "EQUATORIAL_EOD_COORD", "Eq. Coordinates", MAIN_CONTROL_TAB,
87  IP_RW, 60, IPS_IDLE);
88  lastEqState = IPS_IDLE;
89 
90  IUFillNumber(&TargetN[AXIS_RA], "RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
91  IUFillNumber(&TargetN[AXIS_DE], "DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
92  IUFillNumberVector(&TargetNP, TargetN, 2, getDeviceName(), "TARGET_EOD_COORD", "Slew Target", MOTION_TAB, IP_RO, 60,
93  IPS_IDLE);
94 
95  IUFillSwitch(&ParkOptionS[PARK_CURRENT], "PARK_CURRENT", "Current", ISS_OFF);
96  IUFillSwitch(&ParkOptionS[PARK_DEFAULT], "PARK_DEFAULT", "Default", ISS_OFF);
97  IUFillSwitch(&ParkOptionS[PARK_WRITE_DATA], "PARK_WRITE_DATA", "Write Data", ISS_OFF);
98  IUFillSwitch(&ParkOptionS[PARK_PURGE_DATA], "PARK_PURGE_DATA", "Purge Data", ISS_OFF);
99  IUFillSwitchVector(&ParkOptionSP, ParkOptionS, 4, getDeviceName(), "TELESCOPE_PARK_OPTION", "Park Options",
101 
102  IUFillText(&TimeT[0], "UTC", "UTC Time", nullptr);
103  IUFillText(&TimeT[1], "OFFSET", "UTC Offset", nullptr);
104  IUFillTextVector(&TimeTP, TimeT, 2, getDeviceName(), "TIME_UTC", "UTC", SITE_TAB, IP_RW, 60, IPS_IDLE);
105 
106  IUFillNumber(&LocationN[LOCATION_LATITUDE], "LAT", "Lat (dd:mm:ss.s)", "%012.8m", -90, 90, 0, 0.0);
107  IUFillNumber(&LocationN[LOCATION_LONGITUDE], "LONG", "Lon (dd:mm:ss.s)", "%012.8m", 0, 360, 0, 0.0);
108  IUFillNumber(&LocationN[LOCATION_ELEVATION], "ELEV", "Elevation (m)", "%g", -200, 10000, 0, 0);
109  IUFillNumberVector(&LocationNP, LocationN, 3, getDeviceName(), "GEOGRAPHIC_COORD", "Scope Location", SITE_TAB,
110  IP_RW, 60, IPS_IDLE);
111 
112  // Pier Side
113  IUFillSwitch(&PierSideS[PIER_WEST], "PIER_WEST", "West (pointing east)", ISS_OFF);
114  IUFillSwitch(&PierSideS[PIER_EAST], "PIER_EAST", "East (pointing west)", ISS_OFF);
115  IUFillSwitchVector(&PierSideSP, PierSideS, 2, getDeviceName(), "TELESCOPE_PIER_SIDE", "Pier Side", MAIN_CONTROL_TAB,
116  IP_RO, ISR_ATMOST1, 60, IPS_IDLE);
117  // Pier Side Simulation
118  IUFillSwitch(&SimulatePierSideS[0], "SIMULATE_YES", "Yes", ISS_OFF);
119  IUFillSwitch(&SimulatePierSideS[1], "SIMULATE_NO", "No", ISS_ON);
120  IUFillSwitchVector(&SimulatePierSideSP, SimulatePierSideS, 2, getDeviceName(), "SIMULATE_PIER_SIDE", "Simulate Pier Side",
122  IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
123 
124  // PEC State
125  IUFillSwitch(&PECStateS[PEC_OFF], "PEC OFF", "PEC OFF", ISS_ON);
126  IUFillSwitch(&PECStateS[PEC_ON], "PEC ON", "PEC ON", ISS_OFF);
127  IUFillSwitchVector(&PECStateSP, PECStateS, 2, getDeviceName(), "PEC", "PEC Playback", MOTION_TAB, IP_RW, ISR_1OFMANY, 0,
128  IPS_IDLE);
129 
130  // Track Mode. Child class must call AddTrackMode to add members
131  IUFillSwitchVector(&TrackModeSP, TrackModeS, 0, getDeviceName(), "TELESCOPE_TRACK_MODE", "Track Mode", MAIN_CONTROL_TAB,
132  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
133 
134  // Track State
135  IUFillSwitch(&TrackStateS[TRACK_ON], "TRACK_ON", "On", ISS_OFF);
136  IUFillSwitch(&TrackStateS[TRACK_OFF], "TRACK_OFF", "Off", ISS_ON);
137  IUFillSwitchVector(&TrackStateSP, TrackStateS, 2, getDeviceName(), "TELESCOPE_TRACK_STATE", "Tracking", MAIN_CONTROL_TAB,
138  IP_RW, ISR_1OFMANY, 0,
139  IPS_IDLE);
140 
141  // Track Rate
142  IUFillNumber(&TrackRateN[AXIS_RA], "TRACK_RATE_RA", "RA (arcsecs/s)", "%.6f", -16384.0, 16384.0, 0.000001,
144  IUFillNumber(&TrackRateN[AXIS_DE], "TRACK_RATE_DE", "DE (arcsecs/s)", "%.6f", -16384.0, 16384.0, 0.000001, 0.0);
145  IUFillNumberVector(&TrackRateNP, TrackRateN, 2, getDeviceName(), "TELESCOPE_TRACK_RATE", "Track Rates", MAIN_CONTROL_TAB,
146  IP_RW, 60, IPS_IDLE);
147 
148  // On Coord Set actions
149  IUFillSwitch(&CoordS[0], "TRACK", "Track", ISS_ON);
150  IUFillSwitch(&CoordS[1], "SLEW", "Slew", ISS_OFF);
151  IUFillSwitch(&CoordS[2], "SYNC", "Sync", ISS_OFF);
152 
153  // If both GOTO and SYNC are supported
154  if (CanGOTO() && CanSync())
155  IUFillSwitchVector(&CoordSP, CoordS, 3, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
156  ISR_1OFMANY, 60, IPS_IDLE);
157  // If ONLY GOTO is supported
158  else if (CanGOTO())
159  IUFillSwitchVector(&CoordSP, CoordS, 2, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
160  ISR_1OFMANY, 60, IPS_IDLE);
161  // If ONLY SYNC is supported
162  else if (CanSync())
163  {
164  IUFillSwitch(&CoordS[0], "SYNC", "Sync", ISS_ON);
165  IUFillSwitchVector(&CoordSP, CoordS, 1, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
166  ISR_1OFMANY, 60, IPS_IDLE);
167  }
168 
169  if (nSlewRate >= 4)
170  IUFillSwitchVector(&SlewRateSP, SlewRateS, nSlewRate, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
172 
173  if (CanTrackSatellite())
174  {
175  IUFillText(&TLEtoTrackT[0], "TLE", "TLE", "");
176  IUFillTextVector(&TLEtoTrackTP, TLEtoTrackT, 1, getDeviceName(), "SAT_TLE_TEXT", "Orbit Params", SATELLITE_TAB,
177  IP_RW, 60, IPS_IDLE);
178 
179  char curTime[32] = {0};
180  std::time_t t = std::time(nullptr);
181  struct std::tm *utctimeinfo = std::gmtime(&t);
182  strftime(curTime, sizeof(curTime), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
183 
184  IUFillText(&SatPassWindowT[SAT_PASS_WINDOW_END], "SAT_PASS_WINDOW_END", "End UTC", curTime);
185  IUFillText(&SatPassWindowT[SAT_PASS_WINDOW_START], "SAT_PASS_WINDOW_START", "Start UTC", curTime);
187  "SAT_PASS_WINDOW", "Pass Window", SATELLITE_TAB, IP_RW, 60, IPS_IDLE);
188 
189  IUFillSwitch(&TrackSatS[SAT_TRACK], "SAT_TRACK", "Track", ISS_OFF);
190  IUFillSwitch(&TrackSatS[SAT_HALT], "SAT_HALT", "Halt", ISS_ON);
192  "Sat tracking", SATELLITE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
193  }
194 
195  IUFillSwitch(&ParkS[0], "PARK", "Park(ed)", ISS_OFF);
196  IUFillSwitch(&ParkS[1], "UNPARK", "UnPark(ed)", ISS_OFF);
197  IUFillSwitchVector(&ParkSP, ParkS, 2, getDeviceName(), "TELESCOPE_PARK", "Parking", MAIN_CONTROL_TAB, IP_RW,
198  ISR_1OFMANY, 60, IPS_IDLE);
199 
200  IUFillSwitch(&AbortS[0], "ABORT", "Abort", ISS_OFF);
201  IUFillSwitchVector(&AbortSP, AbortS, 1, getDeviceName(), "TELESCOPE_ABORT_MOTION", "Abort Motion", MAIN_CONTROL_TAB,
202  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
203 
204  IUFillSwitch(&MovementNSS[DIRECTION_NORTH], "MOTION_NORTH", "North", ISS_OFF);
205  IUFillSwitch(&MovementNSS[DIRECTION_SOUTH], "MOTION_SOUTH", "South", ISS_OFF);
206  IUFillSwitchVector(&MovementNSSP, MovementNSS, 2, getDeviceName(), "TELESCOPE_MOTION_NS", "Motion N/S", MOTION_TAB,
207  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
208 
209  IUFillSwitch(&MovementWES[DIRECTION_WEST], "MOTION_WEST", "West", ISS_OFF);
210  IUFillSwitch(&MovementWES[DIRECTION_EAST], "MOTION_EAST", "East", ISS_OFF);
211  IUFillSwitchVector(&MovementWESP, MovementWES, 2, getDeviceName(), "TELESCOPE_MOTION_WE", "Motion W/E", MOTION_TAB,
212  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
213 
214  // Reverse NS or WE
215  ReverseMovementSP[REVERSE_NS].fill("REVERSE_NS", "North/South", ISS_OFF);
216  ReverseMovementSP[REVERSE_WE].fill("REVERSE_WE", "West/East", ISS_OFF);
217  ReverseMovementSP.fill(getDeviceName(), "TELESCOPE_REVERSE_MOTION", "Reverse", MOTION_TAB, IP_RW, ISR_NOFMANY, 60,
218  IPS_IDLE);
219 
220  IUFillNumber(&ScopeParametersN[0], "TELESCOPE_APERTURE", "Aperture (mm)", "%g", 10, 5000, 0, 0.0);
221  IUFillNumber(&ScopeParametersN[1], "TELESCOPE_FOCAL_LENGTH", "Focal Length (mm)", "%g", 10, 10000, 0, 0.0);
222  IUFillNumber(&ScopeParametersN[2], "GUIDER_APERTURE", "Guider Aperture (mm)", "%g", 10, 5000, 0, 0.0);
223  IUFillNumber(&ScopeParametersN[3], "GUIDER_FOCAL_LENGTH", "Guider Focal Length (mm)", "%g", 10, 10000, 0, 0.0);
224  IUFillNumberVector(&ScopeParametersNP, ScopeParametersN, 4, getDeviceName(), "TELESCOPE_INFO", "Scope Properties",
225  OPTIONS_TAB, IP_RW, 60, IPS_OK);
226 
227  // Scope config name
228  IUFillText(&ScopeConfigNameT[0], "SCOPE_CONFIG_NAME", "Config Name", "");
229  IUFillTextVector(&ScopeConfigNameTP, ScopeConfigNameT, 1, getDeviceName(), "SCOPE_CONFIG_NAME", "Scope Name",
230  OPTIONS_TAB, IP_RW, 60, IPS_OK);
231 
232  // Switch for aperture/focal length configs
233  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG1], "SCOPE_CONFIG1", "Config #1", ISS_ON);
234  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG2], "SCOPE_CONFIG2", "Config #2", ISS_OFF);
235  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG3], "SCOPE_CONFIG3", "Config #3", ISS_OFF);
236  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG4], "SCOPE_CONFIG4", "Config #4", ISS_OFF);
237  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG5], "SCOPE_CONFIG5", "Config #5", ISS_OFF);
238  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG6], "SCOPE_CONFIG6", "Config #6", ISS_OFF);
239  IUFillSwitchVector(&ScopeConfigsSP, ScopeConfigs, 6, getDeviceName(), "APPLY_SCOPE_CONFIG", "Scope Configs",
241 
242  controller->initProperties();
243 
244  // Joystick motion control
245  IUFillSwitch(&MotionControlModeT[0], "MOTION_CONTROL_MODE_JOYSTICK", "4-Way Joystick", ISS_ON);
246  IUFillSwitch(&MotionControlModeT[1], "MOTION_CONTROL_MODE_AXES", "Two Separate Axes", ISS_OFF);
247  IUFillSwitchVector(&MotionControlModeTP, MotionControlModeT, 2, getDeviceName(), "MOTION_CONTROL_MODE", "Motion Control",
248  "Joystick", IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
249 
250  // Lock Axis
251  IUFillSwitch(&LockAxisS[0], "LOCK_AXIS_1", "West/East", ISS_OFF);
252  IUFillSwitch(&LockAxisS[1], "LOCK_AXIS_2", "North/South", ISS_OFF);
253  IUFillSwitchVector(&LockAxisSP, LockAxisS, 2, getDeviceName(), "JOYSTICK_LOCK_AXIS", "Lock Axis", "Joystick", IP_RW,
254  ISR_ATMOST1, 60, IPS_IDLE);
255 
257 
259 
260  if (telescopeConnection & CONNECTION_SERIAL)
261  {
264  {
265  return callHandshake();
266  });
268  }
269 
270  if (telescopeConnection & CONNECTION_TCP)
271  {
272  tcpConnection = new Connection::TCP(this);
274  {
275  return callHandshake();
276  });
277 
279  }
280 
281  IDSnoopDevice(ActiveDeviceT[0].text, "GEOGRAPHIC_COORD");
282  IDSnoopDevice(ActiveDeviceT[0].text, "TIME_UTC");
283 
284  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_PARK");
285  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_SHUTTER");
286 
288 
289  double longitude = 0, latitude = 0, elevation = 0;
290  // Get value from config file if it exists.
292  {
293  LocationN[LOCATION_LONGITUDE].value = longitude;
294  m_Location.longitude = longitude;
295  }
297  {
298  LocationN[LOCATION_LATITUDE].value = latitude;
299  m_Location.latitude = latitude;
300  }
302  {
303  LocationN[LOCATION_ELEVATION].value = elevation;
304  m_Location.elevation = elevation;
305  }
306 
307  return true;
308 }
309 
310 void Telescope::ISGetProperties(const char *dev)
311 {
312  // First we let our parent populate
314 
315  if (CanGOTO())
316  {
318  loadConfig(true, "ACTIVE_DEVICES");
319 
320  ISState isDomeIgnored = ISS_OFF;
321  if (IUGetConfigSwitch(getDeviceName(), DomePolicySP.name, DomePolicyS[DOME_IGNORED].name, &isDomeIgnored) == 0)
322  {
323  DomePolicyS[DOME_IGNORED].s = isDomeIgnored;
324  DomePolicyS[DOME_LOCKS].s = (isDomeIgnored == ISS_ON) ? ISS_OFF : ISS_ON;
325  }
327  }
328 
331 
332  if (HasDefaultScopeConfig())
333  {
334  LoadScopeConfig();
335  }
336  else
337  {
338  loadConfig(true, "TELESCOPE_INFO");
339  loadConfig(true, "SCOPE_CONFIG_NAME");
340  }
341 
342  if (CanGOTO())
343  controller->ISGetProperties(dev);
344 }
345 
347 {
348  if (isConnected())
349  {
350  controller->mapController("MOTIONDIR", "N/S/W/E Control", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_1");
351  controller->mapController("MOTIONDIRNS", "N/S Control", Controller::CONTROLLER_AXIS, "AXIS_8");
352  controller->mapController("MOTIONDIRWE", "W/E Control", Controller::CONTROLLER_AXIS, "AXIS_7");
353 
354  if (nSlewRate >= 4)
355  {
356  controller->mapController("SLEWPRESET", "Slew Rate", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_2");
357  controller->mapController("SLEWPRESETUP", "Slew Rate Up", Controller::CONTROLLER_BUTTON, "BUTTON_5");
358  controller->mapController("SLEWPRESETDOWN", "Slew Rate Down", Controller::CONTROLLER_BUTTON,
359  "BUTTON_6");
360  }
361  if (CanAbort())
362  controller->mapController("ABORTBUTTON", "Abort", Controller::CONTROLLER_BUTTON, "BUTTON_1");
363  if (CanPark())
364  {
365  controller->mapController("PARKBUTTON", "Park", Controller::CONTROLLER_BUTTON, "BUTTON_2");
366  controller->mapController("UNPARKBUTTON", "UnPark", Controller::CONTROLLER_BUTTON, "BUTTON_3");
367  }
368 
369  // Now we add our telescope specific stuff
370  if (CanGOTO() || CanSync())
373  if (CanAbort())
375 
376  if (HasTrackMode() && TrackModeS != nullptr)
378  if (CanControlTrack())
380  if (HasTrackRate())
382 
383 
384  if (CanGOTO())
385  {
389  if (nSlewRate >= 4)
392  }
393 
394  if (HasTime())
396  if (HasLocation())
398  if (CanPark())
399  {
401  if (parkDataType != PARK_NONE)
402  {
405  }
406  }
407 
408  if (HasPierSide())
410 
411  if (HasPierSideSimulation())
412  {
414  ISState value;
415  if (IUGetConfigSwitch(getDefaultName(), "SIMULATE_PIER_SIDE", "SIMULATE_YES", &value) )
416  setSimulatePierSide(value == ISS_ON);
417  }
418 
419  if (CanTrackSatellite())
420  {
424  }
425 
426  if (HasPECState())
428 
431  }
432  else
433  {
434  if (CanGOTO() || CanSync())
437  if (CanAbort())
439  if (HasTrackMode() && TrackModeS != nullptr)
441  if (HasTrackRate())
443  if (CanControlTrack())
445 
446  if (CanGOTO())
447  {
451  if (nSlewRate >= 4)
454  }
455 
456  if (HasTime())
458  if (HasLocation())
460 
461  if (CanPark())
462  {
464  if (parkDataType != PARK_NONE)
465  {
468  }
469  }
470 
471  if (HasPierSide())
473 
474  if (HasPierSideSimulation())
475  {
477  if (getSimulatePierSide() == true)
479  }
480 
481  if (CanTrackSatellite())
482  {
486  }
487 
488  if (HasPECState())
490 
493  }
494 
495  if (CanGOTO())
496  {
497  controller->updateProperties();
498 
499  auto useJoystick = getSwitch("USEJOYSTICK");
500  if (useJoystick)
501  {
502  if (isConnected())
503  {
504  if (useJoystick[0].getState() == ISS_ON)
505  {
507  loadConfig(true, "MOTION_CONTROL_MODE");
509  loadConfig(true, "LOCK_AXIS");
510  }
511  else
512  {
515  }
516  }
517  else
518  {
521  }
522  }
523  }
524 
525  return true;
526 }
527 
529 {
530  controller->ISSnoopDevice(root);
531 
532  XMLEle *ep = nullptr;
533  const char *propName = findXMLAttValu(root, "name");
534 
535  if (isConnected())
536  {
537  if (HasLocation() && !strcmp(propName, "GEOGRAPHIC_COORD"))
538  {
539  // Only accept IPS_OK state
540  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
541  return false;
542 
543  double longitude = -1, latitude = -1, elevation = -1;
544 
545  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
546  {
547  const char *elemName = findXMLAttValu(ep, "name");
548 
549  if (!strcmp(elemName, "LAT"))
550  latitude = atof(pcdataXMLEle(ep));
551  else if (!strcmp(elemName, "LONG"))
552  longitude = atof(pcdataXMLEle(ep));
553  else if (!strcmp(elemName, "ELEV"))
554  elevation = atof(pcdataXMLEle(ep));
555  }
556 
557  return processLocationInfo(latitude, longitude, elevation);
558  }
559  else if (HasTime() && !strcmp(propName, "TIME_UTC"))
560  {
561  // Only accept IPS_OK state
562  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
563  return false;
564 
565  char utc[MAXINDITSTAMP], offset[MAXINDITSTAMP];
566 
567  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
568  {
569  const char *elemName = findXMLAttValu(ep, "name");
570 
571  if (!strcmp(elemName, "UTC"))
572  strncpy(utc, pcdataXMLEle(ep), MAXINDITSTAMP);
573  else if (!strcmp(elemName, "OFFSET"))
574  strncpy(offset, pcdataXMLEle(ep), MAXINDITSTAMP);
575  }
576 
577  return processTimeInfo(utc, offset);
578  }
579  else if (!strcmp(propName, "DOME_PARK")/* || !strcmp(propName, "DOME_SHUTTER")*/)
580  {
581  // This is handled by Watchdog driver.
582  // Mount shouldn't park due to dome closing in INDI::Telescope
583 #if 0
584  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
585  {
586  // Dome options is dome parks or both and dome is parking.
587  if ((DomeClosedLockT[2].s == ISS_ON || DomeClosedLockT[3].s == ISS_ON) && !IsLocked && !IsParked)
588  {
589  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
590  {
591  const char * elemName = findXMLAttValu(ep, "name");
592  if (( (!strcmp(elemName, "SHUTTER_CLOSE") || !strcmp(elemName, "PARK"))
593  && !strcmp(pcdataXMLEle(ep), "On")))
594  {
596  Park();
597  LOG_INFO("Dome is closing, parking mount...");
598  }
599  }
600  }
601  } // Dome is changing state and Dome options is lock or both. d
602  else
603 #endif
604  if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
605  {
606  bool prevState = IsLocked;
607  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
608  {
609  const char *elemName = findXMLAttValu(ep, "name");
610 
611  if (!IsLocked && (!strcmp(elemName, "PARK")) && !strcmp(pcdataXMLEle(ep), "On"))
612  IsLocked = true;
613  else if (IsLocked && (!strcmp(elemName, "UNPARK")) && !strcmp(pcdataXMLEle(ep), "On"))
614  IsLocked = false;
615  }
616  if (prevState != IsLocked && (DomePolicyS[DOME_LOCKS].s == ISS_ON))
617  LOGF_INFO("Dome status changed. Lock is set to: %s", IsLocked ? "locked" : "unlock");
618  }
619  return true;
620  }
621  }
622 
623  return DefaultDevice::ISSnoopDevice(root);
624 }
625 
626 void Telescope::triggerSnoop(const char *driverName, const char *snoopedProp)
627 {
628  LOGF_DEBUG("Active Snoop, driver: %s, property: %s", driverName, snoopedProp);
629  IDSnoopDevice(driverName, snoopedProp);
630 }
631 
633 {
634  return telescopeConnection;
635 }
636 
637 void Telescope::setTelescopeConnection(const uint8_t &value)
638 {
640 
641  if (value == 0 || (mask & value) == 0)
642  {
643  DEBUGF(Logger::DBG_ERROR, "Invalid connection mode %d", value);
644  return;
645  }
646 
647  telescopeConnection = value;
648 }
649 
651 {
653 
656 
657  // Ensure that we only save valid locations
658  if (HasLocation() && (LocationN[LOCATION_LONGITUDE].value != 0 || LocationN[LOCATION_LATITUDE].value != 0))
660 
661  if (!HasDefaultScopeConfig())
662  {
663  if (ScopeParametersNP.s == IPS_OK)
665  if (ScopeConfigNameTP.s == IPS_OK)
667  }
668 
669  if (CanGOTO())
671 
672  if (SlewRateS != nullptr)
674  if (HasPECState())
676  if (HasTrackMode())
678  if (HasTrackRate())
680 
681  controller->saveConfigItems(fp);
685 
686  return true;
687 }
688 
689 void Telescope::NewRaDec(double ra, double dec)
690 {
691  switch (TrackState)
692  {
693  case SCOPE_PARKED:
694  case SCOPE_IDLE:
695  EqNP.s = IPS_IDLE;
696  break;
697 
698  case SCOPE_SLEWING:
699  case SCOPE_PARKING:
700  EqNP.s = IPS_BUSY;
701  break;
702 
703  case SCOPE_TRACKING:
704  EqNP.s = IPS_OK;
705  break;
706  }
707 
709  {
713  IDSetSwitch(&TrackStateSP, nullptr);
714  }
716  {
720  IDSetSwitch(&TrackStateSP, nullptr);
721  }
722 
723  if (std::abs(EqN[AXIS_RA].value - ra) > EQ_NOTIFY_THRESHOLD ||
724  std::abs(EqN[AXIS_DE].value - dec) > EQ_NOTIFY_THRESHOLD ||
725  EqNP.s != lastEqState)
726  {
727  EqN[AXIS_RA].value = ra;
728  EqN[AXIS_DE].value = dec;
729  lastEqState = EqNP.s;
730  IDSetNumber(&EqNP, nullptr);
731  }
732 }
733 
734 bool Telescope::Sync(double ra, double dec)
735 {
736  INDI_UNUSED(ra);
737  INDI_UNUSED(dec);
738  // if we get here, our mount doesn't support sync
739  DEBUG(Logger::DBG_ERROR, "Telescope does not support Sync.");
740  return false;
741 }
742 
744 {
745  INDI_UNUSED(dir);
746  INDI_UNUSED(command);
747  DEBUG(Logger::DBG_ERROR, "Telescope does not support North/South motion.");
750  IDSetSwitch(&MovementNSSP, nullptr);
751  return false;
752 }
753 
755 {
756  INDI_UNUSED(dir);
757  INDI_UNUSED(command);
758  DEBUG(Logger::DBG_ERROR, "Telescope does not support West/East motion.");
761  IDSetSwitch(&MovementWESP, nullptr);
762  return false;
763 }
764 
765 /**************************************************************************************
766 ** Process Text properties
767 ***************************************************************************************/
768 bool Telescope::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
769 {
770  // first check if it's for our device
771  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
772  {
773  if (!strcmp(name, TimeTP.name))
774  {
775  int utcindex = IUFindIndex("UTC", names, n);
776  int offsetindex = IUFindIndex("OFFSET", names, n);
777 
778  return processTimeInfo(texts[utcindex], texts[offsetindex]);
779  }
780 
781  if (!strcmp(name, ActiveDeviceTP.name))
782  {
784  IUUpdateText(&ActiveDeviceTP, texts, names, n);
785  // Update client display
786  IDSetText(&ActiveDeviceTP, nullptr);
787 
788  IDSnoopDevice(ActiveDeviceT[0].text, "GEOGRAPHIC_COORD");
789  IDSnoopDevice(ActiveDeviceT[0].text, "TIME_UTC");
790 
791  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_PARK");
792  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_SHUTTER");
793  return true;
794  }
795 
796  if (name && std::string(name) == "SCOPE_CONFIG_NAME")
797  {
799  IUUpdateText(&ScopeConfigNameTP, texts, names, n);
800  IDSetText(&ScopeConfigNameTP, nullptr);
802  return true;
803  }
804  }
805 
806  controller->ISNewText(dev, name, texts, names, n);
807 
808  return DefaultDevice::ISNewText(dev, name, texts, names, n);
809 }
810 
811 /**************************************************************************************
812 **
813 ***************************************************************************************/
814 bool Telescope::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
815 {
816  // first check if it's for our device
817  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
818  {
820  // Goto & Sync for Equatorial Coords
822  if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
823  {
824  // this is for us, and it is a goto
825  bool rc = false;
826  double ra = -1;
827  double dec = -100;
828 
829  for (int x = 0; x < n; x++)
830  {
831  INumber *eqp = IUFindNumber(&EqNP, names[x]);
832  if (eqp == &EqN[AXIS_RA])
833  {
834  ra = values[x];
835  }
836  else if (eqp == &EqN[AXIS_DE])
837  {
838  dec = values[x];
839  }
840  }
841  if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
842  {
843  // Check if it is already parked.
844  if (CanPark())
845  {
846  if (isParked())
847  {
849  "Please unpark the mount before issuing any motion/sync commands.");
850  EqNP.s = lastEqState = IPS_IDLE;
851  IDSetNumber(&EqNP, nullptr);
852  return false;
853  }
854  }
855 
856  // Check if it can sync
857  if (CanSync())
858  {
859  ISwitch *sw;
860  sw = IUFindSwitch(&CoordSP, "SYNC");
861  if ((sw != nullptr) && (sw->s == ISS_ON))
862  {
863  rc = Sync(ra, dec);
864  if (rc)
865  EqNP.s = lastEqState = IPS_OK;
866  else
867  EqNP.s = lastEqState = IPS_ALERT;
868  IDSetNumber(&EqNP, nullptr);
869  return rc;
870  }
871  }
872 
873  // Remember Track State
875  // Issue GOTO
876  rc = Goto(ra, dec);
877  if (rc)
878  {
879  EqNP.s = lastEqState = IPS_BUSY;
880  // Now fill in target co-ords, so domes can start turning
881  TargetN[AXIS_RA].value = ra;
882  TargetN[AXIS_DE].value = dec;
883  IDSetNumber(&TargetNP, nullptr);
884  }
885  else
886  {
887  EqNP.s = lastEqState = IPS_ALERT;
888  }
889  IDSetNumber(&EqNP, nullptr);
890  }
891  return rc;
892  }
893 
895  // Geographic Coords
897  if (strcmp(name, "GEOGRAPHIC_COORD") == 0)
898  {
899  int latindex = IUFindIndex("LAT", names, n);
900  int longindex = IUFindIndex("LONG", names, n);
901  int elevationindex = IUFindIndex("ELEV", names, n);
902 
903  if (latindex == -1 || longindex == -1 || elevationindex == -1)
904  {
906  IDSetNumber(&LocationNP, "Location data missing or corrupted.");
907  }
908 
909  double targetLat = values[latindex];
910  double targetLong = values[longindex];
911  double targetElev = values[elevationindex];
912 
913  return processLocationInfo(targetLat, targetLong, targetElev);
914  }
915 
917  // Telescope Info
919  if (strcmp(name, "TELESCOPE_INFO") == 0)
920  {
922 
923  IUUpdateNumber(&ScopeParametersNP, values, names, n);
924  IDSetNumber(&ScopeParametersNP, nullptr);
926  return true;
927  }
928 
930  // Park Position
932  if (strcmp(name, ParkPositionNP.name) == 0)
933  {
934  double axis1 = std::numeric_limits<double>::quiet_NaN(), axis2 = std::numeric_limits<double>::quiet_NaN();
935  for (int x = 0; x < n; x++)
936  {
937  INumber *parkPosAxis = IUFindNumber(&ParkPositionNP, names[x]);
938  if (parkPosAxis == &ParkPositionN[AXIS_RA])
939  {
940  axis1 = values[x];
941  }
942  else if (parkPosAxis == &ParkPositionN[AXIS_DE])
943  {
944  axis2 = values[x];
945  }
946  }
947 
948  if (std::isnan(axis1) == false && std::isnan(axis2) == false)
949  {
950  bool rc = false;
951 
952  rc = SetParkPosition(axis1, axis2);
953 
954  if (rc)
955  {
956  IUUpdateNumber(&ParkPositionNP, values, names, n);
957  Axis1ParkPosition = ParkPositionN[AXIS_RA].value;
958  Axis2ParkPosition = ParkPositionN[AXIS_DE].value;
959  }
960 
961  ParkPositionNP.s = rc ? IPS_OK : IPS_ALERT;
962  }
963  else
965 
966  IDSetNumber(&ParkPositionNP, nullptr);
967  return true;
968  }
969 
971  // Track Rate
973  if (strcmp(name, TrackRateNP.name) == 0)
974  {
975  double preAxis1 = TrackRateN[AXIS_RA].value, preAxis2 = TrackRateN[AXIS_DE].value;
976  bool rc = (IUUpdateNumber(&TrackRateNP, values, names, n) == 0);
977 
978  if (!rc)
979  {
981  IDSetNumber(&TrackRateNP, nullptr);
982  return false;
983  }
984 
985  if (TrackState == SCOPE_TRACKING && !strcmp(IUFindOnSwitch(&TrackModeSP)->name, "TRACK_CUSTOM"))
986  {
987  // Check that we do not abruplty change positive tracking rates to negative ones.
988  // tracking must be stopped first.
989  // Give warning is tracking sign would cause a reverse in direction
990  if ( (preAxis1 * TrackRateN[AXIS_RA].value < 0) || (preAxis2 * TrackRateN[AXIS_DE].value < 0) )
991  {
992  LOG_ERROR("Cannot reverse tracking while tracking is engaged. Disengage tracking then try again.");
993  return false;
994  }
995 
996  // All is fine, ask mount to change tracking rate
997  rc = SetTrackRate(TrackRateN[AXIS_RA].value, TrackRateN[AXIS_DE].value);
998 
999  if (!rc)
1000  {
1001  TrackRateN[AXIS_RA].value = preAxis1;
1002  TrackRateN[AXIS_DE].value = preAxis2;
1003  }
1004  }
1005 
1006  // If we are already tracking but tracking mode is NOT custom
1007  // We just inform the user that it must be set to custom for these values to take
1008  // effect.
1009  if (TrackState == SCOPE_TRACKING && strcmp(IUFindOnSwitch(&TrackModeSP)->name, "TRACK_CUSTOM"))
1010  {
1011  LOG_INFO("Custom tracking rates set. Tracking mode must be set to Custom for these rates to take effect.");
1012  }
1013 
1014  // If mount is NOT tracking, we simply accept whatever valid values for use when mount tracking is engaged.
1015  TrackRateNP.s = rc ? IPS_OK : IPS_ALERT;
1016  IDSetNumber(&TrackRateNP, nullptr);
1017  return true;
1018  }
1019  }
1020 
1021  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
1022 }
1023 
1024 /**************************************************************************************
1025 **
1026 ***************************************************************************************/
1027 bool Telescope::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1028 {
1029  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1030  {
1031  // This one is for us
1032  if (!strcmp(name, CoordSP.name))
1033  {
1034  // client is telling us what to do with co-ordinate requests
1035  CoordSP.s = IPS_OK;
1036  IUUpdateSwitch(&CoordSP, states, names, n);
1037  // Update client display
1038  IDSetSwitch(&CoordSP, nullptr);
1039  return true;
1040  }
1041 
1043  // Slew Rate
1045  if (!strcmp(name, SlewRateSP.name))
1046  {
1047  int preIndex = IUFindOnSwitchIndex(&SlewRateSP);
1048  IUUpdateSwitch(&SlewRateSP, states, names, n);
1049  int nowIndex = IUFindOnSwitchIndex(&SlewRateSP);
1050  if (SetSlewRate(nowIndex) == false)
1051  {
1053  SlewRateS[preIndex].s = ISS_ON;
1055  }
1056  else
1057  SlewRateSP.s = IPS_OK;
1058  IDSetSwitch(&SlewRateSP, nullptr);
1059  return true;
1060  }
1061 
1063  // Parking
1065  if (!strcmp(name, ParkSP.name))
1066  {
1067  if (TrackState == SCOPE_PARKING)
1068  {
1070  ParkSP.s = IPS_ALERT;
1071  Abort();
1072  LOG_INFO("Parking/Unparking aborted.");
1073  IDSetSwitch(&ParkSP, nullptr);
1074  return true;
1075  }
1076 
1077  int preIndex = IUFindOnSwitchIndex(&ParkSP);
1078  IUUpdateSwitch(&ParkSP, states, names, n);
1079 
1080  bool toPark = (ParkS[0].s == ISS_ON);
1081 
1082  if (toPark == false && TrackState != SCOPE_PARKED)
1083  {
1085  ParkS[1].s = ISS_ON;
1086  ParkSP.s = IPS_IDLE;
1087  LOG_INFO("Telescope already unparked.");
1088  IsParked = false;
1089  IDSetSwitch(&ParkSP, nullptr);
1090  return true;
1091  }
1092 
1093  if (toPark == false && isLocked())
1094  {
1096  ParkS[0].s = ISS_ON;
1097  ParkSP.s = IPS_ALERT;
1098  LOG_WARN("Cannot unpark mount when dome is locking. See: Dome Policy in options tab.");
1099  IsParked = true;
1100  IDSetSwitch(&ParkSP, nullptr);
1101  return true;
1102  }
1103 
1104  if (toPark && TrackState == SCOPE_PARKED)
1105  {
1107  ParkS[0].s = ISS_ON;
1108  ParkSP.s = IPS_IDLE;
1109  LOG_INFO("Telescope already parked.");
1110  IDSetSwitch(&ParkSP, nullptr);
1111  return true;
1112  }
1113 
1115 
1117  bool rc = toPark ? Park() : UnPark();
1118  if (rc)
1119  {
1120  if (TrackState == SCOPE_PARKING)
1121  {
1122  ParkS[0].s = toPark ? ISS_ON : ISS_OFF;
1123  ParkS[1].s = toPark ? ISS_OFF : ISS_ON;
1124  ParkSP.s = IPS_BUSY;
1125  }
1126  else
1127  {
1128  ParkS[0].s = toPark ? ISS_ON : ISS_OFF;
1129  ParkS[1].s = toPark ? ISS_OFF : ISS_ON;
1130  ParkSP.s = IPS_OK;
1131  }
1132  }
1133  else
1134  {
1135  ParkS[preIndex].s = ISS_ON;
1136  ParkSP.s = IPS_ALERT;
1137  }
1138 
1139  IDSetSwitch(&ParkSP, nullptr);
1140  return true;
1141  }
1142 
1144  // NS Motion
1146  if (!strcmp(name, MovementNSSP.name))
1147  {
1148  // Check if it is already parked.
1149  if (CanPark())
1150  {
1151  if (isParked())
1152  {
1154  "Please unpark the mount before issuing any motion/sync commands.");
1156  IDSetSwitch(&MovementNSSP, nullptr);
1157  return false;
1158  }
1159  }
1160 
1161  IUUpdateSwitch(&MovementNSSP, states, names, n);
1162 
1163  int current_motion = IUFindOnSwitchIndex(&MovementNSSP);
1164 
1165  // if same move requested, return
1166  if (MovementNSSP.s == IPS_BUSY && current_motion == last_ns_motion)
1167  return true;
1168 
1169  // Time to stop motion
1170  if (current_motion == -1 || (last_ns_motion != -1 && current_motion != last_ns_motion))
1171  {
1172  auto targetDirection = last_ns_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1173  if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1174  targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1175 
1176  if (MoveNS(targetDirection, MOTION_STOP))
1177  {
1180  last_ns_motion = -1;
1181  // Update Target when stopped so that domes can track
1182  TargetN[AXIS_RA].value = EqN[AXIS_RA].value;
1183  TargetN[AXIS_DE].value = EqN[AXIS_DE].value;
1184  IDSetNumber(&TargetNP, nullptr);
1185  }
1186  else
1188  }
1189  else
1190  {
1193 
1194  auto targetDirection = current_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1195  if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1196  targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1197 
1198  if (MoveNS(targetDirection, MOTION_START))
1199  {
1201  last_ns_motion = targetDirection;
1202  }
1203  else
1204  {
1207  last_ns_motion = -1;
1208  }
1209  }
1210 
1211  IDSetSwitch(&MovementNSSP, nullptr);
1212 
1213  return true;
1214  }
1215 
1217  // WE Motion
1219  if (!strcmp(name, MovementWESP.name))
1220  {
1221  // Check if it is already parked.
1222  if (CanPark())
1223  {
1224  if (isParked())
1225  {
1227  "Please unpark the mount before issuing any motion/sync commands.");
1229  IDSetSwitch(&MovementWESP, nullptr);
1230  return false;
1231  }
1232  }
1233 
1234  IUUpdateSwitch(&MovementWESP, states, names, n);
1235 
1236  int current_motion = IUFindOnSwitchIndex(&MovementWESP);
1237 
1238  // if same move requested, return
1239  if (MovementWESP.s == IPS_BUSY && current_motion == last_we_motion)
1240  return true;
1241 
1242  // Time to stop motion
1243  if (current_motion == -1 || (last_we_motion != -1 && current_motion != last_we_motion))
1244  {
1245  auto targetDirection = last_we_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1246  if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1247  targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1248  if (MoveWE(targetDirection, MOTION_STOP))
1249  {
1252  last_we_motion = -1;
1253  // Update Target when stopped so that domes can track
1254  TargetN[AXIS_RA].value = EqN[AXIS_RA].value;
1255  TargetN[AXIS_DE].value = EqN[AXIS_DE].value;
1256  IDSetNumber(&TargetNP, nullptr);
1257  }
1258  else
1260  }
1261  else
1262  {
1265 
1266  auto targetDirection = current_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1267  if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1268  targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1269 
1270  if (MoveWE(targetDirection, MOTION_START))
1271  {
1273  last_we_motion = targetDirection;
1274  }
1275  else
1276  {
1279  last_we_motion = -1;
1280  }
1281  }
1282 
1283  IDSetSwitch(&MovementWESP, nullptr);
1284 
1285  return true;
1286  }
1287 
1289  // WE or NS Reverse Motion
1291  if (ReverseMovementSP.isNameMatch(name))
1292  {
1293  ReverseMovementSP.update(states, names, n);
1297  return true;
1298  }
1299 
1301  // Abort Motion
1303  if (!strcmp(name, AbortSP.name))
1304  {
1306 
1307  if (Abort())
1308  {
1309  AbortSP.s = IPS_OK;
1310 
1311  if (ParkSP.s == IPS_BUSY)
1312  {
1314  ParkSP.s = IPS_ALERT;
1315  IDSetSwitch(&ParkSP, nullptr);
1316 
1317  LOG_INFO("Parking aborted.");
1318  }
1319  if (EqNP.s == IPS_BUSY)
1320  {
1321  EqNP.s = lastEqState = IPS_IDLE;
1322  IDSetNumber(&EqNP, nullptr);
1323  LOG_INFO("Slew/Track aborted.");
1324  }
1325  if (MovementWESP.s == IPS_BUSY)
1326  {
1329  IDSetSwitch(&MovementWESP, nullptr);
1330  }
1331  if (MovementNSSP.s == IPS_BUSY)
1332  {
1335  IDSetSwitch(&MovementNSSP, nullptr);
1336  }
1337 
1339 
1340  // JM 2017-07-28: Abort shouldn't affect tracking state. It should affect motion and that's it.
1341  //if (TrackState != SCOPE_PARKED)
1342  //TrackState = SCOPE_IDLE;
1343  // For Idle, Tracking, Parked state, we do not change its status, it should remain as is.
1344  // For Slewing & Parking, state should go back to last rememberd state.
1346  {
1348  }
1349  }
1350  else
1351  AbortSP.s = IPS_ALERT;
1352 
1353  IDSetSwitch(&AbortSP, nullptr);
1354 
1355  return true;
1356  }
1357 
1359  // Track Mode
1361  if (!strcmp(name, TrackModeSP.name))
1362  {
1363  int prevIndex = IUFindOnSwitchIndex(&TrackModeSP);
1364  IUUpdateSwitch(&TrackModeSP, states, names, n);
1365  int currIndex = IUFindOnSwitchIndex(&TrackModeSP);
1366  // If same as previous index, or if scope is already idle, then just update switch and return. No commands are sent to the mount.
1367  if (prevIndex == currIndex || TrackState == SCOPE_IDLE)
1368  {
1369  TrackModeSP.s = IPS_OK;
1370  IDSetSwitch(&TrackModeSP, nullptr);
1371  return true;
1372  }
1373 
1374  if (TrackState == SCOPE_PARKED)
1375  {
1376  DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before changing track mode.");
1377  return false;
1378  }
1379 
1380  bool rc = SetTrackMode(currIndex);
1381  if (rc)
1382  TrackModeSP.s = IPS_OK;
1383  else
1384  {
1386  TrackModeS[prevIndex].s = ISS_ON;
1388  }
1389  IDSetSwitch(&TrackModeSP, nullptr);
1390  return false;
1391  }
1392 
1394  // Track State
1396  if (!strcmp(name, TrackStateSP.name))
1397  {
1398  int previousState = IUFindOnSwitchIndex(&TrackStateSP);
1399  IUUpdateSwitch(&TrackStateSP, states, names, n);
1400  int targetState = IUFindOnSwitchIndex(&TrackStateSP);
1401 
1402  if (previousState == targetState)
1403  {
1404  IDSetSwitch(&TrackStateSP, nullptr);
1405  return true;
1406  }
1407 
1408  if (TrackState == SCOPE_PARKED)
1409  {
1410  DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before tracking.");
1411  return false;
1412  }
1413 
1414  bool rc = SetTrackEnabled((targetState == TRACK_ON) ? true : false);
1415 
1416  if (rc)
1417  {
1418  TrackState = (targetState == TRACK_ON) ? SCOPE_TRACKING : SCOPE_IDLE;
1419 
1420  TrackStateSP.s = (targetState == TRACK_ON) ? IPS_BUSY : IPS_IDLE;
1421 
1422  TrackStateS[TRACK_ON].s = (targetState == TRACK_ON) ? ISS_ON : ISS_OFF;
1423  TrackStateS[TRACK_OFF].s = (targetState == TRACK_ON) ? ISS_OFF : ISS_ON;
1424  }
1425  else
1426  {
1429  TrackStateS[previousState].s = ISS_ON;
1430  }
1431 
1432  IDSetSwitch(&TrackStateSP, nullptr);
1433  return true;
1434  }
1435 
1437  // Park Options
1439  if (!strcmp(name, ParkOptionSP.name))
1440  {
1441  IUUpdateSwitch(&ParkOptionSP, states, names, n);
1442  int index = IUFindOnSwitchIndex(&ParkOptionSP);
1443  if (index == -1)
1444  return false;
1445 
1447 
1448  bool rc = false;
1449 
1451  MovementWESP.s == IPS_BUSY)
1452  {
1453  LOG_INFO("Can not change park position while slewing or already parked...");
1455  IDSetSwitch(&ParkOptionSP, nullptr);
1456  return false;
1457  }
1458 
1459  switch (index)
1460  {
1461  case PARK_CURRENT:
1462  rc = SetCurrentPark();
1463  break;
1464  case PARK_DEFAULT:
1465  rc = SetDefaultPark();
1466  break;
1467  case PARK_WRITE_DATA:
1468  rc = WriteParkData();
1469  if (rc)
1470  LOG_INFO("Saved Park Status/Position.");
1471  else
1472  DEBUG(Logger::DBG_WARNING, "Can not save Park Status/Position.");
1473  break;
1474  case PARK_PURGE_DATA:
1475  rc = PurgeParkData();
1476  if (rc)
1477  LOG_INFO("Park data purged.");
1478  else
1479  DEBUG(Logger::DBG_WARNING, "Can not purge Park Status/Position.");
1480  break;
1481  }
1482 
1483  ParkOptionSP.s = rc ? IPS_OK : IPS_ALERT;
1484  IDSetSwitch(&ParkOptionSP, nullptr);
1485 
1486  return true;
1487  }
1488 
1490  // Parking Dome Policy
1492  if (!strcmp(name, DomePolicySP.name))
1493  {
1494  IUUpdateSwitch(&DomePolicySP, states, names, n);
1495  if (DomePolicyS[DOME_IGNORED].s == ISS_ON)
1496  LOG_INFO("Dome Policy set to: Dome ignored. Mount can park or unpark regardless of dome parking state.");
1497  else
1498  LOG_WARN("Dome Policy set to: Dome locks. This prevents the mount from unparking when dome is parked.");
1499 #if 0
1500  else if (!strcmp(names[0], DomeClosedLockT[2].name))
1501  LOG_INFO("Warning: Dome parking policy set to: Dome parks. This tells "
1502  "scope to park if dome is parking. This will disable the locking "
1503  "for dome parking, EVEN IF MOUNT PARKING FAILS");
1504  else if (!strcmp(names[0], DomeClosedLockT[3].name))
1505  LOG_INFO("Warning: Dome parking policy set to: Both. This disallows the "
1506  "scope from unparking when dome is parked, and tells scope to "
1507  "park if dome is parking. This will disable the locking for dome "
1508  "parking, EVEN IF MOUNT PARKING FAILS.");
1509 #endif
1510  DomePolicySP.s = IPS_OK;
1511  IDSetSwitch(&DomePolicySP, nullptr);
1512  triggerSnoop(ActiveDeviceT[1].text, "DOME_PARK");
1513  return true;
1514  }
1515 
1517  // Simulate Pier Side
1518  // This ia a major change to the design of the simulated scope, it might not handle changes on the fly
1520  if (!strcmp(name, SimulatePierSideSP.name))
1521  {
1522  IUUpdateSwitch(&SimulatePierSideSP, states, names, n);
1524  if (index == -1)
1525  {
1527  LOG_INFO("Cannot determine whether pier side simulation should be switched on or off.");
1528  IDSetSwitch(&SimulatePierSideSP, nullptr);
1529  return false;
1530  }
1531 
1532  bool pierSideEnabled = index == 0;
1533 
1534  LOGF_INFO("Simulating Pier Side %s.", (pierSideEnabled ? "enabled" : "disabled"));
1535 
1536  setSimulatePierSide(pierSideEnabled);
1537  if (pierSideEnabled)
1538  {
1539  // set the pier side from the current Ra
1540  // assumes we haven't tracked across the meridian
1542  }
1543  return true;
1544  }
1545 
1547  // Joystick Motion Control Mode
1549  if (!strcmp(name, MotionControlModeTP.name))
1550  {
1551  IUUpdateSwitch(&MotionControlModeTP, states, names, n);
1553  IDSetSwitch(&MotionControlModeTP, nullptr);
1555  LOG_INFO("Motion control is set to 4-way joystick.");
1557  LOG_INFO("Motion control is set to 2 separate axes.");
1558  else
1559  DEBUGF(Logger::DBG_WARNING, "Motion control is set to unknown value %d!", n);
1560  return true;
1561  }
1562 
1564  // Joystick Lock Axis
1566  if (!strcmp(name, LockAxisSP.name))
1567  {
1568  IUUpdateSwitch(&LockAxisSP, states, names, n);
1569  LockAxisSP.s = IPS_OK;
1570  IDSetSwitch(&LockAxisSP, nullptr);
1571  if (LockAxisS[AXIS_RA].s == ISS_ON)
1572  LOG_INFO("Joystick motion is locked to West/East axis only.");
1573  else if (LockAxisS[AXIS_DE].s == ISS_ON)
1574  LOG_INFO("Joystick motion is locked to North/South axis only.");
1575  else
1576  LOG_INFO("Joystick motion is unlocked.");
1577  return true;
1578  }
1579 
1581  // Scope Apply Config
1583  if (name && std::string(name) == "APPLY_SCOPE_CONFIG")
1584  {
1585  IUUpdateSwitch(&ScopeConfigsSP, states, names, n);
1586  bool rc = LoadScopeConfig();
1587  ScopeConfigsSP.s = (rc ? IPS_OK : IPS_ALERT);
1588  IDSetSwitch(&ScopeConfigsSP, nullptr);
1589  return true;
1590  }
1591  }
1592 
1593  bool rc = controller->ISNewSwitch(dev, name, states, names, n);
1594  if (rc)
1595  {
1596  auto useJoystick = getSwitch("USEJOYSTICK");
1597  if (useJoystick && useJoystick[0].getState() == ISS_ON)
1598  {
1601  }
1602  else
1603  {
1606  }
1607 
1608  }
1609 
1610  // Nobody has claimed this, so, ignore it
1611  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1612 }
1613 
1615 {
1616  if (telescopeConnection > 0)
1617  {
1620  else if (getActiveConnection() == tcpConnection)
1622  }
1623 
1624  return Handshake();
1625 }
1626 
1628 {
1629  /* Test connection */
1630  return ReadScopeStatus();
1631 }
1632 
1634 {
1635  if (isConnected())
1636  {
1637  bool rc;
1638 
1639  rc = ReadScopeStatus();
1640 
1641  if (!rc)
1642  {
1643  // read was not good
1644  EqNP.s = lastEqState = IPS_ALERT;
1645  IDSetNumber(&EqNP, nullptr);
1646  }
1647 
1649  }
1650 }
1651 
1652 bool Telescope::Goto(double ra, double dec)
1653 {
1654  INDI_UNUSED(ra);
1655  INDI_UNUSED(dec);
1656 
1657  DEBUG(Logger::DBG_WARNING, "GOTO is not supported.");
1658  return false;
1659 }
1660 
1662 {
1663  DEBUG(Logger::DBG_WARNING, "Abort is not supported.");
1664  return false;
1665 }
1666 
1668 {
1669  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1670  return false;
1671 }
1672 
1674 {
1675  DEBUG(Logger::DBG_WARNING, "UnParking is not supported.");
1676  return false;
1677 }
1678 
1679 bool Telescope::SetTrackMode(uint8_t mode)
1680 {
1681  INDI_UNUSED(mode);
1682  DEBUG(Logger::DBG_WARNING, "Tracking mode is not supported.");
1683  return false;
1684 }
1685 
1686 bool Telescope::SetTrackRate(double raRate, double deRate)
1687 {
1688  INDI_UNUSED(raRate);
1689  INDI_UNUSED(deRate);
1690  DEBUG(Logger::DBG_WARNING, "Custom tracking rates is not supported.");
1691  return false;
1692 }
1693 
1694 bool Telescope::SetTrackEnabled(bool enabled)
1695 {
1696  INDI_UNUSED(enabled);
1697  DEBUG(Logger::DBG_WARNING, "Tracking state is not supported.");
1698  return false;
1699 }
1700 
1701 int Telescope::AddTrackMode(const char *name, const char *label, bool isDefault)
1702 {
1703  TrackModeS = (TrackModeS == nullptr) ? static_cast<ISwitch *>(malloc(sizeof(ISwitch))) :
1704  static_cast<ISwitch *>(realloc(TrackModeS, (TrackModeSP.nsp + 1) * sizeof(ISwitch)));
1705 
1706  IUFillSwitch(&TrackModeS[TrackModeSP.nsp], name, label, isDefault ? ISS_ON : ISS_OFF);
1707 
1709  TrackModeSP.nsp++;
1710 
1711  return (TrackModeSP.nsp - 1);
1712 }
1713 
1715 {
1716  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1717  return false;
1718 }
1719 
1721 {
1722  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1723  return false;
1724 }
1725 
1726 bool Telescope::processTimeInfo(const char *utc, const char *offset)
1727 {
1728  struct ln_date utc_date;
1729  double utc_offset = 0;
1730 
1731  if (extractISOTime(utc, &utc_date) == -1)
1732  {
1733  TimeTP.s = IPS_ALERT;
1734  IDSetText(&TimeTP, "Date/Time is invalid: %s.", utc);
1735  return false;
1736  }
1737 
1738  utc_offset = atof(offset);
1739 
1740  if (updateTime(&utc_date, utc_offset))
1741  {
1742  IUSaveText(&TimeT[0], utc);
1743  IUSaveText(&TimeT[1], offset);
1744  TimeTP.s = IPS_OK;
1745  IDSetText(&TimeTP, nullptr);
1746  return true;
1747  }
1748  else
1749  {
1750  TimeTP.s = IPS_ALERT;
1751  IDSetText(&TimeTP, nullptr);
1752  return false;
1753  }
1754 }
1755 
1756 bool Telescope::processLocationInfo(double latitude, double longitude, double elevation)
1757 {
1758  // Do not update if not necessary
1759  // JM 2021-05-26: This can sometimes be problematic. Let child driver deals with duplicate requests.
1760 #if 0
1761  if (latitude == LocationN[LOCATION_LATITUDE].value && longitude == LocationN[LOCATION_LONGITUDE].value &&
1762  elevation == LocationN[LOCATION_ELEVATION].value)
1763  {
1764  LocationNP.s = IPS_OK;
1765  IDSetNumber(&LocationNP, nullptr);
1766  return true;
1767  }
1768  else
1769 #endif
1770  if (latitude == 0 && longitude == 0)
1771  {
1772  LOG_DEBUG("Silently ignoring invalid latitude and longitude.");
1773  LocationNP.s = IPS_IDLE;
1774  IDSetNumber(&LocationNP, nullptr);
1775  return false;
1776  }
1777 
1778  if (updateLocation(latitude, longitude, elevation))
1779  {
1780  LocationNP.s = IPS_OK;
1781  LocationN[LOCATION_LATITUDE].value = latitude;
1782  LocationN[LOCATION_LONGITUDE].value = longitude;
1783  LocationN[LOCATION_ELEVATION].value = elevation;
1784  // Update client display
1785  IDSetNumber(&LocationNP, nullptr);
1786 
1787  // Always save geographic coord config immediately.
1788  saveConfig(true, "GEOGRAPHIC_COORD");
1789 
1790  updateObserverLocation(latitude, longitude, elevation);
1791 
1792  return true;
1793  }
1794  else
1795  {
1797  // Update client display
1798  IDSetNumber(&LocationNP, nullptr);
1799 
1800  return false;
1801  }
1802 }
1803 
1804 bool Telescope::updateTime(ln_date *utc, double utc_offset)
1805 {
1806  INDI_UNUSED(utc);
1807  INDI_UNUSED(utc_offset);
1808  return true;
1809 }
1810 
1811 bool Telescope::updateLocation(double latitude, double longitude, double elevation)
1812 {
1813  INDI_UNUSED(latitude);
1814  INDI_UNUSED(longitude);
1815  INDI_UNUSED(elevation);
1816  return true;
1817 }
1818 
1819 void Telescope::updateObserverLocation(double latitude, double longitude, double elevation)
1820 {
1821  m_Location.longitude = longitude;
1822  m_Location.latitude = latitude;
1823  m_Location.elevation = elevation;
1824  char lat_str[MAXINDIFORMAT] = {0}, lng_str[MAXINDIFORMAT] = {0};
1825 
1826  // Make display longitude to be in the standard 0 to +180 East, and 0 to -180 West.
1827  // No need to confuse new users with INDI format.
1828  double display_longitude = longitude > 180 ? longitude - 360 : longitude;
1829  fs_sexa(lat_str, m_Location.latitude, 2, 36000);
1830  fs_sexa(lng_str, display_longitude, 2, 36000);
1831  // Choose WGS 84, also known as EPSG:4326 for latitude/longitude ordering
1832  LOGF_INFO("Observer location updated: Latitude %.12s (%.2f) Longitude %.12s (%.2f)", lat_str, m_Location.latitude, lng_str,
1833  display_longitude);
1834 }
1835 
1836 bool Telescope::SetParkPosition(double Axis1Value, double Axis2Value)
1837 {
1838  INDI_UNUSED(Axis1Value);
1839  INDI_UNUSED(Axis2Value);
1840  return true;
1841 }
1842 
1843 void Telescope::SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
1844 {
1845  capability = cap;
1846  nSlewRate = slewRateCount;
1847 
1848  // If both GOTO and SYNC are supported
1849  if (CanGOTO() && CanSync())
1850  IUFillSwitchVector(&CoordSP, CoordS, 3, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1851  ISR_1OFMANY, 60, IPS_IDLE);
1852  // If ONLY GOTO is supported
1853  else if (CanGOTO())
1854  IUFillSwitchVector(&CoordSP, CoordS, 2, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1855  ISR_1OFMANY, 60, IPS_IDLE);
1856  // If ONLY SYNC is supported
1857  else if (CanSync())
1858  {
1859  IUFillSwitch(&CoordS[0], "SYNC", "Sync", ISS_ON);
1860  IUFillSwitchVector(&CoordSP, CoordS, 1, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1861  ISR_1OFMANY, 60, IPS_IDLE);
1862  }
1863 
1864  if (nSlewRate >= 4)
1865  {
1866  free(SlewRateS);
1867  SlewRateS = static_cast<ISwitch *>(malloc(sizeof(ISwitch) * nSlewRate));
1868  //int step = nSlewRate / 4;
1869  for (int i = 0; i < nSlewRate; i++)
1870  {
1871  char name[4];
1872  snprintf(name, 4, "%dx", i + 1);
1873  IUFillSwitch(SlewRateS + i, name, name, ISS_OFF);
1874  }
1875 
1876  // strncpy((SlewRateS + (step * 0))->name, "SLEW_GUIDE", MAXINDINAME);
1877  // strncpy((SlewRateS + (step * 1))->name, "SLEW_CENTERING", MAXINDINAME);
1878  // strncpy((SlewRateS + (step * 2))->name, "SLEW_FIND", MAXINDINAME);
1879  // strncpy((SlewRateS + (nSlewRate - 1))->name, "SLEW_MAX", MAXINDINAME);
1880 
1881  // If number of slew rate is EXACTLY 4, then let's use common labels
1882  if (nSlewRate == 4)
1883  {
1884  strncpy((SlewRateS + (0))->label, "Guide", MAXINDILABEL);
1885  strncpy((SlewRateS + (1))->label, "Centering", MAXINDILABEL);
1886  strncpy((SlewRateS + (2))->label, "Find", MAXINDILABEL);
1887  strncpy((SlewRateS + (3))->label, "Max", MAXINDILABEL);
1888  }
1889 
1890  // By Default we set current Slew Rate to 0.5 of max
1891  (SlewRateS + (nSlewRate / 2))->s = ISS_ON;
1892 
1893  IUFillSwitchVector(&SlewRateSP, SlewRateS, nSlewRate, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
1895  }
1896 }
1897 
1899 {
1900  parkDataType = type;
1901 
1902  if (parkDataType != PARK_NONE)
1903  {
1904  switch (parkDataType)
1905  {
1906  case PARK_RA_DEC:
1907  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
1908  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1909  break;
1910 
1911  case PARK_HA_DEC:
1912  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_HA", "HA (hh:mm:ss)", "%010.6m", -12, 12, 0, 0);
1913  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1914  break;
1915 
1916  case PARK_AZ_ALT:
1917  IUFillNumber(&ParkPositionN[AXIS_AZ], "PARK_AZ", "AZ D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
1918  IUFillNumber(&ParkPositionN[AXIS_ALT], "PARK_ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
1919  break;
1920 
1921  case PARK_RA_DEC_ENCODER:
1922  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_RA", "RA Encoder", "%.0f", 0, 16777215, 1, 0);
1923  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC Encoder", "%.0f", 0, 16777215, 1, 0);
1924  break;
1925 
1926  case PARK_AZ_ALT_ENCODER:
1927  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_AZ", "AZ Encoder", "%.0f", 0, 16777215, 1, 0);
1928  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_ALT", "ALT Encoder", "%.0f", 0, 16777215, 1, 0);
1929  break;
1930 
1931  default:
1932  break;
1933  }
1934 
1935  IUFillNumberVector(&ParkPositionNP, ParkPositionN, 2, getDeviceName(), "TELESCOPE_PARK_POSITION",
1936  "Park Position", SITE_TAB, IP_RW, 60, IPS_IDLE);
1937  }
1938 }
1939 
1940 void Telescope::SyncParkStatus(bool isparked)
1941 {
1942  IsParked = isparked;
1944  ParkSP.s = IPS_OK;
1945 
1946  if (IsParked)
1947  {
1948  ParkS[0].s = ISS_ON;
1950  LOG_INFO("Mount is parked.");
1951  }
1952  else
1953  {
1954  ParkS[1].s = ISS_ON;
1956  LOG_INFO("Mount is unparked.");
1957  }
1958 
1959  IDSetSwitch(&ParkSP, nullptr);
1960 }
1961 
1962 void Telescope::SetParked(bool isparked)
1963 {
1964  SyncParkStatus(isparked);
1965 
1966  if (parkDataType != PARK_NONE)
1967  WriteParkData();
1968 }
1969 
1971 {
1972  return IsParked;
1973 }
1974 
1976 {
1977  const char *loadres = LoadParkData();
1978  if (loadres)
1979  {
1980  LOGF_INFO("InitPark: No Park data in file %s: %s", ParkDataFileName.c_str(), loadres);
1981  SyncParkStatus(false);
1982  return false;
1983  }
1984 
1986 
1987  LOGF_DEBUG("InitPark Axis1 %.2f Axis2 %.2f", Axis1ParkPosition, Axis2ParkPosition);
1988  ParkPositionN[AXIS_RA].value = Axis1ParkPosition;
1989  ParkPositionN[AXIS_DE].value = Axis2ParkPosition;
1990  IDSetNumber(&ParkPositionNP, nullptr);
1991 
1992  return true;
1993 }
1994 
1995 const char *Telescope::LoadParkXML()
1996 {
1997  wordexp_t wexp;
1998  FILE *fp = nullptr;
1999  LilXML *lp = nullptr;
2000  static char errmsg[512];
2001 
2002  XMLEle *parkxml = nullptr;
2003  XMLAtt *ap = nullptr;
2004  bool devicefound = false;
2005 
2006  ParkDeviceName = getDeviceName();
2007  ParkstatusXml = nullptr;
2008  ParkdeviceXml = nullptr;
2009  ParkpositionXml = nullptr;
2010  ParkpositionAxis1Xml = nullptr;
2011  ParkpositionAxis2Xml = nullptr;
2012 
2013  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2014  {
2015  wordfree(&wexp);
2016  return "Badly formed filename.";
2017  }
2018 
2019  if (!(fp = fopen(wexp.we_wordv[0], "r")))
2020  {
2021  wordfree(&wexp);
2022  return strerror(errno);
2023  }
2024  wordfree(&wexp);
2025 
2026  lp = newLilXML();
2027 
2028  if (ParkdataXmlRoot)
2029  delXMLEle(ParkdataXmlRoot);
2030 
2031  ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2032  fclose(fp);
2033 
2034  delLilXML(lp);
2035  if (!ParkdataXmlRoot)
2036  return errmsg;
2037 
2038  parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2039 
2040  if (!parkxml)
2041  return "Empty park file.";
2042 
2043  if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2044  {
2045  delXMLEle(parkxml);
2046  return "Not a park data file";
2047  }
2048 
2049  while (parkxml)
2050  {
2051  if (strcmp(tagXMLEle(parkxml), "device"))
2052  {
2053  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2054  continue;
2055  }
2056  ap = findXMLAtt(parkxml, "name");
2057  if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2058  {
2059  devicefound = true;
2060  break;
2061  }
2062  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2063  }
2064 
2065  if (!devicefound)
2066  {
2067  delXMLEle(parkxml);
2068  return "No park data found for this device";
2069  }
2070 
2071  ParkdeviceXml = parkxml;
2072  ParkstatusXml = findXMLEle(parkxml, "parkstatus");
2073  ParkpositionXml = findXMLEle(parkxml, "parkposition");
2074  if (ParkpositionXml)
2075  ParkpositionAxis1Xml = findXMLEle(ParkpositionXml, "axis1position");
2076  if (ParkpositionXml)
2077  ParkpositionAxis2Xml = findXMLEle(ParkpositionXml, "axis2position");
2078 
2079  if (ParkstatusXml == nullptr || ParkpositionAxis1Xml == nullptr || ParkpositionAxis2Xml == nullptr)
2080  {
2081  return "Park data invalid or missing.";
2082  }
2083 
2084  return nullptr;
2085 }
2086 
2088 {
2089  IsParked = false;
2090 
2091  const char *result = LoadParkXML();
2092  if (result != nullptr)
2093  return result;
2094 
2095  if (!strcmp(pcdataXMLEle(ParkstatusXml), "true"))
2096  IsParked = true;
2097 
2098  double axis1Pos = std::numeric_limits<double>::quiet_NaN();
2099  double axis2Pos = std::numeric_limits<double>::quiet_NaN();
2100 
2101  int rc = sscanf(pcdataXMLEle(ParkpositionAxis1Xml), "%lf", &axis1Pos);
2102  if (rc != 1)
2103  {
2104  return "Unable to parse Park Position Axis 1.";
2105  }
2106  rc = sscanf(pcdataXMLEle(ParkpositionAxis2Xml), "%lf", &axis2Pos);
2107  if (rc != 1)
2108  {
2109  return "Unable to parse Park Position Axis 2.";
2110  }
2111 
2112  if (std::isnan(axis1Pos) == false && std::isnan(axis2Pos) == false)
2113  {
2114  Axis1ParkPosition = axis1Pos;
2115  Axis2ParkPosition = axis2Pos;
2116  return nullptr;
2117  }
2118 
2119  return "Failed to parse Park Position.";
2120 }
2121 
2123 {
2124  // We need to refresh parking data in case other devices parking states were updated since we
2125  // read the data the first time.
2126  if (LoadParkXML() != nullptr)
2127  LOG_DEBUG("Failed to refresh parking data.");
2128 
2129  wordexp_t wexp;
2130  FILE *fp = nullptr;
2131  LilXML *lp = nullptr;
2132  static char errmsg[512];
2133 
2134  XMLEle *parkxml = nullptr;
2135  XMLAtt *ap = nullptr;
2136  bool devicefound = false;
2137 
2138  ParkDeviceName = getDeviceName();
2139 
2140  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2141  {
2142  wordfree(&wexp);
2143  return false;
2144  }
2145 
2146  if (!(fp = fopen(wexp.we_wordv[0], "r")))
2147  {
2148  wordfree(&wexp);
2149  LOGF_ERROR("Failed to purge park data: %s", strerror(errno));
2150  return false;
2151  }
2152  wordfree(&wexp);
2153 
2154  lp = newLilXML();
2155 
2156  if (ParkdataXmlRoot)
2157  delXMLEle(ParkdataXmlRoot);
2158 
2159  ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2160  fclose(fp);
2161 
2162  delLilXML(lp);
2163  if (!ParkdataXmlRoot)
2164  return false;
2165 
2166  parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2167 
2168  if (!parkxml)
2169  return false;
2170 
2171  if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2172  {
2173  delXMLEle(parkxml);
2174  return false;
2175  }
2176 
2177  while (parkxml)
2178  {
2179  if (strcmp(tagXMLEle(parkxml), "device"))
2180  {
2181  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2182  continue;
2183  }
2184  ap = findXMLAtt(parkxml, "name");
2185  if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2186  {
2187  devicefound = true;
2188  break;
2189  }
2190  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2191  }
2192 
2193  if (!devicefound)
2194  return false;
2195 
2196  delXMLEle(parkxml);
2197 
2198  ParkstatusXml = nullptr;
2199  ParkdeviceXml = nullptr;
2200  ParkpositionXml = nullptr;
2201  ParkpositionAxis1Xml = nullptr;
2202  ParkpositionAxis2Xml = nullptr;
2203 
2204  wordexp(ParkDataFileName.c_str(), &wexp, 0);
2205  if (!(fp = fopen(wexp.we_wordv[0], "w")))
2206  {
2207  wordfree(&wexp);
2208  LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(), strerror(errno));
2209  return false;
2210  }
2211  prXMLEle(fp, ParkdataXmlRoot, 0);
2212  fclose(fp);
2213  wordfree(&wexp);
2214 
2215  return true;
2216 }
2217 
2219 {
2220  // We need to refresh parking data in case other devices parking states were updated since we
2221  // read the data the first time.
2222  if (LoadParkXML() != nullptr)
2223  LOG_DEBUG("Failed to refresh parking data.");
2224 
2225  wordexp_t wexp;
2226  FILE *fp;
2227  char pcdata[30];
2228  ParkDeviceName = getDeviceName();
2229 
2230  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2231  {
2232  wordfree(&wexp);
2233  LOGF_INFO("WriteParkData: can not write file %s: Badly formed filename.",
2234  ParkDataFileName.c_str());
2235  return false;
2236  }
2237 
2238  if (!(fp = fopen(wexp.we_wordv[0], "w")))
2239  {
2240  wordfree(&wexp);
2241  LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(),
2242  strerror(errno));
2243  return false;
2244  }
2245 
2246  if (!ParkdataXmlRoot)
2247  ParkdataXmlRoot = addXMLEle(nullptr, "parkdata");
2248 
2249  if (!ParkdeviceXml)
2250  {
2251  ParkdeviceXml = addXMLEle(ParkdataXmlRoot, "device");
2252  addXMLAtt(ParkdeviceXml, "name", ParkDeviceName);
2253  }
2254 
2255  if (!ParkstatusXml)
2256  ParkstatusXml = addXMLEle(ParkdeviceXml, "parkstatus");
2257  if (!ParkpositionXml)
2258  ParkpositionXml = addXMLEle(ParkdeviceXml, "parkposition");
2259  if (!ParkpositionAxis1Xml)
2260  ParkpositionAxis1Xml = addXMLEle(ParkpositionXml, "axis1position");
2261  if (!ParkpositionAxis2Xml)
2262  ParkpositionAxis2Xml = addXMLEle(ParkpositionXml, "axis2position");
2263 
2264  editXMLEle(ParkstatusXml, (IsParked ? "true" : "false"));
2265 
2266  snprintf(pcdata, sizeof(pcdata), "%lf", Axis1ParkPosition);
2267  editXMLEle(ParkpositionAxis1Xml, pcdata);
2268  snprintf(pcdata, sizeof(pcdata), "%lf", Axis2ParkPosition);
2269  editXMLEle(ParkpositionAxis2Xml, pcdata);
2270 
2271  prXMLEle(fp, ParkdataXmlRoot, 0);
2272  fclose(fp);
2273  wordfree(&wexp);
2274 
2275  return true;
2276 }
2277 
2279 {
2280  return Axis1ParkPosition;
2281 }
2283 {
2284  return Axis1DefaultParkPosition;
2285 }
2287 {
2288  return Axis2ParkPosition;
2289 }
2291 {
2292  return Axis2DefaultParkPosition;
2293 }
2294 
2295 void Telescope::SetAxis1Park(double value)
2296 {
2297  LOGF_DEBUG("Setting Park Axis1 to %.2f", value);
2298  Axis1ParkPosition = value;
2299  ParkPositionN[AXIS_RA].value = value;
2300  IDSetNumber(&ParkPositionNP, nullptr);
2301 }
2302 
2304 {
2305  LOGF_DEBUG("Setting Default Park Axis1 to %.2f", value);
2306  Axis1DefaultParkPosition = value;
2307 }
2308 
2309 void Telescope::SetAxis2Park(double value)
2310 {
2311  LOGF_DEBUG("Setting Park Axis2 to %.2f", value);
2312  Axis2ParkPosition = value;
2313  ParkPositionN[AXIS_DE].value = value;
2314  IDSetNumber(&ParkPositionNP, nullptr);
2315 }
2316 
2318 {
2319  LOGF_DEBUG("Setting Default Park Axis2 to %.2f", value);
2320  Axis2DefaultParkPosition = value;
2321 }
2322 
2324 {
2325  return DomePolicyS[DOME_LOCKS].s == ISS_ON && IsLocked;
2326 }
2327 
2328 bool Telescope::SetSlewRate(int index)
2329 {
2330  INDI_UNUSED(index);
2331  return true;
2332 }
2333 
2334 void Telescope::processButton(const char *button_n, ISState state)
2335 {
2336  //ignore OFF
2337  if (state == ISS_OFF)
2338  return;
2339 
2340  if (!strcmp(button_n, "ABORTBUTTON"))
2341  {
2342  auto trackSW = getSwitch("TELESCOPE_TRACK_MODE");
2343  // Only abort if we have some sort of motion going on
2344  if (ParkSP.s == IPS_BUSY || MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY || EqNP.s == IPS_BUSY ||
2345  (trackSW && trackSW.getState() == IPS_BUSY))
2346  {
2347  // Invoke parent processing so that Telescope takes care of abort cross-check
2348  ISState states[1] = { ISS_ON };
2349  const char *names[1] = { AbortS[0].name };
2350  ISNewSwitch(getDeviceName(), AbortSP.name, states, const_cast<char **>(names), 1);
2351  }
2352  }
2353  else if (!strcmp(button_n, "PARKBUTTON"))
2354  {
2355  ISState states[2] = { ISS_ON, ISS_OFF };
2356  const char *names[2] = { ParkS[0].name, ParkS[1].name };
2357  ISNewSwitch(getDeviceName(), ParkSP.name, states, const_cast<char **>(names), 2);
2358  }
2359  else if (!strcmp(button_n, "UNPARKBUTTON"))
2360  {
2361  ISState states[2] = { ISS_OFF, ISS_ON };
2362  const char *names[2] = { ParkS[0].name, ParkS[1].name };
2363  ISNewSwitch(getDeviceName(), ParkSP.name, states, const_cast<char **>(names), 2);
2364  }
2365  else if (!strcmp(button_n, "SLEWPRESETUP"))
2366  {
2367  processSlewPresets(1, 270);
2368  }
2369  else if (!strcmp(button_n, "SLEWPRESETDOWN"))
2370  {
2371  processSlewPresets(1, 90);
2372  }
2373 }
2374 
2375 void Telescope::processJoystick(const char *joystick_n, double mag, double angle)
2376 {
2377  if (MotionControlModeTP.sp[MOTION_CONTROL_JOYSTICK].s == ISS_ON && !strcmp(joystick_n, "MOTIONDIR"))
2378  {
2380  {
2381  DEBUG(Logger::DBG_WARNING, "Can not slew while mount is parking/parked.");
2382  return;
2383  }
2384 
2385  processNSWE(mag, angle);
2386  }
2387  else if (!strcmp(joystick_n, "SLEWPRESET"))
2388  processSlewPresets(mag, angle);
2389 }
2390 
2391 void Telescope::processAxis(const char *axis_n, double value)
2392 {
2394  {
2395  if (!strcmp(axis_n, "MOTIONDIRNS") || !strcmp(axis_n, "MOTIONDIRWE"))
2396  {
2398  {
2399  LOG_WARN("Cannot slew while mount is parking/parked.");
2400  return;
2401  }
2402 
2403  if (!strcmp(axis_n, "MOTIONDIRNS"))
2404  {
2405  // South
2406  if (value > 0)
2407  {
2408  motionDirNSValue = -1;
2409  }
2410  // North
2411  else if (value < 0)
2412  {
2413  motionDirNSValue = 1;
2414  }
2415  else
2416  {
2417  motionDirNSValue = 0;
2418  }
2419  }
2420  else if (!strcmp(axis_n, "MOTIONDIRWE"))
2421  {
2422  // East
2423  if (value > 0)
2424  {
2425  motionDirWEValue = 1;
2426  }
2427  // West
2428  else if (value < 0)
2429  {
2430  motionDirWEValue = -1;
2431  }
2432  else
2433  {
2434  motionDirWEValue = 0;
2435  }
2436  }
2437 
2438  float x = motionDirWEValue * sqrt(1 - pow(motionDirNSValue, 2) / 2.0f);
2439  float y = motionDirNSValue * sqrt(1 - pow(motionDirWEValue, 2) / 2.0f);
2440  float angle = atan2(y, x) * (180.0 / 3.141592653589);
2441  float mag = sqrt(pow(y, 2) + pow(x, 2));
2442  while (angle < 0)
2443  {
2444  angle += 360;
2445  }
2446  if (mag == 0)
2447  {
2448  angle = 0;
2449  }
2450 
2451  processNSWE(mag, angle);
2452  }
2453  }
2454 }
2455 
2456 void Telescope::processNSWE(double mag, double angle)
2457 {
2458  if (mag < 0.5)
2459  {
2460  // Moving in the same direction will make it stop
2461  if (MovementNSSP.s == IPS_BUSY)
2462  {
2464  {
2467  IDSetSwitch(&MovementNSSP, nullptr);
2468  }
2469  else
2470  {
2472  IDSetSwitch(&MovementNSSP, nullptr);
2473  }
2474  }
2475 
2476  if (MovementWESP.s == IPS_BUSY)
2477  {
2479  {
2482  IDSetSwitch(&MovementWESP, nullptr);
2483  }
2484  else
2485  {
2487  IDSetSwitch(&MovementWESP, nullptr);
2488  }
2489  }
2490  }
2491  // Put high threshold
2492  else if (mag > 0.9)
2493  {
2494  // Only one axis can move at a time
2495  if (LockAxisS[AXIS_RA].s == ISS_ON)
2496  {
2497  // West
2498  if (angle >= 90 && angle <= 270)
2499  angle = 180;
2500  // East
2501  else
2502  angle = 0;
2503  }
2504  else if (LockAxisS[AXIS_DE].s == ISS_ON)
2505  {
2506  // North
2507  if (angle >= 0 && angle <= 180)
2508  angle = 90;
2509  // South
2510  else
2511  angle = 270;
2512  }
2513 
2514  // Snap angle to x or y direction if close to corresponding axis (i.e. deviation < 15°)
2515  if (angle > 75 && angle < 105)
2516  {
2517  angle = 90;
2518  }
2519  if (angle > 165 && angle < 195)
2520  {
2521  angle = 180;
2522  }
2523  if (angle > 255 && angle < 285)
2524  {
2525  angle = 270;
2526  }
2527  if (angle > 345 || angle < 15)
2528  {
2529  angle = 0;
2530  }
2531 
2532  // North
2533  if (angle > 0 && angle < 180)
2534  {
2535  // Don't try to move if you're busy and moving in the same direction
2536  if (MovementNSSP.s != IPS_BUSY || MovementNSS[0].s != ISS_ON)
2538 
2542  IDSetSwitch(&MovementNSSP, nullptr);
2543  }
2544  // South
2545  if (angle > 180 && angle < 360)
2546  {
2547  // Don't try to move if you're busy and moving in the same direction
2548  if (MovementNSSP.s != IPS_BUSY || MovementNSS[1].s != ISS_ON)
2550 
2554  IDSetSwitch(&MovementNSSP, nullptr);
2555  }
2556  // East
2557  if (angle < 90 || angle > 270)
2558  {
2559  // Don't try to move if you're busy and moving in the same direction
2560  if (MovementWESP.s != IPS_BUSY || MovementWES[1].s != ISS_ON)
2562 
2566  IDSetSwitch(&MovementWESP, nullptr);
2567  }
2568 
2569  // West
2570  if (angle > 90 && angle < 270)
2571  {
2572  // Don't try to move if you're busy and moving in the same direction
2573  if (MovementWESP.s != IPS_BUSY || MovementWES[0].s != ISS_ON)
2575 
2579  IDSetSwitch(&MovementWESP, nullptr);
2580  }
2581  }
2582 }
2583 
2584 void Telescope::processSlewPresets(double mag, double angle)
2585 {
2586  // high threshold, only 1 is accepted
2587  if (mag != 1)
2588  return;
2589 
2590  int currentIndex = IUFindOnSwitchIndex(&SlewRateSP);
2591 
2592  // Up
2593  if (angle > 0 && angle < 180)
2594  {
2595  if (currentIndex <= 0)
2596  return;
2597 
2599  SlewRateS[currentIndex - 1].s = ISS_ON;
2600  SetSlewRate(currentIndex - 1);
2601  }
2602  // Down
2603  else
2604  {
2605  if (currentIndex >= SlewRateSP.nsp - 1)
2606  return;
2607 
2609  SlewRateS[currentIndex + 1].s = ISS_ON;
2610  SetSlewRate(currentIndex - 1);
2611  }
2612 
2613  IDSetSwitch(&SlewRateSP, nullptr);
2614 }
2615 
2616 void Telescope::joystickHelper(const char *joystick_n, double mag, double angle, void *context)
2617 {
2618  static_cast<Telescope *>(context)->processJoystick(joystick_n, mag, angle);
2619 }
2620 
2621 void Telescope::axisHelper(const char *axis_n, double value, void *context)
2622 {
2623  static_cast<Telescope *>(context)->processAxis(axis_n, value);
2624 }
2625 
2626 void Telescope::buttonHelper(const char *button_n, ISState state, void *context)
2627 {
2628  static_cast<Telescope *>(context)->processButton(button_n, state);
2629 }
2630 
2632 {
2633  // ensure that the scope knows it's pier side or the pier side is simulated
2634  if (HasPierSide() == false && getSimulatePierSide() == false)
2635  return;
2636 
2637  currentPierSide = side;
2638 
2640  {
2641  PierSideS[PIER_WEST].s = (side == PIER_WEST) ? ISS_ON : ISS_OFF;
2642  PierSideS[PIER_EAST].s = (side == PIER_EAST) ? ISS_ON : ISS_OFF;
2643  PierSideSP.s = IPS_OK;
2644  IDSetSwitch(&PierSideSP, nullptr);
2645 
2647  }
2648 }
2649 
2656 {
2657  // return unknown if the mount does not have pier side, this will be the case for a fork mount
2658  // where a pier flip is not required.
2659  if (!HasPierSide() && !HasPierSideSimulation())
2661 
2662  // calculate the hour angle and derive the pier side
2664  double hourAngle = get_local_hour_angle(lst, ra);
2665 
2666  return hourAngle <= 0 ? INDI::Telescope::PIER_WEST : INDI::Telescope::PIER_EAST;
2667 }
2668 
2670 {
2671  currentPECState = state;
2672 
2674  {
2675 
2676  PECStateS[PEC_OFF].s = (state == PEC_ON) ? ISS_OFF : ISS_ON;
2677  PECStateS[PEC_ON].s = (state == PEC_ON) ? ISS_ON : ISS_OFF;
2678  PECStateSP.s = IPS_OK;
2679  IDSetSwitch(&PECStateSP, nullptr);
2680 
2682  }
2683 }
2684 
2686 {
2687  if (!CheckFile(ScopeConfigFileName, false))
2688  {
2689  LOGF_INFO("Can't open XML file (%s) for read", ScopeConfigFileName.c_str());
2690  return false;
2691  }
2692  LilXML *XmlHandle = newLilXML();
2693  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2694  XMLEle *RootXmlNode = nullptr;
2695  XMLEle *CurrentXmlNode = nullptr;
2696  XMLAtt *Ap = nullptr;
2697  bool DeviceFound = false;
2698  char ErrMsg[512];
2699 
2700  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2701  fclose(FilePtr);
2702  delLilXML(XmlHandle);
2703  XmlHandle = nullptr;
2704  if (!RootXmlNode)
2705  {
2706  LOGF_INFO("Failed to parse XML file (%s): %s", ScopeConfigFileName.c_str(), ErrMsg);
2707  return false;
2708  }
2709  if (std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2710  {
2711  LOGF_INFO("Not a scope config XML file (%s)", ScopeConfigFileName.c_str());
2712  delXMLEle(RootXmlNode);
2713  return false;
2714  }
2715  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2716  // Find the current telescope in the config file
2717  while (CurrentXmlNode)
2718  {
2719  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2720  {
2721  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2722  continue;
2723  }
2724  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2725  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2726  {
2727  DeviceFound = true;
2728  break;
2729  }
2730  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2731  }
2732  if (!DeviceFound)
2733  {
2734  LOGF_INFO("No a scope config found for %s in the XML file (%s)", getDeviceName(),
2735  ScopeConfigFileName.c_str());
2736  delXMLEle(RootXmlNode);
2737  return false;
2738  }
2739  // Read the values
2740  XMLEle *XmlNode = nullptr;
2741  const int ConfigIndex = GetScopeConfigIndex();
2742  double ScopeFoc = 0, ScopeAp = 0;
2743  double GScopeFoc = 0, GScopeAp = 0;
2744  std::string ConfigName;
2745 
2746  CurrentXmlNode = findXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2747  if (!CurrentXmlNode)
2748  {
2750  "Config %d is not found in the XML file (%s). To save a new config, update and set scope properties and "
2751  "config name.",
2752  ConfigIndex, ScopeConfigFileName.c_str());
2753  delXMLEle(RootXmlNode);
2754  return false;
2755  }
2756  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
2757  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &ScopeFoc) != 1)
2758  {
2759  LOGF_INFO("Can't read the telescope focal length from the XML file (%s)",
2760  ScopeConfigFileName.c_str());
2761  delXMLEle(RootXmlNode);
2762  return false;
2763  }
2764  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
2765  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &ScopeAp) != 1)
2766  {
2767  LOGF_INFO("Can't read the telescope aperture from the XML file (%s)",
2768  ScopeConfigFileName.c_str());
2769  delXMLEle(RootXmlNode);
2770  return false;
2771  }
2772  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
2773  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &GScopeFoc) != 1)
2774  {
2775  LOGF_INFO("Can't read the guide scope focal length from the XML file (%s)",
2776  ScopeConfigFileName.c_str());
2777  delXMLEle(RootXmlNode);
2778  return false;
2779  }
2780  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
2781  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &GScopeAp) != 1)
2782  {
2783  LOGF_INFO("Can't read the guide scope aperture from the XML file (%s)",
2784  ScopeConfigFileName.c_str());
2785  delXMLEle(RootXmlNode);
2786  return false;
2787  }
2788  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
2789  if (!XmlNode)
2790  {
2791  LOGF_INFO("Can't read the telescope config name from the XML file (%s)",
2792  ScopeConfigFileName.c_str());
2793  delXMLEle(RootXmlNode);
2794  return false;
2795  }
2796  ConfigName = pcdataXMLEle(XmlNode);
2797  // Store the loaded values
2798  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH"))
2799  {
2800  IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH")->value = ScopeFoc;
2801  }
2802  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE"))
2803  {
2804  IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE")->value = ScopeAp;
2805  }
2806  if (IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH"))
2807  {
2808  IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH")->value = GScopeFoc;
2809  }
2810  if (IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE"))
2811  {
2812  IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE")->value = GScopeAp;
2813  }
2814  if (IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME"))
2815  {
2816  IUSaveText(IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME"), ConfigName.c_str());
2817  }
2819  IDSetNumber(&ScopeParametersNP, nullptr);
2821  IDSetText(&ScopeConfigNameTP, nullptr);
2822  delXMLEle(RootXmlNode);
2823  return true;
2824 }
2825 
2827 {
2828  if (!CheckFile(ScopeConfigFileName, false))
2829  {
2830  return false;
2831  }
2832  LilXML *XmlHandle = newLilXML();
2833  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2834  XMLEle *RootXmlNode = nullptr;
2835  XMLEle *CurrentXmlNode = nullptr;
2836  XMLAtt *Ap = nullptr;
2837  bool DeviceFound = false;
2838  char ErrMsg[512];
2839 
2840  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2841  fclose(FilePtr);
2842  delLilXML(XmlHandle);
2843  XmlHandle = nullptr;
2844  if (!RootXmlNode)
2845  {
2846  return false;
2847  }
2848  if (std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2849  {
2850  delXMLEle(RootXmlNode);
2851  return false;
2852  }
2853  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2854  // Find the current telescope in the config file
2855  while (CurrentXmlNode)
2856  {
2857  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2858  {
2859  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2860  continue;
2861  }
2862  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2863  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2864  {
2865  DeviceFound = true;
2866  break;
2867  }
2868  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2869  }
2870  if (!DeviceFound)
2871  {
2872  delXMLEle(RootXmlNode);
2873  return false;
2874  }
2875  // Check the existence of Config #1 node
2876  CurrentXmlNode = findXMLEle(CurrentXmlNode, "config1");
2877  if (!CurrentXmlNode)
2878  {
2879  delXMLEle(RootXmlNode);
2880  return false;
2881  }
2882  return true;
2883 }
2884 
2886 {
2887  // Get the config values from the UI
2888  const int ConfigIndex = GetScopeConfigIndex();
2889  double ScopeFoc = 0, ScopeAp = 0;
2890  double GScopeFoc = 0, GScopeAp = 0;
2891  std::string ConfigName;
2892 
2893  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH"))
2894  {
2895  ScopeFoc = IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH")->value;
2896  }
2897  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE"))
2898  {
2899  ScopeAp = IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE")->value;
2900  }
2901  if (IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH"))
2902  {
2903  GScopeFoc = IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH")->value;
2904  }
2905  if (IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE"))
2906  {
2907  GScopeAp = IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE")->value;
2908  }
2909  if (IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME") &&
2910  IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME")->text)
2911  {
2912  ConfigName = IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME")->text;
2913  }
2914  // Save the values to the actual XML file
2915  if (!CheckFile(ScopeConfigFileName, true))
2916  {
2917  LOGF_INFO("Can't open XML file (%s) for write", ScopeConfigFileName.c_str());
2918  return false;
2919  }
2920  // Open the existing XML file for write
2921  LilXML *XmlHandle = newLilXML();
2922  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2923  XMLEle *RootXmlNode = nullptr;
2924  XMLAtt *Ap = nullptr;
2925  bool DeviceFound = false;
2926  char ErrMsg[512];
2927 
2928  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2929  delLilXML(XmlHandle);
2930  XmlHandle = nullptr;
2931  fclose(FilePtr);
2932 
2933  XMLEle *CurrentXmlNode = nullptr;
2934  XMLEle *XmlNode = nullptr;
2935 
2936  if (!RootXmlNode || std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2937  {
2938  RootXmlNode = addXMLEle(nullptr, ScopeConfigRootXmlNode.c_str());
2939  }
2940  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2941  // Find the current telescope in the config file
2942  while (CurrentXmlNode)
2943  {
2944  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2945  {
2946  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2947  continue;
2948  }
2949  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2950  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2951  {
2952  DeviceFound = true;
2953  break;
2954  }
2955  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2956  }
2957  if (!DeviceFound)
2958  {
2959  CurrentXmlNode = addXMLEle(RootXmlNode, ScopeConfigDeviceXmlNode.c_str());
2960  addXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str(), getDeviceName());
2961  }
2962  // Add or update the config node
2963  XmlNode = findXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2964  if (!XmlNode)
2965  {
2966  CurrentXmlNode = addXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2967  }
2968  else
2969  {
2970  CurrentXmlNode = XmlNode;
2971  }
2972  // Add or update the telescope focal length
2973  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
2974  if (!XmlNode)
2975  {
2976  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
2977  }
2978  editXMLEle(XmlNode, std::to_string(ScopeFoc).c_str());
2979  // Add or update the telescope focal aperture
2980  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
2981  if (!XmlNode)
2982  {
2983  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
2984  }
2985  editXMLEle(XmlNode, std::to_string(ScopeAp).c_str());
2986  // Add or update the guide scope focal length
2987  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
2988  if (!XmlNode)
2989  {
2990  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
2991  }
2992  editXMLEle(XmlNode, std::to_string(GScopeFoc).c_str());
2993  // Add or update the guide scope focal aperture
2994  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
2995  if (!XmlNode)
2996  {
2997  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
2998  }
2999  editXMLEle(XmlNode, std::to_string(GScopeAp).c_str());
3000  // Add or update the config name
3001  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
3002  if (!XmlNode)
3003  {
3004  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
3005  }
3006  editXMLEle(XmlNode, ConfigName.c_str());
3007  // Save the final content
3008  FilePtr = fopen(ScopeConfigFileName.c_str(), "w");
3009  prXMLEle(FilePtr, RootXmlNode, 0);
3010  fclose(FilePtr);
3011  delXMLEle(RootXmlNode);
3012  return true;
3013 }
3014 
3015 std::string Telescope::GetHomeDirectory() const
3016 {
3017  // Check first the HOME environmental variable
3018  const char *HomeDir = getenv("HOME");
3019 
3020  // ...otherwise get the home directory of the current user.
3021  if (!HomeDir)
3022  {
3023  HomeDir = getpwuid(getuid())->pw_dir;
3024  }
3025  return (HomeDir ? std::string(HomeDir) : "");
3026 }
3027 
3029 {
3030  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG1") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG1")->s == ISS_ON)
3031  {
3032  return 1;
3033  }
3034  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG2") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG2")->s == ISS_ON)
3035  {
3036  return 2;
3037  }
3038  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG3") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG3")->s == ISS_ON)
3039  {
3040  return 3;
3041  }
3042  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG4") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG4")->s == ISS_ON)
3043  {
3044  return 4;
3045  }
3046  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG5") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG5")->s == ISS_ON)
3047  {
3048  return 5;
3049  }
3050  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG6") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG6")->s == ISS_ON)
3051  {
3052  return 6;
3053  }
3054  return 0;
3055 }
3056 
3057 bool Telescope::CheckFile(const std::string &file_name, bool writable) const
3058 {
3059  FILE *FilePtr = fopen(file_name.c_str(), (writable ? "a" : "r"));
3060 
3061  if (FilePtr)
3062  {
3063  fclose(FilePtr);
3064  return true;
3065  }
3066  return false;
3067 }
3068 
3070 {
3071  char ts[32] = {0};
3072 
3073  std::time_t t = std::time(nullptr);
3074  struct std::tm *utctimeinfo = std::gmtime(&t);
3075 
3076  strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
3077  IUSaveText(&TimeT[0], ts);
3078 
3079  struct std::tm *localtimeinfo = std::localtime(&t);
3080  snprintf(ts, sizeof(ts), "%4.2f", (localtimeinfo->tm_gmtoff / 3600.0));
3081  IUSaveText(&TimeT[1], ts);
3082 
3083  TimeTP.s = IPS_OK;
3084 
3085  IDSetText(&TimeTP, nullptr);
3086 }
3087 
3089 {
3090  return m_simulatePierSide;
3091 }
3092 
3094 {
3095  switch (ps)
3096  {
3097  case PIER_WEST:
3098  return "PIER_WEST";
3099  case PIER_EAST:
3100  return "PIER_EAST";
3101  default:
3102  return "PIER_UNKNOWN";
3103  }
3104 }
3105 
3107 {
3109  SimulatePierSideS[0].s = simulate ? ISS_ON : ISS_OFF;
3110  SimulatePierSideS[1].s = simulate ? ISS_OFF : ISS_ON;
3112  IDSetSwitch(&SimulatePierSideSP, nullptr);
3113 
3114  if (simulate)
3115  {
3118  }
3119  else
3120  {
3121  capability &= static_cast<uint32_t>(~TELESCOPE_HAS_PIER_SIDE);
3123  }
3124 
3125  m_simulatePierSide = simulate;
3126 }
3127 
3128 }
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the intial connection to the device...
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 successfull connectio...
Definition: connectiontcp.h:38
int getPortFD() const
Definition: connectiontcp.h:84
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
INDI::PropertySwitch getSwitch(const char *name) const
Definition: basedevice.cpp:99
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 const char * getDefaultName()=0
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...
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
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 bool initProperties()
Initilize 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.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
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 setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool CanControlTrack()
const std::string ScopeConfigGScopeFocXmlNode
INumberVectorProperty TrackRateNP
TelescopeStatus TrackState
bool isLocked() const
isLocked is mount currently locked?
ISwitchVectorProperty SimulatePierSideSP
virtual bool SetTrackMode(uint8_t mode)
SetTrackMode Set active tracking mode. Do not change track state.
ISwitchVectorProperty TrackStateSP
ISwitchVectorProperty ParkOptionSP
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
ISwitchVectorProperty DomePolicySP
bool HasPierSideSimulation()
TelescopePierSide currentPierSide
IText ActiveDeviceT[2]
virtual bool Goto(double ra, double dec)
Move the scope to the supplied RA and DEC coordinates.
ISwitchVectorProperty MovementNSSP
ITextVectorProperty ScopeConfigNameTP
void processButton(const char *button_n, ISState state)
static void axisHelper(const char *axis_n, double value, void *context)
ISwitchVectorProperty AbortSP
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.
INumberVectorProperty LocationNP
int GetScopeConfigIndex() const
Get the scope config index.
virtual bool SetCurrentPark()
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
@ SAT_PASS_WINDOW_COUNT
Number of indices.
@ SAT_PASS_WINDOW_END
Index for end of the window.
@ SAT_PASS_WINDOW_START
Index for start of the window.
TelescopeParkData parkDataType
INumberVectorProperty ScopeParametersNP
std::string GetHomeDirectory() const
Validate a file name.
ISwitch AbortS[1]
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty ActiveDeviceTP
const std::string ScopeConfigLabelApXmlNode
bool CanTrackSatellite()
ITextVectorProperty TimeTP
IText TLEtoTrackT[1]
virtual void TimerHit() override
Called when setTimer() time is up.
INumberVectorProperty TargetNP
ISwitch MotionControlModeT[2]
ITextVectorProperty SatPassWindowTP
Text Vector property defining the start and end of a satellite pass (window contains pass)....
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
TelescopePierSide lastPierSide
virtual bool SetTrackRate(double raRate, double deRate)
SetTrackRate Set custom tracking rates.
double GetAxis1Park() const
virtual bool UnPark()
Unpark the telescope if already parked.
const std::string ScopeConfigDeviceXmlNode
INumberVectorProperty ParkPositionNP
virtual bool SetSlewRate(int index)
SetSlewRate Set desired slew rate index.
bool getSimulatePierSide() const
ISwitchVectorProperty CoordSP
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
const std::string ScopeConfigScopeFocXmlNode
ISwitchVectorProperty ScopeConfigsSP
bool isParked()
isParked is mount currently parked?
ISwitchVectorProperty PECStateSP
void processNSWE(double mag, double angle)
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty TrackModeSP
ISwitch ParkOptionS[4]
INumber TargetN[2]
ISwitchVectorProperty SlewRateSP
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
ISwitchVectorProperty PierSideSP
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.
const std::string ScopeConfigFileName
The telescope/guide scope configuration file name.
double GetAxis1ParkDefault() const
virtual bool Handshake()
perform handshake with device to check communication
ISwitch MovementWES[2]
virtual bool SetTrackEnabled(bool enabled)
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
const std::string ScopeConfigRootXmlNode
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
INumberVectorProperty EqNP
virtual ~Telescope()
static void buttonHelper(const char *button_n, ISState state, void *context)
uint8_t getTelescopeConnection() const
ISwitch DomePolicyS[2]
const char * LoadParkData()
void setPECState(TelescopePECState state)
ISwitchVectorProperty TrackSatSP
Switch Vector property defining the state of the satellite tracking of the mount. Example implementat...
ISwitchVectorProperty MotionControlModeTP
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...
INumber EqN[2]
ISwitchVectorProperty ParkSP
INumber ScopeParametersN[4]
const std::string ScopeConfigNameXmlNode
ISwitch TrackSatS[SAT_TRACK_COUNT]
INDI::PropertySwitch ReverseMovementSP
const std::string ScopeConfigGScopeApXmlNode
INumber TrackRateN[2]
IGeographicCoordinates m_Location
ISwitchVectorProperty LockAxisSP
ISwitch ScopeConfigs[6]
void processJoystick(const char *joystick_n, double mag, double angle)
ISwitch LockAxisS[2]
ISwitch PierSideS[2]
IText SatPassWindowT[SAT_PASS_WINDOW_COUNT]
ISwitch PECStateS[2]
bool callHandshake()
callHandshake Helper function that sets the port file descriptor before calling the actual Handshake ...
bool LoadScopeConfig()
Load scope settings from XML files.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
bool UpdateScopeConfig()
Save scope settings to XML files.
INumber LocationN[3]
virtual void SyncParkStatus(bool isparked)
SyncParkStatus Update the state and switches for parking.
ISwitch * TrackModeS
void setSimulatePierSide(bool value)
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
INumber ParkPositionN[2]
uint32_t capability
ISwitch ParkS[2]
ISwitch CoordS[3]
ISwitchVectorProperty MovementWESP
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....
const std::string ScopeConfigScopeApXmlNode
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
Start or Stop the telescope motion in the direction dir.
ITextVectorProperty TLEtoTrackTP
Text Vector property defining the orbital elements of an artificial satellite (TLE)....
bool HasDefaultScopeConfig()
Load scope settings from XML files.
ISwitch SimulatePierSideS[2]
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.
const char * getPierSideStr(TelescopePierSide ps)
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
TelescopePECState lastPECState
@ SAT_TRACK
Track signal.
@ SAT_HALT
Halt signal (abort)
@ SAT_TRACK_COUNT
State counter.
ISwitch TrackStateS[2]
TelescopePierSide expectedPierSide(double ra)
Calculate the expected pier side for scopes that do not report this property themselves.
IText ScopeConfigNameT[1]
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
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define MAXINDIFORMAT
Definition: indiapi.h:195
#define MAXINDITSTAMP
Definition: indiapi.h:197
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
#define MAXINDILABEL
Definition: indiapi.h:192
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_NOFMANY
Definition: indiapi.h:175
@ ISR_ATMOST1
Definition: indiapi.h:174
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
@ AXIS_AZ
Definition: indibasetypes.h:42
@ AXIS_ALT
Definition: indibasetypes.h:43
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_EAST
Definition: indibasetypes.h:57
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_SOUTH
Definition: indibasetypes.h:50
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition: indicom.c:1293
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral 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.
#define TRACKRATE_SIDEREAL
Definition: indicom.h:55
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indidevapi.c:66
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
ISwitch * IUFindOnSwitch(const ISwitchVectorProperty *svp)
Returns the first ON switch it finds in the vector switch property.
Definition: indidevapi.c:108
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
int IUFindIndex(const char *needle, char **hay, unsigned int n)
Returns the index of the string in a string array.
Definition: indidevapi.c:117
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
IText * IUFindText(const ITextVectorProperty *tvp, const char *name)
Find an IText member in a vector text property.
Definition: indidevapi.c:56
ISwitch * IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
Find an ISwitch member in a vector switch property.
Definition: indidevapi.c:76
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
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:831
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition: indidriver.c:653
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define 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
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
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.cpp:644
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition: lilxml.cpp:706
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
Definition: lilxml.cpp:698
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition: lilxml.cpp:600
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition: lilxml.cpp:844
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
Definition: lilxml.cpp:653
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition: lilxml.cpp:167
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition: lilxml.cpp:159
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
Namespace to encapsulate INDI client, drivers, and mediator classes.
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:23613
__le16 type
Definition: pwc-ioctl.h:0
One number descriptor.
One switch descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250