Kstars

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

KDE's Doxygen guidelines are available online.