Instrument Neutral Distributed Interface INDI  2.0.2
lx200telescope.cpp
Go to the documentation of this file.
1 /*
2  * Standard LX200 implementation.
3 
4  Copyright (C) 2003 - 2018 Jasem Mutlaq (mutlaqja@ikarustech.com)
5  Minor changes Copyright (C) 2021 James Lancaster
6 
7  This library is free software;
8  you can redistribute it and / or
9  modify it under the terms of the GNU Lesser General Public
10  License as published by the Free Software Foundation;
11  either
12  version 2.1 of the License, or (at your option) any later version.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY;
16  without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  Lesser General Public License for more details.
19 
20  You should have received a copy of the GNU Lesser General Public
21  License along with this library;
22  if not, write to the Free Software
23  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA
24  */
25 
26 #include "lx200telescope.h"
27 
28 #include "indicom.h"
29 #include "lx200driver.h"
30 
31 #include <libnova/sidereal_time.h>
32 
33 #include <cmath>
34 #include <memory>
35 #include <cstring>
36 #include <unistd.h>
37 #include <time.h>
38 
39 /* Simulation Parameters */
40 #define LX200_GENERIC_SLEWRATE 5 /* slew rate, degrees/s */
41 #define SIDRATE 0.004178 /* sidereal rate, degrees/s */
42 
44 {
45 }
46 
48 {
49  INDI_UNUSED(enable);
51 }
52 
54 {
55  return getDefaultName();
56 }
57 
59 {
60  return static_cast<const char *>("Standard LX200");
61 }
62 
63 
65 {
66  /* Make sure to init parent properties first */
68 
69  IUFillSwitch(&AlignmentS[0], "Polar", "", ISS_ON);
70  IUFillSwitch(&AlignmentS[1], "AltAz", "", ISS_OFF);
71  IUFillSwitch(&AlignmentS[2], "Land", "", ISS_OFF);
73  ISR_1OFMANY, 0, IPS_IDLE);
74 
75 #if 0
76  IUFillSwitch(&SlewRateS[SLEW_GUIDE], "SLEW_GUIDE", "Guide", ISS_OFF);
77  IUFillSwitch(&SlewRateS[SLEW_CENTERING], "SLEW_CENTERING", "Centering", ISS_OFF);
78  IUFillSwitch(&SlewRateS[SLEW_FIND], "SLEW_FIND", "Find", ISS_OFF);
79  IUFillSwitch(&SlewRateS[SLEW_MAX], "SLEW_MAX", "Max", ISS_ON);
80  IUFillSwitchVector(&SlewRateSP, SlewRateS, 4, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB, IP_RW,
81  ISR_1OFMANY, 0, IPS_IDLE);
82 #endif
83 
84 #if 0
85  IUFillSwitch(&TrackModeS[0], "TRACK_SIDEREAL", "Sidereal", ISS_ON);
86  IUFillSwitch(&TrackModeS[1], "TRACK_SOLAR", "Solar", ISS_OFF);
87  IUFillSwitch(&TrackModeS[2], "TRACK_LUNAR", "Lunar", ISS_OFF);
88  IUFillSwitch(&TrackModeS[3], "TRACK_CUSTOM", "Custom", ISS_OFF);
89  IUFillSwitchVector(&TrackModeSP, TrackModeS, 4, getDeviceName(), "TELESCOPE_TRACK_MODE", "Track Mode", MOTION_TAB,
91 #endif
92 
93  AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
94  AddTrackMode("TRACK_SOLAR", "Solar");
95  AddTrackMode("TRACK_LUNAR", "Lunar");
96  AddTrackMode("TRACK_CUSTOM", "Custom");
97 
99  {
100  IUFillNumber(&TrackFreqN[0], "trackFreq", "Freq", "%g", 55, 65, 0.00001, 60.16427);
101  }
102  else
103  {
104  IUFillNumber(&TrackFreqN[0], "trackFreq", "Freq", "%g", 56.4, 60.1, 0.1, 60.1);
105  }
106  IUFillNumberVector(&TrackFreqNP, TrackFreqN, 1, getDeviceName(), "Tracking Frequency", "", MOTION_TAB, IP_RW, 0,
107  IPS_IDLE);
108 
109  IUFillSwitch(&UsePulseCmdS[0], "Off", "Off", ISS_OFF);
110  IUFillSwitch(&UsePulseCmdS[1], "On", "On", ISS_ON);
112  ISR_1OFMANY, 0, IPS_IDLE);
113 
114  int selectedSite = 0;
115  IUGetConfigOnSwitchIndex(getDeviceName(), "Sites", &selectedSite);
116  IUFillSwitch(&SiteS[0], "Site 1", "Site 1", selectedSite == 0 ? ISS_ON : ISS_OFF);
117  IUFillSwitch(&SiteS[1], "Site 2", "Site 2", selectedSite == 1 ? ISS_ON : ISS_OFF);
118  IUFillSwitch(&SiteS[2], "Site 3", "Site 3", selectedSite == 2 ? ISS_ON : ISS_OFF);
119  IUFillSwitch(&SiteS[3], "Site 4", "Site 4", selectedSite == 3 ? ISS_ON : ISS_OFF);
121 
122  char siteName[64] = {"NA"};
123  IUGetConfigText(getDeviceName(), "Site Name", "Name", siteName, 64);
124  IUFillText(&SiteNameT[0], "Name", "Name", siteName);
125  IUFillTextVector(&SiteNameTP, SiteNameT, 1, getDeviceName(), "Site Name", "Site Name", SITE_TAB, IP_RW, 0, IPS_IDLE);
126 
127 
129  {
132 
133  // IUFillSwitch(&FocusModeS[0], "FOCUS_HALT", "Halt", ISS_ON);
134  // IUFillSwitch(&FocusModeS[1], "FOCUS_SLOW", "Slow", ISS_OFF);
135  // IUFillSwitch(&FocusModeS[2], "FOCUS_FAST", "Fast", ISS_OFF);
136  // IUFillSwitchVector(&FocusModeSP, FocusModeS, 3, getDeviceName(), "FOCUS_MODE", "Mode", FOCUS_TAB, IP_RW,
137  // ISR_1OFMANY, 0, IPS_IDLE);
138 
139  // Classical speeds slow or fast
140  FocusSpeedN[0].min = 1;
141  FocusSpeedN[0].max = 2;
142  FocusSpeedN[0].value = 1;
144  }
145 
147 
149 
150  /* Add debug/simulation/config controls so we may debug driver if necessary */
151  addAuxControls();
152 
154 
155  if (m_Location.longitude > 0)
157  if (m_Location.latitude != 0)
158  currentDEC = m_Location.latitude > 0 ? 90 : -90;
159  return true;
160 }
161 
162 void LX200Telescope::ISGetProperties(const char *dev)
163 {
164  if (dev != nullptr && strcmp(dev, getDeviceName()) != 0)
165  return;
166 
168 
169  /*
170  if (isConnected())
171  {
172  if (genericCapability & LX200_HAS_ALIGNMENT_TYPE)
173  defineProperty(&AlignmentSP);
174 
175  if (genericCapability & LX200_HAS_TRACKING_FREQ)
176  defineProperty(&TrackFreqNP);
177 
178  if (genericCapability & LX200_HAS_PULSE_GUIDING)
179  defineProperty(&UsePulseCmdSP);
180 
181  if (genericCapability & LX200_HAS_SITES)
182  {
183  defineProperty(&SiteSP);
184  defineProperty(&SiteNameTP);
185  }
186 
187  defineProperty(&GuideNSNP);
188  defineProperty(&GuideWENP);
189 
190  if (genericCapability & LX200_HAS_FOCUS)
191  {
192  defineProperty(&FocusMotionSP);
193  defineProperty(&FocusTimerNP);
194  defineProperty(&FocusModeSP);
195  }
196  }
197  */
198 }
199 
201 {
203 
204  if (isConnected())
205  {
208 
211 
214 
216  {
219  }
220 
223 
225  {
227  //defineProperty(&FocusModeSP);
228  }
229 
230  getBasicData();
231  }
232  else
233  {
236 
239 
242 
244  {
247  }
248 
251 
253  {
255  //deleteProperty(FocusModeSP.name);
256  }
257  }
258 
259  return true;
260 }
261 
263 {
264  if (isSimulation())
265  return true;
266 
267  return (check_lx200_connection(PortFD) == 0);
268 }
269 
271 {
272  return checkConnection();
273 }
274 
276 {
277  return (::isSlewComplete(PortFD) == 1);
278 }
279 
281 {
282  if (!isConnected())
283  return false;
284 
285  if (isSimulation())
286  {
287  mountSim();
288  return true;
289  }
290 
291  //if (check_lx200_connection(PortFD))
292  //return false;
293 
294  if (TrackState == SCOPE_SLEWING)
295  {
296  // Check if LX200 is done slewing
297  if (isSlewComplete())
298  {
299  // Set slew mode to "Centering"
302  IDSetSwitch(&SlewRateSP, nullptr);
303 
305  LOG_INFO("Slew is complete. Tracking...");
306  }
307  }
308  else if (TrackState == SCOPE_PARKING)
309  {
310  if (isSlewComplete())
311  {
312  SetParked(true);
313  }
314  }
315 
317  {
318  EqNP.s = IPS_ALERT;
319  IDSetNumber(&EqNP, "Error reading RA/DEC.");
320  return false;
321  }
322 
324 
325  return true;
326 }
327 
328 bool LX200Telescope::Goto(double ra, double dec)
329 {
330  const struct timespec timeout = {0, 100000000L};
331 
332  targetRA = ra;
333  targetDEC = dec;
334  char RAStr[64] = {0}, DecStr[64] = {0};
335  int fracbase = 0;
336 
337  switch (getLX200EquatorialFormat())
338  {
340  fracbase = 360000;
341  break;
344  default:
345  fracbase = 3600;
346  break;
347  }
348 
349  fs_sexa(RAStr, targetRA, 2, fracbase);
350  fs_sexa(DecStr, targetDEC, 2, fracbase);
351 
352  // If moving, let's stop it first.
353  if (EqNP.s == IPS_BUSY)
354  {
355  if (!isSimulation() && abortSlew(PortFD) < 0)
356  {
357  AbortSP.s = IPS_ALERT;
358  IDSetSwitch(&AbortSP, "Abort slew failed.");
359  return false;
360  }
361 
362  AbortSP.s = IPS_OK;
363  EqNP.s = IPS_IDLE;
364  IDSetSwitch(&AbortSP, "Slew aborted.");
365  IDSetNumber(&EqNP, nullptr);
366 
368  {
371  EqNP.s = IPS_IDLE;
374  IDSetSwitch(&MovementNSSP, nullptr);
375  IDSetSwitch(&MovementWESP, nullptr);
376  }
377 
378  // sleep for 100 mseconds
379  nanosleep(&timeout, nullptr);
380  }
381 
382  if (!isSimulation())
383  {
385  {
386  EqNP.s = IPS_ALERT;
387  IDSetNumber(&EqNP, "Error setting RA/DEC.");
388  return false;
389  }
390 
391  int err = 0;
392 
393  /* Slew reads the '0', that is not the end of the slew */
394  if ((err = Slew(PortFD)))
395  {
396  LOGF_ERROR("Error Slewing to JNow RA %s - DEC %s", RAStr, DecStr);
397  slewError(err);
398  return false;
399  }
400  }
401 
403  //EqNP.s = IPS_BUSY;
404 
405  LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
406 
407  return true;
408 }
409 
410 bool LX200Telescope::Sync(double ra, double dec)
411 {
412  char syncString[256] = {0};
413 
414  if (!isSimulation() && (setObjectRA(PortFD, ra) < 0 || (setObjectDEC(PortFD, dec)) < 0))
415  {
416  EqNP.s = IPS_ALERT;
417  IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync.");
418  return false;
419  }
420 
421  if (!isSimulation() && ::Sync(PortFD, syncString) < 0)
422  {
423  EqNP.s = IPS_ALERT;
424  IDSetNumber(&EqNP, "Synchronization failed.");
425  return false;
426  }
427 
428  currentRA = ra;
429  currentDEC = dec;
430 
431  LOG_INFO("Synchronization successful.");
432 
433  EqNP.s = IPS_OK;
434 
436 
437  return true;
438 }
439 
441 {
442  const struct timespec timeout = {0, 100000000L};
443  if (!isSimulation())
444  {
445  // If scope is moving, let's stop it first.
446  if (EqNP.s == IPS_BUSY)
447  {
448  if (!isSimulation() && abortSlew(PortFD) < 0)
449  {
450  AbortSP.s = IPS_ALERT;
451  IDSetSwitch(&AbortSP, "Abort slew failed.");
452  return false;
453  }
454 
455  AbortSP.s = IPS_OK;
456  EqNP.s = IPS_IDLE;
457  IDSetSwitch(&AbortSP, "Slew aborted.");
458  IDSetNumber(&EqNP, nullptr);
459 
461  {
464  EqNP.s = IPS_IDLE;
467 
468  IDSetSwitch(&MovementNSSP, nullptr);
469  IDSetSwitch(&MovementWESP, nullptr);
470  }
471 
472  // sleep for 100 msec
473  nanosleep(&timeout, nullptr);
474  }
475 
476  if (!isSimulation() && slewToPark(PortFD) < 0)
477  {
478  ParkSP.s = IPS_ALERT;
479  IDSetSwitch(&ParkSP, "Parking Failed.");
480  return false;
481  }
482  }
483 
485  LOG_INFO("Parking telescope in progress...");
486  return true;
487 }
488 
490 {
491  int current_move = (dir == DIRECTION_NORTH) ? LX200_NORTH : LX200_SOUTH;
492 
493  switch (command)
494  {
495  case MOTION_START:
496  if (!isSimulation() && MoveTo(PortFD, current_move) < 0)
497  {
498  LOG_ERROR("Error setting N/S motion direction.");
499  return false;
500  }
501  else
502  LOGF_DEBUG("Moving toward %s.",
503  (current_move == LX200_NORTH) ? "North" : "South");
504  break;
505 
506  case MOTION_STOP:
507  if (!isSimulation() && HaltMovement(PortFD, current_move) < 0)
508  {
509  LOG_ERROR("Error stopping N/S motion.");
510  return false;
511  }
512  else
513  LOGF_DEBUG("Movement toward %s halted.",
514  (current_move == LX200_NORTH) ? "North" : "South");
515  break;
516  }
517 
518  return true;
519 }
520 
522 {
523  int current_move = (dir == DIRECTION_WEST) ? LX200_WEST : LX200_EAST;
524 
525  switch (command)
526  {
527  case MOTION_START:
528  if (!isSimulation() && MoveTo(PortFD, current_move) < 0)
529  {
530  LOG_ERROR("Error setting W/E motion direction.");
531  return false;
532  }
533  else
534  LOGF_DEBUG("Moving toward %s.", (current_move == LX200_WEST) ? "West" : "East");
535  break;
536 
537  case MOTION_STOP:
538  if (!isSimulation() && HaltMovement(PortFD, current_move) < 0)
539  {
540  LOG_ERROR("Error stopping W/E motion.");
541  return false;
542  }
543  else
544  LOGF_DEBUG("Movement toward %s halted.",
545  (current_move == LX200_WEST) ? "West" : "East");
546  break;
547  }
548 
549  return true;
550 }
551 
553 {
554  if (!isSimulation() && abortSlew(PortFD) < 0)
555  {
556  LOG_ERROR("Failed to abort slew.");
557  return false;
558  }
559 
560  if (GuideNSNP.s == IPS_BUSY || GuideWENP.s == IPS_BUSY)
561  {
563  GuideNSN[0].value = GuideNSN[1].value = 0.0;
564  GuideWEN[0].value = GuideWEN[1].value = 0.0;
565 
566  if (GuideNSTID)
567  {
569  GuideNSTID = 0;
570  }
571 
572  if (GuideWETID)
573  {
575  GuideNSTID = 0;
576  }
577 
578  LOG_INFO("Guide aborted.");
579  IDSetNumber(&GuideNSNP, nullptr);
580  IDSetNumber(&GuideWENP, nullptr);
581 
582  return true;
583  }
584 
585  return true;
586 }
587 
588 bool LX200Telescope::setLocalDate(uint8_t days, uint8_t months, uint16_t years)
589 {
590  return (setCalenderDate(PortFD, days, months, years) == 0);
591 }
592 
593 bool LX200Telescope::setLocalTime24(uint8_t hour, uint8_t minute, uint8_t second)
594 {
595  return (setLocalTime(PortFD, hour, minute, second) == 0);
596 }
597 
598 bool LX200Telescope::setUTCOffset(double offset)
599 {
600  return (::setUTCOffset(PortFD, (offset * -1.0)) == 0);
601 }
602 
603 bool LX200Telescope::updateTime(ln_date *utc, double utc_offset)
604 {
605  struct ln_zonedate ltm;
606 
607  if (isSimulation())
608  return true;
609 
610  ln_date_to_zonedate(utc, &ltm, utc_offset * 3600.0);
611 
612  JD = ln_get_julian_day(utc);
613 
614  LOGF_DEBUG("New JD is %.2f", JD);
615 
616  // Meade defines UTC Offset as the offset ADDED to local time to yield UTC, which
617  // is the opposite of the standard definition of UTC offset!
618  if (setUTCOffset(utc_offset) == false)
619  {
620  LOG_ERROR("Error setting UTC Offset.");
621  return false;
622  }
623 
624  // Set Local Time
625  if (setLocalTime24(ltm.hours, ltm.minutes, ltm.seconds) == false)
626  {
627  LOG_ERROR("Error setting local time.");
628  return false;
629  }
630 
631  if (setLocalDate(ltm.days, ltm.months, ltm.years) == false)
632  {
633  LOG_ERROR("Error setting local date.");
634  return false;
635  }
636 
637  LOG_INFO("Time updated, updating planetary data...");
638  return true;
639 }
640 
641 bool LX200Telescope::updateLocation(double latitude, double longitude, double elevation)
642 {
643  INDI_UNUSED(elevation);
644 
645  // JM 2021-04-10: MUST convert from INDI longitude to standard longitude.
646  // DO NOT REMOVE
647  if (longitude > 180)
648  longitude = longitude - 360;
649 
650  if (!isSimulation())
651  {
652  if (setSiteLongitude(PortFD, longitude) < 0)
653  {
654  LOG_ERROR("Error setting site longitude coordinates");
655  return false;
656  }
657 
658  if (setSiteLatitude(PortFD, latitude) < 0)
659  {
660  LOG_ERROR("Error setting site latitude coordinates");
661  return false;
662  }
663  }
664 
665  char l[MAXINDINAME] = {0}, L[MAXINDINAME] = {0};
666  fs_sexa(l, latitude, 2, 36000);
667  fs_sexa(L, longitude, 2, 36000);
668 
669  // Choose WGS 84, also known as EPSG:4326 for latitude/longitude ordering
670  LOGF_INFO("Site location in the mount updated to Latitude %.12s (%g) Longitude %.12s (%g) (Longitude sign in carthography format)",
671  l, latitude, L, longitude);
672 
673  return true;
674 }
675 
676 bool LX200Telescope::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
677 {
678  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
679  {
680  if (!strcmp(name, SiteNameTP.name))
681  {
682  if (!isSimulation() && setSiteName(PortFD, texts[0], currentSiteNum) < 0)
683  {
685  IDSetText(&SiteNameTP, "Setting site name");
686  return false;
687  }
688 
689  SiteNameTP.s = IPS_OK;
690  IText *tp = IUFindText(&SiteNameTP, names[0]);
691  IUSaveText(tp, texts[0]);
692  IDSetText(&SiteNameTP, "Site name updated");
693  return true;
694  }
695  }
696 
697  return INDI::Telescope::ISNewText(dev, name, texts, names, n);
698 }
699 
700 bool LX200Telescope::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
701 {
702  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
703  {
704  if (strstr(name, "FOCUS_"))
705  {
706  return FI::processNumber(dev, name, values, names, n);
707  }
708 
709  // Update Frequency
710  if (!strcmp(name, TrackFreqNP.name))
711  {
712  LOGF_DEBUG("Trying to set track freq of: %04.1f", values[0]);
714  {
715  if (!isSimulation() && setPreciseTrackFreq(PortFD, values[0]) < 0)
716  {
718  IDSetNumber(&TrackFreqNP, "Error setting tracking frequency");
719  return false;
720  }
721  TrackFreqNP.s = IPS_OK;
722  TrackFreqNP.np[0].value = values[0];
723  IDSetNumber(&TrackFreqNP, "Tracking frequency set to %8.5f", values[0]);
724  }
725  else
726  {
727  //Normal Tracking Frequency
728  if (!isSimulation() && setTrackFreq(PortFD, values[0]) < 0)
729 
730  LOGF_DEBUG("Trying to set track freq of: %f\n", values[0]);
731  if (!isSimulation() && setTrackFreq(PortFD, values[0]) < 0)
732  {
733  LOGF_DEBUG("Trying to set track freq of: %f\n", values[0]);
734  if (!isSimulation() && setTrackFreq(PortFD, values[0]) < 0)
735  {
737  IDSetNumber(&TrackFreqNP, "Error setting tracking frequency");
738  return false;
739  }
740  TrackFreqNP.s = IPS_OK;
741  IDSetNumber(&TrackFreqNP, "Error setting tracking frequency");
742  return false;
743  }
744 
745  TrackFreqNP.s = IPS_OK;
746  TrackFreqNP.np[0].value = values[0];
747  IDSetNumber(&TrackFreqNP, "Tracking frequency set to %04.1f", values[0]);
748  }
749 
751  {
753  TrackModeS[0].s = ISS_OFF;
754  TrackModeS[1].s = ISS_OFF;
755  TrackModeS[2].s = ISS_OFF;
756  TrackModeS[3].s = ISS_ON;
757  TrackModeSP.s = IPS_OK;
759  IDSetSwitch(&TrackModeSP, nullptr);
760  }
761 
762  return true;
763  }
764 
765  // if (!strcmp(name, FocusTimerNP.name))
766  // {
767  // // Don't update if busy
768  // if (FocusTimerNP.s == IPS_BUSY)
769  // return true;
770 
771  // IUUpdateNumber(&FocusTimerNP, values, names, n);
772 
773  // FocusTimerNP.s = IPS_OK;
774 
775  // IDSetNumber(&FocusTimerNP, nullptr);
776 
777  // LOGF_DEBUG("Setting focus timer to %.2f", FocusTimerN[0].value);
778 
779  // return true;
780  // }
781 
782  processGuiderProperties(name, values, names, n);
783  }
784 
785  // if we didn't process it, continue up the chain, let somebody else
786  // give it a shot
787  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
788 }
789 
790 bool LX200Telescope::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
791 {
792  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
793  {
794  // Focuser
795  if (strstr(name, "FOCUS"))
796  {
797  return FI::processSwitch(dev, name, states, names, n);
798  }
799 
800  // Alignment
801  if (!strcmp(name, AlignmentSP.name))
802  {
803  if (IUUpdateSwitch(&AlignmentSP, states, names, n) < 0)
804  return false;
805 
806  int index = IUFindOnSwitchIndex(&AlignmentSP);
807 
808  if (!isSimulation() && setAlignmentMode(PortFD, index) < 0)
809  {
811  IDSetSwitch(&AlignmentSP, "Error setting alignment mode.");
812  return false;
813  }
814 
815  AlignmentSP.s = IPS_OK;
816  IDSetSwitch(&AlignmentSP, nullptr);
817  return true;
818  }
819 
820  // Sites
821  if (!strcmp(name, SiteSP.name))
822  {
823  if (IUUpdateSwitch(&SiteSP, states, names, n) < 0)
824  return false;
825 
827 
829  {
830  SiteSP.s = IPS_ALERT;
831  IDSetSwitch(&SiteSP, "Error selecting sites.");
832  return false;
833  }
834  char siteName[64] = {0};
835  if (isSimulation())
836  {
837  IUSaveText(&SiteNameTP.tp[0], "Sample Site");
838  }
839  else
840  {
841  getSiteName(PortFD, siteName, currentSiteNum);
842  IUSaveText(&SiteNameT[0], siteName);
843  }
846 
847  SiteNameTP.s = IPS_OK;
848  SiteSP.s = IPS_OK;
849 
850  IDSetText(&SiteNameTP, nullptr);
851  IDSetSwitch(&SiteSP, nullptr);
852 
853  return false;
854  }
855 
856  // // Focus Motion
857  // if (!strcmp(name, FocusMotionSP.name))
858  // {
859  // // If mode is "halt"
860  // if (FocusModeS[0].s == ISS_ON)
861  // {
862  // FocusMotionSP.s = IPS_IDLE;
863  // IDSetSwitch(&FocusMotionSP, "Focus mode is halt. Select slow or fast mode");
864  // return true;
865  // }
866 
867  // int last_motion = IUFindOnSwitchIndex(&FocusMotionSP);
868 
869  // if (IUUpdateSwitch(&FocusMotionSP, states, names, n) < 0)
870  // return false;
871 
872  // index = IUFindOnSwitchIndex(&FocusMotionSP);
873 
874  // // If same direction and we're busy, stop
875  // if (last_motion == index && FocusMotionSP.s == IPS_BUSY)
876  // {
877  // IUResetSwitch(&FocusMotionSP);
878  // FocusMotionSP.s = IPS_IDLE;
879  // setFocuserSpeedMode(PortFD, 0);
880  // IDSetSwitch(&FocusMotionSP, nullptr);
881  // return true;
882  // }
883 
884  // if (!isSimulation() && setFocuserMotion(PortFD, index) < 0)
885  // {
886  // FocusMotionSP.s = IPS_ALERT;
887  // IDSetSwitch(&FocusMotionSP, "Error setting focuser speed.");
888  // return false;
889  // }
890 
891  // // with a timer
892  // if (FocusTimerN[0].value > 0)
893  // {
894  // FocusTimerNP.s = IPS_BUSY;
895  // FocusMotionSP.s = IPS_BUSY;
896  // IEAddTimer(50, LX200Telescope::updateFocusHelper, this);
897  // }
898 
899  // FocusMotionSP.s = IPS_OK;
900  // IDSetSwitch(&FocusMotionSP, nullptr);
901  // return true;
902  // }
903 
904  // // Focus speed
905  // if (!strcmp(name, FocusModeSP.name))
906  // {
907  // IUResetSwitch(&FocusModeSP);
908  // IUUpdateSwitch(&FocusModeSP, states, names, n);
909 
910  // index = IUFindOnSwitchIndex(&FocusModeSP);
911 
912  // /* disable timer and motion */
913  // if (index == 0)
914  // {
915  // IUResetSwitch(&FocusMotionSP);
916  // FocusMotionSP.s = IPS_IDLE;
917  // FocusTimerNP.s = IPS_IDLE;
918  // IDSetSwitch(&FocusMotionSP, nullptr);
919  // IDSetNumber(&FocusTimerNP, nullptr);
920  // }
921 
922  // if (!isSimulation())
923  // setFocuserSpeedMode(PortFD, index);
924  // FocusModeSP.s = IPS_OK;
925  // IDSetSwitch(&FocusModeSP, nullptr);
926  // return true;
927  // }
928 
929  // Pulse-Guide command support
930  if (!strcmp(name, UsePulseCmdSP.name))
931  {
933  IUUpdateSwitch(&UsePulseCmdSP, states, names, n);
934 
936  IDSetSwitch(&UsePulseCmdSP, nullptr);
937  usePulseCommand = (UsePulseCmdS[1].s == ISS_ON);
938  LOGF_INFO("Pulse guiding is %s.", usePulseCommand ? "enabled" : "disabled");
939  return true;
940  }
941  }
942 
943  // Nobody has claimed this, so pass it to the parent
944  return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
945 }
946 
948 {
949  if (isSimulation())
950  return true;
951 
952  bool rc = (selectTrackingMode(PortFD, mode) == 0);
953 
954  // Only update tracking frequency if it is defined and not deleted by child classes
955  // Note, that LX200_HAS_PRECISE_TRACKING_FREQ can use the same get function
957  {
958  getTrackFreq(PortFD, &TrackFreqN[0].value);
959  IDSetNumber(&TrackFreqNP, nullptr);
960  }
961  return rc;
962 }
963 
965 {
966  // Convert index to Meade format
967  index = 3 - index;
968 
969  if (!isSimulation() && setSlewMode(PortFD, index) < 0)
970  {
971  LOG_ERROR("Error setting slew mode.");
972  return false;
973  }
974 
975  return true;
976 }
977 
979 {
980  if (IUFindOnSwitchIndex(&SlewRateSP) == index)
981  return true;
982 
983  if (!isSimulation() && setSlewMode(PortFD, 3 - index) < 0)
984  {
986  IDSetSwitch(&SlewRateSP, "Error setting slew mode.");
987  return false;
988  }
989 
991  SlewRateS[index].s = ISS_ON;
992  SlewRateSP.s = IPS_OK;
993  IDSetSwitch(&SlewRateSP, nullptr);
994  return true;
995 }
996 
998 {
999  static_cast<LX200Telescope *>(p)->updateFocusTimer();
1000 }
1001 
1003 {
1004  // switch (FocusTimerNP.s)
1005  // {
1006  // case IPS_IDLE:
1007  // break;
1008 
1009  // case IPS_BUSY:
1010  // //if (isDebug())
1011  // //IDLog("Focus Timer Value is %g\n", FocusTimerN[0].value);
1012 
1013  // FocusTimerN[0].value -= 50;
1014 
1015  // if (FocusTimerN[0].value <= 0)
1016  // {
1017  // //if (isDebug())
1018  // //IDLog("Focus Timer Expired\n");
1019 
1020  // if (!isSimulation() && setFocuserSpeedMode(PortFD, 0) < 0)
1021  // {
1022  // FocusModeSP.s = IPS_ALERT;
1023  // IDSetSwitch(&FocusModeSP, "Error setting focuser mode.");
1024 
1025  // //if (isDebug())
1026  // //IDLog("Error setting focuser mode\n");
1027 
1028  // return;
1029  // }
1030 
1031  // FocusMotionSP.s = IPS_IDLE;
1032  // FocusTimerNP.s = IPS_OK;
1033  // FocusModeSP.s = IPS_OK;
1034 
1035  // IUResetSwitch(&FocusMotionSP);
1036  // IUResetSwitch(&FocusModeSP);
1037  // FocusModeS[0].s = ISS_ON;
1038 
1039  // IDSetSwitch(&FocusModeSP, nullptr);
1040  // IDSetSwitch(&FocusMotionSP, nullptr);
1041  // }
1042 
1043  // IDSetNumber(&FocusTimerNP, nullptr);
1044 
1045  // if (FocusTimerN[0].value > 0)
1046  // IEAddTimer(50, LX200Telescope::updateFocusHelper, this);
1047  // break;
1048 
1049  // case IPS_OK:
1050  // break;
1051 
1052  // case IPS_ALERT:
1053  // break;
1054  // }
1055 
1056  AbortFocuser();
1057  FocusTimerNP.s = IPS_OK;
1058  FocusTimerN[0].value = 0;
1059  IDSetNumber(&FocusTimerNP, nullptr);
1060 }
1061 
1063 {
1064  static struct timeval ltv;
1065  struct timeval tv;
1066  double dt = 0, da = 0, dx = 0;
1067  int nlocked = 0;
1068 
1069  /* update elapsed time since last poll, don't presume exactly POLLMS */
1070  gettimeofday(&tv, nullptr);
1071 
1072  if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
1073  ltv = tv;
1074 
1075  dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6;
1076  ltv = tv;
1077  da = LX200_GENERIC_SLEWRATE * dt;
1078 
1079  /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */
1080  switch (TrackState)
1081  {
1082 
1083  case SCOPE_IDLE:
1084  currentRA += (TRACKRATE_SIDEREAL / 3600.0 * dt / 15.);
1085  break;
1086 
1087  case SCOPE_TRACKING:
1088  switch (IUFindOnSwitchIndex(&TrackModeSP))
1089  {
1090  case TRACK_SIDEREAL:
1091  da = 0;
1092  dx = 0;
1093  break;
1094 
1095  case TRACK_LUNAR:
1096  da = ((TRACKRATE_LUNAR - TRACKRATE_SIDEREAL) / 3600.0 * dt / 15.);
1097  dx = 0;
1098  break;
1099 
1100  case TRACK_SOLAR:
1101  da = ((TRACKRATE_SOLAR - TRACKRATE_SIDEREAL) / 3600.0 * dt / 15.);
1102  dx = 0;
1103  break;
1104 
1105  case TRACK_CUSTOM:
1106  da = ((TrackRateN[AXIS_RA].value - TRACKRATE_SIDEREAL) / 3600.0 * dt / 15.);
1107  dx = (TrackRateN[AXIS_DE].value / 3600.0 * dt);
1108  break;
1109 
1110  }
1111 
1112  currentRA += da;
1113  currentDEC += dx;
1114  break;
1115 
1116  case SCOPE_SLEWING:
1117  case SCOPE_PARKING:
1118  /* slewing - nail it when both within one pulse @ LX200_GENERIC_SLEWRATE */
1119  nlocked = 0;
1120 
1121  dx = targetRA - currentRA;
1122 
1123  if (fabs(dx) <= da)
1124  {
1125  currentRA = targetRA;
1126  nlocked++;
1127  }
1128  else if (dx > 0)
1129  currentRA += da / 15.;
1130  else
1131  currentRA -= da / 15.;
1132 
1133  dx = targetDEC - currentDEC;
1134  if (fabs(dx) <= da)
1135  {
1137  nlocked++;
1138  }
1139  else if (dx > 0)
1140  currentDEC += da;
1141  else
1142  currentDEC -= da;
1143 
1144  if (nlocked == 2)
1145  {
1146  if (TrackState == SCOPE_SLEWING)
1148  else
1149  SetParked(true);
1150  }
1151 
1152  break;
1153 
1154  default:
1155  break;
1156  }
1157 
1159 }
1160 
1162 {
1163  if (!isSimulation())
1164  {
1166 
1168  getAlignment();
1169 
1170  // Only check time format if it is not already initialized by the class
1172  {
1173  if (getTimeFormat(PortFD, &timeFormat) < 0)
1174  LOG_ERROR("Failed to retrieve time format from device.");
1175  else
1176  {
1177  timeFormat = (timeFormat == 24) ? LX200_24 : LX200_AM;
1178  // We always do 24 hours
1179  if (timeFormat != LX200_24)
1180  {
1181  // Toggle format and suppress gcc warning
1182  int rc = toggleTimeFormat(PortFD);
1183  INDI_UNUSED(rc);
1184  }
1185  }
1186  }
1187 
1189  {
1190  char siteName[64] = {0};
1191  if (getSiteName(PortFD, siteName, currentSiteNum) < 0)
1192  {
1193  LOG_ERROR("Failed to get site name from device");
1194  }
1195  else
1196  {
1197  IUSaveText(&SiteNameT[0], siteName);
1198  IDSetText(&SiteNameTP, nullptr);
1199  }
1200  }
1201 
1202 
1204  {
1205  if (getTrackFreq(PortFD, &TrackFreqN[0].value) < 0)
1206  LOG_ERROR("Failed to get tracking frequency from device.");
1207  else
1208  IDSetNumber(&TrackFreqNP, nullptr);
1209  }
1210 
1211  }
1212 
1216  sendScopeTime();
1217 }
1218 
1219 void LX200Telescope::slewError(int slewCode)
1220 {
1221  if (slewCode == 1)
1222  LOG_ERROR("Object below horizon.");
1223  else if (slewCode == 2)
1224  LOG_ERROR("Object below the minimum elevation limit.");
1225  else
1226  LOGF_ERROR("Slew failed (%d).", slewCode);
1227 
1228  EqNP.s = IPS_ALERT;
1229  IDSetNumber(&EqNP, nullptr);
1230 }
1231 
1233 {
1234  signed char align = ACK(PortFD);
1235  if (align < 0)
1236  {
1237  IDSetSwitch(&AlignmentSP, "Failed to get telescope alignment.");
1238  return;
1239  }
1240 
1241  AlignmentS[0].s = ISS_OFF;
1242  AlignmentS[1].s = ISS_OFF;
1243  AlignmentS[2].s = ISS_OFF;
1244 
1245  switch (align)
1246  {
1247  case 'P':
1248  AlignmentS[0].s = ISS_ON;
1249  break;
1250  case 'A':
1251  AlignmentS[1].s = ISS_ON;
1252  break;
1253  case 'L':
1254  AlignmentS[2].s = ISS_ON;
1255  break;
1256  }
1257 
1258  AlignmentSP.s = IPS_OK;
1259  IDSetSwitch(&AlignmentSP, nullptr);
1260 }
1261 
1262 bool LX200Telescope::getLocalTime(char *timeString)
1263 {
1264  if (isSimulation())
1265  {
1266  time_t now = time (nullptr);
1267  strftime(timeString, MAXINDINAME, "%T", localtime(&now));
1268  }
1269  else
1270  {
1271  double ctime = 0;
1272  int h, m, s;
1273  getLocalTime24(PortFD, &ctime);
1274  getSexComponents(ctime, &h, &m, &s);
1275  snprintf(timeString, MAXINDINAME, "%02d:%02d:%02d", h, m, s);
1276  }
1277 
1278  return true;
1279 }
1280 
1281 bool LX200Telescope::getLocalDate(char *dateString)
1282 {
1283  if (isSimulation())
1284  {
1285  time_t now = time (nullptr);
1286  strftime(dateString, MAXINDINAME, "%F", localtime(&now));
1287  }
1288  else
1289  {
1290  getCalendarDate(PortFD, dateString);
1291  }
1292 
1293  return true;
1294 }
1295 
1296 bool LX200Telescope::getUTFOffset(double *offset)
1297 {
1298  if (isSimulation())
1299  {
1300  *offset = 3;
1301  return true;
1302  }
1303 
1304  int lx200_utc_offset = 0;
1305  getUTCOffset(PortFD, &lx200_utc_offset);
1306  // LX200 TimeT Offset is defined at the number of hours added to LOCAL TIME to get TimeT. This is contrary to the normal definition.
1307  *offset = lx200_utc_offset * -1;
1308  return true;
1309 }
1310 
1312 {
1313  char cdate[MAXINDINAME] = {0};
1314  char ctime[MAXINDINAME] = {0};
1315  struct tm ltm;
1316  struct tm utm;
1317 
1318  memset(&ltm, 0, sizeof(ltm));
1319  memset(&utm, 0, sizeof(utm));
1320 
1321  time_t time_epoch;
1322 
1323  double offset = 0;
1324  if (getUTFOffset(&offset))
1325  {
1326  char utcStr[8] = {0};
1327  snprintf(utcStr, 8, "%.2f", offset);
1328  IUSaveText(&TimeT[1], utcStr);
1329  }
1330  else
1331  {
1332  LOG_WARN("Could not obtain UTC offset from mount!");
1333  return false;
1334  }
1335 
1336  if (getLocalTime(ctime) == false)
1337  {
1338  LOG_WARN("Could not obtain local time from mount!");
1339  return false;
1340  }
1341 
1342  if (getLocalDate(cdate) == false)
1343  {
1344  LOG_WARN("Could not obtain local date from mount!");
1345  return false;
1346  }
1347 
1348  // To ISO 8601 format in LOCAL TIME!
1349  char datetime[MAXINDINAME] = {0};
1350  snprintf(datetime, MAXINDINAME, "%sT%s", cdate, ctime);
1351 
1352  // Now that date+time are combined, let's get tm representation of it.
1353  if (strptime(datetime, "%FT%T", &ltm) == nullptr)
1354  {
1355  LOGF_WARN("Could not process mount date and time: %s", datetime);
1356  return false;
1357  }
1358 
1359  // Assume no daylight savings always
1360  int isdst = 0;
1361 
1362  // Try to get whether daylight saving time is toggled.
1363  // By default we assume it's inactive.
1364  // N.B. This only works for LX200 Autostar II so shouldn't be used in base class.
1365  //getDaylightSaving(PortFD, &isdst);
1366 
1367  ltm.tm_isdst = isdst;
1368  // Get local time epoch in UNIX seconds
1369  time_epoch = mktime(&ltm);
1370 
1371  // LOCAL to UTC by subtracting offset.
1372  time_epoch -= static_cast<int>(offset * 3600.0);
1373 
1374  // Get UTC (we're using localtime_r, but since we shifted time_epoch above by UTCOffset, we should be getting the real UTC time)
1375  localtime_r(&time_epoch, &utm);
1376 
1377  // Format it into the final UTC ISO 8601
1378  strftime(cdate, MAXINDINAME, "%Y-%m-%dT%H:%M:%S", &utm);
1379  IUSaveText(&TimeT[0], cdate);
1380 
1381  LOGF_DEBUG("Mount controller UTC Time: %s", TimeT[0].text);
1382  LOGF_DEBUG("Mount controller UTC Offset: %s", TimeT[1].text);
1383 
1384  // Let's send everything to the client
1385  TimeTP.s = IPS_OK;
1386  IDSetText(&TimeTP, nullptr);
1387 
1388  return true;
1389 }
1390 
1392 {
1393  int lat_dd = 0, lat_mm = 0, long_dd = 0, long_mm = 0;
1394  double lat_ssf = 0.0, long_ssf = 0.0;
1395  char lat_sexagesimal[MAXINDIFORMAT];
1396  char lng_sexagesimal[MAXINDIFORMAT];
1397 
1398  if (isSimulation())
1399  {
1400  LocationNP.np[LOCATION_LATITUDE].value = 29.5;
1401  LocationNP.np[LOCATION_LONGITUDE].value = 48.0;
1402  LocationNP.np[LOCATION_ELEVATION].value = 10;
1403  LocationNP.s = IPS_OK;
1404  IDSetNumber(&LocationNP, nullptr);
1405  return true;
1406  }
1407 
1408  if (getSiteLatitude(PortFD, &lat_dd, &lat_mm, &lat_ssf) < 0)
1409  {
1410  LOG_WARN("Failed to get site latitude from device.");
1411  return false;
1412  }
1413  else
1414  {
1415  snprintf(lat_sexagesimal, MAXINDIFORMAT, "%02d:%02d:%02.1lf", lat_dd, lat_mm, lat_ssf);
1416  f_scansexa(lat_sexagesimal, &(LocationNP.np[LOCATION_LATITUDE].value));
1417  }
1418 
1419  if (getSiteLongitude(PortFD, &long_dd, &long_mm, &long_ssf) < 0)
1420  {
1421  LOG_WARN("Failed to get site longitude from device.");
1422  return false;
1423  }
1424  else
1425  {
1426  snprintf(lng_sexagesimal, MAXINDIFORMAT, "%02d:%02d:%02.1lf", long_dd, long_mm, long_ssf);
1427  f_scansexa(lng_sexagesimal, &(LocationNP.np[LOCATION_LONGITUDE].value));
1428  }
1429 
1430  LOGF_INFO("Mount has Latitude %s (%g) Longitude %s (%g) (Longitude sign in carthography format)",
1431  lat_sexagesimal,
1433  lng_sexagesimal,
1434  LocationN[LOCATION_LONGITUDE].value);
1435 
1436  IDSetNumber(&LocationNP, nullptr);
1437 
1438  saveConfig(true, "GEOGRAPHIC_COORD");
1439 
1440  return true;
1441 }
1442 
1444 {
1446  {
1447  LOG_ERROR("Cannot guide while slewing or parking in progress. Stop first.");
1448  return IPS_ALERT;
1449  }
1450 
1451  // If we're using pulse command, then MovementXXX should NOT be active at all.
1453  {
1454  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1455  return IPS_ALERT;
1456  }
1457 
1458  if (GuideNSTID)
1459  {
1461  GuideNSTID = 0;
1462  }
1463 
1464  if (usePulseCommand)
1465  {
1467  }
1468  else
1469  {
1471 
1472  ISState states[] = { ISS_ON, ISS_OFF };
1473  const char *names[] = { MovementNSS[DIRECTION_NORTH].name, MovementNSS[DIRECTION_SOUTH].name};
1474  ISNewSwitch(MovementNSSP.device, MovementNSSP.name, states, const_cast<char **>(names), 2);
1475  }
1476 
1478  GuideNSTID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperNS, this);
1479  return IPS_BUSY;
1480 }
1481 
1483 {
1485  {
1486  LOG_ERROR("Cannot guide while slewing or parking in progress. Stop first.");
1487  return IPS_ALERT;
1488  }
1489 
1490  // If we're using pulse command, then MovementXXX should NOT be active at all.
1492  {
1493  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1494  return IPS_ALERT;
1495  }
1496 
1497  if (GuideNSTID)
1498  {
1500  GuideNSTID = 0;
1501  }
1502 
1503  if (usePulseCommand)
1504  {
1506  }
1507  else
1508  {
1510 
1511  ISState states[] = { ISS_OFF, ISS_ON };
1512  const char *names[] = { MovementNSS[DIRECTION_NORTH].name, MovementNSS[DIRECTION_SOUTH].name};
1513  ISNewSwitch(MovementNSSP.device, MovementNSSP.name, states, const_cast<char **>(names), 2);
1514  }
1515 
1517  GuideNSTID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperNS, this);
1518  return IPS_BUSY;
1519 }
1520 
1522 {
1524  {
1525  LOG_ERROR("Cannot guide while slewing or parking in progress. Stop first.");
1526  return IPS_ALERT;
1527  }
1528 
1529  // If we're using pulse command, then MovementXXX should NOT be active at all.
1531  {
1532  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1533  return IPS_ALERT;
1534  }
1535 
1536  if (GuideWETID)
1537  {
1539  GuideWETID = 0;
1540  }
1541 
1542  if (usePulseCommand)
1543  {
1544  SendPulseCmd(LX200_EAST, ms);
1545  }
1546  else
1547  {
1549 
1550  ISState states[] = { ISS_OFF, ISS_ON };
1551  const char *names[] = { MovementWES[DIRECTION_WEST].name, MovementWES[DIRECTION_EAST].name};
1552  ISNewSwitch(MovementWESP.device, MovementWESP.name, states, const_cast<char **>(names), 2);
1553  }
1554 
1556  GuideWETID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperWE, this);
1557  return IPS_BUSY;
1558 }
1559 
1561 {
1563  {
1564  LOG_ERROR("Cannot guide while slewing or parking in progress. Stop first.");
1565  return IPS_ALERT;
1566  }
1567 
1568  // If we're using pulse command, then MovementXXX should NOT be active at all.
1570  {
1571  LOG_ERROR("Cannot pulse guide while manually in motion. Stop first.");
1572  return IPS_ALERT;
1573  }
1574 
1575  if (GuideWETID)
1576  {
1578  GuideWETID = 0;
1579  }
1580 
1581  if (usePulseCommand)
1582  {
1583  SendPulseCmd(LX200_WEST, ms);
1584  }
1585  else
1586  {
1588 
1589  ISState states[] = { ISS_ON, ISS_OFF };
1590  const char *names[] = { MovementWES[DIRECTION_WEST].name, MovementWES[DIRECTION_EAST].name};
1591  ISNewSwitch(MovementWESP.device, MovementWESP.name, states, const_cast<char **>(names), 2);
1592  }
1593 
1595  GuideWETID = IEAddTimer(static_cast<int>(ms), guideTimeoutHelperWE, this);
1596  return IPS_BUSY;
1597 }
1598 
1599 int LX200Telescope::SendPulseCmd(int8_t direction, uint32_t duration_msec)
1600 {
1601  return ::SendPulseCmd(PortFD, direction, duration_msec);
1602 }
1603 
1605 {
1606  static_cast<LX200Telescope *>(p)->guideTimeoutNS();
1607 }
1608 
1610 {
1611  static_cast<LX200Telescope *>(p)->guideTimeoutWE();
1612 }
1613 
1615 {
1616  if (usePulseCommand == false)
1617  {
1618  ISState states[] = { ISS_OFF, ISS_OFF };
1619  const char *names[] = { MovementWES[DIRECTION_WEST].name, MovementWES[DIRECTION_EAST].name};
1620  ISNewSwitch(MovementWESP.device, MovementWESP.name, states, const_cast<char **>(names), 2);
1621  }
1622 
1623  GuideWENP.np[DIRECTION_WEST].value = 0;
1624  GuideWENP.np[DIRECTION_EAST].value = 0;
1625  GuideWENP.s = IPS_IDLE;
1626  GuideWETID = 0;
1627  IDSetNumber(&GuideWENP, nullptr);
1628 }
1629 
1631 {
1632  if (usePulseCommand == false)
1633  {
1634  ISState states[] = { ISS_OFF, ISS_OFF };
1635  const char *names[] = { MovementNSS[DIRECTION_NORTH].name, MovementNSS[DIRECTION_SOUTH].name};
1636  ISNewSwitch(MovementNSSP.device, MovementNSSP.name, states, const_cast<char **>(names), 2);
1637  }
1638 
1639  GuideNSNP.np[0].value = 0;
1640  GuideNSNP.np[1].value = 0;
1641  GuideNSNP.s = IPS_IDLE;
1642  GuideNSTID = 0;
1643  IDSetNumber(&GuideNSNP, nullptr);
1644 
1645 }
1646 
1648 {
1650 
1653 
1655  FI::saveConfigItems(fp);
1656 
1657  return true;
1658 }
1659 
1661 {
1662  INDI_UNUSED(enabled);
1663  return true;
1664 }
1665 
1667 {
1668  return SetFocuserSpeed(0);
1669 }
1670 
1671 IPState LX200Telescope::MoveFocuser(FocusDirection dir, int speed, uint16_t duration)
1672 {
1673  FocusDirection finalDirection = dir;
1674  // Reverse final direction if necessary
1675  if (FocusReverseS[INDI_ENABLED].s == ISS_ON)
1676  finalDirection = (dir == FOCUS_INWARD) ? FOCUS_OUTWARD : FOCUS_INWARD;
1677 
1678  SetFocuserSpeed(speed);
1679 
1680  setFocuserMotion(PortFD, finalDirection);
1681 
1683 
1684  return IPS_BUSY;
1685 }
1686 
1688 {
1689  return (setFocuserSpeedMode(PortFD, speed) == 0);
1690 }
1691 
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
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.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
Provides interface to implement focuser functionality.
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
INumberVectorProperty FocusTimerNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
void initProperties(const char *groupName)
Initilize focuser properties. It is recommended to call this function within initProperties() of your...
bool saveConfigItems(FILE *fp)
saveConfigItems save focuser properties defined in the interface in config file
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process focus number properties.
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process focus switch properties.
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
ISwitchVectorProperty MovementNSSP
ISwitchVectorProperty AbortSP
INumberVectorProperty LocationNP
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ITextVectorProperty TimeTP
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty TrackModeSP
ISwitchVectorProperty SlewRateSP
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
ISwitch MovementWES[2]
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
INumberVectorProperty EqNP
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
ISwitchVectorProperty ParkSP
INumber TrackRateN[2]
IGeographicCoordinates m_Location
uint32_t GetTelescopeCapability() const
GetTelescopeCapability returns the capability of the Telescope.
ISwitch MovementNSS[2]
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
ISwitch * TrackModeS
ISwitch * SlewRateS
ISwitchVectorProperty MovementWESP
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
ISwitch UsePulseCmdS[2]
virtual bool Sync(double ra, double dec) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
uint32_t genericCapability
int8_t guide_direction_we
static void guideTimeoutHelperNS(void *p)
INumber TrackFreqN[1]
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
ITextVectorProperty SiteNameTP
virtual void slewError(int slewCode)
bool updateSlewRate(int index)
virtual bool SetSlewRate(int index) override
SetSlewRate Set desired slew rate index.
static void updateFocusHelper(void *p)
virtual bool getLocalDate(char *dateString)
virtual bool Park() override
Park the telescope to its home position.
virtual bool updateTime(ln_date *utc, double utc_offset) override
Update telescope time, date, and UTC offset.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
ISwitchVectorProperty AlignmentSP
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 getUTFOffset(double *offset)
virtual bool sendScopeLocation()
bool sendLocationOnStartup
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Update telescope location settings.
virtual bool isSlewComplete()
virtual bool Abort() override
Abort any telescope motion including tracking if possible.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual const char * getDefaultName() override
static void guideTimeoutHelperWE(void *p)
virtual bool ReadScopeStatus() override
Read telescope status.
virtual bool getLocalTime(char *timeString)
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
Move the telescope in the direction dir.
INumberVectorProperty TrackFreqNP
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual void debugTriggered(bool enable) override
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
@ LX200_HAS_PRECISE_TRACKING_FREQ
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool checkConnection()
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool sendScopeTime()
virtual bool setLocalDate(uint8_t days, uint8_t months, uint16_t years)
virtual bool setLocalTime24(uint8_t hour, uint8_t minute, uint8_t second)
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
ISwitch SiteS[4]
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual void getBasicData()
virtual bool Goto(double ra, double dec) override
Move the scope to the supplied RA and DEC coordinates.
virtual bool setUTCOffset(double offset)
IText SiteNameT[1]
ISwitch AlignmentS[3]
virtual bool SetTrackMode(uint8_t mode) override
SetTrackMode Set active tracking mode. Do not change track state.
virtual IPState GuideNorth(uint32_t ms) override
Guide north for ms milliseconds. North is defined as DEC+.
virtual IPState MoveFocuser(FocusDirection dir, int speed, uint16_t duration) override
MoveFocuser the focuser in a particular direction with a specific speed for a finite duration.
virtual bool SetFocuserSpeed(int speed) override
SetFocuserSpeed Set Focuser speed.
virtual bool Handshake() override
perform handshake with device to check communication
virtual int SendPulseCmd(int8_t direction, uint32_t duration_msec)
ISwitchVectorProperty SiteSP
virtual const char * getDriverName() override
int8_t guide_direction_ns
ISwitchVectorProperty UsePulseCmdSP
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 * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * FOCUS_TAB
FOCUS_TAB Where all the properties for focuser are located.
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:602
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:582
#define setLocalTime(fd, x, y, z)
Definition: ieq45driver.h:141
double ra
double dec
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
#define MAXINDIFORMAT
Definition: indiapi.h:195
@ IP_RW
Definition: indiapi.h:186
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
#define MAXINDINAME
Definition: indiapi.h:191
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
INDI_DIR_WE
Definition: indibasetypes.h:55
@ DIRECTION_EAST
Definition: indibasetypes.h:57
@ DIRECTION_WEST
Definition: indibasetypes.h:56
INDI_DIR_NS
Definition: indibasetypes.h:48
@ DIRECTION_SOUTH
Definition: indibasetypes.h:50
@ DIRECTION_NORTH
Definition: indibasetypes.h:49
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition: indicom.c:205
void getSexComponents(double value, int *d, int *m, int *s)
Definition: indicom.c:254
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:141
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
#define TRACKRATE_SOLAR
Definition: indicom.h:61
#define TRACKRATE_SIDEREAL
Definition: indicom.h:55
#define TRACKRATE_LUNAR
Definition: indicom.h:64
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
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:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
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:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
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:158
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:198
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:180
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:235
IText * IUFindText(const ITextVectorProperty *tvp, const char *name)
Find an IText member in a vector text property.
Definition: indidevapi.c:56
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition: indidriver.c:889
int IUGetConfigOnSwitchIndex(const char *dev, const char *property, int *index)
IUGetConfigOnSwitchIndex Opens configuration file and reads single switch property to find ON switch ...
Definition: indidriver.c:711
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#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
int setFocuserMotion(int fd, int motionType)
int setAlignmentMode(int fd, unsigned int alignMode)
int setSlewMode(int fd, int slewMode)
int SendPulseCmd(int fd, int direction, int duration_msec, bool wait_after_command, int max_wait_ms)
int getSiteLongitude(int fd, int *ddd, int *mm, double *ssf)
int selectTrackingMode(int fd, int trackMode)
int getSiteName(int fd, char *siteName, int siteNum)
int selectSite(int fd, int siteNum)
int setObjectRA(int fd, double ra, bool addSpace)
int getLX200EquatorialFormat()
int setSiteLongitude(int fd, double CartographicLongitude, bool addSpace)
int setObjectDEC(int fd, double dec, bool addSpace)
int checkLX200EquatorialFormat(int fd)
int abortSlew(int fd)
int setSiteLatitude(int fd, double Lat, bool addSpace)
int setTrackFreq(int fd, double trackF)
int setFocuserSpeedMode(int fd, int speedMode)
int setCalenderDate(int fd, int dd, int mm, int yy, bool addSpace)
int getCalendarDate(int fd, char *date)
int Slew(int fd)
int MoveTo(int fd, int direction)
int getTimeFormat(int fd, int *format)
int getTrackFreq(int fd, double *value)
int setPreciseTrackFreq(int fd, double trackF)
int setSiteName(int fd, char *siteName, int siteNum)
void setLX200Debug(const char *deviceName, unsigned int debug_level)
Definition: lx200driver.cpp:58
int check_lx200_connection(int in_fd)
Definition: lx200driver.cpp:64
int HaltMovement(int fd, int direction)
int getSiteLatitude(int fd, int *dd, int *mm, double *ssf)
#define getUTCOffset(fd, x)
Definition: lx200driver.h:137
#define getLX200DEC(fd, x)
Definition: lx200driver.h:118
@ LX200_AM
Definition: lx200driver.h:65
@ LX200_24
Definition: lx200driver.h:64
#define getLX200RA(fd, x)
Definition: lx200driver.h:117
@ LX200_TRACK_MANUAL
Definition: lx200driver.h:110
@ LX200_WEST
Definition: lx200driver.h:42
@ LX200_SOUTH
Definition: lx200driver.h:44
@ LX200_NORTH
Definition: lx200driver.h:41
@ LX200_EAST
Definition: lx200driver.h:43
@ LX200_EQ_LONGER_FORMAT
Definition: lx200driver.h:52
@ LX200_EQ_SHORT_FORMAT
Definition: lx200driver.h:50
@ LX200_EQ_LONG_FORMAT
Definition: lx200driver.h:51
#define getLocalTime24(fd, x)
Definition: lx200driver.h:122
#define toggleTimeFormat(fd)
Definition: lx200driver.h:165
#define slewToPark(fd)
Definition: lx200driver.h:174
#define LX200_GENERIC_SLEWRATE
One text descriptor.
char name[MAXINDINAME]
Definition: indiapi.h:323
char device[MAXINDIDEVICE]
Definition: indiapi.h:369
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250
#define ACK
Definition: stvdriver.h:35