1 /*
2 IEQ Pro driver
3
4 Copyright (C) 2015 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 "ieqdriverbase.h"
22
23 #include "indicom.h"
24 #include "indilogger.h"
25
26 #include <libnova/julian_day.h>
27
28 #include <cmath>
29 #include <map>
30 #include <cstring>
31 #include <algorithm>
32 #include <termios.h>
33 #include <unistd.h>
34
35 namespace iEQ
36 {
37
Base()38 Base::Base()
39 {
40 }
41
initCommunication(int fd)42 bool Base::initCommunication(int fd)
43 {
44 m_PortFD = fd;
45 bool rc = getModel();
46 if (rc)
47 {
48 rc = getMainFirmware() && getRADEFirmware();
49 if (rc)
50 {
51 for (const auto &oneMount : m_MountList)
52 {
53 if (oneMount.model == m_FirmwareInfo.Model)
54 {
55 // Make sure current mount firmware is larger
56 // than the minimum firmware required by this mount model.
57 if (m_FirmwareInfo.MainBoardFirmware >= oneMount.firmware)
58 return true;
59 else
60 LOGF_ERROR("Main board firmware is %s while minimum required firmware is %s. Please upgrade the mount firmware.",
61 m_FirmwareInfo.MainBoardFirmware.c_str(), oneMount.firmware.c_str());
62 }
63 }
64
65 return false;
66 }
67 }
68
69 return rc;
70 }
71
getModel()72 bool Base::getModel()
73 {
74 char res[DRIVER_LEN] = {0};
75
76 // Do we support this mount?
77 if (sendCommand(":MountInfo#", res, -1, 4))
78 {
79 std::string code = res;
80 auto result = std::find_if(m_MountList.begin(), m_MountList.end(), [code](const MountInfo & oneMount)
81 {
82 return oneMount.code == code;
83 });
84
85 if (result != m_MountList.end())
86 {
87 m_FirmwareInfo.Model = result->model;
88 return true;
89 }
90
91 LOGF_ERROR("Mount with code %s is not recognized.", res);
92 return false;
93 }
94
95 return false;
96 }
97
getMainFirmware()98 bool Base::getMainFirmware()
99 {
100 char res[DRIVER_LEN] = {0};
101
102 if (sendCommand(":FW1#", res))
103 {
104 char board[8] = {0}, controller[8] = {0};
105
106 strncpy(board, res, 6);
107 strncpy(controller, res + 6, 6);
108
109 m_FirmwareInfo.MainBoardFirmware.assign(board, 6);
110 m_FirmwareInfo.ControllerFirmware.assign(controller, 6);
111
112 return true;
113 }
114
115 return false;
116 }
117
getRADEFirmware()118 bool Base::getRADEFirmware()
119 {
120 char res[DRIVER_LEN] = {0};
121
122 if (sendCommand(":FW2#", res))
123 {
124 char ra[8] = {0}, de[8] = {0};
125
126 strncpy(ra, res, 6);
127 strncpy(de, res + 6, 6);
128
129 m_FirmwareInfo.RAFirmware.assign(ra, 6);
130 m_FirmwareInfo.DEFirmware.assign(de, 6);
131
132 return true;
133 }
134
135 return false;
136 }
137
startMotion(Direction dir)138 bool Base::startMotion(Direction dir)
139 {
140 char cmd[DRIVER_LEN] = {0};
141 switch (dir)
142 {
143 case IEQ_N:
144 strcpy(cmd, ":mn#");
145 break;
146 case IEQ_S:
147 strcpy(cmd, ":ms#");
148 break;
149 // case IEQ_W:
150 // strcpy(cmd, ":mw#");
151 // break;
152 // case IEQ_E:
153 // strcpy(cmd, ":me#");
154 // break;
155 // JM 2019-01-17: Appears iOptron implementation is reversed?
156 case IEQ_W:
157 strcpy(cmd, ":me#");
158 break;
159 case IEQ_E:
160 strcpy(cmd, ":mw#");
161 break;
162 }
163
164 return sendCommand(cmd);
165 }
166
stopMotion(Direction dir)167 bool Base::stopMotion(Direction dir)
168 {
169 char cmd[DRIVER_LEN] = {0};
170 char res[DRIVER_LEN] = {0};
171
172 switch (dir)
173 {
174 case IEQ_N:
175 case IEQ_S:
176 strcpy(cmd, ":qD#");
177 break;
178
179 case IEQ_W:
180 case IEQ_E:
181 strcpy(cmd, ":qR#");
182 break;
183 }
184
185 return sendCommand(cmd, res, -1, 1);
186 }
187
findHome()188 bool Base::findHome()
189 {
190 if (!isCommandSupported("MSH"))
191 return false;
192
193 char res[DRIVER_LEN] = {0};
194
195 return sendCommand(":MSH#", res, -1, 1);
196 }
197
gotoHome()198 bool Base::gotoHome()
199 {
200 char res[DRIVER_LEN] = {0};
201 return sendCommand(":MH#", res, -1, 1);
202 }
203
setCurrentHome()204 bool Base::setCurrentHome()
205 {
206 char res[DRIVER_LEN] = {0};
207 return sendCommand(":SZP#", res, -1, 1);
208 }
209
setSlewRate(SlewRate rate)210 bool Base::setSlewRate(SlewRate rate)
211 {
212 char cmd[DRIVER_LEN] = {0};
213 char res[DRIVER_LEN] = {0};
214 snprintf(cmd, DRIVER_LEN, ":SR%d#", (static_cast<int>(rate) + 1));
215 return sendCommand(cmd, res, -1, 1);
216 }
217
setTrackMode(TrackRate rate)218 bool Base::setTrackMode(TrackRate rate)
219 {
220 char cmd[DRIVER_LEN] = {0};
221 char res[DRIVER_LEN] = {0};
222
223 switch (rate)
224 {
225 case TR_SIDEREAL:
226 strcpy(cmd, ":RT0#");
227 break;
228 case TR_LUNAR:
229 strcpy(cmd, ":RT1#");
230 break;
231 case TR_SOLAR:
232 strcpy(cmd, ":RT2#");
233 break;
234 case TR_KING:
235 strcpy(cmd, ":RT3#");
236 break;
237 case TR_CUSTOM:
238 strcpy(cmd, ":RT4#");
239 break;
240 }
241
242 return sendCommand(cmd, res, -1, 1);
243 }
244
setCustomRATrackRate(double rate)245 bool Base::setCustomRATrackRate(double rate)
246 {
247 if (!isCommandSupported("RR"))
248 return false;
249
250 // Limit to 0.5 to 1.5 as per docs
251 rate = std::max(0.5, std::min(rate, 1.5));
252
253 char cmd[DRIVER_LEN] = {0};
254 char res[DRIVER_LEN] = {0};
255 // Need to be in n.nnnn * sidereal_rate format.
256 // e.g. 0.5 * 1e5 ==> 50000
257 snprintf(cmd, DRIVER_LEN, ":RR%05d#", static_cast<int>(rate * 1e5));
258
259 return sendCommand(cmd, res, -1, 1);
260 }
261
setGuideRate(double raRate,double deRate)262 bool Base::setGuideRate(double raRate, double deRate)
263 {
264 if (!isCommandSupported("RG"))
265 return false;
266
267 // Limit to 0.01 to 0.90 as per docs
268 raRate = std::max(0.01, std::min(raRate, 0.9));
269 // Limit to 0.10 to 0.99 as per docs
270 deRate = std::max(0.1, std::min(deRate, 0.99));
271
272 char cmd[DRIVER_LEN] = {0};
273 char res[DRIVER_LEN] = {0};
274 snprintf(cmd, DRIVER_LEN, ":RG%02d%02d#", static_cast<int>(raRate * 100.0), static_cast<int>(deRate * 100.0));
275
276 return sendCommand(cmd, res, -1, 1);
277 }
278
getGuideRate(double * raRate,double * deRate)279 bool Base::getGuideRate(double *raRate, double *deRate)
280 {
281 if (!isCommandSupported("AG"))
282 return false;
283
284 char res[DRIVER_LEN] = {0};
285
286 if (sendCommand(":AG#", res))
287 {
288 *raRate = DecodeString(res, 2, 100.0);
289 *deRate = DecodeString(res + 2, 2, 100.0);
290
291 return true;
292 }
293
294 return false;
295 }
296
startGuide(Direction dir,uint32_t ms)297 bool Base::startGuide(Direction dir, uint32_t ms)
298 {
299 char cmd[DRIVER_LEN] = {0};
300 char dir_c = 0;
301
302 switch (dir)
303 {
304 case IEQ_N:
305 dir_c = 'n';
306 break;
307
308 case IEQ_S:
309 dir_c = 's';
310 break;
311
312 case IEQ_W:
313 dir_c = 'w';
314 break;
315
316 case IEQ_E:
317 dir_c = 'e';
318 break;
319 }
320
321 snprintf(cmd, DRIVER_LEN, ":M%c%05ud#", dir_c, ms);
322
323 return sendCommand(cmd);
324 }
325
park()326 bool Base::park()
327 {
328 if (!isCommandSupported("MP1"))
329 return false;
330
331 char res[DRIVER_LEN] = {0};
332
333 if (sendCommand(":MP1#", res, -1, 1))
334 {
335 return res[0] == '1';
336 }
337
338 return false;
339 }
340
unpark()341 bool Base::unpark()
342 {
343 if (!isCommandSupported("MP0"))
344 return false;
345
346 char res[DRIVER_LEN] = {0};
347 return sendCommand(":MP0#", res, -1, 1);
348 }
349
abort()350 bool Base::abort()
351 {
352 char res[DRIVER_LEN] = {0};
353 return sendCommand(":Q#", res, -1, 1);
354 }
355
slew()356 bool Base::slew()
357 {
358 char res[DRIVER_LEN] = {0};
359
360 if (sendCommand(":MS#", res, -1, 1))
361 {
362 return res[0] == '1';
363 }
364
365 return false;
366 }
367
sync()368 bool Base::sync()
369 {
370 char res[DRIVER_LEN] = {0};
371 return sendCommand(":CM#", res, -1, 1);
372 }
373
setTrackEnabled(bool enabled)374 bool Base::setTrackEnabled(bool enabled)
375 {
376 char res[DRIVER_LEN] = {0};
377 return sendCommand(enabled ? ":ST1#" : ":ST0#", res, -1, 1);
378 }
379
setRA(double ra)380 bool Base::setRA(double ra)
381 {
382 char cmd[DRIVER_LEN] = {0};
383 char res[DRIVER_LEN] = {0};
384
385 // Send as milliseconds resolution
386 int ieqValue = static_cast<int>(ra * 60 * 60 * 1000);
387
388 snprintf(cmd, DRIVER_LEN, ":Sr%08d#", ieqValue);
389
390 return sendCommand(cmd, res, -1, 1);
391 }
392
setDE(double dec)393 bool Base::setDE(double dec)
394 {
395 char cmd[DRIVER_LEN] = {0};
396 char res[DRIVER_LEN] = {0};
397
398 // Send as 0.01 arcseconds resolution
399 int ieqValue = static_cast<int>(fabs(dec) * 60 * 60 * 100);
400
401 snprintf(cmd, DRIVER_LEN, ":Sd%c%08d#", (dec >= 0) ? '+' : '-', ieqValue);
402
403 return sendCommand(cmd, res, -1, 1);
404 }
405
setAz(double az)406 bool Base::setAz(double az)
407 {
408 char cmd[DRIVER_LEN] = {0};
409 char res[DRIVER_LEN] = {0};
410
411 // Send as 0.01 arcsec resolution
412 int ieqValue = static_cast<int>(az * 60 * 60 * 100);
413
414 snprintf(cmd, DRIVER_LEN, ":Sz%09d#", ieqValue);
415
416 return sendCommand(cmd, res, -1, 1);
417 }
418
setAlt(double alt)419 bool Base::setAlt(double alt)
420 {
421 char cmd[DRIVER_LEN] = {0};
422 char res[DRIVER_LEN] = {0};
423
424 // Send as 0.01 arcsec resolution
425 int ieqValue = static_cast<int>(alt * 60 * 60 * 100);
426
427 snprintf(cmd, DRIVER_LEN, ":Sa%c%08d#", (alt >= 0) ? '+' : '-', ieqValue);
428
429 return sendCommand(cmd, res, -1, 1);
430 }
431
setParkAz(double az)432 bool Base::setParkAz(double az)
433 {
434 if (!isCommandSupported("SPA"))
435 return false;
436
437 char cmd[DRIVER_LEN] = {0};
438 char res[DRIVER_LEN] = {0};
439
440 // Send as 0.01 arcsec resolution
441 int ieqValue = static_cast<int>(az * 60 * 60 * 100);
442
443 snprintf(cmd, DRIVER_LEN, ":SPA%09d#", ieqValue);
444
445 return sendCommand(cmd, res, -1, 1);
446 }
447
setParkAlt(double alt)448 bool Base::setParkAlt(double alt)
449 {
450 if (!isCommandSupported("SPH"))
451 return false;
452
453 char cmd[DRIVER_LEN] = {0};
454 char res[DRIVER_LEN] = {0};
455
456 alt = std::max(0.0, alt);
457
458 // Send as 0.01 arcsec resolution
459 int ieqValue = static_cast<int>(alt * 60 * 60 * 100);
460
461 snprintf(cmd, DRIVER_LEN, ":SPH%08d#", ieqValue);
462
463 return sendCommand(cmd, res, -1, 1);
464 }
465
setLongitude(double longitude)466 bool Base::setLongitude(double longitude)
467 {
468 char cmd[DRIVER_LEN] = {0};
469 char res[DRIVER_LEN] = {0};
470
471 int arcsecs = static_cast<int>(fabs(longitude) * 60 * 60);
472 snprintf(cmd, DRIVER_LEN, ":Sg%c%06d#", (longitude >= 0) ? '+' : '-', arcsecs);
473
474 return sendCommand(cmd, res, -1, 1);
475 }
476
setLatitude(double latitude)477 bool Base::setLatitude(double latitude)
478 {
479 char cmd[DRIVER_LEN] = {0};
480 char res[DRIVER_LEN] = {0};
481
482 int arcsecs = static_cast<int>(fabs(latitude) * 60 * 60);
483 snprintf(cmd, DRIVER_LEN, ":St%c%06d#", (latitude >= 0) ? '+' : '-', arcsecs);
484
485 return sendCommand(cmd, res, -1, 1);
486 }
487
setLocalDate(int yy,int mm,int dd)488 bool Base::setLocalDate(int yy, int mm, int dd)
489 {
490 char cmd[DRIVER_LEN] = {0};
491 char res[DRIVER_LEN] = {0};
492
493 snprintf(cmd, DRIVER_LEN, ":SC%02d%02d%02d#", yy, mm, dd);
494
495 return sendCommand(cmd, res, -1, 1);
496 }
497
setLocalTime(int hh,int mm,int ss)498 bool Base::setLocalTime(int hh, int mm, int ss)
499 {
500 char cmd[DRIVER_LEN] = {0};
501 char res[DRIVER_LEN] = {0};
502
503 snprintf(cmd, DRIVER_LEN, ":SL%02d%02d%02d#", hh, mm, ss);
504
505 return sendCommand(cmd, res, -1, 1);
506 }
507
setDST(bool enabled)508 bool Base::setDST(bool enabled)
509 {
510 char res[DRIVER_LEN] = {0};
511 return sendCommand(enabled ? ":SDS1#" : ":SDS0#", res, -1, 1);
512 }
513
setUTCOffset(double offset_hours)514 bool Base::setUTCOffset(double offset_hours)
515 {
516 char cmd[DRIVER_LEN] = {0};
517 char res[DRIVER_LEN] = {0};
518
519 int offset_minutes = static_cast<int>(fabs(offset_hours) * 60.0);
520 snprintf(cmd, 16, ":SG%c%03d#", (offset_hours >= 0) ? '+' : '-', offset_minutes);
521
522 return sendCommand(cmd, res, -1, 1);
523 }
524
getCoords(double * ra,double * dec)525 bool Base::getCoords(double *ra, double *dec)
526 {
527 char res[DRIVER_LEN] = {0};
528
529 if (sendCommand(":GEC#", res))
530 {
531 *ra = Ra = DecodeString(res + 9, 8, ieqHours);
532 *dec = Dec = DecodeString(res, 9, ieqDegrees);
533 return true;
534 }
535
536 return false;
537 }
538
getUTCDateTime(double * utc_hours,int * yy,int * mm,int * dd,int * hh,int * minute,int * ss)539 bool Base::getUTCDateTime(double *utc_hours, int *yy, int *mm, int *dd, int *hh, int *minute, int *ss)
540 {
541 char res[DRIVER_LEN] = {0};
542
543 if (sendCommand(":GLT#", res))
544 {
545 *utc_hours = DecodeString(res, 4, 60.0);
546 *yy = DecodeString(res + 5, 2) + 2000;
547 *mm = DecodeString(res + 7, 2);
548 *dd = DecodeString(res + 9, 2);
549 *hh = DecodeString(res + 11, 2);
550 *minute = DecodeString(res + 13, 2);
551 *ss = DecodeString(res + 15, 2);
552
553 ln_zonedate localTime;
554 ln_date utcTime;
555
556 localTime.years = *yy;
557 localTime.months = *mm;
558 localTime.days = *dd;
559 localTime.hours = *hh;
560 localTime.minutes = *minute;
561 localTime.seconds = *ss;
562 localTime.gmtoff = static_cast<long>(*utc_hours * 3600);
563
564 ln_zonedate_to_date(&localTime, &utcTime);
565
566 *yy = utcTime.years;
567 *mm = utcTime.months;
568 *dd = utcTime.days;
569 *hh = utcTime.hours;
570 *minute = utcTime.minutes;
571 *ss = static_cast<int>(utcTime.seconds);
572
573 return true;
574
575 }
576
577 return false;
578 }
579
getStatus(Info * info)580 bool Base::getStatus(Info *info)
581 {
582 char res[DRIVER_LEN] = {0};
583
584 if (sendCommand(":GLS#", res))
585 {
586 char longitude[8] = {0}, latitude[8] = {0}, status[8] = {0};
587
588 strncpy(longitude, res, 7);
589 strncpy(latitude, res + 7, 6);
590 strncpy(status, res + 13, 6);
591
592 info->longitude = DecodeString(res, 7, 3600.0);
593 info->latitude = DecodeString(res + 7, 6, 3600.0) - 90;
594 info->gpsStatus = static_cast<GPSStatus>(status[0] - '0');
595 info->systemStatus = static_cast<SystemStatus>(status[1] - '0');
596 info->trackRate = static_cast<TrackRate>(status[2] - '0');
597 info->slewRate = static_cast<SlewRate>(status[3] - '0' - 1);
598 info->timeSource = static_cast<TimeSource>(status[4] - '0');
599 info->hemisphere = static_cast<Hemisphere>(status[5] - '0');
600
601 this->info = *info; // keep a local copy
602
603 return true;
604 }
605
606 return false;
607 }
608
pierSideStr(IEQ_PIER_SIDE ps)609 const char * pierSideStr(IEQ_PIER_SIDE ps)
610 {
611 switch (ps)
612 {
613 case IEQ_PIER_EAST:
614 return "EAST";
615 case IEQ_PIER_WEST:
616 return "WEST";
617 case IEQ_PIER_UNKNOWN:
618 return "UNKNOWN";
619 case IEQ_PIER_UNCERTAIN:
620 return "UNCERTAIN";
621 }
622 return "Impossible";
623 }
624
getPierSide(IEQ_PIER_SIDE * pierSide)625 bool Base::getPierSide(IEQ_PIER_SIDE * pierSide)
626 {
627 char res[DRIVER_LEN] = {0};
628
629 // use the GEA command, hoping that it returns the dec and polar angle axis positions
630 // see https://www.indilib.org/forum/mounts/6720-ioptron-cem60-question.html#52154
631 // the polar angle is in units of 1/100 arc second, signed, and the hour angle is in milliseconds,
632 // possibly with an offset. The home axis positions are PA +0.0, HA 12.0.
633 // For the West pointing state the ha = 18 - haAxis.
634 //
635 if (sendCommand(":GEA#", res))
636 {
637 // get the hour angle in hours
638 haAxis = DecodeString(res + 9, 8, ieqHours);
639 // this is the pole angle in degrees
640 decAxis = DecodeString(res, 9, ieqDegrees);
641
642 double axisHa = 0;
643
644 if (decAxis >= 0)
645 {
646 *pierSide = IEQ_PIER_WEST;
647 axisHa = 18 - haAxis; // OK for the West PS
648 }
649 else
650 {
651 *pierSide = IEQ_PIER_EAST;
652 axisHa = haAxis - 6; // OK for the West PS
653 }
654
655 // The pole angle is not exactly at 0 when the dec is 90 and this gives problems with incorrect pier side close to the pole.
656 //
657 // Attempt to handle this by using the hour angle where the HA can be relied on - away from the meridian
658 // Use pole angle when within 2 hours of a meridian.
659 // If the pole angle is less than the difference between the pole angle and the dec report the pier side as unknown
660 //
661 // I know, horrible, but the data the mount reports is so difficult to interpret that this seems to be the least
662 // worst solution, anyway, let's see if it works CR
663
664 double lst = get_local_sidereal_time(info.longitude);
665 double ha = rangeHA(get_local_hour_angle(lst, Ra));
666
667 const char* reason;
668 double decPA = info.latitude >= 0 ? 90 - Dec : 90 +
669 Dec; // the distance from the pole determined using the declination, ok for both hemispheres
670
671 if ((ha > 2 && ha < 10) || (ha < -2 && ha > -10))
672 {
673 // use Ha to determine pier side
674 *pierSide = ha > 0 ? IEQ_PIER_EAST : IEQ_PIER_WEST;
675 reason = "Hour Angle";
676 }
677 else
678 {
679 double decDiff = std::fabs(decPA - std::fabs(decAxis)); // not sure about this in the Southern hemisphere
680 if (decPA > decDiff)
681 {
682 // use the pole angle
683 *pierSide = decAxis > 0 ? IEQ_PIER_WEST : IEQ_PIER_EAST;
684 reason = "pole angle";
685 }
686 else
687 {
688 *pierSide = IEQ_PIER_UNCERTAIN;
689 reason = "uncertain";
690 }
691 }
692
693 LOGF_DEBUG("getPierSide pole Axis %f, haAxis %f, axisHa %f, ha %f, decPa %f, %s pierSide %s", decAxis, haAxis, axisHa, ha,
694 decPA, reason,
695 pierSideStr(*pierSide));
696
697 return true;
698 }
699 *pierSide = IEQ_PIER_UNKNOWN;
700 return false;
701 }
702
sendCommand(const char * cmd,char * res,int cmd_len,int res_len)703 bool Base::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
704 {
705 int nbytes_written = 0, nbytes_read = 0, rc = -1;
706
707 tcflush(m_PortFD, TCIOFLUSH);
708
709 if (cmd_len > 0)
710 {
711 char hex_cmd[DRIVER_LEN * 3] = {0};
712 hexDump(hex_cmd, cmd, cmd_len);
713 LOGF_DEBUG("CMD <%s>", hex_cmd);
714 rc = tty_write(m_PortFD, cmd, cmd_len, &nbytes_written);
715 }
716 else
717 {
718 LOGF_DEBUG("CMD <%s>", cmd);
719 rc = tty_write_string(m_PortFD, cmd, &nbytes_written);
720 }
721
722 if (rc != TTY_OK)
723 {
724 char errstr[MAXRBUF] = {0};
725 tty_error_msg(rc, errstr, MAXRBUF);
726 LOGF_ERROR("Serial write error: %s.", errstr);
727 return false;
728 }
729
730 if (res == nullptr)
731 return true;
732
733 if (res_len > 0)
734 rc = tty_read(m_PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
735 else
736 rc = tty_nread_section(m_PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
737
738 if (rc != TTY_OK)
739 {
740 char errstr[MAXRBUF] = {0};
741 tty_error_msg(rc, errstr, MAXRBUF);
742 LOGF_ERROR("Serial read error: %s.", errstr);
743 return false;
744 }
745
746 if (res_len > 0)
747 {
748 char hex_res[DRIVER_LEN * 3] = {0};
749 hexDump(hex_res, res, res_len);
750 LOGF_DEBUG("RES <%s>", hex_res);
751 }
752 else
753 {
754 LOGF_DEBUG("RES <%s>", res);
755 }
756
757 tcflush(m_PortFD, TCIOFLUSH);
758
759 return true;
760 }
761
hexDump(char * buf,const char * data,int size)762 void Base::hexDump(char * buf, const char * data, int size)
763 {
764 for (int i = 0; i < size; i++)
765 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
766
767 if (size > 0)
768 buf[3 * size - 1] = '\0';
769 }
770
isCommandSupported(const std::string & command,bool silent)771 bool Base::isCommandSupported(const std::string &command, bool silent)
772 {
773 // Find Home
774 if (command == "MSH")
775 {
776 if (m_FirmwareInfo.Model.find("CEM60") == std::string::npos &&
777 m_FirmwareInfo.Model.find("CEM40") == std::string::npos &&
778 m_FirmwareInfo.Model.find("GEM45") == std::string::npos)
779 {
780 if (!silent)
781 LOG_ERROR("Finding home is only supported on CEM40, GEM45 and CEM60 mounts.");
782 return false;
783
784 }
785 }
786 else if (command == "RR")
787 {
788 if (m_FirmwareInfo.Model.find("AA") != std::string::npos)
789 {
790 if (!silent)
791 LOG_ERROR("Tracking rate is not supported on Altitude-Azimuth mounts.");
792 return false;
793 }
794 }
795 else if (command == "RG" || command == "AG")
796 {
797 if (m_FirmwareInfo.Model.find("AA") != std::string::npos)
798 {
799 if (!silent)
800 LOG_ERROR("Guide rate is not supported on Altitude-Azimuth mounts.");
801 return false;
802 }
803 }
804 if (command == "MP0" || command == "MP1" || command == "SPA" || command == "SPH")
805 {
806 if (m_FirmwareInfo.Model.find("CEM60") == std::string::npos &&
807 m_FirmwareInfo.Model.find("CEM40") == std::string::npos &&
808 m_FirmwareInfo.Model.find("GEM45") == std::string::npos &&
809 m_FirmwareInfo.Model.find("iEQ") == std::string::npos)
810 {
811 if (!silent)
812 LOG_ERROR("Parking only supported on CEM40, GEM45, CEM60, iEQPro 30 and iEQ Pro 45.");
813 return false;
814 }
815 }
816
817 return true;
818 }
819
DecodeString(const char * data,size_t size,double factor)820 double Base::DecodeString(const char * data, size_t size, double factor)
821 {
822 return DecodeString(data, size) / factor;
823 }
824
DecodeString(const char * data,size_t size)825 int Base::DecodeString(const char *data, size_t size)
826 {
827 char str[DRIVER_LEN / 2] = {0};
828 strncpy(str, data, size);
829
830 int iVal = atoi(str);
831 return iVal;
832 }
833
834 }
835