Kstars

manager.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikartech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "manager.h"
8
9#include "analyze/analyze.h"
10#include "capture/capture.h"
11#include "scheduler/scheduler.h"
12#include "scheduler/schedulerprocess.h"
13#include "scheduler/schedulermodulestate.h"
14#include "focus/focus.h"
15#include "align/align.h"
16#include "guide/guide.h"
17#include "mount/mount.h"
18#include "observatory/observatory.h"
19
20#include "opsekos.h"
21#include "ekosadaptor.h"
22#include "kstars.h"
23#include "kstarsdata.h"
24#include "Options.h"
25#include "ekos/capture/rotatorsettings.h"
26#include "profileeditor.h"
27#include "profilewizard.h"
28#include "indihub.h"
29#include "auxiliary/darklibrary.h"
30#include "auxiliary/ksmessagebox.h"
31#include "auxiliary/profilesettings.h"
32#include "capture/sequencejob.h"
33#include "capture/captureprocess.h"
34#include "fitsviewer/fitsview.h"
35#include "fitsviewer/fitsdata.h"
36#include "indi/clientmanager.h"
37#include "indi/driverinfo.h"
38#include "indi/drivermanager.h"
39#include "indi/guimanager.h"
40#include "indi/indilistener.h"
41#include "auxiliary/opticaltrainmanager.h"
42#include "auxiliary/opticaltrainsettings.h"
43#include "indi/indiwebmanager.h"
44#include "indi/indigps.h"
45#include "indi/indiguider.h"
46#include "indi/indirotator.h"
47#include "mount/meridianflipstatuswidget.h"
48#include "ekos/auxiliary/rotatorutils.h"
49
50#include "ekoslive/ekosliveclient.h"
51#include "ekoslive/message.h"
52#include "ekoslive/media.h"
53
54#include <basedevice.h>
55
56#include <KConfigDialog>
57#include <KMessageBox>
58#include <KActionCollection>
59#include <KNotifications/KNotification>
60
61#include <QFutureWatcher>
62#include <QComboBox>
63
64#include <ekos_debug.h>
65
66#define MAX_REMOTE_INDI_TIMEOUT 15000
67#define MAX_LOCAL_INDI_TIMEOUT 10000
68
69namespace Ekos
70{
71
72Manager *Manager::_Manager = nullptr;
73
74Manager *Manager::Instance()
75{
76 if (_Manager == nullptr)
77 _Manager = new Manager(Options::independentWindowEkos() ? nullptr : KStars::Instance());
78
79 return _Manager;
80}
81
82void Manager::release()
83{
84 ProfileSettings::release();
85 OpticalTrainManager::release();
86 OpticalTrainSettings::release();
87 RotatorUtils::release();
88 delete _Manager;
89}
90
91Manager::Manager(QWidget * parent) : QDialog(parent)
92{
93#ifdef Q_OS_OSX
94
95 if (Options::independentWindowEkos())
96 setWindowFlags(Qt::Window);
97 else
98 {
99 setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
100 connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this,
101 SLOT(changeAlwaysOnTop(Qt::ApplicationState)));
102 }
103#else
104 if (Options::independentWindowEkos())
105 //setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
106 setWindowFlags(Qt::Window);
107#endif
108 setupUi(this);
109 // do not show empty targets
110 capturePreview->targetLabel->setVisible(false);
111 capturePreview->mountTarget->setVisible(false);
112
113 // position the vertical splitter by 2/3
114 deviceSplitter->setSizes(QList<int>({20000, 10000}));
115
116 qRegisterMetaType<Ekos::CommunicationStatus>("Ekos::CommunicationStatus");
118
119 new EkosAdaptor(this);
120 QDBusConnection::sessionBus().registerObject("/KStars/Ekos", this);
121
122 setWindowIcon(QIcon::fromTheme("kstars_ekos"));
123
124 profileModel.reset(new QStandardItemModel(0, 4));
125 profileModel->setHorizontalHeaderLabels(QStringList() << "id"
126 << "name"
127 << "host"
128 << "port");
129
130 m_CountdownTimer.setInterval(1000);
131 connect(&m_CountdownTimer, &QTimer::timeout, this, &Ekos::Manager::updateCaptureCountDown);
132
133 toolsWidget->setIconSize(QSize(48, 48));
134 connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection);
135
136 // Enable scheduler Tab
137 toolsWidget->setTabEnabled(1, false);
138
139 // Enable analyze Tab
140 toolsWidget->setTabEnabled(2, false);
141
142 // Start/Stop INDI Server
143 connect(processINDIB, &QPushButton::clicked, this, &Ekos::Manager::processINDI);
144 processINDIB->setIcon(QIcon::fromTheme("media-playback-start"));
145 processINDIB->setToolTip(i18n("Start"));
146
147 // Connect/Disconnect INDI devices
148 connect(connectB, &QPushButton::clicked, this, &Ekos::Manager::connectDevices);
149 connect(disconnectB, &QPushButton::clicked, this, &Ekos::Manager::disconnectDevices);
150
151 // Init EkosLive client
153 ekosLiveClient.reset(new EkosLive::Client(this));
154 connect(ekosLiveClient.get(), &EkosLive::Client::connected, this, [this]()
155 {
156 emit ekosLiveStatusChanged(true);
157 });
158 connect(ekosLiveClient.get(), &EkosLive::Client::disconnected, this, [this]()
159 {
160 emit ekosLiveStatusChanged(false);
161 });
162
163 // INDI Control Panel
164 //connect(controlPanelB, &QPushButton::clicked, GUIManager::Instance(), SLOT(show()));
166 {
167 ekosLiveClient.get()->show();
168 ekosLiveClient.get()->raise();
169 });
170
171 connect(this, &Manager::ekosStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus);
172 connect(this, &Manager::indiStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setINDIStatus);
173 connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, this, [&]()
174 {
175 ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg"));
176 });
177 connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, this, [&]()
178 {
179 ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud"));
180 });
181 connect(ekosLiveClient.get()->media(), &EkosLive::Media::newBoundingRect, ekosLiveClient.get()->message(),
182 &EkosLive::Message::setBoundingRect);
183 connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(),
184 &EkosLive::Media::resetPolarView);
185 connect(KSMessageBox::Instance(), &KSMessageBox::newMessage, ekosLiveClient.get()->message(),
186 &EkosLive::Message::sendDialog);
187
188 // Port Selector
189 m_PortSelectorTimer.setInterval(500);
190 m_PortSelectorTimer.setSingleShot(true);
191 connect(&m_PortSelectorTimer, &QTimer::timeout, this, [this]()
192 {
193 if (m_PortSelector && m_CurrentProfile->portSelector)
194 {
195 if (m_PortSelector->shouldShow())
196 {
197 m_PortSelector->show();
198 m_PortSelector->raise();
199
200 ekosLiveClient.get()->message()->requestPortSelection(true);
201 }
202 // If port selector is enabled, but we have zero ports to work with, let's proceed to connecting if it is enabled.
203 else if (m_CurrentProfile->autoConnect)
204 setPortSelectionComplete();
205 }
206 else if (m_CurrentProfile->autoConnect)
207 setPortSelectionComplete();
208 });
210 {
211 if (m_PortSelector)
212 {
213 m_PortSelector->show();
214 m_PortSelector->raise();
215 }
216 });
217
218 connect(this, &Ekos::Manager::ekosStatusChanged, this, [&](Ekos::CommunicationStatus status)
219 {
220 indiControlPanelB->setEnabled(status == Ekos::Success);
221 connectB->setEnabled(false);
222 disconnectB->setEnabled(false);
223 profileGroup->setEnabled(status == Ekos::Idle || status == Ekos::Error);
224 m_isStarted = (status == Ekos::Success || status == Ekos::Pending);
225 if (status == Ekos::Success)
226 {
227 processINDIB->setIcon(QIcon::fromTheme("media-playback-stop"));
228 processINDIB->setToolTip(i18n("Stop"));
229 setWindowTitle(i18nc("@title:window", "Ekos - %1 Profile", m_CurrentProfile->name));
230 }
231 else if (status == Ekos::Error || status == Ekos::Idle)
232 {
233 processINDIB->setIcon(QIcon::fromTheme("media-playback-start"));
234 processINDIB->setToolTip(i18n("Start"));
235 }
236 else
237 {
238 processINDIB->setIcon(QIcon::fromTheme("call-stop"));
239 processINDIB->setToolTip(i18n("Connection in progress. Click to abort."));
240 }
241 });
243 {
244 KStars::Instance()->actionCollection()->action("show_control_panel")->trigger();
245 });
247 {
248 KStars::Instance()->actionCollection()->action("configure")->trigger();
249 });
250 // Save as above, but it appears in all modules
251 connect(ekosOptionsB, &QPushButton::clicked, this, &Ekos::Manager::showEkosOptions);
252
253 // Clear Ekos Log
254 connect(clearB, &QPushButton::clicked, this, &Ekos::Manager::clearLog);
255
256 // Logs
257 KConfigDialog * dialog = new KConfigDialog(this, "logssettings", Options::self());
258 opsLogs = new Ekos::OpsLogs();
259 KPageWidgetItem * page = dialog->addPage(opsLogs, i18n("Logging"));
260 page->setIcon(QIcon::fromTheme("configure"));
262 connect(dialog->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces);
263 connect(dialog->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces);
264
265 // Profiles
266 connect(addProfileB, &QPushButton::clicked, this, &Ekos::Manager::addProfile);
267 connect(editProfileB, &QPushButton::clicked, this, &Ekos::Manager::editProfile);
268 connect(deleteProfileB, &QPushButton::clicked, this, &Ekos::Manager::deleteProfile);
269 connect(profileCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged), this,
270 [ = ](const QString & text)
271 {
272 Options::setProfile(text);
273 if (text == "Simulators")
274 {
275 editProfileB->setEnabled(false);
276 deleteProfileB->setEnabled(false);
277 }
278 else
279 {
280 editProfileB->setEnabled(true);
281 deleteProfileB->setEnabled(true);
282 }
283 });
284
285 // Settle timer
286 // Debounce until property stream settles down for a second.
287 settleTimer.setInterval(1000);
288 connect(&settleTimer, &QTimer::timeout, this, [&]()
289 {
290 if (m_settleStatus != Ekos::Success)
291 {
292 m_settleStatus = Ekos::Success;
293 emit settleStatusChanged(m_settleStatus);
294 }
295 });
296
297 // Ekos Wizard
298 connect(wizardProfileB, &QPushButton::clicked, this, &Ekos::Manager::wizardProfile);
299
303
304 // Set Profile icons
305 addProfileB->setIcon(QIcon::fromTheme("list-add"));
307 editProfileB->setIcon(QIcon::fromTheme("document-edit"));
309 deleteProfileB->setIcon(QIcon::fromTheme("list-remove"));
311 wizardProfileB->setIcon(QIcon::fromTheme("tools-wizard"));
313 customDriversB->setIcon(QIcon::fromTheme("roll"));
315
316 connect(customDriversB, &QPushButton::clicked, DriverManager::Instance(), &DriverManager::showCustomDrivers);
317
318 // Load all drivers
319 loadDrivers();
320
321 // Load add driver profiles
322 loadProfiles();
323
324 // INDI Control Panel and Ekos Options
325 optionsB->setIcon(QIcon::fromTheme("configure", QIcon(":/icons/ekos_setup.png")));
327
328 // Setup Tab
329 toolsWidget->tabBar()->setTabIcon(0, QIcon(":/icons/ekos_setup.png"));
330 toolsWidget->tabBar()->setTabToolTip(0, i18n("Setup"));
331
332 // Initialize Ekos Scheduler Module
333 schedulerProcess.reset(new Scheduler());
334 int index = addModuleTab(EkosModule::Scheduler, schedulerModule(), QIcon(":/icons/ekos_scheduler.png"));
335 toolsWidget->tabBar()->setTabToolTip(index, i18n("Scheduler"));
336 capturePreview->shareSchedulerModuleState(schedulerModule()->moduleState());
337 connect(schedulerModule()->process().data(), &SchedulerProcess::newLog, this, &Ekos::Manager::updateLog);
338 connect(schedulerModule(), &Ekos::Scheduler::newTarget, this, &Manager::setTarget);
339 // Scheduler <---> EkosLive connections
340 connect(schedulerModule(), &Ekos::Scheduler::jobsUpdated, ekosLiveClient.get()->message(),
341 &EkosLive::Message::sendSchedulerJobs, Qt::UniqueConnection);
342 connect(schedulerModule(), &Ekos::Scheduler::settingsUpdated, ekosLiveClient.get()->message(),
343 &EkosLive::Message::sendSchedulerSettings, Qt::UniqueConnection);
344 connect(schedulerModule()->process().data(), &SchedulerProcess::newLog, ekosLiveClient.get()->message(),
345 [this]()
346 {
347 QJsonObject cStatus =
348 {
349 {"log", schedulerModule()->moduleState()->getLogText()}
350 };
351
352 ekosLiveClient.get()->message()->sendSchedulerStatus(cStatus);
353 });
354 connect(schedulerModule(), &Ekos::Scheduler::newStatus, ekosLiveClient.get()->message(),
355 [this](Ekos::SchedulerState state)
356 {
357 QJsonObject cStatus =
358 {
359 {"status", state}
360 };
361
362 ekosLiveClient.get()->message()->sendSchedulerStatus(cStatus);
363 });
364
365 // Initialize Ekos Analyze Module
366 analyzeProcess.reset(new Ekos::Analyze());
367 connect(analyzeProcess.get(), &Ekos::Analyze::newLog, this, &Ekos::Manager::updateLog);
368
369 index = addModuleTab(EkosModule::Analyze, analyzeProcess.get(), QIcon(":/icons/ekos_analyze.png"));
370 toolsWidget->tabBar()->setTabToolTip(index, i18n("Analyze"));
371
372 numPermanentTabs = index + 1;
373
374 // Temporary fix. Not sure how to resize Ekos Dialog to fit contents of the various tabs in the QScrollArea which are added
375 // dynamically. I used setMinimumSize() but it doesn't appear to make any difference.
376 // Also set Layout policy to SetMinAndMaxSize as well. Any idea how to fix this?
377 // FIXME
378 //resize(1000,750);
379
380 m_SummaryView.reset(new SummaryFITSView(capturePreview->previewWidget));
381 m_SummaryView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
382 // sterne-jaeger 2021-08-08: Do not set base size here, otherwise the zoom will be incorrect
383 // summaryPreview->setBaseSize(capturePreview->previewWidget->size());
384 m_SummaryView->createFloatingToolBar();
385 m_SummaryView->setCursorMode(FITSView::dragCursor);
386 m_SummaryView->showProcessInfo(false);
387 capturePreview->setSummaryFITSView(m_SummaryView.get());
388 mountStatusLayout->setAlignment(Qt::AlignVCenter);
389
390 if (Options::ekosLeftIcons())
391 {
392 toolsWidget->setTabPosition(QTabWidget::West);
394 trans.rotate(90);
395
396 for (int i = 0; i < numPermanentTabs; ++i)
397 {
398 QIcon icon = toolsWidget->tabIcon(i);
399 QPixmap pix = icon.pixmap(QSize(48, 48));
400 icon = QIcon(pix.transformed(trans));
401 toolsWidget->setTabIcon(i, icon);
402 }
403 }
404
405 //Note: This is to prevent a button from being called the default button
406 //and then executing when the user hits the enter key such as when on a Text Box
407
409 for (auto &button : qButtons)
410 button->setAutoDefault(false);
411
412
413 resize(Options::ekosWindowWidth(), Options::ekosWindowHeight());
414}
415
416void Manager::changeAlwaysOnTop(Qt::ApplicationState state)
417{
418 if (isVisible())
419 {
420 if (state == Qt::ApplicationActive)
421 setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
422 else
423 setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
424 show();
425 }
426}
427
428Manager::~Manager()
429{
430 toolsWidget->disconnect(this);
431}
432
433void Manager::closeEvent(QCloseEvent * event)
434{
435 // QAction * a = KStars::Instance()->actionCollection()->action("show_ekos");
436 // a->setChecked(false);
437
438 // 2019-02-14 JM: Close event, for some reason, make all the children disappear
439 // when the widget is shown again. Applying a workaround here
440
441 event->ignore();
442 hide();
443}
444
445void Manager::hideEvent(QHideEvent * /*event*/)
446{
447 Options::setEkosWindowWidth(width());
448 Options::setEkosWindowHeight(height());
449
450 QAction * a = KStars::Instance()->actionCollection()->action("show_ekos");
451 a->setChecked(false);
452}
453
454void Manager::showEvent(QShowEvent * /*event*/)
455{
456 QAction * a = KStars::Instance()->actionCollection()->action("show_ekos");
457 a->setChecked(true);
458
459 // Just show the profile wizard ONCE per session
460 if (profileWizardLaunched == false && profiles.count() == 1)
461 {
462 profileWizardLaunched = true;
463 wizardProfile();
464 }
465}
466
467void Manager::resizeEvent(QResizeEvent *)
468{
469 focusManager->updateFocusDetailView();
470 guideManager->updateGuideDetailView();
471}
472
473void Manager::loadProfiles()
474{
475 profiles.clear();
476 KStarsData::Instance()->userdb()->GetAllProfiles(profiles);
477
478 profileModel->clear();
479
480 for (auto &pi : profiles)
481 {
483
484 info << new QStandardItem(pi->id) << new QStandardItem(pi->name) << new QStandardItem(pi->host)
485 << new QStandardItem(pi->port);
486 profileModel->appendRow(info);
487 }
488
489 profileModel->sort(0);
490 profileCombo->blockSignals(true);
491 profileCombo->setModel(profileModel.get());
492 profileCombo->setModelColumn(1);
493 profileCombo->blockSignals(false);
494
495 // Load last used profile from options
496 int index = profileCombo->findText(Options::profile());
497 // If not found, set it to first item
498 if (index == -1)
499 index = 0;
500 profileCombo->setCurrentIndex(index);
501}
502
503int Manager::addModuleTab(Manager::EkosModule module, QWidget *tab, const QIcon &icon)
504{
505 int index = 0;
506 switch(module)
507 {
508 case EkosModule::Observatory:
509 index += guideProcess ? 1 : 0; /* FALLTHRU */
510 case EkosModule::Guide:
511 index += alignProcess ? 1 : 0; /* FALLTHRU */
512 case EkosModule::Align:
513 index += mountProcess ? 1 : 0; /* FALLTHRU */
514 case EkosModule::Mount:
515 index += focusProcess ? 1 : 0; /* FALLTHRU */
516 case EkosModule::Focus:
517 index += captureProcess ? 1 : 0; /* FALLTHRU */
518 case EkosModule::Capture:
519 index += analyzeProcess ? 1 : 0; /* FALLTHRU */
520 case EkosModule::Analyze:
521 index += schedulerProcess ? 1 : 0; /* FALLTHRU */
522 case EkosModule::Scheduler:
523 index += 1; /* FALLTHRU */
524 case EkosModule::Setup:
525 // do nothing
526 break;
527 default:
528 index = toolsWidget->count();
529 break;
530 }
531
532 toolsWidget->insertTab(index, tab, icon, "");
533 return index;
534}
535
536void Manager::loadDrivers()
537{
538 for (auto &dv : DriverManager::Instance()->getDrivers())
539 {
540 if (dv->getDriverSource() != HOST_SOURCE)
541 driversList[dv->getLabel()] = dv;
542 }
543}
544
545void Manager::reset()
546{
547 qCDebug(KSTARS_EKOS) << "Resetting Ekos Manager...";
548
549 ProfileSettings::release();
550 OpticalTrainManager::release();
551 OpticalTrainSettings::release();
552 RotatorUtils::release();
553
554 m_DriverDevicesCount = 0;
555
556 removeTabs();
557
558 captureProcess.reset();
559 focusProcess.reset();
560 guideProcess.reset();
561 alignProcess.reset();
562 mountProcess.reset();
563 observatoryProcess.reset();
564
565 for (auto &oneManger : m_FilterManagers)
567 m_FilterManagers.clear();
568
569 for (auto &oneController : m_RotatorControllers)
571 m_RotatorControllers.clear();
572
573 DarkLibrary::Release();
574 m_PortSelector.reset();
575 m_PortSelectorTimer.stop();
576
577 Ekos::CommunicationStatus previousStatus;
578
579 previousStatus = m_settleStatus;
580 m_settleStatus = Ekos::Idle;
581 if (previousStatus != m_settleStatus)
582 emit settleStatusChanged(m_settleStatus);
583
584 previousStatus = m_ekosStatus;
585 m_ekosStatus = Ekos::Idle;
586 if (previousStatus != m_ekosStatus)
587 emit ekosStatusChanged(m_ekosStatus);
588
589 previousStatus = m_indiStatus;
590 m_indiStatus = Ekos::Idle;
591 if (previousStatus != m_indiStatus)
592 emit indiStatusChanged(m_indiStatus);
593
594 connectB->setEnabled(false);
595 disconnectB->setEnabled(false);
596 //controlPanelB->setEnabled(false);
597 processINDIB->setEnabled(true);
598
599 mountGroup->setEnabled(false);
600 capturePreview->setEnabled(false);
601 capturePreview->reset();
602 mountStatus->setStatus(i18n("Idle"), Qt::gray);
603 mountStatus->setStyleSheet(QString());
604 focusManager->reset();
605 guideManager->reset();
606
607 m_isStarted = false;
608
609 processINDIB->setIcon(QIcon::fromTheme("media-playback-start"));
610 processINDIB->setToolTip(i18n("Start"));
611}
612
613void Manager::processINDI()
614{
615 if (m_isStarted == false)
616 start();
617 else
618 stop();
619}
620
621void Manager::stop()
622{
623 cleanDevices();
624 m_PortSelector.reset();
625 m_PortSelectorTimer.stop();
626 m_CountdownTimer.stop();
627 portSelectorB->setEnabled(false);
628
629 if (indiHubAgent)
630 indiHubAgent->terminate();
631
632 profileGroup->setEnabled(true);
633
634 setWindowTitle(i18nc("@title:window", "Ekos"));
635}
636
637void Manager::start()
638{
639 if (analyzeProcess && Options::analyzeRestartWithEkos())
640 analyzeProcess->restart();
641
642 // Don't start if it is already started before
643 if (m_ekosStatus == Ekos::Pending || m_ekosStatus == Ekos::Success)
644 {
645 qCWarning(KSTARS_EKOS) << "Ekos Manager start called but current Ekos Status is" << m_ekosStatus << "Ignoring request.";
646 return;
647 }
648
649 managedDrivers.clear();
650
651 // If clock was paused, unpaused it and sync time
652 if (KStarsData::Instance()->clock()->isActive() == false)
653 {
654 KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
655 KStarsData::Instance()->clock()->start();
656 }
657
658 // Reset Ekos Manager
659 reset();
660
661 // Get Current Profile
662 getCurrentProfile(m_CurrentProfile);
663 m_LocalMode = m_CurrentProfile->isLocal();
664
665 ProfileSettings::Instance()->setProfile(m_CurrentProfile);
666
667 // Load profile location if one exists
668 updateProfileLocation(m_CurrentProfile);
669
670 bool haveCCD = false, haveGuider = false;
671
672 // If external guide is specified in the profile, set the
673 // corresponding options
674 if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_PHD2)
675 {
676 Options::setPHD2Host(m_CurrentProfile->guiderhost);
677 Options::setPHD2Port(m_CurrentProfile->guiderport);
678 }
679 else if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER)
680 {
681 Options::setLinGuiderHost(m_CurrentProfile->guiderhost);
682 Options::setLinGuiderPort(m_CurrentProfile->guiderport);
683 }
684
685 // Parse script, if any
688 QJsonDocument doc = QJsonDocument::fromJson(m_CurrentProfile->scripts, &jsonError);
689
691 profileScripts = doc.array();
692
693 ekosLiveClient->message()->setPendingPropertiesEnabled(true);
694
695 // For locally running INDI server
696 if (m_LocalMode)
697 {
698 auto drv = driversList.value(m_CurrentProfile->mount());
699
700 if (!drv.isNull())
701 managedDrivers.append(drv->clone());
702
703 drv = driversList.value(m_CurrentProfile->ccd());
704 if (!drv.isNull())
705 {
706 managedDrivers.append(drv->clone());
707 haveCCD = true;
708 }
709
710 Options::setGuiderType(m_CurrentProfile->guidertype);
711
712 drv = driversList.value(m_CurrentProfile->guider());
713 if (!drv.isNull())
714 {
715 haveGuider = true;
716
717 // If the guider and ccd are the same driver, we have two cases:
718 // #1 Drivers that only support ONE device per driver (such as sbig)
719 // #2 Drivers that supports multiples devices per driver (such as sx)
720 // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide"
721 // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider
722 // since this is the only way to find out in real time.
723 if (haveCCD && m_CurrentProfile->guider() == m_CurrentProfile->ccd())
724 {
725 if (checkUniqueBinaryDriver( driversList.value(m_CurrentProfile->ccd()), drv))
726 {
727 drv.clear();
728 }
729 else
730 {
731 drv->setUniqueLabel(drv->getLabel() + " Guide");
732 }
733 }
734
735 if (!drv.isNull())
736 managedDrivers.append(drv->clone());
737 }
738
739 drv = driversList.value(m_CurrentProfile->ao());
740 if (!drv.isNull())
741 managedDrivers.append(drv->clone());
742
743 drv = driversList.value(m_CurrentProfile->filter());
744 if (!drv.isNull())
745 managedDrivers.append(drv->clone());
746
747 drv = driversList.value(m_CurrentProfile->focuser());
748 if (!drv.isNull())
749 managedDrivers.append(drv->clone());
750
751 drv = driversList.value(m_CurrentProfile->dome());
752 if (!drv.isNull())
753 managedDrivers.append(drv->clone());
754
755 drv = driversList.value(m_CurrentProfile->weather());
756 if (!drv.isNull())
757 managedDrivers.append(drv->clone());
758
759 drv = driversList.value(m_CurrentProfile->aux1());
760 if (!drv.isNull())
761 {
762 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
763 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
764 managedDrivers.append(drv->clone());
765 }
766 drv = driversList.value(m_CurrentProfile->aux2());
767 if (!drv.isNull())
768 {
769 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
770 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
771 managedDrivers.append(drv->clone());
772 }
773
774 drv = driversList.value(m_CurrentProfile->aux3());
775 if (!drv.isNull())
776 {
777 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
778 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
779 managedDrivers.append(drv->clone());
780 }
781
782 drv = driversList.value(m_CurrentProfile->aux4());
783 if (!drv.isNull())
784 {
785 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
786 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
787 managedDrivers.append(drv->clone());
788 }
789
790 // Add remote drivers if we have any
791 if (m_CurrentProfile->remotedrivers.isEmpty() == false && m_CurrentProfile->remotedrivers.contains("@"))
792 {
793 for (auto remoteDriver : m_CurrentProfile->remotedrivers.split(","))
794 {
795 QString name, label, host("localhost"), port("7624"), hostport(host + ':' + port);
796
797 // Possible configurations:
798 // - device
799 // - device@host
800 // - device@host:port
801 // - @host
802 // - @host:port
803
804 {
806
807 // device or device@host:port
808 if (device_location.length() > 0)
810
811 // device@host:port or @host:port
812 if (device_location.length() > 1)
814 }
815
816 {
817 QStringList location = hostport.split(':');
818
819 // host or host:port
820 if (location.length() > 0)
821 host = location[0];
822
823 // host:port
824 if (location.length() > 1)
825 port = location[1];
826 }
827
829 dv->setRemoteHost(host);
830 dv->setRemotePort(port);
831
832 label = name;
833 // Remove extra quotes
834 label.remove("\"");
835 dv->setLabel(label);
836 dv->setUniqueLabel(label);
837 managedDrivers.append(dv);
838 }
839 }
840
841
842 if (haveCCD == false && haveGuider == false && m_CurrentProfile->remotedrivers.isEmpty())
843 {
844 KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate."));
845 managedDrivers.clear();
846 m_ekosStatus = Ekos::Error;
847 emit ekosStatusChanged(m_ekosStatus);
848 return;
849 }
850
851 m_DriverDevicesCount = managedDrivers.count();
852 }
853 else
854 {
856
857 remote_indi->setHostParameters(m_CurrentProfile->host, m_CurrentProfile->port);
858
859 remote_indi->setDriverSource(GENERATED_SOURCE);
860
861 managedDrivers.append(remote_indi);
862
863 haveCCD = m_CurrentProfile->drivers.contains("CCD");
864 haveGuider = m_CurrentProfile->drivers.contains("Guider");
865
866 Options::setGuiderType(m_CurrentProfile->guidertype);
867
868 if (haveCCD == false && haveGuider == false && m_CurrentProfile->remotedrivers.isEmpty())
869 {
870 KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate."));
871 m_DriverDevicesCount = 0;
872 m_ekosStatus = Ekos::Error;
873 emit ekosStatusChanged(m_ekosStatus);
874 return;
875 }
876
877 m_DriverDevicesCount = m_CurrentProfile->drivers.count();
878 }
879
880
881 // Prioritize profile script drivers over other drivers
883 for (const auto &oneRule : qAsConst(profileScripts))
884 {
885 auto matchingDriver = std::find_if(managedDrivers.begin(), managedDrivers.end(), [oneRule](const auto & oneDriver)
886 {
887 return oneDriver->getLabel() == oneRule.toObject()["Driver"].toString();
888 });
889
890 if (matchingDriver != managedDrivers.end())
891 {
892 (*matchingDriver)->setStartupRule(oneRule.toObject());
893 sortedList.append(*matchingDriver);
894 }
895 }
896
897 // If we have any profile scripts drivers, let's re-sort managed drivers
898 // so that profile script drivers
899 if (!sortedList.isEmpty())
900 {
901 for (auto &oneDriver : managedDrivers)
902 {
903 if (sortedList.contains(oneDriver) == false)
904 sortedList.append(oneDriver);
905 }
906
907 managedDrivers = sortedList;
908 }
909
910 connect(DriverManager::Instance(), &DriverManager::serverStarted, this,
911 &Manager::setServerStarted, Qt::UniqueConnection);
912 connect(DriverManager::Instance(), &DriverManager::serverFailed, this,
913 &Manager::setServerFailed, Qt::UniqueConnection);
914 connect(DriverManager::Instance(), &DriverManager::clientStarted, this,
915 &Manager::setClientStarted, Qt::UniqueConnection);
916 connect(DriverManager::Instance(), &DriverManager::clientFailed, this,
917 &Manager::setClientFailed, Qt::UniqueConnection);
918 connect(DriverManager::Instance(), &DriverManager::clientTerminated, this,
919 &Manager::setClientTerminated, Qt::UniqueConnection);
920
921 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice);
922 connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection);
923
924
925#ifdef Q_OS_OSX
926 if (m_LocalMode || m_CurrentProfile->host == "localhost")
927 {
928 if (isRunning("PTPCamera"))
929 {
930 if (KMessageBox::Yes ==
931 (KMessageBox::questionYesNo(nullptr,
932 i18n("Ekos detected that PTP Camera is running and may prevent a Canon or Nikon camera from connecting to Ekos. Do you want to quit PTP Camera now?"),
933 i18n("PTP Camera"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
934 "ekos_shutdown_PTPCamera")))
935 {
936 //TODO is there a better way to do this.
937 QProcess p;
938 p.start("killall PTPCamera");
939 p.waitForFinished();
940 }
941 }
942 }
943#endif
944 if (m_LocalMode)
945 {
946 auto executeStartINDIServices = [this]()
947 {
948 appendLogText(i18n("Starting INDI services..."));
949
950 m_ekosStatus = Ekos::Pending;
951 emit ekosStatusChanged(m_ekosStatus);
952
953 DriverManager::Instance()->startDevices(managedDrivers);
954 };
955
956 // If INDI server is already running, let's see if we need to shut it down first
957 if (isRunning("indiserver"))
958 {
959 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeStartINDIServices]()
960 {
961 KSMessageBox::Instance()->disconnect(this);
962 DriverManager::Instance()->stopAllDevices();
963 //TODO is there a better way to do this.
964 QProcess p;
965 const QString program = "pkill";
966 QStringList arguments;
967 arguments << "indiserver";
968 p.start(program, arguments);
969 p.waitForFinished();
970
972 });
973 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this, executeStartINDIServices]()
974 {
975 KSMessageBox::Instance()->disconnect(this);
977 });
978
979 KSMessageBox::Instance()->questionYesNo(i18n("Ekos detected an instance of INDI server running. Do you wish to "
980 "shut down the existing instance before starting a new one?"),
981 i18n("INDI Server"), 5);
982 }
983 else
985
986 }
987 else
988 {
989 auto runConnection = [this]()
990 {
991 // If it got cancelled by the user, return immediately.
992 if (m_ekosStatus != Ekos::Pending)
993 return;
994
995 appendLogText(
996 i18n("Connecting to remote INDI server at %1 on port %2 ...", m_CurrentProfile->host, m_CurrentProfile->port));
997
998 DriverManager::Instance()->connectRemoteHost(managedDrivers.first());
999 };
1000
1001 auto runProfile = [this, runConnection]()
1002 {
1003 // If it got cancelled by the user, return immediately.
1004 if (m_ekosStatus != Ekos::Pending)
1005 return;
1006
1007 INDI::WebManager::syncCustomDrivers(m_CurrentProfile);
1008 INDI::WebManager::checkVersion(m_CurrentProfile);
1009
1010 if (INDI::WebManager::areDriversRunning(m_CurrentProfile) == false)
1011 {
1012 INDI::WebManager::stopProfile(m_CurrentProfile);
1013
1014 if (INDI::WebManager::startProfile(m_CurrentProfile) == false)
1015 {
1016 appendLogText(i18n("Failed to start profile on remote INDI Web Manager."));
1017 return;
1018 }
1019
1020 appendLogText(i18n("Starting profile on remote INDI Web Manager..."));
1021 m_RemoteManagerStart = true;
1022 }
1023
1024 runConnection();
1025 };
1026
1027 m_ekosStatus = Ekos::Pending;
1028 emit ekosStatusChanged(m_ekosStatus);
1029
1030 // If we need to use INDI Web Manager
1031 if (m_CurrentProfile->INDIWebManagerPort > 0)
1032 {
1033 appendLogText(i18n("Establishing communication with remote INDI Web Manager..."));
1034 m_RemoteManagerStart = false;
1036 connect(watcher, &QFutureWatcher<bool>::finished, this, [this, runConnection, runProfile, watcher]()
1037 {
1038 watcher->deleteLater();
1039
1040 // If it got cancelled by the user, return immediately.
1041 if (m_ekosStatus != Ekos::Pending)
1042 return;
1043
1044 // If web manager is online, try to run the profile in it
1045 if (watcher->result())
1046 {
1047 runProfile();
1048 }
1049 // Else, try to connect directly to INDI server as there could be a chance
1050 // that it is already running.
1051 else
1052 {
1053 appendLogText(i18n("Warning: INDI Web Manager is not online."));
1054 runConnection();
1055 }
1056
1057 });
1058
1059 QFuture<bool> result = INDI::AsyncWebManager::isOnline(m_CurrentProfile);
1060 watcher->setFuture(result);
1061 }
1062 else
1063 {
1064 runConnection();
1065 }
1066 }
1067}
1068
1069void Manager::setClientStarted(const QString &host, int port)
1070{
1071 if (managedDrivers.size() > 0)
1072 {
1073 if (m_LocalMode)
1074 {
1075 if (m_CurrentProfile->autoConnect)
1076 appendLogText(i18n("INDI services started on port %1.", port));
1077 else
1078 appendLogText(
1079 i18n("INDI services started on port %1. Please connect devices.", port));
1080 }
1081 else
1082 {
1083 appendLogText(
1084 i18n("INDI services started. Connection to remote INDI server %1:%2 is successful. Waiting for devices...", host, port));
1085 }
1086 }
1087
1088 QTimer::singleShot(MAX_LOCAL_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout);
1089}
1090
1091void Manager::setClientFailed(const QString &host, int port, const QString &errorMessage)
1092{
1093 if (m_LocalMode)
1094 appendLogText(i18n("Failed to connect to local INDI server %1:%2", host, port));
1095 else
1096 appendLogText(i18n("Failed to connect to remote INDI server %1:%2", host, port));
1097
1098 //INDIListener::Instance()->disconnect(this);
1099 // qDeleteAll(managedDrivers);
1100 // managedDrivers.clear();
1101 m_ekosStatus = Ekos::Error;
1102 emit ekosStatusChanged(m_ekosStatus);
1103 KSNotification::error(errorMessage, i18n("Error"), 15);
1104}
1105
1106void Manager::setClientTerminated(const QString &host, int port, const QString &errorMessage)
1107{
1108 if (m_LocalMode)
1109 appendLogText(i18n("Lost connection to local INDI server %1:%2", host, port));
1110 else
1111 appendLogText(i18n("Lost connection to remote INDI server %1:%2", host, port));
1112
1113 //INDIListener::Instance()->disconnect(this);
1114 // qDeleteAll(managedDrivers);
1115 // managedDrivers.clear();
1116 m_ekosStatus = Ekos::Error;
1117 emit ekosStatusChanged(m_ekosStatus);
1118 KSNotification::error(errorMessage, i18n("Error"), 15);
1119}
1120
1121void Manager::setServerStarted(const QString &host, int port)
1122{
1123 if (m_LocalMode && m_CurrentProfile->indihub != INDIHub::None)
1124 {
1125 if (QFile(Options::iNDIHubAgent()).exists())
1126 {
1127 indiHubAgent = new QProcess();
1129
1130 args << "--indi-server" << QString("%1:%2").arg(host).arg(port);
1131 if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_PHD2)
1132 args << "--phd2-server" << QString("%1:%2").arg(m_CurrentProfile->guiderhost).arg(m_CurrentProfile->guiderport);
1133 args << "--mode" << INDIHub::toString(m_CurrentProfile->indihub);
1134 indiHubAgent->start(Options::iNDIHubAgent(), args);
1135
1136 qCDebug(KSTARS_EKOS) << "Started INDIHub agent.";
1137 }
1138 }
1139}
1140
1141void Manager::setServerFailed(const QString &host, int port, const QString &message)
1142{
1143 Q_UNUSED(host)
1144 Q_UNUSED(port)
1145 managedDrivers.clear();
1146 m_ekosStatus = Ekos::Error;
1147 emit ekosStatusChanged(m_ekosStatus);
1148 KSNotification::error(message, i18n("Error"), 15);
1149}
1150
1151//void Manager::setServerTerminated(const QString &host, int port, const QString &message)
1152//{
1153// if ((m_LocalMode && managedDrivers.first()->getPort() == port) ||
1154// (currentProfile->host == host && currentProfile->port == port))
1155// {
1156// cleanDevices(false);
1157// if (indiHubAgent)
1158// indiHubAgent->terminate();
1159// }
1160
1161// INDIListener::Instance()->disconnect(this);
1162// qDeleteAll(managedDrivers);
1163// managedDrivers.clear();
1164// m_ekosStatus = Ekos::Error;
1165// emit ekosStatusChanged(m_ekosStatus);
1166// KSNotification::error(message, i18n("Error"), 15);
1167//}
1168
1169void Manager::checkINDITimeout()
1170{
1171 // Don't check anything unless we're still pending
1172 if (m_ekosStatus != Ekos::Pending)
1173 {
1174 // All devices are connected already, nothing to do.
1175 if (m_indiStatus != Ekos::Pending || m_CurrentProfile->portSelector || m_CurrentProfile->autoConnect == false)
1176 return;
1177
1179 for (auto &oneDevice : INDIListener::devices())
1180 {
1181 if (oneDevice->isConnected() == false)
1182 disconnectedDevices << oneDevice->getDeviceName();
1183 }
1184
1185 QString message;
1186
1187 if (disconnectedDevices.count() == 1)
1188 message = i18n("Failed to connect to %1. Please ensure device is connected and powered on.", disconnectedDevices.first());
1189 else
1190 message = i18n("Failed to connect to \n%1\nPlease ensure each device is connected and powered on.",
1191 disconnectedDevices.join("\n"));
1192
1193 appendLogText(message);
1194 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1195 return;
1196 }
1197
1198
1199 if (m_DriverDevicesCount <= 0)
1200 {
1201 m_ekosStatus = Ekos::Success;
1202 emit ekosStatusChanged(m_ekosStatus);
1203 return;
1204 }
1205
1206 if (m_LocalMode)
1207 {
1209 for (auto &drv : managedDrivers)
1210 {
1211 if (drv->getDevices().count() == 0)
1212 remainingDevices << QString("+ %1").arg(
1213 drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName());
1214 }
1215
1216 if (remainingDevices.count() == 1)
1217 {
1218 QString message = i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.",
1219 remainingDevices.at(0));
1220 appendLogText(message);
1221 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1222 KNotification::beep(i18n("Ekos startup error"));
1223 }
1224 else
1225 {
1226 QString message = i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected "
1227 "and powered on.", remainingDevices.join("\n"));
1228 appendLogText(message);
1229 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1230 KNotification::beep(i18n("Ekos startup error"));
1231 }
1232 }
1233 else
1234 {
1236
1237 for (auto &driver : m_CurrentProfile->drivers.values())
1238 {
1239 bool driverFound = false;
1240
1241 for (auto &device : INDIListener::devices())
1242 {
1243 if (device->getBaseDevice().getDriverName() == driver)
1244 {
1245 driverFound = true;
1246 break;
1247 }
1248 }
1249
1250 if (driverFound == false)
1251 remainingDevices << QString("+ %1").arg(driver);
1252 }
1253
1254 if (remainingDevices.count() == 1)
1255 {
1256 QString message = i18n("Unable to remotely establish:\n%1\nPlease ensure the device is connected and powered on.",
1257 remainingDevices.at(0));
1258 appendLogText(message);
1259 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1260 KNotification::beep(i18n("Ekos startup error"));
1261 }
1262 else
1263 {
1264 QString message = i18n("Unable to remotely establish the following devices:\n%1\nPlease ensure each device is connected "
1265 "and powered on.", remainingDevices.join("\n"));
1266 appendLogText(message);
1267 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1268 KNotification::beep(i18n("Ekos startup error"));
1269 }
1270 }
1271
1272 m_ekosStatus = Ekos::Error;
1273}
1274
1275bool Manager::isINDIReady()
1276{
1277 // Check if already connected
1278 int nConnected = 0;
1279
1280 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1281
1282 auto devices = INDIListener::devices();
1283 for (auto &device : devices)
1284 {
1285 // Make sure we're not only connected, but also ready (i.e. all properties have already been defined).
1286 if (device->isConnected() && device->isReady())
1287 nConnected++;
1288 }
1289 if (devices.count() == nConnected)
1290 {
1291 m_indiStatus = Ekos::Success;
1292 emit indiStatusChanged(m_indiStatus);
1293 return true;
1294 }
1295
1296 m_indiStatus = Ekos::Pending;
1297 if (previousStatus != m_indiStatus)
1298 emit indiStatusChanged(m_indiStatus);
1299
1300 return false;
1301}
1302
1303void Manager::connectDevices()
1304{
1305 if (isINDIReady())
1306 return;
1307
1308 auto devices = INDIListener::devices();
1309
1310 for (auto &device : devices)
1311 {
1312 qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName();
1313 device->Connect();
1314 }
1315
1316 connectB->setEnabled(false);
1317 disconnectB->setEnabled(true);
1318
1319 appendLogText(i18n("Connecting INDI devices..."));
1320}
1321
1322void Manager::disconnectDevices()
1323{
1324 for (auto &device : INDIListener::devices())
1325 {
1326 qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName();
1327 device->Disconnect();
1328 }
1329
1330 appendLogText(i18n("Disconnecting INDI devices..."));
1331}
1332
1333void Manager::cleanDevices(bool stopDrivers)
1334{
1335 if (m_ekosStatus == Ekos::Idle)
1336 return;
1337
1338 if (mountModule())
1339 mountModule()->stopTimers();
1340
1341 ekosLiveClient->message()->setPendingPropertiesEnabled(false);
1342 INDIListener::Instance()->disconnect(this);
1343 DriverManager::Instance()->disconnect(this);
1344
1345 if (managedDrivers.isEmpty() == false)
1346 {
1347 if (m_LocalMode)
1348 {
1349 if (stopDrivers)
1350 DriverManager::Instance()->stopDevices(managedDrivers);
1351 }
1352 else
1353 {
1354 if (stopDrivers)
1355 {
1356 DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first());
1357
1358 if (m_RemoteManagerStart && m_CurrentProfile->INDIWebManagerPort != -1)
1359 INDI::WebManager::stopProfile(m_CurrentProfile);
1360 }
1361 m_RemoteManagerStart = false;
1362 }
1363 }
1364
1365 reset();
1366
1367 profileGroup->setEnabled(true);
1368
1369 appendLogText(i18n("INDI services stopped."));
1370}
1371
1372void Manager::processNewDevice(const QSharedPointer<ISD::GenericDevice> &device)
1373{
1374 qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << device->getDeviceName();
1375
1376 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1377
1378 // for(auto &oneDevice : INDIListener::devices())
1379 // {
1380 // if (oneDevice->getDeviceName() == device->getDeviceName())
1381 // {
1382 // qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring...";
1383 // return;
1384 // }
1385 // }
1386
1387 // Always reset INDI Connection status if we receive a new device
1388 m_indiStatus = Ekos::Idle;
1389 if (previousStatus != m_indiStatus)
1390 emit indiStatusChanged(m_indiStatus);
1391
1392 m_DriverDevicesCount--;
1393
1394 connect(device.get(), &ISD::GenericDevice::ready, this, &Ekos::Manager::setDeviceReady, Qt::UniqueConnection);
1395 connect(device.get(), &ISD::GenericDevice::newMount, this, &Ekos::Manager::addMount, Qt::UniqueConnection);
1396 connect(device.get(), &ISD::GenericDevice::newCamera, this, &Ekos::Manager::addCamera, Qt::UniqueConnection);
1397 connect(device.get(), &ISD::GenericDevice::newGuider, this, &Ekos::Manager::addGuider, Qt::UniqueConnection);
1398 connect(device.get(), &ISD::GenericDevice::newFilterWheel, this, &Ekos::Manager::addFilterWheel, Qt::UniqueConnection);
1399 connect(device.get(), &ISD::GenericDevice::newFocuser, this, &Ekos::Manager::addFocuser, Qt::UniqueConnection);
1400 connect(device.get(), &ISD::GenericDevice::newDome, this, &Ekos::Manager::addDome, Qt::UniqueConnection);
1401 connect(device.get(), &ISD::GenericDevice::newRotator, this, &Ekos::Manager::addRotator, Qt::UniqueConnection);
1402 connect(device.get(), &ISD::GenericDevice::newWeather, this, &Ekos::Manager::addWeather, Qt::UniqueConnection);
1403 connect(device.get(), &ISD::GenericDevice::newDustCap, this, &Ekos::Manager::addDustCap, Qt::UniqueConnection);
1404 connect(device.get(), &ISD::GenericDevice::newLightBox, this, &Ekos::Manager::addLightBox, Qt::UniqueConnection);
1405 connect(device.get(), &ISD::GenericDevice::newGPS, this, &Ekos::Manager::addGPS, Qt::UniqueConnection);
1406
1407 connect(device.get(), &ISD::GenericDevice::Connected, this, &Ekos::Manager::deviceConnected, Qt::UniqueConnection);
1408 connect(device.get(), &ISD::GenericDevice::Disconnected, this, &Ekos::Manager::deviceDisconnected, Qt::UniqueConnection);
1409 connect(device.get(), &ISD::GenericDevice::propertyDefined, this, &Ekos::Manager::processNewProperty, Qt::UniqueConnection);
1410 connect(device.get(), &ISD::GenericDevice::propertyDeleted, this, &Ekos::Manager::processDeleteProperty,
1412 connect(device.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Manager::processUpdateProperty,
1414 connect(device.get(), &ISD::GenericDevice::messageUpdated, this, &Ekos::Manager::processMessage, Qt::UniqueConnection);
1415
1416
1417
1418 // Only look for primary & guider CCDs if we can tell a difference between them
1419 // otherwise rely on saved options
1420 if (m_CurrentProfile->ccd() != m_CurrentProfile->guider())
1421 {
1422 for (auto &oneCamera : INDIListener::devices())
1423 {
1424 if (oneCamera->getDeviceName().startsWith(m_CurrentProfile->ccd(), Qt::CaseInsensitive))
1425 m_PrimaryCamera = QString(oneCamera->getDeviceName());
1426 else if (oneCamera->getDeviceName().startsWith(m_CurrentProfile->guider(), Qt::CaseInsensitive))
1427 m_GuideCamera = QString(oneCamera->getDeviceName());
1428 }
1429 }
1430
1431 if (m_DriverDevicesCount <= 0)
1432 {
1433 m_ekosStatus = Ekos::Success;
1434 emit ekosStatusChanged(m_ekosStatus);
1435
1436 connectB->setEnabled(true);
1437 disconnectB->setEnabled(false);
1438
1439 if (m_LocalMode == false && m_DriverDevicesCount == 0)
1440 {
1441 if (m_CurrentProfile->autoConnect)
1442 appendLogText(i18n("Remote devices established."));
1443 else
1444 appendLogText(i18n("Remote devices established. Please connect devices."));
1445 }
1446 }
1447}
1448
1449void Manager::deviceConnected()
1450{
1451 connectB->setEnabled(false);
1452 disconnectB->setEnabled(true);
1453 processINDIB->setEnabled(false);
1454
1455 auto device = qobject_cast<ISD::GenericDevice *>(sender());
1456
1457 if (Options::verboseLogging())
1458 {
1459 qCInfo(KSTARS_EKOS) << device->getDeviceName()
1460 << "Version:" << device->getDriverVersion()
1461 << "Interface:" << device->getDriverInterface()
1462 << "is connected.";
1463 }
1464
1465 if (Options::neverLoadConfig() == false)
1466 {
1467 INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG;
1468
1469 for (auto &oneDevice : INDIListener::devices())
1470 {
1471 if (oneDevice == device)
1472 {
1473 connect(device, &ISD::GenericDevice::propertyUpdated, this, &Ekos::Manager::watchDebugProperty, Qt::UniqueConnection);
1474
1475 auto configProp = device->getBaseDevice().getSwitch("CONFIG_PROCESS");
1476 if (configProp && configProp.getState() == IPS_IDLE)
1477 device->setConfig(tConfig);
1478 break;
1479 }
1480 }
1481 }
1482}
1483
1484void Manager::deviceDisconnected()
1485{
1486 ISD::GenericDevice * dev = static_cast<ISD::GenericDevice *>(sender());
1487
1488 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1489
1490 if (dev != nullptr)
1491 {
1492 if (dev->getState("CONNECTION") == IPS_ALERT)
1493 m_indiStatus = Ekos::Error;
1494 else if (dev->getState("CONNECTION") == IPS_BUSY)
1495 m_indiStatus = Ekos::Pending;
1496 else
1497 m_indiStatus = Ekos::Idle;
1498
1499 if (Options::verboseLogging())
1500 qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected.";
1501
1502 // In case a device fails to connect, display and log a useful message for the user.
1503 if (m_indiStatus == Ekos::Error)
1504 {
1505 QString message = i18n("%1 failed to connect.\nPlease ensure the device is connected and powered on.",
1506 dev->getDeviceName());
1507 appendLogText(message);
1508 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1509 }
1510 else if (m_indiStatus == Ekos::Idle)
1511 {
1512 QString message = i18n("%1 is disconnected.", dev->getDeviceName());
1513 appendLogText(message);
1514 }
1515 }
1516 else
1517 m_indiStatus = Ekos::Idle;
1518
1519 if (previousStatus != m_indiStatus)
1520 emit indiStatusChanged(m_indiStatus);
1521
1522 connectB->setEnabled(true);
1523 disconnectB->setEnabled(false);
1524 processINDIB->setEnabled(true);
1525}
1526
1527void Manager::addMount(ISD::Mount *device)
1528{
1529 ekosLiveClient->message()->sendScopes();
1530
1531 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1532
1533 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1534}
1535
1536void Manager::addCamera(ISD::Camera * device)
1537{
1538 ekosLiveClient.get()->media()->registerCameras();
1539
1540 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1541
1542 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1543}
1544
1545void Manager::addFilterWheel(ISD::FilterWheel * device)
1546{
1547 QString name = device->getDeviceName();
1548 appendLogText(i18n("%1 filter is online.", name));
1549
1550 createFilterManager(device);
1551
1552 emit newDevice(name, device->getDriverInterface());
1553}
1554
1555void Manager::addFocuser(ISD::Focuser *device)
1556{
1557 appendLogText(i18n("%1 focuser is online.", device->getDeviceName()));
1558
1559 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1560}
1561
1562void Manager::addRotator(ISD::Rotator *device)
1563{
1564 appendLogText(i18n("Rotator %1 is online.", device->getDeviceName()));
1565
1566 // createRotatorControl(device);
1567
1568 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1569}
1570
1571void Manager::addDome(ISD::Dome * device)
1572{
1573 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1574
1575 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1576}
1577
1578void Manager::addWeather(ISD::Weather * device)
1579{
1580 appendLogText(i18n("%1 Weather is online.", device->getDeviceName()));
1581
1582 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1583}
1584
1585void Manager::addGPS(ISD::GPS * device)
1586{
1587 appendLogText(i18n("%1 GPS is online.", device->getDeviceName()));
1588
1589 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1590}
1591
1592void Manager::addDustCap(ISD::DustCap * device)
1593{
1594 OpticalTrainManager::Instance()->syncDevices();
1595
1596 appendLogText(i18n("%1 Dust cap is online.", device->getDeviceName()));
1597
1598 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1599}
1600
1601void Manager::addLightBox(ISD::LightBox * device)
1602{
1603 appendLogText(i18n("%1 Light box is online.", device->getDeviceName()));
1604
1605 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1606}
1607
1608void Manager::syncGenericDevice(const QSharedPointer<ISD::GenericDevice> &device)
1609{
1610 createModules(device);
1611
1612 ////////////////////////////////////////////////////////////////////////////////////////////////////
1613 /// Cameras
1614 ////////////////////////////////////////////////////////////////////////////////////////////////////
1615 auto camera = device->getCamera();
1616 if (camera)
1617 {
1618 // Focus Module
1619 if (focusProcess)
1620 {
1621 if (camera->hasCooler())
1622 {
1624 if (INDIListener::findDevice(camera->getDeviceName(), generic))
1625 focusModule()->addTemperatureSource(generic);
1626 }
1627 }
1628
1629 }
1630
1631 ////////////////////////////////////////////////////////////////////////////////////////////////////
1632 /// Mount
1633 ////////////////////////////////////////////////////////////////////////////////////////////////////
1634 auto mount = device->getMount();
1635 if (mount)
1636 {
1637 if (mountProcess)
1638 {
1640 if (INDIListener::findDevice(mount->getDeviceName(), generic))
1641 {
1642 mountModule()->addTimeSource(generic);
1643 mountModule()->addLocationSource(generic);
1644 }
1645 }
1646
1647 }
1648
1649 ////////////////////////////////////////////////////////////////////////////////////////////////////
1650 /// Focuser
1651 ////////////////////////////////////////////////////////////////////////////////////////////////////
1652 auto focuser = device->getFocuser();
1653 if (focuser)
1654 {
1655 if (focusProcess)
1656 {
1657 // Temperature sources.
1659 if (INDIListener::findDevice(focuser->getDeviceName(), generic))
1660 focusModule()->addTemperatureSource(generic);
1661 }
1662 }
1663
1664 ////////////////////////////////////////////////////////////////////////////////////////////////////
1665 /// Filter Wheel
1666 ////////////////////////////////////////////////////////////////////////////////////////////////////
1667
1668 ////////////////////////////////////////////////////////////////////////////////////////////////////
1669 /// Rotators
1670 ////////////////////////////////////////////////////////////////////////////////////////////////////
1671
1672 ////////////////////////////////////////////////////////////////////////////////////////////////////
1673 /// Domes
1674 ////////////////////////////////////////////////////////////////////////////////////////////////////
1675 auto dome = device->getDome();
1676 if (dome)
1677 {
1678 if (captureProcess)
1679 captureProcess->setDome(dome);
1680 if (alignProcess)
1681 alignProcess->setDome(dome);
1682 if (observatoryProcess)
1683 observatoryProcess->setDome(dome);
1684 }
1685
1686 ////////////////////////////////////////////////////////////////////////////////////////////////////
1687 /// Weather
1688 ////////////////////////////////////////////////////////////////////////////////////////////////////
1689 auto weather = device->getWeather();
1690 if (weather)
1691 {
1692 if (observatoryProcess)
1693 observatoryProcess->addWeatherSource(weather);
1694
1695 if (focusProcess)
1696 {
1698 if (INDIListener::findDevice(weather->getDeviceName(), generic))
1699 focusModule()->addTemperatureSource(generic);
1700 }
1701 }
1702
1703 ////////////////////////////////////////////////////////////////////////////////////////////////////
1704 /// GPS
1705 ////////////////////////////////////////////////////////////////////////////////////////////////////
1706 auto gps = device->getGPS();
1707 if (gps)
1708 {
1709 if (mountProcess)
1710 {
1712 if (INDIListener::findDevice(gps->getDeviceName(), generic))
1713 {
1714 mountModule()->addTimeSource(generic);
1715 mountModule()->addLocationSource(generic);
1716 }
1717 }
1718
1719 }
1720}
1721
1722void Manager::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
1723{
1724 if (alignProcess)
1725 alignModule()->removeDevice(device);
1726 if (captureProcess)
1727 captureProcess->removeDevice(device);
1728 if (focusProcess)
1729 focusModule()->removeDevice(device);
1730 if (mountProcess)
1731 mountModule()->removeDevice(device);
1732 if (guideProcess)
1733 guideProcess->removeDevice(device);
1734 if (observatoryProcess)
1735 observatoryProcess->removeDevice(device);
1736 if (m_PortSelector)
1737 m_PortSelector->removeDevice(device->getDeviceName());
1738
1739 DarkLibrary::Instance()->removeDevice(device);
1740
1741 // Remove from filter managers
1742 for (auto &oneManager : m_FilterManagers)
1743 {
1744 oneManager->removeDevice(device);
1745 }
1746
1747 // Remove from rotator controllers
1748 for (auto &oneController : m_RotatorControllers)
1749 {
1750 oneController->close();
1751 }
1752
1753 appendLogText(i18n("%1 is offline.", device->getDeviceName()));
1754
1755
1756 if (INDIListener::devices().isEmpty())
1757 {
1758 cleanDevices();
1759 removeTabs();
1760 }
1761}
1762
1763void Manager::processDeleteProperty(INDI::Property prop)
1764{
1765 ekosLiveClient.get()->message()->processDeleteProperty(prop);
1766}
1767
1768void Manager::processMessage(int id)
1769{
1770 auto origin = static_cast<ISD::GenericDevice *>(sender());
1771 // Shouldn't happen
1772 if (!origin)
1773 return;
1775 if (!INDIListener::findDevice(origin->getDeviceName(), device))
1776 return;
1777
1778 ekosLiveClient.get()->message()->processMessage(device, id);
1779}
1780
1781void Manager::processUpdateProperty(INDI::Property prop)
1782{
1783 ekosLiveClient.get()->message()->processUpdateProperty(prop);
1784
1785 if (prop.isNameMatch("CCD_INFO") ||
1786 prop.isNameMatch("GUIDER_INFO") ||
1787 prop.isNameMatch("CCD_FRAME") ||
1788 prop.isNameMatch("GUIDER_FRAME"))
1789 {
1790 if (focusModule() != nullptr)
1791 focusModule()->syncCameraInfo();
1792
1793 if (guideModule() != nullptr)
1794 guideModule()->syncCameraInfo();
1795
1796 if (alignModule() != nullptr)
1797 alignModule()->syncCameraInfo();
1798
1799 return;
1800 }
1801}
1802
1803void Manager::processNewProperty(INDI::Property prop)
1804{
1806 if (!INDIListener::findDevice(prop.getDeviceName(), device))
1807 return;
1808
1809 settleTimer.start();
1810
1811 ekosLiveClient.get()->message()->processNewProperty(prop);
1812
1813 if (prop.isNameMatch("DEVICE_PORT_SCAN") || prop.isNameMatch("CONNECTION_TYPE"))
1814 {
1815 if (!m_PortSelector)
1816 {
1817 m_PortSelector.reset(new Selector::Dialog(KStars::Instance()));
1818 connect(m_PortSelector.get(), &Selector::Dialog::accepted, this, &Manager::setPortSelectionComplete);
1819 }
1820 m_PortSelectorTimer.start();
1821 portSelectorB->setEnabled(true);
1822 m_PortSelector->addDevice(device);
1823 return;
1824 }
1825
1826 // Check if we need to turn on DEBUG for logging purposes
1827 if (prop.isNameMatch("DEBUG"))
1828 {
1829 uint16_t interface = device->getDriverInterface();
1830 if ( opsLogs->getINDIDebugInterface() & interface )
1831 {
1832 // Check if we need to enable debug logging for the INDI drivers.
1833 auto debugSP = prop.getSwitch();
1834 debugSP->at(0)->setState(ISS_ON);
1835 debugSP->at(1)->setState(ISS_OFF);
1836 device->sendNewProperty(debugSP);
1837 }
1838 return;
1839 }
1840
1841 // Handle debug levels for logging purposes
1842 if (prop.isNameMatch("DEBUG_LEVEL"))
1843 {
1844 uint16_t interface = device->getDriverInterface();
1845 // Check if the logging option for the specific device class is on and if the device interface matches it.
1846 if ( opsLogs->getINDIDebugInterface() & interface )
1847 {
1848 // Turn on everything
1849 auto debugLevel = prop.getSwitch();
1850 for (auto &it : *debugLevel)
1851 it.setState(ISS_ON);
1852
1853 device->sendNewProperty(debugLevel);
1854 }
1855 return;
1856 }
1857
1858 if (prop.isNameMatch("ASTROMETRY_SOLVER"))
1859 {
1860 for (auto &oneDevice : INDIListener::devices())
1861 {
1862 if (oneDevice->getDeviceName() == prop.getDeviceName())
1863 {
1864 initAlign();
1865 alignModule()->setAstrometryDevice(oneDevice);
1866 break;
1867 }
1868 }
1869
1870 return;
1871 }
1872
1873 if (focusModule() != nullptr && strstr(prop.getName(), "FOCUS_"))
1874 {
1875 focusModule()->checkFocuser();
1876 return;
1877 }
1878}
1879
1880void Manager::processTabChange()
1881{
1882 auto currentWidget = toolsWidget->currentWidget();
1883
1884 if (alignProcess && alignModule() == currentWidget)
1885 {
1886 auto alignReady = alignModule()->isEnabled() == false && alignModule()->isParserOK();
1887 auto captureReady = captureProcess && captureModule()->isEnabled();
1888 auto mountReady = mountProcess && mountModule()->isEnabled();
1889 if (alignReady && captureReady && mountReady)
1890 alignModule()->setEnabled(true);
1891
1892 alignModule()->checkCamera();
1893 }
1894 else if (captureProcess && currentWidget == captureModule())
1895 {
1896 captureModule()->process()->checkCamera();
1897 }
1898 else if (focusProcess && currentWidget == focusModule())
1899 {
1900 focusModule()->checkCamera();
1901 }
1902 else if (guideProcess && currentWidget == guideModule())
1903 {
1904 guideModule()->checkCamera();
1905 }
1906
1907 updateLog();
1908}
1909
1910void Manager::updateLog()
1911{
1912 QWidget * currentWidget = toolsWidget->currentWidget();
1913
1914 if (currentWidget == setupTab)
1915 ekosLogOut->setPlainText(m_LogText.join("\n"));
1916 else if (currentWidget == alignModule())
1917 ekosLogOut->setPlainText(alignModule()->getLogText());
1918 else if (currentWidget == captureModule())
1919 ekosLogOut->setPlainText(captureModule()->getLogText());
1920 else if (currentWidget == focusModule())
1921 ekosLogOut->setPlainText(focusModule()->getLogText());
1922 else if (currentWidget == guideModule())
1923 ekosLogOut->setPlainText(guideModule()->getLogText());
1924 else if (currentWidget == mountModule())
1925 ekosLogOut->setPlainText(mountModule()->getLogText());
1926 else if (currentWidget == schedulerModule())
1927 ekosLogOut->setPlainText(schedulerModule()->moduleState()->getLogText());
1928 else if (currentWidget == observatoryProcess.get())
1929 ekosLogOut->setPlainText(observatoryProcess->getLogText());
1930 else if (currentWidget == analyzeProcess.get())
1931 ekosLogOut->setPlainText(analyzeProcess->getLogText());
1932
1933#ifdef Q_OS_OSX
1934 repaint(); //This is a band-aid for a bug in QT 5.10.0
1935#endif
1936}
1937
1938void Manager::appendLogText(const QString &text)
1939{
1940 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
1941 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
1942
1943 qCInfo(KSTARS_EKOS) << text;
1944
1945 emit newLog(text);
1946
1947 updateLog();
1948}
1949
1950void Manager::clearLog()
1951{
1952 QWidget * currentWidget = toolsWidget->currentWidget();
1953
1954 if (currentWidget == setupTab)
1955 {
1956 m_LogText.clear();
1957 updateLog();
1958 }
1959 else if (currentWidget == alignModule())
1960 alignModule()->clearLog();
1961 else if (currentWidget == captureModule())
1962 captureModule()->clearLog();
1963 else if (currentWidget == focusModule())
1964 focusModule()->clearLog();
1965 else if (currentWidget == guideModule())
1966 guideModule()->clearLog();
1967 else if (currentWidget == mountModule())
1968 mountModule()->clearLog();
1969 else if (currentWidget == schedulerModule())
1970 schedulerModule()->moduleState()->clearLog();
1971 else if (currentWidget == observatoryProcess.get())
1972 observatoryProcess->clearLog();
1973 else if (currentWidget == analyzeProcess.get())
1974 analyzeProcess->clearLog();
1975}
1976
1977void Manager::initCapture()
1978{
1979 if (captureModule() != nullptr)
1980 return;
1981
1982 captureProcess.reset(new Capture());
1983
1984 emit newModule("Capture");
1985
1986 // retrieve the meridian flip state machine from the mount module if the module is already present
1987 if (mountModule() != nullptr)
1988 captureModule()->setMeridianFlipState(mountModule()->getMeridianFlipState());
1989
1990 capturePreview->shareCaptureModule(captureModule());
1991 int index = addModuleTab(EkosModule::Capture, captureModule(), QIcon(":/icons/ekos_ccd.png"));
1992 toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD"));
1993 if (Options::ekosLeftIcons())
1994 {
1996 trans.rotate(90);
1997 QIcon icon = toolsWidget->tabIcon(index);
1998 QPixmap pix = icon.pixmap(QSize(48, 48));
1999 icon = QIcon(pix.transformed(trans));
2000 toolsWidget->setTabIcon(index, icon);
2001 }
2002 connect(captureModule(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog);
2003 connect(captureModule(), &Ekos::Capture::newLog, ekosLiveClient.get()->message(),
2004 [this]()
2005 {
2006 QJsonObject cStatus =
2007 {
2008 {"log", captureModule()->getLogText()}
2009 };
2010
2011 ekosLiveClient.get()->message()->updateCaptureStatus(cStatus);
2012 });
2013 connect(captureModule(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus);
2014 connect(captureModule(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress);
2015 connect(captureModule(), &Ekos::Capture::driverTimedout, this, &Ekos::Manager::restartDriver);
2016 connect(captureModule(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress);
2017 capturePreview->setEnabled(true);
2018
2019 // display capture status changes
2020 connect(captureModule(), &Ekos::Capture::newFilterStatus, capturePreview->captureStatusWidget,
2021 &LedStatusWidget::setFilterState);
2022
2023 // display target drift
2024 connect(schedulerModule(), &Ekos::Scheduler::targetDistance,
2026 connect(schedulerModule(), &Ekos::Scheduler::targetDistance, this, [this](double distance)
2027 {
2028 capturePreview->updateTargetDistance(distance);
2029 });
2030
2031
2032 connectModules();
2033}
2034
2035void Manager::initAlign()
2036{
2037 if (alignModule() != nullptr)
2038 return;
2039
2040 alignProcess.reset(new Ekos::Align(m_CurrentProfile));
2041
2042 emit newModule("Align");
2043
2044 int index = addModuleTab(EkosModule::Align, alignModule(), QIcon(":/icons/ekos_align.png"));
2045 toolsWidget->tabBar()->setTabToolTip(index, i18n("Align"));
2046 connect(alignModule(), &Ekos::Align::newLog, this, &Ekos::Manager::updateLog);
2047 connect(alignModule(), &Ekos::Align::newLog, ekosLiveClient.get()->message(),
2048 [this]()
2049 {
2050 QJsonObject cStatus =
2051 {
2052 {"log", alignModule()->getLogText()}
2053 };
2054
2055 ekosLiveClient.get()->message()->updateAlignStatus(cStatus);
2056 });
2057 if (Options::ekosLeftIcons())
2058 {
2060 trans.rotate(90);
2061 QIcon icon = toolsWidget->tabIcon(index);
2062 QPixmap pix = icon.pixmap(QSize(48, 48));
2063 icon = QIcon(pix.transformed(trans));
2064 toolsWidget->setTabIcon(index, icon);
2065 }
2066
2067 connectModules();
2068}
2069
2070void Manager::initFocus()
2071{
2072 if (focusModule() != nullptr)
2073 return;
2074
2075 focusProcess.reset(new Ekos::Focus());
2076
2077 emit newModule("Focus");
2078
2079 int index = addModuleTab(EkosModule::Focus, focusModule(), QIcon(":/icons/ekos_focus.png"));
2080
2081 toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus"));
2082
2083 // Focus <---> Manager connections
2084 connect(focusModule(), &Ekos::Focus::newLog, this, &Ekos::Manager::updateLog);
2085 connect(focusModule(), &Ekos::Focus::newStatus, this, &Ekos::Manager::updateFocusStatus);
2086 connect(focusModule(), &Ekos::Focus::newStarPixmap, focusManager, &Ekos::FocusManager::updateFocusStarPixmap);
2087 connect(focusModule(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR);
2088 connect(focusModule(), &Ekos::Focus::focuserTimedout, this, &Ekos::Manager::restartDriver);
2089 connect(focusModule(), &Ekos::Focus::newLog, ekosLiveClient.get()->message(),
2090 [this]()
2091 {
2092 QJsonObject cStatus =
2093 {
2094 {"log", focusModule()->getLogText()}
2095 };
2096
2097 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2098 });
2099
2100 // connect HFR plot widget
2101 connect(focusModule(), &Ekos::Focus::initHFRPlot, focusManager->hfrVPlot, &FocusHFRVPlot::init);
2102 connect(focusModule(), &Ekos::Focus::redrawHFRPlot, focusManager->hfrVPlot, &FocusHFRVPlot::redraw);
2103 connect(focusModule(), &Ekos::Focus::newHFRPlotPosition, focusManager->hfrVPlot, &FocusHFRVPlot::addPosition);
2104 connect(focusModule(), &Ekos::Focus::drawPolynomial, focusManager->hfrVPlot, &FocusHFRVPlot::drawPolynomial);
2105 connect(focusModule(), &Ekos::Focus::setTitle, focusManager->hfrVPlot, &FocusHFRVPlot::setTitle);
2106 connect(focusModule(), &Ekos::Focus::finalUpdates, focusManager->hfrVPlot, &FocusHFRVPlot::finalUpdates);
2107 connect(focusModule(), &Ekos::Focus::minimumFound, focusManager->hfrVPlot, &FocusHFRVPlot::drawMinimum);
2108 // setup signal/slots for Linear 1 Pass focus algo
2109 connect(focusModule(), &Ekos::Focus::drawCurve, focusManager->hfrVPlot, &FocusHFRVPlot::drawCurve);
2110 connect(focusModule(), &Ekos::Focus::drawCFZ, focusManager->hfrVPlot, &FocusHFRVPlot::drawCFZ);
2111
2112 if (Options::ekosLeftIcons())
2113 {
2115 trans.rotate(90);
2116 QIcon icon = toolsWidget->tabIcon(index);
2117 QPixmap pix = icon.pixmap(QSize(48, 48));
2118 icon = QIcon(pix.transformed(trans));
2119 toolsWidget->setTabIcon(index, icon);
2120 }
2121
2122 focusManager->init();
2123 focusManager->setEnabled(true);
2124
2125 for (auto &oneDevice : INDIListener::devices())
2126 {
2127 auto prop1 = oneDevice->getProperty("CCD_TEMPERATURE");
2128 auto prop2 = oneDevice->getProperty("FOCUSER_TEMPERATURE");
2129 auto prop3 = oneDevice->getProperty("WEATHER_PARAMETERS");
2130 if (prop1 || prop2 || prop3)
2131 focusModule()->addTemperatureSource(oneDevice);
2132 }
2133
2134 connectModules();
2135}
2136
2137void Manager::updateCurrentHFR(double newHFR, int position, bool inAutofocus)
2138{
2140 focusManager->updateCurrentHFR(newHFR);
2141
2143 {
2144 {"hfr", newHFR},
2145 {"pos", position}
2146 };
2147
2148 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2149}
2150
2151void Manager::updateSigmas(double ra, double de)
2152{
2153 guideManager->updateSigmas(ra, de);
2154
2155 QJsonObject cStatus = { {"rarms", ra}, {"derms", de} };
2156
2157 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2158}
2159
2160void Manager::initMount()
2161{
2162 if (mountModule() != nullptr)
2163 return;
2164
2165 mountProcess.reset(new Ekos::Mount());
2166
2167 // share the meridian flip state with capture if the module is already present
2168 if (captureModule() != nullptr)
2169 captureModule()->setMeridianFlipState(mountModule()->getMeridianFlipState());
2170
2171 emit newModule("Mount");
2172
2173 int index = addModuleTab(EkosModule::Mount, mountModule(), QIcon(":/icons/ekos_mount.png"));
2174
2175 toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount"));
2176 connect(mountModule(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog);
2177 connect(mountModule(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords);
2178 connect(mountModule(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus);
2179 connect(mountModule(), &Ekos::Mount::newTargetName, this, [this](const QString & name)
2180 {
2181 setTarget(name);
2182 });
2183 connect(mountModule(), &Ekos::Mount::pierSideChanged, this, [&](ISD::Mount::PierSide side)
2184 {
2185 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"pierSide", side}}));
2186 });
2187 connect(mountModule()->getMeridianFlipState().get(),
2188 &Ekos::MeridianFlipState::newMountMFStatus, [&](MeridianFlipState::MeridianFlipMountState status)
2189 {
2190 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject(
2191 {
2192 {"meridianFlipStatus", status},
2193 }));
2194 });
2195 connect(mountModule()->getMeridianFlipState().get(),
2196 &Ekos::MeridianFlipState::newMeridianFlipMountStatusText, [&](const QString & text)
2197 {
2198 // Throttle this down
2199 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject(
2200 {
2201 {"meridianFlipText", text},
2202 }), mountModule()->getMeridianFlipState()->getMeridianFlipMountState() == MeridianFlipState::MOUNT_FLIP_NONE);
2203 meridianFlipStatusWidget->setStatus(text);
2204 });
2205 connect(mountModule(), &Ekos::Mount::autoParkCountdownUpdated, this, [&](const QString & text)
2206 {
2207 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"autoParkCountdown", text}}), true);
2208 });
2209
2210 connect(mountModule(), &Ekos::Mount::trainChanged, ekosLiveClient.get()->message(),
2211 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
2212
2213 connect(mountModule(), &Ekos::Mount::slewRateChanged, this, [&](int slewRate)
2214 {
2215 QJsonObject status = { { "slewRate", slewRate} };
2216 ekosLiveClient.get()->message()->updateMountStatus(status);
2217 });
2218
2219 if (Options::ekosLeftIcons())
2220 {
2222 trans.rotate(90);
2223 QIcon icon = toolsWidget->tabIcon(index);
2224 QPixmap pix = icon.pixmap(QSize(48, 48));
2225 icon = QIcon(pix.transformed(trans));
2226 toolsWidget->setTabIcon(index, icon);
2227 }
2228
2229 mountGroup->setEnabled(true);
2230 capturePreview->shareMountModule(mountModule());
2231
2232 connectModules();
2233}
2234
2235void Manager::initGuide()
2236{
2237 if (guideModule() == nullptr)
2238 {
2239 guideProcess.reset(new Ekos::Guide());
2240
2241 emit newModule("Guide");
2242 }
2243
2244 if (toolsWidget->indexOf(guideModule()) == -1)
2245 {
2246 // if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices.value(KSTARS_TELESCOPE)->isConnected())
2247 // guideProcess->addMount(managedDevices.value(KSTARS_TELESCOPE));
2248
2249 int index = addModuleTab(EkosModule::Guide, guideModule(), QIcon(":/icons/ekos_guide.png"));
2250 toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide"));
2251 connect(guideModule(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog);
2252 connect(guideModule(), &Ekos::Guide::driverTimedout, this, &Ekos::Manager::restartDriver);
2253
2254 guideManager->setEnabled(true);
2255
2256 connect(guideModule(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus);
2257 connect(guideModule(), &Ekos::Guide::newStarPixmap, guideManager, &Ekos::GuideManager::updateGuideStarPixmap);
2258 connect(guideModule(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas);
2259 connect(guideModule(), &Ekos::Guide::newAxisDelta, [&](double ra, double de)
2260 {
2261 QJsonObject status = { { "drift_ra", ra}, {"drift_de", de} };
2262 ekosLiveClient.get()->message()->updateGuideStatus(status);
2263 });
2264 connect(guideModule(), &Ekos::Guide::newLog, ekosLiveClient.get()->message(),
2265 [this]()
2266 {
2267 QJsonObject cStatus =
2268 {
2269 {"log", guideModule()->getLogText()}
2270 };
2271
2272 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2273 });
2274
2275 if (Options::ekosLeftIcons())
2276 {
2278 trans.rotate(90);
2279 QIcon icon = toolsWidget->tabIcon(index);
2280 QPixmap pix = icon.pixmap(QSize(48, 48));
2281 icon = QIcon(pix.transformed(trans));
2282 toolsWidget->setTabIcon(index, icon);
2283 }
2284 guideManager->init(guideModule());
2285 }
2286
2287 connectModules();
2288}
2289
2290void Manager::initObservatory()
2291{
2292 if (observatoryProcess.get() == nullptr)
2293 {
2294 // Initialize the Observatory Module
2295 observatoryProcess.reset(new Ekos::Observatory());
2296
2297 emit newModule("Observatory");
2298
2299 int index = addModuleTab(EkosModule::Observatory, observatoryProcess.get(), QIcon(":/icons/ekos_observatory.png"));
2300 toolsWidget->tabBar()->setTabToolTip(index, i18n("Observatory"));
2301 connect(observatoryProcess.get(), &Ekos::Observatory::newLog, this, &Ekos::Manager::updateLog);
2302
2303 if (Options::ekosLeftIcons())
2304 {
2306 trans.rotate(90);
2307 QIcon icon = toolsWidget->tabIcon(index);
2308 QPixmap pix = icon.pixmap(QSize(48, 48));
2309 icon = QIcon(pix.transformed(trans));
2310 toolsWidget->setTabIcon(index, icon);
2311 }
2312 }
2313}
2314
2315void Manager::addGuider(ISD::Guider * device)
2316{
2317 appendLogText(i18n("Guider port from %1 is ready.", device->getDeviceName()));
2318}
2319
2320void Manager::removeTabs()
2321{
2322 disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange);
2323
2324 for (int i = numPermanentTabs; i < toolsWidget->count(); i++)
2325 toolsWidget->removeTab(i);
2326
2327 alignProcess.reset();
2328 captureProcess.reset();
2329 focusProcess.reset();
2330 guideProcess.reset();
2331 mountProcess.reset();
2332 observatoryProcess.reset();
2333
2334 connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection);
2335}
2336
2337bool Manager::isRunning(const QString &process)
2338{
2339 QProcess ps;
2340#ifdef Q_OS_OSX
2341 ps.start("pgrep", QStringList() << process);
2342 ps.waitForFinished();
2343 QString output = ps.readAllStandardOutput();
2344 return output.length() > 0;
2345#else
2346 ps.start("ps", QStringList() << "-o"
2347 << "comm"
2348 << "--no-headers"
2349 << "-C" << process);
2350 ps.waitForFinished();
2351 QString output = ps.readAllStandardOutput();
2352 return output.contains(process);
2353#endif
2354}
2355
2356void Manager::addObjectToScheduler(SkyObject * object)
2357{
2358 if (schedulerModule() != nullptr)
2359 schedulerModule()->addObject(object);
2360}
2361
2362QString Manager::getCurrentJobName()
2363{
2364 return schedulerModule()->getCurrentJobName();
2365}
2366
2367bool Manager::setProfile(const QString &profileName)
2368{
2369 int index = profileCombo->findText(profileName);
2370
2371 if (index < 0)
2372 return false;
2373
2374 profileCombo->setCurrentIndex(index);
2375
2376 return true;
2377}
2378
2379void Manager::editNamedProfile(const QJsonObject &profileInfo)
2380{
2381 ProfileEditor editor(this);
2382 setProfile(profileInfo["name"].toString());
2383 if (getCurrentProfile(m_CurrentProfile))
2384 {
2385 editor.setPi(m_CurrentProfile);
2386 editor.setSettings(profileInfo);
2387 editor.saveProfile();
2388 }
2389}
2390
2391void Manager::addNamedProfile(const QJsonObject &profileInfo)
2392{
2393 ProfileEditor editor(this);
2394
2395 editor.setSettings(profileInfo);
2396 editor.saveProfile();
2397 profiles.clear();
2398 loadProfiles();
2399 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2400 getCurrentProfile(m_CurrentProfile);
2401}
2402
2403void Manager::deleteNamedProfile(const QString &name)
2404{
2405 if (!getCurrentProfile(m_CurrentProfile))
2406 return;
2407
2408 for (auto &pi : profiles)
2409 {
2410 // Do not delete an actively running profile
2411 // Do not delete simulator profile
2412 if (pi->name == "Simulators" || pi->name != name || (pi.get() == m_CurrentProfile && ekosStatus() != Idle))
2413 continue;
2414
2415 KStarsData::Instance()->userdb()->PurgeProfile(pi);
2416 profiles.clear();
2417 loadProfiles();
2418 getCurrentProfile(m_CurrentProfile);
2419 return;
2420 }
2421}
2422
2423QJsonObject Manager::getNamedProfile(const QString &name)
2424{
2426
2427 // Get current profile
2428 for (auto &pi : profiles)
2429 {
2430 if (name == pi->name)
2431 return pi->toJson();
2432 }
2433
2434 return QJsonObject();
2435}
2436
2437QStringList Manager::getProfiles()
2438{
2439 QStringList profiles;
2440
2441 for (int i = 0; i < profileCombo->count(); i++)
2442 profiles << profileCombo->itemText(i);
2443
2444 return profiles;
2445}
2446
2447void Manager::addProfile()
2448{
2449 ProfileEditor editor(this);
2450
2451 if (editor.exec() == QDialog::Accepted)
2452 {
2453 profiles.clear();
2454 loadProfiles();
2455 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2456 }
2457
2458 getCurrentProfile(m_CurrentProfile);
2459}
2460
2461void Manager::editProfile()
2462{
2463 ProfileEditor editor(this);
2464
2465 if (getCurrentProfile(m_CurrentProfile))
2466 {
2467
2468 editor.setPi(m_CurrentProfile);
2469
2470 if (editor.exec() == QDialog::Accepted)
2471 {
2472 int currentIndex = profileCombo->currentIndex();
2473
2474 profiles.clear();
2475 loadProfiles();
2476 profileCombo->setCurrentIndex(currentIndex);
2477 }
2478
2479 getCurrentProfile(m_CurrentProfile);
2480 }
2481}
2482
2483void Manager::deleteProfile()
2484{
2485 if (!getCurrentProfile(m_CurrentProfile))
2486 return;
2487
2488 if (m_CurrentProfile->name == "Simulators")
2489 return;
2490
2491 auto executeDeleteProfile = [&]()
2492 {
2493 KStarsData::Instance()->userdb()->PurgeProfile(m_CurrentProfile);
2494 profiles.clear();
2495 loadProfiles();
2496 getCurrentProfile(m_CurrentProfile);
2497 };
2498
2499 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeDeleteProfile]()
2500 {
2501 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
2502 KSMessageBox::Instance()->disconnect(this);
2504 });
2505
2506 KSMessageBox::Instance()->questionYesNo(i18n("Are you sure you want to delete the profile?"),
2507 i18n("Confirm Delete"));
2508
2509}
2510
2511void Manager::wizardProfile()
2512{
2513 ProfileWizard wz;
2514 if (wz.exec() != QDialog::Accepted)
2515 return;
2516
2517 ProfileEditor editor(this);
2518
2519 editor.setProfileName(wz.profileName);
2520 editor.setAuxDrivers(wz.selectedAuxDrivers());
2521 if (wz.useInternalServer == false)
2522 editor.setHostPort(wz.host, wz.port);
2523 editor.setWebManager(wz.useWebManager);
2524 editor.setGuiderType(wz.selectedExternalGuider());
2525 // Disable connection options
2526 editor.setConnectionOptionsEnabled(false);
2527
2528 if (editor.exec() == QDialog::Accepted)
2529 {
2530 profiles.clear();
2531 loadProfiles();
2532 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2533 }
2534
2535 getCurrentProfile(m_CurrentProfile);
2536}
2537
2538bool Manager::getCurrentProfile(QSharedPointer<ProfileInfo> &profile) const
2539{
2540 // Get current profile
2541 for (auto &pi : profiles)
2542 {
2543 if (profileCombo->currentText() == pi->name)
2544 {
2545 profile = pi;
2546 return true;
2547 }
2548 }
2549
2550 return false;
2551}
2552
2553void Manager::updateProfileLocation(const QSharedPointer<ProfileInfo> &profile)
2554{
2555 if (profile->city.isEmpty() == false)
2556 {
2557 bool cityFound = KStars::Instance()->setGeoLocation(profile->city, profile->province, profile->country);
2558 if (cityFound)
2559 appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName()));
2560 else
2561 appendLogText(i18n("Failed to update site location to %1. City not found.",
2562 KStarsData::Instance()->geo()->fullName()));
2563 }
2564}
2565
2566void Manager::updateMountStatus(ISD::Mount::Status status)
2567{
2568 static ISD::Mount::Status lastStatus = ISD::Mount::MOUNT_IDLE;
2569
2570 if (status == lastStatus)
2571 return;
2572
2574
2575 mountStatus->setMountState(mountModule()->statusString(), status);
2576 mountStatus->setStyleSheet(QString());
2577
2579 {
2580 {"status", mountModule()->statusString(false)}
2581 };
2582
2583 ekosLiveClient.get()->message()->updateMountStatus(cStatus);
2584}
2585
2586void Manager::updateMountCoords(const SkyPoint position, ISD::Mount::PierSide pierSide, const dms &ha)
2587{
2588 Q_UNUSED(pierSide)
2589 raOUT->setText(position.ra().toHMSString());
2590 decOUT->setText(position.dec().toDMSString());
2591 azOUT->setText(position.az().toDMSString());
2592 altOUT->setText(position.alt().toDMSString());
2593
2595 {
2596 {"ra", dms::fromString(raOUT->text(), false).Degrees()},
2597 {"de", dms::fromString(decOUT->text(), true).Degrees()},
2598 {"ra0", position.ra0().Degrees()},
2599 {"de0", position.dec0().Degrees()},
2600 {"az", dms::fromString(azOUT->text(), true).Degrees()},
2601 {"at", dms::fromString(altOUT->text(), true).Degrees()},
2602 {"ha", ha.Degrees()},
2603 };
2604
2605 ekosLiveClient.get()->message()->updateMountStatus(cStatus, true);
2606}
2607
2608void Manager::updateCaptureStatus(Ekos::CaptureState status)
2609{
2610 capturePreview->updateCaptureStatus(status, captureModule()->isActiveJobPreview());
2611
2612 switch (status)
2613 {
2614 case Ekos::CAPTURE_IDLE:
2615 /* Fall through */
2617 /* Fall through */
2619 m_CountdownTimer.stop();
2620 break;
2622 m_CountdownTimer.start();
2623 break;
2624 default:
2625 break;
2626 }
2627
2629 {
2630 {"status", captureStates[status]},
2631 {"seqt", capturePreview->captureCountsWidget->sequenceRemainingTime->text()},
2632 {"ovt", capturePreview->captureCountsWidget->overallRemainingTime->text()}
2633 };
2634
2635 ekosLiveClient.get()->message()->updateCaptureStatus(cStatus);
2636}
2637
2638void Manager::updateCaptureProgress(Ekos::SequenceJob * job, const QSharedPointer<FITSData> &data)
2639{
2640 capturePreview->updateJobProgress(job, data);
2641
2643 {
2644 {"seqv", job->getCompleted()},
2645 {"seqr", job->getCoreProperty(SequenceJob::SJ_Count).toInt()},
2646 {"seql", capturePreview->captureCountsWidget->sequenceRemainingTime->text()}
2647 };
2648
2649 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2650
2651 if (data && job->getStatus() == JOB_BUSY)
2652 {
2653 // Normally FITS Viewer would trigger an upload
2654 // If off, then rely on summary view or raw data
2655 if (Options::useFITSViewer() == false)
2656 ekosLiveClient.get()->media()->sendData(data, data->objectName());
2657
2658 if (job->jobType() != SequenceJob::JOBTYPE_PREVIEW)
2659 ekosLiveClient.get()->cloud()->upload(data, data->objectName());
2660 }
2661}
2662
2663void Manager::updateExposureProgress(Ekos::SequenceJob * job)
2664{
2666 {
2667 {"expv", job->getExposeLeft()},
2668 {"expr", job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()}
2669 };
2670
2671 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2672}
2673
2674void Manager::updateCaptureCountDown()
2675{
2676 capturePreview->updateCaptureCountDown(-1);
2677
2679 {
2680 {"seqt", capturePreview->captureCountsWidget->sequenceRemainingTime->text()},
2681 {"ovt", capturePreview->captureCountsWidget->overallRemainingTime->text()},
2682 {"ovp", capturePreview->captureCountsWidget->gr_overallProgressBar->value()},
2683 {"ovl", capturePreview->captureCountsWidget->gr_overallLabel->text()}
2684 };
2685
2686 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2687}
2688
2689
2690void Manager::updateFocusStatus(Ekos::FocusState status)
2691{
2692 focusManager->updateFocusStatus(status);
2693
2695 {
2696 {"status", getFocusStatusString(status, false)}
2697 };
2698
2699 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2700}
2701
2702void Manager::updateGuideStatus(Ekos::GuideState status)
2703{
2704 guideManager->updateGuideStatus(status);
2706 {
2707 {"status", getGuideStatusString(status, false)}
2708 };
2709
2710 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2711}
2712
2713void Manager::setTarget(const QString &name)
2714{
2715 capturePreview->targetLabel->setVisible(!name.isEmpty());
2716 capturePreview->mountTarget->setVisible(!name.isEmpty());
2717 capturePreview->mountTarget->setText(name);
2718 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", name}}));
2719}
2720
2721void Manager::showEkosOptions()
2722{
2723 QWidget * currentWidget = toolsWidget->currentWidget();
2724
2725 if (alignModule() && alignModule() == currentWidget)
2726 {
2728 if (alignSettings)
2729 {
2730 alignSettings->setEnabled(true);
2731 alignSettings->show();
2732 }
2733 return;
2734 }
2735
2736 if (guideModule() && guideModule() == currentWidget)
2737 {
2738 KConfigDialog::showDialog("guidesettings");
2739 return;
2740 }
2741
2742 if (focusModule() && focusModule() == currentWidget)
2743 {
2745 if (focusSettings)
2746 {
2747 focusSettings->show();
2748 focusSettings->raise();
2749 }
2750 return;
2751 }
2752
2753 const bool isCapture = (captureModule() && captureModule() == currentWidget);
2754 const bool isScheduler = (schedulerModule() && schedulerModule() == currentWidget);
2755 const bool isAnalyze = (analyzeProcess.get() && analyzeProcess.get() == currentWidget);
2757 {
2758 if (opsEkos)
2759 {
2760 int index = 0;
2761 if (isScheduler) index = 1;
2762 else if (isCapture) index = 2;
2763 else if (isAnalyze) index = 3;
2764 opsEkos->setCurrentIndex(index);
2765 }
2767 if (cDialog)
2768 {
2769 cDialog->setCurrentPage(ekosOptionsWidget);
2770 cDialog->show();
2771 cDialog->raise(); // for MacOS
2772 cDialog->activateWindow(); // for Windows
2773 }
2774 return;
2775 }
2776
2777 if (ekosOptionsWidget == nullptr)
2778 {
2779 optionsB->click();
2780 }
2781 else if (KConfigDialog::showDialog("settings"))
2782 {
2784 if (cDialog)
2785 {
2786 cDialog->setCurrentPage(ekosOptionsWidget);
2787 cDialog->show();
2788 cDialog->raise(); // for MacOS
2789 cDialog->activateWindow(); // for Windows
2790 }
2791 }
2792}
2793
2794void Manager::updateDebugInterfaces()
2795{
2797
2798 for (auto &device : INDIListener::devices())
2799 {
2800 auto debugProp = device->getProperty("DEBUG");
2801 if (!debugProp)
2802 continue;
2803
2804 auto debugSP = debugProp.getSwitch();
2805
2806 // Check if the debug interface matches the driver device class
2807 if ( ( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) &&
2808 debugSP->sp[0].s != ISS_ON)
2809 {
2810 debugSP->at(0)->setState(ISS_ON);
2811 debugSP->at(1)->setState(ISS_OFF);
2812 device->sendNewProperty(debugSP);
2813 appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName()));
2814 }
2815 else if ( !( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) &&
2816 debugSP->sp[0].s != ISS_OFF)
2817 {
2818 debugSP->at(0)->setState(ISS_OFF);
2819 debugSP->at(1)->setState(ISS_ON);
2820 device->sendNewProperty(debugSP);
2821 appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName()));
2822 }
2823
2824 if (opsLogs->isINDISettingsChanged())
2825 device->setConfig(SAVE_CONFIG);
2826 }
2827}
2828
2829void Manager::watchDebugProperty(INDI::Property prop)
2830{
2831 if (prop.isNameMatch("DEBUG"))
2832 {
2833 auto svp = prop.getSwitch();
2834
2836
2837 // We don't process pure general interfaces
2838 if (deviceInterface->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE)
2839 return;
2840
2841 // If debug was turned off, but our logging policy requires it then turn it back on.
2842 // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings
2843 if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF &&
2844 (opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface()))
2845 {
2846 svp->sp[0].s = ISS_ON;
2847 svp->sp[1].s = ISS_OFF;
2848 deviceInterface->sendNewProperty(svp);
2849 appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName()));
2850 }
2851 // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings.
2852 // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in
2853 // the log settings, then if the user turns off only CCD logging, the debug logging is NOT
2854 // turned off until he turns off Filter Wheel logging as well.
2855 else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON
2856 && !(opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface()))
2857 {
2858 svp->sp[0].s = ISS_OFF;
2859 svp->sp[1].s = ISS_ON;
2860 deviceInterface->sendNewProperty(svp);
2861 appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName()));
2862 }
2863 }
2864}
2865
2866void Manager::announceEvent(const QString &message, KSNotification::EventSource source, KSNotification::EventType event)
2867{
2868 ekosLiveClient.get()->message()->sendEvent(message, source, event);
2869}
2870
2871void Manager::connectModules()
2872{
2873 // Dark Library
2874 connect(DarkLibrary::Instance(), &DarkLibrary::newImage, ekosLiveClient.get()->media(),
2875 &EkosLive::Media::sendDarkLibraryData, Qt::UniqueConnection);
2876 connect(DarkLibrary::Instance(), &DarkLibrary::trainChanged, ekosLiveClient.get()->message(),
2877 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
2878 connect(DarkLibrary::Instance(), &DarkLibrary::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
2880 connect(DarkLibrary::Instance(), &DarkLibrary::settingsUpdated, ekosLiveClient.get()->message(),
2881 &EkosLive::Message::sendGuideSettings, Qt::UniqueConnection);
2882
2883 // Guide <---> Capture connections
2884 if (captureProcess && guideProcess)
2885 {
2886 // captureProcess.get()->disconnect(guideProcess.get());
2887 // guideProcess.get()->disconnect(captureProcess.get());
2888
2889 // Guide Limits
2890 connect(guideModule(), &Ekos::Guide::newStatus, captureModule(), &Ekos::Capture::setGuideStatus,
2892 connect(guideModule(), &Ekos::Guide::newAxisDelta, captureModule(), &Ekos::Capture::setGuideDeviation,
2894
2895 // Dithering
2896 connect(captureModule(), &Ekos::Capture::newStatus, guideModule(), &Ekos::Guide::setCaptureStatus,
2898
2899 // Guide Head
2900 connect(captureModule(), &Ekos::Capture::suspendGuiding, guideModule(), &Ekos::Guide::suspend,
2902 connect(captureModule(), &Ekos::Capture::resumeGuiding, guideModule(), &Ekos::Guide::resume,
2904 connect(guideModule(), &Ekos::Guide::guideChipUpdated, captureModule(), &Ekos::Capture::setGuideChip,
2906
2907 // Meridian Flip
2908 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, guideModule(), &Ekos::Guide::abort,
2910 connect(captureModule(), &Ekos::Capture::guideAfterMeridianFlip, guideModule(),
2911 &Ekos::Guide::guideAfterMeridianFlip, Qt::UniqueConnection);
2912 }
2913
2914 // Guide <---> Mount connections
2915 if (guideProcess && mountProcess)
2916 {
2917 // Parking
2918 connect(mountModule(), &Ekos::Mount::newStatus, guideModule(), &Ekos::Guide::setMountStatus,
2920 connect(mountModule(), &Ekos::Mount::newCoords, guideModule(), &Ekos::Guide::setMountCoords,
2922
2923 }
2924
2925 // Focus <---> Guide connections
2926 if (guideProcess && focusProcess)
2927 {
2928 // Suspend
2929 connect(focusModule(), &Ekos::Focus::suspendGuiding, guideModule(), &Ekos::Guide::suspend, Qt::UniqueConnection);
2930 connect(focusModule(), &Ekos::Focus::resumeGuiding, guideModule(), &Ekos::Guide::resume, Qt::UniqueConnection);
2931 }
2932
2933 // Capture <---> Focus connections
2934 if (captureProcess && focusProcess)
2935 {
2936 // Check focus HFR value and if above threshold parameter, run autoFocus
2937 connect(captureModule(), &Ekos::Capture::checkFocus, focusModule(), &Ekos::Focus::checkFocus,
2939
2940 // Run autoFocus
2941 connect(captureProcess.get(), &Ekos::Capture::runAutoFocus, focusProcess.get(), &Ekos::Focus::runAutoFocus,
2943
2944 // Reset Focus
2945 connect(captureModule(), &Ekos::Capture::resetFocus, focusModule(), &Ekos::Focus::resetFrame,
2947
2948 // Abort Focus
2949 connect(captureModule(), &Ekos::Capture::abortFocus, focusModule(), &Ekos::Focus::abort,
2951
2952 // New Focus Status
2953 connect(focusModule(), &Ekos::Focus::newStatus, captureModule(), &Ekos::Capture::setFocusStatus,
2955
2956 // Perform adaptive focus
2957 connect(captureModule(), &Ekos::Capture::adaptiveFocus, focusModule(), &Ekos::Focus::adaptiveFocus,
2959
2960 // New Adaptive Focus Status
2961 connect(focusModule(), &Ekos::Focus::focusAdaptiveComplete, captureModule(),
2964
2965 // New Focus HFR
2966 connect(focusModule(), &Ekos::Focus::newHFR, captureModule(), &Ekos::Capture::setHFR, Qt::UniqueConnection);
2967
2968 // New Focus temperature delta
2969 connect(focusModule(), &Ekos::Focus::newFocusTemperatureDelta, captureModule(),
2971
2972 // Meridian Flip
2973 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, focusModule(), &Ekos::Focus::meridianFlipStarted,
2975 }
2976
2977 // Capture <---> Align connections
2978 if (captureProcess && alignProcess)
2979 {
2980 // Alignment flag
2981 connect(alignModule(), &Ekos::Align::newStatus, captureModule(), &Ekos::Capture::setAlignStatus,
2983 // Solver data
2984 connect(alignModule(), &Ekos::Align::newSolverResults, captureModule(), &Ekos::Capture::setAlignResults,
2986 // Capture Status
2987 connect(captureModule(), &Ekos::Capture::newStatus, alignModule(), &Ekos::Align::setCaptureStatus,
2989 }
2990
2991 // Capture <---> Mount connections
2992 if (captureProcess && mountProcess)
2993 {
2994 // Register both modules since both are now created and ready
2995 // In case one module misses the DBus signal, then it will be correctly initialized.
2996 captureModule()->registerNewModule("Mount");
2997 mountModule()->registerNewModule("Capture");
2998
2999 // Meridian Flip states
3000 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, mountModule(), &Ekos::Mount::suspendAltLimits,
3002 connect(captureModule(), &Ekos::Capture::guideAfterMeridianFlip, mountModule(), &Ekos::Mount::resumeAltLimits,
3004
3005 // Mount Status
3006 connect(mountModule(), &Ekos::Mount::newStatus, captureModule(), &Ekos::Capture::setMountStatus,
3008 }
3009
3010 // Optical Train Manager ---> EkosLive connections
3011 if (ekosLiveClient)
3012 {
3013 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, ekosLiveClient->message(),
3014 &EkosLive::Message::sendTrains, Qt::UniqueConnection);
3015 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::configurationRequested, ekosLiveClient->message(),
3016 &EkosLive::Message::requestOpticalTrains, Qt::UniqueConnection);
3017 }
3018
3019 // Capture <---> EkosLive connections
3020 if (captureProcess && ekosLiveClient)
3021 {
3022 //captureProcess.get()->disconnect(ekosLiveClient.get()->message());
3023
3024 connect(captureModule(), &Ekos::Capture::dslrInfoRequested, ekosLiveClient.get()->message(),
3025 &EkosLive::Message::requestDSLRInfo, Qt::UniqueConnection);
3026 connect(captureModule(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(),
3027 &EkosLive::Message::sendCaptureSequence, Qt::UniqueConnection);
3028 connect(captureModule(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(),
3029 &EkosLive::Message::sendCaptureSettings, Qt::UniqueConnection);
3030 connect(captureModule(), &Ekos::Capture::newLocalPreview, ekosLiveClient.get()->message(),
3031 &EkosLive::Message::sendPreviewLabel, Qt::UniqueConnection);
3032 connect(captureModule(), &Ekos::Capture::trainChanged, ekosLiveClient.get()->message(),
3033 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3034 }
3035
3036 // Focus <---> Align connections
3037 if (focusProcess && alignProcess)
3038 {
3039 connect(focusModule(), &Ekos::Focus::newStatus, alignModule(), &Ekos::Align::setFocusStatus,
3041 }
3042
3043 // Focus <---> Mount connections
3044 if (focusProcess && mountProcess)
3045 {
3046 connect(mountModule(), &Ekos::Mount::newStatus, focusModule(), &Ekos::Focus::setMountStatus,
3048 connect(mountModule(), &Ekos::Mount::newCoords, focusModule(), &Ekos::Focus::setMountCoords,
3050 }
3051
3052 // Mount <---> Align connections
3053 if (mountProcess && alignProcess)
3054 {
3055 connect(mountModule(), &Ekos::Mount::newStatus, alignModule(), &Ekos::Align::setMountStatus,
3057 connect(mountModule(), &Ekos::Mount::newTarget, alignModule(), &Ekos::Align::setTarget,
3061 connect(alignModule(), &Ekos::Align::newPAAStage, mountModule(), &Ekos::Mount::paaStageChanged,
3063 }
3064
3065 // Mount <---> Guide connections
3066 if (mountProcess && guideProcess)
3067 {
3068 connect(mountModule(), &Ekos::Mount::pierSideChanged, guideModule(), &Ekos::Guide::setPierSide,
3070 }
3071
3072 // Align <--> EkosLive connections
3073 if (alignProcess && ekosLiveClient)
3074 {
3075 // alignProcess.get()->disconnect(ekosLiveClient.get()->message());
3076 // alignProcess.get()->disconnect(ekosLiveClient.get()->media());
3077
3078 connect(alignModule(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus,
3080 connect(alignModule(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(),
3081 &EkosLive::Message::setAlignSolution, Qt::UniqueConnection);
3082 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newPAHStage,
3083 ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage,
3085 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newPAHMessage,
3086 ekosLiveClient.get()->message(),
3087 &EkosLive::Message::setPAHMessage, Qt::UniqueConnection);
3088 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::PAHEnabled,
3089 ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled,
3091 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::polarResultUpdated,
3092 ekosLiveClient.get()->message(),
3093 &EkosLive::Message::setPolarResults, Qt::UniqueConnection);
3094 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::updatedErrorsChanged,
3095 ekosLiveClient.get()->message(),
3096 &EkosLive::Message::setUpdatedErrors, Qt::UniqueConnection);
3097 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newCorrectionVector,
3098 ekosLiveClient.get()->media(),
3099 &EkosLive::Media::setCorrectionVector, Qt::UniqueConnection);
3100
3101 connect(alignModule(), &Ekos::Align::newImage, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3103 connect(alignModule(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame,
3105
3106 connect(alignModule(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(),
3107 &EkosLive::Message::sendAlignSettings, Qt::UniqueConnection);
3108
3109 connect(alignModule(), &Ekos::Align::trainChanged, ekosLiveClient.get()->message(),
3110 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3111
3112 connect(alignModule(), &Ekos::Align::manualRotatorChanged, ekosLiveClient.get()->message(),
3113 &EkosLive::Message::sendManualRotatorStatus, Qt::UniqueConnection);
3114 }
3115
3116 // Focus <--> EkosLive Connections
3117 if (focusProcess && ekosLiveClient)
3118 {
3119 connect(focusModule(), &Ekos::Focus::settingsUpdated, ekosLiveClient.get()->message(),
3120 &EkosLive::Message::sendFocusSettings, Qt::UniqueConnection);
3121
3122 connect(focusModule(), &Ekos::Focus::newImage, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3124
3125 connect(focusModule(), &Ekos::Focus::trainChanged, ekosLiveClient.get()->message(),
3126 &EkosLive::Message::sendTrainProfiles,
3128
3129 connect(focusModule(), &Ekos::Focus::autofocusAborted,
3130 ekosLiveClient.get()->message(), &EkosLive::Message::autofocusAborted, Qt::UniqueConnection);
3131 }
3132
3133 // Guide <--> EkosLive Connections
3134 if (guideProcess && ekosLiveClient)
3135 {
3136 connect(guideModule(), &Ekos::Guide::settingsUpdated, ekosLiveClient.get()->message(),
3137 &EkosLive::Message::sendGuideSettings, Qt::UniqueConnection);
3138
3139 connect(guideModule(), &Ekos::Guide::trainChanged, ekosLiveClient.get()->message(),
3140 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3141
3142 connect(guideModule(), &Ekos::Guide::newImage, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3144 }
3145
3146 // Analyze connections.
3147 if (analyzeProcess)
3148 {
3149 // Scheduler <---> Analyze
3150 connect(schedulerModule(), &Ekos::Scheduler::jobStarted,
3151 analyzeProcess.get(), &Ekos::Analyze::schedulerJobStarted, Qt::UniqueConnection);
3152 connect(schedulerModule(), &Ekos::Scheduler::jobEnded,
3153 analyzeProcess.get(), &Ekos::Analyze::schedulerJobEnded, Qt::UniqueConnection);
3154 connect(schedulerModule(), &Ekos::Scheduler::targetDistance,
3155 analyzeProcess.get(), &Ekos::Analyze::newTargetDistance, Qt::UniqueConnection);
3156
3157 // Capture <---> Analyze
3158 if (captureProcess)
3159 {
3160 connect(captureModule(), &Ekos::Capture::captureComplete,
3161 analyzeProcess.get(), &Ekos::Analyze::captureComplete, Qt::UniqueConnection);
3162 connect(captureModule(), &Ekos::Capture::captureStarting,
3163 analyzeProcess.get(), &Ekos::Analyze::captureStarting, Qt::UniqueConnection);
3164 connect(captureModule(), &Ekos::Capture::captureAborted,
3165 analyzeProcess.get(), &Ekos::Analyze::captureAborted, Qt::UniqueConnection);
3166#if 0
3167 // Meridian Flip
3168 connect(captureModule(), &Ekos::Capture::meridianFlipStarted,
3169 analyzeProcess.get(), &Ekos::Analyze::meridianFlipStarted, Qt::UniqueConnection);
3170 connect(captureModule(), &Ekos::Capture::meridianFlipCompleted,
3171 analyzeProcess.get(), &Ekos::Analyze::meridianFlipComplete, Qt::UniqueConnection);
3172#endif
3173 }
3174
3175 // Guide <---> Analyze
3176 if (guideProcess)
3177 {
3178 connect(guideModule(), &Ekos::Guide::newStatus,
3179 analyzeProcess.get(), &Ekos::Analyze::guideState, Qt::UniqueConnection);
3180
3181 connect(guideModule(), &Ekos::Guide::guideStats,
3182 analyzeProcess.get(), &Ekos::Analyze::guideStats, Qt::UniqueConnection);
3183 }
3184 }
3185
3186
3187 // Focus <---> Analyze connections
3188 if (focusProcess && analyzeProcess)
3189 {
3190 connect(focusModule(), &Ekos::Focus::autofocusComplete,
3191 analyzeProcess.get(), &Ekos::Analyze::autofocusComplete, Qt::UniqueConnection);
3193 analyzeProcess.get(), &Ekos::Analyze::adaptiveFocusComplete, Qt::UniqueConnection);
3194 connect(focusModule(), &Ekos::Focus::autofocusStarting,
3195 analyzeProcess.get(), &Ekos::Analyze::autofocusStarting, Qt::UniqueConnection);
3196 connect(focusModule(), &Ekos::Focus::autofocusAborted,
3197 analyzeProcess.get(), &Ekos::Analyze::autofocusAborted, Qt::UniqueConnection);
3198 connect(focusModule(), &Ekos::Focus::newFocusTemperatureDelta,
3199 analyzeProcess.get(), &Ekos::Analyze::newTemperature, Qt::UniqueConnection);
3200 }
3201
3202 // Align <---> Analyze connections
3203 if (alignProcess && analyzeProcess)
3204 {
3205 connect(alignModule(), &Ekos::Align::newStatus,
3206 analyzeProcess.get(), &Ekos::Analyze::alignState, Qt::UniqueConnection);
3207
3208 }
3209
3210 // Mount <---> Analyze connections
3211 if (mountProcess && analyzeProcess)
3212 {
3213 connect(mountModule(), &Ekos::Mount::newStatus,
3214 analyzeProcess.get(), &Ekos::Analyze::mountState, Qt::UniqueConnection);
3215 connect(mountModule(), &Ekos::Mount::newCoords,
3216 analyzeProcess.get(), &Ekos::Analyze::mountCoords, Qt::UniqueConnection);
3217 connect(mountModule()->getMeridianFlipState().get(), &Ekos::MeridianFlipState::newMountMFStatus,
3218 analyzeProcess.get(), &Ekos::Analyze::mountFlipStatus, Qt::UniqueConnection);
3219 }
3220}
3221
3222void Manager::setEkosLiveConnected(bool enabled)
3223{
3224 ekosLiveClient.get()->setConnected(enabled);
3225}
3226
3227void Manager::setEkosLiveConfig(bool rememberCredentials, bool autoConnect)
3228{
3229 ekosLiveClient.get()->setConfig(rememberCredentials, autoConnect);
3230}
3231
3232void Manager::setEkosLiveUser(const QString &username, const QString &password)
3233{
3234 ekosLiveClient.get()->setUser(username, password);
3235}
3236
3237bool Manager::ekosLiveStatus()
3238{
3239 return ekosLiveClient.get()->isConnected();
3240}
3241
3242bool Manager::checkUniqueBinaryDriver(const QSharedPointer<DriverInfo> &primaryDriver,
3244{
3246 return false;
3247
3248 return (primaryDriver->getExecutable() == secondaryDriver->getExecutable() &&
3249 primaryDriver->getAuxInfo().value("mdpd", false).toBool() == true);
3250}
3251
3252void Manager::restartDriver(const QString &deviceName)
3253{
3254 qCInfo(KSTARS_EKOS) << "Restarting driver" << deviceName;
3255 if (m_LocalMode)
3256 {
3257 for (auto &oneDevice : INDIListener::devices())
3258 {
3259 if (oneDevice->getDeviceName() == deviceName)
3260 {
3261 DriverManager::Instance()->restartDriver(oneDevice->getDriverInfo());
3262 break;
3263 }
3264 }
3265 }
3266 else
3267 INDI::WebManager::restartDriver(m_CurrentProfile, deviceName);
3268}
3269
3270void Manager::setEkosLoggingEnabled(const QString &name, bool enabled)
3271{
3272 // LOGGING, FILE, DEFAULT are exclusive, so one of them must be SET to TRUE
3273 if (name == "LOGGING")
3274 {
3275 Options::setDisableLogging(!enabled);
3276 if (!enabled)
3278 }
3279 else if (name == "FILE")
3280 {
3281 Options::setLogToFile(enabled);
3282 if (enabled)
3284 }
3285 else if (name == "DEFAULT")
3286 {
3287 Options::setLogToDefault(enabled);
3288 if (enabled)
3290 }
3291 // VERBOSE should be set to TRUE if INDI or Ekos logging is selected.
3292 else if (name == "VERBOSE")
3293 {
3294 Options::setVerboseLogging(enabled);
3296 }
3297 // Toggle INDI Logging
3298 else if (name == "INDI")
3299 {
3300 Options::setINDILogging(enabled);
3302 }
3303 else if (name == "FITS")
3304 {
3305 Options::setFITSLogging(enabled);
3307 }
3308 else if (name == "CAPTURE")
3309 {
3310 Options::setCaptureLogging(enabled);
3311 Options::setINDICCDLogging(enabled);
3312 Options::setINDIFilterWheelLogging(enabled);
3314 }
3315 else if (name == "FOCUS")
3316 {
3317 Options::setFocusLogging(enabled);
3318 Options::setINDIFocuserLogging(enabled);
3320 }
3321 else if (name == "GUIDE")
3322 {
3323 Options::setGuideLogging(enabled);
3324 Options::setINDICCDLogging(enabled);
3326 }
3327 else if (name == "ALIGNMENT")
3328 {
3329 Options::setAlignmentLogging(enabled);
3331 }
3332 else if (name == "MOUNT")
3333 {
3334 Options::setMountLogging(enabled);
3335 Options::setINDIMountLogging(enabled);
3337 }
3338 else if (name == "SCHEDULER")
3339 {
3340 Options::setSchedulerLogging(enabled);
3342 }
3343 else if (name == "OBSERVATORY")
3344 {
3345 Options::setObservatoryLogging(enabled);
3347 }
3348}
3349
3350void Manager::acceptPortSelection()
3351{
3352 if (m_PortSelector)
3353 m_PortSelector->accept();
3354}
3355
3356void Manager::setPortSelectionComplete()
3357{
3358 if (m_CurrentProfile->portSelector)
3359 {
3360 // Turn off port selector
3361 m_CurrentProfile->portSelector = false;
3362 KStarsData::Instance()->userdb()->SaveProfile(m_CurrentProfile);
3363 }
3364
3365 if (m_CurrentProfile->autoConnect)
3366 connectDevices();
3367}
3368
3369void Manager::activateModule(const QString &name, bool popup)
3370{
3371 auto child = toolsWidget->findChild<QWidget *>(name);
3372 if (child)
3373 {
3374 toolsWidget->setCurrentWidget(child);
3375 if (popup)
3376 {
3377 raise();
3378 activateWindow();
3379 showNormal();
3380 }
3381 }
3382}
3383
3384void Manager::createModules(const QSharedPointer<ISD::GenericDevice> &device)
3385{
3386 if (device->isConnected())
3387 {
3388 if (device->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE)
3389 {
3390 initCapture();
3391 initFocus();
3392 initAlign();
3393 initGuide();
3394 }
3395 if (device->getDriverInterface() & INDI::BaseDevice::FILTER_INTERFACE)
3396 {
3397 initCapture();
3398 initFocus();
3399 initAlign();
3400 }
3401 if (device->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE)
3402 initFocus();
3403 if (device->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)
3404 {
3405 initCapture();
3406 initAlign();
3407 initGuide();
3408 initMount();
3409 }
3410 if (device->getDriverInterface() & INDI::BaseDevice::ROTATOR_INTERFACE)
3411 {
3412 initCapture();
3413 initAlign();
3414 }
3415 if (device->getDriverInterface() & INDI::BaseDevice::DOME_INTERFACE)
3416 {
3417 initCapture();
3418 initAlign();
3419 initObservatory();
3420 }
3421 if (device->getDriverInterface() & INDI::BaseDevice::WEATHER_INTERFACE)
3422 {
3423 initFocus();
3424 initObservatory();
3425 }
3426 if (device->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE)
3427 {
3428 initCapture();
3429 }
3430 if (device->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE)
3431 {
3432 initCapture();
3433 }
3434 if (device->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)
3435 {
3436 initMount();
3437 }
3438 }
3439}
3440
3441void Manager::setDeviceReady()
3442{
3443 // Check if ALL our devices are ready.
3444 // Ready indicates that all properties have been defined.
3445 if (isINDIReady() == false)
3446 {
3447 auto device = static_cast<ISD::GenericDevice*>(sender());
3448 if (device)
3449 {
3450
3451 if (device->isConnected() == false && m_CurrentProfile->autoConnect)
3452 {
3453 // Do we have port selector checked?
3454 if (m_CurrentProfile->portSelector)
3455 {
3456 // If port selector was not initialized, kick off the timer
3457 // so we can check if all devices should be connected.
3458 // Otherwise, if port selector is started, then let user
3459 // select ports first and then manually connect time.
3460 if (!m_PortSelector)
3461 m_PortSelectorTimer.start();
3462 }
3463 else
3464 {
3465 qCInfo(KSTARS_EKOS) << "Connecting to" << device->getDeviceName();
3466 device->Connect();
3467 }
3468 }
3469 else
3470 qCInfo(KSTARS_EKOS) << device->getDeviceName() << "is connected and ready.";
3471 }
3472
3473 if (m_ekosStatus != Ekos::Success)
3474 return;
3475 }
3476
3477 // If port selector is active, then do not show optical train dialog unless it is dismissed first.
3478 if (m_DriverDevicesCount <= 0 && (m_CurrentProfile->portSelector == false || !m_PortSelector))
3479 {
3480 for (auto &device : INDIListener::devices())
3481 syncGenericDevice(device);
3482 OpticalTrainManager::Instance()->setProfile(m_CurrentProfile);
3483 }
3484}
3485
3486void Manager::createFilterManager(ISD::FilterWheel *device)
3487{
3488 auto name = device->getDeviceName();
3489 if (m_FilterManagers.contains(name) == false)
3490 {
3491 QSharedPointer<FilterManager> newFM(new FilterManager(this));
3492 newFM->setFilterWheel(device);
3493 m_FilterManagers[name] = newFM;
3494 }
3495 else
3496 m_FilterManagers[name]->setFilterWheel(device);
3497
3498}
3499
3500bool Manager::getFilterManager(const QString &name, QSharedPointer<FilterManager> &fm)
3501{
3502 if (m_FilterManagers.contains(name))
3503 {
3504 fm = m_FilterManagers[name];
3505 return true;
3506 }
3507 return false;
3508}
3509
3510bool Manager::getFilterManager(QSharedPointer<FilterManager> &fm)
3511{
3512 if (m_FilterManagers.size() > 0)
3513 {
3514 fm = m_FilterManagers.values()[0];
3515 return true;
3516 }
3517 return false;
3518}
3519
3520void Manager::createRotatorController(ISD::Rotator *device)
3521{
3522 auto Name = device->getDeviceName();
3523 if (m_RotatorControllers.contains(Name) == false)
3524 {
3525 QSharedPointer<RotatorSettings> newRC(new RotatorSettings(this));
3526 // Properties are fetched in RotatorSettings::initRotator!
3527 m_RotatorControllers[Name] = newRC;
3528 }
3529}
3530
3531bool Manager::getRotatorController(const QString &Name, QSharedPointer<RotatorSettings> &rs)
3532{
3533 if (m_RotatorControllers.contains(Name))
3534 {
3535 rs = m_RotatorControllers[Name];
3536 return true;
3537 }
3538 return false;
3539}
3540
3541bool Manager::existRotatorController()
3542{
3543 return (!m_RotatorControllers.empty());
3544}
3545
3546}
DriverInfo holds all metadata associated with a particular INDI driver.
Definition driverinfo.h:46
DriverManager is the primary class to handle all operations related to starting and stopping INDI dri...
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
Definition align.h:75
Analysis tab for Ekos sessions.
Definition analyze.h:35
void setFocusTemperatureDelta(double focusTemperatureDelta, double absTemperature)
updateAdaptiveFocusStatus Handle new focus state
Definition capture.cpp:243
void setHFR(double newHFR, int position, bool inAutofocus)
setHFR Receive the measured HFR value of the latest frame
Definition capture.cpp:384
void updateTargetDistance(double targetDiff)
Slot receiving the update of the current target distance.
Definition capture.h:576
void setFocusStatus(FocusState newstate)
setFocusStatus Forward the new focus state to the capture module state machine
Definition capture.h:610
void setGuideDeviation(double delta_ra, double delta_dec)
setGuideDeviation Set the guiding deviation as measured by the guiding module.
Definition capture.cpp:252
void focusAdaptiveComplete(bool success)
focusAdaptiveComplete Forward the new focus state to the capture module state machine
Definition capture.h:620
Supports manual focusing and auto focusing using relative and absolute INDI focusers.
Definition focus.h:51
void drawPolynomial(PolynomialFit *poly, bool isVShape, bool activate, bool plot=true)
draw the approximating polynomial into the HFR V-graph
void newHFRPlotPosition(double pos, double hfr, double sigma, bool outlier, int pulseDuration, bool plot=true)
new HFR plot position with sigma
void redrawHFRPlot(PolynomialFit *poly, double solutionPosition, double solutionValue)
redraw the entire HFR plot
void runAutoFocus(const AutofocusReason autofocusReason, const QString &reasonInfo)
Run the autofocus process for the currently selected filter.
Definition focus.cpp:1018
void focuserTimedout(const QString &focuser)
focuserTimedout responding to requests
void initHFRPlot(QString str, double starUnits, bool minimum, bool useWeights, bool showPosition)
initialize the HFR V plot
void adaptiveFocus()
adaptiveFocus moves the focuser between subframes to stay at focus
Definition focus.cpp:992
Q_SCRIPTABLE Q_NOREPLY void resetFrame()
DBUS interface function.
Definition focus.cpp:315
void meridianFlipStarted()
React when a meridian flip has been started.
Definition focus.cpp:1424
void drawCFZ(double minPosition, double minValue, int m_cfzSteps, bool plt)
Draw Critical Focus Zone on graph.
void finalUpdates(const QString &title, bool plot=true)
final updates after focus run comopletes on the focus plot
void minimumFound(double solutionPosition, double solutionValue, bool plot=true)
Focus solution with minimal HFR found.
void setTitle(const QString &title, bool plot=true)
draw a title on the focus plot
void drawCurve(CurveFitting *curve, bool isVShape, bool activate, bool plot=true)
draw the curve into the HFR V-graph
void adaptiveFocusComplete(const QString &filter, double temperature, double tempTicks, double altitude, double altTicks, int prevPosError, int thisPosError, int totalTicks, int position, bool focuserMoved)
Signal Analyze that an Adaptive Focus iteration is complete.
Performs calibration and autoguiding using an ST4 port or directly via the INDI driver.
Definition guide.h:51
Q_SCRIPTABLE bool resume()
DBUS interface function.
Definition guide.cpp:1429
Q_SCRIPTABLE bool suspend()
DBUS interface function.
Definition guide.cpp:1419
Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition guide.cpp:889
Supports controlling INDI telescope devices including setting/retrieving mount properties,...
Definition mount.h:33
void newTarget(SkyPoint &currentCoord)
The mount has finished the slew to a new target.
void paaStageChanged(int stage)
React upon status changes of the polar alignment - mainly to avoid meridian flips happening during po...
Definition mount.cpp:789
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
void newStatus(ISD::Mount::Status status)
Change in the mount status.
void suspendAltLimits()
suspendAltLimits calls enableAltitudeLimits(false).
Definition mount.cpp:925
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:917
Enables the user to set logging options.
Definition opslogs.h:23
INDIListener is responsible for creating ISD::GDInterface generic devices as new devices arrive from ...
Camera class controls an INDI Camera device.
Definition indicamera.h:44
void sendNewProperty(INDI::Property prop)
Send new property command to server.
Class handles control of INDI dome devices.
Definition indidome.h:23
Handles operation of a remotely controlled dust cover cap.
Definition indidustcap.h:23
Focuser class handles control of INDI focuser devices.
Definition indifocuser.h:21
GenericDevice is the Generic Device for INDI devices.
Definition indistd.h:117
Handles operation of a remotely controlled light box.
device handle controlling Mounts.
Definition indimount.h:27
Rotator class handles control of INDI Rotator devices.
Definition indirotator.h:20
Focuser class handles control of INDI Weather devices.
Definition indiweather.h:24
static bool showDialog(const QString &name)
KPageWidgetItem * addPage(QWidget *page, const QString &itemName, const QString &pixmapName=QString(), const QString &header=QString(), bool manage=true)
static KConfigDialog * exists(const QString &name)
static void beep(const QString &reason=QString())
QPushButton * button(QDialogButtonBox::StandardButton which) const
void setIcon(const QIcon &icon)
static void UseDefault()
Use the default logging mechanism.
Definition ksutils.cpp:1024
static void SyncFilterRules()
SyncFilterRules Sync QtLogging filter rules from Options.
Definition ksutils.cpp:1036
static void Disable()
Disable logging.
Definition ksutils.cpp:1029
static void UseFile()
Store all logs into the specified file.
Definition ksutils.cpp:927
static KStarsDateTime currentDateTimeUtc()
This is the main window for KStars.
Definition kstars.h:91
static KStars * Instance()
Definition kstars.h:123
Primary class to handle all Ekos modules.
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 & ra0() const
Definition skypoint.h:251
const CachingDms & ra() const
Definition skypoint.h:263
const dms & az() const
Definition skypoint.h:275
const dms & alt() const
Definition skypoint.h:281
const CachingDms & dec0() const
Definition skypoint.h:257
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
Definition dms.cpp:429
const double & Degrees() const
Definition dms.h:141
void setTarget(const SkyPoint &targetCoord)
Set the alignment target where the mount is expected to point at.
Definition align.cpp:3804
void setTelescopeCoordinates(const SkyPoint &position)
Set the coordinates that the mount reports as its position.
Definition align.h:460
Q_SCRIPTABLE Q_NOREPLY void checkFocus(double requiredHFR)
checkFocus Given the minimum required HFR, check focus and calculate HFR.
Definition focus.cpp:4721
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition focus.cpp:1438
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString fullName(const PartType &type)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:79
CaptureState
Capture states.
Definition ekos.h:92
@ CAPTURE_ABORTED
Definition ekos.h:99
@ CAPTURE_COMPLETE
Definition ekos.h:112
@ CAPTURE_CAPTURING
Definition ekos.h:95
@ CAPTURE_IDLE
Definition ekos.h:93
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
GeoCoordinates geo(const QVariant &location)
QVariant location(const QVariant &res)
QString name(StandardAction id)
KGuiItem reset()
KGuiItem stop()
QString label(StandardShortcut id)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void clicked(bool checked)
void setChecked(bool)
void currentTextChanged(const QString &text)
QCoreApplication * instance()
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void accepted()
void rejected()
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
QJsonArray array() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void clear()
QPixmap transformed(const QTransform &transform, Qt::TransformationMode mode) const const
void start(OpenMode mode)
bool waitForFinished(int msecs)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
AlignVCenter
ApplicationState
CaseInsensitive
UniqueConnection
WA_LayoutUsesWidgetRect
void currentChanged(int index)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
void show()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:48:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.