Kstars

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

KDE's Doxygen guidelines are available online.