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, <m, (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