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