File indexing completed on 2024-04-21 07:30:22

0001 /*
0002     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "indimount.h"
0008 
0009 #include "ksmessagebox.h"
0010 #include "driverinfo.h"
0011 #include "kstars.h"
0012 #include "Options.h"
0013 #include "skymap.h"
0014 #include "skymapcomposite.h"
0015 #include "ksnotification.h"
0016 
0017 #include <KActionCollection>
0018 #include <KLocalizedString>
0019 
0020 #include <QAction>
0021 #include <qdbusmetatype.h>
0022 
0023 #include <indi_debug.h>
0024 
0025 // Qt version calming
0026 #include <qtendl.h>
0027 
0028 namespace ISD
0029 {
0030 
0031 const QList<const char *> Mount::mountStates = { I18N_NOOP("Idle"),  I18N_NOOP("Moving"), I18N_NOOP("Slewing"),
0032                                                  I18N_NOOP("Tracking"), I18N_NOOP("Parking"), I18N_NOOP("Parked"),
0033                                                  I18N_NOOP("Error")
0034                                                };
0035 
0036 Mount::Mount(GenericDevice *parent) : ConcreteDevice(parent)
0037 {
0038     // Set it for 5 seconds for now as not to spam the display update
0039     centerLockTimer.setInterval(5000);
0040     centerLockTimer.setSingleShot(true);
0041     connect(&centerLockTimer, &QTimer::timeout, this, [this]()
0042     {
0043         //runCommand(INDI_CENTER_LOCK);
0044         centerLock();
0045     });
0046 
0047     // Regularly update the coordinates even if no update has been sent from the INDI service
0048     updateCoordinatesTimer.setInterval(1000);
0049     updateCoordinatesTimer.setSingleShot(false);
0050     connect(&updateCoordinatesTimer, &QTimer::timeout, this, [this]()
0051     {
0052         if (isConnected())
0053         {
0054             currentCoords.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
0055             emit newCoords(currentCoords, pierSide(), hourAngle());
0056         }
0057     });
0058 
0059     qRegisterMetaType<ISD::Mount::Status>("ISD::Mount::Status");
0060     qDBusRegisterMetaType<ISD::Mount::Status>();
0061 
0062     qRegisterMetaType<ISD::Mount::PierSide>("ISD::Mount::PierSide");
0063     qDBusRegisterMetaType<ISD::Mount::PierSide>();
0064 
0065     // Need to delay check for alignment model to upon connection is established since the property is defined BEFORE Telescope class is created.
0066     // and therefore no registerProperty is called for these properties since they were already registered _before_ the Telescope
0067     // class was created.
0068     m_hasAlignmentModel = getProperty("ALIGNMENT_POINTSET_ACTION").isValid() || getProperty("ALIGNLIST").isValid();
0069 }
0070 
0071 void Mount::registerProperty(INDI::Property prop)
0072 {
0073     if (prop.isNameMatch("TELESCOPE_INFO"))
0074     {
0075         auto ti = prop.getNumber();
0076 
0077         if (!ti)
0078             return;
0079 
0080         bool aperture_ok = false, focal_ok = false;
0081         double temp = 0;
0082 
0083         auto aperture = ti->findWidgetByName("TELESCOPE_APERTURE");
0084         if (aperture && aperture->getValue() <= 0)
0085         {
0086             if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_APERTURE"))
0087             {
0088                 temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_APERTURE").toDouble(&aperture_ok);
0089                 if (aperture_ok)
0090                 {
0091                     aperture->setValue(temp);
0092                     auto g_aperture = ti->findWidgetByName("GUIDER_APERTURE");
0093                     if (g_aperture && g_aperture->getValue() <= 0)
0094                         g_aperture->setValue(aperture->getValue());
0095                 }
0096             }
0097         }
0098 
0099         auto focal_length = ti->findWidgetByName("TELESCOPE_FOCAL_LENGTH");
0100         if (focal_length && focal_length->getValue() <= 0)
0101         {
0102             if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_FOCAL_LENGTH"))
0103             {
0104                 temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_FOCAL_LENGTH").toDouble(&focal_ok);
0105                 if (focal_ok)
0106                 {
0107                     focal_length->setValue(temp);
0108                     auto g_focal = ti->findWidgetByName("GUIDER_FOCAL_LENGTH");
0109                     if (g_focal && g_focal->getValue() <= 0)
0110                         g_focal->setValue(focal_length->getValue());
0111                 }
0112             }
0113         }
0114 
0115         if (aperture_ok && focal_ok)
0116             sendNewProperty(ti);
0117     }
0118     else if (prop.isNameMatch("ON_COORD_SET"))
0119     {
0120         m_canGoto = IUFindSwitch(prop.getSwitch(), "TRACK") != nullptr;
0121         m_canSync = IUFindSwitch(prop.getSwitch(), "SYNC") != nullptr;
0122         m_canFlip = IUFindSwitch(prop.getSwitch(), "FLIP") != nullptr;
0123     }
0124     else if (prop.isNameMatch("TELESCOPE_PIER_SIDE"))
0125     {
0126         auto svp = prop.getSwitch();
0127         int currentSide = svp->findOnSwitchIndex();
0128         if (currentSide != m_PierSide)
0129         {
0130             m_PierSide = static_cast<PierSide>(currentSide);
0131             emit pierSideChanged(m_PierSide);
0132         }
0133     }
0134     else if (prop.isNameMatch("TELESCOPE_PARK"))
0135         updateParkStatus();
0136     else if (prop.isNameMatch("TELESCOPE_TRACK_STATE"))
0137         m_canControlTrack = true;
0138     else if (prop.isNameMatch("TELESCOPE_TRACK_MODE"))
0139     {
0140         m_hasTrackModes = true;
0141         auto svp = prop.getSwitch();
0142         for (int i = 0; i < svp->count(); i++)
0143         {
0144             if (svp->at(i)->isNameMatch("TRACK_SIDEREAL"))
0145                 TrackMap[TRACK_SIDEREAL] = i;
0146             else if (svp->at(i)->isNameMatch("TRACK_SOLAR"))
0147                 TrackMap[TRACK_SOLAR] = i;
0148             else if (svp->at(i)->isNameMatch("TRACK_LUNAR"))
0149                 TrackMap[TRACK_LUNAR] = i;
0150             else if (svp->at(i)->isNameMatch("TRACK_CUSTOM"))
0151                 TrackMap[TRACK_CUSTOM] = i;
0152         }
0153     }
0154     else if (prop.isNameMatch("TELESCOPE_TRACK_RATE"))
0155         m_hasCustomTrackRate = true;
0156     else if (prop.isNameMatch("TELESCOPE_ABORT_MOTION"))
0157         m_canAbort = true;
0158     else if (prop.isNameMatch("TELESCOPE_PARK_OPTION"))
0159         m_hasCustomParking = true;
0160     else if (prop.isNameMatch("TELESCOPE_SLEW_RATE"))
0161     {
0162         m_hasSlewRates = true;
0163         auto svp = prop.getSwitch();
0164         if (svp)
0165         {
0166             m_slewRates.clear();
0167             for (const auto &it : *svp)
0168                 m_slewRates << it.getLabel();
0169         }
0170     }
0171     else if (prop.isNameMatch("EQUATORIAL_EOD_COORD"))
0172     {
0173         m_isJ2000 = false;
0174         m_hasEquatorialCoordProperty = true;
0175     }
0176     else if (prop.isNameMatch("SAT_TRACKING_STAT"))
0177     {
0178         m_canTrackSatellite = true;
0179     }
0180     else if (prop.isNameMatch("EQUATORIAL_COORD"))
0181     {
0182         m_isJ2000 = true;
0183         m_hasEquatorialCoordProperty = true;
0184     }
0185 }
0186 
0187 void Mount::updateJ2000Coordinates(SkyPoint *coords)
0188 {
0189     SkyPoint J2000Coord(coords->ra(), coords->dec());
0190     J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
0191     coords->setRA0(J2000Coord.ra());
0192     coords->setDec0(J2000Coord.dec());
0193 }
0194 
0195 void ISD::Mount::updateTarget()
0196 {
0197     emit newTarget(currentCoords);
0198     double maxrad = 0.1;
0199     currentObject = KStarsData::Instance()->skyComposite()->objectNearest(&currentCoords, maxrad);
0200     if (currentObject)
0201         emit newTargetName(currentObject->name());
0202     // If there is no object, we must clear target as it might give wrong
0203     // indication we are still on it.
0204     else
0205         emit newTargetName(QString());
0206 }
0207 
0208 void Mount::processNumber(INDI::Property prop)
0209 {
0210     auto nvp = prop.getNumber();
0211     if (nvp->isNameMatch("EQUATORIAL_EOD_COORD") || nvp->isNameMatch("EQUATORIAL_COORD"))
0212     {
0213         auto RA  = nvp->findWidgetByName("RA");
0214         auto DEC = nvp->findWidgetByName("DEC");
0215 
0216         if (RA == nullptr || DEC == nullptr)
0217             return;
0218 
0219         // set both JNow and J2000 coordinates
0220         if (isJ2000())
0221         {
0222             currentCoords.setRA0(RA->value);
0223             currentCoords.setDec0(DEC->value);
0224             currentCoords.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
0225         }
0226         else
0227         {
0228             currentCoords.setRA(RA->value);
0229             currentCoords.setDec(DEC->value);
0230             // calculate J2000 coordinates
0231             updateJ2000Coordinates(&currentCoords);
0232         }
0233 
0234         // calculate horizontal coordinates
0235         currentCoords.EquatorialToHorizontal(KStars::Instance()->data()->lst(),
0236                                              KStars::Instance()->data()->geo()->lat());
0237         // ensure that coordinates are regularly updated
0238         if (! updateCoordinatesTimer.isActive())
0239             updateCoordinatesTimer.start();
0240 
0241         auto currentStatus = status(nvp);
0242 
0243         if (nvp->getState() == IPS_BUSY && EqCoordPreviousState != IPS_BUSY)
0244         {
0245             if (currentStatus == MOUNT_SLEWING)
0246                 KSNotification::event(QLatin1String("SlewStarted"), i18n("Mount is slewing to target location"), KSNotification::Mount);
0247             emit newStatus(currentStatus);
0248         }
0249         else if (EqCoordPreviousState == IPS_BUSY && nvp->getState() == IPS_OK && slewDefined())
0250         {
0251             if (Options::useExternalSkyMap())
0252             {
0253                 // For external skymaps the only way to determine the target is to take the position where the mount
0254                 // starts to track
0255                 updateTarget();
0256             }
0257             else
0258             {
0259                 // In case that we use KStars as skymap, we intentionally do not communicate the target here, since it
0260                 // has been set at the beginning of the slew AND we cannot be sure that the position the INDI
0261                 // mount reports when starting to track is exactly that one where the slew went to.
0262                 KSNotification::event(QLatin1String("SlewCompleted"), i18n("Mount arrived at target location"), KSNotification::Mount);
0263             }
0264             emit newStatus(currentStatus);
0265         }
0266 
0267         EqCoordPreviousState = nvp->getState();
0268 
0269         KStars::Instance()->map()->update();
0270     }
0271     // JM 2022.03.11 Only process HORIZONTAL_COORD if it was the ONLY source of information
0272     // When a driver both sends EQUATORIAL_COORD and HORIZONTAL_COORD, we should prioritize EQUATORIAL_COORD
0273     // especially since the conversion from horizontal to equatorial is not as accurate and can result in weird
0274     // coordinates near the poles.
0275     else if (nvp->isNameMatch("HORIZONTAL_COORD") && m_hasEquatorialCoordProperty == false)
0276     {
0277         auto Az  = nvp->findWidgetByName("AZ");
0278         auto Alt = nvp->findWidgetByName("ALT");
0279 
0280         if (Az == nullptr || Alt == nullptr)
0281             return;
0282 
0283         currentCoords.setAz(Az->value);
0284         currentCoords.setAlt(Alt->value);
0285         currentCoords.HorizontalToEquatorial(KStars::Instance()->data()->lst(),
0286                                              KStars::Instance()->data()->geo()->lat());
0287 
0288         // calculate J2000 coordinates
0289         updateJ2000Coordinates(&currentCoords);
0290 
0291         // ensure that coordinates are regularly updated
0292         if (! updateCoordinatesTimer.isActive())
0293             updateCoordinatesTimer.start();
0294 
0295         KStars::Instance()->map()->update();
0296     }
0297     else if (nvp->isNameMatch("POLLING_PERIOD"))
0298     {
0299         // set the timer how often the coordinates should be published
0300         auto period = nvp->findWidgetByName("PERIOD_MS");
0301         if (period != nullptr)
0302             updateCoordinatesTimer.setInterval(static_cast<int>(period->getValue()));
0303 
0304     }
0305 }
0306 
0307 void Mount::processSwitch(INDI::Property prop)
0308 {
0309     bool manualMotionChanged = false;
0310     auto svp = prop.getSwitch();
0311 
0312     if (svp->isNameMatch("CONNECTION"))
0313     {
0314         auto conSP = svp->findWidgetByName("CONNECT");
0315         if (conSP)
0316         {
0317             // TODO We must allow for multiple mount drivers to be online, not just one
0318             // For the actions taken, the user should be able to specify which mounts shall receive the commands. It could be one
0319             // or more. For now, we enable/disable telescope group on the assumption there is only one mount present.
0320             if (conSP->getState() == ISS_ON)
0321                 KStars::Instance()->slotSetTelescopeEnabled(true);
0322             else
0323             {
0324                 KStars::Instance()->slotSetTelescopeEnabled(false);
0325                 centerLockTimer.stop();
0326             }
0327         }
0328     }
0329     else if (svp->isNameMatch("TELESCOPE_PARK"))
0330         updateParkStatus();
0331     else if (svp->isNameMatch("TELESCOPE_ABORT_MOTION"))
0332     {
0333         if (svp->s == IPS_OK)
0334         {
0335             inCustomParking = false;
0336             KSNotification::event(QLatin1String("MountAborted"), i18n("Mount motion was aborted"), KSNotification::Mount,
0337                                   KSNotification::Warn);
0338         }
0339     }
0340     else if (svp->isNameMatch("TELESCOPE_PIER_SIDE"))
0341     {
0342         int currentSide = IUFindOnSwitchIndex(svp);
0343         if (currentSide != m_PierSide)
0344         {
0345             m_PierSide = static_cast<PierSide>(currentSide);
0346             emit pierSideChanged(m_PierSide);
0347         }
0348     }
0349     else if (svp->isNameMatch("TELESCOPE_TRACK_MODE"))
0350     {
0351         auto sp = svp->findOnSwitch();
0352         if (sp)
0353         {
0354             if (sp->isNameMatch("TRACK_SIDEREAL"))
0355                 currentTrackMode = TRACK_SIDEREAL;
0356             else if (sp->isNameMatch("TRACK_SOLAR"))
0357                 currentTrackMode = TRACK_SOLAR;
0358             else if (sp->isNameMatch("TRACK_LUNAR"))
0359                 currentTrackMode = TRACK_LUNAR;
0360             else
0361                 currentTrackMode = TRACK_CUSTOM;
0362         }
0363     }
0364     else if (svp->isNameMatch("TELESCOPE_MOTION_NS"))
0365         manualMotionChanged = true;
0366     else if (svp->isNameMatch("TELESCOPE_MOTION_WE"))
0367         manualMotionChanged = true;
0368     else if (svp->isNameMatch("TELESCOPE_REVERSE_MOTION"))
0369     {
0370         emit axisReversed(AXIS_DE, svp->at(0)->getState() == ISS_ON);
0371         emit axisReversed(AXIS_RA, svp->at(1)->getState() == ISS_ON);
0372     }
0373 
0374     if (manualMotionChanged)
0375     {
0376         auto NSCurrentMotion = getSwitch("TELESCOPE_MOTION_NS")->getState();
0377         auto WECurrentMotion = getSwitch("TELESCOPE_MOTION_WE")->getState();
0378         inCustomParking = false;
0379         inManualMotion = (NSCurrentMotion == IPS_BUSY || WECurrentMotion == IPS_BUSY);
0380     }
0381 }
0382 
0383 void Mount::processText(INDI::Property prop)
0384 {
0385     auto tvp = prop.getText();
0386     if (tvp->isNameMatch("SAT_TLE_TEXT"))
0387     {
0388         if ((tvp->getState() == IPS_OK) && (m_TLEIsSetForTracking))
0389         {
0390             auto trajWindow = getText("SAT_PASS_WINDOW");
0391             if (!trajWindow)
0392             {
0393                 qCDebug(KSTARS_INDI) << "Property SAT_PASS_WINDOW not found";
0394             }
0395             else
0396             {
0397                 auto trajStart = trajWindow->findWidgetByName("SAT_PASS_WINDOW_START");
0398                 auto trajEnd = trajWindow->findWidgetByName("SAT_PASS_WINDOW_END");
0399 
0400                 if (!trajStart || !trajEnd)
0401                 {
0402                     qCDebug(KSTARS_INDI) << "Start or end in SAT_PASS_WINDOW not found";
0403                 }
0404                 else
0405                 {
0406                     trajStart->setText(g_satPassStart.toString(Qt::ISODate).toLocal8Bit().data());
0407                     trajEnd->setText(g_satPassEnd.toString(Qt::ISODate).toLocal8Bit().data());
0408 
0409                     sendNewProperty(trajWindow);
0410                     m_windowIsSetForTracking = true;
0411                 }
0412             }
0413         }
0414     }
0415     else if (tvp->isNameMatch("SAT_PASS_WINDOW"))
0416     {
0417         if ((tvp->getState() == IPS_OK) && (m_TLEIsSetForTracking) && (m_windowIsSetForTracking))
0418         {
0419             auto trackSwitchV  = getSwitch("SAT_TRACKING_STAT");
0420             if (!trackSwitchV)
0421             {
0422                 qCDebug(KSTARS_INDI) << "Property SAT_TRACKING_STAT not found";
0423             }
0424             else
0425             {
0426                 auto trackSwitch = trackSwitchV->findWidgetByName("SAT_TRACK");
0427                 if (trackSwitch)
0428                 {
0429                     trackSwitchV->reset();
0430                     trackSwitch->setState(ISS_ON);
0431 
0432                     sendNewProperty(trackSwitchV);
0433                     m_TLEIsSetForTracking = false;
0434                     m_windowIsSetForTracking = false;
0435                 }
0436             }
0437         }
0438     }
0439 }
0440 
0441 void Mount::updateParkStatus()
0442 {
0443     auto svp = getSwitch("TELESCOPE_PARK");
0444     if (!svp)
0445         return;
0446 
0447     auto sp = svp->findWidgetByName("PARK");
0448     if (sp)
0449     {
0450         if (svp->getState() == IPS_ALERT)
0451         {
0452             // First, inform everyone watch this that an error occurred.
0453             emit newParkStatus(PARK_ERROR);
0454             // JM 2021-03-08: Reset parking internal state to either PARKED or UNPARKED.
0455             // Whatever the current switch is set to
0456             m_ParkStatus = (sp->getState() == ISS_ON) ? PARK_PARKED : PARK_UNPARKED;
0457             KSNotification::event(QLatin1String("MountParkingFailed"), i18n("Mount parking failed"), KSNotification::Mount,
0458                                   KSNotification::Alert);
0459         }
0460         else if (svp->getState() == IPS_BUSY && sp->s == ISS_ON && m_ParkStatus != PARK_PARKING)
0461         {
0462             m_ParkStatus = PARK_PARKING;
0463             KSNotification::event(QLatin1String("MountParking"), i18n("Mount parking is in progress"), KSNotification::Mount);
0464             currentObject = nullptr;
0465 
0466             emit newParkStatus(m_ParkStatus);
0467         }
0468         else if (svp->getState() == IPS_BUSY && sp->getState() == ISS_OFF && m_ParkStatus != PARK_UNPARKING)
0469         {
0470             m_ParkStatus = PARK_UNPARKING;
0471             KSNotification::event(QLatin1String("MountUnParking"), i18n("Mount unparking is in progress"), KSNotification::Mount);
0472 
0473             emit newParkStatus(m_ParkStatus);
0474         }
0475         else if (svp->getState() == IPS_OK && sp->getState() == ISS_ON && m_ParkStatus != PARK_PARKED)
0476         {
0477             m_ParkStatus = PARK_PARKED;
0478             KSNotification::event(QLatin1String("MountParked"), i18n("Mount parked"), KSNotification::Mount);
0479             currentObject = nullptr;
0480 
0481             emit newParkStatus(m_ParkStatus);
0482 
0483             QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park");
0484             if (parkAction)
0485                 parkAction->setEnabled(false);
0486             QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark");
0487             if (unParkAction)
0488                 unParkAction->setEnabled(true);
0489 
0490             emit newTarget(currentCoords);
0491         }
0492         else if ( (svp->getState() == IPS_OK || svp->getState() == IPS_IDLE) && sp->getState() == ISS_OFF
0493                   && m_ParkStatus != PARK_UNPARKED)
0494         {
0495             m_ParkStatus = PARK_UNPARKED;
0496             KSNotification::event(QLatin1String("MountUnparked"), i18n("Mount unparked"), KSNotification::Mount);
0497             currentObject = nullptr;
0498 
0499             emit newParkStatus(m_ParkStatus);
0500 
0501             QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park");
0502             if (parkAction)
0503                 parkAction->setEnabled(true);
0504             QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark");
0505             if (unParkAction)
0506                 unParkAction->setEnabled(false);
0507         }
0508     }
0509 }
0510 bool Mount::canGuide()
0511 {
0512     auto raPulse  = getNumber("TELESCOPE_TIMED_GUIDE_WE");
0513     auto decPulse = getNumber("TELESCOPE_TIMED_GUIDE_NS");
0514 
0515     return raPulse && decPulse;
0516 }
0517 
0518 bool Mount::canPark()
0519 {
0520     auto parkSP = getSwitch("TELESCOPE_PARK");
0521 
0522     if (!parkSP)
0523         return false;
0524 
0525     auto parkSW = parkSP->findWidgetByName("PARK");
0526 
0527     return (parkSW != nullptr);
0528 }
0529 
0530 bool Mount::isSlewing()
0531 {
0532     auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
0533 
0534     if (!EqProp)
0535         return false;
0536 
0537     return (EqProp->getState() == IPS_BUSY);
0538 }
0539 
0540 bool Mount::isInMotion()
0541 {
0542     return (isSlewing() || inManualMotion);
0543 }
0544 
0545 bool Mount::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
0546 {
0547     if (canGuide() == false)
0548         return false;
0549 
0550     bool raOK  = doPulse(ra_dir, ra_msecs);
0551     bool decOK = doPulse(dec_dir, dec_msecs);
0552 
0553     return raOK && decOK;
0554 }
0555 
0556 bool Mount::doPulse(GuideDirection dir, int msecs)
0557 {
0558     auto raPulse  = getNumber("TELESCOPE_TIMED_GUIDE_WE");
0559     auto decPulse = getNumber("TELESCOPE_TIMED_GUIDE_NS");
0560     INDI::PropertyView<INumber> *npulse   = nullptr;
0561     INDI::WidgetView<INumber>   *dirPulse = nullptr;
0562 
0563     if (!raPulse || !decPulse)
0564         return false;
0565 
0566     switch (dir)
0567     {
0568         case RA_INC_DIR:
0569             npulse = raPulse;
0570             dirPulse = npulse->findWidgetByName("TIMED_GUIDE_W");
0571             break;
0572 
0573         case RA_DEC_DIR:
0574             npulse = raPulse;
0575             dirPulse = npulse->findWidgetByName("TIMED_GUIDE_E");
0576             break;
0577 
0578         case DEC_INC_DIR:
0579             npulse = decPulse;
0580             dirPulse = npulse->findWidgetByName("TIMED_GUIDE_N");
0581             break;
0582 
0583         case DEC_DEC_DIR:
0584             npulse = decPulse;
0585             dirPulse = npulse->findWidgetByName("TIMED_GUIDE_S");
0586             break;
0587 
0588         default:
0589             return false;
0590     }
0591 
0592     if (!dirPulse)
0593         return false;
0594 
0595     dirPulse->setValue(msecs);
0596 
0597     sendNewProperty(npulse);
0598 
0599     return true;
0600 }
0601 
0602 
0603 void Mount::setCustomParking(SkyPoint * coords)
0604 {
0605     bool rc = false;
0606     if (coords == nullptr)
0607         rc = sendCoords(KStars::Instance()->map()->clickedPoint());
0608     else
0609         rc = sendCoords(coords);
0610 
0611     inCustomParking = rc;
0612 }
0613 
0614 void Mount::find()
0615 {
0616     updateJ2000Coordinates(&currentCoords);
0617     double maxrad = 1000.0 / Options::zoomFactor();
0618     SkyObject *currentObject = KStarsData::Instance()->skyComposite()->objectNearest(&currentCoords, maxrad);
0619     KStars::Instance()->map()->setFocusObject(currentObject);
0620     KStars::Instance()->map()->setDestination(currentCoords);
0621 }
0622 void Mount::centerLock()
0623 {
0624     if (Options::isTracking() == false ||
0625             currentCoords.angularDistanceTo(KStars::Instance()->map()->focus()).Degrees() > 0.5)
0626     {
0627         updateJ2000Coordinates(&currentCoords);
0628         KStars::Instance()->map()->setDestination(currentCoords);
0629         KStars::Instance()->map()->setFocusPoint(&currentCoords);
0630         KStars::Instance()->map()->setFocusObject(nullptr);
0631         Options::setIsTracking(true);
0632     }
0633     centerLockTimer.start();
0634 }
0635 
0636 void Mount::centerUnlock()
0637 {
0638     KStars::Instance()->map()->stopTracking();
0639     centerLockTimer.stop();
0640 }
0641 
0642 bool Mount::sendCoords(SkyPoint * ScopeTarget)
0643 {
0644     INumber *RAEle                 = nullptr;
0645     INumber *DecEle                = nullptr;
0646     INumber *AzEle                 = nullptr;
0647     INumber *AltEle                = nullptr;
0648     double currentRA = 0, currentDEC = 0, currentAlt = 0, currentAz = 0;
0649     bool useJ2000(false);
0650 
0651     auto EqProp = getNumber("EQUATORIAL_EOD_COORD");
0652     if (!EqProp)
0653     {
0654         // J2000 Property
0655         EqProp = getNumber("EQUATORIAL_COORD");
0656         if (EqProp)
0657             useJ2000 = true;
0658     }
0659 
0660     auto HorProp = getNumber("HORIZONTAL_COORD");
0661 
0662     if (EqProp && EqProp->getPermission() == IP_RO)
0663         EqProp = nullptr;
0664 
0665     if (HorProp && HorProp->getPermission() == IP_RO)
0666         HorProp = nullptr;
0667 
0668     //qDebug() << Q_FUNC_INFO << "Skymap click - RA: " << scope_target->ra().toHMSString() << " DEC: " << scope_target->dec().toDMSString();
0669 
0670     if (EqProp)
0671     {
0672         RAEle = EqProp->findWidgetByName("RA");
0673         if (!RAEle)
0674             return false;
0675 
0676         DecEle = EqProp->findWidgetByName("DEC");
0677         if (!DecEle)
0678             return false;
0679 
0680         //if (useJ2000)
0681         //ScopeTarget->apparentCoord( KStars::Instance()->data()->ut().djd(), static_cast<long double>(J2000));
0682 
0683         currentRA  = RAEle->value;
0684         currentDEC = DecEle->value;
0685 
0686         ScopeTarget->EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
0687     }
0688 
0689     if (HorProp)
0690     {
0691         AzEle = IUFindNumber(HorProp, "AZ");
0692         if (!AzEle)
0693             return false;
0694         AltEle = IUFindNumber(HorProp, "ALT");
0695         if (!AltEle)
0696             return false;
0697 
0698         currentAz  = AzEle->value;
0699         currentAlt = AltEle->value;
0700     }
0701 
0702     /* Could not find either properties! */
0703     if (EqProp == nullptr && HorProp == nullptr)
0704         return false;
0705 
0706     // Function for sending the coordinates to the INDI mount device
0707     // via the ClientManager. This helper function translates EKOS objects into INDI commands.
0708     auto sendToMountDevice = [ = ]()
0709     {
0710         // communicate the new target only if a slew will be executed for the given coordinates
0711         if (slewDefined())
0712         {
0713             emit newTarget(*ScopeTarget);
0714             if (currentObject)
0715                 emit newTargetName(currentObject->name());
0716             // If there is no object, we must clear target as it might give wrong
0717             // indication we are still on it.
0718             else
0719                 emit newTargetName(QString());
0720         }
0721 
0722         if (EqProp)
0723         {
0724             dms ra, de;
0725 
0726             if (useJ2000)
0727             {
0728                 // If we have invalid DEC, then convert coords to J2000
0729                 if (ScopeTarget->dec0().Degrees() == 180.0)
0730                 {
0731                     ScopeTarget->setRA0(ScopeTarget->ra());
0732                     ScopeTarget->setDec0(ScopeTarget->dec());
0733                     ScopeTarget->catalogueCoord( KStars::Instance()->data()->ut().djd());
0734                     ra = ScopeTarget->ra();
0735                     de = ScopeTarget->dec();
0736                 }
0737                 else
0738                 {
0739                     ra = ScopeTarget->ra0();
0740                     de = ScopeTarget->dec0();
0741                 }
0742             }
0743             else
0744             {
0745                 ra = ScopeTarget->ra();
0746                 de = ScopeTarget->dec();
0747             }
0748 
0749             RAEle->value  = ra.Hours();
0750             DecEle->value = de.Degrees();
0751             sendNewProperty(EqProp);
0752 
0753             qCDebug(KSTARS_INDI) << "ISD:Telescope sending coords RA:" << ra.toHMSString() <<
0754                                  "(" << RAEle->value << ") DE:" << de.toDMSString() <<
0755                                  "(" << DecEle->value << ")";
0756 
0757             RAEle->value  = currentRA;
0758             DecEle->value = currentDEC;
0759         }
0760         // Only send Horizontal Coord property if Equatorial is not available.
0761         else if (HorProp)
0762         {
0763             AzEle->value  = ScopeTarget->az().Degrees();
0764             AltEle->value = ScopeTarget->alt().Degrees();
0765             sendNewProperty(HorProp);
0766             AzEle->value  = currentAz;
0767             AltEle->value = currentAlt;
0768         }
0769 
0770     };
0771 
0772     // Helper function that first checks for the selected target object whether the
0773     // tracking modes have to be adapted (special cases moon and sun), explicitely warns before
0774     // slewing to the sun and finally (independant whether there exists a target object
0775     // for the target coordinates) calls sendToMountDevice
0776     auto checkObjectAndSend = [ = ]()
0777     {
0778         // Search within 0.1 degrees indepdent of zoom level.
0779         double maxrad = 0.1;
0780         currentObject = KStarsData::Instance()->skyComposite()->objectNearest(ScopeTarget, maxrad);
0781         if (currentObject)
0782         {
0783             auto checkTrackModes = [ = ]()
0784             {
0785                 if (m_hasTrackModes)
0786                 {
0787                     // Tracking Moon
0788                     if (currentObject->type() == SkyObject::MOON)
0789                     {
0790                         if (currentTrackMode != TRACK_LUNAR && TrackMap.contains(TRACK_LUNAR))
0791                             setTrackMode(TrackMap.value(TRACK_LUNAR));
0792                     }
0793                     // Tracking Sun
0794                     else if (currentObject->name() == i18n("Sun"))
0795                     {
0796                         if (currentTrackMode != TRACK_SOLAR && TrackMap.contains(TRACK_SOLAR))
0797                             setTrackMode(TrackMap.value(TRACK_SOLAR));
0798                     }
0799                     // If Last track mode was either set to SOLAR or LUNAR but now we are slewing to a different object
0800                     // then we automatically fallback to sidereal. If the current track mode is CUSTOM or something else, nothing
0801                     // changes.
0802                     else if (currentTrackMode == TRACK_SOLAR || currentTrackMode == TRACK_LUNAR)
0803                         setTrackMode(TRACK_SIDEREAL);
0804 
0805                 }
0806             };
0807 
0808             // Sun Warning, but don't ask if tracking is already solar.
0809             if (currentObject->name() == i18n("Sun") && currentTrackMode != TRACK_SOLAR)
0810             {
0811                 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [ = ]()
0812                 {
0813                     KSMessageBox::Instance()->disconnect(this);
0814                     checkTrackModes();
0815                     sendToMountDevice();
0816                 });
0817                 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [ = ]()
0818                 {
0819                     KSMessageBox::Instance()->disconnect(this);
0820                 });
0821 
0822                 KSMessageBox::Instance()->questionYesNo(
0823                     i18n("Warning! Looking at the Sun without proper protection can lead to irreversible eye damage!"),
0824                     i18n("Sun Warning"));
0825             }
0826             else
0827             {
0828                 checkTrackModes();
0829                 sendToMountDevice();
0830             }
0831         }
0832         else
0833             sendToMountDevice();
0834     };
0835 
0836     // If altitude limits is enabled, then reject motion immediately.
0837     double targetAlt = ScopeTarget->altRefracted().Degrees();
0838 
0839     if ((-90 <= minAlt && maxAlt <= 90) && (targetAlt < minAlt || targetAlt > maxAlt))
0840     {
0841         KSNotification::event(QLatin1String("IndiServerMessage"),
0842                               i18n("Requested altitude %1 is outside the specified altitude limit boundary (%2,%3).",
0843                                    QString::number(targetAlt, 'g', 3), QString::number(minAlt, 'g', 3),
0844                                    QString::number(maxAlt, 'g', 3)), KSNotification::Mount, KSNotification::Warn);
0845         qCInfo(KSTARS_INDI) << "Requested altitude " << QString::number(targetAlt, 'g', 3)
0846                             << " is outside the specified altitude limit boundary ("
0847                             << QString::number(minAlt, 'g', 3) << "," << QString::number(maxAlt, 'g', 3) << ").";
0848         return false;
0849     }
0850 
0851     // If disabled, then check if below horizon and warning the user unless the user previously dismissed it.
0852     if (Options::confirmBelowHorizon() && targetAlt < 0 && minAlt == -1)
0853     {
0854         connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [ = ]()
0855         {
0856             if (minAlt < -90 && +90 < maxAlt)
0857                 Options::setConfirmBelowHorizon(false);
0858             KSMessageBox::Instance()->disconnect(this);
0859             checkObjectAndSend();
0860         });
0861         connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [ = ]()
0862         {
0863             KSMessageBox::Instance()->disconnect(this);
0864             if (EqProp)
0865             {
0866                 RAEle->value  = currentRA;
0867                 DecEle->value = currentDEC;
0868             }
0869             if (HorProp)
0870             {
0871                 AzEle->value  = currentAz;
0872                 AltEle->value = currentAlt;
0873             }
0874         });
0875 
0876         KSMessageBox::Instance()->questionYesNo(i18n("Requested altitude is below the horizon. Are you sure you want to proceed?"),
0877                                                 i18n("Telescope Motion"), 15, false);
0878     }
0879     else
0880         checkObjectAndSend();
0881 
0882     return true;
0883 }
0884 
0885 bool Mount::slewDefined()
0886 {
0887     auto motionSP = getSwitch("ON_COORD_SET");
0888 
0889     if (motionSP == nullptr)
0890         return false;
0891     // A slew will happen if either Track, Slew, or Flip
0892     // is selected
0893     auto sp = motionSP->findOnSwitch();
0894     if(sp != nullptr &&
0895             (sp->name == std::string("TRACK") ||
0896              sp->name == std::string("SLEW") ||
0897              sp->name == std::string("FLIP")))
0898     {
0899         return true;
0900     }
0901     else
0902     {
0903         return false;
0904     }
0905 }
0906 
0907 bool Mount::Slew(double ra, double dec, bool flip)
0908 {
0909     SkyPoint target;
0910 
0911     if (m_isJ2000)
0912     {
0913         target.setRA0(ra);
0914         target.setDec0(dec);
0915     }
0916     else
0917     {
0918         target.setRA(ra);
0919         target.setDec(dec);
0920     }
0921 
0922     return Slew(&target, flip);
0923 }
0924 
0925 bool Mount::Slew(SkyPoint * ScopeTarget, bool flip)
0926 {
0927     auto motionSP = getSwitch("ON_COORD_SET");
0928 
0929     if (!motionSP)
0930         return false;
0931 
0932     auto slewSW = flip ? motionSP->findWidgetByName("FLIP") : motionSP->findWidgetByName("TRACK");
0933 
0934     if (flip && (!slewSW))
0935         slewSW = motionSP->findWidgetByName("TRACK");
0936 
0937     if (!slewSW)
0938         slewSW = motionSP->findWidgetByName("SLEW");
0939 
0940     if (!slewSW)
0941         return false;
0942 
0943     if (slewSW->getState() != ISS_ON)
0944     {
0945         motionSP->reset();
0946         slewSW->setState(ISS_ON);
0947         sendNewProperty(motionSP);
0948 
0949         qCDebug(KSTARS_INDI) << "ISD:Telescope: " << slewSW->getName();
0950     }
0951 
0952     return sendCoords(ScopeTarget);
0953 }
0954 
0955 bool Mount::Sync(double ra, double dec)
0956 {
0957     SkyPoint target;
0958 
0959     target.setRA(ra);
0960     target.setDec(dec);
0961 
0962     return Sync(&target);
0963 }
0964 
0965 bool Mount::Sync(SkyPoint * ScopeTarget)
0966 {
0967     auto motionSP = getSwitch("ON_COORD_SET");
0968 
0969     if (!motionSP)
0970         return false;
0971 
0972     auto syncSW = motionSP->findWidgetByName("SYNC");
0973 
0974     if (!syncSW)
0975         return false;
0976 
0977     if (syncSW->getState() != ISS_ON)
0978     {
0979         motionSP->reset();
0980         syncSW->setState(ISS_ON);
0981         sendNewProperty(motionSP);
0982 
0983         qCDebug(KSTARS_INDI) << "ISD:Telescope: Syncing...";
0984     }
0985 
0986     return sendCoords(ScopeTarget);
0987 }
0988 
0989 bool Mount::abort()
0990 {
0991     auto motionSP = getSwitch("TELESCOPE_ABORT_MOTION");
0992 
0993     if (!motionSP)
0994         return false;
0995 
0996     auto abortSW = motionSP->findWidgetByName("ABORT");
0997 
0998     if (!abortSW)
0999         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 
1547 void Mount::stopTimers()
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