Kstars

indimount.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "indimount.h"
8 
9 #include "ksmessagebox.h"
10 #include "driverinfo.h"
11 #include "kstars.h"
12 #include "Options.h"
13 #include "skymap.h"
14 #include "skymapcomposite.h"
15 #include "ksnotification.h"
16 
17 #include <KActionCollection>
18 #include <KLocalizedString>
19 
20 #include <QAction>
21 #include <qdbusmetatype.h>
22 
23 #include <indi_debug.h>
24 
25 // Qt version calming
26 #include <qtendl.h>
27 
28 namespace ISD
29 {
30 
31 const QList<const char *> Mount::mountStates = { I18N_NOOP("Idle"), I18N_NOOP("Moving"), I18N_NOOP("Slewing"),
32  I18N_NOOP("Tracking"), I18N_NOOP("Parking"), I18N_NOOP("Parked"),
33  I18N_NOOP("Error")
34  };
35 
36 Mount::Mount(GenericDevice *parent) : ConcreteDevice(parent)
37 {
38  // Set it for 5 seconds for now as not to spam the display update
39  centerLockTimer.setInterval(5000);
40  centerLockTimer.setSingleShot(true);
41  connect(&centerLockTimer, &QTimer::timeout, this, [this]()
42  {
43  //runCommand(INDI_CENTER_LOCK);
44  centerLock();
45  });
46 
47  // Regularly update the coordinates even if no update has been sent from the INDI service
48  updateCoordinatesTimer.setInterval(1000);
49  updateCoordinatesTimer.setSingleShot(false);
50  connect(&updateCoordinatesTimer, &QTimer::timeout, this, [this]()
51  {
52  if (isConnected())
53  {
54  currentCoords.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
55  emit newCoords(currentCoords, pierSide(), hourAngle());
56  }
57  });
58 
59  qRegisterMetaType<ISD::Mount::Status>("ISD::Mount::Status");
60  qDBusRegisterMetaType<ISD::Mount::Status>();
61 
62  qRegisterMetaType<ISD::Mount::PierSide>("ISD::Mount::PierSide");
63  qDBusRegisterMetaType<ISD::Mount::PierSide>();
64 
65  // Need to delay check for alignment model to upon connection is established since the property is defined BEFORE Telescope class is created.
66  // and therefore no registerProperty is called for these properties since they were already registered _before_ the Telescope
67  // class was created.
68  m_hasAlignmentModel = getProperty("ALIGNMENT_POINTSET_ACTION").isValid() || getProperty("ALIGNLIST").isValid();
69 }
70 
71 void Mount::registerProperty(INDI::Property prop)
72 {
73  if (prop.isNameMatch("TELESCOPE_INFO"))
74  {
75  auto ti = prop.getNumber();
76 
77  if (!ti)
78  return;
79 
80  bool aperture_ok = false, focal_ok = false;
81  double temp = 0;
82 
83  auto aperture = ti->findWidgetByName("TELESCOPE_APERTURE");
84  if (aperture && aperture->getValue() <= 0)
85  {
86  if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_APERTURE"))
87  {
88  temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_APERTURE").toDouble(&aperture_ok);
89  if (aperture_ok)
90  {
91  aperture->setValue(temp);
92  auto g_aperture = ti->findWidgetByName("GUIDER_APERTURE");
93  if (g_aperture && g_aperture->getValue() <= 0)
94  g_aperture->setValue(aperture->getValue());
95  }
96  }
97  }
98 
99  auto focal_length = ti->findWidgetByName("TELESCOPE_FOCAL_LENGTH");
100  if (focal_length && focal_length->getValue() <= 0)
101  {
102  if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_FOCAL_LENGTH"))
103  {
104  temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_FOCAL_LENGTH").toDouble(&focal_ok);
105  if (focal_ok)
106  {
107  focal_length->setValue(temp);
108  auto g_focal = ti->findWidgetByName("GUIDER_FOCAL_LENGTH");
109  if (g_focal && g_focal->getValue() <= 0)
110  g_focal->setValue(focal_length->getValue());
111  }
112  }
113  }
114 
115  if (aperture_ok && focal_ok)
116  sendNewProperty(ti);
117  }
118  else if (prop.isNameMatch("ON_COORD_SET"))
119  {
120  m_canGoto = IUFindSwitch(prop.getSwitch(), "TRACK") != nullptr;
121  m_canSync = IUFindSwitch(prop.getSwitch(), "SYNC") != nullptr;
122  m_canFlip = IUFindSwitch(prop.getSwitch(), "FLIP") != nullptr;
123  }
124  else if (prop.isNameMatch("TELESCOPE_PIER_SIDE"))
125  {
126  auto svp = prop.getSwitch();
127  int currentSide = svp->findOnSwitchIndex();
128  if (currentSide != m_PierSide)
129  {
130  m_PierSide = static_cast<PierSide>(currentSide);
131  emit pierSideChanged(m_PierSide);
132  }
133  }
134  else if (prop.isNameMatch("TELESCOPE_PARK"))
135  updateParkStatus();
136  else if (prop.isNameMatch("TELESCOPE_TRACK_STATE"))
137  m_canControlTrack = true;
138  else if (prop.isNameMatch("TELESCOPE_TRACK_MODE"))
139  {
140  m_hasTrackModes = true;
141  auto svp = prop.getSwitch();
142  for (int i = 0; i < svp->count(); i++)
143  {
144  if (svp->at(i)->isNameMatch("TRACK_SIDEREAL"))
145  TrackMap[TRACK_SIDEREAL] = i;
146  else if (svp->at(i)->isNameMatch("TRACK_SOLAR"))
147  TrackMap[TRACK_SOLAR] = i;
148  else if (svp->at(i)->isNameMatch("TRACK_LUNAR"))
149  TrackMap[TRACK_LUNAR] = i;
150  else if (svp->at(i)->isNameMatch("TRACK_CUSTOM"))
151  TrackMap[TRACK_CUSTOM] = i;
152  }
153  }
154  else if (prop.isNameMatch("TELESCOPE_TRACK_RATE"))
155  m_hasCustomTrackRate = true;
156  else if (prop.isNameMatch("TELESCOPE_ABORT_MOTION"))
157  m_canAbort = true;
158  else if (prop.isNameMatch("TELESCOPE_PARK_OPTION"))
159  m_hasCustomParking = true;
160  else if (prop.isNameMatch("TELESCOPE_SLEW_RATE"))
161  {
162  m_hasSlewRates = true;
163  auto svp = prop.getSwitch();
164  if (svp)
165  {
166  m_slewRates.clear();
167  for (const auto &it : *svp)
168  m_slewRates << it.getLabel();
169  }
170  }
171  else if (prop.isNameMatch("EQUATORIAL_EOD_COORD"))
172  {
173  m_isJ2000 = false;
174  m_hasEquatorialCoordProperty = true;
175  }
176  else if (prop.isNameMatch("SAT_TRACKING_STAT"))
177  {
178  m_canTrackSatellite = true;
179  }
180  else if (prop.isNameMatch("EQUATORIAL_COORD"))
181  {
182  m_isJ2000 = true;
183  m_hasEquatorialCoordProperty = true;
184  }
185 }
186 
187 void Mount::updateJ2000Coordinates(SkyPoint *coords)
188 {
189  SkyPoint J2000Coord(coords->ra(), coords->dec());
190  J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
191  coords->setRA0(J2000Coord.ra());
192  coords->setDec0(J2000Coord.dec());
193 }
194 
196 {
197  emit newTarget(currentCoords);
198  double maxrad = 0.1;
199  currentObject = KStarsData::Instance()->skyComposite()->objectNearest(&currentCoords, maxrad);
200  if (currentObject)
201  emit newTargetName(currentObject->name());
202  // If there is no object, we must clear target as it might give wrong
203  // indication we are still on it.
204  else
205  emit newTargetName(QString());
206 }
207 
208 void Mount::processNumber(INDI::Property prop)
209 {
210  auto nvp = prop.getNumber();
211  if (nvp->isNameMatch("EQUATORIAL_EOD_COORD") || nvp->isNameMatch("EQUATORIAL_COORD"))
212  {
213  auto RA = nvp->findWidgetByName("RA");
214  auto DEC = nvp->findWidgetByName("DEC");
215 
216  if (RA == nullptr || DEC == nullptr)
217  return;
218 
219  // set both JNow and J2000 coordinates
220  if (isJ2000())
221  {
222  currentCoords.setRA0(RA->value);
223  currentCoords.setDec0(DEC->value);
224  currentCoords.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
225  }
226  else
227  {
228  currentCoords.setRA(RA->value);
229  currentCoords.setDec(DEC->value);
230  // calculate J2000 coordinates
231  updateJ2000Coordinates(&currentCoords);
232  }
233 
234  // calculate horizontal coordinates
235  currentCoords.EquatorialToHorizontal(KStars::Instance()->data()->lst(),
236  KStars::Instance()->data()->geo()->lat());
237  // ensure that coordinates are regularly updated
238  if (! updateCoordinatesTimer.isActive())
239  updateCoordinatesTimer.start();
240 
241  auto currentStatus = status(nvp);
242 
243  if (nvp->getState() == IPS_BUSY && EqCoordPreviousState != IPS_BUSY)
244  {
245  if (currentStatus == MOUNT_SLEWING)
246  KSNotification::event(QLatin1String("SlewStarted"), i18n("Mount is slewing to target location"), KSNotification::Mount);
247  emit newStatus(currentStatus);
248  }
249  else if (EqCoordPreviousState == IPS_BUSY && nvp->getState() == IPS_OK && slewDefined())
250  {
251  if (Options::useExternalSkyMap())
252  {
253  // For external skymaps the only way to determine the target is to take the position where the mount
254  // starts to track
255  updateTarget();
256  }
257  else
258  {
259  // In case that we use KStars as skymap, we intentionally do not communicate the target here, since it
260  // has been set at the beginning of the slew AND we cannot be sure that the position the INDI
261  // mount reports when starting to track is exactly that one where the slew went to.
262  KSNotification::event(QLatin1String("SlewCompleted"), i18n("Mount arrived at target location"), KSNotification::Mount);
263  }
264  emit newStatus(currentStatus);
265  }
266 
267  EqCoordPreviousState = nvp->getState();
268 
269  KStars::Instance()->map()->update();
270  }
271  // JM 2022.03.11 Only process HORIZONTAL_COORD if it was the ONLY source of information
272  // When a driver both sends EQUATORIAL_COORD and HORIZONTAL_COORD, we should prioritize EQUATORIAL_COORD
273  // especially since the conversion from horizontal to equatorial is not as accurate and can result in weird
274  // coordinates near the poles.
275  else if (nvp->isNameMatch("HORIZONTAL_COORD") && m_hasEquatorialCoordProperty == false)
276  {
277  auto Az = nvp->findWidgetByName("AZ");
278  auto Alt = nvp->findWidgetByName("ALT");
279 
280  if (Az == nullptr || Alt == nullptr)
281  return;
282 
283  currentCoords.setAz(Az->value);
284  currentCoords.setAlt(Alt->value);
285  currentCoords.HorizontalToEquatorial(KStars::Instance()->data()->lst(),
286  KStars::Instance()->data()->geo()->lat());
287 
288  // calculate J2000 coordinates
289  updateJ2000Coordinates(&currentCoords);
290 
291  // ensure that coordinates are regularly updated
292  if (! updateCoordinatesTimer.isActive())
293  updateCoordinatesTimer.start();
294 
295  KStars::Instance()->map()->update();
296  }
297  else if (nvp->isNameMatch("POLLING_PERIOD"))
298  {
299  // set the timer how often the coordinates should be published
300  auto period = nvp->findWidgetByName("PERIOD_MS");
301  if (period != nullptr)
302  updateCoordinatesTimer.setInterval(static_cast<int>(period->getValue()));
303 
304  }
305 }
306 
307 void Mount::processSwitch(INDI::Property prop)
308 {
309  bool manualMotionChanged = false;
310  auto svp = prop.getSwitch();
311 
312  if (svp->isNameMatch("CONNECTION"))
313  {
314  auto conSP = svp->findWidgetByName("CONNECT");
315  if (conSP)
316  {
317  // TODO We must allow for multiple mount drivers to be online, not just one
318  // For the actions taken, the user should be able to specify which mounts shall receive the commands. It could be one
319  // or more. For now, we enable/disable telescope group on the assumption there is only one mount present.
320  if (conSP->getState() == ISS_ON)
322  else
323  {
325  centerLockTimer.stop();
326  }
327  }
328  }
329  else if (svp->isNameMatch("TELESCOPE_PARK"))
331  else if (svp->isNameMatch("TELESCOPE_ABORT_MOTION"))
332  {
333  if (svp->s == IPS_OK)
334  {
335  inCustomParking = false;
336  KSNotification::event(QLatin1String("MountAborted"), i18n("Mount motion was aborted"), KSNotification::Mount,
337  KSNotification::Warn);
338  }
339  }
340  else if (svp->isNameMatch("TELESCOPE_PIER_SIDE"))
341  {
342  int currentSide = IUFindOnSwitchIndex(svp);
343  if (currentSide != m_PierSide)
344  {
345  m_PierSide = static_cast<PierSide>(currentSide);
346  emit pierSideChanged(m_PierSide);
347  }
348  }
349  else if (svp->isNameMatch("TELESCOPE_TRACK_MODE"))
350  {
351  auto sp = svp->findOnSwitch();
352  if (sp)
353  {
354  if (sp->isNameMatch("TRACK_SIDEREAL"))
355  currentTrackMode = TRACK_SIDEREAL;
356  else if (sp->isNameMatch("TRACK_SOLAR"))
357  currentTrackMode = TRACK_SOLAR;
358  else if (sp->isNameMatch("TRACK_LUNAR"))
359  currentTrackMode = TRACK_LUNAR;
360  else
361  currentTrackMode = TRACK_CUSTOM;
362  }
363  }
364  else if (svp->isNameMatch("TELESCOPE_MOTION_NS"))
365  manualMotionChanged = true;
366  else if (svp->isNameMatch("TELESCOPE_MOTION_WE"))
367  manualMotionChanged = true;
368  else if (svp->isNameMatch("TELESCOPE_REVERSE_MOTION"))
369  {
370  emit axisReversed(AXIS_DE, svp->at(0)->getState() == ISS_ON);
371  emit axisReversed(AXIS_RA, svp->at(1)->getState() == ISS_ON);
372  }
373 
374  if (manualMotionChanged)
375  {
376  auto NSCurrentMotion = getSwitch("TELESCOPE_MOTION_NS")->getState();
377  auto WECurrentMotion = getSwitch("TELESCOPE_MOTION_WE")->getState();
378  inCustomParking = false;
379  inManualMotion = (NSCurrentMotion == IPS_BUSY || WECurrentMotion == IPS_BUSY);
380  }
381 }
382 
383 void Mount::processText(INDI::Property prop)
384 {
385  auto tvp = prop.getText();
386  if (tvp->isNameMatch("SAT_TLE_TEXT"))
387  {
388  if ((tvp->getState() == IPS_OK) && (m_TLEIsSetForTracking))
389  {
390  auto trajWindow = getText("SAT_PASS_WINDOW");
391  if (!trajWindow)
392  {
393  qCDebug(KSTARS_INDI) << "Property SAT_PASS_WINDOW not found";
394  }
395  else
396  {
397  auto trajStart = trajWindow->findWidgetByName("SAT_PASS_WINDOW_START");
398  auto trajEnd = trajWindow->findWidgetByName("SAT_PASS_WINDOW_END");
399 
400  if (!trajStart || !trajEnd)
401  {
402  qCDebug(KSTARS_INDI) << "Start or end in SAT_PASS_WINDOW not found";
403  }
404  else
405  {
406  trajStart->setText(g_satPassStart.toString(Qt::ISODate).toLocal8Bit().data());
407  trajEnd->setText(g_satPassEnd.toString(Qt::ISODate).toLocal8Bit().data());
408 
409  sendNewProperty(trajWindow);
410  m_windowIsSetForTracking = true;
411  }
412  }
413  }
414  }
415  else if (tvp->isNameMatch("SAT_PASS_WINDOW"))
416  {
417  if ((tvp->getState() == IPS_OK) && (m_TLEIsSetForTracking) && (m_windowIsSetForTracking))
418  {
419  auto trackSwitchV = getSwitch("SAT_TRACKING_STAT");
420  if (!trackSwitchV)
421  {
422  qCDebug(KSTARS_INDI) << "Property SAT_TRACKING_STAT not found";
423  }
424  else
425  {
426  auto trackSwitch = trackSwitchV->findWidgetByName("SAT_TRACK");
427  if (trackSwitch)
428  {
429  trackSwitchV->reset();
430  trackSwitch->setState(ISS_ON);
431 
432  sendNewProperty(trackSwitchV);
433  m_TLEIsSetForTracking = false;
434  m_windowIsSetForTracking = false;
435  }
436  }
437  }
438  }
439 }
440 
442 {
443  auto svp = getSwitch("TELESCOPE_PARK");
444  if (!svp)
445  return;
446 
447  auto sp = svp->findWidgetByName("PARK");
448  if (sp)
449  {
450  if (svp->getState() == IPS_ALERT)
451  {
452  // First, inform everyone watch this that an error occurred.
453  emit newParkStatus(PARK_ERROR);
454  // JM 2021-03-08: Reset parking internal state to either PARKED or UNPARKED.
455  // Whatever the current switch is set to
456  m_ParkStatus = (sp->getState() == ISS_ON) ? PARK_PARKED : PARK_UNPARKED;
457  KSNotification::event(QLatin1String("MountParkingFailed"), i18n("Mount parking failed"), KSNotification::Mount,
458  KSNotification::Alert);
459  }
460  else if (svp->getState() == IPS_BUSY && sp->s == ISS_ON && m_ParkStatus != PARK_PARKING)
461  {
462  m_ParkStatus = PARK_PARKING;
463  KSNotification::event(QLatin1String("MountParking"), i18n("Mount parking is in progress"), KSNotification::Mount);
464  currentObject = nullptr;
465 
466  emit newParkStatus(m_ParkStatus);
467  }
468  else if (svp->getState() == IPS_BUSY && sp->getState() == ISS_OFF && m_ParkStatus != PARK_UNPARKING)
469  {
470  m_ParkStatus = PARK_UNPARKING;
471  KSNotification::event(QLatin1String("MountUnParking"), i18n("Mount unparking is in progress"), KSNotification::Mount);
472 
473  emit newParkStatus(m_ParkStatus);
474  }
475  else if (svp->getState() == IPS_OK && sp->getState() == ISS_ON && m_ParkStatus != PARK_PARKED)
476  {
477  m_ParkStatus = PARK_PARKED;
478  KSNotification::event(QLatin1String("MountParked"), i18n("Mount parked"), KSNotification::Mount);
479  currentObject = nullptr;
480 
481  emit newParkStatus(m_ParkStatus);
482 
483  QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park");
484  if (parkAction)
485  parkAction->setEnabled(false);
486  QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark");
487  if (unParkAction)
488  unParkAction->setEnabled(true);
489 
490  emit newTarget(currentCoords);
491  }
492  else if ( (svp->getState() == IPS_OK || svp->getState() == IPS_IDLE) && sp->getState() == ISS_OFF
493  && m_ParkStatus != PARK_UNPARKED)
494  {
495  m_ParkStatus = PARK_UNPARKED;
496  KSNotification::event(QLatin1String("MountUnparked"), i18n("Mount unparked"), KSNotification::Mount);
497  currentObject = nullptr;
498 
499  emit newParkStatus(m_ParkStatus);
500 
501  QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park");
502  if (parkAction)
503  parkAction->setEnabled(true);
504  QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark");
505  if (unParkAction)
506  unParkAction->setEnabled(false);
507  }
508  }
509 }
510 bool Mount::canGuide()
511 {
512  auto raPulse = getNumber("TELESCOPE_TIMED_GUIDE_WE");
513  auto decPulse = getNumber("TELESCOPE_TIMED_GUIDE_NS");
514 
515  return raPulse && decPulse;
516 }
517 
518 bool Mount::canPark()
519 {
520  auto parkSP = getSwitch("TELESCOPE_PARK");
521 
522  if (!parkSP)
523  return false;
524 
525  auto parkSW = parkSP->findWidgetByName("PARK");
526 
527  return (parkSW != nullptr);
528 }
529 
530 bool Mount::isSlewing()
531 {
532  auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
533 
534  if (!EqProp)
535  return false;
536 
537  return (EqProp->getState() == IPS_BUSY);
538 }
539 
540 bool Mount::isInMotion()
541 {
542  return (isSlewing() || inManualMotion);
543 }
544 
545 bool Mount::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
546 {
547  if (canGuide() == false)
548  return false;
549 
550  bool raOK = doPulse(ra_dir, ra_msecs);
551  bool decOK = doPulse(dec_dir, dec_msecs);
552 
553  return raOK && decOK;
554 }
555 
556 bool Mount::doPulse(GuideDirection dir, int msecs)
557 {
558  auto raPulse = getNumber("TELESCOPE_TIMED_GUIDE_WE");
559  auto decPulse = getNumber("TELESCOPE_TIMED_GUIDE_NS");
560  INDI::PropertyView<INumber> *npulse = nullptr;
561  INDI::WidgetView<INumber> *dirPulse = nullptr;
562 
563  if (!raPulse || !decPulse)
564  return false;
565 
566  switch (dir)
567  {
568  case RA_INC_DIR:
569  npulse = raPulse;
570  dirPulse = npulse->findWidgetByName("TIMED_GUIDE_W");
571  break;
572 
573  case RA_DEC_DIR:
574  npulse = raPulse;
575  dirPulse = npulse->findWidgetByName("TIMED_GUIDE_E");
576  break;
577 
578  case DEC_INC_DIR:
579  npulse = decPulse;
580  dirPulse = npulse->findWidgetByName("TIMED_GUIDE_N");
581  break;
582 
583  case DEC_DEC_DIR:
584  npulse = decPulse;
585  dirPulse = npulse->findWidgetByName("TIMED_GUIDE_S");
586  break;
587 
588  default:
589  return false;
590  }
591 
592  if (!dirPulse)
593  return false;
594 
595  dirPulse->setValue(msecs);
596 
597  sendNewProperty(npulse);
598 
599  return true;
600 }
601 
602 
603 void Mount::setCustomParking(SkyPoint * coords)
604 {
605  bool rc = false;
606  if (coords == nullptr)
607  rc = sendCoords(KStars::Instance()->map()->clickedPoint());
608  else
609  rc = sendCoords(coords);
610 
611  inCustomParking = rc;
612 }
613 
614 void Mount::find()
615 {
616  updateJ2000Coordinates(&currentCoords);
617  double maxrad = 1000.0 / Options::zoomFactor();
618  SkyObject *currentObject = KStarsData::Instance()->skyComposite()->objectNearest(&currentCoords, maxrad);
619  KStars::Instance()->map()->setFocusObject(currentObject);
620  KStars::Instance()->map()->setDestination(currentCoords);
621 }
622 void Mount::centerLock()
623 {
624  if (Options::isTracking() == false ||
625  currentCoords.angularDistanceTo(KStars::Instance()->map()->focus()).Degrees() > 0.5)
626  {
627  updateJ2000Coordinates(&currentCoords);
628  KStars::Instance()->map()->setDestination(currentCoords);
629  KStars::Instance()->map()->setFocusPoint(&currentCoords);
630  KStars::Instance()->map()->setFocusObject(nullptr);
631  Options::setIsTracking(true);
632  }
633  centerLockTimer.start();
634 }
635 
636 void Mount::centerUnlock()
637 {
638  KStars::Instance()->map()->stopTracking();
639  centerLockTimer.stop();
640 }
641 
642 bool Mount::sendCoords(SkyPoint * ScopeTarget)
643 {
644  INumber *RAEle = nullptr;
645  INumber *DecEle = nullptr;
646  INumber *AzEle = nullptr;
647  INumber *AltEle = nullptr;
648  double currentRA = 0, currentDEC = 0, currentAlt = 0, currentAz = 0;
649  bool useJ2000(false);
650 
651  auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
652  if (!EqProp)
653  {
654  // J2000 Property
655  EqProp = getNumber("EQUATORIAL_COORD");
656  if (EqProp)
657  useJ2000 = true;
658  }
659 
660  auto HorProp = getNumber("HORIZONTAL_COORD");
661 
662  if (EqProp && EqProp->getPermission() == IP_RO)
663  EqProp = nullptr;
664 
665  if (HorProp && HorProp->getPermission() == IP_RO)
666  HorProp = nullptr;
667 
668  //qDebug() << Q_FUNC_INFO << "Skymap click - RA: " << scope_target->ra().toHMSString() << " DEC: " << scope_target->dec().toDMSString();
669 
670  if (EqProp)
671  {
672  RAEle = EqProp->findWidgetByName("RA");
673  if (!RAEle)
674  return false;
675 
676  DecEle = EqProp->findWidgetByName("DEC");
677  if (!DecEle)
678  return false;
679 
680  //if (useJ2000)
681  //ScopeTarget->apparentCoord( KStars::Instance()->data()->ut().djd(), static_cast<long double>(J2000));
682 
683  currentRA = RAEle->value;
684  currentDEC = DecEle->value;
685 
686  ScopeTarget->EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
687  }
688 
689  if (HorProp)
690  {
691  AzEle = IUFindNumber(HorProp, "AZ");
692  if (!AzEle)
693  return false;
694  AltEle = IUFindNumber(HorProp, "ALT");
695  if (!AltEle)
696  return false;
697 
698  currentAz = AzEle->value;
699  currentAlt = AltEle->value;
700  }
701 
702  /* Could not find either properties! */
703  if (EqProp == nullptr && HorProp == nullptr)
704  return false;
705 
706  // Function for sending the coordinates to the INDI mount device
707  // via the ClientManager. This helper function translates EKOS objects into INDI commands.
708  auto sendToMountDevice = [ = ]()
709  {
710  // communicate the new target only if a slew will be executed for the given coordinates
711  if (slewDefined())
712  {
713  emit newTarget(*ScopeTarget);
714  if (currentObject)
715  emit newTargetName(currentObject->name());
716  // If there is no object, we must clear target as it might give wrong
717  // indication we are still on it.
718  else
719  emit newTargetName(QString());
720  }
721 
722  if (EqProp)
723  {
724  dms ra, de;
725 
726  if (useJ2000)
727  {
728  // If we have invalid DEC, then convert coords to J2000
729  if (ScopeTarget->dec0().Degrees() == 180.0)
730  {
731  ScopeTarget->setRA0(ScopeTarget->ra());
732  ScopeTarget->setDec0(ScopeTarget->dec());
733  ScopeTarget->catalogueCoord( KStars::Instance()->data()->ut().djd());
734  ra = ScopeTarget->ra();
735  de = ScopeTarget->dec();
736  }
737  else
738  {
739  ra = ScopeTarget->ra0();
740  de = ScopeTarget->dec0();
741  }
742  }
743  else
744  {
745  ra = ScopeTarget->ra();
746  de = ScopeTarget->dec();
747  }
748 
749  RAEle->value = ra.Hours();
750  DecEle->value = de.Degrees();
751  sendNewProperty(EqProp);
752 
753  qCDebug(KSTARS_INDI) << "ISD:Telescope sending coords RA:" << ra.toHMSString() <<
754  "(" << RAEle->value << ") DE:" << de.toDMSString() <<
755  "(" << DecEle->value << ")";
756 
757  RAEle->value = currentRA;
758  DecEle->value = currentDEC;
759  }
760  // Only send Horizontal Coord property if Equatorial is not available.
761  else if (HorProp)
762  {
763  AzEle->value = ScopeTarget->az().Degrees();
764  AltEle->value = ScopeTarget->alt().Degrees();
765  sendNewProperty(HorProp);
766  AzEle->value = currentAz;
767  AltEle->value = currentAlt;
768  }
769 
770  };
771 
772  // Helper function that first checks for the selected target object whether the
773  // tracking modes have to be adapted (special cases moon and sun), explicitely warns before
774  // slewing to the sun and finally (independant whether there exists a target object
775  // for the target coordinates) calls sendToMountDevice
776  auto checkObjectAndSend = [ = ]()
777  {
778  // Search within 0.1 degrees indepdent of zoom level.
779  double maxrad = 0.1;
780  currentObject = KStarsData::Instance()->skyComposite()->objectNearest(ScopeTarget, maxrad);
781  if (currentObject)
782  {
783  auto checkTrackModes = [ = ]()
784  {
785  if (m_hasTrackModes)
786  {
787  // Tracking Moon
788  if (currentObject->type() == SkyObject::MOON)
789  {
790  if (currentTrackMode != TRACK_LUNAR && TrackMap.contains(TRACK_LUNAR))
791  setTrackMode(TrackMap.value(TRACK_LUNAR));
792  }
793  // Tracking Sun
794  else if (currentObject->name() == i18n("Sun"))
795  {
796  if (currentTrackMode != TRACK_SOLAR && TrackMap.contains(TRACK_SOLAR))
797  setTrackMode(TrackMap.value(TRACK_SOLAR));
798  }
799  // If Last track mode was either set to SOLAR or LUNAR but now we are slewing to a different object
800  // then we automatically fallback to sidereal. If the current track mode is CUSTOM or something else, nothing
801  // changes.
802  else if (currentTrackMode == TRACK_SOLAR || currentTrackMode == TRACK_LUNAR)
803  setTrackMode(TRACK_SIDEREAL);
804 
805  }
806  };
807 
808  // Sun Warning, but don't ask if tracking is already solar.
809  if (currentObject->name() == i18n("Sun") && currentTrackMode != TRACK_SOLAR)
810  {
811  connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [ = ]()
812  {
813  KSMessageBox::Instance()->disconnect(this);
814  checkTrackModes();
815  sendToMountDevice();
816  });
817  connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [ = ]()
818  {
819  KSMessageBox::Instance()->disconnect(this);
820  });
821 
822  KSMessageBox::Instance()->questionYesNo(
823  i18n("Warning! Looking at the Sun without proper protection can lead to irreversible eye damage!"),
824  i18n("Sun Warning"));
825  }
826  else
827  {
828  checkTrackModes();
829  sendToMountDevice();
830  }
831  }
832  else
833  sendToMountDevice();
834  };
835 
836  // If altitude limits is enabled, then reject motion immediately.
837  double targetAlt = ScopeTarget->altRefracted().Degrees();
838 
839  if ((-90 <= minAlt && maxAlt <= 90) && (targetAlt < minAlt || targetAlt > maxAlt))
840  {
841  KSNotification::event(QLatin1String("IndiServerMessage"),
842  i18n("Requested altitude %1 is outside the specified altitude limit boundary (%2,%3).",
843  QString::number(targetAlt, 'g', 3), QString::number(minAlt, 'g', 3),
844  QString::number(maxAlt, 'g', 3)), KSNotification::Mount, KSNotification::Warn);
845  qCInfo(KSTARS_INDI) << "Requested altitude " << QString::number(targetAlt, 'g', 3)
846  << " is outside the specified altitude limit boundary ("
847  << QString::number(minAlt, 'g', 3) << "," << QString::number(maxAlt, 'g', 3) << ").";
848  return false;
849  }
850 
851  // If disabled, then check if below horizon and warning the user unless the user previously dismissed it.
852  if (Options::confirmBelowHorizon() && targetAlt < 0 && minAlt == -1)
853  {
854  connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [ = ]()
855  {
856  if (minAlt < -90 && +90 < maxAlt)
857  Options::setConfirmBelowHorizon(false);
858  KSMessageBox::Instance()->disconnect(this);
859  checkObjectAndSend();
860  });
861  connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [ = ]()
862  {
863  KSMessageBox::Instance()->disconnect(this);
864  if (EqProp)
865  {
866  RAEle->value = currentRA;
867  DecEle->value = currentDEC;
868  }
869  if (HorProp)
870  {
871  AzEle->value = currentAz;
872  AltEle->value = currentAlt;
873  }
874  });
875 
876  KSMessageBox::Instance()->questionYesNo(i18n("Requested altitude is below the horizon. Are you sure you want to proceed?"),
877  i18n("Telescope Motion"), 15, false);
878  }
879  else
880  checkObjectAndSend();
881 
882  return true;
883 }
884 
886 {
887  auto motionSP = getSwitch("ON_COORD_SET");
888 
889  if (motionSP == nullptr)
890  return false;
891  // A slew will happen if either Track, Slew, or Flip
892  // is selected
893  auto sp = motionSP->findOnSwitch();
894  if(sp != nullptr &&
895  (sp->name == std::string("TRACK") ||
896  sp->name == std::string("SLEW") ||
897  sp->name == std::string("FLIP")))
898  {
899  return true;
900  }
901  else
902  {
903  return false;
904  }
905 }
906 
907 bool Mount::Slew(double ra, double dec, bool flip)
908 {
909  SkyPoint target;
910 
911  if (m_isJ2000)
912  {
913  target.setRA0(ra);
914  target.setDec0(dec);
915  }
916  else
917  {
918  target.setRA(ra);
919  target.setDec(dec);
920  }
921 
922  return Slew(&target, flip);
923 }
924 
925 bool Mount::Slew(SkyPoint * ScopeTarget, bool flip)
926 {
927  auto motionSP = getSwitch("ON_COORD_SET");
928 
929  if (!motionSP)
930  return false;
931 
932  auto slewSW = flip ? motionSP->findWidgetByName("FLIP") : motionSP->findWidgetByName("TRACK");
933 
934  if (flip && (!slewSW))
935  slewSW = motionSP->findWidgetByName("TRACK");
936 
937  if (!slewSW)
938  slewSW = motionSP->findWidgetByName("SLEW");
939 
940  if (!slewSW)
941  return false;
942 
943  if (slewSW->getState() != ISS_ON)
944  {
945  motionSP->reset();
946  slewSW->setState(ISS_ON);
947  sendNewProperty(motionSP);
948 
949  qCDebug(KSTARS_INDI) << "ISD:Telescope: " << slewSW->getName();
950  }
951 
952  return sendCoords(ScopeTarget);
953 }
954 
955 bool Mount::Sync(double ra, double dec)
956 {
957  SkyPoint target;
958 
959  target.setRA(ra);
960  target.setDec(dec);
961 
962  return Sync(&target);
963 }
964 
965 bool Mount::Sync(SkyPoint * ScopeTarget)
966 {
967  auto motionSP = getSwitch("ON_COORD_SET");
968 
969  if (!motionSP)
970  return false;
971 
972  auto syncSW = motionSP->findWidgetByName("SYNC");
973 
974  if (!syncSW)
975  return false;
976 
977  if (syncSW->getState() != ISS_ON)
978  {
979  motionSP->reset();
980  syncSW->setState(ISS_ON);
981  sendNewProperty(motionSP);
982 
983  qCDebug(KSTARS_INDI) << "ISD:Telescope: Syncing...";
984  }
985 
986  return sendCoords(ScopeTarget);
987 }
988 
989 bool Mount::abort()
990 {
991  auto motionSP = getSwitch("TELESCOPE_ABORT_MOTION");
992 
993  if (!motionSP)
994  return false;
995 
996  auto abortSW = motionSP->findWidgetByName("ABORT");
997 
998  if (!abortSW)
999  return false;
1000 
1001  qCDebug(KSTARS_INDI) << "ISD:Telescope: Aborted." << Qt::endl;
1002 
1003  abortSW->setState(ISS_ON);
1004  sendNewProperty(motionSP);
1005 
1006  inCustomParking = false;
1007 
1008  return true;
1009 }
1010 
1011 bool Mount::park()
1012 {
1013  auto parkSP = getSwitch("TELESCOPE_PARK");
1014 
1015  if (!parkSP)
1016  return false;
1017 
1018  auto parkSW = parkSP->findWidgetByName("PARK");
1019 
1020  if (!parkSW)
1021  return false;
1022 
1023  qCDebug(KSTARS_INDI) << "ISD:Telescope: Parking..." << Qt::endl;
1024 
1025  parkSP->reset();
1026  parkSW->setState(ISS_ON);
1027  sendNewProperty(parkSP);
1028 
1029  return true;
1030 }
1031 
1032 bool Mount::unpark()
1033 {
1034  auto parkSP = getSwitch("TELESCOPE_PARK");
1035 
1036  if (!parkSP)
1037  return false;
1038 
1039  auto parkSW = parkSP->findWidgetByName("UNPARK");
1040 
1041  if (!parkSW)
1042  return false;
1043 
1044  qCDebug(KSTARS_INDI) << "ISD:Telescope: UnParking..." << Qt::endl;
1045 
1046  parkSP->reset();
1047  parkSW->setState(ISS_ON);
1048  sendNewProperty(parkSP);
1049 
1050  return true;
1051 }
1052 
1053 bool Mount::getEqCoords(double * ra, double * dec)
1054 {
1055  auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
1056  if (!EqProp)
1057  {
1058  EqProp = getNumber("EQUATORIAL_COORD");
1059  if (!EqProp)
1060  return false;
1061  }
1062 
1063  auto RAEle = EqProp->findWidgetByName("RA");
1064  if (!RAEle)
1065  return false;
1066 
1067  auto DecEle = EqProp->findWidgetByName("DEC");
1068  if (!DecEle)
1069  return false;
1070 
1071  *ra = RAEle->getValue();
1072  *dec = DecEle->getValue();
1073 
1074  return true;
1075 }
1076 
1077 bool Mount::MoveNS(VerticalMotion dir, MotionCommand cmd)
1078 {
1079  auto motionSP = getSwitch("TELESCOPE_MOTION_NS");
1080 
1081  if (!motionSP)
1082  return false;
1083 
1084  auto motionNorth = motionSP->findWidgetByName("MOTION_NORTH");
1085  auto motionSouth = motionSP->findWidgetByName("MOTION_SOUTH");
1086 
1087  if (!motionNorth || !motionSouth)
1088  return false;
1089 
1090  // If same direction, return
1091  if (dir == MOTION_NORTH && motionNorth->getState() == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF))
1092  return true;
1093 
1094  if (dir == MOTION_SOUTH && motionSouth->getState() == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF))
1095  return true;
1096 
1097  motionSP->reset();
1098 
1099  if (cmd == MOTION_START)
1100  {
1101  if (dir == MOTION_NORTH)
1102  motionNorth->setState(ISS_ON);
1103  else
1104  motionSouth->setState(ISS_ON);
1105  }
1106 
1107  sendNewProperty(motionSP);
1108 
1109  return true;
1110 }
1111 
1112 bool Mount::StopWE()
1113 {
1114  auto motionSP = getSwitch("TELESCOPE_MOTION_WE");
1115 
1116  if (!motionSP)
1117  return false;
1118 
1119  motionSP->reset();
1120 
1121  sendNewProperty(motionSP);
1122 
1123  return true;
1124 }
1125 
1126 bool Mount::StopNS()
1127 {
1128  auto motionSP = getSwitch("TELESCOPE_MOTION_NS");
1129 
1130  if (!motionSP)
1131  return false;
1132 
1133  motionSP->reset();
1134 
1135  sendNewProperty(motionSP);
1136 
1137  return true;
1138 }
1139 
1140 bool Mount::MoveWE(HorizontalMotion dir, MotionCommand cmd)
1141 {
1142  auto motionSP = getSwitch("TELESCOPE_MOTION_WE");
1143 
1144  if (!motionSP)
1145  return false;
1146 
1147  auto motionWest = motionSP->findWidgetByName("MOTION_WEST");
1148  auto motionEast = motionSP->findWidgetByName("MOTION_EAST");
1149 
1150  if (!motionWest || !motionEast)
1151  return false;
1152 
1153  // If same direction, return
1154  if (dir == MOTION_WEST && motionWest->getState() == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF))
1155  return true;
1156 
1157  if (dir == MOTION_EAST && motionEast->getState() == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF))
1158  return true;
1159 
1160  motionSP->reset();
1161 
1162  if (cmd == MOTION_START)
1163  {
1164  if (dir == MOTION_WEST)
1165  motionWest->setState(ISS_ON);
1166  else
1167  motionEast->setState(ISS_ON);
1168  }
1169 
1170  sendNewProperty(motionSP);
1171 
1172  return true;
1173 }
1174 
1175 bool Mount::setSlewRate(int index)
1176 {
1177  auto slewRateSP = getSwitch("TELESCOPE_SLEW_RATE");
1178 
1179  if (!slewRateSP)
1180  return false;
1181 
1182  if (index < 0 || index > slewRateSP->count())
1183  return false;
1184  else if (slewRateSP->findOnSwitchIndex() == index)
1185  return true;
1186 
1187  slewRateSP->reset();
1188 
1189  slewRateSP->at(index)->setState(ISS_ON);
1190 
1191  sendNewProperty(slewRateSP);
1192 
1193  emit slewRateChanged(index);
1194 
1195  return true;
1196 }
1197 
1198 int Mount::getSlewRate() const
1199 {
1200  auto slewRateSP = getSwitch("TELESCOPE_SLEW_RATE");
1201 
1202  if (!slewRateSP)
1203  return -1;
1204 
1205  return slewRateSP->findOnSwitchIndex();
1206 }
1207 
1208 void Mount::setAltLimits(double minAltitude, double maxAltitude)
1209 {
1210  minAlt = minAltitude;
1211  maxAlt = maxAltitude;
1212 }
1213 
1214 bool Mount::setAlignmentModelEnabled(bool enable)
1215 {
1216  bool wasExecuted = false;
1217 
1218  // For INDI Alignment Subsystem
1219  auto alignSwitch = getSwitch("ALIGNMENT_SUBSYSTEM_ACTIVE");
1220  if (alignSwitch)
1221  {
1222  alignSwitch->at(0)->setState(enable ? ISS_ON : ISS_OFF);
1223  sendNewProperty(alignSwitch);
1224  wasExecuted = true;
1225  }
1226 
1227  // For EQMod Alignment --- Temporary until all drivers switch fully to INDI Alignment Subsystem
1228  alignSwitch = getSwitch("ALIGNMODE");
1229  if (alignSwitch)
1230  {
1231  alignSwitch->reset();
1232  // For now, always set alignment mode to NSTAR on enable.
1233  if (enable)
1234  alignSwitch->at(2)->setState(ISS_ON);
1235  // Otherwise, set to NO ALIGN
1236  else
1237  alignSwitch->at(0)->setState(ISS_ON);
1238 
1239  sendNewProperty(alignSwitch);
1240  wasExecuted = true;
1241  }
1242 
1243  return wasExecuted;
1244 }
1245 
1246 bool Mount::setSatelliteTLEandTrack(QString tle, const KStarsDateTime satPassStart, const KStarsDateTime satPassEnd)
1247 {
1248  auto tleTextVec = getText("SAT_TLE_TEXT");
1249  if (!tleTextVec)
1250  {
1251  qCDebug(KSTARS_INDI) << "Property SAT_TLE_TEXT not found";
1252  return false;
1253  }
1254 
1255  auto tleText = tleTextVec->findWidgetByName("TLE");
1256  if (!tleText)
1257  return false;
1258 
1259  tleText->setText(tle.toLocal8Bit().data());
1260 
1261  sendNewProperty(tleTextVec);
1262  m_TLEIsSetForTracking = true;
1263  g_satPassStart = satPassStart;
1264  g_satPassEnd = satPassEnd;
1265  return true;
1266  // See Telescope::processText for the following steps (setting window and switch)
1267 }
1268 
1269 
1270 bool Mount::clearParking()
1271 {
1272  auto parkSwitch = getSwitch("TELESCOPE_PARK_OPTION");
1273  if (!parkSwitch)
1274  return false;
1275 
1276  auto clearParkSW = parkSwitch->findWidgetByName("PARK_PURGE_DATA");
1277  if (!clearParkSW)
1278  return false;
1279 
1280  parkSwitch->reset();
1281  clearParkSW->setState(ISS_ON);
1282 
1283  sendNewProperty(parkSwitch);
1284  return true;
1285 }
1286 
1287 bool Mount::clearAlignmentModel()
1288 {
1289  bool wasExecuted = false;
1290 
1291  // Note: Should probably use INDI Alignment Subsystem Client API in the future?
1292  auto clearSwitch = getSwitch("ALIGNMENT_POINTSET_ACTION");
1293  auto commitSwitch = getSwitch("ALIGNMENT_POINTSET_COMMIT");
1294  if (clearSwitch && commitSwitch)
1295  {
1296  clearSwitch->reset();
1297  // ALIGNMENT_POINTSET_ACTION.CLEAR
1298  clearSwitch->at(4)->setState(ISS_ON);
1299  sendNewProperty(clearSwitch);
1300  commitSwitch->at(0)->setState(ISS_ON);
1301  sendNewProperty(commitSwitch);
1302  wasExecuted = true;
1303  }
1304 
1305  // For EQMod Alignment --- Temporary until all drivers switch fully to INDI Alignment Subsystem
1306  clearSwitch = getSwitch("ALIGNLIST");
1307  if (clearSwitch)
1308  {
1309  // ALIGNLISTCLEAR
1310  clearSwitch->reset();
1311  clearSwitch->at(1)->setState(ISS_ON);
1312  sendNewProperty(clearSwitch);
1313  wasExecuted = true;
1314  }
1315 
1316  return wasExecuted;
1317 }
1318 
1319 Mount::Status Mount::status()
1320 {
1321  auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
1322  if (EqProp == nullptr)
1323  {
1324  EqProp = getNumber("EQUATORIAL_COORD");
1325  if (EqProp == nullptr)
1326  return MOUNT_ERROR;
1327  }
1328 
1329  return status(EqProp);
1330 }
1331 
1332 const QString Mount::statusString(Mount::Status status, bool translated) const
1333 {
1334  switch (status)
1335  {
1336  case ISD::Mount::MOUNT_MOVING:
1337  return (translated ? i18n(mountStates[status]) : mountStates[status]) + QString(" %1").arg(getManualMotionString());
1338  default:
1339  return translated ? i18n(mountStates[status]) : mountStates[status];
1340  }
1341 }
1342 
1343 QString Mount::getManualMotionString() const
1344 {
1345  QString NSMotion, WEMotion;
1346 
1347  auto movementSP = getSwitch("TELESCOPE_MOTION_NS");
1348  if (movementSP)
1349  {
1350  if (movementSP->at(MOTION_NORTH)->getState() == ISS_ON)
1351  NSMotion = 'N';
1352  else if (movementSP->at(MOTION_SOUTH)->getState() == ISS_ON)
1353  NSMotion = 'S';
1354  }
1355 
1356  movementSP = getSwitch("TELESCOPE_MOTION_WE");
1357  if (movementSP)
1358  {
1359  if (movementSP->at(MOTION_WEST)->getState() == ISS_ON)
1360  WEMotion = 'W';
1361  else if (movementSP->at(MOTION_EAST)->getState() == ISS_ON)
1362  WEMotion = 'E';
1363  }
1364 
1365  return QString("%1%2").arg(NSMotion, WEMotion);
1366 }
1367 
1368 bool Mount::setTrackEnabled(bool enable)
1369 {
1370  auto trackSP = getSwitch("TELESCOPE_TRACK_STATE");
1371  if (!trackSP)
1372  return false;
1373 
1374  auto trackON = trackSP->findWidgetByName("TRACK_ON");
1375  auto trackOFF = trackSP->findWidgetByName("TRACK_OFF");
1376 
1377  if (!trackON || !trackOFF)
1378  return false;
1379 
1380  trackON->setState(enable ? ISS_ON : ISS_OFF);
1381  trackOFF->setState(enable ? ISS_OFF : ISS_ON);
1382 
1383  sendNewProperty(trackSP);
1384 
1385  return true;
1386 }
1387 
1388 bool Mount::isTracking()
1389 {
1390  return (status() == MOUNT_TRACKING);
1391 }
1392 
1393 bool Mount::setTrackMode(uint8_t index)
1394 {
1395  auto trackModeSP = getSwitch("TELESCOPE_TRACK_MODE");
1396  if (!trackModeSP)
1397  return false;
1398 
1399  if (index >= trackModeSP->nsp)
1400  return false;
1401 
1402  trackModeSP->reset();
1403  trackModeSP->at(index)->setState(ISS_ON);
1404 
1405  sendNewProperty(trackModeSP);
1406 
1407  return true;
1408 }
1409 
1410 bool Mount::getTrackMode(uint8_t &index)
1411 {
1412  auto trackModeSP = getSwitch("TELESCOPE_TRACK_MODE");
1413  if (!trackModeSP)
1414  return false;
1415 
1416  index = trackModeSP->findOnSwitchIndex();
1417 
1418  return true;
1419 }
1420 
1421 bool Mount::setCustomTrackRate(double raRate, double deRate)
1422 {
1423  auto trackRateNP = getNumber("TELESCOPE_TRACK_RATE");
1424  if (!trackRateNP)
1425  return false;
1426 
1427  auto raRateN = trackRateNP->findWidgetByName("TRACK_RATE_RA");
1428  auto deRateN = trackRateNP->findWidgetByName("TRACK_RATE_DE");
1429 
1430  if (!raRateN || !deRateN)
1431  return false;
1432 
1433  raRateN->setValue(raRate);
1434  deRateN->setValue(deRate);
1435 
1436  sendNewProperty(trackRateNP);
1437 
1438  return true;
1439 }
1440 
1441 bool Mount::getCustomTrackRate(double &raRate, double &deRate)
1442 {
1443  auto trackRateNP = getNumber("TELESCOPE_TRACK_RATE");
1444  if (!trackRateNP)
1445  return false;
1446 
1447  auto raRateN = trackRateNP->findWidgetByName("TRACK_RATE_RA");
1448  auto deRateN = trackRateNP->findWidgetByName("TRACK_RATE_DE");
1449 
1450  if (!raRateN || !deRateN)
1451  return false;
1452 
1453  raRate = raRateN->getValue();
1454  deRate = deRateN->getValue();
1455 
1456  return true;
1457 
1458 }
1459 
1460 bool Mount::sendParkingOptionCommand(ParkOptionCommand command)
1461 {
1462  auto parkOptionsSP = getSwitch("TELESCOPE_PARK_OPTION");
1463  if (!parkOptionsSP)
1464  return false;
1465 
1466  parkOptionsSP->reset();
1467  parkOptionsSP->at(command)->setState(ISS_ON);
1468  sendNewProperty(parkOptionsSP);
1469 
1470  return true;
1471 }
1472 
1473 Mount::Status Mount::status(INumberVectorProperty * nvp)
1474 {
1475  switch (nvp->s)
1476  {
1477  case IPS_IDLE:
1478  if (inManualMotion)
1479  return MOUNT_MOVING;
1480  else if (isParked())
1481  return MOUNT_PARKED;
1482  else
1483  return MOUNT_IDLE;
1484 
1485  case IPS_OK:
1486  if (inManualMotion)
1487  return MOUNT_MOVING;
1488  else if (inCustomParking)
1489  {
1490  inCustomParking = false;
1491  // set CURRENT position as the desired parking position
1492  sendParkingOptionCommand(PARK_OPTION_CURRENT);
1493  // Write data to disk
1494  sendParkingOptionCommand(PARK_OPTION_WRITE_DATA);
1495 
1496  return MOUNT_TRACKING;
1497  }
1498  else
1499  return MOUNT_TRACKING;
1500 
1501  case IPS_BUSY:
1502  {
1503  if (inManualMotion)
1504  return MOUNT_MOVING;
1505 
1506  auto parkSP = getSwitch("TELESCOPE_PARK");
1507  if (parkSP && parkSP->getState() == IPS_BUSY)
1508  return MOUNT_PARKING;
1509  else
1510  return MOUNT_SLEWING;
1511  }
1512 
1513  case IPS_ALERT:
1514  inCustomParking = false;
1515  return MOUNT_ERROR;
1516  }
1517 
1518  return MOUNT_ERROR;
1519 }
1520 
1521 const dms Mount::hourAngle() const
1522 {
1523  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1524  return dms(lst.Degrees() - currentCoords.ra().Degrees());
1525 }
1526 
1527 bool Mount::isReversed(INDI_EQ_AXIS axis)
1528 {
1529  auto reversed = getSwitch("TELESCOPE_REVERSE_MOTION");
1530  if (!reversed)
1531  return false;
1532 
1533  return reversed->at(axis == AXIS_DE ? 0 : 1)->getState() == ISS_ON;
1534 }
1535 
1536 bool Mount::setReversedEnabled(INDI_EQ_AXIS axis, bool enabled)
1537 {
1538  auto reversed = getSwitch("TELESCOPE_REVERSE_MOTION");
1539  if (!reversed)
1540  return false;
1541 
1542  reversed->at(axis == AXIS_DE ? 0 : 1)->setState(enabled ? ISS_ON : ISS_OFF);
1543  sendNewProperty(reversed);
1544  return true;
1545 }
1546 
1548 {
1549  updateCoordinatesTimer.stop();
1550  centerLockTimer.stop();
1551 }
1552 
1553 }
1554 
1555 QDBusArgument &operator<<(QDBusArgument &argument, const ISD::Mount::Status &source)
1556 {
1557  argument.beginStructure();
1558  argument << static_cast<int>(source);
1559  argument.endStructure();
1560  return argument;
1561 }
1562 
1563 const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::Mount::Status &dest)
1564 {
1565  int a;
1566  argument.beginStructure();
1567  argument >> a;
1568  argument.endStructure();
1569  dest = static_cast<ISD::Mount::Status>(a);
1570  return argument;
1571 }
1572 
1573 QDBusArgument &operator<<(QDBusArgument &argument, const ISD::Mount::PierSide &source)
1574 {
1575  argument.beginStructure();
1576  argument << static_cast<int>(source);
1577  argument.endStructure();
1578  return argument;
1579 }
1580 
1581 const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::Mount::PierSide &dest)
1582 {
1583  int a;
1584  argument.beginStructure();
1585  argument >> a;
1586  argument.endStructure();
1587  dest = static_cast<ISD::Mount::PierSide>(a);
1588  return argument;
1589 }
1590 
const dms & alt() const
Definition: skypoint.h:281
bool setSatelliteTLEandTrack(QString tle, const KStarsDateTime satPassStart, const KStarsDateTime satPassEnd)
Tracks satellite on provided TLE, initial epoch for trajectory calculation and window in minutes.
Definition: indimount.cpp:1246
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
QAction * action(const QString &name) const
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition: skypoint.h:194
void updateTarget()
updateTarget update target position from {
Definition: indimount.cpp:195
bool isValid() const const
bool contains(const Key &key) const const
bool isActive() const const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
void stopTimers()
stopTimers Stop timers to prevent timing race condition when device is unavailable and timer is still...
Definition: indimount.cpp:1547
const T value(const Key &key, const T &defaultValue) const const
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
void slotSetTelescopeEnabled(bool enable)
slotSetTelescopeEnabled call when telescope comes online or goes offline.
void setDec0(dms d)
Sets Dec0, the catalog Declination.
Definition: skypoint.h:119
void newTarget(SkyPoint &currentCoords)
The mount has finished the slew to a new target.
void update()
virtual QString name(void) const
Definition: skyobject.h:145
INDI::PropertyView< ISwitch > * getSwitch(const QString &name) const
SkyMap * map() const
Definition: kstars.h:141
void updateParkStatus()
updateParkStatus Updating parking status by checking the TELESCOPE_PARK property.
Definition: indimount.cpp:441
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition: skypoint.h:94
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
Definition: skypoint.cpp:77
void setFocusObject(SkyObject *o)
Set the FocusObject pointer to the argument.
Definition: skymap.cpp:368
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:370
dms altRefracted() const
Definition: skypoint.cpp:1050
const dms hourAngle() const
Hour angle of the current coordinates.
Definition: indimount.cpp:1521
static KStars * Instance()
Definition: kstars.h:123
int type(void) const
Definition: skyobject.h:188
void setDestination(const SkyPoint &f)
sets the destination point of the sky map.
Definition: skymap.cpp:983
bool slewDefined()
Check whether sending new coordinates will result into a slew.
Definition: indimount.cpp:885
void start(int msec)
void rejected()
QString i18n(const char *text, const TYPE &arg...)
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
const CachingDms & dec() const
Definition: skypoint.h:269
SkyPoint * focus()
Retrieve the Focus point; the position on the sky at the center of the skymap.
Definition: skymap.h:123
void timeout()
GeoLocation * geo()
Definition: kstarsdata.h:230
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
SkyObject * objectNearest(SkyPoint *p, double &maxrad) override
void setFocusPoint(SkyPoint *f)
set the FocusPoint; the position that is to be the next Destination.
Definition: skymap.h:204
void reset()
QTextStream & dec(QTextStream &stream)
GeoCoordinates geo(const QVariant &location)
void updateJ2000Coordinates(SkyPoint *coords)
Helper function to update the J2000 coordinates of a sky point from its JNow coordinates.
Definition: indimount.cpp:187
dms angularDistanceTo(const SkyPoint *sp, double *const positionAngle=nullptr) const
Computes the angular distance between two SkyObjects.
Definition: skypoint.cpp:899
QCA_EXPORT QVariant getProperty(const QString &name)
SkyMapComposite * skyComposite()
Definition: kstarsdata.h:166
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
void newStatus(ISD::Mount::Status status)
Change in the mount status.
void setAz(dms az)
Sets Az, the Azimuth.
Definition: skypoint.h:230
virtual KActionCollection * actionCollection() const
const CachingDms & ra() const
Definition: skypoint.h:263
void setEnabled(bool)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QDebug operator<<(QDebug d, const QCPVector2D &vec)
Definition: qcustomplot.h:446
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
Definition: skypoint.cpp:700
const CachingDms & dec0() const
Definition: skypoint.h:257
void stop()
const double & Degrees() const
Definition: dms.h:141
void beginStructure()
void sendNewProperty(INDI::Property prop)
Send new property command to server.
const CachingDms & ra0() const
Definition: skypoint.h:251
void setDec(dms d)
Sets Dec, the current Declination.
Definition: skypoint.h:169
bool sendCoords(SkyPoint *ScopeTarget)
Send the coordinates to the mount's INDI driver.
Definition: indimount.cpp:642
void endStructure()
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
Definition: skypoint.cpp:143
#define I18N_NOOP(text)
void setRA(dms &r)
Sets RA, the current Right Ascension.
Definition: skypoint.h:144
QByteArray toLocal8Bit() const const
INDI::PropertyView< IText > * getText(const QString &name) const
QString toString(Qt::DateFormat format) const const
void setInterval(int msec)
QDataStream & operator>>(QDataStream &in, KDateTime &dateTime)
QFuture< void > map(Sequence &sequence, MapFunctor function)
Information about an object in the sky.
Definition: skyobject.h:41
double Hours() const
Definition: dms.h:168
SkyPoint catalogueCoord(long double jdf)
Computes the J2000.0 catalogue coordinates for this SkyPoint using the epoch removing aberration,...
Definition: skypoint.cpp:710
char * data()
void accepted()
const dms & az() const
Definition: skypoint.h:275
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Sep 26 2023 03:55:47 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.