Instrument Neutral Distributed Interface INDI  2.0.2
synscandriver.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #include "synscandriver.h"
20 #include "libastro.h"
23 #include "indicom.h"
24 
25 #include <libnova/transform.h>
26 #include <libnova/precession.h>
27 // libnova specifies round() on old systems and it collides with the new gcc 5.x/6.x headers
28 #define HAVE_ROUND
29 #include <libnova/utility.h>
30 
31 #include <cmath>
32 #include <map>
33 #include <memory>
34 #include <termios.h>
35 #include <cstring>
36 #include <assert.h>
37 
38 constexpr uint16_t SynscanDriver::SIM_SLEW_RATE[];
39 
41 {
42  setVersion(2, 0);
43 
44  m_MountInfo.push_back("--");
45  m_MountInfo.push_back("--");
46  m_MountInfo.push_back("--");
47  m_MountInfo.push_back("--");
48  m_MountInfo.push_back("--");
49 }
50 
52 {
53  return "SynScan";
54 }
55 
57 {
59 
64 
65  // Slew Rates
66  strncpy(SlewRateS[0].label, "1x", MAXINDILABEL);
67  strncpy(SlewRateS[1].label, "8x", MAXINDILABEL);
68  strncpy(SlewRateS[2].label, "16x", MAXINDILABEL);
69  strncpy(SlewRateS[3].label, "32x", MAXINDILABEL);
70  strncpy(SlewRateS[4].label, "64x", MAXINDILABEL);
71  strncpy(SlewRateS[5].label, "128x", MAXINDILABEL);
72  strncpy(SlewRateS[6].label, "400x", MAXINDILABEL);
73  strncpy(SlewRateS[7].label, "600x", MAXINDILABEL);
74  strncpy(SlewRateS[8].label, "Max", MAXINDILABEL);
75  strncpy(SlewRateS[9].label, "Custom", MAXINDILABEL);
77  // Max is the default
78  SlewRateS[8].s = ISS_ON;
79 
83  IUFillText(&StatusT[MI_FW_VERSION], "MI_FW_VERSION", "Firmware", "-");
84  IUFillText(&StatusT[MI_MOUNT_MODEL], "MI_MOUNT_MODEL", "Model", "-");
85  IUFillText(&StatusT[MI_GOTO_STATUS], "MI_GOTO_STATUS", "Goto", "-");
86  IUFillText(&StatusT[MI_POINT_STATUS], "MI_POINT_STATUS", "Pointing", "-");
87  IUFillText(&StatusT[MI_TRACK_MODE], "MI_TRACK_MODE", "Tracking Mode", "-");
88  IUFillTextVector(&StatusTP, StatusT, 5, getDeviceName(), "MOUNT_STATUS",
89  "Status", MOUNT_TAB, IP_RO, 60, IPS_IDLE);
90 
94  IUFillNumber(&CustomSlewRateN[AXIS_RA], "AXIS1", "RA/AZ (arcsecs/s)", "%.2f", 0.05, 800, 10, 0);
95  IUFillNumber(&CustomSlewRateN[AXIS_DE], "AXIS2", "DE/AL (arcsecs/s)", "%.2f", 0.05, 800, 10, 0);
96  IUFillNumberVector(&CustomSlewRateNP, CustomSlewRateN, 2, getDeviceName(), "CUSTOM_SLEW_RATE", "Custom Slew", MOTION_TAB,
97  IP_RW, 60, IPS_IDLE);
98 
102  IUFillNumber(&GuideRateN[AXIS_RA], "GUIDE_RATE_WE", "W/E Rate", "%.2f", 0, 1, 0.1, 0.5);
103  IUFillNumber(&GuideRateN[AXIS_DE], "GUIDE_RATE_NS", "N/S Rate", "%.2f", 0, 1, 0.1, 0.5);
104  IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guiding Rate", GUIDE_TAB, IP_RW, 0,
105  IPS_IDLE);
106 
110  IUFillNumber(&HorizontalCoordsN[AXIS_AZ], "AZ", "Az D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
111  IUFillNumber(&HorizontalCoordsN[AXIS_ALT], "ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
112  IUFillNumberVector(&HorizontalCoordsNP, HorizontalCoordsN, 2, getDeviceName(), "HORIZONTAL_COORD",
113  "Horizontal Coord", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
114 
115  AddTrackMode("TRACK_ALTAZ", "Alt/Az");
116  AddTrackMode("TRACK_EQ", "Equatorial", true);
117  AddTrackMode("TRACK_PEC", "PEC Mode");
118 
119  IUFillSwitch(&GotoModeS[0], "ALTAZ", "Alt/Az", ISS_OFF);
120  IUFillSwitch(&GotoModeS[1], "RADEC", "Ra/Dec", ISS_ON);
121  IUFillSwitchVector(&GotoModeSP, GotoModeS, NARRAY(GotoModeS), getDeviceName(), "GOTOMODE", "Goto mode",
123 
125 
126  // Initialize guiding properties.
128 
129  addAuxControls();
130 
131  //GUIDE Set guider interface.
133 
134  return true;
135 }
136 
138 {
140 
141  if (isConnected())
142  {
143  setupParams();
144 
145  defineProperty(&HorizontalCoordsNP);
146  defineProperty(&StatusTP);
147  defineProperty(&CustomSlewRateNP);
150  defineProperty(&GuideRateNP);
151 
152  if (m_isAltAz)
153  {
154  defineProperty(&GotoModeSP);
155  }
156 
157  if (InitPark())
158  {
159  SetAxis1ParkDefault(359);
160  SetAxis2ParkDefault(m_isAltAz ? 0 : LocationN[LOCATION_LATITUDE].value);
161  }
162  else
163  {
164  SetAxis1Park(359);
165  SetAxis2Park(m_isAltAz ? 0 : LocationN[LOCATION_LATITUDE].value);
166  SetAxis1ParkDefault(359);
167  SetAxis2ParkDefault(m_isAltAz ? 0 : LocationN[LOCATION_LATITUDE].value);
168  }
169  }
170  else
171  {
172  deleteProperty(HorizontalCoordsNP.name);
173  deleteProperty(StatusTP.name);
174  deleteProperty(CustomSlewRateNP.name);
177  deleteProperty(GuideRateNP.name);
178  if (m_isAltAz)
179  {
180  deleteProperty(GotoModeSP.name);
181  }
182  }
183 
184  return true;
185 }
186 
187 void SynscanDriver::setupParams()
188 {
189  readFirmware();
190  //readModel();
191  readTracking();
192 
193  sendLocation();
194  sendTime();
195 }
196 
197 int SynscanDriver::hexStrToInteger(const std::string &res)
198 {
199  int result = 0;
200 
201  try
202  {
203  result = std::stoi(res, nullptr, 16);
204  }
205  catch (std::invalid_argument &)
206  {
207  LOGF_ERROR("Failed to parse %s to integer.", res.c_str());
208  }
209 
210  return result;
211 }
212 
214 {
215  char res[SYN_RES] = {0};
216  if (!echo())
217  return false;
218 
219  // We can only proceed if the mount is aligned.
220  if (!sendCommand("J", res))
221  return false;
222 
223  if (res[0] == 0)
224  {
225  LOG_ERROR("Mount is not aligned. Please align the mount first and connect again.");
226  return false;
227  }
228 
229  readModel();
230 
231  if (m_isAltAz)
232  {
234  }
235 
236  return true;
237 }
238 
239 bool SynscanDriver::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
240 {
241  if (dev && !strcmp(dev, getDeviceName()))
242  {
243  // Guide Rate
244  if (strcmp(name, "GUIDE_RATE") == 0)
245  {
246  IUUpdateNumber(&GuideRateNP, values, names, n);
247  GuideRateNP.s = IPS_OK;
248  IDSetNumber(&GuideRateNP, nullptr);
249  return true;
250  }
251 
252  // Custom Slew Rate
253  if (strcmp(name, CustomSlewRateNP.name) == 0)
254  {
255  if (TrackState == SCOPE_SLEWING)
256  {
257  LOG_ERROR("Cannot change rate while slewing.");
258  CustomSlewRateNP.s = IPS_ALERT;
259  IDSetNumber(&CustomSlewRateNP, nullptr);
260  return true;
261  }
262 
263  IUUpdateNumber(&CustomSlewRateNP, values, names, n);
264  CustomSlewRateNP.s = IPS_OK;
265  IDSetNumber(&CustomSlewRateNP, nullptr);
266  return true;
267  }
268 
269  // Horizonal Coords
270  if (!strcmp(name, HorizontalCoordsNP.name))
271  {
272  if (isParked())
273  {
274  LOG_WARN("Unpark mount before issuing GOTO commands.");
275  HorizontalCoordsNP.s = IPS_IDLE;
276  IDSetNumber(&HorizontalCoordsNP, nullptr);
277  return true;
278  }
279 
280  int nset = 0;
281  double newAlt = 0, newAz = 0;
282  for (int i = 0; i < n; i++)
283  {
284  INumber * horp = IUFindNumber(&HorizontalCoordsNP, names[i]);
285  if (horp == &HorizontalCoordsN[AXIS_AZ])
286  {
287  newAz = values[i];
288  nset += newAz >= 0. && newAz <= 360.0;
289  }
290  else if (horp == &HorizontalCoordsN[AXIS_ALT])
291  {
292  newAlt = values[i];
293  nset += newAlt >= -90. && newAlt <= 90.0;
294  }
295  }
296 
297  if (nset == 2 && GotoAzAlt(newAz, newAlt))
298  return true;
299 
300  HorizontalCoordsNP.s = IPS_ALERT;
301  IDSetNumber(&HorizontalCoordsNP, "Altitude or Azimuth missing or invalid.");
302  return false;
303  }
304 
305  // Guiding
306  if (strcmp(name, GuideNSNP.name) == 0 || strcmp(name, GuideWENP.name) == 0)
307  {
308  processGuiderProperties(name, values, names, n);
309  return true;
310  }
311  }
312 
313  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
314 }
315 
316 bool SynscanDriver::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
317 {
318  if (dev && !strcmp(dev, getDeviceName()))
319  {
320  auto svp = getSwitch(name);
321 
322  if (svp.isNameMatch(GotoModeSP.name))
323  {
324  svp.update(states, names, n);
325  auto sp = svp.findOnSwitch();
326 
327  assert(sp != nullptr);
328 
329  if (sp->isNameMatch(GotoModeS[0].name))
330  SetAltAzMode(true);
331  else
332  SetAltAzMode(false);
333  return true;
334  }
335 
336  }
337 
338  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
339 }
340 
341 bool SynscanDriver::echo()
342 {
343  char res[SYN_RES] = {0};
344  return sendCommand("Kx", res);
345 }
346 
347 bool SynscanDriver::readFirmware()
348 {
349  // Read the handset version
350  char res[SYN_RES] = {0};
351  if (sendCommand("V", res))
352  {
353  m_FirmwareVersion = static_cast<double>(hexStrToInteger(std::string(&res[0], 2)));
354  m_FirmwareVersion += static_cast<double>(hexStrToInteger(std::string(&res[2], 2))) / 100;
355  m_FirmwareVersion += static_cast<double>(hexStrToInteger(std::string(&res[4], 2))) / 10000;
356 
357  LOGF_INFO("Firmware version: %lf", m_FirmwareVersion);
358  m_MountInfo[MI_FW_VERSION] = std::to_string(m_FirmwareVersion);
359  IUSaveText(&StatusT[MI_FW_VERSION], m_MountInfo[MI_FW_VERSION].c_str());
360 
361  if (m_FirmwareVersion < 3.38 || (m_FirmwareVersion >= 4.0 && m_FirmwareVersion < 4.38))
362  {
363  LOGF_WARN("Firmware version is too old. Update Synscan firmware to %s",
364  m_FirmwareVersion < 3.38 ? "v3.38+" : "v4.38+");
365  return false;
366  }
367  else
368  return true;
369  }
370  else
371  LOG_WARN("Firmware version is too old. Update Synscan firmware to v4.38+");
372 
373  return false;
374 }
375 
376 bool SynscanDriver::readTracking()
377 {
378  // Read the handset version
379  char res[SYN_RES] = {0};
380  if (sendCommand("t", res))
381  {
382  // Are we tracking or not?
383  m_TrackingFlag = res[0];
384 
385  // Track mode?
386  if (((m_TrackingFlag - 1) != IUFindOnSwitchIndex(&TrackModeSP)) && (m_TrackingFlag))
387  {
389  TrackModeS[m_TrackingFlag - 1].s = ISS_ON;
390  IDSetSwitch(&TrackModeSP, nullptr);
391  }
392 
393  switch(res[0])
394  {
395  case 0:
396  m_MountInfo[MI_TRACK_MODE] = "Tracking off";
397  break;
398  case 1:
399  m_MountInfo[MI_TRACK_MODE] = "Alt/Az tracking";
400  break;
401  case 2:
402  m_MountInfo[MI_TRACK_MODE] = "EQ tracking";
403  break;
404  case 3:
405  m_MountInfo[MI_TRACK_MODE] = "PEC mode";
406  break;
407  }
408 
409  return true;
410  }
411 
412  return false;
413 }
414 
415 bool SynscanDriver::readModel()
416 {
417  // extended list of mounts
418  std::map<int, std::string> models =
419  {
420  {0, "EQ6 GOTO Series"},
421  {1, "HEQ5 GOTO Series"},
422  {2, "EQ5 GOTO Series"},
423  {3, "EQ3 GOTO Series"},
424  {4, "EQ8 GOTO Series"},
425  {5, "AZ-EQ6 GOTO Series"},
426  {6, "AZ-EQ5 GOTO Series"},
427  {160, "AllView GOTO Series"},
428  {161, "Virtuoso Alt/Az mount"},
429  {165, "AZ-GTi GOTO Series"}
430  };
431 
432  // Read the handset version
433  char res[SYN_RES] = {0};
434 
435  if (!sendCommand("m", res))
436  return false;
437 
438  m_MountModel = res[0];
439 
440  // 128 - 143 --> AZ Goto series
441  if (m_MountModel >= 128 && m_MountModel <= 143)
442  IUSaveText(&StatusT[MI_MOUNT_MODEL], "AZ GOTO Series");
443  // 144 - 159 --> DOB Goto series
444  else if (m_MountModel >= 144 && m_MountModel <= 159)
445  IUSaveText(&StatusT[MI_MOUNT_MODEL], "Dob GOTO Series");
446  else if (models.count(m_MountModel) > 0)
447  IUSaveText(&StatusT[MI_MOUNT_MODEL], models[m_MountModel].c_str());
448  else
449  IUSaveText(&StatusT[MI_MOUNT_MODEL], "Unknown model");
450 
451  m_isAltAz = m_MountModel > 4;
452 
453  LOGF_INFO("Driver is running in %s mode.", m_isAltAz ? "Alt-Az" : "Equatorial");
454  LOGF_INFO("Detected mount: %s. Mount must be aligned from the handcontroller before using the driver.",
455  StatusT[MI_MOUNT_MODEL].text);
456 
457  return true;
458 }
459 
461 {
462  if (isSimulation())
463  {
464  mountSim();
465  return true;
466  }
467 
468  char res[SYN_RES] = {0};
469 
470  // Goto in progress?
471  if (sendCommand("L", res))
472  m_MountInfo[MI_GOTO_STATUS] = res[0];
473 
474  // Pier side
475  if (m_isAltAz == false && sendCommand("p", res))
476  {
477  m_MountInfo[MI_POINT_STATUS] = res[0];
478  // INDI and mount pier sides are opposite to each other
479  setPierSide(res[0] == 'W' ? PIER_EAST : PIER_WEST);
480  }
481 
482  if (readTracking())
483  {
484  if (TrackState == SCOPE_SLEWING)
485  {
486  if (isSlewComplete())
487  {
488  TrackState = (m_TrackingFlag == 2) ? SCOPE_TRACKING : SCOPE_IDLE;
489  HorizontalCoordsNP.s = (m_TrackingFlag == 2) ? IPS_OK : IPS_IDLE;
490  IDSetNumber(&HorizontalCoordsNP, nullptr);
491  }
492  }
493  else if (TrackState == SCOPE_PARKING)
494  {
495  if (isSlewComplete())
496  {
497  HorizontalCoordsNP.s = IPS_IDLE;
498  IDSetNumber(&HorizontalCoordsNP, nullptr);
500  SetTrackEnabled(false);
501  SetParked(true);
502  }
503  }
504  else if (TrackState == SCOPE_IDLE && m_TrackingFlag > 0)
506  else if (TrackState == SCOPE_TRACKING && m_TrackingFlag == 0)
508  }
509 
510  sendStatus();
511 
512  // Get Precise RA/DE
513  memset(res, 0, SYN_RES);
514  if (!sendCommand("e", res))
515  return false;
516 
517  uint32_t n1 = 0, n2 = 0;
518  sscanf(res, "%x,%x#", &n1, &n2);
519  double ra = static_cast<double>(n1) / 0x100000000 * 360.0;
520  double de = static_cast<double>(n2) / 0x100000000 * 360.0;
521 
522  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
523  J2000Pos.rightascension = range24(ra / 15.0);
524  J2000Pos.declination = rangeDec(de);
525 
526  // Synscan reports J2000 coordinates so we need to convert from J2000 to JNow
527  INDI::J2000toObserved(&J2000Pos, ln_get_julian_from_sys(), &epochPos);
528 
529  CurrentRA = epochPos.rightascension;
530  CurrentDE = epochPos.declination;
531 
532  char Axis1Coords[MAXINDINAME] = {0}, Axis2Coords[MAXINDINAME] = {0};
533  fs_sexa(Axis1Coords, J2000Pos.rightascension, 2, 3600);
534  fs_sexa(Axis2Coords, J2000Pos.declination, 2, 3600);
535  LOGF_DEBUG("J2000 RA <%s> DE <%s>", Axis1Coords, Axis2Coords);
536  memset(Axis1Coords, 0, MAXINDINAME);
537  memset(Axis2Coords, 0, MAXINDINAME);
538  fs_sexa(Axis1Coords, CurrentRA, 2, 3600);
539  fs_sexa(Axis2Coords, CurrentDE, 2, 3600);
540  LOGF_DEBUG("JNOW RA <%s> DE <%s>", Axis1Coords, Axis2Coords);
541 
542  // Now feed the rest of the system with corrected data
543  NewRaDec(CurrentRA, CurrentDE);
544 
545  // Get precise az/alt
546  memset(res, 0, SYN_RES);
547  if (!sendCommand("z", res))
548  return false;
549 
550  sscanf(res, "%x,%x#", &n1, &n2);
551  double az = static_cast<double>(n1) / 0x100000000 * 360.0;
552  double al = static_cast<double>(n2) / 0x100000000 * 360.0;
553  al = rangeDec(al);
554 
555  HorizontalCoordsN[AXIS_AZ].value = az;
556  HorizontalCoordsN[AXIS_ALT].value = al;
557 
558  memset(Axis1Coords, 0, MAXINDINAME);
559  memset(Axis2Coords, 0, MAXINDINAME);
560  fs_sexa(Axis1Coords, az, 2, 3600);
561  fs_sexa(Axis2Coords, al, 2, 3600);
562  LOGF_DEBUG("AZ <%s> ALT <%s>", Axis1Coords, Axis2Coords);
563 
564  IDSetNumber(&HorizontalCoordsNP, nullptr);
565 
566  return true;
567 }
568 
570 {
571  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
572 
573  if (isSimulation())
574  return true;
575 
576  cmd[0] = 'T';
577  cmd[1] = enabled ? (IUFindOnSwitchIndex(&TrackModeSP) + 1) : 0;
578  return sendCommand(cmd, res, 2);
579 }
580 
581 bool SynscanDriver::SetTrackMode(uint8_t mode)
582 {
583  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
584 
585  if (isSimulation())
586  return true;
587 
588  cmd[0] = 'T';
589  cmd[1] = mode + 1;
590  return sendCommand(cmd, res);
591 }
592 
593 bool SynscanDriver::SetAltAzMode(bool enable)
594 {
595  IUResetSwitch(&GotoModeSP);
596 
597  if (enable)
598  {
599  ISwitch *sp = IUFindSwitch(&GotoModeSP, "ALTAZ");
600  if (sp)
601  {
602  LOG_INFO("Using AltAz goto.");
603  sp->s = ISS_ON;
604  }
605  goto_AltAz = true;
606  }
607  else
608  {
609  ISwitch *sp = IUFindSwitch(&GotoModeSP, "RADEC");
610  if (sp)
611  {
612  sp->s = ISS_ON;
613  LOG_INFO("Using Ra/Dec goto.");
614  }
615  goto_AltAz = false;
616  }
617 
618  GotoModeSP.s = IPS_OK;
619  IDSetSwitch(&GotoModeSP, nullptr);
620  return true;
621 }
622 
623 bool SynscanDriver::Goto(double ra, double dec)
624 {
625  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
626 
627  TargetRA = ra;
628  TargetDE = dec;
629 
630  if (isSimulation())
631  return true;
632 
633  // INDI is JNow. Synscan Controll uses J2000 Epoch
634  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
635 
636  epochPos.rightascension = ra;
637  epochPos.declination = dec;
638 
639  // For Alt/Az mounts, we must issue Goto Alt/Az
640  if (goto_AltAz && m_isAltAz) // only if enabled to use AltAz goto
641  {
643  INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &altaz);
644  /* libnova measures azimuth from south towards west */
645  double az = altaz.azimuth;
646  double al = altaz.altitude;
647 
648  return GotoAzAlt(az, al);
649  }
650 
651  // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
652  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
653 
654  double dec_pos = J2000Pos.declination;
655  if (J2000Pos.declination < 0)
656  dec_pos = dec_pos + 360;
657  // Mount deals in J2000 coords.
658  uint32_t n1 = J2000Pos.rightascension * 15.0 / 360 * 0x100000000;
659  uint32_t n2 = dec_pos / 360 * 0x100000000;
660 
661  LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
662  J2000Pos.declination);
663 
664  snprintf(cmd, SYN_RES, "r%08X,%08X", n1, n2);
665  if (sendCommand(cmd, res, 18))
666  {
668  HorizontalCoordsNP.s = IPS_BUSY;
669  IDSetNumber(&HorizontalCoordsNP, nullptr);
670  return true;
671  }
672 
673  return false;
674 }
675 
676 bool SynscanDriver::GotoAzAlt(double az, double alt)
677 {
678  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
679 
680  if (isSimulation())
681  return true;
682 
683  if (m_isAltAz == false)
684  {
685  // For EQ Mount, we convert Parking Az/Alt to RA/DE and go to there.
686  INDI::IHorizontalCoordinates horizontalPos;
687  INDI::IEquatorialCoordinates equatorialPos;
688  horizontalPos.azimuth = az;
689  horizontalPos.altitude = alt;
690  INDI::HorizontalToEquatorial(&horizontalPos, &m_Location, ln_get_julian_from_sys(), &equatorialPos);
691  return Goto(equatorialPos.rightascension, equatorialPos.declination);
692  }
693 
694  // Az/Alt to encoders
695  uint32_t n1 = az / 360.0 * 0x100000000;
696  uint32_t n2 = alt / 360.0 * 0x100000000;
697 
698  LOGF_DEBUG("Goto - Az: %.2f Alt: %.2f", az, alt);
699 
700  snprintf(cmd, SYN_RES, "b%08X,%08X", n1, n2);
701  if (sendCommand(cmd, res, 18))
702  {
704  HorizontalCoordsNP.s = IPS_BUSY;
705  IDSetNumber(&HorizontalCoordsNP, nullptr);
706  return true;
707  }
708 
709  return false;
710 }
711 
713 {
714  double parkAZ = GetAxis1Park();
715  double parkAlt = GetAxis2Park();
716 
717  char AzStr[16], AltStr[16];
718  fs_sexa(AzStr, parkAZ, 2, 3600);
719  fs_sexa(AltStr, parkAlt, 2, 3600);
720  LOGF_DEBUG("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
721 
722  if (GotoAzAlt(parkAZ, parkAlt))
723  {
725  LOG_INFO("Parking is in progress...");
726  return true;
727  }
728 
729  return false;
730 }
731 
733 {
734  SetParked(false);
735  SetTrackMode(m_isAltAz ? 1 : 2);
736  SetTrackEnabled(true);
737  return true;
738 }
739 
741 {
742  char res[SYN_RES] = {0};
743 
744  // Get Current Az/Alt
745  memset(res, 0, SYN_RES);
746  if (!sendCommand("z", res))
747  return false;
748 
749  uint32_t n1 = 0, n2 = 0;
750  sscanf(res, "%ux,%ux#", &n1, &n2);
751  double az = static_cast<double>(n1) / 0x100000000 * 360.0;
752  double al = static_cast<double>(n2) / 0x100000000 * 360.0;
753  al = rangeDec(al);
754 
755  char AzStr[16], AltStr[16];
756  fs_sexa(AzStr, az, 2, 3600);
757  fs_sexa(AltStr, al, 2, 3600);
758 
759  LOGF_DEBUG("Setting current parking position to coordinates Az (%s) Alt (%s)...", AzStr, AltStr);
760 
761  SetAxis1Park(az);
762  SetAxis2Park(al);
763 
764  return true;
765 }
766 
768 {
769  // By default az to north, and alt to pole
770  LOG_DEBUG("Setting Park Data to Default.");
771  SetAxis1Park(359);
773 
774  return true;
775 }
776 
778 {
779  if (TrackState == SCOPE_IDLE)
780  return true;
781 
782  LOG_DEBUG("Abort mount...");
784 
785  if (isSimulation())
786  return true;
787 
788  SetTrackEnabled(false);
789  sendCommand("M");
790  sendCommand("M");
791  return true;
792 }
793 
795 {
796  if (isSimulation())
797  return true;
798 
799  bool rc = false;
800  SynscanDirection move;
801 
802  if (currentPierSide == PIER_WEST)
803  move = (dir == DIRECTION_NORTH) ? SYN_N : SYN_S;
804  else
805  move = (dir == DIRECTION_NORTH) ? SYN_S : SYN_N;
806 
807  uint8_t rate = static_cast<uint8_t>(IUFindOnSwitchIndex(&SlewRateSP)) + 1;
808  double customRate = CustomSlewRateN[AXIS_DE].value;
809 
810  // If we have pulse guiding
811  if (m_CustomGuideDE > 0)
812  {
813  rate = 10;
814  customRate = m_CustomGuideDE;
815  }
816 
817  switch (command)
818  {
819  case MOTION_START:
820  rc = (rate < 10) ? slewFixedRate(move, rate) : slewVariableRate(move, customRate);
821  if (!rc)
822  {
823  LOG_ERROR("Error setting N/S motion direction.");
824  return false;
825  }
826  // Only report messages if we are not guiding
827  else if (!m_CustomGuideDE)
828  LOGF_INFO("Moving toward %s.", (move == SYN_N) ? "North" : "South");
829  break;
830 
831  case MOTION_STOP:
832  if (slewFixedRate(move, 0) == false)
833  {
834  LOG_ERROR("Error stopping N/S motion.");
835  return false;
836  }
837  else if (!m_CustomGuideDE)
838  LOGF_INFO("Movement toward %s halted.", (move == SYN_N) ? "North" : "South");
839  break;
840  }
841 
842  return true;
843 }
844 
846 {
847  if (isSimulation())
848  return true;
849 
850  bool rc = false;
851  SynscanDirection move = (dir == DIRECTION_WEST) ? SYN_W : SYN_E;
852  uint8_t rate = static_cast<uint8_t>(IUFindOnSwitchIndex(&SlewRateSP)) + 1;
853  double customRate = CustomSlewRateN[AXIS_RA].value;
854 
855  // If we have pulse guiding
856  if (m_CustomGuideRA > 0)
857  {
858  rate = 10;
859  customRate = m_CustomGuideRA;
860  }
861 
862  switch (command)
863  {
864  case MOTION_START:
865  rc = (rate < 10) ? slewFixedRate(move, rate) : slewVariableRate(move, customRate);
866  if (!rc)
867  {
868  LOG_ERROR("Error setting W/E motion direction.");
869  return false;
870  }
871  // Only report messages if we are not guiding
872  else if (!m_CustomGuideRA)
873  LOGF_INFO("Moving toward %s.", (move == SYN_W) ? "West" : "East");
874  break;
875 
876  case MOTION_STOP:
877  if (slewFixedRate(move, 0) == false)
878  {
879  LOG_ERROR("Error stopping W/E motion.");
880  return false;
881  }
882  else if (!m_CustomGuideRA)
883  LOGF_INFO("Movement toward %s halted.", (move == SYN_W) ? "West" : "East");
884  break;
885  }
886 
887  return true;
888 }
889 
891 {
892  m_TargetSlewRate = s + 1;
893  return true;
894 }
895 
896 #if 0
897 bool SynscanDriver::passThruCommand(int cmd, int target, int msgsize, int data, int numReturn)
898 {
899  char test[20] = {0};
900  int bytesRead, bytesWritten;
901  char a, b, c;
902  int tt = data;
903 
904  a = tt % 256;
905  tt = tt >> 8;
906  b = tt % 256;
907  tt = tt >> 8;
908  c = tt % 256;
909 
910  // format up a passthru command
911  memset(test, 0, 20);
912  test[0] = 80; // passhtru
913  test[1] = msgsize; // set message size
914  test[2] = target; // set the target
915  test[3] = cmd; // set the command
916  test[4] = c; // set data bytes
917  test[5] = b;
918  test[6] = a;
919  test[7] = numReturn;
920 
921  LOGF_DEBUG("CMD <%s>", test);
922  tty_write(PortFD, test, 8, &bytesWritten);
923  memset(test, 0, 20);
924  tty_read(PortFD, test, numReturn + 1, 2, &bytesRead);
925  LOGF_DEBUG("RES <%s>", test);
926  if (numReturn > 0)
927  {
928  int retval = 0;
929  retval = test[0];
930  if (numReturn > 1)
931  {
932  retval = retval << 8;
933  retval += test[1];
934  }
935  if (numReturn > 2)
936  {
937  retval = retval << 8;
938  retval += test[2];
939  }
940  return retval;
941  }
942 
943  return 0;
944 }
945 #endif
946 
947 bool SynscanDriver::sendTime()
948 {
949  LOG_DEBUG("Reading mount time...");
950 
951  if (isSimulation())
952  {
953  char timeString[MAXINDINAME] = {0};
954  time_t now = time (nullptr);
955  strftime(timeString, MAXINDINAME, "%T", gmtime(&now));
956  IUSaveText(&TimeT[0], "3");
957  IUSaveText(&TimeT[1], timeString);
958  TimeTP.s = IPS_OK;
959  IDSetText(&TimeTP, nullptr);
960  return true;
961  }
962 
963  char res[SYN_RES] = {0};
964  if (sendCommand("h", res))
965  {
966  ln_zonedate localTime;
967  ln_date utcTime;
968  int offset, daylightflag;
969 
970  localTime.hours = res[0];
971  localTime.minutes = res[1];
972  localTime.seconds = res[2];
973  localTime.months = res[3];
974  localTime.days = res[4];
975  localTime.years = res[5];
976  offset = static_cast<int>(res[6]);
977  // Negative GMT offset is read. It needs special treatment
978  if (offset > 200)
979  offset -= 256;
980  localTime.gmtoff = offset;
981  // this is the daylight savings flag in the hand controller, needed if we did not set the time
982  daylightflag = res[7];
983  localTime.years += 2000;
984  localTime.gmtoff *= 3600;
985  // now convert to utc
986  ln_zonedate_to_date(&localTime, &utcTime);
987 
988  // now we have time from the hand controller, we need to set some variables
989  int sec;
990  char utc[100];
991  char ofs[10];
992  sec = static_cast<int>(utcTime.seconds);
993  sprintf(utc, "%04d-%02d-%dT%d:%02d:%02d", utcTime.years, utcTime.months, utcTime.days, utcTime.hours,
994  utcTime.minutes, sec);
995  if (daylightflag == 1)
996  offset = offset + 1;
997  sprintf(ofs, "%d", offset);
998 
999  IUSaveText(&TimeT[0], utc);
1000  IUSaveText(&TimeT[1], ofs);
1001  TimeTP.s = IPS_OK;
1002  IDSetText(&TimeTP, nullptr);
1003 
1004  LOGF_INFO("Mount UTC Time %s Offset %d", utc, offset);
1005 
1006  return true;
1007  }
1008  return false;
1009 }
1010 
1011 bool SynscanDriver::sendLocation()
1012 {
1013  char res[SYN_RES] = {0};
1014 
1015  LOG_DEBUG("Reading mount location...");
1016 
1017  if (isSimulation())
1018  {
1019  LocationN[LOCATION_LATITUDE].value = 29.5;
1020  LocationN[LOCATION_LONGITUDE].value = 48;
1021  IDSetNumber(&LocationNP, nullptr);
1022  return true;
1023  }
1024 
1025  if (!sendCommand("w", res))
1026  return false;
1027 
1028  double lat, lon;
1029  // lets parse this data now
1030  int a, b, c, d, e, f, g, h;
1031  a = res[0];
1032  b = res[1];
1033  c = res[2];
1034  d = res[3];
1035  e = res[4];
1036  f = res[5];
1037  g = res[6];
1038  h = res[7];
1039 
1040  double t1, t2, t3;
1041 
1042  t1 = c;
1043  t2 = b;
1044  t3 = a;
1045  t1 = t1 / 3600.0;
1046  t2 = t2 / 60.0;
1047  lat = t1 + t2 + t3;
1048 
1049  t1 = g;
1050  t2 = f;
1051  t3 = e;
1052  t1 = t1 / 3600.0;
1053  t2 = t2 / 60.0;
1054  lon = t1 + t2 + t3;
1055 
1056  if (d == 1)
1057  lat = lat * -1;
1058  if (h == 1)
1059  lon = 360 - lon;
1060  LocationN[LOCATION_LATITUDE].value = lat;
1061  LocationN[LOCATION_LONGITUDE].value = lon;
1062  IDSetNumber(&LocationNP, nullptr);
1063 
1064  saveConfig(true, "GEOGRAPHIC_COORD");
1065 
1066  char LongitudeStr[32] = {0}, LatitudeStr[32] = {0};
1067  fs_sexa(LongitudeStr, lon, 2, 3600);
1068  fs_sexa(LatitudeStr, lat, 2, 3600);
1069  LOGF_INFO("Mount Longitude %s Latitude %s", LongitudeStr, LatitudeStr);
1070 
1071  return true;
1072 }
1073 
1074 bool SynscanDriver::updateTime(ln_date * utc, double utc_offset)
1075 {
1076  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1077 
1078  // start by formatting a time for the hand controller
1079  // we are going to set controller to local time
1080  struct ln_zonedate ltm;
1081 
1082  ln_date_to_zonedate(utc, &ltm, utc_offset * 3600.0);
1083 
1084  int yr = ltm.years;
1085 
1086  yr = yr % 100;
1087 
1088  cmd[0] = 'H';
1089  cmd[1] = ltm.hours;
1090  cmd[2] = ltm.minutes;
1091  cmd[3] = static_cast<char>(ltm.seconds);
1092  cmd[4] = ltm.months;
1093  cmd[5] = ltm.days;
1094  cmd[6] = yr;
1095  // offset from utc so hand controller is running in local time
1096  cmd[7] = utc_offset > 0 ? static_cast<uint8_t>(utc_offset) : static_cast<uint8_t>(256 + utc_offset);
1097  // and no daylight savings adjustments, it's already included in the offset
1098  cmd[8] = 0;
1099 
1100  LOGF_INFO("Setting mount date/time to %04d-%02d-%02d %d:%02d:%02d UTC Offset: %.2f",
1101  ltm.years, ltm.months, ltm.days, ltm.hours, ltm.minutes, static_cast<int>(rint(ltm.seconds)), utc_offset);
1102 
1103  if (isSimulation())
1104  return true;
1105 
1106  return sendCommand(cmd, res, 9);
1107 }
1108 
1109 bool SynscanDriver::updateLocation(double latitude, double longitude, double elevation)
1110 {
1111  INDI_UNUSED(elevation);
1112  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1113  bool IsWest = false;
1114 
1115  ln_lnlat_posn p1 { 0, 0 };
1116  lnh_lnlat_posn p2;
1117 
1118  LocationN[LOCATION_LATITUDE].value = latitude;
1119  LocationN[LOCATION_LONGITUDE].value = longitude;
1120  IDSetNumber(&LocationNP, nullptr);
1121 
1122  if (isSimulation())
1123  {
1124  if (!CurrentDE)
1125  {
1126  CurrentDE = latitude > 0 ? 90 : -90;
1127  CurrentRA = get_local_sidereal_time(longitude);
1128  }
1129  return true;
1130  }
1131 
1132  if (longitude > 180)
1133  {
1134  p1.lng = 360.0 - longitude;
1135  IsWest = true;
1136  }
1137  else
1138  {
1139  p1.lng = longitude;
1140  }
1141  p1.lat = latitude;
1142  ln_lnlat_to_hlnlat(&p1, &p2);
1143  LOGF_INFO("Update location to latitude %d:%d:%1.2f longitude %d:%d:%1.2f",
1144  p2.lat.degrees, p2.lat.minutes, p2.lat.seconds, p2.lng.degrees, p2.lng.minutes, p2.lng.seconds);
1145 
1146  cmd[0] = 'W';
1147  cmd[1] = p2.lat.degrees;
1148  cmd[2] = p2.lat.minutes;
1149  cmd[3] = rint(p2.lat.seconds);
1150  cmd[4] = (p2.lat.neg == 0) ? 0 : 1;
1151  cmd[5] = p2.lng.degrees;
1152  cmd[6] = p2.lng.minutes;
1153  cmd[7] = rint(p2.lng.seconds);
1154  cmd[8] = IsWest ? 1 : 0;
1155 
1156  return sendCommand(cmd, res, 9);
1157 }
1158 
1159 bool SynscanDriver::Sync(double ra, double dec)
1160 {
1161  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1162 
1163  TargetRA = ra;
1164  TargetDE = dec;
1165 
1166  if (isSimulation())
1167  return true;
1168 
1169  // INDI is JNow. Synscan Controll uses J2000 Epoch
1170  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1171  epochPos.rightascension = ra;
1172  epochPos.declination = dec;
1173 
1174  // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
1175  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
1176 
1177  // Mount deals in J2000 coords.
1178  uint32_t n1 = J2000Pos.rightascension * 15.0 / 360 * 0x100000000;
1179  uint32_t n2 = J2000Pos.declination / 360 * 0x100000000;
1180 
1181  LOGF_DEBUG("Sync - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
1182  J2000Pos.declination);
1183 
1184  snprintf(cmd, SYN_RES, "s%08X,%08X", n1, n2);
1185  return sendCommand(cmd, res, 18);
1186 }
1187 
1188 void SynscanDriver::sendStatus()
1189 {
1190  bool BasicMountInfoHasChanged = false;
1191 
1192  if (std::string(StatusT[MI_GOTO_STATUS].text) != m_MountInfo[MI_GOTO_STATUS])
1193  {
1194  IUSaveText(&StatusT[MI_GOTO_STATUS], m_MountInfo[MI_GOTO_STATUS].c_str());
1195  BasicMountInfoHasChanged = true;
1196  }
1197  if (std::string(StatusT[MI_POINT_STATUS].text) != m_MountInfo[MI_POINT_STATUS])
1198  {
1199  IUSaveText(&StatusT[MI_POINT_STATUS], m_MountInfo[MI_POINT_STATUS].c_str());
1200  BasicMountInfoHasChanged = true;
1201  }
1202  if (std::string(StatusT[MI_TRACK_MODE].text) != m_MountInfo[MI_TRACK_MODE])
1203  {
1204  IUSaveText(&StatusT[MI_TRACK_MODE], m_MountInfo[MI_TRACK_MODE].c_str());
1205  BasicMountInfoHasChanged = true;
1206  }
1207 
1208  if (BasicMountInfoHasChanged)
1209  {
1210  StatusTP.s = IPS_OK;
1211  IDSetText(&StatusTP, nullptr);
1212  }
1213 }
1214 
1215 bool SynscanDriver::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
1216 {
1217  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1218 
1219  tcflush(PortFD, TCIOFLUSH);
1220 
1221  if (cmd_len > 0)
1222  {
1223  char hex_cmd[SYN_RES * 3] = {0};
1224  hexDump(hex_cmd, cmd, cmd_len);
1225  LOGF_DEBUG("CMD <%s>", hex_cmd);
1226  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
1227  }
1228  else
1229  {
1230  LOGF_DEBUG("CMD <%s>", cmd);
1231  rc = tty_write_string(PortFD, cmd, &nbytes_written);
1232  }
1233 
1234  if (rc != TTY_OK)
1235  {
1236  char errstr[MAXRBUF] = {0};
1237  tty_error_msg(rc, errstr, MAXRBUF);
1238  LOGF_ERROR("Serial write error: %s.", errstr);
1239  return false;
1240  }
1241 
1242  if (res == nullptr)
1243  return true;
1244 
1245  if (res_len > 0)
1246  rc = tty_read(PortFD, res, res_len, SYN_TIMEOUT, &nbytes_read);
1247  else
1248  rc = tty_nread_section(PortFD, res, SYN_RES, SYN_DEL, SYN_TIMEOUT, &nbytes_read);
1249 
1250  if (rc != TTY_OK)
1251  {
1252  char errstr[MAXRBUF] = {0};
1253  tty_error_msg(rc, errstr, MAXRBUF);
1254  LOGF_ERROR("Serial read error: %s.", errstr);
1255  return false;
1256  }
1257 
1258  if (res_len > 0)
1259  {
1260  char hex_res[SYN_RES * 3] = {0};
1261  hexDump(hex_res, res, res_len);
1262  LOGF_DEBUG("RES <%s>", hex_res);
1263  }
1264  else
1265  {
1266  LOGF_DEBUG("RES <%s>", res);
1267  }
1268 
1269  tcflush(PortFD, TCIOFLUSH);
1270 
1271  return true;
1272 }
1273 
1274 void SynscanDriver::hexDump(char * buf, const char * data, int size)
1275 {
1276  for (int i = 0; i < size; i++)
1277  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1278 
1279  if (size > 0)
1280  buf[3 * size - 1] = '\0';
1281 }
1282 
1283 void SynscanDriver::mountSim()
1284 {
1285  static struct timeval ltv;
1286  struct timeval tv;
1287  double dt, da, dx;
1288  int nlocked;
1289 
1290  /* update elapsed time since last poll, don't presume exactly POLLMS */
1291  gettimeofday(&tv, nullptr);
1292 
1293  if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1294  ltv = tv;
1295 
1296  dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1297  ltv = tv;
1298  double currentSlewRate = SIM_SLEW_RATE[IUFindOnSwitchIndex(&SlewRateSP)] * TRACKRATE_SIDEREAL / 3600.0;
1299  da = currentSlewRate * dt;
1300 
1301  /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */
1302  switch (TrackState)
1303  {
1304  case SCOPE_IDLE:
1305  CurrentRA += (TrackRateN[AXIS_RA].value / 3600.0 * dt) / 15.0;
1306  CurrentRA = range24(CurrentRA);
1307  break;
1308 
1309  case SCOPE_TRACKING:
1310  break;
1311 
1312  case SCOPE_SLEWING:
1313  case SCOPE_PARKING:
1314  /* slewing - nail it when both within one pulse @ SLEWRATE */
1315  nlocked = 0;
1316 
1317  dx = TargetRA - CurrentRA;
1318 
1319  // Take shortest path
1320  if (fabs(dx) > 12)
1321  dx *= -1;
1322 
1323  if (fabs(dx) <= da)
1324  {
1325  CurrentRA = TargetRA;
1326  nlocked++;
1327  }
1328  else if (dx > 0)
1329  CurrentRA += da / 15.;
1330  else
1331  CurrentRA -= da / 15.;
1332 
1333  if (CurrentRA < 0)
1334  CurrentRA += 24;
1335  else if (CurrentRA > 24)
1336  CurrentRA -= 24;
1337 
1338  dx = TargetDE - CurrentDE;
1339  if (fabs(dx) <= da)
1340  {
1341  CurrentDE = TargetDE;
1342  nlocked++;
1343  }
1344  else if (dx > 0)
1345  CurrentDE += da;
1346  else
1347  CurrentDE -= da;
1348 
1349  if (nlocked == 2)
1350  {
1351  if (TrackState == SCOPE_SLEWING)
1353  else
1355  }
1356 
1357  break;
1358 
1359  default:
1360  break;
1361  }
1362 
1363  NewRaDec(CurrentRA, CurrentDE);
1364 }
1365 
1366 bool SynscanDriver::slewFixedRate(SynscanDirection direction, uint8_t rate)
1367 {
1368  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1369 
1370  cmd[0] = 'P';
1371  cmd[1] = 2;
1372  // Axis 17 for DE/AL, 16 for RA/AZ
1373  cmd[2] = (direction == SYN_N || direction == SYN_S) ? 17 : 16;
1374  // Command 36 positive direction, 37 negative direction
1375  if (!m_isAltAz)
1376  cmd[3] = (direction == SYN_N || direction == SYN_W) ? 36 : 37;
1377  else
1378  cmd[3] = (direction == SYN_N || direction == SYN_W) ? 37 : 36;
1379  // Fixed rate (0 to 9) where 0 is stop
1380  cmd[4] = rate;
1381 
1382  return sendCommand(cmd, res, 8);
1383 }
1384 
1385 bool SynscanDriver::slewVariableRate(SynscanDirection direction, double rate)
1386 {
1387  char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1388 
1389  // According to Synscan documentation. We need to multiply by 4
1390  // then separate into high and low bytes
1391  uint16_t synRate = rint(rate * 4);
1392 
1393  cmd[0] = 'P';
1394  cmd[1] = 3;
1395  // Axis 17 for DE/AL, 16 for RA/AZ
1396  cmd[2] = (direction == SYN_N || direction == SYN_S) ? 17 : 16;
1397  // Command 6 positive direction, 7 negative direction
1398  cmd[3] = (direction == SYN_N || direction == SYN_W) ? 6 : 7;
1399  // High byte
1400  cmd[4] = synRate >> 8;
1401  // Low byte
1402  cmd[5] = synRate & 0xFF;
1403 
1404  return sendCommand(cmd, res, 8);
1405 }
1406 
1408 {
1409  if (m_GuideNSTID)
1410  {
1411  IERmTimer(m_GuideNSTID);
1412  m_GuideNSTID = 0;
1413  }
1414 
1415  m_CustomGuideDE = TRACKRATE_SIDEREAL + GuideRateN[AXIS_DE].value * TRACKRATE_SIDEREAL;
1417  m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperNS, this);
1418  return IPS_BUSY;
1419 }
1420 
1422 {
1423  if (m_GuideNSTID)
1424  {
1425  IERmTimer(m_GuideNSTID);
1426  m_GuideNSTID = 0;
1427  }
1428 
1429  m_CustomGuideDE = TRACKRATE_SIDEREAL + GuideRateN[AXIS_DE].value * TRACKRATE_SIDEREAL;
1431  m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperNS, this);
1432  return IPS_BUSY;
1433 }
1434 
1436 {
1437  if (m_GuideWETID)
1438  {
1439  IERmTimer(m_GuideWETID);
1440  m_GuideWETID = 0;
1441  }
1442 
1443  // So if we SID_RATE + 0.5 * SID_RATE for example, that's 150% of sidereal rate
1444  // but for east we'd be going a lot faster since the stars are moving toward the west
1445  // in sideral rate. Just standing still we would SID_RATE moving across. So for east
1446  // we just go GuideRate * SID_RATE without adding any more values.
1447  //m_CustomGuideRA = TRACKRATE_SIDEREAL + GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1448  m_CustomGuideRA = GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1449 
1451  m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperWE, this);
1452  return IPS_BUSY;
1453 }
1454 
1456 {
1457  if (m_GuideWETID)
1458  {
1459  IERmTimer(m_GuideWETID);
1460  m_GuideWETID = 0;
1461  }
1462 
1463  // Sky already going westward (or earth rotating eastward, pick your favorite)
1464  // So we go SID_RATE + whatever guide rate was set to.
1465  m_CustomGuideRA = TRACKRATE_SIDEREAL + GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1467  m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperWE, this);
1468  return IPS_BUSY;
1469 }
1470 
1472 {
1473  static_cast<SynscanDriver *>(context)->guideTimeoutCallbackNS();
1474 }
1475 
1477 {
1478  static_cast<SynscanDriver *>(context)->guideTimeoutCallbackWE();
1479 }
1480 
1481 void SynscanDriver::guideTimeoutCallbackNS()
1482 {
1483  INDI_DIR_NS direction = static_cast<INDI_DIR_NS>(IUFindOnSwitchIndex(&MovementNSSP));
1484  MoveNS(direction, MOTION_STOP);
1486  m_CustomGuideDE = m_GuideNSTID = 0;
1487 }
1488 
1489 void SynscanDriver::guideTimeoutCallbackWE()
1490 {
1491  INDI_DIR_WE direction = static_cast<INDI_DIR_WE>(IUFindOnSwitchIndex(&MovementWESP));
1492  MoveWE(direction, MOTION_STOP);
1494  m_CustomGuideRA = m_GuideWETID = 0;
1495 }
1496 
1497 bool SynscanDriver::isSlewComplete()
1498 {
1499  char res[SYN_RES] = {0};
1500 
1501  if (!sendCommand("L", res))
1502  return false;
1503 
1504  return res[0] == '0';
1505 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
INDI::PropertySwitch getSwitch(const char *name) const
Definition: basedevice.cpp:99
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
virtual void GuideComplete(INDI_EQ_AXIS axis)
Call GuideComplete once the guiding pulse is complete.
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
TelescopePierSide currentPierSide
ISwitchVectorProperty MovementNSSP
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.
INumberVectorProperty LocationNP
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty TimeTP
double GetAxis1Park() const
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty TrackModeSP
ISwitchVectorProperty SlewRateSP
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....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
INumber TrackRateN[2]
IGeographicCoordinates m_Location
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
ISwitch * TrackModeS
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
ISwitchVectorProperty MovementWESP
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.
virtual bool Park() override
Park the telescope to its home position.
static void guideTimeoutHelperWE(void *context)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
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 SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
static void guideTimeoutHelperNS(void *context)
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual const char * getDefaultName() override
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool Handshake() override
perform handshake with device to check communication
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual bool GotoAzAlt(double az, double alt)
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
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.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:602
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition: eventloop.c:582
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define NARRAY(a)
Handy macro to find the number of elements in array a[]. Must be used with actual array,...
Definition: indiapi.h:500
@ 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_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
#define MAXINDILABEL
Definition: indiapi.h:192
@ ISR_1OFMANY
Definition: indiapi.h:173
#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_EAST
Definition: indibasetypes.h:57
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_SOUTH
Definition: indibasetypes.h:50
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition: indicom.c:1235
double rangeDec(double decdegrees)
rangeDec Limits declination value to be in -90 to 90 range.
Definition: indicom.c:1255
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:474
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
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
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
#define TRACKRATE_SIDEREAL
Definition: indicom.h:55
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition: indidevapi.c:66
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
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 LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
void J2000toObserved(IEquatorialCoordinates *J2000pos, double jd, IEquatorialCoordinates *observed)
*J2000toObserved converts catalogue to observed
Definition: libastro.cpp:80
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
void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos)
ObservedToJ2000 converts an observed position to a J2000 catalogue position removes aberration,...
Definition: libastro.cpp:50
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
__u8 cmd[4]
Definition: pwc-ioctl.h:2
One number descriptor.
One switch descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250