Instrument Neutral Distributed Interface INDI 2.2.5
ioptronv3driver.cpp
Go to the documentation of this file.
1/*
2 INDI IOptron v3 Driver for firmware version 20171001 or later.
3
4 Copyright (C) 2018 Jasem Mutlaq
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19*/
20
21#include "ioptronv3driver.h"
22#include "indicom.h"
23
24#include <libnova/julian_day.h>
25#include <cinttypes>
26
27#include <cmath>
28#include <cstring>
29#include <termios.h>
30#include <unistd.h>
31
32namespace IOPv3
33{
34
35const std::map<std::string, std::string> Driver::models =
36{
37 {"0010", "SkyHunter EQ"},
38 {"0011", "SkyHunter AA"},
39 {"0012", "HAE16 EQ"},
40 {"0013", "HAE16 AA"},
41 {"0014", "HAE18 EQ"},
42 {"0015", "HEM15"},
43 {"0022", "HAE18 AA"},
44 {"0025", "HEM27"},
45 {"0026", "CEM26"},
46 {"0027", "CEM26-EC"},
47 {"0028", "GEM28"},
48 {"0029", "GEM28-EC"},
49 {"0030", "HEM27-EC"},
50 {"0031", "HAE29 EQ"},
51 {"0032", "HAE29-EC AA"},
52 {"0033", "HAE29 AA"},
53 {"0034", "HAE29-EC AA"},
54 {"0035", "HAZ31"},
55 {"0036", "HAE29C EQ"},
56 {"0037", "HAE29C-EC EQ"},
57 {"0038", "HAE29C AA"},
58 {"0039", "HAE29C-EC EQ"},
59 {"0040", "CEM40"},
60 {"0041", "CEM40-EC"},
61 {"0043", "GEM45"},
62 {"0045", "HEM44-EC"},
63 {"0046", "HEM44A"},
64 {"0047", "HEM44A-EC"},
65 {"0048", "HAE43 EQ"},
66 {"0049", "HAE43-EC EQ"},
67 {"0050", "HAE43 AA"},
68 {"0051", "HAE43-EC AA"},
69 {"0052", "HAZ46"},
70 {"0053", "HAE43C EQ"},
71 {"0054", "HAE43C-EC EQ"},
72 {"0055", "HAE43C AA"},
73 {"0056", "HAE43C-EC AA"},
74 {"0060", "CEM60"},
75 {"0061", "CEM60-EC"},
76 {"0062", "HAE69 EQ"},
77 {"0063", "HAZ69-EC EQ"},
78 {"0064", "HAE69 AA"},
79 {"0065", "HAE69-EC AA"},
80 {"0066", "HAE69C EQ"},
81 {"0067", "HAE69C-EC EQ"},
82 {"0068", "HAE69C AA"},
83 {"0069", "HAE69C-EC AA"},
84 {"0070", "CEM70"},
85 {"0071", "CEM70-EC"},
86 {"0072", "CEM70-EC2"},
87 {"0073", "HAZ71"},
88 {"0120", "CEM120"},
89 {"0121", "CEM120-EC"},
90 {"0122", "CEM120-EC2"},
91 {"5010", "Cube II AA"},
92 {"5035", "AZ Mount Pro"},
93 {"5045", "iEQ45 Pro AA"}
94};
95
96const uint16_t Driver::IOP_SLEW_RATES[] = {1, 2, 8, 16, 64, 128, 256, 512, 1024};
97
98Driver::Driver(const char *deviceName): m_DeviceName(deviceName) {}
99
100bool Driver::sendCommandOk(const char *command)
101{
102 char res[IOP_BUFFER] = {0};
103
104 if (sendCommand(command, 1, res))
105 return res[0] == '1';
106
107 return false;
108}
109
110bool Driver::sendCommand(const char *command,
111 int count,
112 char *response,
113 int minimumCount,
114 uint8_t timeout,
115 uint8_t debugLog)
116{
117 int errCode = 0;
118 int nbytes_read = 0;
119 int nbytes_written = 0;
120 char errMsg[MAXRBUF];
121 char res[IOP_BUFFER] = {0};
122
123 DEBUGFDEVICE(m_DeviceName, debugLog, "CMD <%s>", command);
124
125 if (m_Simulation)
126 return true;
127
128 // Try to dispatch command and read twice in case of timeouts.
129 for (int i = 0; i < 2; i++)
130 {
131 tcflush(PortFD, TCIOFLUSH);
132
133 if ((errCode = tty_write(PortFD, command, strlen(command), &nbytes_written)) != TTY_OK)
134 {
135 tty_error_msg(errCode, errMsg, MAXRBUF);
136 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "Write Command Error: %s", errMsg);
137 return false;
138 }
139
140 if (count == 0)
141 return true;
142
143 if (count == -1)
144 errCode = tty_read_section(PortFD, res, '#', timeout, &nbytes_read);
145 else
146 errCode = tty_read(PortFD, res, count, timeout, &nbytes_read);
147
148 if (errCode == TTY_OK && (minimumCount < 0 || minimumCount <= count))
149 break;
150 }
151
152 if (errCode != TTY_OK)
153 {
154 tty_error_msg(errCode, errMsg, MAXRBUF);
155 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "Read Command Error: %s", errMsg);
156 return false;
157 }
158
159 // Remove the extra #
160 if (count == -1)
161 res[nbytes_read - 1] = 0;
162
163 DEBUGFDEVICE(m_DeviceName, debugLog, "RES <%s>", res);
164
165 tcflush(PortFD, TCIOFLUSH);
166
167 // Copy response to buffer
168 if (response)
169 snprintf(response, IOP_BUFFER, "%s", res);
170
171 return true;
172}
173
175{
176 char res[IOP_BUFFER] = {0};
177
178 DEBUGDEVICE(m_DeviceName, INDI::Logger::DBG_DEBUG, "Initializing IOptron using :MountInfo# CMD...");
179
180 // Set FD for use
181 PortFD = fd;
182
183 if (m_Simulation)
184 return true;
185
186 for (int i = 0; i < 2; i++)
187 {
188 if (sendCommand(":MountInfo#", 4, res, -1, 3) == false)
189 {
190 usleep(50000);
191 continue;
192 }
193
194 return true;
195 }
196
197 return false;
198 DEBUGDEVICE(m_DeviceName, INDI::Logger::DBG_DEBUG, res);
199}
200
201void Driver::setDebug(bool enable)
202{
203 m_Debug = enable;
204}
205
206void Driver::setSimulation(bool enable)
207{
208 m_Simulation = enable;
209
210 simData.ra_guide_rate = 0.5;
211 simData.de_guide_rate = 0.5;
212 simData.pier_state = IOP_PIER_WEST;
213 simData.cw_state = IOP_CW_NORMAL;
214 simData.JD = ln_get_julian_from_sys();
215 simData.utc_offset_minutes = 3 * 60;
216 simData.day_light_saving = false;
217 simData.mb_state = IOP_MB_FLIP;
218 simData.mb_limit = 3;
219
220 simData.simInfo.gpsStatus = GPS_DATA_OK;
221 simData.simInfo.hemisphere = HEMI_NORTH;
222 simData.simInfo.slewRate = SR_6;
223 simData.simInfo.timeSource = TS_GPS;
224 simData.simInfo.trackRate = TR_SIDEREAL;
225 simData.simInfo.longitude = 48.1;
226 simData.simInfo.latitude = 29.5;
227}
228
230{
231 simData.simInfo.gpsStatus = value;
232}
233
235{
236 simData.simInfo.systemStatus = value;
237}
238
240{
241 simData.simInfo.trackRate = value;
242}
243
245{
246 simData.simInfo.slewRate = value;
247}
248
250{
251 simData.simInfo.timeSource = value;
252}
253
255{
256 simData.simInfo.hemisphere = value;
257}
258
260{
261 simData.ra = ra;
262}
263
264void Driver::setSimDE(double de)
265{
266 simData.de = de;
267}
268
269void Driver::setSimGuideRate(double raRate, double deRate)
270{
271 simData.ra_guide_rate = raRate;
272 simData.de_guide_rate = deRate;
273}
274
275void Driver::setSimLongLat(double longitude, double latitude)
276{
277 simData.simInfo.longitude = longitude;
278 simData.simInfo.latitude = latitude;
279}
280
282{
283 char res[IOP_BUFFER] = {0};
284
285 if (m_Simulation)
286 {
287 int iopLongitude = simData.simInfo.longitude * 360000;
288 int iopLatitude = (simData.simInfo.latitude + 90) * 360000;
289 snprintf(res, IOP_BUFFER, "%c%08d%08d%d%d%d%d%d%d", simData.simInfo.longitude > 0 ? '+' : '-',
290 iopLongitude, iopLatitude, simData.simInfo.gpsStatus, simData.simInfo.systemStatus, simData.simInfo.trackRate,
291 simData.simInfo.slewRate, simData.simInfo.timeSource, simData.simInfo.hemisphere);
292 }
293 else if (sendCommand(":GLS#", -1, res, 23) == false)
294 return false;
295
296
297 if (strlen(res) != 23)
298 {
299 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "%s: Expected 23 bytes but received %d.", __PRETTY_FUNCTION__,
300 strlen(res));
301 return false;
302 }
303
304 char longPart[16] = {0}, latPart[16] = {0};
305 snprintf(longPart, sizeof(longPart), "%.9s", res);
306 snprintf(latPart, sizeof(latPart), "%.8s", res + 9);
307
308 int arcsecLongitude = atoi(longPart);
309 int arcsecLatitude = atoi(latPart);
310
311 info->longitude = arcsecLongitude / 360000.0;
312 info->latitude = arcsecLatitude / 360000.0 - 90.0;
313 info->gpsStatus = static_cast<IOP_GPS_STATUS>(res[17] - '0');
314 info->systemStatus = static_cast<IOP_SYSTEM_STATUS>(res[18] - '0');
315 info->trackRate = static_cast<IOP_TRACK_RATE>(res[19] - '0');
316 info->slewRate = static_cast<IOP_SLEW_RATE>(res[20] - '0');
317 info->timeSource = static_cast<IOP_TIME_SOURCE>(res[21] - '0');
318 info->hemisphere = static_cast<IOP_HEMISPHERE>(res[22] - '0');
319
320 return true;
321}
322
324{
325 bool rc1 = getModel(info->Model);
326
328
329 bool rc3 = getRADEFirmware(info->RAFirmware, info->DEFirmware);
330
331 return (rc1 && rc2 && rc3);
332}
333
334bool Driver::getModel(std::string &model)
335{
336 char res[IOP_BUFFER] = {0};
337
338 if (m_Simulation)
339 strcpy(res, "0120");
340 else if (sendCommand(":MountInfo#", 4, res) == false)
341 return false;
342
343 if (models.find(res) != models.end())
344 model = models.at(res);
345 else
346 model = "Unknown";
347
348 return true;
349}
350
351bool Driver::getMainFirmware(std::string &mainFirmware, std::string &controllerFirmware)
352{
353 char res[IOP_BUFFER] = {0};
354
355 if (m_Simulation)
356 strcpy(res, "180321171001");
357 else if (sendCommand(":FW1#", -1, res) == false)
358 return false;
359
360 char mStr[16] = {0}, cStr[16] = {0};
361 snprintf(mStr, sizeof(mStr), "%.6s", res);
362 snprintf(cStr, sizeof(cStr), "%.6s", res + 6);
363
364 mainFirmware = mStr;
365 controllerFirmware = cStr;
366
367 return true;
368}
369
370bool Driver::getRADEFirmware(std::string &RAFirmware, std::string &DEFirmware)
371{
372 char res[IOP_BUFFER] = {0};
373
374 if (m_Simulation)
375 strcpy(res, "140324140101");
376 else if (sendCommand(":FW2#", -1, res) == false)
377 return false;
378
379 char mStr[16] = {0}, cStr[16] = {0};
380 snprintf(mStr, sizeof(mStr), "%.6s", res);
381 snprintf(cStr, sizeof(cStr), "%.6s", res + 6);
382
383 RAFirmware = mStr;
384 DEFirmware = cStr;
385
386 return true;
387}
388
390{
391 switch (dir)
392 {
393 case IOP_N:
394 return sendCommand(":mn#", 0);
395 case IOP_S:
396 return sendCommand(":ms#", 0);
397 // JM 2020-10-12
398 // We are reversing this since CEM120 moves CW when commanded WEST
399 // leading to INCREASING RA, when it is expected to move CCW leading
400 // to DECREASING RA
401 case IOP_W:
402 return sendCommand(":me#", 0);
403 case IOP_E:
404 return sendCommand(":mw#", 0);
405 }
406
407 return false;
408}
409
411{
412 switch (dir)
413 {
414 case IOP_N:
415 case IOP_S:
416 return sendCommandOk(":qD#");
417
418 case IOP_W:
419 case IOP_E:
420 return sendCommandOk(":qR#");
421 }
422
423 return false;
424}
425
427{
428 return sendCommandOk(":MSH#");
429}
430
432{
433 return sendCommandOk(":MH#");
434}
435
437{
438 return sendCommandOk(":SZP#");
439}
440
441/* v3.0 Added in control for PEC , Train and Data Integrity */
442bool Driver::setPECEnabled(bool enabled)
443{
444 // JM 2024.10.11: Changed to direct command without waiting for response.
445 return sendCommand(enabled ? ":SPP1#" : ":SPP0#");
446}
447
448bool Driver::setPETEnabled(bool enabled)
449{
450 return sendCommandOk(enabled ? ":SPR1#" : ":SPR0#");
451}
452
453bool Driver::getPETEnabled(bool enabled)
454{
455 char res[IOP_BUFFER] = {0};
456 // If enabled true then check data quality -> :GPE#
457 // If enabled false then check if training -> :GPR#
458 if(enabled)
459 {
460 if (sendCommand(":GPE#", 1, res))
461 {
462 if (res[0] == '1')
463 {
464 return true;
465 }
466 }
467 }
468 else
469 {
470 if (sendCommand(":GPR#", 1, res))
471 {
472 if (res[0] == '1')
473 {
474 return true;
475 }
476 }
477 }
478 return false;
479}
480// End Mod */
481
483{
484 char cmd[IOP_BUFFER] = {0};
485 snprintf(cmd, IOP_BUFFER, ":SR%u#", static_cast<uint32_t>(rate + 1));
486
487 simData.simInfo.slewRate = rate;
488
489 return sendCommandOk(cmd);
490}
491
493{
494 simData.simInfo.trackRate = rate;
495
496 switch (rate)
497 {
498 case TR_SIDEREAL:
499 return sendCommandOk(":RT0#");
500 case TR_LUNAR:
501 return sendCommandOk(":RT1#");
502 case TR_SOLAR:
503 return sendCommandOk(":RT2#");
504 case TR_KING:
505 return sendCommandOk(":RT3#");
506 case TR_CUSTOM:
507 return sendCommandOk(":RT4#");
508 }
509
510 return false;
511}
512
514{
515 if (rate < 0.1 || rate > 1.9)
516 return false;
517
518 char cmd[IOP_BUFFER] = {0};
519 snprintf(cmd, IOP_BUFFER, ":RR%05u#", static_cast<uint32_t>(rate * 10000));
520
521 return sendCommandOk(cmd);
522}
523
524bool Driver::setGuideRate(double RARate, double DERate)
525{
526 if (RARate < 0.01 || RARate > 0.9 || DERate < 0.01 || DERate > 0.9)
527 return false;
528
529 char cmd[IOP_BUFFER] = {0};
530 snprintf(cmd, IOP_BUFFER, ":RG%02u%02u#", static_cast<uint32_t>(RARate * 100), static_cast<uint32_t>(DERate * 100));
531
532 return sendCommandOk(cmd);
533}
534
535bool Driver::getGuideRate(double *RARate, double *DERate)
536{
537 char res[IOP_BUFFER] = {0};
538
539 if (m_Simulation)
540 snprintf(res, IOP_BUFFER, "%02u%02u", static_cast<uint32_t>(simData.ra_guide_rate * 100),
541 static_cast<uint32_t>(simData.de_guide_rate * 100));
542 else if (sendCommand(":AG#", -1, res) == false)
543 return false;
544
545 char raStr[8] = {0}, deStr[8] = {0};
546 snprintf(raStr, sizeof(raStr), "%.2s", res);
547 snprintf(deStr, sizeof(deStr), "%.2s", res + 2);
548
549 *RARate = atoi(raStr) / 100.0;
550 *DERate = atoi(deStr) / 100.0;
551
552 return true;
553}
554
555bool Driver::startGuide(IOP_DIRECTION dir, uint32_t ms)
556{
557 char cmd[IOP_BUFFER] = {0};
558 char dir_c = 0;
559
560 switch (dir)
561 {
562 // Dec+
563 case IOP_N:
564 dir_c = 'E';
565 break;
566
567 // Dec-
568 case IOP_S:
569 dir_c = 'C';
570 break;
571
572 // RA-
573 case IOP_W:
574 dir_c = 'Q';
575 break;
576
577 // RA+
578 case IOP_E:
579 dir_c = 'S';
580 break;
581 }
582
583 snprintf(cmd, IOP_BUFFER, ":Z%c%05u#", dir_c, ms);
584
585 return sendCommand(cmd, 0);
586}
587
589{
590 return sendCommandOk(":MP1#");
591}
592
594{
595 //NB: This command only available in CEM120 series, CEM60 series, iEQ45 Pro, iEQ45 Pro
596 //AA and iEQ30 Pro.
598 return sendCommandOk(":MP0#");
599}
600
601bool Driver::setParkAz(double az)
602{
603 char cmd[IOP_BUFFER] = {0};
604
605 // Send as 0.01 arcsec resolution
606 int ieqValue = static_cast<int>(az * 60 * 60 * 100);
607
608 snprintf(cmd, IOP_BUFFER, ":SPA%09d#", ieqValue);
609
610 return sendCommandOk(cmd);
611}
612
613bool Driver::setParkAlt(double alt)
614{
615 char cmd[IOP_BUFFER] = {0};
616
617 alt = std::max(0.0, alt);
618
619 // Send as 0.01 arcsec resolution
620 int ieqValue = static_cast<int>(alt * 60 * 60 * 100);
621 snprintf(cmd, IOP_BUFFER, ":SPH%08d#", ieqValue);
622 return sendCommandOk(cmd);
623}
624
626{
627 if (simData.simInfo.systemStatus == ST_SLEWING)
628 simData.simInfo.systemStatus = simData.simInfo.rememberSystemStatus;
629
630 return sendCommandOk(":Q#");
631}
632
634{
635 simData.simInfo.rememberSystemStatus = simData.simInfo.systemStatus;
636 simData.simInfo.systemStatus = ST_SLEWING;
637
638 return sendCommandOk(":MS1#");
639}
640
642{
643 simData.simInfo.rememberSystemStatus = simData.simInfo.systemStatus;
644 simData.simInfo.systemStatus = ST_SLEWING;
645
646 return sendCommandOk(":MS2#");
647}
648
650{
651 return sendCommand(":CM#", 1);
652}
653
654bool Driver::setTrackEnabled(bool enabled)
655{
656 simData.simInfo.systemStatus = enabled ? ST_TRACKING_PEC_ON : ST_STOPPED;
657 char cmd[IOP_BUFFER] = {0};
658 snprintf(cmd, IOP_BUFFER, ":ST%d#", enabled ? 1 : 0);
659
660 return sendCommand(cmd);
661}
662
663bool Driver::setRA(double ra)
664{
665 // Send RA in centi-arcsecond (0.01) resolution.
666 // ra is passed as hours. casRA is in centi-arcseconds in degrees.
667 uint32_t casRA = ra * 15 * 60 * 60 * 100;
668
669 simData.ra = ra;
670
671 char cmd[IOP_BUFFER] = {0};
672 snprintf(cmd, IOP_BUFFER, ":SRA%09u#", casRA);
673
674 return sendCommandOk(cmd);
675}
676
677bool Driver::setDE(double de)
678{
679 // Send DE in centi-arcsecond (0.01) resolution.
680 // de is passed as degrees. casDE is in centi-arcseconds in degrees.
681 uint32_t casDE = fabs(de) * 60 * 60 * 100;
682
683 simData.de = de;
684
685 char cmd[IOP_BUFFER] = {0};
686 snprintf(cmd, IOP_BUFFER, ":Sd%c%08u#", de >= 0 ? '+' : '-', casDE);
687
688 return sendCommandOk(cmd);
689}
690
691bool Driver::setLongitude(double longitude)
692{
693 uint32_t casLongitude = fabs(longitude) * 60 * 60 * 100;
694
695 simData.simInfo.longitude = longitude;
696
697 char cmd[IOP_BUFFER] = {0};
698 snprintf(cmd, IOP_BUFFER, ":SLO%c%08u#", longitude >= 0 ? '+' : '-', casLongitude);
699
700 return sendCommandOk(cmd);
701}
702
703bool Driver::setLatitude(double latitude)
704{
705 uint32_t casLatitude = fabs(latitude) * 60 * 60 * 100;
706
707 simData.simInfo.latitude = latitude;
708
709 char cmd[IOP_BUFFER] = {0};
710 snprintf(cmd, IOP_BUFFER, ":SLA%c%08u#", latitude >= 0 ? '+' : '-', casLatitude);
711
712 return sendCommandOk(cmd);
713}
714
716{
717 uint64_t msJD = (JD - J2000) * 8.64e+7;
718
719 char cmd[IOP_BUFFER] = {0};
720 snprintf(cmd, IOP_BUFFER, ":SUT%013" PRIu64 "#", msJD);
721
722 simData.JD = JD;
723
724 return sendCommandOk(cmd);
725}
726
727bool Driver::setUTCOffset(int offsetMinutes)
728{
729 char cmd[IOP_BUFFER] = {0};
730 snprintf(cmd, IOP_BUFFER, ":SG%c%03d#", offsetMinutes >= 0 ? '+' : '-', abs(offsetMinutes));
731
732 simData.utc_offset_minutes = offsetMinutes;
733
734 return sendCommandOk(cmd);
735}
736
738{
739 char cmd[IOP_BUFFER] = {0};
740 snprintf(cmd, IOP_BUFFER, ":SDS%c#", enabled ? '1' : '0');
741
742 simData.day_light_saving = enabled;
743
744 return sendCommandOk(cmd);
745}
746
747bool Driver::getCoords(double *ra, double *de, IOP_PIER_STATE *pierState, IOP_CW_STATE *cwState)
748{
749 char res[IOP_BUFFER] = {0};
750 if (m_Simulation)
751 {
752 snprintf(res, IOP_BUFFER, "%c%08u%09u%d%d", (simData.de >= 0 ? '+' : '-'),
753 static_cast<uint32_t>(fabs(simData.de) * 60 * 60 * 100),
754 static_cast<uint32_t>(simData.ra * 15 * 60 * 60 * 100), simData.pier_state, simData.cw_state);
755 }
756 else if (sendCommand(":GEP#", -1, res, 20, IOP_TIMEOUT, INDI::Logger::DBG_EXTRA_1) == false)
757 return false;
758
759 if (strlen(res) != 20)
760 {
761 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "%s: Expected 20 bytes but received %d.", __PRETTY_FUNCTION__,
762 strlen(res));
763 return false;
764 }
765
766 char deStr[16] = {0}, raStr[16] = {0};
767
768 strncpy(deStr, res, 9);
769 snprintf(raStr, sizeof(raStr), "%.9s", res + 9);
770
771 try
772 {
773 *de = std::atoi(deStr) / (60.0 * 60.0 * 100.0);
774 *ra = std::atoi(raStr) / (15.0 * 60.0 * 60.0 * 100.0);
775 }
776 catch(...)
777 {
778 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "Failed to parse coordinates RA: %s DE: %s", raStr, deStr);
779 return false;
780 }
781
782 *pierState = static_cast<IOP_PIER_STATE>(res[18] - '0');
783 *cwState = static_cast<IOP_CW_STATE>(res[19] - '0');
784
785 return true;
786}
787
788bool Driver::getUTCDateTime(double *JD, int *utcOffsetMinutes, bool *dayLightSaving)
789{
790 char res[IOP_BUFFER] = {0};
791 if (m_Simulation)
792 {
793 snprintf(res, IOP_BUFFER, "%c%03d%c%013" PRIu64, (simData.utc_offset_minutes >= 0 ? '+' : '-'),
794 abs(simData.utc_offset_minutes),
795 (simData.day_light_saving ? '1' : '0'), static_cast<uint64_t>((simData.JD - J2000) * 8.64e+7));
796 }
797 else if (sendCommand(":GUT#", -1, res, 18) == false)
798 return false;
799
800 if (strlen(res) != 18)
801 {
802 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "%s: Expected 18 bytes but received %d.", __PRETTY_FUNCTION__,
803 strlen(res));
804 return false;
805 }
806
807 char offsetStr[16] = {0}, JDStr[16] = {0};
808
809 strncpy(offsetStr, res, 4);
810 snprintf(JDStr, sizeof(JDStr), "%.13s", res + 5);
811
812 *utcOffsetMinutes = atoi(offsetStr);
813 *dayLightSaving = (res[4] == '1');
814
815 try
816 {
817 uint64_t iopJD = std::stoull(JDStr);
818 *JD = (iopJD / 8.64e+7) + J2000;
819 }
820 catch(...)
821 {
822 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "Failed to parse JD String: %s", JDStr);
823 return false;
824 }
825
826 return true;
827}
828
829bool Driver::getMeridianBehavior(IOP_MB_STATE &action, uint8_t &degrees)
830{
831 char res[IOP_BUFFER] = {0};
832 if (m_Simulation)
833 {
834 snprintf(res, IOP_BUFFER, "%d%02d", simData.mb_state, simData.mb_limit);
835 }
836 else if (sendCommand(":GMT#", -1, res) == false)
837 return false;
838
839 try
840 {
841 action = static_cast<IOP_MB_STATE>(res[0] - '0');
842 degrees = std::stoi(res + 1);
843 }
844 catch(...)
845 {
846 DEBUGFDEVICE(m_DeviceName, INDI::Logger::DBG_ERROR, "Failed to parse MF Behavior: %s", res);
847 return false;
848 }
849 return true;
850}
851
852bool Driver::setMeridianBehavior(IOP_MB_STATE action, uint8_t degrees)
853{
854 if (m_Simulation)
855 {
856 simData.mb_state = action;
857 simData.mb_limit = degrees;
858 return true;
859 }
860 else
861 {
862 char cmd[IOP_BUFFER] = {0};
863 snprintf(cmd, IOP_BUFFER, ":SMT%d%02d#", action, degrees);
864 return sendCommandOk(cmd);
865 }
866}
867
868}
bool setTrackMode(IOP_TRACK_RATE rate)
void setSimulation(bool enable)
static const std::map< std::string, std::string > models
bool setPETEnabled(bool enabled)
bool setCustomRATrackRate(double rate)
bool setDaylightSaving(bool enabled)
struct IOPv3::Driver::@350 simData
bool startGuide(IOP_DIRECTION dir, uint32_t ms)
bool setRA(double ra)
bool getUTCDateTime(double *JD, int *utcOffsetMinutes, bool *dayLightSaving)
bool setMeridianBehavior(IOP_MB_STATE action, uint8_t degrees)
void setSimGPSstatus(IOP_GPS_STATUS value)
void setSimSlewRate(IOP_SLEW_RATE value)
bool getPETEnabled(bool enabled)
bool getGuideRate(double *RARate, double *DERate)
bool getRADEFirmware(std::string &RAFirmware, std::string &DEFirmware)
bool setGuideRate(double RARate, double DERate)
void setSimTrackRate(IOP_TRACK_RATE value)
bool getStatus(IOPInfo *info)
bool sendCommand(const char *command, int count=1, char *response=nullptr, int minimumCount=-1, uint8_t timeout=IOP_TIMEOUT, uint8_t debugLog=INDI::Logger::DBG_DEBUG)
void setSimTimeSource(IOP_TIME_SOURCE value)
void setSimGuideRate(double raRate, double deRate)
bool setParkAz(double az)
bool setParkAlt(double alt)
bool setLatitude(double latitude)
Driver(const char *deviceName)
bool setLongitude(double longitude)
bool getCoords(double *ra, double *de, IOP_PIER_STATE *pierState, IOP_CW_STATE *cwState)
bool sendCommandOk(const char *command)
void setDebug(bool enable)
bool getMeridianBehavior(IOP_MB_STATE &action, uint8_t &degrees)
void setSimRA(double ra)
bool stopMotion(IOP_DIRECTION dir)
bool setTrackEnabled(bool enabled)
bool setSlewRate(IOP_SLEW_RATE rate)
bool checkConnection(int fd)
void setSimHemisphere(IOP_HEMISPHERE value)
bool setDE(double de)
bool setUTCDateTime(double JD)
void setSimSytemStatus(IOP_SYSTEM_STATUS value)
void setSimLongLat(double longitude, double latitude)
bool setUTCOffset(int offsetMinutes)
void setSimDE(double de)
bool getFirmwareInfo(FirmwareInfo *info)
bool setPECEnabled(bool enabled)
bool startMotion(IOP_DIRECTION dir)
static const uint16_t IOP_SLEW_RATES[]
bool getMainFirmware(std::string &mainFirmware, std::string &controllerFirmware)
bool getModel(std::string &model)
double ra
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
#define J2000
Definition indimacros.h:30
int fd
Encapsulates classes and structures required for iOptron Command Set v3 implementation.
@ ST_TRACKING_PEC_ON
uint8_t cmd[4]
Definition pwc-ioctl.h:2
std::string ControllerFirmware
std::string MainBoardFirmware
IOP_GPS_STATUS gpsStatus
IOP_HEMISPHERE hemisphere
IOP_SYSTEM_STATUS systemStatus
IOP_TRACK_RATE trackRate
IOP_SLEW_RATE slewRate
IOP_TIME_SOURCE timeSource