Instrument Neutral Distributed Interface INDI  2.0.2
rainbow.cpp
Go to the documentation of this file.
1 /*
2  LX200 Rainbow Driver
3  Copyright (C) 2020 Jasem Mutlaq (mutlaqja@ikarustech.com)
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 
19 */
20 
21 #include "rainbow.h"
22 #include "lx200driver.h"
23 
25 #include <indicom.h>
26 
27 #include <cstring>
28 #include <cmath>
29 #include <termios.h>
30 #include <regex>
31 
32 static std::unique_ptr<Rainbow> scope(new Rainbow());
33 
35 {
36  setVersion(1, 2);
37 
47 }
48 
53 {
54  return "Rainbow";
55 }
56 
61 {
63 
65 
67 
68  // Homing
69  IUFillSwitch(&HomeS[0], "HOME", "Go Home", ISS_OFF);
70  IUFillSwitchVector(&HomeSP, HomeS, 1, getDeviceName(), "HOME", "Homing", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60,
71  IPS_IDLE);
72 
73  // Star Alignment on Sync
74  IUFillSwitch(&SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED], "STAR_ALIGNMENT_ENABLED", "Enabled", ISS_OFF);
75  IUFillSwitch(&SaveAlignBeforeSyncS[STAR_ALIGNMENT_DISABLED], "STAR_ALIGNMENT_DISABLED", "Disabled", ISS_ON);
76  IUFillSwitchVector(&SaveAlignBeforeSyncSP, SaveAlignBeforeSyncS, 2, getDeviceName(),
77  "STAR_ALIGNMENT", "Star Alignment", ALIGNMENT_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
78 
79  // Mount's versions
80  IUFillText(&RSTVersionsT[FIRMWARE], "FIRMWARE", "Firmware Version", "");
81  IUFillText(&RSTVersionsT[SERIALNUMBER], "SERIALNUMBER", "Serial Number", "");
82  IUFillTextVector(&RSTVersionsTP, RSTVersionsT, 2, getDeviceName(), "RST_VERSIONS", "Versions", GENERAL_INFO_TAB, IP_RO, 0,
83  IPS_IDLE);
84 
85  // Pull Voltage & Temperatures (possible to disable to reduce load on Serial bus)
86  IUFillSwitch(&PullVoltTempS[PULL_VOLTTEMP_ENABLED], "PULL_VOLTTEMP_ENABLED", "Enabled", ISS_OFF);
87  IUFillSwitch(&PullVoltTempS[PULL_VOLTTEMP_DISABLED], "PULL_VOLTTEMP_DISABLED", "Disabled", ISS_ON);
88  IUFillSwitchVector(&PullVoltTempSP, PullVoltTempS, 2, getDeviceName(),
89  "PULL_VOLTTEMP", "Pull V. & T.", GENERAL_INFO_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
90 
91  // Voltage & Temperatures
92  IUFillNumber(&RSTVoltTempN[VOLTAGE], "VOLTAGE", "Input Voltage (V)", "%2.1f", 0, 20., 0., 0.);
93  IUFillNumber(&RSTVoltTempN[BOARD_TEMPERATURE], "BOARD_TEMPERATURE", "Board Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
94  IUFillNumber(&RSTVoltTempN[RA_M_TEMPERATURE], "RA_M_TEMPERATURE", "RA-Motor Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
95  IUFillNumber(&RSTVoltTempN[DE_M_TEMPERATURE], "DE_M_TEMPERATURE", "DEC-Motor Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
96  IUFillNumberVector(&RSTVoltTempNP, RSTVoltTempN, 4, getDeviceName(), "RST_VOLT_TEMP", "Volt. & Temp.", GENERAL_INFO_TAB,
97  IP_RO, 0,
98  IPS_IDLE);
99 
100  // Motor powers
101  IUFillNumber(&RSTMotorPowN[RA_M_POWER], "RA_M_POWER", "RA-Motor (%)", "%3.1f", 0, 100., 0., 0.);
102  IUFillNumber(&RSTMotorPowN[DE_M_POWER], "DE_M_POWER", "DE-Motor (%)", "%3.1f", 0, 100., 0., 0.);
103  IUFillNumberVector(&RSTMotorPowNP, RSTMotorPowN, 2, getDeviceName(), "RST_MOTOR_POW", "Motor Power", GENERAL_INFO_TAB,
104  IP_RO, 0,
105  IPS_IDLE);
106 
107  // Horizontal Coords
108  IUFillNumber(&HorizontalCoordsN[AXIS_AZ], "AZ", "Az D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
109  IUFillNumber(&HorizontalCoordsN[AXIS_ALT], "ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
110  IUFillNumberVector(&HorizontalCoordsNP, HorizontalCoordsN, 2, getDeviceName(), "HORIZONTAL_COORD",
111  "Horizontal Coord", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
112 
113  AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
114  AddTrackMode("TRACK_SOLAR", "Solar");
115  AddTrackMode("TRACK_LUNAR", "Lunar");
116  AddTrackMode("TRACK_CUSTOM", "Guide");
117 
118  IUFillNumber(&GuideRateN[0], "GUIDE_RATE", "x Sidereal", "%g", 0.1, 1.0, 0.1, 0.5);
119  IUFillNumberVector(&GuideRateNP, GuideRateN, 1, getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RW, 0,
120  IPS_IDLE);
121 
122  IUFillNumber(&SlewSpeedsN[SLEW_SPEED_MAX], "SLEW_SPEED_MAX", "Max (x Siderial)", "%g", 0, 2000, 0, 0);
123  IUFillNumber(&SlewSpeedsN[SLEW_SPEED_FIND], "SLEW_SPEED_FIND", "Find (x Siderial)", "%g", 0, 2000, 0, 0);
124  IUFillNumber(&SlewSpeedsN[SLEW_SPEED_CENTERING], "SLEW_SPEED_CENTERING", "Centering (x Siderial)", "%g", 0, 2000, 0, 0);
125  IUFillNumberVector(&SlewSpeedsNP, SlewSpeedsN, 3, getDeviceName(), "SLEW_SPEED", "Slew speed", MOTION_TAB, IP_RW, 0,
126  IPS_IDLE);
127 
129 
131 
132  addDebugControl();
133 
134  return true;
135 }
136 
141 {
143 
144  if (isConnected())
145  {
146  defineProperty(&HorizontalCoordsNP);
147  defineProperty(&HomeSP);
148 
151  defineProperty(&GuideRateNP);
152  defineProperty(&SlewSpeedsNP);
153 
154  defineProperty(&SaveAlignBeforeSyncSP);
155  defineProperty(&RSTVersionsTP);
156  defineProperty(&PullVoltTempSP);
157  defineProperty(&RSTVoltTempNP);
158  defineProperty(&RSTMotorPowNP);
159 
161  }
162  else
163  {
164  deleteProperty(HorizontalCoordsNP.name);
165  deleteProperty(HomeSP.name);
166 
169  deleteProperty(GuideRateNP.name);
170  deleteProperty(SlewSpeedsNP.name);
171 
172  deleteProperty(SaveAlignBeforeSyncSP.name);
173  deleteProperty(RSTVersionsTP.name);
174  deleteProperty(PullVoltTempSP.name);
175  deleteProperty(RSTVoltTempNP.name);
176  deleteProperty(RSTMotorPowNP.name);
177  }
178 
179  return true;
180 }
181 
185 bool Rainbow::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
186 {
187  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
188  {
189  // Horizontal Coordinates
190  if (!strcmp(name, HorizontalCoordsNP.name))
191  {
192  int i = 0, nset = 0;
193  double newAlt = -1, newAz = -1;
194  INumber *horp = nullptr;
195 
196  for (nset = i = 0; i < n; i++)
197  {
198  if (horp == &HorizontalCoordsN[AXIS_ALT])
199  {
200  newAlt = values[i];
201  nset += newAlt >= -90. && newAlt <= 90.0;
202  }
203  else if (horp == &HorizontalCoordsN[AXIS_AZ])
204  {
205  newAz = values[i];
206  nset += newAz >= 0. && newAz <= 360.0;
207  }
208  }
209 
210  if (nset == 2)
211  {
212  if (slewToHorizontalCoords(newAz, newAlt))
213  {
215  HorizontalCoordsNP.s = IPS_BUSY;
216  IDSetNumber(&HorizontalCoordsNP, nullptr);
217  return true;
218  }
219  else
220  {
221  HorizontalCoordsNP.s = IPS_ALERT;
222  IDSetNumber(&HorizontalCoordsNP, nullptr);
223  LOG_ERROR("Failed to slew to target coordinates.");
224  return true;
225  }
226  }
227  else
228  {
229  HorizontalCoordsNP.s = IPS_ALERT;
230  IDSetNumber(&HorizontalCoordsNP, "Altitude or Azimuth missing or invalid");
231  return true;
232  }
233  }
234  // Guide Rate
235  else if (!strcmp(name, GuideRateNP.name))
236  {
237  if (setGuideRate(values[0]))
238  {
239  IUUpdateNumber(&GuideRateNP, values, names, n);
240  GuideRateNP.s = IPS_OK;
241  LOGF_INFO("Guide rate updated to %.2fX sidereal rate.", values[0]);
242  }
243  else
244  GuideRateNP.s = IPS_ALERT;
245 
246  IDSetNumber(&GuideRateNP, nullptr);
247  return true;
248  }
249  // Slew speeds
250  else if (!strcmp(name, SlewSpeedsNP.name))
251  {
252 
253  if (setSlewSpeedVal(SLEW_SPEED_MAX, values[SLEW_SPEED_MAX])
254  && setSlewSpeedVal(SLEW_SPEED_FIND, values[SLEW_SPEED_FIND])
255  && setSlewSpeedVal(SLEW_SPEED_CENTERING, values[SLEW_SPEED_CENTERING])
256  )
257  {
258  IUUpdateNumber(&SlewSpeedsNP, values, names, n);
259  SlewSpeedsNP.s = IPS_OK;
260  LOG_INFO("Slew speeds updated.");
261  }
262  else
263  SlewSpeedsNP.s = IPS_ALERT;
264  IDSetNumber(&SlewSpeedsNP, nullptr);
265  return true;
266  }
267  else
268  processGuiderProperties(name, values, names, n);
269  }
270 
271 
272  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
273 }
274 
278 bool Rainbow::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
279 {
280  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
281  {
282  // Homing
283  if (!strcmp(HomeSP.name, name))
284  {
285  if (HomeSP.s == IPS_BUSY)
286  {
287  LOG_WARN("Homing is already in progress.");
288  return true;
289  }
290 
291  HomeSP.s = findHome() ? IPS_BUSY : IPS_ALERT;
292  if (HomeSP.s == IPS_BUSY)
293  {
295  HomeS[0].s = ISS_ON;
296  LOG_INFO("Mount is moving to home position...");
297  }
298  else
299  LOG_ERROR("Mount failed to move to home position.");
300 
301  IDSetSwitch(&HomeSP, nullptr);
302  return true;
303  }
304  // Star Align
305  else if (!strcmp(SaveAlignBeforeSyncSP.name, name))
306  {
307 
308  IUUpdateSwitch(&SaveAlignBeforeSyncSP, states, names, n);
309  SaveAlignBeforeSyncSP.s = IPS_OK;
310  saveConfig(true, SaveAlignBeforeSyncSP.name);
311  IDSetSwitch(&SaveAlignBeforeSyncSP, nullptr);
312  return true;
313  }
314  // Pull RST's Voltage and Temperatures
315  else if (!strcmp(PullVoltTempSP.name, name))
316  {
317  IUUpdateSwitch(&PullVoltTempSP, states, names, n);
318  if (PullVoltTempS[PULL_VOLTTEMP_DISABLED].s == ISS_ON)
319  {
320  RSTVoltTempN[VOLTAGE].value = 0.;
321  RSTVoltTempN[BOARD_TEMPERATURE].value = 0.;
322  RSTVoltTempN[RA_M_TEMPERATURE].value = 0.;
323  RSTVoltTempN[DE_M_TEMPERATURE].value = 0.;
324  RSTMotorPowN[RA_M_POWER].value = 0.;
325  RSTMotorPowN[DE_M_POWER].value = 0.;
326  RSTVoltTempNP.s = IPS_IDLE;
327  IDSetNumber(&RSTVoltTempNP, nullptr);
328  RSTMotorPowNP.s = IPS_IDLE;
329  IDSetNumber(&RSTMotorPowNP, nullptr);
330  PullVoltTempSP.s = IPS_IDLE;
331  LOG_INFO("Pulling RST's Voltage and Temperatures set to: off");
332  }
333  else
334  {
335  PullVoltTempSP.s = IPS_OK;
336  LOG_INFO("Pulling RST's Voltage and Temperatures set to: on");
337  }
338  IDSetSwitch(&PullVoltTempSP, nullptr);
339  return true;
340  }
341 
342  }
343 
344  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
345 }
346 
351 {
353  LOGF_INFO("Detected firmware %s", m_Version.c_str());
354 
355  if (getTrackingState())
356  IDSetSwitch(&TrackStateSP, nullptr);
357  if (getGuideRate())
358  IDSetNumber(&GuideRateNP, nullptr);
359  if (getSlewSpeedVal(SLEW_SPEED_MAX) && (getSlewSpeedVal(SLEW_SPEED_FIND) && getSlewSpeedVal(SLEW_SPEED_CENTERING)))
360  IDSetNumber(&SlewSpeedsNP, nullptr);
361 
362  double longitude = 0, latitude = 90;
363  // Get value from config file if it exists.
364  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LONG", &longitude);
365  IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LAT", &latitude);
366 
367  if (InitPark())
368  {
369  // If loading parking data is successful, we just set the default parking values.
370  SetAxis1ParkDefault(latitude >= 0 ? 0 : 180);
371  SetAxis2ParkDefault(latitude);
372  }
373  else
374  {
375  // Otherwise, we set all parking data to default in case no parking data is found.
376  SetAxis1Park(latitude >= 0 ? 0 : 180);
377  SetAxis2Park(latitude);
378  SetAxis1ParkDefault(latitude >= 0 ? 0 : 180);
379  SetAxis2ParkDefault(latitude);
380  }
381 
383  sendScopeTime();
384 }
385 
389 bool Rainbow::updateLocation(double latitude, double longitude, double elevation)
390 {
391  INDI_UNUSED(elevation);
392 
393  char cmd[DRIVER_LEN] = {0};
394  int degrees, minutes, seconds;
395 
396  // Convert from INDI standard to regular east/west -180 to 180
397  if (longitude > 180)
398  longitude -= 360;
399 
400  // Rainbow defines EAST as - and WEST as +
401  // which is opposite of the standard
402  longitude *= -1;
403 
404  getSexComponents(longitude, &degrees, &minutes, &seconds);
405 
406  snprintf(cmd, DRIVER_LEN, ":Sg%c%03d*%02d'%02d#", longitude >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
407 
408  if (!sendCommand(cmd))
409  return false;
410 
411  getSexComponents(latitude, &degrees, &minutes, &seconds);
412 
413  snprintf(cmd, DRIVER_LEN, ":St%c%02d*%02d'%02d#", latitude >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
414 
415  return sendCommand(cmd);
416 }
417 
421 //bool Rainbow::updateTime(ln_date *utc, double utc_offset)
422 //{
423 
424 //}
425 
430 {
431  return getFirmwareVersion();
432 }
433 
438 {
439  char res[DRIVER_LEN] = {0};
440 
441  if (sendCommand(":AV#", res) == false)
442  return false;
443 
444  // :AV190905 --> 190905
445  m_Version = std::string(res + 3);
446 
447  // Write Firmware and Serial Number to INDI Control Panel
448  if (isConnected()) // executed onlyafter properties are updated
449  {
450  char fw[16] = {0}, sn[16] = {0};
451  sscanf(res + 3, "%6s", fw);
452  memset(res, 0, sizeof res);
453  IUSaveText(&RSTVersionsT[FIRMWARE], fw);
454 
455  if (sendCommand(":AS#", res) == false)
456  return false;
457  sscanf(res + 3, "%6s", sn);
458  IUSaveText(&RSTVersionsT[SERIALNUMBER], sn);
459 
460  RSTVersionsTP.s = IPS_OK;
461  IDSetText(&RSTVersionsTP, nullptr);
462  }
463  return true;
464 }
465 
469 bool Rainbow::SetTrackEnabled(bool enabled)
470 {
471  char cmd[DRIVER_LEN] = {0};
472  snprintf(cmd, DRIVER_LEN, ":Ct%c#", enabled ? 'A' : 'L');
473  return sendCommand(cmd);
474 }
475 
480 {
481  char res[DRIVER_LEN] = {0};
482 
483  if (sendCommand(":AT#", res) == false)
484  return false;
485 
486  TrackStateS[TRACK_ON].s = (res[3] == '1') ? ISS_ON : ISS_OFF;
487  TrackStateS[TRACK_OFF].s = (res[3] == '0') ? ISS_ON : ISS_OFF;
489 
490  return true;
491 }
492 
497 {
498  return sendCommand(":Ch#");
499 }
500 
504 bool Rainbow::setGuideRate(double rate)
505 {
506  char cmd[DRIVER_LEN] = {0};
507  snprintf(cmd, DRIVER_LEN, ":Cu0=%3.1f#", rate);
508  return sendCommand(cmd);
509 }
510 
515 {
516  char res[DRIVER_LEN] = {0};
517  char rate[4] = {0};
518 
519  if (!sendCommand(":CU0#", res))
520  return false;
521 
522  memcpy(rate, res + 5, 3);
523 
524  GuideRateN[0].value = std::stod(rate);
525  GuideRateNP.s = IPS_OK;
526  return true;
527 }
528 
532 bool Rainbow::setSlewSpeedVal(int speedtype, double rate)
533 {
534  char cmd[DRIVER_LEN] = {0};
535  snprintf(cmd, DRIVER_LEN, ":Cu%d=%g#", speedtype == SLEW_SPEED_MAX ? 3 : (speedtype == SLEW_SPEED_FIND ? 2 : 1), rate);
536  LOGF_INFO("slew speed set to enum %d and value %g", speedtype, rate);
537  return sendCommand(cmd);
538 }
539 
543 bool Rainbow::getSlewSpeedVal(int speedtype)
544 {
545  char res[DRIVER_LEN] = {0};
546  char rate[4] = {0};
547  char cmd[DRIVER_LEN] = {0};
548  snprintf(cmd, DRIVER_LEN, ":CU%d#", speedtype == SLEW_SPEED_MAX ? 3 : (speedtype == SLEW_SPEED_FIND ? 2 : 1));
549  if (sendCommand(cmd, res))
550  {
551  memcpy(rate, res + 5, 4);
552  SlewSpeedsN[speedtype].value = std::stod(rate);
553  SlewSpeedsNP.s = IPS_OK;
554  }
555  else
556  {
557  SlewSpeedsNP.s = IPS_ALERT;
558  return false;
559  }
560  return true;
561 }
562 
567 {
568  SetAxis1Park(m_CurrentAZ);
569  SetAxis2Park(m_CurrentAL);
570  return true;
571 }
572 
577 {
578  SetAxis1Park(0);
579  SetAxis2Park(0);
580  return true;
581 }
582 
587 {
588  double parkAZ = GetAxis1Park();
589  double parkAlt = GetAxis2Park();
590 
591  if (slewToHorizontalCoords(parkAZ, parkAlt))
592  {
594  HorizontalCoordsNP.s = IPS_BUSY;
595  IDSetNumber(&HorizontalCoordsNP, nullptr);
596  LOG_INFO("Parking is in progress...");
597  return true;
598  }
599 
600  return false;
601 
602 }
603 
608 {
609  if (SetTrackEnabled(true))
610  {
614  IDSetSwitch(&TrackStateSP, nullptr);
615 
616  SetParked(false);
617  return true;
618  }
619 
620  return false;
621 }
622 
627 {
628  char res[DRIVER_LEN] = {0};
629  int nbytes_read = 0;
630  if (tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, 1, &nbytes_read) == TTY_OK)
631  {
632  LOGF_DEBUG("SlewCheck <%s>", res);
633  if (!strcmp(res, ":MM0#") || !strcmp(res, ":CHO#"))
634  {
635  m_SlewErrorCode = 0;
636  return true;
637  }
638  else if (!strcmp(res, ":MML#"))
639  {
640  m_SlewErrorCode = 1;
641  }
642  else if (!strcmp(res, ":MMU#"))
643  {
644  m_SlewErrorCode = 2;
645  }
646  else if (!strcmp(res, ":MME#"))
647  {
648  m_SlewErrorCode = 3;
649  }
650  else if (!strcmp(res, ":CH0#"))
651  {
652  m_SlewErrorCode = 4;
653  }
654  else if (!strcmp(res, ":CH<#"))
655  {
656  m_SlewErrorCode = 5;
657  }
658  }
659 
660 
661  return false;
662 }
663 
668 {
669  if (!isConnected())
670  return false;
671 
672  if (TrackState == SCOPE_SLEWING)
673  {
674  // Check if Slewing is complete
675  if (isSlewComplete())
676  {
677  HorizontalCoordsNP.s = IPS_OK;
678  IDSetNumber(&HorizontalCoordsNP, nullptr);
679 
680  if (HomeSP.s == IPS_BUSY)
681  {
682  LOG_INFO("Homing completed successfully.");
683  HomeSP.s = IPS_OK;
684  HomeS[0].s = ISS_OFF;
685  IDSetSwitch(&HomeSP, nullptr);
687  }
688  else
689  {
691  // For Horizontal Goto, we must explicitly set tracking ON again.
692  if (m_GotoType == Horizontal)
693  SetTrackEnabled(true);
694  LOG_INFO("Slew is complete. Tracking...");
695  }
696  }
697  else if (m_SlewErrorCode > 0)
698  {
699  HorizontalCoordsNP.s = IPS_ALERT;
700  IDSetNumber(&HorizontalCoordsNP, nullptr);
701 
702  EqNP.s = IPS_ALERT;
703 
704  if (HomeSP.s == IPS_BUSY)
705  {
707  HomeSP.s = IPS_ALERT;
708  HomeS[0].s = ISS_OFF;
709  IDSetSwitch(&HomeSP, nullptr);
710  LOGF_ERROR("Homing error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
711  }
712  else
713  {
714  // JM TODO CHECK: Does the mount RESUME tracking after slew failure or it completely stops idle?
715  TrackState = m_GotoType == Horizontal ? SCOPE_IDLE : SCOPE_TRACKING;
716  LOGF_ERROR("Slewing error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
717  }
718  }
719  }
720  else if (TrackState == SCOPE_PARKING)
721  {
722  if (isSlewComplete())
723  {
724  SetParked(true);
725  HorizontalCoordsNP.s = IPS_OK;
726  IDSetNumber(&HorizontalCoordsNP, nullptr);
727  }
728  else if (m_SlewErrorCode > 0)
729  {
730  HorizontalCoordsNP.s = IPS_ALERT;
731  EqNP.s = IPS_ALERT;
732  // JM TODO CHECK: Does the mount RESUME tracking after slew failure or it completely stops idle?
733  TrackState = m_GotoType == Horizontal ? SCOPE_IDLE : SCOPE_TRACKING;
734  LOGF_ERROR("Parking error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
735  IDSetNumber(&HorizontalCoordsNP, nullptr);
736  }
737  }
738 
739  // Equatorial Coords
740  if (!getRA() || !getDE())
741  {
742  EqNP.s = IPS_ALERT;
743  IDSetNumber(&EqNP, "Error reading RA/DEC.");
744  return false;
745  }
746 
747  // Horizontal Coords
748  if (!getAZ() || !getAL())
749  {
750  HorizontalCoordsNP.s = IPS_ALERT;
751  }
752  else
753  {
754  HorizontalCoordsN[AXIS_AZ].value = m_CurrentAZ;
755  HorizontalCoordsN[AXIS_ALT].value = m_CurrentAL;
756  }
757  IDSetNumber(&HorizontalCoordsNP, nullptr);
758 
759  NewRaDec(m_CurrentRA, m_CurrentDE);
760 
761  if (PullVoltTempS[PULL_VOLTTEMP_ENABLED].s == ISS_ON)
762  {
763  // Get Voltage and Temperatures
764  char res[DRIVER_LEN] = {0};
765  float v, bT, rT, dT;
766  if (sendCommand(":Cv#", res) == false)
767  return false;
768  sscanf(res + 3, "%f", &v);
769  memset(res, 0, sizeof res);
770  RSTVoltTempN[VOLTAGE].value = v;
771  if (sendCommand(":CT#", res) == false)
772  return false;
773  sscanf(res + 3, "%f|%f|%f", &bT, &rT, &dT);
774  memset(res, 0, sizeof res);
775  RSTVoltTempN[BOARD_TEMPERATURE].value = bT;
776  RSTVoltTempN[RA_M_TEMPERATURE].value = rT;
777  RSTVoltTempN[DE_M_TEMPERATURE].value = dT;
778  RSTVoltTempNP.s = IPS_OK;
779  IDSetNumber(&RSTVoltTempNP, nullptr);
780 
781  // Get Motor Powers
782  float rP, dP;
783  if (sendCommand(":CP#", res) == false)
784  return false;
785  sscanf(res + 3, "%f|%f", &dP, &rP);
786  memset(res, 0, sizeof res);
787  RSTMotorPowN[RA_M_POWER].value = rP;
788  RSTMotorPowN[DE_M_POWER].value = dP;
789  RSTMotorPowNP.s = IPS_OK;
790  IDSetNumber(&RSTMotorPowNP, nullptr);
791  }
792  return true;
793 }
794 
798 bool Rainbow::Goto(double ra, double dec)
799 {
800  const struct timespec timeout = {0, 100000000L};
801 
802  char RAStr[64] = {0}, DecStr[64] = {0};
803  fs_sexa(RAStr, ra, 2, 36000);
804  fs_sexa(DecStr, dec, 2, 36000);
805 
806  // If moving, let's stop it first.
807  if (EqNP.s == IPS_BUSY)
808  {
809  if (!isSimulation() && Abort() == false)
810  {
811  AbortSP.s = IPS_ALERT;
812  IDSetSwitch(&AbortSP, "Abort slew failed.");
813  return false;
814  }
815 
816  AbortSP.s = IPS_OK;
817  EqNP.s = IPS_IDLE;
818  IDSetSwitch(&AbortSP, "Slew aborted.");
819  IDSetNumber(&EqNP, nullptr);
820 
822  {
825  EqNP.s = IPS_IDLE;
828  IDSetSwitch(&MovementNSSP, nullptr);
829  IDSetSwitch(&MovementWESP, nullptr);
830  }
831 
832  // sleep for 100 mseconds
833  nanosleep(&timeout, nullptr);
834  }
835 
836  if (slewToEquatorialCoords(ra, dec) == false)
837  {
838  LOGF_ERROR("Error Slewing to JNow RA %s - DEC %s", RAStr, DecStr);
839  return false;
840  }
841 
843  LOGF_INFO("Slewing to RA: %s - DE: %s", RAStr, DecStr);
844 
845  // Also set Horizontal Coors to BUSY
846  HorizontalCoordsNP.s = IPS_BUSY;
847  IDSetNumber(&HorizontalCoordsNP, nullptr);
848 
849  return true;
850 }
851 
856 {
857  char res[DRIVER_LEN] = {0};
858  if (sendCommand(":GR#", res) == false)
859  return false;
860 
861  // Skip :GR and read value
862  return (f_scansexa(res + 3, &m_CurrentRA) == 0);
863 }
864 
869 {
870  char res[DRIVER_LEN] = {0};
871  if (sendCommand(":GD#", res) == false)
872  return false;
873 
874  // Skip :GD and read value
875  return (f_scansexa(res + 3, &m_CurrentDE) == 0);
876 }
877 
878 
882 bool Rainbow::setRA(double ra)
883 {
884  char cmd[DRIVER_LEN] = {0};
885  char res[DRIVER_LEN] = {0};
886  int degrees, minutes;
887  double seconds;
888 
889  getSexComponentsIID(ra, &degrees, &minutes, &seconds);
890 
891  snprintf(cmd, DRIVER_LEN, ":Sr%02d:%02d:%04.1f#", degrees, minutes, seconds);
892 
893  if (!sendCommand(cmd, res, -1, 1))
894  return false;
895 
896  return res[0] == '1';
897 }
898 
902 bool Rainbow::setDE(double de)
903 {
904  char cmd[DRIVER_LEN] = {0};
905  char res[DRIVER_LEN] = {0};
906  int degrees, minutes;
907  double seconds;
908 
909  getSexComponentsIID(de, &degrees, &minutes, &seconds);
910 
911  snprintf(cmd, DRIVER_LEN, ":Sd%c%02d*%02d:%04.1f#", de >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
912 
913  if (!sendCommand(cmd, res, -1, 1))
914  return false;
915 
916  return res[0] == '1';
917 }
918 
922 bool Rainbow::slewToEquatorialCoords(double ra, double de)
923 {
924  if (!setRA(ra) || !setDE(de))
925  return false;
926 
927  if (sendCommand(":MS#"))
928  {
929  char RAStr[16], DEStr[16];
930  fs_sexa(RAStr, ra, 2, 36000);
931  fs_sexa(DEStr, de, 2, 36000);
932  LOGF_DEBUG("Slewing to RA (%s) DE (%s)...", RAStr, DEStr);
933  m_GotoType = Equatorial;
934  return true;
935  }
936 
937  return false;
938 }
939 
943 bool Rainbow::getAZ()
944 {
945  char res[DRIVER_LEN] = {0};
946  if (sendCommand(":GZ#", res) == false)
947  return false;
948 
949  // Skip :GZ and read value
950  return (f_scansexa(res + 3, &m_CurrentAZ) == 0);
951 }
952 
956 bool Rainbow::getAL()
957 {
958  char res[DRIVER_LEN] = {0};
959  if (sendCommand(":GA#", res) == false)
960  return false;
961 
962  // Skip :GA and read value
963  return (f_scansexa(res + 3, &m_CurrentAL) == 0);
964 }
965 
969 bool Rainbow::setAZ(double azimuth)
970 {
971  char cmd[DRIVER_LEN] = {0};
972  int degrees, minutes;
973  double seconds;
974 
975  getSexComponentsIID(azimuth, &degrees, &minutes, &seconds);
976 
977  snprintf(cmd, DRIVER_LEN, ":Sz%03d*%02d:%04.1f#", degrees, minutes, seconds);
978 
979  return sendCommand(cmd);
980 }
981 
985 bool Rainbow::setAL(double altitude)
986 {
987  char cmd[DRIVER_LEN] = {0};
988  int degrees, minutes;
989  double seconds;
990 
991  getSexComponentsIID(altitude, &degrees, &minutes, &seconds);
992 
993  snprintf(cmd, DRIVER_LEN, ":Sa%c%02d*%02d:%04.1f#", degrees >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
994 
995  return sendCommand(cmd);
996 }
997 
1001 bool Rainbow::slewToHorizontalCoords(double azimuth, double altitude)
1002 {
1003  if (!setAZ(azimuth) || !setAL(altitude))
1004  return false;
1005 
1006  if (sendCommand(":MA#"))
1007  {
1008  char AzStr[16], AltStr[16];
1009  fs_sexa(AzStr, azimuth, 2, 36000);
1010  fs_sexa(AltStr, altitude, 2, 36000);
1011  LOGF_DEBUG("Slewing to Az (%s) Alt (%s)...", AzStr, AltStr);
1012  m_GotoType = Horizontal;
1013  return true;
1014  }
1015 
1016  return false;
1017 }
1018 
1023 {
1024  switch (command)
1025  {
1026  case MOTION_START:
1027  if (!sendCommand(dir == DIRECTION_NORTH ? ":Mn#" : ":Ms#"))
1028  {
1029  LOG_ERROR("Error setting N/S motion direction.");
1030  return false;
1031  }
1032  else
1033  LOGF_DEBUG("Moving toward %s.", (dir == DIRECTION_NORTH) ? "North" : "South");
1034  break;
1035 
1036  case MOTION_STOP:
1037  if (!sendCommand(":Q#"))
1038  {
1039  LOG_ERROR("Error stopping N/S motion.");
1040  return false;
1041  }
1042  else
1043  LOGF_DEBUG("Movement toward %s halted.", (dir == DIRECTION_NORTH) ? "North" : "South");
1044  break;
1045  }
1046 
1047  return true;
1048 }
1049 
1054 {
1055  switch (command)
1056  {
1057  case MOTION_START:
1058  if (!sendCommand(dir == DIRECTION_WEST ? ":Mw#" : ":Me#"))
1059  {
1060  LOG_ERROR("Error setting W/E motion direction.");
1061  return false;
1062  }
1063  else
1064  LOGF_DEBUG("Moving toward %s.", (dir == DIRECTION_WEST) ? "West" : "East");
1065  break;
1066 
1067  case MOTION_STOP:
1068  if (!sendCommand(":Q#"))
1069  {
1070  LOG_ERROR("Error stopping W/E motion.");
1071  return false;
1072  }
1073  else
1074  LOGF_DEBUG("Movement toward %s halted.", (dir == DIRECTION_WEST) ? "West" : "East");
1075  break;
1076  }
1077 
1078  return true;
1079 }
1080 
1085 {
1086  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
1087  {
1089  GuideNSN[0].value = GuideNSN[1].value = 0.0;
1090  GuideWEN[0].value = GuideWEN[1].value = 0.0;
1091 
1092  if (m_GuideNSTID)
1093  {
1094  IERmTimer(m_GuideNSTID);
1095  m_GuideNSTID = 0;
1096  }
1097 
1098  if (m_GuideWETID)
1099  {
1100  IERmTimer(m_GuideWETID);
1101  m_GuideWETID = 0;
1102  }
1103 
1104  LOG_INFO("Guide aborted.");
1105  IDSetNumber(&GuideNSNP, nullptr);
1106  IDSetNumber(&GuideWENP, nullptr);
1107 
1108  return true;
1109  }
1110 
1111  return sendCommand(":Q#");
1112 }
1113 
1117 bool Rainbow::Sync(double ra, double dec)
1118 {
1119 
1120  char cmd[DRIVER_LEN] = {0};
1121  if (SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED].s == ISS_ON)
1122  {
1123  snprintf(cmd, DRIVER_LEN, ":CN%07.3f%c%06.3f#", ra * 15.0, dec >= 0 ? '+' : '-', std::fabs(dec));
1124  }
1125  else
1126  {
1127  snprintf(cmd, DRIVER_LEN, ":Ck%07.3f%c%06.3f#", ra * 15.0, dec >= 0 ? '+' : '-', std::fabs(dec));
1128  }
1129 
1130  if (sendCommand(cmd))
1131  {
1132  char RAStr[64] = {0}, DecStr[64] = {0};
1133  fs_sexa(RAStr, ra, 2, 36000);
1134  fs_sexa(DecStr, dec, 2, 36000);
1135  LOGF_INFO("Synced to RA %s DE %s%s", RAStr, DecStr,
1136  SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED].s == ISS_ON ? ", and saved as alignment point." : "");
1137  return true;
1138  }
1139  return false;
1140 }
1141 
1145 bool Rainbow::SetTrackMode(uint8_t mode)
1146 {
1147  switch (mode)
1148  {
1149  case TRACK_SIDEREAL:
1150  return sendCommand(":CtR#");
1151  case TRACK_SOLAR:
1152  return sendCommand(":CtS#");
1153  case TRACK_LUNAR:
1154  return sendCommand(":CtL#");
1155  case TRACK_CUSTOM:
1156  return sendCommand(":CtU#");
1157 
1158  }
1159 
1160  return false;
1161 }
1162 
1166 bool Rainbow::SetSlewRate(int index)
1167 {
1168  switch(index)
1169  {
1170  case SLEW_MAX:
1171  return sendCommand(":RS#");
1172  case SLEW_FIND:
1173  return sendCommand(":RM#");
1174  case SLEW_CENTERING:
1175  return sendCommand(":RC#");
1176  case SLEW_GUIDE:
1177  return sendCommand(":RG#");
1178  }
1179 
1180  return false;
1181 }
1185 const std::string Rainbow::getSlewErrorString(uint8_t code)
1186 {
1187  switch (code)
1188  {
1189  case 1:
1190  return "The altitude of the target is lower than lower limit.";
1191  case 2:
1192  return "The altitude of the target is higher than upper limit.";
1193  case 3:
1194  return "Slewing was canceled by user";
1195  case 4:
1196  return "RA Axis homing failed.";
1197  case 5:
1198  return "DE Axis homing failed.";
1199  }
1200 
1201  return "Unknown error";
1202 }
1203 
1208 {
1209  return guide(Direction::North, ms);
1210 }
1211 
1216 {
1217  return guide(Direction::South, ms);
1218 }
1219 
1224 {
1225  return guide(Direction::East, ms);
1226 }
1227 
1232 {
1233  return guide(Direction::West, ms);
1234 }
1235 
1239 IPState Rainbow::guide(Direction direction, uint32_t ms)
1240 {
1241  // set up direction properties
1242  char dc = 'x';
1243  char cmd[DRIVER_LEN] = {0};
1245  ISwitch moveS = MovementNSS[0];
1246  int* guideTID = &m_GuideNSTID;
1247 
1248  // set up pointers to the various things needed
1249  switch (direction)
1250  {
1251  case North:
1252  dc = 'N';
1253  moveSP = &MovementNSSP;
1254  moveS = MovementNSS[0];
1255  guideTID = &m_GuideNSTID;
1256  break;
1257  case South:
1258  dc = 'S';
1259  moveSP = &MovementNSSP;
1260  moveS = MovementNSS[1];
1261  guideTID = &m_GuideNSTID;
1262  break;
1263  case East:
1264  dc = 'E';
1265  moveSP = &MovementWESP;
1266  moveS = MovementWES[1];
1267  guideTID = &m_GuideWETID;
1268  break;
1269  case West:
1270  dc = 'W';
1271  moveSP = &MovementWESP;
1272  moveS = MovementWES[0];
1273  guideTID = &m_GuideWETID;
1274  break;
1275  }
1276 
1278  {
1279  LOG_ERROR("Cannot guide while moving.");
1280  return IPS_ALERT;
1281  }
1282 
1283  // If already moving (no pulse command), then stop movement
1284  if (moveSP->s == IPS_BUSY)
1285  {
1286  LOG_DEBUG("Already moving - stop");
1287  sendCommand(":Q#");
1288  }
1289 
1290  if (*guideTID)
1291  {
1292  LOGF_DEBUG("Stop timer %c", dc);
1293  IERmTimer(*guideTID);
1294  *guideTID = 0;
1295  }
1296 
1297  // Make sure TRACKING is set to Guide
1299  {
1303  IDSetSwitch(&TrackModeSP, nullptr);
1304  LOG_INFO("Tracking mode switched to guide.");
1305  }
1306 
1307  // Make sure SLEWING SPEED is set to Guide
1309  {
1310  // Set slew to guiding
1314  IDSetSwitch(&SlewRateSP, nullptr);
1315  }
1316 
1317  moveS.s = ISS_ON;
1318  snprintf(cmd, DRIVER_LEN, ":M%c#", std::tolower(dc));
1319 
1320  // start movement at HC button rate 1
1321  if (!sendCommand(cmd))
1322  {
1323  LOGF_ERROR("Start motion %c failed", dc);
1324  return IPS_ALERT;
1325  }
1326 
1327  // start the guide timeout timer
1328  addGuideTimer(direction, ms);
1329  return IPS_BUSY;
1330 }
1331 
1336 {
1337  static_cast<Rainbow *>(p)->guideTimeout(North);
1338 }
1339 
1344 {
1345  static_cast<Rainbow *>(p)->guideTimeout(South);
1346 }
1347 
1352 {
1353  static_cast<Rainbow *>(p)->guideTimeout(West);
1354 }
1355 
1360 {
1361  static_cast<Rainbow *>(p)->guideTimeout(East);
1362 }
1363 
1368 {
1369  char cmd[DRIVER_LEN] = {0};
1370  switch(direction)
1371  {
1372  case North:
1373  case South:
1375  IDSetSwitch(&MovementNSSP, nullptr);
1376  GuideNSNP.np[0].value = 0;
1377  GuideNSNP.np[1].value = 0;
1378  GuideNSNP.s = IPS_IDLE;
1379  m_GuideNSTID = 0;
1380  IDSetNumber(&GuideNSNP, nullptr);
1381  snprintf(cmd, DRIVER_LEN, ":Q%c#", direction == North ? 'n' : 's');
1382  break;
1383  case East:
1384  case West:
1386  IDSetSwitch(&MovementWESP, nullptr);
1387  GuideWENP.np[0].value = 0;
1388  GuideWENP.np[1].value = 0;
1389  GuideWENP.s = IPS_IDLE;
1390  m_GuideWETID = 0;
1391  IDSetNumber(&GuideWENP, nullptr);
1392  snprintf(cmd, DRIVER_LEN, ":Q%c#", direction == East ? 'e' : 'w');
1393  break;
1394  }
1395  sendCommand(cmd);
1396  LOGF_DEBUG("Guide %c finished", "NSWE"[direction]);
1397 }
1398 
1402 void Rainbow::addGuideTimer(Direction direction, uint32_t ms)
1403 {
1404  switch(direction)
1405  {
1406  case North:
1407  m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperN, this);
1408  break;
1409  case South:
1410  m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperS, this);
1411  break;
1412  case East:
1413  m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperE, this);
1414  break;
1415  case West:
1416  m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperW, this);
1417  break;
1418  }
1419 }
1420 
1424 bool Rainbow::getLocalTime(char *timeString)
1425 {
1426  if (isSimulation())
1427  {
1428  time_t now = time (nullptr);
1429  strftime(timeString, MAXINDINAME, "%T", localtime(&now));
1430  }
1431  else
1432  {
1433  int h, m, s;
1434  char response[DRIVER_LEN] = {0};
1435  if (!sendCommand(":GL#", response))
1436  return false;
1437 
1438  if (sscanf(response + 3, "%d:%d:%d", &h, &m, &s) != 3)
1439  {
1440  LOG_WARN("Failed to get time from device.");
1441  return false;
1442  }
1443  snprintf(timeString, MAXINDINAME, "%02d:%02d:%02d", h, m, s);
1444  }
1445 
1446  return true;
1447 }
1448 
1452 bool Rainbow::getLocalDate(char *dateString)
1453 {
1454  if (isSimulation())
1455  {
1456  time_t now = time (nullptr);
1457  strftime(dateString, MAXINDINAME, "%F", localtime(&now));
1458  }
1459  else
1460  {
1461  int dd, mm, yy;
1462  char response[DRIVER_LEN] = {0};
1463  char mell_prefix[3] = {0};
1464  if (!sendCommand(":GC#", response))
1465  return false;
1466 
1467  if (sscanf(response + 3, "%d%*c%d%*c%d", &mm, &dd, &yy) != 3)
1468  {
1469  LOG_WARN("Failed to get date from device.");
1470  return false;
1471  }
1472  else
1473  {
1474  if (yy > 50)
1475  strncpy(mell_prefix, "19", 3);
1476  else
1477  strncpy(mell_prefix, "20", 3);
1478  /* We need to have it in YYYY-MM-DD ISO format */
1479  snprintf(dateString, 32, "%s%02d-%02d-%02d", mell_prefix, yy, mm, dd);
1480  }
1481  }
1482 
1483  return true;
1484 }
1485 
1489 bool Rainbow::getUTFOffset(double *offset)
1490 {
1491  if (isSimulation())
1492  {
1493  *offset = 3;
1494  return true;
1495  }
1496 
1497  int rst135_utc_offset = 0;
1498 
1499  char response[DRIVER_LEN] = {0};
1500  if (!sendCommand(":GG#", response))
1501  return false;
1502 
1503  if (sscanf(response + 3, "%d", &rst135_utc_offset) != 1)
1504  {
1505  LOG_WARN("Failed to get UTC offset from device.");
1506  return false;
1507  }
1508 
1509  // LX200 TimeT Offset is defined at the number of hours added to LOCAL TIME to get TimeT. This is contrary to the normal definition.
1510  *offset = rst135_utc_offset * -1;
1511  return true;
1512 }
1513 
1518 {
1519  char cdate[MAXINDINAME] = {0};
1520  char ctime[MAXINDINAME] = {0};
1521  struct tm ltm;
1522  struct tm utm;
1523 
1524  memset(&ltm, 0, sizeof(ltm));
1525  memset(&utm, 0, sizeof(utm));
1526 
1527  time_t time_epoch;
1528 
1529  double offset = 0;
1530  if (getUTFOffset(&offset))
1531  {
1532  char utcStr[8] = {0};
1533  snprintf(utcStr, 8, "%.2f", offset);
1534  IUSaveText(&TimeT[1], utcStr);
1535  }
1536  else
1537  {
1538  LOG_WARN("Could not obtain UTC offset from mount!");
1539  return false;
1540  }
1541 
1542  if (getLocalTime(ctime) == false)
1543  {
1544  LOG_WARN("Could not obtain local time from mount!");
1545  return false;
1546  }
1547 
1548  if (getLocalDate(cdate) == false)
1549  {
1550  LOG_WARN("Could not obtain local date from mount!");
1551  return false;
1552  }
1553 
1554  // To ISO 8601 format in LOCAL TIME!
1555  char datetime[MAXINDINAME] = {0};
1556  snprintf(datetime, MAXINDINAME, "%sT%s", cdate, ctime);
1557 
1558  // Now that date+time are combined, let's get tm representation of it.
1559  if (strptime(datetime, "%FT%T", &ltm) == nullptr)
1560  {
1561  LOGF_WARN("Could not process mount date and time: %s", datetime);
1562  return false;
1563  }
1564 
1565  ltm.tm_isdst = 0;
1566  // Get local time epoch in UNIX seconds
1567  time_epoch = mktime(&ltm);
1568 
1569  // LOCAL to UTC by subtracting offset.
1570  time_epoch -= static_cast<int>(offset * 3600.0);
1571 
1572  // Get UTC (we're using localtime_r, but since we shifted time_epoch above by UTCOffset, we should be getting the real UTC time)
1573  localtime_r(&time_epoch, &utm);
1574 
1575  // Format it into the final UTC ISO 8601
1576  strftime(cdate, MAXINDINAME, "%Y-%m-%dT%H:%M:%S", &utm);
1577  IUSaveText(&TimeT[0], cdate);
1578 
1579  LOGF_DEBUG("Mount controller UTC Time: %s", TimeT[0].text);
1580  LOGF_DEBUG("Mount controller UTC Offset: %s", TimeT[1].text);
1581 
1582  // Let's send everything to the client
1583  TimeTP.s = IPS_OK;
1584  IDSetText(&TimeTP, nullptr);
1585 
1586  return true;
1587 }
1588 
1593 {
1594  double longitude {0}, latitude {0};
1595  double dd = 0, mm = 0, ssf = 0;
1596  char response[DRIVER_LEN] = {0};
1597 
1598  if (isSimulation())
1599  {
1600  LocationNP.np[LOCATION_LATITUDE].value = 29.5;
1601  LocationNP.np[LOCATION_LONGITUDE].value = 48.0;
1602  LocationNP.np[LOCATION_ELEVATION].value = 10;
1603  LocationNP.s = IPS_OK;
1604  IDSetNumber(&LocationNP, nullptr);
1605  return true;
1606  }
1607 
1608  // Latitude
1609  if (!sendCommand(":Gt#", response))
1610  return false;
1611 
1612  if (sscanf(response + 3, "%lf%*[^0-9]%lf%*[^0-9]%lf", &dd, &mm, &ssf) != 3)
1613  {
1614  LOG_WARN("Failed to get site latitude from device.");
1615  return false;
1616  }
1617  else
1618  {
1619  if (dd > 0)
1620  latitude = dd + mm / 60.0 + ssf / 3600.0;
1621  else
1622  latitude = dd - mm / 60.0 - ssf / 3600.0;
1623  }
1624 
1625  // Longitude
1626  if (!sendCommand(":Gg#", response))
1627  return false;
1628 
1629  if (sscanf(response + 3, "%lf%*[^0-9]%lf%*[^0-9]%lf", &dd, &mm, &ssf) != 3)
1630  {
1631  LOG_WARN("Failed to get site longitude from device.");
1632  return false;
1633  }
1634  else
1635  {
1636  if (dd > 0)
1637  longitude = 360.0 - (dd + mm / 60.0 + ssf / 3600.0);
1638  else
1639  longitude = (dd - mm / 60.0 - ssf / 3600.0) * -1.0;
1640 
1641  }
1642 
1643  // Only update if different from current values
1644  // and then immediately save to config.
1645  if (std::abs(LocationN[LOCATION_LONGITUDE].value - longitude) > 0.001 ||
1646  std::abs(LocationN[LOCATION_LATITUDE].value - latitude) > 0.001)
1647  {
1648  LocationN[LOCATION_LATITUDE].value = latitude;
1649  LocationN[LOCATION_LONGITUDE].value = longitude;
1650  LOGF_DEBUG("Mount Controller Latitude: %.3f Longitude: %.3f", LocationN[LOCATION_LATITUDE].value,
1651  LocationN[LOCATION_LONGITUDE].value);
1652  IDSetNumber(&LocationNP, nullptr);
1653  saveConfig(true, LocationNP.name);
1654  }
1655 
1656  return true;
1657 }
1658 
1662 bool Rainbow::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
1663 {
1664  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1665 
1666  tcflush(PortFD, TCIOFLUSH);
1667 
1668  if (cmd_len > 0)
1669  {
1670  char hex_cmd[DRIVER_LEN * 3] = {0};
1671  hexDump(hex_cmd, cmd, cmd_len);
1672  LOGF_DEBUG("CMD <%s>", hex_cmd);
1673  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
1674  }
1675  else
1676  {
1677  LOGF_DEBUG("CMD <%s>", cmd);
1678 
1679  char formatted_command[DRIVER_LEN] = {0};
1680  snprintf(formatted_command, DRIVER_LEN, "%s\r", cmd);
1681  rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
1682  }
1683 
1684  if (rc != TTY_OK)
1685  {
1686  char errstr[MAXRBUF] = {0};
1687  tty_error_msg(rc, errstr, MAXRBUF);
1688  LOGF_ERROR("Serial write error: %s.", errstr);
1689  return false;
1690  }
1691 
1692  if (res == nullptr)
1693  return true;
1694 
1695  if (res_len > 0)
1696  rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
1697  else
1698  rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1699 
1700  if (rc != TTY_OK)
1701  {
1702  char errstr[MAXRBUF] = {0};
1703  tty_error_msg(rc, errstr, MAXRBUF);
1704  LOGF_ERROR("Serial read error: %s.", errstr);
1705  return false;
1706  }
1707 
1708  if (res_len > 0)
1709  {
1710  char hex_res[DRIVER_LEN * 3] = {0};
1711  hexDump(hex_res, res, res_len);
1712  LOGF_DEBUG("RES <%s>", hex_res);
1713  }
1714  else
1715  {
1716  // Remove extra \r
1717  res[nbytes_read - 1] = 0;
1718  LOGF_DEBUG("RES <%s>", res);
1719  }
1720 
1721  tcflush(PortFD, TCIOFLUSH);
1722 
1723  return true;
1724 }
1725 
1729 void Rainbow::hexDump(char * buf, const char * data, int size)
1730 {
1731  for (int i = 0; i < size; i++)
1732  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1733 
1734  if (size > 0)
1735  buf[3 * size - 1] = '\0';
1736 }
1737 
1741 std::vector<std::string> Rainbow::split(const std::string &input, const std::string &regex)
1742 {
1743  // passing -1 as the submatch index parameter performs splitting
1744  std::regex re(regex);
1745  std::sregex_token_iterator
1746  first{input.begin(), input.end(), re, -1},
1747  last;
1748  return {first, last};
1749 }
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
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.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
ISwitchVectorProperty TrackStateSP
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
ISwitchVectorProperty MovementNSSP
ISwitchVectorProperty AbortSP
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
INumberVectorProperty LocationNP
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty TimeTP
double GetAxis1Park() const
double GetAxis2Park() const
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty TrackModeSP
ISwitchVectorProperty SlewRateSP
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
ISwitch MovementWES[2]
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
@ TELESCOPE_HAS_PIER_SIDE_SIMULATION
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
ISwitch * TrackModeS
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
ISwitchVectorProperty MovementWESP
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
ISwitch TrackStateS[2]
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool Sync(double ra, double dec) override
Sync.
Definition: rainbow.cpp:1117
virtual IPState GuideNorth(uint32_t ms) override
Guiding.
Definition: rainbow.cpp:1207
void guideTimeout(Direction direction)
Guide Timeout.
Definition: rainbow.cpp:1367
static void guideTimeoutHelperN(void *p)
Guide Timeout North.
Definition: rainbow.cpp:1335
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
Definition: rainbow.cpp:140
bool getTrackingState()
Definition: rainbow.cpp:479
virtual bool Goto(double ra, double dec) override
Slew RA/DE.
Definition: rainbow.cpp:798
bool findHome()
Definition: rainbow.cpp:496
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: rainbow.cpp:185
virtual bool SetSlewRate(int index) override
Set Slew Rate.
Definition: rainbow.cpp:1166
virtual IPState GuideEast(uint32_t ms) override
Guide East.
Definition: rainbow.cpp:1223
virtual bool SetTrackMode(uint8_t mode) override
Set Track Mode.
Definition: rainbow.cpp:1145
virtual IPState GuideWest(uint32_t ms) override
Guide West.
Definition: rainbow.cpp:1231
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition: rainbow.cpp:60
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Abort.
Definition: rainbow.cpp:1022
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: rainbow.cpp:278
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Location & Time.
Definition: rainbow.cpp:389
virtual bool SetTrackEnabled(bool enabled) override
Tracking Functions.
Definition: rainbow.cpp:469
void getStartupStatus()
Query Functions.
Definition: rainbow.cpp:350
void hexDump(char *buf, const char *data, int size)
Definition: rainbow.cpp:1729
bool getFirmwareVersion()
Definition: rainbow.cpp:437
const char * getDefaultName() override
Definition: rainbow.cpp:52
virtual bool Park() override
Parking & Homing.
Definition: rainbow.cpp:586
virtual bool Handshake() override
perform handshake with device to check communication
Definition: rainbow.cpp:429
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
Definition: rainbow.cpp:576
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
Definition: rainbow.cpp:566
bool sendScopeLocation()
Get Location from mount.
Definition: rainbow.cpp:1592
bool sendScopeTime()
Location & Time.
Definition: rainbow.cpp:1517
Rainbow()
Definition: rainbow.cpp:34
static void guideTimeoutHelperS(void *p)
Guide Timeout South.
Definition: rainbow.cpp:1343
bool getLocalDate(char *dateString)
Get Date from mount.
Definition: rainbow.cpp:1452
bool sendCommand(const char *cmd, char *res=nullptr, int cmd_len=-1, int res_len=-1)
Communication Functions.
Definition: rainbow.cpp:1662
IPState guide(Direction direction, uint32_t ms)
Guide universal function.
Definition: rainbow.cpp:1239
static void guideTimeoutHelperW(void *p)
Guide Timeout West.
Definition: rainbow.cpp:1351
std::vector< std::string > split(const std::string &input, const std::string &regex)
Definition: rainbow.cpp:1741
static void guideTimeoutHelperE(void *p)
Guide Timeout East.
Definition: rainbow.cpp:1359
virtual bool UnPark() override
Unparking.
Definition: rainbow.cpp:607
bool setRA(double ra)
Set Target RA.
Definition: rainbow.cpp:882
bool getLocalTime(char *timeString)
Get Time from mount.
Definition: rainbow.cpp:1424
void addGuideTimer(Direction direction, uint32_t ms)
Add guide timer.
Definition: rainbow.cpp:1402
bool getUTFOffset(double *offset)
GET UTC offset from mount.
Definition: rainbow.cpp:1489
bool setDE(double de)
Set Target Altitude.
Definition: rainbow.cpp:902
bool getDE()
Get DE.
Definition: rainbow.cpp:868
virtual IPState GuideSouth(uint32_t ms) override
Guide South.
Definition: rainbow.cpp:1215
virtual bool Abort() override
Abort.
Definition: rainbow.cpp:1084
Direction
Definition: rainbow.h:40
@ West
Definition: rainbow.h:40
@ South
Definition: rainbow.h:40
@ East
Definition: rainbow.h:40
@ North
Definition: rainbow.h:40
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
W/E Motion.
Definition: rainbow.cpp:1053
bool slewToEquatorialCoords(double ra, double de)
Slew to Equatorial Coordinates.
Definition: rainbow.cpp:922
virtual bool isSlewComplete()
Motion Functions.
Definition: rainbow.cpp:626
bool setGuideRate(double rate)
Definition: rainbow.cpp:504
bool getGuideRate()
Definition: rainbow.cpp:514
virtual bool ReadScopeStatus() override
Read telescope status.
Definition: rainbow.cpp:667
bool getRA()
Get RA.
Definition: rainbow.cpp:855
@ Equatorial
Definition: rainbow.h:39
@ Horizontal
Definition: rainbow.h:39
Class to provide general functionality of a telescope device.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:602
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition: eventloop.c:582
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
@ ISR_ATMOST1
Definition: indiapi.h:174
#define MAXINDINAME
Definition: indiapi.h:191
@ AXIS_AZ
Definition: indibasetypes.h:42
@ AXIS_ALT
Definition: indibasetypes.h:43
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
void getSexComponentsIID(double value, int *d, int *m, double *s)
Definition: indicom.c:277
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_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void getSexComponents(double value, int *d, int *m, int *s)
Definition: indicom.c:254
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
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
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 IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
#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
int IUGetConfigNumber(const char *dev, const char *property, const char *member, double *value)
IUGetConfigNumber Opens configuration file and reads single number property.
Definition: indidriver.c:831
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
#define ALIGNMENT_TAB
Namespace to encapsulate INDI client, drivers, and mediator classes.
__u8 cmd[4]
Definition: pwc-ioctl.h:2
#define GENERAL_INFO_TAB
Definition: rainbow.h:27
One number descriptor.
One switch descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
Switch vector property descriptor.
Definition: indiapi.h:367
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250