Instrument Neutral Distributed Interface INDI  2.0.2
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  std::vector <std::tuple<std::string, std::string>> coords;
149 
150  coords.push_back(std::make_tuple("TRACK","Track"));
151 
152  if(CanGOTO())
153  coords.push_back(std::make_tuple("SLEW","Slew"));
154 
155  if(CanSync())
156  coords.push_back(std::make_tuple("SYNC","Sync"));
157 
158  if(CanFlip())
159  coords.push_back(std::make_tuple("FLIP","Flip"));
160 
161  int j = 0;
162  for(auto i : coords){
163  IUFillSwitch(&CoordS[j], std::get<0>(i).c_str(), std::get<1>(i).c_str(), j==0 ? ISS_ON : ISS_OFF);
164  ++j;
165  }
166 
167  IUFillSwitchVector(&CoordSP, CoordS, static_cast<int>(coords.size()), getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
168  ISR_1OFMANY, 60, IPS_IDLE);
169 
170 
171  if (nSlewRate >= 4)
172  IUFillSwitchVector(&SlewRateSP, SlewRateS, nSlewRate, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
174 
175  if (CanTrackSatellite())
176  {
177  IUFillText(&TLEtoTrackT[0], "TLE", "TLE", "");
178  IUFillTextVector(&TLEtoTrackTP, TLEtoTrackT, 1, getDeviceName(), "SAT_TLE_TEXT", "Orbit Params", SATELLITE_TAB,
179  IP_RW, 60, IPS_IDLE);
180 
181  char curTime[32] = {0};
182  std::time_t t = std::time(nullptr);
183  struct std::tm *utctimeinfo = std::gmtime(&t);
184  strftime(curTime, sizeof(curTime), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
185 
186  IUFillText(&SatPassWindowT[SAT_PASS_WINDOW_END], "SAT_PASS_WINDOW_END", "End UTC", curTime);
187  IUFillText(&SatPassWindowT[SAT_PASS_WINDOW_START], "SAT_PASS_WINDOW_START", "Start UTC", curTime);
189  "SAT_PASS_WINDOW", "Pass Window", SATELLITE_TAB, IP_RW, 60, IPS_IDLE);
190 
191  IUFillSwitch(&TrackSatS[SAT_TRACK], "SAT_TRACK", "Track", ISS_OFF);
192  IUFillSwitch(&TrackSatS[SAT_HALT], "SAT_HALT", "Halt", ISS_ON);
194  "Sat tracking", SATELLITE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
195  }
196 
197  IUFillSwitch(&ParkS[0], "PARK", "Park(ed)", ISS_OFF);
198  IUFillSwitch(&ParkS[1], "UNPARK", "UnPark(ed)", ISS_OFF);
199  IUFillSwitchVector(&ParkSP, ParkS, 2, getDeviceName(), "TELESCOPE_PARK", "Parking", MAIN_CONTROL_TAB, IP_RW,
200  ISR_1OFMANY, 60, IPS_IDLE);
201 
202  IUFillSwitch(&AbortS[0], "ABORT", "Abort", ISS_OFF);
203  IUFillSwitchVector(&AbortSP, AbortS, 1, getDeviceName(), "TELESCOPE_ABORT_MOTION", "Abort Motion", MAIN_CONTROL_TAB,
204  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
205 
206  IUFillSwitch(&MovementNSS[DIRECTION_NORTH], "MOTION_NORTH", "North", ISS_OFF);
207  IUFillSwitch(&MovementNSS[DIRECTION_SOUTH], "MOTION_SOUTH", "South", ISS_OFF);
208  IUFillSwitchVector(&MovementNSSP, MovementNSS, 2, getDeviceName(), "TELESCOPE_MOTION_NS", "Motion N/S", MOTION_TAB,
209  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
210 
211  IUFillSwitch(&MovementWES[DIRECTION_WEST], "MOTION_WEST", "West", ISS_OFF);
212  IUFillSwitch(&MovementWES[DIRECTION_EAST], "MOTION_EAST", "East", ISS_OFF);
213  IUFillSwitchVector(&MovementWESP, MovementWES, 2, getDeviceName(), "TELESCOPE_MOTION_WE", "Motion W/E", MOTION_TAB,
214  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
215 
216  // Reverse NS or WE
217  ReverseMovementSP[REVERSE_NS].fill("REVERSE_NS", "North/South", ISS_OFF);
218  ReverseMovementSP[REVERSE_WE].fill("REVERSE_WE", "West/East", ISS_OFF);
219  ReverseMovementSP.fill(getDeviceName(), "TELESCOPE_REVERSE_MOTION", "Reverse", MOTION_TAB, IP_RW, ISR_NOFMANY, 60,
220  IPS_IDLE);
221 
222  IUFillNumber(&ScopeParametersN[0], "TELESCOPE_APERTURE", "Aperture (mm)", "%g", 10, 5000, 0, 0.0);
223  IUFillNumber(&ScopeParametersN[1], "TELESCOPE_FOCAL_LENGTH", "Focal Length (mm)", "%g", 10, 10000, 0, 0.0);
224  IUFillNumber(&ScopeParametersN[2], "GUIDER_APERTURE", "Guider Aperture (mm)", "%g", 10, 5000, 0, 0.0);
225  IUFillNumber(&ScopeParametersN[3], "GUIDER_FOCAL_LENGTH", "Guider Focal Length (mm)", "%g", 10, 10000, 0, 0.0);
226  IUFillNumberVector(&ScopeParametersNP, ScopeParametersN, 4, getDeviceName(), "TELESCOPE_INFO", "Scope Properties",
227  OPTIONS_TAB, IP_RW, 60, IPS_OK);
228 
229  // Scope config name
230  IUFillText(&ScopeConfigNameT[0], "SCOPE_CONFIG_NAME", "Config Name", "");
231  IUFillTextVector(&ScopeConfigNameTP, ScopeConfigNameT, 1, getDeviceName(), "SCOPE_CONFIG_NAME", "Scope Name",
232  OPTIONS_TAB, IP_RW, 60, IPS_OK);
233 
234  // Switch for aperture/focal length configs
235  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG1], "SCOPE_CONFIG1", "Config #1", ISS_ON);
236  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG2], "SCOPE_CONFIG2", "Config #2", ISS_OFF);
237  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG3], "SCOPE_CONFIG3", "Config #3", ISS_OFF);
238  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG4], "SCOPE_CONFIG4", "Config #4", ISS_OFF);
239  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG5], "SCOPE_CONFIG5", "Config #5", ISS_OFF);
240  IUFillSwitch(&ScopeConfigs[SCOPE_CONFIG6], "SCOPE_CONFIG6", "Config #6", ISS_OFF);
241  IUFillSwitchVector(&ScopeConfigsSP, ScopeConfigs, 6, getDeviceName(), "APPLY_SCOPE_CONFIG", "Scope Configs",
243 
244  controller->initProperties();
245 
246  // Joystick motion control
247  IUFillSwitch(&MotionControlModeT[0], "MOTION_CONTROL_MODE_JOYSTICK", "4-Way Joystick", ISS_ON);
248  IUFillSwitch(&MotionControlModeT[1], "MOTION_CONTROL_MODE_AXES", "Two Separate Axes", ISS_OFF);
249  IUFillSwitchVector(&MotionControlModeTP, MotionControlModeT, 2, getDeviceName(), "MOTION_CONTROL_MODE", "Motion Control",
250  "Joystick", IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
251 
252  // Lock Axis
253  IUFillSwitch(&LockAxisS[0], "LOCK_AXIS_1", "West/East", ISS_OFF);
254  IUFillSwitch(&LockAxisS[1], "LOCK_AXIS_2", "North/South", ISS_OFF);
255  IUFillSwitchVector(&LockAxisSP, LockAxisS, 2, getDeviceName(), "JOYSTICK_LOCK_AXIS", "Lock Axis", "Joystick", IP_RW,
256  ISR_ATMOST1, 60, IPS_IDLE);
257 
259 
261 
262  if (telescopeConnection & CONNECTION_SERIAL)
263  {
266  {
267  return callHandshake();
268  });
270  }
271 
272  if (telescopeConnection & CONNECTION_TCP)
273  {
274  tcpConnection = new Connection::TCP(this);
276  {
277  return callHandshake();
278  });
279 
281  }
282 
283  IDSnoopDevice(ActiveDeviceT[0].text, "GEOGRAPHIC_COORD");
284  IDSnoopDevice(ActiveDeviceT[0].text, "TIME_UTC");
285 
286  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_PARK");
287  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_SHUTTER");
288 
290 
291  double longitude = 0, latitude = 0, elevation = 0;
292  // Get value from config file if it exists.
294  {
295  LocationN[LOCATION_LONGITUDE].value = longitude;
296  m_Location.longitude = longitude;
297  }
299  {
300  LocationN[LOCATION_LATITUDE].value = latitude;
301  m_Location.latitude = latitude;
302  }
304  {
305  LocationN[LOCATION_ELEVATION].value = elevation;
306  m_Location.elevation = elevation;
307  }
308 
309  return true;
310 }
311 
312 void Telescope::ISGetProperties(const char *dev)
313 {
314  // First we let our parent populate
316 
317  if (CanGOTO())
318  {
320  loadConfig(true, "ACTIVE_DEVICES");
321 
322  ISState isDomeIgnored = ISS_OFF;
323  if (IUGetConfigSwitch(getDeviceName(), DomePolicySP.name, DomePolicyS[DOME_IGNORED].name, &isDomeIgnored) == 0)
324  {
325  DomePolicyS[DOME_IGNORED].s = isDomeIgnored;
326  DomePolicyS[DOME_LOCKS].s = (isDomeIgnored == ISS_ON) ? ISS_OFF : ISS_ON;
327  }
329  }
330 
333 
334  if (HasDefaultScopeConfig())
335  {
336  LoadScopeConfig();
337  }
338  else
339  {
340  loadConfig(true, "TELESCOPE_INFO");
341  loadConfig(true, "SCOPE_CONFIG_NAME");
342  }
343 
344  if (CanGOTO())
345  controller->ISGetProperties(dev);
346 }
347 
349 {
350  if (isConnected())
351  {
352  controller->mapController("MOTIONDIR", "N/S/W/E Control", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_1");
353  controller->mapController("MOTIONDIRNS", "N/S Control", Controller::CONTROLLER_AXIS, "AXIS_8");
354  controller->mapController("MOTIONDIRWE", "W/E Control", Controller::CONTROLLER_AXIS, "AXIS_7");
355 
356  if (nSlewRate >= 4)
357  {
358  controller->mapController("SLEWPRESET", "Slew Rate", Controller::CONTROLLER_JOYSTICK, "JOYSTICK_2");
359  controller->mapController("SLEWPRESETUP", "Slew Rate Up", Controller::CONTROLLER_BUTTON, "BUTTON_5");
360  controller->mapController("SLEWPRESETDOWN", "Slew Rate Down", Controller::CONTROLLER_BUTTON,
361  "BUTTON_6");
362  }
363  if (CanAbort())
364  controller->mapController("ABORTBUTTON", "Abort", Controller::CONTROLLER_BUTTON, "BUTTON_1");
365  if (CanPark())
366  {
367  controller->mapController("PARKBUTTON", "Park", Controller::CONTROLLER_BUTTON, "BUTTON_2");
368  controller->mapController("UNPARKBUTTON", "UnPark", Controller::CONTROLLER_BUTTON, "BUTTON_3");
369  }
370 
371  // Now we add our telescope specific stuff
372  if (CanGOTO() || CanSync())
375  if (CanAbort())
377 
378  if (HasTrackMode() && TrackModeS != nullptr)
380  if (CanControlTrack())
382  if (HasTrackRate())
384 
385 
386  if (CanGOTO())
387  {
391  if (nSlewRate >= 4)
394  }
395 
396  if (HasTime())
398  if (HasLocation())
400  if (CanPark())
401  {
403  if (parkDataType != PARK_NONE)
404  {
407  }
408  }
409 
410  if (HasPierSide())
412 
413  if (HasPierSideSimulation())
414  {
416  ISState value;
417  if (IUGetConfigSwitch(getDefaultName(), "SIMULATE_PIER_SIDE", "SIMULATE_YES", &value) )
418  setSimulatePierSide(value == ISS_ON);
419  }
420 
421  if (CanTrackSatellite())
422  {
426  }
427 
428  if (HasPECState())
430 
433  }
434  else
435  {
436  if (CanGOTO() || CanSync())
439  if (CanAbort())
441  if (HasTrackMode() && TrackModeS != nullptr)
443  if (HasTrackRate())
445  if (CanControlTrack())
447 
448  if (CanGOTO())
449  {
453  if (nSlewRate >= 4)
456  }
457 
458  if (HasTime())
460  if (HasLocation())
462 
463  if (CanPark())
464  {
466  if (parkDataType != PARK_NONE)
467  {
470  }
471  }
472 
473  if (HasPierSide())
475 
476  if (HasPierSideSimulation())
477  {
479  if (getSimulatePierSide() == true)
481  }
482 
483  if (CanTrackSatellite())
484  {
488  }
489 
490  if (HasPECState())
492 
495  }
496 
497  if (CanGOTO())
498  {
499  controller->updateProperties();
500 
501  auto useJoystick = getSwitch("USEJOYSTICK");
502  if (useJoystick)
503  {
504  if (isConnected())
505  {
506  if (useJoystick[0].getState() == ISS_ON)
507  {
509  loadConfig(true, "MOTION_CONTROL_MODE");
511  loadConfig(true, "LOCK_AXIS");
512  }
513  else
514  {
517  }
518  }
519  else
520  {
523  }
524  }
525  }
526 
527  return true;
528 }
529 
531 {
532  controller->ISSnoopDevice(root);
533 
534  XMLEle *ep = nullptr;
535  const char *propName = findXMLAttValu(root, "name");
536 
537  if (isConnected())
538  {
539  if (HasLocation() && !strcmp(propName, "GEOGRAPHIC_COORD"))
540  {
541  // Only accept IPS_OK state
542  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
543  return false;
544 
545  double longitude = -1, latitude = -1, elevation = -1;
546 
547  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
548  {
549  const char *elemName = findXMLAttValu(ep, "name");
550 
551  if (!strcmp(elemName, "LAT"))
552  latitude = atof(pcdataXMLEle(ep));
553  else if (!strcmp(elemName, "LONG"))
554  longitude = atof(pcdataXMLEle(ep));
555  else if (!strcmp(elemName, "ELEV"))
556  elevation = atof(pcdataXMLEle(ep));
557  }
558 
559  return processLocationInfo(latitude, longitude, elevation);
560  }
561  else if (HasTime() && !strcmp(propName, "TIME_UTC"))
562  {
563  // Only accept IPS_OK state
564  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
565  return false;
566 
567  char utc[MAXINDITSTAMP], offset[MAXINDITSTAMP];
568 
569  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
570  {
571  const char *elemName = findXMLAttValu(ep, "name");
572 
573  if (!strcmp(elemName, "UTC"))
574  strncpy(utc, pcdataXMLEle(ep), MAXINDITSTAMP);
575  else if (!strcmp(elemName, "OFFSET"))
576  strncpy(offset, pcdataXMLEle(ep), MAXINDITSTAMP);
577  }
578 
579  return processTimeInfo(utc, offset);
580  }
581  else if (!strcmp(propName, "DOME_PARK")/* || !strcmp(propName, "DOME_SHUTTER")*/)
582  {
583  // This is handled by Watchdog driver.
584  // Mount shouldn't park due to dome closing in INDI::Telescope
585 #if 0
586  if (strcmp(findXMLAttValu(root, "state"), "Ok"))
587  {
588  // Dome options is dome parks or both and dome is parking.
589  if ((DomeClosedLockT[2].s == ISS_ON || DomeClosedLockT[3].s == ISS_ON) && !IsLocked && !IsParked)
590  {
591  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
592  {
593  const char * elemName = findXMLAttValu(ep, "name");
594  if (( (!strcmp(elemName, "SHUTTER_CLOSE") || !strcmp(elemName, "PARK"))
595  && !strcmp(pcdataXMLEle(ep), "On")))
596  {
598  Park();
599  LOG_INFO("Dome is closing, parking mount...");
600  }
601  }
602  }
603  } // Dome is changing state and Dome options is lock or both. d
604  else
605 #endif
606  if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
607  {
608  bool prevState = IsLocked;
609  for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
610  {
611  const char *elemName = findXMLAttValu(ep, "name");
612 
613  if (!IsLocked && (!strcmp(elemName, "PARK")) && !strcmp(pcdataXMLEle(ep), "On"))
614  IsLocked = true;
615  else if (IsLocked && (!strcmp(elemName, "UNPARK")) && !strcmp(pcdataXMLEle(ep), "On"))
616  IsLocked = false;
617  }
618  if (prevState != IsLocked && (DomePolicyS[DOME_LOCKS].s == ISS_ON))
619  LOGF_INFO("Dome status changed. Lock is set to: %s", IsLocked ? "locked" : "unlock");
620  }
621  return true;
622  }
623  }
624 
625  return DefaultDevice::ISSnoopDevice(root);
626 }
627 
628 void Telescope::triggerSnoop(const char *driverName, const char *snoopedProp)
629 {
630  LOGF_DEBUG("Active Snoop, driver: %s, property: %s", driverName, snoopedProp);
631  IDSnoopDevice(driverName, snoopedProp);
632 }
633 
635 {
636  return telescopeConnection;
637 }
638 
639 void Telescope::setTelescopeConnection(const uint8_t &value)
640 {
642 
643  if (value == 0 || (mask & value) == 0)
644  {
645  DEBUGF(Logger::DBG_ERROR, "Invalid connection mode %d", value);
646  return;
647  }
648 
649  telescopeConnection = value;
650 }
651 
653 {
655 
658 
659  // Ensure that we only save valid locations
660  if (HasLocation() && (LocationN[LOCATION_LONGITUDE].value != 0 || LocationN[LOCATION_LATITUDE].value != 0))
662 
663  if (!HasDefaultScopeConfig())
664  {
665  if (ScopeParametersNP.s == IPS_OK)
667  if (ScopeConfigNameTP.s == IPS_OK)
669  }
670 
671  if (CanGOTO())
673 
674  if (SlewRateS != nullptr)
676  if (HasPECState())
678  if (HasTrackMode())
680  if (HasTrackRate())
682 
683  controller->saveConfigItems(fp);
687 
688  return true;
689 }
690 
691 void Telescope::NewRaDec(double ra, double dec)
692 {
693  switch (TrackState)
694  {
695  case SCOPE_PARKED:
696  case SCOPE_IDLE:
697  EqNP.s = IPS_IDLE;
698  break;
699 
700  case SCOPE_SLEWING:
701  case SCOPE_PARKING:
702  EqNP.s = IPS_BUSY;
703  break;
704 
705  case SCOPE_TRACKING:
706  EqNP.s = IPS_OK;
707  break;
708  }
709 
711  {
715  IDSetSwitch(&TrackStateSP, nullptr);
716  }
718  {
722  IDSetSwitch(&TrackStateSP, nullptr);
723  }
724 
725  if (std::abs(EqN[AXIS_RA].value - ra) > EQ_NOTIFY_THRESHOLD ||
726  std::abs(EqN[AXIS_DE].value - dec) > EQ_NOTIFY_THRESHOLD ||
727  EqNP.s != lastEqState)
728  {
729  EqN[AXIS_RA].value = ra;
730  EqN[AXIS_DE].value = dec;
731  lastEqState = EqNP.s;
732  IDSetNumber(&EqNP, nullptr);
733  }
734 }
735 
736 bool Telescope::Sync(double ra, double dec)
737 {
738  INDI_UNUSED(ra);
739  INDI_UNUSED(dec);
740  // if we get here, our mount doesn't support sync
741  DEBUG(Logger::DBG_ERROR, "Telescope does not support Sync.");
742  return false;
743 }
744 
746 {
747  INDI_UNUSED(dir);
748  INDI_UNUSED(command);
749  DEBUG(Logger::DBG_ERROR, "Telescope does not support North/South motion.");
752  IDSetSwitch(&MovementNSSP, nullptr);
753  return false;
754 }
755 
757 {
758  INDI_UNUSED(dir);
759  INDI_UNUSED(command);
760  DEBUG(Logger::DBG_ERROR, "Telescope does not support West/East motion.");
763  IDSetSwitch(&MovementWESP, nullptr);
764  return false;
765 }
766 
767 /**************************************************************************************
768 ** Process Text properties
769 ***************************************************************************************/
770 bool Telescope::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
771 {
772  // first check if it's for our device
773  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
774  {
775  if (!strcmp(name, TimeTP.name))
776  {
777  int utcindex = IUFindIndex("UTC", names, n);
778  int offsetindex = IUFindIndex("OFFSET", names, n);
779 
780  return processTimeInfo(texts[utcindex], texts[offsetindex]);
781  }
782 
783  if (!strcmp(name, ActiveDeviceTP.name))
784  {
786  IUUpdateText(&ActiveDeviceTP, texts, names, n);
787  // Update client display
788  IDSetText(&ActiveDeviceTP, nullptr);
789 
790  IDSnoopDevice(ActiveDeviceT[0].text, "GEOGRAPHIC_COORD");
791  IDSnoopDevice(ActiveDeviceT[0].text, "TIME_UTC");
792 
793  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_PARK");
794  IDSnoopDevice(ActiveDeviceT[1].text, "DOME_SHUTTER");
795  return true;
796  }
797 
798  if (name && std::string(name) == "SCOPE_CONFIG_NAME")
799  {
801  IUUpdateText(&ScopeConfigNameTP, texts, names, n);
802  IDSetText(&ScopeConfigNameTP, nullptr);
804  return true;
805  }
806  }
807 
808  controller->ISNewText(dev, name, texts, names, n);
809 
810  return DefaultDevice::ISNewText(dev, name, texts, names, n);
811 }
812 
813 /**************************************************************************************
814 **
815 ***************************************************************************************/
816 bool Telescope::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
817 {
818  // first check if it's for our device
819  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
820  {
822  // Goto & Sync for Equatorial Coords
824  if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
825  {
826  // this is for us, and it is a goto
827  bool rc = false;
828  double ra = -1;
829  double dec = -100;
830 
831  for (int x = 0; x < n; x++)
832  {
833  INumber *eqp = IUFindNumber(&EqNP, names[x]);
834  if (eqp == &EqN[AXIS_RA])
835  {
836  ra = values[x];
837  }
838  else if (eqp == &EqN[AXIS_DE])
839  {
840  dec = values[x];
841  }
842  }
843  if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
844  {
845  // Check if it is already parked.
846  if (CanPark())
847  {
848  if (isParked())
849  {
851  "Please unpark the mount before issuing any motion/sync commands.");
852  EqNP.s = lastEqState = IPS_IDLE;
853  IDSetNumber(&EqNP, nullptr);
854  return false;
855  }
856  }
857 
858  // Check if it can sync
859  if (CanSync())
860  {
861  ISwitch *sw;
862  sw = IUFindSwitch(&CoordSP, "SYNC");
863  if ((sw != nullptr) && (sw->s == ISS_ON))
864  {
865  rc = Sync(ra, dec);
866  if (rc)
867  EqNP.s = lastEqState = IPS_OK;
868  else
869  EqNP.s = lastEqState = IPS_ALERT;
870  IDSetNumber(&EqNP, nullptr);
871  return rc;
872  }
873  }
874 
875  bool doFlip = false;
876  if (CanFlip()){
877  ISwitch *sw;
878  sw = IUFindSwitch(&CoordSP, "FLIP");
879  if ((sw != nullptr) && (sw->s == ISS_ON))
880  {
881  doFlip = true;
882  }
883  }
884 
885  // Remember Track State
887  // Issue GOTO/Flip
888  if(doFlip)
889  {
890  rc = Flip(ra, dec);
891  } else {
892  rc = Goto(ra, dec);
893  }
894  if (rc)
895  {
896  EqNP.s = lastEqState = IPS_BUSY;
897  // Now fill in target co-ords, so domes can start turning
898  TargetN[AXIS_RA].value = ra;
899  TargetN[AXIS_DE].value = dec;
900  IDSetNumber(&TargetNP, nullptr);
901  }
902  else
903  {
904  EqNP.s = lastEqState = IPS_ALERT;
905  }
906  IDSetNumber(&EqNP, nullptr);
907  }
908  return rc;
909  }
910 
912  // Geographic Coords
914  if (strcmp(name, "GEOGRAPHIC_COORD") == 0)
915  {
916  int latindex = IUFindIndex("LAT", names, n);
917  int longindex = IUFindIndex("LONG", names, n);
918  int elevationindex = IUFindIndex("ELEV", names, n);
919 
920  if (latindex == -1 || longindex == -1 || elevationindex == -1)
921  {
923  IDSetNumber(&LocationNP, "Location data missing or corrupted.");
924  }
925 
926  double targetLat = values[latindex];
927  double targetLong = values[longindex];
928  double targetElev = values[elevationindex];
929 
930  return processLocationInfo(targetLat, targetLong, targetElev);
931  }
932 
934  // Telescope Info
936  if (strcmp(name, "TELESCOPE_INFO") == 0)
937  {
939 
940  IUUpdateNumber(&ScopeParametersNP, values, names, n);
941  IDSetNumber(&ScopeParametersNP, nullptr);
943  return true;
944  }
945 
947  // Park Position
949  if (strcmp(name, ParkPositionNP.name) == 0)
950  {
951  double axis1 = std::numeric_limits<double>::quiet_NaN(), axis2 = std::numeric_limits<double>::quiet_NaN();
952  for (int x = 0; x < n; x++)
953  {
954  INumber *parkPosAxis = IUFindNumber(&ParkPositionNP, names[x]);
955  if (parkPosAxis == &ParkPositionN[AXIS_RA])
956  {
957  axis1 = values[x];
958  }
959  else if (parkPosAxis == &ParkPositionN[AXIS_DE])
960  {
961  axis2 = values[x];
962  }
963  }
964 
965  if (std::isnan(axis1) == false && std::isnan(axis2) == false)
966  {
967  bool rc = false;
968 
969  rc = SetParkPosition(axis1, axis2);
970 
971  if (rc)
972  {
973  IUUpdateNumber(&ParkPositionNP, values, names, n);
974  Axis1ParkPosition = ParkPositionN[AXIS_RA].value;
975  Axis2ParkPosition = ParkPositionN[AXIS_DE].value;
976  }
977 
978  ParkPositionNP.s = rc ? IPS_OK : IPS_ALERT;
979  }
980  else
982 
983  IDSetNumber(&ParkPositionNP, nullptr);
984  return true;
985  }
986 
988  // Track Rate
990  if (strcmp(name, TrackRateNP.name) == 0)
991  {
992  double preAxis1 = TrackRateN[AXIS_RA].value, preAxis2 = TrackRateN[AXIS_DE].value;
993  bool rc = (IUUpdateNumber(&TrackRateNP, values, names, n) == 0);
994 
995  if (!rc)
996  {
998  IDSetNumber(&TrackRateNP, nullptr);
999  return false;
1000  }
1001 
1002  if (TrackState == SCOPE_TRACKING && !strcmp(IUFindOnSwitch(&TrackModeSP)->name, "TRACK_CUSTOM"))
1003  {
1004  // Check that we do not abruplty change positive tracking rates to negative ones.
1005  // tracking must be stopped first.
1006  // Give warning is tracking sign would cause a reverse in direction
1007  if ( (preAxis1 * TrackRateN[AXIS_RA].value < 0) || (preAxis2 * TrackRateN[AXIS_DE].value < 0) )
1008  {
1009  LOG_ERROR("Cannot reverse tracking while tracking is engaged. Disengage tracking then try again.");
1010  return false;
1011  }
1012 
1013  // All is fine, ask mount to change tracking rate
1014  rc = SetTrackRate(TrackRateN[AXIS_RA].value, TrackRateN[AXIS_DE].value);
1015 
1016  if (!rc)
1017  {
1018  TrackRateN[AXIS_RA].value = preAxis1;
1019  TrackRateN[AXIS_DE].value = preAxis2;
1020  }
1021  }
1022 
1023  // If we are already tracking but tracking mode is NOT custom
1024  // We just inform the user that it must be set to custom for these values to take
1025  // effect.
1026  if (TrackState == SCOPE_TRACKING && strcmp(IUFindOnSwitch(&TrackModeSP)->name, "TRACK_CUSTOM"))
1027  {
1028  LOG_INFO("Custom tracking rates set. Tracking mode must be set to Custom for these rates to take effect.");
1029  }
1030 
1031  // If mount is NOT tracking, we simply accept whatever valid values for use when mount tracking is engaged.
1032  TrackRateNP.s = rc ? IPS_OK : IPS_ALERT;
1033  IDSetNumber(&TrackRateNP, nullptr);
1034  return true;
1035  }
1036  }
1037 
1038  return DefaultDevice::ISNewNumber(dev, name, values, names, n);
1039 }
1040 
1041 /**************************************************************************************
1042 **
1043 ***************************************************************************************/
1044 bool Telescope::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1045 {
1046  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1047  {
1048  // This one is for us
1049  if (!strcmp(name, CoordSP.name))
1050  {
1051  // client is telling us what to do with co-ordinate requests
1052  CoordSP.s = IPS_OK;
1053  IUUpdateSwitch(&CoordSP, states, names, n);
1054  // Update client display
1055  IDSetSwitch(&CoordSP, nullptr);
1056  return true;
1057  }
1058 
1060  // Slew Rate
1062  if (!strcmp(name, SlewRateSP.name))
1063  {
1064  int preIndex = IUFindOnSwitchIndex(&SlewRateSP);
1065  IUUpdateSwitch(&SlewRateSP, states, names, n);
1066  int nowIndex = IUFindOnSwitchIndex(&SlewRateSP);
1067  if (SetSlewRate(nowIndex) == false)
1068  {
1070  SlewRateS[preIndex].s = ISS_ON;
1072  }
1073  else
1074  SlewRateSP.s = IPS_OK;
1075  IDSetSwitch(&SlewRateSP, nullptr);
1076  return true;
1077  }
1078 
1080  // Parking
1082  if (!strcmp(name, ParkSP.name))
1083  {
1084  if (TrackState == SCOPE_PARKING)
1085  {
1087  ParkSP.s = IPS_ALERT;
1088  Abort();
1089  LOG_INFO("Parking/Unparking aborted.");
1090  IDSetSwitch(&ParkSP, nullptr);
1091  return true;
1092  }
1093 
1094  int preIndex = IUFindOnSwitchIndex(&ParkSP);
1095  IUUpdateSwitch(&ParkSP, states, names, n);
1096 
1097  bool toPark = (ParkS[0].s == ISS_ON);
1098 
1099  if (toPark == false && TrackState != SCOPE_PARKED)
1100  {
1102  ParkS[1].s = ISS_ON;
1103  ParkSP.s = IPS_IDLE;
1104  LOG_INFO("Telescope already unparked.");
1105  IsParked = false;
1106  IDSetSwitch(&ParkSP, nullptr);
1107  return true;
1108  }
1109 
1110  if (toPark == false && isLocked())
1111  {
1113  ParkS[0].s = ISS_ON;
1114  ParkSP.s = IPS_ALERT;
1115  LOG_WARN("Cannot unpark mount when dome is locking. See: Dome Policy in options tab.");
1116  IsParked = true;
1117  IDSetSwitch(&ParkSP, nullptr);
1118  return true;
1119  }
1120 
1121  if (toPark && TrackState == SCOPE_PARKED)
1122  {
1124  ParkS[0].s = ISS_ON;
1125  ParkSP.s = IPS_IDLE;
1126  LOG_INFO("Telescope already parked.");
1127  IDSetSwitch(&ParkSP, nullptr);
1128  return true;
1129  }
1130 
1132 
1134  bool rc = toPark ? Park() : UnPark();
1135  if (rc)
1136  {
1137  if (TrackState == SCOPE_PARKING)
1138  {
1139  ParkS[0].s = toPark ? ISS_ON : ISS_OFF;
1140  ParkS[1].s = toPark ? ISS_OFF : ISS_ON;
1141  ParkSP.s = IPS_BUSY;
1142  }
1143  else
1144  {
1145  ParkS[0].s = toPark ? ISS_ON : ISS_OFF;
1146  ParkS[1].s = toPark ? ISS_OFF : ISS_ON;
1147  ParkSP.s = IPS_OK;
1148  }
1149  }
1150  else
1151  {
1152  ParkS[preIndex].s = ISS_ON;
1153  ParkSP.s = IPS_ALERT;
1154  }
1155 
1156  IDSetSwitch(&ParkSP, nullptr);
1157  return true;
1158  }
1159 
1161  // NS Motion
1163  if (!strcmp(name, MovementNSSP.name))
1164  {
1165  // Check if it is already parked.
1166  if (CanPark())
1167  {
1168  if (isParked())
1169  {
1171  "Please unpark the mount before issuing any motion/sync commands.");
1173  IDSetSwitch(&MovementNSSP, nullptr);
1174  return false;
1175  }
1176  }
1177 
1178  IUUpdateSwitch(&MovementNSSP, states, names, n);
1179 
1180  int current_motion = IUFindOnSwitchIndex(&MovementNSSP);
1181 
1182  // if same move requested, return
1183  if (MovementNSSP.s == IPS_BUSY && current_motion == last_ns_motion)
1184  return true;
1185 
1186  // Time to stop motion
1187  if (current_motion == -1 || (last_ns_motion != -1 && current_motion != last_ns_motion))
1188  {
1189  auto targetDirection = last_ns_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1190  if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1191  targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1192 
1193  if (MoveNS(targetDirection, MOTION_STOP))
1194  {
1197  last_ns_motion = -1;
1198  // Update Target when stopped so that domes can track
1199  TargetN[AXIS_RA].value = EqN[AXIS_RA].value;
1200  TargetN[AXIS_DE].value = EqN[AXIS_DE].value;
1201  IDSetNumber(&TargetNP, nullptr);
1202  }
1203  else
1205  }
1206  else
1207  {
1210 
1211  auto targetDirection = current_motion == 0 ? DIRECTION_NORTH : DIRECTION_SOUTH;
1212  if (ReverseMovementSP[REVERSE_NS].getState() == ISS_ON)
1213  targetDirection = targetDirection == DIRECTION_NORTH ? DIRECTION_SOUTH : DIRECTION_NORTH;
1214 
1215  if (MoveNS(targetDirection, MOTION_START))
1216  {
1218  last_ns_motion = targetDirection;
1219  }
1220  else
1221  {
1224  last_ns_motion = -1;
1225  }
1226  }
1227 
1228  IDSetSwitch(&MovementNSSP, nullptr);
1229 
1230  return true;
1231  }
1232 
1234  // WE Motion
1236  if (!strcmp(name, MovementWESP.name))
1237  {
1238  // Check if it is already parked.
1239  if (CanPark())
1240  {
1241  if (isParked())
1242  {
1244  "Please unpark the mount before issuing any motion/sync commands.");
1246  IDSetSwitch(&MovementWESP, nullptr);
1247  return false;
1248  }
1249  }
1250 
1251  IUUpdateSwitch(&MovementWESP, states, names, n);
1252 
1253  int current_motion = IUFindOnSwitchIndex(&MovementWESP);
1254 
1255  // if same move requested, return
1256  if (MovementWESP.s == IPS_BUSY && current_motion == last_we_motion)
1257  return true;
1258 
1259  // Time to stop motion
1260  if (current_motion == -1 || (last_we_motion != -1 && current_motion != last_we_motion))
1261  {
1262  auto targetDirection = last_we_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1263  if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1264  targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1265  if (MoveWE(targetDirection, MOTION_STOP))
1266  {
1269  last_we_motion = -1;
1270  // Update Target when stopped so that domes can track
1271  TargetN[AXIS_RA].value = EqN[AXIS_RA].value;
1272  TargetN[AXIS_DE].value = EqN[AXIS_DE].value;
1273  IDSetNumber(&TargetNP, nullptr);
1274  }
1275  else
1277  }
1278  else
1279  {
1282 
1283  auto targetDirection = current_motion == 0 ? DIRECTION_WEST : DIRECTION_EAST;
1284  if (ReverseMovementSP[REVERSE_WE].getState() == ISS_ON)
1285  targetDirection = targetDirection == DIRECTION_EAST ? DIRECTION_WEST : DIRECTION_EAST;
1286 
1287  if (MoveWE(targetDirection, MOTION_START))
1288  {
1290  last_we_motion = targetDirection;
1291  }
1292  else
1293  {
1296  last_we_motion = -1;
1297  }
1298  }
1299 
1300  IDSetSwitch(&MovementWESP, nullptr);
1301 
1302  return true;
1303  }
1304 
1306  // WE or NS Reverse Motion
1308  if (ReverseMovementSP.isNameMatch(name))
1309  {
1310  ReverseMovementSP.update(states, names, n);
1314  return true;
1315  }
1316 
1318  // Abort Motion
1320  if (!strcmp(name, AbortSP.name))
1321  {
1323 
1324  if (Abort())
1325  {
1326  AbortSP.s = IPS_OK;
1327 
1328  if (ParkSP.s == IPS_BUSY)
1329  {
1331  ParkSP.s = IPS_ALERT;
1332  IDSetSwitch(&ParkSP, nullptr);
1333 
1334  LOG_INFO("Parking aborted.");
1335  }
1336  if (EqNP.s == IPS_BUSY)
1337  {
1338  EqNP.s = lastEqState = IPS_IDLE;
1339  IDSetNumber(&EqNP, nullptr);
1340  LOG_INFO("Slew/Track aborted.");
1341  }
1342  if (MovementWESP.s == IPS_BUSY)
1343  {
1346  IDSetSwitch(&MovementWESP, nullptr);
1347  }
1348  if (MovementNSSP.s == IPS_BUSY)
1349  {
1352  IDSetSwitch(&MovementNSSP, nullptr);
1353  }
1354 
1356 
1357  // JM 2017-07-28: Abort shouldn't affect tracking state. It should affect motion and that's it.
1358  //if (TrackState != SCOPE_PARKED)
1359  //TrackState = SCOPE_IDLE;
1360  // For Idle, Tracking, Parked state, we do not change its status, it should remain as is.
1361  // For Slewing & Parking, state should go back to last rememberd state.
1363  {
1365  }
1366  }
1367  else
1368  AbortSP.s = IPS_ALERT;
1369 
1370  IDSetSwitch(&AbortSP, nullptr);
1371 
1372  return true;
1373  }
1374 
1376  // Track Mode
1378  if (!strcmp(name, TrackModeSP.name))
1379  {
1380  int prevIndex = IUFindOnSwitchIndex(&TrackModeSP);
1381  IUUpdateSwitch(&TrackModeSP, states, names, n);
1382  int currIndex = IUFindOnSwitchIndex(&TrackModeSP);
1383  // If same as previous index, or if scope is already idle, then just update switch and return. No commands are sent to the mount.
1384  if (prevIndex == currIndex || TrackState == SCOPE_IDLE)
1385  {
1386  TrackModeSP.s = IPS_OK;
1387  IDSetSwitch(&TrackModeSP, nullptr);
1388  return true;
1389  }
1390 
1391  if (TrackState == SCOPE_PARKED)
1392  {
1393  DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before changing track mode.");
1394  return false;
1395  }
1396 
1397  bool rc = SetTrackMode(currIndex);
1398  if (rc)
1399  TrackModeSP.s = IPS_OK;
1400  else
1401  {
1403  TrackModeS[prevIndex].s = ISS_ON;
1405  }
1406  IDSetSwitch(&TrackModeSP, nullptr);
1407  return false;
1408  }
1409 
1411  // Track State
1413  if (!strcmp(name, TrackStateSP.name))
1414  {
1415  int previousState = IUFindOnSwitchIndex(&TrackStateSP);
1416  IUUpdateSwitch(&TrackStateSP, states, names, n);
1417  int targetState = IUFindOnSwitchIndex(&TrackStateSP);
1418 
1419  if (previousState == targetState)
1420  {
1421  IDSetSwitch(&TrackStateSP, nullptr);
1422  return true;
1423  }
1424 
1425  if (TrackState == SCOPE_PARKED)
1426  {
1427  DEBUG(Logger::DBG_WARNING, "Telescope is Parked, Unpark before tracking.");
1428  return false;
1429  }
1430 
1431  bool rc = SetTrackEnabled((targetState == TRACK_ON) ? true : false);
1432 
1433  if (rc)
1434  {
1435  TrackState = (targetState == TRACK_ON) ? SCOPE_TRACKING : SCOPE_IDLE;
1436 
1437  TrackStateSP.s = (targetState == TRACK_ON) ? IPS_BUSY : IPS_IDLE;
1438 
1439  TrackStateS[TRACK_ON].s = (targetState == TRACK_ON) ? ISS_ON : ISS_OFF;
1440  TrackStateS[TRACK_OFF].s = (targetState == TRACK_ON) ? ISS_OFF : ISS_ON;
1441  }
1442  else
1443  {
1446  TrackStateS[previousState].s = ISS_ON;
1447  }
1448 
1449  IDSetSwitch(&TrackStateSP, nullptr);
1450  return true;
1451  }
1452 
1454  // Park Options
1456  if (!strcmp(name, ParkOptionSP.name))
1457  {
1458  IUUpdateSwitch(&ParkOptionSP, states, names, n);
1459  int index = IUFindOnSwitchIndex(&ParkOptionSP);
1460  if (index == -1)
1461  return false;
1462 
1464 
1465  bool rc = false;
1466 
1468  MovementWESP.s == IPS_BUSY)
1469  {
1470  LOG_INFO("Can not change park position while slewing or already parked...");
1472  IDSetSwitch(&ParkOptionSP, nullptr);
1473  return false;
1474  }
1475 
1476  switch (index)
1477  {
1478  case PARK_CURRENT:
1479  rc = SetCurrentPark();
1480  break;
1481  case PARK_DEFAULT:
1482  rc = SetDefaultPark();
1483  break;
1484  case PARK_WRITE_DATA:
1485  rc = WriteParkData();
1486  if (rc)
1487  LOG_INFO("Saved Park Status/Position.");
1488  else
1489  DEBUG(Logger::DBG_WARNING, "Can not save Park Status/Position.");
1490  break;
1491  case PARK_PURGE_DATA:
1492  rc = PurgeParkData();
1493  if (rc)
1494  LOG_INFO("Park data purged.");
1495  else
1496  DEBUG(Logger::DBG_WARNING, "Can not purge Park Status/Position.");
1497  break;
1498  }
1499 
1500  ParkOptionSP.s = rc ? IPS_OK : IPS_ALERT;
1501  IDSetSwitch(&ParkOptionSP, nullptr);
1502 
1503  return true;
1504  }
1505 
1507  // Parking Dome Policy
1509  if (!strcmp(name, DomePolicySP.name))
1510  {
1511  IUUpdateSwitch(&DomePolicySP, states, names, n);
1512  if (DomePolicyS[DOME_IGNORED].s == ISS_ON)
1513  LOG_INFO("Dome Policy set to: Dome ignored. Mount can park or unpark regardless of dome parking state.");
1514  else
1515  LOG_WARN("Dome Policy set to: Dome locks. This prevents the mount from unparking when dome is parked.");
1516 #if 0
1517  else if (!strcmp(names[0], DomeClosedLockT[2].name))
1518  LOG_INFO("Warning: Dome parking policy set to: Dome parks. This tells "
1519  "scope to park if dome is parking. This will disable the locking "
1520  "for dome parking, EVEN IF MOUNT PARKING FAILS");
1521  else if (!strcmp(names[0], DomeClosedLockT[3].name))
1522  LOG_INFO("Warning: Dome parking policy set to: Both. This disallows the "
1523  "scope from unparking when dome is parked, and tells scope to "
1524  "park if dome is parking. This will disable the locking for dome "
1525  "parking, EVEN IF MOUNT PARKING FAILS.");
1526 #endif
1527  DomePolicySP.s = IPS_OK;
1528  IDSetSwitch(&DomePolicySP, nullptr);
1529  triggerSnoop(ActiveDeviceT[1].text, "DOME_PARK");
1530  return true;
1531  }
1532 
1534  // Simulate Pier Side
1535  // This ia a major change to the design of the simulated scope, it might not handle changes on the fly
1537  if (!strcmp(name, SimulatePierSideSP.name))
1538  {
1539  IUUpdateSwitch(&SimulatePierSideSP, states, names, n);
1541  if (index == -1)
1542  {
1544  LOG_INFO("Cannot determine whether pier side simulation should be switched on or off.");
1545  IDSetSwitch(&SimulatePierSideSP, nullptr);
1546  return false;
1547  }
1548 
1549  bool pierSideEnabled = index == 0;
1550 
1551  LOGF_INFO("Simulating Pier Side %s.", (pierSideEnabled ? "enabled" : "disabled"));
1552 
1553  setSimulatePierSide(pierSideEnabled);
1554  if (pierSideEnabled)
1555  {
1556  // set the pier side from the current Ra
1557  // assumes we haven't tracked across the meridian
1559  }
1560  return true;
1561  }
1562 
1564  // Joystick Motion Control Mode
1566  if (!strcmp(name, MotionControlModeTP.name))
1567  {
1568  IUUpdateSwitch(&MotionControlModeTP, states, names, n);
1570  IDSetSwitch(&MotionControlModeTP, nullptr);
1572  LOG_INFO("Motion control is set to 4-way joystick.");
1574  LOG_INFO("Motion control is set to 2 separate axes.");
1575  else
1576  DEBUGF(Logger::DBG_WARNING, "Motion control is set to unknown value %d!", n);
1577  return true;
1578  }
1579 
1581  // Joystick Lock Axis
1583  if (!strcmp(name, LockAxisSP.name))
1584  {
1585  IUUpdateSwitch(&LockAxisSP, states, names, n);
1586  LockAxisSP.s = IPS_OK;
1587  IDSetSwitch(&LockAxisSP, nullptr);
1588  if (LockAxisS[AXIS_RA].s == ISS_ON)
1589  LOG_INFO("Joystick motion is locked to West/East axis only.");
1590  else if (LockAxisS[AXIS_DE].s == ISS_ON)
1591  LOG_INFO("Joystick motion is locked to North/South axis only.");
1592  else
1593  LOG_INFO("Joystick motion is unlocked.");
1594  return true;
1595  }
1596 
1598  // Scope Apply Config
1600  if (name && std::string(name) == "APPLY_SCOPE_CONFIG")
1601  {
1602  IUUpdateSwitch(&ScopeConfigsSP, states, names, n);
1603  bool rc = LoadScopeConfig();
1604  ScopeConfigsSP.s = (rc ? IPS_OK : IPS_ALERT);
1605  IDSetSwitch(&ScopeConfigsSP, nullptr);
1606  return true;
1607  }
1608  }
1609 
1610  bool rc = controller->ISNewSwitch(dev, name, states, names, n);
1611  if (rc)
1612  {
1613  auto useJoystick = getSwitch("USEJOYSTICK");
1614  if (useJoystick && useJoystick[0].getState() == ISS_ON)
1615  {
1618  }
1619  else
1620  {
1623  }
1624 
1625  }
1626 
1627  // Nobody has claimed this, so, ignore it
1628  return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
1629 }
1630 
1632 {
1633  if (telescopeConnection > 0)
1634  {
1637  else if (getActiveConnection() == tcpConnection)
1639  }
1640 
1641  return Handshake();
1642 }
1643 
1645 {
1646  /* Test connection */
1647  return ReadScopeStatus();
1648 }
1649 
1651 {
1652  if (isConnected())
1653  {
1654  bool rc;
1655 
1656  rc = ReadScopeStatus();
1657 
1658  if (!rc)
1659  {
1660  // read was not good
1661  EqNP.s = lastEqState = IPS_ALERT;
1662  IDSetNumber(&EqNP, nullptr);
1663  }
1664 
1666  }
1667 }
1668 
1669 bool Telescope::Flip(double ra, double dec)
1670 {
1671  INDI_UNUSED(ra);
1672  INDI_UNUSED(dec);
1673 
1674  DEBUG(Logger::DBG_WARNING, "Flip is not supported.");
1675  return false;
1676 }
1677 
1678 bool Telescope::Goto(double ra, double dec)
1679 {
1680  INDI_UNUSED(ra);
1681  INDI_UNUSED(dec);
1682 
1683  DEBUG(Logger::DBG_WARNING, "GOTO is not supported.");
1684  return false;
1685 }
1686 
1688 {
1689  DEBUG(Logger::DBG_WARNING, "Abort is not supported.");
1690  return false;
1691 }
1692 
1694 {
1695  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1696  return false;
1697 }
1698 
1700 {
1701  DEBUG(Logger::DBG_WARNING, "UnParking is not supported.");
1702  return false;
1703 }
1704 
1705 bool Telescope::SetTrackMode(uint8_t mode)
1706 {
1707  INDI_UNUSED(mode);
1708  DEBUG(Logger::DBG_WARNING, "Tracking mode is not supported.");
1709  return false;
1710 }
1711 
1712 bool Telescope::SetTrackRate(double raRate, double deRate)
1713 {
1714  INDI_UNUSED(raRate);
1715  INDI_UNUSED(deRate);
1716  DEBUG(Logger::DBG_WARNING, "Custom tracking rates is not supported.");
1717  return false;
1718 }
1719 
1720 bool Telescope::SetTrackEnabled(bool enabled)
1721 {
1722  INDI_UNUSED(enabled);
1723  DEBUG(Logger::DBG_WARNING, "Tracking state is not supported.");
1724  return false;
1725 }
1726 
1727 int Telescope::AddTrackMode(const char *name, const char *label, bool isDefault)
1728 {
1729  TrackModeS = (TrackModeS == nullptr) ? static_cast<ISwitch *>(malloc(sizeof(ISwitch))) :
1730  static_cast<ISwitch *>(realloc(TrackModeS, (TrackModeSP.nsp + 1) * sizeof(ISwitch)));
1731 
1732  IUFillSwitch(&TrackModeS[TrackModeSP.nsp], name, label, isDefault ? ISS_ON : ISS_OFF);
1733 
1735  TrackModeSP.nsp++;
1736 
1737  return (TrackModeSP.nsp - 1);
1738 }
1739 
1741 {
1742  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1743  return false;
1744 }
1745 
1747 {
1748  DEBUG(Logger::DBG_WARNING, "Parking is not supported.");
1749  return false;
1750 }
1751 
1752 bool Telescope::processTimeInfo(const char *utc, const char *offset)
1753 {
1754  struct ln_date utc_date;
1755  double utc_offset = 0;
1756 
1757  if (extractISOTime(utc, &utc_date) == -1)
1758  {
1759  TimeTP.s = IPS_ALERT;
1760  IDSetText(&TimeTP, "Date/Time is invalid: %s.", utc);
1761  return false;
1762  }
1763 
1764  utc_offset = atof(offset);
1765 
1766  if (updateTime(&utc_date, utc_offset))
1767  {
1768  IUSaveText(&TimeT[0], utc);
1769  IUSaveText(&TimeT[1], offset);
1770  TimeTP.s = IPS_OK;
1771  IDSetText(&TimeTP, nullptr);
1772  return true;
1773  }
1774  else
1775  {
1776  TimeTP.s = IPS_ALERT;
1777  IDSetText(&TimeTP, nullptr);
1778  return false;
1779  }
1780 }
1781 
1782 bool Telescope::processLocationInfo(double latitude, double longitude, double elevation)
1783 {
1784  // Do not update if not necessary
1785  // JM 2021-05-26: This can sometimes be problematic. Let child driver deals with duplicate requests.
1786 #if 0
1787  if (latitude == LocationN[LOCATION_LATITUDE].value && longitude == LocationN[LOCATION_LONGITUDE].value &&
1788  elevation == LocationN[LOCATION_ELEVATION].value)
1789  {
1790  LocationNP.s = IPS_OK;
1791  IDSetNumber(&LocationNP, nullptr);
1792  return true;
1793  }
1794  else
1795 #endif
1796  if (latitude == 0 && longitude == 0)
1797  {
1798  LOG_DEBUG("Silently ignoring invalid latitude and longitude.");
1799  LocationNP.s = IPS_IDLE;
1800  IDSetNumber(&LocationNP, nullptr);
1801  return false;
1802  }
1803 
1804  if (updateLocation(latitude, longitude, elevation))
1805  {
1806  LocationNP.s = IPS_OK;
1807  LocationN[LOCATION_LATITUDE].value = latitude;
1808  LocationN[LOCATION_LONGITUDE].value = longitude;
1809  LocationN[LOCATION_ELEVATION].value = elevation;
1810  // Update client display
1811  IDSetNumber(&LocationNP, nullptr);
1812 
1813  // Always save geographic coord config immediately.
1814  saveConfig(true, "GEOGRAPHIC_COORD");
1815 
1816  updateObserverLocation(latitude, longitude, elevation);
1817 
1818  return true;
1819  }
1820  else
1821  {
1823  // Update client display
1824  IDSetNumber(&LocationNP, nullptr);
1825 
1826  return false;
1827  }
1828 }
1829 
1830 bool Telescope::updateTime(ln_date *utc, double utc_offset)
1831 {
1832  INDI_UNUSED(utc);
1833  INDI_UNUSED(utc_offset);
1834  return true;
1835 }
1836 
1837 bool Telescope::updateLocation(double latitude, double longitude, double elevation)
1838 {
1839  INDI_UNUSED(latitude);
1840  INDI_UNUSED(longitude);
1841  INDI_UNUSED(elevation);
1842  return true;
1843 }
1844 
1845 void Telescope::updateObserverLocation(double latitude, double longitude, double elevation)
1846 {
1847  m_Location.longitude = longitude;
1848  m_Location.latitude = latitude;
1849  m_Location.elevation = elevation;
1850  char lat_str[MAXINDIFORMAT] = {0}, lng_str[MAXINDIFORMAT] = {0};
1851 
1852  // Make display longitude to be in the standard 0 to +180 East, and 0 to -180 West.
1853  // No need to confuse new users with INDI format.
1854  double display_longitude = longitude > 180 ? longitude - 360 : longitude;
1855  fs_sexa(lat_str, m_Location.latitude, 2, 36000);
1856  fs_sexa(lng_str, display_longitude, 2, 36000);
1857  // Choose WGS 84, also known as EPSG:4326 for latitude/longitude ordering
1858  LOGF_INFO("Observer location updated: Latitude %.12s (%.2f) Longitude %.12s (%.2f)", lat_str, m_Location.latitude, lng_str,
1859  display_longitude);
1860 }
1861 
1862 bool Telescope::SetParkPosition(double Axis1Value, double Axis2Value)
1863 {
1864  INDI_UNUSED(Axis1Value);
1865  INDI_UNUSED(Axis2Value);
1866  return true;
1867 }
1868 
1869 void Telescope::SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
1870 {
1871  capability = cap;
1872  nSlewRate = slewRateCount;
1873 
1874  // If both GOTO and SYNC are supported
1875  if (CanGOTO() && CanSync())
1876  IUFillSwitchVector(&CoordSP, CoordS, 3, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1877  ISR_1OFMANY, 60, IPS_IDLE);
1878  // If ONLY GOTO is supported
1879  else if (CanGOTO())
1880  IUFillSwitchVector(&CoordSP, CoordS, 2, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1881  ISR_1OFMANY, 60, IPS_IDLE);
1882  // If ONLY SYNC is supported
1883  else if (CanSync())
1884  {
1885  IUFillSwitch(&CoordS[0], "SYNC", "Sync", ISS_ON);
1886  IUFillSwitchVector(&CoordSP, CoordS, 1, getDeviceName(), "ON_COORD_SET", "On Set", MAIN_CONTROL_TAB, IP_RW,
1887  ISR_1OFMANY, 60, IPS_IDLE);
1888  }
1889 
1890  if (nSlewRate >= 4)
1891  {
1892  free(SlewRateS);
1893  SlewRateS = static_cast<ISwitch *>(malloc(sizeof(ISwitch) * nSlewRate));
1894  //int step = nSlewRate / 4;
1895  for (int i = 0; i < nSlewRate; i++)
1896  {
1897  char name[4];
1898  snprintf(name, 4, "%dx", i + 1);
1899  IUFillSwitch(SlewRateS + i, name, name, ISS_OFF);
1900  }
1901 
1902  // strncpy((SlewRateS + (step * 0))->name, "SLEW_GUIDE", MAXINDINAME);
1903  // strncpy((SlewRateS + (step * 1))->name, "SLEW_CENTERING", MAXINDINAME);
1904  // strncpy((SlewRateS + (step * 2))->name, "SLEW_FIND", MAXINDINAME);
1905  // strncpy((SlewRateS + (nSlewRate - 1))->name, "SLEW_MAX", MAXINDINAME);
1906 
1907  // If number of slew rate is EXACTLY 4, then let's use common labels
1908  if (nSlewRate == 4)
1909  {
1910  strncpy((SlewRateS + (0))->label, "Guide", MAXINDILABEL);
1911  strncpy((SlewRateS + (1))->label, "Centering", MAXINDILABEL);
1912  strncpy((SlewRateS + (2))->label, "Find", MAXINDILABEL);
1913  strncpy((SlewRateS + (3))->label, "Max", MAXINDILABEL);
1914  }
1915 
1916  // By Default we set current Slew Rate to 0.5 of max
1917  (SlewRateS + (nSlewRate / 2))->s = ISS_ON;
1918 
1919  IUFillSwitchVector(&SlewRateSP, SlewRateS, nSlewRate, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate",
1921  }
1922 }
1923 
1925 {
1926  parkDataType = type;
1927 
1928  if (parkDataType != PARK_NONE)
1929  {
1930  switch (parkDataType)
1931  {
1932  case PARK_RA_DEC:
1933  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_RA", "RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
1934  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1935  break;
1936 
1937  case PARK_HA_DEC:
1938  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_HA", "HA (hh:mm:ss)", "%010.6m", -12, 12, 0, 0);
1939  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
1940  break;
1941 
1942  case PARK_AZ_ALT:
1943  IUFillNumber(&ParkPositionN[AXIS_AZ], "PARK_AZ", "AZ D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
1944  IUFillNumber(&ParkPositionN[AXIS_ALT], "PARK_ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
1945  break;
1946 
1947  case PARK_RA_DEC_ENCODER:
1948  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_RA", "RA Encoder", "%.0f", 0, 16777215, 1, 0);
1949  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_DEC", "DEC Encoder", "%.0f", 0, 16777215, 1, 0);
1950  break;
1951 
1952  case PARK_AZ_ALT_ENCODER:
1953  IUFillNumber(&ParkPositionN[AXIS_RA], "PARK_AZ", "AZ Encoder", "%.0f", 0, 16777215, 1, 0);
1954  IUFillNumber(&ParkPositionN[AXIS_DE], "PARK_ALT", "ALT Encoder", "%.0f", 0, 16777215, 1, 0);
1955  break;
1956 
1957  default:
1958  break;
1959  }
1960 
1961  IUFillNumberVector(&ParkPositionNP, ParkPositionN, 2, getDeviceName(), "TELESCOPE_PARK_POSITION",
1962  "Park Position", SITE_TAB, IP_RW, 60, IPS_IDLE);
1963  }
1964 }
1965 
1966 void Telescope::SyncParkStatus(bool isparked)
1967 {
1968  IsParked = isparked;
1970  ParkSP.s = IPS_OK;
1971 
1972  if (IsParked)
1973  {
1974  ParkS[0].s = ISS_ON;
1976  LOG_INFO("Mount is parked.");
1977  }
1978  else
1979  {
1980  ParkS[1].s = ISS_ON;
1982  LOG_INFO("Mount is unparked.");
1983  }
1984 
1985  IDSetSwitch(&ParkSP, nullptr);
1986 }
1987 
1988 void Telescope::SetParked(bool isparked)
1989 {
1990  SyncParkStatus(isparked);
1991 
1992  if (parkDataType != PARK_NONE)
1993  WriteParkData();
1994 }
1995 
1997 {
1998  return IsParked;
1999 }
2000 
2002 {
2003  const char *loadres = LoadParkData();
2004  if (loadres)
2005  {
2006  LOGF_INFO("InitPark: No Park data in file %s: %s", ParkDataFileName.c_str(), loadres);
2007  SyncParkStatus(false);
2008  return false;
2009  }
2010 
2012 
2013  LOGF_DEBUG("InitPark Axis1 %.2f Axis2 %.2f", Axis1ParkPosition, Axis2ParkPosition);
2014  ParkPositionN[AXIS_RA].value = Axis1ParkPosition;
2015  ParkPositionN[AXIS_DE].value = Axis2ParkPosition;
2016  IDSetNumber(&ParkPositionNP, nullptr);
2017 
2018  return true;
2019 }
2020 
2021 const char *Telescope::LoadParkXML()
2022 {
2023  wordexp_t wexp;
2024  FILE *fp = nullptr;
2025  LilXML *lp = nullptr;
2026  static char errmsg[512];
2027 
2028  XMLEle *parkxml = nullptr;
2029  XMLAtt *ap = nullptr;
2030  bool devicefound = false;
2031 
2032  ParkDeviceName = getDeviceName();
2033  ParkstatusXml = nullptr;
2034  ParkdeviceXml = nullptr;
2035  ParkpositionXml = nullptr;
2036  ParkpositionAxis1Xml = nullptr;
2037  ParkpositionAxis2Xml = nullptr;
2038 
2039  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2040  {
2041  wordfree(&wexp);
2042  return "Badly formed filename.";
2043  }
2044 
2045  if (!(fp = fopen(wexp.we_wordv[0], "r")))
2046  {
2047  wordfree(&wexp);
2048  return strerror(errno);
2049  }
2050  wordfree(&wexp);
2051 
2052  lp = newLilXML();
2053 
2054  if (ParkdataXmlRoot)
2055  delXMLEle(ParkdataXmlRoot);
2056 
2057  ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2058  fclose(fp);
2059 
2060  delLilXML(lp);
2061  if (!ParkdataXmlRoot)
2062  return errmsg;
2063 
2064  parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2065 
2066  if (!parkxml)
2067  return "Empty park file.";
2068 
2069  if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2070  {
2071  delXMLEle(parkxml);
2072  return "Not a park data file";
2073  }
2074 
2075  while (parkxml)
2076  {
2077  if (strcmp(tagXMLEle(parkxml), "device"))
2078  {
2079  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2080  continue;
2081  }
2082  ap = findXMLAtt(parkxml, "name");
2083  if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2084  {
2085  devicefound = true;
2086  break;
2087  }
2088  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2089  }
2090 
2091  if (!devicefound)
2092  {
2093  delXMLEle(parkxml);
2094  return "No park data found for this device";
2095  }
2096 
2097  ParkdeviceXml = parkxml;
2098  ParkstatusXml = findXMLEle(parkxml, "parkstatus");
2099  ParkpositionXml = findXMLEle(parkxml, "parkposition");
2100  if (ParkpositionXml)
2101  ParkpositionAxis1Xml = findXMLEle(ParkpositionXml, "axis1position");
2102  if (ParkpositionXml)
2103  ParkpositionAxis2Xml = findXMLEle(ParkpositionXml, "axis2position");
2104 
2105  if (ParkstatusXml == nullptr || ParkpositionAxis1Xml == nullptr || ParkpositionAxis2Xml == nullptr)
2106  {
2107  return "Park data invalid or missing.";
2108  }
2109 
2110  return nullptr;
2111 }
2112 
2114 {
2115  IsParked = false;
2116 
2117  const char *result = LoadParkXML();
2118  if (result != nullptr)
2119  return result;
2120 
2121  if (!strcmp(pcdataXMLEle(ParkstatusXml), "true"))
2122  IsParked = true;
2123 
2124  double axis1Pos = std::numeric_limits<double>::quiet_NaN();
2125  double axis2Pos = std::numeric_limits<double>::quiet_NaN();
2126 
2127  int rc = sscanf(pcdataXMLEle(ParkpositionAxis1Xml), "%lf", &axis1Pos);
2128  if (rc != 1)
2129  {
2130  return "Unable to parse Park Position Axis 1.";
2131  }
2132  rc = sscanf(pcdataXMLEle(ParkpositionAxis2Xml), "%lf", &axis2Pos);
2133  if (rc != 1)
2134  {
2135  return "Unable to parse Park Position Axis 2.";
2136  }
2137 
2138  if (std::isnan(axis1Pos) == false && std::isnan(axis2Pos) == false)
2139  {
2140  Axis1ParkPosition = axis1Pos;
2141  Axis2ParkPosition = axis2Pos;
2142  return nullptr;
2143  }
2144 
2145  return "Failed to parse Park Position.";
2146 }
2147 
2149 {
2150  // We need to refresh parking data in case other devices parking states were updated since we
2151  // read the data the first time.
2152  if (LoadParkXML() != nullptr)
2153  LOG_DEBUG("Failed to refresh parking data.");
2154 
2155  wordexp_t wexp;
2156  FILE *fp = nullptr;
2157  LilXML *lp = nullptr;
2158  static char errmsg[512];
2159 
2160  XMLEle *parkxml = nullptr;
2161  XMLAtt *ap = nullptr;
2162  bool devicefound = false;
2163 
2164  ParkDeviceName = getDeviceName();
2165 
2166  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2167  {
2168  wordfree(&wexp);
2169  return false;
2170  }
2171 
2172  if (!(fp = fopen(wexp.we_wordv[0], "r")))
2173  {
2174  wordfree(&wexp);
2175  LOGF_ERROR("Failed to purge park data: %s", strerror(errno));
2176  return false;
2177  }
2178  wordfree(&wexp);
2179 
2180  lp = newLilXML();
2181 
2182  if (ParkdataXmlRoot)
2183  delXMLEle(ParkdataXmlRoot);
2184 
2185  ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
2186  fclose(fp);
2187 
2188  delLilXML(lp);
2189  if (!ParkdataXmlRoot)
2190  return false;
2191 
2192  parkxml = nextXMLEle(ParkdataXmlRoot, 1);
2193 
2194  if (!parkxml)
2195  return false;
2196 
2197  if (!strcmp(tagXMLEle(parkxml), "parkdata"))
2198  {
2199  delXMLEle(parkxml);
2200  return false;
2201  }
2202 
2203  while (parkxml)
2204  {
2205  if (strcmp(tagXMLEle(parkxml), "device"))
2206  {
2207  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2208  continue;
2209  }
2210  ap = findXMLAtt(parkxml, "name");
2211  if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
2212  {
2213  devicefound = true;
2214  break;
2215  }
2216  parkxml = nextXMLEle(ParkdataXmlRoot, 0);
2217  }
2218 
2219  if (!devicefound)
2220  return false;
2221 
2222  delXMLEle(parkxml);
2223 
2224  ParkstatusXml = nullptr;
2225  ParkdeviceXml = nullptr;
2226  ParkpositionXml = nullptr;
2227  ParkpositionAxis1Xml = nullptr;
2228  ParkpositionAxis2Xml = nullptr;
2229 
2230  wordexp(ParkDataFileName.c_str(), &wexp, 0);
2231  if (!(fp = fopen(wexp.we_wordv[0], "w")))
2232  {
2233  wordfree(&wexp);
2234  LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(), strerror(errno));
2235  return false;
2236  }
2237  prXMLEle(fp, ParkdataXmlRoot, 0);
2238  fclose(fp);
2239  wordfree(&wexp);
2240 
2241  return true;
2242 }
2243 
2245 {
2246  // We need to refresh parking data in case other devices parking states were updated since we
2247  // read the data the first time.
2248  if (LoadParkXML() != nullptr)
2249  LOG_DEBUG("Failed to refresh parking data.");
2250 
2251  wordexp_t wexp;
2252  FILE *fp;
2253  char pcdata[30];
2254  ParkDeviceName = getDeviceName();
2255 
2256  if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
2257  {
2258  wordfree(&wexp);
2259  LOGF_INFO("WriteParkData: can not write file %s: Badly formed filename.",
2260  ParkDataFileName.c_str());
2261  return false;
2262  }
2263 
2264  if (!(fp = fopen(wexp.we_wordv[0], "w")))
2265  {
2266  wordfree(&wexp);
2267  LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(),
2268  strerror(errno));
2269  return false;
2270  }
2271 
2272  if (!ParkdataXmlRoot)
2273  ParkdataXmlRoot = addXMLEle(nullptr, "parkdata");
2274 
2275  if (!ParkdeviceXml)
2276  {
2277  ParkdeviceXml = addXMLEle(ParkdataXmlRoot, "device");
2278  addXMLAtt(ParkdeviceXml, "name", ParkDeviceName);
2279  }
2280 
2281  if (!ParkstatusXml)
2282  ParkstatusXml = addXMLEle(ParkdeviceXml, "parkstatus");
2283  if (!ParkpositionXml)
2284  ParkpositionXml = addXMLEle(ParkdeviceXml, "parkposition");
2285  if (!ParkpositionAxis1Xml)
2286  ParkpositionAxis1Xml = addXMLEle(ParkpositionXml, "axis1position");
2287  if (!ParkpositionAxis2Xml)
2288  ParkpositionAxis2Xml = addXMLEle(ParkpositionXml, "axis2position");
2289 
2290  editXMLEle(ParkstatusXml, (IsParked ? "true" : "false"));
2291 
2292  snprintf(pcdata, sizeof(pcdata), "%lf", Axis1ParkPosition);
2293  editXMLEle(ParkpositionAxis1Xml, pcdata);
2294  snprintf(pcdata, sizeof(pcdata), "%lf", Axis2ParkPosition);
2295  editXMLEle(ParkpositionAxis2Xml, pcdata);
2296 
2297  prXMLEle(fp, ParkdataXmlRoot, 0);
2298  fclose(fp);
2299  wordfree(&wexp);
2300 
2301  return true;
2302 }
2303 
2305 {
2306  return Axis1ParkPosition;
2307 }
2309 {
2310  return Axis1DefaultParkPosition;
2311 }
2313 {
2314  return Axis2ParkPosition;
2315 }
2317 {
2318  return Axis2DefaultParkPosition;
2319 }
2320 
2321 void Telescope::SetAxis1Park(double value)
2322 {
2323  LOGF_DEBUG("Setting Park Axis1 to %.2f", value);
2324  Axis1ParkPosition = value;
2325  ParkPositionN[AXIS_RA].value = value;
2326  IDSetNumber(&ParkPositionNP, nullptr);
2327 }
2328 
2330 {
2331  LOGF_DEBUG("Setting Default Park Axis1 to %.2f", value);
2332  Axis1DefaultParkPosition = value;
2333 }
2334 
2335 void Telescope::SetAxis2Park(double value)
2336 {
2337  LOGF_DEBUG("Setting Park Axis2 to %.2f", value);
2338  Axis2ParkPosition = value;
2339  ParkPositionN[AXIS_DE].value = value;
2340  IDSetNumber(&ParkPositionNP, nullptr);
2341 }
2342 
2344 {
2345  LOGF_DEBUG("Setting Default Park Axis2 to %.2f", value);
2346  Axis2DefaultParkPosition = value;
2347 }
2348 
2350 {
2351  return DomePolicyS[DOME_LOCKS].s == ISS_ON && IsLocked;
2352 }
2353 
2354 bool Telescope::SetSlewRate(int index)
2355 {
2356  INDI_UNUSED(index);
2357  return true;
2358 }
2359 
2360 void Telescope::processButton(const char *button_n, ISState state)
2361 {
2362  //ignore OFF
2363  if (state == ISS_OFF)
2364  return;
2365 
2366  if (!strcmp(button_n, "ABORTBUTTON"))
2367  {
2368  auto trackSW = getSwitch("TELESCOPE_TRACK_MODE");
2369  // Only abort if we have some sort of motion going on
2370  if (ParkSP.s == IPS_BUSY || MovementNSSP.s == IPS_BUSY || MovementWESP.s == IPS_BUSY || EqNP.s == IPS_BUSY ||
2371  (trackSW && trackSW.getState() == IPS_BUSY))
2372  {
2373  // Invoke parent processing so that Telescope takes care of abort cross-check
2374  ISState states[1] = { ISS_ON };
2375  const char *names[1] = { AbortS[0].name };
2376  ISNewSwitch(getDeviceName(), AbortSP.name, states, const_cast<char **>(names), 1);
2377  }
2378  }
2379  else if (!strcmp(button_n, "PARKBUTTON"))
2380  {
2381  ISState states[2] = { ISS_ON, ISS_OFF };
2382  const char *names[2] = { ParkS[0].name, ParkS[1].name };
2383  ISNewSwitch(getDeviceName(), ParkSP.name, states, const_cast<char **>(names), 2);
2384  }
2385  else if (!strcmp(button_n, "UNPARKBUTTON"))
2386  {
2387  ISState states[2] = { ISS_OFF, ISS_ON };
2388  const char *names[2] = { ParkS[0].name, ParkS[1].name };
2389  ISNewSwitch(getDeviceName(), ParkSP.name, states, const_cast<char **>(names), 2);
2390  }
2391  else if (!strcmp(button_n, "SLEWPRESETUP"))
2392  {
2393  processSlewPresets(1, 270);
2394  }
2395  else if (!strcmp(button_n, "SLEWPRESETDOWN"))
2396  {
2397  processSlewPresets(1, 90);
2398  }
2399 }
2400 
2401 void Telescope::processJoystick(const char *joystick_n, double mag, double angle)
2402 {
2403  if (MotionControlModeTP.sp[MOTION_CONTROL_JOYSTICK].s == ISS_ON && !strcmp(joystick_n, "MOTIONDIR"))
2404  {
2406  {
2407  DEBUG(Logger::DBG_WARNING, "Can not slew while mount is parking/parked.");
2408  return;
2409  }
2410 
2411  processNSWE(mag, angle);
2412  }
2413  else if (!strcmp(joystick_n, "SLEWPRESET"))
2414  processSlewPresets(mag, angle);
2415 }
2416 
2417 void Telescope::processAxis(const char *axis_n, double value)
2418 {
2420  {
2421  if (!strcmp(axis_n, "MOTIONDIRNS") || !strcmp(axis_n, "MOTIONDIRWE"))
2422  {
2424  {
2425  LOG_WARN("Cannot slew while mount is parking/parked.");
2426  return;
2427  }
2428 
2429  if (!strcmp(axis_n, "MOTIONDIRNS"))
2430  {
2431  // South
2432  if (value > 0)
2433  {
2434  motionDirNSValue = -1;
2435  }
2436  // North
2437  else if (value < 0)
2438  {
2439  motionDirNSValue = 1;
2440  }
2441  else
2442  {
2443  motionDirNSValue = 0;
2444  }
2445  }
2446  else if (!strcmp(axis_n, "MOTIONDIRWE"))
2447  {
2448  // East
2449  if (value > 0)
2450  {
2451  motionDirWEValue = 1;
2452  }
2453  // West
2454  else if (value < 0)
2455  {
2456  motionDirWEValue = -1;
2457  }
2458  else
2459  {
2460  motionDirWEValue = 0;
2461  }
2462  }
2463 
2464  float x = motionDirWEValue * sqrt(1 - pow(motionDirNSValue, 2) / 2.0f);
2465  float y = motionDirNSValue * sqrt(1 - pow(motionDirWEValue, 2) / 2.0f);
2466  float angle = atan2(y, x) * (180.0 / 3.141592653589);
2467  float mag = sqrt(pow(y, 2) + pow(x, 2));
2468  while (angle < 0)
2469  {
2470  angle += 360;
2471  }
2472  if (mag == 0)
2473  {
2474  angle = 0;
2475  }
2476 
2477  processNSWE(mag, angle);
2478  }
2479  }
2480 }
2481 
2482 void Telescope::processNSWE(double mag, double angle)
2483 {
2484  if (mag < 0.5)
2485  {
2486  // Moving in the same direction will make it stop
2487  if (MovementNSSP.s == IPS_BUSY)
2488  {
2490  {
2493  IDSetSwitch(&MovementNSSP, nullptr);
2494  }
2495  else
2496  {
2498  IDSetSwitch(&MovementNSSP, nullptr);
2499  }
2500  }
2501 
2502  if (MovementWESP.s == IPS_BUSY)
2503  {
2505  {
2508  IDSetSwitch(&MovementWESP, nullptr);
2509  }
2510  else
2511  {
2513  IDSetSwitch(&MovementWESP, nullptr);
2514  }
2515  }
2516  }
2517  // Put high threshold
2518  else if (mag > 0.9)
2519  {
2520  // Only one axis can move at a time
2521  if (LockAxisS[AXIS_RA].s == ISS_ON)
2522  {
2523  // West
2524  if (angle >= 90 && angle <= 270)
2525  angle = 180;
2526  // East
2527  else
2528  angle = 0;
2529  }
2530  else if (LockAxisS[AXIS_DE].s == ISS_ON)
2531  {
2532  // North
2533  if (angle >= 0 && angle <= 180)
2534  angle = 90;
2535  // South
2536  else
2537  angle = 270;
2538  }
2539 
2540  // Snap angle to x or y direction if close to corresponding axis (i.e. deviation < 15°)
2541  if (angle > 75 && angle < 105)
2542  {
2543  angle = 90;
2544  }
2545  if (angle > 165 && angle < 195)
2546  {
2547  angle = 180;
2548  }
2549  if (angle > 255 && angle < 285)
2550  {
2551  angle = 270;
2552  }
2553  if (angle > 345 || angle < 15)
2554  {
2555  angle = 0;
2556  }
2557 
2558  // North
2559  if (angle > 0 && angle < 180)
2560  {
2561  // Don't try to move if you're busy and moving in the same direction
2562  if (MovementNSSP.s != IPS_BUSY || MovementNSS[0].s != ISS_ON)
2564 
2568  IDSetSwitch(&MovementNSSP, nullptr);
2569  }
2570  // South
2571  if (angle > 180 && angle < 360)
2572  {
2573  // Don't try to move if you're busy and moving in the same direction
2574  if (MovementNSSP.s != IPS_BUSY || MovementNSS[1].s != ISS_ON)
2576 
2580  IDSetSwitch(&MovementNSSP, nullptr);
2581  }
2582  // East
2583  if (angle < 90 || angle > 270)
2584  {
2585  // Don't try to move if you're busy and moving in the same direction
2586  if (MovementWESP.s != IPS_BUSY || MovementWES[1].s != ISS_ON)
2588 
2592  IDSetSwitch(&MovementWESP, nullptr);
2593  }
2594 
2595  // West
2596  if (angle > 90 && angle < 270)
2597  {
2598  // Don't try to move if you're busy and moving in the same direction
2599  if (MovementWESP.s != IPS_BUSY || MovementWES[0].s != ISS_ON)
2601 
2605  IDSetSwitch(&MovementWESP, nullptr);
2606  }
2607  }
2608 }
2609 
2610 void Telescope::processSlewPresets(double mag, double angle)
2611 {
2612  // high threshold, only 1 is accepted
2613  if (mag != 1)
2614  return;
2615 
2616  int currentIndex = IUFindOnSwitchIndex(&SlewRateSP);
2617 
2618  // Up
2619  if (angle > 0 && angle < 180)
2620  {
2621  if (currentIndex <= 0)
2622  return;
2623 
2625  SlewRateS[currentIndex - 1].s = ISS_ON;
2626  SetSlewRate(currentIndex - 1);
2627  }
2628  // Down
2629  else
2630  {
2631  if (currentIndex >= SlewRateSP.nsp - 1)
2632  return;
2633 
2635  SlewRateS[currentIndex + 1].s = ISS_ON;
2636  SetSlewRate(currentIndex - 1);
2637  }
2638 
2639  IDSetSwitch(&SlewRateSP, nullptr);
2640 }
2641 
2642 void Telescope::joystickHelper(const char *joystick_n, double mag, double angle, void *context)
2643 {
2644  static_cast<Telescope *>(context)->processJoystick(joystick_n, mag, angle);
2645 }
2646 
2647 void Telescope::axisHelper(const char *axis_n, double value, void *context)
2648 {
2649  static_cast<Telescope *>(context)->processAxis(axis_n, value);
2650 }
2651 
2652 void Telescope::buttonHelper(const char *button_n, ISState state, void *context)
2653 {
2654  static_cast<Telescope *>(context)->processButton(button_n, state);
2655 }
2656 
2658 {
2659  // ensure that the scope knows it's pier side or the pier side is simulated
2660  if (HasPierSide() == false && getSimulatePierSide() == false)
2661  return;
2662 
2663  currentPierSide = side;
2664 
2666  {
2667  PierSideS[PIER_WEST].s = (side == PIER_WEST) ? ISS_ON : ISS_OFF;
2668  PierSideS[PIER_EAST].s = (side == PIER_EAST) ? ISS_ON : ISS_OFF;
2669  PierSideSP.s = IPS_OK;
2670  IDSetSwitch(&PierSideSP, nullptr);
2671 
2673  }
2674 }
2675 
2682 {
2683  // return unknown if the mount does not have pier side, this will be the case for a fork mount
2684  // where a pier flip is not required.
2685  if (!HasPierSide() && !HasPierSideSimulation())
2687 
2688  // calculate the hour angle and derive the pier side
2690  double hourAngle = get_local_hour_angle(lst, ra);
2691 
2692  return hourAngle <= 0 ? INDI::Telescope::PIER_WEST : INDI::Telescope::PIER_EAST;
2693 }
2694 
2696 {
2697  currentPECState = state;
2698 
2700  {
2701 
2702  PECStateS[PEC_OFF].s = (state == PEC_ON) ? ISS_OFF : ISS_ON;
2703  PECStateS[PEC_ON].s = (state == PEC_ON) ? ISS_ON : ISS_OFF;
2704  PECStateSP.s = IPS_OK;
2705  IDSetSwitch(&PECStateSP, nullptr);
2706 
2708  }
2709 }
2710 
2712 {
2713  if (!CheckFile(ScopeConfigFileName, false))
2714  {
2715  LOGF_INFO("Can't open XML file (%s) for read", ScopeConfigFileName.c_str());
2716  return false;
2717  }
2718  LilXML *XmlHandle = newLilXML();
2719  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2720  XMLEle *RootXmlNode = nullptr;
2721  XMLEle *CurrentXmlNode = nullptr;
2722  XMLAtt *Ap = nullptr;
2723  bool DeviceFound = false;
2724  char ErrMsg[512];
2725 
2726  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2727  fclose(FilePtr);
2728  delLilXML(XmlHandle);
2729  XmlHandle = nullptr;
2730  if (!RootXmlNode)
2731  {
2732  LOGF_INFO("Failed to parse XML file (%s): %s", ScopeConfigFileName.c_str(), ErrMsg);
2733  return false;
2734  }
2735  if (std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2736  {
2737  LOGF_INFO("Not a scope config XML file (%s)", ScopeConfigFileName.c_str());
2738  delXMLEle(RootXmlNode);
2739  return false;
2740  }
2741  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2742  // Find the current telescope in the config file
2743  while (CurrentXmlNode)
2744  {
2745  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2746  {
2747  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2748  continue;
2749  }
2750  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2751  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2752  {
2753  DeviceFound = true;
2754  break;
2755  }
2756  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2757  }
2758  if (!DeviceFound)
2759  {
2760  LOGF_INFO("No a scope config found for %s in the XML file (%s)", getDeviceName(),
2761  ScopeConfigFileName.c_str());
2762  delXMLEle(RootXmlNode);
2763  return false;
2764  }
2765  // Read the values
2766  XMLEle *XmlNode = nullptr;
2767  const int ConfigIndex = GetScopeConfigIndex();
2768  double ScopeFoc = 0, ScopeAp = 0;
2769  double GScopeFoc = 0, GScopeAp = 0;
2770  std::string ConfigName;
2771 
2772  CurrentXmlNode = findXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2773  if (!CurrentXmlNode)
2774  {
2776  "Config %d is not found in the XML file (%s). To save a new config, update and set scope properties and "
2777  "config name.",
2778  ConfigIndex, ScopeConfigFileName.c_str());
2779  delXMLEle(RootXmlNode);
2780  return false;
2781  }
2782  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
2783  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &ScopeFoc) != 1)
2784  {
2785  LOGF_INFO("Can't read the telescope focal length from the XML file (%s)",
2786  ScopeConfigFileName.c_str());
2787  delXMLEle(RootXmlNode);
2788  return false;
2789  }
2790  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
2791  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &ScopeAp) != 1)
2792  {
2793  LOGF_INFO("Can't read the telescope aperture from the XML file (%s)",
2794  ScopeConfigFileName.c_str());
2795  delXMLEle(RootXmlNode);
2796  return false;
2797  }
2798  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
2799  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &GScopeFoc) != 1)
2800  {
2801  LOGF_INFO("Can't read the guide scope focal length from the XML file (%s)",
2802  ScopeConfigFileName.c_str());
2803  delXMLEle(RootXmlNode);
2804  return false;
2805  }
2806  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
2807  if (!XmlNode || sscanf(pcdataXMLEle(XmlNode), "%lf", &GScopeAp) != 1)
2808  {
2809  LOGF_INFO("Can't read the guide scope aperture from the XML file (%s)",
2810  ScopeConfigFileName.c_str());
2811  delXMLEle(RootXmlNode);
2812  return false;
2813  }
2814  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
2815  if (!XmlNode)
2816  {
2817  LOGF_INFO("Can't read the telescope config name from the XML file (%s)",
2818  ScopeConfigFileName.c_str());
2819  delXMLEle(RootXmlNode);
2820  return false;
2821  }
2822  ConfigName = pcdataXMLEle(XmlNode);
2823  // Store the loaded values
2824  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH"))
2825  {
2826  IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH")->value = ScopeFoc;
2827  }
2828  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE"))
2829  {
2830  IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE")->value = ScopeAp;
2831  }
2832  if (IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH"))
2833  {
2834  IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH")->value = GScopeFoc;
2835  }
2836  if (IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE"))
2837  {
2838  IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE")->value = GScopeAp;
2839  }
2840  if (IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME"))
2841  {
2842  IUSaveText(IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME"), ConfigName.c_str());
2843  }
2845  IDSetNumber(&ScopeParametersNP, nullptr);
2847  IDSetText(&ScopeConfigNameTP, nullptr);
2848  delXMLEle(RootXmlNode);
2849  return true;
2850 }
2851 
2853 {
2854  if (!CheckFile(ScopeConfigFileName, false))
2855  {
2856  return false;
2857  }
2858  LilXML *XmlHandle = newLilXML();
2859  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2860  XMLEle *RootXmlNode = nullptr;
2861  XMLEle *CurrentXmlNode = nullptr;
2862  XMLAtt *Ap = nullptr;
2863  bool DeviceFound = false;
2864  char ErrMsg[512];
2865 
2866  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2867  fclose(FilePtr);
2868  delLilXML(XmlHandle);
2869  XmlHandle = nullptr;
2870  if (!RootXmlNode)
2871  {
2872  return false;
2873  }
2874  if (std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2875  {
2876  delXMLEle(RootXmlNode);
2877  return false;
2878  }
2879  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2880  // Find the current telescope in the config file
2881  while (CurrentXmlNode)
2882  {
2883  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2884  {
2885  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2886  continue;
2887  }
2888  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2889  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2890  {
2891  DeviceFound = true;
2892  break;
2893  }
2894  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2895  }
2896  if (!DeviceFound)
2897  {
2898  delXMLEle(RootXmlNode);
2899  return false;
2900  }
2901  // Check the existence of Config #1 node
2902  CurrentXmlNode = findXMLEle(CurrentXmlNode, "config1");
2903  if (!CurrentXmlNode)
2904  {
2905  delXMLEle(RootXmlNode);
2906  return false;
2907  }
2908  return true;
2909 }
2910 
2912 {
2913  // Get the config values from the UI
2914  const int ConfigIndex = GetScopeConfigIndex();
2915  double ScopeFoc = 0, ScopeAp = 0;
2916  double GScopeFoc = 0, GScopeAp = 0;
2917  std::string ConfigName;
2918 
2919  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH"))
2920  {
2921  ScopeFoc = IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH")->value;
2922  }
2923  if (IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE"))
2924  {
2925  ScopeAp = IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE")->value;
2926  }
2927  if (IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH"))
2928  {
2929  GScopeFoc = IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH")->value;
2930  }
2931  if (IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE"))
2932  {
2933  GScopeAp = IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE")->value;
2934  }
2935  if (IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME") &&
2936  IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME")->text)
2937  {
2938  ConfigName = IUFindText(&ScopeConfigNameTP, "SCOPE_CONFIG_NAME")->text;
2939  }
2940  // Save the values to the actual XML file
2941  if (!CheckFile(ScopeConfigFileName, true))
2942  {
2943  LOGF_INFO("Can't open XML file (%s) for write", ScopeConfigFileName.c_str());
2944  return false;
2945  }
2946  // Open the existing XML file for write
2947  LilXML *XmlHandle = newLilXML();
2948  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
2949  XMLEle *RootXmlNode = nullptr;
2950  XMLAtt *Ap = nullptr;
2951  bool DeviceFound = false;
2952  char ErrMsg[512];
2953 
2954  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
2955  delLilXML(XmlHandle);
2956  XmlHandle = nullptr;
2957  fclose(FilePtr);
2958 
2959  XMLEle *CurrentXmlNode = nullptr;
2960  XMLEle *XmlNode = nullptr;
2961 
2962  if (!RootXmlNode || std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
2963  {
2964  RootXmlNode = addXMLEle(nullptr, ScopeConfigRootXmlNode.c_str());
2965  }
2966  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
2967  // Find the current telescope in the config file
2968  while (CurrentXmlNode)
2969  {
2970  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
2971  {
2972  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2973  continue;
2974  }
2975  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
2976  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
2977  {
2978  DeviceFound = true;
2979  break;
2980  }
2981  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
2982  }
2983  if (!DeviceFound)
2984  {
2985  CurrentXmlNode = addXMLEle(RootXmlNode, ScopeConfigDeviceXmlNode.c_str());
2986  addXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str(), getDeviceName());
2987  }
2988  // Add or update the config node
2989  XmlNode = findXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2990  if (!XmlNode)
2991  {
2992  CurrentXmlNode = addXMLEle(CurrentXmlNode, ("config" + std::to_string(ConfigIndex)).c_str());
2993  }
2994  else
2995  {
2996  CurrentXmlNode = XmlNode;
2997  }
2998  // Add or update the telescope focal length
2999  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
3000  if (!XmlNode)
3001  {
3002  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigScopeFocXmlNode.c_str());
3003  }
3004  editXMLEle(XmlNode, std::to_string(ScopeFoc).c_str());
3005  // Add or update the telescope focal aperture
3006  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
3007  if (!XmlNode)
3008  {
3009  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigScopeApXmlNode.c_str());
3010  }
3011  editXMLEle(XmlNode, std::to_string(ScopeAp).c_str());
3012  // Add or update the guide scope focal length
3013  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
3014  if (!XmlNode)
3015  {
3016  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigGScopeFocXmlNode.c_str());
3017  }
3018  editXMLEle(XmlNode, std::to_string(GScopeFoc).c_str());
3019  // Add or update the guide scope focal aperture
3020  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
3021  if (!XmlNode)
3022  {
3023  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigGScopeApXmlNode.c_str());
3024  }
3025  editXMLEle(XmlNode, std::to_string(GScopeAp).c_str());
3026  // Add or update the config name
3027  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
3028  if (!XmlNode)
3029  {
3030  XmlNode = addXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
3031  }
3032  editXMLEle(XmlNode, ConfigName.c_str());
3033  // Save the final content
3034  FilePtr = fopen(ScopeConfigFileName.c_str(), "w");
3035  prXMLEle(FilePtr, RootXmlNode, 0);
3036  fclose(FilePtr);
3037  delXMLEle(RootXmlNode);
3038  return true;
3039 }
3040 
3041 std::string Telescope::GetHomeDirectory() const
3042 {
3043  // Check first the HOME environmental variable
3044  const char *HomeDir = getenv("HOME");
3045 
3046  // ...otherwise get the home directory of the current user.
3047  if (!HomeDir)
3048  {
3049  HomeDir = getpwuid(getuid())->pw_dir;
3050  }
3051  return (HomeDir ? std::string(HomeDir) : "");
3052 }
3053 
3055 {
3056  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG1") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG1")->s == ISS_ON)
3057  {
3058  return 1;
3059  }
3060  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG2") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG2")->s == ISS_ON)
3061  {
3062  return 2;
3063  }
3064  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG3") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG3")->s == ISS_ON)
3065  {
3066  return 3;
3067  }
3068  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG4") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG4")->s == ISS_ON)
3069  {
3070  return 4;
3071  }
3072  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG5") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG5")->s == ISS_ON)
3073  {
3074  return 5;
3075  }
3076  if (IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG6") && IUFindSwitch(&ScopeConfigsSP, "SCOPE_CONFIG6")->s == ISS_ON)
3077  {
3078  return 6;
3079  }
3080  return 0;
3081 }
3082 
3083 bool Telescope::CheckFile(const std::string &file_name, bool writable) const
3084 {
3085  FILE *FilePtr = fopen(file_name.c_str(), (writable ? "a" : "r"));
3086 
3087  if (FilePtr)
3088  {
3089  fclose(FilePtr);
3090  return true;
3091  }
3092  return false;
3093 }
3094 
3096 {
3097  char ts[32] = {0};
3098 
3099  std::time_t t = std::time(nullptr);
3100  struct std::tm *utctimeinfo = std::gmtime(&t);
3101 
3102  strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", utctimeinfo);
3103  IUSaveText(&TimeT[0], ts);
3104 
3105  struct std::tm *localtimeinfo = std::localtime(&t);
3106  snprintf(ts, sizeof(ts), "%4.2f", (localtimeinfo->tm_gmtoff / 3600.0));
3107  IUSaveText(&TimeT[1], ts);
3108 
3109  TimeTP.s = IPS_OK;
3110 
3111  IDSetText(&TimeTP, nullptr);
3112 }
3113 
3115 {
3116  return m_simulatePierSide;
3117 }
3118 
3120 {
3121  switch (ps)
3122  {
3123  case PIER_WEST:
3124  return "PIER_WEST";
3125  case PIER_EAST:
3126  return "PIER_EAST";
3127  default:
3128  return "PIER_UNKNOWN";
3129  }
3130 }
3131 
3133 {
3135  SimulatePierSideS[0].s = simulate ? ISS_ON : ISS_OFF;
3136  SimulatePierSideS[1].s = simulate ? ISS_OFF : ISS_ON;
3138  IDSetSwitch(&SimulatePierSideSP, nullptr);
3139 
3140  if (simulate)
3141  {
3144  }
3145  else
3146  {
3147  capability &= static_cast<uint32_t>(~TELESCOPE_HAS_PIER_SIDE);
3149  }
3150 
3151  m_simulatePierSide = simulate;
3152 }
3153 
3154 }
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.
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]
@ 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.
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
@ SAT_TRACK
Track signal.
@ SAT_HALT
Halt signal (abort)
@ SAT_TRACK_COUNT
State counter.
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]
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)....
virtual bool Flip(double ra, double dec)
Move and flip the scope to the supplied RA and DEC coordinates.
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
ISwitch CoordS[4]
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