Kstars

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

KDE's Doxygen guidelines are available online.