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

KDE's Doxygen guidelines are available online.