Instrument Neutral Distributed Interface INDI 2.2.5
rainbow.cpp
Go to the documentation of this file.
1/*
2 LX200 Rainbow Driver
3 Copyright (C) 2020 Jasem Mutlaq ([email protected])
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
32static std::unique_ptr<Rainbow> scope(new Rainbow());
33
51
56{
57 return "Rainbow";
58}
59
64{
66
68
70
71 // Homing
72 // IUFillSwitch(&HomeS[0], "GO", "Go", ISS_OFF);
73 // IUFillSwitchVector(&HomeSP, HomeS, 1, getDeviceName(), "TELESCOPE_HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60,
74 // IPS_IDLE);
75
76 // Star Alignment on Sync
77 IUFillSwitch(&SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED], "STAR_ALIGNMENT_ENABLED", "Enabled", ISS_OFF);
78 IUFillSwitch(&SaveAlignBeforeSyncS[STAR_ALIGNMENT_DISABLED], "STAR_ALIGNMENT_DISABLED", "Disabled", ISS_ON);
79 IUFillSwitchVector(&SaveAlignBeforeSyncSP, SaveAlignBeforeSyncS, 2, getDeviceName(),
80 "STAR_ALIGNMENT", "Star Alignment", ALIGNMENT_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
81
82 // Mount's versions
83 IUFillText(&RSTVersionsT[FIRMWARE], "FIRMWARE", "Firmware Version", "");
84 IUFillText(&RSTVersionsT[SERIALNUMBER], "SERIALNUMBER", "Serial Number", "");
85 IUFillTextVector(&RSTVersionsTP, RSTVersionsT, 2, getDeviceName(), "RST_VERSIONS", "Versions", GENERAL_INFO_TAB, IP_RO, 0,
86 IPS_IDLE);
87
88 // Pull Voltage & Temperatures (possible to disable to reduce load on Serial bus)
89 IUFillSwitch(&PullVoltTempS[PULL_VOLTTEMP_ENABLED], "PULL_VOLTTEMP_ENABLED", "Enabled", ISS_OFF);
90 IUFillSwitch(&PullVoltTempS[PULL_VOLTTEMP_DISABLED], "PULL_VOLTTEMP_DISABLED", "Disabled", ISS_ON);
91 IUFillSwitchVector(&PullVoltTempSP, PullVoltTempS, 2, getDeviceName(),
92 "PULL_VOLTTEMP", "Pull V. & T.", GENERAL_INFO_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
93
94 // Voltage & Temperatures
95 IUFillNumber(&RSTVoltTempN[VOLTAGE], "VOLTAGE", "Input Voltage (V)", "%2.1f", 0, 20., 0., 0.);
96 IUFillNumber(&RSTVoltTempN[BOARD_TEMPERATURE], "BOARD_TEMPERATURE", "Board Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
97 IUFillNumber(&RSTVoltTempN[RA_M_TEMPERATURE], "RA_M_TEMPERATURE", "RA-Motor Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
98 IUFillNumber(&RSTVoltTempN[DE_M_TEMPERATURE], "DE_M_TEMPERATURE", "DEC-Motor Temp. (°C)", "%2.1f", -50, 70., 0., 0.);
99 IUFillNumberVector(&RSTVoltTempNP, RSTVoltTempN, 4, getDeviceName(), "RST_VOLT_TEMP", "Volt. & Temp.", GENERAL_INFO_TAB,
100 IP_RO, 0,
101 IPS_IDLE);
102
103 // Motor powers
104 IUFillNumber(&RSTMotorPowN[RA_M_POWER], "RA_M_POWER", "RA-Motor (%)", "%3.1f", 0, 100., 0., 0.);
105 IUFillNumber(&RSTMotorPowN[DE_M_POWER], "DE_M_POWER", "DE-Motor (%)", "%3.1f", 0, 100., 0., 0.);
106 IUFillNumberVector(&RSTMotorPowNP, RSTMotorPowN, 2, getDeviceName(), "RST_MOTOR_POW", "Motor Power", GENERAL_INFO_TAB,
107 IP_RO, 0,
108 IPS_IDLE);
109
110 // Horizontal Coords
111 IUFillNumber(&HorizontalCoordsN[AXIS_AZ], "AZ", "Az D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
112 IUFillNumber(&HorizontalCoordsN[AXIS_ALT], "ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
113 IUFillNumberVector(&HorizontalCoordsNP, HorizontalCoordsN, 2, getDeviceName(), "HORIZONTAL_COORD",
114 "Horizontal Coord", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
115
116 AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
117 AddTrackMode("TRACK_SOLAR", "Solar");
118 AddTrackMode("TRACK_LUNAR", "Lunar");
119 AddTrackMode("TRACK_CUSTOM", "Guide");
120
121 IUFillNumber(&GuideRateN[0], "GUIDE_RATE", "x Sidereal", "%g", 0.1, 1.0, 0.1, 0.5);
122 IUFillNumberVector(&GuideRateNP, GuideRateN, 1, getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RW, 0,
123 IPS_IDLE);
124
125 IUFillNumber(&SlewSpeedsN[SLEW_SPEED_MAX], "SLEW_SPEED_MAX", "Max (x Siderial)", "%g", 0, 2000, 0, 0);
126 IUFillNumber(&SlewSpeedsN[SLEW_SPEED_FIND], "SLEW_SPEED_FIND", "Find (x Siderial)", "%g", 0, 2000, 0, 0);
127 IUFillNumber(&SlewSpeedsN[SLEW_SPEED_CENTERING], "SLEW_SPEED_CENTERING", "Centering (x Siderial)", "%g", 0, 2000, 0, 0);
128 IUFillNumberVector(&SlewSpeedsNP, SlewSpeedsN, 3, getDeviceName(), "SLEW_SPEED", "Slew speed", MOTION_TAB, IP_RW, 0,
129 IPS_IDLE);
130
132
134
136
137 return true;
138}
139
144{
146
147 if (isConnected())
148 {
149 defineProperty(&HorizontalCoordsNP);
150 //defineProperty(&HomeSP);
151
152 defineProperty(&GuideRateNP);
153 defineProperty(&SlewSpeedsNP);
154
155 defineProperty(&SaveAlignBeforeSyncSP);
156 defineProperty(&RSTVersionsTP);
157 defineProperty(&PullVoltTempSP);
158 defineProperty(&RSTVoltTempNP);
159 defineProperty(&RSTMotorPowNP);
160
162 }
163 else
164 {
165 deleteProperty(HorizontalCoordsNP.name);
166 //deleteProperty(HomeSP.name);
167
168 deleteProperty(GuideRateNP.name);
169 deleteProperty(SlewSpeedsNP.name);
170
171 deleteProperty(SaveAlignBeforeSyncSP.name);
172 deleteProperty(RSTVersionsTP.name);
173 deleteProperty(PullVoltTempSP.name);
174 deleteProperty(RSTVoltTempNP.name);
175 deleteProperty(RSTMotorPowNP.name);
176 }
177
179
180 return true;
181}
182
186bool Rainbow::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
187{
188 // Check guider interface
189 if (GI::processNumber(dev, name, values, names, n))
190 return true;
191
192 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
193 {
194 // Horizontal Coordinates
195 if (!strcmp(name, HorizontalCoordsNP.name))
196 {
197 int i = 0, nset = 0;
198 double newAlt = -1, newAz = -1;
199 INumber *horp = nullptr;
200
201 for (nset = i = 0; i < n; i++)
202 {
203 if (horp == &HorizontalCoordsN[AXIS_ALT])
204 {
205 newAlt = values[i];
206 nset += newAlt >= -90. && newAlt <= 90.0;
207 }
208 else if (horp == &HorizontalCoordsN[AXIS_AZ])
209 {
210 newAz = values[i];
211 nset += newAz >= 0. && newAz <= 360.0;
212 }
213 }
214
215 if (nset == 2)
216 {
217 if (slewToHorizontalCoords(newAz, newAlt))
218 {
220 HorizontalCoordsNP.s = IPS_BUSY;
221 IDSetNumber(&HorizontalCoordsNP, nullptr);
222 return true;
223 }
224 else
225 {
226 HorizontalCoordsNP.s = IPS_ALERT;
227 IDSetNumber(&HorizontalCoordsNP, nullptr);
228 LOG_ERROR("Failed to slew to target coordinates.");
229 return true;
230 }
231 }
232 else
233 {
234 HorizontalCoordsNP.s = IPS_ALERT;
235 IDSetNumber(&HorizontalCoordsNP, "Altitude or Azimuth missing or invalid");
236 return true;
237 }
238 }
239 // Guide Rate
240 else if (!strcmp(name, GuideRateNP.name))
241 {
242 if (setGuideRate(values[0]))
243 {
244 IUUpdateNumber(&GuideRateNP, values, names, n);
245 GuideRateNP.s = IPS_OK;
246 LOGF_INFO("Guide rate updated to %.2fX sidereal rate.", values[0]);
247 }
248 else
249 GuideRateNP.s = IPS_ALERT;
250
251 IDSetNumber(&GuideRateNP, nullptr);
252 return true;
253 }
254 // Slew speeds
255 else if (!strcmp(name, SlewSpeedsNP.name))
256 {
257
258 if (setSlewSpeedVal(SLEW_SPEED_MAX, values[SLEW_SPEED_MAX])
259 && setSlewSpeedVal(SLEW_SPEED_FIND, values[SLEW_SPEED_FIND])
260 && setSlewSpeedVal(SLEW_SPEED_CENTERING, values[SLEW_SPEED_CENTERING])
261 )
262 {
263 IUUpdateNumber(&SlewSpeedsNP, values, names, n);
264 SlewSpeedsNP.s = IPS_OK;
265 LOG_INFO("Slew speeds updated.");
266 }
267 else
268 SlewSpeedsNP.s = IPS_ALERT;
269 IDSetNumber(&SlewSpeedsNP, nullptr);
270 return true;
271 }
272 }
273
274
275 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
276}
277
281bool Rainbow::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
282{
283 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
284 {
285 // Homing
286 // Star Align
287 if (!strcmp(SaveAlignBeforeSyncSP.name, name))
288 {
289
290 IUUpdateSwitch(&SaveAlignBeforeSyncSP, states, names, n);
291 SaveAlignBeforeSyncSP.s = IPS_OK;
292 saveConfig(true, SaveAlignBeforeSyncSP.name);
293 IDSetSwitch(&SaveAlignBeforeSyncSP, nullptr);
294 return true;
295 }
296 // Pull RST's Voltage and Temperatures
297 else if (!strcmp(PullVoltTempSP.name, name))
298 {
299 IUUpdateSwitch(&PullVoltTempSP, states, names, n);
300 if (PullVoltTempS[PULL_VOLTTEMP_DISABLED].s == ISS_ON)
301 {
302 RSTVoltTempN[VOLTAGE].value = 0.;
303 RSTVoltTempN[BOARD_TEMPERATURE].value = 0.;
304 RSTVoltTempN[RA_M_TEMPERATURE].value = 0.;
305 RSTVoltTempN[DE_M_TEMPERATURE].value = 0.;
306 RSTMotorPowN[RA_M_POWER].value = 0.;
307 RSTMotorPowN[DE_M_POWER].value = 0.;
308 RSTVoltTempNP.s = IPS_IDLE;
309 IDSetNumber(&RSTVoltTempNP, nullptr);
310 RSTMotorPowNP.s = IPS_IDLE;
311 IDSetNumber(&RSTMotorPowNP, nullptr);
312 PullVoltTempSP.s = IPS_IDLE;
313 LOG_INFO("Pulling RST's Voltage and Temperatures set to: off");
314 }
315 else
316 {
317 PullVoltTempSP.s = IPS_OK;
318 LOG_INFO("Pulling RST's Voltage and Temperatures set to: on");
319 }
320 IDSetSwitch(&PullVoltTempSP, nullptr);
321 return true;
322 }
323
324 }
325
326 return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
327}
328
333{
335 LOGF_INFO("Detected firmware %s", m_Version.c_str());
336
337 if (getTrackingState())
339 if (getGuideRate())
340 IDSetNumber(&GuideRateNP, nullptr);
341 if (getSlewSpeedVal(SLEW_SPEED_MAX) && (getSlewSpeedVal(SLEW_SPEED_FIND) && getSlewSpeedVal(SLEW_SPEED_CENTERING)))
342 IDSetNumber(&SlewSpeedsNP, nullptr);
343
344 double longitude = 0, latitude = 90;
345 // Get value from config file if it exists.
346 IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LONG", &longitude);
347 IUGetConfigNumber(getDeviceName(), "GEOGRAPHIC_COORD", "LAT", &latitude);
348
349 if (InitPark())
350 {
351 // If loading parking data is successful, we just set the default parking values.
352 SetAxis1ParkDefault(latitude >= 0 ? 0 : 180);
353 SetAxis2ParkDefault(latitude);
354 }
355 else
356 {
357 // Otherwise, we set all parking data to default in case no parking data is found.
358 SetAxis1Park(latitude >= 0 ? 0 : 180);
359 SetAxis2Park(latitude);
360 SetAxis1ParkDefault(latitude >= 0 ? 0 : 180);
361 SetAxis2ParkDefault(latitude);
362 }
363
366}
367
371bool Rainbow::updateLocation(double latitude, double longitude, double elevation)
372{
373 INDI_UNUSED(elevation);
374
375 char cmd[DRIVER_LEN] = {0};
376 int degrees, minutes, seconds;
377
378 // Convert from INDI standard to regular east/west -180 to 180
379 if (longitude > 180)
380 longitude -= 360;
381
382 // Rainbow defines EAST as - and WEST as +
383 // which is opposite of the standard
384 longitude *= -1;
385
386 getSexComponents(longitude, &degrees, &minutes, &seconds);
387
388 snprintf(cmd, DRIVER_LEN, ":Sg%c%03d*%02d'%02d#", longitude >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
389
390 if (!sendCommand(cmd))
391 return false;
392
393 getSexComponents(latitude, &degrees, &minutes, &seconds);
394
395 snprintf(cmd, DRIVER_LEN, ":St%c%02d*%02d'%02d#", latitude >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
396
397 return sendCommand(cmd);
398}
399
403//bool Rainbow::updateTime(ln_date *utc, double utc_offset)
404//{
405
406//}
407
412{
413 return getFirmwareVersion();
414}
415
420{
421 char res[DRIVER_LEN] = {0};
422
423 if (sendCommand(":AV#", res) == false)
424 return false;
425
426 // :AV190905 --> 190905
427 m_Version = std::string(res + 3);
428
429 // Write Firmware and Serial Number to INDI Control Panel
430 if (isConnected()) // executed onlyafter properties are updated
431 {
432 char fw[16] = {0}, sn[16] = {0};
433 sscanf(res + 3, "%6s", fw);
434 memset(res, 0, sizeof res);
435 IUSaveText(&RSTVersionsT[FIRMWARE], fw);
436
437 if (sendCommand(":AS#", res) == false)
438 return false;
439 sscanf(res + 3, "%6s", sn);
440 IUSaveText(&RSTVersionsT[SERIALNUMBER], sn);
441
442 RSTVersionsTP.s = IPS_OK;
443 IDSetText(&RSTVersionsTP, nullptr);
444 }
445 return true;
446}
447
451bool Rainbow::SetTrackEnabled(bool enabled)
452{
453 char cmd[DRIVER_LEN] = {0};
454 snprintf(cmd, DRIVER_LEN, ":Ct%c#", enabled ? 'A' : 'L');
455 return sendCommand(cmd);
456}
457
462{
463 char res[DRIVER_LEN] = {0};
464
465 if (sendCommand(":AT#", res) == false)
466 return false;
467
468 TrackStateSP[TRACK_ON].setState((res[3] == '1') ? ISS_ON : ISS_OFF);
469 TrackStateSP[TRACK_OFF].setState((res[3] == '0') ? ISS_ON : ISS_OFF);
471
472 return true;
473}
474
479{
480 bool ret = false;
481 ret = sendCommand(":Ch#");
482 if (ret)
484 return ret;
485}
486
490bool Rainbow::setGuideRate(double rate)
491{
492 char cmd[DRIVER_LEN] = {0};
493 snprintf(cmd, DRIVER_LEN, ":Cu0=%3.1f#", rate);
494 return sendCommand(cmd);
495}
496
501{
502 char res[DRIVER_LEN] = {0};
503 char rate[4] = {0};
504
505 if (!sendCommand(":CU0#", res))
506 return false;
507
508 memcpy(rate, res + 5, 3);
509
510 GuideRateN[0].value = std::stod(rate);
511 GuideRateNP.s = IPS_OK;
512 return true;
513}
514
518bool Rainbow::setSlewSpeedVal(int speedtype, double rate)
519{
520 char cmd[DRIVER_LEN] = {0};
521 snprintf(cmd, DRIVER_LEN, ":Cu%d=%g#", speedtype == SLEW_SPEED_MAX ? 3 : (speedtype == SLEW_SPEED_FIND ? 2 : 1), rate);
522 LOGF_INFO("slew speed set to enum %d and value %g", speedtype, rate);
523 return sendCommand(cmd);
524}
525
529bool Rainbow::getSlewSpeedVal(int speedtype)
530{
531 char res[DRIVER_LEN] = {0};
532 char rate[4] = {0};
533 char cmd[DRIVER_LEN] = {0};
534 snprintf(cmd, DRIVER_LEN, ":CU%d#", speedtype == SLEW_SPEED_MAX ? 3 : (speedtype == SLEW_SPEED_FIND ? 2 : 1));
535 if (sendCommand(cmd, res))
536 {
537 memcpy(rate, res + 5, 4);
538 SlewSpeedsN[speedtype].value = std::stod(rate);
539 SlewSpeedsNP.s = IPS_OK;
540 }
541 else
542 {
543 SlewSpeedsNP.s = IPS_ALERT;
544 return false;
545 }
546 return true;
547}
548
553{
554 SetAxis1Park(m_CurrentAZ);
555 SetAxis2Park(m_CurrentAL);
556 return true;
557}
558
563{
564 SetAxis1Park(0);
565 SetAxis2Park(0);
566 return true;
567}
568
573{
574 double parkAZ = GetAxis1Park();
575 double parkAlt = GetAxis2Park();
576
577 if (slewToHorizontalCoords(parkAZ, parkAlt))
578 {
580 HorizontalCoordsNP.s = IPS_BUSY;
581 IDSetNumber(&HorizontalCoordsNP, nullptr);
582 LOG_INFO("Parking is in progress...");
583 return true;
584 }
585
586 return false;
587
588}
589
594{
595 if (SetTrackEnabled(true))
596 {
601
602 SetParked(false);
603 return true;
604 }
605
606 return false;
607}
608
613{
614 char res[DRIVER_LEN] = {0};
615 int nbytes_read = 0;
616 if (tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, 1, &nbytes_read) == TTY_OK)
617 {
618 LOGF_DEBUG("SlewCheck <%s>", res);
619 if (!strcmp(res, ":MM0#") || !strcmp(res, ":CHO#"))
620 {
621 m_SlewErrorCode = 0;
622 return true;
623 }
624 else if (!strcmp(res, ":MML#"))
625 {
626 m_SlewErrorCode = 1;
627 }
628 else if (!strcmp(res, ":MMU#"))
629 {
630 m_SlewErrorCode = 2;
631 }
632 else if (!strcmp(res, ":MME#"))
633 {
634 m_SlewErrorCode = 3;
635 }
636 else if (!strcmp(res, ":CH0#"))
637 {
638 m_SlewErrorCode = 4;
639 }
640 else if (!strcmp(res, ":CH<#"))
641 {
642 m_SlewErrorCode = 5;
643 }
644 }
645
646
647 return false;
648}
649
654{
655 if (!isConnected())
656 return false;
657
659 {
660 // Check if Slewing is complete
661 if (isSlewComplete())
662 {
663 HorizontalCoordsNP.s = IPS_OK;
664 IDSetNumber(&HorizontalCoordsNP, nullptr);
665
666 if (HomeSP.getState() == IPS_BUSY)
667 {
668 LOG_INFO("Homing completed successfully.");
669 HomeSP.reset();
671 HomeSP.apply();
673 }
674 else
675 {
677 // For Horizontal Goto, we must explicitly set tracking ON again.
678 if (m_GotoType == Horizontal)
679 SetTrackEnabled(true);
680 LOG_INFO("Slew is complete. Tracking...");
681 }
682 }
683 else if (m_SlewErrorCode > 0)
684 {
685 HorizontalCoordsNP.s = IPS_ALERT;
686 IDSetNumber(&HorizontalCoordsNP, nullptr);
687
689
690 if (HomeSP.getState() == IPS_BUSY)
691 {
693 HomeSP.reset();
695 HomeSP.apply();
696 LOGF_ERROR("Homing error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
697 }
698 else
699 {
700 // JM TODO CHECK: Does the mount RESUME tracking after slew failure or it completely stops idle?
702 LOGF_ERROR("Slewing error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
703 }
704 }
705 }
706 else if (TrackState == SCOPE_PARKING)
707 {
708 if (isSlewComplete())
709 {
710 SetParked(true);
711 HorizontalCoordsNP.s = IPS_OK;
712 IDSetNumber(&HorizontalCoordsNP, nullptr);
713 }
714 else if (m_SlewErrorCode > 0)
715 {
716 HorizontalCoordsNP.s = IPS_ALERT;
718 // JM TODO CHECK: Does the mount RESUME tracking after slew failure or it completely stops idle?
720 LOGF_ERROR("Parking error: %s", getSlewErrorString(m_SlewErrorCode).c_str());
721 IDSetNumber(&HorizontalCoordsNP, nullptr);
722 }
723 }
724
725 // Equatorial Coords
726 if (!getRA() || !getDE())
727 {
729 LOG_ERROR("Error reading RA/DEC.");
730 EqNP.apply();
731 return false;
732 }
733
734 // Horizontal Coords
735 if (!getAZ() || !getAL())
736 {
737 HorizontalCoordsNP.s = IPS_ALERT;
738 }
739 else
740 {
741 HorizontalCoordsN[AXIS_AZ].value = m_CurrentAZ;
742 HorizontalCoordsN[AXIS_ALT].value = m_CurrentAL;
743 }
744 IDSetNumber(&HorizontalCoordsNP, nullptr);
745
747
748 NewRaDec(m_CurrentRA, m_CurrentDE);
749
750 if (PullVoltTempS[PULL_VOLTTEMP_ENABLED].s == ISS_ON)
751 {
752 // Get Voltage and Temperatures
753 char res[DRIVER_LEN] = {0};
754 float v, bT, rT, dT;
755 if (sendCommand(":Cv#", res) == false)
756 return false;
757 sscanf(res + 3, "%f", &v);
758 memset(res, 0, sizeof res);
759 RSTVoltTempN[VOLTAGE].value = v;
760 if (sendCommand(":CT#", res) == false)
761 return false;
762 sscanf(res + 3, "%f|%f|%f", &bT, &rT, &dT);
763 memset(res, 0, sizeof res);
764 RSTVoltTempN[BOARD_TEMPERATURE].value = bT;
765 RSTVoltTempN[RA_M_TEMPERATURE].value = rT;
766 RSTVoltTempN[DE_M_TEMPERATURE].value = dT;
767 RSTVoltTempNP.s = IPS_OK;
768 IDSetNumber(&RSTVoltTempNP, nullptr);
769
770 // Get Motor Powers
771 float rP, dP;
772 if (sendCommand(":CP#", res) == false)
773 return false;
774 sscanf(res + 3, "%f|%f", &dP, &rP);
775 memset(res, 0, sizeof res);
776 RSTMotorPowN[RA_M_POWER].value = rP;
777 RSTMotorPowN[DE_M_POWER].value = dP;
778 RSTMotorPowNP.s = IPS_OK;
779 IDSetNumber(&RSTMotorPowNP, nullptr);
780 }
781 return true;
782}
783
787bool Rainbow::Goto(double ra, double dec)
788{
789 const struct timespec timeout = {0, 100000000L};
790
791 char RAStr[64] = {0}, DecStr[64] = {0};
792 fs_sexa(RAStr, ra, 2, 36000);
793 fs_sexa(DecStr, dec, 2, 36000);
794
795 // If moving, let's stop it first.
796 if (EqNP.getState() == IPS_BUSY)
797 {
798 if (!isSimulation() && Abort() == false)
799 {
801 LOG_ERROR("Abort slew failed.");
802 AbortSP.apply();
803 return false;
804 }
805
808 LOG_ERROR("Slew aborted.");
809 AbortSP.apply();
810 EqNP.apply();
811
813 {
821 }
822
823 // sleep for 100 mseconds
824 nanosleep(&timeout, nullptr);
825 }
826
827 if (slewToEquatorialCoords(ra, dec) == false)
828 {
829 LOGF_ERROR("Error Slewing to JNow RA %s - DEC %s", RAStr, DecStr);
830 return false;
831 }
832
834 LOGF_INFO("Slewing to RA: %s - DE: %s", RAStr, DecStr);
835
836 // Also set Horizontal Coors to BUSY
837 HorizontalCoordsNP.s = IPS_BUSY;
838 IDSetNumber(&HorizontalCoordsNP, nullptr);
839
840 return true;
841}
842
847{
848 char res[DRIVER_LEN] = {0};
849 if (sendCommand(":GR#", res) == false)
850 return false;
851
852 // Skip :GR and read value
853 return (f_scansexa(res + 3, &m_CurrentRA) == 0);
854}
855
860{
861 char res[DRIVER_LEN] = {0};
862 if (sendCommand(":GD#", res) == false)
863 return false;
864
865 // Skip :GD and read value
866 return (f_scansexa(res + 3, &m_CurrentDE) == 0);
867}
868
869
873bool Rainbow::setRA(double ra)
874{
875 char cmd[DRIVER_LEN] = {0};
876 char res[DRIVER_LEN] = {0};
877 int degrees, minutes;
878 double seconds;
879
880 getSexComponentsIID(ra, &degrees, &minutes, &seconds);
881
882 snprintf(cmd, DRIVER_LEN, ":Sr%02d:%02d:%04.1f#", degrees, minutes, seconds);
883
884 if (!sendCommand(cmd, res, -1, 1))
885 return false;
886
887 return res[0] == '1';
888}
889
893bool Rainbow::setDE(double de)
894{
895 char cmd[DRIVER_LEN] = {0};
896 char res[DRIVER_LEN] = {0};
897 int degrees, minutes;
898 double seconds;
899
900 getSexComponentsIID(de, &degrees, &minutes, &seconds);
901
902 snprintf(cmd, DRIVER_LEN, ":Sd%c%02d*%02d:%04.1f#", de >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
903
904 if (!sendCommand(cmd, res, -1, 1))
905 return false;
906
907 return res[0] == '1';
908}
909
913bool Rainbow::slewToEquatorialCoords(double ra, double de)
914{
915 if (!setRA(ra) || !setDE(de))
916 return false;
917
918 if (sendCommand(":MS#"))
919 {
920 char RAStr[16], DEStr[16];
921 fs_sexa(RAStr, ra, 2, 36000);
922 fs_sexa(DEStr, de, 2, 36000);
923 LOGF_DEBUG("Slewing to RA (%s) DE (%s)...", RAStr, DEStr);
924 m_GotoType = Equatorial;
925 return true;
926 }
927
928 return false;
929}
930
934bool Rainbow::getAZ()
935{
936 char res[DRIVER_LEN] = {0};
937 if (sendCommand(":GZ#", res) == false)
938 return false;
939
940 // Skip :GZ and read value
941 return (f_scansexa(res + 3, &m_CurrentAZ) == 0);
942}
943
947bool Rainbow::getAL()
948{
949 char res[DRIVER_LEN] = {0};
950 if (sendCommand(":GA#", res) == false)
951 return false;
952
953 // Skip :GA and read value
954 return (f_scansexa(res + 3, &m_CurrentAL) == 0);
955}
956
960bool Rainbow::setAZ(double azimuth)
961{
962 char cmd[DRIVER_LEN] = {0};
963 int degrees, minutes;
964 double seconds;
965
966 getSexComponentsIID(azimuth, &degrees, &minutes, &seconds);
967
968 snprintf(cmd, DRIVER_LEN, ":Sz%03d*%02d:%04.1f#", degrees, minutes, seconds);
969
970 return sendCommand(cmd);
971}
972
976bool Rainbow::setAL(double altitude)
977{
978 char cmd[DRIVER_LEN] = {0};
979 int degrees, minutes;
980 double seconds;
981
982 getSexComponentsIID(altitude, &degrees, &minutes, &seconds);
983
984 snprintf(cmd, DRIVER_LEN, ":Sa%c%02d*%02d:%04.1f#", degrees >= 0 ? '+' : '-', std::abs(degrees), minutes, seconds);
985
986 return sendCommand(cmd);
987}
988
992bool Rainbow::slewToHorizontalCoords(double azimuth, double altitude)
993{
994 if (!setAZ(azimuth) || !setAL(altitude))
995 return false;
996
997 if (sendCommand(":MA#"))
998 {
999 char AzStr[16], AltStr[16];
1000 fs_sexa(AzStr, azimuth, 2, 36000);
1001 fs_sexa(AltStr, altitude, 2, 36000);
1002 LOGF_DEBUG("Slewing to Az (%s) Alt (%s)...", AzStr, AltStr);
1003 m_GotoType = Horizontal;
1004 return true;
1005 }
1006
1007 return false;
1008}
1009
1014{
1015 switch (command)
1016 {
1017 case MOTION_START:
1018 if (!sendCommand(dir == DIRECTION_NORTH ? ":Mn#" : ":Ms#"))
1019 {
1020 LOG_ERROR("Error setting N/S motion direction.");
1021 return false;
1022 }
1023 else
1024 LOGF_DEBUG("Moving toward %s.", (dir == DIRECTION_NORTH) ? "North" : "South");
1025 break;
1026
1027 case MOTION_STOP:
1028 if (!sendCommand(":Q#"))
1029 {
1030 LOG_ERROR("Error stopping N/S motion.");
1031 return false;
1032 }
1033 else
1034 LOGF_DEBUG("Movement toward %s halted.", (dir == DIRECTION_NORTH) ? "North" : "South");
1035 break;
1036 }
1037
1038 return true;
1039}
1040
1045{
1046 switch (command)
1047 {
1048 case MOTION_START:
1049 if (!sendCommand(dir == DIRECTION_WEST ? ":Mw#" : ":Me#"))
1050 {
1051 LOG_ERROR("Error setting W/E motion direction.");
1052 return false;
1053 }
1054 else
1055 LOGF_DEBUG("Moving toward %s.", (dir == DIRECTION_WEST) ? "West" : "East");
1056 break;
1057
1058 case MOTION_STOP:
1059 if (!sendCommand(":Q#"))
1060 {
1061 LOG_ERROR("Error stopping W/E motion.");
1062 return false;
1063 }
1064 else
1065 LOGF_DEBUG("Movement toward %s halted.", (dir == DIRECTION_WEST) ? "West" : "East");
1066 break;
1067 }
1068
1069 return true;
1070}
1071
1076{
1078 {
1081 GuideNSNP[0].setValue(0);
1082 GuideNSNP[1].setValue(0);
1083 GuideWENP[0].setValue(0);
1084 GuideWENP[1].setValue(0);
1085
1086 if (m_GuideNSTID)
1087 {
1088 IERmTimer(m_GuideNSTID);
1089 m_GuideNSTID = 0;
1090 }
1091
1092 if (m_GuideWETID)
1093 {
1094 IERmTimer(m_GuideWETID);
1095 m_GuideWETID = 0;
1096 }
1097
1098 LOG_INFO("Guide aborted.");
1099 GuideNSNP.apply();
1100 GuideWENP.apply();
1101
1102 return true;
1103 }
1104
1105 return sendCommand(":Q#");
1106}
1107
1111bool Rainbow::Sync(double ra, double dec)
1112{
1113
1114 char cmd[DRIVER_LEN] = {0};
1115 if (SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED].s == ISS_ON)
1116 {
1117 snprintf(cmd, DRIVER_LEN, ":CN%07.3f%c%06.3f#", ra * 15.0, dec >= 0 ? '+' : '-', std::fabs(dec));
1118 }
1119 else
1120 {
1121 snprintf(cmd, DRIVER_LEN, ":Ck%07.3f%c%06.3f#", ra * 15.0, dec >= 0 ? '+' : '-', std::fabs(dec));
1122 }
1123
1124 if (sendCommand(cmd))
1125 {
1126 char RAStr[64] = {0}, DecStr[64] = {0};
1127 fs_sexa(RAStr, ra, 2, 36000);
1128 fs_sexa(DecStr, dec, 2, 36000);
1129 LOGF_INFO("Synced to RA %s DE %s%s", RAStr, DecStr,
1130 SaveAlignBeforeSyncS[STAR_ALIGNMENT_ENABLED].s == ISS_ON ? ", and saved as alignment point." : "");
1131 return true;
1132 }
1133 return false;
1134}
1135
1139bool Rainbow::SetTrackMode(uint8_t mode)
1140{
1141 switch (mode)
1142 {
1143 case TRACK_SIDEREAL:
1144 return sendCommand(":CtR#");
1145 case TRACK_SOLAR:
1146 return sendCommand(":CtS#");
1147 case TRACK_LUNAR:
1148 return sendCommand(":CtL#");
1149 case TRACK_CUSTOM:
1150 return sendCommand(":CtU#");
1151
1152 }
1153
1154 return false;
1155}
1156
1161{
1162 switch(index)
1163 {
1164 case SLEW_MAX:
1165 return sendCommand(":RS#");
1166 case SLEW_FIND:
1167 return sendCommand(":RM#");
1168 case SLEW_CENTERING:
1169 return sendCommand(":RC#");
1170 case SLEW_GUIDE:
1171 return sendCommand(":RG#");
1172 }
1173
1174 return false;
1175}
1179const std::string Rainbow::getSlewErrorString(uint8_t code)
1180{
1181 switch (code)
1182 {
1183 case 1:
1184 return "The altitude of the target is lower than lower limit.";
1185 case 2:
1186 return "The altitude of the target is higher than upper limit.";
1187 case 3:
1188 return "Slewing was canceled by user";
1189 case 4:
1190 return "RA Axis homing failed.";
1191 case 5:
1192 return "DE Axis homing failed.";
1193 }
1194
1195 return "Unknown error";
1196}
1197
1202{
1203 return guide(Direction::North, ms);
1204}
1205
1210{
1211 return guide(Direction::South, ms);
1212}
1213
1218{
1219 return guide(Direction::East, ms);
1220}
1221
1226{
1227 return guide(Direction::West, ms);
1228}
1229
1233IPState Rainbow::guide(Direction direction, uint32_t ms)
1234{
1235 // set up direction properties
1236 char dc = 'x';
1237 char cmd[DRIVER_LEN] = {0};
1238 // ISwitchVectorProperty *moveSP = &MovementNSSP;
1239 // ISwitch moveS = MovementNSSP[0];
1240 // int* guideTID = &m_GuideNSTID;
1241 int* guideTID = &m_GuideNSTID;
1242 int index = 0;
1243
1244
1245 // set up pointers to the various things needed
1246 switch (direction)
1247 {
1248 case North:
1249 dc = 'N';
1250 guideTID = &m_GuideNSTID;
1251 break;
1252 case South:
1253 dc = 'S';
1254 guideTID = &m_GuideNSTID;
1255 index = 1;
1256 break;
1257 case East:
1258 dc = 'E';
1259 guideTID = &m_GuideWETID;
1260 break;
1261 case West:
1262 dc = 'W';
1263 guideTID = &m_GuideWETID;
1264 index = 1;
1265 break;
1266 }
1267
1269 {
1270 LOG_ERROR("Cannot guide while moving.");
1271 return IPS_ALERT;
1272 }
1273
1274 auto directionProperty = direction > North ? MovementWESP : MovementNSSP;
1275
1276 // If already moving (no pulse command), then stop movement
1277 if (directionProperty.getState() == IPS_BUSY)
1278 {
1279 LOG_DEBUG("Already moving - stop");
1280 sendCommand(":Q#");
1281 }
1282
1283 if (*guideTID)
1284 {
1285 LOGF_DEBUG("Stop timer %c", dc);
1286 IERmTimer(*guideTID);
1287 *guideTID = 0;
1288 }
1289
1290 // Make sure TRACKING is set to Guide
1292 {
1297 LOG_INFO("Tracking mode switched to guide.");
1298 }
1299
1300 // Make sure SLEWING SPEED is set to Guide
1302 {
1303 // Set slew to guiding
1305 SlewRateSP.reset();
1307 SlewRateSP.apply();
1308 }
1309
1310 directionProperty[index].setState(ISS_ON);
1311 snprintf(cmd, DRIVER_LEN, ":M%c#", std::tolower(dc));
1312
1313 // start movement at HC button rate 1
1314 if (!sendCommand(cmd))
1315 {
1316 LOGF_ERROR("Start motion %c failed", dc);
1317 return IPS_ALERT;
1318 }
1319
1320 // start the guide timeout timer
1321 addGuideTimer(direction, ms);
1322 return IPS_BUSY;
1323}
1324
1329{
1330 static_cast<Rainbow *>(p)->guideTimeout(North);
1331}
1332
1337{
1338 static_cast<Rainbow *>(p)->guideTimeout(South);
1339}
1340
1345{
1346 static_cast<Rainbow *>(p)->guideTimeout(West);
1347}
1348
1353{
1354 static_cast<Rainbow *>(p)->guideTimeout(East);
1355}
1356
1361{
1362 char cmd[DRIVER_LEN] = {0};
1363 switch(direction)
1364 {
1365 case North:
1366 case South:
1369 GuideNSNP[0].setValue(0);
1370 GuideNSNP[1].setValue(0);
1372 m_GuideNSTID = 0;
1373 GuideNSNP.apply();
1374 snprintf(cmd, DRIVER_LEN, ":Q%c#", direction == North ? 'n' : 's');
1375 break;
1376 case East:
1377 case West:
1380 GuideWENP[0].setValue(0);
1381 GuideWENP[1].setValue(0);
1383 m_GuideWETID = 0;
1384 GuideWENP.apply();
1385 snprintf(cmd, DRIVER_LEN, ":Q%c#", direction == East ? 'e' : 'w');
1386 break;
1387 }
1389 LOGF_DEBUG("Guide %c finished", "NSWE"[direction]);
1390}
1391
1395void Rainbow::addGuideTimer(Direction direction, uint32_t ms)
1396{
1397 switch(direction)
1398 {
1399 case North:
1400 m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperN, this);
1401 break;
1402 case South:
1403 m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperS, this);
1404 break;
1405 case East:
1406 m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperE, this);
1407 break;
1408 case West:
1409 m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperW, this);
1410 break;
1411 }
1412}
1413
1417bool Rainbow::getLocalTime(char *timeString)
1418{
1419 if (isSimulation())
1420 {
1421 time_t now = time (nullptr);
1422 strftime(timeString, MAXINDINAME, "%T", localtime(&now));
1423 }
1424 else
1425 {
1426 int h, m, s;
1427 char response[DRIVER_LEN] = {0};
1428 if (!sendCommand(":GL#", response))
1429 return false;
1430
1431 if (sscanf(response + 3, "%d:%d:%d", &h, &m, &s) != 3)
1432 {
1433 LOG_WARN("Failed to get time from device.");
1434 return false;
1435 }
1436 snprintf(timeString, MAXINDINAME, "%02d:%02d:%02d", h, m, s);
1437 }
1438
1439 return true;
1440}
1441
1445bool Rainbow::getLocalDate(char *dateString)
1446{
1447 if (isSimulation())
1448 {
1449 time_t now = time (nullptr);
1450 strftime(dateString, MAXINDINAME, "%F", localtime(&now));
1451 }
1452 else
1453 {
1454 int dd, mm, yy;
1455 char response[DRIVER_LEN] = {0};
1456 char mell_prefix[3] = {0};
1457 if (!sendCommand(":GC#", response))
1458 return false;
1459
1460 if (sscanf(response + 3, "%d%*c%d%*c%d", &mm, &dd, &yy) != 3)
1461 {
1462 LOG_WARN("Failed to get date from device.");
1463 return false;
1464 }
1465 else
1466 {
1467 if (yy > 50)
1468 strncpy(mell_prefix, "19", 3);
1469 else
1470 strncpy(mell_prefix, "20", 3);
1471 /* We need to have it in YYYY-MM-DD ISO format */
1472 snprintf(dateString, 32, "%s%02d-%02d-%02d", mell_prefix, yy, mm, dd);
1473 }
1474 }
1475
1476 return true;
1477}
1478
1482bool Rainbow::getUTFOffset(double *offset)
1483{
1484 if (isSimulation())
1485 {
1486 *offset = 3;
1487 return true;
1488 }
1489
1490 int rst135_utc_offset = 0;
1491
1492 char response[DRIVER_LEN] = {0};
1493 if (!sendCommand(":GG#", response))
1494 return false;
1495
1496 if (sscanf(response + 3, "%d", &rst135_utc_offset) != 1)
1497 {
1498 LOG_WARN("Failed to get UTC offset from device.");
1499 return false;
1500 }
1501
1502 // LX200 TimeT Offset is defined at the number of hours added to LOCAL TIME to get TimeT. This is contrary to the normal definition.
1503 *offset = rst135_utc_offset * -1;
1504 return true;
1505}
1506
1511{
1512 char cdate[MAXINDINAME] = {0};
1513 char ctime[MAXINDINAME] = {0};
1514 struct tm ltm;
1515 struct tm utm;
1516
1517 memset(&ltm, 0, sizeof(ltm));
1518 memset(&utm, 0, sizeof(utm));
1519
1520 time_t time_epoch;
1521
1522 double offset = 0;
1523 if (getUTFOffset(&offset))
1524 {
1525 char utcStr[8] = {0};
1526 snprintf(utcStr, 8, "%.2f", offset);
1527 TimeTP[OFFSET].setText(utcStr);
1528 }
1529 else
1530 {
1531 LOG_WARN("Could not obtain UTC offset from mount!");
1532 return false;
1533 }
1534
1535 if (getLocalTime(ctime) == false)
1536 {
1537 LOG_WARN("Could not obtain local time from mount!");
1538 return false;
1539 }
1540
1541 if (getLocalDate(cdate) == false)
1542 {
1543 LOG_WARN("Could not obtain local date from mount!");
1544 return false;
1545 }
1546
1547 // To ISO 8601 format in LOCAL TIME!
1548 char datetime[MAXINDINAME] = {0};
1549 snprintf(datetime, MAXINDINAME, "%sT%s", cdate, ctime);
1550
1551 // Now that date+time are combined, let's get tm representation of it.
1552 if (strptime(datetime, "%FT%T", &ltm) == nullptr)
1553 {
1554 LOGF_WARN("Could not process mount date and time: %s", datetime);
1555 return false;
1556 }
1557
1558 ltm.tm_isdst = 0;
1559 // Get local time epoch in UNIX seconds
1560 time_epoch = mktime(&ltm);
1561
1562 // LOCAL to UTC by subtracting offset.
1563 time_epoch -= static_cast<int>(offset * 3600.0);
1564
1565 // Get UTC (we're using localtime_r, but since we shifted time_epoch above by UTCOffset, we should be getting the real UTC time)
1566 localtime_r(&time_epoch, &utm);
1567
1568 // Format it into the final UTC ISO 8601
1569 strftime(cdate, MAXINDINAME, "%Y-%m-%dT%H:%M:%S", &utm);
1570 TimeTP[UTC].setText(cdate);
1571
1572 LOGF_DEBUG("Mount controller UTC Time: %s", TimeTP[UTC].getText());
1573 LOGF_DEBUG("Mount controller UTC Offset: %s", TimeTP[OFFSET].getText());
1574
1575 // Let's send everything to the client
1577 TimeTP.apply();
1578
1579 return true;
1580}
1581
1586{
1587 double longitude {0}, latitude {0};
1588 double dd = 0, mm = 0, ssf = 0;
1589 char response[DRIVER_LEN] = {0};
1590
1591 if (isSimulation())
1592 {
1593 LocationNP[LOCATION_LATITUDE].setValue(29.5);
1594 LocationNP[LOCATION_LONGITUDE].setValue(48.0);
1595 LocationNP[LOCATION_ELEVATION].setValue(10);
1597 LocationNP.apply();
1598 return true;
1599 }
1600
1601 // Latitude
1602 if (!sendCommand(":Gt#", response))
1603 return false;
1604
1605 if (sscanf(response + 3, "%lf%*[^0-9]%lf%*[^0-9]%lf", &dd, &mm, &ssf) != 3)
1606 {
1607 LOG_WARN("Failed to get site latitude from device.");
1608 return false;
1609 }
1610 else
1611 {
1612 if (dd > 0)
1613 latitude = dd + mm / 60.0 + ssf / 3600.0;
1614 else
1615 latitude = dd - mm / 60.0 - ssf / 3600.0;
1616 }
1617
1618 // Longitude
1619 if (!sendCommand(":Gg#", response))
1620 return false;
1621
1622 if (sscanf(response + 3, "%lf%*[^0-9]%lf%*[^0-9]%lf", &dd, &mm, &ssf) != 3)
1623 {
1624 LOG_WARN("Failed to get site longitude from device.");
1625 return false;
1626 }
1627 else
1628 {
1629 if (dd > 0)
1630 longitude = 360.0 - (dd + mm / 60.0 + ssf / 3600.0);
1631 else
1632 longitude = (dd - mm / 60.0 - ssf / 3600.0) * -1.0;
1633
1634 }
1635
1636 // Only update if different from current values
1637 // and then immediately save to config.
1638 if (std::abs(LocationNP[LOCATION_LONGITUDE].getValue() - longitude) > 0.001 ||
1639 std::abs(LocationNP[LOCATION_LATITUDE].getValue() - latitude) > 0.001)
1640 {
1641 LocationNP[LOCATION_LATITUDE].setValue(latitude);
1642 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
1643 LOGF_DEBUG("Mount Controller Latitude: %.3f Longitude: %.3f", LocationNP[LOCATION_LATITUDE].getValue(),
1644 LocationNP[LOCATION_LONGITUDE].getValue());
1645 LocationNP.apply();
1646 saveConfig(true, LocationNP.getName());
1647 }
1648
1649 return true;
1650}
1651
1656{
1657 float decAxisAlignmentOffset {0}, decAxis {0};
1658 char cyResponse[DRIVER_LEN] = {0}, cg3Response[DRIVER_LEN] = {0};
1659
1660 if (!sendCommand(":CG3#", cg3Response))
1661 return PIER_UNKNOWN;
1662
1663 sscanf(cg3Response + 4, "%f", &decAxisAlignmentOffset);
1664
1665 if (!sendCommand(":CY#", cyResponse))
1666 return PIER_UNKNOWN;
1667
1668 char rotationAngle[16] = {0};
1669
1670 snprintf(rotationAngle, sizeof(rotationAngle), "%.7s", cyResponse + 3);
1671
1672 sscanf(rotationAngle, "%f", &decAxis);
1673
1674 auto offset = decAxis - decAxisAlignmentOffset;
1675
1676 if (offset > 90)
1677 return PIER_WEST;
1678 else
1679 return PIER_EAST;
1680}
1681
1685bool Rainbow::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
1686{
1687 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1688
1689 tcflush(PortFD, TCIOFLUSH);
1690
1691 if (cmd_len > 0)
1692 {
1693 char hex_cmd[DRIVER_LEN * 3] = {0};
1694 hexDump(hex_cmd, cmd, cmd_len);
1695 LOGF_DEBUG("CMD <%s>", hex_cmd);
1696 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
1697 }
1698 else
1699 {
1700 LOGF_DEBUG("CMD <%s>", cmd);
1701
1702 char formatted_command[DRIVER_LEN] = {0};
1703 snprintf(formatted_command, DRIVER_LEN, "%s\r", cmd);
1704 rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
1705 }
1706
1707 if (rc != TTY_OK)
1708 {
1709 char errstr[MAXRBUF] = {0};
1710 tty_error_msg(rc, errstr, MAXRBUF);
1711 LOGF_ERROR("Serial write error: %s.", errstr);
1712 return false;
1713 }
1714
1715 if (res == nullptr)
1716 return true;
1717
1718 if (res_len > 0)
1719 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
1720 else
1721 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1722
1723 if (rc != TTY_OK)
1724 {
1725 char errstr[MAXRBUF] = {0};
1726 tty_error_msg(rc, errstr, MAXRBUF);
1727 LOGF_ERROR("Serial read error: %s.", errstr);
1728 return false;
1729 }
1730
1731 if (res_len > 0)
1732 {
1733 char hex_res[DRIVER_LEN * 3] = {0};
1734 hexDump(hex_res, res, res_len);
1735 LOGF_DEBUG("RES <%s>", hex_res);
1736 }
1737 else
1738 {
1739 // Remove extra \r
1740 res[nbytes_read - 1] = 0;
1741 LOGF_DEBUG("RES <%s>", res);
1742 }
1743
1744 tcflush(PortFD, TCIOFLUSH);
1745
1746 return true;
1747}
1748
1752void Rainbow::hexDump(char * buf, const char * data, int size)
1753{
1754 for (int i = 0; i < size; i++)
1755 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1756
1757 if (size > 0)
1758 buf[3 * size - 1] = '\0';
1759}
1760
1764std::vector<std::string> Rainbow::split(const std::string &input, const std::string &regex)
1765{
1766 // passing -1 as the submatch index parameter performs splitting
1767 std::regex re(regex);
1768 std::sregex_token_iterator
1769 first{input.begin(), input.end(), re, -1},
1770 last;
1771 return {first, last};
1772}
1773
1777// Home
1779{
1780
1781 switch (action)
1782 {
1783 case HOME_GO:
1784 return findHome() ? IPS_BUSY : IPS_ALERT;
1785
1786 default:
1787 return IPS_ALERT;
1788 }
1789}
int SetSlewRate(void)
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
INDI::PropertyText getText(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
uint32_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INDI::PropertyNumber GuideNSNP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyNumber GuideWENP
void initProperties(const char *groupName)
Initialize guider properties. It is recommended to call this function within initProperties() of your...
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
const char * getName() const
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
double GetAxis1Park() const
double GetAxis2Park() const
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
Connection::Serial * serialConnection
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool Sync(double ra, double dec) override
Sync.
Definition rainbow.cpp:1111
virtual IPState GuideNorth(uint32_t ms) override
Guiding.
Definition rainbow.cpp:1201
virtual IPState ExecuteHomeAction(TelescopeHomeAction action) override
Execute a homing action.
Definition rainbow.cpp:1778
void guideTimeout(Direction direction)
Guide Timeout.
Definition rainbow.cpp:1360
static void guideTimeoutHelperN(void *p)
Guide Timeout North.
Definition rainbow.cpp:1328
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
Definition rainbow.cpp:143
bool getTrackingState()
Definition rainbow.cpp:461
virtual bool Goto(double ra, double dec) override
Slew RA/DE.
Definition rainbow.cpp:787
bool findHome()
Definition rainbow.cpp:478
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition rainbow.cpp:186
virtual bool SetSlewRate(int index) override
Set Slew Rate.
Definition rainbow.cpp:1160
virtual IPState GuideEast(uint32_t ms) override
Guide East.
Definition rainbow.cpp:1217
virtual bool SetTrackMode(uint8_t mode) override
Set Track Mode.
Definition rainbow.cpp:1139
virtual IPState GuideWest(uint32_t ms) override
Guide West.
Definition rainbow.cpp:1225
virtual bool initProperties() override
Called to initialize basic properties required all the time.
Definition rainbow.cpp:63
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Abort.
Definition rainbow.cpp:1013
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition rainbow.cpp:281
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Location & Time.
Definition rainbow.cpp:371
virtual bool SetTrackEnabled(bool enabled) override
Tracking Functions.
Definition rainbow.cpp:451
void getStartupStatus()
Query Functions.
Definition rainbow.cpp:332
void hexDump(char *buf, const char *data, int size)
Definition rainbow.cpp:1752
bool getFirmwareVersion()
Definition rainbow.cpp:419
const char * getDefaultName() override
Definition rainbow.cpp:55
virtual bool Park() override
Parking & Homing.
Definition rainbow.cpp:572
virtual bool Handshake() override
perform handshake with device to check communication
Definition rainbow.cpp:411
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
Definition rainbow.cpp:562
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
Definition rainbow.cpp:552
bool sendScopeLocation()
Get Location from mount.
Definition rainbow.cpp:1585
bool sendScopeTime()
Location & Time.
Definition rainbow.cpp:1510
Rainbow()
Definition rainbow.cpp:34
static void guideTimeoutHelperS(void *p)
Guide Timeout South.
Definition rainbow.cpp:1336
bool getLocalDate(char *dateString)
Get Date from mount.
Definition rainbow.cpp:1445
bool sendCommand(const char *cmd, char *res=nullptr, int cmd_len=-1, int res_len=-1)
Communication Functions.
Definition rainbow.cpp:1685
IPState guide(Direction direction, uint32_t ms)
Guide universal function.
Definition rainbow.cpp:1233
TelescopePierSide getSideOfPier()
Send Command.
Definition rainbow.cpp:1655
static void guideTimeoutHelperW(void *p)
Guide Timeout West.
Definition rainbow.cpp:1344
std::vector< std::string > split(const std::string &input, const std::string &regex)
Definition rainbow.cpp:1764
static void guideTimeoutHelperE(void *p)
Guide Timeout East.
Definition rainbow.cpp:1352
virtual bool UnPark() override
Unparking.
Definition rainbow.cpp:593
bool setRA(double ra)
Set Target RA.
Definition rainbow.cpp:873
bool getLocalTime(char *timeString)
Get Time from mount.
Definition rainbow.cpp:1417
void addGuideTimer(Direction direction, uint32_t ms)
Add guide timer.
Definition rainbow.cpp:1395
bool getUTFOffset(double *offset)
GET UTC offset from mount.
Definition rainbow.cpp:1482
bool setDE(double de)
Set Target Altitude.
Definition rainbow.cpp:893
bool getDE()
Get DE.
Definition rainbow.cpp:859
virtual IPState GuideSouth(uint32_t ms) override
Guide South.
Definition rainbow.cpp:1209
virtual bool Abort() override
Abort.
Definition rainbow.cpp:1075
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:1044
bool slewToEquatorialCoords(double ra, double de)
Slew to Equatorial Coordinates.
Definition rainbow.cpp:913
virtual bool isSlewComplete()
Motion Functions.
Definition rainbow.cpp:612
bool setGuideRate(double rate)
Definition rainbow.cpp:490
bool getGuideRate()
Definition rainbow.cpp:500
virtual bool ReadScopeStatus() override
Read telescope status.
Definition rainbow.cpp:653
bool getRA()
Get RA.
Definition rainbow.cpp:846
@ 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:606
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:586
INDI::PropertySwitch AbortSP
INDI::PropertySwitch MovementNSSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch TrackStateSP
INDI::PropertySwitch HomeSP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertyText TimeTP
INDI::PropertySwitch MovementWESP
INDI::PropertyNumber LocationNP
double ra
double dec
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
#define MAXINDINAME
Definition indiapi.h:189
@ AXIS_AZ
@ AXIS_ALT
INDI_DIR_WE
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_NORTH
void getSexComponentsIID(double value, int *d, int *m, double *s)
Definition indicom.c:266
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:194
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void getSexComponents(double value, int *d, int *m, int *s)
Definition indicom.c:243
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
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:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:490
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:509
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition indidevapi.c:376
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition indidevapi.c:416
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition indidevapi.c:398
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:453
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
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:846
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define 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 getDeviceName
Namespace to encapsulate INDI client, drivers, and mediator classes.
uint8_t cmd[4]
Definition pwc-ioctl.h:2
#define GENERAL_INFO_TAB
Definition rainbow.h:27
#define ALIGNMENT_TAB
Definition rainbow.h:26
One number descriptor.
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248