Kstars

mount.cpp
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 <kio_version.h>
18
19#include "Options.h"
20
21#include "ksmessagebox.h"
22#include "indi/driverinfo.h"
23#include "indi/indicommon.h"
24#include "indi/clientmanager.h"
25#include "indi/indigps.h"
26
27
28#include "mountadaptor.h"
29#include "mountcontrolpanel.h"
30
31#include "ekos/manager.h"
32#include "ekos/auxiliary/opticaltrainmanager.h"
33#include "ekos/auxiliary/profilesettings.h"
34#include "ekos/auxiliary/opticaltrainsettings.h"
35#include "ekos/manager/meridianflipstate.h"
36#include "ekos/align/polaralignmentassistant.h"
37
38#include "kstars.h"
39#include "skymapcomposite.h"
40#include "dialogs/finddialog.h"
41#include "kstarsdata.h"
42
43#include <basedevice.h>
44
45#include <ekos_mount_debug.h>
46
47extern const char *libindi_strings_context;
48
49#define ABORT_DISPATCH_LIMIT 3
50
51namespace Ekos
52{
53
54Mount::Mount()
55{
56 setupUi(this);
57
58 new MountAdaptor(this);
59 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Mount", this);
60 // Set up DBus interfaces
61 QPointer<QDBusInterface> ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos",
64
65 // Connecting DBus signals
66 QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this,
68
69 m_Mount = nullptr;
70
71 // initialize the state machine
72 mf_state.reset(new MeridianFlipState());
73 // connect to the MF state maichine
74 getMeridianFlipState()->connectMount(this);
75
76 // set the status message in the mount tab and write it to the log
77 connect(mf_state.get(), &MeridianFlipState::newMeridianFlipMountStatusText, [&](const QString & text)
78 {
79 meridianFlipStatusWidget->setStatus(text);
80 if (mf_state->getMeridianFlipMountState() != MeridianFlipState::MOUNT_FLIP_NONE &&
81 mf_state->getMeridianFlipMountState() != MeridianFlipState::MOUNT_FLIP_PLANNED)
82 appendLogText(text);
83 });
84 connect(mountToolBoxB, &QPushButton::clicked, this, &Mount::toggleMountToolBox);
85
87
89 {
90 if (m_Mount)
91 m_Mount->clearParking();
92
93 });
94
96 {
97 if (m_Mount)
98 {
99 if (KMessageBox::questionYesNo(KStars::Instance(),
100 i18n("Are you sure you want to clear all mount configurations?"),
101 i18n("Mount Configuration"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
102 "purge_mount_settings_dialog") == KMessageBox::Yes)
103 {
104 resetModel();
105 m_Mount->clearParking();
106 m_Mount->setConfig(PURGE_CONFIG);
107 }
108 }
109 });
110
111 // If time source changes, sync time source
112 connect(Options::self(), &Options::timeSourceChanged, this, &Mount::syncTimeSource);
113 connect(Options::self(), &Options::locationSourceChanged, this, &Mount::syncLocationSource);
114
115 connect(enableAltitudeLimits, &QCheckBox::toggled, this, [this](bool toggled)
116 {
117 m_AltitudeLimitEnabled = toggled;
118 setAltitudeLimits(toggled);
119
120 });
121 m_AltitudeLimitEnabled = enableAltitudeLimits->isChecked();
123
124 // meridian flip
125 connect(mf_state.get(), &MeridianFlipState::newMeridianFlipMountStatusText, meridianFlipStatusWidget,
126 &MeridianFlipStatusWidget::setStatus);
127
128 connect(&autoParkTimer, &QTimer::timeout, this, &Mount::startAutoPark);
129 connect(startTimerB, &QPushButton::clicked, this, &Mount::startParkTimer);
130 connect(stopTimerB, &QPushButton::clicked, this, &Mount::stopParkTimer);
131
132 // Setup Debounce timer to limit over-activation of settings changes
133 m_DebounceTimer.setInterval(500);
134 m_DebounceTimer.setSingleShot(true);
135 connect(&m_DebounceTimer, &QTimer::timeout, this, &Mount::settleSettings);
136
137 stopTimerB->setEnabled(false);
138
139 if (parkEveryDay->isChecked())
140 startTimerB->animateClick();
141
142 m_ControlPanel.reset(new MountControlPanel(KStars::Instance()));
143 connect(m_ControlPanel.get(), &MountControlPanel::newMotionCommand, this, &Mount::motionCommand);
144 connect(m_ControlPanel.get(), &MountControlPanel::aborted, this, &Mount::abort);
145 connect(m_ControlPanel.get(), &MountControlPanel::newSlewRate, this, &Mount::setSlewRate);
146 connect(m_ControlPanel.get(), &MountControlPanel::slew, this, &Mount::slew);
147 connect(m_ControlPanel.get(), &MountControlPanel::sync, this, &Mount::sync);
148 connect(m_ControlPanel.get(), &MountControlPanel::park, this, &Mount::park);
149 connect(m_ControlPanel.get(), &MountControlPanel::unpark, this, &Mount::unpark);
150 connect(m_ControlPanel.get(), &MountControlPanel::updownReversed, this, &Mount::setUpDownReversed);
151 connect(m_ControlPanel.get(), &MountControlPanel::leftrightReversed, this, &Mount::setLeftRightReversed);
152 connect(m_ControlPanel.get(), &MountControlPanel::center, this, &Mount::centerMount);
153
154 connect(mountMotion, &MountMotionWidget::newMotionCommand, this, &Mount::motionCommand);
155 connect(mountMotion, &MountMotionWidget::newSlewRate, this, &Mount::setSlewRate);
156 connect(mountMotion, &MountMotionWidget::aborted, this, &Mount::abort);
157 connect(mountMotion, &MountMotionWidget::updownReversed, this, &Mount::setUpDownReversed);
158 connect(mountMotion, &MountMotionWidget::leftrightReversed, this, &Mount::setLeftRightReversed);
159
160 // forward J2000 selection to the target widget, which does not have its own selection
161 connect(mountPosition, &MountPositionWidget::J2000Enabled, mountTarget, &MountTargetWidget::setJ2000Enabled);
162
163 // forward target commands
164 connect(mountTarget, &MountTargetWidget::slew, this, &Mount::slew);
165 connect(mountTarget, &MountTargetWidget::sync, this, &Mount::sync);
166
167 //Note: This is to prevent a button from being called the default button
168 //and then executing when the user hits the enter key such as when on a Text Box
170 for (auto &button : qButtons)
171 button->setAutoDefault(false);
172
173 loadGlobalSettings();
174 connectSettings();
175
176 setupOpticalTrainManager();
177}
178
179Mount::~Mount()
180{
181 autoParkTimer.stop();
182}
183
184void Mount::setupParkUI()
185{
186 if (m_Mount == nullptr)
187 return;
188
189 if (m_Mount->canPark())
190 {
191 switch(m_Mount->parkStatus())
192 {
193 case ISD::PARK_PARKED:
194 parkingLabel->setText("Parked");
195 break;
196 case ISD::PARK_PARKING:
197 parkingLabel->setText("Parking");
198 break;
199 case ISD::PARK_UNPARKING:
200 parkingLabel->setText("Unparking");
201 break;
202 case ISD::PARK_UNPARKED:
203 parkingLabel->setText("Unparked");
204 break;
205 case ISD::PARK_ERROR:
206 parkingLabel->setText("Park Error");
207 break;
208 case ISD::PARK_UNKNOWN:
209 parkingLabel->setText("Park Status Unknown");
210 break;
211 }
212 parkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_UNPARKED);
213 unparkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_PARKED);
214 }
215 else
216 {
217 parkB->setEnabled(false);
218 unparkB->setEnabled(false);
219 parkingLabel->setText("");
220 }
221}
222
224{
225 if (device && device == m_Mount)
226 {
228 return false;
229 }
230
231 if (m_Mount)
232 m_Mount->disconnect(m_Mount, nullptr, this, nullptr);
233
234 m_Mount = device;
235
236 if (m_Mount)
237 {
238 connect(m_Mount, &ISD::ConcreteDevice::Connected, this, [this]()
239 {
240 setEnabled(true);
241 });
242 connect(m_Mount, &ISD::ConcreteDevice::Disconnected, this, [this]()
243 {
244 setEnabled(false);
245 opticalTrainCombo->setEnabled(true);
246 trainLabel->setEnabled(true);
247 });
248 }
249 else
250 return false;
251
252 mainLayout->setEnabled(true);
253
254 // forward the new mount to the meridian flip state machine
255 mf_state->setMountConnected(device != nullptr);
256
257 connect(m_Mount, &ISD::Mount::propertyUpdated, this, &Mount::updateProperty);
262 connect(m_Mount, &ISD::Mount::slewRateChanged, this, &Mount::slewRateChanged);
263 connect(m_Mount, &ISD::Mount::pierSideChanged, this, &Mount::pierSideChanged);
264 connect(m_Mount, &ISD::Mount::axisReversed, this, &Mount::syncAxisReversed);
265 connect(m_Mount, &ISD::Mount::Disconnected, this, [this]()
266 {
267 m_ControlPanel->hide();
268 });
269 connect(m_Mount, &ISD::Mount::newParkStatus, this, [&](ISD::ParkStatus status)
270 {
271 m_ParkStatus = status;
272 emit newParkStatus(status);
273
274 setupParkUI();
275
276 // If mount is unparked AND every day auto-paro check is ON
277 // AND auto park timer is not yet started, we try to initiate it.
278 if (status == ISD::PARK_UNPARKED && parkEveryDay->isChecked() && autoParkTimer.isActive() == false)
279 startTimerB->animateClick();
280 });
281
282 // If mount is ready then let's set it up.
283 if (m_Mount->isReady())
284 {
285 if (enableAltitudeLimits->isChecked())
286 m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
287 else
288 m_Mount->setAltLimits(-91, +91);
289
291
292 // Send initial status
293 m_Status = m_Mount->status();
294 emit newStatus(m_Status);
295
296 m_ParkStatus = m_Mount->parkStatus();
297 emit newParkStatus(m_ParkStatus);
298 emit ready();
299 }
300 // Otherwise, let's wait for mount to be ready
301 else
302 {
303 connect(m_Mount, &ISD::Mount::ready, this, [this]()
304 {
305 if (enableAltitudeLimits->isChecked())
306 m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
307 else
308 m_Mount->setAltLimits(-91, +91);
309
311
312 // Send initial status
313 m_Status = m_Mount->status();
314 emit newStatus(m_Status);
315
316 m_ParkStatus = m_Mount->parkStatus();
317 emit newParkStatus(m_ParkStatus);
318 emit ready();
319 });
320 }
321
322 return true;
323}
324
326{
327 // No duplicates
328 for (auto &oneSource : m_TimeSources)
329 {
330 if (oneSource->getDeviceName() == device->getDeviceName())
331 return false;
332 }
333
334 m_TimeSources.append(device);
335
336 timeSource->blockSignals(true);
337 timeSource->clear();
338 timeSource->addItem("KStars");
339
340 m_TimeSourcesList.clear();
341 m_TimeSourcesList.append("KStars");
342
343 for (auto &oneSource : m_TimeSources)
344 {
345 auto name = oneSource->getDeviceName();
346
347 m_TimeSourcesList.append(name);
348 timeSource->addItem(name);
349
350 if (name == Options::timeSource())
351 {
352 // If GPS, then refresh
353 auto refreshGPS = oneSource->getProperty("GPS_REFRESH");
354 if (refreshGPS)
355 {
356 auto sw = refreshGPS.getSwitch();
357 sw->at(0)->setState(ISS_ON);
358 oneSource->sendNewProperty(refreshGPS);
359 }
360 }
361 }
362
363 timeSource->setCurrentText(Options::timeSource());
364 timeSource->blockSignals(false);
365 return true;
366}
367
369{
370 // No duplicates
371 for (auto &oneSource : m_LocationSources)
372 {
373 if (oneSource->getDeviceName() == device->getDeviceName())
374 return false;
375 }
376
377 m_LocationSources.append(device);
378 locationSource->blockSignals(true);
379 locationSource->clear();
380 locationSource->addItem("KStars");
381
382 m_LocationSourcesList.clear();
383 m_LocationSourcesList.append("KStars");
384
385 for (auto &oneSource : m_LocationSources)
386 {
387 auto name = oneSource->getDeviceName();
388 locationSource->addItem(name);
389 m_LocationSourcesList.append(name);
390
391 if (name == Options::locationSource())
392 {
393 auto refreshGPS = oneSource->getProperty("GPS_REFRESH");
394 if (refreshGPS)
395 {
396 auto sw = refreshGPS.getSwitch();
397 sw->at(0)->setState(ISS_ON);
398 oneSource->sendNewProperty(refreshGPS);
399 }
400 }
401 }
402
403 locationSource->setCurrentText(Options::locationSource());
404 locationSource->blockSignals(false);
405 return true;
406}
407
408void Mount::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
409{
410 if (m_Mount && m_Mount->getDeviceName() == device->getDeviceName())
411 {
412 m_Mount->disconnect(this);
413 m_ControlPanel->hide();
414 qCDebug(KSTARS_EKOS_MOUNT) << "Removing mount driver" << m_Mount->getDeviceName();
415 m_Mount = nullptr;
416 }
417
418 m_TimeSources.erase(std::remove_if(m_TimeSources.begin(), m_TimeSources.end(), [device](const auto & oneSource)
419 {
420 return device->getDeviceName() == oneSource->getDeviceName();
421 }), m_TimeSources.end());
422
423 m_LocationSources.erase(std::remove_if(m_LocationSources.begin(), m_LocationSources.end(), [device](const auto & oneSource)
424 {
425 return device->getDeviceName() == oneSource->getDeviceName();
426 }), m_LocationSources.end());
427}
428
430{
431 if (!m_Mount || m_Mount->isConnected() == false)
432 return;
433
434 auto svp = m_Mount->getSwitch("TELESCOPE_SLEW_RATE");
435 // sync speed info on the UI and tht separate mount control
436 mountMotion->syncSpeedInfo(svp);
437 m_ControlPanel->mountMotion->syncSpeedInfo(svp);
438
439 if (m_Mount->canPark())
440 {
441 connect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::park, Qt::UniqueConnection);
442 connect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::unpark, Qt::UniqueConnection);
443
444 m_ControlPanel->parkButtonObject->setEnabled(!m_Mount->isParked());
445 m_ControlPanel->unparkButtonObject->setEnabled(m_Mount->isParked());
446 }
447 else
448 {
449 disconnect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::park);
450 disconnect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::unpark);
451
452 m_ControlPanel->parkButtonObject->setEnabled(false);
453 m_ControlPanel->unparkButtonObject->setEnabled(false);
454 }
455
456 m_ControlPanel->setProperty("isJ2000", m_Mount->isJ2000());
457 setupParkUI();
458
459 // Tracking State
460 svp = m_Mount->getSwitch("TELESCOPE_TRACK_STATE");
461 if (svp)
462 {
463 trackingLabel->setEnabled(true);
464 trackOnB->setEnabled(true);
465 trackOffB->setEnabled(true);
466 trackOnB->disconnect();
467 trackOffB->disconnect();
469 {
470 m_Mount->setTrackEnabled(true);
471 });
473 {
474#if KIO_VERSION >= QT_VERSION_CHECK(5, 100, 0)
476 i18n("Are you sure you want to turn off mount tracking?"),
477 i18n("Mount Tracking"),
478 KGuiItem(i18nc("@action:button", "Yes")),
479 KGuiItem(i18nc("@action:button", "No")),
480 "turn_off_mount_tracking_dialog") == KMessageBox::ButtonCode::PrimaryAction)
481 m_Mount->setTrackEnabled(false);
482#else
483 if (KMessageBox::questionYesNo(KStars::Instance(),
484 i18n("Are you sure you want to turn off mount tracking?"),
485 i18n("Mount Tracking"),
486 KStandardGuiItem::yes(),
487 KStandardGuiItem::no(),
488 "turn_off_mount_tracking_dialog") == KMessageBox::Yes)
489 m_Mount->setTrackEnabled(false);
490#endif
491 });
492 }
493 else
494 {
495 trackingLabel->setEnabled(false);
496 trackOnB->setChecked(false);
497 trackOnB->setEnabled(false);
498 trackOffB->setChecked(false);
499 trackOffB->setEnabled(false);
500 }
501
502 m_ControlPanel->mountMotion->leftRightCheckObject->setProperty("checked", m_Mount->isReversed(AXIS_RA));
503 m_ControlPanel->mountMotion->upDownCheckObject->setProperty("checked", m_Mount->isReversed(AXIS_DE));
504}
505
507{
508 if (name == "Capture")
509 {
510 hasCaptureInterface = true;
511 mf_state->setHasCaptureInterface(true);
512 }
513}
514
515void Mount::updateTelescopeCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
516{
517 if (m_Mount == nullptr || !m_Mount->isConnected())
518 return;
519
520 telescopeCoord = position;
521
522 // forward the position to the position widget and control panel
523 mountPosition->updateTelescopeCoords(position, ha);
524 m_ControlPanel->mountPosition->updateTelescopeCoords(position, ha);
525
526 // No need to update coords if we are still parked.
527 if (m_Status == ISD::Mount::MOUNT_PARKED && m_Status == m_Mount->status())
528 return;
529
530 double currentAlt = telescopeCoord.altRefracted().Degrees();
531
532 if (minimumAltLimit->isEnabled() && (currentAlt < minimumAltLimit->value() || currentAlt > maximumAltLimit->value()))
533 {
535 {
536 // Only stop if current altitude is less than last altitude indicate worse situation
537 if (currentAlt < m_LastAltitude &&
538 (m_AbortAltDispatch == -1 ||
539 (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
540 {
541 appendLogText(i18n("Telescope altitude is below minimum altitude limit of %1. Aborting motion...",
542 QString::number(minimumAltLimit->value(), 'g', 3)));
543 m_Mount->abort();
544 m_Mount->setTrackEnabled(false);
545 //KNotification::event( QLatin1String( "OperationFailed" ));
547 m_AbortAltDispatch++;
548 }
549 }
550 else
551 {
552 // Only stop if current altitude is higher than last altitude indicate worse situation
553 if (currentAlt > m_LastAltitude &&
554 (m_AbortAltDispatch == -1 ||
555 (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
556 {
557 appendLogText(i18n("Telescope altitude is above maximum altitude limit of %1. Aborting motion...",
558 QString::number(maximumAltLimit->value(), 'g', 3)));
559 m_Mount->abort();
560 m_Mount->setTrackEnabled(false);
561 //KNotification::event( QLatin1String( "OperationFailed" ));
563 m_AbortAltDispatch++;
564 }
565 }
566 }
567 else
568 m_AbortAltDispatch = -1;
569
570 //qCDebug(KSTARS_EKOS_MOUNT) << "MaximumHaLimit " << MaximumHaLimit->isEnabled() << " value " << MaximumHaLimit->value();
571
572 double haHours = rangeHA(ha.Hours());
573 // handle Ha limit:
574 // Telescope must report Pier Side
575 // MaximumHaLimit must be enabled
576 // for PierSide West -> East if Ha > MaximumHaLimit stop tracking
577 // for PierSide East -> West if Ha > MaximumHaLimit - 12 stop Tracking
578 if (maximumHaLimit->isEnabled())
579 {
580 // get hour angle limit
581 double haLimit = maximumHaLimit->value();
582 bool haLimitReached = false;
583 switch(pierSide)
584 {
585 case ISD::Mount::PierSide::PIER_WEST:
587 break;
588 case ISD::Mount::PierSide::PIER_EAST:
590 break;
591 default:
592 // can't tell so always false
593 haLimitReached = false;
594 break;
595 }
596
597 qCDebug(KSTARS_EKOS_MOUNT) << "Ha: " << haHours <<
598 " haLimit " << haLimit <<
599 " " << ISD::Mount::pierSideStateString(m_Mount->pierSide()) <<
600 " haLimitReached " << (haLimitReached ? "true" : "false") <<
601 " lastHa " << m_LastHourAngle;
602
603 // compare with last ha to avoid multiple calls
604 if (haLimitReached && (rangeHA(haHours - m_LastHourAngle) >= 0 ) &&
605 (m_AbortHADispatch == -1 ||
606 m_Mount->isInMotion()))
607 {
608 // moved past the limit, so stop
609 appendLogText(i18n("Telescope hour angle is more than the maximum hour angle of %1. Aborting motion...",
610 QString::number(maximumHaLimit->value(), 'g', 3)));
611 m_Mount->abort();
612 m_Mount->setTrackEnabled(false);
613 //KNotification::event( QLatin1String( "OperationFailed" ));
615 m_AbortHADispatch++;
616 // ideally we pause and wait until we have passed the pier flip limit,
617 // then do a pier flip and try to resume
618 // this will need changing to use a target position because the current HA has stopped.
619 }
620 }
621 else
622 m_AbortHADispatch = -1;
623
624 m_LastAltitude = currentAlt;
625 m_LastHourAngle = haHours;
626
627 ISD::Mount::Status currentStatus = m_Mount->status();
628 if (m_Status != currentStatus)
629 {
630 qCDebug(KSTARS_EKOS_MOUNT) << "Mount status changed from " << m_Mount->statusString(m_Status)
631 << " to " << m_Mount->statusString(currentStatus);
632
633 //setScopeStatus(currentStatus);
634
635 m_ControlPanel->statusTextObject->setProperty("text", m_Mount->statusString(currentStatus));
636 m_Status = currentStatus;
637 // forward
638 emit newStatus(m_Status);
639
640 setupParkUI();
641 m_ControlPanel->parkButtonObject->setEnabled(!m_Mount->isParked());
642 m_ControlPanel->unparkButtonObject->setEnabled(m_Mount->isParked());
643
644 QAction *a = KStars::Instance()->actionCollection()->action("telescope_track");
645 if (a != nullptr)
646 a->setChecked(currentStatus == ISD::Mount::MOUNT_TRACKING);
647 }
648
649 bool isTracking = (currentStatus == ISD::Mount::MOUNT_TRACKING);
650 if (trackOnB->isEnabled())
651 {
652 trackOnB->setChecked(isTracking);
653 trackOffB->setChecked(!isTracking);
654 }
655
656 // handle pier side display
657 pierSideLabel->setText(ISD::Mount::pierSideStateString(m_Mount->pierSide()));
658
659 // Auto Park Timer
660 if (autoParkTimer.isActive())
661 {
662 QTime remainingTime(0, 0, 0);
663 remainingTime = remainingTime.addMSecs(autoParkTimer.remainingTime());
664 countdownLabel->setText(remainingTime.toString("hh:mm:ss"));
665 emit autoParkCountdownUpdated(countdownLabel->text());
666 }
667}
668
669void Mount::updateProperty(INDI::Property prop)
670{
671 if (prop.isNameMatch("EQUATORIAL_EOD_COORD") || prop.isNameMatch("EQUATORIAL_COORD"))
672 {
673 auto nvp = prop.getNumber();
674
675 // if the meridian flip state machine is not initialized, return
676 if (getMeridianFlipState().isNull())
677 return;
678
679 switch (getMeridianFlipState()->getMeridianFlipStage())
680 {
681 case MeridianFlipState::MF_INITIATED:
682 if (nvp->s == IPS_BUSY && m_Mount != nullptr && m_Mount->isSlewing())
683 getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_FLIPPING);
684 break;
685
686 default:
687 break;
688 }
689 }
690 else if (prop.isNameMatch("TELESCOPE_SLEW_RATE"))
691 {
692 auto svp = prop.getSwitch();
693 mountMotion->updateSpeedInfo(svp);
694 m_ControlPanel->mountMotion->updateSpeedInfo(svp);
695 }
696}
697
698bool Mount::setSlewRate(int index)
699{
700 if (m_Mount)
701 return m_Mount->setSlewRate(index);
702
703 return false;
704}
705
706void Mount::setUpDownReversed(bool enabled)
707{
708 Options::setUpDownReversed(enabled);
709 if (m_Mount)
710 m_Mount->setReversedEnabled(AXIS_DE, enabled);
711}
712
713void Mount::setLeftRightReversed(bool enabled)
714{
715 Options::setLeftRightReversed(enabled);
716 if (m_Mount)
717 m_Mount->setReversedEnabled(AXIS_RA, enabled);
718}
719
720void Mount::setMeridianFlipValues(bool activate, double degrees)
721{
722 executeMeridianFlip->setChecked(activate);
724}
725
727{
728 // Clear the current target position is necessary due to a bug in some mount drivers
729 // which report a mount slew instead of a mount motion. For these mounts, ending a slew
730 // leads to setting the current target position, which is necessary for meridian flips
731 // Since we want to avoid meridian flips during and after finishing PAA, it needs to
732 // be set to nullptr.
733
734 if (stage != PolarAlignmentAssistant::PAH_IDLE)
735 mf_state->clearTargetPosition();
736
737 switch (stage)
738 {
739 // deactivate the meridian flip when the first capture is taken
740 case PolarAlignmentAssistant::PAH_FIRST_CAPTURE:
741 case PolarAlignmentAssistant::PAH_FIRST_SOLVE:
742 if (mf_state->isEnabled())
743 {
744 appendLogText(i18n("Meridian flip set inactive during polar alignment."));
745 mf_state->setEnabled(false);
746 }
747 break;
748 // activate it when the last rotation is finished or stopped
749 // for safety reasons, we add all stages after the last rotation
750 case PolarAlignmentAssistant::PAH_THIRD_CAPTURE:
751 case PolarAlignmentAssistant::PAH_THIRD_SOLVE:
752 case PolarAlignmentAssistant::PAH_STAR_SELECT:
753 case PolarAlignmentAssistant::PAH_REFRESH:
754 case PolarAlignmentAssistant::PAH_POST_REFRESH:
755 case PolarAlignmentAssistant::PAH_IDLE:
756 if (executeMeridianFlip->isChecked() && mf_state->isEnabled() == false)
757 {
758 appendLogText(i18n("Polar alignment motions finished, meridian flip activated."));
759 mf_state->setEnabled(executeMeridianFlip->isChecked());
760 }
761 break;
762 }
763}
764
765void Mount::appendLogText(const QString &text)
766{
767 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
768 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
769
770 qCInfo(KSTARS_EKOS_MOUNT) << text;
771
772 emit newLog(text);
773}
774
775void Mount::updateLog(int messageID)
776{
777 if (m_Mount == nullptr)
778 return;
779
780 auto message = m_Mount->getMessage(messageID);
781 m_LogText.insert(0, i18nc("Message shown in Ekos Mount module", "%1", message));
782
783 emit newLog(message);
784}
785
786void Mount::clearLog()
787{
788 m_LogText.clear();
789 emit newLog(QString());
790}
791
792void Mount::motionCommand(int command, int NS, int WE)
793{
794 if (m_Mount == nullptr || !m_Mount->isConnected())
795 return;
796
797 if (NS != -1)
798 {
799 m_Mount->MoveNS(static_cast<ISD::Mount::VerticalMotion>(NS),
800 static_cast<ISD::Mount::MotionCommand>(command));
801 }
802
803 if (WE != -1)
804 {
805 m_Mount->MoveWE(static_cast<ISD::Mount::HorizontalMotion>(WE),
806 static_cast<ISD::Mount::MotionCommand>(command));
807 }
808}
809
810
811void Mount::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
812{
813 if (m_Mount == nullptr || !m_Mount->isConnected())
814 return;
815
816 m_Mount->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
817}
818
820{
821 if (m_Mount == nullptr)
822 return;
823
824 m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
825}
826
828{
829 if (enable)
830 {
831 minAltLabel->setEnabled(true);
832 maxAltLabel->setEnabled(true);
833
834 minimumAltLimit->setEnabled(true);
835 maximumAltLimit->setEnabled(true);
836
837 if (m_Mount)
838 m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
839 }
840 else
841 {
842 minAltLabel->setEnabled(false);
843 maxAltLabel->setEnabled(false);
844
845 minimumAltLimit->setEnabled(false);
846 maximumAltLimit->setEnabled(false);
847
848 if (m_Mount)
849 m_Mount->setAltLimits(-91, +91);
850 }
851}
852
853// Used for meridian flip
855{
856 //Only enable if it was already enabled before and the MinimumAltLimit is currently disabled.
857 if (m_AltitudeLimitEnabled && minimumAltLimit->isEnabled() == false)
858 setAltitudeLimits(true);
859}
860
861// Used for meridian flip
863{
864 m_AltitudeLimitEnabled = enableAltitudeLimits->isChecked();
865 setAltitudeLimits(false);
866}
867
869{
870 maxHaLabel->setEnabled(enable);
871 maximumHaLimit->setEnabled(enable);
872}
873
875{
876 //Only enable if it was already enabled before and the minHaLimit is currently disabled.
877 if (m_HourAngleLimitEnabled && maximumHaLimit->isEnabled() == false)
879}
880
882{
883 m_HourAngleLimitEnabled = enableHaLimit->isChecked();
884
886}
887
888QList<double> Mount::altitudeLimits()
889{
891
892 limits.append(minimumAltLimit->value());
893 limits.append(maximumAltLimit->value());
894
895 return limits;
896}
897
899{
900 minimumAltLimit->setValue(limits[0]);
901 maximumAltLimit->setValue(limits[1]);
902}
903
905{
906 enableAltitudeLimits->setChecked(enable);
907}
908
909bool Mount::altitudeLimitsEnabled()
910{
911 return enableAltitudeLimits->isChecked();
912}
913
914double Mount::hourAngleLimit()
915{
916 return maximumHaLimit->value();
917}
918
919void Mount::setHourAngleLimit(double limit)
920{
921 maximumHaLimit->setValue(limit);
922}
923
925{
926 enableHaLimit->setChecked(enable);
927}
928
929bool Mount::hourAngleLimitEnabled()
930{
931 return enableHaLimit->isChecked();
932}
933
934void Mount::setJ2000Enabled(bool enabled)
935{
936 m_ControlPanel->setJ2000Enabled(enabled);
937 mountPosition->setJ2000Enabled(enabled);
938}
939
940bool Mount::gotoTarget(const QString &target)
941{
942 SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
943
944 if (object != nullptr)
945 {
946 object->updateCoordsNow(KStarsData::Instance()->updateNum());
947 return slew(object->ra().Hours(), object->dec().Degrees());
948 }
949
950 return false;
951}
952
953bool Mount::gotoTarget(const SkyPoint &target)
954{
955 return slew(target.ra().Hours(), target.dec().Degrees());
956}
957
959{
960 mountTarget->setTargetName(name);
961 m_ControlPanel->setTargetName(name);
962}
963
964bool Mount::syncTarget(const QString &target)
965{
966 SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
967
968 if (object != nullptr)
969 {
970 object->updateCoordsNow(KStarsData::Instance()->updateNum());
971 return sync(object->ra().Hours(), object->dec().Degrees());
972 }
973
974 return false;
975}
976
977bool Mount::slew(double RA, double DEC)
978{
979 if (m_Mount == nullptr || m_Mount->isConnected() == false)
980 return false;
981
982 // calculate the new target
983 targetPosition = new SkyPoint(RA, DEC);
984 SkyPoint J2000Coord(targetPosition->ra(), targetPosition->dec());
985 J2000Coord.catalogueCoord(KStarsData::Instance()->ut().djd());
986 targetPosition->setRA0(J2000Coord.ra());
987 targetPosition->setDec0(J2000Coord.dec());
988
989 mf_state->setTargetPosition(targetPosition);
990 mf_state->resetMeridianFlip();
991
992 m_ControlPanel->setTargetPosition(new SkyPoint(RA, DEC));
993 mountTarget->setTargetPosition(new SkyPoint(RA, DEC));
994
995 qCDebug(KSTARS_EKOS_MOUNT) << "Slewing to RA=" <<
996 targetPosition->ra().toHMSString() <<
997 "DEC=" << targetPosition->dec().toDMSString();
998 qCDebug(KSTARS_EKOS_MOUNT) << "Initial HA " << initialHA() << ", flipDelayHrs " << mf_state->getFlipDelayHrs() <<
999 "MFStatus " << MeridianFlipState::meridianFlipStatusString(mf_state->getMeridianFlipMountState());
1000
1001 // start the slew
1002 return(m_Mount->Slew(targetPosition));
1003}
1004
1005
1007{
1008 if (targetPosition != nullptr)
1009 return *targetPosition;
1010
1011 qCWarning(KSTARS_EKOS_MOUNT) << "No target position defined!";
1012 // since we need to answer something, we take the current mount position
1013 return telescopeCoord;
1014}
1015
1016bool Mount::sync(double RA, double DEC)
1017{
1018 if (m_Mount == nullptr || m_Mount->isConnected() == false)
1019 return false;
1020
1021 return m_Mount->Sync(RA, DEC);
1022}
1023
1025{
1026 if (m_Mount == nullptr)
1027 return false;
1028
1029 return m_Mount->abort();
1030}
1031
1032IPState Mount::slewStatus()
1033{
1034 if (m_Mount == nullptr)
1035 return IPS_ALERT;
1036
1037 return m_Mount->getState("EQUATORIAL_EOD_COORD");
1038}
1039
1040QList<double> Mount::equatorialCoords()
1041{
1042 double ra {0}, dec {0};
1043 QList<double> coords;
1044
1045 if (m_Mount)
1046 m_Mount->getEqCoords(&ra, &dec);
1047 coords.append(ra);
1048 coords.append(dec);
1049
1050 return coords;
1051}
1052
1053QList<double> Mount::horizontalCoords()
1054{
1055 QList<double> coords;
1056
1057 coords.append(telescopeCoord.az().Degrees());
1058 coords.append(telescopeCoord.alt().Degrees());
1059
1060 return coords;
1061}
1062
1063///
1064/// \brief Mount::hourAngle
1065/// \return returns the current mount hour angle in hours in the range -12 to +12
1066///
1067double Mount::hourAngle()
1068{
1069 dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1070 dms ha(lst.Degrees() - telescopeCoord.ra().Degrees());
1071 return rangeHA(ha.Hours());
1072}
1073
1074bool Mount::canPark()
1075{
1076 if (m_Mount == nullptr)
1077 return false;
1078
1079 return m_Mount->canPark();
1080}
1081
1083{
1084 if (m_Mount == nullptr || m_Mount->canPark() == false)
1085 return false;
1086
1087 return m_Mount->park();
1088}
1089
1091{
1092 if (m_Mount == nullptr || m_Mount->canPark() == false)
1093 return false;
1094
1095 return m_Mount->unpark();
1096}
1097
1098
1099void Mount::toggleMountToolBox()
1100{
1101 if (m_ControlPanel->isVisible())
1102 {
1103 m_ControlPanel->hide();
1104 QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1105 if (a)
1106 a->setChecked(false);
1107 }
1108 else
1109 {
1110 m_ControlPanel->show();
1111 QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1112 if (a)
1113 a->setChecked(true);
1114 }
1115}
1116
1117//++++ converters for target coordinate display in Mount Control box
1118
1119bool Mount::raDecToAzAlt(QString qsRA, QString qsDec)
1120{
1121 return m_ControlPanel->mountTarget->raDecToAzAlt(qsRA, qsDec);
1122}
1123
1124bool Mount::raDecToHaDec(QString qsRA)
1125{
1126 return m_ControlPanel->mountTarget->raDecToHaDec(qsRA);
1127}
1128
1129bool Mount::azAltToRaDec(QString qsAz, QString qsAlt)
1130{
1131 return m_ControlPanel->mountTarget->azAltToRaDec(qsAz, qsAlt);
1132}
1133
1134bool Mount::azAltToHaDec(QString qsAz, QString qsAlt)
1135{
1136 return m_ControlPanel->mountTarget->azAltToHaDec(qsAz, qsAlt);
1137}
1138
1139bool Mount::haDecToRaDec(QString qsHA)
1140{
1141 return m_ControlPanel->mountTarget->haDecToRaDec(qsHA);
1142}
1143
1144bool Mount::haDecToAzAlt(QString qsHA, QString qsDec)
1145{
1146 return m_ControlPanel->mountTarget->haDecToAzAlt(qsHA, qsDec);
1147}
1148
1149//---- end: converters for target coordinate display in Mount Control box
1150
1151void Mount::centerMount()
1152{
1153 if (m_Mount)
1154 m_Mount->find();
1155}
1156
1158{
1159 if (m_Mount == nullptr)
1160 return false;
1161
1162 if (m_Mount->hasAlignmentModel() == false)
1163 return false;
1164
1165 if (m_Mount->clearAlignmentModel())
1166 {
1167 appendLogText(i18n("Alignment Model cleared."));
1168 return true;
1169 }
1170
1171 appendLogText(i18n("Failed to clear Alignment Model."));
1172 return false;
1173}
1174
1175
1176void Mount::setScopeStatus(ISD::Mount::Status status)
1177{
1178 if (m_Status != status)
1179 {
1180 m_ControlPanel->statusTextObject->setProperty("text", m_Mount->statusString(status));
1181 m_Status = status;
1182 // forward
1183 emit newStatus(status);
1184 }
1185}
1186
1187
1188
1189void Mount::setTrackEnabled(bool enabled)
1190{
1191 if (enabled)
1192 trackOnB->click();
1193 else
1194 trackOffB->click();
1195}
1196
1197int Mount::slewRate()
1198{
1199 if (m_Mount == nullptr)
1200 return -1;
1201
1202 return m_Mount->getSlewRate();
1203}
1204
1205//QJsonArray Mount::getScopes() const
1206//{
1207// QJsonArray scopes;
1208// if (currentTelescope == nullptr)
1209// return scopes;
1210
1211// QJsonObject primary =
1212// {
1213// {"name", "Primary"},
1214// {"mount", currentTelescope->getDeviceName()},
1215// {"aperture", primaryScopeApertureIN->value()},
1216// {"focalLength", primaryScopeFocalIN->value()},
1217// };
1218
1219// scopes.append(primary);
1220
1221// QJsonObject guide =
1222// {
1223// {"name", "Guide"},
1224// {"mount", currentTelescope->getDeviceName()},
1225// {"aperture", primaryScopeApertureIN->value()},
1226// {"focalLength", primaryScopeFocalIN->value()},
1227// };
1228
1229// scopes.append(guide);
1230
1231// return scopes;
1232//}
1233
1234bool Mount::autoParkEnabled()
1235{
1236 return autoParkTimer.isActive();
1237}
1238
1240{
1241 if (enable)
1242 startParkTimer();
1243 else
1244 stopParkTimer();
1245}
1246
1248{
1249 parkEveryDay->setChecked(enabled);
1250}
1251
1253{
1254 autoParkTime->setTime(startup);
1255}
1256
1257bool Mount::meridianFlipEnabled()
1258{
1259 return executeMeridianFlip->isChecked();
1260}
1261
1262double Mount::meridianFlipValue()
1263{
1264 return meridianFlipOffsetDegrees->value();
1265}
1266
1268{
1269 autoParkTimer.stop();
1270 if (m_Mount)
1271 m_Mount->stopTimers();
1272}
1273
1274void Mount::startParkTimer()
1275{
1276 if (m_Mount == nullptr || m_ParkStatus == ISD::PARK_UNKNOWN)
1277 return;
1278
1279 if (m_Mount->isParked())
1280 {
1281 appendLogText(i18n("Mount already parked."));
1282 return;
1283 }
1284
1285 auto parkTime = autoParkTime->time();
1286
1287 qCDebug(KSTARS_EKOS_MOUNT) << "Parking time is" << parkTime.toString();
1288 QDateTime currentDateTime = KStarsData::Instance()->lt();
1289 QDateTime parkDateTime(currentDateTime);
1290
1291 parkDateTime.setTime(parkTime);
1292 qint64 parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
1293 qCDebug(KSTARS_EKOS_MOUNT) << "Until parking time:" << parkMilliSeconds << "ms or" << parkMilliSeconds / (60 * 60 * 1000)
1294 << "hours";
1295 if (parkMilliSeconds > 0)
1296 {
1297 qCDebug(KSTARS_EKOS_MOUNT) << "Added a day to parking time...";
1298 parkDateTime = parkDateTime.addDays(1);
1299 parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
1300
1301 int hours = static_cast<int>(parkMilliSeconds / (1000 * 60 * 60));
1302 if (hours > 0)
1303 {
1304 // No need to display warning for every day check
1305 if (parkEveryDay->isChecked() == false)
1306 appendLogText(i18n("Parking time cannot be in the past."));
1307 return;
1308 }
1309 }
1310
1312
1313 if (parkMilliSeconds > 24 * 60 * 60 * 1000)
1314 {
1315 appendLogText(i18n("Parking time must be within 24 hours of current time."));
1316 return;
1317 }
1318
1319 if (parkMilliSeconds > 12 * 60 * 60 * 1000)
1320 appendLogText(i18n("Warning! Parking time is more than 12 hours away."));
1321
1322 appendLogText(i18n("Caution: do not use Auto Park while scheduler is active."));
1323
1324 autoParkTimer.setInterval(static_cast<int>(parkMilliSeconds));
1325 autoParkTimer.start();
1326
1327 startTimerB->setEnabled(false);
1328 stopTimerB->setEnabled(true);
1329}
1330
1331void Mount::stopParkTimer()
1332{
1333 autoParkTimer.stop();
1334 countdownLabel->setText("00:00:00");
1335 emit autoParkCountdownUpdated("00:00:00");
1336 stopTimerB->setEnabled(false);
1337 startTimerB->setEnabled(true);
1338}
1339
1340void Mount::startAutoPark()
1341{
1342 appendLogText(i18n("Parking timer is up."));
1343 autoParkTimer.stop();
1344 startTimerB->setEnabled(true);
1345 stopTimerB->setEnabled(false);
1346 countdownLabel->setText("00:00:00");
1347 emit autoParkCountdownUpdated("00:00:00");
1348 if (m_Mount)
1349 {
1350 if (m_Mount->isParked() == false)
1351 {
1352 appendLogText(i18n("Starting auto park..."));
1353 park();
1354 }
1355 }
1356}
1357
1359{
1360 if (axis == AXIS_RA)
1361 m_ControlPanel->mountMotion->leftRightCheckObject->setProperty("checked", reversed);
1362 else
1363 m_ControlPanel->mountMotion->upDownCheckObject->setProperty("checked", reversed);
1364}
1365
1366void Mount::setupOpticalTrainManager()
1367{
1368 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Mount::refreshOpticalTrain);
1369 connect(trainB, &QPushButton::clicked, this, [this]()
1370 {
1371 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
1372 });
1374 {
1375 ProfileSettings::Instance()->setOneSetting(ProfileSettings::MountOpticalTrain,
1376 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
1377 refreshOpticalTrain();
1378 emit trainChanged();
1379 });
1380}
1381
1382void Mount::refreshOpticalTrain()
1383{
1384 opticalTrainCombo->blockSignals(true);
1385 opticalTrainCombo->clear();
1386 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
1387 trainB->setEnabled(true);
1388
1389 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::MountOpticalTrain);
1390
1391 if (trainID.isValid())
1392 {
1393 auto id = trainID.toUInt();
1394
1395 // If train not found, select the first one available.
1396 if (OpticalTrainManager::Instance()->exists(id) == false)
1397 {
1398 qCWarning(KSTARS_EKOS_MOUNT) << "Optical train doesn't exist for id" << id;
1399 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
1400 }
1401
1402 auto name = OpticalTrainManager::Instance()->name(id);
1403
1404 opticalTrainCombo->setCurrentText(name);
1405
1406 auto mount = OpticalTrainManager::Instance()->getMount(name);
1407 setMount(mount);
1408
1409 auto scope = OpticalTrainManager::Instance()->getScope(name);
1410 opticalTrainCombo->setToolTip(scope["name"].toString());
1411
1412 // Load train settings
1413 OpticalTrainSettings::Instance()->setOpticalTrainID(id);
1414 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Mount);
1415 if (settings.isValid())
1416 {
1417 auto map = settings.toJsonObject().toVariantMap();
1418 if (map != m_Settings)
1419 {
1420 m_Settings.clear();
1421 setAllSettings(map);
1422 }
1423 }
1424 else
1425 m_Settings = m_GlobalSettings;
1426 }
1427
1428 opticalTrainCombo->blockSignals(false);
1429}
1430
1431///////////////////////////////////////////////////////////////////////////////////////////
1432///
1433///////////////////////////////////////////////////////////////////////////////////////////
1434QVariantMap Mount::getAllSettings() const
1435{
1436 QVariantMap settings;
1437
1438 // All Combo Boxes
1439 for (auto &oneWidget : findChildren<QComboBox*>())
1440 settings.insert(oneWidget->objectName(), oneWidget->currentText());
1441
1442 // All Double Spin Boxes
1443 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1444 settings.insert(oneWidget->objectName(), oneWidget->value());
1445
1446 // All Spin Boxes
1447 for (auto &oneWidget : findChildren<QSpinBox*>())
1448 settings.insert(oneWidget->objectName(), oneWidget->value());
1449
1450 // All Checkboxes
1451 for (auto &oneWidget : findChildren<QCheckBox*>())
1452 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
1453
1454 // All Time
1455 for (auto &oneWidget : findChildren<QTimeEdit*>())
1456 settings.insert(oneWidget->objectName(), oneWidget->time().toString());
1457
1458 return settings;
1459}
1460
1461///////////////////////////////////////////////////////////////////////////////////////////
1462///
1463///////////////////////////////////////////////////////////////////////////////////////////
1464void Mount::setAllSettings(const QVariantMap &settings)
1465{
1466 // Disconnect settings that we don't end up calling syncSettings while
1467 // performing the changes.
1468 disconnectSyncSettings();
1469
1470 for (auto &name : settings.keys())
1471 {
1472 // Combo
1473 auto comboBox = findChild<QComboBox*>(name);
1474 if (comboBox)
1475 {
1476 syncControl(settings, name, comboBox);
1477 continue;
1478 }
1479
1480 // Double spinbox
1481 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
1482 if (doubleSpinBox)
1483 {
1484 syncControl(settings, name, doubleSpinBox);
1485 continue;
1486 }
1487
1488 // spinbox
1489 auto spinBox = findChild<QSpinBox*>(name);
1490 if (spinBox)
1491 {
1492 syncControl(settings, name, spinBox);
1493 continue;
1494 }
1495
1496 // checkbox
1497 auto checkbox = findChild<QCheckBox*>(name);
1498 if (checkbox)
1499 {
1500 syncControl(settings, name, checkbox);
1501 continue;
1502 }
1503
1504 // timeEdit
1505 auto timeEdit = findChild<QTimeEdit*>(name);
1506 if (timeEdit)
1507 {
1508 syncControl(settings, name, timeEdit);
1509 continue;
1510 }
1511 }
1512
1513 // Sync to options
1514 for (auto &key : settings.keys())
1515 {
1516 auto value = settings[key];
1517 // Save immediately
1518 Options::self()->setProperty(key.toLatin1(), value);
1519 Options::self()->save();
1520
1521 m_Settings[key] = value;
1522 m_GlobalSettings[key] = value;
1523 }
1524
1525 emit settingsUpdated(getAllSettings());
1526
1527 // Save to optical train specific settings as well
1528 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1529 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Mount, m_Settings);
1530
1531 // Restablish connections
1532 connectSyncSettings();
1533}
1534
1535///////////////////////////////////////////////////////////////////////////////////////////
1536///
1537///////////////////////////////////////////////////////////////////////////////////////////
1538bool Mount::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
1539{
1540 QSpinBox *pSB = nullptr;
1541 QDoubleSpinBox *pDSB = nullptr;
1542 QCheckBox *pCB = nullptr;
1543 QComboBox *pComboBox = nullptr;
1544 QTimeEdit *pTimeEdit = nullptr;
1545 QRadioButton *pRadioButton = nullptr;
1546 bool ok = false;
1547
1548 if ((pSB = qobject_cast<QSpinBox *>(widget)))
1549 {
1550 const int value = settings[key].toInt(&ok);
1551 if (ok)
1552 {
1553 pSB->setValue(value);
1554 return true;
1555 }
1556 }
1557 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
1558 {
1559 const double value = settings[key].toDouble(&ok);
1560 if (ok)
1561 {
1562 pDSB->setValue(value);
1563 return true;
1564 }
1565 }
1566 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
1567 {
1568 const bool value = settings[key].toBool();
1569 if (value != pCB->isChecked())
1570 pCB->click();
1571 return true;
1572 }
1573 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
1574 {
1575 const bool value = settings[key].toBool();
1576 if (value)
1577 pRadioButton->click();
1578 return true;
1579 }
1580 // ONLY FOR STRINGS, not INDEX
1581 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
1582 {
1583 const QString value = settings[key].toString();
1584 pComboBox->setCurrentText(value);
1585 return true;
1586 }
1587 else if ((pTimeEdit = qobject_cast<QTimeEdit *>(widget)))
1588 {
1589 const QString value = settings[key].toString();
1590 pTimeEdit->setTime(QTime::fromString(value));
1591 return true;
1592 }
1593
1594 return false;
1595};
1596
1597///////////////////////////////////////////////////////////////////////////////////////////
1598///
1599///////////////////////////////////////////////////////////////////////////////////////////
1600void Mount::syncSettings()
1601{
1602 QDoubleSpinBox *dsb = nullptr;
1603 QSpinBox *sb = nullptr;
1604 QCheckBox *cb = nullptr;
1605 QComboBox *cbox = nullptr;
1606 QTimeEdit *timeEdit = nullptr;
1607 QRadioButton *rb = nullptr;
1608
1609 QString key;
1610 QVariant value;
1611
1613 {
1614 key = dsb->objectName();
1615 value = dsb->value();
1616
1617 }
1618 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
1619 {
1620 key = sb->objectName();
1621 value = sb->value();
1622 }
1623 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
1624 {
1625 key = cb->objectName();
1626 value = cb->isChecked();
1627 }
1628 else if ( (rb = qobject_cast<QRadioButton*>(sender())))
1629 {
1630 key = rb->objectName();
1631 if (rb->isChecked() == false)
1632 {
1633 m_Settings.remove(key);
1634 return;
1635 }
1636 value = true;
1637 }
1638 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
1639 {
1640 key = cbox->objectName();
1641 value = cbox->currentText();
1642 }
1643 else if ( (timeEdit = qobject_cast<QTimeEdit*>(sender())))
1644 {
1645 key = timeEdit->objectName();
1646 value = timeEdit->time().toString();
1647 }
1648
1649 // Save immediately
1650 Options::self()->setProperty(key.toLatin1(), value);
1651 m_Settings[key] = value;
1652 m_GlobalSettings[key] = value;
1653
1654 m_DebounceTimer.start();
1655}
1656
1657///////////////////////////////////////////////////////////////////////////////////////////
1658///
1659///////////////////////////////////////////////////////////////////////////////////////////
1660void Mount::settleSettings()
1661{
1662 Options::self()->save();
1663 emit settingsUpdated(getAllSettings());
1664 // Save to optical train specific settings as well
1665 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1666 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Mount, m_Settings);
1667}
1668
1669///////////////////////////////////////////////////////////////////////////////////////////
1670///
1671///////////////////////////////////////////////////////////////////////////////////////////
1672void Mount::loadGlobalSettings()
1673{
1674 QString key;
1675 QVariant value;
1676
1677 QVariantMap settings;
1678 // All Combo Boxes
1679 for (auto &oneWidget : findChildren<QComboBox*>())
1680 {
1681 if (oneWidget->objectName() == "opticalTrainCombo")
1682 continue;
1683
1684 key = oneWidget->objectName();
1685 value = Options::self()->property(key.toLatin1());
1686 if (value.isValid() && oneWidget->count() > 0)
1687 {
1688 oneWidget->setCurrentText(value.toString());
1689 settings[key] = value;
1690 }
1691 }
1692
1693 // All Double Spin Boxes
1694 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1695 {
1696 key = oneWidget->objectName();
1697 value = Options::self()->property(key.toLatin1());
1698 if (value.isValid())
1699 {
1700 oneWidget->setValue(value.toDouble());
1701 settings[key] = value;
1702 }
1703 }
1704
1705 // All Spin Boxes
1706 for (auto &oneWidget : findChildren<QSpinBox*>())
1707 {
1708 key = oneWidget->objectName();
1709 value = Options::self()->property(key.toLatin1());
1710 if (value.isValid())
1711 {
1712 oneWidget->setValue(value.toInt());
1713 settings[key] = value;
1714 }
1715 }
1716
1717 // All Checkboxes
1718 for (auto &oneWidget : findChildren<QCheckBox*>())
1719 {
1720 key = oneWidget->objectName();
1721 value = Options::self()->property(key.toLatin1());
1722 if (value.isValid())
1723 {
1724 oneWidget->setChecked(value.toBool());
1725 settings[key] = value;
1726 }
1727 }
1728
1729 // initialize meridian flip state machine values
1730 mf_state->setEnabled(Options::executeMeridianFlip());
1731 mf_state->setOffset(Options::meridianFlipOffsetDegrees());
1732
1733 m_GlobalSettings = m_Settings = settings;
1734}
1735
1736///////////////////////////////////////////////////////////////////////////////////////////
1737///
1738///////////////////////////////////////////////////////////////////////////////////////////
1739void Mount::connectSyncSettings()
1740{
1741 // All Combo Boxes
1742 for (auto &oneWidget : findChildren<QComboBox*>())
1743 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
1744
1745 // All Double Spin Boxes
1746 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1747 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1748
1749 // All Spin Boxes
1750 for (auto &oneWidget : findChildren<QSpinBox*>())
1751 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1752
1753 // All Checkboxes
1754 for (auto &oneWidget : findChildren<QCheckBox*>())
1755 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
1756
1757 // All Radio buttons
1758 for (auto &oneWidget : findChildren<QRadioButton*>())
1759 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
1760
1761 // All QDateTimeEdit
1762 for (auto &oneWidget : findChildren<QDateTimeEdit*>())
1763 connect(oneWidget, &QDateTimeEdit::editingFinished, this, &Ekos::Mount::syncSettings);
1764}
1765
1766///////////////////////////////////////////////////////////////////////////////////////////
1767///
1768///////////////////////////////////////////////////////////////////////////////////////////
1769void Mount::disconnectSyncSettings()
1770{
1771 // All Combo Boxes
1772 for (auto &oneWidget : findChildren<QComboBox*>())
1773 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
1774
1775 // All Double Spin Boxes
1776 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1777 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1778
1779 // All Spin Boxes
1780 for (auto &oneWidget : findChildren<QSpinBox*>())
1781 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1782
1783 // All Checkboxes
1784 for (auto &oneWidget : findChildren<QCheckBox*>())
1785 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
1786
1787 // All Radio buttons
1788 for (auto &oneWidget : findChildren<QRadioButton*>())
1789 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
1790
1791 for (auto &oneWidget : findChildren<QDateTimeEdit*>())
1792 disconnect(oneWidget, &QDateTimeEdit::editingFinished, this, &Ekos::Mount::syncSettings);
1793}
1794
1795///////////////////////////////////////////////////////////////////////////////////////////
1796///
1797///////////////////////////////////////////////////////////////////////////////////////////
1798void Mount::connectSettings()
1799{
1800 connectSyncSettings();
1801
1802 // connections to the meridian flip state machine
1803 connect(executeMeridianFlip, &QCheckBox::toggled, mf_state.get(), &MeridianFlipState::setEnabled);
1805 mf_state.get(), &MeridianFlipState::setOffset);
1806 connect(this, &Mount::newParkStatus, mf_state.get(), &MeridianFlipState::setMountParkStatus);
1807 connect(mf_state.get(), &MeridianFlipState::slewTelescope, [&](SkyPoint pos)
1808 {
1809 if (m_Mount)
1810 m_Mount->Slew(&pos, (m_Mount->canFlip() && Options::forcedFlip()));
1811 });
1812
1813 // Train combo box should NOT be synced.
1814 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
1815}
1816
1817///////////////////////////////////////////////////////////////////////////////////////////
1818///
1819///////////////////////////////////////////////////////////////////////////////////////////
1820void Mount::disconnectSettings()
1821{
1822 disconnectSyncSettings();
1823
1824 // cut connections to the meridian flip state machine
1825 disconnect(executeMeridianFlip, &QCheckBox::toggled, mf_state.get(), &MeridianFlipState::setEnabled);
1827 mf_state.get(), &MeridianFlipState::setOffset);
1828 disconnect(this, &Mount::newParkStatus, mf_state.get(), &MeridianFlipState::setMountParkStatus);
1829 disconnect(mf_state.get(), &MeridianFlipState::slewTelescope, nullptr, nullptr);
1830}
1831
1832///////////////////////////////////////////////////////////////////////////////////////////
1833///
1834///////////////////////////////////////////////////////////////////////////////////////////
1836{
1837 return mf_state->initialPositionHA();
1838}
1839
1840///////////////////////////////////////////////////////////////////////////////////////////
1841///
1842///////////////////////////////////////////////////////////////////////////////////////////
1844{
1845 appendLogText(i18n("Updating master time source to %1.", Options::locationSource()));
1846}
1847
1848///////////////////////////////////////////////////////////////////////////////////////////
1849///
1850///////////////////////////////////////////////////////////////////////////////////////////
1852{
1853 auto name = Options::locationSource();
1854 auto it = std::find_if(m_LocationSources.begin(), m_LocationSources.end(), [name](const auto & oneSource)
1855 {
1856 return oneSource->getDeviceName() == name;
1857 });
1858 if (it != m_LocationSources.end())
1859 {
1860 auto property = (*it)->getProperty("GPS_REFRESH");
1861 if (property)
1862 {
1863 auto sw = property.getSwitch();
1864 sw->at(0)->setState(ISS_ON);
1865 (*it)->sendNewProperty(property);
1866 appendLogText(i18n("Updating master location source to %1. Updating GPS...", name));
1867 }
1868 else
1869 {
1870 property = (*it)->getProperty("GEOGRAPHIC_COORD");
1871 if (property)
1872 {
1873 (*it)->processNumber(property);
1874 appendLogText(i18n("Updating master location source to %1.", name));
1875 }
1876 }
1877 }
1878}
1879}
void newTarget(SkyPoint &currentCoord)
The mount has finished the slew to a new target.
void saveLimits()
saveLimits Saves altitude limit to the user options and updates the INDI telescope driver limits
Definition mount.cpp:819
Q_SCRIPTABLE void setAutoParkDailyEnabled(bool enabled)
setAutoParkDailyEnabled toggles everyday Auto Park
Definition mount.cpp:1247
Q_SCRIPTABLE void setAltitudeLimitsEnabled(bool enable)
DBUS interface function.
Definition mount.cpp:904
void enableHaLimits()
enableHaLimits calls enableHourAngleLimits(true).
Definition mount.cpp:874
Q_INVOKABLE Q_SCRIPTABLE bool park()
DBUS interface function.
Definition mount.cpp:1082
Q_INVOKABLE Q_SCRIPTABLE bool sync(double RA, double DEC)
DBUS interface function.
Definition mount.cpp:1016
bool addLocationSource(const QSharedPointer< ISD::GenericDevice > &device)
addLocationSource Add an INDI driver that can be used for a master location source
Definition mount.cpp:368
void syncLocationSource()
syncLocationSource When location source changes, update all INDI drivers to this location source
Definition mount.cpp:1851
void updateProperty(INDI::Property prop)
updateProperty Update properties under watch in the mount module
Definition mount.cpp:669
void doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
Send a guide pulse to the telescope.
Definition mount.cpp:811
Q_INVOKABLE Q_SCRIPTABLE bool resetModel()
DBUS interface function.
Definition mount.cpp:1157
Q_INVOKABLE Q_SCRIPTABLE bool gotoTarget(const QString &target)
DBUS interface function.
Definition mount.cpp:940
void syncTimeSource()
syncTimeSource When time source changes, update all INDI drivers to this time source
Definition mount.cpp:1843
void setTargetName(const QString &name)
setTargetName Set the name of the current target
Definition mount.cpp:958
Q_INVOKABLE void setTrackEnabled(bool enabled)
DBUS interface function.
Definition mount.cpp:1189
void disableHaLimits()
disableAltLimits calls enableHourAngleLimits(false).
Definition mount.cpp:881
Q_SCRIPTABLE double initialHA()
DBUS interface function.
Definition mount.cpp:1835
Q_SCRIPTABLE void setAutoParkStartup(QTime startup)
setAutoParkStartup Set time when automatic parking is activated.
Definition mount.cpp:1252
void paaStageChanged(int stage)
React upon status changes of the polar alignment - mainly to avoid meridian flips happening during po...
Definition mount.cpp:726
void setMeridianFlipValues(bool activate, double degrees)
set meridian flip activation and hours
Definition mount.cpp:720
void syncTelescopeInfo()
syncTelescopeInfo Update telescope information to reflect any property changes
Definition mount.cpp:429
void syncAxisReversed(INDI_EQ_AXIS axis, bool reversed)
syncAxisReversed Update Mount Control GUI on the reverse motion toggled state.
Definition mount.cpp:1358
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:792
void registerNewModule(const QString &name)
registerNewModule Register an Ekos module as it arrives via DBus and create the appropriate DBus inte...
Definition mount.cpp:506
Q_INVOKABLE Q_SCRIPTABLE bool unpark()
DBUS interface function.
Definition mount.cpp:1090
double hourAngle
Mount::hourAngle.
Definition mount.h:47
bool addTimeSource(const QSharedPointer< ISD::GenericDevice > &device)
addTimeSource Add an INDI driver that can be used for a master time source
Definition mount.cpp:325
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
Q_INVOKABLE Q_SCRIPTABLE bool syncTarget(const QString &target)
DBUS interface function.
Definition mount.cpp:964
void stopTimers()
stopTimers Need to stop update timers when profile is disconnected but due to timing and race conditi...
Definition mount.cpp:1267
Q_SCRIPTABLE Q_NOREPLY void setHourAngleLimit(double limit)
DBUS interface function.
Definition mount.cpp:919
void newStatus(ISD::Mount::Status status)
Change in the mount status.
Q_INVOKABLE Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition mount.cpp:1024
Q_SCRIPTABLE SkyPoint currentTarget()
DBUS interface function.
Definition mount.cpp:1006
void updateLog(int messageID)
updateLog Update mount module log to include any messages arriving for the telescope driver
Definition mount.cpp:775
bool setMount(ISD::Mount *device)
addMount Add a new Mount device
Definition mount.cpp:223
QSharedPointer< MeridianFlipState > getMeridianFlipState() const
getMeridianFlipState
Definition mount.h:138
Q_SCRIPTABLE Q_NOREPLY void setAltitudeLimits(QList< double > limits)
DBUS interface function.
Definition mount.cpp:898
Q_SCRIPTABLE void setHourAngleLimitEnabled(bool enable)
DBUS interface function.
Definition mount.cpp:924
void enableHourAngleLimits(bool enable)
enableHourAngleLimits Enable or disable hour angle limits
Definition mount.cpp:868
void suspendAltLimits()
suspendAltLimits calls enableAltitudeLimits(false).
Definition mount.cpp:862
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:515
Q_INVOKABLE Q_SCRIPTABLE bool slew(double RA, double DEC)
DBUS interface function.
Definition mount.cpp:977
void newCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
Update event with the current telescope position.
void resumeAltLimits()
resumeAltLimits calls enableAltitudeLimits(true).
Definition mount.cpp:854
Q_SCRIPTABLE void setAutoParkEnabled(bool enable)
setAutoParkEnabled Toggle Auto Park
Definition mount.cpp:1239
INDI::PropertyView< ISwitch > * getSwitch(const QString &name) const
device handle controlling Mounts.
Definition indimount.h:27
void stopTimers()
stopTimers Stop timers to prevent timing race condition when device is unavailable and timer is still...
void newTarget(SkyPoint &currentCoords)
The mount has finished the slew to a new target.
void newCoords(const SkyPoint &position, const PierSide pierside, const dms &ha)
Update event with the current telescope position.
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
static void beep(const QString &reason=QString())
static KStars * Instance()
Definition kstars.h:123
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:42
The sky coordinates of a point in the sky.
Definition skypoint.h:45
const CachingDms & dec() const
Definition skypoint.h:269
const CachingDms & ra() const
Definition skypoint.h:263
dms altRefracted() const
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition skypoint.h:94
const dms & az() const
Definition skypoint.h:275
const dms & alt() const
Definition skypoint.h:281
void setDec0(dms d)
Sets Dec0, the catalog Declination.
Definition skypoint.h:119
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
double Hours() const
Definition dms.h:168
const double & Degrees() const
Definition dms.h:141
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:79
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
QString name(StandardAction id)
KGuiItem insert()
bool isChecked() const const
void clicked(bool checked)
void toggled(bool checked)
void setChecked(bool)
void activated(int index)
void currentIndexChanged(int index)
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void valueChanged(double d)
void append(QList< T > &&value)
iterator begin()
void clear()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
iterator insert(const_iterator before, parameter_type value)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QList< T > findChildren(Qt::FindChildOptions options) const const
QVariant property(const char *name) const const
QObject * sender() const const
T * get() const const
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
UniqueConnection
QTextStream & dec(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QTime addMSecs(int ms) const const
QTime fromString(QStringView string, QStringView format)
QString toString(QStringView format) const const
void setInterval(int msec)
bool isActive() const const
void setSingleShot(bool singleShot)
void start()
void stop()
void timeout()
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const
uint toUInt(bool *ok) const const
void setEnabled(bool)
void setupUi(QWidget *widget)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 12:00:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.