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

KDE's Doxygen guidelines are available online.