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 KStarsData::Instance()->clock()->setRealTime(true);
825
826 // Reset Ekos Manager
827 reset();
828
829 // Get Current Profile
830 getCurrentProfile(m_CurrentProfile);
831 m_LocalMode = m_CurrentProfile->isLocal();
832
833 ProfileSettings::Instance()->setProfile(m_CurrentProfile);
834
835 // Load profile location if one exists
836 updateProfileLocation(m_CurrentProfile);
837
838 bool haveCCD = false, haveGuider = false;
839
840 // If external guide is specified in the profile, set the
841 // corresponding options
842 if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_PHD2)
843 {
844 Options::setPHD2Host(m_CurrentProfile->guiderhost);
845 Options::setPHD2Port(m_CurrentProfile->guiderport);
846 }
847 else if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER)
848 {
849 Options::setLinGuiderHost(m_CurrentProfile->guiderhost);
850 Options::setLinGuiderPort(m_CurrentProfile->guiderport);
851 }
852
853 // Parse script, if any
854 QJsonParseError jsonError;
855 QJsonArray profileScripts;
856 QJsonDocument doc = QJsonDocument::fromJson(m_CurrentProfile->scripts, &jsonError);
857
858 if (jsonError.error == QJsonParseError::NoError)
859 profileScripts = doc.array();
860
861 ekosLiveClient->message()->setPendingPropertiesEnabled(true);
862
863 // For locally running INDI server
864 if (m_LocalMode)
865 {
866 auto drv = driversList.value(m_CurrentProfile->mount());
867
868 if (!drv.isNull())
869 managedDrivers.append(drv->clone());
870
871 drv = driversList.value(m_CurrentProfile->ccd());
872 if (!drv.isNull())
873 {
874 managedDrivers.append(drv->clone());
875 haveCCD = true;
876 }
877
878 Options::setGuiderType(m_CurrentProfile->guidertype);
879
880 drv = driversList.value(m_CurrentProfile->guider());
881 if (!drv.isNull())
882 {
883 haveGuider = true;
884
885 // If the guider and ccd are the same driver, we have two cases:
886 // #1 Drivers that only support ONE device per driver (such as sbig)
887 // #2 Drivers that supports multiples devices per driver (such as sx)
888 // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide"
889 // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider
890 // since this is the only way to find out in real time.
891 if (haveCCD && m_CurrentProfile->guider() == m_CurrentProfile->ccd())
892 {
893 if (checkUniqueBinaryDriver( driversList.value(m_CurrentProfile->ccd()), drv))
894 {
895 drv.clear();
896 }
897 else
898 {
899 drv->setUniqueLabel(drv->getLabel() + " Guide");
900 }
901 }
902
903 if (!drv.isNull())
904 managedDrivers.append(drv->clone());
905 }
906
907 drv = driversList.value(m_CurrentProfile->ao());
908 if (!drv.isNull())
909 managedDrivers.append(drv->clone());
910
911 drv = driversList.value(m_CurrentProfile->filter());
912 if (!drv.isNull())
913 managedDrivers.append(drv->clone());
914
915 drv = driversList.value(m_CurrentProfile->focuser());
916 if (!drv.isNull())
917 managedDrivers.append(drv->clone());
918
919 drv = driversList.value(m_CurrentProfile->dome());
920 if (!drv.isNull())
921 managedDrivers.append(drv->clone());
922
923 drv = driversList.value(m_CurrentProfile->weather());
924 if (!drv.isNull())
925 managedDrivers.append(drv->clone());
926
927 drv = driversList.value(m_CurrentProfile->aux1());
928 if (!drv.isNull())
929 {
930 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
931 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
932 managedDrivers.append(drv->clone());
933 }
934 drv = driversList.value(m_CurrentProfile->aux2());
935 if (!drv.isNull())
936 {
937 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
938 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
939 managedDrivers.append(drv->clone());
940 }
941
942 drv = driversList.value(m_CurrentProfile->aux3());
943 if (!drv.isNull())
944 {
945 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
946 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
947 managedDrivers.append(drv->clone());
948 }
949
950 drv = driversList.value(m_CurrentProfile->aux4());
951 if (!drv.isNull())
952 {
953 if (!checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->ccd()), drv) &&
954 !checkUniqueBinaryDriver(driversList.value(m_CurrentProfile->guider()), drv))
955 managedDrivers.append(drv->clone());
956 }
957
958 // Add remote drivers if we have any
959 if (m_CurrentProfile->remotedrivers.isEmpty() == false && m_CurrentProfile->remotedrivers.contains("@"))
960 {
961 for (auto remoteDriver : m_CurrentProfile->remotedrivers.split(","))
962 {
963 QString name, label, host("localhost"), port("7624"), hostport(host + ':' + port);
964
965 // Possible configurations:
966 // - device
967 // - device@host
968 // - device@host:port
969 // - @host
970 // - @host:port
971
972 {
973 QStringList device_location = remoteDriver.split('@');
974
975 // device or device@host:port
976 if (device_location.length() > 0)
977 name = device_location[0];
978
979 // device@host:port or @host:port
980 if (device_location.length() > 1)
981 hostport = device_location[1];
982 }
983
984 {
985 QStringList location = hostport.split(':');
986
987 // host or host:port
988 if (location.length() > 0)
989 host = location[0];
990
991 // host:port
992 if (location.length() > 1)
993 port = location[1];
994 }
995
996 QSharedPointer<DriverInfo> dv(new DriverInfo(name));
997 dv->setRemoteHost(host);
998 dv->setRemotePort(port);
999
1000 label = name;
1001 // Remove extra quotes
1002 label.remove("\"");
1003 dv->setLabel(label);
1004 dv->setUniqueLabel(label);
1005 managedDrivers.append(dv);
1006 }
1007 }
1008
1009
1010 if (haveCCD == false && haveGuider == false && m_CurrentProfile->remotedrivers.isEmpty())
1011 {
1012 KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate."));
1013 managedDrivers.clear();
1014 m_ekosStatus = Ekos::Error;
1015 emit ekosStatusChanged(m_ekosStatus);
1016 return;
1017 }
1018
1019 m_DriverDevicesCount = managedDrivers.count();
1020 }
1021 else
1022 {
1023 QSharedPointer<DriverInfo> remote_indi(new DriverInfo(QString("Ekos Remote Host")));
1024
1025 remote_indi->setHostParameters(m_CurrentProfile->host, m_CurrentProfile->port);
1026
1027 remote_indi->setDriverSource(GENERATED_SOURCE);
1028
1029 managedDrivers.append(remote_indi);
1030
1031 haveCCD = m_CurrentProfile->drivers.contains("CCD");
1032 haveGuider = m_CurrentProfile->drivers.contains("Guider");
1033
1034 Options::setGuiderType(m_CurrentProfile->guidertype);
1035
1036 if (haveCCD == false && haveGuider == false && m_CurrentProfile->remotedrivers.isEmpty())
1037 {
1038 KSNotification::error(i18n("Ekos requires at least one CCD or Guider to operate."));
1039 m_DriverDevicesCount = 0;
1040 m_ekosStatus = Ekos::Error;
1041 emit ekosStatusChanged(m_ekosStatus);
1042 return;
1043 }
1044
1045 m_DriverDevicesCount = m_CurrentProfile->drivers.count();
1046 }
1047
1048
1049 // Prioritize profile script drivers over other drivers
1050 QList<QSharedPointer<DriverInfo >> sortedList;
1051 for (const auto &oneRule : qAsConst(profileScripts))
1052 {
1053 auto driver = oneRule.toObject()["Driver"].toString();
1054 auto matchingDriver = std::find_if(managedDrivers.begin(), managedDrivers.end(), [oneRule, driver](const auto & oneDriver)
1055 {
1056 // Account for both local and remote drivers
1057 return oneDriver->getLabel() == driver || (driver.startsWith("@") && !oneDriver->getRemoteHost().isEmpty());
1058 });
1059
1060 if (matchingDriver != managedDrivers.end())
1061 {
1062 (*matchingDriver)->setStartupShutdownRule(oneRule.toObject());
1063 sortedList.append(*matchingDriver);
1064 }
1065 }
1066
1067 // If we have any profile scripts drivers, let's re-sort managed drivers
1068 // so that profile script drivers
1069 if (!sortedList.isEmpty())
1070 {
1071 for (auto &oneDriver : managedDrivers)
1072 {
1073 if (sortedList.contains(oneDriver) == false)
1074 sortedList.append(oneDriver);
1075 }
1076
1077 managedDrivers = sortedList;
1078 }
1079
1080 connect(DriverManager::Instance(), &DriverManager::serverStarted, this,
1081 &Manager::setServerStarted, Qt::UniqueConnection);
1082 connect(DriverManager::Instance(), &DriverManager::serverFailed, this,
1083 &Manager::setServerFailed, Qt::UniqueConnection);
1084 connect(DriverManager::Instance(), &DriverManager::clientStarted, this,
1085 &Manager::setClientStarted, Qt::UniqueConnection);
1086 connect(DriverManager::Instance(), &DriverManager::clientFailed, this,
1087 &Manager::setClientFailed, Qt::UniqueConnection);
1088 connect(DriverManager::Instance(), &DriverManager::clientTerminated, this,
1089 &Manager::setClientTerminated, Qt::UniqueConnection);
1090
1091 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice);
1092 connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection);
1093
1094
1095#ifdef Q_OS_MACOS
1096 if (m_LocalMode || m_CurrentProfile->host == "localhost")
1097 {
1098 if (isRunning("PTPCamera"))
1099 {
1101 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?"),
1102 i18n("PTP Camera")))
1103 {
1104 //TODO is there a better way to do this.
1105 QProcess p;
1106 p.start("killall PTPCamera");
1107 p.waitForFinished();
1108 }
1109 }
1110 }
1111#endif
1112 if (m_LocalMode)
1113 {
1114 auto executeStartINDIServices = [this]()
1115 {
1116 appendLogText(i18n("Starting INDI services..."));
1117
1118 m_ekosStatus = Ekos::Pending;
1119 emit ekosStatusChanged(m_ekosStatus);
1120
1121 DriverManager::Instance()->startDevices(managedDrivers);
1122 };
1123
1124 // If INDI server is already running, let's see if we need to shut it down first
1125 if (isRunning("indiserver"))
1126 {
1127 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeStartINDIServices]()
1128 {
1129 KSMessageBox::Instance()->disconnect(this);
1130 DriverManager::Instance()->stopAllDevices();
1131 //TODO is there a better way to do this.
1132 QProcess p;
1133 const QString program = "pkill";
1134 QStringList arguments;
1135 arguments << "indiserver";
1136 p.start(program, arguments);
1137 p.waitForFinished();
1138
1139 QTimer::singleShot(1000, this, executeStartINDIServices);
1140 });
1141 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this, executeStartINDIServices]()
1142 {
1143 KSMessageBox::Instance()->disconnect(this);
1144 executeStartINDIServices();
1145 });
1146
1147 KSMessageBox::Instance()->questionYesNo(i18n("Ekos detected an instance of INDI server running. Do you wish to "
1148 "shut down the existing instance before starting a new one?"),
1149 i18n("INDI Server"), 5);
1150 }
1151 else
1152 executeStartINDIServices();
1153
1154 }
1155 else
1156 {
1157 auto runConnection = [this]()
1158 {
1159 // If it got cancelled by the user, return immediately.
1160 if (m_ekosStatus != Ekos::Pending)
1161 return;
1162
1163 appendLogText(
1164 i18n("Connecting to remote INDI server at %1 on port %2 ...", m_CurrentProfile->host, m_CurrentProfile->port));
1165
1166 DriverManager::Instance()->connectRemoteHost(managedDrivers.first());
1167 };
1168
1169 auto runProfile = [this, runConnection]()
1170 {
1171 // If it got cancelled by the user, return immediately.
1172 if (m_ekosStatus != Ekos::Pending)
1173 return;
1174
1175 INDI::WebManager::syncCustomDrivers(m_CurrentProfile);
1176 INDI::WebManager::checkVersion(m_CurrentProfile);
1177
1178 if (INDI::WebManager::areDriversRunning(m_CurrentProfile) == false)
1179 {
1180 INDI::WebManager::stopProfile(m_CurrentProfile);
1181
1182 if (INDI::WebManager::startProfile(m_CurrentProfile) == false)
1183 {
1184 appendLogText(i18n("Failed to start profile on remote INDI Web Manager."));
1185 return;
1186 }
1187
1188 appendLogText(i18n("Starting profile on remote INDI Web Manager..."));
1189 m_RemoteManagerStart = true;
1190 }
1191
1192 runConnection();
1193 };
1194
1195 m_ekosStatus = Ekos::Pending;
1196 emit ekosStatusChanged(m_ekosStatus);
1197
1198 // If we need to use INDI Web Manager
1199 if (m_CurrentProfile->INDIWebManagerPort > 0)
1200 {
1201 appendLogText(i18n("Establishing communication with remote INDI Web Manager..."));
1202 m_RemoteManagerStart = false;
1203 QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>();
1204 connect(watcher, &QFutureWatcher<bool>::finished, this, [this, runConnection, runProfile, watcher]()
1205 {
1206 watcher->deleteLater();
1207
1208 // If it got cancelled by the user, return immediately.
1209 if (m_ekosStatus != Ekos::Pending)
1210 return;
1211
1212 // If web manager is online, try to run the profile in it
1213 if (watcher->result())
1214 {
1215 runProfile();
1216 }
1217 // Else, try to connect directly to INDI server as there could be a chance
1218 // that it is already running.
1219 else
1220 {
1221 appendLogText(i18n("Warning: INDI Web Manager is not online."));
1222 runConnection();
1223 }
1224
1225 });
1226
1227 QFuture<bool> result = INDI::AsyncWebManager::isOnline(m_CurrentProfile);
1228 watcher->setFuture(result);
1229 }
1230 else
1231 {
1232 runConnection();
1233 }
1234 }
1235
1236 // Search for extensions
1237 if (m_extensions.discover())
1238 {
1239 foreach (QString extension, m_extensions.found->keys())
1240 {
1241 extensions::extDetails m_ext = m_extensions.found->value(extension);
1242 extensionCombo->addItem(m_ext.icon, extension);
1243 }
1244 }
1245 if (extensionCombo->count() > 0)
1246 {
1247 groupBox_4->setHidden(false);
1248 }
1249}
1250
1251void Manager::setClientStarted(const QString &host, int port)
1252{
1253 if (managedDrivers.size() > 0)
1254 {
1255 if (m_LocalMode)
1256 {
1257 if (m_CurrentProfile->autoConnect)
1258 appendLogText(i18n("INDI services started on port %1.", port));
1259 else
1260 appendLogText(
1261 i18n("INDI services started on port %1. Please connect devices.", port));
1262 }
1263 else
1264 {
1265 appendLogText(
1266 i18n("INDI services started. Connection to remote INDI server %1:%2 is successful. Waiting for devices...", host, port));
1267 }
1268 }
1269
1270 auto maxTimeout = MAX_LOCAL_INDI_TIMEOUT;
1271
1272 // Parse script, if any
1273 QJsonParseError jsonError;
1274 QJsonArray profileScripts;
1275 QJsonDocument doc = QJsonDocument::fromJson(m_CurrentProfile->scripts, &jsonError);
1276
1277 // If we have any rules that delay startup of drivers, we need to take that into account
1278 // otherwise Ekos would prematurely declare that drivers failed to connect.
1279 if (jsonError.error == QJsonParseError::NoError)
1280 {
1281 profileScripts = doc.array();
1282 for (const auto &oneRule : qAsConst(profileScripts))
1283 {
1284 const auto &oneRuleObj = oneRule.toObject();
1285 auto totalDelay = (oneRuleObj["PreDelay"].toDouble(0) + oneRuleObj["PostDelay"].toDouble(0)) * 1000;
1286 if (totalDelay >= maxTimeout)
1287 maxTimeout = totalDelay + MAX_LOCAL_INDI_TIMEOUT;
1288 }
1289 }
1290
1291 QTimer::singleShot(maxTimeout, this, &Ekos::Manager::checkINDITimeout);
1292}
1293
1294void Manager::setClientFailed(const QString &host, int port, const QString &errorMessage)
1295{
1296 if (m_LocalMode)
1297 appendLogText(i18n("Failed to connect to local INDI server %1:%2", host, port));
1298 else
1299 appendLogText(i18n("Failed to connect to remote INDI server %1:%2", host, port));
1300
1301 //INDIListener::Instance()->disconnect(this);
1302 // qDeleteAll(managedDrivers);
1303 // managedDrivers.clear();
1304 m_ekosStatus = Ekos::Error;
1305 emit ekosStatusChanged(m_ekosStatus);
1306 KSNotification::error(errorMessage, i18n("Error"), 15);
1307}
1308
1309void Manager::setClientTerminated(const QString &host, int port, const QString &errorMessage)
1310{
1311 if (m_LocalMode)
1312 appendLogText(i18n("Lost connection to local INDI server %1:%2", host, port));
1313 else
1314 appendLogText(i18n("Lost connection to remote INDI server %1:%2", host, port));
1315
1316 //INDIListener::Instance()->disconnect(this);
1317 // qDeleteAll(managedDrivers);
1318 // managedDrivers.clear();
1319 m_ekosStatus = Ekos::Error;
1320 emit ekosStatusChanged(m_ekosStatus);
1321 KSNotification::error(errorMessage, i18n("Error"), 15);
1322}
1323
1324void Manager::setServerStarted(const QString &host, int port)
1325{
1326 if (m_LocalMode && m_CurrentProfile->indihub != INDIHub::None)
1327 {
1328 if (QFile(Options::iNDIHubAgent()).exists())
1329 {
1330 indiHubAgent = new QProcess();
1331 QStringList args;
1332
1333 args << "--indi-server" << QString("%1:%2").arg(host).arg(port);
1334 if (m_CurrentProfile->guidertype == Ekos::Guide::GUIDE_PHD2)
1335 args << "--phd2-server" << QString("%1:%2").arg(m_CurrentProfile->guiderhost).arg(m_CurrentProfile->guiderport);
1336 args << "--mode" << INDIHub::toString(m_CurrentProfile->indihub);
1337 indiHubAgent->start(Options::iNDIHubAgent(), args);
1338
1339 qCDebug(KSTARS_EKOS) << "Started INDIHub agent.";
1340 }
1341 }
1342}
1343
1344void Manager::setServerFailed(const QString &host, int port, const QString &message)
1345{
1346 Q_UNUSED(host)
1347 Q_UNUSED(port)
1348 managedDrivers.clear();
1349 m_ekosStatus = Ekos::Error;
1350 emit ekosStatusChanged(m_ekosStatus);
1351 KSNotification::error(message, i18n("Error"), 15);
1352}
1353
1354//void Manager::setServerTerminated(const QString &host, int port, const QString &message)
1355//{
1356// if ((m_LocalMode && managedDrivers.first()->getPort() == port) ||
1357// (currentProfile->host == host && currentProfile->port == port))
1358// {
1359// cleanDevices(false);
1360// if (indiHubAgent)
1361// indiHubAgent->terminate();
1362// }
1363
1364// INDIListener::Instance()->disconnect(this);
1365// qDeleteAll(managedDrivers);
1366// managedDrivers.clear();
1367// m_ekosStatus = Ekos::Error;
1368// emit ekosStatusChanged(m_ekosStatus);
1369// KSNotification::error(message, i18n("Error"), 15);
1370//}
1371
1372void Manager::checkINDITimeout()
1373{
1374 // Don't check anything unless we're still pending
1375 if (m_ekosStatus != Ekos::Pending)
1376 {
1377 // All devices are connected already, nothing to do.
1378 if (m_indiStatus != Ekos::Pending || m_CurrentProfile->portSelector || m_CurrentProfile->autoConnect == false)
1379 return;
1380
1381 QStringList disconnectedDevices;
1382 for (auto &oneDevice : INDIListener::devices())
1383 {
1384 if (oneDevice->isConnected() == false)
1385 disconnectedDevices << oneDevice->getDeviceName();
1386 }
1387
1388 QString message;
1389
1390 if (disconnectedDevices.count() == 1)
1391 message = i18n("Failed to connect to %1. Please ensure device is connected and powered on.", disconnectedDevices.first());
1392 else
1393 message = i18n("Failed to connect to \n%1\nPlease ensure each device is connected and powered on.",
1394 disconnectedDevices.join("\n"));
1395
1396 appendLogText(message);
1397 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1398 return;
1399 }
1400
1401
1402 if (m_DriverDevicesCount <= 0)
1403 {
1404 m_ekosStatus = Ekos::Success;
1405 emit ekosStatusChanged(m_ekosStatus);
1406 return;
1407 }
1408
1409 if (m_LocalMode)
1410 {
1411 QStringList remainingDevices;
1412 for (auto &drv : managedDrivers)
1413 {
1414 if (drv->getDevices().count() == 0)
1415 remainingDevices << QString("+ %1").arg(
1416 drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName());
1417 }
1418
1419 if (remainingDevices.count() == 1)
1420 {
1421 QString message = i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.",
1422 remainingDevices.at(0));
1423 appendLogText(message);
1424 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1425 KNotification::beep(i18n("Ekos startup error"));
1426 }
1427 else
1428 {
1429 QString message = i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected "
1430 "and powered on.", remainingDevices.join("\n"));
1431 appendLogText(message);
1432 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1433 KNotification::beep(i18n("Ekos startup error"));
1434 }
1435 }
1436 else
1437 {
1438 QStringList remainingDevices;
1439
1440 for (auto &driver : m_CurrentProfile->drivers.values())
1441 {
1442 bool driverFound = false;
1443
1444 for (auto &device : INDIListener::devices())
1445 {
1446 if (device->getBaseDevice().getDriverName() == driver)
1447 {
1448 driverFound = true;
1449 break;
1450 }
1451 }
1452
1453 if (driverFound == false)
1454 remainingDevices << QString("+ %1").arg(driver);
1455 }
1456
1457 if (remainingDevices.count() == 1)
1458 {
1459 QString message = i18n("Unable to remotely establish:\n%1\nPlease ensure the device is connected and powered on.",
1460 remainingDevices.at(0));
1461 appendLogText(message);
1462 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1463 KNotification::beep(i18n("Ekos startup error"));
1464 }
1465 else
1466 {
1467 QString message = i18n("Unable to remotely establish the following devices:\n%1\nPlease ensure each device is connected "
1468 "and powered on.", remainingDevices.join("\n"));
1469 appendLogText(message);
1470 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1471 KNotification::beep(i18n("Ekos startup error"));
1472 }
1473 }
1474
1475 m_ekosStatus = Ekos::Error;
1476}
1477
1478bool Manager::isINDIReady()
1479{
1480 // Check if already connected
1481 int nConnected = 0;
1482
1483 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1484
1485 auto devices = INDIListener::devices();
1486 for (auto &device : devices)
1487 {
1488 // Make sure we're not only connected, but also ready (i.e. all properties have already been defined).
1489 if (device->isConnected() && device->isReady())
1490 nConnected++;
1491 }
1492 if (devices.count() == nConnected)
1493 {
1494 m_indiStatus = Ekos::Success;
1495 emit indiStatusChanged(m_indiStatus);
1496 return true;
1497 }
1498
1499 m_indiStatus = Ekos::Pending;
1500 if (previousStatus != m_indiStatus)
1501 emit indiStatusChanged(m_indiStatus);
1502
1503 return false;
1504}
1505
1506void Manager::connectDevices()
1507{
1508 if (isINDIReady())
1509 return;
1510
1511 auto devices = INDIListener::devices();
1512
1513 for (auto &device : devices)
1514 {
1515 qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName();
1516 device->Connect();
1517 }
1518
1519 connectB->setEnabled(false);
1520 disconnectB->setEnabled(true);
1521 extensionCombo->setEnabled(true);
1522 if (extensionCombo->currentText() != "")
1523 extensionB->setEnabled(true);
1524
1525 appendLogText(i18n("Connecting INDI devices..."));
1526}
1527
1528void Manager::disconnectDevices()
1529{
1530 for (auto &device : INDIListener::devices())
1531 {
1532 qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName();
1533 device->Disconnect();
1534 }
1535
1536 appendLogText(i18n("Disconnecting INDI devices..."));
1537}
1538
1539void Manager::cleanDevices(bool stopDrivers)
1540{
1541 if (m_ekosStatus == Ekos::Idle)
1542 return;
1543
1544 if (mountModule())
1545 mountModule()->stopTimers();
1546
1547 ekosLiveClient->message()->setPendingPropertiesEnabled(false);
1548 INDIListener::Instance()->disconnect(this);
1549 DriverManager::Instance()->disconnect(this);
1550
1551 if (managedDrivers.isEmpty() == false)
1552 {
1553 if (m_LocalMode)
1554 {
1555 if (stopDrivers)
1556 DriverManager::Instance()->stopDevices(managedDrivers);
1557 }
1558 else
1559 {
1560 if (stopDrivers)
1561 {
1562 DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first());
1563
1564 if (m_RemoteManagerStart && m_CurrentProfile->INDIWebManagerPort != -1)
1565 INDI::WebManager::stopProfile(m_CurrentProfile);
1566 }
1567 m_RemoteManagerStart = false;
1568 }
1569 }
1570
1571 reset();
1572
1573 profileGroup->setEnabled(true);
1574
1575 appendLogText(i18n("INDI services stopped."));
1576}
1577
1578void Manager::processNewDevice(const QSharedPointer<ISD::GenericDevice> &device)
1579{
1580 qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << device->getDeviceName();
1581
1582 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1583
1584 // for(auto &oneDevice : INDIListener::devices())
1585 // {
1586 // if (oneDevice->getDeviceName() == device->getDeviceName())
1587 // {
1588 // qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring...";
1589 // return;
1590 // }
1591 // }
1592
1593 // Always reset INDI Connection status if we receive a new device
1594 m_indiStatus = Ekos::Idle;
1595 if (previousStatus != m_indiStatus)
1596 emit indiStatusChanged(m_indiStatus);
1597
1598 m_DriverDevicesCount--;
1599
1600 connect(device.get(), &ISD::GenericDevice::ready, this, &Ekos::Manager::setDeviceReady, Qt::UniqueConnection);
1601 connect(device.get(), &ISD::GenericDevice::newMount, this, &Ekos::Manager::addMount, Qt::UniqueConnection);
1602 connect(device.get(), &ISD::GenericDevice::newCamera, this, &Ekos::Manager::addCamera, Qt::UniqueConnection);
1603 connect(device.get(), &ISD::GenericDevice::newGuider, this, &Ekos::Manager::addGuider, Qt::UniqueConnection);
1604 connect(device.get(), &ISD::GenericDevice::newFilterWheel, this, &Ekos::Manager::addFilterWheel, Qt::UniqueConnection);
1605 connect(device.get(), &ISD::GenericDevice::newFocuser, this, &Ekos::Manager::addFocuser, Qt::UniqueConnection);
1606 connect(device.get(), &ISD::GenericDevice::newDome, this, &Ekos::Manager::addDome, Qt::UniqueConnection);
1607 connect(device.get(), &ISD::GenericDevice::newRotator, this, &Ekos::Manager::addRotator, Qt::UniqueConnection);
1608 connect(device.get(), &ISD::GenericDevice::newWeather, this, &Ekos::Manager::addWeather, Qt::UniqueConnection);
1609 connect(device.get(), &ISD::GenericDevice::newDustCap, this, &Ekos::Manager::addDustCap, Qt::UniqueConnection);
1610 connect(device.get(), &ISD::GenericDevice::newLightBox, this, &Ekos::Manager::addLightBox, Qt::UniqueConnection);
1611 connect(device.get(), &ISD::GenericDevice::newGPS, this, &Ekos::Manager::addGPS, Qt::UniqueConnection);
1612
1613 connect(device.get(), &ISD::GenericDevice::Connected, this, &Ekos::Manager::deviceConnected, Qt::UniqueConnection);
1614 connect(device.get(), &ISD::GenericDevice::Disconnected, this, &Ekos::Manager::deviceDisconnected, Qt::UniqueConnection);
1615 connect(device.get(), &ISD::GenericDevice::propertyDefined, this, &Ekos::Manager::processNewProperty, Qt::UniqueConnection);
1616 connect(device.get(), &ISD::GenericDevice::propertyDeleted, this, &Ekos::Manager::processDeleteProperty,
1618 connect(device.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Manager::processUpdateProperty,
1620 connect(device.get(), &ISD::GenericDevice::messageUpdated, this, &Ekos::Manager::processMessage, Qt::UniqueConnection);
1621
1622
1623
1624 // Only look for primary & guider CCDs if we can tell a difference between them
1625 // otherwise rely on saved options
1626 if (m_CurrentProfile->ccd() != m_CurrentProfile->guider())
1627 {
1628 for (auto &oneCamera : INDIListener::devices())
1629 {
1630 if (oneCamera->getDeviceName().startsWith(m_CurrentProfile->ccd(), Qt::CaseInsensitive))
1631 m_PrimaryCamera = QString(oneCamera->getDeviceName());
1632 else if (oneCamera->getDeviceName().startsWith(m_CurrentProfile->guider(), Qt::CaseInsensitive))
1633 m_GuideCamera = QString(oneCamera->getDeviceName());
1634 }
1635 }
1636
1637 if (m_DriverDevicesCount <= 0)
1638 {
1639 m_ekosStatus = Ekos::Success;
1640 emit ekosStatusChanged(m_ekosStatus);
1641
1642 connectB->setEnabled(true);
1643 disconnectB->setEnabled(false);
1644 extensionCombo->setEnabled(false);
1645 extensionB->setEnabled(false);
1646
1647 if (m_LocalMode == false && m_DriverDevicesCount == 0)
1648 {
1649 if (m_CurrentProfile->autoConnect)
1650 appendLogText(i18n("Remote devices established."));
1651 else
1652 appendLogText(i18n("Remote devices established. Please connect devices."));
1653 }
1654 }
1655}
1656
1657void Manager::deviceConnected()
1658{
1659 connectB->setEnabled(false);
1660 disconnectB->setEnabled(true);
1661 processINDIB->setEnabled(false);
1662 extensionCombo->setEnabled(true);
1663 if (extensionCombo->currentText() != "")
1664 extensionB->setEnabled(true);
1665
1666 auto device = qobject_cast<ISD::GenericDevice *>(sender());
1667
1668 if (Options::verboseLogging())
1669 {
1670 qCInfo(KSTARS_EKOS) << device->getDeviceName()
1671 << "Version:" << device->getDriverVersion()
1672 << "Interface:" << device->getDriverInterface()
1673 << "is connected.";
1674 }
1675
1676 if (Options::neverLoadConfig() == false)
1677 {
1678 INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG;
1679
1680 for (auto &oneDevice : INDIListener::devices())
1681 {
1682 if (oneDevice == device)
1683 {
1684 connect(device, &ISD::GenericDevice::propertyUpdated, this, &Ekos::Manager::watchDebugProperty, Qt::UniqueConnection);
1685
1686 auto configProp = device->getBaseDevice().getSwitch("CONFIG_PROCESS");
1687 if (configProp && configProp.getState() == IPS_IDLE)
1688 device->setConfig(tConfig);
1689 break;
1690 }
1691 }
1692 }
1693}
1694
1695void Manager::deviceDisconnected()
1696{
1697 ISD::GenericDevice * dev = static_cast<ISD::GenericDevice *>(sender());
1698
1699 Ekos::CommunicationStatus previousStatus = m_indiStatus;
1700
1701 if (dev != nullptr)
1702 {
1703 if (dev->getState("CONNECTION") == IPS_ALERT)
1704 m_indiStatus = Ekos::Error;
1705 else if (dev->getState("CONNECTION") == IPS_BUSY)
1706 m_indiStatus = Ekos::Pending;
1707 else
1708 m_indiStatus = Ekos::Idle;
1709
1710 if (Options::verboseLogging())
1711 qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected.";
1712
1713 // In case a device fails to connect, display and log a useful message for the user.
1714 if (m_indiStatus == Ekos::Error)
1715 {
1716 QString message = i18n("%1 failed to connect.\nPlease ensure the device is connected and powered on.",
1717 dev->getDeviceName());
1718 appendLogText(message);
1719 KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::General, KSNotification::Warn);
1720 }
1721 else if (m_indiStatus == Ekos::Idle)
1722 {
1723 QString message = i18n("%1 is disconnected.", dev->getDeviceName());
1724 appendLogText(message);
1725 }
1726 }
1727 else
1728 m_indiStatus = Ekos::Idle;
1729
1730 if (previousStatus != m_indiStatus)
1731 emit indiStatusChanged(m_indiStatus);
1732
1733 connectB->setEnabled(true);
1734 disconnectB->setEnabled(false);
1735 processINDIB->setEnabled(true);
1736 extensionCombo->setEnabled(false);
1737 extensionB->setEnabled(false);
1738}
1739
1740void Manager::addMount(ISD::Mount *device)
1741{
1742 ekosLiveClient->message()->sendScopes();
1743
1744 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1745
1746 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1747}
1748
1749void Manager::addCamera(ISD::Camera * device)
1750{
1751 ekosLiveClient.get()->media()->registerCameras();
1752
1753 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1754
1755 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1756}
1757
1758void Manager::addFilterWheel(ISD::FilterWheel * device)
1759{
1760 QString name = device->getDeviceName();
1761 appendLogText(i18n("%1 filter is online.", name));
1762
1763 createFilterManager(device);
1764
1765 emit newDevice(name, device->getDriverInterface());
1766}
1767
1768void Manager::addFocuser(ISD::Focuser *device)
1769{
1770 appendLogText(i18n("%1 focuser is online.", device->getDeviceName()));
1771
1772 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1773}
1774
1775void Manager::addRotator(ISD::Rotator *device)
1776{
1777 appendLogText(i18n("Rotator %1 is online.", device->getDeviceName()));
1778
1779 // createRotatorControl(device);
1780
1781 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1782}
1783
1784void Manager::addDome(ISD::Dome * device)
1785{
1786 appendLogText(i18n("%1 is online.", device->getDeviceName()));
1787
1788 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1789}
1790
1791void Manager::addWeather(ISD::Weather * device)
1792{
1793 appendLogText(i18n("%1 Weather is online.", device->getDeviceName()));
1794
1795 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1796}
1797
1798void Manager::addGPS(ISD::GPS * device)
1799{
1800 appendLogText(i18n("%1 GPS is online.", device->getDeviceName()));
1801
1802 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1803}
1804
1805void Manager::addDustCap(ISD::DustCap * device)
1806{
1807 OpticalTrainManager::Instance()->syncDevices();
1808
1809 appendLogText(i18n("%1 Dust cap is online.", device->getDeviceName()));
1810
1811 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1812}
1813
1814void Manager::addLightBox(ISD::LightBox * device)
1815{
1816 appendLogText(i18n("%1 Light box is online.", device->getDeviceName()));
1817
1818 emit newDevice(device->getDeviceName(), device->getDriverInterface());
1819}
1820
1821void Manager::syncGenericDevice(const QSharedPointer<ISD::GenericDevice> &device)
1822{
1823 createModules(device);
1824
1825 ////////////////////////////////////////////////////////////////////////////////////////////////////
1826 /// Cameras
1827 ////////////////////////////////////////////////////////////////////////////////////////////////////
1828 auto camera = device->getCamera();
1829 if (camera)
1830 {
1831 // Focus Module
1832 if (focusProcess)
1833 {
1834 if (camera->hasCooler())
1835 {
1836 QSharedPointer<ISD::GenericDevice> generic;
1837 if (INDIListener::findDevice(camera->getDeviceName(), generic))
1838 focusModule()->addTemperatureSource(generic);
1839 }
1840 }
1841
1842 }
1843
1844 ////////////////////////////////////////////////////////////////////////////////////////////////////
1845 /// Mount
1846 ////////////////////////////////////////////////////////////////////////////////////////////////////
1847 auto mount = device->getMount();
1848 if (mount)
1849 {
1850 if (mountProcess)
1851 {
1852 QSharedPointer<ISD::GenericDevice> generic;
1853 if (INDIListener::findDevice(mount->getDeviceName(), generic))
1854 {
1855 mountModule()->addTimeSource(generic);
1856 mountModule()->addLocationSource(generic);
1857 }
1858 }
1859
1860 }
1861
1862 ////////////////////////////////////////////////////////////////////////////////////////////////////
1863 /// Focuser
1864 ////////////////////////////////////////////////////////////////////////////////////////////////////
1865 auto focuser = device->getFocuser();
1866 if (focuser)
1867 {
1868 if (focusProcess)
1869 {
1870 // Temperature sources.
1871 QSharedPointer<ISD::GenericDevice> generic;
1872 if (INDIListener::findDevice(focuser->getDeviceName(), generic))
1873 focusModule()->addTemperatureSource(generic);
1874 }
1875 }
1876
1877 ////////////////////////////////////////////////////////////////////////////////////////////////////
1878 /// Filter Wheel
1879 ////////////////////////////////////////////////////////////////////////////////////////////////////
1880
1881 ////////////////////////////////////////////////////////////////////////////////////////////////////
1882 /// Rotators
1883 ////////////////////////////////////////////////////////////////////////////////////////////////////
1884
1885 ////////////////////////////////////////////////////////////////////////////////////////////////////
1886 /// Domes
1887 ////////////////////////////////////////////////////////////////////////////////////////////////////
1888 auto dome = device->getDome();
1889 if (dome)
1890 {
1891 if (captureProcess)
1892 captureProcess->setDome(dome);
1893 if (alignProcess)
1894 alignProcess->setDome(dome);
1895 if (observatoryProcess)
1896 observatoryProcess->setDome(dome);
1897 }
1898
1899 ////////////////////////////////////////////////////////////////////////////////////////////////////
1900 /// Weather
1901 ////////////////////////////////////////////////////////////////////////////////////////////////////
1902 auto weather = device->getWeather();
1903 if (weather)
1904 {
1905 if (observatoryProcess)
1906 observatoryProcess->addWeatherSource(weather);
1907
1908 if (focusProcess)
1909 {
1910 QSharedPointer<ISD::GenericDevice> generic;
1911 if (INDIListener::findDevice(weather->getDeviceName(), generic))
1912 focusModule()->addTemperatureSource(generic);
1913 }
1914 }
1915
1916 ////////////////////////////////////////////////////////////////////////////////////////////////////
1917 /// GPS
1918 ////////////////////////////////////////////////////////////////////////////////////////////////////
1919 auto gps = device->getGPS();
1920 if (gps)
1921 {
1922 if (mountProcess)
1923 {
1924 QSharedPointer<ISD::GenericDevice> generic;
1925 if (INDIListener::findDevice(gps->getDeviceName(), generic))
1926 {
1927 mountModule()->addTimeSource(generic);
1928 mountModule()->addLocationSource(generic);
1929 }
1930 }
1931
1932 }
1933}
1934
1935void Manager::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
1936{
1937 if (alignProcess)
1938 alignModule()->removeDevice(device);
1939 if (captureProcess)
1940 captureProcess->removeDevice(device);
1941 if (focusProcess)
1942 focusModule()->removeDevice(device);
1943 if (mountProcess)
1944 mountModule()->removeDevice(device);
1945 if (guideProcess)
1946 guideProcess->removeDevice(device);
1947 if (observatoryProcess)
1948 observatoryProcess->removeDevice(device);
1949 if (m_PortSelector)
1950 m_PortSelector->removeDevice(device->getDeviceName());
1951
1952 DarkLibrary::Instance()->removeDevice(device);
1953
1954 // Remove from filter managers
1955 for (auto &oneManager : m_FilterManagers)
1956 {
1957 oneManager->removeDevice(device);
1958 }
1959
1960 // Remove from rotator controllers
1961 for (auto &oneController : m_RotatorControllers)
1962 {
1963 oneController->close();
1964 }
1965
1966 appendLogText(i18n("%1 is offline.", device->getDeviceName()));
1967
1968
1969 if (INDIListener::devices().isEmpty())
1970 {
1971 cleanDevices();
1972 removeTabs();
1973 }
1974}
1975
1976void Manager::processDeleteProperty(INDI::Property prop)
1977{
1978 ekosLiveClient.get()->message()->processDeleteProperty(prop);
1979}
1980
1981void Manager::processMessage(int id)
1982{
1983 auto origin = static_cast<ISD::GenericDevice *>(sender());
1984 // Shouldn't happen
1985 if (!origin)
1986 return;
1987 QSharedPointer<ISD::GenericDevice> device;
1988 if (!INDIListener::findDevice(origin->getDeviceName(), device))
1989 return;
1990
1991 ekosLiveClient.get()->message()->processMessage(device, id);
1992}
1993
1994void Manager::processUpdateProperty(INDI::Property prop)
1995{
1996 ekosLiveClient.get()->message()->processUpdateProperty(prop);
1997
1998 if (prop.isNameMatch("CCD_INFO") ||
1999 prop.isNameMatch("GUIDER_INFO") ||
2000 prop.isNameMatch("CCD_FRAME") ||
2001 prop.isNameMatch("GUIDER_FRAME"))
2002 {
2003 if (focusModule() != nullptr)
2004 focusModule()->syncCameraInfo(prop.getDeviceName());
2005
2006 if (guideModule() != nullptr && guideModule()->camera() == prop.getDeviceName())
2007 guideModule()->syncCameraInfo();
2008
2009 if (alignModule() != nullptr && alignModule()->camera() == prop.getDeviceName())
2010 alignModule()->syncCameraInfo();
2011
2012 return;
2013 }
2014}
2015
2016void Manager::processNewProperty(INDI::Property prop)
2017{
2018 QSharedPointer<ISD::GenericDevice> device;
2019 if (!INDIListener::findDevice(prop.getDeviceName(), device))
2020 return;
2021
2022 settleTimer.start();
2023
2024 ekosLiveClient.get()->message()->processNewProperty(prop);
2025
2026 if (prop.isNameMatch("DEVICE_PORT_SCAN") || prop.isNameMatch("CONNECTION_TYPE"))
2027 {
2028 if (!m_PortSelector)
2029 {
2030 m_PortSelector.reset(new Selector::Dialog(KStars::Instance()));
2031 connect(m_PortSelector.get(), &Selector::Dialog::accepted, this, &Manager::setPortSelectionComplete);
2032 }
2033 m_PortSelectorTimer.start();
2034 portSelectorB->setEnabled(true);
2035 m_PortSelector->addDevice(device);
2036 return;
2037 }
2038
2039 // Check if we need to turn on DEBUG for logging purposes
2040 if (prop.isNameMatch("DEBUG"))
2041 {
2042 uint16_t interface = device->getDriverInterface();
2043 if ( opsLogs->getINDIDebugInterface() & interface )
2044 {
2045 // Check if we need to enable debug logging for the INDI drivers.
2046 auto debugSP = prop.getSwitch();
2047 debugSP->at(0)->setState(ISS_ON);
2048 debugSP->at(1)->setState(ISS_OFF);
2049 device->sendNewProperty(debugSP);
2050 }
2051 return;
2052 }
2053
2054 // Handle debug levels for logging purposes
2055 if (prop.isNameMatch("DEBUG_LEVEL"))
2056 {
2057 uint16_t interface = device->getDriverInterface();
2058 // Check if the logging option for the specific device class is on and if the device interface matches it.
2059 if ( opsLogs->getINDIDebugInterface() & interface )
2060 {
2061 // Turn on everything
2062 auto debugLevel = prop.getSwitch();
2063 for (auto &it : *debugLevel)
2064 it.setState(ISS_ON);
2065
2066 device->sendNewProperty(debugLevel);
2067 }
2068 return;
2069 }
2070
2071 if (prop.isNameMatch("ASTROMETRY_SOLVER"))
2072 {
2073 for (auto &oneDevice : INDIListener::devices())
2074 {
2075 if (oneDevice->getDeviceName() == prop.getDeviceName())
2076 {
2077 initAlign();
2078 alignModule()->setAstrometryDevice(oneDevice);
2079 break;
2080 }
2081 }
2082
2083 return;
2084 }
2085
2086 if (focusModule() != nullptr && strstr(prop.getName(), "FOCUS_"))
2087 {
2088 focusModule()->checkFocusers();
2089 return;
2090 }
2091}
2092
2093void Manager::processTabChange()
2094{
2095 auto currentWidget = toolsWidget->currentWidget();
2096
2097 if (alignProcess && alignModule() == currentWidget)
2098 {
2099 auto alignReady = alignModule()->isEnabled() == false && alignModule()->isParserOK();
2100 auto captureReady = captureProcess && captureModule()->isEnabled();
2101 auto mountReady = mountProcess && mountModule()->isEnabled();
2102 if (alignReady && captureReady && mountReady)
2103 alignModule()->setEnabled(true);
2104
2105 alignModule()->checkCamera();
2106 }
2107 else if (captureProcess && currentWidget == captureModule())
2108 {
2109 captureModule()->process()->checkCamera();
2110 }
2111 else if (focusProcess && currentWidget == focusModule())
2112 {
2113 focusModule()->checkCameras();
2114 }
2115 else if (guideProcess && currentWidget == guideModule())
2116 {
2117 guideModule()->checkCamera();
2118 }
2119
2120 updateLog();
2121}
2122
2123void Manager::updateLog()
2124{
2125 QWidget * currentWidget = toolsWidget->currentWidget();
2126
2127 if (currentWidget == setupTab)
2128 ekosLogOut->setPlainText(m_LogText.join("\n"));
2129 else if (currentWidget == alignModule())
2130 ekosLogOut->setPlainText(alignModule()->getLogText());
2131 else if (currentWidget == captureModule())
2132 ekosLogOut->setPlainText(captureModule()->getLogText());
2133 else if (currentWidget == focusModule())
2134 ekosLogOut->setPlainText(focusModule()->getLogText());
2135 else if (currentWidget == guideModule())
2136 ekosLogOut->setPlainText(guideModule()->getLogText());
2137 else if (currentWidget == mountModule())
2138 ekosLogOut->setPlainText(mountModule()->getLogText());
2139 else if (currentWidget == schedulerModule())
2140 ekosLogOut->setPlainText(schedulerModule()->moduleState()->getLogText());
2141 else if (currentWidget == observatoryProcess.get())
2142 ekosLogOut->setPlainText(observatoryProcess->getLogText());
2143 else if (currentWidget == analyzeProcess.get())
2144 ekosLogOut->setPlainText(analyzeProcess->getLogText());
2145
2146#ifdef Q_OS_MACOS
2147 repaint(); //This is a band-aid for a bug in QT 5.10.0
2148#endif
2149}
2150
2151void Manager::appendLogText(const QString &text)
2152{
2153 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
2154 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
2155
2156 qCInfo(KSTARS_EKOS) << text;
2157
2158 emit newLog(text);
2159
2160 updateLog();
2161}
2162
2163void Manager::clearLog()
2164{
2165 QWidget * currentWidget = toolsWidget->currentWidget();
2166
2167 if (currentWidget == setupTab)
2168 {
2169 m_LogText.clear();
2170 updateLog();
2171 }
2172 else if (currentWidget == alignModule())
2173 alignModule()->clearLog();
2174 else if (currentWidget == captureModule())
2175 captureModule()->clearLog();
2176 else if (currentWidget == focusModule())
2177 focusModule()->clearLog();
2178 else if (currentWidget == guideModule())
2179 guideModule()->clearLog();
2180 else if (currentWidget == mountModule())
2181 mountModule()->clearLog();
2182 else if (currentWidget == schedulerModule())
2183 schedulerModule()->process()->clearLog();
2184 else if (currentWidget == observatoryProcess.get())
2185 observatoryProcess->clearLog();
2186 else if (currentWidget == analyzeProcess.get())
2187 analyzeProcess->clearLog();
2188}
2189
2190void Manager::initCapture()
2191{
2192 if (captureModule() != nullptr)
2193 return;
2194
2195 captureProcess.reset(new Capture());
2196
2197 emit newModule("Capture");
2198
2199 // retrieve the meridian flip state machine from the mount module if the module is already present
2200 if (mountModule() != nullptr)
2201 captureModule()->setMeridianFlipState(mountModule()->getMeridianFlipState());
2202
2203 capturePreview->shareCaptureModule(captureModule());
2204 int index = addModuleTab(EkosModule::Capture, captureModule(), QIcon(":/icons/ekos_ccd.png"));
2205 toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD"));
2206 if (Options::ekosLeftIcons())
2207 {
2208 QTransform trans;
2209 trans.rotate(90);
2210 QIcon icon = toolsWidget->tabIcon(index);
2211 QPixmap pix = icon.pixmap(QSize(48, 48));
2212 icon = QIcon(pix.transformed(trans));
2213 toolsWidget->setTabIcon(index, icon);
2214 }
2215 connect(captureModule(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog);
2216 connect(captureModule(), &Ekos::Capture::newLog, this, [this]()
2217 {
2218 QJsonObject cStatus =
2219 {
2220 {"log", captureModule()->getLogText()}
2221 };
2222
2223 ekosLiveClient.get()->message()->updateCaptureStatus(cStatus);
2224 });
2225 connect(captureModule(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus);
2226 connect(captureModule(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress);
2227 connect(captureModule(), &Ekos::Capture::driverTimedout, this, &Ekos::Manager::restartDriver);
2228 connect(captureModule(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress);
2229 capturePreview->setEnabled(true);
2230
2231 // display capture status changes
2232 connect(captureModule(), &Ekos::Capture::newFilterStatus, capturePreview->captureStatusWidget,
2233 &LedStatusWidget::setFilterState);
2234
2235 // display target drift
2236 connect(schedulerModule(), &Ekos::Scheduler::targetDistance,
2238 connect(schedulerModule(), &Ekos::Scheduler::targetDistance, this, [this](double distance)
2239 {
2240 capturePreview->updateTargetDistance(distance);
2241 });
2242
2243
2244 connectModules();
2245}
2246
2247void Manager::initAlign()
2248{
2249 if (alignModule() != nullptr)
2250 return;
2251
2252 alignProcess.reset(new Ekos::Align(m_CurrentProfile));
2253
2254 emit newModule("Align");
2255
2256 int index = addModuleTab(EkosModule::Align, alignModule(), QIcon(":/icons/ekos_align.png"));
2257 toolsWidget->tabBar()->setTabToolTip(index, i18n("Align"));
2258 connect(alignModule(), &Ekos::Align::newLog, this, &Ekos::Manager::updateLog);
2259 connect(alignModule(), &Ekos::Align::newLog, this, [this]()
2260 {
2261 QJsonObject cStatus =
2262 {
2263 {"log", alignModule()->getLogText()}
2264 };
2265
2266 ekosLiveClient.get()->message()->updateAlignStatus(cStatus);
2267 });
2268 connect(alignModule(), &Ekos::Align::newDownloadProgress, this, [this](QString info)
2269 {
2270 QJsonObject cStatus =
2271 {
2272 {"downloadProgress", info}
2273 };
2274
2275 ekosLiveClient.get()->message()->updateAlignStatus(cStatus);
2276 });
2277 if (Options::ekosLeftIcons())
2278 {
2279 QTransform trans;
2280 trans.rotate(90);
2281 QIcon icon = toolsWidget->tabIcon(index);
2282 QPixmap pix = icon.pixmap(QSize(48, 48));
2283 icon = QIcon(pix.transformed(trans));
2284 toolsWidget->setTabIcon(index, icon);
2285 }
2286
2287 connectModules();
2288}
2289
2290void Manager::initFocus()
2291{
2292 if (focusModule() != nullptr)
2293 return;
2294
2295 focusProcess.reset(new Ekos::FocusModule());
2296
2297 emit newModule("Focus");
2298
2299 int index = addModuleTab(EkosModule::Focus, focusModule(), QIcon(":/icons/ekos_focus.png"));
2300
2301 toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus"));
2302
2303 // Focus <---> Manager connections (restricted to the main focuser)
2304 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newStatus, this, &Ekos::Manager::updateFocusStatus);
2305 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newStarPixmap, focusProgressWidget,
2306 &Ekos::FocusProgressWidget::updateFocusStarPixmap);
2307 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR);
2308 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::focuserTimedout, this, &Ekos::Manager::restartDriver);
2309 connect(focusModule(), &Ekos::FocusModule::newLog, this, [this]()
2310 {
2311 // update the logging in the client
2312 updateLog();
2313
2314 QJsonObject cStatus =
2315 {
2316 {"log", focusModule()->getLogText()}
2317 };
2318
2319 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2320 });
2321 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newFocusAdvisorMessage, this, [this](const QString & message)
2322 {
2323 QJsonObject cStatus =
2324 {
2325 {"focusAdvisorMessage", message}
2326 };
2327
2328 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2329 });
2330 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newFocusAdvisorStage, ekosLiveClient.get()->message(),
2331 [this](int stage)
2332 {
2333 QJsonObject cStatus =
2334 {
2335 {"focusAdvisorStage", stage}
2336 };
2337
2338 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2339 });
2340
2341
2342 // connect HFR plot widget
2343 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::initHFRPlot, [this](QString str, double starUnits, bool minimum,
2344 bool useWeights,
2345 bool showPosition)
2346 {
2347 focusProgressWidget->hfrVPlot->init(str, starUnits, minimum, useWeights, showPosition);
2348 QJsonObject cStatus =
2349 {
2350 {"focusinitHFRPlot", true}
2351 };
2352
2353 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2354 });
2355
2356 // Update title
2357 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::setTitle, [this](const QString & title, bool plot)
2358 {
2359 focusProgressWidget->hfrVPlot->setTitle(title, plot);
2360 QJsonObject cStatus =
2361 {
2362 {"title", title}
2363 };
2364
2365 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2366 });
2367 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::setTitle, focusProgressWidget->hfrVPlot,
2368 &FocusHFRVPlot::setTitle);
2369 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::redrawHFRPlot, focusProgressWidget->hfrVPlot,
2370 &FocusHFRVPlot::redraw);
2371 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newHFRPlotPosition, focusProgressWidget->hfrVPlot,
2372 &FocusHFRVPlot::addPosition);
2373 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::drawPolynomial, focusProgressWidget->hfrVPlot,
2374 &FocusHFRVPlot::drawPolynomial);
2375 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::finalUpdates, focusProgressWidget->hfrVPlot,
2376 &FocusHFRVPlot::finalUpdates);
2377 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::minimumFound, focusProgressWidget->hfrVPlot,
2378 &FocusHFRVPlot::drawMinimum);
2379 // setup signal/slots for Linear 1 Pass focus algo
2380 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::drawCurve, focusProgressWidget->hfrVPlot,
2381 &FocusHFRVPlot::drawCurve);
2382 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::drawCFZ, focusProgressWidget->hfrVPlot, &FocusHFRVPlot::drawCFZ);
2383
2384 if (Options::ekosLeftIcons())
2385 {
2386 QTransform trans;
2387 trans.rotate(90);
2388 QIcon icon = toolsWidget->tabIcon(index);
2389 QPixmap pix = icon.pixmap(QSize(48, 48));
2390 icon = QIcon(pix.transformed(trans));
2391 toolsWidget->setTabIcon(index, icon);
2392 }
2393
2394 focusProgressWidget->init();
2395 focusProgressWidget->setEnabled(true);
2396
2397 for (auto &oneDevice : INDIListener::devices())
2398 {
2399 auto prop1 = oneDevice->getProperty("CCD_TEMPERATURE");
2400 auto prop2 = oneDevice->getProperty("FOCUSER_TEMPERATURE");
2401 auto prop3 = oneDevice->getProperty("WEATHER_PARAMETERS");
2402 if (prop1 || prop2 || prop3)
2403 focusModule()->addTemperatureSource(oneDevice);
2404 }
2405
2406 connectModules();
2407}
2408
2409void Manager::updateCurrentHFR(double newHFR, int position, bool inAutofocus)
2410{
2411 Q_UNUSED(inAutofocus);
2412 focusProgressWidget->updateCurrentHFR(newHFR);
2413
2414 QJsonObject cStatus =
2415 {
2416 {"hfr", newHFR},
2417 {"pos", position}
2418 };
2419
2420 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2421}
2422
2423void Manager::updateSigmas(double ra, double de)
2424{
2425 guideManager->updateSigmas(ra, de);
2426
2427 QJsonObject cStatus = { {"rarms", ra}, {"derms", de} };
2428
2429 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2430}
2431
2432void Manager::initMount()
2433{
2434 if (mountModule() != nullptr)
2435 return;
2436
2437 mountProcess.reset(new Ekos::Mount());
2438
2439 // share the meridian flip state with capture if the module is already present
2440 if (captureModule() != nullptr)
2441 captureModule()->setMeridianFlipState(mountModule()->getMeridianFlipState());
2442
2443 emit newModule("Mount");
2444
2445 int index = addModuleTab(EkosModule::Mount, mountModule(), QIcon(":/icons/ekos_mount.png"));
2446
2447 toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount"));
2448 connect(mountModule(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog);
2449 connect(mountModule(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords);
2450 connect(mountModule(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus);
2451 connect(mountModule(), &Ekos::Mount::newTargetName, this, [this](const QString & name)
2452 {
2453 setTarget(name);
2454 });
2455 connect(mountModule(), &Ekos::Mount::pierSideChanged, this, [&](ISD::Mount::PierSide side)
2456 {
2457 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"pierSide", side}}));
2458 });
2459 connect(mountModule()->getMeridianFlipState().get(),
2460 &Ekos::MeridianFlipState::newMountMFStatus, [&](MeridianFlipState::MeridianFlipMountState status)
2461 {
2462 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject(
2463 {
2464 {"meridianFlipStatus", status},
2465 }));
2466 });
2467 connect(mountModule()->getMeridianFlipState().get(),
2468 &Ekos::MeridianFlipState::newMeridianFlipMountStatusText, [&](const QString & text)
2469 {
2470 // Throttle this down
2471 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject(
2472 {
2473 {"meridianFlipText", text},
2474 }), mountModule()->getMeridianFlipState()->getMeridianFlipMountState() == MeridianFlipState::MOUNT_FLIP_NONE);
2475 meridianFlipStatusWidget->setStatus(text);
2476 });
2477 connect(mountModule(), &Ekos::Mount::autoParkCountdownUpdated, this, [&](const QString & text)
2478 {
2479 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"autoParkCountdown", text}}), true);
2480 });
2481
2482 connect(mountModule(), &Ekos::Mount::trainChanged, ekosLiveClient.get()->message(),
2483 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
2484
2485 connect(mountModule(), &Ekos::Mount::slewRateChanged, this, [&](int slewRate)
2486 {
2487 QJsonObject status = { { "slewRate", slewRate} };
2488 ekosLiveClient.get()->message()->updateMountStatus(status);
2489 });
2490
2491 if (Options::ekosLeftIcons())
2492 {
2493 QTransform trans;
2494 trans.rotate(90);
2495 QIcon icon = toolsWidget->tabIcon(index);
2496 QPixmap pix = icon.pixmap(QSize(48, 48));
2497 icon = QIcon(pix.transformed(trans));
2498 toolsWidget->setTabIcon(index, icon);
2499 }
2500
2501 mountGroup->setEnabled(true);
2502 capturePreview->shareMountModule(mountModule());
2503
2504 connectModules();
2505}
2506
2507void Manager::initGuide()
2508{
2509 if (guideModule() == nullptr)
2510 {
2511 guideProcess.reset(new Ekos::Guide());
2512
2513 emit newModule("Guide");
2514 }
2515
2516 if (toolsWidget->indexOf(guideModule()) == -1)
2517 {
2518 // if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices.value(KSTARS_TELESCOPE)->isConnected())
2519 // guideProcess->addMount(managedDevices.value(KSTARS_TELESCOPE));
2520
2521 int index = addModuleTab(EkosModule::Guide, guideModule(), QIcon(":/icons/ekos_guide.png"));
2522 toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide"));
2523 connect(guideModule(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog);
2524 connect(guideModule(), &Ekos::Guide::driverTimedout, this, &Ekos::Manager::restartDriver);
2525
2526 guideManager->setEnabled(true);
2527
2528 connect(guideModule(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus);
2529 connect(guideModule(), &Ekos::Guide::newStarPixmap, guideManager, &Ekos::GuideManager::updateGuideStarPixmap);
2530 connect(guideModule(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas);
2531 connect(guideModule(), &Ekos::Guide::newAxisDelta, [&](double ra, double de)
2532 {
2533 QJsonObject status = { { "drift_ra", ra}, {"drift_de", de} };
2534 ekosLiveClient.get()->message()->updateGuideStatus(status);
2535 });
2536 connect(guideModule(), &Ekos::Guide::newLog, ekosLiveClient.get()->message(),
2537 [this]()
2538 {
2539 QJsonObject cStatus =
2540 {
2541 {"log", guideModule()->getLogText()}
2542 };
2543
2544 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2545 });
2546
2547 if (Options::ekosLeftIcons())
2548 {
2549 QTransform trans;
2550 trans.rotate(90);
2551 QIcon icon = toolsWidget->tabIcon(index);
2552 QPixmap pix = icon.pixmap(QSize(48, 48));
2553 icon = QIcon(pix.transformed(trans));
2554 toolsWidget->setTabIcon(index, icon);
2555 }
2556 guideManager->init(guideModule());
2557 }
2558
2559 connectModules();
2560}
2561
2562void Manager::initObservatory()
2563{
2564 if (observatoryProcess.get() == nullptr)
2565 {
2566 // Initialize the Observatory Module
2567 observatoryProcess.reset(new Ekos::Observatory());
2568
2569 emit newModule("Observatory");
2570
2571 int index = addModuleTab(EkosModule::Observatory, observatoryProcess.get(), QIcon(":/icons/ekos_observatory.png"));
2572 toolsWidget->tabBar()->setTabToolTip(index, i18n("Observatory"));
2573 connect(observatoryProcess.get(), &Ekos::Observatory::newLog, this, &Ekos::Manager::updateLog);
2574
2575 if (Options::ekosLeftIcons())
2576 {
2577 QTransform trans;
2578 trans.rotate(90);
2579 QIcon icon = toolsWidget->tabIcon(index);
2580 QPixmap pix = icon.pixmap(QSize(48, 48));
2581 icon = QIcon(pix.transformed(trans));
2582 toolsWidget->setTabIcon(index, icon);
2583 }
2584 }
2585}
2586
2587void Manager::addGuider(ISD::Guider * device)
2588{
2589 appendLogText(i18n("Guider port from %1 is ready.", device->getDeviceName()));
2590}
2591
2592void Manager::removeTabs()
2593{
2594 disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange);
2595
2596 for (int i = numPermanentTabs; i < toolsWidget->count(); i++)
2597 toolsWidget->removeTab(i);
2598
2599 alignProcess.reset();
2600 captureProcess.reset();
2601 focusProcess.reset();
2602 guideProcess.reset();
2603 mountProcess.reset();
2604 observatoryProcess.reset();
2605
2606 connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection);
2607}
2608
2609bool Manager::isRunning(const QString &process)
2610{
2611 QProcess ps;
2612#ifdef Q_OS_MACOS
2613 ps.start("pgrep", QStringList() << process);
2614 ps.waitForFinished();
2615 QString output = ps.readAllStandardOutput();
2616 return output.length() > 0;
2617#else
2618 ps.start("ps", QStringList() << "-o"
2619 << "comm"
2620 << "--no-headers"
2621 << "-C" << process);
2622 ps.waitForFinished();
2623 QString output = ps.readAllStandardOutput();
2624 return output.contains(process);
2625#endif
2626}
2627
2628void Manager::addObjectToScheduler(SkyObject * object)
2629{
2630 if (schedulerModule() != nullptr)
2631 schedulerModule()->addObject(object);
2632}
2633
2634QString Manager::getCurrentJobName()
2635{
2636 return schedulerModule()->getCurrentJobName();
2637}
2638
2639bool Manager::setProfile(const QString &profileName)
2640{
2641 int index = profileCombo->findText(profileName);
2642
2643 if (index < 0)
2644 return false;
2645
2646 profileCombo->setCurrentIndex(index);
2647
2648 return true;
2649}
2650
2651void Manager::editNamedProfile(const QJsonObject &profileInfo)
2652{
2653 ProfileEditor editor(this);
2654 setProfile(profileInfo["name"].toString());
2655 if (getCurrentProfile(m_CurrentProfile))
2656 {
2657 editor.setPi(m_CurrentProfile);
2658 editor.setSettings(profileInfo);
2659 editor.saveProfile();
2660 }
2661}
2662
2663void Manager::addNamedProfile(const QJsonObject &profileInfo)
2664{
2665 ProfileEditor editor(this);
2666
2667 editor.setSettings(profileInfo);
2668 editor.saveProfile();
2669 profiles.clear();
2670 loadProfiles();
2671 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2672 getCurrentProfile(m_CurrentProfile);
2673}
2674
2675void Manager::deleteNamedProfile(const QString &name)
2676{
2677 if (!getCurrentProfile(m_CurrentProfile))
2678 return;
2679
2680 for (auto &pi : profiles)
2681 {
2682 // Do not delete an actively running profile
2683 // Do not delete simulator profile
2684 if (pi->name == "Simulators" || pi->name != name || (pi.get() == m_CurrentProfile && ekosStatus() != Idle))
2685 continue;
2686
2687 KStarsData::Instance()->userdb()->PurgeProfile(pi);
2688 profiles.clear();
2689 loadProfiles();
2690 getCurrentProfile(m_CurrentProfile);
2691 return;
2692 }
2693}
2694
2695QJsonObject Manager::getNamedProfile(const QString &name)
2696{
2697 QJsonObject profileInfo;
2698
2699 // Get current profile
2700 for (auto &pi : profiles)
2701 {
2702 if (name == pi->name)
2703 return pi->toJson();
2704 }
2705
2706 return QJsonObject();
2707}
2708
2709QStringList Manager::getProfiles()
2710{
2711 QStringList profiles;
2712
2713 for (int i = 0; i < profileCombo->count(); i++)
2714 profiles << profileCombo->itemText(i);
2715
2716 return profiles;
2717}
2718
2719void Manager::addProfile()
2720{
2721 ProfileEditor editor(this);
2722
2723 if (editor.exec() == QDialog::Accepted)
2724 {
2725 profiles.clear();
2726 loadProfiles();
2727 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2728 }
2729
2730 getCurrentProfile(m_CurrentProfile);
2731}
2732
2733void Manager::editProfile()
2734{
2735 ProfileEditor editor(this);
2736
2737 if (getCurrentProfile(m_CurrentProfile))
2738 {
2739
2740 editor.setPi(m_CurrentProfile);
2741
2742 if (editor.exec() == QDialog::Accepted)
2743 {
2744 int currentIndex = profileCombo->currentIndex();
2745
2746 profiles.clear();
2747 loadProfiles();
2748 profileCombo->setCurrentIndex(currentIndex);
2749 }
2750
2751 getCurrentProfile(m_CurrentProfile);
2752 }
2753}
2754
2755void Manager::deleteProfile()
2756{
2757 if (!getCurrentProfile(m_CurrentProfile))
2758 return;
2759
2760 if (m_CurrentProfile->name == "Simulators")
2761 return;
2762
2763 auto executeDeleteProfile = [&]()
2764 {
2765 KStarsData::Instance()->userdb()->PurgeProfile(m_CurrentProfile);
2766 profiles.clear();
2767 loadProfiles();
2768 getCurrentProfile(m_CurrentProfile);
2769 };
2770
2771 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeDeleteProfile]()
2772 {
2773 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
2774 KSMessageBox::Instance()->disconnect(this);
2775 executeDeleteProfile();
2776 });
2777
2778 KSMessageBox::Instance()->questionYesNo(i18n("Are you sure you want to delete the profile?"),
2779 i18n("Confirm Delete"));
2780
2781}
2782
2783void Manager::wizardProfile()
2784{
2785 ProfileWizard wz;
2786 if (wz.exec() != QDialog::Accepted)
2787 return;
2788
2789 ProfileEditor editor(this);
2790
2791 editor.setProfileName(wz.profileName);
2792 editor.setAuxDrivers(wz.selectedAuxDrivers());
2793 if (wz.useInternalServer == false)
2794 editor.setHostPort(wz.host, wz.port);
2795 editor.setWebManager(wz.useWebManager);
2796 editor.setGuiderType(wz.selectedExternalGuider());
2797 // Disable connection options
2798 editor.setConnectionOptionsEnabled(false);
2799
2800 if (editor.exec() == QDialog::Accepted)
2801 {
2802 profiles.clear();
2803 loadProfiles();
2804 profileCombo->setCurrentIndex(profileCombo->count() - 1);
2805 }
2806
2807 getCurrentProfile(m_CurrentProfile);
2808}
2809
2810bool Manager::getCurrentProfile(QSharedPointer<ProfileInfo> &profile) const
2811{
2812 // Get current profile
2813 for (auto &pi : profiles)
2814 {
2815 if (profileCombo->currentText() == pi->name)
2816 {
2817 profile = pi;
2818 return true;
2819 }
2820 }
2821
2822 return false;
2823}
2824
2825void Manager::updateProfileLocation(const QSharedPointer<ProfileInfo> &profile)
2826{
2827 if (profile->city.isEmpty() == false)
2828 {
2829 bool cityFound = KStars::Instance()->setGeoLocation(profile->city, profile->province, profile->country);
2830 if (cityFound)
2831 appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName()));
2832 else
2833 appendLogText(i18n("Failed to update site location to %1. City not found.",
2834 KStarsData::Instance()->geo()->fullName()));
2835 }
2836}
2837
2838void Manager::updateMountStatus(ISD::Mount::Status status)
2839{
2840 static ISD::Mount::Status lastStatus = ISD::Mount::MOUNT_IDLE;
2841
2842 if (status == lastStatus)
2843 return;
2844
2845 lastStatus = status;
2846
2847 mountStatus->setMountState(mountModule()->statusString(), status);
2848 mountStatus->setStyleSheet(QString());
2849
2850 QJsonObject cStatus =
2851 {
2852 {"status", mountModule()->statusString(false)}
2853 };
2854
2855 ekosLiveClient.get()->message()->updateMountStatus(cStatus);
2856}
2857
2858void Manager::updateMountCoords(const SkyPoint position, ISD::Mount::PierSide pierSide, const dms &ha)
2859{
2860 Q_UNUSED(pierSide)
2861 raOUT->setText(position.ra().toHMSString());
2862 decOUT->setText(position.dec().toDMSString());
2863 azOUT->setText(position.az().toDMSString());
2864 altOUT->setText(position.alt().toDMSString());
2865
2866 QJsonObject cStatus =
2867 {
2868 {"ra", dms::fromString(raOUT->text(), false).Degrees()},
2869 {"de", dms::fromString(decOUT->text(), true).Degrees()},
2870 {"ra0", position.ra0().Degrees()},
2871 {"de0", position.dec0().Degrees()},
2872 {"az", dms::fromString(azOUT->text(), true).Degrees()},
2873 {"at", dms::fromString(altOUT->text(), true).Degrees()},
2874 {"ha", ha.Degrees()},
2875 };
2876
2877 ekosLiveClient.get()->message()->updateMountStatus(cStatus, true);
2878}
2879
2880void Manager::updateCaptureStatus(Ekos::CaptureState status, const QString &trainname)
2881{
2882 capturePreview->updateCaptureStatus(status, captureModule()->isActiveJobPreview(), trainname);
2883
2884 switch (status)
2885 {
2886 case Ekos::CAPTURE_IDLE:
2887 /* Fall through */
2889 /* Fall through */
2891 m_CountdownTimer.stop();
2892 break;
2894 m_CountdownTimer.start();
2895 break;
2896 default:
2897 break;
2898 }
2899
2900 QJsonObject cStatus =
2901 {
2902 {"status", QString::fromLatin1(captureStates[status].untranslatedText())},
2903 {"seqt", capturePreview->captureCountsWidget->sequenceRemainingTime->text()},
2904 {"ovt", capturePreview->captureCountsWidget->overallRemainingTime->text()},
2905 {"train", trainname}
2906 };
2907
2908 ekosLiveClient.get()->message()->updateCaptureStatus(cStatus);
2909}
2910
2911void Manager::updateCaptureProgress(const QSharedPointer<SequenceJob> &job, const QSharedPointer<FITSData> &data,
2912 const QString &trainname)
2913{
2914 capturePreview->updateJobProgress(job, data, trainname);
2915
2916 QJsonObject status =
2917 {
2918 {"seqv", job->getCompleted()},
2919 {"seqr", job->getCoreProperty(SequenceJob::SJ_Count).toInt()},
2920 {"seql", capturePreview->captureCountsWidget->sequenceRemainingTime->text()}
2921 };
2922
2923 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2924
2925 if (data && job->getStatus() == JOB_BUSY)
2926 {
2927 // Normally FITS Viewer would trigger an upload
2928 // If off, then rely on summary view or raw data
2929 if (Options::useFITSViewer() == false)
2930 ekosLiveClient.get()->media()->sendData(data, data->objectName());
2931
2932 if (job->jobType() != SequenceJob::JOBTYPE_PREVIEW)
2933 ekosLiveClient.get()->cloud()->sendData(data, data->objectName());
2934 }
2935}
2936
2937void Manager::updateExposureProgress(const QSharedPointer<SequenceJob> &job, const QString &trainname)
2938{
2939 QJsonObject status
2940 {
2941 {"expv", job->getExposeLeft()},
2942 {"expr", job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()},
2943 {"train", trainname}
2944 };
2945
2946 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2947}
2948
2949void Manager::updateCaptureCountDown()
2950{
2951 capturePreview->updateCaptureCountDown(-1);
2952
2953 QJsonObject status =
2954 {
2955 {"seqt", capturePreview->captureCountsWidget->sequenceRemainingTime->text()},
2956 {"ovt", capturePreview->captureCountsWidget->overallRemainingTime->text()},
2957 {"ovp", capturePreview->captureCountsWidget->gr_overallProgressBar->value()},
2958 {"ovl", capturePreview->captureCountsWidget->gr_overallLabel->text()}
2959 };
2960
2961 ekosLiveClient.get()->message()->updateCaptureStatus(status);
2962}
2963
2964
2965void Manager::updateFocusStatus(Ekos::FocusState status)
2966{
2967 focusProgressWidget->updateFocusStatus(status);
2968
2969 QJsonObject cStatus =
2970 {
2971 {"status", getFocusStatusString(status, false)}
2972 };
2973
2974 ekosLiveClient.get()->message()->updateFocusStatus(cStatus);
2975}
2976
2977void Manager::updateGuideStatus(Ekos::GuideState status)
2978{
2979 guideManager->updateGuideStatus(status);
2980 QJsonObject cStatus =
2981 {
2982 {"status", getGuideStatusString(status, false)}
2983 };
2984
2985 ekosLiveClient.get()->message()->updateGuideStatus(cStatus);
2986}
2987
2988void Manager::setTarget(const QString &name)
2989{
2990 capturePreview->targetLabel->setVisible(!name.isEmpty());
2991 capturePreview->mountTarget->setVisible(!name.isEmpty());
2992 capturePreview->mountTarget->setText(name);
2993 ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", name}}));
2994 // forward it to the mount tab
2995 if (mountModule())
2996 mountModule()->setTargetName(name);
2997}
2998
2999void Manager::showEkosOptions()
3000{
3001 QWidget * currentWidget = toolsWidget->currentWidget();
3002
3003 if (alignModule() && alignModule() == currentWidget)
3004 {
3005 KConfigDialog * alignSettings = KConfigDialog::exists("alignsettings");
3006 if (alignSettings)
3007 {
3008 alignSettings->setEnabled(true);
3009 alignSettings->show();
3010 }
3011 return;
3012 }
3013
3014 if (guideModule() && guideModule() == currentWidget)
3015 {
3016 KConfigDialog::showDialog("guidesettings");
3017 return;
3018 }
3019
3020 if (focusModule() && focusModule() == currentWidget)
3021 {
3022 focusModule()->showOptions();
3023 return;
3024 }
3025
3026 if (schedulerModule() == currentWidget)
3027 {
3028 KConfigDialog * cDialog = KConfigDialog::exists("schedulersettings");
3029 if(cDialog)
3030 {
3031 cDialog->show();
3032 cDialog->raise(); // for MacOS
3033 cDialog->activateWindow(); // for Windows
3034 }
3035 return;
3036 }
3037
3038 if(captureModule() == currentWidget)
3039 {
3040 KConfigDialog * cDialog = KConfigDialog::exists("capturesettings");
3041 if(cDialog)
3042 {
3043 cDialog->show();
3044 cDialog->raise(); // for MacOS
3045 cDialog->activateWindow(); // for Windows
3046 }
3047 return;
3048 }
3049
3050 const bool isAnalyze = (analyzeProcess.get() && analyzeProcess.get() == currentWidget);
3051 if (isAnalyze)
3052 {
3053 if (opsEkos)
3054 {
3055 int index = 0;
3056 if (isAnalyze) index = 1;
3057 opsEkos->setCurrentIndex(index);
3058 }
3059 KConfigDialog * cDialog = KConfigDialog::exists("settings");
3060 if (cDialog)
3061 {
3062 cDialog->setCurrentPage(ekosOptionsWidget);
3063 cDialog->show();
3064 cDialog->raise(); // for MacOS
3065 cDialog->activateWindow(); // for Windows
3066 }
3067 return;
3068 }
3069
3070 if (ekosOptionsWidget == nullptr)
3071 {
3072 optionsB->click();
3073 }
3074 else if (KConfigDialog::showDialog("settings"))
3075 {
3076 KConfigDialog * cDialog = KConfigDialog::exists("settings");
3077 if (cDialog)
3078 {
3079 cDialog->setCurrentPage(ekosOptionsWidget);
3080 cDialog->show();
3081 cDialog->raise(); // for MacOS
3082 cDialog->activateWindow(); // for Windows
3083 }
3084 }
3085}
3086
3087void Manager::updateDebugInterfaces()
3088{
3090
3091 for (auto &device : INDIListener::devices())
3092 {
3093 auto debugProp = device->getProperty("DEBUG");
3094 if (!debugProp)
3095 continue;
3096
3097 auto debugSP = debugProp.getSwitch();
3098
3099 // Check if the debug interface matches the driver device class
3100 if ( ( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) &&
3101 debugSP->sp[0].s != ISS_ON)
3102 {
3103 debugSP->at(0)->setState(ISS_ON);
3104 debugSP->at(1)->setState(ISS_OFF);
3105 device->sendNewProperty(debugSP);
3106 appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName()));
3107 }
3108 else if ( !( opsLogs->getINDIDebugInterface() & device->getDriverInterface() ) &&
3109 debugSP->sp[0].s != ISS_OFF)
3110 {
3111 debugSP->at(0)->setState(ISS_OFF);
3112 debugSP->at(1)->setState(ISS_ON);
3113 device->sendNewProperty(debugSP);
3114 appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName()));
3115 }
3116
3117 if (opsLogs->isINDISettingsChanged())
3118 device->setConfig(SAVE_CONFIG);
3119 }
3120}
3121
3122void Manager::watchDebugProperty(INDI::Property prop)
3123{
3124 if (prop.isNameMatch("DEBUG"))
3125 {
3126 auto svp = prop.getSwitch();
3127
3128 ISD::GenericDevice * deviceInterface = qobject_cast<ISD::GenericDevice *>(sender());
3129
3130 // We don't process pure general interfaces
3131 if (deviceInterface->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE)
3132 return;
3133
3134 // If debug was turned off, but our logging policy requires it then turn it back on.
3135 // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings
3136 if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF &&
3137 (opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface()))
3138 {
3139 svp->sp[0].s = ISS_ON;
3140 svp->sp[1].s = ISS_OFF;
3141 deviceInterface->sendNewProperty(svp);
3142 appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName()));
3143 }
3144 // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings.
3145 // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in
3146 // the log settings, then if the user turns off only CCD logging, the debug logging is NOT
3147 // turned off until he turns off Filter Wheel logging as well.
3148 else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON
3149 && !(opsLogs->getINDIDebugInterface() & deviceInterface->getDriverInterface()))
3150 {
3151 svp->sp[0].s = ISS_OFF;
3152 svp->sp[1].s = ISS_ON;
3153 deviceInterface->sendNewProperty(svp);
3154 appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName()));
3155 }
3156 }
3157}
3158
3159void Manager::announceEvent(const QString &message, KSNotification::EventSource source, KSNotification::EventType event)
3160{
3161 ekosLiveClient.get()->message()->sendEvent(message, source, event);
3162}
3163
3164void Manager::connectModules()
3165{
3166 // Dark Library
3167 connect(DarkLibrary::Instance(), &DarkLibrary::newImage, ekosLiveClient.get()->media(),
3168 &EkosLive::Media::sendDarkLibraryData, Qt::UniqueConnection);
3169 connect(DarkLibrary::Instance(), &DarkLibrary::trainChanged, ekosLiveClient.get()->message(),
3170 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3171 connect(DarkLibrary::Instance(), &DarkLibrary::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3173 connect(DarkLibrary::Instance(), &DarkLibrary::settingsUpdated, ekosLiveClient.get()->message(),
3174 &EkosLive::Message::sendGuideSettings, Qt::UniqueConnection);
3175
3176 // Guide <---> Capture connections
3177 if (captureProcess && guideProcess)
3178 {
3179 // captureProcess.get()->disconnect(guideProcess.get());
3180 // guideProcess.get()->disconnect(captureProcess.get());
3181
3182 // Guide Limits
3183 connect(guideModule(), &Ekos::Guide::newStatus, captureModule(), &Ekos::Capture::setGuideStatus,
3185 connect(guideModule(), &Ekos::Guide::newAxisDelta, captureModule(), &Ekos::Capture::setGuideDeviation,
3187
3188 // Dithering
3189 connect(captureModule(), &Ekos::Capture::dither, guideModule(), &Ekos::Guide::dither, Qt::UniqueConnection);
3190 connect(captureModule(), &Ekos::Capture::resetNonGuidedDither, guideModule(), &Ekos::Guide::resetNonGuidedDither,
3192
3193 // Guide Head
3194 connect(captureModule(), &Ekos::Capture::suspendGuiding, guideModule(), &Ekos::Guide::suspend,
3196 connect(captureModule(), &Ekos::Capture::resumeGuiding, guideModule(), &Ekos::Guide::resume,
3198 connect(guideModule(), &Ekos::Guide::guideChipUpdated, captureModule(), &Ekos::Capture::setGuideChip,
3200
3201 // Meridian Flip
3202 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, guideModule(), &Ekos::Guide::abort,
3204 connect(captureModule(), &Ekos::Capture::guideAfterMeridianFlip, guideModule(),
3205 &Ekos::Guide::guideAfterMeridianFlip, Qt::UniqueConnection);
3206 }
3207
3208 // Guide <---> Mount connections
3209 if (guideProcess && mountProcess)
3210 {
3211 // Parking
3212 connect(mountModule(), &Ekos::Mount::newStatus, guideModule(), &Ekos::Guide::setMountStatus,
3214 connect(mountModule(), &Ekos::Mount::newCoords, guideModule(), &Ekos::Guide::setMountCoords,
3216
3217 }
3218
3219 // Focus <---> Guide connections
3220 if (guideProcess && focusProcess)
3221 {
3222 // Suspend
3223 connect(focusModule(), &Ekos::FocusModule::suspendGuiding, guideModule(), &Ekos::Guide::suspend,
3225 connect(focusModule(), &Ekos::FocusModule::resumeGuiding, guideModule(), &Ekos::Guide::resume,
3227 }
3228
3229 // Capture <---> Focus connections
3230 if (captureProcess && focusProcess)
3231 {
3232 // Check focus HFR value and if above threshold parameter, run autoFocus
3233 connect(captureModule(), &Ekos::Capture::checkFocus, focusModule(), &Ekos::FocusModule::checkFocus,
3235
3236 // Run autoFocus
3237 connect(captureProcess.get(), &Ekos::Capture::runAutoFocus, focusModule(), &Ekos::FocusModule::runAutoFocus,
3239
3240 // Reset Frame
3241 connect(captureModule(), &Ekos::Capture::resetFocusFrame, focusModule(), &Ekos::FocusModule::resetFrame,
3243
3244 // Abort Focus
3245 connect(captureModule(), &Ekos::Capture::abortFocus, focusModule(), &Ekos::FocusModule::abort, Qt::UniqueConnection);
3246
3247 // New Focus Status
3248 connect(focusModule(), &Ekos::FocusModule::newStatus, captureModule(), &Ekos::Capture::setFocusStatus,
3250
3251 // Perform adaptive focus
3252 connect(captureModule(), &Ekos::Capture::adaptiveFocus, focusModule(), &Ekos::FocusModule::adaptiveFocus,
3254
3255 // New Adaptive Focus Status
3256 connect(focusModule(), &Ekos::FocusModule::focusAdaptiveComplete, captureModule(), &Ekos::Capture::focusAdaptiveComplete,
3258
3259 // New Focus HFR
3260 connect(focusModule(), &Ekos::FocusModule::newHFR, captureModule(), &Ekos::Capture::setHFR, Qt::UniqueConnection);
3261
3262 // New Focus temperature delta
3263 connect(focusModule(), &Ekos::FocusModule::newFocusTemperatureDelta, captureModule(),
3265
3266 // User requested AF
3267 connect(focusModule(), &Ekos::FocusModule::inSequenceAF, captureModule(),
3269
3270 // Meridian Flip
3271 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, focusModule(), &Ekos::FocusModule::meridianFlipStarted,
3273 }
3274
3275 // Capture <---> Align connections
3276 if (captureProcess && alignProcess)
3277 {
3278 // Alignment flag
3279 connect(alignModule(), &Ekos::Align::newStatus, captureModule(), &Ekos::Capture::setAlignStatus,
3281 // Solver data
3282 connect(alignModule(), &Ekos::Align::newSolverResults, captureModule(), &Ekos::Capture::setAlignResults,
3284 // Capture Status
3285 connect(captureModule(), &Ekos::Capture::newStatus, alignModule(), &Ekos::Align::setCaptureStatus,
3287 }
3288
3289 // Capture <---> Mount connections
3290 if (captureProcess && mountProcess)
3291 {
3292 // Register both modules since both are now created and ready
3293 // In case one module misses the DBus signal, then it will be correctly initialized.
3294 captureModule()->registerNewModule("Mount");
3295 mountModule()->registerNewModule("Capture");
3296
3297 // Meridian Flip states
3298 connect(captureModule(), &Ekos::Capture::meridianFlipStarted, mountModule(), &Ekos::Mount::suspendAltLimits,
3300 connect(captureModule(), &Ekos::Capture::guideAfterMeridianFlip, mountModule(), &Ekos::Mount::resumeAltLimits,
3302
3303 // Mount Status
3304 connect(mountModule(), &Ekos::Mount::newStatus, captureModule(), &Ekos::Capture::setMountStatus,
3306 }
3307
3308 // Optical Train Manager ---> EkosLive connections
3309 if (ekosLiveClient)
3310 {
3311 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, ekosLiveClient->message(),
3312 &EkosLive::Message::sendTrains, Qt::UniqueConnection);
3313 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::configurationRequested, ekosLiveClient->message(),
3314 &EkosLive::Message::requestOpticalTrains, Qt::UniqueConnection);
3315 }
3316
3317 // Capture <---> EkosLive connections
3318 if (captureProcess && ekosLiveClient)
3319 {
3320 //captureProcess.get()->disconnect(ekosLiveClient.get()->message());
3321
3322 connect(captureModule(), &Ekos::Capture::dslrInfoRequested, ekosLiveClient.get()->message(),
3323 &EkosLive::Message::requestDSLRInfo, Qt::UniqueConnection);
3324 connect(captureModule(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(),
3325 &EkosLive::Message::sendCaptureSequence, Qt::UniqueConnection);
3326 connect(captureModule(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(),
3327 &EkosLive::Message::sendCaptureSettings, Qt::UniqueConnection);
3328 connect(captureModule(), &Ekos::Capture::newLocalPreview, ekosLiveClient.get()->message(),
3329 &EkosLive::Message::sendPreviewLabel, Qt::UniqueConnection);
3330 connect(captureModule(), &Ekos::Capture::trainChanged, ekosLiveClient.get()->message(),
3331 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3332 }
3333
3334 // Focus <---> Align connections
3335 if (focusProcess && alignProcess)
3336 {
3337 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newStatus, alignModule(), &Ekos::Align::setFocusStatus,
3339 }
3340
3341 // Focus <---> Mount connections
3342 if (focusProcess && mountProcess)
3343 {
3344 connect(mountModule(), &Ekos::Mount::newStatus, focusModule(), &Ekos::FocusModule::setMountStatus, Qt::UniqueConnection);
3345 connect(mountModule(), &Ekos::Mount::newCoords, focusModule(), &Ekos::FocusModule::setMountCoords, Qt::UniqueConnection);
3346 }
3347
3348 // Mount <---> Align connections
3349 if (mountProcess && alignProcess)
3350 {
3351 connect(mountModule(), &Ekos::Mount::newStatus, alignModule(), &Ekos::Align::setMountStatus,
3353 connect(mountModule(), &Ekos::Mount::newTarget, alignModule(), &Ekos::Align::setTarget,
3357 connect(alignModule(), &Ekos::Align::newPAAStage, mountModule(), &Ekos::Mount::paaStageChanged,
3359 }
3360
3361 // Mount <---> Guide connections
3362 if (mountProcess && guideProcess)
3363 {
3364 connect(mountModule(), &Ekos::Mount::pierSideChanged, guideModule(), &Ekos::Guide::setPierSide,
3366 }
3367
3368 // Align <--> EkosLive connections
3369 if (alignProcess && ekosLiveClient)
3370 {
3371 // alignProcess.get()->disconnect(ekosLiveClient.get()->message());
3372 // alignProcess.get()->disconnect(ekosLiveClient.get()->media());
3373
3374 connect(alignModule(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus,
3376 connect(alignModule(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(),
3377 &EkosLive::Message::setAlignSolution, Qt::UniqueConnection);
3378 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newPAHStage,
3379 ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage,
3381 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newPAHMessage,
3382 ekosLiveClient.get()->message(),
3383 &EkosLive::Message::setPAHMessage, Qt::UniqueConnection);
3384 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::PAHEnabled,
3385 ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled,
3387 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::polarResultUpdated,
3388 ekosLiveClient.get()->message(),
3389 &EkosLive::Message::setPolarResults, Qt::UniqueConnection);
3390 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::updatedErrorsChanged,
3391 ekosLiveClient.get()->message(),
3392 &EkosLive::Message::setUpdatedErrors, Qt::UniqueConnection);
3393 connect(alignModule()->polarAlignmentAssistant(), &Ekos::PolarAlignmentAssistant::newCorrectionVector,
3394 ekosLiveClient.get()->media(),
3395 &EkosLive::Media::setCorrectionVector, Qt::UniqueConnection);
3396
3397 connect(alignModule(), &Ekos::Align::newImage, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3399 connect(alignModule(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame,
3401
3402 connect(alignModule(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(),
3403 &EkosLive::Message::sendAlignSettings, Qt::UniqueConnection);
3404
3405 connect(alignModule(), &Ekos::Align::trainChanged, ekosLiveClient.get()->message(),
3406 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3407
3408 connect(alignModule(), &Ekos::Align::manualRotatorChanged, ekosLiveClient.get()->message(),
3409 &EkosLive::Message::sendManualRotatorStatus, Qt::UniqueConnection);
3410 }
3411
3412 // Focus <--> EkosLive Connections
3413 if (focusProcess && ekosLiveClient)
3414 {
3415 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::settingsUpdated, ekosLiveClient.get()->message(),
3416 &EkosLive::Message::sendFocusSettings, Qt::UniqueConnection);
3417
3418 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newImage, ekosLiveClient.get()->media(),
3419 &EkosLive::Media::sendModuleFrame,
3421
3422 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::trainChanged, ekosLiveClient.get()->message(),
3423 &EkosLive::Message::sendTrainProfiles,
3425
3426 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::autofocusAborted,
3427 ekosLiveClient.get()->message(), &EkosLive::Message::autofocusAborted, Qt::UniqueConnection);
3428 }
3429
3430 // Guide <--> EkosLive Connections
3431 if (guideProcess && ekosLiveClient)
3432 {
3433 connect(guideModule(), &Ekos::Guide::settingsUpdated, ekosLiveClient.get()->message(),
3434 &EkosLive::Message::sendGuideSettings, Qt::UniqueConnection);
3435
3436 connect(guideModule(), &Ekos::Guide::trainChanged, ekosLiveClient.get()->message(),
3437 &EkosLive::Message::sendTrainProfiles, Qt::UniqueConnection);
3438
3439 connect(guideModule(), &Ekos::Guide::newImage, ekosLiveClient.get()->media(), &EkosLive::Media::sendModuleFrame,
3441 }
3442
3443 // Analyze connections.
3444 if (analyzeProcess)
3445 {
3446 // Scheduler <---> Analyze
3447 connect(schedulerModule(), &Ekos::Scheduler::jobStarted,
3448 analyzeProcess.get(), &Ekos::Analyze::schedulerJobStarted, Qt::UniqueConnection);
3449 connect(schedulerModule(), &Ekos::Scheduler::jobEnded,
3450 analyzeProcess.get(), &Ekos::Analyze::schedulerJobEnded, Qt::UniqueConnection);
3451 connect(schedulerModule(), &Ekos::Scheduler::targetDistance,
3452 analyzeProcess.get(), &Ekos::Analyze::newTargetDistance, Qt::UniqueConnection);
3453
3454 // Capture <---> Analyze
3455 if (captureProcess)
3456 {
3457 connect(captureModule(), &Ekos::Capture::captureComplete,
3458 analyzeProcess.get(), &Ekos::Analyze::captureComplete, Qt::UniqueConnection);
3459 connect(captureModule(), &Ekos::Capture::captureStarting,
3460 analyzeProcess.get(), &Ekos::Analyze::captureStarting, Qt::UniqueConnection);
3461 connect(captureModule(), &Ekos::Capture::captureAborted,
3462 analyzeProcess.get(), &Ekos::Analyze::captureAborted, Qt::UniqueConnection);
3463#if 0
3464 // Meridian Flip
3465 connect(captureModule(), &Ekos::Capture::meridianFlipStarted,
3466 analyzeProcess.get(), &Ekos::Analyze::meridianFlipStarted, Qt::UniqueConnection);
3467 connect(captureModule(), &Ekos::Capture::meridianFlipCompleted,
3468 analyzeProcess.get(), &Ekos::Analyze::meridianFlipComplete, Qt::UniqueConnection);
3469#endif
3470 }
3471
3472 // Guide <---> Analyze
3473 if (guideProcess)
3474 {
3475 connect(guideModule(), &Ekos::Guide::newStatus,
3476 analyzeProcess.get(), &Ekos::Analyze::guideState, Qt::UniqueConnection);
3477
3478 connect(guideModule(), &Ekos::Guide::guideStats,
3479 analyzeProcess.get(), &Ekos::Analyze::guideStats, Qt::UniqueConnection);
3480 }
3481 }
3482
3483
3484 // Focus <---> Analyze connections
3485 if (focusProcess && analyzeProcess)
3486 {
3487 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::autofocusComplete,
3488 analyzeProcess.get(), &Ekos::Analyze::autofocusComplete, Qt::UniqueConnection);
3489 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::adaptiveFocusComplete,
3490 analyzeProcess.get(), &Ekos::Analyze::adaptiveFocusComplete, Qt::UniqueConnection);
3491 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::autofocusStarting,
3492 analyzeProcess.get(), &Ekos::Analyze::autofocusStarting, Qt::UniqueConnection);
3493 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::autofocusAborted,
3494 analyzeProcess.get(), &Ekos::Analyze::autofocusAborted, Qt::UniqueConnection);
3495 connect(focusModule()->mainFocuser().get(), &Ekos::Focus::newFocusTemperatureDelta,
3496 analyzeProcess.get(), &Ekos::Analyze::newTemperature, Qt::UniqueConnection);
3497 }
3498
3499 // Align <---> Analyze connections
3500 if (alignProcess && analyzeProcess)
3501 {
3502 connect(alignModule(), &Ekos::Align::newStatus,
3503 analyzeProcess.get(), &Ekos::Analyze::alignState, Qt::UniqueConnection);
3504
3505 }
3506
3507 // Mount <---> Analyze connections
3508 if (mountProcess && analyzeProcess)
3509 {
3510 connect(mountModule(), &Ekos::Mount::newStatus,
3511 analyzeProcess.get(), &Ekos::Analyze::mountState, Qt::UniqueConnection);
3512 connect(mountModule(), &Ekos::Mount::newCoords,
3513 analyzeProcess.get(), &Ekos::Analyze::mountCoords, Qt::UniqueConnection);
3514 connect(mountModule()->getMeridianFlipState().get(), &Ekos::MeridianFlipState::newMountMFStatus,
3515 analyzeProcess.get(), &Ekos::Analyze::mountFlipStatus, Qt::UniqueConnection);
3516 }
3517}
3518
3519void Manager::setEkosLiveConnected(bool enabled)
3520{
3521 ekosLiveClient.get()->setConnected(enabled);
3522}
3523
3524void Manager::setEkosLiveConfig(bool rememberCredentials, bool autoConnect)
3525{
3526 ekosLiveClient.get()->setConfig(rememberCredentials, autoConnect);
3527}
3528
3529void Manager::setEkosLiveUser(const QString &username, const QString &password)
3530{
3531 ekosLiveClient.get()->setUser(username, password);
3532}
3533
3534bool Manager::ekosLiveStatus()
3535{
3536 return ekosLiveClient.get()->isConnected();
3537}
3538
3539bool Manager::checkUniqueBinaryDriver(const QSharedPointer<DriverInfo> &primaryDriver,
3540 const QSharedPointer<DriverInfo> &secondaryDriver)
3541{
3542 if (!primaryDriver || !secondaryDriver)
3543 return false;
3544
3545 return (primaryDriver->getExecutable() == secondaryDriver->getExecutable() &&
3546 primaryDriver->getAuxInfo().value("mdpd", false).toBool() == true);
3547}
3548
3549void Manager::restartDriver(const QString &deviceName)
3550{
3551 qCInfo(KSTARS_EKOS) << "Restarting driver" << deviceName;
3552 if (m_LocalMode)
3553 {
3554 for (auto &oneDevice : INDIListener::devices())
3555 {
3556 if (oneDevice->getDeviceName() == deviceName)
3557 {
3558 DriverManager::Instance()->restartDriver(oneDevice->getDriverInfo());
3559 break;
3560 }
3561 }
3562 }
3563 else
3564 INDI::WebManager::restartDriver(m_CurrentProfile, deviceName);
3565}
3566
3567void Manager::setEkosLoggingEnabled(const QString &name, bool enabled)
3568{
3569 // LOGGING, FILE, DEFAULT are exclusive, so one of them must be SET to TRUE
3570 if (name == "LOGGING")
3571 {
3572 Options::setDisableLogging(!enabled);
3573 if (!enabled)
3575 }
3576 else if (name == "FILE")
3577 {
3578 Options::setLogToFile(enabled);
3579 if (enabled)
3581 }
3582 else if (name == "DEFAULT")
3583 {
3584 Options::setLogToDefault(enabled);
3585 if (enabled)
3587 }
3588 // VERBOSE should be set to TRUE if INDI or Ekos logging is selected.
3589 else if (name == "VERBOSE")
3590 {
3591 Options::setVerboseLogging(enabled);
3593 }
3594 // Toggle INDI Logging
3595 else if (name == "INDI")
3596 {
3597 Options::setINDILogging(enabled);
3599 }
3600 else if (name == "FITS")
3601 {
3602 Options::setFITSLogging(enabled);
3604 }
3605 else if (name == "CAPTURE")
3606 {
3607 Options::setCaptureLogging(enabled);
3608 Options::setINDICCDLogging(enabled);
3609 Options::setINDIFilterWheelLogging(enabled);
3611 }
3612 else if (name == "FOCUS")
3613 {
3614 Options::setFocusLogging(enabled);
3615 Options::setINDIFocuserLogging(enabled);
3617 }
3618 else if (name == "GUIDE")
3619 {
3620 Options::setGuideLogging(enabled);
3621 Options::setINDICCDLogging(enabled);
3623 }
3624 else if (name == "ALIGNMENT")
3625 {
3626 Options::setAlignmentLogging(enabled);
3628 }
3629 else if (name == "MOUNT")
3630 {
3631 Options::setMountLogging(enabled);
3632 Options::setINDIMountLogging(enabled);
3634 }
3635 else if (name == "SCHEDULER")
3636 {
3637 Options::setSchedulerLogging(enabled);
3639 }
3640 else if (name == "OBSERVATORY")
3641 {
3642 Options::setObservatoryLogging(enabled);
3644 }
3645}
3646
3647void Manager::acceptPortSelection()
3648{
3649 if (m_PortSelector)
3650 m_PortSelector->accept();
3651}
3652
3653void Manager::setPortSelectionComplete()
3654{
3655 if (m_CurrentProfile->portSelector)
3656 {
3657 // Turn off port selector
3658 m_CurrentProfile->portSelector = false;
3659 KStarsData::Instance()->userdb()->SaveProfile(m_CurrentProfile);
3660 }
3661
3662 if (m_CurrentProfile->autoConnect)
3663 connectDevices();
3664}
3665
3666void Manager::activateModule(const QString &name, bool popup)
3667{
3668 auto child = toolsWidget->findChild<QWidget *>(name);
3669 if (child)
3670 {
3671 toolsWidget->setCurrentWidget(child);
3672 if (popup)
3673 {
3674 raise();
3675 activateWindow();
3676 showNormal();
3677 }
3678 }
3679}
3680
3681void Manager::createModules(const QSharedPointer<ISD::GenericDevice> &device)
3682{
3683 if (device->isConnected())
3684 {
3685 if (device->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE)
3686 {
3687 initCapture();
3688 initFocus();
3689 initAlign();
3690 initGuide();
3691 }
3692 if (device->getDriverInterface() & INDI::BaseDevice::FILTER_INTERFACE)
3693 {
3694 initCapture();
3695 initFocus();
3696 initAlign();
3697 }
3698 if (device->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE)
3699 initFocus();
3700 if (device->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)
3701 {
3702 initCapture();
3703 initAlign();
3704 initGuide();
3705 initMount();
3706 }
3707 if (device->getDriverInterface() & INDI::BaseDevice::ROTATOR_INTERFACE)
3708 {
3709 initCapture();
3710 initAlign();
3711 }
3712 if (device->getDriverInterface() & INDI::BaseDevice::DOME_INTERFACE)
3713 {
3714 initCapture();
3715 initAlign();
3716 initObservatory();
3717 }
3718 if (device->getDriverInterface() & INDI::BaseDevice::WEATHER_INTERFACE)
3719 {
3720 initFocus();
3721 initObservatory();
3722 }
3723 if (device->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE)
3724 {
3725 initCapture();
3726 }
3727 if (device->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE)
3728 {
3729 initCapture();
3730 }
3731 if (device->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)
3732 {
3733 initMount();
3734 }
3735 }
3736}
3737
3738void Manager::setDeviceReady()
3739{
3740 // Check if ALL our devices are ready.
3741 // Ready indicates that all properties have been defined.
3742 if (isINDIReady() == false)
3743 {
3744 auto device = static_cast<ISD::GenericDevice*>(sender());
3745 if (device)
3746 {
3747
3748 if (device->isConnected() == false && m_CurrentProfile->autoConnect)
3749 {
3750 // Do we have port selector checked?
3751 if (m_CurrentProfile->portSelector)
3752 {
3753 // If port selector was not initialized, kick off the timer
3754 // so we can check if all devices should be connected.
3755 // Otherwise, if port selector is started, then let user
3756 // select ports first and then manually connect time.
3757 if (!m_PortSelector)
3758 m_PortSelectorTimer.start();
3759 }
3760 else
3761 {
3762 qCInfo(KSTARS_EKOS) << "Connecting to" << device->getDeviceName();
3763 device->Connect();
3764 }
3765 }
3766 else
3767 qCInfo(KSTARS_EKOS) << device->getDeviceName() << "is connected and ready.";
3768 }
3769
3770 if (m_ekosStatus != Ekos::Success)
3771 return;
3772 }
3773
3774 // If port selector is active, then do not show optical train dialog unless it is dismissed first.
3775 if (m_DriverDevicesCount <= 0 && (m_CurrentProfile->portSelector == false || !m_PortSelector))
3776 {
3777 for (auto &device : INDIListener::devices())
3778 syncGenericDevice(device);
3779 OpticalTrainManager::Instance()->setProfile(m_CurrentProfile);
3780 }
3781}
3782
3783void Manager::createFilterManager(ISD::FilterWheel *device)
3784{
3785 auto name = device->getDeviceName();
3786 if (m_FilterManagers.contains(name) == false)
3787 {
3788 QSharedPointer<FilterManager> newFM(new FilterManager(this));
3789 newFM->setFilterWheel(device);
3790 m_FilterManagers.insert(name, std::move(newFM));
3791 }
3792 else
3793 m_FilterManagers[name]->setFilterWheel(device);
3794
3795}
3796
3797bool Manager::getFilterManager(const QString &name, QSharedPointer<FilterManager> &fm)
3798{
3799 if (m_FilterManagers.contains(name))
3800 {
3801 fm = m_FilterManagers[name];
3802 return true;
3803 }
3804 return false;
3805}
3806
3807bool Manager::getFilterManager(QSharedPointer<FilterManager> &fm)
3808{
3809 if (m_FilterManagers.size() > 0)
3810 {
3811 fm = m_FilterManagers.values()[0];
3812 return true;
3813 }
3814 return false;
3815}
3816
3817void Manager::createRotatorController(ISD::Rotator *device)
3818{
3819 auto Name = device->getDeviceName();
3820 if (m_RotatorControllers.contains(Name) == false)
3821 {
3822 QSharedPointer<RotatorSettings> newRC(new RotatorSettings(this));
3823 // Properties are fetched in RotatorSettings::initRotator!
3824 m_RotatorControllers[Name] = newRC;
3825 }
3826}
3827
3828bool Manager::getRotatorController(const QString &Name, QSharedPointer<RotatorSettings> &rs)
3829{
3830 if (m_RotatorControllers.contains(Name))
3831 {
3832 rs = m_RotatorControllers[Name];
3833 return true;
3834 }
3835 return false;
3836}
3837
3838bool Manager::existRotatorController()
3839{
3840 return (!m_RotatorControllers.empty());
3841}
3842
3843void Manager::setFITSfromFile(bool previewFromFile)
3844{
3845 if (previewFromFile && !FITSfromFile)
3846 {
3847 // Prevent preview from Capture module
3848 QObject::disconnect(captureModule(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress);
3849 FITSfromFile = previewFromFile;
3850 appendLogText(i18n("Preview source set to external"));
3851 }
3852 else if (!previewFromFile && FITSfromFile)
3853 {
3854 // Reset preview from Capture module
3855 QObject::connect(captureModule(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress);
3856 FITSfromFile = previewFromFile;
3857 appendLogText(i18n("Preview source reset to internal"));
3858 }
3859}
3860
3861void Manager::previewFile(QString filePath)
3862{
3863 capturePreview->updateJobPreview(filePath);
3864 appendLogText(i18n("Received external preview file"));
3865}
3866}
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 Apr 25 2025 11:58:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.