Instrument Neutral Distributed Interface INDI  2.0.2
lx200_10micron.cpp
Go to the documentation of this file.
1 /*
2  10micron INDI driver
3  GM1000HPS GM2000QCI GM2000HPS GM3000HPS GM4000QCI GM4000HPS AZ2000
4  Mount Command Protocol 2.14.11
5 
6  Copyright (C) 2017-2020 Hans Lambermont
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Lesser General Public
10  License as published by the Free Software Foundation; either
11  version 2.1 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public
19  License along with this library; if not, write to the Free Software
20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 
30 #include "lx200_10micron.h"
31 #include "indicom.h"
32 #include "lx200driver.h"
33 
34 #include <cstring>
35 #include <strings.h>
36 #include <termios.h>
37 #include <math.h>
38 #include <libnova/libnova.h>
39 
40 #define PRODUCT_TAB "Product"
41 #define ALIGNMENT_TAB "Alignment"
42 #define LX200_TIMEOUT 5 /* FD timeout in seconds */
43 
44 // INDI Number and Text names
45 #define REFRACTION_MODEL_TEMPERATURE "REFRACTION_MODEL_TEMPERATURE"
46 #define REFRACTION_MODEL_PRESSURE "REFRACTION_MODEL_PRESSURE"
47 #define MODEL_COUNT "MODEL_COUNT"
48 #define ALIGNMENT_POINTS "ALIGNMENT_POINTS"
49 #define ALIGNMENT_STATE "Alignment"
50 #define MINIMAL_NEW_ALIGNMENT_POINT_RO "MINIMAL_NEW_ALIGNMENT_POINT_RO"
51 #define MINIMAL_NEW_ALIGNMENT_POINT "MINIMAL_NEW_ALIGNMENT_POINT"
52 #define NEW_ALIGNMENT_POINT "NEW_ALIGNMENT_POINT"
53 #define NEW_ALIGNMENT_POINTS "NEW_ALIGNMENT_POINTS"
54 #define NEW_MODEL_NAME "NEW_MODEL_NAME"
55 #define PRODUCT_INFO "PRODUCT_INFO"
56 #define TLE_TEXT "TLE_TEXT"
57 #define TLE_NUMBER "TLE_NUMBER"
58 #define TRAJECTORY_TIME "TRAJECTORY_TIME"
59 #define SAT_TRACKING_STAT "SAT_TRACKING_STAT"
60 #define UNATTENDED_FLIP "UNATTENDED_FLIP"
61 
63 {
65 
78  4
79  );
80 
81  setVersion(1, 1);
82 }
83 
84 // Called by INDI::DefaultDevice::ISGetProperties
85 // Note that getDriverName calls ::getDefaultName which returns LX200 Generic
87 {
88  return "10micron";
89 }
90 
91 // Called by INDI::Telescope::callHandshake, either TCP Connect or Serial Port Connect
93 {
94  fd = PortFD;
95 
96  if (isSimulation() == true)
97  {
98  LOG_INFO("Simulate Connect.");
99  return true;
100  }
101 
102  // Set Ultra Precision Mode #:U2# , replies like 15:58:19.49 instead of 15:21.2
103  LOG_INFO("Setting Ultra Precision Mode.");
104  // #:U2#
105  // Set ultra precision mode. In ultra precision mode, extra decimal digits are returned for
106  // some commands, and there is no more difference between different emulation modes.
107  // Returns: nothing
108  // Available from version 2.10.
109  if (setCommandInt(fd, 2, "#:U") < 0)
110  {
111  LOG_ERROR("Failed to set Ultra Precision Mode.");
112  return false;
113  }
114 
115  return true;
116 }
117 
118 // Called only once by DefaultDevice::ISGetProperties
119 // Initialize basic properties that are required all the time
121 {
122  const bool result = LX200Generic::initProperties();
123  if (result)
124  {
125  // TODO initialize properties additional to INDI::Telescope
126  IUFillSwitch(&UnattendedFlipS[UNATTENDED_FLIP_DISABLED], "Disabled", "Disabled", ISS_ON);
129  UNATTENDED_FLIP, "Unattended Flip", MOTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
130 
131  IUFillNumber(&RefractionModelTemperatureN[0], "TEMPERATURE", "Celsius", "%+6.1f", -999.9, 999.9, 0, 0.);
134 
135  IUFillNumber(&RefractionModelPressureN[0], "PRESSURE", "hPa", "%6.1f", 0.0, 9999.9, 0, 0.);
138 
139  IUFillNumber(&ModelCountN[0], "COUNT", "#", "%.0f", 0, 999, 0, 0);
141  MODEL_COUNT, "Models", ALIGNMENT_TAB, IP_RO, 60, IPS_IDLE);
142 
143  IUFillNumber(&AlignmentPointsN[0], "COUNT", "#", "%.0f", 0, 100, 0, 0);
145  ALIGNMENT_POINTS, "Points", ALIGNMENT_TAB, IP_RO, 60, IPS_IDLE);
146 
147  IUFillSwitch(&AlignmentStateS[ALIGN_IDLE], "Idle", "Idle", ISS_ON);
148  IUFillSwitch(&AlignmentStateS[ALIGN_START], "Start", "Start new model", ISS_OFF);
149  IUFillSwitch(&AlignmentStateS[ALIGN_END], "End", "End new model", ISS_OFF);
150  IUFillSwitch(&AlignmentStateS[ALIGN_DELETE_CURRENT], "Del", "Delete current model", ISS_OFF);
153 
154  IUFillNumber(&MiniNewAlpRON[MALPRO_MRA], "MRA", "Mount RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
155  IUFillNumber(&MiniNewAlpRON[MALPRO_MDEC], "MDEC", "Mount DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
156  IUFillNumber(&MiniNewAlpRON[MALPRO_MSIDE], "MSIDE", "Pier Side (0=E 1=W)", "%.0f", 0, 1, 0, 0);
157  IUFillNumber(&MiniNewAlpRON[MALPRO_SIDTIME], "SIDTIME", "Sidereal Time (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
160 
161  IUFillNumber(&MiniNewAlpN[MALP_PRA], "PRA", "Solved RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
162  IUFillNumber(&MiniNewAlpN[MALP_PDEC], "PDEC", "Solved DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
165 
166  IUFillNumber(&NewAlpN[ALP_MRA], "MRA", "Mount RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
167  IUFillNumber(&NewAlpN[ALP_MDEC], "MDEC", "Mount DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
168  IUFillNumber(&NewAlpN[ALP_MSIDE], "MSIDE", "Pier Side (0=E 1=W)", "%.0f", 0, 1, 0, 0);
169  IUFillNumber(&NewAlpN[ALP_SIDTIME], "SIDTIME", "Sidereal Time (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
170  IUFillNumber(&NewAlpN[ALP_PRA], "PRA", "Solved RA (hh:mm:ss)", "%010.6m", 0, 24, 0, 0);
171  IUFillNumber(&NewAlpN[ALP_PDEC], "PDEC", "Solved DEC (dd:mm:ss)", "%010.6m", -90, 90, 0, 0);
173  NEW_ALIGNMENT_POINT, "New Point", ALIGNMENT_TAB, IP_RW, 60, IPS_IDLE);
174 
175  IUFillNumber(&NewAlignmentPointsN[0], "COUNT", "#", "%.0f", 0, 100, 1, 0);
177  NEW_ALIGNMENT_POINTS, "New Points", ALIGNMENT_TAB, IP_RO, 60, IPS_IDLE);
178 
179  IUFillText(&NewModelNameT[0], "NAME", "Model Name", "newmodel");
181  NEW_MODEL_NAME, "New Name", ALIGNMENT_TAB, IP_RW, 60, IPS_IDLE);
182 
183  IUFillNumber(&TLEfromDatabaseN[0], "NUMBER", "#", "%.0f", 1, 999, 1, 1);
185  "TLE_NUMBER", "Database TLE ", SATELLITE_TAB, IP_RW, 60, IPS_IDLE);
186  }
187  return result;
188 }
189 
191 {
194  return true;
195 }
196 
197 // Called by INDI::Telescope when connected state changes to add/remove properties
199 {
200  bool result = LX200Generic::updateProperties();
201 
202  if (isConnected())
203  {
205  // getMountInfo defines ProductTP
217 
218  // read UnAttendedFlip setting from config and apply if available
219  int readit = 0;
220  for (int i = 0; readit == 0 && i < UnattendedFlipSP.nsp; i++)
221  {
223  }
224  if (readit == 0)
225  {
227  {
228  LOGF_INFO("Unattended Flip from config and mount are %s",
229  (UnattendedFlipS[UNATTENDED_FLIP_ENABLED].s == ISS_ON) ? "enabled" : "disabled");
230  }
231  else
232  {
233  LOGF_INFO("Read Unattended Flip %s from config while mount has %s, updating mount",
234  (UnattendedFlipS[UNATTENDED_FLIP_ENABLED].s == ISS_ON) ? "enabled" : "disabled",
235  (UnattendedFlip == UNATTENDED_FLIP_ENABLED) ? "enabled" : "disabled");
237  }
238  }
239  else
240  {
241  LOG_INFO("Did not find an Unattended Flip setting in the config file. Specify desired behaviour in Motion Control tab and save config in Options tab.");
242  }
243  }
244  else
245  {
259  }
260 
261  return result;
262 }
263 
264 // Called by LX200Generic::updateProperties
266 {
267  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
268 
269  // cannot call LX200Generic::getBasicData(); as getTimeFormat :Gc# (and getSiteName :GM#) are not implemented on 10Micron
270  if (!isSimulation())
271  {
272  getMountInfo();
273 
274  getAlignment();
277 
278  if (getTrackFreq(PortFD, &TrackFreqN[0].value) < 0)
279  {
280  LOG_WARN("Failed to get tracking frequency from device.");
281  }
282  else
283  {
284  LOGF_INFO("Tracking frequency is %.1f Hz", TrackFreqN[0].value);
285  IDSetNumber(&TrackFreqNP, nullptr);
286  }
287 
288  char RefractionModelTemperature[80];
289  getCommandString(PortFD, RefractionModelTemperature, "#:GRTMP#");
290  float rmtemp;
291  sscanf(RefractionModelTemperature, "%f#", &rmtemp);
292  RefractionModelTemperatureN[0].value = rmtemp;
293  LOGF_INFO("RefractionModelTemperature is %0+6.1f degrees C", RefractionModelTemperatureN[0].value);
295 
296  char RefractionModelPressure[80];
297  getCommandString(PortFD, RefractionModelPressure, "#:GRPRS#");
298  float rmpres;
299  sscanf(RefractionModelPressure, "%f#", &rmpres);
300  RefractionModelPressureN[0].value = rmpres;
301  LOGF_INFO("RefractionModelPressure is %06.1f hPa", RefractionModelPressureN[0].value);
303 
304  int ModelCount;
305  getCommandInt(PortFD, &ModelCount, "#:modelcnt#");
306  ModelCountN[0].value = (double) ModelCount;
307  LOGF_INFO("%d Alignment Models", static_cast<int>(ModelCountN[0].value));
308  IDSetNumber(&ModelCountNP, nullptr);
309 
310  int AlignmentPoints;
311  getCommandInt(PortFD, &AlignmentPoints, "#:getalst#");
312  AlignmentPointsN[0].value = AlignmentPoints;
313  LOGF_INFO("%d Alignment Stars in active model", static_cast<int>(AlignmentPointsN[0].value));
314  IDSetNumber(&AlignmentPointsNP, nullptr);
315 
316  if (false == getUnattendedFlipSetting())
317  {
320  LOG_INFO("Unattended Flip is disabled.");
321  }
322  else
323  {
326  LOG_INFO("Unattended Flip is enabled.");
327  }
329  IDSetSwitch(&UnattendedFlipSP, nullptr);
330  }
331 
333  {
334  LOG_INFO("sendLocationOnStartup is enabled, call sendScopeLocation.");
336  }
337  else
338  {
339  LOG_INFO("sendLocationOnStartup is disabled, do not call sendScopeLocation.");
340  }
341  if (sendTimeOnStartup)
342  {
343  LOG_INFO("sendTimeOnStartup is enabled, call sendScopeTime.");
344  sendScopeTime();
345  }
346  else
347  {
348  LOG_INFO("sendTimeOnStartup is disabled, do not call sendScopeTime.");
349  }
350 }
351 
352 // Called by our getBasicData
353 bool LX200_10MICRON::getMountInfo()
354 {
355  char ProductName[80];
356  getCommandString(PortFD, ProductName, "#:GVP#");
357  char ControlBox[80];
358  getCommandString(PortFD, ControlBox, "#:GVZ#");
359  char FirmwareVersion[80];
360  getCommandString(PortFD, FirmwareVersion, "#:GVN#");
361  char FirmwareDate1[80];
362  getCommandString(PortFD, FirmwareDate1, "#:GVD#");
363  char FirmwareDate2[80];
364  char mon[4];
365  int dd, yyyy;
366  sscanf(FirmwareDate1, "%s %02d %04d", mon, &dd, &yyyy);
367  getCommandString(PortFD, FirmwareDate2, "#:GVT#");
368  char FirmwareDate[80];
369  snprintf(FirmwareDate, 80, "%04d-%02d-%02dT%s", yyyy, monthToNumber(mon), dd, FirmwareDate2);
370 
371  LOGF_INFO("Product:%s Control box:%s Firmware:%s of %s", ProductName, ControlBox, FirmwareVersion, FirmwareDate);
372 
373  IUFillText(&ProductT[PRODUCT_NAME], "NAME", "Product Name", ProductName);
374  IUFillText(&ProductT[PRODUCT_CONTROL_BOX], "CONTROL_BOX", "Control Box", ControlBox);
375  IUFillText(&ProductT[PRODUCT_FIRMWARE_VERSION], "FIRMWARE_VERSION", "Firmware Version", FirmwareVersion);
376  IUFillText(&ProductT[PRODUCT_FIRMWARE_DATE], "FIRMWARE_DATE", "Firmware Date", FirmwareDate);
378  PRODUCT_INFO, "Product", PRODUCT_TAB, IP_RO, 60, IPS_IDLE);
379 
381 
382  return true;
383 }
384 
385 // INDI::Telescope calls ReadScopeStatus() every POLLMS to check the link to the telescope and update its state and position.
386 // The child class should call newRaDec() whenever a new value is read from the telescope.
388 {
389  if (!isConnected())
390  {
391  return false;
392  }
393  if (isSimulation())
394  {
395  mountSim();
396  return true;
397  }
398 
399  // Read scope status, based loosely on LX200_GENERIC::getCommandString
400  // #:Ginfo#
401  // Get multiple information. Returns a string where multiple data are encoded, separated
402  // by commas ',', and terminated by '#'. Data are recognized by their position in the string.
403  // The data are the following:
404  // Position Datum
405  // 1 The telescope right ascension in hours and decimals (from 000.00000 to 23.99999),
406  // true equinox and equator of date of observation (i.e. Jnow).
407  // 2 The telescope declination in degrees and decimals (from –90.0000 to +90.0000),
408  // true equinox and equator of date of observation (i.e. Jnow).
409  // 3 A flag indicating the side of the pier on which the telescope is currently positioned
410  // ("E" or "W").
411  // 4 The telescope azimuth in degrees and decimals (from 000.0000 to 359.9999).
412  // 5 The telescope altitude in degrees and decimals (from -90.0000 to +90.0000).
413  // 6 The julian date (JJJJJJJ.JJJJJJJJ), UTC, with leap second flag (see command
414  // :GJD2# for the description of this datum).
415  // 7 A number encoding the status of the mount as in the :Gstat command.
416  // 8 A number returning the slew status (0 if :D# would return no slew, 1 otherwise).
417  // The string is terminated by '#'. Other parameters may be added in future at the end of
418  // the string: do not assume that the number of parameters will stay the same.
419  // Available from version 2.14.9 (previous versions may have this command but it was
420  // experimental and possibly with a different format).
421  char cmd[] = "#:Ginfo#";
422  char data[80];
423  char *term;
424  int error_type;
425  int nbytes_write = 0, nbytes_read = 0;
426  // DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", cmd);
427  if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK)
428  {
429  return false;
430  }
431  error_type = tty_read_section(fd, data, '#', LX200_TIMEOUT, &nbytes_read);
432  tcflush(fd, TCIFLUSH);
433  if (error_type != TTY_OK)
434  {
435  return false;
436  }
437  term = strchr(data, '#');
438  if (term)
439  {
440  *(term + 1) = '\0';
441  }
442  else
443  {
444  return false;
445  }
446  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> RES <%s>", cmd, data);
447 
448  // TODO: check if this needs changing when satellite tracking
449  // Now parse the data. This format may consist of more parts some day
450  nbytes_read = sscanf(data, "%g,%g,%c,%g,%g,%g,%d,%d#", &Ginfo.RA_JNOW, &Ginfo.DEC_JNOW, &Ginfo.SideOfPier,
451  &Ginfo.AZ, &Ginfo.ALT, &Ginfo.Jdate, &Ginfo.Gstat, &Ginfo.SlewStatus);
452  if (nbytes_read < 0)
453  {
454  return false;
455  }
456 
457  if (Ginfo.Gstat != OldGstat)
458  {
459  if (OldGstat != GSTAT_UNSET)
460  {
461  LOGF_INFO("Gstat changed from %d to %d", OldGstat, Ginfo.Gstat);
462  }
463  else
464  {
465  LOGF_INFO("Gstat initialized at %d", Ginfo.Gstat);
466  }
467  }
468  switch (Ginfo.Gstat)
469  {
470  case GSTAT_TRACKING:
472  break;
473  case GSTAT_STOPPED:
475  break;
476  case GSTAT_PARKING:
478  break;
479  case GSTAT_UNPARKING:
481  break;
484  break;
485  case GSTAT_PARKED:
487  if (!isParked())
488  SetParked(true);
489  break;
492  break;
495  break;
498  break;
501  break;
504  break;
505  case GSTAT_NEED_USEROK:
507  break;
510  break;
511  case GSTAT_ERROR:
513  break;
514  default:
515  return false;
516  }
517  setPierSide((toupper(Ginfo.SideOfPier) == 'E') ? INDI::Telescope::PIER_EAST : INDI::Telescope::PIER_WEST);
518 
519  OldGstat = Ginfo.Gstat;
520  NewRaDec(Ginfo.RA_JNOW, Ginfo.DEC_JNOW);
521 
522  // Update alignment Mini new alignment point Read-Only fields
523  char LocalSiderealTimeS[80];
524  getCommandString(fd, LocalSiderealTimeS, "#:GS#");
525  f_scansexa(LocalSiderealTimeS, &Ginfo.SiderealTime);
526  MiniNewAlpRON[MALPRO_MRA].value = Ginfo.RA_JNOW;
527  MiniNewAlpRON[MALPRO_MDEC].value = Ginfo.DEC_JNOW;
528  MiniNewAlpRON[MALPRO_MSIDE].value = (toupper(Ginfo.SideOfPier) == 'E') ? 0 : 1;
529  MiniNewAlpRON[MALPRO_SIDTIME].value = Ginfo.SiderealTime;
530  IDSetNumber(&MiniNewAlpRONP, nullptr);
531 
532  return true;
533 }
534 
536 {
537  // #:KA#
538  // Slew to park position
539  // Returns: nothing
540  LOG_INFO("Parking.");
541  if (setStandardProcedureWithoutRead(fd, "#:KA#") < 0)
542  {
543  ParkSP.s = IPS_ALERT;
544  IDSetSwitch(&ParkSP, "Park command failed.");
545  return false;
546  }
547 
548  ParkSP.s = IPS_BUSY;
550  IDSetSwitch(&ParkSP, nullptr);
551  // postpone SetParked(true) for ReadScopeStatus so that we know it is actually correct
552  return true;
553 }
554 
556 {
557  // #:PO#
558  // Unpark
559  // Returns:nothing
560  LOG_INFO("Unparking.");
561  if (setStandardProcedureWithoutRead(fd, "#:PO#") < 0)
562  {
563  ParkSP.s = IPS_ALERT;
564  IDSetSwitch(&ParkSP, "Unpark command failed.");
565  return false;
566  }
567 
568  ParkSP.s = IPS_OK;
570  SetParked(false);
571  IDSetSwitch(&ParkSP, nullptr);
572  return true;
573 }
574 
576 {
577  // #:Guaf#
578  // Returns the unattended flip setting.
579  // Returns:
580  // 0 disabled
581  // 1 enabled
582  // Available from version 2.11.
583  // Note: unattended flip didn't work properly in firmware versions up to 2.13.8 included.
584  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
585  char guaf[80];
586  getCommandString(PortFD, guaf, "#:Guaf#");
587  if ('1' == guaf[0])
588  {
590  }
591  else
592  {
594  }
595  return '1' == guaf[0];
596 }
597 
599 {
600  // #:SuafN#
601  // Enables or disables the unattended flip. Use N=1 to enable, N=0 to disable. This is set always to 0 after power up.
602  // Returns: nothing
603  // Available from version 2.11.
604  // unattended flip didn't work properly in firmware versions up to 2.13.8 included.
605  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
606  char data[64];
607  snprintf(data, sizeof(data), "#:Suaf%d#", (setting == false) ? 0 : 1);
608  if (0 == setStandardProcedureWithoutRead(fd, data))
609  {
610  if (setting == false)
611  {
613  }
614  else
615  {
617  }
618  return true;
619  }
620  return false;
621 }
622 
624 {
625  // #:FLIP#
626  // This command acts in different ways on the AZ2000 and german equatorial (GM1000 – GM4000) mounts.
627  // On an AZ2000 mount: When observing an object near the lowest culmination, requests to make a 360° turn of the azimuth axis and point the object again.
628  // On a german equatorial mount: When observing an object near the meridian, requests to make a 180° turn of the RA axis and move the declination axis in order to
629  // point the object with the telescope on the other side of the mount.
630  // Returns:
631  // 1 if successful
632  // 0 if the movement cannot be done
633  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
634  char data[64];
635  snprintf(data, sizeof(data), "#:FLIP#");
636  return 0 == setStandardProcedureAndExpectChar(fd, data, "1");
637 }
638 
640 {
641  // #:CMCFGn#
642  // Configures the behaviour of the :CM# and :CMR# commands depending on the value
643  // of n. If n=0, :the commands :CM# and :CMR# work in the default mode, i.e. they
644  // synchronize the position ot the mount with the coordinates of the currently selected
645  // target by correcting the axis offset values. If n=1, the commands :CM# and :CMR#
646  // work by using the synchronization position as an additional alignment star for refining
647  // the alignment model.
648  // Returns:
649  // the string "0#" if the value 0 has been passed
650  // the string "1#" if the value 1 has been passed
651  // Available from version 2.8.15.
652  LOG_INFO("SyncConfig.");
653  if (setCommandInt(fd, cmcfg, "#:CMCFG") < 0)
654  {
655  return false;
656  }
657  return true;
658 }
659 
660 bool LX200_10MICRON::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
661 {
662  // #:SCYYYY-MM-DD#
663  // Set date to YYYY-MM-DD (year, month, day). The date is expressed in local time.
664  // Returns:
665  // 0 if the date is invalid
666  // The character "1" without additional strings in ultra-precision mode (regardless of
667  // emulation).
668  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "<%s>", __FUNCTION__);
669  char data[64];
670  snprintf(data, sizeof(data), ":SC%04d-%02d-%02d#", years, months, days);
671  return 0 == setStandardProcedureAndExpectChar(fd, data, "1");
672 }
673 
674 bool LX200_10MICRON::SetTLEtoFollow(const char *tle)
675 {
676  // #:TLEL0<two line element>#
677  // Loads satellite orbital elements in two-line format directly from the command protocol.
678  // <two line element> is a string containing the two line elements. Each lines can be
679  // terminated by escaped newline (ASCII code 10), carriage return (ASCII code 13) or a
680  // combination of both. The first line may contain the satellite name. The entire string is
681  // escaped with the mechanism described in the "escaped strings" section below.
682  // The TLE format is described here:
683  // https://www.celestrak.com/NORAD/documentation/tle-fmt.asp
684  // For example, loading the NOAA 14 element set of that page can be accomplished with:
685  // :TLEL0NOAA·14·················$0a
686  // 1·23455U·94089A···97320.90946019··.00000140··00000-0··10191-3·0··2621$0a
687  // 2·23455··99.0090·272.6745·0008546·223.1686·136.8816·14.11711747148495#
688  // Returns:
689  // E# invalid format
690  // V# valid format
691  // Available from version 2.13.20.
692  LOGF_INFO("The function is called with TLE %s", tle);
693  if (strlen(tle) > 230)
694  {
695  LOG_WARN("TLE is too long");
696  }
697 
698  std::string tle_str;
699  std::string sep = "$0a";
700  std::string search = "\n";
701  tle_str = (std::string) tle;
702  for( size_t pos = 0; ; pos += sep.length() )
703  {
704  // Locate the substring to replace
705  pos = tle_str.find( search, pos );
706  if( pos == std::string::npos ) break;
707  // Replace by erasing and inserting
708  tle_str.erase( pos, search.length() );
709  tle_str.insert( pos, sep );
710  }
711  char command[250];
712  snprintf(command, sizeof(command), ":TLEL0%s#", tle_str.c_str());
713 
714  if ( !isSimulation() )
715  {
716  LOG_INFO(command);
717  char response[2];
718  if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 2) )
719  {
720  LOG_ERROR("TLE set error");
721  return 1;
722  }
723  if (response[0] == 'E')
724  {
725  LOG_ERROR("Invalid formatting of TLE, trying to split:");
726  char *pch = strtok ((char*) tle, "\n");
727  while (pch != nullptr)
728  {
729  LOGF_INFO("%s\n", pch);
730  pch = strtok (nullptr, "\n");
731  }
732  return 1;
733  }
734  }
735  else
736  {
737  char *pch = strtok ((char*) tle, "\n");
738  while (pch != nullptr)
739  {
740  LOGF_INFO("%s\n", pch);
741  pch = strtok (nullptr, "\n");
742  }
743  }
744  return 0;
745 }
746 
748 {
749  // #:TLEDLn#
750  // Loads orbital elements for a satellite from the TLE database in the mount. n is the index of the
751  // orbital elements in the database, starting from 1 to the number returned by the TLEDN command.
752  // Returns:
753  // E# the mount database doesn't contain a TLE with the given index.
754  // <two line elements># an escaped string containing the TLE data from the mount
755  // database which has been loaded. Lines are terminated by ASCII newline (ASCII code 10).
756  // Available from version 2.13.20.
757  char command[12];
758  snprintf(command, sizeof(command), ":TLEDL%d#", tleN);
759 
760  LOG_INFO("Setting TLE from Database");
761  if ( !isSimulation() )
762  {
763  LOG_INFO(command);
764  char response[210];
765  if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 210) )
766  {
767  LOG_ERROR("TLE set error");
768  return 1;
769  }
770  if (response[0] == 'E')
771  {
772  LOG_ERROR("TLE number not in mount");
773  return 1;
774  }
775  }
776  return 0;
777 }
778 
779 bool LX200_10MICRON::CalculateSatTrajectory(std::string start_pass_isodatetime, std::string end_pass_isodatetime)
780 {
781  // #:TLEPJD,min#
782  // Precalulates the first transit of the satellite with the currently loaded orbital elements,
783  // starting from Julian Date JD and for a period of min minutes, where min is from 1 to 1440.
784  // Two-line elements have to be loaded with the :TLEL command.
785  // Returns:
786  // E# no TLE loaded or invalid command
787  // N# no passes in the given amount of time
788  // JDstart,JDend,flags# data for the first pass in the given interval. JDstart and JDend
789  // mark the beginning and the end of the given transit. Flags is a string which can be
790  // empty or contain the letter F – meaning that mount will flip during the transit.
791  // Available from version 2.13.20.
792  struct ln_date start_pass;
793  if (extractISOTime(start_pass_isodatetime.c_str(), &start_pass) == -1)
794  {
795  LOGF_ERROR("Date/Time is invalid: %s.", start_pass_isodatetime.c_str());
796  return 1;
797  }
798 
799  struct ln_date end_pass;
800  if (extractISOTime(end_pass_isodatetime.c_str(), &end_pass) == -1)
801  {
802  LOGF_ERROR("Date/Time is invalid: %s.", end_pass_isodatetime.c_str());
803  return 1;
804  }
805 
806  double JD_start;
807  double JD_end;
808  JD_start = ln_get_julian_day(&start_pass);
809  JD_end = ln_get_julian_day(&end_pass);
810  int nextPassInMinutes = static_cast<int>(ceil((JD_end - JD_start) * 24 * 60));
811  int nextPassinMinutesUpTo1440 = std::min(nextPassInMinutes, 1440);
812  int nextPassinMinutesBetween1and1440 = std::max(nextPassinMinutesUpTo1440, 1);
813 
814  char command[28];
815  snprintf(command, sizeof(command), ":TLEP%7.8f,%01d#", JD_start, nextPassinMinutesBetween1and1440);
816  LOGF_INFO("Julian day %7.8f", JD_start);
817  LOGF_INFO("For the next %01d minutes", nextPassinMinutesBetween1and1440);
818  LOGF_INFO("Command: %s", command);
819  if ( !isSimulation() )
820  {
821  LOG_INFO(command);
822  char response[36];
823  if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 36) )
824  {
825  LOG_ERROR("TLE calculate error");
826  return 1;
827  }
828  if (response[0] == 'E')
829  {
830  LOG_ERROR("TLE not loaded or invalid command");
831  return 1;
832  }
833  if (response[0] == 'N')
834  {
835  LOG_ERROR("No passes loaded");
836  return 1;
837  }
838  }
839  return 0;
840 }
841 
843 {
844  // #:TLES#
845  // Slews to the start of the satellite transit that has been precalculated with the :TLEP command.
846  // Returns:
847  // E# no transit has been precalculated
848  // F# slew failed due to mount parked or other status blocking slews
849  // V# slewing to the start of the transit, the mount will automatically start tracking the satellite.
850  // S# the transit has already started, slewing to catch the satellite
851  // Q# the transit has already ended, no slew occurs
852  // Available from version 2.13.20.
853  char command[7];
854  snprintf(command, sizeof(command), ":TLES#");
855  if ( !isSimulation() )
856  {
857  LOG_INFO(command);
858  char response[2];
859  if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 2) )
860  {
861  LOG_ERROR("TLE track error");
862  return 1;
863  }
864  if (response[0] == 'E')
865  {
866  LOG_ERROR("TLE transit not calculated");
867  return 2;
868  }
869  if (response[0] == 'F')
870  {
871  LOG_ERROR("Slew failed");
872  return 3;
873  }
874  if (response[0] == 'V')
875  {
876  LOG_INFO("Slewing to start of transit");
877  return 0;
878  }
879  if (response[0] == 'S')
880  {
881  LOG_INFO("Slewing to transiting satellite");
882  return 0;
883  }
884  if (response[0] == 'Q')
885  {
886  LOG_ERROR("Transit is already over");
887  return 4;
888  }
889  }
890  return 0;
891 }
892 
894 {
895  // #:SRTMPsTTT.T#
896  // Sets the temperature used in the refraction model to sTTT.T degrees Celsius (°C).
897  // Returns:
898  // 0 invalid
899  // 1 valid
900  // Available from version 2.3.0.
901  char data[16];
902  snprintf(data, 16, "#:SRTMP%0+6.1f#", temperature);
903  return setStandardProcedure(fd, data);
904 }
905 
907 {
908  // #:SRPRSPPPP.P#
909  // Sets the atmospheric pressure used in the refraction model to PPPP.P hPa. Note
910  // that this is the pressure at the location of the telescope, and not the pressure at sea level.
911  // Returns:
912  // 0 invalid
913  // 1 valid
914  // Available from version 2.3.0.
915  char data[16];
916  snprintf(data, 16, "#:SRPRS%06.1f#", pressure);
917  return setStandardProcedure(fd, data);
918 }
919 
920 int LX200_10MICRON::AddSyncPoint(double MRa, double MDec, double MSide, double PRa, double PDec, double SidTime)
921 {
922  // #:newalptMRA,MDEC,MSIDE,PRA,PDEC,SIDTIME#
923  // Add a new point to the alignment specification. The parameters are:
924  // MRA – the mount-reported right ascension, expressed as HH:MM:SS.S
925  // MDEC – the mount-reported declination, expressed as sDD:MM:SS
926  // MSIDE – the mount-reported pier side (the letter 'E' or 'W', as reported by the :pS# command)
927  // PRA – the plate-solved right ascension (i.e. the right ascension the telescope was
928  // effectively pointing to), expressed as HH:MM:SS.S
929  // PDEC – the plate-solved declination (i.e. the declination the telescope was effectively
930  // pointing to), expressed as sDD:MM:SS
931  // SIDTIME – the local sidereal time at the time of the measurement of the point,
932  // expressed as HH:MM:SS.S
933  // Returns:
934  // the string "nnn#" if the point is valid, where nnn is the current number of points in the
935  // alignment specification (including this one)
936  // the string "E#" if the point is not valid
937  // See also the paragraph Entering an alignment model.
938  // Available from version 2.8.15.
939  char MRa_str[32], MDec_str[32];
940  fs_sexa(MRa_str, MRa, 0, 36000);
941  fs_sexa(MDec_str, MDec, 0, 3600);
942 
943  char MSide_char;
944  (static_cast<int>(MSide) == 0) ? MSide_char = 'E' : MSide_char = 'W';
945 
946  char PRa_str[32], PDec_str[32];
947  fs_sexa(PRa_str, PRa, 0, 36000);
948  fs_sexa(PDec_str, PDec, 0, 3600);
949 
950  char SidTime_str[32];
951  fs_sexa(SidTime_str, SidTime, 0, 36000);
952 
953  char command[80];
954  snprintf(command, 80, "#:newalpt%s,%s,%c,%s,%s,%s#", MRa_str, MDec_str, MSide_char, PRa_str, PDec_str, SidTime_str);
955  LOGF_INFO("AddSyncPoint %s", command);
956 
957  char response[6];
958  if (0 != setStandardProcedureAndReturnResponse(fd, command, response, 5) || response[0] == 'E')
959  {
960  LOG_ERROR("AddSyncPoint error");
961  return 1;
962  }
963  response[4] = 0;
964  int points;
965  int nbytes_read = sscanf(response, "%3d#", &points);
966  if (nbytes_read < 0)
967  {
968  LOGF_ERROR("AddSyncPoint response error %d", nbytes_read);
969  return 1;
970  }
971  LOGF_INFO("AddSyncPoint responded [%4s], there are now %d new alignment points", response, points);
972  NewAlignmentPointsN[0].value = points;
974 
975  return 0;
976 }
977 
978 int LX200_10MICRON::AddSyncPointHere(double PRa, double PDec)
979 {
980  double MSide = (toupper(Ginfo.SideOfPier) == 'E') ? 0 : 1;
981  return AddSyncPoint(Ginfo.RA_JNOW, Ginfo.DEC_JNOW, MSide, PRa, PDec, Ginfo.SiderealTime);
982 }
983 
984 bool LX200_10MICRON::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
985 {
986  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
987  {
988  if (strcmp(name, REFRACTION_MODEL_TEMPERATURE) == 0)
989  {
990  IUUpdateNumber(&RefractionModelTemperatureNP, values, names, n);
992  {
993  LOG_ERROR("SetRefractionModelTemperature error");
996  return false;
997  }
1000  LOGF_INFO("RefractionModelTemperature set to %0+6.1f degrees C", RefractionModelTemperatureN[0].value);
1001  return true;
1002  }
1003  if (strcmp(name, REFRACTION_MODEL_PRESSURE) == 0)
1004  {
1005  IUUpdateNumber(&RefractionModelPressureNP, values, names, n);
1007  {
1008  LOG_ERROR("SetRefractionModelPressure error");
1011  return false;
1012  }
1015  LOGF_INFO("RefractionModelPressure set to %06.1f hPa", RefractionModelPressureN[0].value);
1016  return true;
1017  }
1018  if (strcmp(name, MODEL_COUNT) == 0)
1019  {
1020  IUUpdateNumber(&ModelCountNP, values, names, n);
1021  ModelCountNP.s = IPS_OK;
1022  IDSetNumber(&ModelCountNP, nullptr);
1023  LOGF_INFO("ModelCount %d", ModelCountN[0].value);
1024  return true;
1025  }
1026  if (strcmp(name, MINIMAL_NEW_ALIGNMENT_POINT_RO) == 0)
1027  {
1028  IUUpdateNumber(&MiniNewAlpNP, values, names, n);
1030  IDSetNumber(&MiniNewAlpRONP, nullptr);
1031  return true;
1032  }
1033  if (strcmp(name, MINIMAL_NEW_ALIGNMENT_POINT) == 0)
1034  {
1035  if (AlignmentState != ALIGN_START)
1036  {
1037  LOG_ERROR("Cannot add alignment points yet, need to start a new alignment first");
1038  return false;
1039  }
1040 
1041  IUUpdateNumber(&MiniNewAlpNP, values, names, n);
1042  if (0 != AddSyncPointHere(MiniNewAlpN[MALP_PRA].value, MiniNewAlpN[MALP_PDEC].value))
1043  {
1044  LOG_ERROR("AddSyncPointHere error");
1046  IDSetNumber(&MiniNewAlpNP, nullptr);
1047  return false;
1048  }
1049  MiniNewAlpNP.s = IPS_OK;
1050  IDSetNumber(&MiniNewAlpNP, nullptr);
1051  return true;
1052  }
1053  if (strcmp(name, NEW_ALIGNMENT_POINT) == 0)
1054  {
1055  if (AlignmentState != ALIGN_START)
1056  {
1057  LOG_ERROR("Cannot add alignment points yet, need to start a new alignment first");
1058  return false;
1059  }
1060 
1061  IUUpdateNumber(&NewAlpNP, values, names, n);
1062  if (0 != AddSyncPoint(NewAlpN[ALP_MRA].value, NewAlpN[ALP_MDEC].value, NewAlpN[ALP_MSIDE].value,
1063  NewAlpN[ALP_PRA].value, NewAlpN[ALP_PDEC].value, NewAlpN[ALP_SIDTIME].value))
1064  {
1065  LOG_ERROR("AddSyncPoint error");
1066  NewAlpNP.s = IPS_ALERT;
1067  IDSetNumber(&NewAlpNP, nullptr);
1068  return false;
1069  }
1070  NewAlpNP.s = IPS_OK;
1071  IDSetNumber(&NewAlpNP, nullptr);
1072  return true;
1073  }
1074  if (strcmp(name, NEW_ALIGNMENT_POINTS) == 0)
1075  {
1076  IUUpdateNumber(&NewAlignmentPointsNP, values, names, n);
1078  IDSetNumber(&NewAlignmentPointsNP, nullptr);
1079  LOGF_INFO("New unnamed Model now has %d alignment points", NewAlignmentPointsN[0].value);
1080  return true;
1081  }
1082  if (strcmp(name, "TLE_NUMBER") == 0)
1083  {
1084  LOG_INFO("I am trying to set from Database");
1085 
1086  IUUpdateNumber(&TLEfromDatabaseNP, values, names, n);
1087  if ( 0 != SetTLEfromDatabase(TLEfromDatabaseN[0].value) )
1088  {
1090  IDSetNumber(&TLEfromDatabaseNP, nullptr);
1091  return false;
1092  }
1095  IDSetText(&TLEtoTrackTP, nullptr);
1096  IDSetNumber(&TLEfromDatabaseNP, nullptr);
1097  LOGF_INFO("Selected TLE nr %.0f from database", TLEfromDatabaseN[0].value);
1098 
1099  return true;
1100  }
1101  }
1102 
1103  // Let INDI::LX200Generic handle any other number properties
1104  return LX200Generic::ISNewNumber(dev, name, values, names, n);
1105 }
1106 
1107 bool LX200_10MICRON::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
1108 {
1109  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1110  {
1111  if (strcmp(AlignmentStateSP.name, name) == 0)
1112  {
1113  IUUpdateSwitch(&AlignmentStateSP, states, names, n);
1114  int index = IUFindOnSwitchIndex(&AlignmentStateSP);
1115 
1116  switch (index)
1117  {
1118  case ALIGN_IDLE:
1119  AlignmentState = ALIGN_IDLE;
1120  LOG_INFO("Alignment state is IDLE");
1121  break;
1122 
1123  case ALIGN_START:
1124  // #:newalig#
1125  // Start creating a new alignment specification, that will be entered with the :newalpt command.
1126  // Returns:
1127  // the string "V#" (this is always successful).
1128  // Available from version 2.8.15.
1129  if (0 != setStandardProcedureAndExpectChar(fd, "#:newalig#", "V"))
1130  {
1131  LOG_ERROR("New alignment start error");
1133  IDSetSwitch(&AlignmentStateSP, nullptr);
1134  return false;
1135  }
1136  LOG_INFO("New Alignment started");
1137  AlignmentState = ALIGN_START;
1138  break;
1139 
1140  case ALIGN_END:
1141  // #:endalig#
1142  // Completes the alignment specification and computes a new alignment from the given
1143  // alignment points.
1144  // Returns:
1145  // the string "V#" if the alignment has been computed successfully
1146  // the string "E#" if the alignment couldn't be computed successfully with the current
1147  // alignment specification. In this case the previous alignment is retained.
1148  // Available from version 2.8.15.
1149  if (0 != setStandardProcedureAndExpectChar(fd, "#:endalig#", "V"))
1150  {
1151  LOG_ERROR("New alignment end error");
1153  IDSetSwitch(&AlignmentStateSP, nullptr);
1154  return false;
1155  }
1156  LOG_INFO("New Alignment ended");
1157  AlignmentState = ALIGN_END;
1158  break;
1159 
1160  case ALIGN_DELETE_CURRENT:
1161  // #:delalig#
1162  // Deletes the current alignment model and stars.
1163  // Returns: an empty string terminated by '#'.
1164  // Available from version 2.8.15.
1165  if (0 != setStandardProcedureAndExpectChar(fd, "#:delalig#", "#"))
1166  {
1167  LOG_ERROR("Delete current alignment error");
1169  IDSetSwitch(&AlignmentStateSP, nullptr);
1170  return false;
1171  }
1172  LOG_INFO("Current Alignment deleted");
1173  AlignmentState = ALIGN_DELETE_CURRENT;
1174  break;
1175 
1176  default:
1178  IDSetSwitch(&AlignmentStateSP, "Unknown alignment index %d", index);
1179  AlignmentState = ALIGN_IDLE;
1180  return false;
1181  }
1182 
1184  IDSetSwitch(&AlignmentStateSP, nullptr);
1185  return true;
1186  }
1187  if (strcmp(TrackSatSP.name, name) == 0)
1188  {
1189 
1190  IUUpdateSwitch(&TrackSatSP, states, names, n);
1191  int index = IUFindOnSwitchIndex(&TrackSatSP);
1192 
1193  switch (index)
1194  {
1195  case SAT_TRACK:
1196  if ( 0 != TrackSat() )
1197  {
1199  IDSetSwitch(&TrackSatSP, nullptr);
1200  LOG_ERROR("Tracking failed");
1201  return false;
1202  }
1203  TrackSatSP.s = IPS_OK;
1204  IDSetSwitch(&TrackSatSP, nullptr);
1205  LOG_INFO("Tracking satellite");
1206  return true;
1207  case SAT_HALT:
1208  if ( !Abort() )
1209  {
1211  IDSetSwitch(&TrackSatSP, nullptr);
1212  LOG_ERROR("Halt failed");
1213  return false;
1214  }
1215  TrackSatSP.s = IPS_OK;
1216  IDSetSwitch(&TrackSatSP, nullptr);
1217  LOG_INFO("Halt tracking");
1218  return true;
1219  default:
1221  IDSetSwitch(&TrackSatSP, "Unknown tracking modus %d", index);
1222  return false;
1223  }
1224  }
1225 
1226  if (strcmp(UnattendedFlipSP.name, name) == 0)
1227  {
1228  IUUpdateSwitch(&UnattendedFlipSP, states, names, n);
1229  int index = IUFindOnSwitchIndex(&UnattendedFlipSP);
1230  switch (index)
1231  {
1233  if (false == setUnattendedFlipSetting(false))
1234  {
1235  LOG_ERROR("Setting unattended flip failed");
1237  IDSetSwitch(&UnattendedFlipSP, nullptr);
1238  return false;
1239  }
1240  LOG_INFO("Unattended flip disabled");
1241  break;
1243  if (false == setUnattendedFlipSetting(true))
1244  {
1245  LOG_ERROR("Setting unattended flip failed");
1247  IDSetSwitch(&UnattendedFlipSP, nullptr);
1248  return false;
1249  }
1250  LOG_INFO("Unattended flip enabled");
1251  break;
1252  default:
1254  IDSetSwitch(&UnattendedFlipSP, "Unknown unattended flip setting %d", index);
1255  return false;
1256  }
1258  IDSetSwitch(&UnattendedFlipSP, nullptr);
1259  return true;
1260  }
1261  }
1262 
1263  return LX200Generic::ISNewSwitch(dev, name, states, names, n);
1264 }
1265 
1266 bool LX200_10MICRON::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
1267 {
1268  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1269  {
1270  if (strcmp(name, NEW_MODEL_NAME) == 0)
1271  {
1272  IUUpdateText(&NewModelNameTP, texts, names, n);
1274  IDSetText(&NewModelNameTP, nullptr);
1275  LOGF_INFO("Model saved with name %s", NewModelNameT[0].text);
1276  return true;
1277  }
1278  if (strcmp(name, "SAT_TLE_TEXT") == 0)
1279  {
1280 
1281  IUUpdateText(&TLEtoTrackTP, texts, names, n);
1282  if (0 == SetTLEtoFollow(TLEtoTrackT[0].text))
1283  {
1284  TLEtoTrackTP.s = IPS_OK;
1286  IDSetText(&TLEtoTrackTP, nullptr);
1287  IDSetNumber(&TLEfromDatabaseNP, nullptr);
1288  LOGF_INFO("Selected TLE %s", TLEtoTrackT[0].text);
1289  return true;
1290  }
1291  else
1292  {
1295  IDSetText(&TLEtoTrackTP, nullptr);
1296  IDSetNumber(&TLEfromDatabaseNP, nullptr);
1297  LOG_ERROR("TLE was not correctly uploaded");
1298  return false;
1299  }
1300  }
1301  if (strcmp(name, "SAT_PASS_WINDOW") == 0)
1302  {
1303  IUUpdateText(&SatPassWindowTP, texts, names, n);
1305  {
1307  IDSetText(&SatPassWindowTP, nullptr);
1308  LOG_INFO("Trajectory set");
1309  return true;
1310  }
1311  else
1312  {
1314  IDSetText(&SatPassWindowTP, nullptr);
1315  LOG_ERROR("Trajectory could not be calculated");
1316  return false;
1317  }
1318  }
1319  }
1320  return LX200Generic::ISNewText(dev, name, texts, names, n);
1321 }
1322 
1323 // this should move to some generic library
1324 int LX200_10MICRON::monthToNumber(const char *monthName)
1325 {
1326  struct entry
1327  {
1328  const char *name;
1329  int id;
1330  };
1331  entry month_table[] = { { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }, { "Apr", 4 }, { "May", 5 },
1332  { "Jun", 6 }, { "Jul", 7 }, { "Aug", 8 }, { "Sep", 9 }, { "Oct", 10 },
1333  { "Nov", 11 }, { "Dec", 12 }, { nullptr, 0 }
1334  };
1335  entry *p = month_table;
1336  while (p->name != nullptr)
1337  {
1338  if (strcasecmp(p->name, monthName) == 0)
1339  return p->id;
1340  ++p;
1341  }
1342  return 0;
1343 }
1344 
1346 {
1347  int error_type;
1348  int nbytes_write = 0;
1349 
1350  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1351  tcflush(fd, TCIFLUSH);
1352  if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1353  {
1354  LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1355  return error_type;
1356  }
1357  tcflush(fd, TCIFLUSH);
1358  return 0;
1359 }
1360 
1361 int LX200_10MICRON::setStandardProcedureAndExpectChar(int fd, const char *data, const char *expect)
1362 {
1363  char bool_return[2];
1364  int error_type;
1365  int nbytes_write = 0, nbytes_read = 0;
1366 
1367  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1368  tcflush(fd, TCIFLUSH);
1369  if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1370  {
1371  LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1372  return error_type;
1373  }
1374  error_type = tty_read(fd, bool_return, 1, LX200_TIMEOUT, &nbytes_read);
1375  tcflush(fd, TCIFLUSH);
1376 
1377  if (nbytes_read < 1)
1378  {
1379  LOGF_ERROR("CMD <%s> read ERROR %d", data, error_type);
1380  return error_type;
1381  }
1382 
1383  if (bool_return[0] != expect[0])
1384  {
1385  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> failed.", data);
1386  return -1;
1387  }
1388 
1389  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s> successful.", data);
1390 
1391  return 0;
1392 }
1393 
1394 int LX200_10MICRON::setStandardProcedureAndReturnResponse(int fd, const char *data, char *response, int max_response_length)
1395 {
1396  int error_type;
1397  int nbytes_write = 0, nbytes_read = 0;
1398 
1399  DEBUGFDEVICE(getDefaultName(), DBG_SCOPE, "CMD <%s>", data);
1400  tcflush(fd, TCIFLUSH);
1401  if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK)
1402  {
1403  LOGF_ERROR("CMD <%s> write ERROR %d", data, error_type);
1404  return error_type;
1405  }
1406  error_type = tty_read(fd, response, max_response_length, LX200_TIMEOUT, &nbytes_read);
1407  tcflush(fd, TCIFLUSH);
1408 
1409  if (nbytes_read < 1)
1410  {
1411  LOGF_ERROR("CMD <%s> read ERROR %d", data, error_type);
1412  return error_type;
1413  }
1414 
1415  return 0;
1416 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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
TelescopeStatus TrackState
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
IText TLEtoTrackT[1]
@ SAT_PASS_WINDOW_END
Index for end of the window.
@ SAT_PASS_WINDOW_START
Index for start of the window.
ITextVectorProperty SatPassWindowTP
Text Vector property defining the start and end of a satellite pass (window contains pass)....
bool isParked()
isParked is mount currently parked?
@ SAT_TRACK
Track signal.
@ SAT_HALT
Halt signal (abort)
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
ISwitchVectorProperty TrackSatSP
Switch Vector property defining the state of the satellite tracking of the mount. Example implementat...
ISwitchVectorProperty ParkSP
IText SatPassWindowT[SAT_PASS_WINDOW_COUNT]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
ITextVectorProperty TLEtoTrackTP
Text Vector property defining the orbital elements of an artificial satellite (TLE)....
virtual bool initProperties() override
Called to initialize basic properties required all the time.
INumber TrackFreqN[1]
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool sendScopeLocation()
bool sendLocationOnStartup
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
INumberVectorProperty TrackFreqNP
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool sendScopeTime()
void setLX200Capability(uint32_t cap)
INumber RefractionModelTemperatureN[1]
void getBasicData() override
bool Handshake() override
perform handshake with device to check communication
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
INumberVectorProperty ModelCountNP
INumberVectorProperty NewAlignmentPointsNP
ISwitchVectorProperty AlignmentStateSP
ITextVectorProperty NewModelNameTP
bool CalculateSatTrajectory(std::string start_pass_isodatetime, std::string end_pass_isodatetime)
INumberVectorProperty NewAlpNP
INumber NewAlignmentPointsN[1]
int setStandardProcedureAndExpectChar(int fd, const char *data, const char *expect)
bool UnPark() override
Unpark the telescope if already parked.
int monthToNumber(const char *monthName)
INumberVectorProperty RefractionModelPressureNP
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
int setStandardProcedureWithoutRead(int fd, const char *data)
INumberVectorProperty RefractionModelTemperatureNP
bool SetTLEtoFollow(const char *tle)
bool getUnattendedFlipSetting()
IText NewModelNameT[1]
ITextVectorProperty ProductTP
bool setLocalDate(uint8_t days, uint8_t months, uint16_t years) override
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
const char * getDefaultName() override
virtual int SetRefractionModelPressure(double pressure)
INumber MiniNewAlpN[MALP_COUNT]
bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INumberVectorProperty TLEfromDatabaseNP
INumber NewAlpN[ALP_COUNT]
int setStandardProcedureAndReturnResponse(int fd, const char *data, char *response, int max_response_length)
@ GSTAT_TRACKING_OUTSIDE_LIMITS
@ GSTAT_NOT_TRACKING_AND_NOT_MOVING
bool ReadScopeStatus() override
Read telescope status.
ISwitch UnattendedFlipS[UNATTENDED_FLIP_COUNT]
bool initProperties() override
Called to initialize basic properties required all the time.
INumber MiniNewAlpRON[MALPRO_COUNT]
INumber ModelCountN[1]
bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual int SetRefractionModelTemperature(double temperature)
bool Park() override
Park the telescope to its home position.
bool SyncConfigBehaviour(bool cmcfg)
INumberVectorProperty MiniNewAlpRONP
ISwitchVectorProperty UnattendedFlipSP
INumber TLEfromDatabaseN[1]
INumber AlignmentPointsN[1]
int AddSyncPoint(double MRa, double MDec, double MSide, double PRa, double PDec, double SidTime)
INumberVectorProperty MiniNewAlpNP
ISwitch AlignmentStateS[ALIGN_COUNT]
INumberVectorProperty AlignmentPointsNP
bool SetTLEfromDatabase(int tleN)
int AddSyncPointHere(double PRa, double PDec)
bool setUnattendedFlipSetting(bool setting)
INumber RefractionModelPressureN[1]
const char * SATELLITE_TAB
SATELLITE_TAB.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
double max(void)
double min(void)
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:566
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition: indicom.c:205
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:474
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.
@ TTY_OK
Definition: indicom.h:150
int extractISOTime(const char *timestr, struct ln_date *iso_date)
Extract ISO 8601 time and store it in a tm struct.
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
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 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
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
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
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
int IUGetConfigSwitch(const char *dev, const char *property, const char *member, ISState *value)
IUGetConfigSwitch Opens configuration file and reads single switch property.
Definition: indidriver.c:653
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_WARN(txt)
Definition: indilogger.h:73
#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 DEBUGFDEVICE(device, priority, msg,...)
Definition: indilogger.h:61
int fd
Definition: intelliscope.c:43
#define ALIGNMENT_POINTS
#define NEW_ALIGNMENT_POINT
#define MINIMAL_NEW_ALIGNMENT_POINT
#define REFRACTION_MODEL_PRESSURE
#define ALIGNMENT_TAB
#define NEW_ALIGNMENT_POINTS
#define PRODUCT_TAB
#define MINIMAL_NEW_ALIGNMENT_POINT_RO
#define MODEL_COUNT
#define NEW_MODEL_NAME
#define PRODUCT_INFO
#define REFRACTION_MODEL_TEMPERATURE
#define ALIGNMENT_STATE
#define UNATTENDED_FLIP
#define LX200_TIMEOUT
int getCommandInt(int fd, int *value, const char *cmd)
int setStandardProcedure(int fd, const char *data)
int checkLX200EquatorialFormat(int fd)
int setCommandInt(int fd, int data, const char *cmd)
int getTrackFreq(int fd, double *value)
int getCommandString(int fd, char *data, const char *cmd)
@ LX200_24
Definition: lx200driver.h:64
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250