diff --git a/kstars/ekos/ekosmanager.cpp b/kstars/ekos/ekosmanager.cpp index a0e8962e0..cdcce6ac0 100644 --- a/kstars/ekos/ekosmanager.cpp +++ b/kstars/ekos/ekosmanager.cpp @@ -1,2855 +1,2885 @@ /* Ekos Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "ekosmanager.h" #include "ekosadaptor.h" #include "kstars.h" #include "kstarsdata.h" #include "opsekos.h" #include "Options.h" #include "profileeditor.h" #include "profilewizard.h" #include "skymap.h" #include "auxiliary/darklibrary.h" #include "auxiliary/QProgressIndicator.h" #include "capture/sequencejob.h" #include "fitsviewer/fitstab.h" #include "fitsviewer/fitsview.h" #include "indi/clientmanager.h" #include "indi/driverinfo.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #include "indi/indielement.h" #include "indi/indilistener.h" #include "indi/indiproperty.h" #include "indi/indiwebmanager.h" #include "ekoslive/ekosliveclient.h" #include "ekoslive/message.h" #include "ekoslive/media.h" #include #include #include #include #include #include #include #define MAX_REMOTE_INDI_TIMEOUT 15000 #define MAX_LOCAL_INDI_TIMEOUT 5000 EkosManager::EkosManager(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX if (Options::independentWindowEkos()) setWindowFlags(Qt::Window); else { setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(changeAlwaysOnTop(Qt::ApplicationState))); } #endif setupUi(this); new EkosAdaptor(this); QDBusConnection::sessionBus().registerObject("/KStars/Ekos", this); setWindowIcon(QIcon::fromTheme("kstars_ekos")); profileModel.reset(new QStandardItemModel(0, 4)); profileModel->setHorizontalHeaderLabels(QStringList() << "id" << "name" << "host" << "port"); captureProgress->setValue(0); sequenceProgress->setValue(0); sequenceProgress->setDecimals(0); sequenceProgress->setFormat("%v"); imageProgress->setValue(0); imageProgress->setDecimals(1); imageProgress->setFormat("%v"); imageProgress->setBarStyle(QRoundProgressBar::StyleLine); countdownTimer.setInterval(1000); connect(&countdownTimer, SIGNAL(timeout()), this, SLOT(updateCaptureCountDown())); toolsWidget->setIconSize(QSize(48, 48)); connect(toolsWidget, SIGNAL(currentChanged(int)), this, SLOT(processTabChange())); // Enable scheduler Tab toolsWidget->setTabEnabled(1, false); // Start/Stop INDI Server connect(processINDIB, SIGNAL(clicked()), this, SLOT(processINDI())); // Connect/Disconnect INDI devices connect(connectB, SIGNAL(clicked()), this, SLOT(connectDevices())); connect(disconnectB, SIGNAL(clicked()), this, SLOT(disconnectDevices())); ekosLiveClient.reset(new EkosLive::Client(this)); // INDI Control Panel //connect(controlPanelB, SIGNAL(clicked()), GUIManager::Instance(), SLOT(show())); connect(ekosLiveB, &QPushButton::clicked, [&]() { ekosLiveClient.get()->show(); ekosLiveClient.get()->raise(); }); connect(this, &EkosManager::newEkosStartingStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus); connect(ekosLiveClient.get()->media(), &EkosLive::Media::newBoundingRect, ekosLiveClient.get()->message(), &EkosLive::Message::setBoundingRect); connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(), &EkosLive::Media::resetPolarView); connect(optionsB, SIGNAL(clicked()), KStars::Instance(), SLOT(slotViewOps())); // Save as above, but it appears in all modules connect(ekosOptionsB, SIGNAL(clicked()), SLOT(showEkosOptions())); // Clear Ekos Log connect(clearB, SIGNAL(clicked()), this, SLOT(clearLog())); // Logs KConfigDialog *dialog = new KConfigDialog(this, "logssettings", Options::self()); opsLogs = new Ekos::OpsLogs(); KPageWidgetItem *page = dialog->addPage(opsLogs, i18n("Logging")); page->setIcon(QIcon::fromTheme("configure")); connect(logsB, SIGNAL(clicked()), dialog, SLOT(show())); connect(dialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(updateDebugInterfaces())); connect(dialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(updateDebugInterfaces())); // Summary // previewPixmap = new QPixmap(QPixmap(":/images/noimage.png")); // Profiles connect(addProfileB, SIGNAL(clicked()), this, SLOT(addProfile())); connect(editProfileB, SIGNAL(clicked()), this, SLOT(editProfile())); connect(deleteProfileB, SIGNAL(clicked()), this, SLOT(deleteProfile())); connect(profileCombo, static_cast(&QComboBox::currentTextChanged), [=](const QString &text) { Options::setProfile(text); if (text == "Simulators") { editProfileB->setEnabled(false); deleteProfileB->setEnabled(false); } else { editProfileB->setEnabled(true); deleteProfileB->setEnabled(true); } }); // Ekos Wizard connect(wizardProfileB, SIGNAL(clicked()), this, SLOT(wizardProfile())); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Set Profile icons addProfileB->setIcon(QIcon::fromTheme("list-add")); addProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); editProfileB->setIcon(QIcon::fromTheme("document-edit")); editProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); deleteProfileB->setIcon(QIcon::fromTheme("list-remove")); deleteProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); wizardProfileB->setIcon(QIcon::fromTheme("tools-wizard")); wizardProfileB->setAttribute(Qt::WA_LayoutUsesWidgetRect); customDriversB->setIcon(QIcon::fromTheme("roll")); customDriversB->setAttribute(Qt::WA_LayoutUsesWidgetRect); connect(customDriversB, &QPushButton::clicked, DriverManager::Instance(), &DriverManager::showCustomDrivers); // Load all drivers loadDrivers(); // Load add driver profiles loadProfiles(); // INDI Control Panel and Ekos Options ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud")); ekosLiveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); optionsB->setIcon(QIcon::fromTheme("configure", QIcon(":/icons/ekos_setup.png"))); optionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Setup Tab toolsWidget->tabBar()->setTabIcon(0, QIcon(":/icons/ekos_setup.png")); toolsWidget->tabBar()->setTabToolTip(0, i18n("Setup")); // Initialize Ekos Scheduler Module schedulerProcess.reset(new Ekos::Scheduler()); toolsWidget->addTab(schedulerProcess.get(), QIcon(":/icons/ekos_scheduler.png"), ""); toolsWidget->tabBar()->setTabToolTip(1, i18n("Scheduler")); connect(schedulerProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); //connect(schedulerProcess.get(), SIGNAL(newTarget(QString)), mountTarget, SLOT(setText(QString))); connect(schedulerProcess.get(), &Ekos::Scheduler::newTarget, [&](const QString &target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); // Temporary fix. Not sure how to resize Ekos Dialog to fit contents of the various tabs in the QScrollArea which are added // dynamically. I used setMinimumSize() but it doesn't appear to make any difference. // Also set Layout policy to SetMinAndMaxSize as well. Any idea how to fix this? // FIXME //resize(1000,750); summaryPreview.reset(new FITSView(previewWidget, FITS_NORMAL)); previewWidget->setContentsMargins(0, 0, 0, 0); summaryPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); summaryPreview->setBaseSize(previewWidget->size()); summaryPreview->createFloatingToolBar(); summaryPreview->setCursorMode(FITSView::dragCursor); QVBoxLayout *vlayout = new QVBoxLayout(); vlayout->addWidget(summaryPreview.get()); previewWidget->setLayout(vlayout); connect(summaryPreview.get(), &FITSView::imageLoaded, [&]() { // UUID binds the cloud & preview frames by a common key QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegularExpression("[-{}]")); ekosLiveClient.get()->media()->sendPreviewImage(summaryPreview.get(), uuid); ekosLiveClient.get()->cloud()->sendPreviewImage(summaryPreview.get(), uuid); }); if (Options::ekosLeftIcons()) { toolsWidget->setTabPosition(QTabWidget::West); QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(0); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(0, icon); icon = toolsWidget->tabIcon(1); pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(1, icon); } //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box #ifdef Q_OS_OSX QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); #endif resize(Options::ekosWindowWidth(), Options::ekosWindowHeight()); } void EkosManager::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } EkosManager::~EkosManager() { toolsWidget->disconnect(this); //delete previewPixmap; } void EkosManager::closeEvent(QCloseEvent * /*event*/) { QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); Options::setEkosWindowWidth(width()); Options::setEkosWindowHeight(height()); } void EkosManager::hideEvent(QHideEvent * /*event*/) { QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); } void EkosManager::showEvent(QShowEvent * /*event*/) { QAction *a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(true); // Just show the profile wizard ONCE per session if (profileWizardLaunched == false && profiles.count() == 1) { profileWizardLaunched = true; wizardProfile(); } } void EkosManager::resizeEvent(QResizeEvent *) { //previewImage->setPixmap(previewPixmap->scaled(previewImage->width(), previewImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (focusStarPixmap.get() != nullptr) focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (focusProfilePixmap) //focusProfileImage->setPixmap(focusProfilePixmap->scaled(focusProfileImage->width(), focusProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (guideStarPixmap.get() != nullptr) guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); //if (guideProfilePixmap) //guideProfileImage->setPixmap(guideProfilePixmap->scaled(guideProfileImage->width(), guideProfileImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void EkosManager::loadProfiles() { profiles.clear(); KStarsData::Instance()->userdb()->GetAllProfiles(profiles); profileModel->clear(); for (auto& pi : profiles) { QList info; info << new QStandardItem(pi->id) << new QStandardItem(pi->name) << new QStandardItem(pi->host) << new QStandardItem(pi->port); profileModel->appendRow(info); } profileModel->sort(0); profileCombo->blockSignals(true); profileCombo->setModel(profileModel.get()); profileCombo->setModelColumn(1); profileCombo->blockSignals(false); // Load last used profile from options int index = profileCombo->findText(Options::profile()); // If not found, set it to first item if (index == -1) index = 0; profileCombo->setCurrentIndex(index); } void EkosManager::loadDrivers() { foreach (DriverInfo *dv, DriverManager::Instance()->getDrivers()) { if (dv->getDriverSource() != HOST_SOURCE) driversList[dv->getLabel()] = dv; } } void EkosManager::reset() { qCDebug(KSTARS_EKOS) << "Resetting Ekos Manager..."; // Filter Manager filterManager.reset(new Ekos::FilterManager()); nDevices = 0; useGuideHead = false; useST4 = false; removeTabs(); genericDevices.clear(); managedDevices.clear(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); domeProcess.reset(); alignProcess.reset(); mountProcess.reset(); weatherProcess.reset(); dustCapProcess.reset(); ekosStartingStatus = EKOS_STATUS_IDLE; emit newEkosStartingStatus(ekosStartingStatus); indiConnectionStatus = EKOS_STATUS_IDLE; connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); processINDIB->setEnabled(true); mountGroup->setEnabled(false); focusGroup->setEnabled(false); captureGroup->setEnabled(false); guideGroup->setEnabled(false); sequenceLabel->setText(i18n("Sequence")); sequenceProgress->setValue(0); captureProgress->setValue(0); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); imageRemainingTime->setText("--:--:--"); mountStatus->setText(i18n("Idle")); captureStatus->setText(i18n("Idle")); focusStatus->setText(i18n("Idle")); guideStatus->setText(i18n("Idle")); if (capturePI) capturePI->stopAnimation(); if (mountPI) mountPI->stopAnimation(); if (focusPI) focusPI->stopAnimation(); if (guidePI) guidePI->stopAnimation(); isStarted = false; processINDIB->setText(i18n("Start INDI")); } void EkosManager::processINDI() { if (isStarted == false) start(); else stop(); } bool EkosManager::stop() { cleanDevices(); profileGroup->setEnabled(true); return true; } bool EkosManager::start() { if (localMode) qDeleteAll(managedDrivers); managedDrivers.clear(); // If clock was paused, unpaused it and sync time if (KStarsData::Instance()->clock()->isActive() == false) { KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); KStarsData::Instance()->clock()->start(); } reset(); currentProfile = getCurrentProfile(); localMode = currentProfile->isLocal(); // Load profile location if one exists updateProfileLocation(currentProfile); bool haveCCD = false, haveGuider = false; if (currentProfile->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(currentProfile->guiderhost); Options::setPHD2Port(currentProfile->guiderport); } else if (currentProfile->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(currentProfile->guiderhost); Options::setLinGuiderPort(currentProfile->guiderport); } if (localMode) { DriverInfo *drv = driversList.value(currentProfile->mount()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->ccd()); if (drv != nullptr) { managedDrivers.append(drv->clone()); haveCCD = true; } Options::setGuiderType(currentProfile->guidertype); drv = driversList.value(currentProfile->guider()); if (drv != nullptr) { haveGuider = true; // If the guider and ccd are the same driver, we have two cases: // #1 Drivers that only support ONE device per driver (such as sbig) // #2 Drivers that supports multiples devices per driver (such as sx) // For #1, we modify guider_di to make a unique label for the other device with postfix "Guide" // For #2, we set guider_di to nullptr and we prompt the user to select which device is primary ccd and which is guider // since this is the only way to find out in real time. if (haveCCD && currentProfile->guider() == currentProfile->ccd()) { if (drv->getAuxInfo().value("mdpd", false).toBool() == true) { drv = nullptr; } else { drv->setUniqueLabel(drv->getLabel() + " Guide"); } } if (drv) managedDrivers.append(drv->clone()); } drv = driversList.value(currentProfile->ao()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->filter()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->focuser()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->dome()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->weather()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux1()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux2()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux3()); if (drv != nullptr) managedDrivers.append(drv->clone()); drv = driversList.value(currentProfile->aux4()); if (drv != nullptr) managedDrivers.append(drv->clone()); + // Add remote drivers if we have any + if (currentProfile->remotedrivers.isEmpty() == false && currentProfile->remotedrivers.contains("@")) + { + for (auto remoteDriver : currentProfile->remotedrivers.split(",")) + { + QString name, label, host("localhost"), port("7624"); + QStringList properties = remoteDriver.split(QRegExp("[@:]")); + if (properties.length() > 1) + { + name = properties[0]; + host = properties[1]; + + if (properties.length() > 2) + port = properties[2]; + } + + DriverInfo *dv = new DriverInfo(name); + dv->setRemoteHost(host); + dv->setRemotePort(port); + + label = name; + // Remove extra quotes + label.remove("\""); + dv->setLabel(label); + dv->setUniqueLabel(label); + managedDrivers.append(dv); + } + } + + if (haveCCD == false && haveGuider == false) { KMessageBox::error(this, i18n("Ekos requires at least one CCD or Guider to operate.")); managedDrivers.clear(); return false; - } + } nDevices = managedDrivers.count(); } else { DriverInfo *remote_indi = new DriverInfo(QString("Ekos Remote Host")); remote_indi->setHostParameters(currentProfile->host, QString::number(currentProfile->port)); remote_indi->setDriverSource(GENERATED_SOURCE); managedDrivers.append(remote_indi); haveCCD = currentProfile->drivers.contains("CCD"); haveGuider = currentProfile->drivers.contains("Guider"); Options::setGuiderType(currentProfile->guidertype); if (haveCCD == false && haveGuider == false) { KMessageBox::error(this, i18n("Ekos requires at least one CCD or Guider to operate.")); delete (remote_indi); nDevices = 0; return false; } nDevices = currentProfile->drivers.count(); } connect(INDIListener::Instance(), SIGNAL(newDevice(ISD::GDInterface*)), this, SLOT(processNewDevice(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newTelescope(ISD::GDInterface*)), this, SLOT(setTelescope(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newCCD(ISD::GDInterface*)), this, SLOT(setCCD(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newFilter(ISD::GDInterface*)), this, SLOT(setFilter(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newFocuser(ISD::GDInterface*)), this, SLOT(setFocuser(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newDome(ISD::GDInterface*)), this, SLOT(setDome(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newWeather(ISD::GDInterface*)), this, SLOT(setWeather(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newDustCap(ISD::GDInterface*)), this, SLOT(setDustCap(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newLightBox(ISD::GDInterface*)), this, SLOT(setLightBox(ISD::GDInterface*))); connect(INDIListener::Instance(), SIGNAL(newST4(ISD::ST4*)), this, SLOT(setST4(ISD::ST4*))); connect(INDIListener::Instance(), SIGNAL(deviceRemoved(ISD::GDInterface*)), this, SLOT(removeDevice(ISD::GDInterface*)), Qt::DirectConnection); #ifdef Q_OS_OSX if (localMode||currentProfile->host=="localhost") { if (isRunning("PTPCamera")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(0, 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?"), i18n("PTP Camera"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_PTPCamera"))) { //TODO is there a better way to do this. QProcess p; p.start("killall PTPCamera"); p.waitForFinished(); } } } #endif if (localMode) { if (isRunning("indiserver")) { if (KMessageBox::Yes == (KMessageBox::questionYesNo(nullptr, i18n("Ekos detected an instance of INDI server running. Do you wish to " "shut down the existing instance before starting a new one?"), i18n("INDI Server"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "ekos_shutdown_existing_indiserver"))) { DriverManager::Instance()->stopAllDevices(); //TODO is there a better way to do this. QProcess p; p.start("pkill indiserver"); p.waitForFinished(); } } appendLogText(i18n("Starting INDI services...")); - if (DriverManager::Instance()->startDevices(managedDrivers, currentProfile->remotedrivers.split(",")) == false) + if (DriverManager::Instance()->startDevices(managedDrivers) == false) { INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); ekosStartingStatus = EKOS_STATUS_ERROR; emit newEkosStartingStatus(ekosStartingStatus); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString,QString)), this, SLOT(processServerTermination(QString,QString))); ekosStartingStatus = EKOS_STATUS_PENDING; emit newEkosStartingStatus(ekosStartingStatus); if (currentProfile->autoConnect) appendLogText(i18n("INDI services started on port %1.", managedDrivers.first()->getPort())); else appendLogText( i18n("INDI services started on port %1. Please connect devices.", managedDrivers.first()->getPort())); QTimer::singleShot(MAX_LOCAL_INDI_TIMEOUT, this, SLOT(checkINDITimeout())); } else { // If we need to use INDI Web Manager if (currentProfile->INDIWebManagerPort > 0) { appendLogText(i18n("Establishing communication with remote INDI Web Manager...")); remoteManagerStart = false; if (INDI::WebManager::isOnline(currentProfile)) { INDI::WebManager::syncCustomDrivers(currentProfile); if (INDI::WebManager::areDriversRunning(currentProfile) == false) { INDI::WebManager::stopProfile(currentProfile); if (INDI::WebManager::startProfile(currentProfile) == false) { appendLogText(i18n("Failed to start profile on remote INDI Web Manager.")); return false; } appendLogText(i18n("Starting profile on remote INDI Web Manager...")); remoteManagerStart = true; } } else appendLogText(i18n("Warning: INDI Web Manager is not online.")); } appendLogText( i18n("Connecting to remote INDI server at %1 on port %2 ...", currentProfile->host, currentProfile->port)); qApp->processEvents(); QApplication::setOverrideCursor(Qt::WaitCursor); if (DriverManager::Instance()->connectRemoteHost(managedDrivers.first()) == false) { appendLogText(i18n("Failed to connect to remote INDI server!")); INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); ekosStartingStatus = EKOS_STATUS_ERROR; emit newEkosStartingStatus(ekosStartingStatus); QApplication::restoreOverrideCursor(); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString,QString)), this, SLOT(processServerTermination(QString,QString))); QApplication::restoreOverrideCursor(); ekosStartingStatus = EKOS_STATUS_PENDING; emit newEkosStartingStatus(ekosStartingStatus); appendLogText( i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); QTimer::singleShot(MAX_REMOTE_INDI_TIMEOUT, this, SLOT(checkINDITimeout())); } connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); profileGroup->setEnabled(false); isStarted = true; processINDIB->setText(i18n("Stop INDI")); return true; } void EkosManager::checkINDITimeout() { // Don't check anything unless we're still pending if (ekosStartingStatus != EKOS_STATUS_PENDING) return; if (nDevices <= 0) { ekosStartingStatus = EKOS_STATUS_SUCCESS; emit newEkosStartingStatus(ekosStartingStatus); return; } if (localMode) { QStringList remainingDevices; foreach (DriverInfo *drv, managedDrivers) { if (drv->getDevices().count() == 0) remainingDevices << QString("+ %1").arg( drv->getUniqueLabel().isEmpty() == false ? drv->getUniqueLabel() : drv->getName()); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish:\n%1\nPlease ensure the device is connected and powered on.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish the following devices:\n%1\nPlease ensure each device is connected " "and powered on.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } else { QStringList remainingDevices; for (auto &driver : currentProfile->drivers.values()) { bool driverFound = false; for (auto &device : genericDevices) { if (device->getBaseDevice()->getDriverName() == driver) { driverFound = true; break; } } if (driverFound == false) remainingDevices << QString("+ %1").arg(driver); } if (remainingDevices.count() == 1) { appendLogText(i18n("Unable to establish remote device:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.at(0))); KNotification::beep(i18n("Ekos startup error")); } else { appendLogText(i18n("Unable to establish remote devices:\n%1\nPlease ensure remote device name corresponds " "to actual device name.", remainingDevices.join("\n"))); KNotification::beep(i18n("Ekos startup error")); } } ekosStartingStatus = EKOS_STATUS_ERROR; } void EkosManager::connectDevices() { // Check if already connected int nConnected = 0; for (auto &device : genericDevices) { if (device->isConnected()) nConnected++; } if (genericDevices.count() == nConnected) { indiConnectionStatus = EKOS_STATUS_SUCCESS; return; } indiConnectionStatus = EKOS_STATUS_PENDING; for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName(); device->Connect(); } connectB->setEnabled(false); disconnectB->setEnabled(true); appendLogText(i18n("Connecting INDI devices...")); } void EkosManager::disconnectDevices() { for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName(); device->Disconnect(); } appendLogText(i18n("Disconnecting INDI devices...")); } void EkosManager::processServerTermination(const QString &host, const QString &port) { if ((localMode && managedDrivers.first()->getPort() == port) || (currentProfile->host == host && currentProfile->port == port.toInt())) { cleanDevices(false); } } void EkosManager::cleanDevices(bool stopDrivers) { if (ekosStartingStatus == EKOS_STATUS_IDLE) return; INDIListener::Instance()->disconnect(this); DriverManager::Instance()->disconnect(this); if (managedDrivers.isEmpty() == false) { if (localMode) { if (stopDrivers) DriverManager::Instance()->stopDevices(managedDrivers); } else { if (stopDrivers) DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first()); if (remoteManagerStart && currentProfile->INDIWebManagerPort != -1) { INDI::WebManager::stopProfile(currentProfile); remoteManagerStart = false; } } } reset(); profileGroup->setEnabled(true); appendLogText(i18n("INDI services stopped.")); } void EkosManager::processNewDevice(ISD::GDInterface *devInterface) { qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << devInterface->getDeviceName(); for(auto &device: genericDevices) { if (!strcmp(device->getDeviceName(), devInterface->getDeviceName())) { qCWarning(KSTARS_EKOS) << "Found duplicate device, ignoring..."; return; } } // Always reset INDI Connection status if we receive a new device indiConnectionStatus = EKOS_STATUS_IDLE; genericDevices.append(devInterface); nDevices--; connect(devInterface, SIGNAL(Connected()), this, SLOT(deviceConnected())); connect(devInterface, SIGNAL(Disconnected()), this, SLOT(deviceDisconnected())); connect(devInterface, SIGNAL(propertyDefined(INDI::Property*)), this, SLOT(processNewProperty(INDI::Property*))); if (nDevices <= 0) { ekosStartingStatus = EKOS_STATUS_SUCCESS; emit newEkosStartingStatus(ekosStartingStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); //controlPanelB->setEnabled(true); if (localMode == false && nDevices == 0) { if (currentProfile->autoConnect) appendLogText(i18n("Remote devices established.")); else appendLogText(i18n("Remote devices established. Please connect devices.")); } } } void EkosManager::deviceConnected() { connectB->setEnabled(false); disconnectB->setEnabled(true); processINDIB->setEnabled(false); if (Options::verboseLogging()) { ISD::GDInterface *device = (ISD::GDInterface *)sender(); qCInfo(KSTARS_EKOS) << device->getDeviceName() << "is connected."; } int nConnectedDevices = 0; foreach (ISD::GDInterface *device, genericDevices) { if (device->isConnected()) nConnectedDevices++; } qCDebug(KSTARS_EKOS) << nConnectedDevices << " devices connected out of " << genericDevices.count(); //if (nConnectedDevices >= pi->drivers.count()) if (nConnectedDevices >= genericDevices.count()) { indiConnectionStatus = EKOS_STATUS_SUCCESS; qCInfo(KSTARS_EKOS)<< "All INDI devices are now connected."; } else indiConnectionStatus = EKOS_STATUS_PENDING; ISD::GDInterface *dev = static_cast(sender()); if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE) { if (mountProcess.get() != nullptr) { mountProcess->setEnabled(true); if (alignProcess.get() != nullptr) alignProcess->setEnabled(true); } } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(true); if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); if (alignProcess.get() != nullptr) { if (mountProcess.get() && mountProcess->isEnabled()) alignProcess->setEnabled(true); else alignProcess->setEnabled(false); } if (guideProcess.get() != nullptr) guideProcess->setEnabled(true); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(true); } if (Options::neverLoadConfig()) return; INDIConfig tConfig = Options::loadConfigOnConnection() ? LOAD_LAST_CONFIG : LOAD_DEFAULT_CONFIG; foreach (ISD::GDInterface *device, genericDevices) { if (device == dev) { connect(dev, SIGNAL(switchUpdated(ISwitchVectorProperty*)), this, SLOT(watchDebugProperty(ISwitchVectorProperty*))); ISwitchVectorProperty *configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); if (configProp && configProp->s == IPS_IDLE) device->setConfig(tConfig); break; } } } void EkosManager::deviceDisconnected() { ISD::GDInterface *dev = static_cast(sender()); if (dev != nullptr) { if (dev->getState("CONNECTION") == IPS_ALERT) indiConnectionStatus = EKOS_STATUS_ERROR; else if (dev->getState("CONNECTION") == IPS_BUSY) indiConnectionStatus = EKOS_STATUS_PENDING; else indiConnectionStatus = EKOS_STATUS_IDLE; if (Options::verboseLogging()) qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected."; appendLogText(i18n("%1 is disconnected.", dev->getDeviceName())); } else indiConnectionStatus = EKOS_STATUS_IDLE; connectB->setEnabled(true); disconnectB->setEnabled(false); processINDIB->setEnabled(true); if (dev != nullptr && dev->getBaseDevice() && (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) { if (mountProcess.get() != nullptr) mountProcess->setEnabled(false); } // Do not disable modules on device connection loss, let them handle it /* else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE) { if (captureProcess.get() != nullptr) captureProcess->setEnabled(false); if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); if (alignProcess.get() != nullptr) alignProcess->setEnabled(false); if (guideProcess.get() != nullptr) guideProcess->setEnabled(false); } else if (dev->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::FOCUSER_INTERFACE) { if (focusProcess.get() != nullptr) focusProcess->setEnabled(false); }*/ } void EkosManager::setTelescope(ISD::GDInterface *scopeDevice) { //mount = scopeDevice; managedDevices[KSTARS_TELESCOPE] = scopeDevice; appendLogText(i18n("%1 is online.", scopeDevice->getDeviceName())); connect(scopeDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); initMount(); mountProcess->setTelescope(scopeDevice); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver mountProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); if (guideProcess.get() != nullptr) { guideProcess->setTelescope(scopeDevice); guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(scopeDevice); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } if (domeProcess.get() != nullptr) domeProcess->setTelescope(scopeDevice); ekosLiveClient->message()->sendMounts(); ekosLiveClient->message()->sendScopes(); } void EkosManager::setCCD(ISD::GDInterface *ccdDevice) { // No duplicates for (auto oneCCD : findDevices(KSTARS_CCD)) if (oneCCD == ccdDevice) return; managedDevices.insertMulti(KSTARS_CCD, ccdDevice); initCapture(); captureProcess->setEnabled(true); captureProcess->addCCD(ccdDevice); QString primaryCCD, guiderCCD; // Only look for primary & guider CCDs if we can tell a difference between them // otherwise rely on saved options if (currentProfile->ccd() != currentProfile->guider()) { foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) { if (QString(device->getDeviceName()).startsWith(currentProfile->ccd(), Qt::CaseInsensitive)) primaryCCD = QString(device->getDeviceName()); else if (QString(device->getDeviceName()).startsWith(currentProfile->guider(), Qt::CaseInsensitive)) guiderCCD = QString(device->getDeviceName()); } } bool rc = false; if (Options::defaultCaptureCCD().isEmpty() == false) rc = captureProcess->setCCD(Options::defaultCaptureCCD()); if (rc == false && primaryCCD.isEmpty() == false) captureProcess->setCCD(primaryCCD); initFocus(); focusProcess->addCCD(ccdDevice); rc = false; if (Options::defaultFocusCCD().isEmpty() == false) rc = focusProcess->setCCD(Options::defaultFocusCCD()); if (rc == false && primaryCCD.isEmpty() == false) focusProcess->setCCD(primaryCCD); initAlign(); alignProcess->addCCD(ccdDevice); rc = false; if (Options::defaultAlignCCD().isEmpty() == false) rc = alignProcess->setCCD(Options::defaultAlignCCD()); if (rc == false && primaryCCD.isEmpty() == false) alignProcess->setCCD(primaryCCD); initGuide(); guideProcess->addCCD(ccdDevice); rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCCD(Options::defaultGuideCCD()); if (rc == false && guiderCCD.isEmpty() == false) guideProcess->setCCD(guiderCCD); appendLogText(i18n("%1 is online.", ccdDevice->getDeviceName())); connect(ccdDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); if (managedDevices.contains(KSTARS_TELESCOPE)) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); } } void EkosManager::setFilter(ISD::GDInterface *filterDevice) { // No duplicates for (auto oneFilter : findDevices(KSTARS_FILTER)) if (oneFilter == filterDevice) return; managedDevices.insertMulti(KSTARS_FILTER, filterDevice); appendLogText(i18n("%1 filter is online.", filterDevice->getDeviceName())); initCapture(); connect(filterDevice, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(processNewNumber(INumberVectorProperty*)), Qt::UniqueConnection); connect(filterDevice, SIGNAL(textUpdated(ITextVectorProperty*)), this, SLOT(processNewText(ITextVectorProperty*)), Qt::UniqueConnection); captureProcess->addFilter(filterDevice); initFocus(); focusProcess->addFilter(filterDevice); initAlign(); alignProcess->addFilter(filterDevice); if (Options::defaultAlignFW().isEmpty() == false) alignProcess->setFilter(Options::defaultAlignFW(), -1); } void EkosManager::setFocuser(ISD::GDInterface *focuserDevice) { // No duplicates for (auto oneFocuser : findDevices(KSTARS_FOCUSER)) if (oneFocuser == focuserDevice) return; managedDevices.insertMulti(KSTARS_FOCUSER, focuserDevice); initCapture(); initFocus(); focusProcess->addFocuser(focuserDevice); if (Options::defaultFocusFocuser().isEmpty() == false) focusProcess->setFocuser(Options::defaultFocusFocuser()); appendLogText(i18n("%1 focuser is online.", focuserDevice->getDeviceName())); } void EkosManager::setDome(ISD::GDInterface *domeDevice) { managedDevices[KSTARS_DOME] = domeDevice; initDome(); domeProcess->setDome(domeDevice); if (captureProcess.get() != nullptr) captureProcess->setDome(domeDevice); if (alignProcess.get() != nullptr) alignProcess->setDome(domeDevice); if (managedDevices.contains(KSTARS_TELESCOPE)) domeProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); appendLogText(i18n("%1 is online.", domeDevice->getDeviceName())); } void EkosManager::setWeather(ISD::GDInterface *weatherDevice) { managedDevices[KSTARS_WEATHER] = weatherDevice; initWeather(); weatherProcess->setWeather(weatherDevice); appendLogText(i18n("%1 is online.", weatherDevice->getDeviceName())); } void EkosManager::setDustCap(ISD::GDInterface *dustCapDevice) { managedDevices.insertMulti(KSTARS_AUXILIARY, dustCapDevice); initDustCap(); dustCapProcess->setDustCap(dustCapDevice); appendLogText(i18n("%1 is online.", dustCapDevice->getDeviceName())); if (captureProcess.get() != nullptr) captureProcess->setDustCap(dustCapDevice); } void EkosManager::setLightBox(ISD::GDInterface *lightBoxDevice) { managedDevices.insertMulti(KSTARS_AUXILIARY, lightBoxDevice); if (captureProcess.get() != nullptr) captureProcess->setLightBox(lightBoxDevice); } void EkosManager::removeDevice(ISD::GDInterface *devInterface) { switch (devInterface->getType()) { case KSTARS_CCD: removeTabs(); break; case KSTARS_TELESCOPE: if (mountProcess.get() != nullptr) { mountProcess.reset(); } break; case KSTARS_FOCUSER: // TODO this should be done for all modules if (focusProcess.get() != nullptr) focusProcess.get()->removeDevice(devInterface); break; default: break; } appendLogText(i18n("%1 is offline.", devInterface->getDeviceName())); // #1 Remove from Generic Devices // Generic devices are ALL the devices we receive from INDI server // Whether Ekos cares about them (i.e. selected equipment) or extra devices we // do not care about foreach (ISD::GDInterface *genericDevice, genericDevices) if (!strcmp(genericDevice->getDeviceName(), devInterface->getDeviceName())) { genericDevices.removeOne(genericDevice); break; } // #2 Remove from Ekos Managed Device // Managed devices are devices selected by the user in the device profile foreach (ISD::GDInterface *device, managedDevices.values()) { if (device == devInterface) { managedDevices.remove(managedDevices.key(device)); if (managedDevices.count() == 0) cleanDevices(); break; } } } void EkosManager::processNewText(ITextVectorProperty *tvp) { if (!strcmp(tvp->name, "FILTER_NAME")) { ekosLiveClient.get()->message()->sendFilterWheels(); } } void EkosManager::processNewNumber(INumberVectorProperty *nvp) { if (!strcmp(nvp->name, "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(nvp->name, "CCD_INFO") || !strcmp(nvp->name, "GUIDER_INFO") || !strcmp(nvp->name, "CCD_FRAME") || !strcmp(nvp->name, "GUIDER_FRAME")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } /* if (!strcmp(nvp->name, "FILTER_SLOT")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); } */ } void EkosManager::processNewProperty(INDI::Property *prop) { ISD::GenericDevice *deviceInterface = qobject_cast(sender()); if (!strcmp(prop->getName(), "CONNECTION") && currentProfile->autoConnect) { deviceInterface->Connect(); return; } // Check if we need to turn on DEBUG for logging purposes if (!strcmp(prop->getName(), "DEBUG")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); if ( opsLogs->getINDIDebugInterface() & interface ) { // Check if we need to enable debug logging for the INDI drivers. ISwitchVectorProperty *debugSP = prop->getSwitch(); debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); } } // Handle debug levels for logging purposes if (!strcmp(prop->getName(), "DEBUG_LEVEL")) { uint16_t interface = deviceInterface->getBaseDevice()->getDriverInterface(); // Check if the logging option for the specific device class is on and if the device interface matches it. if ( opsLogs->getINDIDebugInterface() & interface ) { // Turn on everything ISwitchVectorProperty *debugLevel = prop->getSwitch(); for (int i=0; i < debugLevel->nsp; i++) debugLevel->sp[i].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(debugLevel); } } if (!strcmp(prop->getName(), "TELESCOPE_INFO") || !strcmp(prop->getName(), "TELESCOPE_SLEW_RATE") || !strcmp(prop->getName(), "TELESCOPE_PARK")) { ekosLiveClient.get()->message()->sendMounts(); ekosLiveClient.get()->message()->sendScopes(); } if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "CCD_TEMPERATURE") || !strcmp(prop->getName(), "CCD_ISO")) { ekosLiveClient.get()->message()->sendCameras(); ekosLiveClient.get()->media()->registerCameras(); } if (!strcmp(prop->getName(), "FILTER_SLOT")) ekosLiveClient.get()->message()->sendFilterWheels(); if (!strcmp(prop->getName(), "CCD_INFO") || !strcmp(prop->getName(), "GUIDER_INFO")) { if (focusProcess.get() != nullptr) focusProcess->syncCCDInfo(); if (guideProcess.get() != nullptr) guideProcess->syncCCDInfo(); if (alignProcess.get() != nullptr) alignProcess->syncCCDInfo(); return; } if (!strcmp(prop->getName(), "TELESCOPE_INFO") && managedDevices.contains(KSTARS_TELESCOPE)) { if (guideProcess.get() != nullptr) { guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //guideProcess->syncTelescopeInfo(); } if (alignProcess.get() != nullptr) { alignProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //alignProcess->syncTelescopeInfo(); } if (mountProcess.get() != nullptr) { mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); //mountProcess->syncTelescopeInfo(); } return; } if (!strcmp(prop->getName(), "GUIDER_EXPOSURE")) { foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initCapture(); initGuide(); useGuideHead = true; captureProcess->addGuideHead(device); guideProcess->addGuideHead(device); bool rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCCD(Options::defaultGuideCCD()); if (rc == false) guideProcess->setCCD(QString(device->getDeviceName()) + QString(" Guider")); return; } } return; } if (!strcmp(prop->getName(), "CCD_FRAME_TYPE")) { if (captureProcess.get() != nullptr) { foreach (ISD::GDInterface *device, findDevices(KSTARS_CCD)) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { captureProcess->syncFrameType(device); return; } } } return; } if (!strcmp(prop->getName(), "TELESCOPE_PARK") && managedDevices.contains(KSTARS_TELESCOPE)) { if (captureProcess.get() != nullptr) captureProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); if (mountProcess.get() != nullptr) mountProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); return; } /* if (!strcmp(prop->getName(), "FILTER_NAME")) { if (captureProcess.get() != nullptr) captureProcess->checkFilter(); if (focusProcess.get() != nullptr) focusProcess->checkFilter(); if (alignProcess.get() != nullptr) alignProcess->checkFilter(); return; } */ if (!strcmp(prop->getName(), "ASTROMETRY_SOLVER")) { foreach (ISD::GDInterface *device, genericDevices) { if (!strcmp(device->getDeviceName(), prop->getDeviceName())) { initAlign(); alignProcess->setAstrometryDevice(device); break; } } } if (!strcmp(prop->getName(), "ABS_ROTATOR_ANGLE")) { managedDevices[KSTARS_ROTATOR] = deviceInterface; if (captureProcess.get() != nullptr) captureProcess->setRotator(deviceInterface); if (alignProcess.get() != nullptr) alignProcess->setRotator(deviceInterface); } if (!strcmp(prop->getName(), "GPS_REFRESH")) { managedDevices.insertMulti(KSTARS_AUXILIARY, deviceInterface); if (mountProcess.get() != nullptr) mountProcess->setGPS(deviceInterface); } if (focusProcess.get() != nullptr && strstr(prop->getName(), "FOCUS_")) { focusProcess->checkFocuser(); } } QList EkosManager::findDevices(DeviceFamily type) { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); if (i.key() == type) deviceList.append(i.value()); } return deviceList; } void EkosManager::processTabChange() { QWidget *currentWidget = toolsWidget->currentWidget(); //if (focusProcess.get() != nullptr && currentWidget != focusProcess) //focusProcess->resetFrame(); if (alignProcess.get() && alignProcess.get() == currentWidget) { if (alignProcess->isEnabled() == false && captureProcess->isEnabled()) { if (managedDevices[KSTARS_CCD]->isConnected() && managedDevices.contains(KSTARS_TELESCOPE)) { if (alignProcess->isParserOK()) alignProcess->setEnabled(true); //#ifdef Q_OS_WIN else { // If current setting is remote astrometry and profile doesn't contain // remote astrometry, then we switch to online solver. Otherwise, the whole align // module remains disabled and the user cannot change re-enable it since he cannot select online solver ProfileInfo *pi = getCurrentProfile(); if (Options::solverType() == Ekos::Align::SOLVER_REMOTE && pi->aux1() != "Astrometry" && pi->aux2() != "Astrometry" && pi->aux3() != "Astrometry" && pi->aux4() != "Astrometry") { Options::setSolverType(Ekos::Align::SOLVER_ONLINE); alignModule()->setSolverType(Ekos::Align::SOLVER_ONLINE); alignProcess->setEnabled(true); } } //#endif } } alignProcess->checkCCD(); } else if (captureProcess.get() != nullptr && currentWidget == captureProcess.get()) { captureProcess->checkCCD(); } else if (focusProcess.get() != nullptr && currentWidget == focusProcess.get()) { focusProcess->checkCCD(); } else if (guideProcess.get() != nullptr && currentWidget == guideProcess.get()) { guideProcess->checkCCD(); } updateLog(); } void EkosManager::updateLog() { //if (enableLoggingCheck->isChecked() == false) //return; QWidget *currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) ekosLogOut->setPlainText(logText.join("\n")); else if (currentWidget == alignProcess.get()) ekosLogOut->setPlainText(alignProcess->getLogText()); else if (currentWidget == captureProcess.get()) ekosLogOut->setPlainText(captureProcess->getLogText()); else if (currentWidget == focusProcess.get()) ekosLogOut->setPlainText(focusProcess->getLogText()); else if (currentWidget == guideProcess.get()) ekosLogOut->setPlainText(guideProcess->getLogText()); else if (currentWidget == mountProcess.get()) ekosLogOut->setPlainText(mountProcess->getLogText()); if (currentWidget == schedulerProcess.get()) ekosLogOut->setPlainText(schedulerProcess->getLogText()); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 #endif } void EkosManager::appendLogText(const QString &text) { logText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); qCInfo(KSTARS_EKOS) << text; updateLog(); } void EkosManager::clearLog() { QWidget *currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) { logText.clear(); updateLog(); } else if (currentWidget == alignProcess.get()) alignProcess->clearLog(); else if (currentWidget == captureProcess.get()) captureProcess->clearLog(); else if (currentWidget == focusProcess.get()) focusProcess->clearLog(); else if (currentWidget == guideProcess.get()) guideProcess->clearLog(); else if (currentWidget == mountProcess.get()) mountProcess->clearLog(); else if (currentWidget == schedulerProcess.get()) schedulerProcess->clearLog(); } void EkosManager::initCapture() { if (captureProcess.get() != nullptr) return; captureProcess.reset(new Ekos::Capture()); captureProcess->setEnabled(false); int index = toolsWidget->addTab(captureProcess.get(), QIcon(":/icons/ekos_ccd.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18nc("Charge-Coupled Device", "CCD")); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } connect(captureProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); connect(captureProcess.get(), SIGNAL(newStatus(Ekos::CaptureState)), this, SLOT(updateCaptureStatus(Ekos::CaptureState))); connect(captureProcess.get(), SIGNAL(newImage(QImage*,Ekos::SequenceJob*)), this, SLOT(updateCaptureProgress(QImage*,Ekos::SequenceJob*))); connect(captureProcess.get(), SIGNAL(newExposureProgress(Ekos::SequenceJob*)), this, SLOT(updateExposureProgress(Ekos::SequenceJob*))); connect(captureProcess.get(), &Ekos::Capture::sequenceChanged, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSequence); connect(captureProcess.get(), &Ekos::Capture::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendCaptureSettings); captureGroup->setEnabled(true); sequenceProgress->setEnabled(true); captureProgress->setEnabled(true); imageProgress->setEnabled(true); captureProcess->setFilterManager(filterManager); if (!capturePI) { capturePI = new QProgressIndicator(captureProcess.get()); captureStatusLayout->insertWidget(0, capturePI); } foreach (ISD::GDInterface *device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::DUSTCAP_INTERFACE) captureProcess->setDustCap(device); if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::LIGHTBOX_INTERFACE) captureProcess->setLightBox(device); } if (focusProcess.get() != nullptr) { // Autofocus connect(captureProcess.get(), SIGNAL(checkFocus(double)), focusProcess.get(), SLOT(checkFocus(double)), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), captureProcess.get(), SLOT(setFocusStatus(Ekos::FocusState)), Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), SIGNAL(meridianFlipStarted()), focusProcess.get(), SLOT(resetFrame()), Qt::UniqueConnection); } if (alignProcess.get() != nullptr) { // Alignment flag connect(alignProcess.get(), SIGNAL(newStatus(Ekos::AlignState)), captureProcess.get(), SLOT(setAlignStatus(Ekos::AlignState)), Qt::UniqueConnection); // Solver data connect(alignProcess.get(), SIGNAL(newSolverResults(double,double,double,double)), captureProcess.get(), SLOT(setAlignResults(double,double,double,double)), Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), SIGNAL(newStatus(Ekos::CaptureState)), alignProcess.get(), SLOT(setCaptureStatus(Ekos::CaptureState)), Qt::UniqueConnection); } if (mountProcess.get() != nullptr) { // Meridian Flip connect(captureProcess.get(), SIGNAL(meridianFlipStarted()), mountProcess.get(), SLOT(disableAltLimits()), Qt::UniqueConnection); connect(captureProcess.get(), SIGNAL(meridianFlipCompleted()), mountProcess.get(), SLOT(enableAltLimits()), Qt::UniqueConnection); connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), captureProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); } if (managedDevices.contains(KSTARS_DOME)) { captureProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { captureProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } } void EkosManager::initAlign() { if (alignProcess.get() != nullptr) return; alignProcess.reset(new Ekos::Align(currentProfile)); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); alignProcess->setEnabled(false); int index = toolsWidget->addTab(alignProcess.get(), QIcon(":/icons/ekos_align.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Align")); connect(alignProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } alignProcess->setFilterManager(filterManager); if (captureProcess.get() != nullptr) { // Align Status connect(alignProcess.get(), SIGNAL(newStatus(Ekos::AlignState)), captureProcess.get(), SLOT(setAlignStatus(Ekos::AlignState)), Qt::UniqueConnection); // Solver data connect(alignProcess.get(), SIGNAL(newSolverResults(double,double,double,double)), captureProcess.get(), SLOT(setAlignResults(double,double,double,double)), Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), SIGNAL(newStatus(Ekos::CaptureState)), alignProcess.get(), SLOT(setCaptureStatus(Ekos::CaptureState)), Qt::UniqueConnection); } if (focusProcess.get() != nullptr) { connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), alignProcess.get(), SLOT(setFocusStatus(Ekos::FocusState)), Qt::UniqueConnection); } if (mountProcess.get() != nullptr) connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), alignProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); if (ekosLiveClient.get() != nullptr) { connect(alignProcess.get(), &Ekos::Align::newStatus, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignStatus); connect(alignProcess.get(), &Ekos::Align::newSolution, ekosLiveClient.get()->message(), &EkosLive::Message::setAlignSolution); connect(alignProcess.get(), &Ekos::Align::newPAHStage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHStage); connect(alignProcess.get(), &Ekos::Align::newPAHMessage, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHMessage); connect(alignProcess.get(), &Ekos::Align::PAHEnabled, ekosLiveClient.get()->message(), &EkosLive::Message::setPAHEnabled); //connect(alignProcess.get(), &Ekos::Align::newFOVTelescopeType, ekosLiveClient.get()->message(), &EkosLive::Message::setFOVTelescopeType); connect(alignProcess.get(), &Ekos::Align::newImage, [&](FITSView *view) { ekosLiveClient.get()->media()->sendPreviewImage(view, QString()); }); connect(alignProcess.get(), &Ekos::Align::newFrame, ekosLiveClient.get()->media(), &EkosLive::Media::sendUpdatedFrame); connect(alignProcess.get(), &Ekos::Align::polarResultUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::setPolarResults); connect(alignProcess.get(), &Ekos::Align::settingsUpdated, ekosLiveClient.get()->message(), &EkosLive::Message::sendAlignSettings); connect(alignProcess.get(), &Ekos::Align::newCorrectionVector, ekosLiveClient.get()->media(), &EkosLive::Media::setCorrectionVector); } if (managedDevices.contains(KSTARS_DOME)) { alignProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { alignProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } } void EkosManager::initFocus() { if (focusProcess.get() != nullptr) return; focusProcess.reset(new Ekos::Focus()); int index = toolsWidget->addTab(focusProcess.get(), QIcon(":/icons/ekos_focus.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Focus")); connect(focusProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), this, SLOT(setFocusStatus(Ekos::FocusState))); connect(focusProcess.get(), SIGNAL(newStarPixmap(QPixmap&)), this, SLOT(updateFocusStarPixmap(QPixmap&))); connect(focusProcess.get(), SIGNAL(newProfilePixmap(QPixmap&)), this, SLOT(updateFocusProfilePixmap(QPixmap&))); connect(focusProcess.get(), SIGNAL(newHFR(double,int)), this, SLOT(updateCurrentHFR(double,int))); focusProcess->setFilterManager(filterManager); connect(filterManager.data(), SIGNAL(checkFocus(double)), focusProcess.get(), SLOT(checkFocus(double)), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), filterManager.data(), SLOT(setFocusStatus(Ekos::FocusState)), Qt::UniqueConnection); connect(filterManager.data(), SIGNAL(newFocusOffset(int,bool)), focusProcess.get(), SLOT(adjustFocusOffset(int,bool)), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(focusPositionAdjusted()), filterManager.data(), SLOT(setFocusOffsetComplete()), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(absolutePositionChanged(int)), filterManager.data(), SLOT(setFocusAbsolutePosition(int)), Qt::UniqueConnection); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } focusGroup->setEnabled(true); if (!focusPI) { focusPI = new QProgressIndicator(focusProcess.get()); focusStatusLayout->insertWidget(0, focusPI); } if (captureProcess.get() != nullptr) { // Autofocus connect(captureProcess.get(), SIGNAL(checkFocus(double)), focusProcess.get(), SLOT(checkFocus(double)), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), captureProcess.get(), SLOT(setFocusStatus(Ekos::FocusState)), Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), SIGNAL(meridianFlipStarted()), focusProcess.get(), SLOT(resetFrame()), Qt::UniqueConnection); } if (guideProcess.get() != nullptr) { // Suspend connect(focusProcess.get(), SIGNAL(suspendGuiding()), guideProcess.get(), SLOT(suspend()), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(resumeGuiding()), guideProcess.get(), SLOT(resume()), Qt::UniqueConnection); } if (alignProcess.get() != nullptr) { connect(focusProcess.get(), SIGNAL(newStatus(Ekos::FocusState)), alignProcess.get(), SLOT(setFocusStatus(Ekos::FocusState)), Qt::UniqueConnection); } if (mountProcess.get() != nullptr) connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), focusProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); } void EkosManager::updateCurrentHFR(double newHFR, int position) { currentHFR->setText(QString("%1").arg(newHFR, 0, 'f', 2) + " px"); QJsonObject cStatus = { {"hfr", newHFR}, {"pos", position} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void EkosManager::updateSigmas(double ra, double de) { errRA->setText(QString::number(ra, 'f', 2) + "\""); errDEC->setText(QString::number(de, 'f', 2) + "\""); QJsonObject cStatus = { {"rarms", ra}, {"derms", de} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void EkosManager::initMount() { if (mountProcess.get() != nullptr) return; mountProcess.reset(new Ekos::Mount()); int index = toolsWidget->addTab(mountProcess.get(), QIcon(":/icons/ekos_mount.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Mount")); connect(mountProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); connect(mountProcess.get(), SIGNAL(newCoords(QString,QString,QString,QString)), this, SLOT(updateMountCoords(QString,QString,QString,QString))); connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), this, SLOT(updateMountStatus(ISD::Telescope::TelescopeStatus))); //connect(mountProcess.get(), SIGNAL(newTarget(QString)), mountTarget, SLOT(setText(QString))); connect(mountProcess.get(), &Ekos::Mount::newTarget, [&](const QString &target) { mountTarget->setText(target); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", target}})); }); connect(mountProcess.get(), &Ekos::Mount::slewRateChanged, [&](int slewRate) { QJsonObject status = { { "slewRate", slewRate} }; ekosLiveClient.get()->message()->updateMountStatus(status); } ); foreach (ISD::GDInterface *device, findDevices(KSTARS_AUXILIARY)) { if (device->getBaseDevice()->getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE) mountProcess->setGPS(device); } if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } if (!mountPI) { mountPI = new QProgressIndicator(mountProcess.get()); mountStatusLayout->insertWidget(0, mountPI); } mountGroup->setEnabled(true); if (captureProcess.get() != nullptr) { // Meridian Flip connect(captureProcess.get(), SIGNAL(meridianFlipStarted()), mountProcess.get(), SLOT(disableAltLimits()), Qt::UniqueConnection); connect(captureProcess.get(), SIGNAL(meridianFlipCompleted()), mountProcess.get(), SLOT(enableAltLimits()), Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), captureProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); } if (alignProcess.get() != nullptr) connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), alignProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); if (focusProcess.get() != nullptr) connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), focusProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); if (guideProcess.get() != nullptr) { connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), guideProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); } } void EkosManager::initGuide() { if (guideProcess.get() == nullptr) { guideProcess.reset(new Ekos::Guide()); double primaryScopeFL=0, primaryScopeAperture=0, guideScopeFL=0, guideScopeAperture=0; getCurrentProfileTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); // Save telescope info in mount driver guideProcess->setTelescopeInfo(primaryScopeFL, primaryScopeAperture, guideScopeFL, guideScopeAperture); } //if ( (haveGuider || ccdCount > 1 || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess) == -1) if ((findDevices(KSTARS_CCD).isEmpty() == false || useGuideHead) && useST4 && toolsWidget->indexOf(guideProcess.get()) == -1) { //if (mount && mount->isConnected()) if (managedDevices.contains(KSTARS_TELESCOPE) && managedDevices[KSTARS_TELESCOPE]->isConnected()) guideProcess->setTelescope(managedDevices[KSTARS_TELESCOPE]); int index = toolsWidget->addTab(guideProcess.get(), QIcon(":/icons/ekos_guide.png"), ""); toolsWidget->tabBar()->setTabToolTip(index, i18n("Guide")); connect(guideProcess.get(), SIGNAL(newLog()), this, SLOT(updateLog())); guideGroup->setEnabled(true); if (!guidePI) { guidePI = new QProgressIndicator(guideProcess.get()); guideStatusLayout->insertWidget(0, guidePI); } connect(guideProcess.get(), SIGNAL(newStatus(Ekos::GuideState)), this, SLOT(updateGuideStatus(Ekos::GuideState))); connect(guideProcess.get(), SIGNAL(newStarPixmap(QPixmap&)), this, SLOT(updateGuideStarPixmap(QPixmap&))); connect(guideProcess.get(), SIGNAL(newProfilePixmap(QPixmap&)), this, SLOT(updateGuideProfilePixmap(QPixmap&))); connect(guideProcess.get(), SIGNAL(sigmasUpdated(double,double)), this, SLOT(updateSigmas(double,double))); if (Options::ekosLeftIcons()) { QTransform trans; trans.rotate(90); QIcon icon = toolsWidget->tabIcon(index); QPixmap pix = icon.pixmap(QSize(48, 48)); icon = QIcon(pix.transformed(trans)); toolsWidget->setTabIcon(index, icon); } } if (captureProcess.get() != nullptr) { guideProcess->disconnect(captureProcess.get()); captureProcess->disconnect(guideProcess.get()); // Guide Limits //connect(guideProcess.get(), SIGNAL(guideReady()), captureProcess, SLOT(enableGuideLimits())); connect(guideProcess.get(), SIGNAL(newStatus(Ekos::GuideState)), captureProcess.get(), SLOT(setGuideStatus(Ekos::GuideState))); connect(guideProcess.get(), SIGNAL(newAxisDelta(double,double)), captureProcess.get(), SLOT(setGuideDeviation(double,double))); // Dithering connect(captureProcess.get(), SIGNAL(newStatus(Ekos::CaptureState)), guideProcess.get(), SLOT(setCaptureStatus(Ekos::CaptureState)), Qt::UniqueConnection); // Guide Head connect(captureProcess.get(), SIGNAL(suspendGuiding()), guideProcess.get(), SLOT(suspend())); connect(captureProcess.get(), SIGNAL(resumeGuiding()), guideProcess.get(), SLOT(resume())); connect(guideProcess.get(), SIGNAL(guideChipUpdated(ISD::CCDChip*)), captureProcess.get(), SLOT(setGuideChip(ISD::CCDChip*))); // Meridian Flip connect(captureProcess.get(), SIGNAL(meridianFlipStarted()), guideProcess.get(), SLOT(abort()), Qt::UniqueConnection); connect(captureProcess.get(), SIGNAL(meridianFlipCompleted()), guideProcess.get(), SLOT(startAutoCalibrateGuide()), Qt::UniqueConnection); } if (mountProcess.get() != nullptr) { // Parking connect(mountProcess.get(), SIGNAL(newStatus(ISD::Telescope::TelescopeStatus)), guideProcess.get(), SLOT(setMountStatus(ISD::Telescope::TelescopeStatus)), Qt::UniqueConnection); } if (focusProcess.get() != nullptr) { // Suspend connect(focusProcess.get(), SIGNAL(suspendGuiding()), guideProcess.get(), SLOT(suspend()), Qt::UniqueConnection); connect(focusProcess.get(), SIGNAL(resumeGuiding()), guideProcess.get(), SLOT(resume()), Qt::UniqueConnection); } } void EkosManager::initDome() { if (domeProcess.get() != nullptr) return; domeProcess.reset(new Ekos::Dome()); } void EkosManager::initWeather() { if (weatherProcess.get() != nullptr) return; weatherProcess.reset(new Ekos::Weather()); } void EkosManager::initDustCap() { if (dustCapProcess.get() != nullptr) return; dustCapProcess.reset(new Ekos::DustCap()); } void EkosManager::setST4(ISD::ST4 *st4Driver) { appendLogText(i18n("Guider port from %1 is ready.", st4Driver->getDeviceName())); useST4 = true; initGuide(); guideProcess->addST4(st4Driver); if (Options::defaultST4Driver().isEmpty() == false) guideProcess->setST4(Options::defaultST4Driver()); } void EkosManager::removeTabs() { disconnect(toolsWidget, SIGNAL(currentChanged(int)), this, SLOT(processTabChange())); for (int i = 2; i < toolsWidget->count(); i++) toolsWidget->removeTab(i); alignProcess.reset(); captureProcess.reset(); focusProcess.reset(); guideProcess.reset(); mountProcess.reset(); domeProcess.reset(); weatherProcess.reset(); dustCapProcess.reset(); managedDevices.clear(); connect(toolsWidget, SIGNAL(currentChanged(int)), this, SLOT(processTabChange())); } bool EkosManager::isRunning(const QString &process) { QProcess ps; #ifdef Q_OS_OSX ps.start("pgrep", QStringList() << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.length()>0; #else ps.start("ps", QStringList() << "-o" << "comm" << "--no-headers" << "-C" << process); ps.waitForFinished(); QString output = ps.readAllStandardOutput(); return output.contains(process); #endif } void EkosManager::addObjectToScheduler(SkyObject *object) { if (schedulerProcess.get() != nullptr) schedulerProcess->addObject(object); } QString EkosManager::getCurrentJobName() { return schedulerProcess->getCurrentJobName(); } bool EkosManager::setProfile(const QString &profileName) { int index = profileCombo->findText(profileName); if (index < 0) return false; profileCombo->setCurrentIndex(index); return true; } QStringList EkosManager::getProfiles() { QStringList profiles; for (int i = 0; i < profileCombo->count(); i++) profiles << profileCombo->itemText(i); return profiles; } void EkosManager::addProfile() { ProfileEditor editor(this); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } void EkosManager::editProfile() { ProfileEditor editor(this); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); if (editor.exec() == QDialog::Accepted) { int currentIndex = profileCombo->currentIndex(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(currentIndex); } currentProfile = getCurrentProfile(); } void EkosManager::deleteProfile() { currentProfile = getCurrentProfile(); if (currentProfile->name == "Simulators") return; if (KMessageBox::questionYesNo(this, i18n("Are you sure you want to delete the profile?"), i18n("Confirm Delete")) == KMessageBox::No) return; KStarsData::Instance()->userdb()->DeleteProfile(currentProfile); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); } void EkosManager::wizardProfile() { ProfileWizard wz; if (wz.exec() != QDialog::Accepted) return; ProfileEditor editor(this); editor.setProfileName(wz.profileName); editor.setAuxDrivers(wz.selectedAuxDrivers()); if (wz.useInternalServer == false) editor.setHostPort(wz.host, wz.port); editor.setWebManager(wz.useWebManager); editor.setGuiderType(wz.selectedExternalGuider()); // Disable connection options editor.setConnectionOptionsEnabled(false); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } ProfileInfo *EkosManager::getCurrentProfile() { ProfileInfo *currProfile = nullptr; // Get current profile for (auto& pi : profiles) { if (profileCombo->currentText() == pi->name) { currProfile = pi.get(); break; } } return currProfile; } void EkosManager::updateProfileLocation(ProfileInfo *pi) { if (pi->city.isEmpty() == false) { bool cityFound = KStars::Instance()->setGeoLocation(pi->city, pi->province, pi->country); if (cityFound) appendLogText(i18n("Site location updated to %1.", KStarsData::Instance()->geo()->fullName())); else appendLogText(i18n("Failed to update site location to %1. City not found.", KStarsData::Instance()->geo()->fullName())); } } void EkosManager::updateMountStatus(ISD::Telescope::TelescopeStatus status) { static ISD::Telescope::TelescopeStatus lastStatus = ISD::Telescope::MOUNT_IDLE; if (status == lastStatus) return; lastStatus = status; mountStatus->setText(dynamic_cast(managedDevices[KSTARS_TELESCOPE])->getStatusString(status)); switch (status) { case ISD::Telescope::MOUNT_PARKING: case ISD::Telescope::MOUNT_SLEWING: case ISD::Telescope::MOUNT_MOVING: mountPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; case ISD::Telescope::MOUNT_TRACKING: mountPI->setColor(Qt::darkGreen); if (mountPI->isAnimated() == false) mountPI->startAnimation(); break; default: if (mountPI->isAnimated()) mountPI->stopAnimation(); } QJsonObject cStatus = { {"status", mountStatus->text()} }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void EkosManager::updateMountCoords(const QString &ra, const QString &dec, const QString &az, const QString &alt) { raOUT->setText(ra); decOUT->setText(dec); azOUT->setText(az); altOUT->setText(alt); QJsonObject cStatus = { {"ra", dms::fromString(ra, false).Degrees()}, {"de", dms::fromString(dec, true).Degrees()}, {"az", dms::fromString(az, true).Degrees()}, {"at", dms::fromString(alt, true).Degrees()}, }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void EkosManager::updateCaptureStatus(Ekos::CaptureState status) { captureStatus->setText(Ekos::getCaptureStatusString(status)); captureProgress->setValue(captureProcess->getProgressPercentage()); overallCountDown.setHMS(0, 0, 0); overallCountDown = overallCountDown.addSecs(captureProcess->getOverallRemainingTime()); sequenceCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(captureProcess->getActiveJobRemainingTime()); if (status != Ekos::CAPTURE_ABORTED && status != Ekos::CAPTURE_COMPLETE && status != Ekos::CAPTURE_IDLE) { if (status == Ekos::CAPTURE_CAPTURING) capturePI->setColor(Qt::darkGreen); else capturePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (capturePI->isAnimated() == false) { capturePI->startAnimation(); countdownTimer.start(); } } else { if (capturePI->isAnimated()) { capturePI->stopAnimation(); countdownTimer.stop(); if (focusStatus->text() == "Complete") { if (focusPI->isAnimated()) focusPI->stopAnimation(); } imageProgress->setValue(0); sequenceLabel->setText(i18n("Sequence")); imageRemainingTime->setText("--:--:--"); overallRemainingTime->setText("--:--:--"); sequenceRemainingTime->setText("--:--:--"); } } QJsonObject cStatus = { {"status", captureStatus->text()}, {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(cStatus); } void EkosManager::updateCaptureProgress(QImage *image, Ekos::SequenceJob *job) { // Image is set to nullptr only on initial capture start up int completed = 0; if (job->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) completed = job->getCompleted() + 1; else completed = (image == nullptr) ? job->getCompleted() : job->getCompleted() + 1; if (job->isPreview() == false) { sequenceLabel->setText(QString("Job # %1/%2 %3 (%4/%5)") .arg(captureProcess->getActiveJobID() + 1) .arg(captureProcess->getJobCount()) .arg(job->getFullPrefix()) .arg(completed) .arg(job->getCount())); } else sequenceLabel->setText(i18n("Preview")); sequenceProgress->setRange(0, job->getCount()); sequenceProgress->setValue(completed); QJsonObject status = { {"seqv", completed}, {"seqr", job->getCount()}, {"seql", sequenceLabel->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void EkosManager::updateExposureProgress(Ekos::SequenceJob *job) { imageCountDown.setHMS(0, 0, 0); imageCountDown = imageCountDown.addSecs(job->getExposeLeft()); if (imageCountDown.hour() == 23) imageCountDown.setHMS(0, 0, 0); imageProgress->setRange(0, job->getExposure()); imageProgress->setValue(job->getExposeLeft()); imageRemainingTime->setText(imageCountDown.toString("hh:mm:ss")); QJsonObject status { {"expv", job->getExposeLeft()}, {"expr", job->getExposure()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void EkosManager::updateCaptureCountDown() { overallCountDown = overallCountDown.addSecs(-1); if (overallCountDown.hour() == 23) overallCountDown.setHMS(0, 0, 0); sequenceCountDown = sequenceCountDown.addSecs(-1); if (sequenceCountDown.hour() == 23) sequenceCountDown.setHMS(0, 0, 0); overallRemainingTime->setText(overallCountDown.toString("hh:mm:ss")); sequenceRemainingTime->setText(sequenceCountDown.toString("hh:mm:ss")); QJsonObject status = { {"seqt", sequenceRemainingTime->text()}, {"ovt", overallRemainingTime->text()} }; ekosLiveClient.get()->message()->updateCaptureStatus(status); } void EkosManager::updateFocusStarPixmap(QPixmap &starPixmap) { if (starPixmap.isNull()) return; focusStarPixmap.reset(new QPixmap(starPixmap)); focusStarImage->setPixmap(focusStarPixmap->scaled(focusStarImage->width(), focusStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void EkosManager::updateFocusProfilePixmap(QPixmap &profilePixmap) { if (profilePixmap.isNull()) return; focusProfileImage->setPixmap(profilePixmap); } void EkosManager::setFocusStatus(Ekos::FocusState status) { focusStatus->setText(Ekos::getFocusStatusString(status)); if (status >= Ekos::FOCUS_PROGRESS) { focusPI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else if (status == Ekos::FOCUS_COMPLETE && Options::enforceAutofocus() && captureProcess->getActiveJobID() != -1) { focusPI->setColor(Qt::darkGreen); if (focusPI->isAnimated() == false) focusPI->startAnimation(); } else { if (focusPI->isAnimated()) focusPI->stopAnimation(); } QJsonObject cStatus = { {"status", focusStatus->text()} }; ekosLiveClient.get()->message()->updateFocusStatus(cStatus); } void EkosManager::updateGuideStatus(Ekos::GuideState status) { guideStatus->setText(Ekos::getGuideStatusString(status)); switch (status) { case Ekos::GUIDE_IDLE: case Ekos::GUIDE_CALIBRATION_ERROR: case Ekos::GUIDE_ABORTED: case Ekos::GUIDE_SUSPENDED: case Ekos::GUIDE_DITHERING_ERROR: case Ekos::GUIDE_CALIBRATION_SUCESS: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; case Ekos::GUIDE_CALIBRATING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_GUIDING: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING: guidePI->setColor(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor"))); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; case Ekos::GUIDE_DITHERING_SUCCESS: guidePI->setColor(Qt::darkGreen); if (guidePI->isAnimated() == false) guidePI->startAnimation(); break; default: if (guidePI->isAnimated()) guidePI->stopAnimation(); break; } QJsonObject cStatus = { {"status", guideStatus->text()} }; ekosLiveClient.get()->message()->updateGuideStatus(cStatus); } void EkosManager::updateGuideStarPixmap(QPixmap &starPix) { if (starPix.isNull()) return; guideStarPixmap.reset(new QPixmap(starPix)); guideStarImage->setPixmap(guideStarPixmap->scaled(guideStarImage->width(), guideStarImage->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void EkosManager::updateGuideProfilePixmap(QPixmap &profilePix) { if (profilePix.isNull()) return; guideProfileImage->setPixmap(profilePix); } void EkosManager::setTarget(SkyObject *o) { mountTarget->setText(o->name()); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", o->name()}})); } void EkosManager::showEkosOptions() { QWidget *currentWidget = toolsWidget->currentWidget(); if (alignProcess.get() && alignProcess.get() == currentWidget) { KConfigDialog *alignSettings = KConfigDialog::exists("alignsettings"); if (alignSettings) { alignSettings->setEnabled(true); alignSettings->show(); } return; } if (guideProcess.get() && guideProcess.get() == currentWidget) { KConfigDialog::showDialog("guidesettings"); return; } if (ekosOptionsWidget == nullptr) { optionsB->click(); } else if (KConfigDialog::showDialog("settings")) { KConfigDialog *cDialog = KConfigDialog::exists("settings"); cDialog->setCurrentPage(ekosOptionsWidget); } } void EkosManager::getCurrentProfileTelescopeInfo(double &primaryFocalLength, double &primaryAperture, double &guideFocalLength, double &guideAperture) { ProfileInfo *pi = getCurrentProfile(); if (pi) { int primaryScopeID=0, guideScopeID=0; primaryScopeID=pi->primaryscope; guideScopeID=pi->guidescope; if (primaryScopeID > 0 || guideScopeID > 0) { // Get all OAL equipment filter list QList m_scopeList; KStarsData::Instance()->userdb()->GetAllScopes(m_scopeList); foreach(OAL::Scope *oneScope, m_scopeList) { if (oneScope->id().toInt() == primaryScopeID) { primaryFocalLength = oneScope->focalLength(); primaryAperture = oneScope->aperture(); } if (oneScope->id().toInt() == guideScopeID) { guideFocalLength = oneScope->focalLength(); guideAperture = oneScope->aperture(); } } } } } void EkosManager::updateDebugInterfaces() { KSUtils::Logging::SyncFilterRules(); for (ISD::GDInterface *device : genericDevices) { INDI::Property *debugProp = device->getProperty("DEBUG"); ISwitchVectorProperty *debugSP = nullptr; if (debugProp) debugSP = debugProp->getSwitch(); else continue; // Check if the debug interface matches the driver device class if ( ( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && debugSP->sp[0].s != ISS_ON) { debugSP->sp[0].s = ISS_ON; debugSP->sp[1].s = ISS_OFF; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Enabling debug logging for %1...", device->getDeviceName())); } else if ( !( opsLogs->getINDIDebugInterface() & device->getBaseDevice()->getDriverInterface() ) && debugSP->sp[0].s != ISS_OFF) { debugSP->sp[0].s = ISS_OFF; debugSP->sp[1].s = ISS_ON; device->getDriverInfo()->getClientManager()->sendNewSwitch(debugSP); appendLogText(i18n("Disabling debug logging for %1...", device->getDeviceName())); } if (opsLogs->isINDISettingsChanged()) device->setConfig(SAVE_CONFIG); } } void EkosManager::watchDebugProperty(ISwitchVectorProperty *svp) { if (!strcmp(svp->name, "DEBUG")) { ISD::GenericDevice *deviceInterface = qobject_cast(sender()); // We don't process pure general interfaces if (deviceInterface->getBaseDevice()->getDriverInterface() == INDI::BaseDevice::GENERAL_INTERFACE) return; // If debug was turned off, but our logging policy requires it then turn it back on. // We turn on debug logging if AT LEAST one driver interface is selected by the logging settings if (svp->s == IPS_OK && svp->sp[0].s == ISS_OFF && (opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_ON; svp->sp[1].s = ISS_OFF; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-enabling debug logging for %1...", deviceInterface->getDeviceName())); } // To turn off debug logging, NONE of the driver interfaces should be enabled in logging settings. // For example, if we have CCD+FilterWheel device and CCD + Filter Wheel logging was turned on in // the log settings, then if the user turns off only CCD logging, the debug logging is NOT // turned off until he turns off Filter Wheel logging as well. else if (svp->s == IPS_OK && svp->sp[0].s == ISS_ON && !(opsLogs->getINDIDebugInterface() & deviceInterface->getBaseDevice()->getDriverInterface())) { svp->sp[0].s = ISS_OFF; svp->sp[1].s = ISS_ON; deviceInterface->getDriverInfo()->getClientManager()->sendNewSwitch(svp); appendLogText(i18n("Re-disabling debug logging for %1...", deviceInterface->getDeviceName())); } } } void EkosManager::announceEvent(const QString &message, KSNotification::EventType event) { ekosLiveClient.get()->message()->sendEvent(message, event); } diff --git a/kstars/ekos/ekosmanager.ui b/kstars/ekos/ekosmanager.ui index 348e18cc8..94279c6ca 100644 --- a/kstars/ekos/ekosmanager.ui +++ b/kstars/ekos/ekosmanager.ui @@ -1,1593 +1,1593 @@ EkosManager 0 0 604 643 16777215 16777215 0 0 Ekos 1 3 3 3 3 Qt::Vertical 0 0 0 0 0 3 3 3 3 3 1 1. Select Profile 1 3 3 3 3 Profile: 0 0 QComboBox::AdjustToContents 0 0 22 22 22 22 Add profile 22 22 0 0 22 22 22 22 Edit profile 22 22 0 0 22 22 22 22 Remove profile 22 22 22 22 22 22 Custom Drivers 22 22 22 22 22 22 Launch Ekos Profile Wizard 22 22 Qt::Horizontal QSizePolicy::Minimum 1 20 2. Start && Stop INDI 1 3 3 3 3 1 true 32 32 Start INDI 32 32 Logs true 32 32 Ekos Live 22 22 true 32 32 Ekos Options 22 22 3. Connect && Disconnect Devices 1 3 3 3 3 1 false 32 32 Connect false 32 32 Disconnect true 0 0 0 0 Summary 1 3 3 3 3 false Capture 3 3 3 3 1 font-weight:bold; Status: font-weight:bold; Idle Qt::Horizontal 1 20 250 250 1 false 3 1 Image Qt::AlignCenter 1 Qt::Horizontal 13 20 false 100 100 100 100 Qt::Horizontal 13 20 --:--:-- Qt::AlignCenter 1 Sequence Qt::AlignCenter 1 Qt::Horizontal 13 20 false 100 100 100 100 Qt::Horizontal 13 20 --:--:-- Qt::AlignCenter 1 Overall Qt::AlignCenter 1 Qt::Horizontal 13 20 false 100 100 100 100 1 3 3 3 3 Qt::Horizontal 13 20 --:--:-- Qt::AlignCenter 1 false Mount && Alignment 1 3 3 3 3 1 font-weight:bold; Status: Qt::PlainText font-weight:bold; Idle Qt::PlainText Qt::Horizontal 40 20 font-weight:bold; Target: 50 20 1 Right Ascension RA: true Declination DE: true Azimuth AZ: true Altitude AL: true Qt::Vertical 10 10 false 0 0 Focus false 1 3 3 3 3 1 font-weight:bold; Status: font-weight:bold; Idle Qt::Horizontal 40 20 HFR: 0 0 20 20 16777215 20 1 QLayout::SetDefaultConstraint 0 0 150 70 0 0 ArrowCursor Focus Profile false background-color: qlineargradient(x1:0, y1:0, x2:0, y2:0.5, stop:0 darkgray, stop:1 black); QFrame::Panel true Qt::AlignCenter 0 0 70 70 Focus Star false background-color: qlineargradient(x1:0, y1:0, x2:0, y2:0.5, stop:0 darkgray, stop:1 black); QFrame::Panel false Qt::AlignCenter false Guide 1 3 3 3 3 1 font-weight:bold; Status: font-weight:bold; Idle Qt::Horizontal QSizePolicy::Minimum 1 20 σRA: 0 0 30 20 25 20 σDEC: 0 0 30 20 25 20 1 150 70 Guide Profile false background-color: qlineargradient(x1:0, y1:0, x2:0, y2:0.5, stop:0 darkgray, stop:1 black); QFrame::Panel true Qt::AlignCenter 70 70 ArrowCursor Guide Star false background-color: qlineargradient(x1:0, y1:0, x2:0, y2:0.5, stop:0 darkgray, stop:1 black); QFrame::Panel false Qt::AlignCenter 0 0 0 50 16777215 16777215 QAbstractScrollArea::AdjustToContents true Qt::Vertical 20 40 Advanced Ekos Options Options... Clear QRoundProgressBar QWidget
QRoundProgressBar.h
1
- toolsWidget + processINDIB + connectB + disconnectB profileCombo addProfileB editProfileB deleteProfileB customDriversB wizardProfileB - processINDIB logsB ekosLiveB optionsB - connectB - disconnectB ekosOptionsB clearB ekosLogOut raOUT - azOUT decOUT + azOUT altOUT + toolsWidget
diff --git a/kstars/ekos/profileeditor.cpp b/kstars/ekos/profileeditor.cpp index c911b975a..f7cb9bf27 100644 --- a/kstars/ekos/profileeditor.cpp +++ b/kstars/ekos/profileeditor.cpp @@ -1,837 +1,842 @@ /* Profile Editor Copyright (C) 2016 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "profileeditor.h" #include "geolocation.h" #include "kstarsdata.h" #include "Options.h" #include "guide/guide.h" #include "indi/driverinfo.h" #include "indi/drivermanager.h" #include "oal/equipmentwriter.h" ProfileEditorUI::ProfileEditorUI(QWidget *p) : QFrame(p) { setupUi(this); } ProfileEditor::ProfileEditor(QWidget *w) : QDialog(w) { setObjectName("profileEditorDialog"); #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif ui = new ProfileEditorUI(this); pi = nullptr; QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setLayout(mainLayout); setWindowTitle(i18n("Profile Editor")); // Create button box and link it to save and reject functions QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Close, this); buttonBox->setObjectName("dialogButtons"); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(saveProfile())); connect(ui->openWebManagerB, &QPushButton::clicked, this, [this]() { QUrl url(QString("http://" + ui->remoteHost->text() + ":8624")); QDesktopServices::openUrl(url); }); - connect(ui->INDIWebManagerCheck, SIGNAL(toggled(bool)), ui->openWebManagerB, SLOT(setEnabled(bool))); + connect(ui->INDIWebManagerCheck, &QCheckBox::toggled, [&](bool enabled) { + ui->openWebManagerB->setEnabled(enabled); + ui->remoteDrivers->setEnabled(enabled || ui->localMode->isChecked()); + }); connect(ui->guideTypeCombo, SIGNAL(activated(int)), this, SLOT(updateGuiderSelection(int))); connect(ui->addScopeB, &QPushButton::clicked, this, [this]() { QPointer equipmentdlg = new EquipmentWriter(); equipmentdlg->loadEquipment(); equipmentdlg->exec(); delete equipmentdlg; loadScopeEquipment(); }); #ifdef Q_OS_WIN ui->remoteMode->setChecked(true); ui->localMode->setEnabled(false); setRemoteMode(true); #else connect(ui->remoteMode, SIGNAL(toggled(bool)), this, SLOT(setRemoteMode(bool))); #endif // Load all drivers loadDrivers(); // Load scope equipment loadScopeEquipment(); } void ProfileEditor::loadScopeEquipment() { // Get all OAL equipment filter list KStarsData::Instance()->userdb()->GetAllScopes(m_scopeList); ui->primaryScopeCombo->clear(); ui->guideScopeCombo->clear(); ui->primaryScopeCombo->addItem(i18n("Default")); ui->primaryScopeCombo->setItemData(0, i18n("Use scope data from INDI"), Qt::ToolTipRole); ui->guideScopeCombo->addItem(i18n("Default")); ui->guideScopeCombo->setItemData(0, i18n("Use scope data from INDI"), Qt::ToolTipRole); int primaryScopeIndex=0; int guideScopeIndex=0; for (int i=0; i < m_scopeList.count(); i++) { OAL::Scope *oneScope = m_scopeList[i]; ui->primaryScopeCombo->addItem(oneScope->name()); if (pi && oneScope->id().toInt() == pi->primaryscope) primaryScopeIndex = i+1; ui->guideScopeCombo->addItem(oneScope->name()); if (pi && oneScope->id().toInt() == pi->guidescope) guideScopeIndex = i+1; double FocalLength = oneScope->focalLength(); double Aperture = oneScope->aperture(); ui->primaryScopeCombo->setItemData(i+1, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(FocalLength / Aperture, 'f', 1), QString::number(FocalLength, 'f', 2), QString::number(Aperture, 'f', 2)), Qt::ToolTipRole); ui->guideScopeCombo->setItemData(i+1, i18nc("F-Number, Focal Length, Aperture", "F%1 Focal Length: %2 mm Aperture: %3 mm2", QString::number(FocalLength / Aperture, 'f', 1), QString::number(FocalLength, 'f', 2), QString::number(Aperture, 'f', 2)), Qt::ToolTipRole); } ui->primaryScopeCombo->setCurrentIndex(primaryScopeIndex); ui->guideScopeCombo->setCurrentIndex(guideScopeIndex); } void ProfileEditor::saveProfile() { bool newProfile = (pi == nullptr); if (ui->profileIN->text().isEmpty()) { KMessageBox::error(this, i18n("Cannot save an empty profile!")); return; } if (newProfile) { int id = KStarsData::Instance()->userdb()->AddProfile(ui->profileIN->text()); pi = new ProfileInfo(id, ui->profileIN->text()); } else pi->name = ui->profileIN->text(); // Local Mode if (ui->localMode->isChecked()) { pi->host.clear(); pi->port = -1; pi->INDIWebManagerPort = -1; //pi->customDrivers = ui->customDriversIN->text(); } // Remote Mode else { pi->host = ui->remoteHost->text().trimmed(); pi->port = ui->remotePort->text().toInt(); if (ui->INDIWebManagerCheck->isChecked()) pi->INDIWebManagerPort = ui->INDIWebManagerPort->text().toInt(); else pi->INDIWebManagerPort = -1; //pi->customDrivers.clear(); } // City Info if (ui->loadSiteCheck->isEnabled() && ui->loadSiteCheck->isChecked()) { pi->city = KStarsData::Instance()->geo()->name(); pi->province = KStarsData::Instance()->geo()->province(); pi->country = KStarsData::Instance()->geo()->country(); } else { pi->city.clear(); pi->province.clear(); pi->country.clear(); } // Auto Connect pi->autoConnect = ui->autoConnectCheck->isChecked(); // Guider Type pi->guidertype = ui->guideTypeCombo->currentIndex(); if (pi->guidertype != Ekos::Guide::GUIDE_INTERNAL) { pi->guiderhost = ui->externalGuideHost->text(); pi->guiderport = ui->externalGuidePort->text().toInt(); if (pi->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(pi->guiderhost); Options::setPHD2Port(pi->guiderport); } else if (pi->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(pi->guiderhost); Options::setLinGuiderPort(pi->guiderport); } } // Scope list pi->primaryscope=0; pi->guidescope=0; QString selectedScope = ui->primaryScopeCombo->currentText(); QString selectedGuide = ui->guideScopeCombo->currentText(); foreach(OAL::Scope *oneScope, m_scopeList) { if (selectedScope == oneScope->name()) pi->primaryscope = oneScope->id().toInt(); if (selectedGuide == oneScope->name()) pi->guidescope = oneScope->id().toInt(); } if (ui->mountCombo->currentText().isEmpty() || ui->mountCombo->currentText() == "--") pi->drivers.remove("Mount"); else pi->drivers["Mount"] = ui->mountCombo->currentText(); if (ui->ccdCombo->currentText().isEmpty() || ui->ccdCombo->currentText() == "--") pi->drivers.remove("CCD"); else pi->drivers["CCD"] = ui->ccdCombo->currentText(); if (ui->guiderCombo->currentText().isEmpty() || ui->guiderCombo->currentText() == "--") pi->drivers.remove("Guider"); else pi->drivers["Guider"] = ui->guiderCombo->currentText(); if (ui->focuserCombo->currentText().isEmpty() || ui->focuserCombo->currentText() == "--") pi->drivers.remove("Focuser"); else pi->drivers["Focuser"] = ui->focuserCombo->currentText(); if (ui->filterCombo->currentText().isEmpty() || ui->filterCombo->currentText() == "--") pi->drivers.remove("Filter"); else pi->drivers["Filter"] = ui->filterCombo->currentText(); if (ui->AOCombo->currentText().isEmpty() || ui->AOCombo->currentText() == "--") pi->drivers.remove("AO"); else pi->drivers["AO"] = ui->AOCombo->currentText(); if (ui->domeCombo->currentText().isEmpty() || ui->domeCombo->currentText() == "--") pi->drivers.remove("Dome"); else pi->drivers["Dome"] = ui->domeCombo->currentText(); if (ui->weatherCombo->currentText().isEmpty() || ui->weatherCombo->currentText() == "--") pi->drivers.remove("Weather"); else pi->drivers["Weather"] = ui->weatherCombo->currentText(); if (ui->aux1Combo->currentText().isEmpty() || ui->aux1Combo->currentText() == "--") pi->drivers.remove("Aux1"); else pi->drivers["Aux1"] = ui->aux1Combo->currentText(); if (ui->aux2Combo->currentText().isEmpty() || ui->aux2Combo->currentText() == "--") pi->drivers.remove("Aux2"); else pi->drivers["Aux2"] = ui->aux2Combo->currentText(); if (ui->aux3Combo->currentText().isEmpty() || ui->aux3Combo->currentText() == "--") pi->drivers.remove("Aux3"); else pi->drivers["Aux3"] = ui->aux3Combo->currentText(); if (ui->aux4Combo->currentText().isEmpty() || ui->aux4Combo->currentText() == "--") pi->drivers.remove("Aux4"); else pi->drivers["Aux4"] = ui->aux4Combo->currentText(); pi->remotedrivers = ui->remoteDrivers->text(); KStarsData::Instance()->userdb()->SaveProfile(pi); // Ekos manager will reload and new profiles will be created if (newProfile) delete (pi); accept(); } void ProfileEditor::setRemoteMode(bool enable) { loadDrivers(); //This is needed to reload the drivers because some may not be available locally ui->remoteHost->setEnabled(enable); ui->remoteHostLabel->setEnabled(enable); ui->remotePort->setEnabled(enable); ui->remotePortLabel->setEnabled(enable); //ui->customLabel->setEnabled(!enable); //ui->customDriversIN->setEnabled(!enable); ui->mountCombo->setEditable(enable); ui->ccdCombo->setEditable(enable); ui->guiderCombo->setEditable(enable); ui->focuserCombo->setEditable(enable); ui->filterCombo->setEditable(enable); ui->AOCombo->setEditable(enable); ui->domeCombo->setEditable(enable); ui->weatherCombo->setEditable(enable); ui->aux1Combo->setEditable(enable); ui->aux2Combo->setEditable(enable); ui->aux3Combo->setEditable(enable); ui->aux4Combo->setEditable(enable); + ui->remoteDrivers->setEnabled(!enable); + ui->loadSiteCheck->setEnabled(enable); ui->INDIWebManagerCheck->setEnabled(enable); if (enable == false) ui->INDIWebManagerCheck->setChecked(false); ui->INDIWebManagerPort->setEnabled(enable); } void ProfileEditor::setPi(ProfileInfo *value) { pi = value; ui->profileIN->setText(pi->name); ui->loadSiteCheck->setChecked(!pi->city.isEmpty()); ui->autoConnectCheck->setChecked(pi->autoConnect); if (pi->city.isEmpty() == false) { if (pi->province.isEmpty()) ui->loadSiteCheck->setText(ui->loadSiteCheck->text() + QString(" (%1, %2)").arg(pi->country, pi->city)); else ui->loadSiteCheck->setText(ui->loadSiteCheck->text() + QString(" (%1, %2, %3)").arg(pi->country, pi->province, pi->city)); } if (pi->host.isEmpty() == false) { ui->remoteHost->setText(pi->host); ui->remotePort->setText(QString::number(pi->port)); ui->remoteMode->setChecked(true); if (pi->INDIWebManagerPort > 0) { ui->INDIWebManagerCheck->setChecked(true); ui->INDIWebManagerPort->setText(QString::number(pi->INDIWebManagerPort)); } else { ui->INDIWebManagerCheck->setChecked(false); ui->INDIWebManagerPort->setText("8624"); } } if (pi->remotedrivers.isEmpty() == false) ui->remoteDrivers->setText(pi->remotedrivers); ui->guideTypeCombo->setCurrentIndex(pi->guidertype); updateGuiderSelection(ui->guideTypeCombo->currentIndex()); if (pi->guidertype == Ekos::Guide::GUIDE_PHD2) { Options::setPHD2Host(pi->guiderhost); Options::setPHD2Port(pi->guiderport); } else if (pi->guidertype == Ekos::Guide::GUIDE_LINGUIDER) { Options::setLinGuiderHost(pi->guiderhost); Options::setLinGuiderPort(pi->guiderport); } QMapIterator i(pi->drivers); int row = 0; while (i.hasNext()) { i.next(); QString key = i.key(); QString value = i.value(); if (key == "Mount") { // If driver doesn't exist, let's add it to the list if ((row = ui->mountCombo->findText(value)) == -1) { ui->mountCombo->addItem(value); row = ui->mountCombo->count() - 1; } // Set index to our driver ui->mountCombo->setCurrentIndex(row); } else if (key == "CCD") { if ((row = ui->ccdCombo->findText(value)) == -1) { ui->ccdCombo->addItem(value); row = ui->ccdCombo->count() - 1; } ui->ccdCombo->setCurrentIndex(row); } else if (key == "Guider") { if ((row = ui->guiderCombo->findText(value)) == -1) { ui->guiderCombo->addItem(value); row = ui->guiderCombo->count() - 1; } ui->guiderCombo->setCurrentIndex(row); } else if (key == "Focuser") { if ((row = ui->focuserCombo->findText(value)) == -1) { ui->focuserCombo->addItem(value); row = ui->focuserCombo->count() - 1; } ui->focuserCombo->setCurrentIndex(row); } else if (key == "Filter") { if ((row = ui->filterCombo->findText(value)) == -1) { ui->filterCombo->addItem(value); row = ui->filterCombo->count() - 1; } ui->filterCombo->setCurrentIndex(row); } else if (key == "AO") { if ((row = ui->AOCombo->findText(value)) == -1) { ui->AOCombo->addItem(value); row = ui->AOCombo->count() - 1; } ui->AOCombo->setCurrentIndex(row); } else if (key == "Dome") { if ((row = ui->domeCombo->findText(value)) == -1) { ui->domeCombo->addItem(value); row = ui->domeCombo->count() - 1; } ui->domeCombo->setCurrentIndex(row); } else if (key == "Weather") { if ((row = ui->weatherCombo->findText(value)) == -1) { ui->weatherCombo->addItem(value); row = ui->weatherCombo->count() - 1; } ui->weatherCombo->setCurrentIndex(row); } else if (key == "Aux1") { if ((row = ui->aux1Combo->findText(value)) == -1) { ui->aux1Combo->addItem(value); row = ui->aux1Combo->count() - 1; } ui->aux1Combo->setCurrentIndex(row); } else if (key == "Aux2") { if ((row = ui->aux2Combo->findText(value)) == -1) { ui->aux2Combo->addItem(value); row = ui->aux2Combo->count() - 1; } ui->aux2Combo->setCurrentIndex(row); } else if (key == "Aux3") { if ((row = ui->aux3Combo->findText(value)) == -1) { ui->aux3Combo->addItem(value); row = ui->aux3Combo->count() - 1; } ui->aux3Combo->setCurrentIndex(row); } else if (key == "Aux4") { if ((row = ui->aux4Combo->findText(value)) == -1) { ui->aux4Combo->addItem(value); row = ui->aux4Combo->count() - 1; } ui->aux4Combo->setCurrentIndex(row); } } loadScopeEquipment(); } void ProfileEditor::loadDrivers() { QVector boxes; boxes.append(ui->mountCombo); boxes.append(ui->ccdCombo); boxes.append(ui->guiderCombo); boxes.append(ui->AOCombo); boxes.append(ui->focuserCombo); boxes.append(ui->filterCombo); boxes.append(ui->domeCombo); boxes.append(ui->weatherCombo); boxes.append(ui->aux1Combo); boxes.append(ui->aux2Combo); boxes.append(ui->aux3Combo); boxes.append(ui->aux4Combo); QVector selectedItems; foreach (QComboBox *box, boxes) { selectedItems.append(box->currentText()); box->clear(); box->addItem("--"); box->setMaxVisibleItems(20); } QIcon remoteIcon = QIcon::fromTheme("network-modem"); foreach (DriverInfo *dv, DriverManager::Instance()->getDrivers()) { bool locallyAvailable = false; QIcon icon; if (dv->getAuxInfo().contains("LOCALLY_AVAILABLE")) locallyAvailable = dv->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool(); if (!locallyAvailable) { if (ui->localMode->isChecked()) continue; else icon = remoteIcon; } QString toolTipText; if (!locallyAvailable) toolTipText = i18n( "Available as Remote Driver. To use locally, install the corresponding driver."); else toolTipText = i18n("Label: %1 ━ Driver: %2 ━ Exec: %3", dv->getLabel(), dv->getName(), dv->getExecutable()); switch (dv->getType()) { case KSTARS_TELESCOPE: { ui->mountCombo->addItem(icon, dv->getLabel()); ui->mountCombo->setItemData(ui->mountCombo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_CCD: { ui->ccdCombo->addItem(icon, dv->getLabel()); ui->ccdCombo->setItemData(ui->ccdCombo->count() - 1, toolTipText, Qt::ToolTipRole); ui->guiderCombo->addItem(icon, dv->getLabel()); ui->guiderCombo->setItemData(ui->guiderCombo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux1Combo->addItem(icon, dv->getLabel()); ui->aux1Combo->setItemData(ui->aux1Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux2Combo->addItem(icon, dv->getLabel()); ui->aux2Combo->setItemData(ui->aux2Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux3Combo->addItem(icon, dv->getLabel()); ui->aux3Combo->setItemData(ui->aux3Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux4Combo->addItem(icon, dv->getLabel()); ui->aux4Combo->setItemData(ui->aux4Combo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_ADAPTIVE_OPTICS: { ui->AOCombo->addItem(icon, dv->getLabel()); ui->AOCombo->setItemData(ui->AOCombo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_FOCUSER: { ui->focuserCombo->addItem(icon, dv->getLabel()); ui->focuserCombo->setItemData(ui->focuserCombo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux1Combo->addItem(icon, dv->getLabel()); ui->aux1Combo->setItemData(ui->aux1Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux2Combo->addItem(icon, dv->getLabel()); ui->aux2Combo->setItemData(ui->aux2Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux3Combo->addItem(icon, dv->getLabel()); ui->aux3Combo->setItemData(ui->aux3Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux4Combo->addItem(icon, dv->getLabel()); ui->aux4Combo->setItemData(ui->aux4Combo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_FILTER: { ui->filterCombo->addItem(icon, dv->getLabel()); ui->filterCombo->setItemData(ui->filterCombo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux1Combo->addItem(icon, dv->getLabel()); ui->aux1Combo->setItemData(ui->aux1Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux2Combo->addItem(icon, dv->getLabel()); ui->aux2Combo->setItemData(ui->aux2Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux3Combo->addItem(icon, dv->getLabel()); ui->aux3Combo->setItemData(ui->aux3Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux4Combo->addItem(icon, dv->getLabel()); ui->aux4Combo->setItemData(ui->aux4Combo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_DOME: { ui->domeCombo->addItem(icon, dv->getLabel()); ui->domeCombo->setItemData(ui->domeCombo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_WEATHER: { ui->weatherCombo->addItem(icon, dv->getLabel()); ui->weatherCombo->setItemData(ui->weatherCombo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux1Combo->addItem(icon, dv->getLabel()); ui->aux1Combo->setItemData(ui->aux1Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux2Combo->addItem(icon, dv->getLabel()); ui->aux2Combo->setItemData(ui->aux2Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux3Combo->addItem(icon, dv->getLabel()); ui->aux3Combo->setItemData(ui->aux3Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux4Combo->addItem(icon, dv->getLabel()); ui->aux4Combo->setItemData(ui->aux4Combo->count() - 1, toolTipText, Qt::ToolTipRole); } break; case KSTARS_AUXILIARY: case KSTARS_SPECTROGRAPHS: case KSTARS_DETECTORS: { ui->aux1Combo->addItem(icon, dv->getLabel()); ui->aux1Combo->setItemData(ui->aux1Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux2Combo->addItem(icon, dv->getLabel()); ui->aux2Combo->setItemData(ui->aux2Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux3Combo->addItem(icon, dv->getLabel()); ui->aux3Combo->setItemData(ui->aux3Combo->count() - 1, toolTipText, Qt::ToolTipRole); ui->aux4Combo->addItem(icon, dv->getLabel()); ui->aux4Combo->setItemData(ui->aux4Combo->count() - 1, toolTipText, Qt::ToolTipRole); } break; default: continue; } } //ui->mountCombo->setCurrentIndex(-1); for (int i = 0; i < boxes.count(); i++) { QComboBox *box = boxes.at(i); QString selectedItemText = selectedItems.at(i); int index = box->findText(selectedItemText); if (index == -1) { if (ui->localMode->isChecked()) box->setCurrentIndex(0); else box->addItem(remoteIcon, selectedItemText); } else { box->setCurrentIndex(index); } box->model()->sort(0); } } void ProfileEditor::setProfileName(const QString &name) { ui->profileIN->setText(name); } void ProfileEditor::setAuxDrivers(const QStringList &aux) { QStringList auxList(aux); if (auxList.isEmpty()) return; ui->aux1Combo->setCurrentText(auxList.first()); auxList.removeFirst(); if (auxList.isEmpty()) return; ui->aux2Combo->setCurrentText(auxList.first()); auxList.removeFirst(); if (auxList.isEmpty()) return; ui->aux3Combo->setCurrentText(auxList.first()); auxList.removeFirst(); if (auxList.isEmpty()) return; ui->aux4Combo->setCurrentText(auxList.first()); } void ProfileEditor::setHostPort(const QString &host, const QString &port) { ui->remoteMode->setChecked(true); ui->remoteHost->setText(host); ui->remotePort->setText(port); } void ProfileEditor::setWebManager(bool enabled, const QString &port) { ui->INDIWebManagerCheck->setChecked(enabled); ui->INDIWebManagerPort->setText(port); } void ProfileEditor::setGuiderType(int type) { ui->guideTypeCombo->setCurrentIndex(type); if (type != Ekos::Guide::GUIDE_INTERNAL) { ui->externalGuideHostLabel->setEnabled(true); ui->externalGuideHost->setEnabled(true); ui->externalGuidePortLabel->setEnabled(true); ui->externalGuidePort->setEnabled(true); } } void ProfileEditor::setConnectionOptionsEnabled(bool enable) { // Enable or disable connection related options ui->modeLabel->setEnabled(enable); ui->localMode->setEnabled(enable); ui->remoteMode->setEnabled(enable); ui->remoteHostLabel->setEnabled(enable); ui->remoteHost->setEnabled(enable); ui->remotePortLabel->setEnabled(enable); ui->remotePort->setEnabled(enable); ui->INDIWebManagerCheck->setEnabled(enable); ui->INDIWebManagerPort->setEnabled(enable); ui->INDIWebManagerPortLabel->setEnabled(enable); ui->guidingTypeLabel->setEnabled(enable); ui->guideTypeCombo->setEnabled(enable); ui->remoteDrivers->setEnabled(enable); updateGuiderSelection(ui->guideTypeCombo->currentIndex()); if (enable == false) ui->mountCombo->setFocus(); } void ProfileEditor::updateGuiderSelection(int id) { if (id == Ekos::Guide::GUIDE_INTERNAL) { ui->externalGuideHost->setText("localhost"); ui->externalGuidePort->clear(); ui->externalGuideHost->setEnabled(false); ui->externalGuideHostLabel->setEnabled(false); ui->externalGuidePort->setEnabled(false); ui->externalGuidePortLabel->setEnabled(false); return; } QString host; int port = -1; ui->externalGuideHost->setEnabled(true); ui->externalGuideHostLabel->setEnabled(true); ui->externalGuidePort->setEnabled(true); ui->externalGuidePortLabel->setEnabled(true); if (pi && pi->guidertype == id) { host = pi->guiderhost; port = pi->guiderport; } if (id == Ekos::Guide::GUIDE_PHD2) { if (host.isEmpty()) host = Options::pHD2Host(); if (port < 0) port = Options::pHD2Port(); } else if (id == Ekos::Guide::GUIDE_LINGUIDER) { if (host.isEmpty()) host = Options::linGuiderHost(); if (port < 0) port = Options::linGuiderPort(); } ui->externalGuideHost->setText(host); ui->externalGuidePort->setText(QString::number(port)); } diff --git a/kstars/ekos/profileeditor.ui b/kstars/ekos/profileeditor.ui index f452e6639..b0bdf56eb 100644 --- a/kstars/ekos/profileeditor.ui +++ b/kstars/ekos/profileeditor.ui @@ -1,739 +1,739 @@ ProfileEditorUI 0 0 380 370 3 3 3 3 3 Profile 3 3 3 3 3 Name: <html><head/><body><p>After establishing connection with INDI server, automatically connect all devices.</p></body></html> Auto Connect true false Load current site settings when Ekos is online. This option should only be used when connecting to a remote geographic site. Site Info Qt::Horizontal 40 20 false 0 0 INDI Web Manager port 8624 false Port: false Open Web Manager in browser Web Manager .. 20 20 Guiding: false Port: Mode: false Host: false 0 0 false Store profile on remote INDI Web Manager. Use INDI Web Manager on the remote device to start/stop INDI server. INDI Web Manager false 0 0 Remote INDI Server Port 7624 false 0 0 localhost Re&mote false 0 0 localhost false Host: false Port: &Local true Internal PHD2 LinGuider Select Devices 0 0 false Auxliary #4 Aux 4: Focuser: Filter Wheel Filter: Guider: 0 0 false 0 0 false Auxliary #3 Aux 3: 0 0 false 0 0 false 0 0 false Weather Station Weather: 0 0 false Dome: Adaptive Optics AO: 0 0 false Mount: CCD: 0 0 false Auxliary #1 Aux 1: 0 0 false 0 0 false Auxliary #2 Aux 2: 0 0 false - <html><head/><body><p>Specify Remote drivers to chain with. Remote INDI server must be already established. If port is different from default 7624 port, then it must be specified. For example, to connect to ZWO ASI120MC driver running on 192.168.1.50 on port 8000, the connection string is:</p><p><br/></p><p><span style=" font-weight:600;">&quot;ZWO ASI120MC&quot;@192.168.1.50:8000</span></p></body></html> + <html><head/><body><p>Specify Remote drivers to chain with INDI server. Remote INDI drivers must be already running. If port is different from the default (7624), then it must be specified. For example, to connect to ZWO ASI120MC driver running on 192.168.1.50 on port 8000, the connection string is:</p><p><span style=" font-weight:600;">&quot;ZWO ASI120MC&quot;@192.168.1.50:8000</span></p></body></html> Remote: driver1@remotehost:port,driver2@remotehost:port Select Telescopes 3 3 3 3 3 Primary: 0 0 Guide: 0 0 22 22 22 22 .. 22 22 Qt::Vertical 20 0 profileIN localMode remoteMode remoteHost remotePort INDIWebManagerCheck INDIWebManagerPort mountCombo ccdCombo guiderCombo focuserCombo filterCombo AOCombo domeCombo weatherCombo aux1Combo aux2Combo aux3Combo aux4Combo loadSiteCheck diff --git a/kstars/indi/driverinfo.cpp b/kstars/indi/driverinfo.cpp index df11e9b3e..615938875 100644 --- a/kstars/indi/driverinfo.cpp +++ b/kstars/indi/driverinfo.cpp @@ -1,164 +1,166 @@ /* INDI Device Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "driverinfo.h" #include "deviceinfo.h" #include "servermanager.h" #include #include DriverInfo::DriverInfo(const QString &inName) { uniqueLabel.clear(); name = inName; hostname = "localhost"; port = "-1"; userPort = "-1"; } DriverInfo::DriverInfo(DriverInfo *di) { name = di->getName(); - label = di->getLabel(); + label = di->getLabel(); uniqueLabel = di->getUniqueLabel(); - exec = di->getExecutable(); + exec = di->getExecutable(); version = di->getVersion(); userPort = di->getUserPort(); skelFile = di->getSkeletonFile(); port = di->getPort(); hostname = di->getHost(); + remotePort = di->getRemotePort(); + remoteHostname= di->getRemoteHost(); type = di->getType(); serverState = di->getServerState(); clientState = di->getClientState(); driverSource = di->getDriverSource(); serverManager = di->getServerManager(); clientManager = di->getClientManager(); auxInfo = di->getAuxInfo(); devices = di->getDevices(); } DriverInfo *DriverInfo::clone(bool resetClone) { DriverInfo * clone = new DriverInfo(this); if (resetClone) { clone->reset(); clone->resetDevices(); } return clone; } DriverInfo::~DriverInfo() { qDeleteAll(devices); } void DriverInfo::reset() { serverState = false; clientState = false; serverManager = nullptr; clientManager = nullptr; } QString DriverInfo::getServerBuffer() const { if (serverManager != nullptr) return serverManager->getLogBuffer(); return QString(); } void DriverInfo::setServerState(bool inState) { if (inState == serverState) return; serverState = inState; if (serverState == false) serverManager = nullptr; emit deviceStateChanged(this); } void DriverInfo::setClientState(bool inState) { //qDebug() << "Request to change " << name << " client status to " << (inState ? "True" : "False") << endl; if (inState == clientState) return; clientState = inState; if (clientState == false) clientManager = nullptr; //qDebug() << "Client state for this device changed, calling device state changed signal " << endl; emit deviceStateChanged(this); } void DriverInfo::setUserPort(const QString &inUserPort) { if (inUserPort.isEmpty() == false) userPort = inUserPort; else userPort = "-1"; } void DriverInfo::addDevice(DeviceInfo *idv) { devices.append(idv); } void DriverInfo::removeDevice(DeviceInfo *idv) { devices.removeOne(idv); delete (idv); } DeviceInfo *DriverInfo::getDevice(const QString &deviceName) const { foreach (DeviceInfo *idv, devices) { if (idv->getBaseDevice()->getDeviceName() == deviceName) return idv; } return nullptr; } QVariantMap DriverInfo::getAuxInfo() const { return auxInfo; } void DriverInfo::setAuxInfo(const QVariantMap &value) { auxInfo = value; } void DriverInfo::addAuxInfo(const QString &key, const QVariant &value) { auxInfo[key] = value; } void DriverInfo::setUniqueLabel(const QString &inUniqueLabel) { // N.B. We NEVER set unique label for multiple devices per driver "driver" if (auxInfo.value("mdpd", false).toBool() == true || driverSource >= HOST_SOURCE) return; uniqueLabel = inUniqueLabel; } diff --git a/kstars/indi/driverinfo.h b/kstars/indi/driverinfo.h index dcd1eebd1..c08a7808a 100644 --- a/kstars/indi/driverinfo.h +++ b/kstars/indi/driverinfo.h @@ -1,168 +1,177 @@ /* INDI Driver Info Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "indicommon.h" #include #include class ClientManager; class DeviceInfo; class ServerManager; /** * @class DriverInfo * DriverInfo holds all metadata associated with a particular INDI driver. * An INDI drivers holds both static and dynamic information. Example for static information: *
    *
  • Device name.
  • *
  • Device label.
  • *
  • Driver version.
  • *
  • Device Family: Telescope, CCD, Focuser...etc
  • * * Dynamic information include associated Server and Client managers, port in use, associated devices...etc. * Most INDI drivers operate only one device, but some driver can present multiple devices simultaneously. * * Driver information are obtained from multiple sources: *
      *
    1. INDI driver XML file: All INDI driver install an XML file (usually to /usr/share/indi) that contains information * on the driver and device or devices it is capabale of running.
    2. *
    3. Client DriverInfos: Users can add a new host/port combo in the Device Manager in order to connect to * to local or remote INDI servers.
    4. *
    5. Generated DriverInfos: DriverInfo can be created programatically to connect to local or remote INDI server with unknown * number of actual drivers/devices at the server side. *
    * * @author Jasem Mutlaq */ class DriverInfo : public QObject { Q_OBJECT public: explicit DriverInfo(const QString &inName); explicit DriverInfo(DriverInfo *di); ~DriverInfo(); DriverInfo *clone(bool resetClone=true); void reset(); void resetDevices() { devices.clear(); } QString getServerBuffer() const; bool isEmpty() const { return devices.isEmpty(); } // Actual name of the driver // i.e. what getDefaultName() returns const QString &getName() const { return name; } void setName(const QString &newName) { name = newName; } // Driver executable void setExecutable(const QString &newDriver) { exec = newDriver; } const QString &getExecutable() const { return exec; } // Driver Label/Alias. We _rename_ the INDI driver in _runtime_ to the label // Internally INDI server changes the actual driver "name" above to the label value // It's a method of running multiple instances of the same driver with multiple names. void setLabel(const QString &inlabel) { label = inlabel; } const QString &getLabel() const { return label; } void setUniqueLabel(const QString &inUniqueLabel); const QString &getUniqueLabel() const { return uniqueLabel; } void setVersion(const QString &newVersion) { version = newVersion; } const QString &getVersion() const { return version; } void setType(DeviceFamily newType) { type = newType; } DeviceFamily getType() const { return type; } void setDriverSource(DriverSource newDriverSource) { driverSource = newDriverSource; } DriverSource getDriverSource() const { return driverSource; } void setServerManager(ServerManager *newServerManager) { serverManager = newServerManager; } ServerManager *getServerManager() const { return serverManager; } void setClientManager(ClientManager *newClientManager) { clientManager = newClientManager; } ClientManager *getClientManager() const { return clientManager; } void setUserPort(const QString &inUserPort); const QString &getUserPort() const { return userPort; } void setSkeletonFile(const QString &inSkeleton) { skelFile = inSkeleton; } const QString &getSkeletonFile() const { return skelFile; } void setServerState(bool inState); bool getServerState() const { return serverState; } void setClientState(bool inState); bool getClientState() const { return clientState; } void setHostParameters(const QString &inHost, const QString &inPort) { hostname = inHost; port = inPort; } void setPort(const QString &inPort) { port = inPort; } void setHost(const QString &inHost) { hostname = inHost; } const QString &getHost() const { return hostname; } const QString &getPort() const { return port; } + void setRemotePort(const QString &inPort) { remotePort = inPort; } + void setRemoteHost(const QString &inHost) { remoteHostname = inHost; } + const QString &getRemoteHost() const { return remoteHostname; } + const QString &getRemotePort() const { return remotePort; } + //void setBaseDevice(INDI::BaseDevice *idv) { baseDevice = idv;} //INDI::BaseDevice* getBaseDevice() { return baseDevice; } void addDevice(DeviceInfo *idv); void removeDevice(DeviceInfo *idv); DeviceInfo *getDevice(const QString &deviceName) const; QList getDevices() const { return devices; } QVariantMap getAuxInfo() const; void setAuxInfo(const QVariantMap &value); void addAuxInfo(const QString &key, const QVariant &value); private: /// Actual device name as defined by INDI server QString name; /// How it appears in the GUI initially as read from source QString label; /// How it appears in INDI Menu in case tree_label above is taken by another device QString uniqueLabel; /// Exec for the driver QString exec; /// Version of the driver (optional) QString version; /// INDI server port as the user wants it. QString userPort; /// Skeleton file, if any; QString skelFile; /// INDI Host port QString port; /// INDI Host hostname QString hostname; + // INDI Remote Hostname (for remote drivers) + QString remoteHostname; + // INDI remote port (for remote drivers) + QString remotePort; /// Device type (Telescope, CCD..etc), if known (optional) DeviceFamily type { KSTARS_UNKNOWN }; /// Is the driver in the server running? bool serverState { false }; /// Is the client connected to the server running the desired driver? bool clientState { false }; /// How did we read the driver information? From XML file? From 3rd party file? ..etc. DriverSource driverSource { PRIMARY_XML }; /// Who is managing this device? ServerManager *serverManager { nullptr }; /// Any GUI client handling this device? ClientManager *clientManager { nullptr }; /// Any additional properties in key, value pairs QVariantMap auxInfo; QList devices; signals: void deviceStateChanged(DriverInfo *); }; diff --git a/kstars/indi/drivermanager.cpp b/kstars/indi/drivermanager.cpp index b009075c7..e4f9d744c 100644 --- a/kstars/indi/drivermanager.cpp +++ b/kstars/indi/drivermanager.cpp @@ -1,1568 +1,1567 @@ /* INDI Server Manager Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "drivermanager.h" #include "config-kstars.h" #include "clientmanager.h" #include "driverinfo.h" #include "guimanager.h" #include "indilistener.h" #include "kspaths.h" #include "kstars.h" #include "indidbus.h" #include "kstarsdata.h" #include "Options.h" #include "servermanager.h" #include "ui_indihostconf.h" #include "auxiliary/ksnotification.h" #include #ifndef KSTARS_LITE #include #include #include #endif #include #include #define INDI_MAX_TRIES 2 #define ERRMSG_SIZE 1024 DriverManagerUI::DriverManagerUI(QWidget *parent) : QFrame(parent) { setupUi(this); localTreeWidget->setSortingEnabled(false); localTreeWidget->setRootIsDecorated(true); clientTreeWidget->setSortingEnabled(false); runningPix = QIcon::fromTheme("system-run"); stopPix = QIcon::fromTheme("dialog-cancel"); localMode = QIcon::fromTheme("computer"); serverMode = QIcon::fromTheme("network-server"); connected = QIcon::fromTheme("network-connect"); disconnected = QIcon::fromTheme("network-disconnect"); connect(localTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(makePortEditable(QTreeWidgetItem*,int))); } void DriverManagerUI::makePortEditable(QTreeWidgetItem *selectedItem, int column) { // If it's the port column, then make it user-editable if (column == ::DriverManager::LOCAL_PORT_COLUMN) selectedItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled); localTreeWidget->editItem(selectedItem, ::DriverManager::LOCAL_PORT_COLUMN); } DriverManager *DriverManager::_DriverManager = nullptr; DriverManager *DriverManager::Instance() { if (_DriverManager == nullptr) { _DriverManager = new DriverManager(KStars::Instance()); INDIDBus *indiDBUS = new INDIDBus(KStars::Instance()); Q_UNUSED(indiDBUS); } return _DriverManager; } DriverManager::DriverManager(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif currentPort = Options::serverPortStart().toInt() - 1; QVBoxLayout *mainLayout = new QVBoxLayout; ui = new DriverManagerUI(this); mainLayout->addWidget(ui); setLayout(mainLayout); setWindowTitle(i18n("Device Manager")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(ui->addB, SIGNAL(clicked()), this, SLOT(addINDIHost())); connect(ui->modifyB, SIGNAL(clicked()), this, SLOT(modifyINDIHost())); connect(ui->removeB, SIGNAL(clicked()), this, SLOT(removeINDIHost())); connect(ui->connectHostB, SIGNAL(clicked()), this, SLOT(activateHostConnection())); connect(ui->disconnectHostB, SIGNAL(clicked()), this, SLOT(activateHostDisconnection())); connect(ui->runServiceB, SIGNAL(clicked()), this, SLOT(activateRunService())); connect(ui->stopServiceB, SIGNAL(clicked()), this, SLOT(activateStopService())); connect(ui->localTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(updateLocalTab())); connect(ui->clientTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(updateClientTab())); connect(ui->localTreeWidget, SIGNAL(expanded(QModelIndex)), this, SLOT(resizeDeviceColumn())); // Do not use KSPaths here, this is for INDI if (Options::indiDriversDir().isEmpty()) Options::setIndiDriversDir( QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi", QStandardPaths::LocateDirectory)); readXMLDrivers(); readINDIHosts(); m_CustomDrivers = new CustomDrivers(this, driversList); updateCustomDrivers(); #ifdef Q_OS_WIN ui->localTreeWidget->setEnabled(false); #endif } DriverManager::~DriverManager() { clearServers(); qDeleteAll(driversList); } void DriverManager::processDeviceStatus(DriverInfo *dv) { if (dv == nullptr) return; if (dv->getDriverSource() == GENERATED_SOURCE) return; QString currentDriver; ServerMode mode = connectionMode; ServerManager *manager = dv->getServerManager(); bool dState = false; bool cState = false; if (dv->getDriverSource() != HOST_SOURCE) { if (ui->localTreeWidget->currentItem()) currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN); for (auto &item : ui->localTreeWidget->findItems(dv->getLabel(), Qt::MatchExactly | Qt::MatchRecursive)) { item->setText(LOCAL_VERSION_COLUMN, dv->getVersion()); if (manager) mode = manager->getMode(); dState = dv->getServerState(); cState = dv->getClientState() && dState; bool locallyAvailable = false; if (dv->getAuxInfo().contains("LOCALLY_AVAILABLE")) locallyAvailable = dv->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool(); switch (mode) { case SERVER_ONLY: if (locallyAvailable) { ui->runServiceB->setEnabled(!dState); ui->stopServiceB->setEnabled(dState); item->setIcon(LOCAL_STATUS_COLUMN, dState ? ui->runningPix : ui->stopPix); } else { ui->runServiceB->setEnabled(false); ui->stopServiceB->setEnabled(false); } if (dState) { item->setIcon(LOCAL_MODE_COLUMN, ui->serverMode); if (manager) item->setText(LOCAL_PORT_COLUMN, QString(manager->getPort())); } else { item->setIcon(LOCAL_MODE_COLUMN, QIcon()); item->setText(LOCAL_PORT_COLUMN, dv->getUserPort()); } break; case SERVER_CLIENT: if (locallyAvailable) { ui->runServiceB->setEnabled(!cState); ui->stopServiceB->setEnabled(cState); item->setIcon(LOCAL_STATUS_COLUMN, cState ? ui->runningPix : ui->stopPix); } else { ui->runServiceB->setEnabled(false); ui->stopServiceB->setEnabled(false); } if (cState) { item->setIcon(LOCAL_MODE_COLUMN, ui->localMode); if (manager) item->setText(LOCAL_PORT_COLUMN, QString(manager->getPort())); } else { item->setIcon(LOCAL_MODE_COLUMN, QIcon()); item->setText(LOCAL_PORT_COLUMN, dv->getUserPort() == "-1" ? "" : dv->getUserPort()); } break; } // Only update the log if the current driver is selected if (currentDriver == dv->getLabel()) { ui->serverLogText->clear(); ui->serverLogText->append(dv->getServerBuffer()); } } } else { for (auto &item : ui->clientTreeWidget->findItems(dv->getName(), Qt::MatchExactly, HOST_NAME_COLUMN)) { if (dv->getClientState()) { item->setIcon(HOST_STATUS_COLUMN, ui->connected); ui->connectHostB->setEnabled(false); ui->disconnectHostB->setEnabled(true); } else { item->setIcon(HOST_STATUS_COLUMN, ui->disconnected); ui->connectHostB->setEnabled(true); ui->disconnectHostB->setEnabled(false); } } } } void DriverManager::getUniqueHosts(QList &dList, QList> &uHosts) { bool found = false; + // Iterate over all drivers for (DriverInfo *dv : dList) { QList uList; + // Let's see for drivers with idential hosts and ports for (DriverInfo *idv : dList) { - if (dv->getHost() == idv->getHost() && dv->getPort() == idv->getPort()) + // If we get a match between port and hostname, we add it to the list + if ( (dv->getHost() == idv->getHost() && dv->getPort() == idv->getPort())) { // Check if running already if (dv->getClientState() || dv->getServerState()) { int ans = KMessageBox::warningContinueCancel( nullptr, i18n("Driver %1 is already running, do you want to restart it?", dv->getLabel())); if (ans == KMessageBox::Cancel) continue; else { QList stopList; stopList.append(dv); stopDevices(stopList); } } found = false; + // Check to see if the driver already been added elsewhere for (auto &qdi : uHosts) { for (DriverInfo *di : qdi) { if (di == idv) { found = true; break; } } } if (found == false) uList.append(idv); } } if (uList.empty() == false) uHosts.append(uList); } } -bool DriverManager::startDevices(QList &dList, QStringList remoteDrivers) +bool DriverManager::startDevices(QList &dList) { ServerManager *serverManager = nullptr; ClientManager *clientManager = nullptr; int port = -1; QList> uHosts; bool connectionToServer = false; getUniqueHosts(dList, uHosts); qCDebug(KSTARS_INDI) << "INDI: Starting local drivers..."; for (auto &qdv : uHosts) { if (qdv.empty()) continue; port = qdv.at(0)->getPort().toInt(); // Select random port within range is none specified. if (port == -1) port = getINDIPort(port); if (port <= 0) { KSNotification::error(i18n("Cannot start INDI server: port error.")); return false; } serverManager = new ServerManager(qdv.at(0)->getHost(), ((uint)port)); if (serverManager == nullptr) { qWarning() << "Warning: device manager has not been established properly"; return false; } serverManager->setMode(connectionMode); connect(serverManager, SIGNAL(newServerLog()), this, SLOT(updateLocalTab())); if (serverManager->start()) servers.append(serverManager); else { delete serverManager; return false; } qCDebug(KSTARS_INDI) << "INDI: INDI Server started locally on port " << port; for (DriverInfo *dv : qdv) { if (serverManager->startDriver(dv) == false) { servers.removeOne(serverManager); serverManager->stop(); emit serverTerminated(serverManager->getHost(), serverManager->getPort()); delete serverManager; return false; } } - // Only start remote drivers if we are managing a single server - // as we cannot know where the remote drivers are supposed to be chained to - if (remoteDrivers.empty() == false && uHosts.count() == 1) - serverManager->startRemoteDrivers(remoteDrivers); - // Nothing to do more if SERVER ONLY if (connectionMode == SERVER_ONLY) continue; clientManager = new ClientManager(); for (DriverInfo *dv : qdv) clientManager->appendManagedDriver(dv); connect(clientManager, SIGNAL(connectionFailure(ClientManager*)), this, SLOT(processClientTermination(ClientManager*))); clientManager->setServer(qdv.at(0)->getHost().toLatin1().constData(), ((uint)port)); GUIManager::Instance()->addClient(clientManager); INDIListener::Instance()->addClient(clientManager); for (int i = 0; i < INDI_MAX_TRIES; i++) { qCDebug(KSTARS_INDI) << "INDI: Connecting to local INDI server on port " << port << " ..."; connectionToServer = clientManager->connectServer(); if (connectionToServer) break; qApp->processEvents(); //usleep(100000); QThread::usleep(100000); } if (connectionToServer) { qCDebug(KSTARS_INDI) << "Connection to INDI server is successful"; clients.append(clientManager); updateMenuActions(); } else { qCDebug(KSTARS_INDI) << "INDI: Connection to local INDI server on port " << port << " failed!"; KNotification::beep(); QPointer msgBox = new QMessageBox(); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setStandardButtons(QMessageBox::Ok); msgBox->setWindowTitle(i18n("Error")); msgBox->setText(i18n("Connection to INDI server locally on port %1 failed.", port)); msgBox->setModal(false); msgBox->setIcon(QMessageBox::Critical); msgBox->show(); for (DriverInfo *dv : qdv) processDeviceStatus(dv); GUIManager::Instance()->removeClient(clientManager); INDIListener::Instance()->removeClient(clientManager); return false; } } return true; } void DriverManager::stopDevices(const QList &dList) { qCDebug(KSTARS_INDI) << "INDI: Stopping local drivers..."; // #1 Disconnect all clients for (DriverInfo *dv : dList) { ClientManager *cm = dv->getClientManager(); if (cm == nullptr) continue; cm->removeManagedDriver(dv); if (cm->count() == 0) { GUIManager::Instance()->removeClient(cm); INDIListener::Instance()->removeClient(cm); cm->disconnectServer(); clients.removeOne(cm); cm->deleteLater(); KStars::Instance()->slotSetTelescopeEnabled(false); KStars::Instance()->slotSetDomeEnabled(false); } } // #2 Disconnect all servers for (DriverInfo *dv : dList) { ServerManager *sm = dv->getServerManager(); if (sm != nullptr) { sm->stopDriver(dv); if (sm->size() == 0) { sm->stop(); servers.removeOne(sm); sm->deleteLater(); } } } // Reset current port currentPort = Options::serverPortStart().toInt() - 1; updateMenuActions(); } void DriverManager::clearServers() { foreach (ServerManager *serverManager, servers) serverManager->terminate(); qDeleteAll(servers); } void DriverManager::activateRunService() { processLocalTree(true); } void DriverManager::activateStopService() { processLocalTree(false); } void DriverManager::activateHostConnection() { processRemoteTree(true); } void DriverManager::activateHostDisconnection() { processRemoteTree(false); } ClientManager *DriverManager::getClientManager(DriverInfo *dv) { return dv->getClientManager(); } void DriverManager::updateLocalTab() { if (ui->localTreeWidget->currentItem() == nullptr) return; QString currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN); foreach (DriverInfo *device, driversList) { if (currentDriver == device->getLabel()) { processDeviceStatus(device); break; } } } void DriverManager::updateClientTab() { QTreeWidgetItem *item = ui->clientTreeWidget->currentItem(); if (item == nullptr) return; QString hostname = item->text(HOST_NAME_COLUMN); QString hostport = item->text(HOST_PORT_COLUMN); for (auto &dv : driversList) { if (hostname == dv->getName() && hostport == dv->getPort()) { processDeviceStatus(dv); break; } } } void DriverManager::processLocalTree(bool dState) { QList processed_devices; int port = -1; bool portOK = false; connectionMode = ui->localR->isChecked() ? SERVER_CLIENT : SERVER_ONLY; foreach (QTreeWidgetItem *item, ui->localTreeWidget->selectedItems()) { foreach (DriverInfo *device, driversList) { port = -1; //device->state = (dev_request == DriverInfo::DEV_TERMINATE) ? DriverInfo::DEV_START : DriverInfo::DEV_TERMINATE; if (item->text(LOCAL_NAME_COLUMN) == device->getLabel() && device->getServerState() != dState) { processed_devices.append(device); // N.B. If multiple devices are selected to run under one device manager // then we select the port for the first device that has a valid port // entry, the rest are ignored. if (port == -1 && item->text(LOCAL_PORT_COLUMN).isEmpty() == false) { port = item->text(LOCAL_PORT_COLUMN).toInt(&portOK); // If we encounter conversion error, we abort if (portOK == false) { KSNotification::error(i18n("Invalid port entry: %1", item->text(LOCAL_PORT_COLUMN))); return; } } device->setHostParameters("localhost", QString("%1").arg(port)); } } } if (processed_devices.empty()) return; if (dState) startDevices(processed_devices); else stopDevices(processed_devices); } void DriverManager::processClientTermination(ClientManager *client) { if (client == nullptr) return; ServerManager *manager = client->getServerManager(); if (manager) { servers.removeOne(manager); delete manager; } emit serverTerminated(client->getHost(), QString("%1").arg(client->getPort())); GUIManager::Instance()->removeClient(client); INDIListener::Instance()->removeClient(client); KSNotification::error(i18n("Connection to INDI host at %1 on port %2 lost. Server disconnected.", client->getHost(), client->getPort())); clients.removeOne(client); client->deleteLater(); updateMenuActions(); updateLocalTab(); } void DriverManager::processServerTermination(ServerManager *server) { if (server == nullptr) return; for (auto &dv : driversList) if (dv->getServerManager() == server) { dv->reset(); } if (server->getMode() == SERVER_ONLY) { KSNotification::error(i18n("Connection to INDI host at %1 on port %2 encountered an error: %3.", server->getHost(), server->getPort(), server->errorString())); } emit serverTerminated(server->getHost(), server->getPort()); servers.removeOne(server); server->deleteLater(); updateLocalTab(); } void DriverManager::processRemoteTree(bool dState) { QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem(); if (!currentItem) return; for (auto &dv : driversList) { if (dv->getDriverSource() != HOST_SOURCE) continue; //qDebug() << "Current item port " << currentItem->text(HOST_PORT_COLUMN) << " current dev " << dv->getName() << " -- port " << dv->getPort() << endl; //qDebug() << "dState is : " << (dState ? "True" : "False") << endl; if (currentItem->text(HOST_NAME_COLUMN) == dv->getName() && currentItem->text(HOST_PORT_COLUMN) == dv->getPort()) { // Nothing changed, return if (dv->getClientState() == dState) return; // connect to host if (dState) connectRemoteHost(dv); // Disconnect form host else disconnectRemoteHost(dv); } } } bool DriverManager::connectRemoteHost(DriverInfo *dv) { bool hostPortOk = false; bool connectionToServer = false; ClientManager *clientManager = nullptr; dv->getPort().toInt(&hostPortOk); if (hostPortOk == false) { KSNotification::error(i18n("Invalid host port %1", dv->getPort())); return false; } clientManager = new ClientManager(); clientManager->appendManagedDriver(dv); connect(clientManager, SIGNAL(connectionFailure(ClientManager*)), this, SLOT(processClientTermination(ClientManager*))); clientManager->setServer(dv->getHost().toLatin1().constData(), (uint)(dv->getPort().toInt())); GUIManager::Instance()->addClient(clientManager); INDIListener::Instance()->addClient(clientManager); for (int i = 0; i < INDI_MAX_TRIES; i++) { connectionToServer = clientManager->connectServer(); if (connectionToServer) break; QThread::usleep(100000); } if (connectionToServer) { clients.append(clientManager); updateMenuActions(); KSNotification::event(QLatin1String("ConnectionSuccessful"), i18n("Connected to INDI server"), KSNotification::EVENT_ALERT); } else { GUIManager::Instance()->removeClient(clientManager); INDIListener::Instance()->removeClient(clientManager); KNotification::beep(); QMessageBox *msgBox = new QMessageBox(); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setStandardButtons(QMessageBox::Ok); msgBox->setWindowTitle(i18n("Error")); msgBox->setText( i18n("Connection to INDI server at host %1 with port %2 failed.", dv->getHost(), dv->getPort())); KSNotification::event(QLatin1String("ConnectionFailed"), msgBox->text(), KSNotification::EVENT_ALERT); msgBox->setModal(false); msgBox->setIcon(QMessageBox::Critical); msgBox->show(); processDeviceStatus(dv); return false; } return true; } bool DriverManager::disconnectRemoteHost(DriverInfo *dv) { ClientManager *clientManager = nullptr; clientManager = dv->getClientManager(); if (clientManager) { clientManager->removeManagedDriver(dv); clientManager->disconnectServer(); GUIManager::Instance()->removeClient(clientManager); INDIListener::Instance()->removeClient(clientManager); clients.removeOne(clientManager); delete clientManager; updateMenuActions(); return true; } return false; } void DriverManager::resizeDeviceColumn() { ui->localTreeWidget->resizeColumnToContents(0); } void DriverManager::updateMenuActions() { // We iterate over devices, we enable INDI Control Panel if we have any active device // We enable capture image sequence if we have any imaging device QAction *tmpAction = nullptr; bool activeDevice = false; if (clients.size() > 0) activeDevice = true; tmpAction = KStars::Instance()->actionCollection()->action("indi_cpl"); if (tmpAction != nullptr) { //qDebug() << "indi_cpl action set to active" << endl; tmpAction->setEnabled(activeDevice); } } int DriverManager::getINDIPort(int customPort) { #ifdef Q_OS_WIN qWarning() << "INDI server is currently not supported on Windows."; return -1; #else int lastPort = Options::serverPortEnd().toInt(); ; bool success = false; currentPort++; // recycle if (currentPort > lastPort) currentPort = Options::serverPortStart().toInt(); QTcpServer temp_server; if (customPort != -1) { success = temp_server.listen(QHostAddress::LocalHost, customPort); if (success) { temp_server.close(); return customPort; } else return -1; } for (; currentPort <= lastPort; currentPort++) { success = temp_server.listen(QHostAddress::LocalHost, currentPort); if (success) { temp_server.close(); return currentPort; } } return -1; #endif } bool DriverManager::readINDIHosts() { QString indiFile("indihosts.xml"); //QFile localeFile; QFile file; char errmsg[1024]; char c; LilXML *xmlParser = newLilXML(); XMLEle *root = nullptr; XMLAtt *ap = nullptr; QString hName, hHost, hPort; lastGroup = nullptr; file.setFileName(KSPaths::locate(QStandardPaths::GenericDataLocation, indiFile)); if (file.fileName().isEmpty() || !file.open(QIODevice::ReadOnly)) return false; while (file.getChar(&c)) { root = readXMLEle(xmlParser, c, errmsg); if (root) { // Get host name ap = findXMLAtt(root, "name"); if (!ap) { delLilXML(xmlParser); return false; } hName = QString(valuXMLAtt(ap)); // Get host name ap = findXMLAtt(root, "hostname"); if (!ap) { delLilXML(xmlParser); return false; } hHost = QString(valuXMLAtt(ap)); ap = findXMLAtt(root, "port"); if (!ap) { delLilXML(xmlParser); return false; } hPort = QString(valuXMLAtt(ap)); DriverInfo *dv = new DriverInfo(hName); dv->setHostParameters(hHost, hPort); dv->setDriverSource(HOST_SOURCE); connect(dv, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*))); driversList.append(dv); QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget, lastGroup); lastGroup = item; item->setIcon(HOST_STATUS_COLUMN, ui->disconnected); item->setText(HOST_NAME_COLUMN, hName); item->setText(HOST_PORT_COLUMN, hPort); delXMLEle(root); } else if (errmsg[0]) { qDebug() << errmsg; delLilXML(xmlParser); return false; } } delLilXML(xmlParser); return true; } bool DriverManager::readXMLDrivers() { QDir indiDir; QString driverName; // This is the XML file shipped with KStars that contains all supported INDI drivers. /*QString indiDriversXML = KSPaths::locate(QStandardPaths::GenericDataLocation, "indidrivers.xml"); if (indiDriversXML.isEmpty() == false) processXMLDriver(indiDriversXML); */ processXMLDriver(QLatin1Literal(":/indidrivers.xml")); QString driversDir = Options::indiDriversDir(); #ifdef Q_OS_OSX if (Options::indiDriversAreInternal()) driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport"; #endif if (indiDir.cd(driversDir) == false) { KSNotification::error(i18n("Unable to find INDI Drivers directory: %1\nPlease make sure to set the correct " "path in KStars configuration", driversDir)); return false; } indiDir.setNameFilters(QStringList("*.xml")); indiDir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = indiDir.entryInfoList(); for (auto &fileInfo : list) { // libindi 0.7.1: Skip skeleton files if (fileInfo.fileName().endsWith(QLatin1String("_sk.xml"))) continue; if (fileInfo.fileName() == "drivers.xml") { // Let first attempt to load the local version of drivers.xml driverName = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "drivers.xml"; // If found, we continue, otherwise, we load the system file if (driverName.isEmpty() == false && QFile(driverName).exists()) { processXMLDriver(driverName); continue; } } driverName = QString("%1/%2").arg(driversDir, fileInfo.fileName()); processXMLDriver(driverName); } return true; } void DriverManager::processXMLDriver(const QString &driverName) { QFile file(driverName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { KSNotification::error(i18n("Failed to open INDI Driver file: %1", driverName)); return; } char errmsg[ERRMSG_SIZE]; char c; LilXML *xmlParser = newLilXML(); XMLEle *root = nullptr; XMLEle *ep = nullptr; if (driverName.endsWith(QLatin1String("drivers.xml"))) driverSource = PRIMARY_XML; else driverSource = THIRD_PARTY_XML; while (file.getChar(&c)) { root = readXMLEle(xmlParser, c, errmsg); if (root) { // If the XML file is using the INDI Library v1.3+ format if (!strcmp(tagXMLEle(root), "driversList")) { for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) { if (!buildDeviceGroup(ep, errmsg)) prXMLEle(stderr, ep, 0); } } // If using the older format else { if (!buildDeviceGroup(root, errmsg)) prXMLEle(stderr, root, 0); } delXMLEle(root); } else if (errmsg[0]) { qCDebug(KSTARS_INDI) << QString(errmsg); delLilXML(xmlParser); return; } } delLilXML(xmlParser); } bool DriverManager::buildDeviceGroup(XMLEle *root, char errmsg[]) { XMLAtt *ap; XMLEle *ep; QString groupName; QTreeWidgetItem *group; DeviceFamily groupType = KSTARS_TELESCOPE; // avoid overflow if (strlen(tagXMLEle(root)) > 1024) return false; // Get device grouping name ap = findXMLAtt(root, "group"); if (!ap) { snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a group attribute", tagXMLEle(root)); return false; } groupName = valuXMLAtt(ap); groupType = DeviceFamilyLabels.key(groupName); #ifndef HAVE_CFITSIO // We do not create these groups if we don't have CFITSIO support if (groupType == KSTARS_CCD || groupType == KSTARS_VIDEO) return true; #endif // Find if the group already exists QList treeList = ui->localTreeWidget->findItems(groupName, Qt::MatchExactly); if (!treeList.isEmpty()) group = treeList[0]; else group = new QTreeWidgetItem(ui->localTreeWidget, lastGroup); group->setText(0, groupName); lastGroup = group; for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0)) { if (!buildDriverElement(ep, group, groupType, errmsg)) return false; } return true; } bool DriverManager::buildDriverElement(XMLEle *root, QTreeWidgetItem *DGroup, DeviceFamily groupType, char errmsg[]) { XMLAtt *ap; XMLEle *el; DriverInfo *dv; QString label; QString driver; QString version; QString name; QString port; QString skel; QVariantMap vMap; double focal_length(-1), aperture(-1); ap = findXMLAtt(root, "label"); if (!ap) { snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a label attribute", tagXMLEle(root)); return false; } label = valuXMLAtt(ap); // Label is unique, so if we have the same label, we simply ignore if (findDriverByLabel(label) != nullptr) return true; // Search for optional port attribute ap = findXMLAtt(root, "port"); if (ap) port = valuXMLAtt(ap); // Search for skel file, if any // Search for optional port attribute ap = findXMLAtt(root, "skel"); if (ap) skel = valuXMLAtt(ap); // Let's look for telescope-specific attributes: focal length and aperture ap = findXMLAtt(root, "focal_length"); if (ap) { focal_length = QString(valuXMLAtt(ap)).toDouble(); if (focal_length > 0) vMap.insert("TELESCOPE_FOCAL_LENGTH", focal_length); } // Find MDPD: Multiple Devices Per Driver ap = findXMLAtt(root, "mdpd"); if (ap) { bool mdpd = false; mdpd = (QString(valuXMLAtt(ap)) == QString("true")) ? true : false; vMap.insert("mdpd", mdpd); } ap = findXMLAtt(root, "aperture"); if (ap) { aperture = QString(valuXMLAtt(ap)).toDouble(); if (aperture > 0) vMap.insert("TELESCOPE_APERTURE", aperture); } el = findXMLEle(root, "driver"); if (!el) return false; driver = pcdataXMLEle(el); ap = findXMLAtt(el, "name"); if (!ap) { snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a name attribute", tagXMLEle(el)); return false; } name = valuXMLAtt(ap); el = findXMLEle(root, "version"); if (!el) return false; version = pcdataXMLEle(el); bool versionOK=false; version.toDouble(&versionOK); if (versionOK == false) version = "1.0"; bool driverIsAvailable = checkDriverAvailability(driver); vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable); QIcon remoteIcon = QIcon::fromTheme("network-modem"); QTreeWidgetItem *device = new QTreeWidgetItem(DGroup); device->setText(LOCAL_NAME_COLUMN, label); if (driverIsAvailable) device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix); else device->setIcon(LOCAL_STATUS_COLUMN, remoteIcon); device->setText(LOCAL_VERSION_COLUMN, version); device->setText(LOCAL_PORT_COLUMN, port); //if ((driverSource == PRIMARY_XML) && driversStringList.contains(driver) == false) if (groupType == KSTARS_TELESCOPE && driversStringList.contains(driver) == false) driversStringList.append(driver); dv = new DriverInfo(name); dv->setLabel(label); dv->setVersion(version); dv->setExecutable(driver); dv->setSkeletonFile(skel); dv->setType(groupType); dv->setDriverSource(driverSource); dv->setUserPort(port); dv->setAuxInfo(vMap); connect(dv, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*))); driversList.append(dv); return true; } bool DriverManager::checkDriverAvailability(const QString &driver) { QString indiServerDir = Options::indiServer(); if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); QFile driverFile(indiServerDir + '/' + driver); return driverFile.exists(); } void DriverManager::updateCustomDrivers() { for (const QVariantMap & oneDriver : m_CustomDrivers->customDrivers()) { DriverInfo *dv = new DriverInfo(oneDriver["Name"].toString()); dv->setLabel(oneDriver["Label"].toString()); dv->setUniqueLabel(dv->getLabel()); dv->setExecutable(oneDriver["Exec"].toString()); dv->setVersion(oneDriver["Version"].toString()); dv->setType(DeviceFamilyLabels.key(oneDriver["Family"].toString())); dv->setDriverSource(CUSTOM_SOURCE); bool driverIsAvailable = checkDriverAvailability(oneDriver["Exec"].toString()); QVariantMap vMap; vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable); dv->setAuxInfo(vMap); driversList.append(dv); } } // JM 2018-07-23: Disabling the old custom drivers method #if 0 void DriverManager::updateCustomDrivers() { QString label; QString driver; QString version; QString name; QTreeWidgetItem *group = nullptr; QTreeWidgetItem *widgetDev = nullptr; QVariantMap vMap; DriverInfo *drv = nullptr; // Find if the group already exists QList treeList = ui->localTreeWidget->findItems("Telescopes", Qt::MatchExactly); if (!treeList.isEmpty()) group = treeList[0]; else return; KStarsData::Instance()->logObject()->readAll(); // Find custom telescope to ADD/UPDATE foreach (OAL::Scope *s, *(KStarsData::Instance()->logObject()->scopeList())) { name = label = s->name(); if (s->driver() == i18n("None")) continue; // If driver already exists, just update values if ((drv = findDriverByLabel(label))) { if (s->aperture() > 0 && s->focalLength() > 0) { vMap.insert("TELESCOPE_APERTURE", s->aperture()); vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength()); drv->setAuxInfo(vMap); } drv->setExecutable(s->driver()); continue; } driver = s->driver(); version = QString("1.0"); QTreeWidgetItem *device = new QTreeWidgetItem(group); device->setText(LOCAL_NAME_COLUMN, QString(label)); device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix); device->setText(LOCAL_VERSION_COLUMN, QString(version)); DriverInfo *dv = new DriverInfo(name); dv->setLabel(label); dv->setExecutable(driver); dv->setVersion(version); dv->setType(KSTARS_TELESCOPE); dv->setDriverSource(EM_XML); if (s->aperture() > 0 && s->focalLength() > 0) { vMap.insert("TELESCOPE_APERTURE", s->aperture()); vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength()); dv->setAuxInfo(vMap); } connect(dv, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*))); driversList.append(dv); } // Find custom telescope to REMOVE foreach (DriverInfo *dev, driversList) { // If it's from primary xml file or it is in a running state, continue. if (dev->getDriverSource() != EM_XML || dev->getClientState()) continue; if (KStarsData::Instance()->logObject()->findScopeByName(dev->getName())) continue; // Find if the group already exists QList devList = ui->localTreeWidget->findItems(dev->getLabel(), Qt::MatchExactly | Qt::MatchRecursive); if (!devList.isEmpty()) { widgetDev = devList[0]; group->removeChild(widgetDev); } else return; driversList.removeOne(dev); delete (dev); } } #endif void DriverManager::addINDIHost() { QDialog hostConfDialog; Ui::INDIHostConf hostConf; hostConf.setupUi(&hostConfDialog); hostConfDialog.setWindowTitle(i18n("Add Host")); bool portOk = false; if (hostConfDialog.exec() == QDialog::Accepted) { DriverInfo *hostItem = new DriverInfo(hostConf.nameIN->text()); hostConf.portnumber->text().toInt(&portOk); if (portOk == false) { KSNotification::error(i18n("Error: the port number is invalid.")); delete hostItem; return; } hostItem->setHostParameters(hostConf.hostname->text(), hostConf.portnumber->text()); //search for duplicates //for (uint i=0; i < ksw->data()->INDIHostsList.count(); i++) foreach (DriverInfo *host, driversList) if (hostItem->getName() == host->getName() && hostItem->getPort() == host->getPort()) { KSNotification::error( i18n("Host: %1 Port: %2 already exists.", hostItem->getName(), hostItem->getPort())); delete hostItem; return; } hostItem->setDriverSource(HOST_SOURCE); connect(hostItem, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*))); driversList.append(hostItem); QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget); item->setIcon(HOST_STATUS_COLUMN, ui->disconnected); item->setText(HOST_NAME_COLUMN, hostConf.nameIN->text()); item->setText(HOST_PORT_COLUMN, hostConf.portnumber->text()); } saveHosts(); } void DriverManager::modifyINDIHost() { QDialog hostConfDialog; Ui::INDIHostConf hostConf; hostConf.setupUi(&hostConfDialog); hostConfDialog.setWindowTitle(i18n("Modify Host")); QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem(); if (currentItem == nullptr) return; foreach (DriverInfo *host, driversList) { if (currentItem->text(HOST_NAME_COLUMN) == host->getName() && currentItem->text(HOST_PORT_COLUMN) == host->getPort()) { hostConf.nameIN->setText(host->getName()); hostConf.hostname->setText(host->getHost()); hostConf.portnumber->setText(host->getPort()); if (hostConfDialog.exec() == QDialog::Accepted) { //INDIHostsInfo *hostItem = new INDIHostsInfo; host->setName(hostConf.nameIN->text()); host->setHostParameters(hostConf.hostname->text(), hostConf.portnumber->text()); currentItem->setText(HOST_NAME_COLUMN, hostConf.nameIN->text()); currentItem->setText(HOST_PORT_COLUMN, hostConf.portnumber->text()); //ksw->data()->INDIHostsList.replace(i, hostItem); saveHosts(); return; } } } } void DriverManager::removeINDIHost() { if (ui->clientTreeWidget->currentItem() == nullptr) return; foreach (DriverInfo *host, driversList) if (ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN) == host->getName() && ui->clientTreeWidget->currentItem()->text(HOST_PORT_COLUMN) == host->getPort()) { if (host->getClientState()) { KSNotification::error(i18n("You need to disconnect the client before removing it.")); return; } if (KMessageBox::warningContinueCancel(nullptr, i18n("Are you sure you want to remove the %1 client?", ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN)), i18n("Delete Confirmation"), KStandardGuiItem::del()) != KMessageBox::Continue) return; driversList.removeOne(host); delete host; delete (ui->clientTreeWidget->currentItem()); break; } saveHosts(); } void DriverManager::saveHosts() { QFile file; QString hostData; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "indihosts.xml"); //determine filename in local user KDE directory tree. if (!file.open(QIODevice::WriteOnly)) { KSNotification::sorry( i18n( "Unable to write to file 'indihosts.xml'\nAny changes to INDI hosts configurations will not be saved."), i18n("Could Not Open File")); return; } QTextStream outstream(&file); //for (uint i= 0; i < ksw->data()->INDIHostsList.count(); i++) foreach (DriverInfo *host, driversList) { if (host->getDriverSource() != HOST_SOURCE) continue; hostData = "\n"; outstream << hostData; } file.close(); } DriverInfo *DriverManager::findDriverByName(const QString &name) { for (auto &dv : driversList) { if (dv->getName() == name) return dv; } return nullptr; } DriverInfo *DriverManager::findDriverByLabel(const QString &label) { for (auto &dv : driversList) { if (dv->getLabel() == label) return dv; } return nullptr; } DriverInfo *DriverManager::findDriverByExec(const QString &exec) { for (auto &dv : driversList) { if (dv->getExecutable() == exec) return dv; } return nullptr; } QString DriverManager::getUniqueDeviceLabel(const QString &label) { int nset = 0; QString uniqueLabel = label; for (auto &cm : clients) { auto& devices = cm->getDevices(); for (auto &dv : devices) { if (label == QString(dv->getDeviceName())) nset++; } } if (nset > 0) uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1); return uniqueLabel; } diff --git a/kstars/indi/drivermanager.h b/kstars/indi/drivermanager.h index 20f33a09f..43458da62 100644 --- a/kstars/indi/drivermanager.h +++ b/kstars/indi/drivermanager.h @@ -1,186 +1,194 @@ /* INDI Driver Manager Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "indicommon.h" #include "customdrivers.h" #include "ui_drivermanager.h" #include #include #include #include #include #include class QStringList; class QTreeWidgetItem; class DriverManager; class ServerManager; class ClientManager; class DriverInfo; class DriverManagerUI : public QFrame, public Ui::DriverManager { Q_OBJECT public: explicit DriverManagerUI(QWidget *parent = nullptr); public slots: void makePortEditable(QTreeWidgetItem *selectedItem, int column); public: QIcon runningPix; QIcon stopPix; QIcon connected; QIcon disconnected; QIcon localMode; QIcon serverMode; }; /** * @brief DriverManager is the primary class to handle all operations related to starting and stopping INDI drivers. * * INDI drivers can be local or remote drivers. For remote hosts, driver information is not known and devices are built * as they arrive dynamically. The class parses INDI primary devices XML file (drivers.xml) and any 3rd party INDI Driver * XML file to build a tree of devices grouped by driver family type. * * When starting local drivers, DriverManager also establishes an INDI server with the requested drivers and then connect to * the local server to receive the devices dynamically. * * The class also handles INDI hosts which can be added in order to connect to a local or remote INDI server. * * @author Jasem Mutlaq */ class DriverManager : public QDialog { Q_OBJECT public: static DriverManager *Instance(); enum { LOCAL_NAME_COLUMN = 0, LOCAL_STATUS_COLUMN, LOCAL_MODE_COLUMN, LOCAL_VERSION_COLUMN, LOCAL_PORT_COLUMN }; enum { HOST_STATUS_COLUMN = 0, HOST_NAME_COLUMN, HOST_PORT_COLUMN }; bool readXMLDrivers(); bool readINDIHosts(); void processXMLDriver(const QString &driverName); bool buildDeviceGroup(XMLEle *root, char errmsg[]); bool buildDriverElement(XMLEle *root, QTreeWidgetItem *DGroup, DeviceFamily groupType, char errmsg[]); int getINDIPort(int customPort); bool isDeviceRunning(const QString &deviceLabel); void saveHosts(); void processLocalTree(bool dState); void processRemoteTree(bool dState); DriverInfo *findDriverByName(const QString &name); DriverInfo *findDriverByLabel(const QString &label); DriverInfo *findDriverByExec(const QString &exec); ClientManager *getClientManager(DriverInfo *dv); const QList &getDrivers() const { return driversList; } const QList &getCustomDrivers() const { return m_CustomDrivers->customDrivers(); } const QStringList &getDriversStringList() { return driversStringList; } + /** + * @brief getUniqueHosts Given a list of DriverInfos, extract all the host:port information from all the drivers. + * and then consolidate each groups of drivers that belong to the same server & port to a specific list + * e.g. If we have driver1 (localhost:7624), driver2(192.168.1.90:7624), driver3(localhost:7624) then this would create + * two lists. First list contains [driver1,driver3] and second list contains [driver2] making each list _unique_ in terms of host params. + * @param dList list of driver to examine + * @param uHosts List of unique hosts, each with a group of drivers that belong to it. + */ void getUniqueHosts(QList &dList, QList> &uHosts); void addDriver(DriverInfo *di) { driversList.append(di); } void removeDriver(DriverInfo *di) { driversList.removeOne(di); } - bool startDevices(QList &dList, QStringList remoteDrivers = QStringList()); + bool startDevices(QList &dList); void stopDevices(const QList &dList); void stopAllDevices() { stopDevices(driversList); } bool connectRemoteHost(DriverInfo *dv); bool disconnectRemoteHost(DriverInfo *dv); QString getUniqueDeviceLabel(const QString &label); void clearServers(); private: DriverManager(QWidget *parent); ~DriverManager(); bool checkDriverAvailability(const QString &driver); static DriverManager *_DriverManager; ServerMode connectionMode { SERVER_CLIENT }; QTreeWidgetItem *lastGroup { nullptr }; int currentPort; //DriverInfo::XMLSource xmlSource; DriverSource driverSource; DriverManagerUI *ui { nullptr }; QList driversList; QList servers; QList clients; QStringList driversStringList; QPointer m_CustomDrivers; public slots: //void enableDevice(INDI_D *device); //void disableDevice(INDI_D *device); void resizeDeviceColumn(); void updateLocalTab(); void updateClientTab(); void updateMenuActions(); void addINDIHost(); void modifyINDIHost(); void removeINDIHost(); void activateRunService(); void activateStopService(); void activateHostConnection(); void activateHostDisconnection(); void updateCustomDrivers(); void processClientTermination(ClientManager *client); void processServerTermination(ServerManager *server); void processDeviceStatus(DriverInfo *dv); void showCustomDrivers() { m_CustomDrivers->show(); } signals: void clientTerminated(ClientManager *); void serverTerminated(const QString &host, const QString &port); /* signals: void newDevice(); void newTelescope(); void newCCD(); */ }; diff --git a/kstars/indi/servermanager.cpp b/kstars/indi/servermanager.cpp index ba95cc5a7..b3187b861 100644 --- a/kstars/indi/servermanager.cpp +++ b/kstars/indi/servermanager.cpp @@ -1,353 +1,347 @@ /* INDI Server Manager Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "servermanager.h" #include "driverinfo.h" #include "drivermanager.h" #include "auxiliary/kspaths.h" #include "Options.h" #include #include #include #include #include ServerManager::ServerManager(const QString& inHost, uint inPort) { host = inHost; port = QString::number(inPort); //qDebug() << "We got port unit with value of " << inPort << " and now as tring it is equal to #" << port << "#" << endl; } ServerManager::~ServerManager() { serverSocket.close(); indiFIFO.close(); QFile::remove(indiFIFO.fileName()); if (serverProcess.get() != nullptr) serverProcess->close(); } bool ServerManager::start() { #ifdef Q_OS_WIN qWarning() << "INDI server is currently not supported on Windows."; return false; #else bool connected = false; int fd = 0; if (serverProcess.get() == nullptr) { serverBuffer.open(); serverProcess.reset(new QProcess(this)); #ifdef Q_OS_OSX QString driversDir = Options::indiDriversDir(); if (Options::indiDriversAreInternal()) driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport"; QString indiServerDir = Options::indiServer(); if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("PATH", driversDir + ':' + indiServerDir + ":/usr/local/bin:/usr/bin:/bin"); QString gscDirPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "gsc"; env.insert("GSCDAT", gscDirPath); insertEnvironmentPath(&env, "INDIPREFIX", "/../../"); insertEnvironmentPath(&env, "IOLIBS", "/../Resources/DriverSupport/gphoto/IOLIBS"); insertEnvironmentPath(&env, "CAMLIBS", "/../Resources/DriverSupport/gphoto/CAMLIBS"); serverProcess->setProcessEnvironment(env); #endif } QStringList args; args << "-v" << "-p" << port; QString fifoFile = QString("/tmp/indififo%1").arg(QUuid::createUuid().toString().mid(1, 8)); if ((fd = mkfifo(fifoFile.toLatin1(), S_IRUSR | S_IWUSR) < 0)) { KMessageBox::error(nullptr, i18n("Error making FIFO file %1: %2.", fifoFile, strerror(errno))); return false; } indiFIFO.setFileName(fifoFile); driverCrashed = false; if (!indiFIFO.open(QIODevice::ReadWrite | QIODevice::Text)) { qCCritical(KSTARS_INDI) << "Unable to create INDI FIFO file: " << fifoFile << endl; return false; } args << "-f" << fifoFile; qCDebug(KSTARS_INDI) << "Starting INDI Server: " << args << "-f" << fifoFile; serverProcess->setProcessChannelMode(QProcess::SeparateChannels); serverProcess->setReadChannel(QProcess::StandardError); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) serverProcess->start(QCoreApplication::applicationDirPath() + "/indi/indiserver", args); else #endif serverProcess->start(Options::indiServer(), args); connected = serverProcess->waitForStarted(); if (connected) { connect(serverProcess.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(processServerError(QProcess::ProcessError))); connect(serverProcess.get(), SIGNAL(readyReadStandardError()), this, SLOT(processStandardError())); emit started(); } else KMessageBox::error(nullptr, i18n("INDI server failed to start: %1", serverProcess->errorString())); qCDebug(KSTARS_INDI) << "INDI Server Started? " << connected; return connected; #endif } void ServerManager::insertEnvironmentPath(QProcessEnvironment *env, QString variable, QString relativePath) { QString environmentPath = QCoreApplication::applicationDirPath() + relativePath; if (QFileInfo::exists(environmentPath) && Options::indiDriversAreInternal()) env->insert(variable, QDir(environmentPath).absolutePath()); } -bool ServerManager::startRemoteDrivers(QStringList remoteDrivers) -{ - QTextStream out(&indiFIFO); - - for (auto remoteDriver : remoteDrivers) - { - qCDebug(KSTARS_INDI) << "Starting INDI Remote Driver" << remoteDriver; - remoteDriver.replace("\"", "\\\""); - out << "start " << remoteDriver << endl; - } - - out.flush(); - - return true; -} - bool ServerManager::startDriver(DriverInfo *dv) { QTextStream out(&indiFIFO); // Check for duplicates within existing clients if (dv->getUniqueLabel().isEmpty() && dv->getLabel().isEmpty() == false) dv->setUniqueLabel(DriverManager::Instance()->getUniqueDeviceLabel(dv->getLabel())); // Check for duplicates within managed drivers if (dv->getUniqueLabel().isEmpty() == false) { int nset = 0; QString uniqueLabel; QString label = dv->getUniqueLabel(); foreach (DriverInfo *drv, managedDrivers) { if (label == drv->getUniqueLabel()) nset++; } if (nset > 0) { uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1); dv->setUniqueLabel(uniqueLabel); } } managedDrivers.append(dv); dv->setServerManager(this); QString driversDir = Options::indiDriversDir(); QString indiServerDir = Options::indiServer(); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; if (Options::indiDriversAreInternal()) driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); #endif - QStringList paths; - paths << "/usr/bin" - << "/usr/local/bin" << driversDir << indiServerDir; - - if (QStandardPaths::findExecutable(dv->getExecutable()).isEmpty()) + if (dv->getRemoteHost().isEmpty() == false) + { + QString driverString = dv->getName() + "@" + dv->getRemoteHost() + ":" + dv->getRemotePort(); + qCDebug(KSTARS_INDI) << "Starting Remote INDI Driver" << driverString; + out << "start " << driverString << endl; + out.flush(); + } + else { - if (QStandardPaths::findExecutable(dv->getExecutable(), paths).isEmpty()) + QStringList paths; + paths << "/usr/bin" + << "/usr/local/bin" << driversDir << indiServerDir; + + if (QStandardPaths::findExecutable(dv->getExecutable()).isEmpty()) { - KMessageBox::error(nullptr, i18n("Driver %1 was not found on the system. Please make sure the package that " - "provides the '%1' binary is installed.", - dv->getExecutable())); - return false; + if (QStandardPaths::findExecutable(dv->getExecutable(), paths).isEmpty()) + { + KMessageBox::error(nullptr, i18n("Driver %1 was not found on the system. Please make sure the package that " + "provides the '%1' binary is installed.", + dv->getExecutable())); + return false; + } } - } - qCDebug(KSTARS_INDI) << "Starting INDI Driver " << dv->getExecutable(); + qCDebug(KSTARS_INDI) << "Starting INDI Driver " << dv->getExecutable(); - out << "start " << dv->getExecutable(); - if (dv->getUniqueLabel().isEmpty() == false) - out << " -n \"" << dv->getUniqueLabel() << "\""; - if (dv->getSkeletonFile().isEmpty() == false) - out << " -s \"" << driversDir << QDir::separator() << dv->getSkeletonFile() << "\""; - out << endl; + out << "start " << dv->getExecutable(); + if (dv->getUniqueLabel().isEmpty() == false) + out << " -n \"" << dv->getUniqueLabel() << "\""; + if (dv->getSkeletonFile().isEmpty() == false) + out << " -s \"" << driversDir << QDir::separator() << dv->getSkeletonFile() << "\""; + out << endl; - out.flush(); + out.flush(); - dv->setServerState(true); + dv->setServerState(true); - dv->setPort(port); + dv->setPort(port); + } return true; } void ServerManager::stopDriver(DriverInfo *dv) { QTextStream out(&indiFIFO); managedDrivers.removeOne(dv); qCDebug(KSTARS_INDI) << "Stopping INDI Driver " << dv->getExecutable(); if (dv->getUniqueLabel().isEmpty() == false) out << "stop " << dv->getExecutable() << " -n \"" << dv->getUniqueLabel() << "\"" << endl; else out << "stop " << dv->getExecutable() << endl; out.flush(); dv->setServerState(false); dv->setPort(dv->getUserPort()); } void ServerManager::stop() { if (serverProcess.get() == nullptr) return; foreach (DriverInfo *device, managedDrivers) { device->reset(); } qCDebug(KSTARS_INDI) << "Stopping INDI Server " << host << "@" << port; serverProcess->disconnect(SIGNAL(error(QProcess::ProcessError))); serverBuffer.close(); serverProcess->terminate(); serverProcess->waitForFinished(); serverProcess.reset(); } void ServerManager::terminate() { if (serverProcess.get() == nullptr) return; serverProcess->terminate(); serverProcess->waitForFinished(); } void ServerManager::connectionSuccess() { foreach (DriverInfo *device, managedDrivers) device->setServerState(true); connect(serverProcess.get(), SIGNAL(readyReadStandardError()), this, SLOT(processStandardError())); emit started(); } void ServerManager::processServerError(QProcess::ProcessError err) { INDI_UNUSED(err); emit serverFailure(this); } void ServerManager::processStandardError() { #ifdef Q_OS_WIN qWarning() << "INDI server is currently not supported on Windows."; return; #else QString stderr = serverProcess->readAllStandardError(); for (auto &msg : stderr.split('\n')) qCDebug(KSTARS_INDI) << "INDI Server: " << msg; if (driverCrashed == false && (stderr.contains("stdin EOF") || stderr.contains("stderr EOF"))) { QStringList parts = stderr.split("Driver"); for (auto &driver : parts) { if (driver.contains("stdin EOF") || driver.contains("stderr EOF")) { driverCrashed = true; QString driverName = driver.left(driver.indexOf(':')).trimmed(); qCCritical(KSTARS_INDI) << "INDI driver " << driverName << " crashed!"; KMessageBox::information( nullptr, i18n("KStars detected INDI driver %1 crashed. Please check INDI server log in the Device Manager.", driverName)); break; } } } serverBuffer.write(stderr.toLatin1()); emit newServerLog(); #endif } QString ServerManager::errorString() { if (serverProcess.get() != nullptr) return serverProcess->errorString(); return nullptr; } QString ServerManager::getLogBuffer() { serverBuffer.flush(); serverBuffer.close(); serverBuffer.open(); return serverBuffer.readAll(); } diff --git a/kstars/indi/servermanager.h b/kstars/indi/servermanager.h index d1e7358cf..7592ac159 100644 --- a/kstars/indi/servermanager.h +++ b/kstars/indi/servermanager.h @@ -1,85 +1,83 @@ /* INDI Server Manager Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "indicommon.h" #include #include #include #include #include #include class DriverInfo; /** * @class ServerManager * ServerManager is responsible for starting and shutting local INDI servers. * * @author Jasem Mutlaq */ class ServerManager : public QObject { Q_OBJECT public: ServerManager(const QString& inHost, uint inPort); ~ServerManager(); bool start(); void stop(); void terminate(); QString getLogBuffer(); const QString &getHost() { return host; } const QString &getPort() { return port; } bool startDriver(DriverInfo *dv); - void stopDriver(DriverInfo *dv); - - bool startRemoteDrivers(QStringList remoteDrivers); + void stopDriver(DriverInfo *dv); void setMode(ServerMode inMode) { mode = inMode; } ServerMode getMode() { return mode; } QString errorString(); int size() { return managedDrivers.size(); } public slots: void connectionSuccess(); void processServerError(QProcess::ProcessError); void processStandardError(); private: QTcpSocket serverSocket; QString host; QString port; QTemporaryFile serverBuffer; std::unique_ptr serverProcess; void insertEnvironmentPath(QProcessEnvironment *env, QString variable, QString relativePath); ServerMode mode { SERVER_CLIENT }; bool driverCrashed { false }; QList managedDrivers; QFile indiFIFO; signals: void serverFailure(ServerManager *); void newServerLog(); void started(); void finished(int exit_code, QProcess::ExitStatus exit_status); };