Instrument Neutral Distributed Interface INDI 2.2.5
paramount.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3 2021 Chris Lewicki. Refactor of TCP connections and error handling
4
5 Driver for using TheSkyX Pro Scripted operations for mounts via the TCP server.
6 While this technically can operate any mount connected to the TheSkyX Pro, it is
7 intended for Paramount mounts control.
8
9 Ref TheSky Functions:
10 https://www.bisque.com/wp-content/scripttheskyx/classsky6_r_a_s_c_o_m_tele.html
11
12 This library is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Library General Public
14 License version 2 as published by the Free Software Foundation.
15 .
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
20 .
21 You should have received a copy of the GNU Library General Public License
22 along with this library; see the file COPYING.LIB. If not, write to
23 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 Boston, MA 02110-1301, USA.
25*******************************************************************************/
26
27/*******************************************************************************
28 * TODO for improving / completing the driver
29 *
30 * 1. SlewToAZAlt()
31 * 2. GetAzAlt()
32 * 3. (3) Presets for SlewToAZAlt positions
33 * 4. DoCommand(16) get/set atmospheric pressure for interfacing with other INDI devices
34*******************************************************************************/
35
36#include "paramount.h"
37
38#include "indicom.h"
39
40#include <libnova/sidereal_time.h>
41#include <libnova/transform.h>
42
43#include <cmath>
44#include <cstring>
45#include <memory>
46#include <termios.h>
47
48// We declare an auto pointer to Paramount.
49std::unique_ptr<Paramount> paramount_mount(new Paramount());
50
51#define GOTO_RATE 5 /* slew rate, degrees/s */
52#define SLEW_RATE 0.5 /* slew rate, degrees/s */
53#define FINE_SLEW_RATE 0.1 /* slew rate, degrees/s */
54
55#define GOTO_LIMIT 5.5 /* Move at GOTO_RATE until distance from target is GOTO_LIMIT degrees */
56#define SLEW_LIMIT 1 /* Move at SLEW_LIMIT until distance from target is SLEW_LIMIT degrees */
57
58#define PARAMOUNT_TIMEOUT 3 /* Timeout in seconds */
59#define PARAMOUNT_NORTH 0
60#define PARAMOUNT_SOUTH 1
61#define PARAMOUNT_EAST 2
62#define PARAMOUNT_WEST 3
63
64#define RA_AXIS 0
65#define DEC_AXIS 1
66
67/* Preset Slew Speeds */
68#define SLEWMODES 9
69const double slewspeeds[SLEWMODES] = { 1.0, 2.0, 4.0, 8.0, 32.0, 64.0, 128.0, 256.0, 512.0 };
70
72{
73 setVersion(1, 5);
74
75 DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE");
76
80 9
81 );
83
84 m_NSTimer.setSingleShot(true);
85 m_WETimer.setSingleShot(true);
86
87 // Called when timer is up
88 m_NSTimer.callOnTimeout([this]()
89 {
91 GuideNSNP[0].setValue(0);
92 GuideNSNP[1].setValue(0);
94 });
95
96 m_WETimer.callOnTimeout([this]()
97 {
99 GuideWENP[0].setValue(0);
100 GuideWENP[1].setValue(0);
102 });
103}
104
106{
107 return "Paramount";
108}
109
111{
112 /* Make sure to init parent properties first */
114
115 for (size_t i = 0; i < SlewRateSP.count() - 1; i++)
116 {
117 SlewRateSP[i].setLabel(std::to_string(slewspeeds[i]) + "x");
118
119 SlewRateSP[i].setAux((void *)&slewspeeds[i]);
120 }
121
122 // Set 64x as default speed
124
125 /* How fast do we guide compared to sidereal rate */
126 IUFillNumber(&JogRateN[RA_AXIS], "JOG_RATE_WE", "W/E Rate (arcmin)", "%g", 0, 600, 60, 30);
127 IUFillNumber(&JogRateN[DEC_AXIS], "JOG_RATE_NS", "N/S Rate (arcmin)", "%g", 0, 600, 60, 30);
128 IUFillNumberVector(&JogRateNP, JogRateN, 2, getDeviceName(), "JOG_RATE", "Jog Rate", MOTION_TAB, IP_RW, 0,
129 IPS_IDLE);
130
131 /* How fast do we guide compared to sidereal rate */
132 IUFillNumber(&GuideRateN[RA_AXIS], "GUIDE_RATE_WE", "W/E Rate", "%1.1f", 0.0, 1.0, 0.1, 0.5);
133 IUFillNumber(&GuideRateN[DEC_AXIS], "GUIDE_RATE_NS", "N/S Rate", "%1.1f", 0.0, 1.0, 0.1, 0.5);
134 IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RW, 0,
135 IPS_IDLE);
136
137 // Homing
138 // IUFillSwitch(&HomeS[0], "GO", "Go", ISS_OFF);
139 // IUFillSwitchVector(&HomeSP, HomeS, 1, getDeviceName(), "TELESCOPE_HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60,
140 // IPS_IDLE);
141
142 // Tracking Mode
143 AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
144 AddTrackMode("TRACK_SOLAR", "Solar");
145 AddTrackMode("TRACK_LUNAR", "Lunar");
146 AddTrackMode("TRACK_CUSTOM", "Custom");
147
149
151
153
155
157
159 currentDEC = LocationNP[LOCATION_LATITUDE].getValue() > 0 ? 90 : -90;
160 return true;
161}
162
164{
166
167 if (isConnected())
168 {
169 if (isTheSkyTracking())
170 {
171 IUResetSwitch(&TrackModeSP);
172 TrackModeS[TRACK_SIDEREAL].s = ISS_ON;
174 }
175 else
176 {
177 IUResetSwitch(&TrackModeSP);
179 }
180
181 //defineProperty(&TrackModeSP);
182 //defineProperty(&TrackRateNP);
183
184 defineProperty(&JogRateNP);
185
186 defineProperty(&GuideRateNP);
187
188 // Initial HA to 0 and currentDEC (+90 or -90)
189 if (InitPark())
190 {
191 // If loading parking data is successful, we just set the default parking values.
193 SetAxis2ParkDefault(currentDEC);
194 }
195 else
196 {
197 // Otherwise, we set all parking data to default in case no parking data is found.
198 SetAxis1Park(0);
199 SetAxis2Park(currentDEC);
201 SetAxis2ParkDefault(currentDEC);
202 }
203
204 SetParked(isTheSkyParked());
205 }
206 else
207 {
208 //deleteProperty(TrackModeSP.name);
209 //deleteProperty(TrackRateNP.name);
210
211 deleteProperty(JogRateNP.name);
212
213 deleteProperty(GuideRateNP.name);
214 }
215
217
218 return true;
219}
220
221/*******************************************************************************
222* Note that for all successful TheSky TCP requests, the following string is
223* prepended to the result:
224*
225* |No error. Error = 0.
226*
227* This is true everywhere except for the Handshake(), which just returns "1" on success.
228*
229* In order to know when the response is complete, we append the # character in
230* Javascript commands and read from the port until the # character is reached.
231*******************************************************************************/
232
234{
235 if (isSimulation())
236 return true;
237
238 int rc = 0, nbytes_written = 0, nbytes_read = 0;
239 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
240
241 strncpy(pCMD,
242 "/* Java Script */"
243 "var Out;"
244 "sky6RASCOMTele.ConnectAndDoNotUnpark();"
245 "Out = sky6RASCOMTele.IsConnected + '#';",
246 MAXRBUF);
247
248 LOGF_DEBUG("CMD: %s", pCMD);
249
250 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
251 {
252 LOGF_ERROR("Error writing Handshake to TheSkyX TCP server. Result: %d", rc);
253 return false;
254 }
255
256 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
257 {
258 LOGF_ERROR("Error reading Handshake from TheSkyX TCP server. Result: %d", rc);
259 return false;
260 }
261
262 if (strcmp(pRES, "1#") != 0)
263 {
264 LOGF_ERROR("Error connecting to TheSky. Result: %s", pRES);
265 return false;
266 }
267
268 return true;
269}
270
271bool Paramount::getMountRADE()
272{
273 int rc = 0, nbytes_written = 0, nbytes_read = 0;
274 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
275 double SkyXRA = 0., SkyXDEC = 0.;
276
277 strncpy(pCMD,
278 "/* Java Script */"
279 "var Out;"
280 "sky6RASCOMTele.GetRaDec();"
281 "Out = String(sky6RASCOMTele.dRa) + ',' + String(sky6RASCOMTele.dDec) + '#';",
282 MAXRBUF);
283
284 LOGF_DEBUG("CMD: %s", pCMD);
285
286 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
287 {
288 LOGF_ERROR("Error writing GetRaDec to TheSkyX TCP server. Response: %d", rc);
289 return false;
290 }
291
292 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
293 {
294 LOGF_ERROR("Error reading GetRaDec from TheSkyX TCP server. Result: %d", rc);
295 return false;
296 }
297
298 LOGF_DEBUG("RES: %s", pRES);
299
300 // Read results successfully into temporary values before committing
301 if (sscanf(pRES, "|No error. Error = 0.%lf,%lf#", &SkyXRA, &SkyXDEC) == 2)
302 {
303 currentRA = SkyXRA;
304 currentDEC = SkyXDEC;
305 return true;
306 }
307
308 LOGF_ERROR("Error reading coordinates. Result: %s", pRES);
309 return false;
310}
311
312INDI::Telescope::TelescopePierSide Paramount::getPierSide()
313{
314 int rc = 0, nbytes_written = 0, nbytes_read = 0;
315 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
316 int SkyXPierSide = -1;
317
318 strncpy(pCMD,
319 "/* Java Script */"
320 "var Out;"
321 "sky6RASCOMTele.DoCommand(11, \"Pier Side\");"
322 "Out = sky6RASCOMTele.DoCommandOutput + '#';",
323 MAXRBUF);
324
325 LOGF_DEBUG("CMD: %s", pCMD);
326
327 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
328 {
329 LOGF_ERROR("Error writing DoCommand(Pier Side) to TheSkyX TCP server. Result: %d", rc);
330 return PIER_UNKNOWN;
331 }
332
333 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
334 {
335 LOGF_ERROR("Error reading Pier Side from TheSkyX TCP server. Result: %d", rc);
336 return PIER_UNKNOWN;
337 }
338
339 LOGF_DEBUG("RES: %s", pRES);
340
341 if (sscanf(pRES, "|No error. Error = 0.%d#", &SkyXPierSide) == 1)
342 {
343 return SkyXPierSide == 1 ? PIER_WEST : PIER_EAST;
344 }
345
346 LOGF_ERROR("Error reading Pier Side. Result: %s", pRES);
347 return PIER_UNKNOWN;
348}
349
351{
352 if (isSimulation())
353 {
354 mountSim();
355 return true;
356 }
357
359 {
360 // Check if Scope is done slewing
361 if (isSlewComplete())
362 {
364
365 if (HomeSP.getState() == IPS_BUSY)
366 {
367 HomeSP.reset();
369 LOG_INFO("Finding home completed.");
370 HomeSP.apply();
371 }
372 else
373 LOG_INFO("Slew is complete. Tracking...");
374 }
375 }
376 else if (TrackState == SCOPE_PARKING)
377 {
378 if (isTheSkyParked())
379 {
380 SetParked(true);
381 }
382 }
383
384 if (!getMountRADE())
385 return false;
386
387 char RAStr[64], DecStr[64];
388
389 fs_sexa(RAStr, currentRA, 2, 3600);
390 fs_sexa(DecStr, currentDEC, 2, 3600);
391
392 DEBUGF(DBG_SCOPE, "Current RA: %s Current DEC: %s", RAStr, DecStr);
393
394 setPierSide(getPierSide());
395
396 NewRaDec(currentRA, currentDEC);
397 return true;
398}
399
400bool Paramount::Goto(double r, double d)
401{
402 targetRA = r;
403 targetDEC = d;
404 char RAStr[64], DecStr[64];
405
406 fs_sexa(RAStr, targetRA, 2, 3600);
407 fs_sexa(DecStr, targetDEC, 2, 3600);
408
409 char pCMD[MAXRBUF] = {0};
410 snprintf(pCMD, MAXRBUF,
411 "sky6RASCOMTele.Asynchronous = true;"
412 "sky6RASCOMTele.SlewToRaDec(%g, %g,'');",
413 targetRA, targetDEC);
414
415 if (!sendTheSkyOKCommand(pCMD, "Slewing to target"))
416 return false;
417
419
420 LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
421 return true;
422}
423
424bool Paramount::isSlewComplete()
425{
426 int rc = 0, nbytes_written = 0, nbytes_read = 0;
427 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
428
429 strncpy(pCMD,
430 "/* Java Script */"
431 "var Out;"
432 "Out = sky6RASCOMTele.IsSlewComplete + '#';",
433 MAXRBUF);
434
435 LOGF_DEBUG("CMD: %s", pCMD);
436
437 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
438 {
439 LOGF_ERROR("Error writing IsSlewComplete to TheSkyX TCP server. Result: %d", rc);
440 return false;
441 }
442
443 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
444 {
445 LOGF_ERROR("Error reading IsSlewComplete from TheSkyX TCP server. Result: %d", rc);
446 return false;
447 }
448
449 LOGF_DEBUG("RES: %s", pRES);
450
451 int isComplete = 0;
452 if (sscanf(pRES, "|No error. Error = 0.%d#", &isComplete) == 1)
453 {
454 return isComplete == 1 ? 1 : 0;
455 }
456
457 LOGF_ERROR("Error reading isSlewComplete. Result: %s", pRES);
458 return false;
459}
460
461bool Paramount::isTheSkyParked()
462{
463 int rc = 0, nbytes_written = 0, nbytes_read = 0;
464 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
465
466 strncpy(pCMD,
467 "/* Java Script */"
468 "var Out;"
469 "Out = sky6RASCOMTele.IsParked() + '#';",
470 MAXRBUF);
471
472 LOGF_DEBUG("CMD: %s", pCMD);
473
474 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
475 {
476 LOGF_ERROR("Error writing sky6RASCOMTele.IsParked() to TheSkyX TCP server. Result: %d", rc);
477 return false;
478 }
479
480 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
481 {
482 LOGF_ERROR("Error reading sky6RASCOMTele.IsParked() from TheSkyX TCP server. Result: %d", rc);
483 return false;
484 }
485
486 LOGF_DEBUG("RES: %s", pRES);
487
488 if (strcmp(pRES, "|No error. Error = 0.true#") == 0)
489 return true;
490 if (strcmp(pRES, "|No error. Error = 0.false#") == 0)
491 return false;
492
493 LOGF_ERROR("Error checking for park. Invalid response: %s", pRES);
494 return false;
495}
496
497bool Paramount::isTheSkyTracking()
498{
499 int rc = 0, nbytes_written = 0, nbytes_read = 0;
500 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
501
502 strncpy(pCMD,
503 "/* Java Script */"
504 "var Out;"
505 "Out = sky6RASCOMTele.IsTracking + '#';",
506 MAXRBUF);
507
508 LOGF_DEBUG("CMD: %s", pCMD);
509
510 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
511 {
512 LOGF_ERROR("Error writing sky6RASCOMTele.IsTracking to TheSkyX TCP server. Result: %d", rc);
513 return false;
514 }
515
516 if ((rc = tty_read_section(PortFD, pRES, '#', PARAMOUNT_TIMEOUT, &nbytes_read)) != TTY_OK)
517 {
518 LOGF_ERROR("Error reading sky6RASCOMTele.IsTracking from TheSkyX TCP server. Result: %d", rc);
519 return false;
520 }
521
522 LOGF_DEBUG("RES: %s", pRES);
523
524 double SkyXTrackRate = 0.;
525 if (sscanf(pRES, "|No error. Error = 0.%lf#", &SkyXTrackRate) == 1)
526 {
527 if (SkyXTrackRate == 0)
528 return false;
529 else if (SkyXTrackRate > 0)
530 return true;
531 }
532
533 LOGF_ERROR("Error checking for tracking. Invalid response: %s", pRES);
534 return false;
535}
536
537bool Paramount::Sync(double ra, double dec)
538{
539 char pCMD[MAXRBUF] = {0};
540
541 snprintf(pCMD, MAXRBUF, "sky6RASCOMTele.Sync(%g, %g,'');", targetRA, targetDEC);
542 if (!sendTheSkyOKCommand(pCMD, "Syncing to target"))
543 return false;
544
545 currentRA = ra;
546 currentDEC = dec;
547
548 LOG_INFO("Sync is successful.");
549
551
552 NewRaDec(currentRA, currentDEC);
553
554 return true;
555}
556
558{
559 double targetHA = GetAxis1Park();
560 targetRA = range24(get_local_sidereal_time(LocationNP[LOCATION_LONGITUDE].getValue()) - targetHA);
561 targetDEC = GetAxis2Park();
562
563 char pCMD[MAXRBUF] = {0};
564 strncpy(pCMD,
565 "sky6RASCOMTele.Asynchronous = true;"
566 "sky6RASCOMTele.ParkAndDoNotDisconnect();",
567 MAXRBUF);
568
569 if (!sendTheSkyOKCommand(pCMD, "Parking mount"))
570 return false;
572 LOG_INFO("Parking telescope in progress...");
573
574 return true;
575}
576
578{
579 char pCMD[MAXRBUF] = {0};
580 strncpy(pCMD, "sky6RASCOMTele.Unpark();", MAXRBUF);
581 if (!sendTheSkyOKCommand(pCMD, "Unparking mount"))
582 return false;
583
584 // Confirm we unparked
585 if (isTheSkyParked())
586 LOG_ERROR("Could not unpark for some reason.");
587 else
588 SetParked(false);
589
590 return true;
591}
592
593bool Paramount::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
594{
595 // Check guider interface
596 if (GI::processNumber(dev, name, values, names, n))
597 return true;
598
599 // first check if it's for our device
600 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
601 {
602 if (strcmp(name, "JOG_RATE") == 0)
603 {
604 IUUpdateNumber(&JogRateNP, values, names, n);
605 JogRateNP.s = IPS_OK;
606 IDSetNumber(&JogRateNP, nullptr);
607 return true;
608 }
609
610 // Guiding Rate
611 if (strcmp(name, GuideRateNP.name) == 0)
612 {
613 IUUpdateNumber(&GuideRateNP, values, names, n);
614 GuideRateNP.s = IPS_OK;
615 IDSetNumber(&GuideRateNP, nullptr);
616 return true;
617 }
618 }
619
620 // if we didn't process it, continue up the chain, let somebody else give it a shot
621 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
622}
623
625{
626 char pCMD[MAXRBUF] = {0};
627
628 strncpy(pCMD, "sky6RASCOMTele.Abort();", MAXRBUF);
629 return sendTheSkyOKCommand(pCMD, "Abort mount slew");
630}
631
632bool Paramount::findHome()
633{
634 char pCMD[MAXRBUF] = {0};
635
636 strncpy(pCMD, "sky6RASCOMTele.FindHome();"
637 "while(!sky6RASCOMTele.IsSlewComplete) {"
638 "sky6Web.Sleep(1000);}",
639 MAXRBUF);
640 return sendTheSkyOKCommand(pCMD, "Find home", 60);
641}
642
643
645{
647 {
648 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
649 return false;
650 }
651
652 int motion = (dir == DIRECTION_NORTH) ? PARAMOUNT_NORTH : PARAMOUNT_SOUTH;
653 //int rate = IUFindOnSwitchIndex(&SlewRateSP);
655
656 switch (command)
657 {
658 case MOTION_START:
659 if (!isSimulation() && !startOpenLoopMotion(motion, rate))
660 {
661 LOG_ERROR("Error setting N/S motion direction.");
662 return false;
663 }
664 else
665 LOGF_INFO("Moving toward %s.", (motion == PARAMOUNT_NORTH) ? "North" : "South");
666 break;
667
668 case MOTION_STOP:
669 if (!isSimulation() && !stopOpenLoopMotion())
670 {
671 LOG_ERROR("Error stopping N/S motion.");
672 return false;
673 }
674 else
675 LOGF_INFO("Moving toward %s halted.",
676 (motion == PARAMOUNT_NORTH) ? "North" : "South");
677 break;
678 }
679
680 return true;
681}
682
684{
686 {
687 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
688 return false;
689 }
690
691 int motion = (dir == DIRECTION_WEST) ? PARAMOUNT_WEST : PARAMOUNT_EAST;
692 int rate = SlewRateSP.findOnSwitchIndex();
693
694 switch (command)
695 {
696 case MOTION_START:
697 if (!isSimulation() && !startOpenLoopMotion(motion, rate))
698 {
699 LOG_ERROR("Error setting W/E motion direction.");
700 return false;
701 }
702 else
703 LOGF_INFO("Moving toward %s.", (motion == PARAMOUNT_WEST) ? "West" : "East");
704 break;
705
706 case MOTION_STOP:
707 if (!isSimulation() && !stopOpenLoopMotion())
708 {
709 LOG_ERROR("Error stopping W/E motion.");
710 return false;
711 }
712 else
713 LOGF_INFO("Movement toward %s halted.",
714 (motion == PARAMOUNT_WEST) ? "West" : "East");
715 break;
716 }
717
718 return true;
719}
720
721bool Paramount::startOpenLoopMotion(uint8_t motion, uint16_t rate)
722{
723 char pCMD[MAXRBUF] = {0};
724
725 snprintf(pCMD, MAXRBUF, "sky6RASCOMTele.DoCommand(9,'%d|%d');", motion, rate);
726 return sendTheSkyOKCommand(pCMD, "Starting open loop motion");
727}
728
729bool Paramount::stopOpenLoopMotion()
730{
731 char pCMD[MAXRBUF] = {0};
732
733 strncpy(pCMD, "sky6RASCOMTele.DoCommand(10,'');", MAXRBUF);
734 return sendTheSkyOKCommand(pCMD, "Stopping open loop motion");
735}
736
737bool Paramount::updateTime(ln_date *utc, double utc_offset)
738{
739 INDI_UNUSED(utc);
740 INDI_UNUSED(utc_offset);
741 return true;
742}
743
745{
746 char pCMD[MAXRBUF] = {0};
747
748 strncpy(pCMD, "sky6RASCOMTele.SetParkPosition();", MAXRBUF);
749 if (!sendTheSkyOKCommand(pCMD, "Setting Park Position"))
750 return false;
751
752 double lst = get_local_sidereal_time(LocationNP[LOCATION_LONGITUDE].getValue());
753 double ha = get_local_hour_angle(lst, currentRA);
754
755 SetAxis1Park(ha);
756 SetAxis2Park(currentDEC);
757
758 return true;
759}
760
762{
763 // By default set HA to 0
764 SetAxis1Park(0);
765
766 // Set DEC to 90 or -90 depending on the hemisphere
767 SetAxis2Park((LocationNP[LOCATION_LATITUDE].getValue() > 0) ? 90 : -90);
768
769 return true;
770}
771
772bool Paramount::SetParkPosition(double Axis1Value, double Axis2Value)
773{
774 INDI_UNUSED(Axis1Value);
775 INDI_UNUSED(Axis2Value);
776 LOG_ERROR("Setting custom parking position directly is not supported. Slew to the desired "
777 "parking position and click Current.");
778 return false;
779}
780
781void Paramount::mountSim()
782{
783 static struct timeval ltv
784 {
785 0, 0
786 };
787 struct timeval tv
788 {
789 0, 0
790 };
791 double dt, dx, da_ra = 0, da_dec = 0;
792 int nlocked;
793
794 /* update elapsed time since last poll, don't presume exactly POLLMS */
795 gettimeofday(&tv, nullptr);
796
797 if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
798 ltv = tv;
799
800 dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
801 ltv = tv;
802
803 if (fabs(targetRA - currentRA) * 15. >= GOTO_LIMIT)
804 da_ra = GOTO_RATE * dt;
805 else if (fabs(targetRA - currentRA) * 15. >= SLEW_LIMIT)
806 da_ra = SLEW_RATE * dt;
807 else
808 da_ra = FINE_SLEW_RATE * dt;
809
810 if (fabs(targetDEC - currentDEC) >= GOTO_LIMIT)
811 da_dec = GOTO_RATE * dt;
812 else if (fabs(targetDEC - currentDEC) >= SLEW_LIMIT)
813 da_dec = SLEW_RATE * dt;
814 else
815 da_dec = FINE_SLEW_RATE * dt;
816
817 double motionRate = 0;
818
820 motionRate = JogRateN[0].value;
821 else if (MovementWESP.getState() == IPS_BUSY)
822 motionRate = JogRateN[1].value;
823
824 if (motionRate != 0)
825 {
826 da_ra = motionRate * dt * 0.05;
827 da_dec = motionRate * dt * 0.05;
828
829 switch (MovementNSSP.getState())
830 {
831 case IPS_BUSY:
832 if (MovementNSSP[DIRECTION_NORTH].getState() == ISS_ON)
833 currentDEC += da_dec;
834 else if (MovementNSSP[DIRECTION_SOUTH].getState() == ISS_ON)
835 currentDEC -= da_dec;
836 break;
837
838 default:
839 break;
840 }
841
842 switch (MovementWESP.getState())
843 {
844 case IPS_BUSY:
845 if (MovementWESP[DIRECTION_WEST].getState() == ISS_ON)
846 currentRA += da_ra / 15.;
847 else if (MovementWESP[DIRECTION_EAST].getState() == ISS_ON)
848 currentRA -= da_ra / 15.;
849 break;
850
851 default:
852 break;
853 }
854
855 NewRaDec(currentRA, currentDEC);
856 return;
857 }
858
859 /* Process per current state. We check the state of EQUATORIAL_COORDS and act accordingly */
860 switch (TrackState)
861 {
862 case SCOPE_IDLE:
863 /* RA moves at sidereal, Dec stands still */
864 currentRA += (TRACKRATE_SIDEREAL / 3600.0 * dt / 15.);
865 break;
866
867 case SCOPE_SLEWING:
868 case SCOPE_PARKING:
869 /* slewing - nail it when both within one pulse @ SLEWRATE */
870 nlocked = 0;
871
872 dx = targetRA - currentRA;
873
874 // Take shortest path
875 if (fabs(dx) > 12)
876 dx *= -1;
877
878 if (fabs(dx) <= da_ra)
879 {
880 currentRA = targetRA;
881 nlocked++;
882 }
883 else if (dx > 0)
884 currentRA += da_ra / 15.;
885 else
886 currentRA -= da_ra / 15.;
887
888 if (currentRA < 0)
889 currentRA += 24;
890 else if (currentRA > 24)
891 currentRA -= 24;
892
893 dx = targetDEC - currentDEC;
894 if (fabs(dx) <= da_dec)
895 {
896 currentDEC = targetDEC;
897 nlocked++;
898 }
899 else if (dx > 0)
900 currentDEC += da_dec;
901 else
902 currentDEC -= da_dec;
903
904 if (nlocked == 2)
905 {
908 else
909 SetParked(true);
910 }
911 break;
912
913 default:
914 break;
915 }
916
917 NewRaDec(currentRA, currentDEC);
918}
919
920bool Paramount::sendTheSkyOKCommand(const char *command, const char *errorMessage, uint8_t timeout)
921{
922 int rc = 0, nbytes_written = 0, nbytes_read = 0;
923 char pCMD[MAXRBUF] = {0}, pRES[MAXRBUF] = {0};
924
925 snprintf(pCMD, MAXRBUF,
926 "/* Java Script */"
927 "var Out;"
928 "try {"
929 "%s"
930 "Out = 'OK#'; }"
931 "catch (err) {Out = err; }",
932 command);
933
934 LOGF_DEBUG("CMD: %s", pCMD);
935
936 tcflush(PortFD, TCIOFLUSH);
937
938 if ((rc = tty_write_string(PortFD, pCMD, &nbytes_written)) != TTY_OK)
939 {
940 LOGF_ERROR("Error writing sendTheSkyOKCommand to TheSkyX TCP server. Result: $%d", rc);
941 return false;
942 }
943
944 if ((rc = tty_read_section(PortFD, pRES, '#', timeout, &nbytes_read)) != TTY_OK)
945 {
946 LOGF_ERROR("Error reading sendTheSkyOKCommand from TheSkyX TCP server. Result: %d", rc);
947 return false;
948 }
949
950 LOGF_DEBUG("RES: %s", pRES);
951
952 tcflush(PortFD, TCIOFLUSH);
953
954 if (strcmp("|No error. Error = 0.OK#", pRES) == 0 )
955 return true;
956 else
957 {
958 LOGF_ERROR("sendTheSkyOKCommand Error %s - Invalid response: %s", errorMessage, pRES);
959 return false;
960 }
961}
962
964{
965 return GuideNS(static_cast<int>(ms));
966}
967
969{
970 return GuideNS(-static_cast<int>(ms));
971}
972
974{
975 return GuideWE(static_cast<int>(ms));
976}
977
979{
980 return GuideWE(-static_cast<int>(ms));
981}
982
984{
986 {
987 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
988 return IPS_ALERT;
989 }
990
991 // Movement in arcseconds
992 // Send async
993 double dDec = GuideRateN[DEC_AXIS].value * TRACKRATE_SIDEREAL * ms / 1000.0;
994 char pCMD[MAXRBUF] = {0};
995 snprintf(pCMD, MAXRBUF,
996 "sky6RASCOMTele.Asynchronous = true;"
997 "sky6DirectGuide.MoveTelescope(%g, %g);", 0., dDec);
998
999 if (!sendTheSkyOKCommand(pCMD, "Guide North-South"))
1000 return IPS_ALERT;
1001
1002 m_NSTimer.start(ms);
1003
1004 return IPS_BUSY;
1005}
1006
1008{
1009 if (TrackState == SCOPE_PARKED)
1010 {
1011 LOG_ERROR("Please unpark the mount before issuing any motion commands.");
1012 return IPS_ALERT;
1013 }
1014
1015 // Movement in arcseconds
1016 // Send async
1017 double dRA = GuideRateN[RA_AXIS].value * TRACKRATE_SIDEREAL * ms / 1000.0;
1018 char pCMD[MAXRBUF] = {0};
1019 snprintf(pCMD, MAXRBUF,
1020 "sky6RASCOMTele.Asynchronous = true;"
1021 "sky6DirectGuide.MoveTelescope(%g, %g);", dRA, 0.);
1022
1023 if (!sendTheSkyOKCommand(pCMD, "Guide West-East"))
1024 return IPS_ALERT;
1025
1026 m_WETimer.start(ms);
1027
1028 return IPS_BUSY;
1029}
1030
1031bool Paramount::setTheSkyTracking(bool enable, bool isSidereal, double raRate, double deRate)
1032{
1033 int on = enable ? 1 : 0;
1034 int ignore = isSidereal ? 1 : 0;
1035 char pCMD[MAXRBUF] = {0};
1036
1037 snprintf(pCMD, MAXRBUF, "sky6RASCOMTele.SetTracking(%d, %d, %g, %g);", on, ignore, raRate, deRate);
1038 return sendTheSkyOKCommand(pCMD, "Setting tracking rate");
1039}
1040
1041bool Paramount::SetTrackRate(double raRate, double deRate)
1042{
1043 return setTheSkyTracking(true, false, raRate, deRate);
1044}
1045
1046bool Paramount::SetTrackMode(uint8_t mode)
1047{
1048 bool isSidereal = (mode == TRACK_SIDEREAL);
1049 double dRA = 0, dDE = 0;
1050
1051 if (mode == TRACK_SOLAR)
1052 dRA = TRACKRATE_SOLAR;
1053 else if (mode == TRACK_LUNAR)
1054 dRA = TRACKRATE_LUNAR;
1055 else if (mode == TRACK_CUSTOM)
1056 {
1057 dRA = TrackRateNP[RA_AXIS].getValue();
1058 dDE = TrackRateNP[DEC_AXIS].getValue();
1059 }
1060 return setTheSkyTracking(true, isSidereal, dRA, dDE);
1061}
1062
1064{
1065 // On engaging track, we simply set the current track mode and it will take care of the rest including custom track rates.
1066 if (enabled)
1067 return SetTrackMode(IUFindOnSwitchIndex(&TrackModeSP));
1068 else
1069 // Otherwise, simply switch everything off
1070 return setTheSkyTracking(0, 0, 0., 0.);
1071}
1072
1074{
1075 switch (action)
1076 {
1077 case HOME_GO:
1078 LOG_INFO("Moving to home position. Please stand by...");
1079 return findHome() ? IPS_BUSY : IPS_ALERT;
1080
1081 default:
1082 return IPS_ALERT;
1083 }
1084}
#define SLEW_RATE
#define GOTO_RATE
#define FINE_SLEW_RATE
#define SLEW_LIMIT
#define GOTO_LIMIT
bool isConnected() const
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
INDI::PropertyNumber GuideNSNP
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INDI::PropertyNumber GuideWENP
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
void setTelescopeConnection(const uint8_t &value)
setTelescopeConnection Set telescope connection mode. Child class should call this in the constructor...
double GetAxis2Park() const
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.
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....
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.
void setSingleShot(bool singleShot)
Set whether the timer is a single-shot timer.
void callOnTimeout(const std::function< void()> &callback)
Definition inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition inditimer.cpp:82
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
virtual bool SetTrackRate(double raRate, double deRate) override
SetTrackRate Set custom tracking rates.
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
IPState GuideNS(int32_t ms)
IPState GuideWE(int32_t ms)
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
virtual bool Goto(double, double) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual IPState ExecuteHomeAction(TelescopeHomeAction action) override
Execute a homing action.
virtual bool Park() override
Park the telescope to its home position.
virtual bool Handshake() override
perform handshake with device to check communication
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool SetParkPosition(double Axis1Value, double Axis2Value) override
SetParkPosition Set desired parking position to the supplied value. This ONLY sets the desired park p...
virtual const char * getDefaultName() override
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
INDI::PropertyNumber TrackRateNP
INDI::PropertySwitch MovementNSSP
INDI::PropertyNumber EqNP
INDI::PropertySwitch HomeSP
INDI::PropertySwitch SlewRateSP
INDI::PropertySwitch MovementWESP
INDI::PropertyNumber LocationNP
#define currentDEC
Definition ieq45.cpp:48
#define currentRA
Definition ieq45.cpp:47
double ra
double dec
#define RA_AXIS
#define DEC_AXIS
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
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
INDI_DIR_WE
@ DIRECTION_EAST
@ DIRECTION_WEST
INDI_DIR_NS
@ DIRECTION_SOUTH
@ DIRECTION_NORTH
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:653
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition indicom.c:1311
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
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
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition indicom.c:1379
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
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition indidevapi.c:337
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition indidevapi.c:366
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
#define INDI_UNUSED(x)
Definition indidevapi.h:132
void IDSetNumber(const INumberVectorProperty *nvp, 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.
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#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 DEBUGF(priority, msg,...)
Definition indilogger.h:57
#define TRACKRATE_SOLAR
Definition indimacros.h:45
#define TRACKRATE_SIDEREAL
Definition indimacros.h:39
#define TRACKRATE_LUNAR
Definition indimacros.h:48
#define getDeviceName
const double slewspeeds[SLEWMODES]
Definition paramount.cpp:69
#define PARAMOUNT_NORTH
Definition paramount.cpp:59
#define SLEWMODES
Definition paramount.cpp:68
#define PARAMOUNT_EAST
Definition paramount.cpp:61
#define PARAMOUNT_SOUTH
Definition paramount.cpp:60
#define PARAMOUNT_WEST
Definition paramount.cpp:62
#define PARAMOUNT_TIMEOUT
Definition paramount.cpp:58
std::unique_ptr< Paramount > paramount_mount(new Paramount())
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName)
Adds a new debugging level to the driver.
char name[MAXINDINAME]
Definition indiapi.h:321