Instrument Neutral Distributed Interface INDI  2.0.2
skywatcherAPIMount.cpp
Go to the documentation of this file.
1 
24 #include "skywatcherAPIMount.h"
25 
26 #include "indicom.h"
28 #include "alignment/DriverCommon.h"
30 
31 #include <chrono>
32 #include <thread>
33 
34 #include <sys/stat.h>
35 
36 #define DEBUG_PID
37 
38 using namespace INDI::AlignmentSubsystem;
39 
40 // We declare an auto pointer to SkywatcherAPIMount.
41 static std::unique_ptr<SkywatcherAPIMount> SkywatcherAPIMountPtr(new SkywatcherAPIMount());
42 
43 /* Preset Slew Speeds */
44 #define SLEWMODES 9
45 static double SlewSpeeds[SLEWMODES] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 600.0 };
46 
51 {
52  // Set up the logging pointer in SkyWatcherAPI
53  pChildTelescope = this;
54  SetTelescopeCapability(TELESCOPE_CAN_PARK |
55  TELESCOPE_CAN_SYNC |
56  TELESCOPE_CAN_GOTO |
57  TELESCOPE_CAN_ABORT |
58  TELESCOPE_HAS_TIME |
59  TELESCOPE_HAS_LOCATION |
60  TELESCOPE_HAS_TRACK_MODE |
61  TELESCOPE_CAN_CONTROL_TRACK,
62  SLEWMODES);
63 
64  m_LastCustomDirection[AXIS1] = m_LastCustomDirection[AXIS2] = 0;
65  setVersion(1, 8);
66 }
67 
72 {
73  DEBUG(DBG_SCOPE, "SkywatcherAPIMount::Handshake");
74  if (!getActiveConnection()->name().compare("CONNECTION_TCP"))
75  {
77  }
78 
79  SetSerialPort(PortFD);
80  bool Result = InitMount();
81  DEBUGF(DBG_SCOPE, "SkywatcherAPIMount::Handshake - Result: %d", Result);
82  return Result;
83 }
84 
89 {
90  return "Skywatcher Alt-Az";
91 }
92 
97 {
98  // Allow the base class to initialise its visible before connection properties
100 
101  for (int i = 0; i < SlewRateSP.nsp; ++i)
102  {
103  sprintf(SlewRateSP.sp[i].label, "%.fx", SlewSpeeds[i]);
104  SlewRateSP.sp[i].aux = &SlewSpeeds[i];
105  }
106  strncpy(SlewRateSP.sp[SlewRateSP.nsp - 1].name, "SLEW_MAX", MAXINDINAME);
107 
108  AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
109  AddTrackMode("TRACK_SOLAR", "Solar");
110  AddTrackMode("TRACK_LUNAR", "Lunar");
111 
112  // Add default properties
113  addDebugControl();
114  addConfigurationControl();
115 
116  // Add alignment properties
117  InitAlignmentProperties(this);
118 
119  // Force the alignment system to always be on
120  getSwitch("ALIGNMENT_SUBSYSTEM_ACTIVE")[0].setState(ISS_ON);
121 
122  // Set up property variables
123  IUFillText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], "MOTOR_CONTROL_FIRMWARE_VERSION",
124  "Motor control firmware version", "-");
125  IUFillText(&BasicMountInfoT[MOUNT_CODE], "MOUNT_CODE", "Mount code", "-");
126  IUFillText(&BasicMountInfoT[MOUNT_NAME], "MOUNT_NAME", "Mount name", "-");
127  IUFillText(&BasicMountInfoT[IS_DC_MOTOR], "IS_DC_MOTOR", "Is DC motor", "-");
128  IUFillTextVector(&BasicMountInfoTP, BasicMountInfoT, 4, getDeviceName(), "BASIC_MOUNT_INFO",
129  "Basic mount information", MountInfoTab, IP_RO, 60, IPS_IDLE);
130 
131  IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
132  "%.0f", 0, 0xFFFFFF, 1, 0);
133  IUFillNumber(&AxisOneInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Stepper clock frequency", "%.0f", 0,
134  0xFFFFFF, 1, 0);
135  IUFillNumber(&AxisOneInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
136  IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
137  "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
138 
139  IUFillNumberVector(&AxisOneInfoNP, AxisOneInfoN, 4, getDeviceName(), "AXIS_ONE_INFO", "Axis one information",
140  MountInfoTab, IP_RO, 60, IPS_IDLE);
141 
142  IUFillSwitch(&AxisOneStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
143  IUFillSwitch(&AxisOneStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
144  IUFillSwitch(&AxisOneStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
145  IUFillSwitch(&AxisOneStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
146  IUFillSwitch(&AxisOneStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
147  IUFillSwitch(&AxisOneStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
148  IUFillSwitchVector(&AxisOneStateSP, AxisOneStateS, 6, getDeviceName(), "AXIS_ONE_STATE", "Axis one state",
149  MountInfoTab, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
150 
151  IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
152  "%.0f", 0, 0xFFFFFF, 1, 0);
153  IUFillNumber(&AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Step timer frequency", "%.0f", 0,
154  0xFFFFFF, 1, 0);
155  IUFillNumber(&AxisTwoInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
156  IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
157  "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
158 
159  IUFillNumberVector(&AxisTwoInfoNP, AxisTwoInfoN, 4, getDeviceName(), "AXIS_TWO_INFO", "Axis two information",
160  MountInfoTab, IP_RO, 60, IPS_IDLE);
161 
162  IUFillSwitch(&AxisTwoStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
163  IUFillSwitch(&AxisTwoStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
164  IUFillSwitch(&AxisTwoStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
165  IUFillSwitch(&AxisTwoStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
166  IUFillSwitch(&AxisTwoStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
167  IUFillSwitch(&AxisTwoStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
168  IUFillSwitchVector(&AxisTwoStateSP, AxisTwoStateS, 6, getDeviceName(), "AXIS_TWO_STATE", "Axis two state",
169  MountInfoTab, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
170 
171  IUFillNumber(&AxisOneEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
172  IUFillNumber(&AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
173  "%.4f", 0, 0xFFFFFF, 1, 0);
174  IUFillNumber(&AxisOneEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
175  0xFFFFFF, 1, 0);
176  IUFillNumber(&AxisOneEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
177  -1000.0, 1000.0, 1, 0);
178 
179  IUFillNumberVector(&AxisOneEncoderValuesNP, AxisOneEncoderValuesN, 4, getDeviceName(), "AXIS1_ENCODER_VALUES",
180  "Axis 1 Encoder values", MountInfoTab, IP_RO, 60, IPS_IDLE);
181 
182  IUFillNumber(&AxisTwoEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
183  IUFillNumber(&AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
184  "%.4f", 0, 0xFFFFFF, 1, 0);
185  IUFillNumber(&AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
186  0xFFFFFF, 1, 0);
187  IUFillNumber(&AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
188  -1000.0, 1000.0, 1, 0);
189 
190  IUFillNumberVector(&AxisTwoEncoderValuesNP, AxisTwoEncoderValuesN, 4, getDeviceName(), "AXIS2_ENCODER_VALUES",
191  "Axis 2 Encoder values", MountInfoTab, IP_RO, 60, IPS_IDLE);
192  // Register any visible before connection properties
193 
194  // Slew modes
195  IUFillSwitch(&SlewModesS[SLEW_SILENT], "SLEW_SILENT", "Silent", ISS_OFF);
196  IUFillSwitch(&SlewModesS[SLEW_NORMAL], "SLEW_NORMAL", "Normal", ISS_ON);
197  IUFillSwitchVector(&SlewModesSP, SlewModesS, 2, getDeviceName(), "TELESCOPE_MOTION_SLEWMODE", "Slew Mode",
199 
200  // SoftPEC modes
201  IUFillSwitch(&SoftPECModesS[SOFTPEC_ENABLED], "SOFTPEC_ENABLED", "Enable for tracking", ISS_OFF);
202  IUFillSwitch(&SoftPECModesS[SOFTPEC_DISABLED], "SOFTPEC_DISABLED", "Disabled", ISS_ON);
203  IUFillSwitchVector(&SoftPECModesSP, SoftPECModesS, 2, getDeviceName(), "TELESCOPE_MOTION_SOFTPECMODE",
204  "SoftPEC Mode", MOTION_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
205 
206  // SoftPEC value for tracking mode
207  IUFillNumber(&SoftPecN, "SOFTPEC_VALUE", "degree/minute (Alt)", "%1.3f", 0.001, 1.0, 0.001, 0.009);
208  IUFillNumberVector(&SoftPecNP, &SoftPecN, 1, getDeviceName(), "SOFTPEC", "SoftPEC Value", MOTION_TAB, IP_RW, 60,
209  IPS_IDLE);
210 
211  // Guiding rates for RA/DEC axes
212  IUFillNumber(&GuidingRatesN[0], "GUIDERA_RATE", "arcsec/seconds (RA)", "%1.3f", 1.0, 6000.0, 1.0, 120.0);
213  IUFillNumber(&GuidingRatesN[1], "GUIDEDEC_RATE", "arcsec/seconds (Dec)", "%1.3f", 1.0, 6000.0, 1.0, 120.0);
214  IUFillNumberVector(&GuidingRatesNP, GuidingRatesN, 2, getDeviceName(), "GUIDE_RATES", "Guide Rates", MOTION_TAB,
215  IP_RW, 60, IPS_IDLE);
216 
217  // AUX Encoders
218  AUXEncoderSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_ON);
219  AUXEncoderSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_OFF);
220  AUXEncoderSP.fill(getDeviceName(), "AUX_ENCODERS", "AUX Encoders", TRACKING_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
221 
222  // Snap port
223  SnapPortSP[INDI_ENABLED].fill("INDI_ENABLED", "On", ISS_OFF);
224  SnapPortSP[INDI_DISABLED].fill("INDI_DISABLED", "Off", ISS_ON);
225  SnapPortSP.fill(getDeviceName(), "SNAP_PORT", "Snap Port", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
226 
227  // PID Control
228  Axis1PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0.1, 100, 1, 1.1);
229  Axis1PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 500, 10, 0.01);
230  Axis1PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 500, 10, 0.65);
231  Axis1PIDNP.fill(getDeviceName(), "AXIS1_PID", "Axis1 PID", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
232 
233  Axis2PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0.1, 100, 1, 0.75);
234  Axis2PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 100, 10, 0.01);
235  Axis2PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 100, 10, 0.13);
236  Axis2PIDNP.fill(getDeviceName(), "AXIS2_PID", "Axis2 PID", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
237 
238  // Dead Zone
239  AxisDeadZoneNP[AXIS1].fill("AXIS1", "AZ (steps)", "%.f", 0, 100, 10, 10);
240  AxisDeadZoneNP[AXIS2].fill("AXIS2", "AL (steps)", "%.f", 0, 100, 10, 10);
241  AxisDeadZoneNP.fill(getDeviceName(), "DEAD_ZONE", "Dead Zone", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
242 
243  // Clock Multiplier
244  AxisClockNP[AXIS1].fill("AXIS1", "AZ %", "%.f", 1, 200, 10, 100);
245  AxisClockNP[AXIS2].fill("AXIS2", "AL %", "%.f", 1, 200, 10, 100);
246  AxisClockNP.fill(getDeviceName(), "AXIS_CLOCK", "Clock Rate", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
247 
248  // Offsets
249  AxisOffsetNP[RAOffset].fill("RAOffset", "RA (deg)", "%.2f", -1, 1, 0.05, 0);
250  AxisOffsetNP[DEOffset].fill("DEOffset", "DE (deg)", "%.2f", -1, 1, 0.05, 0);
251  AxisOffsetNP[AZSteps].fill("AZEncoder", "AZ (steps)", "%.f", -10000, 10000, 1000, 0);
252  AxisOffsetNP[ALSteps].fill("ALEncoder", "AL (steps)", "%.f", -10000, 10000, 1000, -100.0);
253  AxisOffsetNP[JulianOffset].fill("JulianOffset", "JD (s)", "%.f", -5, 5, 0.1, 0);
254  AxisOffsetNP.fill(getDeviceName(), "AXIS_OFFSET", "Offsets", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
255 
256  // Tracking Rate
257  Axis1TrackRateNP[TrackDirection].fill("TrackDirection", "West/East", "%.f", 0, 1, 1, 0);
258  Axis1TrackRateNP[TrackClockRate].fill("TrackClockRate", "Freq/Step (Hz/s)", "%.f", 0, 16000000, 500000, 0);
259  Axis1TrackRateNP.fill(getDeviceName(), "AXIS1TrackRate", "Axis 1 Track", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
260 
261  Axis2TrackRateNP[TrackDirection].fill("TrackDirection", "North/South", "%.f", 0, 1, 1, 0);
262  Axis2TrackRateNP[TrackClockRate].fill("TrackClockRate", "Freq/Stel (Hz/s)", "%.f", 0, 16000000, 500000, 0);
263  Axis2TrackRateNP.fill(getDeviceName(), "AXIS2TrackRate", "Axis 2 Track", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
264 
265 
266  tcpConnection->setDefaultHost("192.168.4.1");
267  tcpConnection->setDefaultPort(11880);
268  tcpConnection->setConnectionType(Connection::TCP::TYPE_UDP);
269 
270  if (strstr(getDeviceName(), "Wired"))
271  {
272  setActiveConnection(serialConnection);
273  }
274  else if (strstr(getDeviceName(), "GTi"))
275  {
276  setActiveConnection(tcpConnection);
277  tcpConnection->setLANSearchEnabled(true);
278  }
279 
280  SetParkDataType(PARK_AZ_ALT_ENCODER);
281 
282  // Guiding support
283  initGuiderProperties(getDeviceName(), GUIDE_TAB);
284  setDriverInterface(getDriverInterface() | GUIDER_INTERFACE);
285 
286  //Set default values in parent class
287  IUFindNumber(&ScopeParametersNP, "TELESCOPE_APERTURE")->value = 200;
288  IUFindNumber(&ScopeParametersNP, "TELESCOPE_FOCAL_LENGTH")->value = 2000;
289  IUFindNumber(&ScopeParametersNP, "GUIDER_APERTURE")->value = 30;
290  IUFindNumber(&ScopeParametersNP, "GUIDER_FOCAL_LENGTH")->value = 120;
291 
292  return true;
293 }
294 
298 bool SkywatcherAPIMount::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
299  char *formats[], char *names[], int n)
300 {
301  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
302  {
303  // It is for us
304  ProcessAlignmentBLOBProperties(this, name, sizes, blobsizes, blobs, formats, names, n);
305  }
306  // Pass it up the chain
307  return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
308 }
309 
313 bool SkywatcherAPIMount::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
314 {
315  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
316  {
317  ProcessAlignmentNumberProperties(this, name, values, names, n);
318 
319  if (strcmp(name, "SOFTPEC") == 0)
320  {
321  SoftPecNP.s = IPS_OK;
322  IUUpdateNumber(&SoftPecNP, values, names, n);
323  IDSetNumber(&SoftPecNP, nullptr);
324  return true;
325  }
326 
327  if (strcmp(name, "GUIDE_RATES") == 0)
328  {
329  ResetGuidePulses();
330  GuidingRatesNP.s = IPS_OK;
331  IUUpdateNumber(&GuidingRatesNP, values, names, n);
332  IDSetNumber(&GuidingRatesNP, nullptr);
333  return true;
334  }
335 
336  // Dead Zone
337  if (AxisDeadZoneNP.isNameMatch(name))
338  {
339  AxisDeadZoneNP.update(values, names, n);
340  AxisDeadZoneNP.setState(IPS_OK);
341  AxisDeadZoneNP.apply();
342  saveConfig(true, AxisDeadZoneNP.getName());
343  return true;
344  }
345 
346  // Clock Rate
347  if (AxisClockNP.isNameMatch(name))
348  {
349  AxisClockNP.update(values, names, n);
350  AxisClockNP.setState(IPS_OK);
351  AxisClockNP.apply();
352  saveConfig(true, AxisClockNP.getName());
353  return true;
354  }
355 
356  // Offsets
357  if (AxisOffsetNP.isNameMatch(name))
358  {
359  AxisOffsetNP.update(values, names, n);
360  AxisOffsetNP.setState(IPS_OK);
361  AxisOffsetNP.apply();
362  saveConfig(true, AxisOffsetNP.getName());
363  return true;
364  }
365 
366  // Axis 1
367  if (Axis1TrackRateNP.isNameMatch(name))
368  {
369  Axis1TrackRateNP.update(values, names, n);
370  Axis1TrackRateNP.setState(IPS_OK);
371  Axis1TrackRateNP.apply();
372  saveConfig(true, Axis1TrackRateNP.getName());
373  return true;
374  }
375 
376  // Axis 2
377  if (Axis2TrackRateNP.isNameMatch(name))
378  {
379  Axis2TrackRateNP.update(values, names, n);
380  Axis2TrackRateNP.setState(IPS_OK);
381  Axis2TrackRateNP.apply();
382  saveConfig(true, Axis2TrackRateNP.getName());
383  return true;
384  }
385 
386  // Axis1 PID
387  if (Axis1PIDNP.isNameMatch(name))
388  {
389  Axis1PIDNP.update(values, names, n);
390  Axis1PIDNP.setState(IPS_OK);
391  Axis1PIDNP.apply();
392  saveConfig(Axis1PIDNP);
393 
394  m_Controllers[AXIS1].reset(new PID(getPollingPeriod() / 1000, 50, -50,
395  Axis1PIDNP[Propotional].getValue(),
396  Axis1PIDNP[Derivative].getValue(),
397  Axis1PIDNP[Integral].getValue()));
398  return true;
399  }
400 
401  // Axis2 PID
402  if (Axis2PIDNP.isNameMatch(name))
403  {
404  Axis2PIDNP.update(values, names, n);
405  Axis2PIDNP.setState(IPS_OK);
406  Axis2PIDNP.apply();
407  saveConfig(Axis2PIDNP);
408 
409  m_Controllers[AXIS2].reset(new PID(getPollingPeriod() / 1000, 50, -50,
410  Axis2PIDNP[Propotional].getValue(),
411  Axis2PIDNP[Derivative].getValue(),
412  Axis2PIDNP[Integral].getValue()));
413  return true;
414  }
415 
416 
417  // Let our driver do sync operation in park position
418  if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
419  {
420  double ra = -1;
421  double dec = -100;
422 
423  for (int x = 0; x < n; x++)
424  {
425  INumber *eqp = IUFindNumber(&EqNP, names[x]);
426  if (eqp == &EqN[AXIS_RA])
427  {
428  ra = values[x];
429  }
430  else if (eqp == &EqN[AXIS_DE])
431  {
432  dec = values[x];
433  }
434  }
435  if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
436  {
437  ISwitch *sw = IUFindSwitch(&CoordSP, "SYNC");
438 
439  if (sw != nullptr && sw->s == ISS_ON && isParked())
440  {
441  return Sync(ra, dec);
442  }
443  }
444  }
445 
446  processGuiderProperties(name, values, names, n);
447  }
448  // Pass it up the chain
449  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
450 }
451 
455 bool SkywatcherAPIMount::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
456 {
457  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
458  {
459  // Auxiliary Encoders
460  if (AUXEncoderSP.isNameMatch(name))
461  {
462  AUXEncoderSP.update(states, names, n);
463  AUXEncoderSP.setState(IPS_OK);
464  AUXEncoderSP.apply();
465  auto enabled = AUXEncoderSP.findOnSwitchIndex() == INDI_ENABLED;
466  TurnRAEncoder(enabled);
467  TurnDEEncoder(enabled);
468  saveConfig(true, AUXEncoderSP.getName());
469  return true;
470  }
471 
472  // Snap Port
473  if (SnapPortSP.isNameMatch(name))
474  {
475  SnapPortSP.update(states, names, n);
476  auto enabled = SnapPortSP.findOnSwitchIndex() == INDI_ENABLED;
477  toggleSnapPort(enabled);
478  if (enabled)
479  LOG_INFO("Toggling snap port on...");
480  else
481  LOG_INFO("Toggling snap port off...");
482  SnapPortSP.setState(enabled ? IPS_OK : IPS_IDLE);
483  SnapPortSP.apply();
484  return true;
485  }
486 
487  ProcessAlignmentSwitchProperties(this, name, states, names, n);
488  }
489  // Pass it up the chain
490  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
491 }
492 
496 bool SkywatcherAPIMount::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
497 {
498  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
499  {
500  ProcessAlignmentTextProperties(this, name, texts, names, n);
501  }
502  // Pass it up the chain
503  return INDI::Telescope::ISNewText(dev, name, texts, names, n);
504 }
505 
509 bool SkywatcherAPIMount::Goto(double ra, double dec)
510 {
511  if (m_IterativeGOTOPending)
512  {
513  char RAStr[32], DecStr[32];
514  fs_sexa(RAStr, m_SkyCurrentRADE.rightascension, 2, 3600);
515  fs_sexa(DecStr, m_SkyCurrentRADE.declination, 2, 3600);
516  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Iterative GOTO RA %lf DEC %lf (Current Sky RA %s DE %s)", ra, dec, RAStr,
517  DecStr);
518  }
519  else
520  {
521  if (TrackState != SCOPE_IDLE)
522  Abort();
523 
524  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "GOTO RA %lf DEC %lf", ra, dec);
525 
526  if (IUFindSwitch(&CoordSP, "TRACK")->s == ISS_ON || IUFindSwitch(&CoordSP, "SLEW")->s == ISS_ON)
527  {
528  char RAStr[32], DecStr[32];
529  fs_sexa(RAStr, ra, 2, 3600);
530  fs_sexa(DecStr, dec, 2, 3600);
531  m_SkyTrackingTarget.rightascension = ra;
532  m_SkyTrackingTarget.declination = dec;
533  LOGF_INFO("Goto target RA %s DEC %s", RAStr, DecStr);
534  }
535  }
536 
539 
540  // Transform Celestial to Telescope coordinates.
541  // We have no good way to estimate how long will the mount takes to reach target (with deceleration,
542  // and not just speed). So we will use iterative GOTO once the first GOTO is complete.
543  if (TransformCelestialToTelescope(ra, dec, 0.0, TDV))
544  {
545  INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
546  AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
547  INDI::HorizontalToEquatorial(&AltAz, &m_Location, ln_get_julian_from_sys(), &EquatorialCoordinates);
548 
549  char RAStr[32], DecStr[32];
550  fs_sexa(RAStr, EquatorialCoordinates.rightascension, 2, 3600);
551  fs_sexa(DecStr, EquatorialCoordinates.declination, 2, 3600);
552 
553  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Sky -> Mount RA %s DE %s (TDV x %lf y %lf z %lf)", RAStr, DecStr, TDV.x,
554  TDV.y, TDV.z);
555  }
556  else
557  {
558  // Try a conversion with the stored observatory position if any
559  INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
560  EquatorialCoordinates.rightascension = ra;
561  EquatorialCoordinates.declination = dec;
562  INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys(), &AltAz);
563  TDV = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz);
564  switch (GetApproximateMountAlignment())
565  {
566  case ZENITH:
567  break;
568 
570  // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 minus
571  // the (positive)observatory latitude. The vector itself is rotated anticlockwise
572  TDV.RotateAroundY(m_Location.latitude - 90.0);
573  break;
574 
576  // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 plus
577  // the (negative)observatory latitude. The vector itself is rotated clockwise
578  TDV.RotateAroundY(m_Location.latitude + 90.0);
579  break;
580  }
581  AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
582  }
583 
585  "Sky -> Mount AZ %lf° (%ld) AL %lf° (%ld)",
586  AltAz.azimuth,
587  DegreesToMicrosteps(AXIS1, AltAz.azimuth),
588  AltAz.altitude,
589  DegreesToMicrosteps(AXIS2, AltAz.altitude));
590 
591  // Update the current encoder positions
592  GetEncoder(AXIS1);
593  GetEncoder(AXIS2);
594 
595  long AzimuthOffsetMicrosteps = DegreesToMicrosteps(AXIS1,
596  AltAz.azimuth) + ZeroPositionEncoders[AXIS1] - (CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue());
597  long AltitudeOffsetMicrosteps = DegreesToMicrosteps(AXIS2,
598  AltAz.altitude) + ZeroPositionEncoders[AXIS2] - (CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue());
599 
600  if (AzimuthOffsetMicrosteps > MicrostepsPerRevolution[AXIS1] / 2)
601  {
602  // Going the long way round - send it the other way
603  AzimuthOffsetMicrosteps -= MicrostepsPerRevolution[AXIS1];
604  }
605 
606  // Do I need to take out any complete revolutions before I do this test?
607  if (AltitudeOffsetMicrosteps > MicrostepsPerRevolution[AXIS2] / 2)
608  {
609  // Going the long way round - send it the other way
610  AltitudeOffsetMicrosteps -= MicrostepsPerRevolution[AXIS2];
611  }
612 
613  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Current Axis1 %ld microsteps (Zero %ld) Axis2 %ld microsteps (Zero %ld)",
614  CurrentEncoders[AXIS1], ZeroPositionEncoders[AXIS1], CurrentEncoders[AXIS2], ZeroPositionEncoders[AXIS2]);
615  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Azimuth offset %ld microsteps | Altitude offset %ld microsteps",
616  AzimuthOffsetMicrosteps, AltitudeOffsetMicrosteps);
617 
618  SilentSlewMode = (IUFindSwitch(&SlewModesSP, "SLEW_SILENT") != nullptr
619  && IUFindSwitch(&SlewModesSP, "SLEW_SILENT")->s == ISS_ON);
620 
621  SlewTo(AXIS1, AzimuthOffsetMicrosteps);
622  SlewTo(AXIS2, AltitudeOffsetMicrosteps);
623 
624  TrackState = SCOPE_SLEWING;
625 
626  return true;
627 }
628 
633 {
635 
636  if (isConnected())
637  {
638  // Fill in any real values now available MCInit should have been called already
639  UpdateDetailedMountInformation(false);
640 
641  // Define our connected only properties to the base driver
642  // e.g. defineProperty(MyNumberVectorPointer);
643  // This will register our properties and send a IDDefXXXX mewssage to any connected clients
644  defineProperty(&BasicMountInfoTP);
645  defineProperty(&AxisOneInfoNP);
646  defineProperty(&AxisOneStateSP);
647  defineProperty(&AxisTwoInfoNP);
648  defineProperty(&AxisTwoStateSP);
649  defineProperty(&AxisOneEncoderValuesNP);
650  defineProperty(&AxisTwoEncoderValuesNP);
651  defineProperty(&SlewModesSP);
652  defineProperty(&SoftPECModesSP);
653  defineProperty(&SoftPecNP);
654  defineProperty(&GuidingRatesNP);
655  defineProperty(&GuideNSNP);
656  defineProperty(&GuideWENP);
657  }
658 }
659 
664 {
665  ISwitch *Switch = IUFindOnSwitch(&SlewRateSP);
666  return *(static_cast<double *>(Switch->aux));
667 }
668 
673 {
674  double speed =
675  (dir == DIRECTION_NORTH) ? GetSlewRate() * LOW_SPEED_MARGIN / 2 : -GetSlewRate() * LOW_SPEED_MARGIN / 2;
676  const char *dirStr = (dir == DIRECTION_NORTH) ? "North" : "South";
677 
678  switch (command)
679  {
680  case MOTION_START:
681  DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
682  // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
683  Slew(AXIS2, speed, true);
684  m_ManualMotionActive = true;
685  break;
686 
687  case MOTION_STOP:
688  DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
689  SlowStop(AXIS2);
690  break;
691  }
692 
693  return true;
694 }
695 
700 {
701  double speed =
702  (dir == DIRECTION_WEST) ? -GetSlewRate() * LOW_SPEED_MARGIN / 2 : GetSlewRate() * LOW_SPEED_MARGIN / 2;
703  const char *dirStr = (dir == DIRECTION_WEST) ? "West" : "East";
704 
705  switch (command)
706  {
707  case MOTION_START:
708  DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
709  // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
710  Slew(AXIS1, speed, true);
711  m_ManualMotionActive = true;
712  break;
713 
714  case MOTION_STOP:
715  DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
716  SlowStop(AXIS1);
717  break;
718  }
719 
720  return true;
721 }
722 
727 {
728  // Move the telescope to the desired position
729  long AltitudeOffsetMicrosteps = GetAxis2Park() - CurrentEncoders[AXIS2];
730  long AzimuthOffsetMicrosteps = GetAxis1Park() - CurrentEncoders[AXIS1];
732  "Parking: Altitude offset %ld microsteps Azimuth offset %ld microsteps", AltitudeOffsetMicrosteps,
733  AzimuthOffsetMicrosteps);
734 
735  if (IUFindSwitch(&SlewModesSP, "SLEW_SILENT") != nullptr && IUFindSwitch(&SlewModesSP, "SLEW_SILENT")->s == ISS_ON)
736  {
737  SilentSlewMode = true;
738  }
739  else
740  {
741  SilentSlewMode = false;
742  }
743  SlewTo(AXIS1, AzimuthOffsetMicrosteps);
744  SlewTo(AXIS2, AltitudeOffsetMicrosteps);
745 
746  TrackState = SCOPE_PARKING;
747  return true;
748 }
749 
754 {
755  SetParked(false);
756  return true;
757 }
758 
763 {
764  if (enabled)
765  {
766  TrackState = SCOPE_TRACKING;
767  resetTracking();
768  m_SkyTrackingTarget.rightascension = EqN[AXIS_RA].value;
769  m_SkyTrackingTarget.declination = EqN[AXIS_DE].value;
770  }
771  else
772  {
773  TrackState = SCOPE_IDLE;
774  SlowStop(AXIS1);
775  SlowStop(AXIS2);
776  TrackState = SCOPE_IDLE;
777 
778  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
779  {
780  GuideNSNP.s = GuideWENP.s = IPS_IDLE;
781  GuideNSN[0].value = GuideNSN[1].value = 0.0;
782  GuideWEN[0].value = GuideWEN[1].value = 0.0;
783  IDSetNumber(&GuideNSNP, nullptr);
784  IDSetNumber(&GuideWENP, nullptr);
785  }
786  }
787 
788  return true;
789 }
790 
795 {
796  if (!GetStatus(AXIS1))
797  return false;
798 
799  if (!GetStatus(AXIS2))
800  return false;
801 
802  // Update Axis Position
803  if (!GetEncoder(AXIS1))
804  return false;
805  if (!GetEncoder(AXIS2))
806  return false;
807 
808  UpdateDetailedMountInformation(true);
809 
810  bool resetTrackingTimers = false;
811 
812  // Calculate new RA DEC
814  AltAz.azimuth = range360(MicrostepsToDegrees(AXIS1,
815  CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
816  AltAz.altitude = MicrostepsToDegrees(AXIS2,
817  CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
818  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis1 encoder %ld (Zero %ld) -> AZ %lf°",
819  CurrentEncoders[AXIS1], ZeroPositionEncoders[AXIS1], AltAz.azimuth);
820  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis2 encoder %ld (Zero %ld) -> AL %lf°",
821  CurrentEncoders[AXIS2], ZeroPositionEncoders[AXIS2], AltAz.altitude);
822 
823  // Update current horizontal coords.
824  m_MountAltAz = AltAz;
825 
826  // Get equatorial coords.
827  getCurrentRADE(AltAz, m_SkyCurrentRADE);
828  char RAStr[32], DecStr[32];
829  fs_sexa(RAStr, m_SkyCurrentRADE.rightascension, 2, 3600);
830  fs_sexa(DecStr, m_SkyCurrentRADE.declination, 2, 3600);
831  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Sky RA %s DE %s", RAStr, DecStr);
832 
833  if (TrackState == SCOPE_SLEWING)
834  {
835  if ((AxesStatus[AXIS1].FullStop) && (AxesStatus[AXIS2].FullStop))
836  {
837  // If iterative GOTO was already engaged, stop it.
838  if (m_IterativeGOTOPending)
839  m_IterativeGOTOPending = false;
840  // If not, then perform the iterative GOTO once more.
841  else
842  {
843  m_IterativeGOTOPending = true;
844  return Goto(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination);
845  }
846 
847  if (ISS_ON == IUFindSwitch(&CoordSP, "TRACK")->s)
848  {
849  // Goto has finished start tracking
850  TrackState = SCOPE_TRACKING;
851  resetTrackingTimers = true;
852  LOG_INFO("Tracking started.");
853  }
854  else
855  {
856  TrackState = SCOPE_IDLE;
857  }
858  }
859  }
860  else if (TrackState == SCOPE_PARKING)
861  {
862  if (!IsInMotion(AXIS1) && !IsInMotion(AXIS2))
863  {
864  SlowStop(AXIS1);
865  SlowStop(AXIS2);
866  SetParked(true);
867  }
868  }
869 
870  if (resetTrackingTimers)
871  resetTracking();
872 
873  NewRaDec(m_SkyCurrentRADE.rightascension, m_SkyCurrentRADE.declination);
874  return true;
875 }
876 
880 bool SkywatcherAPIMount::getCurrentAltAz(INDI::IHorizontalCoordinates &altaz)
881 {
882  // Update Axis Position
883  if (GetEncoder(AXIS1) && GetEncoder(AXIS2))
884  {
885  altaz.azimuth = range360(MicrostepsToDegrees(AXIS1,
886  CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
887  altaz.altitude = MicrostepsToDegrees(AXIS2,
888  CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
889  return true;
890  }
891 
892  return false;
893 }
894 
898 bool SkywatcherAPIMount::getCurrentRADE(INDI::IHorizontalCoordinates altaz, INDI::IEquatorialCoordinates &rade)
899 {
900  TelescopeDirectionVector TDV = TelescopeDirectionVectorFromAltitudeAzimuth(altaz);
901  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "TDV x %lf y %lf z %lf", TDV.x, TDV.y, TDV.z);
902 
903  double RightAscension, Declination;
904  if (!TransformTelescopeToCelestial(TDV, RightAscension, Declination))
905  {
906  TelescopeDirectionVector RotatedTDV(TDV);
907  switch (GetApproximateMountAlignment())
908  {
909  case ZENITH:
910  break;
911 
913  // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 minus
914  // the (positive)observatory latitude. The vector itself is rotated clockwise
915  RotatedTDV.RotateAroundY(90.0 - m_Location.latitude);
916  AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, altaz);
917  break;
918 
920  // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 plus
921  // the (negative)observatory latitude. The vector itself is rotated anticlockwise
922  RotatedTDV.RotateAroundY(-90.0 - m_Location.latitude);
923  AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, altaz);
924  break;
925  }
926 
927  INDI::IEquatorialCoordinates EquatorialCoordinates;
928  INDI::HorizontalToEquatorial(&altaz, &m_Location, ln_get_julian_from_sys(), &EquatorialCoordinates);
929  RightAscension = EquatorialCoordinates.rightascension;
930  Declination = EquatorialCoordinates.declination;
931  }
932 
933  rade.rightascension = RightAscension;
934  rade.declination = Declination;
935  return true;
936 }
941 {
942  SaveAlignmentConfigProperties(fp);
943 
944  Axis1PIDNP.save(fp);
945  Axis2PIDNP.save(fp);
946  AxisDeadZoneNP.save(fp);
947  AxisClockNP.save(fp);
948  AxisOffsetNP.save(fp);
949  Axis1TrackRateNP.save(fp);
950  Axis2TrackRateNP.save(fp);
951 
953 }
954 
958 bool SkywatcherAPIMount::Sync(double ra, double dec)
959 {
960  DEBUG(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "SkywatcherAPIMount::Sync");
961 
962  // Compute a telescope direction vector from the current encoders
963  if (!GetEncoder(AXIS1))
964  return false;
965  if (!GetEncoder(AXIS2))
966  return false;
967 
968  // Syncing is treated specially when the telescope position is known in park position to spare
969  // "a huge-jump point" in the alignment model.
970  if (isParked())
971  {
974 
975  if (TransformCelestialToTelescope(ra, dec, 0.0, TDV))
976  {
977  AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
978  double OrigAlt = AltAz.altitude;
979  ZeroPositionEncoders[AXIS1] = PolarisPositionEncoders[AXIS1] - DegreesToMicrosteps(AXIS1, AltAz.azimuth);
980  ZeroPositionEncoders[AXIS2] = PolarisPositionEncoders[AXIS2] - DegreesToMicrosteps(AXIS2, AltAz.altitude);
981  LOGF_INFO("Sync (Alt: %lf Az: %lf) in park position", OrigAlt, AltAz.azimuth);
982  GetAlignmentDatabase().clear();
983  return true;
984  }
985  }
986 
987  // Might as well do this
988  UpdateDetailedMountInformation(true);
989 
991 
992  AltAz.azimuth = range360(MicrostepsToDegrees(AXIS1,
993  CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
994  AltAz.altitude = MicrostepsToDegrees(AXIS2,
995  CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
996 
997  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis1 encoder %ld initial %ld AZ %lf°",
998  CurrentEncoders[AXIS1], ZeroPositionEncoders[AXIS1], AltAz.azimuth);
999  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis2 encoder %ld initial %ld AL %lf°",
1000  CurrentEncoders[AXIS2], ZeroPositionEncoders[AXIS2], AltAz.altitude);
1001 
1002  AlignmentDatabaseEntry NewEntry;
1003  NewEntry.ObservationJulianDate = ln_get_julian_from_sys();
1004  NewEntry.RightAscension = ra;
1005  NewEntry.Declination = dec;
1006  NewEntry.TelescopeDirection = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz);
1007  NewEntry.PrivateDataSize = 0;
1008 
1009  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "New sync point Date %lf RA %lf DEC %lf TDV(x %lf y %lf z %lf)",
1010  NewEntry.ObservationJulianDate, NewEntry.RightAscension, NewEntry.Declination, NewEntry.TelescopeDirection.x,
1011  NewEntry.TelescopeDirection.y, NewEntry.TelescopeDirection.z);
1012 
1013  m_IterativeGOTOPending = false;
1014 
1015  if (!CheckForDuplicateSyncPoint(NewEntry))
1016  {
1017  GetAlignmentDatabase().push_back(NewEntry);
1018 
1019  // Tell the client about size change
1020  UpdateSize();
1021 
1022  // Tell the math plugin to reinitialise
1023  Initialise(this);
1024 
1025  // Force read before restarting
1026  ReadScopeStatus();
1027 
1028  // The tracking seconds should be reset to restart the drift compensation
1029  resetTracking();
1030 
1031  return true;
1032  }
1033  return false;
1034 }
1035 
1040 {
1041  //DEBUG(DBG_SCOPE, "SkywatcherAPIMount::Abort");
1042  m_IterativeGOTOPending = false;
1043  SlowStop(AXIS1);
1044  SlowStop(AXIS2);
1045  TrackState = SCOPE_IDLE;
1046 
1047  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
1048  {
1049  GuideNSNP.s = GuideWENP.s = IPS_IDLE;
1050  GuideNSN[0].value = GuideNSN[1].value = 0.0;
1051  GuideWEN[0].value = GuideWEN[1].value = 0.0;
1052 
1053  LOG_INFO("Guide aborted.");
1054  IDSetNumber(&GuideNSNP, nullptr);
1055  IDSetNumber(&GuideWENP, nullptr);
1056 
1057  return true;
1058  }
1059 
1060  return true;
1061 }
1062 
1067 {
1068  // Call parent to read ReadScopeStatus
1070 
1071  switch (TrackState)
1072  {
1073  case SCOPE_SLEWING:
1074  GuideDeltaAlt = 0;
1075  GuideDeltaAz = 0;
1076  ResetGuidePulses();
1077  GuidingPulses.clear();
1078  break;
1079 
1080  case SCOPE_TRACKING:
1081  {
1082  // Check if manual motion in progress but we stopped
1083  if (m_ManualMotionActive && !IsInMotion(AXIS1) && !IsInMotion(AXIS2))
1084  {
1085  m_ManualMotionActive = false;
1086  resetTracking();
1087  }
1088  // If we're manually moving by WESN controls, update the tracking coordinates.
1089  if (m_ManualMotionActive)
1090  {
1091  break;
1092  }
1093  else
1094  {
1095  // Continue or start tracking
1096  // Calculate where the mount needs to be in POLLMS time
1097  // TODO may need to make this longer to get a meaningful result
1098  //double JulianOffset = (getCurrentPollingPeriod() / 1000) / (24.0 * 60 * 60);
1101 
1102  // We modify the SkyTrackingTarget for non-sidereal objects (Moon or Sun)
1103  // FIXME: This was not tested.
1104  if (TrackModeS[TRACK_LUNAR].s == ISS_ON)
1105  {
1106  // TRACKRATE_LUNAR how many arcsecs the Moon moved in one second.
1107  // TRACKRATE_SIDEREAL how many arcsecs the Sky moved in one second.
1108  double dRA = (TRACKRATE_LUNAR - TRACKRATE_SIDEREAL) * m_TrackingRateTimer.elapsed() / 1000.0;
1109  m_SkyTrackingTarget.rightascension += (dRA / 3600.0) * 15.0;
1110  m_TrackingRateTimer.restart();
1111  }
1112  else if (TrackModeS[TRACK_SOLAR].s == ISS_ON)
1113  {
1114  double dRA = (TRACKRATE_SOLAR - TRACKRATE_SIDEREAL) * m_TrackingRateTimer.elapsed() / 1000.0;
1115  m_SkyTrackingTarget.rightascension += (dRA / 3600.0) * 15.0;
1116  m_TrackingRateTimer.restart();
1117  }
1118 
1119  auto ra = m_SkyTrackingTarget.rightascension + AxisOffsetNP[RAOffset].getValue() / 15.0;
1120  auto de = m_SkyTrackingTarget.declination + AxisOffsetNP[DEOffset].getValue();
1121  auto JDOffset = AxisOffsetNP[JulianOffset].getValue() / 86400.0;
1122 
1123  if (TransformCelestialToTelescope(ra, de, JDOffset, TDV))
1124  {
1125  DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "TDV x %lf y %lf z %lf", TDV.x, TDV.y, TDV.z);
1126  AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
1127  }
1128  else
1129  {
1130  INDI::IEquatorialCoordinates EquatorialCoordinates { ra, de };
1131  INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys() + JDOffset, &AltAz);
1132  }
1133 
1134  DEBUGF(DBG_SCOPE,
1135  "New Tracking Target AZ %lf° (%ld microsteps) AL %lf° (%ld microsteps) ",
1136  AltAz.azimuth,
1137  DegreesToMicrosteps(AXIS1, AltAz.azimuth),
1138  AltAz.altitude,
1139  DegreesToMicrosteps(AXIS2, AltAz.altitude));
1140 
1141  // Calculate the auto-guiding delta degrees
1142  double DeltaAlt = 0;
1143  double DeltaAz = 0;
1144 
1145  for (auto Iter = GuidingPulses.begin(); Iter != GuidingPulses.end(); )
1146  {
1147  // We treat the guide calibration specially
1148  if (Iter->OriginalDuration == 1000)
1149  {
1150  DeltaAlt += Iter->DeltaAlt;
1151  DeltaAz += Iter->DeltaAz;
1152  }
1153  else
1154  {
1155  DeltaAlt += Iter->DeltaAlt / 2;
1156  DeltaAz += Iter->DeltaAz / 2;
1157  }
1158  Iter->Duration -= getCurrentPollingPeriod();
1159 
1160  if (Iter->Duration < static_cast<int>(getCurrentPollingPeriod()))
1161  {
1162  Iter = GuidingPulses.erase(Iter);
1163  if (Iter == GuidingPulses.end())
1164  {
1165  break;
1166  }
1167  continue;
1168  }
1169  ++Iter;
1170  }
1171 
1172  GuideDeltaAlt += DeltaAlt;
1173  GuideDeltaAz += DeltaAz;
1174 
1175  long SetPoint[2] = {0, 0}, Measurement[2] = {0, 0}, Error[2] = {0, 0};
1176  double TrackingRate[2] = {0, 0};
1177 
1178  SetPoint[AXIS1] = DegreesToMicrosteps(AXIS1, AltAz.azimuth + GuideDeltaAz);
1179  Measurement[AXIS1] = CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1];
1180 
1181  SetPoint[AXIS2] = DegreesToMicrosteps(AXIS2, AltAz.altitude + GuideDeltaAlt);
1182  Measurement[AXIS2] = CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2];
1183 
1184  // Going the long way round - send it the other way
1185  while (SetPoint[AXIS1] > MicrostepsPerRevolution[AXIS1] / 2)
1186  SetPoint[AXIS1] -= MicrostepsPerRevolution[AXIS1];
1187 
1188  while (SetPoint[AXIS2] > MicrostepsPerRevolution[AXIS2] / 2)
1189  SetPoint[AXIS2] -= MicrostepsPerRevolution[AXIS2];
1190 
1191  Error[AXIS1] = SetPoint[AXIS1] - Measurement[AXIS1];
1192  Error[AXIS2] = SetPoint[AXIS2] - Measurement[AXIS2];
1193 
1194  auto Axis1CustomClockRate = Axis1TrackRateNP[TrackClockRate].getValue();
1195 
1196 
1197  if (!AxesStatus[AXIS1].FullStop && (
1198  (Axis1CustomClockRate == 0 && ((AxesStatus[AXIS1].SlewingForward && (Error[AXIS1] < -AxisDeadZoneNP[AXIS1].getValue())) ||
1199  (!AxesStatus[AXIS1].SlewingForward && (Error[AXIS1] > AxisDeadZoneNP[AXIS1].getValue())))) ||
1200  (Axis1CustomClockRate > 0 && Axis1TrackRateNP[TrackDirection].getValue() != m_LastCustomDirection[AXIS1])))
1201  {
1202  m_LastCustomDirection[AXIS1] = Axis1TrackRateNP[TrackDirection].getValue();
1203  // Direction change whilst axis running
1204  // Abandon tracking for this clock tick
1205  LOG_DEBUG("Tracking -> AXIS1 direction change.");
1206  LOGF_DEBUG("AXIS1 Setpoint %d Measurement %d Error %d Rate %f",
1207  SetPoint[AXIS1],
1208  Measurement[AXIS1],
1209  Error[AXIS1],
1210  TrackingRate[AXIS1]);
1211  SlowStop(AXIS1);
1212  }
1213  else
1214  {
1215  TrackingRate[AXIS1] = m_Controllers[AXIS1]->calculate(SetPoint[AXIS1], Measurement[AXIS1]);
1216  char Direction = TrackingRate[AXIS1] > 0 ? '0' : '1';
1217  TrackingRate[AXIS1] = std::fabs(TrackingRate[AXIS1]);
1218  if (TrackingRate[AXIS1] != 0)
1219  {
1220  auto clockRate = (StepperClockFrequency[AXIS1] / TrackingRate[AXIS1]) * (AxisClockNP[AXIS1].getValue() / 100.0);
1221 
1222  if (Axis1CustomClockRate > 0)
1223  {
1224  clockRate = Axis1CustomClockRate;
1225  Direction = Axis1TrackRateNP[TrackDirection].getValue() == 0 ? '0' : '1';
1226  }
1227 
1228  LOGF_DEBUG("AXIS1 Setpoint %d Measurement %d Error %d Rate %f Freq %f Dir %s",
1229  SetPoint[AXIS1],
1230  Measurement[AXIS1],
1231  Error[AXIS1],
1232  TrackingRate[AXIS1],
1233  clockRate,
1234  Direction == '0' ? "Forward" : "Backward");
1235 #ifdef DEBUG_PID
1236  LOGF_DEBUG("Tracking AZ P: %f I: %f D: %f",
1237  m_Controllers[AXIS1]->propotionalTerm(),
1238  m_Controllers[AXIS1]->integralTerm(),
1239  m_Controllers[AXIS1]->derivativeTerm());
1240 #endif
1241 
1242  SetClockTicksPerMicrostep(AXIS1, clockRate);
1243  if (AxesStatus[AXIS1].FullStop)
1244  {
1245  LOG_DEBUG("Tracking -> AXIS1 restart.");
1246  SetAxisMotionMode(AXIS1, '1', Direction);
1247  StartAxisMotion(AXIS1);
1248  }
1249  }
1250  }
1251 
1252 
1253  auto Axis2CustomClockRate = Axis2TrackRateNP[TrackClockRate].getValue();
1254 
1255  if (!AxesStatus[AXIS2].FullStop && (
1256  (Axis2CustomClockRate == 0 && ((AxesStatus[AXIS2].SlewingForward && (Error[AXIS2] < -AxisDeadZoneNP[AXIS2].getValue())) ||
1257  (!AxesStatus[AXIS2].SlewingForward && (Error[AXIS2] > AxisDeadZoneNP[AXIS2].getValue())))) ||
1258  (Axis2CustomClockRate > 0 && Axis2TrackRateNP[TrackDirection].getValue() != m_LastCustomDirection[AXIS2])))
1259  {
1260  m_LastCustomDirection[AXIS2] = Axis2TrackRateNP[TrackDirection].getValue();
1261 
1262  LOG_DEBUG("Tracking -> AXIS2 direction change.");
1263  LOGF_DEBUG("AXIS2 Setpoint %d Measurement %d Error %d Rate %f",
1264  SetPoint[AXIS2],
1265  Measurement[AXIS2],
1266  Error[AXIS2],
1267  TrackingRate[AXIS2]);
1268  SlowStop(AXIS2);
1269  }
1270  else
1271  {
1272  TrackingRate[AXIS2] = m_Controllers[AXIS2]->calculate(SetPoint[AXIS2], Measurement[AXIS2]);
1273  char Direction = TrackingRate[AXIS2] > 0 ? '0' : '1';
1274  TrackingRate[AXIS2] = std::fabs(TrackingRate[AXIS2]);
1275  if (TrackingRate[AXIS2] != 0)
1276  {
1277  auto clockRate = StepperClockFrequency[AXIS2] / TrackingRate[AXIS2] * (AxisClockNP[AXIS2].getValue() / 100.0);
1278 
1279  if (Axis2CustomClockRate > 0)
1280  {
1281  clockRate = Axis2CustomClockRate;
1282  Direction = Axis2TrackRateNP[TrackDirection].getValue() == 0 ? '0' : '1';
1283  }
1284 
1285  LOGF_DEBUG("AXIS2 Setpoint %d Measurement %d Error %d Rate %f Freq %f Dir %s",
1286  SetPoint[AXIS2],
1287  Measurement[AXIS2],
1288  Error[AXIS2],
1289  TrackingRate[AXIS2],
1290  clockRate,
1291  Error[AXIS2] > 0 ? "Forward" : "Backward");
1292 #ifdef DEBUG_PID
1293  LOGF_DEBUG("Tracking AZ P: %f I: %f D: %f",
1294  m_Controllers[AXIS2]->propotionalTerm(),
1295  m_Controllers[AXIS2]->integralTerm(),
1296  m_Controllers[AXIS2]->derivativeTerm());
1297 #endif
1298 
1299  SetClockTicksPerMicrostep(AXIS2, clockRate);
1300  if (AxesStatus[AXIS2].FullStop)
1301  {
1302  LOG_DEBUG("Tracking -> AXIS2 restart.");
1303  SetAxisMotionMode(AXIS2, '1', Direction);
1304  StartAxisMotion(AXIS2);
1305  }
1306  }
1307  }
1308  }
1309 
1310  break;
1311  }
1312 
1313  default:
1314  GuideDeltaAlt = 0;
1315  GuideDeltaAz = 0;
1316  ResetGuidePulses();
1317  GuidingPulses.clear();
1318  break;
1319  }
1320 }
1321 
1325 bool SkywatcherAPIMount::updateLocation(double latitude, double longitude, double elevation)
1326 {
1327  //DEBUG(DBG_SCOPE, "SkywatcherAPIMount::updateLocation");
1328  UpdateLocation(latitude, longitude, elevation);
1329  return true;
1330 }
1331 
1336 {
1338 
1339  if (isConnected())
1340  {
1341  // Update location if loaded already from config
1342  if (m_Location.longitude > 0)
1343  UpdateLocation(m_Location.latitude, m_Location.longitude, m_Location.elevation);
1344 
1345  // Fill in any real values now available MCInit should have been called already
1346  UpdateDetailedMountInformation(false);
1347 
1348  // Define our connected only properties to the base driver
1349  // e.g. defineProperty(MyNumberVectorPointer);
1350  // This will register our properties and send a IDDefXXXX message to any connected clients
1351  // I have now idea why I have to do this here as well as in ISGetProperties. It makes me
1352  // concerned there is a design or implementation flaw somewhere.
1353  defineProperty(&BasicMountInfoTP);
1354  defineProperty(&AxisOneInfoNP);
1355  defineProperty(&AxisOneStateSP);
1356  defineProperty(&AxisTwoInfoNP);
1357  defineProperty(&AxisTwoStateSP);
1358  defineProperty(&AxisOneEncoderValuesNP);
1359  defineProperty(&AxisTwoEncoderValuesNP);
1360  defineProperty(&SlewModesSP);
1361  defineProperty(&SoftPECModesSP);
1362  defineProperty(&SoftPecNP);
1363  defineProperty(&GuidingRatesNP);
1364  defineProperty(&GuideNSNP);
1365  defineProperty(&GuideWENP);
1366  defineProperty(Axis1PIDNP);
1367  defineProperty(Axis2PIDNP);
1368  defineProperty(AxisDeadZoneNP);
1369  defineProperty(AxisClockNP);
1370  defineProperty(AxisOffsetNP);
1371  defineProperty(Axis1TrackRateNP);
1372  defineProperty(Axis2TrackRateNP);
1373 
1374  if (HasAuxEncoders())
1375  {
1376  LOG_INFO("AUX encoders detected. Turning on...");
1377  TurnRAEncoder(true);
1378  TurnDEEncoder(true);
1379  defineProperty(AUXEncoderSP);
1380  }
1381 
1382  if (InitPark())
1383  {
1384  // If loading parking data is successful, we just set the default parking values.
1385  SetAxis1ParkDefault(GetAxis1Park());
1386  SetAxis2ParkDefault(GetAxis2Park());
1387  }
1388  else
1389  {
1390  // Otherwise, we set all parking data to default in case no parking data is found.
1391  SetAxis1Park(0x800000);
1392  SetAxis2Park(0x800000);
1393  SetAxis1ParkDefault(0x800000);
1394  SetAxis2ParkDefault(0x800000);
1395  }
1396 
1397  if (isParked())
1398  {
1399  SetEncoder(AXIS1, GetAxis1Park());
1400  SetEncoder(AXIS2, GetAxis2Park());
1401 
1402  }
1403  return true;
1404  }
1405  else
1406  {
1407  // Delete any connected only properties from the base driver's list
1408  // e.g. deleteProperty(MyNumberVector.name);
1409  deleteProperty(BasicMountInfoTP.name);
1410  deleteProperty(AxisOneInfoNP.name);
1411  deleteProperty(AxisOneStateSP.name);
1412  deleteProperty(AxisTwoInfoNP.name);
1413  deleteProperty(AxisTwoStateSP.name);
1414  deleteProperty(AxisOneEncoderValuesNP.name);
1415  deleteProperty(AxisTwoEncoderValuesNP.name);
1416  deleteProperty(SlewModesSP.name);
1417  deleteProperty(SoftPECModesSP.name);
1418  deleteProperty(SoftPecNP.name);
1419  deleteProperty(GuidingRatesNP.name);
1420  deleteProperty(GuideNSNP.name);
1421  deleteProperty(GuideWENP.name);
1422  deleteProperty(Axis1PIDNP);
1423  deleteProperty(Axis2PIDNP);
1424  deleteProperty(AxisDeadZoneNP);
1425  deleteProperty(AxisClockNP);
1426  deleteProperty(AxisOffsetNP);
1427  deleteProperty(Axis1TrackRateNP);
1428  deleteProperty(Axis2TrackRateNP);
1429 
1430  if (HasAuxEncoders())
1431  deleteProperty(AUXEncoderSP.getName());
1432 
1433  return true;
1434  }
1435 }
1436 
1441 {
1442  GuidingPulse Pulse;
1443 
1444  CalculateGuidePulses();
1445  Pulse.DeltaAz = NorthPulse.DeltaAz;
1446  Pulse.DeltaAlt = NorthPulse.DeltaAlt;
1447  Pulse.Duration = ms;
1448  Pulse.OriginalDuration = ms;
1449  GuidingPulses.push_back(Pulse);
1450  return IPS_OK;
1451 }
1452 
1457 {
1458  GuidingPulse Pulse;
1459 
1460  CalculateGuidePulses();
1461  Pulse.DeltaAz = -NorthPulse.DeltaAz;
1462  Pulse.DeltaAlt = -NorthPulse.DeltaAlt;
1463  Pulse.Duration = ms;
1464  Pulse.OriginalDuration = ms;
1465  GuidingPulses.push_back(Pulse);
1466  return IPS_OK;
1467 }
1468 
1473 {
1474  GuidingPulse Pulse;
1475 
1476  CalculateGuidePulses();
1477  Pulse.DeltaAz = WestPulse.DeltaAz;
1478  Pulse.DeltaAlt = WestPulse.DeltaAlt;
1479  Pulse.Duration = ms;
1480  Pulse.OriginalDuration = ms;
1481  GuidingPulses.push_back(Pulse);
1482  return IPS_OK;
1483 }
1484 
1489 {
1490  GuidingPulse Pulse;
1491 
1492  CalculateGuidePulses();
1493  Pulse.DeltaAz = -WestPulse.DeltaAz;
1494  Pulse.DeltaAlt = -WestPulse.DeltaAlt;
1495  Pulse.Duration = ms;
1496  Pulse.OriginalDuration = ms;
1497  GuidingPulses.push_back(Pulse);
1498  return IPS_OK;
1499 }
1500 
1504 void SkywatcherAPIMount::CalculateGuidePulses()
1505 {
1506  if (NorthPulse.Duration != 0 || WestPulse.Duration != 0)
1507  return;
1508 
1509  // Calculate the west reference delta
1510  // Note: The RA is multiplied by 3.75 (90/24) to be more comparable with DEC values.
1511  const double WestRate = IUFindNumber(&GuidingRatesNP, "GUIDERA_RATE")->value / 10 * -1.0 / 60 / 60 * 3.75 / 100;
1512 
1513  ConvertGuideCorrection(WestRate, 0, WestPulse.DeltaAlt, WestPulse.DeltaAz);
1514  WestPulse.Duration = 1;
1515 
1516  // Calculate the north reference delta
1517  // Note: By some reason, it must be multiplied by 100 to match with the RA values.
1518  const double NorthRate = IUFindNumber(&GuidingRatesNP, "GUIDEDEC_RATE")->value / 10 * 1.0 / 60 / 60 * 100 / 100;
1519 
1520  ConvertGuideCorrection(0, NorthRate, NorthPulse.DeltaAlt, NorthPulse.DeltaAz);
1521  NorthPulse.Duration = 1;
1522 }
1523 
1527 void SkywatcherAPIMount::ResetGuidePulses()
1528 {
1529  NorthPulse.Duration = 0;
1530  WestPulse.Duration = 0;
1531 }
1532 
1536 void SkywatcherAPIMount::ConvertGuideCorrection(double delta_ra, double delta_dec, double &delta_alt, double &delta_az)
1537 {
1538  INDI::IHorizontalCoordinates OldAltAz { 0, 0 };
1539  INDI::IHorizontalCoordinates NewAltAz { 0, 0 };
1540  TelescopeDirectionVector OldTDV;
1541  TelescopeDirectionVector NewTDV;
1542 
1543  TransformCelestialToTelescope(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination, 0.0, OldTDV);
1544  AltitudeAzimuthFromTelescopeDirectionVector(OldTDV, OldAltAz);
1545  TransformCelestialToTelescope(m_SkyTrackingTarget.rightascension + delta_ra,
1546  m_SkyTrackingTarget.declination + delta_dec, 0.0, NewTDV);
1547  AltitudeAzimuthFromTelescopeDirectionVector(NewTDV, NewAltAz);
1548  delta_alt = NewAltAz.altitude - OldAltAz.altitude;
1549  delta_az = NewAltAz.azimuth - OldAltAz.azimuth;
1550 }
1551 
1555 void SkywatcherAPIMount::SkywatcherMicrostepsFromTelescopeDirectionVector(
1556  const TelescopeDirectionVector TelescopeDirectionVector, long &Axis1Microsteps, long &Axis2Microsteps)
1557 {
1558  // For the time being I assume that all skywathcer mounts share the same encoder conventions
1559  double Axis1Angle = 0;
1560  double Axis2Angle = 0;
1561  SphericalCoordinateFromTelescopeDirectionVector(TelescopeDirectionVector, Axis1Angle,
1563  FROM_AZIMUTHAL_PLANE);
1564 
1565  Axis1Microsteps = RadiansToMicrosteps(AXIS1, Axis1Angle);
1566  Axis2Microsteps = RadiansToMicrosteps(AXIS2, Axis2Angle);
1567 }
1568 
1573 SkywatcherAPIMount::TelescopeDirectionVectorFromSkywatcherMicrosteps(long Axis1Microsteps, long Axis2Microsteps)
1574 {
1575  // For the time being I assume that all skywathcer mounts share the same encoder conventions
1576  double Axis1Angle = MicrostepsToRadians(AXIS1, Axis1Microsteps);
1577  double Axis2Angle = MicrostepsToRadians(AXIS2, Axis2Microsteps);
1578  return TelescopeDirectionVectorFromSphericalCoordinate(
1579  Axis1Angle, TelescopeDirectionVectorSupportFunctions::CLOCKWISE, Axis2Angle, FROM_AZIMUTHAL_PLANE);
1580 }
1581 
1585 void SkywatcherAPIMount::UpdateDetailedMountInformation(bool InformClient)
1586 {
1587  bool BasicMountInfoHasChanged = false;
1588 
1589  if (std::string(BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION].text) != std::to_string(MCVersion))
1590  {
1591  IUSaveText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], std::to_string(MCVersion).c_str());
1592  BasicMountInfoHasChanged = true;
1593  }
1594  if (std::string(BasicMountInfoT[MOUNT_CODE].text) != std::to_string(MountCode))
1595  {
1596  IUSaveText(&BasicMountInfoT[MOUNT_CODE], std::to_string(MountCode).c_str());
1597  SetApproximateMountAlignmentFromMountType(ALTAZ);
1598  BasicMountInfoHasChanged = true;
1599  }
1600  if (std::string(BasicMountInfoT[IS_DC_MOTOR].text) != std::to_string(IsDCMotor))
1601  {
1602  IUSaveText(&BasicMountInfoT[IS_DC_MOTOR], std::to_string(IsDCMotor).c_str());
1603  BasicMountInfoHasChanged = true;
1604  }
1605  if (BasicMountInfoHasChanged && InformClient)
1606  IDSetText(&BasicMountInfoTP, nullptr);
1607 
1608  IUSaveText(&BasicMountInfoT[MOUNT_NAME], mountTypeToString(MountCode));
1609 
1610  bool AxisOneInfoHasChanged = false;
1611 
1612  if (AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[0])
1613  {
1614  AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[0];
1615  AxisOneInfoHasChanged = true;
1616  }
1617  if (AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[0])
1618  {
1619  AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[0];
1620  AxisOneInfoHasChanged = true;
1621  }
1622  if (AxisOneInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[0])
1623  {
1624  AxisOneInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[0];
1625  AxisOneInfoHasChanged = true;
1626  }
1627  if (AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[0])
1628  {
1629  AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[0];
1630  AxisOneInfoHasChanged = true;
1631  }
1632  if (AxisOneInfoHasChanged && InformClient)
1633  IDSetNumber(&AxisOneInfoNP, nullptr);
1634 
1635  bool AxisOneStateHasChanged = false;
1636  if (AxisOneStateS[FULL_STOP].s != (AxesStatus[0].FullStop ? ISS_ON : ISS_OFF))
1637  {
1638  AxisOneStateS[FULL_STOP].s = AxesStatus[0].FullStop ? ISS_ON : ISS_OFF;
1639  AxisOneStateHasChanged = true;
1640  }
1641  if (AxisOneStateS[SLEWING].s != (AxesStatus[0].Slewing ? ISS_ON : ISS_OFF))
1642  {
1643  AxisOneStateS[SLEWING].s = AxesStatus[0].Slewing ? ISS_ON : ISS_OFF;
1644  AxisOneStateHasChanged = true;
1645  }
1646  if (AxisOneStateS[SLEWING_TO].s != (AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF))
1647  {
1648  AxisOneStateS[SLEWING_TO].s = AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF;
1649  AxisOneStateHasChanged = true;
1650  }
1651  if (AxisOneStateS[SLEWING_FORWARD].s != (AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF))
1652  {
1653  AxisOneStateS[SLEWING_FORWARD].s = AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF;
1654  AxisOneStateHasChanged = true;
1655  }
1656  if (AxisOneStateS[HIGH_SPEED].s != (AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF))
1657  {
1658  AxisOneStateS[HIGH_SPEED].s = AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF;
1659  AxisOneStateHasChanged = true;
1660  }
1661  if (AxisOneStateS[NOT_INITIALISED].s != (AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF))
1662  {
1663  AxisOneStateS[NOT_INITIALISED].s = AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF;
1664  AxisOneStateHasChanged = true;
1665  }
1666  if (AxisOneStateHasChanged && InformClient)
1667  IDSetSwitch(&AxisOneStateSP, nullptr);
1668 
1669  bool AxisTwoInfoHasChanged = false;
1670  if (AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[1])
1671  {
1672  AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[1];
1673  AxisTwoInfoHasChanged = true;
1674  }
1675  if (AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[1])
1676  {
1677  AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[1];
1678  AxisTwoInfoHasChanged = true;
1679  }
1680  if (AxisTwoInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[1])
1681  {
1682  AxisTwoInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[1];
1683  AxisTwoInfoHasChanged = true;
1684  }
1685  if (AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[1])
1686  {
1687  AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[1];
1688  AxisTwoInfoHasChanged = true;
1689  }
1690  if (AxisTwoInfoHasChanged && InformClient)
1691  IDSetNumber(&AxisTwoInfoNP, nullptr);
1692 
1693  bool AxisTwoStateHasChanged = false;
1694  if (AxisTwoStateS[FULL_STOP].s != (AxesStatus[1].FullStop ? ISS_ON : ISS_OFF))
1695  {
1696  AxisTwoStateS[FULL_STOP].s = AxesStatus[1].FullStop ? ISS_ON : ISS_OFF;
1697  AxisTwoStateHasChanged = true;
1698  }
1699  if (AxisTwoStateS[SLEWING].s != (AxesStatus[1].Slewing ? ISS_ON : ISS_OFF))
1700  {
1701  AxisTwoStateS[SLEWING].s = AxesStatus[1].Slewing ? ISS_ON : ISS_OFF;
1702  AxisTwoStateHasChanged = true;
1703  }
1704  if (AxisTwoStateS[SLEWING_TO].s != (AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF))
1705  {
1706  AxisTwoStateS[SLEWING_TO].s = AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF;
1707  AxisTwoStateHasChanged = true;
1708  }
1709  if (AxisTwoStateS[SLEWING_FORWARD].s != (AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF))
1710  {
1711  AxisTwoStateS[SLEWING_FORWARD].s = AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF;
1712  AxisTwoStateHasChanged = true;
1713  }
1714  if (AxisTwoStateS[HIGH_SPEED].s != (AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF))
1715  {
1716  AxisTwoStateS[HIGH_SPEED].s = AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF;
1717  AxisTwoStateHasChanged = true;
1718  }
1719  if (AxisTwoStateS[NOT_INITIALISED].s != (AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF))
1720  {
1721  AxisTwoStateS[NOT_INITIALISED].s = AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF;
1722  AxisTwoStateHasChanged = true;
1723  }
1724  if (AxisTwoStateHasChanged && InformClient)
1725  IDSetSwitch(&AxisTwoStateSP, nullptr);
1726 
1727  bool AxisOneEncoderValuesHasChanged = false;
1728  if ((AxisOneEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS1]) ||
1729  (AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1]))
1730  {
1731  AxisOneEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS1];
1732  AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS1] / 3600.0;
1733  AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1];
1734  AxisOneEncoderValuesN[DEGREES_FROM_INITIAL].value =
1735  MicrostepsToDegrees(AXIS1, CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1]);
1736  AxisOneEncoderValuesHasChanged = true;
1737  }
1738  if (AxisOneEncoderValuesHasChanged && InformClient)
1739  IDSetNumber(&AxisOneEncoderValuesNP, nullptr);
1740 
1741  bool AxisTwoEncoderValuesHasChanged = false;
1742  if ((AxisTwoEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS2]) ||
1743  (AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2]))
1744  {
1745  AxisTwoEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS2];
1746  AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS2] / 3600.0;
1747  AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2];
1748  AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL].value =
1749  MicrostepsToDegrees(AXIS2, CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2]);
1750  AxisTwoEncoderValuesHasChanged = true;
1751  }
1752  if (AxisTwoEncoderValuesHasChanged && InformClient)
1753  IDSetNumber(&AxisTwoEncoderValuesNP, nullptr);
1754 }
1755 
1760 {
1761  SetAxis1Park(CurrentEncoders[AXIS1]);
1762  SetAxis2Park(CurrentEncoders[AXIS2]);
1763  return true;
1764 }
1765 
1770 {
1771  // Zero azimuth looking north/south (depending on hemisphere)
1772  SetAxis1Park(ZeroPositionEncoders[AXIS1]);
1773  SetAxis2Park(ZeroPositionEncoders[AXIS2]);
1774  return true;
1775 }
1776 
1780 void SkywatcherAPIMount::resetTracking()
1781 {
1782  m_TrackingRateTimer.restart();
1783  GuideDeltaAlt = 0;
1784  GuideDeltaAz = 0;
1785  m_Controllers[AXIS_AZ].reset(new PID(std::max(0.001, getPollingPeriod() / 1000.), 50, -50,
1786  Axis1PIDNP[Propotional].getValue(),
1787  Axis1PIDNP[Derivative].getValue(),
1788  Axis1PIDNP[Integral].getValue()));
1789  m_Controllers[AXIS_AZ]->setIntegratorLimits(-100, 100);
1790  m_Controllers[AXIS_ALT].reset(new PID(std::max(0.001, getPollingPeriod() / 1000.), 50, -50,
1791  Axis2PIDNP[Propotional].getValue(),
1792  Axis2PIDNP[Derivative].getValue(),
1793  Axis2PIDNP[Integral].getValue()));
1794  m_Controllers[AXIS_ALT]->setIntegratorLimits(-100, 100);
1795  ResetGuidePulses();
1796 }
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.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual void TimerHit() override
Called when setTimer() time is up.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual 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 updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
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.
Definition: pid.h:29
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual void TimerHit() override
Called when setTimer() time is up.
virtual bool ReadScopeStatus() override
Read telescope status.
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 Abort() override
Abort any telescope motion including tracking if possible.
virtual const char * getDefaultName() override
Misc.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool Handshake() override
Communication.
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool Park() override
Parking.
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Motion.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
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 UnPark() override
Unpark the telescope if already parked.
virtual IPState GuideNorth(uint32_t ms) override
Guiding.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
double max(void)
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
@ ISR_1OFMANY
Definition: indiapi.h:173
@ 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
@ AXIS_AZ
Definition: indibasetypes.h:42
@ AXIS_ALT
Definition: indibasetypes.h:43
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
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition: indicom.c:1245
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.
#define TRACKRATE_SOLAR
Definition: indicom.h:61
#define TRACKRATE_SIDEREAL
Definition: indicom.h:55
#define TRACKRATE_LUNAR
Definition: indicom.h:64
const char * Direction[]
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 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
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 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 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 LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition: indilogger.h:57
unsigned int DBG_SCOPE
Definition: lx200driver.cpp:53
int Sync(int fd, char *matchedObject)
int Slew(int fd)
Namespace to encapsulate the INDI Alignment Subsystem classes. For more information see "INDI Alignme...
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
const char * getDeviceName()
bool isParked(const int fd)
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
#define SLEWMODES
double SlewSpeeds[SLEWMODES]
Entry in the in memory alignment database.
Definition: Common.h:152
double RightAscension
Right ascension in decimal hours. N.B. libnova works in decimal degrees so conversion is always neede...
Definition: Common.h:190
TelescopeDirectionVector TelescopeDirection
Normalised vector giving telescope pointing direction. This is referred to elsewhere as the "apparent...
Definition: Common.h:197
double Declination
Declination in decimal degrees.
Definition: Common.h:193
int PrivateDataSize
This size in bytes of any private data.
Definition: Common.h:203
Holds a nomalised direction vector (direction cosines)
Definition: Common.h:69
void RotateAroundY(double Angle)
Rotate the reference frame around the Y axis. This has the affect of rotating the vector itself in th...
Definition: Common.cpp:18
One number descriptor.
One switch descriptor.