Kstars

mount.cpp
1 /*
2  SPDX-FileCopyrightText: 2015 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "mount.h"
8 
9 #include <QQuickView>
10 #include <QQuickItem>
11 #include <indicom.h>
12 
13 #include <KNotifications/KNotification>
14 #include <KLocalizedContext>
15 #include <KActionCollection>
16 
17 #include "Options.h"
18 
19 #include "ksmessagebox.h"
20 #include "indi/driverinfo.h"
21 #include "indi/indicommon.h"
22 #include "indi/clientmanager.h"
23 #include "indi/indifilterwheel.h"
24 #include "indi/indigps.h"
25 
26 
27 #include "mountadaptor.h"
28 
29 #include "ekos/manager.h"
30 
31 #include "kstars.h"
32 #include "skymapcomposite.h"
33 #include "kspaths.h"
34 #include "dialogs/finddialog.h"
35 #include "kstarsdata.h"
36 #include "ksutils.h"
37 
38 #include <basedevice.h>
39 
40 #include <ekos_mount_debug.h>
41 
42 extern const char *libindi_strings_context;
43 
44 #define ABORT_DISPATCH_LIMIT 3
45 
46 namespace Ekos
47 {
48 
50 {
51  setupUi(this);
52 
53  new MountAdaptor(this);
54  QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Mount", this);
55  // Set up DBus interfaces
56  QPointer<QDBusInterface> ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos",
58  qDBusRegisterMetaType<SkyPoint>();
59 
60  // Connecting DBus signals
61  QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this,
62  SLOT(registerNewModule(QString)));
63 
64  m_Mount = nullptr;
65 
66  m_AbortDispatch = -1;
67 
68  minAltLimit->setValue(Options::minimumAltLimit());
69  maxAltLimit->setValue(Options::maximumAltLimit());
70  maxHaLimit->setValue(Options::maximumHaLimit());
71 
75 
76  connect(mountToolBoxB, &QPushButton::clicked, this, &Mount::toggleMountToolBox);
77 
78  connect(saveB, &QPushButton::clicked, this, &Mount::save);
79 
80  connect(clearAlignmentModelB, &QPushButton::clicked, this, [this]()
81  {
82  resetModel();
83  });
84 
85  connect(clearParkingB, &QPushButton::clicked, this, [this]()
86  {
87  if (m_Mount)
88  m_Mount->clearParking();
89 
90  });
91 
92  connect(purgeConfigB, &QPushButton::clicked, this, [this]()
93  {
94  if (m_Mount)
95  {
97  i18n("Are you sure you want to clear all mount configurations?"),
98  i18n("Mount Configuration"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
99  "purge_mount_settings_dialog") == KMessageBox::Yes)
100  {
101  resetModel();
102  m_Mount->clearParking();
103  m_Mount->setConfig(PURGE_CONFIG);
104  }
105  }
106  });
107 
108  connect(enableLimitsCheck, &QCheckBox::toggled, this, &Mount::enableAltitudeLimits);
109  enableLimitsCheck->setChecked(Options::enableAltitudeLimits());
110  m_AltitudeLimitEnabled = enableLimitsCheck->isChecked();
111  connect(enableHaLimitCheck, &QCheckBox::toggled, this, &Mount::enableHourAngleLimits);
112  enableHaLimitCheck->setChecked(Options::enableHaLimit());
113  //haLimitEnabled = enableHaLimitCheck->isChecked();
114 
115  // meridian flip
116  meridianFlipCheckBox->setChecked(Options::executeMeridianFlip());
117 
118  // Meridian Flip Unit
119  meridianFlipDegreesR->setChecked(Options::meridianFlipUnitDegrees());
120  meridianFlipHoursR->setChecked(!Options::meridianFlipUnitDegrees());
121 
122  // This is always in hours
123  double offset = Options::meridianFlipOffset();
124  // Hours --> Degrees
125  if (meridianFlipDegreesR->isChecked())
126  offset *= 15.0;
127  meridianFlipTimeBox->setValue(offset);
128  connect(meridianFlipCheckBox, &QCheckBox::toggled, this, &Ekos::Mount::meridianFlipSetupChanged);
129  connect(meridianFlipTimeBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
130  &Ekos::Mount::meridianFlipSetupChanged);
131  connect(meridianFlipDegreesR, &QRadioButton::toggled, this, [this]()
132  {
133  Options::setMeridianFlipUnitDegrees(meridianFlipDegreesR->isChecked());
134  // Hours ---> Degrees
135  if (meridianFlipDegreesR->isChecked())
136  meridianFlipTimeBox->setValue(meridianFlipTimeBox->value() * 15.0);
137  // Degrees --> Hours
138  else
139  meridianFlipTimeBox->setValue(rangeHA(meridianFlipTimeBox->value() / 15.0));
140  });
141 
142  everyDayCheck->setChecked(Options::parkEveryDay());
143  connect(everyDayCheck, &QCheckBox::toggled, this, [](bool toggled)
144  {
145  Options::setParkEveryDay(toggled);
146  });
147 
148  startupTimeEdit->setTime(QTime::fromString(Options::parkTime()));
149  connect(startupTimeEdit, &QTimeEdit::editingFinished, this, [this]()
150  {
151  Options::setParkTime(startupTimeEdit->time().toString());
152  });
153 
154  connect(&autoParkTimer, &QTimer::timeout, this, &Mount::startAutoPark);
155  connect(startTimerB, &QPushButton::clicked, this, &Mount::startParkTimer);
156  connect(stopTimerB, &QPushButton::clicked, this, &Mount::stopParkTimer);
157 
158  stopTimerB->setEnabled(false);
159 
160  if (everyDayCheck->isChecked())
161  startTimerB->animateClick();
162 
163  // QML Stuff
164  m_BaseView = new QQuickView();
165 
166  m_BaseView->setSource(QUrl("qrc:/qml/mount/mountbox.qml"));
167 
168  m_BaseView->setTitle(i18n("Mount Control"));
169 #ifdef Q_OS_OSX
171 #else
173 #endif
174 
175  // Theming?
176  m_BaseView->setColor(Qt::black);
177 
178  m_BaseObj = m_BaseView->rootObject();
179 
180  m_Ctxt = m_BaseView->rootContext();
181  ///Use instead of KDeclarative
182  m_Ctxt->setContextObject(new KLocalizedContext(m_BaseView));
183 
184  m_Ctxt->setContextProperty("mount", this);
185 
187 
188  m_SpeedSlider = m_BaseObj->findChild<QQuickItem *>("speedSliderObject");
189  m_SpeedLabel = m_BaseObj->findChild<QQuickItem *>("speedLabelObject");
190  m_raValue = m_BaseObj->findChild<QQuickItem *>("raValueObject");
191  m_deValue = m_BaseObj->findChild<QQuickItem *>("deValueObject");
192  m_azValue = m_BaseObj->findChild<QQuickItem *>("azValueObject");
193  m_altValue = m_BaseObj->findChild<QQuickItem *>("altValueObject");
194  m_haValue = m_BaseObj->findChild<QQuickItem *>("haValueObject");
195  m_zaValue = m_BaseObj->findChild<QQuickItem *>("zaValueObject");
196  m_targetText = m_BaseObj->findChild<QQuickItem *>("targetTextObject");
197  m_targetRAText = m_BaseObj->findChild<QQuickItem *>("targetRATextObject");
198  m_targetDEText = m_BaseObj->findChild<QQuickItem *>("targetDETextObject");
199  m_J2000Check = m_BaseObj->findChild<QQuickItem *>("j2000CheckObject");
200  m_JNowCheck = m_BaseObj->findChild<QQuickItem *>("jnowCheckObject");
201  m_Park = m_BaseObj->findChild<QQuickItem *>("parkButtonObject");
202  m_Unpark = m_BaseObj->findChild<QQuickItem *>("unparkButtonObject");
203  m_statusText = m_BaseObj->findChild<QQuickItem *>("statusTextObject");
204  m_equatorialCheck = m_BaseObj->findChild<QQuickItem *>("equatorialCheckObject");
205  m_horizontalCheck = m_BaseObj->findChild<QQuickItem *>("horizontalCheckObject");
206  m_haEquatorialCheck = m_BaseObj->findChild<QQuickItem *>("haEquatorialCheckObject");
207  m_leftRightCheck = m_BaseObj->findChild<QQuickItem *>("leftRightCheckObject");
208  m_upDownCheck = m_BaseObj->findChild<QQuickItem *>("upDownCheckObject");
209 
210  m_leftRightCheck->setProperty("checked", Options::leftRightReversed());
211  m_upDownCheck->setProperty("checked", Options::upDownReversed());
212 
213  //Note: This is to prevent a button from being called the default button
214  //and then executing when the user hits the enter key such as when on a Text Box
215  QList<QPushButton *> qButtons = findChildren<QPushButton *>();
216  for (auto &button : qButtons)
217  button->setAutoDefault(false);
218 }
219 
220 Mount::~Mount()
221 {
222  autoParkTimer.stop();
223  delete(m_Ctxt);
224  delete(m_BaseObj);
225  delete(currentTargetPosition);
226 }
227 
228 void Mount::setupParkUI()
229 {
230  if (m_Mount == nullptr)
231  return;
232 
233  if (m_Mount->canPark())
234  {
235  switch(m_Mount->parkStatus())
236  {
237  case ISD::PARK_PARKED:
238  parkingTitle->setTitle("Parked");
239  break;
240  case ISD::PARK_PARKING:
241  parkingTitle->setTitle("Parking");
242  break;
243  case ISD::PARK_UNPARKING:
244  parkingTitle->setTitle("Unparking");
245  break;
246  case ISD::PARK_UNPARKED:
247  parkingTitle->setTitle("Unparked");
248  break;
249  case ISD::PARK_ERROR:
250  parkingTitle->setTitle("Park Error");
251  break;
252  case ISD::PARK_UNKNOWN:
253  parkingTitle->setTitle("Park Status Unknown");
254  break;
255  }
256  parkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_UNPARKED);
257  unparkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_PARKED);
258  }
259  else
260  {
261  parkB->setEnabled(false);
262  unparkB->setEnabled(false);
263  parkingTitle->setTitle("");
264  }
265 }
266 
268 {
269  // No duplicates
270  for (auto &oneMount : m_Mounts)
271  {
272  if (oneMount->getDeviceName() == device->getDeviceName())
273  return false;
274  }
275 
276  for (auto &oneMount : m_Mounts)
277  oneMount->disconnect(this);
278 
279  m_Mount = device;
280  m_Mounts.append(device);
281 
282  // if (newTelescope == m_Mount)
283  // {
284  // if (enableLimitsCheck->isChecked())
285  // m_Mount->setAltLimits(minAltLimit->value(), maxAltLimit->value());
286  // syncTelescopeInfo();
287  // return;
288  // }
289 
290  if (m_GPS != nullptr)
291  syncGPS();
292 
293  connect(m_Mount, &ISD::Mount::numberUpdated, this, &Mount::updateNumber);
294  connect(m_Mount, &ISD::Mount::switchUpdated, this, &Mount::updateSwitch);
295  connect(m_Mount, &ISD::Mount::textUpdated, this, &Mount::updateText);
300  connect(m_Mount, &ISD::Mount::slewRateChanged, this, &Mount::slewRateChanged);
301  connect(m_Mount, &ISD::Mount::pierSideChanged, this, &Mount::pierSideChanged);
302  connect(m_Mount, &ISD::Mount::axisReversed, this, &Mount::syncAxisReversed);
303  connect(m_Mount, &ISD::Mount::Disconnected, this, [this]()
304  {
305  m_BaseView->hide();
306  });
307  connect(m_Mount, &ISD::Mount::newParkStatus, this, [&](ISD::ParkStatus status)
308  {
309  m_ParkStatus = status;
310  emit newParkStatus(status);
311 
312  setupParkUI();
313 
314  // If mount is unparked AND every day auto-paro check is ON
315  // AND auto park timer is not yet started, we try to initiate it.
316  if (status == ISD::PARK_UNPARKED && everyDayCheck->isChecked() && autoParkTimer.isActive() == false)
317  startTimerB->animateClick();
318  });
319  connect(m_Mount, &ISD::Mount::ready, this, &Mount::ready);
320 
321  //Disable this for now since ALL INDI drivers now log their messages to verbose output
322  //connect(currentTelescope, SIGNAL(messageUpdated(int)), this, SLOT(updateLog(int)), Qt::UniqueConnection);
323 
324  if (enableLimitsCheck->isChecked())
325  m_Mount->setAltLimits(minAltLimit->value(), maxAltLimit->value());
326 
328 
329  // Send initial status
330  m_Status = m_Mount->status();
331  emit newStatus(m_Status);
332 
333  m_ParkStatus = m_Mount->parkStatus();
334  emit newParkStatus(m_ParkStatus);
335  return true;
336 }
337 
338 bool Mount::addGPS(ISD::GPS * device)
339 {
340  // No duplicates
341  for (auto &oneGPS : m_GPSes)
342  {
343  if (oneGPS->getDeviceName() == device->getDeviceName())
344  return false;
345  }
346 
347  for (auto &oneGPS : m_GPSes)
348  oneGPS->disconnect(this);
349 
350  m_GPSes.append(device);
351 
352  auto executeSetGPS = [this, device]()
353  {
354  m_GPS = device;
355  connect(m_GPS, &ISD::GPS::numberUpdated, this, &Ekos::Mount::updateNumber, Qt::UniqueConnection);
356  appendLogText(i18n("GPS driver detected. KStars and mount time and location settings are now synced to the GPS driver."));
357  syncGPS();
358  };
359 
360  if (Options::useGPSSource() == false)
361  {
362  connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeSetGPS]()
363  {
364  KSMessageBox::Instance()->disconnect(this);
365  Options::setUseKStarsSource(false);
366  Options::setUseMountSource(false);
367  Options::setUseGPSSource(true);
368  executeSetGPS();
369  });
370 
371  KSMessageBox::Instance()->questionYesNo(i18n("GPS is detected. Do you want to switch time and location source to GPS?"),
372  i18n("GPS Settings"), 10);
373  }
374  else
375  executeSetGPS();
376 
377  return true;
378 }
379 
380 void Mount::syncGPS()
381 {
382  // We only update when location is OK
383  auto location = m_GPS->getNumber("GEOGRAPHIC_COORD");
384  if (!location || location->getState() != IPS_OK)
385  return;
386 
387  // Sync name
388  if (m_Mount)
389  {
390  auto activeDevices = m_Mount->getText("ACTIVE_DEVICES");
391  if (activeDevices)
392  {
393  auto activeGPS = activeDevices->findWidgetByName("ACTIVE_GPS");
394  if (activeGPS)
395  {
396  if (activeGPS->getText() != m_GPS->getDeviceName())
397  {
398  activeGPS->setText(m_GPS->getDeviceName().toLatin1().constData());
399  m_Mount->getDriverInfo()->getClientManager()->sendNewText(activeDevices);
400  }
401  }
402  }
403  }
404 
405  // GPS Refresh should only be called once automatically.
406  if (GPSInitialized == false)
407  {
408  auto refreshGPS = m_GPS->getSwitch("GPS_REFRESH");
409  if (refreshGPS)
410  {
411  refreshGPS->at(0)->setState(ISS_ON);
412  m_GPS->getDriverInfo()->getClientManager()->sendNewSwitch(refreshGPS);
413  GPSInitialized = true;
414  }
415  }
416 }
417 
418 void Mount::removeDevice(ISD::GenericDevice *device)
419 {
420  for (auto &oneMount : m_Mounts)
421  {
422  if (oneMount->getDeviceName() == device->getDeviceName())
423  {
424  oneMount->disconnect(this);
425  m_Mounts.removeOne(oneMount);
426  m_BaseView->hide();
427  qCDebug(KSTARS_EKOS_MOUNT) << "Removing mount driver" << oneMount->getDeviceName();
428  m_Mount = nullptr;
429  break;
430  }
431  }
432 
433  for (auto &oneGPS : m_GPSes)
434  {
435  if (oneGPS->getDeviceName() == device->getDeviceName())
436  {
437  oneGPS->disconnect(this);
438  m_GPSes.removeOne(oneGPS);
439  m_GPS = nullptr;
440  break;
441  }
442  }
443 }
444 
446 {
447  if (!m_Mount || m_Mount->isConnected() == false)
448  return;
449 
450  auto nvp = m_Mount->getNumber("TELESCOPE_INFO");
451 
452  if (nvp)
453  {
454  primaryScopeGroup->setTitle(m_Mount->getDeviceName());
455  guideScopeGroup->setTitle(i18n("%1 guide scope", m_Mount->getDeviceName()));
456 
457  auto np = nvp->findWidgetByName("TELESCOPE_APERTURE");
458 
459  if (np && np->getValue() > 0)
460  primaryScopeApertureIN->setValue(np->getValue());
461 
462  np = nvp->findWidgetByName("TELESCOPE_FOCAL_LENGTH");
463  if (np && np->getValue() > 0)
464  primaryScopeFocalIN->setValue(np->getValue());
465 
466  np = nvp->findWidgetByName("GUIDER_APERTURE");
467  if (np && np->getValue() > 0)
468  guideScopeApertureIN->setValue(np->getValue());
469 
470  np = nvp->findWidgetByName("GUIDER_FOCAL_LENGTH");
471  if (np && np->getValue() > 0)
472  guideScopeFocalIN->setValue(np->getValue());
473  }
474 
475  auto svp = m_Mount->getSwitch("TELESCOPE_SLEW_RATE");
476 
477  if (svp)
478  {
479  int index = svp->findOnSwitchIndex();
480 
481  // QtQuick
482  m_SpeedSlider->setEnabled(true);
483  m_SpeedSlider->setProperty("maximumValue", svp->count() - 1);
484  m_SpeedSlider->setProperty("value", index);
485 
486  m_SpeedLabel->setProperty("text", i18nc(libindi_strings_context, svp->at(index)->getLabel()));
487  m_SpeedLabel->setEnabled(true);
488  }
489  else
490  {
491  // QtQuick
492  m_SpeedSlider->setEnabled(false);
493  m_SpeedLabel->setEnabled(false);
494  }
495 
496  if (m_Mount->canPark())
497  {
498  connect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::Park, Qt::UniqueConnection);
499  connect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::UnPark, Qt::UniqueConnection);
500 
501  // QtQuick
502  m_Park->setEnabled(!m_Mount->isParked());
503  m_Unpark->setEnabled(m_Mount->isParked());
504  }
505  else
506  {
507  disconnect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::Park);
508  disconnect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::UnPark);
509 
510  // QtQuick
511  m_Park->setEnabled(false);
512  m_Unpark->setEnabled(false);
513  }
514  setupParkUI();
515 
516  // Configs
517  svp = m_Mount->getSwitch("APPLY_SCOPE_CONFIG");
518  if (svp)
519  {
520  scopeConfigCombo->disconnect();
521  scopeConfigCombo->clear();
522  for (const auto &it : *svp)
523  scopeConfigCombo->addItem(it.getLabel());
524 
525  scopeConfigCombo->setCurrentIndex(IUFindOnSwitchIndex(svp));
526  connect(scopeConfigCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this, &Mount::setScopeConfig);
527  }
528 
529  // Tracking State
530  svp = m_Mount->getSwitch("TELESCOPE_TRACK_STATE");
531  if (svp)
532  {
533  trackingGroup->setEnabled(true);
534  trackOnB->disconnect();
535  trackOffB->disconnect();
536  connect(trackOnB, &QPushButton::clicked, this, [&]()
537  {
538  m_Mount->setTrackEnabled(true);
539  });
540  connect(trackOffB, &QPushButton::clicked, this, [&]()
541  {
543  i18n("Are you sure you want to turn off mount tracking?"),
544  i18n("Mount Tracking"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
545  "turn_off_mount_tracking_dialog") == KMessageBox::Yes)
546  m_Mount->setTrackEnabled(false);
547  });
548  }
549  else
550  {
551  trackOnB->setChecked(false);
552  trackOffB->setChecked(false);
553  trackingGroup->setEnabled(false);
554  }
555 
556  auto tvp = m_Mount->getText("SCOPE_CONFIG_NAME");
557  if (tvp)
558  scopeConfigNameEdit->setText(tvp->at(0)->getText());
559 
560  m_leftRightCheck->setProperty("checked", m_Mount->isReversed(AXIS_RA));
561  m_upDownCheck->setProperty("checked", m_Mount->isReversed(AXIS_DE));
562 }
563 
565 {
566  if (name == "Capture" && captureInterface == nullptr)
567  {
568  captureInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos/Capture", "org.kde.kstars.Ekos.Capture",
570  }
571 
572 }
573 
574 
575 void Mount::updateText(ITextVectorProperty *tvp)
576 {
577  if (!strcmp(tvp->name, "SCOPE_CONFIG_NAME"))
578  {
579  scopeConfigNameEdit->setText(tvp->tp[0].text);
580  }
581 }
582 
583 bool Mount::setScopeConfig(int index)
584 {
585  auto svp = m_Mount->getSwitch("APPLY_SCOPE_CONFIG");
586  if (!svp)
587  return false;
588 
589  svp->reset();
590  svp->at(index)->setState(ISS_ON);
591 
592  // Clear scope config name so that it gets filled by INDI
593  scopeConfigNameEdit->clear();
594 
595  m_Mount->getDriverInfo()->getClientManager()->sendNewSwitch(svp);
596  return true;
597 }
598 
599 void Mount::updateTelescopeCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
600 {
601  if (m_Mount == nullptr || !m_Mount->isConnected())
602  return;
603 
604  telescopeCoord = position;
605 
606  // No need to update coords if we are still parked.
607  if (m_Status == ISD::Mount::MOUNT_PARKED && m_Status == m_Mount->status())
608  return;
609 
610  // Ekos Mount Tab coords are always in JNow
611  raOUT->setText(telescopeCoord.ra().toHMSString());
612  decOUT->setText(telescopeCoord.dec().toDMSString());
613 
614  // Mount Control Panel coords depend on the switch
615  if (m_JNowCheck->property("checked").toBool())
616  {
617  m_raValue->setProperty("text", telescopeCoord.ra().toHMSString());
618  m_deValue->setProperty("text", telescopeCoord.dec().toDMSString());
619  }
620  else
621  {
622  m_raValue->setProperty("text", telescopeCoord.ra0().toHMSString());
623  m_deValue->setProperty("text", telescopeCoord.dec0().toDMSString());
624  }
625 
626  // Get horizontal coords
627  azOUT->setText(telescopeCoord.az().toDMSString());
628  m_azValue->setProperty("text", telescopeCoord.az().toDMSString());
629  altOUT->setText(telescopeCoord.alt().toDMSString());
630  m_altValue->setProperty("text", telescopeCoord.alt().toDMSString());
631 
632  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
633  dms haSigned(ha);
634  QChar sgn('+');
635 
636  if (haSigned.Hours() > 12.0)
637  {
638  haSigned.setH(24.0 - haSigned.Hours());
639  sgn = '-';
640  }
641 
642  haOUT->setText(QString("%1%2").arg(sgn).arg(haSigned.toHMSString()));
643 
644  m_haValue->setProperty("text", haOUT->text());
645  lstOUT->setText(lst.toHMSString());
646 
647  double currentAlt = telescopeCoord.altRefracted().Degrees();
648 
649  m_zaValue->setProperty("text", dms(90 - currentAlt).toDMSString());
650 
651  if (minAltLimit->isEnabled() && (currentAlt < minAltLimit->value() || currentAlt > maxAltLimit->value()))
652  {
653  if (currentAlt < minAltLimit->value())
654  {
655  // Only stop if current altitude is less than last altitude indicate worse situation
656  if (currentAlt < m_LastAltitude &&
657  (m_AbortDispatch == -1 ||
658  (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
659  {
660  appendLogText(i18n("Telescope altitude is below minimum altitude limit of %1. Aborting motion...",
661  QString::number(minAltLimit->value(), 'g', 3)));
662  m_Mount->Abort();
663  m_Mount->setTrackEnabled(false);
664  //KNotification::event( QLatin1String( "OperationFailed" ));
666  m_AbortDispatch++;
667  }
668  }
669  else
670  {
671  // Only stop if current altitude is higher than last altitude indicate worse situation
672  if (currentAlt > m_LastAltitude &&
673  (m_AbortDispatch == -1 ||
674  (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
675  {
676  appendLogText(i18n("Telescope altitude is above maximum altitude limit of %1. Aborting motion...",
677  QString::number(maxAltLimit->value(), 'g', 3)));
678  m_Mount->Abort();
679  m_Mount->setTrackEnabled(false);
680  //KNotification::event( QLatin1String( "OperationFailed" ));
682  m_AbortDispatch++;
683  }
684  }
685  }
686  else
687  m_AbortDispatch = -1;
688 
689  //qCDebug(KSTARS_EKOS_MOUNT) << "maxHaLimit " << maxHaLimit->isEnabled() << " value " << maxHaLimit->value();
690 
691  double haHours = rangeHA(ha.Hours());
692  // handle Ha limit:
693  // Telescope must report Pier Side
694  // maxHaLimit must be enabled
695  // for PierSide West -> East if Ha > maxHaLimit stop tracking
696  // for PierSide East -> West if Ha > maxHaLimit - 12 stop Tracking
697  if (maxHaLimit->isEnabled())
698  {
699  // get hour angle limit
700  double haLimit = maxHaLimit->value();
701  bool haLimitReached = false;
702  switch(pierSide)
703  {
704  case ISD::Mount::PierSide::PIER_WEST:
705  haLimitReached = haHours > haLimit;
706  break;
707  case ISD::Mount::PierSide::PIER_EAST:
708  haLimitReached = rangeHA(haHours + 12.0) > haLimit;
709  break;
710  default:
711  // can't tell so always false
712  haLimitReached = false;
713  break;
714  }
715 
716  qCDebug(KSTARS_EKOS_MOUNT) << "Ha: " << haHours <<
717  " haLimit " << haLimit <<
718  " " << pierSideStateString() <<
719  " haLimitReached " << (haLimitReached ? "true" : "false") <<
720  " lastHa " << m_LastHourAngle;
721 
722  // compare with last ha to avoid multiple calls
723  if (haLimitReached && (rangeHA(haHours - m_LastHourAngle) >= 0 ) &&
724  (m_AbortDispatch == -1 ||
725  m_Mount->isInMotion()))
726  {
727  // moved past the limit, so stop
728  appendLogText(i18n("Telescope hour angle is more than the maximum hour angle of %1. Aborting motion...",
729  QString::number(maxHaLimit->value(), 'g', 3)));
730  m_Mount->Abort();
731  m_Mount->setTrackEnabled(false);
732  //KNotification::event( QLatin1String( "OperationFailed" ));
734  m_AbortDispatch++;
735  // ideally we pause and wait until we have passed the pier flip limit,
736  // then do a pier flip and try to resume
737  // this will need changing to use a target position because the current HA has stopped.
738  }
739  }
740  else
741  m_AbortDispatch = -1;
742 
743  m_LastAltitude = currentAlt;
744  m_LastHourAngle = haHours;
745 
746  ISD::Mount::Status currentStatus = m_Mount->status();
747  if (m_Status != currentStatus)
748  {
749  qCDebug(KSTARS_EKOS_MOUNT) << "Mount status changed from " << m_Mount->statusString(m_Status)
750  << " to " << m_Mount->statusString(currentStatus);
751  // If we just finished a slew, let's update initialHA and the current target's position,
752  // but only if the meridian flip is not deactived
753  if (currentStatus == ISD::Mount::MOUNT_TRACKING && m_Status == ISD::Mount::MOUNT_SLEWING
754  && m_MFStatus != FLIP_INACTIVE)
755  {
756  if (m_MFStatus == FLIP_NONE)
757  {
758  flipDelayHrs = 0;
759  }
760  setInitialHA((sgn == '-' ? -1 : 1) * ha.Hours());
761  delete currentTargetPosition;
762  currentTargetPosition = new SkyPoint(telescopeCoord.ra(), telescopeCoord.dec());
763  qCDebug(KSTARS_EKOS_MOUNT) << "Slew finished, MFStatus " << meridianFlipStatusString(m_MFStatus);
764  }
765 
766  //setScopeStatus(currentStatus);
767 
768  m_statusText->setProperty("text", m_Mount->statusString(currentStatus));
769  m_Status = currentStatus;
770  // forward
771  emit newStatus(m_Status);
772 
773  setupParkUI();
774  m_Park->setEnabled(!m_Mount->isParked());
775  m_Unpark->setEnabled(m_Mount->isParked());
776 
777  QAction *a = KStars::Instance()->actionCollection()->action("telescope_track");
778  if (a != nullptr)
779  a->setChecked(currentStatus == ISD::Mount::MOUNT_TRACKING);
780  }
781 
782  bool isTracking = (currentStatus == ISD::Mount::MOUNT_TRACKING);
783  if (trackingGroup->isEnabled())
784  {
785  trackOnB->setChecked(isTracking);
786  trackOffB->setChecked(!isTracking);
787  }
788 
789  // handle pier side display
790  pierSideLabel->setText(pierSideStateString());
791 
792  // Auto Park Timer
793  if (autoParkTimer.isActive())
794  {
795  QTime remainingTime(0, 0, 0);
796  remainingTime = remainingTime.addMSecs(autoParkTimer.remainingTime());
797  countdownLabel->setText(remainingTime.toString("hh:mm:ss"));
798  }
799 
800  if (isTracking && checkMeridianFlip(lst))
801  executeMeridianFlip();
802  else
803  {
804  const QString message(i18n("Status: inactive (parked)"));
805  if (m_Mount->isParked() && meridianFlipStatusText->text() != message)
806  {
807  meridianFlipStatusText->setText(message);
808  emit newMeridianFlipText(meridianFlipStatusText->text());
809  }
810  }
811 
812 }
813 
814 void Mount::updateNumber(INumberVectorProperty * nvp)
815 {
816  if (!strcmp(nvp->name, "TELESCOPE_INFO"))
817  {
818  if (nvp->s == IPS_ALERT)
819  {
820  QString newMessage;
821  if (primaryScopeApertureIN->value() <= 1 || primaryScopeFocalIN->value() <= 1)
822  newMessage = i18n("Error syncing telescope info. Please fill telescope aperture and focal length.");
823  else
824  newMessage = i18n("Error syncing telescope info. Check INDI control panel for more details.");
825  if (newMessage != lastNotificationMessage)
826  {
827  appendLogText(newMessage);
828  lastNotificationMessage = newMessage;
829  }
830  }
831  else
832  {
834  QString newMessage = i18n("Telescope info updated successfully.");
835  if (newMessage != lastNotificationMessage)
836  {
837  appendLogText(newMessage);
838  lastNotificationMessage = newMessage;
839  }
840  }
841  }
842 
843  if (m_GPS != nullptr && (nvp->device == m_GPS->getDeviceName()) && !strcmp(nvp->name, "GEOGRAPHIC_COORD")
844  && nvp->s == IPS_OK)
845  syncGPS();
846 }
847 
848 bool Mount::setSlewRate(int index)
849 {
850  if (m_Mount)
851  return m_Mount->setSlewRate(index);
852 
853  return false;
854 }
855 
856 void Mount::setUpDownReversed(bool enabled)
857 {
858  Options::setUpDownReversed(enabled);
859  if (m_Mount)
860  m_Mount->setReversedEnabled(AXIS_DE, enabled);
861 }
862 
863 void Mount::setLeftRightReversed(bool enabled)
864 {
865  Options::setLeftRightReversed(enabled);
866  if (m_Mount)
867  m_Mount->setReversedEnabled(AXIS_RA, enabled);
868 }
869 
870 void Mount::setMeridianFlipValues(bool activate, double hours)
871 {
872  meridianFlipCheckBox->setChecked(activate);
873  // Hours --> Degrees
874  if (meridianFlipDegreesR->isChecked())
875  meridianFlipTimeBox->setValue(hours * 15.0);
876  else
877  meridianFlipTimeBox->setValue(hours);
878 
879  meridianFlipSetupChanged();
880 }
881 
882 void Mount::paaStageChanged(int stage)
883 {
884  // Clear the current target position is necessary due to a bug in some mount drivers
885  // which report a mount slew instead of a mount motion. For these mounts, ending a slew
886  // leads to setting the current target position, which is necessary for meridian flips
887  // Since we want to avoid meridian flips during and after finishing PAA, it needs to
888  // be set to nullptr.
889 
890  if (stage != PolarAlignmentAssistant::PAH_IDLE)
891  currentTargetPosition = nullptr;
892 
893  switch (stage)
894  {
895  // deactivate the meridian flip when the first capture is taken
896  case PolarAlignmentAssistant::PAH_FIRST_CAPTURE:
897  case PolarAlignmentAssistant::PAH_FIRST_SOLVE:
898  if (m_MFStatus != FLIP_INACTIVE)
899  {
900  appendLogText(i18n("Meridian flip set inactive during polar alignment."));
901  m_MFStatus = FLIP_INACTIVE;
902  }
903  break;
904  // activate it when the last rotation is finished or stopped
905  // for safety reasons, we add all stages after the last rotation
906  case PolarAlignmentAssistant::PAH_THIRD_CAPTURE:
907  case PolarAlignmentAssistant::PAH_THIRD_SOLVE:
908  case PolarAlignmentAssistant::PAH_STAR_SELECT:
909  case PolarAlignmentAssistant::PAH_REFRESH:
910  case PolarAlignmentAssistant::PAH_POST_REFRESH:
911  case PolarAlignmentAssistant::PAH_IDLE:
912  if (m_MFStatus == FLIP_INACTIVE)
913  {
914  appendLogText(i18n("Polar alignment motions finished, meridian flip activated."));
915  m_MFStatus = FLIP_NONE;
916  }
917  break;
918  }
919 }
920 
921 void Mount::meridianFlipSetupChanged()
922 {
923  if (meridianFlipCheckBox->isChecked() == false)
924  // reset meridian flip
925  setMeridianFlipStatus(FLIP_NONE);
926 
927  Options::setExecuteMeridianFlip(meridianFlipCheckBox->isChecked());
928 
929  double offset = meridianFlipTimeBox->value();
930  // Degrees --> Hours
931  if (meridianFlipDegreesR->isChecked())
932  offset /= 15.0;
933  // It is always saved in hours
934  Options::setMeridianFlipOffset(offset);
935 }
936 
937 void Mount::setMeridianFlipStatus(MeridianFlipStatus status)
938 {
939  if (m_MFStatus != status)
940  {
941  m_MFStatus = status;
942  qCDebug (KSTARS_EKOS_MOUNT) << "Setting meridian flip status to " << meridianFlipStatusString(status);
943 
944  meridianFlipStatusChangedInternal(status);
945  emit newMeridianFlipStatus(status);
946  }
947 }
948 
949 void Mount::updateSwitch(ISwitchVectorProperty * svp)
950 {
951  if (!strcmp(svp->name, "TELESCOPE_SLEW_RATE"))
952  {
953  int index = IUFindOnSwitchIndex(svp);
954 
955  m_SpeedSlider->setProperty("value", index);
956  m_SpeedLabel->setProperty("text", i18nc(libindi_strings_context, svp->sp[index].label));
957  }
958 }
959 
960 void Mount::appendLogText(const QString &text)
961 {
962  m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
963  KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
964 
965  qCInfo(KSTARS_EKOS_MOUNT) << text;
966 
967  emit newLog(text);
968 }
969 
970 void Mount::updateLog(int messageID)
971 {
972  auto message = m_Mount->getMessage(messageID);
973  m_LogText.insert(0, i18nc("Message shown in Ekos Mount module", "%1", message));
974 
975  emit newLog(message);
976 }
977 
978 void Mount::clearLog()
979 {
980  m_LogText.clear();
981  emit newLog(QString());
982 }
983 
984 void Mount::motionCommand(int command, int NS, int WE)
985 {
986  if (NS != -1)
987  {
988  m_Mount->MoveNS(static_cast<ISD::Mount::VerticalMotion>(NS),
989  static_cast<ISD::Mount::MotionCommand>(command));
990  }
991 
992  if (WE != -1)
993  {
994  m_Mount->MoveWE(static_cast<ISD::Mount::HorizontalMotion>(WE),
995  static_cast<ISD::Mount::MotionCommand>(command));
996  }
997 }
998 
999 
1000 void Mount::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
1001 {
1002  m_Mount->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
1003 }
1004 
1005 
1007 {
1008  if (m_Mount == nullptr)
1009  return;
1010 
1011  if (scopeConfigNameEdit->text().isEmpty() == false)
1012  {
1013  auto tvp = m_Mount->getText("SCOPE_CONFIG_NAME");
1014  if (tvp)
1015  {
1016  tvp->at(0)->setText(scopeConfigNameEdit->text().toLatin1().constData());
1017  m_Mount->getDriverInfo()->getClientManager()->sendNewText(tvp);
1018  }
1019  }
1020 
1021  auto nvp = m_Mount->getNumber("TELESCOPE_INFO");
1022 
1023  if (nvp)
1024  {
1025  bool dirty = false;
1026  primaryScopeGroup->setTitle(m_Mount->getDeviceName());
1027  guideScopeGroup->setTitle(i18n("%1 guide scope", m_Mount->getDeviceName()));
1028 
1029  auto np = nvp->findWidgetByName("TELESCOPE_APERTURE");
1030  if (np && std::fabs(np->getValue() - primaryScopeApertureIN->value()) > 0)
1031  {
1032  dirty = true;
1033  np->setValue(primaryScopeApertureIN->value());
1034  }
1035 
1036  np = nvp->findWidgetByName("TELESCOPE_FOCAL_LENGTH");
1037  if (np && std::fabs(np->getValue() - primaryScopeFocalIN->value()) > 0)
1038  np->setValue(primaryScopeFocalIN->value());
1039 
1040  np = nvp->findWidgetByName("GUIDER_APERTURE");
1041  if (np && std::fabs(np->getValue() - guideScopeApertureIN->value()) > 0)
1042  {
1043  dirty = true;
1044  np->setValue(guideScopeApertureIN->value() <= 1 ? primaryScopeApertureIN->value() : guideScopeApertureIN->value());
1045  }
1046 
1047  np = nvp->findWidgetByName("GUIDER_FOCAL_LENGTH");
1048  if (np && std::fabs(np->getValue() - guideScopeFocalIN->value()) > 0)
1049  {
1050  dirty = true;
1051  np->setValue(guideScopeFocalIN->value() <= 1 ? primaryScopeFocalIN->value() : guideScopeFocalIN->value());
1052  }
1053 
1054  ClientManager *clientManager = m_Mount->getDriverInfo()->getClientManager();
1055 
1056  clientManager->sendNewNumber(nvp);
1057 
1058  if (dirty)
1059  m_Mount->setConfig(SAVE_CONFIG);
1060  }
1061  else
1062  appendLogText(i18n("Failed to save telescope information."));
1063 }
1064 
1066 {
1067  Options::setMinimumAltLimit(minAltLimit->value());
1068  Options::setMaximumAltLimit(maxAltLimit->value());
1069  m_Mount->setAltLimits(minAltLimit->value(), maxAltLimit->value());
1070 
1071  Options::setMaximumHaLimit(maxHaLimit->value());
1072 }
1073 
1075 {
1076  Options::setEnableAltitudeLimits(enable);
1077 
1078  if (enable)
1079  {
1080  minAltLabel->setEnabled(true);
1081  maxAltLabel->setEnabled(true);
1082 
1083  minAltLimit->setEnabled(true);
1084  maxAltLimit->setEnabled(true);
1085 
1086  if (m_Mount)
1087  m_Mount->setAltLimits(minAltLimit->value(), maxAltLimit->value());
1088  }
1089  else
1090  {
1091  minAltLabel->setEnabled(false);
1092  maxAltLabel->setEnabled(false);
1093 
1094  minAltLimit->setEnabled(false);
1095  maxAltLimit->setEnabled(false);
1096 
1097  if (m_Mount)
1098  m_Mount->setAltLimits(-1, -1);
1099  }
1100 }
1101 
1103 {
1104  //Only enable if it was already enabled before and the minAltLimit is currently disabled.
1105  if (m_AltitudeLimitEnabled && minAltLimit->isEnabled() == false)
1106  enableAltitudeLimits(true);
1107 }
1108 
1110 {
1111  m_AltitudeLimitEnabled = enableLimitsCheck->isChecked();
1112 
1113  enableAltitudeLimits(false);
1114 }
1115 
1117 {
1118  Options::setEnableHaLimit(enable);
1119 
1120  maxHaLabel->setEnabled(enable);
1121  maxHaLimit->setEnabled(enable);
1122 }
1123 
1125 {
1126  //Only enable if it was already enabled before and the minHaLimit is currently disabled.
1127  if (m_HourAngleLimitEnabled && maxHaLimit->isEnabled() == false)
1128  enableHourAngleLimits(true);
1129 }
1130 
1132 {
1133  m_HourAngleLimitEnabled = enableHaLimitCheck->isChecked();
1134 
1135  enableHourAngleLimits(false);
1136 }
1137 
1138 QList<double> Mount::altitudeLimits()
1139 {
1140  QList<double> limits;
1141 
1142  limits.append(minAltLimit->value());
1143  limits.append(maxAltLimit->value());
1144 
1145  return limits;
1146 }
1147 
1149 {
1150  minAltLimit->setValue(limits[0]);
1151  maxAltLimit->setValue(limits[1]);
1152 }
1153 
1155 {
1156  enableLimitsCheck->setChecked(enable);
1157 }
1158 
1159 bool Mount::altitudeLimitsEnabled()
1160 {
1161  return enableLimitsCheck->isChecked();
1162 }
1163 
1164 double Mount::hourAngleLimit()
1165 {
1166  return maxHaLimit->value();
1167 }
1168 
1169 void Mount::setHourAngleLimit(double limit)
1170 {
1171  maxHaLimit->setValue(limit);
1172 }
1173 
1175 {
1176  enableHaLimitCheck->setChecked(enable);
1177 }
1178 
1179 bool Mount::hourAngleLimitEnabled()
1180 {
1181  return enableHaLimitCheck->isChecked();
1182 }
1183 
1184 void Mount::setJ2000Enabled(bool enabled)
1185 {
1186  m_J2000Check->setProperty("checked", enabled);
1187 }
1188 
1189 bool Mount::gotoTarget(const QString &target)
1190 {
1191  SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
1192 
1193  if (object != nullptr)
1194  {
1195  object->updateCoordsNow(KStarsData::Instance()->updateNum());
1196  return slew(object->ra().Hours(), object->dec().Degrees());
1197  }
1198 
1199  return false;
1200 }
1201 
1202 bool Mount::gotoTarget(const SkyPoint &target)
1203 {
1204  return slew(target.ra().Hours(), target.dec().Degrees());
1205 }
1206 
1207 bool Mount::syncTarget(const QString &target)
1208 {
1209  SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
1210 
1211  if (object != nullptr)
1212  {
1213  object->updateCoordsNow(KStarsData::Instance()->updateNum());
1214  return sync(object->ra().Hours(), object->dec().Degrees());
1215  }
1216 
1217  return false;
1218 }
1219 
1220 bool Mount::slew(const QString &RA, const QString &DEC)
1221 {
1222  dms ra, de;
1223 
1224  if (m_equatorialCheck->property("checked").toBool())
1225  {
1226  ra = dms::fromString(RA, false);
1227  de = dms::fromString(DEC, true);
1228  }
1229 
1230  if (m_horizontalCheck->property("checked").toBool())
1231  {
1232  dms az = dms::fromString(RA, true);
1233  dms at = dms::fromString(DEC, true);
1234  SkyPoint target;
1235  target.setAz(az);
1236  target.setAlt(at);
1237  target.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1238  ra = target.ra();
1239  de = target.dec();
1240  }
1241 
1242  if (m_haEquatorialCheck->property("checked").toBool())
1243  {
1244  dms ha = dms::fromString(RA, false);
1245  de = dms::fromString(DEC, true);
1246  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1247  ra = (lst - ha + dms(360.0)).reduce();
1248  }
1249 
1250  // If J2000 was checked and the Mount is _not_ already using native J2000 coordinates
1251  // then we need to convert J2000 to JNow. Otherwise, we send J2000 as is.
1252  if (m_J2000Check->property("checked").toBool() && m_Mount && m_Mount->isJ2000() == false)
1253  {
1254  // J2000 ---> JNow
1255  SkyPoint J2000Coord(ra, de);
1256  J2000Coord.setRA0(ra);
1257  J2000Coord.setDec0(de);
1258  J2000Coord.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
1259 
1260  ra = J2000Coord.ra();
1261  de = J2000Coord.dec();
1262  }
1263 
1264  return slew(ra.Hours(), de.Degrees());
1265 }
1266 
1267 bool Mount::slew(double RA, double DEC)
1268 {
1269  if (m_Mount == nullptr || m_Mount->isConnected() == false)
1270  return false;
1271 
1272  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1273  double HA = lst.Hours() - RA;
1274  HA = rangeHA(HA);
1275  // if (HA > 12.0)
1276  // HA -= 24.0;
1277  setInitialHA(HA);
1278  // reset the meridian flip status if the slew is not the meridian flip itself
1279  if (m_MFStatus != FLIP_RUNNING)
1280  {
1281  setMeridianFlipStatus(FLIP_NONE);
1282  flipDelayHrs = 0;
1283  qCDebug(KSTARS_EKOS_MOUNT) << "flipDelayHrs set to zero in slew, m_MFStatus=" <<
1284  meridianFlipStatusString(m_MFStatus);
1285  }
1286 
1287  delete currentTargetPosition;
1288  currentTargetPosition = new SkyPoint(RA, DEC);
1289  SkyPoint J2000Coord(currentTargetPosition->ra(), currentTargetPosition->dec());
1290  J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
1291  currentTargetPosition->setRA0(J2000Coord.ra());
1292  currentTargetPosition->setDec0(J2000Coord.dec());
1293 
1294 
1295  qCDebug(KSTARS_EKOS_MOUNT) << "Slewing to RA=" <<
1296  currentTargetPosition->ra().toHMSString() <<
1297  "DEC=" << currentTargetPosition->dec().toDMSString();
1298  qCDebug(KSTARS_EKOS_MOUNT) << "Initial HA " << initialHA() << ", flipDelayHrs " << flipDelayHrs <<
1299  "MFStatus " << meridianFlipStatusString(m_MFStatus);
1300 
1301  // start the slew
1302  return(m_Mount->Slew(currentTargetPosition));
1303 }
1304 
1305 ///
1306 /// \brief Mount::checkMeridianFlip This updates the Meridian Flip Status state machine using the LST supplied,
1307 /// the mount target position and the pier side if available.
1308 /// \param lst
1309 /// \return true if a flip slew can be started, false otherwise
1310 ///
1312 {
1313  // checks if a flip is possible
1314  if (m_Mount == nullptr || m_Mount->isConnected() == false)
1315  {
1316  meridianFlipStatusText->setText(i18n("Status: inactive (no scope connected)"));
1317  emit newMeridianFlipText(meridianFlipStatusText->text());
1318  setMeridianFlipStatus(FLIP_NONE);
1319  return false;
1320  }
1321 
1322  if (meridianFlipCheckBox->isChecked() == false)
1323  {
1324  meridianFlipStatusText->setText(i18n("Status: inactive (flip not requested)"));
1325  emit newMeridianFlipText(meridianFlipStatusText->text());
1326  return false;
1327  }
1328 
1329  // Will never get called when parked!
1330  if (m_Mount->isParked())
1331  {
1332  meridianFlipStatusText->setText(i18n("Status: inactive (parked)"));
1333  emit newMeridianFlipText(meridianFlipStatusText->text());
1334  return false;
1335  }
1336 
1337  if (currentTargetPosition == nullptr || m_MFStatus == FLIP_INACTIVE)
1338  {
1339  meridianFlipStatusText->setText(i18n("Status: inactive (no Target set)"));
1340  emit newMeridianFlipText(meridianFlipStatusText->text());
1341  return false;
1342  }
1343 
1344  // get the time after the meridian that the flip is called for
1345  double offset = meridianFlipTimeBox->value();
1346  // Degrees --> Hours
1347  if (meridianFlipDegreesR->isChecked())
1348  offset = rangeHA(offset / 15.0);
1349 
1350  double hrsToFlip = 0; // time to go to the next flip - hours -ve means a flip is required
1351 
1352  double ha = rangeHA(lst.Hours() - telescopeCoord.ra().Hours()); // -12 to 0 to +12
1353 
1354  // calculate time to next flip attempt. This uses the current hour angle, the pier side if available
1355  // and the meridian flip offset to get the time to the flip
1356  //
1357  // *** should it use the target position so it will continue to track the target even if the mount is not tracking?
1358  //
1359  // Note: the PierSide code relies on the mount reporting the pier side correctly
1360  // It is possible that a mount can flip before the meridian and this has caused problems so hrsToFlip is calculated
1361  // assuming the the mount can flip up to three hours early.
1362 
1363  static ISD::Mount::PierSide
1364  initialPierSide; // used when the flip has completed to determine if the flip was successful
1365 
1366  // adjust ha according to the pier side.
1367  switch (m_Mount->pierSide())
1368  {
1369  case ISD::Mount::PierSide::PIER_WEST:
1370  // this is the normal case, tracking from East to West, flip is near Ha 0.
1371  break;
1372  case ISD::Mount::PierSide::PIER_EAST:
1373  // this is the below the pole case, tracking West to East, flip is near Ha 12.
1374  // shift ha by 12h
1375  ha = rangeHA(ha + 12);
1376  break;
1377  default:
1378  // This is the case where the PierSide is not available, make one attempt only
1379  flipDelayHrs = 0;
1380  // we can only attempt a flip if the mount started before the meridian, assumed in the unflipped state
1381  if (initialHA() >= 0)
1382  {
1383  meridianFlipStatusText->setText(i18n("Status: inactive (slew after meridian)"));
1384  emit newMeridianFlipText(meridianFlipStatusText->text());
1385  if (m_MFStatus == FLIP_NONE)
1386  return false;
1387  }
1388  break;
1389  }
1390  // get the time to the next flip, allowing for the pier side and
1391  // the possibility of an early flip
1392  // adjust ha so an early flip is allowed for
1393  if (ha >= 9.0)
1394  ha -= 24.0;
1395  hrsToFlip = offset + flipDelayHrs - ha;
1396 
1397  int hh = static_cast<int> (hrsToFlip);
1398  int mm = static_cast<int> ((hrsToFlip - hh) * 60);
1399  int ss = static_cast<int> ((hrsToFlip - hh - mm / 60.0) * 3600);
1400  QString message = i18n("Meridian flip in %1", QTime(hh, mm, ss).toString(Qt::TextDate));
1401 
1402  // handle the meridian flip state machine
1403  switch (m_MFStatus)
1404  {
1405  case FLIP_NONE:
1406  meridianFlipStatusText->setText(message);
1407  emit newMeridianFlipText(meridianFlipStatusText->text());
1408 
1409  if (hrsToFlip <= 0)
1410  {
1411  // signal that a flip can be done
1412  qCDebug(KSTARS_EKOS_MOUNT) << "Meridian flip planned with LST=" <<
1413  lst.toHMSString() <<
1414  " scope RA=" << telescopeCoord.ra().toHMSString() <<
1415  " ha=" << ha <<
1416  ", meridian diff=" << offset <<
1417  ", hrstoFlip=" << hrsToFlip <<
1418  ", flipDelayHrs=" << flipDelayHrs <<
1419  ", " << pierSideStateString();
1420 
1421  initialPierSide = m_Mount->pierSide();
1422  setMeridianFlipStatus(FLIP_PLANNED);
1423  }
1424  break;
1425 
1426  case FLIP_PLANNED:
1427  // handle the case where there is no Capture module
1428  if (captureInterface == nullptr)
1429  {
1430  qCDebug(KSTARS_EKOS_MOUNT) << "no capture interface, starting flip slew.";
1431  setMeridianFlipStatus(FLIP_ACCEPTED);
1432  return true;
1433  }
1434  return false;
1435 
1436  case FLIP_ACCEPTED:
1437  // set by the Capture module when it's ready
1438  return true;
1439 
1440  case FLIP_RUNNING:
1441  if (m_Mount->isTracking())
1442  {
1443  // meridian flip slew completed, did it work?
1444  bool flipFailed = false;
1445 
1446  // pointing state change check only for mounts that report pier side
1447  if (m_Mount->pierSide() == ISD::Mount::PIER_UNKNOWN)
1448  {
1449  // check how long it took
1450  if (minMeridianFlipEndTime > QDateTime::currentDateTimeUtc())
1451  {
1452  // don't fail, we have tried but we don't know where the mount was when it started
1453  appendLogText(i18n("Meridian flip failed - time too short, pier side unknown."));
1454  // signal that capture can resume
1455  setMeridianFlipStatus(FLIP_COMPLETED);
1456  return false;
1457  }
1458  }
1459  else if (m_Mount->pierSide() == initialPierSide)
1460  {
1461  flipFailed = true;
1462  qCWarning(KSTARS_EKOS_MOUNT) << "Meridian flip failed, pier side not changed";
1463  }
1464 
1465  if (flipFailed)
1466  {
1467  if (flipDelayHrs <= 1.0)
1468  {
1469  // Set next flip attempt to be 4 minutes in the future.
1470  // These depend on the assignment to flipDelayHrs above.
1471  constexpr double delayHours = 4.0 / 60.0;
1472  if (m_Mount->pierSide() == ISD::Mount::PierSide::PIER_EAST)
1473  flipDelayHrs = rangeHA(ha + 12 + delayHours) - offset;
1474  else
1475  flipDelayHrs = ha + delayHours - offset;
1476 
1477  // check to stop an infinite loop, 1.0 hrs for now but should use the Ha limit
1478  appendLogText(i18n("meridian flip failed, retrying in 4 minutes"));
1479  }
1480  else
1481  {
1482  appendLogText(i18n("No successful Meridian Flip done, delay too long"));
1483  }
1484  setMeridianFlipStatus(FLIP_COMPLETED); // this will resume imaging and try again after the extra delay
1485  }
1486  else
1487  {
1488  flipDelayHrs = 0;
1489  appendLogText(i18n("Meridian flip completed OK."));
1490  // signal that capture can resume
1491  setMeridianFlipStatus(FLIP_COMPLETED);
1492  }
1493  }
1494  break;
1495 
1496  case FLIP_COMPLETED:
1497  setMeridianFlipStatus(FLIP_NONE);
1498  break;
1499 
1500  default:
1501  break;
1502  }
1503  return false;
1504 }
1505 
1506 bool Mount::executeMeridianFlip()
1507 {
1508  if (/*initialHA() > 0 || */ currentTargetPosition == nullptr)
1509  {
1510  // no meridian flip necessary
1511  qCDebug(KSTARS_EKOS_MOUNT) << "No meridian flip: currentTargetPosition is null";
1512  return false;
1513  }
1514 
1515  if (m_Mount->status() != ISD::Mount::MOUNT_TRACKING)
1516  {
1517  // no meridian flip necessary
1518  qCDebug(KSTARS_EKOS_MOUNT) << "No meridian flip: mount not tracking";
1519  return false;
1520  }
1521 
1522  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1523  double HA = rangeHA(lst.Hours() - currentTargetPosition->ra().Hours());
1524 
1525  // execute meridian flip
1526  qCInfo(KSTARS_EKOS_MOUNT) << "Meridian flip: slewing to RA=" <<
1527  currentTargetPosition->ra().toHMSString() <<
1528  "DEC=" << currentTargetPosition->dec().toDMSString() <<
1529  " Hour Angle " << dms(HA).toHMSString();
1530  setMeridianFlipStatus(FLIP_RUNNING);
1531 
1532  minMeridianFlipEndTime = KStarsData::Instance()->clock()->utc().addSecs(minMeridianFlipDurationSecs);
1533 
1534  if (slew(currentTargetPosition->ra().Hours(), currentTargetPosition->dec().Degrees()))
1535  {
1536  appendLogText(i18n("Meridian flip slew started..."));
1537  return true;
1538  }
1539  else
1540  {
1541  qCWarning(KSTARS_EKOS_MOUNT) << "Meridian flip FAILED: slewing to RA=" <<
1542  currentTargetPosition->ra().toHMSString() <<
1543  "DEC=" << currentTargetPosition->dec().toDMSString();
1544  return false;
1545  }
1546 }
1547 
1548 // This method should just be called by the signal coming from Capture, indicating the
1549 // internal state of Capture.
1550 void Mount::meridianFlipStatusChanged(Mount::MeridianFlipStatus status)
1551 {
1552  qCDebug(KSTARS_EKOS_MOUNT) << "Received capture meridianFlipStatusChange " << meridianFlipStatusString(status);
1553 
1554  // only the states FLIP_WAITING and FLIP_ACCEPTED are relevant as answers
1555  // to FLIP_PLANNED, all other states are set only internally
1556  if (status == FLIP_WAITING || status == FLIP_ACCEPTED)
1557  meridianFlipStatusChangedInternal(status);
1558 }
1559 
1560 void Mount::meridianFlipStatusChangedInternal(Mount::MeridianFlipStatus status)
1561 {
1562  m_MFStatus = status;
1563 
1564  qCDebug(KSTARS_EKOS_MOUNT) << "meridianFlipStatusChanged " << meridianFlipStatusString(status);
1565 
1566  switch (status)
1567  {
1568  case FLIP_NONE:
1569  meridianFlipStatusText->setText(i18n("Status: inactive"));
1570  emit newMeridianFlipText(meridianFlipStatusText->text());
1571  break;
1572 
1573  case FLIP_PLANNED:
1574  meridianFlipStatusText->setText(i18n("Meridian flip planned..."));
1575  emit newMeridianFlipText(meridianFlipStatusText->text());
1576  break;
1577 
1578  case FLIP_WAITING:
1579  meridianFlipStatusText->setText(i18n("Meridian flip waiting..."));
1580  emit newMeridianFlipText(meridianFlipStatusText->text());
1581  appendLogText(i18n("Meridian flip waiting."));
1582  break;
1583 
1584  case FLIP_ACCEPTED:
1585  if (m_Mount == nullptr || m_Mount->isTracking() == false)
1586  // if the mount is not tracking, we go back one step
1587  setMeridianFlipStatus(FLIP_PLANNED);
1588  // otherwise do nothing, execution of meridian flip initianted in updateTelescopeCoords()
1589  break;
1590 
1591  case FLIP_RUNNING:
1592  meridianFlipStatusText->setText(i18n("Meridian flip running..."));
1593  emit newMeridianFlipText(meridianFlipStatusText->text());
1594  appendLogText(i18n("Meridian flip started."));
1595  break;
1596 
1597  case FLIP_COMPLETED:
1598  meridianFlipStatusText->setText(i18n("Meridian flip completed."));
1599  emit newMeridianFlipText(meridianFlipStatusText->text());
1600  appendLogText(i18n("Meridian flip completed."));
1601  break;
1602 
1603  default:
1604  break;
1605  }
1606 }
1607 
1609 {
1610  return *currentTargetPosition;
1611 }
1612 
1613 
1614 bool Mount::sync(const QString &RA, const QString &DEC)
1615 {
1616  dms ra, de;
1617 
1618  if (m_equatorialCheck->property("checked").toBool())
1619  {
1620  ra = dms::fromString(RA, false);
1621  de = dms::fromString(DEC, true);
1622  }
1623 
1624  if (m_horizontalCheck->property("checked").toBool())
1625  {
1626  dms az = dms::fromString(RA, true);
1627  dms at = dms::fromString(DEC, true);
1628  SkyPoint target;
1629  target.setAz(az);
1630  target.setAlt(at);
1631  target.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1632  ra = target.ra();
1633  de = target.dec();
1634  }
1635 
1636  if (m_haEquatorialCheck->property("checked").toBool())
1637  {
1638  dms ha = dms::fromString(RA, false);
1639  de = dms::fromString(DEC, true);
1640  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1641  ra = (lst - ha + dms(360.0)).reduce();
1642  }
1643 
1644  if (m_J2000Check->property("checked").toBool())
1645  {
1646  // J2000 ---> JNow
1647  SkyPoint J2000Coord(ra, de);
1648  J2000Coord.setRA0(ra);
1649  J2000Coord.setDec0(de);
1650  J2000Coord.updateCoordsNow(KStarsData::Instance()->updateNum());
1651 
1652  ra = J2000Coord.ra();
1653  de = J2000Coord.dec();
1654  }
1655 
1656  return sync(ra.Hours(), de.Degrees());
1657 }
1658 
1659 bool Mount::sync(double RA, double DEC)
1660 {
1661  if (m_Mount == nullptr || m_Mount->isConnected() == false)
1662  return false;
1663 
1664  return m_Mount->Sync(RA, DEC);
1665 }
1666 
1668 {
1669  return m_Mount->Abort();
1670 }
1671 
1672 IPState Mount::slewStatus()
1673 {
1674  if (m_Mount == nullptr)
1675  return IPS_ALERT;
1676 
1677  return m_Mount->getState("EQUATORIAL_EOD_COORD");
1678 }
1679 
1680 QList<double> Mount::equatorialCoords()
1681 {
1682  double ra, dec;
1683  QList<double> coords;
1684 
1685  m_Mount->getEqCoords(&ra, &dec);
1686  coords.append(ra);
1687  coords.append(dec);
1688 
1689  return coords;
1690 }
1691 
1692 QList<double> Mount::horizontalCoords()
1693 {
1694  QList<double> coords;
1695 
1696  coords.append(telescopeCoord.az().Degrees());
1697  coords.append(telescopeCoord.alt().Degrees());
1698 
1699  return coords;
1700 }
1701 
1702 ///
1703 /// \brief Mount::hourAngle
1704 /// \return returns the current mount hour angle in hours in the range -12 to +12
1705 ///
1706 double Mount::hourAngle()
1707 {
1708  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1709  dms ha(lst.Degrees() - telescopeCoord.ra().Degrees());
1710  double HA = rangeHA(ha.Hours());
1711 
1712  return HA;
1713  // if (HA > 12.0)
1714  // return (HA - 24.0);
1715  // else
1716  // return HA;
1717 }
1718 
1719 QList<double> Mount::telescopeInfo()
1720 {
1721  QList<double> info;
1722 
1723  info.append(primaryScopeFocalIN->value());
1724  info.append(primaryScopeApertureIN->value());
1725  info.append(guideScopeFocalIN->value());
1726  info.append(guideScopeApertureIN->value());
1727 
1728  return info;
1729 }
1730 
1732 {
1733  if (info[0] > 0)
1734  primaryScopeFocalIN->setValue(info[0]);
1735  if (info[1] > 0)
1736  primaryScopeApertureIN->setValue(info[1]);
1737  if (info[2] > 0)
1738  guideScopeFocalIN->setValue(info[2]);
1739  if (info[3] > 0)
1740  guideScopeApertureIN->setValue(info[3]);
1741 
1742  if (scopeConfigNameEdit->text().isEmpty() == false)
1743  appendLogText(i18n("Warning: Overriding %1 configuration.", scopeConfigNameEdit->text()));
1744 
1745  save();
1746 }
1747 
1748 bool Mount::canPark()
1749 {
1750  if (m_Mount == nullptr)
1751  return false;
1752 
1753  return m_Mount->canPark();
1754 }
1755 
1757 {
1758  if (m_Mount == nullptr || m_Mount->canPark() == false)
1759  return false;
1760 
1761  return m_Mount->Park();
1762 }
1763 
1765 {
1766  if (m_Mount == nullptr || m_Mount->canPark() == false)
1767  return false;
1768 
1769  return m_Mount->UnPark();
1770 }
1771 
1772 
1773 void Mount::toggleMountToolBox()
1774 {
1775  if (m_BaseView->isVisible())
1776  {
1777  m_BaseView->hide();
1778  QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1779  if (a)
1780  a->setChecked(false);
1781  }
1782  else
1783  {
1784  m_BaseView->show();
1785  QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1786  if (a)
1787  a->setChecked(true);
1788  }
1789 }
1790 
1791 void Mount::findTarget()
1792 {
1793  if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) == QDialog::Accepted)
1794  {
1795  SkyObject *object = FindDialog::Instance()->targetObject();
1796  if (object != nullptr)
1797  {
1798  KStarsData * const data = KStarsData::Instance();
1799 
1800  SkyObject *o = object->clone();
1801  o->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
1802 
1803  m_equatorialCheck->setProperty("checked", true);
1804 
1805  m_targetText->setProperty("text", o->name());
1806 
1807  if (m_JNowCheck->property("checked").toBool())
1808  {
1809  m_targetRAText->setProperty("text", o->ra().toHMSString());
1810  m_targetDEText->setProperty("text", o->dec().toDMSString());
1811  }
1812  else
1813  {
1814  m_targetRAText->setProperty("text", o->ra0().toHMSString());
1815  m_targetDEText->setProperty("text", o->dec0().toDMSString());
1816  }
1817  }
1818  }
1819 }
1820 
1821 //++++ converters for target coordinate display in Mount Control box
1822 
1823 bool Mount::raDecToAzAlt(QString qsRA, QString qsDec)
1824 {
1825  dms RA, Dec;
1826 
1827  if (!RA.setFromString(qsRA, false) || !Dec.setFromString(qsDec, true))
1828  return false;
1829 
1830  SkyPoint targetCoord(RA, Dec);
1831 
1832  targetCoord.EquatorialToHorizontal(KStarsData::Instance()->lst(),
1833  KStarsData::Instance()->geo()->lat());
1834 
1835  m_targetRAText->setProperty("text", targetCoord.az().toDMSString());
1836  m_targetDEText->setProperty("text", targetCoord.alt().toDMSString());
1837 
1838  return true;
1839 }
1840 
1841 bool Mount::raDecToHaDec(QString qsRA)
1842 {
1843  dms RA;
1844 
1845  if (!RA.setFromString(qsRA, false))
1846  return false;
1847 
1848  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1849 
1850  dms HA = (lst - RA + dms(360.0)).reduce();
1851 
1852  QChar sgn('+');
1853  if (HA.Hours() > 12.0)
1854  {
1855  HA.setH(24.0 - HA.Hours());
1856  sgn = '-';
1857  }
1858 
1859  m_targetRAText->setProperty("text", QString("%1%2").arg(sgn).arg(HA.toHMSString()));
1860 
1861  return true;
1862 }
1863 
1864 bool Mount::azAltToRaDec(QString qsAz, QString qsAlt)
1865 {
1866  dms Az, Alt;
1867 
1868  if (!Az.setFromString(qsAz, true) || !Alt.setFromString(qsAlt, true))
1869  return false;
1870 
1871  SkyPoint targetCoord;
1872  targetCoord.setAz(Az);
1873  targetCoord.setAlt(Alt);
1874 
1875  targetCoord.HorizontalToEquatorial(KStars::Instance()->data()->lst(),
1876  KStars::Instance()->data()->geo()->lat());
1877 
1878  m_targetRAText->setProperty("text", targetCoord.ra().toHMSString());
1879  m_targetDEText->setProperty("text", targetCoord.dec().toDMSString());
1880 
1881  return true;
1882 }
1883 
1884 bool Mount::azAltToHaDec(QString qsAz, QString qsAlt)
1885 {
1886  dms Az, Alt;
1887 
1888  if (!Az.setFromString(qsAz, true) || !Alt.setFromString(qsAlt, true))
1889  return false;
1890 
1891  SkyPoint targetCoord;
1892  targetCoord.setAz(Az);
1893  targetCoord.setAlt(Alt);
1894 
1895  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1896 
1897  targetCoord.HorizontalToEquatorial(&lst, KStars::Instance()->data()->geo()->lat());
1898 
1899  dms HA = (lst - targetCoord.ra() + dms(360.0)).reduce();
1900 
1901  QChar sgn('+');
1902  if (HA.Hours() > 12.0)
1903  {
1904  HA.setH(24.0 - HA.Hours());
1905  sgn = '-';
1906  }
1907 
1908  m_targetRAText->setProperty("text", QString("%1%2").arg(sgn).arg(HA.toHMSString()));
1909  m_targetDEText->setProperty("text", targetCoord.dec().toDMSString());
1910 
1911 
1912  return true;
1913 }
1914 
1915 bool Mount::haDecToRaDec(QString qsHA)
1916 {
1917  dms HA;
1918 
1919  if (!HA.setFromString(qsHA, false))
1920  return false;
1921 
1922  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1923  dms RA = (lst - HA + dms(360.0)).reduce();
1924 
1925  m_targetRAText->setProperty("text", RA.toHMSString());
1926 
1927  return true;
1928 }
1929 
1930 bool Mount::haDecToAzAlt(QString qsHA, QString qsDec)
1931 {
1932  dms HA, Dec;
1933 
1934  if (!HA.setFromString(qsHA, false) || !Dec.setFromString(qsDec, true))
1935  return false;
1936 
1937  dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1938  dms RA = (lst - HA + dms(360.0)).reduce();
1939 
1940  SkyPoint targetCoord;
1941  targetCoord.setRA(RA);
1942  targetCoord.setDec(Dec);
1943 
1944  targetCoord.EquatorialToHorizontal(&lst, KStars::Instance()->data()->geo()->lat());
1945 
1946  m_targetRAText->setProperty("text", targetCoord.az().toDMSString());
1947  m_targetDEText->setProperty("text", targetCoord.alt().toDMSString());
1948 
1949  return true;
1950 }
1951 
1952 //---- end: converters for target coordinate display in Mount Control box
1953 
1954 void Mount::centerMount()
1955 {
1956  if (m_Mount)
1957  m_Mount->find();
1958 }
1959 
1961 {
1962  if (m_Mount == nullptr)
1963  return false;
1964 
1965  if (m_Mount->hasAlignmentModel() == false)
1966  return false;
1967 
1968  if (m_Mount->clearAlignmentModel())
1969  {
1970  appendLogText(i18n("Alignment Model cleared."));
1971  return true;
1972  }
1973 
1974  appendLogText(i18n("Failed to clear Alignment Model."));
1975  return false;
1976 }
1977 
1978 
1979 void Mount::setScopeStatus(ISD::Mount::Status status)
1980 {
1981  if (m_Status != status)
1982  {
1983  m_statusText->setProperty("text", m_Mount->statusString(status));
1984  m_Status = status;
1985  // forward
1986  emit newStatus(status);
1987  }
1988 }
1989 
1990 void Mount::setTrackEnabled(bool enabled)
1991 {
1992  if (enabled)
1993  trackOnB->click();
1994  else
1995  trackOffB->click();
1996 }
1997 
1998 int Mount::slewRate()
1999 {
2000  if (m_Mount == nullptr)
2001  return -1;
2002 
2003  return m_Mount->getSlewRate();
2004 }
2005 
2006 //QJsonArray Mount::getScopes() const
2007 //{
2008 // QJsonArray scopes;
2009 // if (currentTelescope == nullptr)
2010 // return scopes;
2011 
2012 // QJsonObject primary =
2013 // {
2014 // {"name", "Primary"},
2015 // {"mount", currentTelescope->getDeviceName()},
2016 // {"aperture", primaryScopeApertureIN->value()},
2017 // {"focalLength", primaryScopeFocalIN->value()},
2018 // };
2019 
2020 // scopes.append(primary);
2021 
2022 // QJsonObject guide =
2023 // {
2024 // {"name", "Guide"},
2025 // {"mount", currentTelescope->getDeviceName()},
2026 // {"aperture", primaryScopeApertureIN->value()},
2027 // {"focalLength", primaryScopeFocalIN->value()},
2028 // };
2029 
2030 // scopes.append(guide);
2031 
2032 // return scopes;
2033 //}
2034 
2035 bool Mount::autoParkEnabled()
2036 {
2037  return autoParkTimer.isActive();
2038 }
2039 
2040 void Mount::setAutoParkEnabled(bool enable)
2041 {
2042  if (enable)
2043  startParkTimer();
2044  else
2045  stopParkTimer();
2046 }
2047 
2049 {
2050  everyDayCheck->setChecked(enabled);
2051 }
2052 
2054 {
2055  startupTimeEdit->setTime(startup);
2056 }
2057 
2058 bool Mount::meridianFlipEnabled()
2059 {
2060  return meridianFlipCheckBox->isChecked();
2061 }
2062 
2063 double Mount::meridianFlipValue()
2064 {
2065  return meridianFlipTimeBox->value();
2066 }
2067 
2069 {
2070  autoParkTimer.stop();
2071  if (m_Mount)
2072  m_Mount->stopTimers();
2073 }
2074 
2075 void Mount::startParkTimer()
2076 {
2077  if (m_Mount == nullptr || m_ParkStatus == ISD::PARK_UNKNOWN)
2078  return;
2079 
2080  if (m_Mount->isParked())
2081  {
2082  appendLogText(i18n("Mount already parked."));
2083  return;
2084  }
2085 
2086  QTime parkTime = startupTimeEdit->time();
2087 
2088  qCDebug(KSTARS_EKOS_MOUNT) << "Parking time is" << parkTime.toString();
2089  QDateTime currentDateTime = KStarsData::Instance()->lt();
2090  QDateTime parkDateTime(currentDateTime);
2091 
2092  parkDateTime.setTime(parkTime);
2093  qint64 parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
2094  qCDebug(KSTARS_EKOS_MOUNT) << "Until parking time:" << parkMilliSeconds << "ms or" << parkMilliSeconds / (60 * 60 * 1000)
2095  << "hours";
2096  if (parkMilliSeconds > 0)
2097  {
2098  qCDebug(KSTARS_EKOS_MOUNT) << "Added a day to parking time...";
2099  parkDateTime = parkDateTime.addDays(1);
2100  parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
2101 
2102  int hours = static_cast<int>(parkMilliSeconds / (1000 * 60 * 60));
2103  if (hours > 0)
2104  {
2105  // No need to display warning for every day check
2106  if (everyDayCheck->isChecked() == false)
2107  appendLogText(i18n("Parking time cannot be in the past."));
2108  return;
2109  }
2110  }
2111 
2112  parkMilliSeconds = std::abs(parkMilliSeconds);
2113 
2114  if (parkMilliSeconds > 24 * 60 * 60 * 1000)
2115  {
2116  appendLogText(i18n("Parking time must be within 24 hours of current time."));
2117  return;
2118  }
2119 
2120  if (parkMilliSeconds > 12 * 60 * 60 * 1000)
2121  appendLogText(i18n("Warning! Parking time is more than 12 hours away."));
2122 
2123  appendLogText(i18n("Caution: do not use Auto Park while scheduler is active."));
2124 
2125  autoParkTimer.setInterval(static_cast<int>(parkMilliSeconds));
2126  autoParkTimer.start();
2127 
2128  startTimerB->setEnabled(false);
2129  stopTimerB->setEnabled(true);
2130 }
2131 
2132 void Mount::stopParkTimer()
2133 {
2134  autoParkTimer.stop();
2135  countdownLabel->setText("00:00:00");
2136  stopTimerB->setEnabled(false);
2137  startTimerB->setEnabled(true);
2138 }
2139 
2140 void Mount::startAutoPark()
2141 {
2142  appendLogText(i18n("Parking timer is up."));
2143  autoParkTimer.stop();
2144  startTimerB->setEnabled(true);
2145  stopTimerB->setEnabled(false);
2146  countdownLabel->setText("00:00:00");
2147  if (m_Mount)
2148  {
2149  if (m_Mount->isParked() == false)
2150  {
2151  appendLogText(i18n("Starting auto park..."));
2152  park();
2153  }
2154  }
2155 }
2156 
2157 QString Mount::meridianFlipStatusString(MeridianFlipStatus status)
2158 {
2159  switch (status)
2160  {
2161  case FLIP_NONE:
2162  return "FLIP_NONE";
2163  case FLIP_PLANNED:
2164  return "FLIP_PLANNED";
2165  case FLIP_WAITING:
2166  return "FLIP_WAITING";
2167  case FLIP_ACCEPTED:
2168  return "FLIP_ACCEPTED";
2169  case FLIP_RUNNING:
2170  return "FLIP_RUNNING";
2171  case FLIP_COMPLETED:
2172  return "FLIP_COMPLETED";
2173  case FLIP_ERROR:
2174  return "FLIP_ERROR";
2175  case FLIP_INACTIVE:
2176  return "FLIP_INACTIVE";
2177  }
2178  return "not possible";
2179 }
2180 
2181 QString Mount::pierSideStateString()
2182 {
2183  switch (m_Mount->pierSide())
2184  {
2185  case ISD::Mount::PierSide::PIER_EAST:
2186  return "Pier Side: East (pointing West)";
2187  case ISD::Mount::PierSide::PIER_WEST:
2188  return "Pier Side: West (pointing East)";
2189  default:
2190  return "Pier Side: Unknown";
2191  }
2192 }
2193 
2194 void Mount::syncAxisReversed(INDI_EQ_AXIS axis, bool reversed)
2195 {
2196  if (axis == AXIS_RA)
2197  m_leftRightCheck->setProperty("checked", reversed);
2198  else
2199  m_upDownCheck->setProperty("checked", reversed);
2200 }
2201 }
Q_INVOKABLE void setTrackEnabled(bool enabled)
DBUS interface function.
Definition: mount.cpp:1990
void append(const T &value)
void stopTimers()
stopTimers Need to stop update timers when profile is disconnected but due to timing and race conditi...
Definition: mount.cpp:2068
QQmlContext * rootContext() const const
const dms & alt() const
Definition: skypoint.h:281
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
Q_SCRIPTABLE SkyPoint currentTarget()
DBUS interface function.
Definition: mount.cpp:1608
QAction * action(const QString &name) const
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition: skypoint.h:194
void save()
save Save telescope focal length and aperture in the INDI telescope driver configuration.
Definition: mount.cpp:1006
void setEnabled(bool)
void newCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
Update event with the current telescope position.
bool isActive() const const
Q_SCRIPTABLE void setHourAngleLimitEnabled(bool enable)
DBUS interface function.
Definition: mount.cpp:1174
double hourAngle
Mount::hourAngle.
Definition: mount.h:46
Q_SCRIPTABLE Q_NOREPLY void setAltitudeLimits(QList< double > limits)
DBUS interface function.
Definition: mount.cpp:1148
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void show()
QString number(int n, int base)
void stopTimers()
stopTimers Stop timers to prevent timing race condition when device is unavailable and timer is still...
Definition: indimount.cpp:1506
void disableHaLimits()
disableAltLimits calls enableHourAngleLimits(false).
Definition: mount.cpp:1131
const KStarsDateTime & lt() const
Definition: kstarsdata.h:150
void updateSwitch(ISwitchVectorProperty *svp)
updateSwitch Update switch properties under watch in the mount module
Definition: mount.cpp:949
QTime fromString(const QString &string, Qt::DateFormat format)
Q_INVOKABLE Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition: mount.cpp:1667
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:70
Q_INVOKABLE Q_SCRIPTABLE bool syncTarget(const QString &target)
DBUS interface function.
Definition: mount.cpp:1207
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
bool addMount(ISD::Mount *device)
addMount Add a new Mount device
Definition: mount.cpp:267
void setDec0(dms d)
Sets Dec0, the catalog Declination.
Definition: skypoint.h:119
void newTarget(SkyPoint &currentCoords)
The mount has finished the slew to a new target.
void clicked(bool checked)
bool isVisible() const const
Q_INVOKABLE Q_SCRIPTABLE bool resetModel()
DBUS interface function.
Definition: mount.cpp:1960
CachingDms * lst()
Definition: kstarsdata.h:223
static QString meridianFlipStatusString(MeridianFlipStatus status)
meridianFlipStatusString
Definition: mount.cpp:2157
virtual QString name(void) const
Definition: skyobject.h:145
INDI::PropertyView< ISwitch > * getSwitch(const QString &name) const
void enableHourAngleLimits(bool enable)
enableHourAngleLimits Enable or disable hour angle limits
Definition: mount.cpp:1116
QTime addMSecs(int ms) const const
void hide()
QDateTime currentDateTimeUtc()
void setContextProperty(const QString &name, QObject *value)
void updateTelescopeCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
updateTelescopeCoords is triggered by the ISD::Mount::newCoord() event and updates the displayed coor...
Definition: mount.cpp:599
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
void newCoords(const SkyPoint &position, const PierSide pierside, const dms &ha)
Update event with the current telescope position.
Q_SCRIPTABLE void setAltitudeLimitsEnabled(bool enable)
DBUS interface function.
Definition: mount.cpp:1154
void paaStageChanged(int stage)
React upon status changes of the polar alignment - mainly to avoid meridian flips happening during po...
Definition: mount.cpp:882
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition: skypoint.h:94
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
Definition: skypoint.cpp:77
void updateLog(int messageID)
updateLog Update mount module log to include any messages arriving for the telescope driver
Definition: mount.cpp:970
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setFlags(Qt::WindowFlags flags)
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:370
void valueChanged(double d)
KStarsDateTime addSecs(double s) const
dms altRefracted() const
Definition: skypoint.cpp:1050
void toggled(bool checked)
void setColor(const QColor &color)
virtual SkyObject * clone() const
Create copy of object.
Definition: skyobject.cpp:50
QQuickItem * rootObject() const const
static KStars * Instance()
Definition: kstars.h:125
Q_SCRIPTABLE Q_NOREPLY void setHourAngleLimit(double limit)
DBUS interface function.
Definition: mount.cpp:1169
virtual void setH(const double &x)
Sets floating-point value of angle, in hours.
Definition: dms.h:210
void start(int msec)
virtual void updateCoords(const KSNumbers *num, bool includePlanets=true, const CachingDms *lat=nullptr, const CachingDms *LST=nullptr, bool forceRecompute=false)
Determine the current coordinates (RA, Dec) from the catalog coordinates (RA0, Dec0),...
Definition: skypoint.cpp:582
SkyObject * findByName(const QString &name, bool exact=true) override
Search the children of this SkyMapComposite for a SkyObject whose name matches the argument.
QString i18n(const char *text, const TYPE &arg...)
QDBusConnection sessionBus()
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
const CachingDms & dec() const
Definition: skypoint.h:269
const CachingDms * lat() const
Definition: geolocation.h:70
void updateNumber(INumberVectorProperty *nvp)
updateNumber Update number properties under watch in the mount module
Definition: mount.cpp:814
void enableAltitudeLimits(bool enable)
enableAltitudeLimits Enable or disable altitude limits
Definition: mount.cpp:1074
void setTitle(const QString &)
Q_SCRIPTABLE void setAutoParkEnabled(bool enable)
setAutoParkEnabled Toggle Auto Park
Definition: mount.cpp:2040
void timeout()
GeoLocation * geo()
Definition: kstarsdata.h:229
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
void newTarget(SkyPoint &currentCoord)
The mount has finished the slew to a new target.
Q_INVOKABLE Q_SCRIPTABLE bool park()
DBUS interface function.
Definition: mount.cpp:1756
void newStatus(ISD::Mount::Status status)
Change in the mount status.
Q_INVOKABLE SimClock * clock()
Definition: kstarsdata.h:217
Q_SCRIPTABLE double initialHA()
DBUS interface function.
Definition: mount.h:284
KGuiItem yes()
UniqueConnection
void motionCommand(int command, int NS, int WE)
move Issues motion command to the mount to move in a particular direction based the request NS and WE...
Definition: mount.cpp:984
virtual void updateCoordsNow(const KSNumbers *num)
updateCoordsNow Shortcut for updateCoords( const KSNumbers *num, false, nullptr, nullptr,...
Definition: skypoint.h:382
virtual bool setFromString(const QString &s, bool isDeg=true)
Attempt to parse the string argument as a dms value, and set the dms object accordingly.
Definition: dms.cpp:48
GeoCoordinates geo(const QVariant &location)
void setupUi(QWidget *widget)
static void beep(const QString &reason=QString(), QWidget *widget=nullptr)
void syncAxisReversed(INDI_EQ_AXIS axis, bool reversed)
syncAxisReversed Update Mount Control GUI on the reverse motion toggled state.
Definition: mount.cpp:2194
void insert(int i, const T &value)
bool toBool() const const
T findChild(const QString &name, Qt::FindChildOptions options) const const
void setSource(const QUrl &url)
SkyMapComposite * skyComposite()
Definition: kstarsdata.h:165
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
bool setProperty(const char *name, const QVariant &value)
Q_SCRIPTABLE void setAutoParkDailyEnabled(bool enabled)
setAutoParkDailyEnabled toggles everyday Auto Park
Definition: mount.cpp:2048
void setAz(dms az)
Sets Az, the Azimuth.
Definition: skypoint.h:230
virtual KActionCollection * actionCollection() const
const CachingDms & ra() const
Definition: skypoint.h:263
void editingFinished()
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
Definition: skypoint.cpp:700
const CachingDms & dec0() const
Definition: skypoint.h:257
void stop()
bool checkMeridianFlip(dms lst)
Check if a meridian flip if necessary.
Definition: mount.cpp:1311
const double & Degrees() const
Definition: dms.h:141
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
Q_INVOKABLE Q_SCRIPTABLE bool slew(double RA, double DEC)
DBUS interface function.
Definition: mount.cpp:1267
const CachingDms & ra0() const
Definition: skypoint.h:251
void setDec(dms d)
Sets Dec, the current Declination.
Definition: skypoint.h:169
void setMeridianFlipValues(bool activate, double hours)
set meridian flip activation and hours
Definition: mount.cpp:870
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString toString(Qt::DateFormat format) const const
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
Definition: skypoint.cpp:143
Q_INVOKABLE Q_SCRIPTABLE bool sync(double RA, double DEC)
DBUS interface function.
Definition: mount.cpp:1659
void setRA(dms &r)
Sets RA, the current Right Ascension.
Definition: skypoint.h:144
KStarsData * data() const
Definition: kstars.h:137
void setResizeMode(QQuickView::ResizeMode)
INDI::PropertyView< IText > * getText(const QString &name) const
void setChecked(bool)
void setInterval(int msec)
void enableAltLimits()
enableAltLimits calls enableAltitudeLimits(true).
Definition: mount.cpp:1102
void disableAltLimits()
disableAltLimits calls enableAltitudeLimits(false).
Definition: mount.cpp:1109
void setContextObject(QObject *object)
void doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
Send a guide pulse to the telescope.
Definition: mount.cpp:1000
ButtonCode questionYesNo(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
void enableHaLimits()
enableHaLimits calls enableHourAngleLimits(true).
Definition: mount.cpp:1124
void updateText(ITextVectorProperty *tvp)
updateText Update text properties under watch in the mount module
Definition: mount.cpp:575
Q_INVOKABLE Q_SCRIPTABLE bool unpark()
DBUS interface function.
Definition: mount.cpp:1764
const KStarsDateTime & utc() const
Definition: simclock.h:37
Q_SCRIPTABLE void setAutoParkStartup(QTime startup)
setAutoParkStartup Set time when automatic parking is activated.
Definition: mount.cpp:2053
void activated(int index)
Information about an object in the sky.
Definition: skyobject.h:41
void registerNewModule(const QString &name)
registerNewModule Register an Ekos module as it arrives via DBus and create the appropriate DBus inte...
Definition: mount.cpp:564
QString message
double Hours() const
Definition: dms.h:168
Q_SCRIPTABLE Q_NOREPLY void setTelescopeInfo(const QList< double > &info)
DBUS interface function.
Definition: mount.cpp:1731
void saveLimits()
saveLimits Saves altitude limit to the user options and updates the INDI telescope driver limits
Definition: mount.cpp:1065
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
Definition: dms.cpp:421
TextDate
SkyPoint catalogueCoord(long double jdf)
Computes the J2000.0 catalogue coordinates for this SkyPoint using the epoch removing aberration,...
Definition: skypoint.cpp:710
void syncTelescopeInfo()
syncTelescopeInfo Update telescope information to reflect any property changes
Definition: mount.cpp:445
bool addGPS(ISD::GPS *device)
addGPS Add a new GPS device
Definition: mount.cpp:338
QVariant property(const char *name) const const
Q_INVOKABLE Q_SCRIPTABLE bool gotoTarget(const QString &target)
DBUS interface function.
Definition: mount.cpp:1189
void accepted()
const dms & az() const
Definition: skypoint.h:275
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.