Instrument Neutral Distributed Interface INDI 2.2.5
crux_mount.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Driver type: TitanTCS for HOBYM CRUX Mount INDI Driver
3
4 Copyright(c) 2020 Park Suyoung <[email protected]>. All rights reserved.
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
9 .
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14 .
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*******************************************************************************/
20
21#include <cstring>
22
23#include "crux_mount.h"
24
25#include "indicom.h"
26
27#include <cmath>
28#include <memory>
29
30#define PRODUCT_NAME "TitanTCS CRUX"
31#define HANDSHAKE_NAME "TiTaN TCS"
32#define MIN_FW_VERSION "3.1.0"
33#define MAX_CMD_LEN 256
34
35static std::unique_ptr<TitanTCS> titanTCS(new TitanTCS());
36
38{
39 setVersion(1, 0);
40
49#if USE_PEC
51#endif
54 //TELESCOPE_HAS_TRACK_RATE
55 , 4);
56
58
59 LOG_INFO("Initializing from " PRODUCT_NAME " device...");
60
61 m_Connect = 0;
62}
63
65{
66 m_Connect = 1;
67
68 bool bResult = Telescope::Connect();
69 LOGF_DEBUG("Connect() => %s", (bResult ? "true" : "false"));
70
71 if(bResult)
72 m_Connect = 2;
73 else
74 m_Connect = -1;
75
76 return bResult;
77}
78
80{
81 m_Connect = 0;
82
83 bool bResult = Telescope::Disconnect();
84 LOGF_DEBUG("Disconnect() => %s", (bResult ? "true" : "false"));
85
86 return bResult;
87}
88
89/**************************************************************************************
90** We init our properties here. The only thing we want to init are the Debug controls
91***************************************************************************************/
93{
94#if USE_PEC
95 _PECStatus = -1;
96#endif
97 //
99
102
103 AddTrackMode("TRACK_SIDEREAL", "Sidereal");
104 AddTrackMode("TRACK_SOLAR", "Solar");
105 AddTrackMode("TRACK_LUNAR", "Lunar");
106
108
109#if USE_PEC
110 // PEC Training
111 IUFillSwitch(&PECTrainingS[0], "PEC_Start", "Start", ISS_OFF);
112 IUFillSwitch(&PECTrainingS[1], "PEC_Stop", "Stop", ISS_OFF);
113 IUFillSwitchVector(&PECTrainingSP, PECTrainingS, 2, getDeviceName(), "PEC_TRAINING", "PEC Training", MOTION_TAB, IP_RW,
114 ISR_1OFMANY, 0,
115 IPS_IDLE);
116
117 // PEC Details
118 IUFillText(&PECInfoT[0], "PEC_INFO", "PEC", "");
119 IUFillText(&PECInfoT[1], "PEC_TR_INFO", "Training", "");
120 IUFillTextVector(&PECInfoTP, PECInfoT, 2, getDeviceName(), "PEC_INFOS", "PEC Info", MOTION_TAB,
121 IP_RO, 60, IPS_IDLE);
122#endif
123 // Mount Details
124 IUFillText(&MountInfoT[0], "MOUNT_PARK", "Park", "");
125 IUFillText(&MountInfoT[1], "MOUNT_TRACKING", "Tracking", "");
126 IUFillTextVector(&MountInfoTP, MountInfoT, 2, getDeviceName(), "MOUNT_INFOS", "Mount Info", MAIN_CONTROL_TAB,
127 IP_RO, 60, IPS_IDLE);
128
129 // Park mode, mirroring the two options in the TitanTCS ASCOM application:
130 // park at the saved point (':hP1#', default) or lock at the current
131 // position (':hP8#'). See Park() for details.
132 ParkModeSP[PARK_MODE_AT_CURRENT].fill("PARK_MODE_AT_CURRENT", "At current position", ISS_OFF);
133 ParkModeSP[PARK_MODE_AT_SAVED].fill("PARK_MODE_AT_SAVED", "At saved position", ISS_ON);
134 ParkModeSP.fill(getDeviceName(), "PARK_MODE", "Park", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
135 ParkModeSP.load();
136
138
139 return true;
140}
141
142
144{
146
147 if (isConnected())
148 {
149#if USE_PEC
150 defineProperty(&PECTrainingSP);
151 defineProperty(&PECInfoTP);
152#endif
153 defineProperty(&MountInfoTP);
154 defineProperty(ParkModeSP);
155 //
159 //
160 // Load saved park position, or fall back to sensible defaults (pole).
161 if(InitPark())
162 {
163 LOG_INFO("Park data loaded from file.");
164 }
165 else
166 {
167 // No saved park data — default to the celestial pole, which is
168 // where this mount physically parks (telescope points north).
170 SetAxis2ParkDefault(LocationNP[LOCATION_LATITUDE].getValue() >= 0 ? 90.0 : -90.0);
171 SetAxis1Park(0.0);
172 SetAxis2Park(LocationNP[LOCATION_LATITUDE].getValue() >= 0 ? 90.0 : -90.0);
173 }
174 //
175 GetMountParams();
176 }
177 else
178 {
179#if USE_PEC
180 deleteProperty(PECTrainingSP.name);
181 deleteProperty(PECInfoTP.name);
182#endif
183 deleteProperty(MountInfoTP.name);
184 deleteProperty(ParkModeSP);
185 //
186 }
187
189
190 return true;
191}
192
193bool TitanTCS::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
194{
195 // Check guider interface
196 if (GI::processNumber(dev, name, values, names, n))
197 return true;
198
199 return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
200}
201
202bool TitanTCS::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
203{
204 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
205 {
206 // ---------------------------------------------------------------------
207 // Park — let base class handle IsParked flag and property updates.
208 // Our Park()/UnPark() functions are called by the base class, and
209 // GetMountParams() updates TrackState from the mount's reported status.
210 // ---------------------------------------------------------------------
211 if (ParkModeSP.isNameMatch(name))
212 {
213 ParkModeSP.update(states, names, n);
214 ParkModeSP.setState(IPS_OK);
215 ParkModeSP.apply();
216 saveConfig(true, ParkModeSP.getName());
217
218 if(ParkModeSP.findOnSwitchIndex() == PARK_MODE_AT_CURRENT)
219 LOG_INFO("Park mode: lock the mount at its current position.");
220 else
221 LOG_INFO("Park mode: slew to the saved park position, then lock.");
222
223 return true;
224 }
225#if USE_PEC
226 if (PECStateSP.isNameMatch(name))
227 {
228 //int preIndex = IUFindOnSwitchIndex(&PECStateSP);
229 PECStateSP.update(states, names, n);
230 int nowIndex = PECStateSP.findOnSwitchIndex();
231
233
234 if(nowIndex == 0)
235 {
236 SendCommand("#:\\e10#:\\e11#");
237 }
238 else if(nowIndex == 1)
239 {
240 SendCommand("#:\\e12#");
241 }
242 return true;
243 }
244 //
245 if (!strcmp(name, PECTrainingSP.name))
246 {
247 //int preIndex = IUFindOnSwitchIndex(&PECTrainingSP);
248 IUUpdateSwitch(&PECTrainingSP, states, names, n);
249 int nowIndex = IUFindOnSwitchIndex(&PECTrainingSP);
250
251 IDSetSwitch(&PECTrainingSP, nullptr);
252
253 if(nowIndex == 0)
254 {
255 SendCommand("#:\\e20#:\\e21#");
256 }
257 else if(nowIndex == 1)
258 {
259 SendCommand("#:\\e23#");
260 }
261 return true;
262 }
263#endif
264 //
265 if (TrackStateSP.isNameMatch(name))
266 {
267 TrackStateSP.update(states, names, n);
268 int nowIndex = TrackStateSP.findOnSwitchIndex();
269
271
272 if(nowIndex == 0)
273 {
274 SetTrackEnabled(true);
275 }
276 else if(nowIndex == 1)
277 {
278 SetTrackEnabled(false);
279 }
280 return true;
281 }
282 //
283 if (TrackModeSP.isNameMatch(name))
284 {
285 TrackModeSP.update(states, names, n);
286 int nowIndex = TrackModeSP.findOnSwitchIndex();
287
289
290 SetTrackMode(nowIndex);
291
292 return true;
293 }
294 }
295
296 return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
297}
298
299bool TitanTCS::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
300{
301 return INDI::Telescope::ISNewText(dev, name, texts, names, n);
302}
303
304/**************************************************************************************
305** INDI is asking us to check communication with the device via a handshake
306***************************************************************************************/
308{
309 if(!isConnected())
310 return true;
311
312 char szResponse[MAX_CMD_LEN] = { 0 };
313
314 if(CommandResponseStr("#:GVP#", "", '#', szResponse, sizeof(szResponse)))
315 {
316 LOGF_INFO("Product Name = '%s'", szResponse);
317 if(strstr(szResponse, HANDSHAKE_NAME))
318 {
319 if(CommandResponseStr("#:GVN#", "", '#', szResponse, sizeof(szResponse)))
320 {
321 LOGF_INFO("Firmware Version = '%s'", szResponse);
322 int comp = strcmp(szResponse, MIN_FW_VERSION);
323 if(comp >= 0)
324 return true;
325 else
326 {
327 LOGF_ERROR("Firmware version '%s' is too old. Required > %s", szResponse, MIN_FW_VERSION);
328 }
329 }
330 else
331 {
332 LOG_ERROR("The firmware version cannot be read.");
333 }
334 }
335 else
336 {
337 LOGF_ERROR("TitanTCS could not be found. return code = '%s'", szResponse);
338 }
339 }
340
341 LOG_ERROR("Handshake() failed!");
342
343 //Disconnect();
344 return false;
345}
346
347static int Char2Num(char chr)
348{
349 if((chr >= '0') && (chr <= '9'))
350 return chr - '0';
351
352 if((chr >= 'A') && (chr <= 'F'))
353 return chr - 'A' + 10;
354
355 if((chr >= 'a') && (chr <= 'f'))
356 return chr - 'a' + 10;
357
358 return 0;
359}
360
361static int GetDigitParam(const char *pStr, int* pDigit, char* pDelimeter = nullptr)
362{
363 /*
364 HH:MM.T#
365 HH:MM:SS#
366 */
367
368 int cntParam = 0;
369 short sign = 1;
370 int i, chrCnt;
371
372 while (*pStr == ' ' || *pStr == '+')
373 {
374 ++pStr;
375 if(*pStr == 0)
376 {
377 return 0;
378 }
379 }
380
381 if (*pStr == '-')
382 {
383 sign = -1;
384 ++pStr;
385 }
386
387 pDigit[cntParam] = sign;
388 if(pDelimeter)
389 pDelimeter[cntParam] = 0;
390 cntParam++;
391
392 for(i = 0; i < 3; i++)
393 {
394 pDigit[cntParam] = 0;
395 chrCnt = 0;
396 for(;;)
397 {
398 if(*pStr == 0)
399 {
400 if(chrCnt > 0)
401 cntParam++;
402 return cntParam;
403 }
404
405 if(!isdigit(*pStr))
406 {
407 if(pDelimeter)
408 pDelimeter[cntParam] = *pStr;
409 break;
410 }
411
412 pDigit[cntParam] *= 10;
413 pDigit[cntParam] += Char2Num(*pStr);
414
415 chrCnt++;
416 pStr++;
417 }
418 cntParam++;
419
420 if(*pStr == 0)
421 return cntParam;
422
423 if(!isdigit(*pStr))
424 {
425 pStr++;
426 }
427 }
428
429 return cntParam;
430}
431
432// 00:02:43
433static bool HMS2Hour(const char* pStr, double* hr)
434{
435 int digit[5] = { 0, 0, 0, 0, 0 };
436 int rtnCode = GetDigitParam(pStr, digit);
437
438 int sec = 0;
439 for(int i = 1; i < 4; i++)
440 {
441 sec *= 60;
442 sec += digit[i];
443 }
444
445 if(digit[0] < 0)
446 sec = -sec;
447
448 *hr = sec / 3600.0;
449 return rtnCode >= 3;
450}
451
452static void formatRA(long secRa, char* pStr)
453{
454 int sign = 1;
455 if(secRa < 0)
456 {
457 sign = -1;
458 secRa = -secRa;
459 }
460
461 int h = secRa / 3600;
462 secRa -= (h * 3600);
463
464 int m = secRa / 60;
465 secRa -= (m * 60);
466
467 int s = secRa;
468
469 if(sign < 0)
470 sprintf(pStr, "-%02d:%02d:%02d", h, m, s);
471 else
472 sprintf(pStr, "%02d:%02d:%02d", h, m, s);
473}
474
475static void formatDEC(long secDec, char* pStr)
476{
477 if (secDec > (270 * 3600))
478 secDec = secDec - (360 * 3600);
479 else if (secDec > (90 * 3600))
480 secDec = (180 * 3600) - secDec;
481
482 char sign;
483 if (secDec >= 0)
484 {
485 sign = '+';
486 }
487 else
488 {
489 sign = '-';
490 secDec = -secDec;
491 }
492
493 int h = secDec / 3600;
494 secDec -= (h * 3600);
495
496 int m = secDec / 60;
497 secDec -= (m * 60);
498
499 int s = secDec;
500
501 sprintf(pStr, "%c%02d*%02d:%02d", sign, h, m, s);
502}
503
504/**************************************************************************************
505** Guiding
506***************************************************************************************/
508{
509 SendCommand(":Mgn%d#", (int)ms);
510 //
512 return IPS_ALERT;
513
514 if (GuideNSTID)
515 {
516 IERmTimer(GuideNSTID);
517 GuideNSTID = 0;
518 }
519
520 GuideNSTID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperNS, this);
521 return IPS_BUSY;
522}
523
525{
526 SendCommand(":Mgs%d#", (int)ms);
527 //
529 return IPS_ALERT;
530
531 if (GuideNSTID)
532 {
533 IERmTimer(GuideNSTID);
534 GuideNSTID = 0;
535 }
536
537 GuideNSTID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperNS, this);
538 return IPS_BUSY;
539}
540
542{
543 SendCommand(":Mge%d#", (int)ms);
544 //
546 return IPS_ALERT;
547
548 if (GuideWETID)
549 {
550 IERmTimer(GuideWETID);
551 GuideWETID = 0;
552 }
553
554 GuideWETID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperWE, this);
555 return IPS_BUSY;
556}
557
559{
560 SendCommand(":Mgw%d#", (int)ms);
561 //
563 return IPS_ALERT;
564
565 if (GuideWETID)
566 {
567 IERmTimer(GuideWETID);
568 GuideWETID = 0;
569 }
570
571 GuideWETID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperWE, this);
572 return IPS_BUSY;
573}
574
575/**************************************************************************************
576** INDI is asking us for our default device name
577***************************************************************************************/
579{
580 return PRODUCT_NAME;
581}
582
583/**************************************************************************************
584** Client is asking us to slew to a new position
585***************************************************************************************/
586bool TitanTCS::Goto(double ra, double dec)
587{
588 if(!SetTarget(ra, dec))
589 return false;
590
591 char rtnCode;
592 char szCommand[MAX_CMD_LEN];
593 sprintf(szCommand, ":MS#");
594
595 if(!CommandResponseChar(szCommand, "", &rtnCode))
596 {
597 LOG_ERROR("Goto / No response");
598 return false;
599 }
600 if(rtnCode != '0')
601 {
602 LOGF_ERROR("Goto / Error Code = '%c'", rtnCode);
603 // Send abort to clear the mount's internal goto-error state.
604 // Without this, the firmware retains the failed target and may
605 // set tracking status bit5 (goto-error flag), which can cause
606 // the mount to self-park after a timeout.
607 SendCommand("#:Q#");
608 return false;
609 }
610
612 // Reset lightweight-poll completion detection for the new slew
613 m_HaveLastPos = false;
614 m_StableCount = 0;
615
616 LOG_INFO("Slewing ...");
617 return true;
618}
619
620/**************************************************************************************
621** Client is asking us to abort our motion
622***************************************************************************************/
624{
626 UnPark();
627
628 LOG_DEBUG("Abort()");
629 return SendCommand("#:Q#");
630}
631
632/**************************************************************************************
633** Client is asking us to report telescope status
634***************************************************************************************/
636{
637 LOGF_DEBUG("ReadScopeStatus(s %d)", TrackState);
638
639 // During slewing/parking, use lightweight individual queries to avoid
640 // overloading the firmware's command processor. The composite \GE command
641 // requires significant parsing time which can interfere with step
642 // generation and cause the RA motor to stall.
643 // When tracking/idle, use the full composite query for efficiency.
645 {
646 GetMountParamsLight();
647 }
648 else
649 {
650 GetMountParams();
651 }
652
653 return true;
654}
655
656//------------------------------------------------------------------------------
657bool TitanTCS::GetParamStr(const char* pInStr, char* pOutStr, int len, const char* pResponse, char delimeter)
658{
659 //LOGF_DEBUG("GetParamStr('%s', '%s', '%c')", pInStr, pResponse, (delimeter == 0 ? '0' : delimeter));
660
661 if((pResponse != NULL) && (*pResponse == 0))
662 pResponse = NULL;
663
664 if(pResponse)
665 {
666 const char* pFind = strstr(pInStr, pResponse);
667 if(pFind == 0)
668 {
669 LOG_ERROR("Fail!");
670 return false;
671 }
672 int l = strlen(pResponse);
673 pInStr = pFind + l;
674 len -= l;
675 }
676
677 if(len <= 0)
678 return false;
679
680 for(int i = 0; i < 3; i++)
681 {
682 if(*pInStr == ' ')
683 {
684 pInStr++;
685 len--;
686
687 if(len <= 0)
688 return false;
689 }
690 else
691 break;
692 }
693
694 snprintf(pOutStr, len, "%s", pInStr);
695
696 if(delimeter)
697 {
698 char* pDel = strchr(pOutStr, delimeter);
699 if(pDel == 0)
700 {
701 LOG_ERROR("Fail!");
702 return false;
703 }
704
705 *pDel = 0;
706 }
707
708 LOG_DEBUG(pOutStr);
709
710 return true;
711}
712
713bool TitanTCS::GetParamNumber(const char* pInStr, char* pOutStr, int len, const char* pResponse, char delimeter,
714 double *pDouble, int *pInteger)
715{
716 if(!GetParamStr(pInStr, pOutStr, len, pResponse, delimeter))
717 return false;
718
719 if(pDouble)
720 *pDouble = atof(pOutStr);
721 if(pInteger)
722 *pInteger = atoi(pOutStr);
723
724 return true;
725}
726
727bool TitanTCS::GetParamHour(const char* pInStr, char* pOutStr, int len, const char* pResponse, char delimeter,
728 double *pHour)
729{
730 if(!GetParamStr(pInStr, pOutStr, len, pResponse, delimeter))
731 return false;
732
733 //LOGF_DEBUG("pResult = '%s'", pOutStr);
734 if(HMS2Hour(pOutStr, pHour))
735 return true;
736
737 return false;
738}
739
740// -----------------------------------------------------------------------------
741bool TitanTCS::SendCommand(const char *cmd)
742{
743 if (isSimulation())
744 return false;
745
746 // tcflush(PortFD, TCIOFLUSH); // Error with Bluetooth!
747 ReadFlush();
748
749 int nbytes_written = 0;
750 int err_code;
751
752 if ((err_code = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written) != TTY_OK))
753 {
754 char titanfocus_error[256];
755 tty_error_msg(err_code, titanfocus_error, 256);
756 LOGF_ERROR("tty_write() error detected: %s", titanfocus_error);
757 return false;
758 }
759
760 // tcflush(PortFD, TCIOFLUSH); // Error with Bluetooth!
761
762 return true;
763}
764
765bool TitanTCS::SendCommand(const char *cmd, int val)
766{
767 char szBuff[128];
768 sprintf(szBuff, cmd, val);
769
770 return SendCommand(szBuff);
771}
772
773bool TitanTCS::SendCommand(const char *cmd, double val)
774{
775 char szBuff[128];
776 sprintf(szBuff, cmd, val);
777
778 return SendCommand(szBuff);
779}
780// -----------------------------------------------------------------------------
781void TitanTCS::ReadFlush()
782{
783 if (isSimulation())
784 return;
785
786 char buff[256];
787 int bytesRead = 0;
788
789 // tcflush(PortFD, TCIOFLUSH); // Error with Bluetooth!
790
791 for(int i = 0; i < 3; i++)
792 {
793 int err_code;
794 if ((err_code = tty_read(PortFD, buff, sizeof(buff) - 1, 0, &bytesRead)) != TTY_OK)
795 {
796 return;
797 }
798 if(bytesRead <= 0)
799 return;
800
801 buff[bytesRead] = 0;
802 LOGF_DEBUG("Buffer Flush '%s'", buff);
803 }
804}
805
806int TitanTCS::ReadResponse(char *buf, int len, char delimeter, int timeout)
807{
808 if (isSimulation())
809 return 0;
810
811 *buf = 0;
812 int bytesRead = 0;
813 int recv_len = 0;
814
815 // tcflush(PortFD, TCIOFLUSH); // Error with Bluetooth!
816
817 for(int i = 0; i < len; i++)
818 {
819 int err_code = tty_read(PortFD, buf + recv_len, 1, timeout, &bytesRead);
820 if (err_code != TTY_OK)
821 {
822 //if(err_code == TTY_TIME_OUT)
823 // continue;
824
825 char titanfocus_error[256] = {0};
826 tty_error_msg(err_code, titanfocus_error, 256);
827 LOGF_ERROR("tty_read() error detected: '%s' len %d, %s", buf, recv_len, titanfocus_error);
828 return -1;
829 }
830
831 char read_ch = buf[recv_len];
832 recv_len++;
833 buf[recv_len] = 0;
834
835 if(delimeter == 0)
836 {
837 if(recv_len >= len)
838 {
839 return recv_len;
840 }
841 }
842 else
843 {
844 if((recv_len + 1) >= len)
845 {
846 LOGF_ERROR("TTY error detected: overflow %d, %d", recv_len, len);
847 return 0;
848 }
849 }
850
851 if(delimeter)
852 {
853 if(read_ch == delimeter)
854 {
855 return recv_len;
856 }
857 }
858 }
859
860 return -1;
861}
862// -----------------------------------------------------------------------------
863bool TitanTCS::CommandResponse(const char* pCommand, const char* pResponse, char delimeter, double *pDouble, int *pInteger)
864{
865 ReadFlush();
866 if (!SendCommand(pCommand))
867 return false;
868
869 char szResponse[MAX_CMD_LEN] = { 0 };
870 char szResult[MAX_CMD_LEN] = { 0 };
871
872 int rd_count = ReadResponse(szResponse, sizeof(szResponse), delimeter);
873 if (rd_count <= 0)
874 {
875 LOGF_ERROR("No response '%s'", pCommand);
876 return false;
877 }
878 LOGF_DEBUG("ReadResponse('%s')", szResponse);
879
880 if(!GetParamNumber(szResponse, szResult, sizeof(szResult) - 1, pResponse, delimeter, pDouble, pInteger))
881 {
882 LOGF_DEBUG("CommandResponse('%s', '%s') Fail!", pCommand, pResponse);
883 return false;
884 }
885
886 return true;
887}
888
889bool TitanTCS::CommandResponseHour(const char* pCommand, const char* pResponse, char delimeter, double* Hour)
890{
891 ReadFlush();
892 if (!SendCommand(pCommand))
893 return false;
894
895 char szResponse[MAX_CMD_LEN] = { 0 };
896 char szResult[MAX_CMD_LEN] = { 0 };
897
898 int rd_count = ReadResponse(szResponse, sizeof(szResponse), delimeter);
899 if (rd_count <= 0)
900 {
901 LOGF_ERROR("No response '%s'", pCommand);
902 return false;
903 }
904 LOGF_DEBUG("ReadResponse('%s')", szResponse);
905
906 if(!GetParamHour(szResponse, szResult, sizeof(szResult) - 1, pResponse, delimeter, Hour))
907 {
908 LOGF_DEBUG("CommandResponseHour('%s', '%s') Fail!", pCommand, pResponse);
909 return false;
910 }
911
912 return true;
913}
914
915bool TitanTCS::CommandResponseStr(const char* pCommand, const char* pResponse, char delimeter, char* pReturn, int len)
916{
917 ReadFlush();
918 if (!SendCommand(pCommand))
919 return false;
920
921 char szResponse[MAX_CMD_LEN * 2] = { 0 };
922
923 int rd_count = ReadResponse(szResponse, sizeof(szResponse), delimeter);
924 if (rd_count <= 0)
925 {
926 LOGF_ERROR("No response '%s'", pCommand);
927 return false;
928 }
929 LOGF_DEBUG("ReadResponse('%s')", szResponse);
930
931 if(!GetParamStr(szResponse, pReturn, len, pResponse, delimeter))
932 {
933 LOGF_DEBUG("CommandResponseStr('%s', '%s') Fail!", pCommand, pResponse);
934 return false;
935 }
936
937 LOGF_DEBUG("%s : %s", pCommand, pReturn);
938
939 return true;
940}
941
942bool TitanTCS::CommandResponseChar(const char* pCommand, const char* pResponse, char* pReturn)
943{
944 ReadFlush();
945 if (!SendCommand(pCommand))
946 return false;
947
948 char szResponse[MAX_CMD_LEN] = { 0 };
949 char szResult[MAX_CMD_LEN] = { 0 };
950
951 int rd_count = ReadResponse(szResponse, 1, 0);
952 if (rd_count <= 0)
953 {
954 LOGF_ERROR("No response '%s'", pCommand);
955 return false;
956 }
957 //LOGF_DEBUG("ReadResponse('%s')", szResponse);
958
959 if(!GetParamStr(szResponse, szResult, sizeof(szResult) - 1, pResponse, 0))
960 {
961 LOGF_DEBUG("CommandResponseChar('%s', '%s') Fail!", pCommand, pResponse);
962 return false;
963 }
964
965 *pReturn = szResult[0];
966
967 LOGF_DEBUG("%s : %c", pCommand, *pReturn);
968
969 return true;
970}
971
972bool TitanTCS::SetTarget(double ra, double dec)
973{
974 char szRA[MAX_CMD_LEN], szDEC[MAX_CMD_LEN];
975 formatRA(ra * 3600, szRA);
976 formatDEC(dec * 3600, szDEC);
977
978 char rtnCode = 0;
979 char szCommand[MAX_CMD_LEN * 2];
980
981 sprintf(szCommand, "#:Sr %s#", szRA);
982 if(!CommandResponseChar(szCommand, "", &rtnCode))
983 {
984 LOG_ERROR("SetTarget RA / No response");
985 return false;
986 }
987 if(rtnCode != '1')
988 {
989 LOGF_ERROR("SetTarget DEC / Error Code = '%c'", rtnCode);
990 return false;
991 }
992
993 sprintf(szCommand, "#:Sd %s#", szDEC);
994 if(!CommandResponseChar(szCommand, "", &rtnCode))
995 {
996 LOG_ERROR("SetTarget DEC / No response");
997 return false;
998 }
999 if(rtnCode != '1')
1000 {
1001 LOGF_ERROR("SetTarget DEC / Error Code = '%c'", rtnCode);
1002 return false;
1003 }
1004
1005 LOGF_INFO("Set target RA:%s, DEC:%s", szRA, szDEC);
1006 return true;
1007}
1008
1009void TitanTCS::guideTimeoutNS()
1010{
1011 GuideNSNP[0].setValue(0);
1012 GuideNSNP[1].setValue(0);
1014 GuideNSTID = 0;
1015 GuideNSNP.apply();
1016}
1017
1018void TitanTCS::guideTimeoutWE()
1019{
1020 GuideWENP[0].setValue(0);
1021 GuideWENP[1].setValue(0);
1023 GuideWETID = 0;
1024 GuideWENP.apply();
1025}
1026
1027void TitanTCS::guideTimeoutHelperNS(void * p)
1028{
1029 static_cast<TitanTCS *>(p)->guideTimeoutNS();
1030}
1031
1032void TitanTCS::guideTimeoutHelperWE(void * p)
1033{
1034 static_cast<TitanTCS *>(p)->guideTimeoutWE();
1035}
1036
1037bool TitanTCS::GetMountParamsLight()
1038{
1039 // Lightweight polling during slewing — individual short LX200 queries only.
1040 // This avoids the composite \GE command which can overload the firmware's
1041 // command processor and cause RA motor stalls during high-speed slewing.
1042
1043 double ra = 0, dec = 0;
1044
1045 // Get RA via standard LX200 :GR# command
1046 if(!CommandResponseHour("#:GR#", "", '#', &ra))
1047 {
1048 LOG_ERROR("GetMountParamsLight: failed to read RA");
1049 return false;
1050 }
1051
1052 // Get DEC via standard LX200 :GD# command
1053 if(!CommandResponseHour("#:GD#", "", '#', &dec))
1054 {
1055 LOG_ERROR("GetMountParamsLight: failed to read DEC");
1056 return false;
1057 }
1058
1059 LOGF_DEBUG("RA %g, DEC %g", ra, dec);
1060 NewRaDec(ra, dec);
1061
1062 // Detect slew/park completion by watching for the position to stop changing.
1063 // We only fire the (heavy) composite status query once the mount has clearly
1064 // STOPPED — at that point the motors are idle and the composite command is
1065 // safe (it only causes stalls during active high-speed slewing).
1066 //
1067 // To avoid firing during a slow deceleration phase (where a single sample
1068 // might dip below threshold while still moving), we require TWO consecutive
1069 // stable samples before declaring the mount stopped.
1070 if(m_HaveLastPos)
1071 {
1072 double dRA = fabs(ra - m_LastRA);
1073 double dDEC = fabs(dec - m_LastDEC);
1074 // Handle RA wraparound across 0h/24h (e.g. crossing the pole)
1075 if(dRA > 12.0)
1076 dRA = 24.0 - dRA;
1077
1078 // Threshold ~0.01h (9 arcsec/poll) — well above sidereal drift (~0.07
1079 // arcmin/poll) but far below any real slew speed.
1080 bool stable = (dRA < 0.01 && dDEC < 0.01);
1081
1082 if(stable)
1083 {
1084 m_StableCount++;
1085 if(m_StableCount >= 2)
1086 {
1087 // Mount has stopped moving — safe to query full status now.
1088 m_HaveLastPos = false;
1089 m_StableCount = 0;
1090 LOG_DEBUG("Slew/park appears complete, querying full status");
1091 GetMountParams();
1092 return true;
1093 }
1094 }
1095 else
1096 {
1097 m_StableCount = 0;
1098 }
1099 }
1100
1101 m_LastRA = ra;
1102 m_LastDEC = dec;
1103 m_HaveLastPos = true;
1104
1105 return true;
1106}
1107
1108bool TitanTCS::GetMountParams(bool bAll)
1109{
1110 INDI_UNUSED(bAll);
1111
1112 static int cnt = 0;
1113
1114 char szCommand[256];
1115 sprintf(szCommand, "#:\\GE($GR #:GR#"
1116 ":\\GE$GD #:GD#"
1117 "#:hP?#"
1118 ":\\?pe#"
1119 ":\\?tm#"
1120 ":\\?tr#"
1121 ":\\?ts#"
1122 ":\\GE%d)#", cnt++);
1123
1124 //strcpy(szCommand, "#:hP?#" ":\\GE}#");
1125
1126 char szResponse[256];
1127 if(!CommandResponseStr(szCommand, "(", ')', szResponse, sizeof(szResponse) - 1))
1128 return false;
1129
1130 char szToken[128];
1131 // RA & DEC Coordinate
1132 if(GetParamHour(szResponse, szToken, sizeof(szToken) - 1, "$GR", '#', &info.ra))
1133 {
1134 if(GetParamHour(szResponse, szToken, sizeof(szToken) - 1, "$GD", '#', &info.dec))
1135 {
1136 LOGF_DEBUG("RA %g, DEC %g", info.ra, info.dec);
1137 NewRaDec(info.ra, info.dec);
1138
1139 // Report pier side for Ekos meridian-flip automation. This mount's
1140 // LX200 subset exposes no pier-side query (the ASCOM driver derives
1141 // it from a separate encoder protocol we don't use), so we report the
1142 // INDI-convention expected side computed from the hour angle:
1143 // HA <= 0 -> PIER_WEST, HA > 0 -> PIER_EAST.
1144 // NOTE: this is the INDI convention Ekos expects; its label reads
1145 // OPPOSITE to the ASCOM app's SideOfPier for the same position — that
1146 // is a known INDI/ASCOM naming difference, not an error.
1148 }
1149 }
1150
1151#if USE_PEC
1152 // PEC Status
1153 if(GetParamNumber(szResponse, szToken, sizeof(szToken) - 1, "$?pe", '#', NULL, &info.PECStatus))
1154 {
1155 LOGF_DEBUG("PEC Status %d", info.PECStatus);
1156 _setPECState(info.PECStatus);
1157 }
1158#endif
1159
1160 // Slewing Status bit0:RA Tracking, bit1:DEC Tracking, bit2:RA Slewing, bit3:DEC Slewing, bit4,5:Goto status
1161 if(GetParamNumber(szResponse, szToken, sizeof(szToken) - 1, "$?ts", '#', NULL, &info.TrackingStatus))
1162 {
1163 LOGF_DEBUG("Tracking Status %d", info.TrackingStatus);
1164
1165 // Bits 2-3 indicate actual axis motion (RA/DEC slewing).
1166 // Bits 4-5 are goto status flags. Bit5 alone (value 0x20) can
1167 // indicate a goto-error/pending state where the mount is NOT
1168 // physically moving. Only treat as SLEWING when axes are
1169 // actually in motion (bits 2-3) or a valid goto is active
1170 // (bit4 set together with motion).
1171 bool axisMoving = (info.TrackingStatus & 0x0C) != 0; // bit2 or bit3: RA/DEC slew
1172 bool gotoActive = (info.TrackingStatus & 0x30) != 0; // bit4 or bit5: goto status
1173
1174 if(axisMoving)
1175 {
1177 }
1178 else if(gotoActive && !(info.TrackingStatus & 0x03))
1179 {
1180 // Goto flag set but no tracking bits — mount stopped, error state
1181 LOGF_WARN("Goto error state detected (tracking status %d), sending abort", info.TrackingStatus);
1182 SendCommand("#:Q#");
1184 }
1185 else if(gotoActive && (info.TrackingStatus & 0x03))
1186 {
1187 // Goto flag set but mount is still tracking (e.g. status 35 = 0x23)
1188 // This is the goto-error state where axes track but goto failed.
1189 // Clear it and remain in tracking.
1190 LOGF_WARN("Goto error flag with tracking active (status %d), sending abort", info.TrackingStatus);
1191 SendCommand("#:Q#");
1193 }
1194 else if(info.TrackingStatus == 3)
1195 {
1197 }
1198 else
1199 {
1201 }
1202 }
1203 // Parking Status
1204 if(GetParamNumber(szResponse, szToken, sizeof(szToken) - 1, "$hP", '#', NULL, &info.Parking))
1205 {
1206 LOGF_DEBUG("Parking Status %d", info.Parking);
1207
1208 if(info.Parking == 1)
1209 {
1211 IsParked = false;
1215 IUSaveText(&MountInfoT[0], "Parking");
1216 }
1217 else if(info.Parking == 2)
1218 {
1220 IsParked = true;
1224 IUSaveText(&MountInfoT[0], "Parked");
1225 }
1226 else if(info.Parking == 0)
1227 {
1228 IsParked = false;
1232 IUSaveText(&MountInfoT[0], "Unpark");
1233 }
1234
1235 ParkSP.apply();
1236 }
1237 // Tracking On / Off
1239 {
1244
1246 IUSaveText(&MountInfoT[1], "Parking");
1247 else if(TrackState == SCOPE_PARKED)
1248 IUSaveText(&MountInfoT[1], "Parked");
1249 else if(TrackState == SCOPE_SLEWING)
1250 IUSaveText(&MountInfoT[1], "Slewing");
1251 }
1252 else
1253 {
1254 if(GetParamNumber(szResponse, szToken, sizeof(szToken) - 1, "$?tm", '#', NULL, &info.Landscape))
1255 {
1256 LOGF_DEBUG("? %d, %d", TrackState, info.Landscape);
1257
1258 if((TrackState == SCOPE_TRACKING) && (info.Landscape == 0))
1259 {
1264
1265 IUSaveText(&MountInfoT[1], "Tracking ON / Skyview");
1266 }
1267 else
1268 {
1272 TrackStateSP.apply( nullptr);
1273
1274 if(info.Landscape == 1)
1275 IUSaveText(&MountInfoT[1], "Tracking OFF / Landscape");
1276 else
1277 IUSaveText(&MountInfoT[1], "Tracking OFF / Idle");
1278 }
1279 }
1280 }
1281 //
1282 MountInfoTP.s = IPS_OK;
1283 IDSetText(&MountInfoTP, nullptr);
1284 //
1285 if(GetParamNumber(szResponse, szToken, sizeof(szToken) - 1, "$?tr", '#', NULL, &info.TrackingRate))
1286 {
1287 LOGF_DEBUG("Tracking rate %d", info.TrackingRate);
1288
1293 }
1294 //
1295 static int prev_TrackState = -1;
1296 if(prev_TrackState != TrackState)
1297 {
1298 prev_TrackState = TrackState;
1299
1300 switch(TrackState)
1301 {
1302 case SCOPE_IDLE:
1303 LOG_INFO("Track State : IDLE");
1304 break;
1305 case SCOPE_SLEWING:
1306 LOG_INFO("Track State : SLEWING");
1307 break;
1308 case SCOPE_TRACKING:
1309 LOG_INFO("Track State : TRACKING");
1310 break;
1311 case SCOPE_PARKING:
1312 LOG_INFO("Track State : PARKING");
1313 break;
1314 case SCOPE_PARKED:
1315 LOG_INFO("Track State : PARKED");
1316 break;
1317 }
1318 }
1319
1320 return true;
1321}
1322#if USE_PEC
1323void TitanTCS::_setPECState(int pec_status)
1324{
1325 if (_PECStatus != pec_status)
1326 {
1327 //LOGF_INFO("_setPECState(%x)", pec_status);
1328 char szText[128];
1329
1330 _PECStatus = pec_status;
1331 // PEC Enabled BIT 0
1332 // PEC Valid BIT 1
1333 // PEC Training BIT 2
1334 // PEC Stopping BIT 3
1335
1336 if(pec_status & 0x30)
1337 {
1338 // Training ...
1339 PECTrainingS[0].s = ISS_OFF;
1340 PECTrainingS[1].s = ISS_ON;
1341
1342 sprintf(szText, "PEC Training %d %%", (pec_status >> 8));
1343 IUSaveText(&PECInfoT[1], szText);
1344 }
1345 else
1346 {
1347 PECTrainingS[0].s = ISS_ON;
1348 PECTrainingS[1].s = ISS_OFF;
1349
1350 IUSaveText(&PECInfoT[1], "");
1351 }
1352
1353 if(pec_status & 2)
1354 {
1355 // Valid
1356 if(pec_status & 1)
1357 {
1360
1361 IUSaveText(&PECInfoT[0], "PEC is running.");
1362 }
1363 else
1364 {
1367
1368 IUSaveText(&PECInfoT[0], "PEC is available.");
1369 }
1371 }
1372 else
1373 {
1374 // Invalid
1378
1379 if(pec_status & 0x30)
1380 IUSaveText(&PECInfoT[0], "");
1381 else
1382 IUSaveText(&PECInfoT[0], "PEC training is required.");
1383 }
1384
1386 PECStateSP.apply();
1387
1388 PECTrainingSP.s = IPS_OK;
1389 IDSetSwitch(&PECTrainingSP, nullptr);
1390 //
1391 PECInfoTP.s = IPS_OK;
1392 IDSetText(&PECInfoTP, nullptr);
1393 }
1394}
1395#endif
1396
1397// -----------------------------------------------------------------------------
1398bool TitanTCS::updateTime(ln_date *utc, double utc_offset)
1399{
1400 ln_zonedate ltm;
1401
1402 if (isSimulation())
1403 return true;
1404
1405 double JD = ln_get_julian_day(utc);
1406
1407 LOGF_DEBUG("New JD is %.2f", JD);
1408
1409 ln_date_to_zonedate(utc, &ltm, utc_offset * 3600);
1410
1411 LOGF_DEBUG("Local time is %02d:%02d:%02g", ltm.hours, ltm.minutes, ltm.seconds);
1412
1413 char szText[128];
1414 sprintf(szText, "#:SG %.1f#:SC %02d/%02d/%02d#:SL %02d:%02d:%02d#",
1415 -utc_offset,
1416 ltm.months, ltm.days, ltm.years % 100,
1417 ltm.hours, ltm.minutes, (int)ltm.seconds % 60);
1418
1419 LOGF_INFO("Set datetime '%s'", szText);
1420
1421 return SendCommand(szText);
1422}
1423
1424// -----------------------------------------------------------------------------
1425bool TitanTCS::updateLocation(double latitude, double longitude, double elevation)
1426{
1427 INDI_UNUSED(elevation);
1428
1429 if((fabs(latitude) < 0.001) && (fabs(longitude) < 0.001))
1430 return false;
1431
1432 int d = 0, m = 0, s = 0;
1433 char szCommand[128];
1434 char rtnCode = 0;
1435
1436 getSexComponents(latitude, &d, &m, &s);
1437 sprintf(szCommand, "#:St %03d:%02d:%02d#", d, m, s);
1438 LOGF_INFO("Set latitude '%s'", szCommand);
1439 if(!CommandResponseChar(szCommand, NULL, &rtnCode))
1440 return false;
1441
1442 getSexComponents(-longitude, &d, &m, &s);
1443 sprintf(szCommand, "#:Sg %03d:%02d:%02d#", d, m, s);
1444 LOGF_INFO("Set longitude '%s'", szCommand);
1445 return CommandResponseChar(szCommand, NULL, &rtnCode);
1446}
1447
1448bool TitanTCS::Sync(double ra, double dec)
1449{
1450 if(!SetTarget(ra, dec))
1451 return false;
1452
1453 char rtnCode[64] = { "" };
1454 char szCommand[MAX_CMD_LEN];
1455 sprintf(szCommand, ":CM#");
1456
1457 if(!CommandResponseStr(szCommand, "", '#', rtnCode, sizeof(rtnCode) - 1))
1458 {
1459 LOG_ERROR("Sync / No response");
1460 return false;
1461 }
1462 if(strcmp(rtnCode, "1") != 0)
1463 {
1464 LOGF_ERROR("Sync / Error Code = '%s'", rtnCode);
1465 return false;
1466 }
1467
1468 LOG_INFO("Sync");
1469 return true;
1470}
1471// -----------------------------------------------------------------------------
1473{
1474 char chDir = 0;
1475
1476 switch(dir)
1477 {
1478 case DIRECTION_NORTH:
1479 chDir = 'n';
1480 break;
1481 case DIRECTION_SOUTH:
1482 chDir = 's';
1483 break;
1484 default:
1485 return false;
1486 break;
1487 }
1488
1489 char szCommand[MAX_CMD_LEN];
1490 if(command == MOTION_START)
1491 {
1492 sprintf(szCommand, ":M%c#", chDir);
1493 }
1494 else
1495 {
1496 sprintf(szCommand, ":Q%c#", chDir);
1497 }
1498
1499 //TrackState = SCOPE_SLEWING;
1500 LOGF_INFO("Moving command:%s", szCommand);
1501 return SendCommand(szCommand);
1502}
1503
1504// -----------------------------------------------------------------------------
1506{
1507 char chDir = 0;
1508
1509 switch(dir)
1510 {
1511 case DIRECTION_EAST:
1512 chDir = 'e';
1513 break;
1514 case DIRECTION_WEST:
1515 chDir = 'w';
1516 break;
1517 default:
1518 return false;
1519 break;
1520 }
1521
1522 char szCommand[MAX_CMD_LEN];
1523 if(command == MOTION_START)
1524 {
1525 sprintf(szCommand, ":M%c#", chDir);
1526 }
1527 else
1528 {
1529 sprintf(szCommand, ":Q%c#", chDir);
1530 }
1531
1532 //TrackState = SCOPE_SLEWING;
1533 LOGF_INFO("Moving command:%s", szCommand);
1534 return SendCommand(szCommand);
1535}
1536// -----------------------------------------------------------------------------
1537// Park. Two modes, selectable via the "Park" property on the Options tab, using
1538// the mount's own firmware park commands (decoded from the TitanTCS ASCOM driver
1539// serial trace):
1540//
1541// PARK_MODE_AT_SAVED (default): ':hP1#' — the firmware moves to the saved park
1542// point and parks. The park point is stored in the mount (see SetCurrentPark,
1543// ':hS#'). The firmware knows the true MECHANICAL position, so this reaches
1544// the correct home on the correct side of the pier every time — something the
1545// driver cannot compute from sky coordinates near the pole.
1546//
1547// PARK_MODE_AT_CURRENT: ':hP8#' — park (lock) wherever the mount currently points.
1549{
1550 bool atCurrent = (ParkModeSP.findOnSwitchIndex() == PARK_MODE_AT_CURRENT);
1551 const char *cmd = atCurrent ? ":hP8#" : ":hP1#";
1552
1553 LOGF_INFO("Parking (%s) ...", atCurrent ? "at current position" : "to saved park point");
1554
1555 if(!SendCommand(cmd))
1556 return false;
1557
1560 // The firmware performs any slew and the lock itself. Reset the lightweight
1561 // poll detector so completion (mount stopped + $hP=2) transitions to PARKED.
1562 m_HaveLastPos = false;
1563 m_StableCount = 0;
1564 return true;
1565}
1566// -----------------------------------------------------------------------------
1568{
1569 LOG_INFO("Unparking ...");
1570
1571 if(SendCommand(":hP0#"))
1572 {
1574 // Set to IDLE temporarily — GetMountParams() will update to
1575 // SCOPE_TRACKING once the mount reports tracking status 3.
1577 SetParked(false);
1578 return true;
1579 }
1580 return false;
1581}
1582
1583// -----------------------------------------------------------------------------
1584bool TitanTCS::SetTrackMode(uint8_t mode)
1585{
1586 LOGF_INFO("SetTrackMode(%d)", mode);
1587 return SendCommand("#:\\T%d#", mode);
1588}
1589// -----------------------------------------------------------------------------
1590/*
1591T.B.D
1592bool TitanTCS::SetTrackRate(double raRate, double deRate)
1593{
1594 return true;
1595}
1596*/
1597// -----------------------------------------------------------------------------
1599{
1600 if(enabled)
1601 {
1602 LOG_INFO("Tracking ON");
1603 return SendCommand("#:\\t0#"); // Tracking
1604 }
1605 else
1606 {
1607 LOG_INFO("Tracking OFF");
1608 return SendCommand("#:\\t1#"); // Stop
1609 }
1610}
1611
1612// -----------------------------------------------------------------------------
1613bool TitanTCS::SetParkPosition(double Axis1Value, double Axis2Value)
1614{
1615 // Park data type is PARK_HA_DEC, so Axis1 = Hour Angle, Axis2 = DEC.
1616 // The base class stores these values; the driver just accepts them.
1617 SetAxis1Park(Axis1Value);
1618 SetAxis2Park(Axis2Value);
1619 LOGF_INFO("Park position set to HA %.4f h, DEC %.4f deg", Axis1Value, Axis2Value);
1620 return true;
1621}
1622
1623// -----------------------------------------------------------------------------
1624// Save the CURRENT mount position as the park position.
1625// Park data type is PARK_HA_DEC → Axis1 = Hour Angle, Axis2 = DEC.
1627{
1628 // Save the current mount position as the firmware's park point using ':hS#'
1629 // (the same command the TitanTCS controller and ASCOM driver use — the
1630 // controller beeps and shows "Park point saved"). The firmware stores the
1631 // true mechanical position, which is then reached by ':hP1#' on park. This is
1632 // far more reliable than storing sky coordinates, which are degenerate at the
1633 // pole where this mount homes.
1634 if(!SendCommand(":hS#"))
1635 {
1636 LOG_ERROR("SetCurrentPark: failed to send save-park command (:hS#)");
1637 return false;
1638 }
1639 LOG_INFO("Saved current position as the mount's park point (:hS#).");
1640
1641 // Keep INDI's park-position fields in sync for display only (not used for the
1642 // actual firmware park, which uses the mount's own stored point).
1643 double lst = get_local_sidereal_time(LocationNP[LOCATION_LONGITUDE].getValue());
1644 double ha = get_local_hour_angle(lst, info.ra);
1645 SetAxis1Park(ha);
1646 SetAxis2Park(info.dec);
1647 return true;
1648}
1649// -----------------------------------------------------------------------------
1650// Default park position: the celestial pole (this mount physically parks
1651// pointing north). HA is arbitrary at the pole, so use 0.
1653{
1654 double poleDec = (LocationNP[LOCATION_LATITUDE].getValue() >= 0) ? 90.0 : -90.0;
1655
1656 SetAxis1Park(0.0);
1657 SetAxis2Park(poleDec);
1658
1659 LOGF_INFO("Default park position set: HA 0.0 h, DEC %.1f deg (pole)", poleDec);
1660 return true;
1661}
1662
1664{
1665 LOGF_INFO("Set Slew Rate '%d'", index);
1666
1667 switch (index)
1668 {
1669 case 3:
1670 return SendCommand(":RS#");
1671 case 2:
1672 return SendCommand(":RM#");
1673 case 1:
1674 return SendCommand(":RC#");
1675 case 0:
1676 return SendCommand(":RG#");
1677 }
1678 return false;
1679}
bool isConnected() 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
uint32_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
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
bool load()
load Attempt to load property values from configuration file.
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
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.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
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....
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.
TelescopePierSide expectedPierSide(double ra)
Calculate the expected pier side for scopes that do not report this property themselves.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool Disconnect() override
Disconnect from device.
virtual bool Park() override
Park the telescope to its home position.
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
virtual bool Handshake() override
perform handshake with device to check communication
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool ISNewText(const char *dev, const char *name, char **texts, char **names, int n) override
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Start or Stop the telescope motion in the direction dir.
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
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 SetParkPosition(double Axis1Value, double Axis2Value) override
SetParkPosition Set desired parking position to the supplied value. This ONLY sets the desired park p...
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
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 const char * getDefaultName() override
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
virtual bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
#define HANDSHAKE_NAME
#define MAX_CMD_LEN
#define PRODUCT_NAME
#define MIN_FW_VERSION
#define USE_PEC
Definition crux_mount.h:27
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.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
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
INDI::PropertySwitch ParkSP
INDI::PropertySwitch MovementNSSP
INDI::PropertySwitch TrackStateSP
INDI::PropertySwitch PECStateSP
INDI::PropertySwitch TrackModeSP
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
@ 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
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
void getSexComponents(double value, int *d, int *m, int *s)
Definition indicom.c:243
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
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.
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition indidevapi.c:337
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
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition indidevapi.c:44
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 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
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
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 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 getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:369
char name[MAXINDINAME]
Definition indiapi.h:248
int Landscape
Definition crux_mount.h:38
int TrackingRate
Definition crux_mount.h:39
int TrackingStatus
Definition crux_mount.h:40
double dec
Definition crux_mount.h:32
double ra
Definition crux_mount.h:31
int PECStatus
Definition crux_mount.h:36