1 /*******************************************************************************
2   Copyright(c) 2010 Gerry Rozema. All rights reserved.
3   Copyright(c) 2018 Jasem Mutlaq. All rights reserved.
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License version 2 as published by the Free Software Foundation.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB.  If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 *******************************************************************************/
19 
20 #include "synscandriverlegacy.h"
21 #include "connectionplugins/connectioninterface.h"
22 #include "connectionplugins/connectiontcp.h"
23 #include "indicom.h"
24 #include "libastro.h"
25 
26 #include <libnova/transform.h>
27 #include <libnova/precession.h>
28 // libnova specifies round() on old systems and it collides with the new gcc 5.x/6.x headers
29 #define HAVE_ROUND
30 #include <libnova/utility.h>
31 
32 #include <cmath>
33 #include <memory>
34 #include <cstring>
35 
36 #define SYNSCAN_SLEW_RATES 9
37 
38 constexpr uint16_t SynscanLegacyDriver::SLEW_RATE[];
39 
SynscanLegacyDriver()40 SynscanLegacyDriver::SynscanLegacyDriver()
41 {
42     SetTelescopeCapability(TELESCOPE_CAN_PARK | TELESCOPE_CAN_ABORT | TELESCOPE_CAN_SYNC | TELESCOPE_CAN_GOTO |
43                            TELESCOPE_HAS_TIME | TELESCOPE_HAS_LOCATION,
44                            SYNSCAN_SLEW_RATES);
45     strncpy(LastParkRead, "", 1);
46 }
47 
Connect()48 bool SynscanLegacyDriver::Connect()
49 {
50     if (isConnected())
51         return true;
52 
53     bool rc = INDI::Telescope::Connect();
54 
55     if (rc)
56         return AnalyzeMount();
57 
58     return rc;
59 }
60 
getDefaultName()61 const char *SynscanLegacyDriver::getDefaultName()
62 {
63     return "SynScan Legacy";
64 }
65 
initProperties()66 bool SynscanLegacyDriver::initProperties()
67 {
68     INDI::Telescope::initProperties();
69 
70     SetTelescopeCapability(TELESCOPE_CAN_PARK | TELESCOPE_CAN_ABORT | TELESCOPE_CAN_SYNC | TELESCOPE_CAN_GOTO |
71                            TELESCOPE_HAS_TIME | TELESCOPE_HAS_LOCATION | TELESCOPE_HAS_PIER_SIDE,
72                            SYNSCAN_SLEW_RATES);
73     SetParkDataType(PARK_RA_DEC_ENCODER);
74 
75     // Slew Rates
76     strncpy(SlewRateS[0].label, "1x", MAXINDILABEL);
77     strncpy(SlewRateS[1].label, "8x", MAXINDILABEL);
78     strncpy(SlewRateS[2].label, "16x", MAXINDILABEL);
79     strncpy(SlewRateS[3].label, "32x", MAXINDILABEL);
80     strncpy(SlewRateS[4].label, "64x", MAXINDILABEL);
81     strncpy(SlewRateS[5].label, "128x", MAXINDILABEL);
82     strncpy(SlewRateS[6].label, "400x", MAXINDILABEL);
83     strncpy(SlewRateS[7].label, "600x", MAXINDILABEL);
84     strncpy(SlewRateS[8].label, "MAX", MAXINDILABEL);
85     IUResetSwitch(&SlewRateSP);
86     // Max is the default
87     SlewRateS[8].s = ISS_ON;
88 
89     //////////////////////////////////////////////////////////////////////////////////////////////////
90     /// Mount Info Text Property
91     //////////////////////////////////////////////////////////////////////////////////////////////////
92     IUFillText(&BasicMountInfoT[MI_FW_VERSION], "FW_VERSION", "Firmware version", "-");
93     IUFillText(&BasicMountInfoT[MI_MOUNT_CODE], "MOUNT_CODE", "Mount code", "-");
94     IUFillText(&BasicMountInfoT[MI_ALIGN_STATUS], "ALIGNMENT_STATUS", "Alignment status", "-");
95     IUFillText(&BasicMountInfoT[MI_GOTO_STATUS], "GOTO_STATUS", "Goto status", "-");
96     IUFillText(&BasicMountInfoT[MI_POINT_STATUS], "MOUNT_POINTING_STATUS",
97                "Mount pointing status", "-");
98     IUFillText(&BasicMountInfoT[MI_TRACK_MODE], "TRACKING_MODE", "Tracking mode", "-");
99     IUFillTextVector(&BasicMountInfoTP, BasicMountInfoT, 6, getDeviceName(), "BASIC_MOUNT_INFO",
100                      "Mount information", MountInfoPage, IP_RO, 60, IPS_IDLE);
101 
102     //////////////////////////////////////////////////////////////////////////////////////////////////
103     /// Use WiFi Switch Property
104     //////////////////////////////////////////////////////////////////////////////////////////////////
105     //    IUFillSwitch(&UseWiFiS[WIFI_ENABLED], "Enabled", "Enabled", ISS_OFF);
106     //    IUFillSwitch(&UseWiFiS[WIFI_DISABLED], "Disabled", "Disabled", ISS_ON);
107     //    IUFillSwitchVector(&UseWiFiSP, UseWiFiS, 2, getDeviceName(), "WIFI_SELECT", "Use WiFi?", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
108 
109     addAuxControls();
110 
111     return true;
112 }
113 
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)114 bool SynscanLegacyDriver::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
115 {
116     return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
117 }
118 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)119 bool SynscanLegacyDriver::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
120 {
121 #if 0
122     if (!strcmp(dev, getDeviceName()))
123     {
124         if (!strcmp(name, UseWiFiSP.name))
125         {
126             if (isConnected())
127             {
128                 UseWiFiSP.s = IPS_ALERT;
129                 IDSetSwitch(&UseWiFiSP, nullptr);
130                 LOG_ERROR("Cannot select WiFi mode while already connected. It must be selected when when disconnected.");
131                 return true;
132             }
133 
134             IUUpdateSwitch(&UseWiFiSP, states, names, n);
135 
136             if (UseWiFiS[WIFI_ENABLED].s == ISS_ON)
137             {
138                 setTelescopeConnection(CONNECTION_TCP);
139                 tcpConnection->setDefaultHost("192.168.4.2");
140                 tcpConnection->setDefaultPort(11882);
141 
142                 LOG_INFO("Driver is configured for WiFi connection to 192.168.4.2 at TCP port 11882");
143 
144                 saveConfig(true, "WIFI_SELECT");
145                 UseWiFiSP.s = IPS_OK;
146                 IDSetSwitch(&UseWiFiSP, nullptr);
147 
148                 ISState newStates[] = { ISS_OFF, ISS_ON };
149                 const char *newNames[] = { "CONNECTION_SERIAL", "CONNECTION_TCP" };
150                 ISNewSwitch(getDeviceName(), "CONNECTION_MODE", newStates, const_cast<char **>(newNames), 2);
151             }
152             else
153             {
154                 setTelescopeConnection(CONNECTION_SERIAL);
155                 LOG_INFO("Driver is configured for serial connection to the hand controller.");
156 
157                 UseWiFiSP.s = IPS_OK;
158                 IDSetSwitch(&UseWiFiSP, nullptr);
159 
160                 ISState newStates[] = { ISS_ON, ISS_OFF };
161                 const char *newNames[] = { "CONNECTION_SERIAL", "CONNECTION_TCP" };
162                 ISNewSwitch(getDeviceName(), "CONNECTION_MODE", newStates, const_cast<char **>(newNames), 2);
163             }
164 
165             return true;
166         }
167     }
168 #endif
169     return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
170 }
171 
ISNewBLOB(const char * dev,const char * name,int sizes[],int blobsizes[],char * blobs[],char * formats[],char * names[],int n)172 bool SynscanLegacyDriver::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
173                                     char *formats[], char *names[], int n)
174 {
175     return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
176 }
177 
ISNewText(const char * dev,const char * name,char * texts[],char * names[],int n)178 bool SynscanLegacyDriver::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
179 {
180     return INDI::Telescope::ISNewText(dev, name, texts, names, n);
181 }
182 
updateProperties()183 bool SynscanLegacyDriver::updateProperties()
184 {
185     INDI::Telescope::updateProperties();
186 
187     if (isConnected())
188     {
189         UpdateMountInformation(false);
190         defineProperty(&BasicMountInfoTP);
191     }
192     else
193     {
194         deleteProperty(BasicMountInfoTP.name);
195     }
196 
197     return true;
198 }
199 
HexStrToInteger(const std::string & res)200 int SynscanLegacyDriver::HexStrToInteger(const std::string &res)
201 {
202     int result = 0;
203 
204     try
205     {
206         result = std::stoi(res, nullptr, 16);
207     }
208     catch (std::invalid_argument &)
209     {
210         LOGF_ERROR("Failed to parse %s to integer.", res.c_str());
211     }
212 
213     return result;
214 }
215 
AnalyzeMount()216 bool SynscanLegacyDriver::AnalyzeMount()
217 {
218     LOG_DEBUG("Analyzing Mount...");
219 
220     bool rc = true;
221     int tmp = 0;
222     int bytesWritten = 0;
223     int bytesRead, numread;
224     char res[MAX_SYN_BUF] = {0};
225 
226     // JM 2018-08-15 Why are we reading caps here? Looks like it serves no purpose
227     //caps = GetTelescopeCapability();
228 
229     rc = ReadLocation();
230     if (rc)
231     {
232         CanSetLocation = true;
233         ReadTime();
234     }
235 
236     if (isSimulation() == false)
237     {
238         bytesRead = 0;
239         memset(res, 0, MAX_SYN_BUF);
240         LOG_DEBUG("CMD <J>");
241         tty_write(PortFD, "J", 1, &bytesWritten);
242         tty_read(PortFD, res, 2, 2, &bytesRead);
243         LOGF_DEBUG("RES <%s>", res);
244 
245         if (res[0] == 0)
246         {
247             LOG_ERROR("Mount is not aligned. Please align the mount first and connection again.");
248             return false;
249         }
250 
251         if (getActiveConnection()->type() == Connection::Interface::CONNECTION_SERIAL)
252         {
253             // Read the handset version
254             bytesRead = 0;
255             memset(res, 0, MAX_SYN_BUF);
256             LOG_DEBUG("Getting Firmware version...");
257             LOG_DEBUG("CMD <V>");
258             tty_write(PortFD, "V", 1, &bytesWritten);
259 
260             tty_read(PortFD, res, 7, 2, &bytesRead);
261             LOGF_DEBUG("RES <%s>", res);
262 
263             if (bytesRead == 3)
264             {
265                 int tmp1 { 0 }, tmp2 { 0 };
266 
267                 tmp  = res[0];
268                 tmp1 = res[1];
269                 tmp2 = res[2];
270                 FirmwareVersion = tmp2;
271                 FirmwareVersion /= 100;
272                 FirmwareVersion += tmp1;
273                 FirmwareVersion /= 100;
274                 FirmwareVersion += tmp;
275             }
276             else
277             {
278                 FirmwareVersion = (double)HexStrToInteger(std::string(&res[0], 2));
279                 FirmwareVersion += (double)HexStrToInteger(std::string(&res[2], 2)) / 100;
280                 FirmwareVersion += (double)HexStrToInteger(std::string(&res[4], 2)) / 10000;
281             }
282 
283             LOGF_INFO("Firmware version: %lf", FirmwareVersion);
284 
285             if (FirmwareVersion < 3.38 || (FirmwareVersion >= 4.0 && FirmwareVersion < 4.38))
286             {
287                 LOG_WARN("Firmware version is too old. Update Synscan firmware to v4.38+");
288             }
289             else
290             {
291                 NewFirmware = true;
292             }
293 
294             HandsetFwVersion = std::to_string(FirmwareVersion);
295 
296             // Mount Model
297             memset(res, 0, MAX_SYN_BUF);
298             LOG_DEBUG("CMD <m>");
299             tty_write(PortFD, "m", 1, &bytesWritten);
300             tty_read(PortFD, res, 2, 2, &bytesRead);
301             LOGF_DEBUG("RES <%s>", res);
302 
303             if (bytesRead == 2)
304             {
305                 // This workaround is needed because the firmware 3.39 sends these bytes swapped.
306                 if (res[1] == '#')
307                     MountCode = static_cast<int>(*reinterpret_cast<unsigned char*>(&res[0]));
308                 else
309                     MountCode = static_cast<int>(*reinterpret_cast<unsigned char*>(&res[1]));
310             }
311         }
312 
313         // Check the tracking status
314         LOG_DEBUG("Getting Tracking status...");
315         memset(res, 0, MAX_SYN_BUF);
316         LOG_DEBUG("CMD <t>");
317         tty_write(PortFD, "t", 1, &bytesWritten);
318         numread = tty_read(PortFD, res, 2, 2, &bytesRead);
319         LOGF_DEBUG("RES <%s>", res);
320 
321         if (res[1] == '#' && static_cast<int>(res[0]) != 0)
322         {
323             TrackState = SCOPE_TRACKING;
324         }
325     }
326 
327     initParking();
328 
329     LOG_DEBUG("Analyzing mount complete.");
330 
331     return true;
332 }
333 
initParking()334 void SynscanLegacyDriver::initParking()
335 {
336     LOG_DEBUG("Initializing parking...");
337     if (InitPark())
338     {
339         SetAxis1ParkDefault(0);
340         SetAxis2ParkDefault(90);
341     }
342     else
343     {
344         SetAxis1Park(0);
345         SetAxis2Park(90);
346         SetAxis1ParkDefault(0);
347         SetAxis2ParkDefault(90);
348     }
349 }
350 
ReadScopeStatus()351 bool SynscanLegacyDriver::ReadScopeStatus()
352 {
353     if (isSimulation())
354     {
355         MountSim();
356         return true;
357     }
358 
359     char res[MAX_SYN_BUF] = {0};
360     int bytesWritten, bytesRead;
361     int numread;
362     double ra, dec;
363     long unsigned int n1, n2;
364 
365     LOG_DEBUG("CMD <Ka>");
366     tty_write(PortFD, "Ka", 2, &bytesWritten); //  test for an echo
367 
368     tty_read(PortFD, res, 2, 2, &bytesRead);   //  Read 2 bytes of response
369     LOGF_DEBUG("RES <%s>", res);
370 
371     if (res[1] != '#')
372     {
373         LOG_WARN("Synscan Mount not responding");
374         // Usually, Abort() recovers the communication
375         RecoverTrials++;
376         Abort();
377         //        HasFailed = true;
378         return false;
379     }
380     RecoverTrials = 0;
381 
382     /*
383     //  With 3.37 firmware, on the older line of eq6 mounts
384     //  The handset does not always initialize the communication with the motors correctly
385     //  We can check for this condition by querying the motors for firmware version
386     //  and if it returns zero, it means we need to power cycle the handset
387     //  and try again after it restarts again
388 
389         if(HasFailed) {
390             int v1,v2;
391         //fprintf(stderr,"Calling passthru command to get motor firmware versions\n");
392             v1=PassthruCommand(0xfe,0x11,1,0,2);
393             v2=PassthruCommand(0xfe,0x10,1,0,2);
394             fprintf(stderr,"Motor firmware versions %d %d\n",v1,v2);
395             if((v1==0)||(v2==0)) {
396                 IDMessage(getDeviceName(),"Cannot proceed");
397                 IDMessage(getDeviceName(),"Handset is responding, but Motors are Not Responding");
398                 return false;
399         }
400             //  if we get here, both motors are responding again
401             //  so the problem is solved
402         HasFailed=false;
403         }
404     */
405 
406     //  on subsequent passes, we just need to read the time
407     //    if (HasTime())
408     //    {
409     //        ReadTime();
410     //    }
411     if (HasLocation())
412     {
413         //  this flag is set when we get a new lat/long from the host
414         //  so we should go thru the read routine once now, so things update
415         //  correctly in the client displays
416         if (ReadLatLong)
417         {
418             ReadLocation();
419         }
420     }
421 
422     // Query mount information
423     memset(res, 0, MAX_SYN_BUF);
424     LOG_DEBUG("CMD <J>");
425     tty_write(PortFD, "J", 1, &bytesWritten);
426     numread = tty_read(PortFD, res, 2, 2, &bytesRead);
427     LOGF_DEBUG("RES <%s>", res);
428     if (res[1] == '#')
429     {
430         AlignmentStatus = std::to_string((int)res[0]);
431     }
432     memset(res, 0, MAX_SYN_BUF);
433     LOG_DEBUG("CMD <L>");
434     tty_write(PortFD, "L", 1, &bytesWritten);
435     numread = tty_read(PortFD, res, 2, 2, &bytesRead);
436     LOGF_DEBUG("RES <%s>", res);
437     if (res[1] == '#')
438     {
439         GotoStatus = res[0];
440     }
441     memset(res, 0, MAX_SYN_BUF);
442     LOG_DEBUG("CMD <p>");
443     tty_write(PortFD, "p", 1, &bytesWritten);
444     numread = tty_read(PortFD, res, 2, 2, &bytesRead);
445     LOGF_DEBUG("RES <%s>", res);
446     if (res[1] == '#')
447     {
448         PointingStatus = res[0];
449 
450         // INDI and mount pier sides are opposite to each other
451         setPierSide(res[0] == 'W' ? PIER_EAST : PIER_WEST);
452     }
453     memset(res, 0, MAX_SYN_BUF);
454     LOG_DEBUG("CMD <t>");
455     tty_write(PortFD, "t", 1, &bytesWritten);
456     numread = tty_read(PortFD, res, 2, 2, &bytesRead);
457     LOGF_DEBUG("RES <%s>", res);
458     if (res[1] == '#')
459     {
460         TrackingStatus = res[0];
461         switch((int)res[0])
462         {
463             case 0:
464                 TrackingMode = "Tracking off";
465                 break;
466             case 1:
467                 TrackingMode = "Alt/Az tracking";
468                 break;
469             case 2:
470                 TrackingMode = "EQ tracking";
471                 break;
472             case 3:
473                 TrackingMode = "PEC mode";
474                 break;
475         }
476     }
477 
478     UpdateMountInformation(true);
479 
480     if (TrackState == SCOPE_SLEWING)
481     {
482         //  We have a slew in progress
483         //  lets see if it's complete
484         //  This only works for ra/dec goto commands
485         //  The goto complete flag doesn't trip for ALT/AZ commands
486         if (GotoStatus != "0")
487         {
488             //  Nothing to do here
489         }
490         else if (MountCode < 128)
491         {
492             if (TrackingStatus[0] != 0)
493                 TrackState = SCOPE_TRACKING;
494             else
495                 TrackState = SCOPE_IDLE;
496         }
497     }
498     if (TrackState == SCOPE_PARKING)
499     {
500         if (FirmwareVersion == 4.103500)
501         {
502             //  With this firmware the correct way
503             //  is to check the slewing flat
504             memset(res, 0, 3);
505             LOG_DEBUG("CMD <L>");
506             tty_write(PortFD, "L", 1, &bytesWritten);
507             numread = tty_read(PortFD, res, 2, 3, &bytesRead);
508             LOGF_DEBUG("RES <%s>", res);
509             if (res[0] != 48)
510             {
511                 //  Nothing to do here
512             }
513             else
514             {
515                 if (NumPark++ < 2)
516                 {
517                     Park();
518                 }
519                 else
520                 {
521                     TrackState = SCOPE_PARKED;
522                     SetParked(true);
523                 }
524             }
525         }
526         else
527         {
528             //  ok, lets try read where we are
529             //  and see if we have reached the park position
530             //  newer firmware versions dont read it back the same way
531             //  so we watch now to see if we get the same read twice in a row
532             //  to confirm that it has stopped moving
533             memset(res, 0, MAX_SYN_BUF);
534             LOG_DEBUG("CMD <z>");
535             tty_write(PortFD, "z", 1, &bytesWritten);
536             numread = tty_read(PortFD, res, 18, 2, &bytesRead);
537             LOGF_DEBUG("RES <%s>", res);
538 
539             //IDMessage(getDeviceName(),"Park Read %s %d",res,StopCount);
540 
541             if (strncmp((char *)res, LastParkRead, 18) == 0)
542             {
543                 //  We find that often after it stops from park
544                 //  it's off the park position by a small amount
545                 //  issuing another park command gets a small movement and then
546                 if (++StopCount > 2)
547                 {
548                     if (NumPark++ < 2)
549                     {
550                         StopCount = 0;
551                         //IDMessage(getDeviceName(),"Sending park again");
552                         Park();
553                     }
554                     else
555                     {
556                         TrackState = SCOPE_PARKED;
557                         //ParkSP.s=IPS_OK;
558                         //IDSetSwitch(&ParkSP,nullptr);
559                         //IDMessage(getDeviceName(),"Telescope is Parked.");
560                         SetParked(true);
561                     }
562                 }
563                 else
564                 {
565                     //StopCount=0;
566                 }
567             }
568             else
569             {
570                 StopCount = 0;
571             }
572             strncpy(LastParkRead, res, 20);
573         }
574     }
575 
576     memset(res, 0, MAX_SYN_BUF);
577     LOG_DEBUG("CMD <e>");
578     tty_write(PortFD, "e", 1, &bytesWritten);
579     numread = tty_read(PortFD, res, 18, 1, &bytesRead);
580     LOGF_DEBUG("RES <%s>", res);
581     if (bytesRead != 18)
582     {
583         LOG_DEBUG("Read current position failed");
584         return false;
585     }
586 
587     sscanf(res, "%lx,%lx#", &n1, &n2);
588     ra  = static_cast<double>(n1) / 0x100000000 * 24.0;
589     dec = static_cast<double>(n2) / 0x100000000 * 360.0;
590 
591     INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
592     J2000Pos.rightascension  = range24(ra);
593     J2000Pos.declination = rangeDec(dec);
594 
595     // Synscan reports J2000 coordinates so we need to convert from J2000 to JNow
596     INDI::J2000toObserved(&J2000Pos, ln_get_julian_from_sys(), &epochPos);
597 
598     CurrentRA  = epochPos.rightascension;
599     CurrentDEC = epochPos.declination;
600 
601     //  Now feed the rest of the system with corrected data
602     NewRaDec(CurrentRA, CurrentDEC);
603 
604     if (TrackState == SCOPE_SLEWING && MountCode >= 128 && (SlewTargetAz != -1 || SlewTargetAlt != -1))
605     {
606         INDI::IHorizontalCoordinates CurrentAltAz { 0, 0 };
607         double DiffAlt { 0 };
608         double DiffAz { 0 };
609 
610         INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &CurrentAltAz);
611         DiffAlt = CurrentAltAz.altitude - SlewTargetAlt;
612         if (SlewTargetAlt != -1 && std::abs(DiffAlt) > 0.01)
613         {
614             int NewRate = 2;
615 
616             if (std::abs(DiffAlt) > 4)
617             {
618                 NewRate = 9;
619             }
620             else if (std::abs(DiffAlt) > 1.2)
621             {
622                 NewRate = 7;
623             }
624             else if (std::abs(DiffAlt) > 0.5)
625             {
626                 NewRate = 5;
627             }
628             else if (std::abs(DiffAlt) > 0.2)
629             {
630                 NewRate = 4;
631             }
632             else if (std::abs(DiffAlt) > 0.025)
633             {
634                 NewRate = 3;
635             }
636             LOGF_DEBUG("Slewing Alt axis: %1.3f-%1.3f -> %1.3f (speed: %d)",
637                        CurrentAltAz.altitude, SlewTargetAlt, CurrentAltAz.altitude - SlewTargetAlt, CustomNSSlewRate);
638             if (NewRate != CustomNSSlewRate)
639             {
640                 if (DiffAlt < 0)
641                 {
642                     CustomNSSlewRate = NewRate;
643                     MoveNS(DIRECTION_NORTH, MOTION_START);
644                 }
645                 else
646                 {
647                     CustomNSSlewRate = NewRate;
648                     MoveNS(DIRECTION_SOUTH, MOTION_START);
649                 }
650             }
651         }
652         else if (SlewTargetAlt != -1 && std::abs(DiffAlt) < 0.01)
653         {
654             MoveNS(DIRECTION_NORTH, MOTION_STOP);
655             SlewTargetAlt = -1;
656             LOG_DEBUG("Slewing on Alt axis finished");
657         }
658         DiffAz = CurrentAltAz.azimuth - SlewTargetAz;
659         if (DiffAz < -180)
660             DiffAz = (DiffAz + 360) * 2;
661         else if (DiffAz > 180)
662             DiffAz = (DiffAz - 360) * 2;
663         if (SlewTargetAz != -1 && std::abs(DiffAz) > 0.01)
664         {
665             int NewRate = 2;
666 
667             if (std::abs(DiffAz) > 4)
668             {
669                 NewRate = 9;
670             }
671             else if (std::abs(DiffAz) > 1.2)
672             {
673                 NewRate = 7;
674             }
675             else if (std::abs(DiffAz) > 0.5)
676             {
677                 NewRate = 5;
678             }
679             else if (std::abs(DiffAz) > 0.2)
680             {
681                 NewRate = 4;
682             }
683             else if (std::abs(DiffAz) > 0.025)
684             {
685                 NewRate = 3;
686             }
687             LOGF_DEBUG("Slewing Az axis: %1.3f-%1.3f -> %1.3f (speed: %d)",
688                        CurrentAltAz.azimuth, SlewTargetAz, CurrentAltAz.azimuth - SlewTargetAz, CustomWESlewRate);
689             if (NewRate != CustomWESlewRate)
690             {
691                 if (DiffAz > 0)
692                 {
693                     CustomWESlewRate = NewRate;
694                     MoveWE(DIRECTION_WEST, MOTION_START);
695                 }
696                 else
697                 {
698                     CustomWESlewRate = NewRate;
699                     MoveWE(DIRECTION_EAST, MOTION_START);
700                 }
701             }
702         }
703         else if (SlewTargetAz != -1 && std::abs(DiffAz) < 0.01)
704         {
705             MoveWE(DIRECTION_WEST, MOTION_STOP);
706             SlewTargetAz = -1;
707             LOG_DEBUG("Slewing on Az axis finished");
708         }
709         if (SlewTargetAz == -1 && SlewTargetAlt == -1)
710         {
711             StartTrackMode();
712         }
713     }
714     return true;
715 }
716 
StartTrackMode()717 bool SynscanLegacyDriver::StartTrackMode()
718 {
719     char res[MAX_SYN_BUF] = {0};
720     int numread, bytesWritten, bytesRead;
721 
722     TrackState = SCOPE_TRACKING;
723     LOG_INFO("Tracking started.");
724 
725     if (isSimulation())
726         return true;
727 
728     // Start tracking
729     res[0] = 'T';
730     // Check the mount type to choose tracking mode
731     if (MountCode >= 128)
732     {
733         // Alt/Az tracking mode
734         res[1] = 1;
735     }
736     else
737     {
738         // EQ tracking mode
739         res[1] = 2;
740     }
741     tty_write(PortFD, res, 2, &bytesWritten);
742     numread = tty_read(PortFD, res, 1, 2, &bytesRead);
743     if (bytesRead != 1 || res[0] != '#')
744     {
745         LOG_DEBUG("Timeout waiting for scope to start tracking.");
746         return false;
747     }
748     return true;
749 }
750 
Goto(double ra,double dec)751 bool SynscanLegacyDriver::Goto(double ra, double dec)
752 {
753     char res[MAX_SYN_BUF] = {0};
754     int bytesWritten, bytesRead;
755     INDI::IHorizontalCoordinates TargetAltAz { 0, 0 };
756 
757     if (isSimulation() == false)
758     {
759         LOG_DEBUG("CMD <Ka>");
760         tty_write(PortFD, "Ka", 2, &bytesWritten); //  test for an echo
761         tty_read(PortFD, res, 2, 2, &bytesRead);   //  Read 2 bytes of response
762         LOGF_DEBUG("RES <%s>", res);
763         if (res[1] != '#')
764         {
765             LOG_WARN("Wrong answer from the mount");
766             //  this is not a correct echo
767             //  so we are not talking to a mount properly
768             return false;
769         }
770     }
771 
772     TrackState = SCOPE_SLEWING;
773     // EQ mount has a different Goto mode
774     if (MountCode < 128 && isSimulation() == false)
775     {
776         INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
777         epochPos.rightascension  = ra;
778         epochPos.declination = dec;
779 
780         // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
781         INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
782 
783         // Mount deals in J2000 coords.
784         int n1 = J2000Pos.rightascension * 0x1000000 / 24;
785         int n2 = J2000Pos.declination * 0x1000000 / 360;
786         int numread;
787 
788         LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
789                    J2000Pos.declination);
790 
791         n1 = n1 << 8;
792         n2 = n2 << 8;
793         LOGF_DEBUG("CMD <%s>", res);
794         snprintf(res, MAX_SYN_BUF, "r%08X,%08X", n1, n2);
795         tty_write(PortFD, res, 18, &bytesWritten);
796         memset(&res[18], 0, 1);
797 
798         numread = tty_read(PortFD, res, 1, 60, &bytesRead);
799         if (bytesRead != 1 || res[0] != '#')
800         {
801             LOG_DEBUG("Timeout waiting for scope to complete goto.");
802             return false;
803         }
804 
805         return true;
806     }
807 
808     INDI::IEquatorialCoordinates epochPos { ra, dec };
809     INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &TargetAltAz);
810     LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g (az: %g alt: %g)", ra, dec, TargetAltAz.azimuth, TargetAltAz.altitude);
811     char RAStr[MAX_SYN_BUF] = {0}, DEStr[MAX_SYN_BUF] = {0}, AZStr[MAX_SYN_BUF] = {0}, ATStr[MAX_SYN_BUF] = {0};
812     fs_sexa(RAStr, ra, 2, 3600);
813     fs_sexa(DEStr, dec, 2, 3600);
814     fs_sexa(AZStr, TargetAltAz.azimuth, 2, 3600);
815     fs_sexa(ATStr, TargetAltAz.altitude, 2, 3600);
816 
817     LOGF_INFO("Goto RA: %s DE: %s AZ: %s ALT: %s", RAStr, DEStr, AZStr, ATStr);
818 
819     SlewTargetAz = TargetAltAz.azimuth;
820     SlewTargetAlt = TargetAltAz.altitude;
821 
822     TargetRA = ra;
823     TargetDEC = dec;
824 
825     return true;
826 }
827 
Park()828 bool SynscanLegacyDriver::Park()
829 {
830     char res[MAX_SYN_BUF] = {0};
831     int numread, bytesWritten, bytesRead;
832 
833     if (isSimulation() == false)
834     {
835         strncpy(LastParkRead, "", 1);
836         memset(res, 0, 3);
837         tty_write(PortFD, "Ka", 2, &bytesWritten); //  test for an echo
838         tty_read(PortFD, res, 2, 2, &bytesRead);   //  Read 2 bytes of response
839         if (res[1] != '#')
840         {
841             //  this is not a correct echo
842             //  so we are not talking to a mount properly
843             return false;
844         }
845         //  Now we stop tracking
846         res[0] = 'T';
847         res[1] = 0;
848         tty_write(PortFD, res, 2, &bytesWritten);
849         numread = tty_read(PortFD, res, 1, 60, &bytesRead);
850         if (bytesRead != 1 || res[0] != '#')
851         {
852             LOG_DEBUG("Timeout waiting for scope to stop tracking.");
853             return false;
854         }
855 
856         //sprintf((char *)res,"b%08X,%08X",0x0,0x40000000);
857         tty_write(PortFD, "b00000000,40000000", 18, &bytesWritten);
858         numread = tty_read(PortFD, res, 1, 60, &bytesRead);
859         if (bytesRead != 1 || res[0] != '#')
860         {
861             LOG_DEBUG("Timeout waiting for scope to respond to park.");
862             return false;
863         }
864     }
865 
866     TrackState = SCOPE_PARKING;
867     if (NumPark == 0)
868     {
869         LOG_INFO("Parking Mount...");
870     }
871     StopCount = 0;
872     return true;
873 }
874 
UnPark()875 bool SynscanLegacyDriver::UnPark()
876 {
877     SetParked(false);
878     NumPark = 0;
879     return true;
880 }
881 
SetCurrentPark()882 bool SynscanLegacyDriver::SetCurrentPark()
883 {
884     LOG_INFO("Setting arbitrary park positions is not supported yet.");
885     return false;
886 }
887 
SetDefaultPark()888 bool SynscanLegacyDriver::SetDefaultPark()
889 {
890     // By default az to north, and alt to pole
891     LOG_DEBUG("Setting Park Data to Default.");
892     SetAxis1Park(0);
893     SetAxis2Park(90);
894 
895     return true;
896 }
897 
Abort()898 bool SynscanLegacyDriver::Abort()
899 {
900     if (TrackState == SCOPE_IDLE || RecoverTrials >= 3)
901         return true;
902 
903     char res[MAX_SYN_BUF] = {0};
904     int numread, bytesWritten, bytesRead;
905 
906     LOG_DEBUG("Abort mount...");
907     TrackState = SCOPE_IDLE;
908 
909     if (isSimulation())
910         return true;
911 
912     SlewTargetAlt = -1;
913     SlewTargetAz = -1;
914     CustomNSSlewRate = -1;
915     CustomWESlewRate = -1;
916     // Stop tracking
917     res[0] = 'T';
918     res[1] = 0;
919 
920     LOGF_DEBUG("CMD <%s>", res);
921     tty_write(PortFD, res, 2, &bytesWritten);
922 
923     numread = tty_read(PortFD, res, 1, 2, &bytesRead);
924     LOGF_DEBUG("RES <%s>", res);
925 
926     if (bytesRead != 1 || res[0] != '#')
927     {
928         LOG_DEBUG("Timeout waiting for scope to stop tracking.");
929         return false;
930     }
931 
932     // Hmmm twice only stops it
933     LOG_DEBUG("CMD <M>");
934     tty_write(PortFD, "M", 1, &bytesWritten);
935     tty_read(PortFD, res, 1, 1, &bytesRead);
936     LOGF_DEBUG("RES <%c>", res[0]);
937 
938     LOG_DEBUG("CMD <M>");
939     tty_write(PortFD, "M", 1, &bytesWritten);
940     tty_read(PortFD, res, 1, 1, &bytesRead);
941     LOGF_DEBUG("RES <%c>", res[0]);
942 
943     return true;
944 }
945 
MoveNS(INDI_DIR_NS dir,TelescopeMotionCommand command)946 bool SynscanLegacyDriver::MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
947 {
948     if (isSimulation())
949         return true;
950 
951     if (command != MOTION_START)
952     {
953         PassthruCommand(37, 17, 2, 0, 0);
954     }
955     else
956     {
957         int tt = (CustomNSSlewRate == -1 ? SlewRate : CustomNSSlewRate);
958 
959         tt = tt << 16;
960         if (dir != DIRECTION_NORTH)
961         {
962             PassthruCommand(37, 17, 2, tt, 0);
963         }
964         else
965         {
966             PassthruCommand(36, 17, 2, tt, 0);
967         }
968     }
969 
970     return true;
971 }
972 
MoveWE(INDI_DIR_WE dir,TelescopeMotionCommand command)973 bool SynscanLegacyDriver::MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
974 {
975     if (isSimulation())
976         return true;
977 
978     if (command != MOTION_START)
979     {
980         PassthruCommand(37, 16, 2, 0, 0);
981     }
982     else
983     {
984         int tt = (CustomWESlewRate == -1 ? SlewRate : CustomWESlewRate);
985 
986         tt = tt << 16;
987         if (dir != DIRECTION_WEST)
988         {
989             PassthruCommand(36, 16, 2, tt, 0);
990         }
991         else
992         {
993             PassthruCommand(37, 16, 2, tt, 0);
994         }
995     }
996 
997     return true;
998 }
999 
SetSlewRate(int s)1000 bool SynscanLegacyDriver::SetSlewRate(int s)
1001 {
1002     SlewRate = s + 1;
1003     return true;
1004 }
1005 
PassthruCommand(int cmd,int target,int msgsize,int data,int numReturn)1006 int SynscanLegacyDriver::PassthruCommand(int cmd, int target, int msgsize, int data, int numReturn)
1007 {
1008     char test[20] = {0};
1009     int bytesRead, bytesWritten;
1010     char a, b, c;
1011     int tt = data;
1012 
1013     a  = tt % 256;
1014     tt = tt >> 8;
1015     b  = tt % 256;
1016     tt = tt >> 8;
1017     c  = tt % 256;
1018 
1019     //  format up a passthru command
1020     memset(test, 0, 20);
1021     test[0] = 80;      // passhtru
1022     test[1] = msgsize; // set message size
1023     test[2] = target;  // set the target
1024     test[3] = cmd;     // set the command
1025     test[4] = c;       // set data bytes
1026     test[5] = b;
1027     test[6] = a;
1028     test[7] = numReturn;
1029 
1030     LOGF_DEBUG("CMD <%s>", test);
1031     tty_write(PortFD, test, 8, &bytesWritten);
1032     memset(test, 0, 20);
1033     tty_read(PortFD, test, numReturn + 1, 2, &bytesRead);
1034     LOGF_DEBUG("RES <%s>", test);
1035     if (numReturn > 0)
1036     {
1037         int retval = 0;
1038         retval     = test[0];
1039         if (numReturn > 1)
1040         {
1041             retval = retval << 8;
1042             retval += test[1];
1043         }
1044         if (numReturn > 2)
1045         {
1046             retval = retval << 8;
1047             retval += test[2];
1048         }
1049         return retval;
1050     }
1051 
1052     return 0;
1053 }
1054 
ReadTime()1055 bool SynscanLegacyDriver::ReadTime()
1056 {
1057     LOG_DEBUG("Reading time...");
1058 
1059     if (isSimulation())
1060     {
1061         char timeString[MAXINDINAME] = {0};
1062         time_t now = time (nullptr);
1063         strftime(timeString, MAXINDINAME, "%T", gmtime(&now));
1064         IUSaveText(&TimeT[0], "3");
1065         IUSaveText(&TimeT[1], timeString);
1066         TimeTP.s = IPS_OK;
1067         IDSetText(&TimeTP, nullptr);
1068         return true;
1069     }
1070 
1071     char res[MAX_SYN_BUF] = {0};
1072     int bytesWritten = 0, bytesRead = 0;
1073 
1074     //  lets see if this hand controller responds to a time request
1075     bytesRead = 0;
1076     LOG_DEBUG("CMD <h>");
1077     tty_write(PortFD, "h", 1, &bytesWritten);
1078 
1079     tty_read(PortFD, res, 9, 2, &bytesRead);
1080     LOGF_DEBUG("RES <%s>", res);
1081 
1082     if (res[8] == '#')
1083     {
1084         ln_zonedate localTime;
1085         ln_date utcTime;
1086         int offset, daylightflag;
1087 
1088         localTime.hours   = res[0];
1089         localTime.minutes = res[1];
1090         localTime.seconds = res[2];
1091         localTime.months  = res[3];
1092         localTime.days    = res[4];
1093         localTime.years   = res[5];
1094         offset            = (int)res[6];
1095         // Negative GMT offset is read. It needs special treatment
1096         if (offset > 200)
1097             offset -= 256;
1098         localTime.gmtoff = offset;
1099         //  this is the daylight savings flag in the hand controller, needed if we did not set the time
1100         daylightflag = res[7];
1101         localTime.years += 2000;
1102         localTime.gmtoff *= 3600;
1103         //  now convert to utc
1104         ln_zonedate_to_date(&localTime, &utcTime);
1105 
1106         //  now we have time from the hand controller, we need to set some variables
1107         int sec;
1108         char utc[100];
1109         char ofs[10];
1110         sec = (int)utcTime.seconds;
1111         sprintf(utc, "%04d-%02d-%dT%d:%02d:%02d", utcTime.years, utcTime.months, utcTime.days, utcTime.hours,
1112                 utcTime.minutes, sec);
1113         if (daylightflag == 1)
1114             offset = offset + 1;
1115         sprintf(ofs, "%d", offset);
1116 
1117         IUSaveText(&TimeT[0], utc);
1118         IUSaveText(&TimeT[1], ofs);
1119         TimeTP.s = IPS_OK;
1120         IDSetText(&TimeTP, nullptr);
1121 
1122         LOGF_INFO("Mount UTC Time %s Offset %d", utc, offset);
1123 
1124         return true;
1125     }
1126     return false;
1127 }
1128 
ReadLocation()1129 bool SynscanLegacyDriver::ReadLocation()
1130 {
1131     LOG_DEBUG("Reading Location...");
1132 
1133     if (isSimulation())
1134     {
1135         LocationN[LOCATION_LATITUDE].value  = 29.5;
1136         LocationN[LOCATION_LONGITUDE].value = 48;
1137         IDSetNumber(&LocationNP, nullptr);
1138         ReadLatLong = false;
1139         return true;
1140     }
1141 
1142     char res[MAX_SYN_BUF] = {0};
1143     int bytesWritten = 0, bytesRead = 0;
1144 
1145     LOG_DEBUG("CMD <Ka>");
1146     //  test for an echo
1147     tty_write(PortFD, "Ka", 2, &bytesWritten);
1148     //  Read 2 bytes of response
1149     tty_read(PortFD, res, 2, 2, &bytesRead);
1150     LOGF_DEBUG("RES <%s>", res);
1151 
1152     if (res[1] != '#')
1153     {
1154         LOG_WARN("Bad echo in ReadLocation");
1155     }
1156     else
1157     {
1158         //  lets see if this hand controller responds to a location request
1159         bytesRead = 0;
1160         LOG_DEBUG("CMD <w>");
1161         tty_write(PortFD, "w", 1, &bytesWritten);
1162 
1163         tty_read(PortFD, res, 9, 2, &bytesRead);
1164         LOGF_DEBUG("RES <%s>", res);
1165 
1166         if (res[8] == '#')
1167         {
1168             double lat, lon;
1169             //  lets parse this data now
1170             int a, b, c, d, e, f, g, h;
1171             a = res[0];
1172             b = res[1];
1173             c = res[2];
1174             d = res[3];
1175             e = res[4];
1176             f = res[5];
1177             g = res[6];
1178             h = res[7];
1179 
1180             LOGF_DEBUG("Pos %d:%d:%d  %d:%d:%d", a, b, c, e, f, g);
1181 
1182             double t1, t2, t3;
1183 
1184             t1  = c;
1185             t2  = b;
1186             t3  = a;
1187             t1  = t1 / 3600.0;
1188             t2  = t2 / 60.0;
1189             lat = t1 + t2 + t3;
1190 
1191             t1  = g;
1192             t2  = f;
1193             t3  = e;
1194             t1  = t1 / 3600.0;
1195             t2  = t2 / 60.0;
1196             lon = t1 + t2 + t3;
1197 
1198             if (d == 1)
1199                 lat = lat * -1;
1200             if (h == 1)
1201                 lon = 360 - lon;
1202             LocationN[LOCATION_LATITUDE].value  = lat;
1203             LocationN[LOCATION_LONGITUDE].value = lon;
1204             IDSetNumber(&LocationNP, nullptr);
1205 
1206             saveConfig(true, "GEOGRAPHIC_COORD");
1207 
1208             char LongitudeStr[32] = {0}, LatitudeStr[32] = {0};
1209             fs_sexa(LongitudeStr, lon, 2, 3600);
1210             fs_sexa(LatitudeStr, lat, 2, 3600);
1211             LOGF_INFO("Mount Longitude %s Latitude %s", LongitudeStr, LatitudeStr);
1212 
1213             //  We dont need to keep reading this one on every cycle
1214             //  only need to read it when it's been changed
1215             ReadLatLong = false;
1216             return true;
1217         }
1218         else
1219         {
1220             LOG_INFO("Mount does not support setting location.");
1221         }
1222     }
1223     return false;
1224 }
1225 
updateTime(ln_date * utc,double utc_offset)1226 bool SynscanLegacyDriver::updateTime(ln_date *utc, double utc_offset)
1227 {
1228     char res[MAX_SYN_BUF] = {0};
1229     int bytesWritten = 0, bytesRead = 0;
1230 
1231     //  start by formatting a time for the hand controller
1232     //  we are going to set controller to local time
1233     struct ln_zonedate ltm;
1234 
1235     ln_date_to_zonedate(utc, &ltm, (long)utc_offset * 3600.0);
1236 
1237     int yr = ltm.years;
1238 
1239     yr = yr % 100;
1240 
1241     res[0] = 'H';
1242     res[1] = ltm.hours;
1243     res[2] = ltm.minutes;
1244     res[3] = (char)(int)ltm.seconds;
1245     res[4] = ltm.months;
1246     res[5] = ltm.days;
1247     res[6] = yr;
1248     // Strangely enough static_cast<int>(double) results 0 for negative values on arm
1249     // We need to use old C-like casts in this case.
1250     res[7] = (char)(int)utc_offset; //  offset from utc so hand controller is running in local time
1251     res[8] = 0;          //  and no daylight savings adjustments, it's already included in the offset
1252     //  lets write a time to the hand controller
1253     bytesRead = 0;
1254 
1255     LOGF_INFO("Setting mount date/time to %04d-%02d-%02d %d:%02d:%02d UTC Offset: %d",
1256               ltm.years, ltm.months, ltm.days, ltm.hours, ltm.minutes, ltm.seconds, utc_offset);
1257 
1258     if (isSimulation())
1259         return true;
1260 
1261     LOGF_DEBUG("CMD <%s>", res);
1262     tty_write(PortFD, res, 9, &bytesWritten);
1263 
1264     tty_read(PortFD, res, 1, 2, &bytesRead);
1265     LOGF_DEBUG("RES <%c>", res[0]);
1266 
1267     if (res[0] != '#')
1268     {
1269         LOG_INFO("Invalid return from set time");
1270     }
1271     return true;
1272 }
1273 
updateLocation(double latitude,double longitude,double elevation)1274 bool SynscanLegacyDriver::updateLocation(double latitude, double longitude, double elevation)
1275 {
1276     INDI_UNUSED(elevation);
1277 
1278     char res[MAX_SYN_BUF] = {0};
1279     int bytesWritten = 0, bytesRead = 0;
1280     int s = 0;
1281     bool IsWest = false;
1282     double tmp = 0;
1283 
1284     ln_lnlat_posn p1 { 0, 0 };
1285     lnh_lnlat_posn p2;
1286 
1287     if (isSimulation())
1288     {
1289         if (CurrentDEC == 0)
1290         {
1291             CurrentDEC = latitude > 0 ? 90 : -90;
1292             CurrentRA = get_local_sidereal_time(longitude);
1293         }
1294         return true;
1295     }
1296 
1297     if (!CanSetLocation)
1298     {
1299         return true;
1300     }
1301     else
1302     {
1303         if (longitude > 180)
1304         {
1305             p1.lng = 360.0 - longitude;
1306             IsWest = true;
1307         }
1308         else
1309         {
1310             p1.lng = longitude;
1311         }
1312         p1.lat = latitude;
1313         ln_lnlat_to_hlnlat(&p1, &p2);
1314         LOGF_INFO("Update location to latitude %d:%d:%1.2f longitude %d:%d:%1.2f",
1315                   p2.lat.degrees, p2.lat.minutes, p2.lat.seconds, p2.lng.degrees, p2.lng.minutes, p2.lng.seconds);
1316 
1317         res[0] = 'W';
1318         res[1] = p2.lat.degrees;
1319         res[2] = p2.lat.minutes;
1320         tmp    = p2.lat.seconds + 0.5;
1321         s      = (int)tmp; //  put in an int that's rounded
1322         res[3] = s;
1323         if (p2.lat.neg == 0)
1324         {
1325             res[4] = 0;
1326         }
1327         else
1328         {
1329             res[4] = 1;
1330         }
1331 
1332         res[5] = p2.lng.degrees;
1333         res[6] = p2.lng.minutes;
1334         s      = (int)(p2.lng.seconds + 0.5); //  make an int, that's rounded
1335         res[7] = s;
1336         if (IsWest)
1337             res[8] = 1;
1338         else
1339             res[8] = 0;
1340         //  All formatted, now send to the hand controller;
1341         bytesRead = 0;
1342 
1343         LOGF_DEBUG("CMD <%s>", res);
1344         tty_write(PortFD, res, 9, &bytesWritten);
1345 
1346         tty_read(PortFD, res, 1, 2, &bytesRead);
1347         LOGF_DEBUG("RES <%c>", res[0]);
1348 
1349         if (res[0] != '#')
1350         {
1351             LOG_INFO("Invalid response for location setting");
1352         }
1353         //  want to read it on the next cycle, so we update the fields in the client
1354         ReadLatLong = true;
1355 
1356         return true;
1357     }
1358 }
1359 
Sync(double ra,double dec)1360 bool SynscanLegacyDriver::Sync(double ra, double dec)
1361 {
1362     /*
1363      * Frank Liu, R&D Engineer for Skywatcher, says to only issue a Sync
1364      * command, and not to use the Position Reset command, when syncing. I
1365      * removed the position reset code for EQ mounts, but left it in for
1366      * Alt/Az mounts, since it seems to be working, at least for the person
1367      * (@kecsap) who put it in there in the first place. :)
1368      *
1369      * The code prior to kecsap's recent fix would always send a position
1370      * reset command, but it would send Alt/Az coordinates, even to an EQ
1371      * mount. This would really screw up EQ mount alignment.
1372      *
1373      * The reason a lone Sync command appeared to not work before, is because
1374      * it will only accept a Sync command if the offset is relatively small,
1375      * within 6-7 degrees or so. So you must already have done an alignment
1376      * through the handset (a 1-star alignment would suffice), and only use
1377      * the Sync command to "touch-up" the alignment. You can't take a scope,
1378      * power it on, point it to a random place in the sky, do a plate-solve,
1379      * and sync. That won't work.
1380      */
1381 
1382     bool IsTrackingBeforeSync = (TrackState == SCOPE_TRACKING);
1383 
1384     // Abort any motion before syncing
1385     Abort();
1386 
1387     LOGF_INFO("Sync JNow %g %g -> %g %g", CurrentRA, CurrentDEC, ra, dec);
1388     char res[MAX_SYN_BUF] = {0};
1389     int numread, bytesWritten, bytesRead;
1390 
1391     if (isSimulation())
1392     {
1393         CurrentRA = ra;
1394         CurrentDEC = dec;
1395         return true;
1396     }
1397 
1398     // Alt/Az sync mode
1399     if (MountCode >= 128)
1400     {
1401         INDI::IHorizontalCoordinates TargetAltAz { 0, 0 };
1402         INDI::IEquatorialCoordinates epochPos {ra, dec};
1403         INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &TargetAltAz);
1404         LOGF_DEBUG("Sync - ra: %g de: %g to az: %g alt: %g", ra, dec, TargetAltAz.azimuth, TargetAltAz.altitude);
1405         // Assemble the Reset Position command for Az axis
1406         int Az = (int)(TargetAltAz.azimuth * 16777216 / 360);
1407 
1408         res[0] = 'P';
1409         res[1] = 4;
1410         res[2] = 16;
1411         res[3] = 4;
1412         *reinterpret_cast<unsigned char*>(&res[4]) = (unsigned char)(Az / 65536);
1413         Az -= (Az / 65536) * 65536;
1414         *reinterpret_cast<unsigned char*>(&res[5]) = (unsigned char)(Az / 256);
1415         Az -= (Az / 256) * 256;
1416         *reinterpret_cast<unsigned char*>(&res[6]) = (unsigned char)Az;
1417         res[7] = 0;
1418         tty_write(PortFD, res, 8, &bytesWritten);
1419         numread = tty_read(PortFD, res, 1, 3, &bytesRead);
1420         // Assemble the Reset Position command for Alt axis
1421         int Alt = (int)(TargetAltAz.altitude * 16777216 / 360);
1422 
1423         res[0] = 'P';
1424         res[1] = 4;
1425         res[2] = 17;
1426         res[3] = 4;
1427         *reinterpret_cast<unsigned char*>(&res[4]) = (unsigned char)(Alt / 65536);
1428         Alt -= (Alt / 65536) * 65536;
1429         *reinterpret_cast<unsigned char*>(&res[5]) = (unsigned char)(Alt / 256);
1430         Alt -= (Alt / 256) * 256;
1431         *reinterpret_cast<unsigned char*>(&res[6]) = (unsigned char)Alt;
1432         res[7] = 0;
1433         LOGF_DEBUG("CMD <%s>", res);
1434         tty_write(PortFD, res, 8, &bytesWritten);
1435 
1436         numread = tty_read(PortFD, res, 1, 2, &bytesRead);
1437         LOGF_DEBUG("CMD <%c>", res[0]);
1438     }
1439 
1440     INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1441 
1442     epochPos.rightascension  = ra;
1443     epochPos.declination = dec;
1444 
1445     // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
1446     //ln_get_equ_prec2(&epochPos, ln_get_julian_from_sys(), JD2000, &J2000Pos);
1447     INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
1448 
1449     // Pass the sync command to the handset
1450     int n1 = J2000Pos.rightascension * 0x1000000 / 24;
1451     int n2 = J2000Pos.declination * 0x1000000 / 360;
1452 
1453     n1 = n1 << 8;
1454     n2 = n2 << 8;
1455     snprintf(res, MAX_SYN_BUF, "s%08X,%08X", n1, n2);
1456     memset(&res[18], 0, 1);
1457 
1458     LOGF_DEBUG("CMD <%s>", res);
1459     tty_write(PortFD, res, 18, &bytesWritten);
1460 
1461     numread = tty_read(PortFD, res, 1, 60, &bytesRead);
1462     LOGF_DEBUG("RES <%c>", res[0]);
1463 
1464     if (bytesRead != 1 || res[0] != '#')
1465     {
1466         LOG_DEBUG("Timeout waiting for scope to complete syncing.");
1467         return false;
1468     }
1469 
1470     // Start tracking again
1471     if (IsTrackingBeforeSync)
1472         StartTrackMode();
1473 
1474     return true;
1475 }
1476 
UpdateMountInformation(bool inform_client)1477 void SynscanLegacyDriver::UpdateMountInformation(bool inform_client)
1478 {
1479     bool BasicMountInfoHasChanged = false;
1480     std::string MountCodeStr = std::to_string(MountCode);
1481 
1482     if (std::string(BasicMountInfoT[MI_FW_VERSION].text) != HandsetFwVersion)
1483     {
1484         IUSaveText(&BasicMountInfoT[MI_FW_VERSION], HandsetFwVersion.c_str());
1485         BasicMountInfoHasChanged = true;
1486     }
1487     if (std::string(BasicMountInfoT[MI_MOUNT_CODE].text) != MountCodeStr)
1488     {
1489         IUSaveText(&BasicMountInfoT[MI_MOUNT_CODE], MountCodeStr.c_str());
1490         BasicMountInfoHasChanged = true;
1491     }
1492 
1493     if (std::string(BasicMountInfoT[MI_ALIGN_STATUS].text) != AlignmentStatus)
1494     {
1495         IUSaveText(&BasicMountInfoT[MI_ALIGN_STATUS], AlignmentStatus.c_str());
1496         BasicMountInfoHasChanged = true;
1497     }
1498     if (std::string(BasicMountInfoT[MI_GOTO_STATUS].text) != GotoStatus)
1499     {
1500         IUSaveText(&BasicMountInfoT[MI_GOTO_STATUS], GotoStatus.c_str());
1501         BasicMountInfoHasChanged = true;
1502     }
1503     if (std::string(BasicMountInfoT[MI_POINT_STATUS].text) != PointingStatus)
1504     {
1505         IUSaveText(&BasicMountInfoT[MI_POINT_STATUS], PointingStatus.c_str());
1506         BasicMountInfoHasChanged = true;
1507     }
1508     if (std::string(BasicMountInfoT[MI_TRACK_MODE].text) != TrackingMode)
1509     {
1510         IUSaveText(&BasicMountInfoT[MI_TRACK_MODE], TrackingMode.c_str());
1511         BasicMountInfoHasChanged = true;
1512     }
1513 
1514     if (BasicMountInfoHasChanged && inform_client)
1515         IDSetText(&BasicMountInfoTP, nullptr);
1516 }
1517 
MountSim()1518 void SynscanLegacyDriver::MountSim()
1519 {
1520     static struct timeval ltv;
1521     struct timeval tv;
1522     double dt, da, dx;
1523     int nlocked;
1524 
1525     /* update elapsed time since last poll, don't presume exactly POLLMS */
1526     gettimeofday(&tv, nullptr);
1527 
1528     if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1529         ltv = tv;
1530 
1531     dt  = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1532     ltv = tv;
1533     double currentSlewRate = SLEW_RATE[IUFindOnSwitchIndex(&SlewRateSP)] * TRACKRATE_SIDEREAL / 3600.0;
1534     da  = currentSlewRate * dt;
1535 
1536     /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */
1537     switch (TrackState)
1538     {
1539         case SCOPE_IDLE:
1540             CurrentRA += (TrackRateN[AXIS_RA].value / 3600.0 * dt) / 15.0;
1541             CurrentRA = range24(CurrentRA);
1542             break;
1543 
1544         case SCOPE_TRACKING:
1545             break;
1546 
1547         case SCOPE_SLEWING:
1548         case SCOPE_PARKING:
1549             /* slewing - nail it when both within one pulse @ SLEWRATE */
1550             nlocked = 0;
1551 
1552             dx = TargetRA - CurrentRA;
1553 
1554             // Take shortest path
1555             if (fabs(dx) > 12)
1556                 dx *= -1;
1557 
1558             if (fabs(dx) <= da)
1559             {
1560                 CurrentRA = TargetRA;
1561                 nlocked++;
1562             }
1563             else if (dx > 0)
1564                 CurrentRA += da / 15.;
1565             else
1566                 CurrentRA -= da / 15.;
1567 
1568             if (CurrentRA < 0)
1569                 CurrentRA += 24;
1570             else if (CurrentRA > 24)
1571                 CurrentRA -= 24;
1572 
1573             dx = TargetDEC - CurrentDEC;
1574             if (fabs(dx) <= da)
1575             {
1576                 CurrentDEC = TargetDEC;
1577                 nlocked++;
1578             }
1579             else if (dx > 0)
1580                 CurrentDEC += da;
1581             else
1582                 CurrentDEC -= da;
1583 
1584             if (nlocked == 2)
1585             {
1586                 if (TrackState == SCOPE_SLEWING)
1587                     TrackState = SCOPE_TRACKING;
1588                 else
1589                     TrackState = SCOPE_PARKED;
1590             }
1591 
1592             break;
1593 
1594         default:
1595             break;
1596     }
1597 
1598     NewRaDec(CurrentRA, CurrentDEC);
1599 }
1600