Instrument Neutral Distributed Interface INDI 2.2.5
skywatcherAPIMount.cpp
Go to the documentation of this file.
1
24#include "skywatcherAPIMount.h"
25
26#include "indicom.h"
30
31#include <chrono>
32#include <thread>
33
34#include <sys/stat.h>
35
36#define DEBUG_PID
37
38using namespace INDI::AlignmentSubsystem;
39
40// We declare an auto pointer to SkywatcherAPIMount.
41static std::unique_ptr<SkywatcherAPIMount> SkywatcherAPIMountPtr(new SkywatcherAPIMount());
42
43/* Preset Slew Speeds */
44#define SLEWMODES 10
45static double SlewSpeeds[SLEWMODES] = { 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 600.0, 800.0 };
46
51{
52 // Set up the logging pointer in SkyWatcherAPI
53 pChildTelescope = this;
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 // reset connection in case of packet loss
79 }
80
82 bool Result = InitMount();
83 DEBUGF(DBG_SCOPE, "SkywatcherAPIMount::Handshake - Result: %d", Result);
84 return Result;
85}
86
91{
92 return "Skywatcher Alt-Az";
93}
94
99{
100 // Allow the base class to initialise its visible before connection properties
102
103 for (size_t i = 0; i < SlewRateSP.count(); ++i)
104 {
105 SlewRateSP[i].setLabel(std::to_string(static_cast<int>(SlewSpeeds[i])) + "x");
106 SlewRateSP[i].setAux(&SlewSpeeds[i]);
107 }
108 SlewRateSP[SlewRateSP.count() - 1].setName("SLEW_MAX");
109
110 AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
111 AddTrackMode("TRACK_SOLAR", "Solar");
112 AddTrackMode("TRACK_LUNAR", "Lunar");
113
114 // Add default properties
117
118 // Add alignment properties
120
121 // Force the alignment system to always be on
122 getSwitch("ALIGNMENT_SUBSYSTEM_ACTIVE")[0].setState(ISS_ON);
123
124 // Set up property variables
125 IUFillText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], "MOTOR_CONTROL_FIRMWARE_VERSION",
126 "Motor control firmware version", "-");
127 IUFillText(&BasicMountInfoT[MOUNT_CODE], "MOUNT_CODE", "Mount code", "-");
128 IUFillText(&BasicMountInfoT[MOUNT_NAME], "MOUNT_NAME", "Mount name", "-");
129 IUFillText(&BasicMountInfoT[IS_DC_MOTOR], "IS_DC_MOTOR", "Is DC motor", "-");
130 IUFillTextVector(&BasicMountInfoTP, BasicMountInfoT, 4, getDeviceName(), "BASIC_MOUNT_INFO",
131 "Basic mount information", MountInfoTab, IP_RO, 60, IPS_IDLE);
132
133 IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
134 "%.0f", 0, 0xFFFFFF, 1, 0);
135 IUFillNumber(&AxisOneInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Stepper clock frequency", "%.0f", 0,
136 0xFFFFFF, 1, 0);
137 IUFillNumber(&AxisOneInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
138 IUFillNumber(&AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
139 "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
140
141 IUFillNumberVector(&AxisOneInfoNP, AxisOneInfoN, 4, getDeviceName(), "AXIS_ONE_INFO", "Axis one information",
142 MountInfoTab, IP_RO, 60, IPS_IDLE);
143
144 IUFillSwitch(&AxisOneStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
145 IUFillSwitch(&AxisOneStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
146 IUFillSwitch(&AxisOneStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
147 IUFillSwitch(&AxisOneStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
148 IUFillSwitch(&AxisOneStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
149 IUFillSwitch(&AxisOneStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
150 IUFillSwitchVector(&AxisOneStateSP, AxisOneStateS, 6, getDeviceName(), "AXIS_ONE_STATE", "Axis one state",
151 MountInfoTab, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
152
153 IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION], "MICROSTEPS_PER_REVOLUTION", "Microsteps per revolution",
154 "%.0f", 0, 0xFFFFFF, 1, 0);
155 IUFillNumber(&AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY], "STEPPER_CLOCK_FREQUENCY", "Step timer frequency", "%.0f", 0,
156 0xFFFFFF, 1, 0);
157 IUFillNumber(&AxisTwoInfoN[HIGH_SPEED_RATIO], "HIGH_SPEED_RATIO", "High speed ratio", "%.0f", 0, 0xFFFFFF, 1, 0);
158 IUFillNumber(&AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION], "MICROSTEPS_PER_WORM_REVOLUTION",
159 "Microsteps per worm revolution", "%.0f", 0, 0xFFFFFF, 1, 0);
160
161 IUFillNumberVector(&AxisTwoInfoNP, AxisTwoInfoN, 4, getDeviceName(), "AXIS_TWO_INFO", "Axis two information",
162 MountInfoTab, IP_RO, 60, IPS_IDLE);
163
164 IUFillSwitch(&AxisTwoStateS[FULL_STOP], "FULL_STOP", "FULL_STOP", ISS_OFF);
165 IUFillSwitch(&AxisTwoStateS[SLEWING], "SLEWING", "SLEWING", ISS_OFF);
166 IUFillSwitch(&AxisTwoStateS[SLEWING_TO], "SLEWING_TO", "SLEWING_TO", ISS_OFF);
167 IUFillSwitch(&AxisTwoStateS[SLEWING_FORWARD], "SLEWING_FORWARD", "SLEWING_FORWARD", ISS_OFF);
168 IUFillSwitch(&AxisTwoStateS[HIGH_SPEED], "HIGH_SPEED", "HIGH_SPEED", ISS_OFF);
169 IUFillSwitch(&AxisTwoStateS[NOT_INITIALISED], "NOT_INITIALISED", "NOT_INITIALISED", ISS_ON);
170 IUFillSwitchVector(&AxisTwoStateSP, AxisTwoStateS, 6, getDeviceName(), "AXIS_TWO_STATE", "Axis two state",
171 MountInfoTab, IP_RO, ISR_NOFMANY, 60, IPS_IDLE);
172
173 IUFillNumber(&AxisOneEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
174 IUFillNumber(&AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
175 "%.4f", 0, 0xFFFFFF, 1, 0);
176 IUFillNumber(&AxisOneEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
177 0xFFFFFF, 1, 0);
178 IUFillNumber(&AxisOneEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
179 -1000.0, 1000.0, 1, 0);
180
181 IUFillNumberVector(&AxisOneEncoderValuesNP, AxisOneEncoderValuesN, 4, getDeviceName(), "AXIS1_ENCODER_VALUES",
182 "Axis 1 Encoder values", MountInfoTab, IP_RO, 60, IPS_IDLE);
183
184 IUFillNumber(&AxisTwoEncoderValuesN[RAW_MICROSTEPS], "RAW_MICROSTEPS", "Raw Microsteps", "%.0f", 0, 0xFFFFFF, 1, 0);
185 IUFillNumber(&AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC], "MICROSTEPS_PER_ARCSEC", "Microsteps/arcsecond",
186 "%.4f", 0, 0xFFFFFF, 1, 0);
187 IUFillNumber(&AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL], "OFFSET_FROM_INITIAL", "Offset from initial", "%.0f", 0,
188 0xFFFFFF, 1, 0);
189 IUFillNumber(&AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL], "DEGREES_FROM_INITIAL", "Degrees from initial", "%.2f",
190 -1000.0, 1000.0, 1, 0);
191
192 IUFillNumberVector(&AxisTwoEncoderValuesNP, AxisTwoEncoderValuesN, 4, getDeviceName(), "AXIS2_ENCODER_VALUES",
193 "Axis 2 Encoder values", MountInfoTab, IP_RO, 60, IPS_IDLE);
194 // Register any visible before connection properties
195
196 // Slew modes
197 IUFillSwitch(&SlewModesS[SLEW_SILENT], "SLEW_SILENT", "Silent", ISS_OFF);
198 IUFillSwitch(&SlewModesS[SLEW_NORMAL], "SLEW_NORMAL", "Normal", ISS_ON);
199 IUFillSwitchVector(&SlewModesSP, SlewModesS, 2, getDeviceName(), "TELESCOPE_MOTION_SLEWMODE", "Slew Mode",
201
202 // SoftPEC modes
203 IUFillSwitch(&SoftPECModesS[SOFTPEC_ENABLED], "SOFTPEC_ENABLED", "Enable for tracking", ISS_OFF);
204 IUFillSwitch(&SoftPECModesS[SOFTPEC_DISABLED], "SOFTPEC_DISABLED", "Disabled", ISS_ON);
205 IUFillSwitchVector(&SoftPECModesSP, SoftPECModesS, 2, getDeviceName(), "TELESCOPE_MOTION_SOFTPECMODE",
206 "SoftPEC Mode", MOTION_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
207
208 // SoftPEC value for tracking mode
209 IUFillNumber(&SoftPecN, "SOFTPEC_VALUE", "degree/minute (Alt)", "%1.3f", 0.001, 1.0, 0.001, 0.009);
210 IUFillNumberVector(&SoftPecNP, &SoftPecN, 1, getDeviceName(), "SOFTPEC", "SoftPEC Value", MOTION_TAB, IP_RW, 60,
211 IPS_IDLE);
212
213 // Guiding rates for RA/DEC axes
214 IUFillNumber(&GuidingRatesN[0], "GUIDERA_RATE", "arcsec/seconds (RA)", "%1.3f", 1.0, 6000.0, 1.0, 120.0);
215 IUFillNumber(&GuidingRatesN[1], "GUIDEDEC_RATE", "arcsec/seconds (Dec)", "%1.3f", 1.0, 6000.0, 1.0, 120.0);
216 IUFillNumberVector(&GuidingRatesNP, GuidingRatesN, 2, getDeviceName(), "GUIDE_RATES", "Guide Rates", MOTION_TAB,
217 IP_RW, 60, IPS_IDLE);
218
219 // AUX Encoders
220 AUXEncoderSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_ON);
221 AUXEncoderSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_OFF);
222 AUXEncoderSP.fill(getDeviceName(), "AUX_ENCODERS", "AUX Encoders", TRACKING_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
223 AUXEncoderSP.load();
224
225 // Snap port
226 SnapPortSP[INDI_ENABLED].fill("INDI_ENABLED", "On", ISS_OFF);
227 SnapPortSP[INDI_DISABLED].fill("INDI_DISABLED", "Off", ISS_ON);
228 SnapPortSP.fill(getDeviceName(), "SNAP_PORT", "Snap Port", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
229
230 // PID Control
231 Axis1PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0.1, 100, 1, 0.1);
232 Axis1PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 500, 10, 0.05);
233 Axis1PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 500, 10, 0.05);
234 Axis1PIDNP.fill(getDeviceName(), "AXIS1_PID", "Axis1 PID", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
235
236 Axis2PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0.1, 100, 1, 0.2);
237 Axis2PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 100, 10, 0.1);
238 Axis2PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 100, 10, 0.1);
239 Axis2PIDNP.fill(getDeviceName(), "AXIS2_PID", "Axis2 PID", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
240
241 // Dead Zone
242 AxisDeadZoneNP[AXIS1].fill("AXIS1", "AZ (steps)", "%.f", 0, 100, 10, 10);
243 AxisDeadZoneNP[AXIS2].fill("AXIS2", "AL (steps)", "%.f", 0, 100, 10, 10);
244 AxisDeadZoneNP.fill(getDeviceName(), "DEAD_ZONE", "Dead Zone", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
245
246 // Clock Multiplier
247 AxisClockNP[AXIS1].fill("AXIS1", "AZ %", "%.f", 1, 200, 10, 100);
248 AxisClockNP[AXIS2].fill("AXIS2", "AL %", "%.f", 1, 200, 10, 100);
249 AxisClockNP.fill(getDeviceName(), "AXIS_CLOCK", "Clock Rate", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
250
251 // Offsets
252 AxisOffsetNP[RAOffset].fill("RAOffset", "RA (deg)", "%.2f", -1, 1, 0.05, 0);
253 AxisOffsetNP[DEOffset].fill("DEOffset", "DE (deg)", "%.2f", -1, 1, 0.05, 0);
254 AxisOffsetNP[AZSteps].fill("AZEncoder", "AZ (steps)", "%.f", -10000, 10000, 1000, 0);
255 AxisOffsetNP[ALSteps].fill("ALEncoder", "AL (steps)", "%.f", -10000, 10000, 1000, -100.0);
256 AxisOffsetNP[JulianOffset].fill("JulianOffset", "JD (s)", "%.f", -5, 5, 0.1, 0);
257 AxisOffsetNP.fill(getDeviceName(), "AXIS_OFFSET", "Offsets", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
258
259 // Tracking Rate
260 Axis1TrackRateNP[TrackDirection].fill("TrackDirection", "West/East", "%.f", 0, 1, 1, 0);
261 Axis1TrackRateNP[TrackClockRate].fill("TrackClockRate", "Freq/Step (Hz/s)", "%.f", 0, 16000000, 500000, 0);
262 Axis1TrackRateNP.fill(getDeviceName(), "AXIS1TrackRate", "Axis 1 Track", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
263
264 Axis2TrackRateNP[TrackDirection].fill("TrackDirection", "North/South", "%.f", 0, 1, 1, 0);
265 Axis2TrackRateNP[TrackClockRate].fill("TrackClockRate", "Freq/Stel (Hz/s)", "%.f", 0, 16000000, 500000, 0);
266 Axis2TrackRateNP.fill(getDeviceName(), "AXIS2TrackRate", "Axis 2 Track", TRACKING_TAB, IP_RW, 60, IPS_IDLE);
267
268
271
272 tcpConnection->setDefaultHost("192.168.4.1");
275
276 if (strstr(getDeviceName(), "Wired"))
277 {
279 }
280 else if (strstr(getDeviceName(), "GTi"))
281 {
284 }
285
287
288 // Guiding support
291
292 return true;
293}
294
298bool 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
313bool SkywatcherAPIMount::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
314{
315 // Check guider interface
316 if (GI::processNumber(dev, name, values, names, n))
317 return true;
318
319 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
320 {
321 ProcessAlignmentNumberProperties(this, name, values, names, n);
322
323 if (strcmp(name, "SOFTPEC") == 0)
324 {
325 SoftPecNP.s = IPS_OK;
326 IUUpdateNumber(&SoftPecNP, values, names, n);
327 IDSetNumber(&SoftPecNP, nullptr);
328 return true;
329 }
330
331 if (strcmp(name, "GUIDE_RATES") == 0)
332 {
333 ResetGuidePulses();
334 GuidingRatesNP.s = IPS_OK;
335 IUUpdateNumber(&GuidingRatesNP, values, names, n);
336 IDSetNumber(&GuidingRatesNP, nullptr);
337 return true;
338 }
339
340 // Dead Zone
341 if (AxisDeadZoneNP.isNameMatch(name))
342 {
343 AxisDeadZoneNP.update(values, names, n);
344 AxisDeadZoneNP.setState(IPS_OK);
345 AxisDeadZoneNP.apply();
346 saveConfig(true, AxisDeadZoneNP.getName());
347 return true;
348 }
349
350 // Clock Rate
351 if (AxisClockNP.isNameMatch(name))
352 {
353 AxisClockNP.update(values, names, n);
354 AxisClockNP.setState(IPS_OK);
355 AxisClockNP.apply();
356 saveConfig(true, AxisClockNP.getName());
357 return true;
358 }
359
360 // Offsets
361 if (AxisOffsetNP.isNameMatch(name))
362 {
363 AxisOffsetNP.update(values, names, n);
364 AxisOffsetNP.setState(IPS_OK);
365 AxisOffsetNP.apply();
366 saveConfig(true, AxisOffsetNP.getName());
367 return true;
368 }
369
370 // Axis 1
371 if (Axis1TrackRateNP.isNameMatch(name))
372 {
373 Axis1TrackRateNP.update(values, names, n);
374 Axis1TrackRateNP.setState(IPS_OK);
375 Axis1TrackRateNP.apply();
376 saveConfig(true, Axis1TrackRateNP.getName());
377 return true;
378 }
379
380 // Axis 2
381 if (Axis2TrackRateNP.isNameMatch(name))
382 {
383 Axis2TrackRateNP.update(values, names, n);
384 Axis2TrackRateNP.setState(IPS_OK);
385 Axis2TrackRateNP.apply();
386 saveConfig(true, Axis2TrackRateNP.getName());
387 return true;
388 }
389
390 // Axis1 PID
391 if (Axis1PIDNP.isNameMatch(name))
392 {
393 Axis1PIDNP.update(values, names, n);
394 Axis1PIDNP.setState(IPS_OK);
395 Axis1PIDNP.apply();
396 saveConfig(Axis1PIDNP);
397
398 m_Controllers[AXIS1].reset(new PID(getPollingPeriod() / 1000, 50, -50,
399 Axis1PIDNP[Propotional].getValue(),
400 Axis1PIDNP[Derivative].getValue(),
401 Axis1PIDNP[Integral].getValue()));
402 return true;
403 }
404
405 // Axis2 PID
406 if (Axis2PIDNP.isNameMatch(name))
407 {
408 Axis2PIDNP.update(values, names, n);
409 Axis2PIDNP.setState(IPS_OK);
410 Axis2PIDNP.apply();
411 saveConfig(Axis2PIDNP);
412
413 m_Controllers[AXIS2].reset(new PID(getPollingPeriod() / 1000, 50, -50,
414 Axis2PIDNP[Propotional].getValue(),
415 Axis2PIDNP[Derivative].getValue(),
416 Axis2PIDNP[Integral].getValue()));
417 return true;
418 }
419
420
421 // Let our driver do sync operation in park position
422 if (strcmp(name, "EQUATORIAL_EOD_COORD") == 0)
423 {
424 double ra = -1;
425 double dec = -100;
426
427 for (int x = 0; x < n; x++)
428 {
429 if (EqNP[AXIS_RA].isNameMatch(names[x]))
430 ra = values[x];
431 else if (EqNP[AXIS_DE].isNameMatch(names[x]))
432 dec = values[x];
433 }
434 if ((ra >= 0) && (ra <= 24) && (dec >= -90) && (dec <= 90))
435 {
436 if (CoordSP.isSwitchOn("SYNC") && isParked())
437 {
438 return Sync(ra, dec);
439 }
440 }
441 }
442 }
443 // Pass it up the chain
444 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
445}
446
450bool SkywatcherAPIMount::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
451{
452 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
453 {
454 // Auxiliary Encoders
455 if (AUXEncoderSP.isNameMatch(name))
456 {
457 AUXEncoderSP.update(states, names, n);
458 AUXEncoderSP.setState(IPS_OK);
459 AUXEncoderSP.apply();
460 auto enabled = AUXEncoderSP.findOnSwitchIndex() == INDI_ENABLED;
461 TurnRAEncoder(enabled);
462 TurnDEEncoder(enabled);
463 saveConfig(AUXEncoderSP);
464 return true;
465 }
466
467 // Snap Port
468 if (SnapPortSP.isNameMatch(name))
469 {
470 SnapPortSP.update(states, names, n);
471 auto enabled = SnapPortSP.findOnSwitchIndex() == INDI_ENABLED;
472 toggleSnapPort(enabled);
473 if (enabled)
474 LOG_INFO("Toggling snap port on...");
475 else
476 LOG_INFO("Toggling snap port off...");
477 SnapPortSP.setState(enabled ? IPS_OK : IPS_IDLE);
478 SnapPortSP.apply();
479 return true;
480 }
481
482 ProcessAlignmentSwitchProperties(this, name, states, names, n);
483 }
484 // Pass it up the chain
485 return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
486}
487
491bool SkywatcherAPIMount::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
492{
493 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
494 {
495 ProcessAlignmentTextProperties(this, name, texts, names, n);
496 }
497 // Pass it up the chain
498 return INDI::Telescope::ISNewText(dev, name, texts, names, n);
499}
500
504bool SkywatcherAPIMount::Goto(double ra, double dec)
505{
506 if (m_IterativeGOTOPending)
507 {
508 char RAStr[32], DecStr[32];
509 fs_sexa(RAStr, m_SkyCurrentRADE.rightascension, 2, 3600);
510 fs_sexa(DecStr, m_SkyCurrentRADE.declination, 2, 3600);
511 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Iterative GOTO RA %lf DEC %lf (Current Sky RA %s DE %s)", ra, dec, RAStr,
512 DecStr);
513 }
514 else
515 {
516 if (TrackState != SCOPE_IDLE)
517 Abort();
518
519 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "GOTO RA %lf DEC %lf", ra, dec);
520
521 auto onSwitch = CoordSP.findOnSwitch();
522 if (onSwitch && (onSwitch->isNameMatch("TRACK") || onSwitch->isNameMatch("SLEW")))
523 {
524 char RAStr[32], DecStr[32];
525 fs_sexa(RAStr, ra, 2, 3600);
526 fs_sexa(DecStr, dec, 2, 3600);
527 m_SkyTrackingTarget.rightascension = ra;
528 m_SkyTrackingTarget.declination = dec;
529 LOGF_INFO("Goto target RA %s DEC %s", RAStr, DecStr);
530 }
531 }
532
533 INDI::IHorizontalCoordinates AltAz { 0, 0 };
535
536 // Transform Celestial to Telescope coordinates.
537 // We have no good way to estimate how long will the mount takes to reach target (with deceleration,
538 // and not just speed). So we will use iterative GOTO once the first GOTO is complete.
539 if (TransformCelestialToTelescopeJD(ra, dec, ln_get_julian_from_sys(), TDV))
540 {
541 INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
543 INDI::HorizontalToEquatorial(&AltAz, &m_Location, ln_get_julian_from_sys(), &EquatorialCoordinates);
544
545 char RAStr[32], DecStr[32];
546 fs_sexa(RAStr, EquatorialCoordinates.rightascension, 2, 3600);
547 fs_sexa(DecStr, EquatorialCoordinates.declination, 2, 3600);
548
549 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Sky -> Mount RA %s DE %s (TDV x %lf y %lf z %lf)", RAStr, DecStr, TDV.x,
550 TDV.y, TDV.z);
551 }
552 else
553 {
554 // Try a conversion with the stored observatory position if any
555 INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
556 EquatorialCoordinates.rightascension = ra;
557 EquatorialCoordinates.declination = dec;
558 INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys(), &AltAz);
561 {
562 case ZENITH:
563 break;
564
566 // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 minus
567 // the (positive)observatory latitude. The vector itself is rotated anticlockwise
569 break;
570
572 // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 plus
573 // the (negative)observatory latitude. The vector itself is rotated clockwise
575 break;
576 }
578 }
579
581 "Sky -> Mount AZ %lf° (%ld) AL %lf° (%ld)",
582 AltAz.azimuth,
583 DegreesToMicrosteps(AXIS1, AltAz.azimuth),
584 AltAz.altitude,
585 DegreesToMicrosteps(AXIS2, AltAz.altitude));
586
587 // Update the current encoder positions
590
591 long AzimuthOffsetMicrosteps = DegreesToMicrosteps(AXIS1,
592 AltAz.azimuth) + ZeroPositionEncoders[AXIS1] - (CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue());
593 long AltitudeOffsetMicrosteps = DegreesToMicrosteps(AXIS2,
594 AltAz.altitude) + ZeroPositionEncoders[AXIS2] - (CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue());
595
596 if (AzimuthOffsetMicrosteps > MicrostepsPerRevolution[AXIS1] / 2)
597 {
598 // Going the long way round - send it the other way
599 AzimuthOffsetMicrosteps -= MicrostepsPerRevolution[AXIS1];
600 }
601
602 // Do I need to take out any complete revolutions before I do this test?
603 if (AltitudeOffsetMicrosteps > MicrostepsPerRevolution[AXIS2] / 2)
604 {
605 // Going the long way round - send it the other way
606 AltitudeOffsetMicrosteps -= MicrostepsPerRevolution[AXIS2];
607 }
608
609 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Current Axis1 %ld microsteps (Zero %ld) Axis2 %ld microsteps (Zero %ld)",
611 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Azimuth offset %ld microsteps | Altitude offset %ld microsteps",
612 AzimuthOffsetMicrosteps, AltitudeOffsetMicrosteps);
613
614 SilentSlewMode = (IUFindSwitch(&SlewModesSP, "SLEW_SILENT") != nullptr
615 && IUFindSwitch(&SlewModesSP, "SLEW_SILENT")->s == ISS_ON);
616
618 {
619 long deltaAz = DegreesToMicrosteps(AXIS1, AZ_BACKLASH_DEG);
620 long deltaAlt = DegreesToMicrosteps(AXIS2, ALT_BACKLASH_DEG);
621 AzimuthOffsetMicrosteps -= deltaAz;
622 AltitudeOffsetMicrosteps -= deltaAlt;
623 }
624 SlewTo(AXIS1, AzimuthOffsetMicrosteps);
625 SlewTo(AXIS2, AltitudeOffsetMicrosteps);
626
628
629 return true;
630}
631
636{
638
639 if (isConnected())
640 {
641 // Fill in any real values now available MCInit should have been called already
642 UpdateDetailedMountInformation(false);
643
644 // Define our connected only properties to the base driver
645 // e.g. defineProperty(MyNumberVectorPointer);
646 // This will register our properties and send a IDDefXXXX mewssage to any connected clients
647 defineProperty(&BasicMountInfoTP);
648 defineProperty(&AxisOneInfoNP);
649 defineProperty(&AxisOneStateSP);
650 defineProperty(&AxisTwoInfoNP);
651 defineProperty(&AxisTwoStateSP);
652 defineProperty(&AxisOneEncoderValuesNP);
653 defineProperty(&AxisTwoEncoderValuesNP);
654 defineProperty(&SlewModesSP);
655 defineProperty(&SoftPECModesSP);
656 defineProperty(&SoftPecNP);
657 defineProperty(&GuidingRatesNP);
660 }
661}
662
667{
668 ISwitch *Switch = SlewRateSP.findOnSwitch();
669 return *(static_cast<double *>(Switch->aux));
670}
671
676{
677 double speed =
679 const char *dirStr = (dir == DIRECTION_NORTH) ? "North" : "South";
680
681 switch (command)
682 {
683 case MOTION_START:
684 DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
685 // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
686 Slew(AXIS2, speed, true);
687 m_ManualMotionActive[AXIS2] = true;
688 break;
689
690 case MOTION_STOP:
691 DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
693 break;
694 }
695
696 return true;
697}
698
703{
704 double speed =
706 const char *dirStr = (dir == DIRECTION_WEST) ? "West" : "East";
707
708 switch (command)
709 {
710 case MOTION_START:
711 DEBUGF(DBG_SCOPE, "Starting Slew %s", dirStr);
712 // Ignore the silent mode because MoveNS() is called by the manual motion UI controls.
713 Slew(AXIS1, speed, true);
714 m_ManualMotionActive[AXIS1] = true;
715 break;
716
717 case MOTION_STOP:
718 DEBUGF(DBG_SCOPE, "Stopping Slew %s", dirStr);
720 break;
721 }
722
723 return true;
724}
725
730{
731 // Move the telescope to the desired position
732 long AltitudeOffsetMicrosteps = GetAxis2Park() - CurrentEncoders[AXIS2];
733 long AzimuthOffsetMicrosteps = GetAxis1Park() - CurrentEncoders[AXIS1];
735 "Parking: Altitude offset %ld microsteps Azimuth offset %ld microsteps", AltitudeOffsetMicrosteps,
736 AzimuthOffsetMicrosteps);
737
738 if (IUFindSwitch(&SlewModesSP, "SLEW_SILENT") != nullptr && IUFindSwitch(&SlewModesSP, "SLEW_SILENT")->s == ISS_ON)
739 {
740 SilentSlewMode = true;
741 }
742 else
743 {
744 SilentSlewMode = false;
745 }
746 SlewTo(AXIS1, AzimuthOffsetMicrosteps);
747 SlewTo(AXIS2, AltitudeOffsetMicrosteps);
748
750 return true;
751}
752
757{
758 SetParked(false);
759 return true;
760}
761
766{
767 if (enabled)
768 {
770 resetTracking();
771 m_SkyTrackingTarget.rightascension = EqNP[AXIS_RA].getValue();
772 m_SkyTrackingTarget.declination = EqNP[AXIS_DE].getValue();
773 }
774 else
775 {
780 m_ManualMotionActive[AXIS1] = m_ManualMotionActive[AXIS2] = false;
781
783 {
786 GuideNSNP[0].setValue(0);
787 GuideNSNP[1].setValue(0);
788 GuideWENP[0].setValue(0);
789 GuideWENP[1].setValue(0);
792 }
793 }
794
795 return true;
796}
797
802{
803 if (!GetStatus(AXIS1))
804 return false;
805
806 if (!GetStatus(AXIS2))
807 return false;
808
809 // Update Axis Position
810 if (!GetEncoder(AXIS1))
811 return false;
812 if (!GetEncoder(AXIS2))
813 return false;
814
815 UpdateDetailedMountInformation(true);
816
817 bool resetTrackingTimers = false;
818
819 // Calculate new RA DEC
820 INDI::IHorizontalCoordinates AltAz { 0, 0 };
821 AltAz.azimuth = range360(MicrostepsToDegrees(AXIS1,
822 CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
823 AltAz.altitude = MicrostepsToDegrees(AXIS2,
824 CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
825 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis1 encoder %ld (Zero %ld) -> AZ %lf°",
827 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis2 encoder %ld (Zero %ld) -> AL %lf°",
829
830 // Update current horizontal coords.
831 m_MountAltAz = AltAz;
832
833 // Get equatorial coords.
834 getCurrentRADE(AltAz, m_SkyCurrentRADE);
835 char RAStr[32], DecStr[32];
836 fs_sexa(RAStr, m_SkyCurrentRADE.rightascension, 2, 3600);
837 fs_sexa(DecStr, m_SkyCurrentRADE.declination, 2, 3600);
838 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Sky RA %s DE %s", RAStr, DecStr);
839
841 {
842 if ((AxesStatus[AXIS1].FullStop) && (AxesStatus[AXIS2].FullStop))
843 {
844 // If iterative GOTO was already engaged, stop it.
845 if (m_IterativeGOTOPending)
846 m_IterativeGOTOPending = false;
847 // If not, then perform the iterative GOTO once more.
848 else
849 {
850 m_IterativeGOTOPending = true;
851 return Goto(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination);
852 }
853
854 auto onSwitch = CoordSP.findOnSwitch();
855 if (onSwitch && (onSwitch->isNameMatch("TRACK")))
856 {
857 // Goto has finished start tracking
859 resetTrackingTimers = true;
860 LOG_INFO("Tracking started.");
861 }
862 else
863 {
865 }
866 }
867 }
868 else if (TrackState == SCOPE_PARKING)
869 {
870 if (!IsInMotion(AXIS1) && !IsInMotion(AXIS2))
871 {
874 SetParked(true);
875 }
876 }
877
878 if (resetTrackingTimers)
879 resetTracking();
880
881 NewRaDec(m_SkyCurrentRADE.rightascension, m_SkyCurrentRADE.declination);
882 return true;
883}
884
888bool SkywatcherAPIMount::getCurrentAltAz(INDI::IHorizontalCoordinates &altaz)
889{
890 // Update Axis Position
892 {
894 CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
896 CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
897 return true;
898 }
899
900 return false;
901}
902
906bool SkywatcherAPIMount::getCurrentRADE(INDI::IHorizontalCoordinates altaz, INDI::IEquatorialCoordinates &rade)
907{
909 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "TDV x %lf y %lf z %lf", TDV.x, TDV.y, TDV.z);
910
911 double RightAscension, Declination;
912 if (!TransformTelescopeToCelestialJD(TDV, RightAscension, Declination, ln_get_julian_from_sys()))
913 {
914 TelescopeDirectionVector RotatedTDV(TDV);
916 {
917 case ZENITH:
918 break;
919
921 // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 minus
922 // the (positive)observatory latitude. The vector itself is rotated clockwise
923 RotatedTDV.RotateAroundY(90.0 - m_Location.latitude);
925 break;
926
928 // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 plus
929 // the (negative)observatory latitude. The vector itself is rotated anticlockwise
930 RotatedTDV.RotateAroundY(-90.0 - m_Location.latitude);
932 break;
933 }
934
935 INDI::IEquatorialCoordinates EquatorialCoordinates;
936 INDI::HorizontalToEquatorial(&altaz, &m_Location, ln_get_julian_from_sys(), &EquatorialCoordinates);
937 RightAscension = EquatorialCoordinates.rightascension;
938 Declination = EquatorialCoordinates.declination;
939 }
940
941 rade.rightascension = RightAscension;
942 rade.declination = Declination;
943 return true;
944}
949{
951
952 Axis1PIDNP.save(fp);
953 Axis2PIDNP.save(fp);
954 AxisDeadZoneNP.save(fp);
955 AxisClockNP.save(fp);
956 AxisOffsetNP.save(fp);
957 Axis1TrackRateNP.save(fp);
958 Axis2TrackRateNP.save(fp);
959 AUXEncoderSP.save(fp);
960
962}
963
967bool SkywatcherAPIMount::Sync(double ra, double dec)
968{
969 DEBUG(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "SkywatcherAPIMount::Sync");
970
971 // Compute a telescope direction vector from the current encoders
972 if (!GetEncoder(AXIS1))
973 return false;
974 if (!GetEncoder(AXIS2))
975 return false;
976
977 // Syncing is treated specially when the telescope position is known in park position to spare
978 // "a huge-jump point" in the alignment model.
979 if (isParked())
980 {
981 INDI::IHorizontalCoordinates AltAz { 0, 0 };
983
984 if (TransformCelestialToTelescopeJD(ra, dec, ln_get_julian_from_sys(), TDV))
985 {
987 double OrigAlt = AltAz.altitude;
990 LOGF_INFO("Sync (Alt: %lf Az: %lf) in park position", OrigAlt, AltAz.azimuth);
991 GetAlignmentDatabase().clear();
992 return true;
993 }
994 }
995
996 // Might as well do this
997 UpdateDetailedMountInformation(true);
998
999 INDI::IHorizontalCoordinates AltAz { 0, 0 };
1000
1001 AltAz.azimuth = range360(MicrostepsToDegrees(AXIS1,
1002 CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1]));
1003 AltAz.altitude = MicrostepsToDegrees(AXIS2,
1004 CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2]);
1005
1006 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis1 encoder %ld initial %ld AZ %lf°",
1008 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis2 encoder %ld initial %ld AL %lf°",
1009 CurrentEncoders[AXIS2], ZeroPositionEncoders[AXIS2], AltAz.altitude);
1010
1011 AlignmentDatabaseEntry NewEntry;
1012 NewEntry.ObservationJulianDate = ln_get_julian_from_sys();
1013 NewEntry.RightAscension = ra;
1014 NewEntry.Declination = dec;
1016 NewEntry.PrivateDataSize = 0;
1017
1018 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "New sync point Date %lf RA %lf DEC %lf TDV(x %lf y %lf z %lf)",
1019 NewEntry.ObservationJulianDate, NewEntry.RightAscension, NewEntry.Declination, NewEntry.TelescopeDirection.x,
1020 NewEntry.TelescopeDirection.y, NewEntry.TelescopeDirection.z);
1021
1022 m_IterativeGOTOPending = false;
1023
1024 if (!CheckForDuplicateSyncPoint(NewEntry))
1025 {
1026 GetAlignmentDatabase().push_back(NewEntry);
1027
1028 // Tell the client about size change
1029 UpdateSize();
1030
1031 // Tell the math plugin to reinitialise
1032 Initialise(this);
1033
1034 // Force read before restarting
1036
1037 // The tracking seconds should be reset to restart the drift compensation
1038 resetTracking();
1039
1040 return true;
1041 }
1042 return false;
1043}
1044
1049{
1050 //DEBUG(DBG_SCOPE, "SkywatcherAPIMount::Abort");
1051 m_IterativeGOTOPending = false;
1052 SlowStop(AXIS1);
1053 SlowStop(AXIS2);
1055 m_ManualMotionActive[AXIS1] = m_ManualMotionActive[AXIS2] = false;
1056
1058 {
1061 GuideNSNP[0].setValue(0);
1062 GuideNSNP[1].setValue(0);
1063 GuideWENP[0].setValue(0);
1064 GuideWENP[1].setValue(0);
1065
1066 LOG_INFO("Guide aborted.");
1067 GuideNSNP.apply();
1068 GuideWENP.apply();
1069
1070 return true;
1071 }
1072
1073 return true;
1074}
1075
1080{
1081 // Call parent to read ReadScopeStatus
1083
1084 switch (TrackState)
1085 {
1086 case SCOPE_SLEWING:
1087 GuideDeltaAlt = 0;
1088 GuideDeltaAz = 0;
1089 ResetGuidePulses();
1090 GuidingPulses.clear();
1091 break;
1092
1093 case SCOPE_TRACKING:
1094 {
1095 // Check if manual motion in progress but we stopped
1096 bool manualMotionStopped = false;
1097 // Check each axis individually - the other may still be driven (tracking) and never stop on its own.
1098 for (AXISID axis : { AXIS1, AXIS2 })
1099 {
1100 auto &movementSP = (axis == AXIS1) ? MovementWESP : MovementNSSP;
1101 // check that for this axis the motor is not in motion and the user isn't feeding input (movement switch prop)
1102 if (m_ManualMotionActive[axis] && !IsInMotion(axis) && movementSP.getState() != IPS_BUSY)
1103 {
1104 m_ManualMotionActive[axis] = false;
1105 manualMotionStopped = true;
1106 }
1107 }
1108 // if either axis was being moved manually but has now stopped, reset tracking.
1109 if (manualMotionStopped)
1110 {
1111 resetTracking();
1112 m_SkyTrackingTarget.rightascension = EqNP[AXIS_RA].getValue();
1113 m_SkyTrackingTarget.declination = EqNP[AXIS_DE].getValue();
1114 }
1115 // If we're manually moving by WESN controls, update the tracking coordinates.
1116 if (m_ManualMotionActive[AXIS1] || m_ManualMotionActive[AXIS2])
1117 {
1118 break;
1119 }
1120 else
1121 {
1122 // TODO add switch to select between them
1123 //trackUsingPID();
1124 trackUsingPredictiveRates();
1125 }
1126
1127 break;
1128 }
1129
1130 default:
1131 GuideDeltaAlt = 0;
1132 GuideDeltaAz = 0;
1133 ResetGuidePulses();
1134 GuidingPulses.clear();
1135 break;
1136 }
1137}
1138
1142bool SkywatcherAPIMount::updateLocation(double latitude, double longitude, double elevation)
1143{
1144 //DEBUG(DBG_SCOPE, "SkywatcherAPIMount::updateLocation");
1145 UpdateLocation(latitude, longitude, elevation);
1146 return true;
1147}
1148
1153{
1155
1156 if (isConnected())
1157 {
1158 // Update location if loaded already from config
1159 if (m_Location.longitude > 0)
1161
1162 // Fill in any real values now available MCInit should have been called already
1163 UpdateDetailedMountInformation(false);
1164
1165 // Define our connected only properties to the base driver
1166 // e.g. defineProperty(MyNumberVectorPointer);
1167 // This will register our properties and send a IDDefXXXX message to any connected clients
1168 // I have now idea why I have to do this here as well as in ISGetProperties. It makes me
1169 // concerned there is a design or implementation flaw somewhere.
1170 defineProperty(&BasicMountInfoTP);
1171 defineProperty(&AxisOneInfoNP);
1172 defineProperty(&AxisOneStateSP);
1173 defineProperty(&AxisTwoInfoNP);
1174 defineProperty(&AxisTwoStateSP);
1175 defineProperty(&AxisOneEncoderValuesNP);
1176 defineProperty(&AxisTwoEncoderValuesNP);
1177 defineProperty(&SlewModesSP);
1178 defineProperty(&SoftPECModesSP);
1179 defineProperty(&SoftPecNP);
1180 defineProperty(&GuidingRatesNP);
1181 defineProperty(Axis1PIDNP);
1182 defineProperty(Axis2PIDNP);
1183 defineProperty(AxisDeadZoneNP);
1184 defineProperty(AxisClockNP);
1185 defineProperty(AxisOffsetNP);
1186 defineProperty(Axis1TrackRateNP);
1187 defineProperty(Axis2TrackRateNP);
1188
1189 if (HasAuxEncoders())
1190 {
1191 // Since config is loaded, let's use this as starting point.
1192 // We should not force AUX encoders if the user explicitly turned them off.
1193 auto enabled = AUXEncoderSP[INDI_ENABLED].getState() == ISS_ON;
1194 LOGF_INFO("AUX encoders detected. Turning %s...", enabled ? "on" : "off");
1195 TurnRAEncoder(enabled);
1196 TurnDEEncoder(enabled);
1197 defineProperty(AUXEncoderSP);
1198 }
1199
1200 if (InitPark())
1201 {
1202 // If loading parking data is successful, we just set the default parking values.
1205 }
1206 else
1207 {
1208 // Otherwise, we set all parking data to default in case no parking data is found.
1209 SetAxis1Park(0x800000);
1210 SetAxis2Park(0x800000);
1211 SetAxis1ParkDefault(0x800000);
1212 SetAxis2ParkDefault(0x800000);
1213 }
1214
1215 if (isParked())
1216 {
1219
1220 }
1221 return true;
1222 }
1223 else
1224 {
1225 // Delete any connected only properties from the base driver's list
1226 // e.g. deleteProperty(MyNumberVector.name);
1227 deleteProperty(BasicMountInfoTP.name);
1228 deleteProperty(AxisOneInfoNP.name);
1229 deleteProperty(AxisOneStateSP.name);
1230 deleteProperty(AxisTwoInfoNP.name);
1231 deleteProperty(AxisTwoStateSP.name);
1232 deleteProperty(AxisOneEncoderValuesNP.name);
1233 deleteProperty(AxisTwoEncoderValuesNP.name);
1234 deleteProperty(SlewModesSP.name);
1235 deleteProperty(SoftPECModesSP.name);
1236 deleteProperty(SoftPecNP.name);
1237 deleteProperty(GuidingRatesNP.name);
1238 deleteProperty(Axis1PIDNP);
1239 deleteProperty(Axis2PIDNP);
1240 deleteProperty(AxisDeadZoneNP);
1241 deleteProperty(AxisClockNP);
1242 deleteProperty(AxisOffsetNP);
1243 deleteProperty(Axis1TrackRateNP);
1244 deleteProperty(Axis2TrackRateNP);
1245
1246 if (HasAuxEncoders())
1247 deleteProperty(AUXEncoderSP);
1248
1249 return true;
1250 }
1251
1253}
1254
1259{
1260 GuidingPulse Pulse;
1261
1262 CalculateGuidePulses();
1263 Pulse.DeltaAz = NorthPulse.DeltaAz;
1264 Pulse.DeltaAlt = NorthPulse.DeltaAlt;
1265 Pulse.Duration = ms;
1266 Pulse.OriginalDuration = ms;
1267 GuidingPulses.push_back(Pulse);
1268 return IPS_OK;
1269}
1270
1275{
1276 GuidingPulse Pulse;
1277
1278 CalculateGuidePulses();
1279 Pulse.DeltaAz = -NorthPulse.DeltaAz;
1280 Pulse.DeltaAlt = -NorthPulse.DeltaAlt;
1281 Pulse.Duration = ms;
1282 Pulse.OriginalDuration = ms;
1283 GuidingPulses.push_back(Pulse);
1284 return IPS_OK;
1285}
1286
1291{
1292 GuidingPulse Pulse;
1293
1294 CalculateGuidePulses();
1295 Pulse.DeltaAz = WestPulse.DeltaAz;
1296 Pulse.DeltaAlt = WestPulse.DeltaAlt;
1297 Pulse.Duration = ms;
1298 Pulse.OriginalDuration = ms;
1299 GuidingPulses.push_back(Pulse);
1300 return IPS_OK;
1301}
1302
1307{
1308 GuidingPulse Pulse;
1309
1310 CalculateGuidePulses();
1311 Pulse.DeltaAz = -WestPulse.DeltaAz;
1312 Pulse.DeltaAlt = -WestPulse.DeltaAlt;
1313 Pulse.Duration = ms;
1314 Pulse.OriginalDuration = ms;
1315 GuidingPulses.push_back(Pulse);
1316 return IPS_OK;
1317}
1318
1322void SkywatcherAPIMount::CalculateGuidePulses()
1323{
1324 if (NorthPulse.Duration != 0 || WestPulse.Duration != 0)
1325 return;
1326
1327 // Calculate the west reference delta
1328 // Note: The RA is multiplied by 3.75 (90/24) to be more comparable with DEC values.
1329 const double WestRate = IUFindNumber(&GuidingRatesNP, "GUIDERA_RATE")->value / 10 * -1.0 / 60 / 60 * 3.75 / 100;
1330
1331 ConvertGuideCorrection(WestRate, 0, WestPulse.DeltaAlt, WestPulse.DeltaAz);
1332 WestPulse.Duration = 1;
1333
1334 // Calculate the north reference delta
1335 // Note: By some reason, it must be multiplied by 100 to match with the RA values.
1336 const double NorthRate = IUFindNumber(&GuidingRatesNP, "GUIDEDEC_RATE")->value / 10 * 1.0 / 60 / 60 * 100 / 100;
1337
1338 ConvertGuideCorrection(0, NorthRate, NorthPulse.DeltaAlt, NorthPulse.DeltaAz);
1339 NorthPulse.Duration = 1;
1340}
1341
1345void SkywatcherAPIMount::ResetGuidePulses()
1346{
1347 NorthPulse.Duration = 0;
1348 WestPulse.Duration = 0;
1349}
1350
1354void SkywatcherAPIMount::ConvertGuideCorrection(double delta_ra, double delta_dec, double &delta_alt, double &delta_az)
1355{
1356 INDI::IHorizontalCoordinates OldAltAz { 0, 0 };
1357 INDI::IHorizontalCoordinates NewAltAz { 0, 0 };
1360
1361 TransformCelestialToTelescopeJD(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination,
1362 ln_get_julian_from_sys(), OldTDV);
1364 TransformCelestialToTelescopeJD(m_SkyTrackingTarget.rightascension + delta_ra,
1365 m_SkyTrackingTarget.declination + delta_dec, ln_get_julian_from_sys(), NewTDV);
1367 delta_alt = NewAltAz.altitude - OldAltAz.altitude;
1368 delta_az = NewAltAz.azimuth - OldAltAz.azimuth;
1369}
1370
1374void SkywatcherAPIMount::SkywatcherMicrostepsFromTelescopeDirectionVector(
1375 const TelescopeDirectionVector TelescopeDirectionVector, long &Axis1Microsteps, long &Axis2Microsteps)
1376{
1377 // For the time being I assume that all skywathcer mounts share the same encoder conventions
1378 double Axis1Angle = 0;
1379 double Axis2Angle = 0;
1383
1384 Axis1Microsteps = RadiansToMicrosteps(AXIS1, Axis1Angle);
1385 Axis2Microsteps = RadiansToMicrosteps(AXIS2, Axis2Angle);
1386}
1387
1392SkywatcherAPIMount::TelescopeDirectionVectorFromSkywatcherMicrosteps(long Axis1Microsteps, long Axis2Microsteps)
1393{
1394 // For the time being I assume that all skywathcer mounts share the same encoder conventions
1395 double Axis1Angle = MicrostepsToRadians(AXIS1, Axis1Microsteps);
1396 double Axis2Angle = MicrostepsToRadians(AXIS2, Axis2Microsteps);
1399}
1400
1404void SkywatcherAPIMount::UpdateDetailedMountInformation(bool InformClient)
1405{
1406 bool BasicMountInfoHasChanged = false;
1407
1408 if (std::string(BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION].text) != std::to_string(MCVersion))
1409 {
1410 IUSaveText(&BasicMountInfoT[MOTOR_CONTROL_FIRMWARE_VERSION], std::to_string(MCVersion).c_str());
1411 BasicMountInfoHasChanged = true;
1412 }
1413 if (std::string(BasicMountInfoT[MOUNT_CODE].text) != std::to_string(MountCode))
1414 {
1415 IUSaveText(&BasicMountInfoT[MOUNT_CODE], std::to_string(MountCode).c_str());
1417 BasicMountInfoHasChanged = true;
1418 }
1419 if (std::string(BasicMountInfoT[IS_DC_MOTOR].text) != std::to_string(IsDCMotor))
1420 {
1421 IUSaveText(&BasicMountInfoT[IS_DC_MOTOR], std::to_string(IsDCMotor).c_str());
1422 BasicMountInfoHasChanged = true;
1423 }
1424 if (BasicMountInfoHasChanged && InformClient)
1425 IDSetText(&BasicMountInfoTP, nullptr);
1426
1427 IUSaveText(&BasicMountInfoT[MOUNT_NAME], mountTypeToString(MountCode));
1428
1429 bool AxisOneInfoHasChanged = false;
1430
1431 if (AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[0])
1432 {
1433 AxisOneInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[0];
1434 AxisOneInfoHasChanged = true;
1435 }
1436 if (AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[0])
1437 {
1438 AxisOneInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[0];
1439 AxisOneInfoHasChanged = true;
1440 }
1441 if (AxisOneInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[0])
1442 {
1443 AxisOneInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[0];
1444 AxisOneInfoHasChanged = true;
1445 }
1446 if (AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[0])
1447 {
1448 AxisOneInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[0];
1449 AxisOneInfoHasChanged = true;
1450 }
1451 if (AxisOneInfoHasChanged && InformClient)
1452 IDSetNumber(&AxisOneInfoNP, nullptr);
1453
1454 bool AxisOneStateHasChanged = false;
1455 if (AxisOneStateS[FULL_STOP].s != (AxesStatus[0].FullStop ? ISS_ON : ISS_OFF))
1456 {
1457 AxisOneStateS[FULL_STOP].s = AxesStatus[0].FullStop ? ISS_ON : ISS_OFF;
1458 AxisOneStateHasChanged = true;
1459 }
1460 if (AxisOneStateS[SLEWING].s != (AxesStatus[0].Slewing ? ISS_ON : ISS_OFF))
1461 {
1462 AxisOneStateS[SLEWING].s = AxesStatus[0].Slewing ? ISS_ON : ISS_OFF;
1463 AxisOneStateHasChanged = true;
1464 }
1465 if (AxisOneStateS[SLEWING_TO].s != (AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF))
1466 {
1467 AxisOneStateS[SLEWING_TO].s = AxesStatus[0].SlewingTo ? ISS_ON : ISS_OFF;
1468 AxisOneStateHasChanged = true;
1469 }
1470 if (AxisOneStateS[SLEWING_FORWARD].s != (AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF))
1471 {
1472 AxisOneStateS[SLEWING_FORWARD].s = AxesStatus[0].SlewingForward ? ISS_ON : ISS_OFF;
1473 AxisOneStateHasChanged = true;
1474 }
1475 if (AxisOneStateS[HIGH_SPEED].s != (AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF))
1476 {
1477 AxisOneStateS[HIGH_SPEED].s = AxesStatus[0].HighSpeed ? ISS_ON : ISS_OFF;
1478 AxisOneStateHasChanged = true;
1479 }
1480 if (AxisOneStateS[NOT_INITIALISED].s != (AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF))
1481 {
1482 AxisOneStateS[NOT_INITIALISED].s = AxesStatus[0].NotInitialized ? ISS_ON : ISS_OFF;
1483 AxisOneStateHasChanged = true;
1484 }
1485 if (AxisOneStateHasChanged && InformClient)
1486 IDSetSwitch(&AxisOneStateSP, nullptr);
1487
1488 bool AxisTwoInfoHasChanged = false;
1489 if (AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value != MicrostepsPerRevolution[1])
1490 {
1491 AxisTwoInfoN[MICROSTEPS_PER_REVOLUTION].value = MicrostepsPerRevolution[1];
1492 AxisTwoInfoHasChanged = true;
1493 }
1494 if (AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value != StepperClockFrequency[1])
1495 {
1496 AxisTwoInfoN[STEPPER_CLOCK_FREQUENCY].value = StepperClockFrequency[1];
1497 AxisTwoInfoHasChanged = true;
1498 }
1499 if (AxisTwoInfoN[HIGH_SPEED_RATIO].value != HighSpeedRatio[1])
1500 {
1501 AxisTwoInfoN[HIGH_SPEED_RATIO].value = HighSpeedRatio[1];
1502 AxisTwoInfoHasChanged = true;
1503 }
1504 if (AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value != MicrostepsPerWormRevolution[1])
1505 {
1506 AxisTwoInfoN[MICROSTEPS_PER_WORM_REVOLUTION].value = MicrostepsPerWormRevolution[1];
1507 AxisTwoInfoHasChanged = true;
1508 }
1509 if (AxisTwoInfoHasChanged && InformClient)
1510 IDSetNumber(&AxisTwoInfoNP, nullptr);
1511
1512 bool AxisTwoStateHasChanged = false;
1513 if (AxisTwoStateS[FULL_STOP].s != (AxesStatus[1].FullStop ? ISS_ON : ISS_OFF))
1514 {
1515 AxisTwoStateS[FULL_STOP].s = AxesStatus[1].FullStop ? ISS_ON : ISS_OFF;
1516 AxisTwoStateHasChanged = true;
1517 }
1518 if (AxisTwoStateS[SLEWING].s != (AxesStatus[1].Slewing ? ISS_ON : ISS_OFF))
1519 {
1520 AxisTwoStateS[SLEWING].s = AxesStatus[1].Slewing ? ISS_ON : ISS_OFF;
1521 AxisTwoStateHasChanged = true;
1522 }
1523 if (AxisTwoStateS[SLEWING_TO].s != (AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF))
1524 {
1525 AxisTwoStateS[SLEWING_TO].s = AxesStatus[1].SlewingTo ? ISS_ON : ISS_OFF;
1526 AxisTwoStateHasChanged = true;
1527 }
1528 if (AxisTwoStateS[SLEWING_FORWARD].s != (AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF))
1529 {
1530 AxisTwoStateS[SLEWING_FORWARD].s = AxesStatus[1].SlewingForward ? ISS_ON : ISS_OFF;
1531 AxisTwoStateHasChanged = true;
1532 }
1533 if (AxisTwoStateS[HIGH_SPEED].s != (AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF))
1534 {
1535 AxisTwoStateS[HIGH_SPEED].s = AxesStatus[1].HighSpeed ? ISS_ON : ISS_OFF;
1536 AxisTwoStateHasChanged = true;
1537 }
1538 if (AxisTwoStateS[NOT_INITIALISED].s != (AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF))
1539 {
1540 AxisTwoStateS[NOT_INITIALISED].s = AxesStatus[1].NotInitialized ? ISS_ON : ISS_OFF;
1541 AxisTwoStateHasChanged = true;
1542 }
1543 if (AxisTwoStateHasChanged && InformClient)
1544 IDSetSwitch(&AxisTwoStateSP, nullptr);
1545
1546 bool AxisOneEncoderValuesHasChanged = false;
1547 if ((AxisOneEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS1]) ||
1548 (AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1]))
1549 {
1550 AxisOneEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS1];
1551 AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS1] / 3600.0;
1552 AxisOneEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS1] - ZeroPositionEncoders[AXIS1];
1553 AxisOneEncoderValuesN[DEGREES_FROM_INITIAL].value =
1555 AxisOneEncoderValuesHasChanged = true;
1556 }
1557 if (AxisOneEncoderValuesHasChanged && InformClient)
1558 IDSetNumber(&AxisOneEncoderValuesNP, nullptr);
1559
1560 bool AxisTwoEncoderValuesHasChanged = false;
1561 if ((AxisTwoEncoderValuesN[RAW_MICROSTEPS].value != CurrentEncoders[AXIS2]) ||
1562 (AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value != CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2]))
1563 {
1564 AxisTwoEncoderValuesN[RAW_MICROSTEPS].value = CurrentEncoders[AXIS2];
1565 AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC].value = MicrostepsPerDegree[AXIS2] / 3600.0;
1566 AxisTwoEncoderValuesN[OFFSET_FROM_INITIAL].value = CurrentEncoders[AXIS2] - ZeroPositionEncoders[AXIS2];
1567 AxisTwoEncoderValuesN[DEGREES_FROM_INITIAL].value =
1569 AxisTwoEncoderValuesHasChanged = true;
1570 }
1571 if (AxisTwoEncoderValuesHasChanged && InformClient)
1572 IDSetNumber(&AxisTwoEncoderValuesNP, nullptr);
1573}
1574
1584
1589{
1590 // Zero azimuth looking north/south (depending on hemisphere)
1593 return true;
1594}
1595
1599void SkywatcherAPIMount::resetTracking()
1600{
1601 m_TrackingRateTimer.restart();
1602 GuideDeltaAlt = 0;
1603 GuideDeltaAz = 0;
1604 m_Controllers[AXIS_AZ].reset(new PID(std::max(0.001, getPollingPeriod() / 1000.), 1000, -1000,
1605 Axis1PIDNP[Propotional].getValue(),
1606 Axis1PIDNP[Derivative].getValue(),
1607 Axis1PIDNP[Integral].getValue()));
1608 m_Controllers[AXIS_AZ]->setIntegratorLimits(-1000, 1000);
1609 m_Controllers[AXIS_ALT].reset(new PID(std::max(0.001, getPollingPeriod() / 1000.), 1000, -1000,
1610 Axis2PIDNP[Propotional].getValue(),
1611 Axis2PIDNP[Derivative].getValue(),
1612 Axis2PIDNP[Integral].getValue()));
1613 m_Controllers[AXIS_ALT]->setIntegratorLimits(-1000, 1000);
1614 ResetGuidePulses();
1615}
1616
1620bool SkywatcherAPIMount::trackByRate(AXISID axis, double rate)
1621{
1622 if (std::abs(rate) > 0 && rate == m_LastTrackRate[axis])
1623 return true;
1624
1625 m_LastTrackRate[axis] = rate;
1626
1627 // If we are already stopped and rate is zero, we immediately return
1628 if (AxesStatus[axis].FullStop && rate == 0)
1629 return true;
1630 // If rate is zero, or direction changed then we should stop.
1631 else if (!AxesStatus[axis].FullStop && (rate == 0 || (AxesStatus[axis].SlewingForward && rate < 0)
1632 || (!AxesStatus[axis].SlewingForward && rate > 0)))
1633 {
1634 SlowStop(axis);
1635 LOGF_DEBUG("Tracking -> %s direction change.", (axis == AXIS1 ? "Axis 1" : "Axis 2"));
1636 return true;
1637 }
1638
1639 char Direction = rate > 0 ? '0' : '1';
1640 auto stepsPerSecond = static_cast<uint32_t>(std::abs(rate * (axis == AXIS1 ?
1641 AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC].value : AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC].value)));
1642 auto clockRate = (StepperClockFrequency[axis] / std::max(1u, stepsPerSecond));
1643
1644 if (AxesStatus[axis].FullStop)
1645 {
1646 LOGF_DEBUG("Tracking -> %s restart.", (axis == AXIS1 ? "Axis 1" : "Axis 2"));
1647 SetAxisMotionMode(axis, '1', Direction);
1648 SetClockTicksPerMicrostep(axis, clockRate);
1649 StartAxisMotion(axis);
1650 }
1651 else
1652 {
1653 SetClockTicksPerMicrostep(axis, clockRate);
1654 }
1655
1656 return true;
1657}
1658
1662bool SkywatcherAPIMount::trackUsingPID()
1663{
1664 // Continue or start tracking
1665 // Calculate where the mount needs to be in POLLMS time
1666 // TODO may need to make this longer to get a meaningful result
1667 //double JulianOffset = (getCurrentPollingPeriod() / 1000) / (24.0 * 60 * 60);
1670
1671 // We modify the SkyTrackingTarget for non-sidereal objects (Moon or Sun)
1672 // The Moon and Sun appear to move eastward (increasing RA) relative to the stars
1673 // because their westward motion due to Earth's rotation is slower than the sidereal rate.
1674 if (TrackModeSP[TRACK_LUNAR].getState() == ISS_ON)
1675 {
1676 // TRACKRATE_LUNAR: how many arcsecs/sec the Moon moves westward (apparent motion)
1677 // TRACKRATE_SIDEREAL: how many arcsecs/sec the stars move westward (apparent motion)
1678 // Since the Moon moves slower westward, it effectively moves eastward relative to stars
1679 double dRA = (TRACKRATE_SIDEREAL - TRACKRATE_LUNAR) * m_TrackingRateTimer.elapsed() / 1000.0;
1680 m_SkyTrackingTarget.rightascension += dRA / (3600.0 * 15.0);
1681 m_TrackingRateTimer.restart();
1682 }
1683 else if (TrackModeSP[TRACK_SOLAR].getState() == ISS_ON)
1684 {
1685 // Similar logic: Sun moves slower westward than stars, so it moves eastward relative to stars
1686 double dRA = (TRACKRATE_SIDEREAL - TRACKRATE_SOLAR) * m_TrackingRateTimer.elapsed() / 1000.0;
1687 m_SkyTrackingTarget.rightascension += dRA / (3600.0 * 15.0);
1688 m_TrackingRateTimer.restart();
1689 }
1690
1691 auto ra = m_SkyTrackingTarget.rightascension + AxisOffsetNP[RAOffset].getValue() / 15.0;
1692 auto de = m_SkyTrackingTarget.declination + AxisOffsetNP[DEOffset].getValue();
1693 auto JDOffset = AxisOffsetNP[JulianOffset].getValue() / 86400.0;
1694
1695 if (TransformCelestialToTelescopeJD(ra, de, ln_get_julian_from_sys() + JDOffset, TDV))
1696 {
1697 DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "TDV x %lf y %lf z %lf", TDV.x, TDV.y, TDV.z);
1699 }
1700 else
1701 {
1702 INDI::IEquatorialCoordinates EquatorialCoordinates { ra, de };
1703 INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys() + JDOffset, &AltAz);
1704 }
1705
1707 "New Tracking Target AZ %lf° (%ld microsteps) AL %lf° (%ld microsteps) ",
1708 AltAz.azimuth,
1710 AltAz.altitude,
1711 DegreesToMicrosteps(AXIS2, AltAz.altitude));
1712
1713 // Calculate the auto-guiding delta degrees
1714 double DeltaAlt = 0;
1715 double DeltaAz = 0;
1716
1717 getGuidePulses(DeltaAz, DeltaAlt);
1718
1719 long SetPoint[2] = {0, 0}, Measurement[2] = {0, 0}, Error[2] = {0, 0};
1720 double TrackingRate[2] = {0, 0};
1721
1722 SetPoint[AXIS1] = DegreesToMicrosteps(AXIS1, AltAz.azimuth + GuideDeltaAz);
1723 Measurement[AXIS1] = CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1];
1724
1725 SetPoint[AXIS2] = DegreesToMicrosteps(AXIS2, AltAz.altitude + GuideDeltaAlt);
1726 Measurement[AXIS2] = CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2];
1727
1728 // Going the long way round - send it the other way
1729 while (SetPoint[AXIS1] > MicrostepsPerRevolution[AXIS1] / 2)
1730 SetPoint[AXIS1] -= MicrostepsPerRevolution[AXIS1];
1731
1732 while (SetPoint[AXIS2] > MicrostepsPerRevolution[AXIS2] / 2)
1733 SetPoint[AXIS2] -= MicrostepsPerRevolution[AXIS2];
1734
1735 Error[AXIS1] = SetPoint[AXIS1] - Measurement[AXIS1];
1736 Error[AXIS2] = SetPoint[AXIS2] - Measurement[AXIS2];
1737
1738 auto Axis1CustomClockRate = Axis1TrackRateNP[TrackClockRate].getValue();
1739
1740 if (!AxesStatus[AXIS1].FullStop && (
1741 (Axis1CustomClockRate == 0 && ((AxesStatus[AXIS1].SlewingForward && (Error[AXIS1] < -AxisDeadZoneNP[AXIS1].getValue())) ||
1742 (!AxesStatus[AXIS1].SlewingForward && (Error[AXIS1] > AxisDeadZoneNP[AXIS1].getValue())))) ||
1743 (Axis1CustomClockRate > 0 && Axis1TrackRateNP[TrackDirection].getValue() != m_LastCustomDirection[AXIS1])))
1744 {
1745 m_LastCustomDirection[AXIS1] = Axis1TrackRateNP[TrackDirection].getValue();
1746 // Direction change whilst axis running
1747 // Abandon tracking for this clock tick
1748 LOG_DEBUG("Tracking -> AXIS1 direction change.");
1749 LOGF_DEBUG("AXIS1 Setpoint %d Measurement %d Error %d Rate %f",
1750 SetPoint[AXIS1],
1751 Measurement[AXIS1],
1752 Error[AXIS1],
1753 TrackingRate[AXIS1]);
1754 SlowStop(AXIS1);
1755 }
1756 else
1757 {
1758 TrackingRate[AXIS1] = m_Controllers[AXIS1]->calculate(SetPoint[AXIS1], Measurement[AXIS1]);
1759 char Direction = TrackingRate[AXIS1] > 0 ? '0' : '1';
1760 TrackingRate[AXIS1] = std::fabs(TrackingRate[AXIS1]);
1761 if (TrackingRate[AXIS1] != 0)
1762 {
1763 auto clockRate = (StepperClockFrequency[AXIS1] / TrackingRate[AXIS1]) * (AxisClockNP[AXIS1].getValue() / 100.0);
1764
1765 if (Axis1CustomClockRate > 0)
1766 {
1767 clockRate = Axis1CustomClockRate;
1768 Direction = Axis1TrackRateNP[TrackDirection].getValue() == 0 ? '0' : '1';
1769 }
1770
1771 LOGF_DEBUG("AXIS1 Setpoint %d Measurement %d Error %d Rate %f Freq %f Dir %s",
1772 SetPoint[AXIS1],
1773 Measurement[AXIS1],
1774 Error[AXIS1],
1775 TrackingRate[AXIS1],
1776 clockRate,
1777 Direction == '0' ? "Forward" : "Backward");
1778#ifdef DEBUG_PID
1779 LOGF_DEBUG("Tracking AZ P: %f I: %f D: %f",
1780 m_Controllers[AXIS1]->proportionalTerm(),
1781 m_Controllers[AXIS1]->integralTerm(),
1782 m_Controllers[AXIS1]->derivativeTerm());
1783#endif
1784
1785 SetClockTicksPerMicrostep(AXIS1, clockRate);
1786 if (AxesStatus[AXIS1].FullStop)
1787 {
1788 LOG_DEBUG("Tracking -> AXIS1 restart.");
1791 }
1792 }
1793 }
1794
1795
1796 auto Axis2CustomClockRate = Axis2TrackRateNP[TrackClockRate].getValue();
1797
1798 if (!AxesStatus[AXIS2].FullStop && (
1799 (Axis2CustomClockRate == 0 && ((AxesStatus[AXIS2].SlewingForward && (Error[AXIS2] < -AxisDeadZoneNP[AXIS2].getValue())) ||
1800 (!AxesStatus[AXIS2].SlewingForward && (Error[AXIS2] > AxisDeadZoneNP[AXIS2].getValue())))) ||
1801 (Axis2CustomClockRate > 0 && Axis2TrackRateNP[TrackDirection].getValue() != m_LastCustomDirection[AXIS2])))
1802 {
1803 m_LastCustomDirection[AXIS2] = Axis2TrackRateNP[TrackDirection].getValue();
1804
1805 LOG_DEBUG("Tracking -> AXIS2 direction change.");
1806 LOGF_DEBUG("AXIS2 Setpoint %d Measurement %d Error %d Rate %f",
1807 SetPoint[AXIS2],
1808 Measurement[AXIS2],
1809 Error[AXIS2],
1810 TrackingRate[AXIS2]);
1811 SlowStop(AXIS2);
1812 }
1813 else
1814 {
1815 TrackingRate[AXIS2] = m_Controllers[AXIS2]->calculate(SetPoint[AXIS2], Measurement[AXIS2]);
1816 char Direction = TrackingRate[AXIS2] > 0 ? '0' : '1';
1817 TrackingRate[AXIS2] = std::fabs(TrackingRate[AXIS2]);
1818 if (TrackingRate[AXIS2] != 0)
1819 {
1820 auto clockRate = StepperClockFrequency[AXIS2] / TrackingRate[AXIS2] * (AxisClockNP[AXIS2].getValue() / 100.0);
1821
1822 if (Axis2CustomClockRate > 0)
1823 {
1824 clockRate = Axis2CustomClockRate;
1825 Direction = Axis2TrackRateNP[TrackDirection].getValue() == 0 ? '0' : '1';
1826 }
1827
1828 LOGF_DEBUG("AXIS2 Setpoint %d Measurement %d Error %d Rate %f Freq %f Dir %s",
1829 SetPoint[AXIS2],
1830 Measurement[AXIS2],
1831 Error[AXIS2],
1832 TrackingRate[AXIS2],
1833 clockRate,
1834 Error[AXIS2] > 0 ? "Forward" : "Backward");
1835#ifdef DEBUG_PID
1836 LOGF_DEBUG("Tracking AZ P: %f I: %f D: %f",
1837 m_Controllers[AXIS2]->proportionalTerm(),
1838 m_Controllers[AXIS2]->integralTerm(),
1839 m_Controllers[AXIS2]->derivativeTerm());
1840#endif
1841
1842 SetClockTicksPerMicrostep(AXIS2, clockRate);
1843 if (AxesStatus[AXIS2].FullStop)
1844 {
1845 LOG_DEBUG("Tracking -> AXIS2 restart.");
1848 }
1849 }
1850 }
1851
1852 return true;
1853}
1854
1858bool SkywatcherAPIMount::trackUsingPredictiveRates()
1859{
1861 TelescopeDirectionVector futureTDV;
1863 INDI::IHorizontalCoordinates targetMountAxisCoordinates { 0, 0 };
1864 INDI::IHorizontalCoordinates pastMountAxisCoordinates { 0, 0 };
1865 INDI::IHorizontalCoordinates futureMountAxisCoordinates { 0, 0 };
1866 // time step for tracking rate estimation in seconds
1867 double timeStep { 5.0 };
1868 // The same in days
1869 double JDoffset { timeStep / (60 * 60 * 24) } ;
1870
1871 // We modify the SkyTrackingTarget for non-sidereal objects (Moon or Sun)
1872 // The Moon and Sun appear to move eastward (increasing RA) relative to the stars
1873 // because their westward motion due to Earth's rotation is slower than the sidereal rate.
1874 if (TrackModeSP[TRACK_LUNAR].getState() == ISS_ON)
1875 {
1876 // TRACKRATE_LUNAR: how many arcsecs/sec the Moon moves westward (apparent motion)
1877 // TRACKRATE_SIDEREAL: how many arcsecs/sec the stars move westward (apparent motion)
1878 // Since the Moon moves slower westward, it effectively moves eastward relative to stars
1879 double dRA = (TRACKRATE_SIDEREAL - TRACKRATE_LUNAR) * m_TrackingRateTimer.elapsed() / 1000.0;
1880 m_SkyTrackingTarget.rightascension += (dRA / 3600.0) / 15.0;
1881 m_TrackingRateTimer.restart();
1882 }
1883 else if (TrackModeSP[TRACK_SOLAR].getState() == ISS_ON)
1884 {
1885 // Similar logic: Sun moves slower westward than stars, so it moves eastward relative to stars
1886 double dRA = (TRACKRATE_SIDEREAL - TRACKRATE_SOLAR) * m_TrackingRateTimer.elapsed() / 1000.0;
1887 m_SkyTrackingTarget.rightascension += (dRA / 3600.0) / 15.0;
1888 m_TrackingRateTimer.restart();
1889 }
1890
1891 // Start by transforming tracking target celestial coordinates to telescope coordinates.
1892 double JDnow {ln_get_julian_from_sys()};
1893 if (TransformCelestialToTelescopeJD(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination,
1894 JDnow, TDV))
1895 {
1896 // If mount is Alt-Az then that's all we need to do
1897 AltitudeAzimuthFromTelescopeDirectionVector(TDV, targetMountAxisCoordinates);
1898 TransformCelestialToTelescopeJD(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination,
1899 JDnow + JDoffset, futureTDV);
1900 AltitudeAzimuthFromTelescopeDirectionVector(futureTDV, futureMountAxisCoordinates);
1901 TransformCelestialToTelescopeJD(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination,
1902 JDnow - JDoffset, pastTDV);
1903 AltitudeAzimuthFromTelescopeDirectionVector(pastTDV, pastMountAxisCoordinates);
1904
1905 }
1906 // If transformation failed.
1907 else
1908 {
1909 INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
1910 EquatorialCoordinates.rightascension = m_SkyTrackingTarget.rightascension;
1911 EquatorialCoordinates.declination = m_SkyTrackingTarget.declination;
1912 INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, JDnow, &targetMountAxisCoordinates);
1913 INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, JDnow + JDoffset, &futureMountAxisCoordinates);
1914 INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, JDnow - JDoffset, &pastMountAxisCoordinates);
1915 }
1916
1917 double azGuideOffset = 0, altGuideOffset = 0;
1918 getGuidePulses(azGuideOffset, altGuideOffset);
1919
1920 // Now add the guiding offsets, if any.
1921 targetMountAxisCoordinates.azimuth += azGuideOffset;
1922 pastMountAxisCoordinates.azimuth += azGuideOffset;
1923 futureMountAxisCoordinates.azimuth += azGuideOffset;
1924
1925 targetMountAxisCoordinates.altitude += altGuideOffset;
1926 pastMountAxisCoordinates.altitude += altGuideOffset;
1927 futureMountAxisCoordinates.altitude += altGuideOffset;
1928
1929 // Calculate expected tracking rates
1930 double predRate[2] = {0, 0};
1931 // Central difference, error quadratic in timestep
1932 // Rates in deg/s
1933 predRate[AXIS_AZ] = range180(AzimuthToDegrees(futureMountAxisCoordinates.azimuth - pastMountAxisCoordinates.azimuth)) /
1934 timeStep / 2;
1935 predRate[AXIS_ALT] = (futureMountAxisCoordinates.altitude - pastMountAxisCoordinates.altitude) / timeStep / 2;
1936
1937 // Rates arcsec/s
1938 predRate[AXIS_AZ] = 3600 * predRate[AXIS_AZ];
1939 predRate[AXIS_ALT] = 3600 * predRate[AXIS_ALT];
1940
1941 LOGF_DEBUG("Predicted positions (AZ): %9.4f %9.4f (now, future, degs)",
1942 AzimuthToDegrees(targetMountAxisCoordinates.azimuth),
1943 AzimuthToDegrees(futureMountAxisCoordinates.azimuth)) ;
1944 LOGF_DEBUG("Predicted positions (AL): %9.4f %9.4f (now, future, degs)", targetMountAxisCoordinates.altitude,
1945 futureMountAxisCoordinates.altitude);
1946 LOGF_DEBUG("Predicted Rates (AZ, ALT): %9.4f %9.4f (arcsec/s)", predRate[AXIS_AZ], predRate[AXIS_ALT]);
1947
1948 // If we had guiding pulses active, mark them as complete
1949 if (GuideWENP.getState() == IPS_BUSY)
1951 if (GuideNSNP.getState() == IPS_BUSY)
1953
1954 // Next get current alt-az
1955 INDI::IHorizontalCoordinates currentAltAz { 0, 0 };
1956
1957 // Current Azimuth
1958 auto Axis1Steps = CurrentEncoders[AXIS1] - AxisOffsetNP[AZSteps].getValue() - ZeroPositionEncoders[AXIS1];
1959 currentAltAz.azimuth = DegreesToAzimuth(MicrostepsToDegrees(AXIS1, Axis1Steps));
1960 // Current Altitude
1961 auto Axis2Steps = CurrentEncoders[AXIS2] - AxisOffsetNP[ALSteps].getValue() - ZeroPositionEncoders[AXIS2];
1962 currentAltAz.altitude = MicrostepsToDegrees(AXIS2, Axis2Steps);
1963
1964 // Offset between target and current horizontal coordinates in arcsecs
1965 double offsetAngle[2] = {0, 0};
1966 offsetAngle[AXIS_AZ] = range180(targetMountAxisCoordinates.azimuth - currentAltAz.azimuth) * 3600;
1967 offsetAngle[AXIS_ALT] = (targetMountAxisCoordinates.altitude - currentAltAz.altitude) * 3600;
1968
1969 int32_t targetSteps[2] = {0, 0};
1970 int32_t offsetSteps[2] = {0, 0};
1971 double trackRates[2] = {0, 0};
1972
1973 // Convert offsets from arcsecs to steps
1974 offsetSteps[AXIS_AZ] = offsetAngle[AXIS_AZ] * AxisOneEncoderValuesN[MICROSTEPS_PER_ARCSEC].value;
1975 offsetSteps[AXIS_ALT] = offsetAngle[AXIS_ALT] * AxisTwoEncoderValuesN[MICROSTEPS_PER_ARCSEC].value;
1976
1978 {
1979 m_OffsetSwitchSettle[AXIS_AZ] = 0;
1980 m_LastOffset[AXIS_AZ] = offsetSteps[AXIS_AZ];
1981 targetSteps[AXIS_AZ] = DegreesToMicrosteps(AXIS1, AzimuthToDegrees(targetMountAxisCoordinates.azimuth));
1982 // Track rate: predicted + PID controlled correction based on tracking error: offsetSteps
1983 trackRates[AXIS_AZ] = predRate[AXIS_AZ] + m_Controllers[AXIS_AZ]->calculate(0, -offsetAngle[AXIS_AZ]);
1984 //
1985 // make sure we never change direction of the trackRate - reduce to predRate * MIN_TRACK_RATE_FACTOR in same direction
1986 // since tracking direction change can lead to poor tracking
1987 //
1988 double minTrackRate = predRate[AXIS_AZ] * MIN_TRACK_RATE_FACTOR;
1989 if(trackRates[AXIS_AZ] * predRate[AXIS_AZ] < 0 || std::abs(trackRates[AXIS_AZ]) < std::abs(minTrackRate))
1990 trackRates[AXIS_AZ] = minTrackRate;
1991
1992 LOGF_DEBUG("Tracking AZ Now: %8.f Target: %8d Offset: %8d Rate: %8.2f", Axis1Steps, targetSteps[AXIS_AZ],
1993 offsetSteps[AXIS_AZ], trackRates[AXIS_AZ]);
1994#ifdef DEBUG_PID
1995 LOGF_DEBUG("Tracking AZ P: %8.1f I: %8.1f D: %8.1f O: %8.1f",
1996 m_Controllers[AXIS_AZ]->proportionalTerm(),
1997 m_Controllers[AXIS_AZ]->integralTerm(),
1998 m_Controllers[AXIS_AZ]->derivativeTerm(),
1999 trackRates[AXIS_AZ] - predRate[AXIS_AZ]);
2000#endif
2001
2002 // Set the tracking rate
2003 trackByRate(AXIS1, trackRates[AXIS_AZ]);
2004 }
2005
2007 {
2008 m_OffsetSwitchSettle[AXIS_ALT] = 0;
2009 m_LastOffset[AXIS_ALT] = offsetAngle[AXIS_ALT];
2010 targetSteps[AXIS_ALT] = DegreesToMicrosteps(AXIS2, targetMountAxisCoordinates.altitude);
2011 // Track rate: predicted + PID controlled correction based on tracking error: offsetSteps
2012 trackRates[AXIS_ALT] = predRate[AXIS_ALT] + m_Controllers[AXIS_ALT]->calculate(0, -offsetAngle[AXIS_ALT]);
2013
2014 //
2015 // make sure we never change direction of the trackRate - reduce to predRate * MIN_TRACK_RATE_FACTOR in same direction
2016 // since tracking direction change can lead to poor tracking
2017 //
2018 double minTrackRate = predRate[AXIS_ALT] * MIN_TRACK_RATE_FACTOR;
2019 if(trackRates[AXIS_ALT] * predRate[AXIS_ALT] < 0 || std::abs(trackRates[AXIS_ALT]) < std::abs(minTrackRate))
2020 trackRates[AXIS_ALT] = minTrackRate;
2021
2022 LOGF_DEBUG("Tracking AL Now: %8.f Target: %8d Offset: %8d Rate: %8.2f", Axis2Steps,
2023 targetSteps[AXIS_ALT],
2024 offsetSteps[AXIS_ALT], trackRates[AXIS_ALT]);
2025#ifdef DEBUG_PID
2026 LOGF_DEBUG("Tracking AL P: %8.1f I: %8.1f D: %8.1f O: %8.1f",
2027 m_Controllers[AXIS_ALT]->proportionalTerm(),
2028 m_Controllers[AXIS_ALT]->integralTerm(),
2029 m_Controllers[AXIS_ALT]->derivativeTerm(),
2030 trackRates[AXIS_ALT] - predRate[AXIS_ALT]);
2031#endif
2032 trackByRate(AXIS2, trackRates[AXIS_ALT]);
2033 }
2034
2035 return true;
2036}
2037
2041double SkywatcherAPIMount::AzimuthToDegrees(double degree)
2042{
2043 if (isNorthHemisphere())
2044 return range360(degree);
2045 else
2046 return range360(degree + 180);
2047}
2048
2052double SkywatcherAPIMount::DegreesToAzimuth(double degree)
2053{
2054 if (isNorthHemisphere())
2055 return range360(degree);
2056 else
2057 return range360(degree + 180);
2058}
2059
2060void SkywatcherAPIMount::getGuidePulses(double &az, double &alt)
2061{
2062 double DeltaAz = 0, DeltaAlt = 0;
2063
2064 for (auto Iter = GuidingPulses.begin(); Iter != GuidingPulses.end(); )
2065 {
2066 // We treat the guide calibration specially
2067 if (Iter->OriginalDuration == 1000)
2068 {
2069 DeltaAlt += Iter->DeltaAlt;
2070 DeltaAz += Iter->DeltaAz;
2071 }
2072 else
2073 {
2074 DeltaAlt += Iter->DeltaAlt / 2;
2075 DeltaAz += Iter->DeltaAz / 2;
2076 }
2077 Iter->Duration -= getCurrentPollingPeriod();
2078
2079 if (Iter->Duration < static_cast<int>(getCurrentPollingPeriod()))
2080 {
2081 Iter = GuidingPulses.erase(Iter);
2082 if (Iter == GuidingPulses.end())
2083 {
2084 break;
2085 }
2086 continue;
2087 }
2088 ++Iter;
2089 }
2090
2091 az = DeltaAlt;
2092 alt = DeltaAz;
2093}
void setDefaultHost(const char *addressHost)
void setLANSearchEnabled(bool enabled)
void setConnectionType(int type)
TODO should be renamed to setDefaultConnectionType.
void setDefaultPort(uint32_t addressPort)
void ProcessAlignmentNumberProperties(Telescope *pTelescope, const char *name, double values[], char *names[], int n)
Call this function from within the ISNewNumber processing path. The function will handle any alignmen...
void ProcessAlignmentSwitchProperties(Telescope *pTelescope, const char *name, ISState *states, char *names[], int n)
Call this function from within the ISNewSwitch processing path. The function will handle any alignmen...
void InitAlignmentProperties(Telescope *pTelescope)
Initialize alignment subsystem properties. It is recommended to call this function within initPropert...
void ProcessAlignmentBLOBProperties(Telescope *pTelescope, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Call this function from within the ISNewBlob processing path. The function will handle any alignment ...
void SaveAlignmentConfigProperties(FILE *fp)
Call this function to save persistent alignment related properties. This function should be called fr...
void ProcessAlignmentTextProperties(Telescope *pTelescope, const char *name, char *texts[], char *names[], int n)
Call this function from within the ISNewText processing path. The function will handle any alignment ...
bool CheckForDuplicateSyncPoint(const AlignmentDatabaseEntry &CandidateEntry, double Tolerance=0.1) const
Check if a entry already exists in the database.
AlignmentDatabaseType & GetAlignmentDatabase()
Get a reference to the in memory database.
void UpdateLocation(double latitude, double longitude, double elevation)
Call this function from within the updateLocation processing path.
void UpdateSize()
Call this function when the number of entries in the database changes.
bool TransformTelescopeToCelestialJD(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianDate)
TransformTelescopeToCelestialJD Transforms Mount Coords to Celestial (Sky) Coordinates.
bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector)
TransformCelestialToTelescopeJD Transforms Celestial (Sky) Coords to Mount Coordinates.
bool Initialise(InMemoryDatabase *pInMemoryDatabase)
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
MountAlignment_t GetApproximateMountAlignment()
Get the approximate alignment of the mount.
void SetApproximateMountAlignmentFromMountType(MountType_t Type)
Call this function to set the ApproximateMountAlignment property of the current Math Plugin....
const TelescopeDirectionVector TelescopeDirectionVectorFromSphericalCoordinate(const double AzimuthAngle, AzimuthAngleDirection_t AzimuthAngleDirection, const double PolarAngle, PolarAngleDirection_t PolarAngleDirection)
Calculates a telescope direction vector from the supplied spherical coordinate information.
void SphericalCoordinateFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, double &AzimuthAngle, AzimuthAngleDirection_t AzimuthAngleDirection, double &PolarAngle, PolarAngleDirection_t PolarAngleDirection)
Calculates a spherical coordinate from the supplied telescope direction vector.
const TelescopeDirectionVector TelescopeDirectionVectorFromAltitudeAzimuth(INDI::IHorizontalCoordinates HorizontalCoordinates)
Calculates a normalised direction vector from the supplied altitude and azimuth.
void AltitudeAzimuthFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, INDI::IHorizontalCoordinates &HorizontalCoordinates)
Calculates an altitude and azimuth from the supplied normalised direction vector and declination.
bool isConnected() const
INDI::PropertySwitch getSwitch(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void addConfigurationControl()
Add Configuration control to the driver.
void setActiveConnection(Connection::Interface *existingConnection)
setActiveConnection Switch the active connection to the passed connection plugin
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
uint32_t getDriverInterface() const
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.
Connection::Interface * getActiveConnection()
void addDebugControl()
Add Debug control to the driver.
int64_t elapsed() const
Returns the number of milliseconds since this ElapsedTimer was last started.
int64_t restart()
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
INDI::PropertyNumber GuideNSNP
virtual void GuideComplete(INDI_EQ_AXIS axis)
Call GuideComplete once the guiding pulse is complete.
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyNumber GuideWENP
void initProperties(const char *groupName)
Initialize guider properties. It is recommended to call this function within initProperties() of your...
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void setName(const char *name)
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool isSwitchOn(const std::string &name) const
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
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.
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 GetAxis1Park() const
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...
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
Connection::TCP * tcpConnection
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
IGeographicCoordinates m_Location
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
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.
double MicrostepsToRadians(AXISID Axis, long Microsteps)
Convert microsteps to angle in radians.
void TurnRAEncoder(bool on)
void SetSerialPort(int port)
Set the serail port to be usb for mount communication.
static constexpr double SIDEREALRATE
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 RadiansToMicrosteps(AXISID Axis, double AngleInRadians)
Convert angle in radians to microsteps.
bool SetEncoder(AXISID Axis, long Microsteps)
Set axis encoder to the specified value.
void TurnDEEncoder(bool on)
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 StartAxisMotion(AXISID Axis)
Start the axis slewing in the previously selected mode.
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.
bool SetAxisMotionMode(AXISID Axis, char Func, char Direction)
Set the motion mode per the specified axis.
long MicrostepsPerRevolution[2]
bool IsInMotion(AXISID Axis)
Check if an axis is moving.
long StepperClockFrequency[2]
bool toggleSnapPort(bool enabled)
Toggle the snap port on or off.
bool SetClockTicksPerMicrostep(AXISID Axis, long ClockTicksPerMicrostep)
Set the PIC internal divider variable which determines how many clock interrupts have to occur betwee...
INDI::Telescope * pChildTelescope
static const char * mountTypeToString(uint8_t type)
AXISSTATUS AxesStatus[2]
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.
INDI::PropertySwitch MovementNSSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertySwitch MountTypeSP
INDI::PropertySwitch MovementWESP
INDI::PropertySwitch CoordSP
double ra
double dec
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
@ AXIS_DE
@ AXIS_RA
@ AXIS_AZ
@ AXIS_ALT
INDI_DIR_WE
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_NORTH
void tty_set_generic_udp_format(int enabled)
Definition indicom.c:364
void tty_set_auto_reset_udp_session(int flag)
automatically reset UDP session on write
Definition indicom.c:354
double range180(double r)
range180 Limits an angle to be between -180 to +180 degrees
Definition indicom.c:1331
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition indicom.c:1321
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
Implementations for common driver routines.
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:490
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:509
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition indidevapi.c:275
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
ISwitch * IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
Find an ISwitch member in a vector switch property.
Definition indidevapi.c:285
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:376
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:416
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:398
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:453
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#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
#define TRACKRATE_SOLAR
Definition indimacros.h:45
#define TRACKRATE_SIDEREAL
Definition indimacros.h:39
#define TRACKRATE_LUNAR
Definition indimacros.h:48
#define getDeviceName
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:141
void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, IEquatorialCoordinates *position)
HorizontalToEquatorial Calculate Equatorial EOD Coordinates from horizontal coordinates.
Definition libastro.cpp:161
#define SLEWMODES
Definition paramount.cpp:68
#define SLEWMODES
bool NotInitialized
bool SlewingForward
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 normalised 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 switch descriptor.
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248