File indexing completed on 2024-03-24 03:46:54
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(¢erLockTimer, &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(¤tCoords, 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(¤tCoords); 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(¤tCoords); 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(¤tCoords); 0617 double maxrad = 1000.0 / Options::zoomFactor(); 0618 SkyObject *currentObject = KStarsData::Instance()->skyComposite()->objectNearest(¤tCoords, 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(¤tCoords); 0628 KStars::Instance()->map()->setDestination(currentCoords); 0629 KStars::Instance()->map()->setFocusPoint(¤tCoords); 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