Instrument Neutral Distributed Interface INDI  2.0.2
skywatcherAltAzSimple.cpp
Go to the documentation of this file.
1 
16 #include "skywatcherAltAzSimple.h"
17 
18 #include "indicom.h"
20 
21 #include <libnova/transform.h>
22 // libnova specifies round() on old systems and it collides with the new gcc 5.x/6.x headers
23 #define HAVE_ROUND
24 #include <libnova/utility.h>
25 
26 #include <chrono>
27 #include <cstring>
28 #include <fstream>
29 #include <iostream>
30 #include <thread>
31 
32 #include <sys/stat.h>
33 
34 // We declare an auto pointer to SkywatcherAltAzSimple.
35 std::unique_ptr<SkywatcherAltAzSimple> SkywatcherAltAzSimplePtr(new SkywatcherAltAzSimple());
36 
37 /* Preset Slew Speeds */
38 #define SLEWMODES 9
39 double SlewSpeeds[SLEWMODES] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 600.0 };
40 
41 void ISPoll(void *p);
42 
43 namespace
44 {
45 bool FileExists(const std::string &name)
46 {
47  struct stat buffer;
48 
49  return (stat (name.c_str(), &buffer) == 0);
50 }
51 
52 std::string GetLogTimestamp()
53 {
54  time_t Now = time(nullptr);
55  struct tm TimeStruct;
56  char Buffer[60];
57  std::string FinalStr;
58 
59  TimeStruct = *localtime(&Now);
60  strftime(Buffer, sizeof(Buffer), "%Y%m%d %H:%M:%S", &TimeStruct);
61  FinalStr = Buffer;
62  // Add the millisecond part
63  std::chrono::system_clock::time_point NowClock = std::chrono::system_clock::now();
64  std::chrono::system_clock::duration TimePassed = NowClock.time_since_epoch();
65 
66  TimePassed -= std::chrono::duration_cast<std::chrono::seconds>(TimePassed);
67  FinalStr += "." + std::to_string(static_cast<unsigned>(TimePassed / std::chrono::milliseconds(1)));
68  return FinalStr;
69 }
70 } // namespace
71 
72 SkywatcherAltAzSimple::SkywatcherAltAzSimple() : TrackLogFileName(GetHomeDirectory() + "/.indi/sw_mount_track_log.txt")
73 {
74  // Set up the logging pointer in SkyWatcherAPI
75  pChildTelescope = this;
78  SLEWMODES);
79  std::remove(TrackLogFileName.c_str());
80 }
81 
83 {
84  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::Abort");
85  LogMessage("MOVE ABORT");
86  SlowStop(AXIS1);
87  SlowStop(AXIS2);
89 
90  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
91  {
93  GuideNSN[0].value = GuideNSN[1].value = 0.0;
94  GuideWEN[0].value = GuideWEN[1].value = 0.0;
95 
96  IDMessage(getDeviceName(), "Guide aborted.");
97  IDSetNumber(&GuideNSNP, nullptr);
98  IDSetNumber(&GuideWENP, nullptr);
99 
100  return true;
101  }
102 
103  return true;
104 }
105 
107 {
108  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::Handshake");
110 
111  Connection::Interface *activeConnection = getActiveConnection();
112  if (!activeConnection->name().compare("CONNECTION_TCP"))
113  {
115  }
116 
117  bool Result = InitMount();
118 
120  {
121  SerialPortName = serialConnection->port();
122  }
123  else
124  {
125  SerialPortName = "";
126  }
127 
128  RecoverAfterReconnection = false;
129  DEBUGF(DBG_SCOPE, "SkywatcherAltAzSimple::Handshake - Result: %d", Result);
130  return Result;
131 }
132 
134 {
135  //DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::getDefaultName\n");
136  return "Skywatcher Alt-Az Wedge";
137 }
138 
139 bool SkywatcherAltAzSimple::Goto(double ra, double dec)
140 {
141  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::Goto");
142 
143  if (TrackState != SCOPE_IDLE)
144  Abort();
145 
146  DEBUGF(DBG_SCOPE, "RA %lf DEC %lf", ra, dec);
147 
148  if (IUFindSwitch(&CoordSP, "TRACK")->s == ISS_ON || IUFindSwitch(&CoordSP, "SLEW")->s == ISS_ON)
149  {
150  char RAStr[32], DecStr[32];
151  fs_sexa(RAStr, ra, 2, 3600);
152  fs_sexa(DecStr, dec, 2, 3600);
153  CurrentTrackingTarget.rightascension = ra;
154  CurrentTrackingTarget.declination = dec;
155  DEBUGF(INDI::Logger::DBG_SESSION, "New Tracking target RA %s DEC %s", RAStr, DecStr);
156  }
157 
159 
160  AltAz = GetAltAzPosition(ra, dec);
161  DEBUGF(DBG_SCOPE, "New Altitude %lf degrees %ld microsteps Azimuth %lf degrees %ld microsteps", AltAz.altitude,
162  DegreesToMicrosteps(AXIS2, AltAz.altitude), AltAz.azimuth, DegreesToMicrosteps(AXIS1, AltAz.azimuth));
163  LogMessage("NEW GOTO TARGET: Ra %lf Dec %lf - Alt %lf Az %lf - microsteps %ld %ld", ra, dec, AltAz.altitude,
164  AltAz.azimuth,
166 
167  // Update the current encoder positions
168  GetEncoder(AXIS1);
169  GetEncoder(AXIS2);
170 
171  long AltitudeOffsetMicrosteps =
173  long AzimuthOffsetMicrosteps =
175 
176  DEBUGF(DBG_SCOPE, "Initial deltas Altitude %ld microsteps Azimuth %ld microsteps", AltitudeOffsetMicrosteps,
177  AzimuthOffsetMicrosteps);
178  if (AltitudeOffsetMicrosteps > MicrostepsPerRevolution[AXIS2] / 2)
179  {
180  // Going the long way round - send it the other way
181  AltitudeOffsetMicrosteps -= MicrostepsPerRevolution[AXIS2];
182  }
183  if (AzimuthOffsetMicrosteps > MicrostepsPerRevolution[AXIS1] / 2)
184  {
185  // Going the long way round - send it the other way
186  AzimuthOffsetMicrosteps -= MicrostepsPerRevolution[AXIS1];
187  }
188  if (AltitudeOffsetMicrosteps < -MicrostepsPerRevolution[AXIS2] / 2)
189  {
190  // Going the long way round - send it the other way
191  AltitudeOffsetMicrosteps += MicrostepsPerRevolution[AXIS2];
192  }
193  if (AzimuthOffsetMicrosteps < -MicrostepsPerRevolution[AXIS1] / 2)
194  {
195  // Going the long way round - send it the other way
196  AzimuthOffsetMicrosteps += MicrostepsPerRevolution[AXIS1];
197  }
198  DEBUGF(DBG_SCOPE, "Initial Axis2 %ld microsteps Axis1 %ld microsteps",
200  DEBUGF(DBG_SCOPE, "Current Axis2 %ld microsteps Axis1 %ld microsteps",
202  DEBUGF(DBG_SCOPE, "Altitude offset %ld microsteps Azimuth offset %ld microsteps",
203  AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps);
204 
205  if (IUFindSwitch(&SlewModesSP, "SLEW_NORMAL")->s == ISS_ON)
206  {
207  SilentSlewMode = false;
208  }
209  else
210  {
211  SilentSlewMode = true;
212  }
213  SlewTo(AXIS1, AzimuthOffsetMicrosteps);
214  SlewTo(AXIS2, AltitudeOffsetMicrosteps);
215 
217 
218  return true;
219 }
220 
222 {
223  IDLog("SkywatcherAltAzSimple::initProperties\n");
224 
225  // Allow the base class to initialise its visible before connection properties
227 
228  for (int i = 0; i < SlewRateSP.nsp; ++i)
229  {
230  sprintf(SlewRateSP.sp[i].label, "%.fx", SlewSpeeds[i]);
231  SlewRateSP.sp[i].aux = (void *)&SlewSpeeds[i];
232  }
233  strncpy(SlewRateSP.sp[SlewRateSP.nsp - 1].name, "SLEW_MAX", MAXINDINAME);
234 
235  // Add default properties
236  addDebugControl();
238 
239  // Set up property variables
240  IUFillText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], "MOTOR_CONTROL_FIRMWARE_VERSION",
241  "Motor control firmware version", "-");
242  IUFillText(&BasicMountInfoT[MOUNT_CODE], "MOUNT_CODE", "Mount code", "-");
243  IUFillText(&BasicMountInfoT[MOUNT_NAME], "MOUNT_NAME", "Mount name", "-");
244  IUFillText(&BasicMountInfoT[IS_DC_MOTOR], "IS_DC_MOTOR", "Is DC motor", "-");
245  IUFillTextVector(&BasicMountInfoTP, BasicMountInfoT, 4, getDeviceName(), "BASIC_MOUNT_INFO",
246  "Basic mount information", DetailedMountInfoPage, IP_RO, 60, IPS_IDLE);
247 
248  IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
249  "%.0f", 0, 0xFFFFFF, 1, 0);
250  IUFillNumber(&AxisOneInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Stepper clock frequency", "%.0f", 0,
251  0xFFFFFF, 1, 0);
252  IUFillNumber(&AxisOneInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
253  IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
254  "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
255 
256  IUFillNumberVector(&AxisOneInfoNP, AxisOneInfoN, 4, getDeviceName(), "AXIS_ONE_INFO", "Axis one information",
257  DetailedMountInfoPage, IP_RO, 60, IPS_IDLE);
258 
259  IUFillSwitch(&AxisOneStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
260  IUFillSwitch(&AxisOneStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
261  IUFillSwitch(&AxisOneStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
262  IUFillSwitch(&AxisOneStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
263  IUFillSwitch(&AxisOneStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
264  IUFillSwitch(&AxisOneStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
265  IUFillSwitchVector(&AxisOneStateSP, AxisOneStateS, 6, getDeviceName(), "AXIS_ONE_STATE", "Axis one state",
266  DetailedMountInfoPage, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
267 
268  IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
269  "%.0f", 0, 0xFFFFFF, 1, 0);
270  IUFillNumber(&AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Step timer frequency", "%.0f", 0,
271  0xFFFFFF, 1, 0);
272  IUFillNumber(&AxisTwoInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
273  IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
274  "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
275 
276  IUFillNumberVector(&AxisTwoInfoNP, AxisTwoInfoN, 4, getDeviceName(), "AXIS_TWO_INFO", "Axis two information",
277  DetailedMountInfoPage, IP_RO, 60, IPS_IDLE);
278 
279  IUFillSwitch(&AxisTwoStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
280  IUFillSwitch(&AxisTwoStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
281  IUFillSwitch(&AxisTwoStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
282  IUFillSwitch(&AxisTwoStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
283  IUFillSwitch(&AxisTwoStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
284  IUFillSwitch(&AxisTwoStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
285  IUFillSwitchVector(&AxisTwoStateSP, AxisTwoStateS, 6, getDeviceName(), "AXIS_TWO_STATE", "Axis two state",
286  DetailedMountInfoPage, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
287 
288  IUFillNumber(&AxisOneEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
289  IUFillNumber(&AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
290  "%.4f", 0, 0xFFFFFF, 1, 0);
291  IUFillNumber(&AxisOneEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
292  0xFFFFFF, 1, 0);
293  IUFillNumber(&AxisOneEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
294  -1000.0, 1000.0, 1, 0);
295 
296  IUFillNumberVector(&AxisOneEncoderValuesNP, AxisOneEncoderValuesN, 4, getDeviceName(), "AXIS1_ENCODER_VALUES",
297  "Axis 1 Encoder values", DetailedMountInfoPage, IP_RO, 60, IPS_IDLE);
298 
299  IUFillNumber(&AxisTwoEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
300  IUFillNumber(&AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
301  "%.4f", 0, 0xFFFFFF, 1, 0);
302  IUFillNumber(&AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
303  0xFFFFFF, 1, 0);
304  IUFillNumber(&AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
305  -1000.0, 1000.0, 1, 0);
306 
307  IUFillNumberVector(&AxisTwoEncoderValuesNP, AxisTwoEncoderValuesN, 4, getDeviceName(), "AXIS2_ENCODER_VALUES",
308  "Axis 2 Encoder values", DetailedMountInfoPage, IP_RO, 60, IPS_IDLE);
309  // Register any visible before connection properties
310 
311  // Slew modes
312  IUFillSwitch(&SlewModesS[SLEW_SILENT], "SLEW_SILENT", "Silent", ISS_OFF);
313  IUFillSwitch(&SlewModesS[SLEW_NORMAL], "SLEW_NORMAL", "Normal", ISS_OFF);
314  IUFillSwitchVector(&SlewModesSP, SlewModesS, 2, getDeviceName(), "TELESCOPE_MOTION_SLEWMODE", "Slew Mode",
316 
317  // Wedge mode
318  IUFillSwitch(&WedgeModeS[WEDGE_SIMPLE], "WEDGE_SIMPLE", "Simple wedge", ISS_OFF);
319  IUFillSwitch(&WedgeModeS[WEDGE_EQ], "WEDGE_EQ", "EQ wedge", ISS_OFF);
320  IUFillSwitch(&WedgeModeS[WEDGE_DISABLED], "WEDGE_DISABLED", "Disabled", ISS_OFF);
321  IUFillSwitchVector(&WedgeModeSP, WedgeModeS, 3, getDeviceName(), "TELESCOPE_MOTION_WEDGEMODE",
322  "Wedge Mode", MOTION_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
323 
324  // Track logging mode
325  IUFillSwitch(&TrackLogModeS[TRACKLOG_ENABLED], "TRACKLOG_ENABLED", "Enable logging", ISS_OFF);
326  IUFillSwitch(&TrackLogModeS[TRACKLOG_DISABLED], "TRACKLOG_DISABLED", "Disabled", ISS_ON);
327  IUFillSwitchVector(&TrackLogModeSP, TrackLogModeS, 2, getDeviceName(), "TELESCOPE_MOTION_TRACKLOGMODE",
328  "Track Logging Mode", MOTION_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
329 
330  // Guiding rates for RA/DEC axes
331  IUFillNumber(&GuidingRatesN[0], "GUIDERA_RATE", "microsteps/seconds (RA)", "%1.3f", 0.00001, 100000.0, 0.00001, 1.0);
332  IUFillNumber(&GuidingRatesN[1], "GUIDEDEC_RATE", "microsteps/seconds (Dec)", "%1.3f", 0.00001, 100000.0, 0.00001, 1.0);
333  IUFillNumberVector(&GuidingRatesNP, GuidingRatesN, 2, getDeviceName(), "GUIDE_RATES", "Guide Rates", MOTION_TAB,
334  IP_RW, 60, IPS_IDLE);
335 
336  // Tracking rate
337  // For Skywatcher Virtuoso:
338  // Alt rate: 0.72, Az rate: 0.72, timeout: 1000 msec
339  // For Skywatcher Merlin:
340  // Alt rate: 0.64, Az rate: 0.64, timeout: 1000 msec
341  IUFillNumber(&TrackingValuesN[0], "TRACKING_RATE_ALT", "rate (Alt)", "%1.3f", 0.001, 10.0, 0.000001, 0.64);
342  IUFillNumber(&TrackingValuesN[1], "TRACKING_RATE_AZ", "rate (Az)", "%1.3f", 0.001, 10.0, 0.000001, 0.64);
343  IUFillNumber(&TrackingValuesN[2], "TRACKING_TIMEOUT", "msec (period)", "%1.3f", 0.001, 10000.0, 0.000001, 1000.0);
344  IUFillNumberVector(&TrackingValuesNP, TrackingValuesN, 3, getDeviceName(), "TRACKING_VALUES", "Tracking Values", MOTION_TAB,
345  IP_RW, 60, IPS_IDLE);
346 
347  // Park movement directions
348  IUFillSwitch(&ParkMovementDirectionS[PARK_COUNTERCLOCKWISE], "PMD_COUNTERCLOCKWISE", "Counterclockwise", ISS_ON);
349  IUFillSwitch(&ParkMovementDirectionS[PARK_CLOCKWISE], "PMD_CLOCKWISE", "Clockwise", ISS_OFF);
350  IUFillSwitchVector(&ParkMovementDirectionSP, ParkMovementDirectionS, 2, getDeviceName(), "PARK_DIRECTION",
351  "Park Direction", MOTION_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
352 
353  // Park positions
354  IUFillSwitch(&ParkPositionS[PARK_NORTH], "PARK_NORTH", "North", ISS_ON);
355  IUFillSwitch(&ParkPositionS[PARK_EAST], "PARK_EAST", "East", ISS_OFF);
356  IUFillSwitch(&ParkPositionS[PARK_SOUTH], "PARK_SOUTH", "South", ISS_OFF);
357  IUFillSwitch(&ParkPositionS[PARK_WEST], "PARK_WEST", "West", ISS_OFF);
358  IUFillSwitchVector(&ParkPositionSP, ParkPositionS, 4, getDeviceName(), "PARK_POSITION", "Park Position", MOTION_TAB,
359  IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
360 
361  // Unpark positions
362  IUFillSwitch(&UnparkPositionS[PARK_NORTH], "UNPARK_NORTH", "North", ISS_OFF);
363  IUFillSwitch(&UnparkPositionS[PARK_EAST], "UNPARK_EAST", "East", ISS_OFF);
364  IUFillSwitch(&UnparkPositionS[PARK_SOUTH], "UNPARK_SOUTH", "South", ISS_OFF);
365  IUFillSwitch(&UnparkPositionS[PARK_WEST], "UNPARK_WEST", "West", ISS_OFF);
366  IUFillSwitchVector(&UnparkPositionSP, UnparkPositionS, 4, getDeviceName(), "UNPARK_POSITION", "Unpark Position",
368 
369  // Guiding support
372 
373  return true;
374 }
375 
377 {
378  IDLog("SkywatcherAltAzSimple::ISGetProperties\n");
380 
381  if (isConnected())
382  {
383  // Fill in any real values now available MCInit should have been called already
384  UpdateDetailedMountInformation(false);
385 
386  // Define our connected only properties to the base driver
387  // e.g. defineProperty(MyNumberVectorPointer);
388  // This will register our properties and send a IDDefXXXX mewssage to any connected clients
389  defineProperty(&BasicMountInfoTP);
390  defineProperty(&AxisOneInfoNP);
391  defineProperty(&AxisOneStateSP);
392  defineProperty(&AxisTwoInfoNP);
393  defineProperty(&AxisTwoStateSP);
394  defineProperty(&AxisOneEncoderValuesNP);
395  defineProperty(&AxisTwoEncoderValuesNP);
396  defineProperty(&SlewModesSP);
397  defineProperty(&WedgeModeSP);
398  defineProperty(&TrackLogModeSP);
399  defineProperty(&GuidingRatesNP);
400  defineProperty(&TrackingValuesNP);
401  defineProperty(&ParkMovementDirectionSP);
402  defineProperty(&ParkPositionSP);
403  defineProperty(&UnparkPositionSP);
406  }
407 }
408 
409 bool SkywatcherAltAzSimple::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
410  char *formats[], char *names[], int n)
411 {
412  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
413  {
414  // It is for us
415  }
416  // Pass it up the chain
417  return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
418 }
419 
420 bool SkywatcherAltAzSimple::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
421 {
422  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
423  {
424  if (strcmp(name, "GUIDE_RATES") == 0)
425  {
426  ResetGuidePulses();
427  GuidingRatesNP.s = IPS_OK;
428  IUUpdateNumber(&GuidingRatesNP, values, names, n);
429  IDSetNumber(&GuidingRatesNP, nullptr);
430  return true;
431  }
432 
433  if (strcmp(name, "TRACKING_VALUES") == 0)
434  {
435  TrackingValuesNP.s = IPS_OK;
436  IUUpdateNumber(&TrackingValuesNP, values, names, n);
437  IDSetNumber(&TrackingValuesNP, nullptr);
438  return true;
439  }
440 
441  // Let our driver do sync operation in park position
442  if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
443  {
444  double ra = -1;
445  double dec = -100;
446 
447  for (int x = 0; x < n; x++)
448  {
449  INumber *eqp = IUFindNumber(&EqNP, names[x]);
450  if (eqp == &EqN[AXIS_RA])
451  {
452  ra = values[x];
453  }
454  else if (eqp == &EqN[AXIS_DE])
455  {
456  dec = values[x];
457  }
458  }
459  if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
460  {
461  ISwitch *sw = IUFindSwitch(&CoordSP, "SYNC");
462 
463  if (sw != nullptr && sw->s == ISS_ON && isParked())
464  {
465  return Sync(ra, dec);
466  }
467  }
468  }
469 
470  processGuiderProperties(name, values, names, n);
471  }
472  // Pass it up the chain
473  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
474 }
475 
476 bool SkywatcherAltAzSimple::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
477 {
479  svp = getSwitch(name);
480  if (svp == nullptr)
481  {
482  LOGF_WARN("getSwitch failed for %s", name);
483  }
484  else
485  {
486  LOGF_DEBUG("getSwitch OK %s", name);
487  IUUpdateSwitch(svp, states, names, n);
488  }
489  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
490  {
491  // It is for us
492  }
493  // Pass it up the chain
494  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
495 }
496 
497 bool SkywatcherAltAzSimple::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
498 {
499  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
500  {
501  // It is for us
502  }
503  // Pass it up the chain
504  bool Ret = INDI::Telescope::ISNewText(dev, name, texts, names, n);
505 
506  // The scope config switch must be updated after the config is saved to disk
507  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
508  {
509  if (name && std::string(name) == "SCOPE_CONFIG_NAME")
510  {
511  UpdateScopeConfigSwitch();
512  }
513  }
514  return Ret;
515 }
516 
517 void SkywatcherAltAzSimple::UpdateScopeConfigSwitch()
518 {
519  if (!CheckFile(ScopeConfigFileName, false))
520  {
521  DEBUGF(INDI::Logger::DBG_SESSION, "Can't open XML file (%s) for read", ScopeConfigFileName.c_str());
522  return;
523  }
524  LilXML *XmlHandle = newLilXML();
525  FILE *FilePtr = fopen(ScopeConfigFileName.c_str(), "r");
526  XMLEle *RootXmlNode = nullptr;
527  XMLEle *CurrentXmlNode = nullptr;
528  XMLAtt *Ap = nullptr;
529  bool DeviceFound = false;
530  char ErrMsg[512];
531 
532  RootXmlNode = readXMLFile(FilePtr, XmlHandle, ErrMsg);
533  delLilXML(XmlHandle);
534  XmlHandle = nullptr;
535  if (!RootXmlNode)
536  {
537  DEBUGF(INDI::Logger::DBG_SESSION, "Failed to parse XML file (%s): %s", ScopeConfigFileName.c_str(), ErrMsg);
538  return;
539  }
540  if (std::string(tagXMLEle(RootXmlNode)) != ScopeConfigRootXmlNode)
541  {
542  DEBUGF(INDI::Logger::DBG_SESSION, "Not a scope config XML file (%s)", ScopeConfigFileName.c_str());
543  delXMLEle(RootXmlNode);
544  return;
545  }
546  CurrentXmlNode = nextXMLEle(RootXmlNode, 1);
547  // Find the current telescope in the config file
548  while (CurrentXmlNode)
549  {
550  if (std::string(tagXMLEle(CurrentXmlNode)) != ScopeConfigDeviceXmlNode)
551  {
552  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
553  continue;
554  }
555  Ap = findXMLAtt(CurrentXmlNode, ScopeConfigNameXmlNode.c_str());
556  if (Ap && !strcmp(valuXMLAtt(Ap), getDeviceName()))
557  {
558  DeviceFound = true;
559  break;
560  }
561  CurrentXmlNode = nextXMLEle(RootXmlNode, 0);
562  }
563  if (!DeviceFound)
564  {
565  DEBUGF(INDI::Logger::DBG_SESSION, "No a scope config found for %s in the XML file (%s)", getDeviceName(),
566  ScopeConfigFileName.c_str());
567  delXMLEle(RootXmlNode);
568  return;
569  }
570  // Read the values
571  XMLEle *XmlNode = nullptr;
572  XMLEle *DeviceXmlNode = CurrentXmlNode;
573  std::string ConfigName;
574 
575  for (int i = 1; i < 7; ++i)
576  {
577  bool Found = true;
578 
579  CurrentXmlNode = findXMLEle(DeviceXmlNode, ("config" + std::to_string(i)).c_str());
580  if (CurrentXmlNode)
581  {
582  XmlNode = findXMLEle(CurrentXmlNode, ScopeConfigLabelApXmlNode.c_str());
583  if (XmlNode)
584  {
585  ConfigName = pcdataXMLEle(XmlNode);
586  }
587  }
588  else
589  {
590  Found = false;
591  }
592  // Change the switch label
593  ISwitch *configSwitch = IUFindSwitch(&ScopeConfigsSP, ("SCOPE_CONFIG" + std::to_string(i)).c_str());
594 
595  if (configSwitch != nullptr)
596  {
597  // The config is not used yet
598  if (!Found)
599  {
600  strncpy(configSwitch->label, ("Config #" + std::to_string(i) + " - Not used").c_str(), MAXINDILABEL);
601  continue;
602  }
603  // Empty switch label
604  if (ConfigName.empty())
605  {
606  strncpy(configSwitch->label, ("Config #" + std::to_string(i) + " - Untitled").c_str(), MAXINDILABEL);
607  continue;
608  }
609  strncpy(configSwitch->label, ("Config #" + std::to_string(i) + " - " + ConfigName).c_str(), MAXINDILABEL);
610  }
611  }
612  delXMLEle(RootXmlNode);
613  // Delete the joystick control to get the telescope config switch to the bottom of the page
614  deleteProperty("USEJOYSTICK");
615  // Recreate the switch control
618 }
619 
621 {
622  ISwitch *Switch = IUFindOnSwitch(&SlewRateSP);
623  double Rate = *((double *)Switch->aux);
624 
625  return Rate;
626 }
627 
629 {
630  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::MoveNS");
631 
632  double speed =
634  const char *dirStr = (dir == DIRECTION_NORTH) ? "North" : "South";
635 
636  if (IsMerlinMount())
637  {
638  speed = -speed;
639  }
640 
641  switch (command)
642  {
643  case MOTION_START:
644  DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
645  // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
646  Slew(AXIS2, speed, true);
647  moving = true;
648  break;
649 
650  case MOTION_STOP:
651  DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
652  SlowStop(AXIS2);
653  moving = false;
654  break;
655  }
656 
657  return true;
658 }
659 
661 {
662  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::MoveWE");
663 
664  double speed =
666  const char *dirStr = (dir == DIRECTION_WEST) ? "West" : "East";
667 
668  speed = -speed;
669 
670  switch (command)
671  {
672  case MOTION_START:
673  DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
674  // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
675  Slew(AXIS1, speed, true);
676  moving = true;
677  break;
678 
679  case MOTION_STOP:
680  DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
681  SlowStop(AXIS1);
682  moving = false;
683  break;
684  }
685 
686  return true;
687 }
688 
690 {
691  double Result = 0;
692 
693  DEBUGF(DBG_SCOPE, "GetParkDeltaAz: direction %d - position: %d", (int)target_direction, (int)target_position);
694  // Calculate delta degrees (target: NORTH)
695  if (target_position == PARK_NORTH)
696  {
697  if (target_direction == PARK_COUNTERCLOCKWISE)
698  {
699  Result = -CurrentAltAz.azimuth;
700  }
701  else
702  {
703  Result = 360 - CurrentAltAz.azimuth;
704  }
705  }
706  // Calculate delta degrees (target: EAST)
707  if (target_position == PARK_EAST)
708  {
709  if (target_direction == PARK_COUNTERCLOCKWISE)
710  {
711  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 90)
712  Result = -270 - CurrentAltAz.azimuth;
713  else
714  Result = -CurrentAltAz.azimuth + 90;
715  }
716  else
717  {
718  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 90)
719  Result = 90 - CurrentAltAz.azimuth;
720  else
721  Result = 360 - CurrentAltAz.azimuth + 90;
722  }
723  }
724  // Calculate delta degrees (target: SOUTH)
725  if (target_position == PARK_SOUTH)
726  {
727  if (target_direction == PARK_COUNTERCLOCKWISE)
728  {
729  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 180)
730  Result = -180 - CurrentAltAz.azimuth;
731  else
732  Result = -CurrentAltAz.azimuth + 180;
733  }
734  else
735  {
736  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 180)
737  Result = 180 - CurrentAltAz.azimuth;
738  else
739  Result = 360 - CurrentAltAz.azimuth + 180;
740  }
741  }
742  // Calculate delta degrees (target: WEST)
743  if (target_position == PARK_WEST)
744  {
745  if (target_direction == PARK_COUNTERCLOCKWISE)
746  {
747  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 270)
748  Result = -90 - CurrentAltAz.azimuth;
749  else
750  Result = -CurrentAltAz.azimuth + 270;
751  }
752  else
753  {
754  if (CurrentAltAz.azimuth > 0 && CurrentAltAz.azimuth < 270)
755  Result = 270 - CurrentAltAz.azimuth;
756  else
757  Result = 360 - CurrentAltAz.azimuth + 270;
758  }
759  }
760  if (Result >= 360)
761  {
762  Result -= 360;
763  }
764  if (Result <= -360)
765  {
766  Result += 360;
767  }
768  return Result;
769 }
770 
772 {
773  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::Park");
774  ParkPosition_t TargetPosition = PARK_NORTH;
775  ParkDirection_t TargetDirection = PARK_COUNTERCLOCKWISE;
776  double DeltaAlt = 0;
777  double DeltaAz = 0;
778 
779  // Determinate the target position and direction
780  if (IUFindSwitch(&ParkPositionSP, "PARK_NORTH") != nullptr &&
781  IUFindSwitch(&ParkPositionSP, "PARK_NORTH")->s == ISS_ON)
782  {
783  TargetPosition = PARK_NORTH;
784  }
785  if (IUFindSwitch(&ParkPositionSP, "PARK_EAST") != nullptr &&
786  IUFindSwitch(&ParkPositionSP, "PARK_EAST")->s == ISS_ON)
787  {
788  TargetPosition = PARK_EAST;
789  }
790  if (IUFindSwitch(&ParkPositionSP, "PARK_SOUTH") != nullptr &&
791  IUFindSwitch(&ParkPositionSP, "PARK_SOUTH")->s == ISS_ON)
792  {
793  TargetPosition = PARK_SOUTH;
794  }
795  if (IUFindSwitch(&ParkPositionSP, "PARK_WEST") != nullptr &&
796  IUFindSwitch(&ParkPositionSP, "PARK_WEST")->s == ISS_ON)
797  {
798  TargetPosition = PARK_WEST;
799  }
800 
801  if (IUFindSwitch(&ParkMovementDirectionSP, "PMD_COUNTERCLOCKWISE") != nullptr &&
802  IUFindSwitch(&ParkMovementDirectionSP, "PMD_COUNTERCLOCKWISE")->s == ISS_ON)
803  {
804  TargetDirection = PARK_COUNTERCLOCKWISE;
805  }
806  if (IUFindSwitch(&ParkMovementDirectionSP, "PMD_CLOCKWISE") != nullptr &&
807  IUFindSwitch(&ParkMovementDirectionSP, "PMD_CLOCKWISE")->s == ISS_ON)
808  {
809  TargetDirection = PARK_CLOCKWISE;
810  }
811  DeltaAz = GetParkDeltaAz(TargetDirection, TargetPosition);
812 
813  // Move the telescope to the desired position
814  long AltitudeOffsetMicrosteps = DegreesToMicrosteps(AXIS2, DeltaAlt);
815  long AzimuthOffsetMicrosteps = DegreesToMicrosteps(AXIS1, DeltaAz);
816 
817  DEBUGF(DBG_SCOPE, "Parking: Delta altitude %1.2f - delta azimuth %1.2f", DeltaAlt, DeltaAz);
818  DEBUGF(DBG_SCOPE, "Parking: Altitude offset %ld microsteps Azimuth offset %ld microsteps",
819  AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps);
820 
821  if (IUFindSwitch(&SlewModesSP, "SLEW_NORMAL")->s == ISS_ON)
822  {
823  SilentSlewMode = false;
824  }
825  else
826  {
827  SilentSlewMode = true;
828  }
829  SlewTo(AXIS1, AzimuthOffsetMicrosteps);
830  SlewTo(AXIS2, AltitudeOffsetMicrosteps);
831 
833  return true;
834 }
835 
837 {
838  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::UnPark");
839 
840  ParkPosition_t TargetPosition = PARK_NORTH;
841  ParkDirection_t TargetDirection = PARK_COUNTERCLOCKWISE;
842  double DeltaAlt = 0;
843  double DeltaAz = 0;
844 
845  // Determinate the target position and direction
846  if (IUFindSwitch(&UnparkPositionSP, "UNPARK_NORTH") != nullptr &&
847  IUFindSwitch(&UnparkPositionSP, "UNPARK_NORTH")->s == ISS_ON)
848  {
849  TargetPosition = PARK_NORTH;
850  }
851  if (IUFindSwitch(&UnparkPositionSP, "UNPARK_EAST") != nullptr &&
852  IUFindSwitch(&UnparkPositionSP, "UNPARK_EAST")->s == ISS_ON)
853  {
854  TargetPosition = PARK_EAST;
855  }
856  if (IUFindSwitch(&UnparkPositionSP, "UNPARK_SOUTH") != nullptr &&
857  IUFindSwitch(&UnparkPositionSP, "UNPARK_SOUTH")->s == ISS_ON)
858  {
859  TargetPosition = PARK_SOUTH;
860  }
861  if (IUFindSwitch(&UnparkPositionSP, "UNPARK_WEST") != nullptr &&
862  IUFindSwitch(&UnparkPositionSP, "UNPARK_WEST")->s == ISS_ON)
863  {
864  TargetPosition = PARK_WEST;
865  }
866 
867  // Note: The reverse direction is used for unparking.
868  if (IUFindSwitch(&ParkMovementDirectionSP, "PMD_COUNTERCLOCKWISE") != nullptr &&
869  IUFindSwitch(&ParkMovementDirectionSP, "PMD_COUNTERCLOCKWISE")->s == ISS_ON)
870  {
871  TargetDirection = PARK_CLOCKWISE;
872  }
873  if (IUFindSwitch(&ParkMovementDirectionSP, "PMD_CLOCKWISE") != nullptr &&
874  IUFindSwitch(&ParkMovementDirectionSP, "PMD_CLOCKWISE")->s == ISS_ON)
875  {
876  TargetDirection = PARK_COUNTERCLOCKWISE;
877  }
878  DeltaAz = GetParkDeltaAz(TargetDirection, TargetPosition);
879  // Altitude 3360 points the telescope upwards
880  DeltaAlt = CurrentAltAz.altitude - 3360;
881 
882  // Move the telescope to the desired position
883  long AltitudeOffsetMicrosteps = DegreesToMicrosteps(AXIS2, DeltaAlt);
884  long AzimuthOffsetMicrosteps = DegreesToMicrosteps(AXIS1, DeltaAz);
885 
886  DEBUGF(DBG_SCOPE, "Unparking: Delta altitude %1.2f - delta azimuth %1.2f", DeltaAlt, DeltaAz);
887  DEBUGF(DBG_SCOPE, "Unparking: Altitude offset %ld microsteps Azimuth offset %ld microsteps",
888  AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps);
889 
890  if (IUFindSwitch(&SlewModesSP, "SLEW_NORMAL")->s == ISS_ON)
891  {
892  SilentSlewMode = false;
893  }
894  else
895  {
896  SilentSlewMode = true;
897  }
898  SlewTo(AXIS1, AzimuthOffsetMicrosteps);
899  SlewTo(AXIS2, AltitudeOffsetMicrosteps);
900 
901  SetParked(false);
903  return true;
904 }
905 
907 {
908  // leave the following stuff in for the time being it is mostly harmless
909  // Quick check of the mount
910  if (UpdateCount == 0 && !GetMotorBoardVersion(AXIS1))
911  return false;
912 
913  if (!GetStatus(AXIS1))
914  return false;
915 
916  if (!GetStatus(AXIS2))
917  return false;
918 
919  // Update Axis Position
920  if (!GetEncoder(AXIS1))
921  return false;
922  if (!GetEncoder(AXIS2))
923  return false;
924 
925  if (UpdateCount % 5 == 0)
926  UpdateDetailedMountInformation(true);
927 
928  UpdateCount++;
929  if (TrackState == SCOPE_PARKING)
930  {
931  if (!IsInMotion(AXIS1) && !IsInMotion(AXIS2))
932  {
933  SetParked(true);
934  }
935  }
936 
937  // Calculate new RA DEC
939 
941  if (VerboseScopeStatus)
942  {
943  DEBUGF(DBG_SCOPE, "Axis2 encoder %ld initial %ld alt(degrees) %lf",
945  }
947  CurrentAltAz = AltAz;
948  if (VerboseScopeStatus)
949  {
950  DEBUGF(DBG_SCOPE, "Axis1 encoder %ld initial %ld az(degrees) %lf",
952  }
953 
954  INDI::IEquatorialCoordinates RaDec { 0, 0 };
955 
956  RaDec = GetRaDecPosition(AltAz.altitude, AltAz.azimuth);
957  if (VerboseScopeStatus)
958  {
959  DEBUGF(DBG_SCOPE, "New RA %lf (hours) DEC %lf (degrees)", RaDec.rightascension, RaDec.declination);
960  }
961  LogMessage("STATUS: Ra %lf Dec %lf - Alt %lf Az %lf - microsteps %ld %ld", RaDec.rightascension, RaDec.declination,
964  NewRaDec(RaDec.rightascension, RaDec.declination);
965  VerboseScopeStatus = false;
966  return true;
967 }
968 
969 
971 {
972  IUSaveConfigSwitch(fp, &SlewModesSP);
973  IUSaveConfigSwitch(fp, &WedgeModeSP);
974  IUSaveConfigSwitch(fp, &TrackLogModeSP);
975  IUSaveConfigNumber(fp, &GuidingRatesNP);
976  IUSaveConfigNumber(fp, &TrackingValuesNP);
977  IUSaveConfigSwitch(fp, &ParkMovementDirectionSP);
978  IUSaveConfigSwitch(fp, &ParkPositionSP);
979  IUSaveConfigSwitch(fp, &UnparkPositionSP);
980 
982 }
983 
984 
985 bool SkywatcherAltAzSimple::Sync(double ra, double dec)
986 {
987  DEBUG(DBG_SCOPE, "SkywatcherAltAzSimple::Sync");
988 
989  // Compute a telescope direction vector from the current encoders
990  if (!GetEncoder(AXIS1))
991  return false;
992  if (!GetEncoder(AXIS2))
993  return false;
994 
996 
997  AltAz = GetAltAzPosition(ra, dec);
998  double DeltaAz = CurrentAltAz.azimuth - AltAz.azimuth;
999  double DeltaAlt = CurrentAltAz.altitude - AltAz.altitude;
1000 
1001  LogMessage("SYNC: Ra %lf Dec %lf", ra, dec);
1002  MYDEBUGF(INDI::Logger::DBG_SESSION, "Sync ra: %lf dec: %lf => CurAz: %lf -> NewAz: %lf",
1003  ra, dec, CurrentAltAz.azimuth, AltAz.azimuth);
1008 
1009  // The tracking seconds should be reset to restart the drift compensation
1010  ResetTrackingSeconds = true;
1011 
1012  // Stop any movements
1014  {
1015  Abort();
1016  }
1017 
1018  // Might as well do this
1019  UpdateDetailedMountInformation(true);
1020  return true;
1021 }
1022 
1023 
1025 {
1026  static bool Slewing = false;
1027  static bool Tracking = false;
1028  static int ElapsedTime = 0;
1029 
1030  if (!ReadScopeStatus())
1031  {
1032  SetTimer(TimeoutDuration);
1033  return;
1034  }
1035 
1036  LogMessage("SET TIMER: %d msec", TimeoutDuration);
1037  SetTimer(TimeoutDuration);
1038  ElapsedTime += TimeoutDuration;
1039  if (ElapsedTime >= 5000)
1040  {
1041  ElapsedTime = 0;
1042  VerboseScopeStatus = true;
1043  }
1044 
1045  switch (TrackState)
1046  {
1047  case SCOPE_SLEWING:
1048  if (!Slewing)
1049  {
1050  LOG_INFO("Slewing started");
1051  TrackingStartTimer = 0;
1052  }
1053  TrackingMsecs = 0;
1054  GuideDeltaAlt = 0;
1055  GuideDeltaAz = 0;
1056  ResetGuidePulses();
1057  TimeoutDuration = 400;
1058  Tracking = false;
1059  Slewing = true;
1060  GuidingPulses.clear();
1061  if ((AxesStatus[AXIS1].FullStop) && (AxesStatus[AXIS2].FullStop))
1062  {
1063  TrackingStartTimer += TimeoutDuration;
1064  if (TrackingStartTimer < 3000)
1065  return;
1066 
1067  if (IUFindSwitch(&WedgeModeSP, "WEDGE_EQ")->s == ISS_ON ||
1068  IUFindSwitch(&CoordSP, "TRACK")->s == ISS_ON)
1069 
1070  {
1071  // Goto has finished start tracking
1073  }
1074  else
1075  {
1077  break;
1078  }
1079  }
1080  break;
1081 
1082  case SCOPE_TRACKING:
1083  {
1084  if (!Tracking)
1085  {
1086  LOG_INFO("Tracking started");
1087  TrackingMsecs = 0;
1088  TimeoutDuration = (int)IUFindNumber(&TrackingValuesNP, "TRACKING_TIMEOUT")->value;
1089  GuideDeltaAlt = 0;
1090  GuideDeltaAz = 0;
1091  ResetGuidePulses();
1092  }
1093 
1094  if (moving)
1095  {
1096  CurrentTrackingTarget.rightascension = EqN[AXIS_RA].value;
1097  CurrentTrackingTarget.declination = EqN[AXIS_DE].value;
1098  }
1099  else
1100  {
1101  // Restart the drift compensation after syncing
1102  if (ResetTrackingSeconds)
1103  {
1104  ResetTrackingSeconds = false;
1105  TrackingMsecs = 0;
1106  GuideDeltaAlt = 0;
1107  GuideDeltaAz = 0;
1108  ResetGuidePulses();
1109  }
1110  TrackingMsecs += TimeoutDuration;
1111  if (TrackingMsecs % 60000 == 0)
1112  {
1113  DEBUGF(INDI::Logger::DBG_SESSION, "Tracking in progress (%d seconds elapsed)", TrackingMsecs / 1000);
1114  }
1115  Tracking = true;
1116  Slewing = false;
1117  // Continue or start tracking
1118  // INDI::IHorizontalCoordinates AltAz { 0, 0 };
1119  INDI::IHorizontalCoordinates FutureAltAz { 0, 0 };
1120 
1121  // AltAz.altitude = MicrostepsToDegrees(AXIS2, CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2]);
1122  // AltAz.azimuth = MicrostepsToDegrees(AXIS1, CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1]);
1123  FutureAltAz = GetAltAzPosition(CurrentTrackingTarget.rightascension, CurrentTrackingTarget.declination,
1124  (double)TimeoutDuration / 1000);
1125  // DEBUGF(DBG_SCOPE,
1126  // "Tracking AXIS1 CurrentEncoder %ld OldTrackingTarget %ld AXIS2 CurrentEncoder %ld OldTrackingTarget "
1127  // "%ld",
1128  // CurrentEncoders[AXIS1], OldTrackingTarget[AXIS1], CurrentEncoders[AXIS2], OldTrackingTarget[AXIS2]);
1129  // DEBUGF(DBG_SCOPE,
1130  // "New Tracking Target Altitude %lf degrees %ld microsteps Azimuth %lf degrees %ld microsteps",
1131  // AltAz.altitude, DegreesToMicrosteps(AXIS2, AltAz.altitude), AltAz.azimuth, DegreesToMicrosteps(AXIS1, AltAz.azimuth));
1132 
1133  // Calculate the auto-guiding delta degrees
1134  for (auto pulse : GuidingPulses)
1135  {
1136  GuideDeltaAlt += pulse.DeltaAlt;
1137  GuideDeltaAz += pulse.DeltaAz;
1138  }
1139  GuidingPulses.clear();
1140 
1141  long AltitudeOffsetMicrosteps = DegreesToMicrosteps(AXIS2, FutureAltAz.altitude - CurrentAltAz.altitude + GuideDeltaAlt);
1142  long AzimuthOffsetMicrosteps = DegreesToMicrosteps(AXIS1, FutureAltAz.azimuth - CurrentAltAz.azimuth + GuideDeltaAz);
1143 
1144  // When the Alt/Az mount is on the top of an EQ mount, the EQ mount already tracks in
1145  // sidereal speed. Only autoguiding is enabled in tracking mode.
1146  if (IUFindSwitch(&WedgeModeSP, "WEDGE_EQ")->s == ISS_ON)
1147  {
1148  AltitudeOffsetMicrosteps = (long)((float)IUFindNumber(&GuidingRatesNP, "GUIDEDEC_RATE")->value * GuideDeltaAlt);
1149  AzimuthOffsetMicrosteps = (long)((float)IUFindNumber(&GuidingRatesNP, "GUIDERA_RATE")->value * GuideDeltaAz);
1150  GuideDeltaAlt = 0;
1151  GuideDeltaAz = 0;
1152  // Correct the movements of the EQ mount
1153  double DeltaAz = CurrentAltAz.azimuth - FutureAltAz.azimuth;
1154  double DeltaAlt = CurrentAltAz.altitude - FutureAltAz.altitude;
1155 
1160  }
1161 
1162  if (AltitudeOffsetMicrosteps > MicrostepsPerRevolution[AXIS2] / 2)
1163  {
1164  // Going the long way round - send it the other way
1165  AltitudeOffsetMicrosteps -= MicrostepsPerRevolution[AXIS2];
1166  }
1167  if (AzimuthOffsetMicrosteps > MicrostepsPerRevolution[AXIS1] / 2)
1168  {
1169  // Going the long way round - send it the other way
1170  AzimuthOffsetMicrosteps -= MicrostepsPerRevolution[AXIS1];
1171  }
1172  if (AltitudeOffsetMicrosteps < -MicrostepsPerRevolution[AXIS2] / 2)
1173  {
1174  // Going the long way round - send it the other way
1175  AltitudeOffsetMicrosteps += MicrostepsPerRevolution[AXIS2];
1176  }
1177  if (AzimuthOffsetMicrosteps < -MicrostepsPerRevolution[AXIS1] / 2)
1178  {
1179  // Going the long way round - send it the other way
1180  AzimuthOffsetMicrosteps += MicrostepsPerRevolution[AXIS1];
1181  }
1182 
1183  AltitudeOffsetMicrosteps = (long)((double)AltitudeOffsetMicrosteps * IUFindNumber(&TrackingValuesNP,
1184  "TRACKING_RATE_ALT")->value);
1185  AzimuthOffsetMicrosteps = (long)((double)AzimuthOffsetMicrosteps * IUFindNumber(&TrackingValuesNP,
1186  "TRACKING_RATE_AZ")->value);
1187 
1188  LogMessage("TRACKING: now Alt %lf Az %lf - future Alt %lf Az %lf - microsteps_diff Alt %ld Az %ld",
1189  CurrentAltAz.altitude, CurrentAltAz.azimuth, FutureAltAz.altitude, FutureAltAz.azimuth,
1190  AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps);
1191 
1192  // DEBUGF(DBG_SCOPE, "New Tracking Target AltitudeOffset %ld microsteps AzimuthOffset %ld microsteps",
1193  // AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps);
1194 
1195  if (0 != AzimuthOffsetMicrosteps)
1196  {
1197  SlewTo(AXIS1, AzimuthOffsetMicrosteps, false);
1198  }
1199  else
1200  {
1201  // Nothing to do - stop the axis
1202  SlowStop(AXIS1);
1203  }
1204 
1205  if (0 != AltitudeOffsetMicrosteps)
1206  {
1207  SlewTo(AXIS2, AltitudeOffsetMicrosteps, false);
1208  }
1209  else
1210  {
1211  // Nothing to do - stop the axis
1212  SlowStop(AXIS2);
1213  }
1214 
1215  DEBUGF(DBG_SCOPE, "Tracking - AXIS1 error %d (offset: %ld) AXIS2 error %d (offset: %ld)",
1216  OldTrackingTarget[AXIS1] - CurrentEncoders[AXIS1], AzimuthOffsetMicrosteps,
1217  OldTrackingTarget[AXIS2] - CurrentEncoders[AXIS2], AltitudeOffsetMicrosteps);
1218 
1219  OldTrackingTarget[AXIS1] = AzimuthOffsetMicrosteps + CurrentEncoders[AXIS1];
1220  OldTrackingTarget[AXIS2] = AltitudeOffsetMicrosteps + CurrentEncoders[AXIS2];
1221  }
1222  break;
1223  }
1224  break;
1225 
1226  default:
1227  if (Slewing)
1228  {
1229  LOG_INFO("Slewing stopped");
1230  }
1231  if (Tracking)
1232  {
1233  LOG_INFO("Tracking stopped");
1234  }
1235  TrackingMsecs = 0;
1236  GuideDeltaAlt = 0;
1237  GuideDeltaAz = 0;
1238  ResetGuidePulses();
1239  TimeoutDuration = 1000;
1240  Tracking = false;
1241  Slewing = false;
1242  GuidingPulses.clear();
1243  break;
1244  }
1245 }
1246 
1248 {
1250 
1251  if (isConnected())
1252  {
1253  // Fill in any real values now available MCInit should have been called already
1254  UpdateDetailedMountInformation(false);
1255 
1256  // Define our connected only properties to the base driver
1257  // e.g. defineProperty(MyNumberVectorPointer);
1258  // This will register our properties and send a IDDefXXXX message to any connected clients
1259  // I have now idea why I have to do this here as well as in ISGetProperties. It makes me
1260  // concerned there is a design or implementation flaw somewhere.
1261  defineProperty(&BasicMountInfoTP);
1262  defineProperty(&AxisOneInfoNP);
1263  defineProperty(&AxisOneStateSP);
1264  defineProperty(&AxisTwoInfoNP);
1265  defineProperty(&AxisTwoStateSP);
1266  defineProperty(&AxisOneEncoderValuesNP);
1267  defineProperty(&AxisTwoEncoderValuesNP);
1268  defineProperty(&SlewModesSP);
1269  defineProperty(&WedgeModeSP);
1270  defineProperty(&TrackLogModeSP);
1271  defineProperty(&GuidingRatesNP);
1272  defineProperty(&TrackingValuesNP);
1273  defineProperty(&ParkMovementDirectionSP);
1274  defineProperty(&ParkPositionSP);
1275  defineProperty(&UnparkPositionSP);
1276 
1279  return true;
1280  }
1281  else
1282  {
1283  // Delete any connected only properties from the base driver's list
1284  // e.g. deleteProperty(MyNumberVector.name);
1285  deleteProperty(BasicMountInfoTP.name);
1286  deleteProperty(AxisOneInfoNP.name);
1287  deleteProperty(AxisOneStateSP.name);
1288  deleteProperty(AxisTwoInfoNP.name);
1289  deleteProperty(AxisTwoStateSP.name);
1290  deleteProperty(AxisOneEncoderValuesNP.name);
1291  deleteProperty(AxisTwoEncoderValuesNP.name);
1292  deleteProperty(SlewModesSP.name);
1293  deleteProperty(WedgeModeSP.name);
1294  deleteProperty(TrackLogModeSP.name);
1295  deleteProperty(GuidingRatesNP.name);
1296  deleteProperty(TrackingValuesNP.name);
1297  deleteProperty(ParkMovementDirectionSP.name);
1298  deleteProperty(ParkPositionSP.name);
1299  deleteProperty(UnparkPositionSP.name);
1300 
1303  return true;
1304  }
1305 }
1306 
1308 {
1309  GuidingPulse Pulse;
1310 
1311  LogMessage("GUIDE NORTH: %1.4f", ms);
1312  Pulse.DeltaAz = 0;
1313  Pulse.DeltaAlt = ms;
1314  GuidingPulses.push_back(Pulse);
1315  return IPS_OK;
1316 }
1317 
1319 {
1320  GuidingPulse Pulse;
1321 
1322  LogMessage("GUIDE SOUTH: %1.4f", ms);
1323  Pulse.DeltaAz = 0;
1324  Pulse.DeltaAlt = -ms;
1325  GuidingPulses.push_back(Pulse);
1326  return IPS_OK;
1327 }
1328 
1330 {
1331  GuidingPulse Pulse;
1332 
1333  LogMessage("GUIDE WEST: %1.4f", ms);
1334  Pulse.DeltaAz = ms;
1335  Pulse.DeltaAlt = 0;
1336  GuidingPulses.push_back(Pulse);
1337  return IPS_OK;
1338 }
1339 
1341 {
1342  GuidingPulse Pulse;
1343 
1344  LogMessage("GUIDE EAST: %1.4f", ms);
1345  Pulse.DeltaAz = -ms;
1346  Pulse.DeltaAlt = 0;
1347  GuidingPulses.push_back(Pulse);
1348  return IPS_OK;
1349 }
1350 
1351 // Private methods
1352 
1353 void SkywatcherAltAzSimple::ResetGuidePulses()
1354 {
1355  GuidingPulses.clear();
1356 }
1357 
1358 int SkywatcherAltAzSimple::recover_tty_reconnect()
1359 {
1360  if (!RecoverAfterReconnection && !SerialPortName.empty() && !FileExists(SerialPortName))
1361  {
1362  RecoverAfterReconnection = true;
1365  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
1366  if (!serialConnection->Connect())
1367  {
1368  RecoverAfterReconnection = true;
1369  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
1370  if (!serialConnection->Connect())
1371  {
1372  RecoverAfterReconnection = false;
1373  return 0;
1374  }
1375  }
1377  SerialPortName = serialConnection->port();
1378  RecoverAfterReconnection = false;
1379  return 1;
1380  }
1381  else
1382  {
1383  return -1;
1384  }
1385 }
1386 
1387 void SkywatcherAltAzSimple::UpdateDetailedMountInformation(bool InformClient)
1388 {
1389  bool BasicMountInfoHasChanged = false;
1390 
1391  if (std::string(BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION].text) != std::to_string(MCVersion))
1392  {
1393  IUSaveText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], std::to_string(MCVersion).c_str());
1394  BasicMountInfoHasChanged = true;
1395  }
1396  if (std::string(BasicMountInfoT[MOUNT_CODE].text) != std::to_string(MountCode))
1397  {
1398  IUSaveText(&BasicMountInfoT[MOUNT_CODE], std::to_string(MountCode).c_str());
1399  BasicMountInfoHasChanged = true;
1400  }
1401  if (std::string(BasicMountInfoT[IS_DC_MOTOR].text) != std::to_string(IsDCMotor))
1402  {
1403  IUSaveText(&BasicMountInfoT[IS_DC_MOTOR], std::to_string(IsDCMotor).c_str());
1404  BasicMountInfoHasChanged = true;
1405  }
1406  if (BasicMountInfoHasChanged && InformClient)
1407  IDSetText(&BasicMountInfoTP, nullptr);
1408 
1409  if (MountCode == 128)
1410  IUSaveText(&BasicMountInfoT[MOUNT_NAME], "Merlin");
1411  else if (MountCode >= 129 && MountCode <= 143)
1412  IUSaveText(&BasicMountInfoT[MOUNT_NAME], "Az Goto");
1413  else if (MountCode >= 144 && MountCode <= 159)
1414  IUSaveText(&BasicMountInfoT[MOUNT_NAME], "Dob Goto");
1415  else if (MountCode == 161)
1416  IUSaveText(&BasicMountInfoT[MOUNT_NAME], "Virtuoso");
1417  else if (MountCode >= 160)
1418  IUSaveText(&BasicMountInfoT[MOUNT_NAME], "AllView Goto");
1419 
1420  bool AxisOneInfoHasChanged = false;
1421 
1422  if (AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[0])
1423  {
1424  AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[0];
1425  AxisOneInfoHasChanged = true;
1426  }
1427  if (AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[0])
1428  {
1429  AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[0];
1430  AxisOneInfoHasChanged = true;
1431  }
1432  if (AxisOneInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[0])
1433  {
1434  AxisOneInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[0];
1435  AxisOneInfoHasChanged = true;
1436  }
1437  if (AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[0])
1438  {
1439  AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[0];
1440  AxisOneInfoHasChanged = true;
1441  }
1442  if (AxisOneInfoHasChanged && InformClient)
1443  IDSetNumber(&AxisOneInfoNP, nullptr);
1444 
1445  bool AxisOneStateHasChanged = false;
1446  if (AxisOneStateS[FULL_STOP].s != (AxesStatus[0].FullStop ? ISS_ON : ISS_OFF))
1447  {
1448  AxisOneStateS[FULL_STOP].s = AxesStatus[0].FullStop ? ISS_ON : ISS_OFF;
1449  AxisOneStateHasChanged = true;
1450  }
1451  if (AxisOneStateS[SLEWING].s != (AxesStatus[0].Slewing ? ISS_ON : ISS_OFF))
1452  {
1453  AxisOneStateS[SLEWING].s = AxesStatus[0].Slewing ? ISS_ON : ISS_OFF;
1454  AxisOneStateHasChanged = true;
1455  }
1456  if (AxisOneStateS[SLEWING_TO].s != (AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF))
1457  {
1458  AxisOneStateS[SLEWING_TO].s = AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF;
1459  AxisOneStateHasChanged = true;
1460  }
1461  if (AxisOneStateS[SLEWING_FORWARD].s != (AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF))
1462  {
1463  AxisOneStateS[SLEWING_FORWARD].s = AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF;
1464  AxisOneStateHasChanged = true;
1465  }
1466  if (AxisOneStateS[HIGH_SPEED].s != (AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF))
1467  {
1468  AxisOneStateS[HIGH_SPEED].s = AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF;
1469  AxisOneStateHasChanged = true;
1470  }
1471  if (AxisOneStateS[NOT_INITIALISED].s != (AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF))
1472  {
1473  AxisOneStateS[NOT_INITIALISED].s = AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF;
1474  AxisOneStateHasChanged = true;
1475  }
1476  if (AxisOneStateHasChanged && InformClient)
1477  IDSetSwitch(&AxisOneStateSP, nullptr);
1478 
1479  bool AxisTwoInfoHasChanged = false;
1480  if (AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[1])
1481  {
1482  AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[1];
1483  AxisTwoInfoHasChanged = true;
1484  }
1485  if (AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[1])
1486  {
1487  AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[1];
1488  AxisTwoInfoHasChanged = true;
1489  }
1490  if (AxisTwoInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[1])
1491  {
1492  AxisTwoInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[1];
1493  AxisTwoInfoHasChanged = true;
1494  }
1495  if (AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[1])
1496  {
1497  AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[1];
1498  AxisTwoInfoHasChanged = true;
1499  }
1500  if (AxisTwoInfoHasChanged && InformClient)
1501  IDSetNumber(&AxisTwoInfoNP, nullptr);
1502 
1503  bool AxisTwoStateHasChanged = false;
1504  if (AxisTwoStateS[FULL_STOP].s != (AxesStatus[1].FullStop ? ISS_ON : ISS_OFF))
1505  {
1506  AxisTwoStateS[FULL_STOP].s = AxesStatus[1].FullStop ? ISS_ON : ISS_OFF;
1507  AxisTwoStateHasChanged = true;
1508  }
1509  if (AxisTwoStateS[SLEWING].s != (AxesStatus[1].Slewing ? ISS_ON : ISS_OFF))
1510  {
1511  AxisTwoStateS[SLEWING].s = AxesStatus[1].Slewing ? ISS_ON : ISS_OFF;
1512  AxisTwoStateHasChanged = true;
1513  }
1514  if (AxisTwoStateS[SLEWING_TO].s != (AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF))
1515  {
1516  AxisTwoStateS[SLEWING_TO].s = AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF;
1517  AxisTwoStateHasChanged = true;
1518  }
1519  if (AxisTwoStateS[SLEWING_FORWARD].s != (AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF))
1520  {
1521  AxisTwoStateS[SLEWING_FORWARD].s = AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF;
1522  AxisTwoStateHasChanged = true;
1523  }
1524  if (AxisTwoStateS[HIGH_SPEED].s != (AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF))
1525  {
1526  AxisTwoStateS[HIGH_SPEED].s = AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF;
1527  AxisTwoStateHasChanged = true;
1528  }
1529  if (AxisTwoStateS[NOT_INITIALISED].s != (AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF))
1530  {
1531  AxisTwoStateS[NOT_INITIALISED].s = AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF;
1532  AxisTwoStateHasChanged = true;
1533  }
1534  if (AxisTwoStateHasChanged && InformClient)
1535  IDSetSwitch(&AxisTwoStateSP, nullptr);
1536 
1537  bool AxisOneEncoderValuesHasChanged = false;
1538  if ((AxisOneEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS1]) ||
1539  (AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1]))
1540  {
1541  AxisOneEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS1];
1542  AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS1] / 3600.0;
1543  AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1];
1544  AxisOneEncoderValuesN[DEGREES_FROM_INITIAL].value =
1546  AxisOneEncoderValuesHasChanged = true;
1547  }
1548  if (AxisOneEncoderValuesHasChanged && InformClient)
1549  IDSetNumber(&AxisOneEncoderValuesNP, nullptr);
1550 
1551  bool AxisTwoEncoderValuesHasChanged = false;
1552  if ((AxisTwoEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS2]) ||
1553  (AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2]))
1554  {
1555  AxisTwoEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS2];
1556  AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS2] / 3600.0;
1557  AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2];
1558  AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL].value =
1560  AxisTwoEncoderValuesHasChanged = true;
1561  }
1562  if (AxisTwoEncoderValuesHasChanged && InformClient)
1563  IDSetNumber(&AxisTwoEncoderValuesNP, nullptr);
1564 }
1565 
1566 
1567 INDI::IHorizontalCoordinates SkywatcherAltAzSimple::GetAltAzPosition(double ra, double dec, double offset_in_sec)
1568 {
1572  double JulianOffset = offset_in_sec / (24.0 * 60 * 60);
1573 
1574  // Set the current location
1575  if (IUFindSwitch(&WedgeModeSP, "WEDGE_SIMPLE")->s != ISS_OFF ||
1576  IUFindSwitch(&WedgeModeSP, "WEDGE_EQ")->s != ISS_OFF)
1577  {
1578  if (LocationN[LOCATION_LATITUDE].value > 0)
1579  {
1580  Location.latitude = 90;
1581  Location.longitude = 0;
1582  }
1583  else
1584  {
1585  Location.latitude = -90;
1586  Location.longitude = 0;
1587  }
1588  }
1589 
1590  INDI::EquatorialToHorizontal(&Eq, &m_Location, ln_get_julian_from_sys() + JulianOffset, &AltAz);
1591  return AltAz;
1592 }
1593 
1594 
1595 INDI::IEquatorialCoordinates SkywatcherAltAzSimple::GetRaDecPosition(double alt, double az)
1596 {
1598  INDI::IEquatorialCoordinates Eq { 0, 0 };
1599  INDI::IHorizontalCoordinates AltAz { az, alt };
1600 
1601  // Set the current location
1602  if (IUFindSwitch(&WedgeModeSP, "WEDGE_SIMPLE")->s != ISS_OFF ||
1603  IUFindSwitch(&WedgeModeSP, "WEDGE_EQ")->s != ISS_OFF)
1604  {
1605  if (LocationN[LOCATION_LATITUDE].value > 0)
1606  {
1607  Location.latitude = 90;
1608  Location.longitude = 0;
1609  }
1610  else
1611  {
1612  Location.latitude = -90;
1613  Location.longitude = 0;
1614  }
1615  }
1616 
1617  INDI::HorizontalToEquatorial(&AltAz, &m_Location, ln_get_julian_from_sys(), &Eq);
1618  return Eq;
1619 }
1620 
1621 
1622 void SkywatcherAltAzSimple::LogMessage(const char* format, ...)
1623 {
1624  if (!format || IUFindSwitch(&TrackLogModeSP, "TRACKLOG_ENABLED")->s == ISS_OFF)
1625  return;
1626 
1627  va_list Ap;
1628  va_start(Ap, format);
1629 
1630  char TempStr[512];
1631  std::ofstream LogFile;
1632 
1633  LogFile.open(TrackLogFileName.c_str(), std::ios::out | std::ios::app);
1634  if (!LogFile.is_open())
1635  {
1636  return;
1637  }
1638  vsnprintf(TempStr, sizeof(TempStr), format, Ap);
1639  LogFile << GetLogTimestamp() << " | " << TempStr << "\n";
1640  LogFile.close();
1641  va_end(Ap);
1642 }
The Interface class is the base class for all INDI connection plugins.
virtual std::string name()=0
bool Refresh(bool silent=false)
virtual bool Disconnect() override
Disconnect Disconnect from device.
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
virtual const char * port()
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
void addConfigurationControl()
Add Configuration control to the driver.
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 ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Process the client newBLOB 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.
uint16_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
bool CheckFile(const std::string &file_name, bool writable) const
Check if a file exists and it is readable.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
const std::string ScopeConfigLabelApXmlNode
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
const std::string ScopeConfigDeviceXmlNode
ISwitchVectorProperty CoordSP
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...
ISwitchVectorProperty ScopeConfigsSP
bool isParked()
isParked is mount currently parked?
ISwitchVectorProperty SlewRateSP
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
const std::string ScopeConfigFileName
The telescope/guide scope configuration file name.
const std::string ScopeConfigRootXmlNode
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
INumber EqN[2]
const std::string ScopeConfigNameXmlNode
IGeographicCoordinates m_Location
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool IsMerlinMount() const
Check if the current mount is a Virtuoso (AltAz)
void SetSerialPort(int port)
Set the serail port to be usb for mount communication.
bool GetMotorBoardVersion(AXISID Axis)
static constexpr double LOW_SPEED_MARGIN
double MicrostepsToDegrees(AXISID Axis, long Microsteps)
Convert microsteps to angle in degrees.
unsigned long MountCode
void SlewTo(AXISID Axis, long OffsetInMicrosteps, bool verbose=true)
Slew to the given offset and stop.
long ZeroPositionEncoders[2]
Zero position encoder values (microsteps).
unsigned int DBG_SCOPE
long CurrentEncoders[2]
Current encoder values (microsteps).
bool GetStatus(AXISID Axis)
unsigned long MCVersion
long PolarisPositionEncoders[2]
Polaris position (initial) encoder values (microsteps).
bool GetEncoder(AXISID Axis)
Set the CurrentEncoders status variable to the current encoder value in microsteps for the specified ...
long MicrostepsPerWormRevolution[2]
long HighSpeedRatio[2]
double MicrostepsPerDegree[2]
long DegreesToMicrosteps(AXISID Axis, double AngleInDegrees)
Convert angle in degrees to microsteps.
bool SlowStop(AXISID Axis)
Bring the axis to slow stop in the distance specified by SetSlewModeDeccelerationRampLength.
bool InitMount()
Initialize the communication to the mount.
void Slew(AXISID Axis, double SpeedInRadiansPerSecond, bool IgnoreSilentMode=true)
Start the axis slewing at the given rate.
long MicrostepsPerRevolution[2]
bool IsInMotion(AXISID Axis)
Check if an axis is moving.
long StepperClockFrequency[2]
INDI::Telescope * pChildTelescope
AXISSTATUS AxesStatus[2]
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual const char * getDefaultName() override
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n) override
Process the client newBLOB command.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
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...
virtual bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool Handshake() override
perform handshake with device to check communication
virtual bool Park() override
Park the telescope to its home position.
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual void TimerHit() override
Called when setTimer() time is up.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
double GetParkDeltaAz(ParkDirection_t target_direction, ParkPosition_t target_position)
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
#define MAXINDILABEL
Definition: indiapi.h:192
@ ISR_NOFMANY
Definition: indiapi.h:175
@ ISR_ATMOST1
Definition: indiapi.h:174
#define MAXINDINAME
Definition: indiapi.h:191
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
void tty_set_generic_udp_format(int enabled)
Definition: indicom.c:370
void IDLog(const char *fmt,...)
Definition: indicom.c:316
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
Implementations for common driver routines.
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
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
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 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
ISwitch * IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
Find an ISwitch member in a vector switch property.
Definition: indidevapi.c:76
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
void IDMessage(const char *dev, const char *fmt,...)
Definition: indidriver.c:960
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
#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 LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#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
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition: lilxml.cpp:600
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 * 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
std::vector< uint8_t > buffer
void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, IHorizontalCoordinates *position)
EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates.
Definition: libastro.cpp:140
void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, IEquatorialCoordinates *position)
HorizontalToEquatorial Calculate Equatorial EOD Coordinates from horizontal coordinates.
Definition: libastro.cpp:156
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
ParkDirection_t
@ PARK_CLOCKWISE
@ PARK_COUNTERCLOCKWISE
ParkPosition_t
@ PARK_SOUTH
@ PARK_EAST
@ PARK_NORTH
@ PARK_WEST
#define MYDEBUGF(priority, msg,...)
Definition: skywatcherAPI.h:27
#define SLEWMODES
double SlewSpeeds[SLEWMODES]
std::unique_ptr< SkywatcherAltAzSimple > SkywatcherAltAzSimplePtr(new SkywatcherAltAzSimple())
void ISPoll(void *p)
Definition: fli_wheel.c:393
bool NotInitialized
Definition: skywatcherAPI.h:46
bool SlewingTo
Definition: skywatcherAPI.h:43
bool HighSpeed
Definition: skywatcherAPI.h:45
bool SlewingForward
Definition: skywatcherAPI.h:44
One number descriptor.
One switch descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
Switch vector property descriptor.
Definition: indiapi.h:367
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250