Instrument Neutral Distributed Interface INDI  2.0.2
synscandriverlegacy.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2010 Gerry Rozema. All rights reserved.
3  Copyright(c) 2018 Jasem Mutlaq. All rights reserved.
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License version 2 as published by the Free Software Foundation.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 *******************************************************************************/
19 
20 #include "synscandriverlegacy.h"
23 #include "indicom.h"
24 #include "libastro.h"
25 
26 #include <libnova/transform.h>
27 #include <libnova/precession.h>
28 // libnova specifies round() on old systems and it collides with the new gcc 5.x/6.x headers
29 #define HAVE_ROUND
30 #include <libnova/utility.h>
31 
32 #include <cmath>
33 #include <memory>
34 #include <cstring>
35 
36 #define SYNSCAN_SLEW_RATES 9
37 
38 constexpr uint16_t SynscanLegacyDriver::SLEW_RATE[];
39 
41 {
45  strncpy(LastParkRead, "", 1);
46 }
47 
49 {
50  if (isConnected())
51  return true;
52 
53  bool rc = INDI::Telescope::Connect();
54 
55  if (rc)
56  return AnalyzeMount();
57 
58  return rc;
59 }
60 
62 {
63  return "SynScan Legacy";
64 }
65 
67 {
69 
74 
75  // Slew Rates
76  strncpy(SlewRateS[0].label, "1x", MAXINDILABEL);
77  strncpy(SlewRateS[1].label, "8x", MAXINDILABEL);
78  strncpy(SlewRateS[2].label, "16x", MAXINDILABEL);
79  strncpy(SlewRateS[3].label, "32x", MAXINDILABEL);
80  strncpy(SlewRateS[4].label, "64x", MAXINDILABEL);
81  strncpy(SlewRateS[5].label, "128x", MAXINDILABEL);
82  strncpy(SlewRateS[6].label, "400x", MAXINDILABEL);
83  strncpy(SlewRateS[7].label, "600x", MAXINDILABEL);
84  strncpy(SlewRateS[8].label, "MAX", MAXINDILABEL);
86  // Max is the default
87  SlewRateS[8].s = ISS_ON;
88 
92  IUFillText(&BasicMountInfoT[MI_FW_VERSION], "FW_VERSION", "Firmware version", "-");
93  IUFillText(&BasicMountInfoT[MI_MOUNT_CODE], "MOUNT_CODE", "Mount code", "-");
94  IUFillText(&BasicMountInfoT[MI_ALIGN_STATUS], "ALIGNMENT_STATUS", "Alignment status", "-");
95  IUFillText(&BasicMountInfoT[MI_GOTO_STATUS], "GOTO_STATUS", "Goto status", "-");
96  IUFillText(&BasicMountInfoT[MI_POINT_STATUS], "MOUNT_POINTING_STATUS",
97  "Mount pointing status", "-");
98  IUFillText(&BasicMountInfoT[MI_TRACK_MODE], "TRACKING_MODE", "Tracking mode", "-");
99  IUFillTextVector(&BasicMountInfoTP, BasicMountInfoT, 6, getDeviceName(), "BASIC_MOUNT_INFO",
100  "Mount information", MountInfoPage, IP_RO, 60, IPS_IDLE);
101 
105  // IUFillSwitch(&UseWiFiS[WIFI_ENABLED], "Enabled", "Enabled", ISS_OFF);
106  // IUFillSwitch(&UseWiFiS[WIFI_DISABLED], "Disabled", "Disabled", ISS_ON);
107  // IUFillSwitchVector(&UseWiFiSP, UseWiFiS, 2, getDeviceName(), "WIFI_SELECT", "Use WiFi?", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
108 
109  addAuxControls();
110 
111  return true;
112 }
113 
114 bool SynscanLegacyDriver::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
115 {
116  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
117 }
118 
119 bool SynscanLegacyDriver::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
120 {
121 #if 0
122  if (!strcmp(dev, getDeviceName()))
123  {
124  if (!strcmp(name, UseWiFiSP.name))
125  {
126  if (isConnected())
127  {
128  UseWiFiSP.s = IPS_ALERT;
129  IDSetSwitch(&UseWiFiSP, nullptr);
130  LOG_ERROR("Cannot select WiFi mode while already connected. It must be selected when when disconnected.");
131  return true;
132  }
133 
134  IUUpdateSwitch(&UseWiFiSP, states, names, n);
135 
136  if (UseWiFiS[WIFI_ENABLED].s == ISS_ON)
137  {
139  tcpConnection->setDefaultHost("192.168.4.2");
141 
142  LOG_INFO("Driver is configured for WiFi connection to 192.168.4.2 at TCP port 11882");
143 
144  saveConfig(true, "WIFI_SELECT");
145  UseWiFiSP.s = IPS_OK;
146  IDSetSwitch(&UseWiFiSP, nullptr);
147 
148  ISState newStates[] = { ISS_OFF, ISS_ON };
149  const char *newNames[] = { "CONNECTION_SERIAL", "CONNECTION_TCP" };
150  ISNewSwitch(getDeviceName(), "CONNECTION_MODE", newStates, const_cast<char **>(newNames), 2);
151  }
152  else
153  {
155  LOG_INFO("Driver is configured for serial connection to the hand controller.");
156 
157  UseWiFiSP.s = IPS_OK;
158  IDSetSwitch(&UseWiFiSP, nullptr);
159 
160  ISState newStates[] = { ISS_ON, ISS_OFF };
161  const char *newNames[] = { "CONNECTION_SERIAL", "CONNECTION_TCP" };
162  ISNewSwitch(getDeviceName(), "CONNECTION_MODE", newStates, const_cast<char **>(newNames), 2);
163  }
164 
165  return true;
166  }
167  }
168 #endif
169  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
170 }
171 
172 bool SynscanLegacyDriver::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
173  char *formats[], char *names[], int n)
174 {
175  return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
176 }
177 
178 bool SynscanLegacyDriver::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
179 {
180  return INDI::Telescope::ISNewText(dev, name, texts, names, n);
181 }
182 
184 {
186 
187  if (isConnected())
188  {
189  UpdateMountInformation(false);
190  defineProperty(&BasicMountInfoTP);
191  }
192  else
193  {
194  deleteProperty(BasicMountInfoTP.name);
195  }
196 
197  return true;
198 }
199 
200 int SynscanLegacyDriver::HexStrToInteger(const std::string &res)
201 {
202  int result = 0;
203 
204  try
205  {
206  result = std::stoi(res, nullptr, 16);
207  }
208  catch (std::invalid_argument &)
209  {
210  LOGF_ERROR("Failed to parse %s to integer.", res.c_str());
211  }
212 
213  return result;
214 }
215 
217 {
218  LOG_DEBUG("Analyzing Mount...");
219 
220  bool rc = true;
221  int tmp = 0;
222  int bytesWritten = 0;
223  int bytesRead;
224  char res[MAX_SYN_BUF] = {0};
225 
226  // JM 2018-08-15 Why are we reading caps here? Looks like it serves no purpose
227  //caps = GetTelescopeCapability();
228 
229  rc = ReadLocation();
230  if (rc)
231  {
232  CanSetLocation = true;
233  ReadTime();
234  }
235 
236  if (isSimulation() == false)
237  {
238  bytesRead = 0;
239  memset(res, 0, MAX_SYN_BUF);
240  LOG_DEBUG("CMD <J>");
241  tty_write(PortFD, "J", 1, &bytesWritten);
242  tty_read(PortFD, res, 2, 2, &bytesRead);
243  LOGF_DEBUG("RES <%s>", res);
244 
245  if (res[0] == 0)
246  {
247  LOG_ERROR("Mount is not aligned. Please align the mount first and connection again.");
248  return false;
249  }
250 
252  {
253  // Read the handset version
254  bytesRead = 0;
255  memset(res, 0, MAX_SYN_BUF);
256  LOG_DEBUG("Getting Firmware version...");
257  LOG_DEBUG("CMD <V>");
258  tty_write(PortFD, "V", 1, &bytesWritten);
259 
260  tty_read(PortFD, res, 7, 2, &bytesRead);
261  LOGF_DEBUG("RES <%s>", res);
262 
263  if (bytesRead == 3)
264  {
265  int tmp1 { 0 }, tmp2 { 0 };
266 
267  tmp = res[0];
268  tmp1 = res[1];
269  tmp2 = res[2];
270  FirmwareVersion = tmp2;
271  FirmwareVersion /= 100;
272  FirmwareVersion += tmp1;
273  FirmwareVersion /= 100;
274  FirmwareVersion += tmp;
275  }
276  else
277  {
278  FirmwareVersion = (double)HexStrToInteger(std::string(&res[0], 2));
279  FirmwareVersion += (double)HexStrToInteger(std::string(&res[2], 2)) / 100;
280  FirmwareVersion += (double)HexStrToInteger(std::string(&res[4], 2)) / 10000;
281  }
282 
283  LOGF_INFO("Firmware version: %lf", FirmwareVersion);
284 
285  if (FirmwareVersion < 3.38 || (FirmwareVersion >= 4.0 && FirmwareVersion < 4.38))
286  {
287  LOG_WARN("Firmware version is too old. Update Synscan firmware to v4.38+");
288  }
289  else
290  {
291  NewFirmware = true;
292  }
293 
295 
296  // Mount Model
297  memset(res, 0, MAX_SYN_BUF);
298  LOG_DEBUG("CMD <m>");
299  tty_write(PortFD, "m", 1, &bytesWritten);
300  tty_read(PortFD, res, 2, 2, &bytesRead);
301  LOGF_DEBUG("RES <%s>", res);
302 
303  if (bytesRead == 2)
304  {
305  // This workaround is needed because the firmware 3.39 sends these bytes swapped.
306  if (res[1] == '#')
307  MountCode = static_cast<int>(*reinterpret_cast<unsigned char*>(&res[0]));
308  else
309  MountCode = static_cast<int>(*reinterpret_cast<unsigned char*>(&res[1]));
310  }
311  }
312 
313  // Check the tracking status
314  LOG_DEBUG("Getting Tracking status...");
315  memset(res, 0, MAX_SYN_BUF);
316  LOG_DEBUG("CMD <t>");
317  tty_write(PortFD, "t", 1, &bytesWritten);
318  tty_read(PortFD, res, 2, 2, &bytesRead);
319  LOGF_DEBUG("RES <%s>", res);
320 
321  if (res[1] == '#' && static_cast<int>(res[0]) != 0)
322  {
324  }
325  }
326 
327  initParking();
328 
329  LOG_DEBUG("Analyzing mount complete.");
330 
331  return true;
332 }
333 
335 {
336  LOG_DEBUG("Initializing parking...");
337  if (InitPark())
338  {
341  }
342  else
343  {
344  SetAxis1Park(0);
345  SetAxis2Park(90);
348  }
349 }
350 
352 {
353  if (isSimulation())
354  {
355  MountSim();
356  return true;
357  }
358 
359  char res[MAX_SYN_BUF] = {0};
360  int bytesWritten, bytesRead;
361  double ra, dec;
362  long unsigned int n1, n2;
363 
364  LOG_DEBUG("CMD <Ka>");
365  tty_write(PortFD, "Ka", 2, &bytesWritten); // test for an echo
366 
367  tty_read(PortFD, res, 2, 2, &bytesRead); // Read 2 bytes of response
368  LOGF_DEBUG("RES <%s>", res);
369 
370  if (res[1] != '#')
371  {
372  LOG_WARN("Synscan Mount not responding");
373  // Usually, Abort() recovers the communication
374  RecoverTrials++;
375  Abort();
376  // HasFailed = true;
377  return false;
378  }
379  RecoverTrials = 0;
380 
381  /*
382  // With 3.37 firmware, on the older line of eq6 mounts
383  // The handset does not always initialize the communication with the motors correctly
384  // We can check for this condition by querying the motors for firmware version
385  // and if it returns zero, it means we need to power cycle the handset
386  // and try again after it restarts again
387 
388  if(HasFailed) {
389  int v1,v2;
390  v1=PassthruCommand(0xfe,0x11,1,0,2);
391  v2=PassthruCommand(0xfe,0x10,1,0,2);
392  fprintf(stderr,"Motor firmware versions %d %d\n",v1,v2);
393  if((v1==0)||(v2==0)) {
394  IDMessage(getDeviceName(),"Cannot proceed");
395  IDMessage(getDeviceName(),"Handset is responding, but Motors are Not Responding");
396  return false;
397  }
398  // if we get here, both motors are responding again
399  // so the problem is solved
400  HasFailed=false;
401  }
402  */
403 
404  // on subsequent passes, we just need to read the time
405  // if (HasTime())
406  // {
407  // ReadTime();
408  // }
409  if (HasLocation())
410  {
411  // this flag is set when we get a new lat/long from the host
412  // so we should go thru the read routine once now, so things update
413  // correctly in the client displays
414  if (ReadLatLong)
415  {
416  ReadLocation();
417  }
418  }
419 
420  // Query mount information
421  memset(res, 0, MAX_SYN_BUF);
422  LOG_DEBUG("CMD <J>");
423  tty_write(PortFD, "J", 1, &bytesWritten);
424  tty_read(PortFD, res, 2, 2, &bytesRead);
425  LOGF_DEBUG("RES <%s>", res);
426  if (res[1] == '#')
427  {
428  AlignmentStatus = std::to_string((int)res[0]);
429  }
430  memset(res, 0, MAX_SYN_BUF);
431  LOG_DEBUG("CMD <L>");
432  tty_write(PortFD, "L", 1, &bytesWritten);
433  tty_read(PortFD, res, 2, 2, &bytesRead);
434  LOGF_DEBUG("RES <%s>", res);
435  if (res[1] == '#')
436  {
437  GotoStatus = res[0];
438  }
439  memset(res, 0, MAX_SYN_BUF);
440  LOG_DEBUG("CMD <p>");
441  tty_write(PortFD, "p", 1, &bytesWritten);
442  tty_read(PortFD, res, 2, 2, &bytesRead);
443  LOGF_DEBUG("RES <%s>", res);
444  if (res[1] == '#')
445  {
446  PointingStatus = res[0];
447 
448  // INDI and mount pier sides are opposite to each other
449  setPierSide(res[0] == 'W' ? PIER_EAST : PIER_WEST);
450  }
451  memset(res, 0, MAX_SYN_BUF);
452  LOG_DEBUG("CMD <t>");
453  tty_write(PortFD, "t", 1, &bytesWritten);
454  tty_read(PortFD, res, 2, 2, &bytesRead);
455  LOGF_DEBUG("RES <%s>", res);
456  if (res[1] == '#')
457  {
458  TrackingStatus = res[0];
459  switch((int)res[0])
460  {
461  case 0:
462  TrackingMode = "Tracking off";
463  break;
464  case 1:
465  TrackingMode = "Alt/Az tracking";
466  break;
467  case 2:
468  TrackingMode = "EQ tracking";
469  break;
470  case 3:
471  TrackingMode = "PEC mode";
472  break;
473  }
474  }
475 
476  UpdateMountInformation(true);
477 
478  if (TrackState == SCOPE_SLEWING)
479  {
480  // We have a slew in progress
481  // lets see if it's complete
482  // This only works for ra/dec goto commands
483  // The goto complete flag doesn't trip for ALT/AZ commands
484  if (GotoStatus != "0")
485  {
486  // Nothing to do here
487  }
488  else if (MountCode < 128)
489  {
490  if (TrackingStatus[0] != 0)
492  else
494  }
495  }
496  if (TrackState == SCOPE_PARKING)
497  {
498  if (FirmwareVersion == 4.103500)
499  {
500  // With this firmware the correct way
501  // is to check the slewing flat
502  memset(res, 0, 3);
503  LOG_DEBUG("CMD <L>");
504  tty_write(PortFD, "L", 1, &bytesWritten);
505  tty_read(PortFD, res, 2, 3, &bytesRead);
506  LOGF_DEBUG("RES <%s>", res);
507  if (res[0] != 48)
508  {
509  // Nothing to do here
510  }
511  else
512  {
513  if (NumPark++ < 2)
514  {
515  Park();
516  }
517  else
518  {
520  SetParked(true);
521  }
522  }
523  }
524  else
525  {
526  // ok, lets try read where we are
527  // and see if we have reached the park position
528  // newer firmware versions dont read it back the same way
529  // so we watch now to see if we get the same read twice in a row
530  // to confirm that it has stopped moving
531  memset(res, 0, MAX_SYN_BUF);
532  LOG_DEBUG("CMD <z>");
533  tty_write(PortFD, "z", 1, &bytesWritten);
534  tty_read(PortFD, res, 18, 2, &bytesRead);
535  LOGF_DEBUG("RES <%s>", res);
536 
537  //IDMessage(getDeviceName(),"Park Read %s %d",res,StopCount);
538 
539  if (strncmp((char *)res, LastParkRead, 18) == 0)
540  {
541  // We find that often after it stops from park
542  // it's off the park position by a small amount
543  // issuing another park command gets a small movement and then
544  if (++StopCount > 2)
545  {
546  if (NumPark++ < 2)
547  {
548  StopCount = 0;
549  //IDMessage(getDeviceName(),"Sending park again");
550  Park();
551  }
552  else
553  {
555  //ParkSP.s=IPS_OK;
556  //IDSetSwitch(&ParkSP,nullptr);
557  //IDMessage(getDeviceName(),"Telescope is Parked.");
558  SetParked(true);
559  }
560  }
561  else
562  {
563  //StopCount=0;
564  }
565  }
566  else
567  {
568  StopCount = 0;
569  }
570  strncpy(LastParkRead, res, 20);
571  }
572  }
573 
574  memset(res, 0, MAX_SYN_BUF);
575  LOG_DEBUG("CMD <e>");
576  tty_write(PortFD, "e", 1, &bytesWritten);
577  tty_read(PortFD, res, 18, 1, &bytesRead);
578  LOGF_DEBUG("RES <%s>", res);
579  if (bytesRead != 18)
580  {
581  LOG_DEBUG("Read current position failed");
582  return false;
583  }
584 
585  sscanf(res, "%lx,%lx#", &n1, &n2);
586  ra = static_cast<double>(n1) / 0x100000000 * 24.0;
587  dec = static_cast<double>(n2) / 0x100000000 * 360.0;
588 
589  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
590  J2000Pos.rightascension = range24(ra);
591  J2000Pos.declination = rangeDec(dec);
592 
593  // Synscan reports J2000 coordinates so we need to convert from J2000 to JNow
594  INDI::J2000toObserved(&J2000Pos, ln_get_julian_from_sys(), &epochPos);
595 
596  CurrentRA = epochPos.rightascension;
597  CurrentDEC = epochPos.declination;
598 
599  // Now feed the rest of the system with corrected data
601 
602  if (TrackState == SCOPE_SLEWING && MountCode >= 128 && (SlewTargetAz != -1 || SlewTargetAlt != -1))
603  {
604  INDI::IHorizontalCoordinates CurrentAltAz { 0, 0 };
605  double DiffAlt { 0 };
606  double DiffAz { 0 };
607 
608  INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &CurrentAltAz);
609  DiffAlt = CurrentAltAz.altitude - SlewTargetAlt;
610  if (SlewTargetAlt != -1 && std::abs(DiffAlt) > 0.01)
611  {
612  int NewRate = 2;
613 
614  if (std::abs(DiffAlt) > 4)
615  {
616  NewRate = 9;
617  }
618  else if (std::abs(DiffAlt) > 1.2)
619  {
620  NewRate = 7;
621  }
622  else if (std::abs(DiffAlt) > 0.5)
623  {
624  NewRate = 5;
625  }
626  else if (std::abs(DiffAlt) > 0.2)
627  {
628  NewRate = 4;
629  }
630  else if (std::abs(DiffAlt) > 0.025)
631  {
632  NewRate = 3;
633  }
634  LOGF_DEBUG("Slewing Alt axis: %1.3f-%1.3f -> %1.3f (speed: %d)",
635  CurrentAltAz.altitude, SlewTargetAlt, CurrentAltAz.altitude - SlewTargetAlt, CustomNSSlewRate);
636  if (NewRate != CustomNSSlewRate)
637  {
638  if (DiffAlt < 0)
639  {
640  CustomNSSlewRate = NewRate;
642  }
643  else
644  {
645  CustomNSSlewRate = NewRate;
647  }
648  }
649  }
650  else if (SlewTargetAlt != -1 && std::abs(DiffAlt) < 0.01)
651  {
653  SlewTargetAlt = -1;
654  LOG_DEBUG("Slewing on Alt axis finished");
655  }
656  DiffAz = CurrentAltAz.azimuth - SlewTargetAz;
657  if (DiffAz < -180)
658  DiffAz = (DiffAz + 360) * 2;
659  else if (DiffAz > 180)
660  DiffAz = (DiffAz - 360) * 2;
661  if (SlewTargetAz != -1 && std::abs(DiffAz) > 0.01)
662  {
663  int NewRate = 2;
664 
665  if (std::abs(DiffAz) > 4)
666  {
667  NewRate = 9;
668  }
669  else if (std::abs(DiffAz) > 1.2)
670  {
671  NewRate = 7;
672  }
673  else if (std::abs(DiffAz) > 0.5)
674  {
675  NewRate = 5;
676  }
677  else if (std::abs(DiffAz) > 0.2)
678  {
679  NewRate = 4;
680  }
681  else if (std::abs(DiffAz) > 0.025)
682  {
683  NewRate = 3;
684  }
685  LOGF_DEBUG("Slewing Az axis: %1.3f-%1.3f -> %1.3f (speed: %d)",
686  CurrentAltAz.azimuth, SlewTargetAz, CurrentAltAz.azimuth - SlewTargetAz, CustomWESlewRate);
687  if (NewRate != CustomWESlewRate)
688  {
689  if (DiffAz > 0)
690  {
691  CustomWESlewRate = NewRate;
693  }
694  else
695  {
696  CustomWESlewRate = NewRate;
698  }
699  }
700  }
701  else if (SlewTargetAz != -1 && std::abs(DiffAz) < 0.01)
702  {
704  SlewTargetAz = -1;
705  LOG_DEBUG("Slewing on Az axis finished");
706  }
707  if (SlewTargetAz == -1 && SlewTargetAlt == -1)
708  {
709  StartTrackMode();
710  }
711  }
712  return true;
713 }
714 
716 {
717  char res[MAX_SYN_BUF] = {0};
718  int bytesWritten, bytesRead;
719 
721  LOG_INFO("Tracking started.");
722 
723  if (isSimulation())
724  return true;
725 
726  // Start tracking
727  res[0] = 'T';
728  // Check the mount type to choose tracking mode
729  if (MountCode >= 128)
730  {
731  // Alt/Az tracking mode
732  res[1] = 1;
733  }
734  else
735  {
736  // EQ tracking mode
737  res[1] = 2;
738  }
739  tty_write(PortFD, res, 2, &bytesWritten);
740  tty_read(PortFD, res, 1, 2, &bytesRead);
741  if (bytesRead != 1 || res[0] != '#')
742  {
743  LOG_DEBUG("Timeout waiting for scope to start tracking.");
744  return false;
745  }
746  return true;
747 }
748 
749 bool SynscanLegacyDriver::Goto(double ra, double dec)
750 {
751  char res[MAX_SYN_BUF] = {0};
752  int bytesWritten, bytesRead;
753  INDI::IHorizontalCoordinates TargetAltAz { 0, 0 };
754 
755  if (isSimulation() == false)
756  {
757  LOG_DEBUG("CMD <Ka>");
758  tty_write(PortFD, "Ka", 2, &bytesWritten); // test for an echo
759  tty_read(PortFD, res, 2, 2, &bytesRead); // Read 2 bytes of response
760  LOGF_DEBUG("RES <%s>", res);
761  if (res[1] != '#')
762  {
763  LOG_WARN("Wrong answer from the mount");
764  // this is not a correct echo
765  // so we are not talking to a mount properly
766  return false;
767  }
768  }
769 
771  // EQ mount has a different Goto mode
772  if (MountCode < 128 && isSimulation() == false)
773  {
774  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
775  epochPos.rightascension = ra;
776  epochPos.declination = dec;
777 
778  // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
779  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
780 
781  // Mount deals in J2000 coords.
782  int n1 = J2000Pos.rightascension * 0x1000000 / 24;
783  int n2 = J2000Pos.declination * 0x1000000 / 360;
784 
785  LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
786  J2000Pos.declination);
787 
788  n1 = n1 << 8;
789  n2 = n2 << 8;
790  LOGF_DEBUG("CMD <%s>", res);
791  snprintf(res, MAX_SYN_BUF, "r%08X,%08X", n1, n2);
792  tty_write(PortFD, res, 18, &bytesWritten);
793  memset(&res[18], 0, 1);
794 
795  tty_read(PortFD, res, 1, 60, &bytesRead);
796  if (bytesRead != 1 || res[0] != '#')
797  {
798  LOG_DEBUG("Timeout waiting for scope to complete goto.");
799  return false;
800  }
801 
802  return true;
803  }
804 
805  INDI::IEquatorialCoordinates epochPos { ra, dec };
806  INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &TargetAltAz);
807  LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g (az: %g alt: %g)", ra, dec, TargetAltAz.azimuth, TargetAltAz.altitude);
808  char RAStr[MAX_SYN_BUF] = {0}, DEStr[MAX_SYN_BUF] = {0}, AZStr[MAX_SYN_BUF] = {0}, ATStr[MAX_SYN_BUF] = {0};
809  fs_sexa(RAStr, ra, 2, 3600);
810  fs_sexa(DEStr, dec, 2, 3600);
811  fs_sexa(AZStr, TargetAltAz.azimuth, 2, 3600);
812  fs_sexa(ATStr, TargetAltAz.altitude, 2, 3600);
813 
814  LOGF_INFO("Goto RA: %s DE: %s AZ: %s ALT: %s", RAStr, DEStr, AZStr, ATStr);
815 
816  SlewTargetAz = TargetAltAz.azimuth;
817  SlewTargetAlt = TargetAltAz.altitude;
818 
819  TargetRA = ra;
820  TargetDEC = dec;
821 
822  return true;
823 }
824 
826 {
827  char res[MAX_SYN_BUF] = {0};
828  int bytesWritten, bytesRead;
829 
830  if (isSimulation() == false)
831  {
832  strncpy(LastParkRead, "", 1);
833  memset(res, 0, 3);
834  tty_write(PortFD, "Ka", 2, &bytesWritten); // test for an echo
835  tty_read(PortFD, res, 2, 2, &bytesRead); // Read 2 bytes of response
836  if (res[1] != '#')
837  {
838  // this is not a correct echo
839  // so we are not talking to a mount properly
840  return false;
841  }
842  // Now we stop tracking
843  res[0] = 'T';
844  res[1] = 0;
845  tty_write(PortFD, res, 2, &bytesWritten);
846  tty_read(PortFD, res, 1, 60, &bytesRead);
847  if (bytesRead != 1 || res[0] != '#')
848  {
849  LOG_DEBUG("Timeout waiting for scope to stop tracking.");
850  return false;
851  }
852 
853  //sprintf((char *)res,"b%08X,%08X",0x0,0x40000000);
854  tty_write(PortFD, "b00000000,40000000", 18, &bytesWritten);
855  tty_read(PortFD, res, 1, 60, &bytesRead);
856  if (bytesRead != 1 || res[0] != '#')
857  {
858  LOG_DEBUG("Timeout waiting for scope to respond to park.");
859  return false;
860  }
861  }
862 
864  if (NumPark == 0)
865  {
866  LOG_INFO("Parking Mount...");
867  }
868  StopCount = 0;
869  return true;
870 }
871 
873 {
874  SetParked(false);
875  NumPark = 0;
876  return true;
877 }
878 
880 {
881  LOG_INFO("Setting arbitrary park positions is not supported yet.");
882  return false;
883 }
884 
886 {
887  // By default az to north, and alt to pole
888  LOG_DEBUG("Setting Park Data to Default.");
889  SetAxis1Park(0);
890  SetAxis2Park(90);
891 
892  return true;
893 }
894 
896 {
897  if (TrackState == SCOPE_IDLE || RecoverTrials >= 3)
898  return true;
899 
900  char res[MAX_SYN_BUF] = {0};
901  int bytesWritten, bytesRead;
902 
903  LOG_DEBUG("Abort mount...");
905 
906  if (isSimulation())
907  return true;
908 
909  SlewTargetAlt = -1;
910  SlewTargetAz = -1;
911  CustomNSSlewRate = -1;
912  CustomWESlewRate = -1;
913  // Stop tracking
914  res[0] = 'T';
915  res[1] = 0;
916 
917  LOGF_DEBUG("CMD <%s>", res);
918  tty_write(PortFD, res, 2, &bytesWritten);
919 
920  tty_read(PortFD, res, 1, 2, &bytesRead);
921  LOGF_DEBUG("RES <%s>", res);
922 
923  if (bytesRead != 1 || res[0] != '#')
924  {
925  LOG_DEBUG("Timeout waiting for scope to stop tracking.");
926  return false;
927  }
928 
929  // Hmmm twice only stops it
930  LOG_DEBUG("CMD <M>");
931  tty_write(PortFD, "M", 1, &bytesWritten);
932  tty_read(PortFD, res, 1, 1, &bytesRead);
933  LOGF_DEBUG("RES <%c>", res[0]);
934 
935  LOG_DEBUG("CMD <M>");
936  tty_write(PortFD, "M", 1, &bytesWritten);
937  tty_read(PortFD, res, 1, 1, &bytesRead);
938  LOGF_DEBUG("RES <%c>", res[0]);
939 
940  return true;
941 }
942 
944 {
945  if (isSimulation())
946  return true;
947 
948  if (command != MOTION_START)
949  {
950  PassthruCommand(37, 17, 2, 0, 0);
951  }
952  else
953  {
954  int tt = (CustomNSSlewRate == -1 ? SlewRate : CustomNSSlewRate);
955 
956  tt = tt << 16;
957  if (dir != DIRECTION_NORTH)
958  {
959  PassthruCommand(37, 17, 2, tt, 0);
960  }
961  else
962  {
963  PassthruCommand(36, 17, 2, tt, 0);
964  }
965  }
966 
967  return true;
968 }
969 
971 {
972  if (isSimulation())
973  return true;
974 
975  if (command != MOTION_START)
976  {
977  PassthruCommand(37, 16, 2, 0, 0);
978  }
979  else
980  {
981  int tt = (CustomWESlewRate == -1 ? SlewRate : CustomWESlewRate);
982 
983  tt = tt << 16;
984  if (dir != DIRECTION_WEST)
985  {
986  PassthruCommand(36, 16, 2, tt, 0);
987  }
988  else
989  {
990  PassthruCommand(37, 16, 2, tt, 0);
991  }
992  }
993 
994  return true;
995 }
996 
998 {
999  SlewRate = s + 1;
1000  return true;
1001 }
1002 
1003 int SynscanLegacyDriver::PassthruCommand(int cmd, int target, int msgsize, int data, int numReturn)
1004 {
1005  char test[20] = {0};
1006  int bytesRead, bytesWritten;
1007  char a, b, c;
1008  int tt = data;
1009 
1010  a = tt % 256;
1011  tt = tt >> 8;
1012  b = tt % 256;
1013  tt = tt >> 8;
1014  c = tt % 256;
1015 
1016  // format up a passthru command
1017  memset(test, 0, 20);
1018  test[0] = 80; // passhtru
1019  test[1] = msgsize; // set message size
1020  test[2] = target; // set the target
1021  test[3] = cmd; // set the command
1022  test[4] = c; // set data bytes
1023  test[5] = b;
1024  test[6] = a;
1025  test[7] = numReturn;
1026 
1027  LOGF_DEBUG("CMD <%s>", test);
1028  tty_write(PortFD, test, 8, &bytesWritten);
1029  memset(test, 0, 20);
1030  tty_read(PortFD, test, numReturn + 1, 2, &bytesRead);
1031  LOGF_DEBUG("RES <%s>", test);
1032  if (numReturn > 0)
1033  {
1034  int retval = 0;
1035  retval = test[0];
1036  if (numReturn > 1)
1037  {
1038  retval = retval << 8;
1039  retval += test[1];
1040  }
1041  if (numReturn > 2)
1042  {
1043  retval = retval << 8;
1044  retval += test[2];
1045  }
1046  return retval;
1047  }
1048 
1049  return 0;
1050 }
1051 
1053 {
1054  LOG_DEBUG("Reading time...");
1055 
1056  if (isSimulation())
1057  {
1058  char timeString[MAXINDINAME] = {0};
1059  time_t now = time (nullptr);
1060  strftime(timeString, MAXINDINAME, "%T", gmtime(&now));
1061  IUSaveText(&TimeT[0], "3");
1062  IUSaveText(&TimeT[1], timeString);
1063  TimeTP.s = IPS_OK;
1064  IDSetText(&TimeTP, nullptr);
1065  return true;
1066  }
1067 
1068  char res[MAX_SYN_BUF] = {0};
1069  int bytesWritten = 0, bytesRead = 0;
1070 
1071  // lets see if this hand controller responds to a time request
1072  bytesRead = 0;
1073  LOG_DEBUG("CMD <h>");
1074  tty_write(PortFD, "h", 1, &bytesWritten);
1075 
1076  tty_read(PortFD, res, 9, 2, &bytesRead);
1077  LOGF_DEBUG("RES <%s>", res);
1078 
1079  if (res[8] == '#')
1080  {
1081  ln_zonedate localTime;
1082  ln_date utcTime;
1083  int offset, daylightflag;
1084 
1085  localTime.hours = res[0];
1086  localTime.minutes = res[1];
1087  localTime.seconds = res[2];
1088  localTime.months = res[3];
1089  localTime.days = res[4];
1090  localTime.years = res[5];
1091  offset = (int)res[6];
1092  // Negative GMT offset is read. It needs special treatment
1093  if (offset > 200)
1094  offset -= 256;
1095  localTime.gmtoff = offset;
1096  // this is the daylight savings flag in the hand controller, needed if we did not set the time
1097  daylightflag = res[7];
1098  localTime.years += 2000;
1099  localTime.gmtoff *= 3600;
1100  // now convert to utc
1101  ln_zonedate_to_date(&localTime, &utcTime);
1102 
1103  // now we have time from the hand controller, we need to set some variables
1104  int sec;
1105  char utc[100];
1106  char ofs[10];
1107  sec = (int)utcTime.seconds;
1108  sprintf(utc, "%04d-%02d-%dT%d:%02d:%02d", utcTime.years, utcTime.months, utcTime.days, utcTime.hours,
1109  utcTime.minutes, sec);
1110  if (daylightflag == 1)
1111  offset = offset + 1;
1112  sprintf(ofs, "%d", offset);
1113 
1114  IUSaveText(&TimeT[0], utc);
1115  IUSaveText(&TimeT[1], ofs);
1116  TimeTP.s = IPS_OK;
1117  IDSetText(&TimeTP, nullptr);
1118 
1119  LOGF_INFO("Mount UTC Time %s Offset %d", utc, offset);
1120 
1121  return true;
1122  }
1123  return false;
1124 }
1125 
1127 {
1128  LOG_DEBUG("Reading Location...");
1129 
1130  if (isSimulation())
1131  {
1132  LocationN[LOCATION_LATITUDE].value = 29.5;
1133  LocationN[LOCATION_LONGITUDE].value = 48;
1134  IDSetNumber(&LocationNP, nullptr);
1135  ReadLatLong = false;
1136  return true;
1137  }
1138 
1139  char res[MAX_SYN_BUF] = {0};
1140  int bytesWritten = 0, bytesRead = 0;
1141 
1142  LOG_DEBUG("CMD <Ka>");
1143  // test for an echo
1144  tty_write(PortFD, "Ka", 2, &bytesWritten);
1145  // Read 2 bytes of response
1146  tty_read(PortFD, res, 2, 2, &bytesRead);
1147  LOGF_DEBUG("RES <%s>", res);
1148 
1149  if (res[1] != '#')
1150  {
1151  LOG_WARN("Bad echo in ReadLocation");
1152  }
1153  else
1154  {
1155  // lets see if this hand controller responds to a location request
1156  bytesRead = 0;
1157  LOG_DEBUG("CMD <w>");
1158  tty_write(PortFD, "w", 1, &bytesWritten);
1159 
1160  tty_read(PortFD, res, 9, 2, &bytesRead);
1161  LOGF_DEBUG("RES <%s>", res);
1162 
1163  if (res[8] == '#')
1164  {
1165  double lat, lon;
1166  // lets parse this data now
1167  int a, b, c, d, e, f, g, h;
1168  a = res[0];
1169  b = res[1];
1170  c = res[2];
1171  d = res[3];
1172  e = res[4];
1173  f = res[5];
1174  g = res[6];
1175  h = res[7];
1176 
1177  LOGF_DEBUG("Pos %d:%d:%d %d:%d:%d", a, b, c, e, f, g);
1178 
1179  double t1, t2, t3;
1180 
1181  t1 = c;
1182  t2 = b;
1183  t3 = a;
1184  t1 = t1 / 3600.0;
1185  t2 = t2 / 60.0;
1186  lat = t1 + t2 + t3;
1187 
1188  t1 = g;
1189  t2 = f;
1190  t3 = e;
1191  t1 = t1 / 3600.0;
1192  t2 = t2 / 60.0;
1193  lon = t1 + t2 + t3;
1194 
1195  if (d == 1)
1196  lat = lat * -1;
1197  if (h == 1)
1198  lon = 360 - lon;
1199  LocationN[LOCATION_LATITUDE].value = lat;
1200  LocationN[LOCATION_LONGITUDE].value = lon;
1201  IDSetNumber(&LocationNP, nullptr);
1202 
1203  saveConfig(true, "GEOGRAPHIC_COORD");
1204 
1205  char LongitudeStr[32] = {0}, LatitudeStr[32] = {0};
1206  fs_sexa(LongitudeStr, lon, 2, 3600);
1207  fs_sexa(LatitudeStr, lat, 2, 3600);
1208  LOGF_INFO("Mount Longitude %s Latitude %s", LongitudeStr, LatitudeStr);
1209 
1210  // We dont need to keep reading this one on every cycle
1211  // only need to read it when it's been changed
1212  ReadLatLong = false;
1213  return true;
1214  }
1215  else
1216  {
1217  LOG_INFO("Mount does not support setting location.");
1218  }
1219  }
1220  return false;
1221 }
1222 
1223 bool SynscanLegacyDriver::updateTime(ln_date *utc, double utc_offset)
1224 {
1225  char res[MAX_SYN_BUF] = {0};
1226  int bytesWritten = 0, bytesRead = 0;
1227 
1228  // start by formatting a time for the hand controller
1229  // we are going to set controller to local time
1230  struct ln_zonedate ltm;
1231 
1232  ln_date_to_zonedate(utc, &ltm, (long)utc_offset * 3600.0);
1233 
1234  int yr = ltm.years;
1235 
1236  yr = yr % 100;
1237 
1238  res[0] = 'H';
1239  res[1] = ltm.hours;
1240  res[2] = ltm.minutes;
1241  res[3] = (char)(int)ltm.seconds;
1242  res[4] = ltm.months;
1243  res[5] = ltm.days;
1244  res[6] = yr;
1245  // Strangely enough static_cast<int>(double) results 0 for negative values on arm
1246  // We need to use old C-like casts in this case.
1247  res[7] = (char)(int)utc_offset; // offset from utc so hand controller is running in local time
1248  res[8] = 0; // and no daylight savings adjustments, it's already included in the offset
1249  // lets write a time to the hand controller
1250  bytesRead = 0;
1251 
1252  LOGF_INFO("Setting mount date/time to %04d-%02d-%02d %d:%02d:%02d UTC Offset: %d",
1253  ltm.years, ltm.months, ltm.days, ltm.hours, ltm.minutes, ltm.seconds, utc_offset);
1254 
1255  if (isSimulation())
1256  return true;
1257 
1258  LOGF_DEBUG("CMD <%s>", res);
1259  tty_write(PortFD, res, 9, &bytesWritten);
1260 
1261  tty_read(PortFD, res, 1, 2, &bytesRead);
1262  LOGF_DEBUG("RES <%c>", res[0]);
1263 
1264  if (res[0] != '#')
1265  {
1266  LOG_INFO("Invalid return from set time");
1267  }
1268  return true;
1269 }
1270 
1271 bool SynscanLegacyDriver::updateLocation(double latitude, double longitude, double elevation)
1272 {
1273  INDI_UNUSED(elevation);
1274 
1275  char res[MAX_SYN_BUF] = {0};
1276  int bytesWritten = 0, bytesRead = 0;
1277  int s = 0;
1278  bool IsWest = false;
1279  double tmp = 0;
1280 
1281  ln_lnlat_posn p1 { 0, 0 };
1282  lnh_lnlat_posn p2;
1283 
1284  if (isSimulation())
1285  {
1286  if (CurrentDEC == 0)
1287  {
1288  CurrentDEC = latitude > 0 ? 90 : -90;
1289  CurrentRA = get_local_sidereal_time(longitude);
1290  }
1291  return true;
1292  }
1293 
1294  if (!CanSetLocation)
1295  {
1296  return true;
1297  }
1298  else
1299  {
1300  if (longitude > 180)
1301  {
1302  p1.lng = 360.0 - longitude;
1303  IsWest = true;
1304  }
1305  else
1306  {
1307  p1.lng = longitude;
1308  }
1309  p1.lat = latitude;
1310  ln_lnlat_to_hlnlat(&p1, &p2);
1311  LOGF_INFO("Update location to latitude %d:%d:%1.2f longitude %d:%d:%1.2f",
1312  p2.lat.degrees, p2.lat.minutes, p2.lat.seconds, p2.lng.degrees, p2.lng.minutes, p2.lng.seconds);
1313 
1314  res[0] = 'W';
1315  res[1] = p2.lat.degrees;
1316  res[2] = p2.lat.minutes;
1317  tmp = p2.lat.seconds + 0.5;
1318  s = (int)tmp; // put in an int that's rounded
1319  res[3] = s;
1320  if (p2.lat.neg == 0)
1321  {
1322  res[4] = 0;
1323  }
1324  else
1325  {
1326  res[4] = 1;
1327  }
1328 
1329  res[5] = p2.lng.degrees;
1330  res[6] = p2.lng.minutes;
1331  s = (int)(p2.lng.seconds + 0.5); // make an int, that's rounded
1332  res[7] = s;
1333  if (IsWest)
1334  res[8] = 1;
1335  else
1336  res[8] = 0;
1337  // All formatted, now send to the hand controller;
1338  bytesRead = 0;
1339 
1340  LOGF_DEBUG("CMD <%s>", res);
1341  tty_write(PortFD, res, 9, &bytesWritten);
1342 
1343  tty_read(PortFD, res, 1, 2, &bytesRead);
1344  LOGF_DEBUG("RES <%c>", res[0]);
1345 
1346  if (res[0] != '#')
1347  {
1348  LOG_INFO("Invalid response for location setting");
1349  }
1350  // want to read it on the next cycle, so we update the fields in the client
1351  ReadLatLong = true;
1352 
1353  return true;
1354  }
1355 }
1356 
1357 bool SynscanLegacyDriver::Sync(double ra, double dec)
1358 {
1359  /*
1360  * Frank Liu, R&D Engineer for Skywatcher, says to only issue a Sync
1361  * command, and not to use the Position Reset command, when syncing. I
1362  * removed the position reset code for EQ mounts, but left it in for
1363  * Alt/Az mounts, since it seems to be working, at least for the person
1364  * (@kecsap) who put it in there in the first place. :)
1365  *
1366  * The code prior to kecsap's recent fix would always send a position
1367  * reset command, but it would send Alt/Az coordinates, even to an EQ
1368  * mount. This would really screw up EQ mount alignment.
1369  *
1370  * The reason a lone Sync command appeared to not work before, is because
1371  * it will only accept a Sync command if the offset is relatively small,
1372  * within 6-7 degrees or so. So you must already have done an alignment
1373  * through the handset (a 1-star alignment would suffice), and only use
1374  * the Sync command to "touch-up" the alignment. You can't take a scope,
1375  * power it on, point it to a random place in the sky, do a plate-solve,
1376  * and sync. That won't work.
1377  */
1378 
1379  bool IsTrackingBeforeSync = (TrackState == SCOPE_TRACKING);
1380 
1381  // Abort any motion before syncing
1382  Abort();
1383 
1384  LOGF_INFO("Sync JNow %g %g -> %g %g", CurrentRA, CurrentDEC, ra, dec);
1385  char res[MAX_SYN_BUF] = {0};
1386  int bytesWritten, bytesRead;
1387 
1388  if (isSimulation())
1389  {
1390  CurrentRA = ra;
1391  CurrentDEC = dec;
1392  return true;
1393  }
1394 
1395  // Alt/Az sync mode
1396  if (MountCode >= 128)
1397  {
1398  INDI::IHorizontalCoordinates TargetAltAz { 0, 0 };
1399  INDI::IEquatorialCoordinates epochPos {ra, dec};
1400  INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &TargetAltAz);
1401  LOGF_DEBUG("Sync - ra: %g de: %g to az: %g alt: %g", ra, dec, TargetAltAz.azimuth, TargetAltAz.altitude);
1402  // Assemble the Reset Position command for Az axis
1403  int Az = (int)(TargetAltAz.azimuth * 16777216 / 360);
1404 
1405  res[0] = 'P';
1406  res[1] = 4;
1407  res[2] = 16;
1408  res[3] = 4;
1409  *reinterpret_cast<unsigned char*>(&res[4]) = (unsigned char)(Az / 65536);
1410  Az -= (Az / 65536) * 65536;
1411  *reinterpret_cast<unsigned char*>(&res[5]) = (unsigned char)(Az / 256);
1412  Az -= (Az / 256) * 256;
1413  *reinterpret_cast<unsigned char*>(&res[6]) = (unsigned char)Az;
1414  res[7] = 0;
1415  tty_write(PortFD, res, 8, &bytesWritten);
1416  tty_read(PortFD, res, 1, 3, &bytesRead);
1417  // Assemble the Reset Position command for Alt axis
1418  int Alt = (int)(TargetAltAz.altitude * 16777216 / 360);
1419 
1420  res[0] = 'P';
1421  res[1] = 4;
1422  res[2] = 17;
1423  res[3] = 4;
1424  *reinterpret_cast<unsigned char*>(&res[4]) = (unsigned char)(Alt / 65536);
1425  Alt -= (Alt / 65536) * 65536;
1426  *reinterpret_cast<unsigned char*>(&res[5]) = (unsigned char)(Alt / 256);
1427  Alt -= (Alt / 256) * 256;
1428  *reinterpret_cast<unsigned char*>(&res[6]) = (unsigned char)Alt;
1429  res[7] = 0;
1430  LOGF_DEBUG("CMD <%s>", res);
1431  tty_write(PortFD, res, 8, &bytesWritten);
1432 
1433  tty_read(PortFD, res, 1, 2, &bytesRead);
1434  LOGF_DEBUG("CMD <%c>", res[0]);
1435  }
1436 
1437  INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1438 
1439  epochPos.rightascension = ra;
1440  epochPos.declination = dec;
1441 
1442  // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
1443  //ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
1444  INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
1445 
1446  // Pass the sync command to the handset
1447  int n1 = J2000Pos.rightascension * 0x1000000 / 24;
1448  int n2 = J2000Pos.declination * 0x1000000 / 360;
1449 
1450  n1 = n1 << 8;
1451  n2 = n2 << 8;
1452  snprintf(res, MAX_SYN_BUF, "s%08X,%08X", n1, n2);
1453  memset(&res[18], 0, 1);
1454 
1455  LOGF_DEBUG("CMD <%s>", res);
1456  tty_write(PortFD, res, 18, &bytesWritten);
1457 
1458  tty_read(PortFD, res, 1, 60, &bytesRead);
1459  LOGF_DEBUG("RES <%c>", res[0]);
1460 
1461  if (bytesRead != 1 || res[0] != '#')
1462  {
1463  LOG_DEBUG("Timeout waiting for scope to complete syncing.");
1464  return false;
1465  }
1466 
1467  // Start tracking again
1468  if (IsTrackingBeforeSync)
1469  StartTrackMode();
1470 
1471  return true;
1472 }
1473 
1474 void SynscanLegacyDriver::UpdateMountInformation(bool inform_client)
1475 {
1476  bool BasicMountInfoHasChanged = false;
1477  std::string MountCodeStr = std::to_string(MountCode);
1478 
1479  if (std::string(BasicMountInfoT[MI_FW_VERSION].text) != HandsetFwVersion)
1480  {
1481  IUSaveText(&BasicMountInfoT[MI_FW_VERSION], HandsetFwVersion.c_str());
1482  BasicMountInfoHasChanged = true;
1483  }
1484  if (std::string(BasicMountInfoT[MI_MOUNT_CODE].text) != MountCodeStr)
1485  {
1486  IUSaveText(&BasicMountInfoT[MI_MOUNT_CODE], MountCodeStr.c_str());
1487  BasicMountInfoHasChanged = true;
1488  }
1489 
1490  if (std::string(BasicMountInfoT[MI_ALIGN_STATUS].text) != AlignmentStatus)
1491  {
1492  IUSaveText(&BasicMountInfoT[MI_ALIGN_STATUS], AlignmentStatus.c_str());
1493  BasicMountInfoHasChanged = true;
1494  }
1495  if (std::string(BasicMountInfoT[MI_GOTO_STATUS].text) != GotoStatus)
1496  {
1497  IUSaveText(&BasicMountInfoT[MI_GOTO_STATUS], GotoStatus.c_str());
1498  BasicMountInfoHasChanged = true;
1499  }
1500  if (std::string(BasicMountInfoT[MI_POINT_STATUS].text) != PointingStatus)
1501  {
1502  IUSaveText(&BasicMountInfoT[MI_POINT_STATUS], PointingStatus.c_str());
1503  BasicMountInfoHasChanged = true;
1504  }
1505  if (std::string(BasicMountInfoT[MI_TRACK_MODE].text) != TrackingMode)
1506  {
1507  IUSaveText(&BasicMountInfoT[MI_TRACK_MODE], TrackingMode.c_str());
1508  BasicMountInfoHasChanged = true;
1509  }
1510 
1511  if (BasicMountInfoHasChanged && inform_client)
1512  IDSetText(&BasicMountInfoTP, nullptr);
1513 }
1514 
1515 void SynscanLegacyDriver::MountSim()
1516 {
1517  static struct timeval ltv;
1518  struct timeval tv;
1519  double dt, da, dx;
1520  int nlocked;
1521 
1522  /* update elapsed time since last poll, don't presume exactly POLLMS */
1523  gettimeofday(&tv, nullptr);
1524 
1525  if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1526  ltv = tv;
1527 
1528  dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1529  ltv = tv;
1530  double currentSlewRate = SLEW_RATE[IUFindOnSwitchIndex(&SlewRateSP)] * TRACKRATE_SIDEREAL / 3600.0;
1531  da = currentSlewRate * dt;
1532 
1533  /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */
1534  switch (TrackState)
1535  {
1536  case SCOPE_IDLE:
1537  CurrentRA += (TrackRateN[AXIS_RA].value / 3600.0 * dt) / 15.0;
1539  break;
1540 
1541  case SCOPE_TRACKING:
1542  break;
1543 
1544  case SCOPE_SLEWING:
1545  case SCOPE_PARKING:
1546  /* slewing - nail it when both within one pulse @ SLEWRATE */
1547  nlocked = 0;
1548 
1549  dx = TargetRA - CurrentRA;
1550 
1551  // Take shortest path
1552  if (fabs(dx) > 12)
1553  dx *= -1;
1554 
1555  if (fabs(dx) <= da)
1556  {
1557  CurrentRA = TargetRA;
1558  nlocked++;
1559  }
1560  else if (dx > 0)
1561  CurrentRA += da / 15.;
1562  else
1563  CurrentRA -= da / 15.;
1564 
1565  if (CurrentRA < 0)
1566  CurrentRA += 24;
1567  else if (CurrentRA > 24)
1568  CurrentRA -= 24;
1569 
1570  dx = TargetDEC - CurrentDEC;
1571  if (fabs(dx) <= da)
1572  {
1574  nlocked++;
1575  }
1576  else if (dx > 0)
1577  CurrentDEC += da;
1578  else
1579  CurrentDEC -= da;
1580 
1581  if (nlocked == 2)
1582  {
1583  if (TrackState == SCOPE_SLEWING)
1585  else
1587  }
1588 
1589  break;
1590 
1591  default:
1592  break;
1593  }
1594 
1596 }
#define SLEW_RATE
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
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()
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
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.
INumberVectorProperty LocationNP
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty TimeTP
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
void setTelescopeConnection(const uint8_t &value)
setTelescopeConnection Set telescope connection mode. Child class should call this in the constructor...
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....
Connection::TCP * tcpConnection
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
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
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 ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
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 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 Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual const char * getDefaultName() override
int HexStrToInteger(const std::string &str)
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool Park() override
Park the telescope to its home position.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RO
Definition: indiapi.h:184
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
#define MAXINDILABEL
Definition: indiapi.h:192
#define MAXINDINAME
Definition: indiapi.h:191
@ AXIS_RA
Definition: indibasetypes.h:35
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 fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
Implementations for common driver routines.
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
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 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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
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 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
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 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
__le16 type
Definition: pwc-ioctl.h:0
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:250
#define SYNSCAN_SLEW_RATES