Instrument Neutral Distributed Interface INDI 2.2.5
synscandriver.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*******************************************************************************/
18
19#include "synscandriver.h"
20#include "libastro.h"
23#include "indicom.h"
24
25#include <libnova/transform.h>
26#include <libnova/precession.h>
27// libnova specifies round() on old systems and it collides with the new gcc 5.x/6.x headers
28#define HAVE_ROUND
29#include <libnova/utility.h>
30
31#include <cmath>
32#include <map>
33#include <memory>
34#include <termios.h>
35#include <cstring>
36#include <assert.h>
37
38constexpr uint16_t SynscanDriver::SIM_SLEW_RATE[];
39
41{
42 setVersion(2, 0);
43
44 m_MountInfo.push_back("--");
45 m_MountInfo.push_back("--");
46 m_MountInfo.push_back("--");
47 m_MountInfo.push_back("--");
48 m_MountInfo.push_back("--");
49}
50
52{
53 return "SynScan";
54}
55
57{
59
64
65 // Slew Rates
66 SlewRateSP[0].setLabel("1x");
67 SlewRateSP[1].setLabel("8x");
68 SlewRateSP[2].setLabel("16x");
69 SlewRateSP[3].setLabel("32x");
70 SlewRateSP[4].setLabel("64x");
71 SlewRateSP[5].setLabel("128x");
72 SlewRateSP[6].setLabel("400x");
73 SlewRateSP[7].setLabel("600x");
74 SlewRateSP[8].setLabel("MAX");
75 SlewRateSP[9].setLabel("Custom");
77 // Max is the default
79
83 IUFillText(&StatusT[MI_FW_VERSION], "MI_FW_VERSION", "Firmware", "-");
84 IUFillText(&StatusT[MI_MOUNT_MODEL], "MI_MOUNT_MODEL", "Model", "-");
85 IUFillText(&StatusT[MI_GOTO_STATUS], "MI_GOTO_STATUS", "Goto", "-");
86 IUFillText(&StatusT[MI_POINT_STATUS], "MI_POINT_STATUS", "Pointing", "-");
87 IUFillText(&StatusT[MI_TRACK_MODE], "MI_TRACK_MODE", "Tracking Mode", "-");
88 IUFillTextVector(&StatusTP, StatusT, 5, getDeviceName(), "MOUNT_STATUS",
89 "Status", MOUNT_TAB, IP_RO, 60, IPS_IDLE);
90
94 IUFillNumber(&CustomSlewRateN[AXIS_RA], "AXIS1", "RA/AZ (arcsecs/s)", "%.2f", 0.05, 800, 10, 0);
95 IUFillNumber(&CustomSlewRateN[AXIS_DE], "AXIS2", "DE/AL (arcsecs/s)", "%.2f", 0.05, 800, 10, 0);
96 IUFillNumberVector(&CustomSlewRateNP, CustomSlewRateN, 2, getDeviceName(), "CUSTOM_SLEW_RATE", "Custom Slew", MOTION_TAB,
97 IP_RW, 60, IPS_IDLE);
98
102 IUFillNumber(&GuideRateN[AXIS_RA], "GUIDE_RATE_WE", "W/E Rate", "%.2f", 0, 1, 0.1, 0.5);
103 IUFillNumber(&GuideRateN[AXIS_DE], "GUIDE_RATE_NS", "N/S Rate", "%.2f", 0, 1, 0.1, 0.5);
104 IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guiding Rate", GUIDE_TAB, IP_RW, 0,
105 IPS_IDLE);
106
110 IUFillNumber(&HorizontalCoordsN[AXIS_AZ], "AZ", "Az D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
111 IUFillNumber(&HorizontalCoordsN[AXIS_ALT], "ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
112 IUFillNumberVector(&HorizontalCoordsNP, HorizontalCoordsN, 2, getDeviceName(), "HORIZONTAL_COORD",
113 "Horizontal Coord", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
114
115 AddTrackMode("TRACK_ALTAZ", "Alt/Az");
116 AddTrackMode("TRACK_EQ", "Equatorial", true);
117 AddTrackMode("TRACK_PEC", "PEC Mode");
118
119 IUFillSwitch(&GotoModeS[0], "ALTAZ", "Alt/Az", ISS_OFF);
120 IUFillSwitch(&GotoModeS[1], "RADEC", "Ra/Dec", ISS_ON);
121 IUFillSwitchVector(&GotoModeSP, GotoModeS, NARRAY(GotoModeS), getDeviceName(), "GOTOMODE", "Goto mode",
123
125
126 // Initialize guiding properties.
128
130
131 //GUIDE Set guider interface.
133
134 return true;
135}
136
138{
140
141 if (isConnected())
142 {
143 setupParams();
144
145 defineProperty(&HorizontalCoordsNP);
146 defineProperty(&StatusTP);
147 defineProperty(&CustomSlewRateNP);
148 defineProperty(&GuideRateNP);
149
150 if (m_isAltAz)
151 {
152 defineProperty(&GotoModeSP);
153 }
154
155 if (InitPark())
156 {
158 SetAxis2ParkDefault(m_isAltAz ? 0 : LocationNP[LOCATION_LATITUDE].getValue());
159 }
160 else
161 {
162 SetAxis1Park(359);
163 SetAxis2Park(m_isAltAz ? 0 : LocationNP[LOCATION_LATITUDE].getValue());
165 SetAxis2ParkDefault(m_isAltAz ? 0 : LocationNP[LOCATION_LATITUDE].getValue());
166 }
167 }
168 else
169 {
170 deleteProperty(HorizontalCoordsNP.name);
171 deleteProperty(StatusTP.name);
172 deleteProperty(CustomSlewRateNP.name);
173 deleteProperty(GuideRateNP.name);
174 if (m_isAltAz)
175 {
176 deleteProperty(GotoModeSP.name);
177 }
178 }
179
181
182 return true;
183}
184
185void SynscanDriver::setupParams()
186{
187 readFirmware();
188 //readModel();
189 readTracking();
190
191 sendLocation();
192 sendTime();
193}
194
195int SynscanDriver::hexStrToInteger(const std::string &res)
196{
197 int result = 0;
198
199 try
200 {
201 result = std::stoi(res, nullptr, 16);
202 }
203 catch (std::invalid_argument &)
204 {
205 LOGF_ERROR("Failed to parse %s to integer.", res.c_str());
206 }
207
208 return result;
209}
210
212{
213 char res[SYN_RES] = {0};
214 if (!echo())
215 return false;
216
217 // We can only proceed if the mount is aligned.
218 if (!sendCommand("J", res))
219 return false;
220
221 if (res[0] == 0)
222 {
223 LOG_ERROR("Mount is not aligned. Please align the mount first and connect again.");
224 return false;
225 }
226
227 readModel();
228
229 if (m_isAltAz)
230 {
232 }
233
234 return true;
235}
236
237bool SynscanDriver::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
238{
239 // Check guider interface
240 if (GI::processNumber(dev, name, values, names, n))
241 return true;
242
243 if (dev && !strcmp(dev, getDeviceName()))
244 {
245 // Guide Rate
246 if (strcmp(name, "GUIDE_RATE") == 0)
247 {
248 IUUpdateNumber(&GuideRateNP, values, names, n);
249 GuideRateNP.s = IPS_OK;
250 IDSetNumber(&GuideRateNP, nullptr);
251 return true;
252 }
253
254 // Custom Slew Rate
255 if (strcmp(name, CustomSlewRateNP.name) == 0)
256 {
258 {
259 LOG_ERROR("Cannot change rate while slewing.");
260 CustomSlewRateNP.s = IPS_ALERT;
261 IDSetNumber(&CustomSlewRateNP, nullptr);
262 return true;
263 }
264
265 IUUpdateNumber(&CustomSlewRateNP, values, names, n);
266 CustomSlewRateNP.s = IPS_OK;
267 IDSetNumber(&CustomSlewRateNP, nullptr);
268 return true;
269 }
270
271 // Horizontal Coords
272 if (!strcmp(name, HorizontalCoordsNP.name))
273 {
274 if (isParked())
275 {
276 LOG_WARN("Unpark mount before issuing GOTO commands.");
277 HorizontalCoordsNP.s = IPS_IDLE;
278 IDSetNumber(&HorizontalCoordsNP, nullptr);
279 return true;
280 }
281
282 int nset = 0;
283 double newAlt = 0, newAz = 0;
284 for (int i = 0; i < n; i++)
285 {
286 INumber * horp = IUFindNumber(&HorizontalCoordsNP, names[i]);
287 if (horp == &HorizontalCoordsN[AXIS_AZ])
288 {
289 newAz = values[i];
290 nset += newAz >= 0. && newAz <= 360.0;
291 }
292 else if (horp == &HorizontalCoordsN[AXIS_ALT])
293 {
294 newAlt = values[i];
295 nset += newAlt >= -90. && newAlt <= 90.0;
296 }
297 }
298
299 if (nset == 2 && GotoAzAlt(newAz, newAlt))
300 return true;
301
302 HorizontalCoordsNP.s = IPS_ALERT;
303 IDSetNumber(&HorizontalCoordsNP, "Altitude or Azimuth missing or invalid.");
304 return false;
305 }
306 }
307
308 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
309}
310
311bool SynscanDriver::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
312{
313 if (dev && !strcmp(dev, getDeviceName()))
314 {
315 auto svp = getSwitch(name);
316
317 if (svp.isNameMatch(GotoModeSP.name))
318 {
319 svp.update(states, names, n);
320 auto sp = svp.findOnSwitch();
321
322 assert(sp != nullptr);
323
324 if (sp->isNameMatch(GotoModeS[0].name))
325 SetAltAzMode(true);
326 else
327 SetAltAzMode(false);
328 return true;
329 }
330
331 }
332
333 return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
334}
335
336bool SynscanDriver::echo()
337{
338 char res[SYN_RES] = {0};
339 return sendCommand("Kx", res);
340}
341
342bool SynscanDriver::readFirmware()
343{
344 // Read the handset version
345 char res[SYN_RES] = {0};
346 if (sendCommand("V", res))
347 {
348 m_FirmwareVersion = static_cast<double>(hexStrToInteger(std::string(&res[0], 2)));
349 m_FirmwareVersion += static_cast<double>(hexStrToInteger(std::string(&res[2], 2))) / 100;
350 m_FirmwareVersion += static_cast<double>(hexStrToInteger(std::string(&res[4], 2))) / 10000;
351
352 LOGF_INFO("Firmware version: %lf", m_FirmwareVersion);
353 m_MountInfo[MI_FW_VERSION] = std::to_string(m_FirmwareVersion);
354 IUSaveText(&StatusT[MI_FW_VERSION], m_MountInfo[MI_FW_VERSION].c_str());
355
356 if (m_FirmwareVersion < 3.38 || (m_FirmwareVersion >= 4.0 && m_FirmwareVersion < 4.38))
357 {
358 LOGF_WARN("Firmware version is too old. Update Synscan firmware to %s",
359 m_FirmwareVersion < 3.38 ? "v3.38+" : "v4.38+");
360 return false;
361 }
362 else
363 return true;
364 }
365 else
366 LOG_WARN("Firmware version is too old. Update Synscan firmware to v4.38+");
367
368 return false;
369}
370
371bool SynscanDriver::readTracking()
372{
373 // Read the handset version
374 char res[SYN_RES] = {0};
375 if (sendCommand("t", res))
376 {
377 // Are we tracking or not?
378 m_TrackingFlag = res[0];
379
380 // Track mode?
381 if (((m_TrackingFlag - 1) != TrackModeSP.findOnSwitchIndex()) && (m_TrackingFlag))
382 {
384 TrackModeSP[m_TrackingFlag - 1].setState(ISS_ON);
386 }
387
388 switch(res[0])
389 {
390 case 0:
391 m_MountInfo[MI_TRACK_MODE] = "Tracking off";
392 break;
393 case 1:
394 m_MountInfo[MI_TRACK_MODE] = "Alt/Az tracking";
395 break;
396 case 2:
397 m_MountInfo[MI_TRACK_MODE] = "EQ tracking";
398 break;
399 case 3:
400 m_MountInfo[MI_TRACK_MODE] = "PEC mode";
401 break;
402 }
403
404 return true;
405 }
406
407 return false;
408}
409
410bool SynscanDriver::readModel()
411{
412 // extended list of mounts
413 std::map<int, std::string> models =
414 {
415 {0, "EQ6 GOTO Series"},
416 {1, "HEQ5 GOTO Series"},
417 {2, "EQ5 GOTO Series"},
418 {3, "EQ3 GOTO Series"},
419 {4, "EQ8 GOTO Series"},
420 {5, "AZ-EQ6 GOTO Series"},
421 {6, "AZ-EQ5 GOTO Series"},
422 {160, "AllView GOTO Series"},
423 {161, "Virtuoso Alt/Az mount"},
424 {165, "AZ-GTi GOTO Series"}
425 };
426
427 // Read the handset version
428 char res[SYN_RES] = {0};
429
430 if (!sendCommand("m", res))
431 return false;
432
433 m_MountModel = res[0];
434
435 // 128 - 143 --> AZ Goto series
436 if (m_MountModel >= 128 && m_MountModel <= 143)
437 IUSaveText(&StatusT[MI_MOUNT_MODEL], "AZ GOTO Series");
438 // 144 - 159 --> DOB Goto series
439 else if (m_MountModel >= 144 && m_MountModel <= 159)
440 IUSaveText(&StatusT[MI_MOUNT_MODEL], "Dob GOTO Series");
441 else if (models.count(m_MountModel) > 0)
442 IUSaveText(&StatusT[MI_MOUNT_MODEL], models[m_MountModel].c_str());
443 else
444 IUSaveText(&StatusT[MI_MOUNT_MODEL], "Unknown model");
445
446 m_isAltAz = m_MountModel > 4;
447
448 LOGF_INFO("Driver is running in %s mode.", m_isAltAz ? "Alt-Az" : "Equatorial");
449 LOGF_INFO("Detected mount: %s. Mount must be aligned from the handcontroller before using the driver.",
450 StatusT[MI_MOUNT_MODEL].text);
451
452 return true;
453}
454
456{
457 if (isSimulation())
458 {
459 mountSim();
460 return true;
461 }
462
463 char res[SYN_RES] = {0};
464
465 // Goto in progress?
466 if (sendCommand("L", res))
467 m_MountInfo[MI_GOTO_STATUS] = res[0];
468
469 // Pier side
470 if (m_isAltAz == false && sendCommand("p", res))
471 {
472 m_MountInfo[MI_POINT_STATUS] = res[0];
473 // INDI and mount pier sides are opposite to each other
474 setPierSide(res[0] == 'W' ? PIER_EAST : PIER_WEST);
475 }
476
477 if (readTracking())
478 {
480 {
481 if (isSlewComplete())
482 {
483 TrackState = (m_TrackingFlag == 2) ? SCOPE_TRACKING : SCOPE_IDLE;
484 HorizontalCoordsNP.s = (m_TrackingFlag == 2) ? IPS_OK : IPS_IDLE;
485 IDSetNumber(&HorizontalCoordsNP, nullptr);
486 }
487 }
488 else if (TrackState == SCOPE_PARKING)
489 {
490 if (isSlewComplete())
491 {
492 HorizontalCoordsNP.s = IPS_IDLE;
493 IDSetNumber(&HorizontalCoordsNP, nullptr);
495 SetTrackEnabled(false);
496 SetParked(true);
497 }
498 }
499 else if (TrackState == SCOPE_IDLE && m_TrackingFlag > 0)
501 else if (TrackState == SCOPE_TRACKING && m_TrackingFlag == 0)
503 }
504
505 sendStatus();
506
507 // Get Precise RA/DE
508 memset(res, 0, SYN_RES);
509 if (!sendCommand("e", res))
510 return false;
511
512 uint32_t n1 = 0, n2 = 0;
513 sscanf(res, "%x,%x#", &n1, &n2);
514 double ra = static_cast<double>(n1) / 0x100000000 * 360.0;
515 double de = static_cast<double>(n2) / 0x100000000 * 360.0;
516
517 INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
518 J2000Pos.rightascension = range24(ra / 15.0);
519 J2000Pos.declination = rangeDec(de);
520
521 // Synscan reports J2000 coordinates so we need to convert from J2000 to JNow
522 INDI::J2000toObserved(&J2000Pos, ln_get_julian_from_sys(), &epochPos);
523
524 CurrentRA = epochPos.rightascension;
525 CurrentDE = epochPos.declination;
526
527 char Axis1Coords[MAXINDINAME] = {0}, Axis2Coords[MAXINDINAME] = {0};
528 fs_sexa(Axis1Coords, J2000Pos.rightascension, 2, 3600);
529 fs_sexa(Axis2Coords, J2000Pos.declination, 2, 3600);
530 LOGF_DEBUG("J2000 RA <%s> DE <%s>", Axis1Coords, Axis2Coords);
531 memset(Axis1Coords, 0, MAXINDINAME);
532 memset(Axis2Coords, 0, MAXINDINAME);
533 fs_sexa(Axis1Coords, CurrentRA, 2, 3600);
534 fs_sexa(Axis2Coords, CurrentDE, 2, 3600);
535 LOGF_DEBUG("JNOW RA <%s> DE <%s>", Axis1Coords, Axis2Coords);
536
537 // Now feed the rest of the system with corrected data
538 NewRaDec(CurrentRA, CurrentDE);
539
540 // Get precise az/alt
541 memset(res, 0, SYN_RES);
542 if (!sendCommand("z", res))
543 return false;
544
545 sscanf(res, "%x,%x#", &n1, &n2);
546 double az = static_cast<double>(n1) / 0x100000000 * 360.0;
547 double al = static_cast<double>(n2) / 0x100000000 * 360.0;
548 al = rangeDec(al);
549
550 HorizontalCoordsN[AXIS_AZ].value = az;
551 HorizontalCoordsN[AXIS_ALT].value = al;
552
553 memset(Axis1Coords, 0, MAXINDINAME);
554 memset(Axis2Coords, 0, MAXINDINAME);
555 fs_sexa(Axis1Coords, az, 2, 3600);
556 fs_sexa(Axis2Coords, al, 2, 3600);
557 LOGF_DEBUG("AZ <%s> ALT <%s>", Axis1Coords, Axis2Coords);
558
559 IDSetNumber(&HorizontalCoordsNP, nullptr);
560
561 return true;
562}
563
565{
566 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
567
568 if (isSimulation())
569 return true;
570
571 cmd[0] = 'T';
572 cmd[1] = enabled ? (TrackModeSP.findOnSwitchIndex() + 1) : 0;
573 return sendCommand(cmd, res, 2);
574}
575
577{
578 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
579
580 if (isSimulation())
581 return true;
582
583 cmd[0] = 'T';
584 cmd[1] = mode + 1;
585 return sendCommand(cmd, res);
586}
587
588bool SynscanDriver::SetAltAzMode(bool enable)
589{
590 IUResetSwitch(&GotoModeSP);
591
595
596 if (enable)
597 {
598 ISwitch *sp = IUFindSwitch(&GotoModeSP, "ALTAZ");
599 if (sp)
600 {
601 LOG_INFO("Using AltAz goto.");
602 sp->s = ISS_ON;
603 }
604 goto_AltAz = true;
605 }
606 else
607 {
608 ISwitch *sp = IUFindSwitch(&GotoModeSP, "RADEC");
609 if (sp)
610 {
611 sp->s = ISS_ON;
612 LOG_INFO("Using Ra/Dec goto.");
613 }
614 goto_AltAz = false;
615 }
616
617 GotoModeSP.s = IPS_OK;
618 IDSetSwitch(&GotoModeSP, nullptr);
619 return true;
620}
621
622bool SynscanDriver::Goto(double ra, double dec)
623{
624 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
625
626 TargetRA = ra;
627 TargetDE = dec;
628
629 if (isSimulation())
630 return true;
631
632 // INDI is JNow. Synscan Controll uses J2000 Epoch
633 INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
634
635 epochPos.rightascension = ra;
636 epochPos.declination = dec;
637
638 // For Alt/Az mounts, we must issue Goto Alt/Az
639 if (goto_AltAz && m_isAltAz) // only if enabled to use AltAz goto
640 {
642 INDI::EquatorialToHorizontal(&epochPos, &m_Location, ln_get_julian_from_sys(), &altaz);
643 /* libnova measures azimuth from south towards west */
644 double az = altaz.azimuth;
645 double al = altaz.altitude;
646
647 return GotoAzAlt(az, al);
648 }
649
650 // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
651 INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
652
653 double dec_pos = J2000Pos.declination;
654 if (J2000Pos.declination < 0)
655 dec_pos = dec_pos + 360;
656 // Mount deals in J2000 coords.
657 uint32_t n1 = J2000Pos.rightascension * 15.0 / 360 * 0x100000000;
658 uint32_t n2 = dec_pos / 360 * 0x100000000;
659
660 LOGF_DEBUG("Goto - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
661 J2000Pos.declination);
662
663 snprintf(cmd, SYN_RES, "r%08X,%08X", n1, n2);
664 if (sendCommand(cmd, res, 18))
665 {
667 HorizontalCoordsNP.s = IPS_BUSY;
668 IDSetNumber(&HorizontalCoordsNP, nullptr);
669 return true;
670 }
671
672 return false;
673}
674
675bool SynscanDriver::GotoAzAlt(double az, double alt)
676{
677 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
678
679 if (isSimulation())
680 return true;
681
682 if (m_isAltAz == false)
683 {
684 // For EQ Mount, we convert Parking Az/Alt to RA/DE and go to there.
685 INDI::IHorizontalCoordinates horizontalPos;
686 INDI::IEquatorialCoordinates equatorialPos;
687 horizontalPos.azimuth = az;
688 horizontalPos.altitude = alt;
689 INDI::HorizontalToEquatorial(&horizontalPos, &m_Location, ln_get_julian_from_sys(), &equatorialPos);
690 return Goto(equatorialPos.rightascension, equatorialPos.declination);
691 }
692
693 // Az/Alt to encoders
694 uint32_t n1 = az / 360.0 * 0x100000000;
695 uint32_t n2 = alt / 360.0 * 0x100000000;
696
697 LOGF_DEBUG("Goto - Az: %.2f Alt: %.2f", az, alt);
698
699 snprintf(cmd, SYN_RES, "b%08X,%08X", n1, n2);
700 if (sendCommand(cmd, res, 18))
701 {
703 HorizontalCoordsNP.s = IPS_BUSY;
704 IDSetNumber(&HorizontalCoordsNP, nullptr);
705 return true;
706 }
707
708 return false;
709}
710
712{
713 double parkAZ = GetAxis1Park();
714 double parkAlt = GetAxis2Park();
715
716 char AzStr[16], AltStr[16];
717 fs_sexa(AzStr, parkAZ, 2, 3600);
718 fs_sexa(AltStr, parkAlt, 2, 3600);
719 LOGF_DEBUG("Parking to Az (%s) Alt (%s)...", AzStr, AltStr);
720
721 if (GotoAzAlt(parkAZ, parkAlt))
722 {
724 LOG_INFO("Parking is in progress...");
725 return true;
726 }
727
728 return false;
729}
730
732{
733 SetParked(false);
734 SetTrackMode(m_isAltAz ? 1 : 2);
735 SetTrackEnabled(true);
736 return true;
737}
738
740{
741 char res[SYN_RES] = {0};
742
743 // Get Current Az/Alt
744 memset(res, 0, SYN_RES);
745 if (!sendCommand("z", res))
746 return false;
747
748 uint32_t n1 = 0, n2 = 0;
749 sscanf(res, "%ux,%ux#", &n1, &n2);
750 double az = static_cast<double>(n1) / 0x100000000 * 360.0;
751 double al = static_cast<double>(n2) / 0x100000000 * 360.0;
752 al = rangeDec(al);
753
754 char AzStr[16], AltStr[16];
755 fs_sexa(AzStr, az, 2, 3600);
756 fs_sexa(AltStr, al, 2, 3600);
757
758 LOGF_DEBUG("Setting current parking position to coordinates Az (%s) Alt (%s)...", AzStr, AltStr);
759
760 SetAxis1Park(az);
761 SetAxis2Park(al);
762
763 return true;
764}
765
767{
768 // By default az to north, and alt to pole
769 LOG_DEBUG("Setting Park Data to Default.");
770 SetAxis1Park(359);
772
773 return true;
774}
775
777{
778 if (TrackState == SCOPE_IDLE)
779 return true;
780
781 LOG_DEBUG("Abort mount...");
783
784 if (isSimulation())
785 return true;
786
787 SetTrackEnabled(false);
788 sendCommand("M");
789 sendCommand("M");
790 return true;
791}
792
794{
795 if (isSimulation())
796 return true;
797
798 bool rc = false;
799 SynscanDirection move;
800
802 move = (dir == DIRECTION_NORTH) ? SYN_N : SYN_S;
803 else
804 move = (dir == DIRECTION_NORTH) ? SYN_S : SYN_N;
805
806 uint8_t rate = static_cast<uint8_t>(SlewRateSP.findOnSwitchIndex()) + 1;
807 double customRate = CustomSlewRateN[AXIS_DE].value;
808
809 // If we have pulse guiding
810 if (m_CustomGuideDE > 0)
811 {
812 rate = 10;
813 customRate = m_CustomGuideDE;
814 }
815
816 switch (command)
817 {
818 case MOTION_START:
819 rc = (rate < 10) ? slewFixedRate(move, rate) : slewVariableRate(move, customRate);
820 if (!rc)
821 {
822 LOG_ERROR("Error setting N/S motion direction.");
823 return false;
824 }
825 // Only report messages if we are not guiding
826 else if (!m_CustomGuideDE)
827 LOGF_INFO("Moving toward %s.", (move == SYN_N) ? "North" : "South");
828 break;
829
830 case MOTION_STOP:
831 if (slewFixedRate(move, 0) == false)
832 {
833 LOG_ERROR("Error stopping N/S motion.");
834 return false;
835 }
836 else if (!m_CustomGuideDE)
837 LOGF_INFO("Movement toward %s halted.", (move == SYN_N) ? "North" : "South");
838 break;
839 }
840
841 return true;
842}
843
845{
846 if (isSimulation())
847 return true;
848
849 bool rc = false;
850 SynscanDirection move = (dir == DIRECTION_WEST) ? SYN_W : SYN_E;
851 uint8_t rate = static_cast<uint8_t>(SlewRateSP.findOnSwitchIndex()) + 1;
852 double customRate = CustomSlewRateN[AXIS_RA].value;
853
854 // If we have pulse guiding
855 if (m_CustomGuideRA > 0)
856 {
857 rate = 10;
858 customRate = m_CustomGuideRA;
859 }
860
861 switch (command)
862 {
863 case MOTION_START:
864 rc = (rate < 10) ? slewFixedRate(move, rate) : slewVariableRate(move, customRate);
865 if (!rc)
866 {
867 LOG_ERROR("Error setting W/E motion direction.");
868 return false;
869 }
870 // Only report messages if we are not guiding
871 else if (!m_CustomGuideRA)
872 LOGF_INFO("Moving toward %s.", (move == SYN_W) ? "West" : "East");
873 break;
874
875 case MOTION_STOP:
876 if (slewFixedRate(move, 0) == false)
877 {
878 LOG_ERROR("Error stopping W/E motion.");
879 return false;
880 }
881 else if (!m_CustomGuideRA)
882 LOGF_INFO("Movement toward %s halted.", (move == SYN_W) ? "West" : "East");
883 break;
884 }
885
886 return true;
887}
888
890{
891 m_TargetSlewRate = s + 1;
892 return true;
893}
894
895#if 0
896bool SynscanDriver::passThruCommand(int cmd, int target, int msgsize, int data, int numReturn)
897{
898 char test[20] = {0};
899 int bytesRead, bytesWritten;
900 char a, b, c;
901 int tt = data;
902
903 a = tt % 256;
904 tt = tt >> 8;
905 b = tt % 256;
906 tt = tt >> 8;
907 c = tt % 256;
908
909 // format up a passthru command
910 memset(test, 0, 20);
911 test[0] = 80; // passhtru
912 test[1] = msgsize; // set message size
913 test[2] = target; // set the target
914 test[3] = cmd; // set the command
915 test[4] = c; // set data bytes
916 test[5] = b;
917 test[6] = a;
918 test[7] = numReturn;
919
920 LOGF_DEBUG("CMD <%s>", test);
921 tty_write(PortFD, test, 8, &bytesWritten);
922 memset(test, 0, 20);
923 tty_read(PortFD, test, numReturn + 1, 2, &bytesRead);
924 LOGF_DEBUG("RES <%s>", test);
925 if (numReturn > 0)
926 {
927 int retval = 0;
928 retval = test[0];
929 if (numReturn > 1)
930 {
931 retval = retval << 8;
932 retval += test[1];
933 }
934 if (numReturn > 2)
935 {
936 retval = retval << 8;
937 retval += test[2];
938 }
939 return retval;
940 }
941
942 return 0;
943}
944#endif
945
946bool SynscanDriver::sendTime()
947{
948 LOG_DEBUG("Reading mount time...");
949
950 if (isSimulation())
951 {
952 char timeString[MAXINDINAME] = {0};
953 time_t now = time (nullptr);
954 strftime(timeString, MAXINDINAME, "%T", gmtime(&now));
955 TimeTP[UTC].setText("3");
956 TimeTP[OFFSET].setText(timeString);
958 TimeTP.apply();
959 return true;
960 }
961
962 char res[SYN_RES] = {0};
963 if (sendCommand("h", res))
964 {
965 ln_zonedate localTime;
966 ln_date utcTime;
967 int offset, daylightflag;
968
969 localTime.hours = res[0];
970 localTime.minutes = res[1];
971 localTime.seconds = res[2];
972 localTime.months = res[3];
973 localTime.days = res[4];
974 localTime.years = res[5];
975 offset = static_cast<int>(res[6]);
976 // Negative GMT offset is read. It needs special treatment
977 if (offset > 200)
978 offset -= 256;
979 localTime.gmtoff = offset;
980 // this is the daylight savings flag in the hand controller, needed if we did not set the time
981 daylightflag = res[7];
982 localTime.years += 2000;
983 localTime.gmtoff *= 3600;
984 // now convert to utc
985 ln_zonedate_to_date(&localTime, &utcTime);
986
987 // now we have time from the hand controller, we need to set some variables
988 int sec;
989 char utc[100];
990 char ofs[16] = {0};
991 sec = static_cast<int>(utcTime.seconds);
992 sprintf(utc, "%04d-%02d-%dT%d:%02d:%02d", utcTime.years, utcTime.months, utcTime.days, utcTime.hours,
993 utcTime.minutes, sec);
994 if (daylightflag == 1)
995 offset = offset + 1;
996 snprintf(ofs, 16, "%d", offset);
997
998 TimeTP[UTC].setText(utc);
999 TimeTP[OFFSET].setText(ofs);
1001 TimeTP.apply();
1002
1003 LOGF_INFO("Mount UTC Time %s Offset %d", utc, offset);
1004
1005 return true;
1006 }
1007 return false;
1008}
1009
1010bool SynscanDriver::sendLocation()
1011{
1012 char res[SYN_RES] = {0};
1013
1014 LOG_DEBUG("Reading mount location...");
1015
1016 if (isSimulation())
1017 {
1018 LocationNP[LOCATION_LATITUDE].setValue(29.5);
1019 LocationNP[LOCATION_LONGITUDE].setValue(48);
1020 LocationNP.apply();
1021 return true;
1022 }
1023
1024 if (!sendCommand("w", res))
1025 return false;
1026
1027 double lat, lon;
1028 // lets parse this data now
1029 int a, b, c, d, e, f, g, h;
1030 a = res[0];
1031 b = res[1];
1032 c = res[2];
1033 d = res[3];
1034 e = res[4];
1035 f = res[5];
1036 g = res[6];
1037 h = res[7];
1038
1039 double t1, t2, t3;
1040
1041 t1 = c;
1042 t2 = b;
1043 t3 = a;
1044 t1 = t1 / 3600.0;
1045 t2 = t2 / 60.0;
1046 lat = t1 + t2 + t3;
1047
1048 t1 = g;
1049 t2 = f;
1050 t3 = e;
1051 t1 = t1 / 3600.0;
1052 t2 = t2 / 60.0;
1053 lon = t1 + t2 + t3;
1054
1055 if (d == 1)
1056 lat = lat * -1;
1057 if (h == 1)
1058 lon = 360 - lon;
1059 LocationNP[LOCATION_LATITUDE].setValue(lat);
1060 LocationNP[LOCATION_LONGITUDE].setValue(lon);
1061 LocationNP.apply();
1062
1063 saveConfig(true, "GEOGRAPHIC_COORD");
1064
1065 char LongitudeStr[32] = {0}, LatitudeStr[32] = {0};
1066 fs_sexa(LongitudeStr, lon, 2, 3600);
1067 fs_sexa(LatitudeStr, lat, 2, 3600);
1068 LOGF_INFO("Mount Longitude %s Latitude %s", LongitudeStr, LatitudeStr);
1069
1070 return true;
1071}
1072
1073bool SynscanDriver::updateTime(ln_date * utc, double utc_offset)
1074{
1075 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1076
1077 // start by formatting a time for the hand controller
1078 // we are going to set controller to local time
1079 struct ln_zonedate ltm;
1080
1081 ln_date_to_zonedate(utc, &ltm, utc_offset * 3600.0);
1082
1083 int yr = ltm.years;
1084
1085 yr = yr % 100;
1086
1087 cmd[0] = 'H';
1088 cmd[1] = ltm.hours;
1089 cmd[2] = ltm.minutes;
1090 cmd[3] = static_cast<char>(ltm.seconds);
1091 cmd[4] = ltm.months;
1092 cmd[5] = ltm.days;
1093 cmd[6] = yr;
1094 // offset from utc so hand controller is running in local time
1095 cmd[7] = utc_offset > 0 ? static_cast<uint8_t>(utc_offset) : static_cast<uint8_t>(256 + utc_offset);
1096 // and no daylight savings adjustments, it's already included in the offset
1097 cmd[8] = 0;
1098
1099 LOGF_INFO("Setting mount date/time to %04d-%02d-%02d %d:%02d:%02d UTC Offset: %.2f",
1100 ltm.years, ltm.months, ltm.days, ltm.hours, ltm.minutes, static_cast<int>(rint(ltm.seconds)), utc_offset);
1101
1102 if (isSimulation())
1103 return true;
1104
1105 return sendCommand(cmd, res, 9);
1106}
1107
1108bool SynscanDriver::updateLocation(double latitude, double longitude, double elevation)
1109{
1110 INDI_UNUSED(elevation);
1111 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1112 bool IsWest = false;
1113
1114 ln_lnlat_posn p1 { 0, 0 };
1115 lnh_lnlat_posn p2;
1116
1117 LocationNP[LOCATION_LATITUDE].setValue(latitude);
1118 LocationNP[LOCATION_LONGITUDE].setValue(longitude);
1119 LocationNP.apply();
1120
1121 if (isSimulation())
1122 {
1123 if (!CurrentDE)
1124 {
1125 CurrentDE = latitude > 0 ? 90 : -90;
1126 CurrentRA = get_local_sidereal_time(longitude);
1127 }
1128 return true;
1129 }
1130
1131 if (longitude > 180)
1132 {
1133 p1.lng = 360.0 - longitude;
1134 IsWest = true;
1135 }
1136 else
1137 {
1138 p1.lng = longitude;
1139 }
1140 p1.lat = latitude;
1141 ln_lnlat_to_hlnlat(&p1, &p2);
1142 LOGF_INFO("Update location to latitude %d:%d:%1.2f longitude %d:%d:%1.2f",
1143 p2.lat.degrees, p2.lat.minutes, p2.lat.seconds, p2.lng.degrees, p2.lng.minutes, p2.lng.seconds);
1144
1145 cmd[0] = 'W';
1146 cmd[1] = p2.lat.degrees;
1147 cmd[2] = p2.lat.minutes;
1148 cmd[3] = rint(p2.lat.seconds);
1149 cmd[4] = (p2.lat.neg == 0) ? 0 : 1;
1150 cmd[5] = p2.lng.degrees;
1151 cmd[6] = p2.lng.minutes;
1152 cmd[7] = rint(p2.lng.seconds);
1153 cmd[8] = IsWest ? 1 : 0;
1154
1155 return sendCommand(cmd, res, 9);
1156}
1157
1158bool SynscanDriver::Sync(double ra, double dec)
1159{
1160 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1161
1162 TargetRA = ra;
1163 TargetDE = dec;
1164
1165 if (isSimulation())
1166 return true;
1167
1168 // INDI is JNow. Synscan Controll uses J2000 Epoch
1169 INDI::IEquatorialCoordinates epochPos { 0, 0 }, J2000Pos { 0, 0 };
1170 epochPos.rightascension = ra;
1171 epochPos.declination = dec;
1172
1173 // Synscan accepts J2000 coordinates so we need to convert from JNow to J2000
1174 INDI::ObservedToJ2000(&epochPos, ln_get_julian_from_sys(), &J2000Pos);
1175
1176 // Mount deals in J2000 coords.
1177 uint32_t n1 = J2000Pos.rightascension * 15.0 / 360 * 0x100000000;
1178 uint32_t n2 = J2000Pos.declination / 360 * 0x100000000;
1179
1180 LOGF_DEBUG("Sync - JNow RA: %g JNow DE: %g J2000 RA: %g J2000 DE: %g", ra, dec, J2000Pos.rightascension,
1181 J2000Pos.declination);
1182
1183 snprintf(cmd, SYN_RES, "s%08X,%08X", n1, n2);
1184 return sendCommand(cmd, res, 18);
1185}
1186
1187void SynscanDriver::sendStatus()
1188{
1189 bool BasicMountInfoHasChanged = false;
1190
1191 if (std::string(StatusT[MI_GOTO_STATUS].text) != m_MountInfo[MI_GOTO_STATUS])
1192 {
1193 IUSaveText(&StatusT[MI_GOTO_STATUS], m_MountInfo[MI_GOTO_STATUS].c_str());
1194 BasicMountInfoHasChanged = true;
1195 }
1196 if (std::string(StatusT[MI_POINT_STATUS].text) != m_MountInfo[MI_POINT_STATUS])
1197 {
1198 IUSaveText(&StatusT[MI_POINT_STATUS], m_MountInfo[MI_POINT_STATUS].c_str());
1199 BasicMountInfoHasChanged = true;
1200 }
1201 if (std::string(StatusT[MI_TRACK_MODE].text) != m_MountInfo[MI_TRACK_MODE])
1202 {
1203 IUSaveText(&StatusT[MI_TRACK_MODE], m_MountInfo[MI_TRACK_MODE].c_str());
1204 BasicMountInfoHasChanged = true;
1205 }
1206
1207 if (BasicMountInfoHasChanged)
1208 {
1209 StatusTP.s = IPS_OK;
1210 IDSetText(&StatusTP, nullptr);
1211 }
1212}
1213
1214bool SynscanDriver::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
1215{
1216 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1217
1218 tcflush(PortFD, TCIOFLUSH);
1219
1220 if (cmd_len > 0)
1221 {
1222 char hex_cmd[SYN_RES * 3] = {0};
1223 hexDump(hex_cmd, cmd, cmd_len);
1224 LOGF_DEBUG("CMD <%s>", hex_cmd);
1225 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
1226 }
1227 else
1228 {
1229 LOGF_DEBUG("CMD <%s>", cmd);
1230 rc = tty_write_string(PortFD, cmd, &nbytes_written);
1231 }
1232
1233 if (rc != TTY_OK)
1234 {
1235 char errstr[MAXRBUF] = {0};
1236 tty_error_msg(rc, errstr, MAXRBUF);
1237 LOGF_ERROR("Serial write error: %s.", errstr);
1238 return false;
1239 }
1240
1241 if (res == nullptr)
1242 return true;
1243
1244 if (res_len > 0)
1245 rc = tty_read(PortFD, res, res_len, SYN_TIMEOUT, &nbytes_read);
1246 else
1247 rc = tty_nread_section(PortFD, res, SYN_RES, SYN_DEL, SYN_TIMEOUT, &nbytes_read);
1248
1249 if (rc != TTY_OK)
1250 {
1251 char errstr[MAXRBUF] = {0};
1252 tty_error_msg(rc, errstr, MAXRBUF);
1253 LOGF_ERROR("Serial read error: %s.", errstr);
1254 return false;
1255 }
1256
1257 if (res_len > 0)
1258 {
1259 char hex_res[SYN_RES * 3] = {0};
1260 hexDump(hex_res, res, res_len);
1261 LOGF_DEBUG("RES <%s>", hex_res);
1262 }
1263 else
1264 {
1265 LOGF_DEBUG("RES <%s>", res);
1266 }
1267
1268 tcflush(PortFD, TCIOFLUSH);
1269
1270 return true;
1271}
1272
1273void SynscanDriver::hexDump(char * buf, const char * data, int size)
1274{
1275 for (int i = 0; i < size; i++)
1276 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1277
1278 if (size > 0)
1279 buf[3 * size - 1] = '\0';
1280}
1281
1282void SynscanDriver::mountSim()
1283{
1284 static struct timeval ltv;
1285 struct timeval tv;
1286 double dt, da, dx;
1287 int nlocked;
1288
1289 /* update elapsed time since last poll, don't presume exactly POLLMS */
1290 gettimeofday(&tv, nullptr);
1291
1292 if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1293 ltv = tv;
1294
1295 dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1296 ltv = tv;
1297 double currentSlewRate = SIM_SLEW_RATE[SlewRateSP.findOnSwitchIndex()] * TRACKRATE_SIDEREAL / 3600.0;
1298 da = currentSlewRate * dt;
1299
1300 /* Process per current state. We check the state of EQUATORIAL_COORDS and act accordingly */
1301 switch (TrackState)
1302 {
1303 case SCOPE_IDLE:
1304 CurrentRA += (TrackRateNP[AXIS_RA].getValue() / 3600.0 * dt) / 15.0;
1305 CurrentRA = range24(CurrentRA);
1306 break;
1307
1308 case SCOPE_TRACKING:
1309 break;
1310
1311 case SCOPE_SLEWING:
1312 case SCOPE_PARKING:
1313 /* slewing - nail it when both within one pulse @ SLEWRATE */
1314 nlocked = 0;
1315
1316 dx = TargetRA - CurrentRA;
1317
1318 // Take shortest path
1319 if (fabs(dx) > 12)
1320 dx *= -1;
1321
1322 if (fabs(dx) <= da)
1323 {
1324 CurrentRA = TargetRA;
1325 nlocked++;
1326 }
1327 else if (dx > 0)
1328 CurrentRA += da / 15.;
1329 else
1330 CurrentRA -= da / 15.;
1331
1332 if (CurrentRA < 0)
1333 CurrentRA += 24;
1334 else if (CurrentRA > 24)
1335 CurrentRA -= 24;
1336
1337 dx = TargetDE - CurrentDE;
1338 if (fabs(dx) <= da)
1339 {
1340 CurrentDE = TargetDE;
1341 nlocked++;
1342 }
1343 else if (dx > 0)
1344 CurrentDE += da;
1345 else
1346 CurrentDE -= da;
1347
1348 if (nlocked == 2)
1349 {
1352 else
1354 }
1355
1356 break;
1357
1358 default:
1359 break;
1360 }
1361
1362 NewRaDec(CurrentRA, CurrentDE);
1363}
1364
1365bool SynscanDriver::slewFixedRate(SynscanDirection direction, uint8_t rate)
1366{
1367 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1368
1369 cmd[0] = 'P';
1370 cmd[1] = 2;
1371 // Axis 17 for DE/AL, 16 for RA/AZ
1372 cmd[2] = (direction == SYN_N || direction == SYN_S) ? 17 : 16;
1373 // Command 36 positive direction, 37 negative direction
1374 if (!m_isAltAz)
1375 cmd[3] = (direction == SYN_N || direction == SYN_W) ? 36 : 37;
1376 else
1377 cmd[3] = (direction == SYN_N || direction == SYN_W) ? 37 : 36;
1378 // Fixed rate (0 to 9) where 0 is stop
1379 cmd[4] = rate;
1380
1381 return sendCommand(cmd, res, 8);
1382}
1383
1384bool SynscanDriver::slewVariableRate(SynscanDirection direction, double rate)
1385{
1386 char cmd[SYN_RES] = {0}, res[SYN_RES] = {0};
1387
1388 // According to Synscan documentation. We need to multiply by 4
1389 // then separate into high and low bytes
1390 uint16_t synRate = rint(rate * 4);
1391
1392 cmd[0] = 'P';
1393 cmd[1] = 3;
1394 // Axis 17 for DE/AL, 16 for RA/AZ
1395 cmd[2] = (direction == SYN_N || direction == SYN_S) ? 17 : 16;
1396 // Command 6 positive direction, 7 negative direction
1397 cmd[3] = (direction == SYN_N || direction == SYN_W) ? 6 : 7;
1398 // High byte
1399 cmd[4] = synRate >> 8;
1400 // Low byte
1401 cmd[5] = synRate & 0xFF;
1402
1403 return sendCommand(cmd, res, 8);
1404}
1405
1407{
1408 if (m_GuideNSTID)
1409 {
1410 IERmTimer(m_GuideNSTID);
1411 m_GuideNSTID = 0;
1412 }
1413
1414 m_CustomGuideDE = TRACKRATE_SIDEREAL + GuideRateN[AXIS_DE].value * TRACKRATE_SIDEREAL;
1416 m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperNS, this);
1417 return IPS_BUSY;
1418}
1419
1421{
1422 if (m_GuideNSTID)
1423 {
1424 IERmTimer(m_GuideNSTID);
1425 m_GuideNSTID = 0;
1426 }
1427
1428 m_CustomGuideDE = TRACKRATE_SIDEREAL + GuideRateN[AXIS_DE].value * TRACKRATE_SIDEREAL;
1430 m_GuideNSTID = IEAddTimer(ms, guideTimeoutHelperNS, this);
1431 return IPS_BUSY;
1432}
1433
1435{
1436 if (m_GuideWETID)
1437 {
1438 IERmTimer(m_GuideWETID);
1439 m_GuideWETID = 0;
1440 }
1441
1442 // So if we SID_RATE + 0.5 * SID_RATE for example, that's 150% of sidereal rate
1443 // but for east we'd be going a lot faster since the stars are moving toward the west
1444 // in sidereal rate. Just standing still we would SID_RATE moving across. So for east
1445 // we just go GuideRate * SID_RATE without adding any more values.
1446 //m_CustomGuideRA = TRACKRATE_SIDEREAL + GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1447 m_CustomGuideRA = GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1448
1450 m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperWE, this);
1451 return IPS_BUSY;
1452}
1453
1455{
1456 if (m_GuideWETID)
1457 {
1458 IERmTimer(m_GuideWETID);
1459 m_GuideWETID = 0;
1460 }
1461
1462 // Sky already going westward (or earth rotating eastward, pick your favorite)
1463 // So we go SID_RATE + whatever guide rate was set to.
1464 m_CustomGuideRA = TRACKRATE_SIDEREAL + GuideRateN[AXIS_RA].value * TRACKRATE_SIDEREAL;
1466 m_GuideWETID = IEAddTimer(ms, guideTimeoutHelperWE, this);
1467 return IPS_BUSY;
1468}
1469
1471{
1472 static_cast<SynscanDriver *>(context)->guideTimeoutCallbackNS();
1473}
1474
1476{
1477 static_cast<SynscanDriver *>(context)->guideTimeoutCallbackWE();
1478}
1479
1480void SynscanDriver::guideTimeoutCallbackNS()
1481{
1482 INDI_DIR_NS direction = static_cast<INDI_DIR_NS>(MovementNSSP.findOnSwitchIndex());
1483 MoveNS(direction, MOTION_STOP);
1485 m_CustomGuideDE = m_GuideNSTID = 0;
1486}
1487
1488void SynscanDriver::guideTimeoutCallbackWE()
1489{
1490 INDI_DIR_WE direction = static_cast<INDI_DIR_WE>(MovementWESP.findOnSwitchIndex());
1491 MoveWE(direction, MOTION_STOP);
1493 m_CustomGuideRA = m_GuideWETID = 0;
1494}
1495
1496bool SynscanDriver::isSlewComplete()
1497{
1498 char res[SYN_RES] = {0};
1499
1500 if (!sendCommand("L", res))
1501 return false;
1502
1503 return res[0] == '0';
1504}
bool isConnected() const
INDI::PropertySwitch getSwitch(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
uint32_t getDriverInterface() const
virtual void GuideComplete(INDI_EQ_AXIS axis)
Call GuideComplete once the guiding pulse is complete.
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
void initProperties(const char *groupName)
Initialize guider properties. It is recommended to call this function within initProperties() of your...
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void setLabel(const char *label)
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
double GetAxis1Park() const
double GetAxis2Park() const
bool isParked()
isParked is mount currently parked?
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
IGeographicCoordinates m_Location
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
void setPierSide(TelescopePierSide side)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool Park() override
Park the telescope to its home position.
static void guideTimeoutHelperWE(void *context)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
static void guideTimeoutHelperNS(void *context)
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual const char * getDefaultName() override
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool Handshake() override
perform handshake with device to check communication
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual bool GotoAzAlt(double az, double alt)
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition eventloop.c:606
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition eventloop.c:586
TelescopePierSide currentPierSide
INDI::PropertyNumber TrackRateNP
INDI::PropertySwitch MovementNSSP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch TrackModeSP
INDI::PropertyText TimeTP
INDI::PropertySwitch MountTypeSP
INDI::PropertySwitch MovementWESP
INDI::PropertyNumber LocationNP
double ra
double dec
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
#define NARRAY(a)
Handy macro to find the number of elements in array a[]. Must be used with actual array,...
Definition indiapi.h:498
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
#define MAXINDINAME
Definition indiapi.h:189
@ AXIS_DE
@ AXIS_RA
@ AXIS_AZ
@ AXIS_ALT
INDI_DIR_WE
@ DIRECTION_EAST
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_SOUTH
@ DIRECTION_NORTH
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition indicom.c:1311
double rangeDec(double decdegrees)
rangeDec Limits declination value to be in -90 to 90 range.
Definition indicom.c:1341
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:490
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition indidevapi.c:366
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:509
INumber * IUFindNumber(const INumberVectorProperty *nvp, const char *name)
Find an INumber member in a number text property.
Definition indidevapi.c:275
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
ISwitch * IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
Find an ISwitch member in a vector switch property.
Definition indidevapi.c:285
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition indidevapi.c:376
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition indidevapi.c:416
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition indidevapi.c:398
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition indidevapi.c:453
#define INDI_UNUSED(x)
Definition indidevapi.h:132
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define TRACKRATE_SIDEREAL
Definition indimacros.h:39
#define getDeviceName
void J2000toObserved(IEquatorialCoordinates *J2000pos, double jd, IEquatorialCoordinates *observed)
*J2000toObserved converts catalogue to observed
Definition libastro.cpp:81
void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, IHorizontalCoordinates *position)
EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates.
Definition libastro.cpp:141
void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, IEquatorialCoordinates *position)
HorizontalToEquatorial Calculate Equatorial EOD Coordinates from horizontal coordinates.
Definition libastro.cpp:161
void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos)
ObservedToJ2000 converts an observed position to a J2000 catalogue position removes aberration,...
Definition libastro.cpp:51
uint8_t cmd[4]
Definition pwc-ioctl.h:2
One number descriptor.
One switch descriptor.
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248