diff --git a/kstars/indi/customdrivers.cpp b/kstars/indi/customdrivers.cpp index ee6547203..47ddc0539 100644 --- a/kstars/indi/customdrivers.cpp +++ b/kstars/indi/customdrivers.cpp @@ -1,134 +1,137 @@ /* Driver Alias Copyright (C) 2018 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 "customdrivers.h" #include "Options.h" #include "kstars.h" #include "driverinfo.h" #include "kspaths.h" #include "kstarsdata.h" #include "ksnotification.h" CustomDrivers::CustomDrivers(QWidget *parent, const QList &driversList) : QDialog(parent), m_DriversList(driversList) { setupUi(this); userdb = QSqlDatabase::cloneDatabase(KStarsData::Instance()->userdb()->GetDatabase(), "custom_drivers_db"); userdb.open(); model = new QSqlTableModel(this, userdb); model->setTable("customdrivers"); driversView->setModel(model); driversView->hideColumn(0); refreshFromDB(); tipLabel->setPixmap((QIcon::fromTheme("help-hint").pixmap(32, 32))); + cautionLabel->setPixmap((QIcon::fromTheme("emblem-warning").pixmap(32, 32))); + + familyCombo->addItems(DeviceFamilyLabels.values()); for (const DriverInfo *oneDriver : driversList) { // MDPD drivers CANNOT have aliases. if (oneDriver->getAuxInfo().value("mdpd", false).toBool() == true) continue; QString label = oneDriver->getLabel(); if (label.isEmpty()) continue; driverCombo->addItem(label); } syncDriver(); connect(driverCombo, static_cast(&QComboBox::activated), this, &CustomDrivers::syncDriver); connect(labelIN, &QLineEdit::textChanged, [&]() { addDriverB->setEnabled(labelIN->text().isEmpty() == false); }); connect(driversView, &QTableView::pressed, [&]() { removeDriverB->setEnabled(true); }); connect(addDriverB, &QPushButton::clicked, this, &CustomDrivers::addDriver); connect(removeDriverB, &QPushButton::clicked, this, &CustomDrivers::removeDriver); } CustomDrivers::~CustomDrivers() { userdb.close(); } void CustomDrivers::refreshFromDB() { KStarsData::Instance()->userdb()->GetAllCustomDrivers(m_CustomDrivers); model->select(); } void CustomDrivers::syncDriver() { const QString currentDriverLabel = driverCombo->currentText(); for (const DriverInfo *oneDriver : m_DriversList) { if (currentDriverLabel == oneDriver->getLabel()) { familyCombo->setCurrentIndex(oneDriver->getType()); execIN->setText(oneDriver->getExecutable()); nameIN->setText(oneDriver->getName()); break; } } } void CustomDrivers::addDriver() { // Make sure label is unique in canonical drivers for (const DriverInfo *oneDriver : m_DriversList) { if (labelIN->text() == oneDriver->getLabel()) { KSNotification::error(i18n("Label already exists. Label must be unique.")); return; } } QVariantMap newDriver; newDriver["Label"] = labelIN->text(); newDriver["Name"] = nameIN->text(); newDriver["Family"] = familyCombo->currentText(); newDriver["Exec"] = execIN->text(); // TODO Try to make this editable as well newDriver["Version"] = "1.0"; if (KStarsData::Instance()->userdb()->AddCustomDriver(newDriver) == false) KSNotification::error(i18n("Failed to add new driver. Is the label unique?")); refreshFromDB(); } void CustomDrivers::removeDriver() { int row = driversView->currentIndex().row(); if (row < 0) return; QVariantMap oneDriver = m_CustomDrivers[row]; KStarsData::Instance()->userdb()->DeleteCustomDriver(oneDriver["id"].toString()); refreshFromDB(); } diff --git a/kstars/indi/customdrivers.h b/kstars/indi/customdrivers.h index 4526fd693..9b6045639 100644 --- a/kstars/indi/customdrivers.h +++ b/kstars/indi/customdrivers.h @@ -1,51 +1,52 @@ /* Driver Alias Copyright (C) 2018 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. */ #pragma once #include #include #include #include #include "ui_customdrivers.h" class DriverInfo; /** * @class CustomDrivers * @short Handles adding new drivers to database. This would enable to add arbitrary aliases of existing drivers. * * @author Jasem Mutlaq * @version 1.0 */ class CustomDrivers : public QDialog, public Ui::CustomDrivers { Q_OBJECT public: explicit CustomDrivers(QWidget *parent, const QList &driversList); ~CustomDrivers(); + const QList & customDrivers() const { return m_CustomDrivers; } void refreshFromDB(); protected slots: void syncDriver(); void addDriver(); void removeDriver(); private: QList m_CustomDrivers; const QList &m_DriversList; QSqlDatabase userdb; QPointer model; }; diff --git a/kstars/indi/customdrivers.ui b/kstars/indi/customdrivers.ui index bee4373b5..43b5db15b 100644 --- a/kstars/indi/customdrivers.ui +++ b/kstars/indi/customdrivers.ui @@ -1,317 +1,292 @@ CustomDrivers 0 0 - 534 - 254 + 553 + 306 Custom Drivers 3 3 3 3 3 0 QAbstractItemView::SelectRows 3 3 Driver 0 0 true Family - - - - Adaptive Optics - - - - - Agent - - - - - Auxiliary - - - - - CCDs - - - - - Detectors - - - - - Domes - - - - - Filter Wheels - - - - - Focusers - - - - - Rotators - - - - - Spectrographs - - - - - Telescopes - - - - - Weather - - - + Executable Name 3 Label color:red * 3 false Remove Qt::Horizontal 40 20 false Add New - + 3 32 32 32 32 <html><head/><body><p>To create an <span style=" font-weight:600;">Alias</span> from an existing driver, select an existing driver and then only change the <span style=" font-weight:600;">Label </span>then press<span style=" font-weight:600;"> Add.</span></p></body></html> true + + + + 3 + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + + + + <html><head/><body><p>KStars must be restarted for new drivers to take effect.</p></body></html> + + + true + + + + + Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() CustomDrivers accept() 248 254 157 274 buttonBox rejected() CustomDrivers reject() 316 260 286 274 diff --git a/kstars/indi/driverinfo.cpp b/kstars/indi/driverinfo.cpp index 4514e2071..df11e9b3e 100644 --- a/kstars/indi/driverinfo.cpp +++ b/kstars/indi/driverinfo.cpp @@ -1,164 +1,164 @@ /* 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(); - treeLabel = di->getLabel(); + label = di->getLabel(); uniqueLabel = di->getUniqueLabel(); exec = di->getExecutable(); version = di->getVersion(); userPort = di->getUserPort(); skelFile = di->getSkeletonFile(); port = di->getPort(); hostname = di->getHost(); 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 bbfbc248f..dcd1eebd1 100644 --- a/kstars/indi/driverinfo.h +++ b/kstars/indi/driverinfo.h @@ -1,168 +1,168 @@ /* 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 &inTreeLabel) { treeLabel = inTreeLabel; } - const QString &getLabel() const { return treeLabel; } + 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 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 treeLabel; + 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; /// 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 85c700b37..5c72ddbc4 100644 --- a/kstars/indi/drivermanager.cpp +++ b/kstars/indi/drivermanager.cpp @@ -1,1566 +1,1563 @@ /* 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(); - updateCustomDrivers(); - 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; for (DriverInfo *dv : dList) { QList uList; for (DriverInfo *idv : dList) { if (dv->getHost() == idv->getHost() && dv->getPort() == idv->getPort()) { // Check if running already if (dv->getClientState() || dv->getServerState()) { int ans = KMessageBox::warningContinueCancel( - 0, i18n("Driver %1 is already running, do you want to restart it?", dv->getLabel())); + 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; 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) { 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; } } // 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); - - if (groupName.indexOf("Telescopes") != -1) - groupType = KSTARS_TELESCOPE; - else if (groupName.indexOf("CCDs") != -1) - groupType = KSTARS_CCD; - else if (groupName.indexOf("Filter Wheels") != -1) - groupType = KSTARS_FILTER; - else if (groupName.indexOf("Focusers") != -1) - groupType = KSTARS_FOCUSER; - else if (groupName.indexOf("Adaptive Optics") != -1) - groupType = KSTARS_ADAPTIVE_OPTICS; - else if (groupName.indexOf("Domes") != -1) - groupType = KSTARS_DOME; - else if (groupName.indexOf("Detectors") != -1) - groupType = KSTARS_DETECTORS; - else if (groupName.indexOf("Auxiliary") != -1) - groupType = KSTARS_AUXILIARY; - else if (groupName.indexOf("Spectrographs") != -1) - groupType = KSTARS_SPECTROGRAPHS; - else if (groupName.indexOf("Agent") != -1) - groupType = KSTARS_AGENT; - else if (groupName.indexOf("Rotators") != -1) - groupType = KSTARS_ROTATOR; - else if (groupName.indexOf("Weather") != -1) - groupType = KSTARS_WEATHER; - else - groupType = KSTARS_UNKNOWN; + 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); - - if (vMap.isEmpty() == false) - dv->setAuxInfo(vMap); + 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(0, + 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/indicommon.h b/kstars/indi/indicommon.h index 00bf55e44..6ee70e742 100644 --- a/kstars/indi/indicommon.h +++ b/kstars/indi/indicommon.h @@ -1,191 +1,210 @@ /* INDI Common Defs 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. */ #ifndef INDICOMMON_H #define INDICOMMON_H +#include +#include + /*! \page INDI "INDI Overview" \tableofcontents \section Intro Introduction INDI is a simple XML-like communications protocol described for interactive and automated remote control of diverse instrumentation. INDI is small, easy to parse, and stateless. The main key concept in INDI is that devices have the ability to describe themselves. This is accomplished by using XML to describe a generic hierarchy that can represent both canonical and non-canonical devices. All devices may contain one or more properties. A property in the INDI paradigm describes a specific function of the driver. Any property may contain one or more elements. There are four types of INDI properties:
    • Text property: Property to transfer simple text information in ISO-8859-1. The text is not encoded or encrypted on transfer. If the text includes elements that can break XML syntax, a BLOB property should be used to make the transfer.
    • Number property: Property to transfer numeric information with configurable minimum, maximum, and step values. The supported number formats are decimal and sexigesmal. The property includes a GUI hint element in printf style format to enable clients to properly display numeric properties.
    • Switch property: Property to hold a group of options or selections (Represented in GUI by buttons and check boxes).
    • Light property: Property to hold a group of status indicators (Represented in GUI by colored LEDs).
    • BLOB property: BLOB is a Binary Large OBject used to transfer binary data to and from drivers.
    For example, all INDI devices share the CONNECTION standard switch property. The CONNECTION property has two elements: CONNECT and DISCONNECT switches. GUI clients parse the generic XML description of properties and builds a GUI representation suitable for direct human interaction. KStars is a fully featured INDI client. INDI Data (i.e. devices and properties) and GUI (how they appear on the screen) are separated. INDI adopts is a server/client architecture where one or more servers communicate with one or more drivers, each driver can communicate with one or more actual hardware devices. Clients connect to the server, obtain a list of devices, and generate GUI to represent the devices for user interaction and control. The creation of GUI happens in run time via introspection. \section Structure Hierarchy The following is a description of some of the various classes that support INDI:
    • DriverManager: Manages local drivers as installed by INDI library. Enables users to start/stop local INDI servers (ServerManager) running one or more drivers (DriverInfo). Also enables connecting to local or remote INDI servers. For both local and remote connection, it creates a ClientManager to handle incoming data from each INDI server started, and creates a GUIManager to render the devices in INDI Control Panel. The user may also choose to only start an INDI server without a client manager and GUI.
    • ClientManager: Manages sending and receiving data from and to an INDI server. The ClientManager sends notifications (signals) when a new device or property is defined, updated, or deleted.
    • GUIManager: Handles creation of GUI interface for devices (INDI_D) and their properties and updates the interface in accord with any data emitted by the associated ClientManager. The GUI manager supports multiple ClientManagers and consolidate all devices from all the ClientManagers into a single INDI Control Panel where each device is created as a tab.
    • INDIListener: Once a ClientManager is created in DriverManager after successfully connecting to an INDI server, it is added to INDIListener where it monitors any new devices and if a new device is detected, it creates an ISD::GenericDevice to hold the data of the device. It also moniters new properties and registers them. If it detects an INDI standard property associated with a particular device family (e.g. Property EQUATORIAL_EOD_COORD is a standard property of a Telescope device), then it extends the ISD::GenericDevice to the particular specialized device type using the Decorator Pattern. All updated properties from INDI server are delegated to their respective devices.
    • ServerManager
    • Manages establishment and shutdown of local INDI servers.
    • DriverInfo
    • : Simple class that holds information on INDI drivers such as name, version, device type..etc.
    • ISD::GDInterface: Abstract class where the ISD::DeviceDecorator and ISD::GenericDevice are derived.
    • ISD::DeviceDecorator: Base class of all specialized decorators such as ISD::Telescope, ISD::Filter, and ISD::CCD devices.
    • ISD::GenericDevice: Base class for all INDI devices. It implements processes shared across all INDI devices such as handling connection/disconnection, setting of configuration..etc.. When a specialized decorator is created (e.g. ISD::Telescope), the decorator is passed an ISD::GenericDevice pointer. Since ISD::Telescope is a child of ISD::DeviceDecorator, it can override some or all the device specific functions as defined in ISD::GDInterface (e.g. ProcessNumber(INumberVectorProperty *nvp)). For any function that is not overridden, the parent ISD::DeviceDecorator will invoke the function in ISD::GenericDevice instead. Moreover, The specialized decorator device can explicitly call DeviceDecorator::ProcessNumber(INumberVectorProperty *nvp) for example to cause the ISD::DeviceDecorator to invoke the same function but as defined in ISD::GenericDevice.
    \section Example Example Suppose the user wishes to control an EQMod mount with \e indi_eqmod_telescope driver. From the DriverManager GUI, the user selects the driver and \e starts INDI services. The DriverManager will create a ServerManager instance to run an INDI server with the EQMod driver executable. After establishing the server, the DriverManager will create an instance of ClientManager and set its INDI server address as the host and port of the server created in ServerManager. For local servers, the host name is \e localhost and the default INDI port is 7624. If connection to the INDI server is successful, DriverManager then adds the ClientManager to both INDIListener (to handle data), and GUIManager (to handle GUI). Since the ClientManager emits signals whenever a new, updated, or deleted device/property is received from INDI server, it affects both the data handling part as well as GUI rendering. INDIListener holds a list of all INDI devices in KStars regardless of their origin. Once INDIListener receives a new device from the ClientManager, it creates a new ISD::GenericDevice. At the GUIManager side, it creates INDI Control Panel and adds a new tab with the device name. It also creates a separate tab for each property group received. The user is presented with a basic GUI to set the connection port of EQMod and to connect/disconnect to/from the telescope. If the user clicks connect, the status of the connection property is updated, and INDI_P sends the new switch status (CONNECT=ON) to INDI server via the ClientManager. If the connection is successful at the driver side, it will start defining new properties to cover the complete functionality of the EQMod driver, one of the standard properties is EQUATORIAL_EOD_COORD which will be detected in INDIListener. Upon detection of this key signature property, INDIListener creates a new ISD::Telescope device while passing to it the ISD::GenericDevice instance created earlier. Now suppose an updated Number property arrives from INDI server, the ClientManager emits a signal indicating a number property has a new updated value and INDIListener delegates the INDI Number property to the device, which is now of type ISD::Telescope. The ISD::Telescope overridden the processNumber(INumberVectorProperty *nvp) function in ISD::DeviceDecorator because it wants to handle some telescope specific numbers such as EQUATORIAL_EOD_COORD in order to move the telescope marker on the sky map as it changes. If the received property was indeed EQUATORIAL_EOD_COORD or any property handled by the ISD::Telescope ProcessNumber() function, then there is no futher action needed. But what if the property is not processed in ISD::Telescope ProcessNumber() function? In this case, the ProcessNumber() function simply calls DeviceDecorator::ProcessNumber() and it will delgate the call to ISD::GenericDevice ProcessNumber() to process. This is how the Decorator pattern work, the decorator classes implements extended functionlity, but the basic class is still responsible for handling all of the basic functions. */ #define INDIVERSION 1.7 /* we support this or less */ -typedef enum { PRIMARY_XML, THIRD_PARTY_XML, EM_XML, HOST_SOURCE, GENERATED_SOURCE } DriverSource; +typedef enum { PRIMARY_XML, THIRD_PARTY_XML, EM_XML, HOST_SOURCE, CUSTOM_SOURCE, GENERATED_SOURCE } DriverSource; typedef enum { SERVER_CLIENT, SERVER_ONLY } ServerMode; typedef enum { DATA_FITS, DATA_VIDEO, DATA_CCDPREVIEW, DATA_ASCII, DATA_OTHER } INDIDataTypes; typedef enum { LOAD_LAST_CONFIG, SAVE_CONFIG, LOAD_DEFAULT_CONFIG } INDIConfig; typedef enum { NO_DIR = 0, RA_INC_DIR, RA_DEC_DIR, DEC_INC_DIR, DEC_DEC_DIR } GuideDirection; /* GUI layout */ #define PROPERTY_LABEL_WIDTH 100 #define ELEMENT_LABEL_WIDTH 175 #define ELEMENT_READ_WIDTH 175 #define ELEMENT_WRITE_WIDTH 175 #define ELEMENT_FULL_WIDTH 340 #define MIN_SET_WIDTH 50 #define MAX_SET_WIDTH 110 #define MED_INDI_FONT 2 #define MAX_LABEL_LENGTH 20 // Pulse tracking #define INDI_PULSE_TRACKING 15000 typedef enum { PG_NONE = 0, PG_TEXT, PG_NUMERIC, PG_BUTTONS, PG_RADIO, PG_MENU, PG_LIGHTS, PG_BLOB } PGui; /* new versions of glibc define TIME_UTC as a macro */ #undef TIME_UTC /* INDI std properties */ enum stdProperties { CONNECTION, DEVICE_PORT, TIME_UTC, TIME_LST, TIME_UTC_OFFSET, GEOGRAPHIC_COORD, /* General */ EQUATORIAL_COORD, EQUATORIAL_EOD_COORD, EQUATORIAL_EOD_COORD_REQUEST, HORIZONTAL_COORD, /* Telescope */ TELESCOPE_ABORT_MOTION, ON_COORD_SET, SOLAR_SYSTEM, TELESCOPE_MOTION_NS, /* Telescope */ TELESCOPE_MOTION_WE, TELESCOPE_PARK, /* Telescope */ CCD_EXPOSURE, CCD_TEMPERATURE_REQUEST, CCD_FRAME, /* CCD */ CCD_FRAME_TYPE, CCD_BINNING, CCD_INFO, CCD_VIDEO_STREAM, /* Video */ FOCUS_SPEED, FOCUS_MOTION, FOCUS_TIMER, /* Focuser */ FILTER_SLOT, /* Filter */ WATCHDOG_HEARTBEAT }; /* Watchdog */ /* Devices families that we explicitly support (i.e. with std properties) */ typedef enum { KSTARS_ADAPTIVE_OPTICS, KSTARS_AGENT, KSTARS_AUXILIARY, KSTARS_CCD, KSTARS_DETECTORS, KSTARS_DOME, KSTARS_FILTER, KSTARS_FOCUSER, KSTARS_ROTATOR, KSTARS_SPECTROGRAPHS, KSTARS_TELESCOPE, KSTARS_WEATHER, KSTARS_UNKNOWN } DeviceFamily; +const QMap DeviceFamilyLabels = { + {KSTARS_ADAPTIVE_OPTICS, "Adaptive Optics"}, + {KSTARS_AGENT, "Agent"}, + {KSTARS_AUXILIARY, "Auxiliary"}, + {KSTARS_CCD, "CCDs"}, + {KSTARS_DETECTORS, "Detectors"}, + {KSTARS_DOME, "Domes"}, + {KSTARS_FILTER, "Filter Wheels"}, + {KSTARS_FOCUSER, "Focusers"}, + {KSTARS_ROTATOR, "Rotators"}, + {KSTARS_SPECTROGRAPHS, "Spectrographs"}, + {KSTARS_TELESCOPE, "Telescopes"}, + {KSTARS_WEATHER, "Weather"}, + {KSTARS_UNKNOWN, "Unknown"}, +}; + typedef enum { FRAME_LIGHT, FRAME_BIAS, FRAME_DARK, FRAME_FLAT } CCDFrameType; typedef enum { SINGLE_BIN, DOUBLE_BIN, TRIPLE_BIN, QUADRAPLE_BIN } CCDBinType; typedef enum { INDI_SEND_COORDS, INDI_ENGAGE_TRACKING, INDI_CENTER_LOCK, INDI_CENTER_UNLOCK, INDI_CUSTOM_PARKING, INDI_SET_PORT, INDI_CONNECT, INDI_DISCONNECT, INDI_SET_FILTER, INDI_SET_ROTATOR_TICKS, INDI_SET_ROTATOR_ANGLE } DeviceCommand; typedef enum { SOURCE_MANUAL, SOURCE_FLATCAP, SOURCE_WALL, SOURCE_DAWN_DUSK, SOURCE_DARKCAP } FlatFieldSource; typedef enum { DURATION_MANUAL, DURATION_ADU } FlatFieldDuration; #endif // INDICOMMON_H