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