diff --git a/kstars/ekos/auxiliary/filtermanager.cpp b/kstars/ekos/auxiliary/filtermanager.cpp index c21a34f23..5ee1c720d 100644 --- a/kstars/ekos/auxiliary/filtermanager.cpp +++ b/kstars/ekos/auxiliary/filtermanager.cpp @@ -1,667 +1,688 @@ /* Ekos Filter Manager Copyright (C) 2017 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 "filtermanager.h" #include "indi_debug.h" #include "kstarsdata.h" #include "kstars.h" #include "Options.h" #include "auxiliary/kspaths.h" #include "ekos/auxiliary/filterdelegate.h" #include #include #include #include #include #include namespace Ekos { FilterManager::FilterManager() : QDialog(KStars::Instance()) { - #ifdef Q_OS_OSX +#ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - #endif +#endif setupUi(this); connect(buttonBox, SIGNAL(accepted()), this, SLOT(close())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); //QSqlDatabase::removeDatabase("filter_db"); QSqlDatabase userdb = QSqlDatabase::cloneDatabase(KStarsData::Instance()->userdb()->GetDatabase(), "filter_db"); userdb.open(); //delete (filterModel); filterModel = new QSqlTableModel(this, userdb); filterView->setModel(filterModel); // No Edit delegate noEditDelegate = new NotEditableDelegate(filterView); filterView->setItemDelegateForColumn(4, noEditDelegate); // Exposure delegate exposureDelegate = new ExposureDelegate(filterView); filterView->setItemDelegateForColumn(5, exposureDelegate); // Offset delegate offsetDelegate = new OffsetDelegate(filterView); filterView->setItemDelegateForColumn(6, offsetDelegate); // Auto Focus delegate useAutoFocusDelegate = new UseAutoFocusDelegate(filterView); filterView->setItemDelegateForColumn(7, useAutoFocusDelegate); // Set Delegates lockDelegate = new LockDelegate(m_currentFilterLabels, filterView); filterView->setItemDelegateForColumn(8, lockDelegate); // Absolute Focus Position filterView->setItemDelegateForColumn(9, noEditDelegate); - connect(filterModel, &QSqlTableModel::dataChanged, [this](const QModelIndex &topLeft, const QModelIndex &, const QVector &) + connect(filterModel, &QSqlTableModel::dataChanged, [this](const QModelIndex & topLeft, const QModelIndex &, const QVector &) { reloadFilters(); if (topLeft.column() == 5) emit exposureChanged(filterModel->data(topLeft).toDouble()); }); } void FilterManager::refreshFilterModel() { if (m_currentFilterDevice == nullptr || m_currentFilterLabels.empty()) return; QString vendor(m_currentFilterDevice->getDeviceName()); //QSqlDatabase::removeDatabase("filter_db"); //QSqlDatabase userdb = QSqlDatabase::cloneDatabase(KStarsData::Instance()->userdb()->GetDatabase(), "filter_db"); //userdb.open(); //delete (filterModel); //filterModel = new QSqlTableModel(this, userdb); filterModel->setTable("filter"); filterModel->setFilter(QString("vendor='%1'").arg(vendor)); filterModel->select(); filterModel->setEditStrategy(QSqlTableModel::OnFieldChange); // If we have an existing table but it doesn't match the number of current filters // then we remove it. if (filterModel->rowCount() > 0 && filterModel->rowCount() != m_currentFilterLabels.count()) { - for (int i=0; i < filterModel->rowCount(); i++) + for (int i = 0; i < filterModel->rowCount(); i++) filterModel->removeRow(i); filterModel->select(); } // If it is first time, let's populate data if (filterModel->rowCount() == 0) { for (QString &filter : m_currentFilterLabels) KStarsData::Instance()->userdb()->AddFilter(vendor, "", "", filter, 0, 1.0, false, "--", 0); filterModel->select(); // Seems ->select() is not enough, have to create a new model. /*delete (filterModel); filterModel = new QSqlTableModel(this, userdb); filterModel->setTable("filter"); filterModel->setFilter(QString("vendor='%1'").arg(m_currentFilterDevice->getDeviceName())); filterModel->select(); filterModel->setEditStrategy(QSqlTableModel::OnManualSubmit);*/ } // Make sure all the filter colors match DB. If not update model to sync with INDI filter values else { for (int i = 0; i < filterModel->rowCount(); ++i) { QModelIndex index = filterModel->index(i, 4); if (filterModel->data(index).toString() != m_currentFilterLabels[i]) { filterModel->setData(index, m_currentFilterLabels[i]); } } } lockDelegate->setCurrentFilterList(m_currentFilterLabels); filterModel->setHeaderData(4, Qt::Horizontal, i18n("Filter")); filterModel->setHeaderData(5, Qt::Horizontal, i18n("Filter exposure time during focus"), Qt::ToolTipRole); filterModel->setHeaderData(5, Qt::Horizontal, i18n("Exposure")); filterModel->setHeaderData(6, Qt::Horizontal, i18n("Relative offset in steps"), Qt::ToolTipRole); filterModel->setHeaderData(6, Qt::Horizontal, i18n("Offset")); filterModel->setHeaderData(7, Qt::Horizontal, i18n("Start Auto Focus when filter is activated"), Qt::ToolTipRole); filterModel->setHeaderData(7, Qt::Horizontal, i18n("Auto Focus")); filterModel->setHeaderData(8, Qt::Horizontal, i18n("Lock specific filter when running Auto Focus"), Qt::ToolTipRole); filterModel->setHeaderData(8, Qt::Horizontal, i18n("Lock Filter")); filterModel->setHeaderData(9, Qt::Horizontal, i18n("Flat frames are captured at this focus position. It is updated automatically by focus process if enabled."), Qt::ToolTipRole); filterModel->setHeaderData(9, Qt::Horizontal, i18n("Flat Focus Position")); filterView->hideColumn(0); filterView->hideColumn(1); filterView->hideColumn(2); filterView->hideColumn(3); reloadFilters(); } void FilterManager::reloadFilters() { qDeleteAll(m_ActiveFilters); currentFilter = nullptr; - targetFilter = nullptr; + targetFilter = nullptr; m_ActiveFilters.clear(); operationQueue.clear(); for (int i = 0; i < filterModel->rowCount(); ++i) { QSqlRecord record = filterModel->record(i); QString id = record.value("id").toString(); QString vendor = record.value("Vendor").toString(); QString model = record.value("Model").toString(); QString type = record.value("Type").toString(); QString color = record.value("Color").toString(); double exposure = record.value("Exposure").toDouble(); int offset = record.value("Offset").toInt(); QString lockedFilter = record.value("LockedFilter").toString(); bool useAutoFocus = record.value("UseAutoFocus").toInt() == 1; int absFocusPos = record.value("AbsoluteFocusPosition").toInt(); OAL::Filter *o = new OAL::Filter(id, model, vendor, type, color, exposure, offset, useAutoFocus, lockedFilter, absFocusPos); m_ActiveFilters.append(o); } } void FilterManager::setCurrentFilterWheel(ISD::GDInterface *filter) { if (m_currentFilterDevice == filter) return; else m_currentFilterDevice->disconnect(this); m_currentFilterDevice = filter; connect(filter, SIGNAL(textUpdated(ITextVectorProperty*)), this, SLOT(processText(ITextVectorProperty*))); connect(filter, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(processNumber(INumberVectorProperty*))); connect(filter, SIGNAL(switchUpdated(ISwitchVectorProperty*)), this, SLOT(processSwitch(ISwitchVectorProperty*))); - connect(filter, &ISD::GDInterface::Disconnected, [&]() { + connect(filter, &ISD::GDInterface::Disconnected, [&]() + { m_currentFilterLabels.clear(); m_currentFilterPosition = -1; //m_currentFilterDevice = nullptr; m_FilterNameProperty = nullptr; m_FilterPositionProperty = nullptr; }); + m_FilterNameProperty = nullptr; + m_FilterPositionProperty = nullptr; + m_FilterConfirmSet = nullptr; initFilterProperties(); } void FilterManager::initFilterProperties() { if (m_FilterNameProperty && m_FilterPositionProperty) + { + if (m_FilterConfirmSet == nullptr) + m_FilterConfirmSet = m_currentFilterDevice->getBaseDevice()->getSwitch("CONFIRM_FILTER_SET"); return; + } filterNameLabel->setText(m_currentFilterDevice->getDeviceName()); m_currentFilterLabels.clear(); m_FilterNameProperty = m_currentFilterDevice->getBaseDevice()->getText("FILTER_NAME"); m_FilterPositionProperty = m_currentFilterDevice->getBaseDevice()->getNumber("FILTER_SLOT"); + m_FilterConfirmSet = m_currentFilterDevice->getBaseDevice()->getSwitch("CONFIRM_FILTER_SET"); m_currentFilterPosition = getFilterPosition(true); m_currentFilterLabels = getFilterLabels(true); if (m_currentFilterLabels.isEmpty() == false) refreshFilterModel(); if (m_ActiveFilters.count() >= m_currentFilterPosition) - lastFilterOffset = m_ActiveFilters[m_currentFilterPosition-1]->offset(); + lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset(); } QStringList FilterManager::getFilterLabels(bool forceRefresh) { if (forceRefresh == false || m_FilterNameProperty == nullptr) return m_currentFilterLabels; QStringList filterList; QStringList filterAlias = Options::filterAlias(); for (int i = 0; i < m_FilterPositionProperty->np[0].max; i++) { QString item; if (m_FilterNameProperty != nullptr && (i < m_FilterNameProperty->ntp)) item = m_FilterNameProperty->tp[i].text; else if (i < filterAlias.count() && filterAlias[i].isEmpty() == false) item = filterAlias.at(i); filterList.append(item); } return filterList; } int FilterManager::getFilterPosition(bool forceRefresh) { if (forceRefresh == false) return m_currentFilterPosition; return static_cast(m_FilterPositionProperty->np[0].value); } bool FilterManager::setFilterPosition(uint8_t position, FilterPolicy policy) { // Position 1 to Max if (position > m_ActiveFilters.count()) return false; m_Policy = policy; - currentFilter= m_ActiveFilters[m_currentFilterPosition - 1]; - targetFilter = m_ActiveFilters[position-1]; + currentFilter = m_ActiveFilters[m_currentFilterPosition - 1]; + targetFilter = m_ActiveFilters[position - 1]; if (currentFilter == targetFilter) { emit ready(); return true; } buildOperationQueue(FILTER_CHANGE); executeOperationQueue(); return true; } void FilterManager::processNumber(INumberVectorProperty *nvp) { - if (nvp->s != IPS_OK || strcmp(nvp->name, "FILTER_SLOT") || m_currentFilterDevice == nullptr || strcmp(nvp->device,m_currentFilterDevice->getDeviceName())) + if (nvp->s != IPS_OK || strcmp(nvp->name, "FILTER_SLOT") || m_currentFilterDevice == nullptr || strcmp(nvp->device, m_currentFilterDevice->getDeviceName())) return; m_FilterPositionProperty = nvp; if (m_currentFilterPosition != static_cast(m_FilterPositionProperty->np[0].value)) { m_currentFilterPosition = static_cast(m_FilterPositionProperty->np[0].value); emit positionChanged(m_currentFilterPosition); } if (state == FILTER_CHANGE) executeOperationQueue(); // If filter is changed externally, record its current offset as the starting offset. else if (state == FILTER_IDLE && m_ActiveFilters.count() >= m_currentFilterPosition) - lastFilterOffset = m_ActiveFilters[m_currentFilterPosition-1]->offset(); + lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset(); // Check if we have to apply Focus Offset // Focus offsets are always applied first // Check if we have to start Auto Focus // If new filter position changed, and new filter policy is to perform auto-focus then autofocus is initiated. // Capture Module // 3x L ---> 3x HA ---> 3x L // Capture calls setFilterPosition("L"). // 0. Change filter to desired filter "L" // 1. Is there any offset from last offset that needs to be applied? // 1.1 Yes --> Apply focus offset and wait until position is changed: // 1.1.1 Position complete, now check for useAutoFocus policy (#2). // 1.1.2 Position failed, retry? // 1.2 No --> Go to #2 // 2. Is there autofocus policy for current filter? // 2.1. Yes --> Check filter lock policy // 2.1.1 If filter lock is another filter --> Change filter // 2.1.2 If filter lock is same filter --> proceed to 2.3 // 2.2 No --> Process to 2.3 // 2.3 filter lock policy filter is applied, start autofocus. // 2.4 Autofocus complete. Check filter lock policy // 2.4.1 If filter lock policy was applied --> revert filter // 2.4.1.1 If filter offset policy is applicable --> Apply offset // 2.4.1.2 If no filter offset policy is applicable --> Go to 2.5 // 2.4.2 No filter lock policy, go to 2.5 // 2.5 All complete, emit ready() // Example. Current filter L. setFilterPosition("HA"). AutoFocus = YES. HA lock policy: L, HA offset policy: +100 with respect to L // Operation Stack. offsetDiff = 100 // If AutoFocus && current filter = lock policy filter // AUTO_FOCUS (on L) // CHANGE_FILTER (to HA) // APPLY_OFFSET: +100 // Example. Current filter L. setFilterPosition("HA"). AutoFocus = No. HA lock policy: L, HA offset policy: +100 with respect to L // Operation Stack. offsetDiff = 100 // CHANGE_FILTER (to HA) // APPLY_OFFSET: +100 // Example. Current filter R. setFilterPosition("B"). AutoFocus = YES. B lock policy: "--", B offset policy: +70 with respect to L // R offset = -50 with respect to L // FILTER_CHANGE (to B) // FILTER_OFFSET (+120) // AUTO_FOCUS // Example. Current filter R. setFilterPosition("HA"). AutoFocus = YES. HA lock policy: L, HA offset policy: +100 with respect to L // R offset = -50 with respect to L // Operation Stack. offsetDiff = +150 // CHANGE_FILTER (to L) // APPLY_OFFSET: +50 (L - R) // AUTO_FOCUS // CHANGE_FILTER (HA) // APPLY_OFFSET: +100 // Example. Current filter R. setFilterPosition("HA"). AutoFocus = No. HA lock policy: L, HA offset policy: +100 with respect to L // R offset = -50 with respect to L // Operation Stack. offsetDiff = +150 // CHANGE_FILTER (to HA) // APPLY_OFFSET: +150 (HA - R) // Example. Current filter L. setFilterPosition("R"). AutoFocus = Yes. R lock policy: R, R offset policy: -50 with respect to L // Operation Stack. offsetDiff = -50 // CHANGE_FILTER (to R) // APPLY_OFFSET: -50 (R - L) // AUTO_FOCUS } void FilterManager::processText(ITextVectorProperty *tvp) { if (strcmp(tvp->name, "FILTER_NAME") || m_currentFilterDevice == nullptr || strcmp(tvp->device, m_currentFilterDevice->getDeviceName()) ) - return; + return; m_FilterNameProperty = tvp; QStringList newFilterLabels = getFilterLabels(true); if (newFilterLabels != m_currentFilterLabels) { m_currentFilterLabels = newFilterLabels; refreshFilterModel(); emit labelsChanged(newFilterLabels); } } void FilterManager::processSwitch(ISwitchVectorProperty *svp) { if (m_currentFilterDevice == nullptr || strcmp(svp->device, m_currentFilterDevice->getDeviceName())) return; } void FilterManager::buildOperationQueue(FilterState operation) { - operationQueue.clear(); + operationQueue.clear(); m_useTargetFilter = false; switch (operation) { - case FILTER_CHANGE: - { - if ( (m_Policy & CHANGE_POLICY) && targetFilter != currentFilter) - m_useTargetFilter = true; - - if (m_useTargetFilter) - { - operationQueue.enqueue(FILTER_CHANGE); - if (m_FocusReady && (m_Policy & OFFSET_POLICY)) - operationQueue.enqueue(FILTER_OFFSET); - } + case FILTER_CHANGE: + { + if ( (m_Policy & CHANGE_POLICY) && targetFilter != currentFilter) + m_useTargetFilter = true; - if (m_FocusReady && (m_Policy & AUTOFOCUS_POLICY) && targetFilter->useAutoFocus()) - operationQueue.enqueue(FILTER_AUTOFOCUS); - } - break; + if (m_useTargetFilter) + { + operationQueue.enqueue(FILTER_CHANGE); + if (m_FocusReady && (m_Policy & OFFSET_POLICY)) + operationQueue.enqueue(FILTER_OFFSET); + } - default: + if (m_FocusReady && (m_Policy & AUTOFOCUS_POLICY) && targetFilter->useAutoFocus()) + operationQueue.enqueue(FILTER_AUTOFOCUS); + } break; + + default: + break; } } bool FilterManager::executeOperationQueue() { if (operationQueue.isEmpty()) { state = FILTER_IDLE; emit newStatus(state); emit ready(); return false; } FilterState nextOperation = operationQueue.dequeue(); bool actionRequired = true; switch (nextOperation) { - case FILTER_CHANGE: - { - state = FILTER_CHANGE; - if (m_useTargetFilter) - targetFilterPosition = m_ActiveFilters.indexOf(targetFilter) + 1; - m_currentFilterDevice->runCommand(INDI_SET_FILTER, &targetFilterPosition); - emit newStatus(state); - } - break; - - case FILTER_OFFSET: - { - state = FILTER_OFFSET; - if (m_useTargetFilter) + case FILTER_CHANGE: { - targetFilterOffset = targetFilter->offset() - lastFilterOffset; - lastFilterOffset = targetFilter->offset(); - currentFilter = targetFilter; - m_useTargetFilter = false; - } - if (targetFilterOffset == 0) - actionRequired = false; - else - { - emit newFocusOffset(targetFilterOffset, false); + state = FILTER_CHANGE; + if (m_useTargetFilter) + targetFilterPosition = m_ActiveFilters.indexOf(targetFilter) + 1; + m_currentFilterDevice->runCommand(INDI_SET_FILTER, &targetFilterPosition); emit newStatus(state); + + if (m_FilterConfirmSet) + { + if (KMessageBox::questionYesNo(KStars::Instance(), + i18n("Set filter to %1. Is filter set?", targetFilter->color()), + i18n("Confirm Filter")) == KMessageBox::Yes) + m_currentFilterDevice->runCommand(INDI_CONFIRM_FILTER); + } } - } break; - case FILTER_AUTOFOCUS: - state = FILTER_AUTOFOCUS; - emit newStatus(state); - emit checkFocus(0.01); + case FILTER_OFFSET: + { + state = FILTER_OFFSET; + if (m_useTargetFilter) + { + targetFilterOffset = targetFilter->offset() - lastFilterOffset; + lastFilterOffset = targetFilter->offset(); + currentFilter = targetFilter; + m_useTargetFilter = false; + } + if (targetFilterOffset == 0) + actionRequired = false; + else + { + emit newFocusOffset(targetFilterOffset, false); + emit newStatus(state); + } + } break; - default: - break; + case FILTER_AUTOFOCUS: + state = FILTER_AUTOFOCUS; + emit newStatus(state); + emit checkFocus(0.01); + break; + + default: + break; } // If an additional action is required, return return and continue later if (actionRequired) return true; // Otherwise, continue processing the queue else return executeOperationQueue(); } bool FilterManager::executeOneOperation(FilterState operation) { bool actionRequired = false; switch (operation) { - default: - break; + default: + break; } return actionRequired; } void FilterManager::setFocusOffsetComplete() { if (state == FILTER_OFFSET) executeOperationQueue(); } double FilterManager::getFilterExposure(const QString &name) const { if (m_currentFilterLabels.empty()) return 1; QString color = name; if (color.isEmpty()) - color = m_currentFilterLabels[m_currentFilterPosition-1]; + color = m_currentFilterLabels[m_currentFilterPosition - 1]; // Search for locked filter by filter color name - auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [color](OAL::Filter *oneFilter) - {return (oneFilter->color() == color);}); + auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [color](OAL::Filter * oneFilter) + { + return (oneFilter->color() == color); + }); if (pos != m_ActiveFilters.end()) return (*pos)->exposure(); // Default value return 1; } bool FilterManager::setFilterExposure(int index, double exposure) { if (m_currentFilterLabels.empty()) return false; - QString color = m_currentFilterLabels[index]; - for (int i=0; i < m_ActiveFilters.count(); i++) - { - if (color == m_ActiveFilters[i]->color()) - { - filterModel->setData(filterModel->index(i, 5), exposure); - filterModel->submitAll(); - refreshFilterModel(); - return true; - } - } - - return false; + QString color = m_currentFilterLabels[index]; + for (int i = 0; i < m_ActiveFilters.count(); i++) + { + if (color == m_ActiveFilters[i]->color()) + { + filterModel->setData(filterModel->index(i, 5), exposure); + filterModel->submitAll(); + refreshFilterModel(); + return true; + } + } + + return false; } bool FilterManager::setFilterAbsoluteFocusPosition(int index, int absFocusPos) { if (index < 0 || index >= m_currentFilterLabels.count()) return false; QString color = m_currentFilterLabels[index]; - for (int i=0; i < m_ActiveFilters.count(); i++) + for (int i = 0; i < m_ActiveFilters.count(); i++) { if (color == m_ActiveFilters[i]->color()) { filterModel->setData(filterModel->index(i, 9), absFocusPos); filterModel->submitAll(); refreshFilterModel(); return true; } } return false; } QString FilterManager::getFilterLock(const QString &name) const { // Search for locked filter by filter color name - auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [name](OAL::Filter *oneFilter) - {return (oneFilter->color() == name);}); + auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [name](OAL::Filter * oneFilter) + { + return (oneFilter->color() == name); + }); if (pos != m_ActiveFilters.end()) return (*pos)->lockedFilter(); // Default value return "--"; } void FilterManager::removeDevice(ISD::GDInterface *device) -{ +{ if (device == m_currentFilterDevice) { m_FilterNameProperty = nullptr; m_FilterPositionProperty = nullptr; m_currentFilterDevice = nullptr; m_currentFilterLabels.clear(); m_currentFilterPosition = 0; qDeleteAll(m_ActiveFilters); m_ActiveFilters.clear(); delete(filterModel); filterModel = nullptr; } } void FilterManager::setFocusStatus(Ekos::FocusState focusState) { if (state == FILTER_AUTOFOCUS) { switch (focusState) { case FOCUS_COMPLETE: - executeOperationQueue(); - break; + executeOperationQueue(); + break; case FOCUS_FAILED: - if (++retries == 3) - { - retries = 0; - emit failed(); - return; - } - // Restart again - emit checkFocus(0.01); - break; - - default: - break; + if (++retries == 3) + { + retries = 0; + emit failed(); + return; + } + // Restart again + emit checkFocus(0.01); + break; + + default: + break; } } } bool FilterManager::syncAbsoluteFocusPosition(int index) { if (index < 0 || index > m_ActiveFilters.count()) { qCWarning(KSTARS_INDI) << __FUNCTION__ << "index" << index << "is out of bounds."; return false; } int absFocusPos = m_ActiveFilters[index]->absoluteFocusPosition(); if (m_FocusAbsPosition == absFocusPos) { m_FocusAbsPositionPending = false; return true; } else if (m_FocusAbsPositionPending == false) { m_FocusAbsPositionPending = true; emit newFocusOffset(absFocusPos, true); } return false; } } diff --git a/kstars/ekos/auxiliary/filtermanager.h b/kstars/ekos/auxiliary/filtermanager.h index bd971976c..9f4a65b95 100644 --- a/kstars/ekos/auxiliary/filtermanager.h +++ b/kstars/ekos/auxiliary/filtermanager.h @@ -1,199 +1,200 @@ /* Ekos Filter Manager Copyright (C) 2017 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 "ui_filtersettings.h" #include "ekos/ekos.h" #include #include #include #include #include #include #include class QSqlTableModel; class LockDelegate; class NotEditableDelegate; class ExposureDelegate; class OffsetDelegate; class UseAutoFocusDelegate; namespace Ekos { class FilterManager : public QDialog, public Ui::FilterSettings { Q_OBJECT public: typedef enum { CHANGE_POLICY = 1 << 0, OFFSET_POLICY = 1 << 1, AUTOFOCUS_POLICY = 1 << 2, ALL_POLICIES = CHANGE_POLICY | OFFSET_POLICY | AUTOFOCUS_POLICY } FilterPolicy; FilterManager(); void refreshFilterModel(); QStringList getFilterLabels(bool forceRefresh=false); int getFilterPosition(bool forceRefresh=false); // The target position and offset int getTargetFilterPosition() { return targetFilterPosition; } int getTargetFilterOffset() { return targetFilterOffset; } bool setFilterAbsoluteFocusPosition(int index, int absFocusPos); // Set absolute focus position, if supported, to the current filter absolute focus position. bool syncAbsoluteFocusPosition(int index); /** * @brief getFilterExposure Get optimal exposure time for the specified filter * @param name filter to obtain exposure time for * @return exposure time in seconds. */ double getFilterExposure(const QString &name = QString()) const; bool setFilterExposure(int index, double exposure); /** * @brief getFilterLock Return filter that should be used when running autofocus for the supplied filter * For example, "Red" filter can be locked to use "Lum" when doing autofocus. "Green" filter can be locked to "--" * which means that no filter change is necessary. * @param name filter which we need to query its locked filter. * @return locked filter. "--" indicates no locked filter and whatever current filter should be used. * */ QString getFilterLock(const QString &name) const; /** * @brief setCurrentFilterWheel Set the FilterManager active filter wheel. * @param filter pointer to filter wheel device */ void setCurrentFilterWheel(ISD::GDInterface *filter); /** * @brief setFocusReady Set whether a focuser device is active and in use. * @param enabled true if focus is ready, false otherwise. */ void setFocusReady(bool enabled) { m_FocusReady = enabled;} /** * @brief applyFilterFocusPolicies Check if we need to apply any filter policies for focus operations. */ void applyFilterFocusPolicies(); public slots: // Position. if applyPolicy is true then all filter offsets and autofocus & lock policies are applied. bool setFilterPosition(uint8_t position, Ekos::FilterManager::FilterPolicy policy = ALL_POLICIES); // Offset Request completed void setFocusOffsetComplete(); // Remove Device void removeDevice(ISD::GDInterface *device); // Refresh Filters after model update void reloadFilters(); // Focus Status void setFocusStatus(Ekos::FocusState focusState); // Set absolute focus position void setFocusAbsolutePosition(int value) { m_FocusAbsPosition = value; } // Inti filter property after connection void initFilterProperties(); signals: // Emitted only when there is a change in the filter slot number void positionChanged(int); // Emitted when filter change operation completed successfully including any focus offsets or auto-focus operation void labelsChanged(QStringList); // Emitted when filter exposure duration changes void exposureChanged(double); // Emitted when filter change completed including all required actions void ready(); // Emitted when operation fails void failed(); // Status signal void newStatus(Ekos::FilterState state); // Check Focus void checkFocus(double); // New Focus offset requested void newFocusOffset(int value, bool useAbsoluteOffset); private slots: void processText(ITextVectorProperty *tvp); void processNumber(INumberVectorProperty *nvp); void processSwitch(ISwitchVectorProperty *svp); private: // Filter Wheel Devices ISD::GDInterface *m_currentFilterDevice = { nullptr }; // Position and Labels QStringList m_currentFilterLabels; int m_currentFilterPosition = { -1 }; double m_currentFilterExposure = { -1 }; // Filter Structure QList m_ActiveFilters; OAL::Filter *targetFilter = { nullptr }; OAL::Filter *currentFilter = { nullptr }; - bool m_useTargetFilter = { false }; + bool m_useTargetFilter = { false }; // Autofocus retries uint8_t retries = { 0 }; int16_t lastFilterOffset { 0 }; // Table model QSqlTableModel *filterModel = { nullptr }; // INDI Properties of current active filter ITextVectorProperty *m_FilterNameProperty { nullptr }; INumberVectorProperty *m_FilterPositionProperty { nullptr }; + ISwitchVectorProperty *m_FilterConfirmSet { nullptr }; // Operation stack void buildOperationQueue(FilterState operation); bool executeOperationQueue(); bool executeOneOperation(FilterState operation); // Update model void syncDBToINDI(); // Operation Queue QQueue operationQueue; FilterState state = { FILTER_IDLE }; int targetFilterPosition { -1 }; int targetFilterOffset { - 1 }; bool m_FocusReady { false }; bool m_FocusAbsPositionPending { false}; int m_FocusAbsPosition { -1 }; // Delegates QPointer lockDelegate; QPointer noEditDelegate; QPointer exposureDelegate; QPointer offsetDelegate; QPointer useAutoFocusDelegate; // Policies FilterPolicy m_Policy = { ALL_POLICIES }; }; } diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp index fa0eb48d4..8f7c2668a 100644 --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -1,3143 +1,3146 @@ /* 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 "manager.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 "fitsviewer/fitsdata.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 namespace Ekos { Manager::Manager(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))); } #else // if (Options::independentWindowEkos()) // setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); #endif setupUi(this); qRegisterMetaType("Ekos::CommunicationStatus"); qDBusRegisterMetaType(); 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, &QTimer::timeout, this, &Ekos::Manager::updateCaptureCountDown); toolsWidget->setIconSize(QSize(48, 48)); connect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); // Enable scheduler Tab toolsWidget->setTabEnabled(1, false); // Start/Stop INDI Server connect(processINDIB, &QPushButton::clicked, this, &Ekos::Manager::processINDI); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); // Connect/Disconnect INDI devices connect(connectB, &QPushButton::clicked, this, &Ekos::Manager::connectDevices); connect(disconnectB, &QPushButton::clicked, this, &Ekos::Manager::disconnectDevices); ekosLiveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); ekosLiveClient.reset(new EkosLive::Client(this)); connect(ekosLiveClient.get(), &EkosLive::Client::connected, [this]() { emit ekosLiveStatusChanged(true); }); connect(ekosLiveClient.get(), &EkosLive::Client::disconnected, [this]() { emit ekosLiveStatusChanged(false); }); // INDI Control Panel //connect(controlPanelB, &QPushButton::clicked, GUIManager::Instance(), SLOT(show())); connect(ekosLiveB, &QPushButton::clicked, [&]() { ekosLiveClient.get()->show(); ekosLiveClient.get()->raise(); }); connect(this, &Manager::ekosStatusChanged, ekosLiveClient.get()->message(), &EkosLive::Message::setEkosStatingStatus); connect(ekosLiveClient.get()->message(), &EkosLive::Message::connected, [&]() { ekosLiveB->setIcon(QIcon(":/icons/cloud-online.svg")); }); connect(ekosLiveClient.get()->message(), &EkosLive::Message::disconnected, [&]() { ekosLiveB->setIcon(QIcon::fromTheme("folder-cloud")); }); 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); // Serial Port Assistat connect(serialPortAssistantB, &QPushButton::clicked, [&]() { serialPortAssistant->show(); serialPortAssistant->raise(); }); connect(this, &Ekos::Manager::ekosStatusChanged, [&](Ekos::CommunicationStatus status) { indiControlPanelB->setEnabled(status == Ekos::Success); }); connect(indiControlPanelB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("show_control_panel")->trigger(); }); connect(optionsB, &QPushButton::clicked, [&]() { KStars::Instance()->actionCollection()->action("configure")->trigger(); }); // Save as above, but it appears in all modules connect(ekosOptionsB, &QPushButton::clicked, this, &Ekos::Manager::showEkosOptions); // Clear Ekos Log connect(clearB, &QPushButton::clicked, this, &Ekos::Manager::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, &QPushButton::clicked, dialog, &KConfigDialog::show); connect(dialog->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); connect(dialog->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Ekos::Manager::updateDebugInterfaces); // Summary // previewPixmap = new QPixmap(QPixmap(":/images/noimage.png")); // Profiles connect(addProfileB, &QPushButton::clicked, this, &Ekos::Manager::addProfile); connect(editProfileB, &QPushButton::clicked, this, &Ekos::Manager::editProfile); connect(deleteProfileB, &QPushButton::clicked, this, &Ekos::Manager::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, &QPushButton::clicked, this, &Ekos::Manager::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 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(), &Scheduler::newLog, this, &Ekos::Manager::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); // JM 2019-01-19: Why cloud images depend on summary preview? // connect(summaryPreview.get(), &FITSView::loaded, [&]() // { // // 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 QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); resize(Options::ekosWindowWidth(), Options::ekosWindowHeight()); } void Manager::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } Manager::~Manager() { toolsWidget->disconnect(this); //delete previewPixmap; } void Manager::closeEvent(QCloseEvent * event) { // QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); // a->setChecked(false); // 2019-02-14 JM: Close event, for some reason, make all the children disappear // when the widget is shown again. Applying a workaround here event->ignore(); hide(); } void Manager::hideEvent(QHideEvent * /*event*/) { Options::setEkosWindowWidth(width()); Options::setEkosWindowHeight(height()); QAction * a = KStars::Instance()->actionCollection()->action("show_ekos"); a->setChecked(false); } void Manager::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 Manager::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 Manager::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 Manager::loadDrivers() { foreach (DriverInfo * dv, DriverManager::Instance()->getDrivers()) { if (dv->getDriverSource() != HOST_SOURCE) driversList[dv->getLabel()] = dv; } } void Manager::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(); Ekos::CommunicationStatus previousStatus = m_ekosStatus; m_ekosStatus = Ekos::Idle; if (previousStatus != m_ekosStatus) emit ekosStatusChanged(m_ekosStatus); previousStatus = m_indiStatus; m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); 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")); mountStatus->setStyleSheet(QString()); 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(); m_isStarted = false; //processINDIB->setText(i18n("Start INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-start")); processINDIB->setToolTip(i18n("Start")); } void Manager::processINDI() { if (m_isStarted == false) start(); else stop(); } bool Manager::stop() { cleanDevices(); serialPortAssistant.reset(); serialPortAssistantB->setEnabled(false); profileGroup->setEnabled(true); return true; } bool Manager::start() { // Don't start if it is already started before if (m_ekosStatus == Ekos::Pending || m_ekosStatus == Ekos::Success) { qCDebug(KSTARS_EKOS) << "Ekos Manager start called but current Ekos Status is" << m_ekosStatus << "Ignoring request."; return true; } if (m_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(); m_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 (m_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(), &INDIListener::newDevice, this, &Ekos::Manager::processNewDevice); connect(INDIListener::Instance(), &INDIListener::newTelescope, this, &Ekos::Manager::setTelescope); connect(INDIListener::Instance(), &INDIListener::newCCD, this, &Ekos::Manager::setCCD); connect(INDIListener::Instance(), &INDIListener::newFilter, this, &Ekos::Manager::setFilter); connect(INDIListener::Instance(), &INDIListener::newFocuser, this, &Ekos::Manager::setFocuser); connect(INDIListener::Instance(), &INDIListener::newDome, this, &Ekos::Manager::setDome); connect(INDIListener::Instance(), &INDIListener::newWeather, this, &Ekos::Manager::setWeather); connect(INDIListener::Instance(), &INDIListener::newDustCap, this, &Ekos::Manager::setDustCap); connect(INDIListener::Instance(), &INDIListener::newLightBox, this, &Ekos::Manager::setLightBox); connect(INDIListener::Instance(), &INDIListener::newST4, this, &Ekos::Manager::setST4); connect(INDIListener::Instance(), &INDIListener::deviceRemoved, this, &Ekos::Manager::removeDevice, Qt::DirectConnection); #ifdef Q_OS_OSX if (m_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 (m_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) == false) { INDIListener::Instance()->disconnect(this); qDeleteAll(managedDrivers); managedDrivers.clear(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString, QString)), this, SLOT(processServerTermination(QString, QString))); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); 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, &Ekos::Manager::checkINDITimeout); } else { // If we need to use INDI Web Manager if (currentProfile->INDIWebManagerPort > 0) { appendLogText(i18n("Establishing communication with remote INDI Web Manager...")); m_RemoteManagerStart = false; if (INDI::WebManager::isOnline(currentProfile)) { INDI::WebManager::syncCustomDrivers(currentProfile); currentProfile->isStellarMate = INDI::WebManager::isStellarMate(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...")); m_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(); m_ekosStatus = Ekos::Error; emit ekosStatusChanged(m_ekosStatus); QApplication::restoreOverrideCursor(); return false; } connect(DriverManager::Instance(), SIGNAL(serverTerminated(QString, QString)), this, SLOT(processServerTermination(QString, QString))); QApplication::restoreOverrideCursor(); m_ekosStatus = Ekos::Pending; emit ekosStatusChanged(m_ekosStatus); appendLogText( i18n("INDI services started. Connection to remote INDI server is successful. Waiting for devices...")); QTimer::singleShot(MAX_REMOTE_INDI_TIMEOUT, this, &Ekos::Manager::checkINDITimeout); } connectB->setEnabled(false); disconnectB->setEnabled(false); //controlPanelB->setEnabled(false); profileGroup->setEnabled(false); m_isStarted = true; //processINDIB->setText(i18n("Stop INDI")); processINDIB->setIcon(QIcon::fromTheme("media-playback-stop")); processINDIB->setToolTip(i18n("Stop")); return true; } void Manager::checkINDITimeout() { // Don't check anything unless we're still pending if (m_ekosStatus != Ekos::Pending) return; if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); return; } if (m_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")); } } m_ekosStatus = Ekos::Error; } void Manager::connectDevices() { // Check if already connected int nConnected = 0; Ekos::CommunicationStatus previousStatus = m_indiStatus; for (auto &device : genericDevices) { if (device->isConnected()) nConnected++; } if (genericDevices.count() == nConnected) { m_indiStatus = Ekos::Success; emit indiStatusChanged(m_indiStatus); return; } m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Connecting " << device->getDeviceName(); device->Connect(); } connectB->setEnabled(false); disconnectB->setEnabled(true); appendLogText(i18n("Connecting INDI devices...")); } void Manager::disconnectDevices() { for (auto &device : genericDevices) { qCDebug(KSTARS_EKOS) << "Disconnecting " << device->getDeviceName(); device->Disconnect(); } appendLogText(i18n("Disconnecting INDI devices...")); } void Manager::processServerTermination(const QString &host, const QString &port) { if ((m_LocalMode && managedDrivers.first()->getPort() == port) || (currentProfile->host == host && currentProfile->port == port.toInt())) { cleanDevices(false); } } void Manager::cleanDevices(bool stopDrivers) { if (m_ekosStatus == Ekos::Idle) return; INDIListener::Instance()->disconnect(this); DriverManager::Instance()->disconnect(this); if (managedDrivers.isEmpty() == false) { if (m_LocalMode) { if (stopDrivers) DriverManager::Instance()->stopDevices(managedDrivers); } else { if (stopDrivers) DriverManager::Instance()->disconnectRemoteHost(managedDrivers.first()); if (m_RemoteManagerStart && currentProfile->INDIWebManagerPort != -1) { INDI::WebManager::stopProfile(currentProfile); m_RemoteManagerStart = false; } } } reset(); profileGroup->setEnabled(true); appendLogText(i18n("INDI services stopped.")); } void Manager::processNewDevice(ISD::GDInterface * devInterface) { qCInfo(KSTARS_EKOS) << "Ekos received a new device: " << devInterface->getDeviceName(); Ekos::CommunicationStatus previousStatus = m_indiStatus; 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 m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); genericDevices.append(devInterface); nDevices--; connect(devInterface, &ISD::GDInterface::Connected, this, &Ekos::Manager::deviceConnected); connect(devInterface, &ISD::GDInterface::Disconnected, this, &Ekos::Manager::deviceDisconnected); connect(devInterface, &ISD::GDInterface::propertyDefined, this, &Ekos::Manager::processNewProperty); if (currentProfile->isStellarMate) { connect(devInterface, &ISD::GDInterface::systemPortDetected, [this, devInterface]() { if (!serialPortAssistant) { serialPortAssistant.reset(new SerialPortAssistant(currentProfile, this)); serialPortAssistantB->setEnabled(true); } uint32_t driverInterface = devInterface->getDriverInterface(); // Ignore CCD interface if (driverInterface & INDI::BaseDevice::CCD_INTERFACE) return; if (driverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE || driverInterface & INDI::BaseDevice::FOCUSER_INTERFACE || driverInterface & INDI::BaseDevice::FILTER_INTERFACE || driverInterface & INDI::BaseDevice::AUX_INTERFACE || driverInterface & INDI::BaseDevice::GPS_INTERFACE) serialPortAssistant->addDevice(devInterface); if (Options::autoLoadSerialAssistant()) serialPortAssistant->show(); }); } if (nDevices <= 0) { m_ekosStatus = Ekos::Success; emit ekosStatusChanged(m_ekosStatus); connectB->setEnabled(true); disconnectB->setEnabled(false); //controlPanelB->setEnabled(true); if (m_LocalMode == false && nDevices == 0) { if (currentProfile->autoConnect) appendLogText(i18n("Remote devices established.")); else appendLogText(i18n("Remote devices established. Please connect devices.")); } } } void Manager::deviceConnected() { connectB->setEnabled(false); disconnectB->setEnabled(true); processINDIB->setEnabled(false); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (Options::verboseLogging()) { ISD::GDInterface * device = qobject_cast(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()) { m_indiStatus = Ekos::Success; qCInfo(KSTARS_EKOS) << "All INDI devices are now connected."; } else m_indiStatus = Ekos::Pending; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); 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, &ISD::GDInterface::switchUpdated, this, &Ekos::Manager::watchDebugProperty); ISwitchVectorProperty * configProp = device->getBaseDevice()->getSwitch("CONFIG_PROCESS"); if (configProp && configProp->s == IPS_IDLE) device->setConfig(tConfig); break; } } } void Manager::deviceDisconnected() { ISD::GDInterface * dev = static_cast(sender()); Ekos::CommunicationStatus previousStatus = m_indiStatus; if (dev != nullptr) { if (dev->getState("CONNECTION") == IPS_ALERT) m_indiStatus = Ekos::Error; else if (dev->getState("CONNECTION") == IPS_BUSY) m_indiStatus = Ekos::Pending; else m_indiStatus = Ekos::Idle; if (Options::verboseLogging()) qCDebug(KSTARS_EKOS) << dev->getDeviceName() << " is disconnected."; appendLogText(i18n("%1 is disconnected.", dev->getDeviceName())); } else m_indiStatus = Ekos::Idle; if (previousStatus != m_indiStatus) emit indiStatusChanged(m_indiStatus); 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 Manager::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(QList() << 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 Manager::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->setCamera(Options::defaultCaptureCCD()); if (rc == false && primaryCCD.isEmpty() == false) captureProcess->setCamera(primaryCCD); initFocus(); focusProcess->addCCD(ccdDevice); rc = false; if (Options::defaultFocusCCD().isEmpty() == false) rc = focusProcess->setCamera(Options::defaultFocusCCD()); if (rc == false && primaryCCD.isEmpty() == false) focusProcess->setCamera(primaryCCD); initAlign(); alignProcess->addCCD(ccdDevice); rc = false; if (Options::defaultAlignCCD().isEmpty() == false) rc = alignProcess->setCamera(Options::defaultAlignCCD()); if (rc == false && primaryCCD.isEmpty() == false) alignProcess->setCamera(primaryCCD); initGuide(); guideProcess->addCamera(ccdDevice); rc = false; if (Options::defaultGuideCCD().isEmpty() == false) rc = guideProcess->setCamera(Options::defaultGuideCCD()); if (rc == false && guiderCCD.isEmpty() == false) guideProcess->setCamera(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 Manager::setFilter(ISD::GDInterface * filterDevice) { // No duplicates if (findDevices(KSTARS_FILTER).contains(filterDevice) == false) 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->setFilterWheel(Options::defaultAlignFW()); } void Manager::setFocuser(ISD::GDInterface * focuserDevice) { // No duplicates if (findDevices(KSTARS_FOCUSER).contains(focuserDevice) == false) 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 Manager::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 Manager::setWeather(ISD::GDInterface * weatherDevice) { managedDevices[KSTARS_WEATHER] = weatherDevice; initWeather(); weatherProcess->setWeather(weatherDevice); appendLogText(i18n("%1 is online.", weatherDevice->getDeviceName())); } void Manager::setDustCap(ISD::GDInterface * dustCapDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(dustCapDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, dustCapDevice); initDustCap(); dustCapProcess->setDustCap(dustCapDevice); appendLogText(i18n("%1 is online.", dustCapDevice->getDeviceName())); if (captureProcess.get() != nullptr) captureProcess->setDustCap(dustCapDevice); } void Manager::setLightBox(ISD::GDInterface * lightBoxDevice) { // No duplicates if (findDevices(KSTARS_AUXILIARY).contains(lightBoxDevice) == false) managedDevices.insertMulti(KSTARS_AUXILIARY, lightBoxDevice); if (captureProcess.get() != nullptr) captureProcess->setLightBox(lightBoxDevice); } void Manager::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 Manager::processNewText(ITextVectorProperty * tvp) { if (!strcmp(tvp->name, "FILTER_NAME")) { ekosLiveClient.get()->message()->sendFilterWheels(); } } void Manager::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 Manager::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") || !strcmp(prop->getName(), "CCD_GAIN") || !strcmp(prop->getName(), "CCD_CONTROLS")) { ekosLiveClient.get()->message()->sendCameras(); ekosLiveClient.get()->media()->registerCameras(); } if (!strcmp(prop->getName(), "ABS_DOME_POSITION") || !strcmp(prop->getName(), "DOME_ABORT_MOTION") || !strcmp(prop->getName(), "DOME_PARK")) { ekosLiveClient.get()->message()->sendDomes(); } if (!strcmp(prop->getName(), "CAP_PARK") || !strcmp(prop->getName(), "FLAT_LIGHT_CONTROL")) { ekosLiveClient.get()->message()->sendCaps(); } if (!strcmp(prop->getName(), "FILTER_NAME")) ekosLiveClient.get()->message()->sendFilterWheels(); if (!strcmp(prop->getName(), "FILTER_NAME")) filterManager.data()->initFilterProperties(); + if (!strcmp(prop->getName(), "CONFIRM_FILTER_SET")) + filterManager.data()->initFilterProperties(); + 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->setCamera(Options::defaultGuideCCD()); if (rc == false) guideProcess->setCamera(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 Manager::findDevices(DeviceFamily type) { QList deviceList; QMapIterator i(managedDevices); while (i.hasNext()) { i.next(); if (i.key() == type) deviceList.append(i.value()); } return deviceList; } void Manager::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 Manager::updateLog() { //if (enableLoggingCheck->isChecked() == false) //return; QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) ekosLogOut->setPlainText(m_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 Manager::appendLogText(const QString &text) { m_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; emit newLog(text); updateLog(); } void Manager::clearLog() { QWidget * currentWidget = toolsWidget->currentWidget(); if (currentWidget == setupTab) { m_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 Manager::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(), &Ekos::Capture::newLog, this, &Ekos::Manager::updateLog); connect(captureProcess.get(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus); connect(captureProcess.get(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress); connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString & filename, const QString & previewFITS) { if (Options::useSummaryPreview() && QFile::exists(filename)) { if (Options::autoImageToFITS()) { if (previewFITS.isEmpty() == false) summaryPreview->loadFITS(previewFITS); } else summaryPreview->loadFITS(filename); } }); connect(captureProcess.get(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress); 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 (managedDevices.contains(KSTARS_DOME)) { captureProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { captureProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Capture"); } void Manager::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(), &Ekos::Align::newLog, this, &Ekos::Manager::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 (managedDevices.contains(KSTARS_DOME)) { alignProcess->setDome(managedDevices[KSTARS_DOME]); } if (managedDevices.contains(KSTARS_ROTATOR)) { alignProcess->setRotator(managedDevices[KSTARS_ROTATOR]); } connectModules(); emit newModule("Align"); } void Manager::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")); // Focus <---> Manager connections connect(focusProcess.get(), &Ekos::Focus::newLog, this, &Ekos::Manager::updateLog); connect(focusProcess.get(), &Ekos::Focus::newStatus, this, &Ekos::Manager::setFocusStatus); connect(focusProcess.get(), &Ekos::Focus::newStarPixmap, this, &Ekos::Manager::updateFocusStarPixmap); connect(focusProcess.get(), &Ekos::Focus::newProfilePixmap, this, &Ekos::Manager::updateFocusProfilePixmap); connect(focusProcess.get(), &Ekos::Focus::newHFR, this, &Ekos::Manager::updateCurrentHFR); // Focus <---> Filter Manager connections focusProcess->setFilterManager(filterManager); connect(filterManager.data(), &Ekos::FilterManager::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::newStatus, filterManager.data(), &Ekos::FilterManager::setFocusStatus, Qt::UniqueConnection); connect(filterManager.data(), &Ekos::FilterManager::newFocusOffset, focusProcess.get(), &Ekos::Focus::adjustFocusOffset, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::focusPositionAdjusted, filterManager.data(), &Ekos::FilterManager::setFocusOffsetComplete, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::absolutePositionChanged, filterManager.data(), &Ekos::FilterManager::setFocusAbsolutePosition, 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); } connectModules(); emit newModule("Focus"); } void Manager::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 Manager::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 Manager::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(), &Ekos::Mount::newLog, this, &Ekos::Manager::updateLog); connect(mountProcess.get(), &Ekos::Mount::newCoords, this, &Ekos::Manager::updateMountCoords); connect(mountProcess.get(), &Ekos::Mount::newStatus, this, &Ekos::Manager::updateMountStatus); 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); connectModules(); emit newModule("Mount"); } void Manager::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(), &Ekos::Guide::newLog, this, &Ekos::Manager::updateLog); guideGroup->setEnabled(true); if (!guidePI) { guidePI = new QProgressIndicator(guideProcess.get()); guideStatusLayout->insertWidget(0, guidePI); } connect(guideProcess.get(), &Ekos::Guide::newStatus, this, &Ekos::Manager::updateGuideStatus); connect(guideProcess.get(), &Ekos::Guide::newStarPixmap, this, &Ekos::Manager::updateGuideStarPixmap); connect(guideProcess.get(), &Ekos::Guide::newProfilePixmap, this, &Ekos::Manager::updateGuideProfilePixmap); connect(guideProcess.get(), &Ekos::Guide::newAxisSigma, this, &Ekos::Manager::updateSigmas); 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); } } connectModules(); emit newModule("Guide"); } void Manager::initDome() { if (domeProcess.get() != nullptr) return; domeProcess.reset(new Ekos::Dome()); connect(domeProcess.get(), &Ekos::Dome::newStatus, [&](ISD::Dome::Status newStatus) { QJsonObject status = { { "status", ISD::Dome::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); connect(domeProcess.get(), &Ekos::Dome::azimuthPositionChanged, [&](double pos) { QJsonObject status = { { "az", pos} }; ekosLiveClient.get()->message()->updateDomeStatus(status); }); emit newModule("Dome"); ekosLiveClient->message()->sendDomes(); } void Manager::initWeather() { if (weatherProcess.get() != nullptr) return; weatherProcess.reset(new Ekos::Weather()); emit newModule("Weather"); } void Manager::initDustCap() { if (dustCapProcess.get() != nullptr) return; dustCapProcess.reset(new Ekos::DustCap()); connect(dustCapProcess.get(), &Ekos::DustCap::newStatus, [&](ISD::DustCap::Status newStatus) { QJsonObject status = { { "status", ISD::DustCap::getStatusString(newStatus)} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightToggled, [&](bool enabled) { QJsonObject status = { { "lightS", enabled} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); connect(dustCapProcess.get(), &Ekos::DustCap::lightIntensityChanged, [&](uint16_t value) { QJsonObject status = { { "lightB", value} }; ekosLiveClient.get()->message()->updateCapStatus(status); }); emit newModule("DustCap"); ekosLiveClient->message()->sendCaps(); } void Manager::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 Manager::removeTabs() { disconnect(toolsWidget, &QTabWidget::currentChanged, this, &Ekos::Manager::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, &QTabWidget::currentChanged, this, &Ekos::Manager::processTabChange, Qt::UniqueConnection); } bool Manager::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 Manager::addObjectToScheduler(SkyObject * object) { if (schedulerProcess.get() != nullptr) schedulerProcess->addObject(object); } QString Manager::getCurrentJobName() { return schedulerProcess->getCurrentJobName(); } bool Manager::setProfile(const QString &profileName) { int index = profileCombo->findText(profileName); if (index < 0) return false; profileCombo->setCurrentIndex(index); return true; } void Manager::editNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); setProfile(profileInfo["name"].toString()); currentProfile = getCurrentProfile(); editor.setPi(currentProfile); editor.setSettings(profileInfo); editor.saveProfile(); } void Manager::addNamedProfile(const QJsonObject &profileInfo) { ProfileEditor editor(this); editor.setSettings(profileInfo); editor.saveProfile(); profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); currentProfile = getCurrentProfile(); } void Manager::deleteNamedProfile(const QString &name) { currentProfile = getCurrentProfile(); for (auto &pi : profiles) { // Do not delete an actively running profile // Do not delete simulator profile if (pi->name == "Simulators" || pi->name != name || (pi.get() == currentProfile && ekosStatus() != Idle)) continue; KStarsData::Instance()->userdb()->DeleteProfile(pi.get()); profiles.clear(); loadProfiles(); currentProfile = getCurrentProfile(); return; } } QJsonObject Manager::getNamedProfile(const QString &name) { QJsonObject profileInfo; // Get current profile for (auto &pi : profiles) { if (name == pi->name) return pi->toJson(); } return QJsonObject(); } QStringList Manager::getProfiles() { QStringList profiles; for (int i = 0; i < profileCombo->count(); i++) profiles << profileCombo->itemText(i); return profiles; } void Manager::addProfile() { ProfileEditor editor(this); if (editor.exec() == QDialog::Accepted) { profiles.clear(); loadProfiles(); profileCombo->setCurrentIndex(profileCombo->count() - 1); } currentProfile = getCurrentProfile(); } void Manager::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 Manager::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 Manager::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 * Manager::getCurrentProfile() { ProfileInfo * currProfile = nullptr; // Get current profile for (auto &pi : profiles) { if (profileCombo->currentText() == pi->name) { currProfile = pi.get(); break; } } return currProfile; } void Manager::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 Manager::updateMountStatus(ISD::Telescope::Status status) { static ISD::Telescope::Status lastStatus = ISD::Telescope::MOUNT_IDLE; if (status == lastStatus) return; lastStatus = status; mountStatus->setText(dynamic_cast(managedDevices[KSTARS_TELESCOPE])->getStatusString(status)); mountStatus->setStyleSheet(QString()); 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; case ISD::Telescope::MOUNT_PARKED: mountStatus->setStyleSheet("font-weight:bold;background-color:red;border:2px solid black;"); if (mountPI->isAnimated()) mountPI->stopAnimation(); break; default: if (mountPI->isAnimated()) mountPI->stopAnimation(); } QJsonObject cStatus = { {"status", mountStatus->text()} }; ekosLiveClient.get()->message()->updateMountStatus(cStatus); } void Manager::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 Manager::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 Manager::updateCaptureProgress(Ekos::SequenceJob * job) { // Image is set to nullptr only on initial capture start up int completed = job->getCompleted(); // if (job->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) // completed = job->getCompleted() + 1; // else // completed = job->isPreview() ? 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); if (job->getStatus() == SequenceJob::JOB_BUSY) { QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegularExpression("[-{}]")); // FITSView *image = job->getActiveChip()->getImageView(FITS_NORMAL); // ekosLiveClient.get()->media()->sendPreviewImage(image, uuid); // ekosLiveClient.get()->cloud()->sendPreviewImage(image, uuid); QString filename = job->property("filename").toString(); ekosLiveClient.get()->media()->sendPreviewImage(filename, uuid); if (job->isPreview() == false) ekosLiveClient.get()->cloud()->sendPreviewImage(filename, uuid); } } void Manager::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 Manager::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 Manager::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 Manager::updateFocusProfilePixmap(QPixmap &profilePixmap) { if (profilePixmap.isNull()) return; focusProfileImage->setPixmap(profilePixmap); } void Manager::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 Manager::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 Manager::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 Manager::updateGuideProfilePixmap(QPixmap &profilePix) { if (profilePix.isNull()) return; guideProfileImage->setPixmap(profilePix); } void Manager::setTarget(SkyObject * o) { mountTarget->setText(o->name()); ekosLiveClient.get()->message()->updateMountStatus(QJsonObject({{"target", o->name()}})); } void Manager::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 Manager::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 Manager::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 Manager::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 Manager::announceEvent(const QString &message, KSNotification::EventType event) { ekosLiveClient.get()->message()->sendEvent(message, event); } void Manager::connectModules() { // Guide <---> Capture connections if (captureProcess.get() && guideProcess.get()) { captureProcess.get()->disconnect(guideProcess.get()); guideProcess.get()->disconnect(captureProcess.get()); // Guide Limits connect(guideProcess.get(), &Ekos::Guide::newStatus, captureProcess.get(), &Ekos::Capture::setGuideStatus, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::newAxisDelta, captureProcess.get(), &Ekos::Capture::setGuideDeviation); // Dithering connect(captureProcess.get(), &Ekos::Capture::newStatus, guideProcess.get(), &Ekos::Guide::setCaptureStatus, Qt::UniqueConnection); // Guide Head connect(captureProcess.get(), &Ekos::Capture::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); connect(guideProcess.get(), &Ekos::Guide::guideChipUpdated, captureProcess.get(), &Ekos::Capture::setGuideChip, Qt::UniqueConnection); // Meridian Flip connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, guideProcess.get(), &Ekos::Guide::abort, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, guideProcess.get(), [&]() { if (Options::resetGuideCalibration()) guideProcess->clearCalibration(); guideProcess->guide(); }); } // Guide <---> Mount connections if (guideProcess.get() && mountProcess.get()) { // Parking connect(mountProcess.get(), &Ekos::Mount::newStatus, guideProcess.get(), &Ekos::Guide::setMountStatus, Qt::UniqueConnection); } // Focus <---> Guide connections if (guideProcess.get() && focusProcess.get()) { // Suspend connect(focusProcess.get(), &Ekos::Focus::suspendGuiding, guideProcess.get(), &Ekos::Guide::suspend, Qt::UniqueConnection); connect(focusProcess.get(), &Ekos::Focus::resumeGuiding, guideProcess.get(), &Ekos::Guide::resume, Qt::UniqueConnection); } // Capture <---> Focus connections if (captureProcess.get() && focusProcess.get()) { // Check focus HFR value connect(captureProcess.get(), &Ekos::Capture::checkFocus, focusProcess.get(), &Ekos::Focus::checkFocus, Qt::UniqueConnection); // Reset Focus connect(captureProcess.get(), &Ekos::Capture::resetFocus, focusProcess.get(), &Ekos::Focus::resetFrame, Qt::UniqueConnection); // New Focus Status connect(focusProcess.get(), &Ekos::Focus::newStatus, captureProcess.get(), &Ekos::Capture::setFocusStatus, Qt::UniqueConnection); // New Focus HFR connect(focusProcess.get(), &Ekos::Focus::newHFR, captureProcess.get(), &Ekos::Capture::setHFR, Qt::UniqueConnection); } // Capture <---> Align connections if (captureProcess.get() && alignProcess.get()) { // Alignment flag connect(alignProcess.get(), &Ekos::Align::newStatus, captureProcess.get(), &Ekos::Capture::setAlignStatus, Qt::UniqueConnection); // Solver data connect(alignProcess.get(), &Ekos::Align::newSolverResults, captureProcess.get(), &Ekos::Capture::setAlignResults, Qt::UniqueConnection); // Capture Status connect(captureProcess.get(), &Ekos::Capture::newStatus, alignProcess.get(), &Ekos::Align::setCaptureStatus, Qt::UniqueConnection); } // Capture <---> Mount connections if (captureProcess.get() && mountProcess.get()) { // Meridian Flip Setup Values connect(captureProcess.get(), &Ekos::Capture::newMeridianFlipSetup, mountProcess.get(), &Ekos::Mount::setMeridianFlipValues, Qt::UniqueConnection); connect(mountProcess.get(), &Ekos::Mount::newMeridianFlipSetup, captureProcess.get(), &Ekos::Capture::setMeridianFlipValues, Qt::UniqueConnection); // Meridian Flip states connect(captureProcess.get(), &Ekos::Capture::meridianFlipStarted, mountProcess.get(), &Ekos::Mount::disableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::meridianFlipCompleted, mountProcess.get(), &Ekos::Mount::enableAltLimits, Qt::UniqueConnection); connect(captureProcess.get(), &Ekos::Capture::newMeridianFlipStatus, mountProcess.get(), &Ekos::Mount::meridianFlipStatusChanged, Qt::UniqueConnection); connect(mountProcess.get(), &Ekos::Mount::newMeridianFlipStatus, captureProcess.get(), &Ekos::Capture::meridianFlipStatusChanged, Qt::UniqueConnection); // Mount Status connect(mountProcess.get(), &Ekos::Mount::newStatus, captureProcess.get(), &Ekos::Capture::setMountStatus, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Focus <---> Mount connections if (focusProcess.get() && mountProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, focusProcess.get(), &Ekos::Focus::setMountStatus, Qt::UniqueConnection); } // Mount <---> Align connections if (mountProcess.get() && alignProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::newStatus, alignProcess.get(), &Ekos::Align::setMountStatus, Qt::UniqueConnection); } // Mount <---> Guide connections if (mountProcess.get() && guideProcess.get()) { connect(mountProcess.get(), &Ekos::Mount::pierSideChanged, guideProcess.get(), &Ekos::Guide::setPierSide, Qt::UniqueConnection); } // Focus <---> Align connections if (focusProcess.get() && alignProcess.get()) { connect(focusProcess.get(), &Ekos::Focus::newStatus, alignProcess.get(), &Ekos::Align::setFocusStatus, Qt::UniqueConnection); } // Align <--> EkosLive connections if (alignProcess.get() && ekosLiveClient.get()) { alignProcess.get()->disconnect(ekosLiveClient.get()); 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::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); } } void Manager::setEkosLiveConnected(bool enabled) { ekosLiveClient.get()->setConnected(enabled); } void Manager::setEkosLiveConfig(bool onlineService, bool rememberCredentials, bool autoConnect) { ekosLiveClient.get()->setConfig(onlineService, rememberCredentials, autoConnect); } void Manager::setEkosLiveUser(const QString &username, const QString &password) { ekosLiveClient.get()->setUser(username, password); } bool Manager::ekosLiveStatus() { return ekosLiveClient.get()->isConnected(); } } diff --git a/kstars/indi/indicommon.h b/kstars/indi/indicommon.h index a5e7fef6a..4fd5dfd70 100644 --- a/kstars/indi/indicommon.h +++ b/kstars/indi/indicommon.h @@ -1,207 +1,208 @@ /* 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 monitors 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 further 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 functionality, 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, 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 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_CONFIRM_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 diff --git a/kstars/indi/indistd.cpp b/kstars/indi/indistd.cpp index e2f77036a..51462e1b4 100644 --- a/kstars/indi/indistd.cpp +++ b/kstars/indi/indistd.cpp @@ -1,1079 +1,1089 @@ /* INDI STD 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. Handle INDI Standard properties. */ #include "indistd.h" #include "clientmanager.h" #include "driverinfo.h" #include "deviceinfo.h" #include "imageviewer.h" #include "indi_debug.h" #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "skymap.h" #include #include namespace ISD { GDSetCommand::GDSetCommand(INDI_PROPERTY_TYPE inPropertyType, const QString &inProperty, const QString &inElement, QVariant qValue, QObject *parent) : QObject(parent) { propType = inPropertyType; indiProperty = inProperty; indiElement = inElement; elementValue = qValue; } GenericDevice::GenericDevice(DeviceInfo &idv) { deviceInfo = &idv; driverInfo = idv.getDriverInfo(); baseDevice = idv.getBaseDevice(); clientManager = driverInfo->getClientManager(); dType = KSTARS_UNKNOWN; registerDBusType(); } void GenericDevice::registerDBusType() { #ifndef KSTARS_LITE static bool isRegistered = false; if (isRegistered == false) { qRegisterMetaType("ISD::ParkStatus"); qDBusRegisterMetaType(); isRegistered = true; } #endif } const char *GenericDevice::getDeviceName() { return baseDevice->getDeviceName(); } void GenericDevice::registerProperty(INDI::Property *prop) { foreach (INDI::Property *pp, properties) { if (pp == prop) return; } properties.append(prop); emit propertyDefined(prop); // In case driver already started if (!strcmp(prop->getName(), "CONNECTION")) { ISwitchVectorProperty *svp = prop->getSwitch(); if (svp == nullptr) return; // Still connecting/disconnecting... if (svp->s == IPS_BUSY) return; ISwitch *conSP = IUFindSwitch(svp, "CONNECT"); if (conSP == nullptr) return; if (svp->s == IPS_OK && conSP->s == ISS_ON) { connected = true; emit Connected(); createDeviceInit(); } } if (!strcmp(prop->getName(), "DRIVER_INFO")) { ITextVectorProperty *tvp = prop->getText(); if (tvp) { IText *tp = IUFindText(tvp, "DRIVER_INTERFACE"); if (tp) driverInterface = static_cast(atoi(tp->text)); } } else if (!strcmp(prop->getName(), "SYSTEM_PORTS")) { // Check if our current port is set to one of the system ports. This indicates that the port // is not mapped yet to a permenant designation ISwitchVectorProperty *svp = prop->getSwitch(); ITextVectorProperty *port = baseDevice->getText("DEVICE_PORT"); if (svp && port) { - for (int i=0; i < svp->nsp; i++) + for (int i = 0; i < svp->nsp; i++) { if (!strcmp(port->tp[0].text, svp->sp[i].name)) { emit systemPortDetected(); break; } } } } else if (!strcmp(prop->getName(), "TIME_UTC") && Options::useTimeUpdate() && Options::useKStarsSource()) { ITextVectorProperty *tvp = prop->getText(); if (tvp && tvp->p != IP_RO) updateTime(); } else if (!strcmp(prop->getName(), "GEOGRAPHIC_COORD") && Options::useGeographicUpdate() && Options::useKStarsSource()) { INumberVectorProperty *nvp = prop->getNumber(); if (nvp && nvp->p != IP_RO) updateLocation(); } else if (!strcmp(prop->getName(), "WATCHDOG_HEARTBEAT")) { INumberVectorProperty *nvp = prop->getNumber(); if (nvp) { if (watchDogTimer == nullptr) { watchDogTimer = new QTimer(this); connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(resetWatchdog())); } if (connected && nvp->np[0].value > 0) { // Send immediately a heart beat clientManager->sendNewNumber(nvp); //watchDogTimer->start(0); } } } } void GenericDevice::removeProperty(INDI::Property *prop) { properties.removeOne(prop); emit propertyDeleted(prop); } void GenericDevice::processSwitch(ISwitchVectorProperty *svp) { if (!strcmp(svp->name, "CONNECTION")) { ISwitch *conSP = IUFindSwitch(svp, "CONNECT"); if (conSP == nullptr) return; // Still connecting/disconnecting... if (svp->s == IPS_BUSY) return; if (svp->s == IPS_OK && conSP->s == ISS_ON) { connected = true; emit Connected(); createDeviceInit(); if (watchDogTimer != nullptr) { INumberVectorProperty *nvp = baseDevice->getNumber("WATCHDOG_HEARTBEAT"); if (nvp && nvp->np[0].value > 0) { // Send immediately clientManager->sendNewNumber(nvp); //watchDogTimer->start(0); } } } else { connected = false; emit Disconnected(); } } emit switchUpdated(svp); } void GenericDevice::processNumber(INumberVectorProperty *nvp) { QString deviceName = getDeviceName(); uint32_t interface = getDriverInterface(); Q_UNUSED(interface); if (!strcmp(nvp->name, "GEOGRAPHIC_COORD") && nvp->s == IPS_OK && - ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) || - (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)))) + ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) || + (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)))) { // Update KStars Location once we receive update from INDI, if the source is set to DEVICE dms lng, lat; - double elev=0; + double elev = 0; INumber *np = nullptr; np = IUFindNumber(nvp, "LONG"); if (!np) return; // INDI Longitude convention is 0 to 360. We need to turn it back into 0 to 180 EAST, 0 to -180 WEST if (np->value < 180) lng.setD(np->value); else lng.setD(np->value - 360.0); np = IUFindNumber(nvp, "LAT"); if (!np) return; lat.setD(np->value); // Double check we have valid values - if (lng.Degrees() == 0 && lat.Degrees() ==0) + if (lng.Degrees() == 0 && lat.Degrees() == 0) { qCWarning(KSTARS_INDI) << "Ignoring invalid device coordinates."; return; } np = IUFindNumber(nvp, "ELEV"); if (np) elev = np->value; GeoLocation *geo = KStars::Instance()->data()->geo(); std::unique_ptr tempGeo; QString newLocationName; if (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE) newLocationName = i18n("GPS Location"); else newLocationName = i18n("Mount Location"); if (geo->name() != newLocationName) { double TZ0 = geo->TZ0(); TimeZoneRule *rule = geo->tzrule(); tempGeo.reset(new GeoLocation(lng, lat, newLocationName, "", "", TZ0, rule, elev)); geo = tempGeo.get(); } else { geo->setLong(lng); geo->setLat(lat); } qCInfo(KSTARS_INDI) << "Setting location from device:" << deviceName << "Longitude:" << lng.toDMSString() << "Latitude:" << lat.toDMSString(); KStars::Instance()->data()->setLocation(*geo); } else if (!strcmp(nvp->name, "WATCHDOG_HEARTBEAT")) { if (watchDogTimer == nullptr) { watchDogTimer = new QTimer(this); connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(resetWatchdog())); } if (connected && nvp->np[0].value > 0) { // Reset timer 15 seconds before it is due watchDogTimer->start(nvp->np[0].value * 60 * 1000 - 15 * 1000); } else if (nvp->np[0].value == 0) watchDogTimer->stop(); } emit numberUpdated(nvp); } void GenericDevice::processText(ITextVectorProperty *tvp) { // Update KStars time once we receive update from INDI, if the source is set to DEVICE if (!strcmp(tvp->name, "TIME_UTC") && tvp->s == IPS_OK && - ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) || - (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)))) + ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) || + (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)))) { IText *tp = nullptr; int d, m, y, min, sec, hour; float utcOffset; QDate indiDate; QTime indiTime; tp = IUFindText(tvp, "UTC"); if (!tp) { qCWarning(KSTARS_INDI) << "UTC property missing from TIME_UTC"; return; } sscanf(tp->text, "%d%*[^0-9]%d%*[^0-9]%dT%d%*[^0-9]%d%*[^0-9]%d", &y, &m, &d, &hour, &min, &sec); indiDate.setDate(y, m, d); indiTime.setHMS(hour, min, sec); KStarsDateTime indiDateTime(QDateTime(indiDate, indiTime, Qt::UTC)); tp = IUFindText(tvp, "OFFSET"); if (!tp) { qCWarning(KSTARS_INDI) << "Offset property missing from TIME_UTC"; return; } sscanf(tp->text, "%f", &utcOffset); qCInfo(KSTARS_INDI) << "Setting UTC time from device:" << getDeviceName() << indiDateTime.toString(); KStars::Instance()->data()->changeDateTime(indiDateTime); KStars::Instance()->data()->syncLST(); GeoLocation *geo = KStars::Instance()->data()->geo(); if (geo->tzrule()) utcOffset -= geo->tzrule()->deltaTZ(); // TZ0 is the timezone WTIHOUT any DST offsets. Above, we take INDI UTC Offset (with DST already included) // and subtract from it the deltaTZ from the current TZ rule. geo->setTZ0(utcOffset); } emit textUpdated(tvp); } void GenericDevice::processLight(ILightVectorProperty *lvp) { emit lightUpdated(lvp); } void GenericDevice::processMessage(int messageID) { emit messageUpdated(messageID); } void GenericDevice::processBLOB(IBLOB *bp) { // Ignore write-only BLOBs since we only receive it for state-change if (bp->bvp->p == IP_WO) return; QFile *data_file = nullptr; INDIDataTypes dataType; if (!strcmp(bp->format, ".ascii")) dataType = DATA_ASCII; else dataType = DATA_OTHER; QString currentDir = Options::fitsDir(); int nr, n = 0; if (currentDir.endsWith('/')) currentDir.truncate(sizeof(currentDir) - 1); QString filename(currentDir + '/'); QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); filename += QString("%1_").arg(bp->label) + ts + QString(bp->format).trimmed(); strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME); bp->aux2 = BLOBFilename; if (dataType == DATA_ASCII) { if (bp->aux0 == nullptr) { bp->aux0 = new int(); QFile *ascii_data_file = new QFile(); ascii_data_file->setFileName(filename); if (!ascii_data_file->open(QIODevice::WriteOnly)) { qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << ascii_data_file->fileName() << endl; return; } bp->aux1 = ascii_data_file; } data_file = (QFile *)bp->aux1; QDataStream out(data_file); for (nr = 0; nr < (int)bp->size; nr += n) n = out.writeRawData(static_cast(bp->blob) + nr, bp->size - nr); out.writeRawData((const char *)"\n", 1); data_file->flush(); } else { QFile fits_temp_file(filename); if (!fits_temp_file.open(QIODevice::WriteOnly)) { qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName() << endl; return; } QDataStream out(&fits_temp_file); for (nr = 0; nr < (int)bp->size; nr += n) n = out.writeRawData(static_cast(bp->blob) + nr, bp->size - nr); fits_temp_file.close(); QByteArray fmt = QString(bp->format).toLower().remove('.').toUtf8(); if (QImageReader::supportedImageFormats().contains(fmt)) { QUrl url(filename); url.setScheme("file"); ImageViewer *iv = new ImageViewer(url, QString(), KStars::Instance()); if (iv) iv->show(); } } if (dataType == DATA_OTHER) KStars::Instance()->statusBar()->showMessage(i18n("Data file saved to %1", filename), 0); emit BLOBUpdated(bp); } bool GenericDevice::setConfig(INDIConfig tConfig) { ISwitchVectorProperty *svp = baseDevice->getSwitch("CONFIG_PROCESS"); if (svp == nullptr) return false; ISwitch *sp = nullptr; IUResetSwitch(svp); switch (tConfig) { case LOAD_LAST_CONFIG: sp = IUFindSwitch(svp, "CONFIG_LOAD"); if (sp == nullptr) return false; IUResetSwitch(svp); sp->s = ISS_ON; break; case SAVE_CONFIG: sp = IUFindSwitch(svp, "CONFIG_SAVE"); if (sp == nullptr) return false; IUResetSwitch(svp); sp->s = ISS_ON; break; case LOAD_DEFAULT_CONFIG: sp = IUFindSwitch(svp, "CONFIG_DEFAULT"); if (sp == nullptr) return false; IUResetSwitch(svp); sp->s = ISS_ON; break; } clientManager->sendNewSwitch(svp); return true; } void GenericDevice::createDeviceInit() { if (Options::showINDIMessages()) KStars::Instance()->statusBar()->showMessage(i18n("%1 is online.", baseDevice->getDeviceName()), 0); KStars::Instance()->map()->forceUpdateNow(); } /*********************************************************************************/ /* Update the Driver's Time */ /*********************************************************************************/ void GenericDevice::updateTime() { QString offset, isoTS; offset = QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2); //QTime newTime( KStars::Instance()->data()->ut().time()); //QDate newDate( KStars::Instance()->data()->ut().date()); //isoTS = QString("%1-%2-%3T%4:%5:%6").arg(newDate.year()).arg(newDate.month()).arg(newDate.day()).arg(newTime.hour()).arg(newTime.minute()).arg(newTime.second()); isoTS = KStars::Instance()->data()->ut().toString(Qt::ISODate).remove('Z'); /* Update Date/Time */ ITextVectorProperty *timeUTC = baseDevice->getText("TIME_UTC"); if (timeUTC) { IText *timeEle = IUFindText(timeUTC, "UTC"); if (timeEle) IUSaveText(timeEle, isoTS.toLatin1().constData()); IText *offsetEle = IUFindText(timeUTC, "OFFSET"); if (offsetEle) IUSaveText(offsetEle, offset.toLatin1().constData()); if (timeEle && offsetEle) clientManager->sendNewText(timeUTC); } } /*********************************************************************************/ /* Update the Driver's Geographical Location */ /*********************************************************************************/ void GenericDevice::updateLocation() { GeoLocation *geo = KStars::Instance()->data()->geo(); double longNP; if (geo->lng()->Degrees() >= 0) longNP = geo->lng()->Degrees(); else longNP = dms(geo->lng()->Degrees() + 360.0).Degrees(); INumberVectorProperty *nvp = baseDevice->getNumber("GEOGRAPHIC_COORD"); if (nvp == nullptr) return; INumber *np = IUFindNumber(nvp, "LONG"); if (np == nullptr) return; np->value = longNP; np = IUFindNumber(nvp, "LAT"); if (np == nullptr) return; np->value = geo->lat()->Degrees(); np = IUFindNumber(nvp, "ELEV"); if (np == nullptr) return; np->value = geo->elevation(); clientManager->sendNewNumber(nvp); } bool GenericDevice::Connect() { return runCommand(INDI_CONNECT, nullptr); } bool GenericDevice::Disconnect() { return runCommand(INDI_DISCONNECT, nullptr); } bool GenericDevice::runCommand(int command, void *ptr) { switch (command) { case INDI_CONNECT: clientManager->connectDevice(baseDevice->getDeviceName()); break; case INDI_DISCONNECT: clientManager->disconnectDevice(baseDevice->getDeviceName()); break; case INDI_SET_PORT: { if (ptr == nullptr) return false; ITextVectorProperty *tvp = baseDevice->getText("DEVICE_PORT"); if (tvp == nullptr) return false; IText *tp = IUFindText(tvp, "PORT"); IUSaveText(tp, (static_cast(ptr))->toLatin1().constData()); clientManager->sendNewText(tvp); } break; // We do it here because it could be either CCD or FILTER interfaces, so no need to duplicate code case INDI_SET_FILTER: { if (ptr == nullptr) return false; INumberVectorProperty *nvp = baseDevice->getNumber("FILTER_SLOT"); if (nvp == nullptr) return false; int requestedFilter = *((int *)ptr); if (requestedFilter == nvp->np[0].value) break; nvp->np[0].value = requestedFilter; clientManager->sendNewNumber(nvp); } break; + case INDI_CONFIRM_FILTER: + { + ISwitchVectorProperty *svp = baseDevice->getSwitch("CONFIRM_FILTER_SET"); + if (svp == nullptr) + return false; + svp->sp[0].s = ISS_ON; + clientManager->sendNewSwitch(svp); + } + break; + // We do it here because it could be either FOCUSER or ROTATOR interfaces, so no need to duplicate code case INDI_SET_ROTATOR_ANGLE: { if (ptr == nullptr) return false; INumberVectorProperty *nvp = baseDevice->getNumber("ABS_ROTATOR_ANGLE"); if (nvp == nullptr) return false; double requestedAngle = *((double *)ptr); if (requestedAngle == nvp->np[0].value) break; nvp->np[0].value = requestedAngle; clientManager->sendNewNumber(nvp); } break; // We do it here because it could be either FOCUSER or ROTATOR interfaces, so no need to duplicate code case INDI_SET_ROTATOR_TICKS: { if (ptr == nullptr) return false; INumberVectorProperty *nvp = baseDevice->getNumber("ABS_ROTATOR_POSITION"); if (nvp == nullptr) return false; int32_t requestedTicks = *((int32_t *)ptr); if (requestedTicks == nvp->np[0].value) break; nvp->np[0].value = requestedTicks; clientManager->sendNewNumber(nvp); } break; } return true; } bool GenericDevice::setProperty(QObject *setPropCommand) { GDSetCommand *indiCommand = static_cast(setPropCommand); //qDebug() << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue << endl; INDI::Property *pp = baseDevice->getProperty(indiCommand->indiProperty.toLatin1().constData()); if (pp == nullptr) return false; switch (indiCommand->propType) { case INDI_SWITCH: { ISwitchVectorProperty *svp = pp->getSwitch(); if (svp == nullptr) return false; ISwitch *sp = IUFindSwitch(svp, indiCommand->indiElement.toLatin1().constData()); if (sp == nullptr) return false; if (svp->r == ISR_1OFMANY || svp->r == ISR_ATMOST1) IUResetSwitch(svp); sp->s = indiCommand->elementValue.toInt() == 0 ? ISS_OFF : ISS_ON; //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off") << endl; clientManager->sendNewSwitch(svp); return true; } break; case INDI_NUMBER: { INumberVectorProperty *nvp = pp->getNumber(); if (nvp == nullptr) return false; INumber *np = IUFindNumber(nvp, indiCommand->indiElement.toLatin1().constData()); if (np == nullptr) return false; double value = indiCommand->elementValue.toDouble(); if (value == np->value) return true; np->value = value; //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off") << endl; clientManager->sendNewNumber(nvp); } break; // TODO: Add set property for other types of properties default: break; } return true; } bool GenericDevice::getMinMaxStep(const QString &propName, const QString &elementName, double *min, double *max, double *step) { INumberVectorProperty *nvp = baseDevice->getNumber(propName.toLatin1()); if (nvp == nullptr) return false; INumber *np = IUFindNumber(nvp, elementName.toLatin1()); if (np == nullptr) return false; *min = np->min; *max = np->max; *step = np->step; return true; } IPState GenericDevice::getState(const QString &propName) { return baseDevice->getPropertyState(propName.toLatin1().constData()); } IPerm GenericDevice::getPermission(const QString &propName) { return baseDevice->getPropertyPermission(propName.toLatin1().constData()); } INDI::Property *GenericDevice::getProperty(const QString &propName) { for (auto &oneProp : properties) { if (propName == QString(oneProp->getName())) return oneProp; } return nullptr; } void GenericDevice::resetWatchdog() { INumberVectorProperty *nvp = baseDevice->getNumber("WATCHDOG_HEARTBEAT"); if (nvp) // Send heartbeat to driver clientManager->sendNewNumber(nvp); } DeviceDecorator::DeviceDecorator(GDInterface *iPtr) { interfacePtr = iPtr; connect(iPtr, SIGNAL(Connected()), this, SIGNAL(Connected())); connect(iPtr, SIGNAL(Disconnected()), this, SIGNAL(Disconnected())); connect(iPtr, SIGNAL(propertyDefined(INDI::Property*)), this, SIGNAL(propertyDefined(INDI::Property*))); connect(iPtr, SIGNAL(propertyDeleted(INDI::Property*)), this, SIGNAL(propertyDeleted(INDI::Property*))); connect(iPtr, SIGNAL(messageUpdated(int)), this, SIGNAL(messageUpdated(int))); connect(iPtr, SIGNAL(switchUpdated(ISwitchVectorProperty*)), this, SIGNAL(switchUpdated(ISwitchVectorProperty*))); connect(iPtr, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SIGNAL(numberUpdated(INumberVectorProperty*))); connect(iPtr, SIGNAL(textUpdated(ITextVectorProperty*)), this, SIGNAL(textUpdated(ITextVectorProperty*))); connect(iPtr, SIGNAL(BLOBUpdated(IBLOB*)), this, SIGNAL(BLOBUpdated(IBLOB*))); connect(iPtr, SIGNAL(lightUpdated(ILightVectorProperty*)), this, SIGNAL(lightUpdated(ILightVectorProperty*))); baseDevice = interfacePtr->getBaseDevice(); clientManager = interfacePtr->getDriverInfo()->getClientManager(); } DeviceDecorator::~DeviceDecorator() { delete (interfacePtr); } bool DeviceDecorator::runCommand(int command, void *ptr) { return interfacePtr->runCommand(command, ptr); } bool DeviceDecorator::setProperty(QObject *setPropCommand) { return interfacePtr->setProperty(setPropCommand); } void DeviceDecorator::processBLOB(IBLOB *bp) { interfacePtr->processBLOB(bp); } void DeviceDecorator::processLight(ILightVectorProperty *lvp) { interfacePtr->processLight(lvp); } void DeviceDecorator::processNumber(INumberVectorProperty *nvp) { interfacePtr->processNumber(nvp); } void DeviceDecorator::processSwitch(ISwitchVectorProperty *svp) { interfacePtr->processSwitch(svp); } void DeviceDecorator::processText(ITextVectorProperty *tvp) { interfacePtr->processText(tvp); } void DeviceDecorator::processMessage(int messageID) { interfacePtr->processMessage(messageID); } void DeviceDecorator::registerProperty(INDI::Property *prop) { interfacePtr->registerProperty(prop); } void DeviceDecorator::removeProperty(INDI::Property *prop) { interfacePtr->removeProperty(prop); } bool DeviceDecorator::setConfig(INDIConfig tConfig) { return interfacePtr->setConfig(tConfig); } DeviceFamily DeviceDecorator::getType() { return interfacePtr->getType(); } DriverInfo *DeviceDecorator::getDriverInfo() { return interfacePtr->getDriverInfo(); } DeviceInfo *DeviceDecorator::getDeviceInfo() { return interfacePtr->getDeviceInfo(); } const char *DeviceDecorator::getDeviceName() { return interfacePtr->getDeviceName(); } INDI::BaseDevice *DeviceDecorator::getBaseDevice() { return interfacePtr->getBaseDevice(); } uint32_t DeviceDecorator::getDriverInterface() { return interfacePtr->getDriverInterface(); } QList DeviceDecorator::getProperties() { return interfacePtr->getProperties(); } INDI::Property *DeviceDecorator::getProperty(const QString &propName) { return interfacePtr->getProperty(propName); } bool DeviceDecorator::isConnected() { return interfacePtr->isConnected(); } bool DeviceDecorator::Connect() { return interfacePtr->Connect(); } bool DeviceDecorator::Disconnect() { return interfacePtr->Disconnect(); } bool DeviceDecorator::getMinMaxStep(const QString &propName, const QString &elementName, double *min, double *max, double *step) { return interfacePtr->getMinMaxStep(propName, elementName, min, max, step); } IPState DeviceDecorator::getState(const QString &propName) { return interfacePtr->getState(propName); } IPerm DeviceDecorator::getPermission(const QString &propName) { return interfacePtr->getPermission(propName); } ST4::ST4(INDI::BaseDevice *bdv, ClientManager *cm) { baseDevice = bdv; clientManager = cm; } const char *ST4::getDeviceName() { return baseDevice->getDeviceName(); } void ST4::setDECSwap(bool enable) { swapDEC = enable; } bool ST4::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { bool raOK = false, decOK = false; raOK = doPulse(ra_dir, ra_msecs); decOK = doPulse(dec_dir, dec_msecs); if (raOK && decOK) return true; else return false; } bool ST4::doPulse(GuideDirection dir, int msecs) { INumberVectorProperty *raPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_WE"); INumberVectorProperty *decPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_NS"); INumberVectorProperty *npulse = nullptr; INumber *dirPulse = nullptr; if (raPulse == nullptr || decPulse == nullptr) return false; if (dir == RA_INC_DIR || dir == RA_DEC_DIR) raPulse->np[0].value = raPulse->np[1].value = 0; else decPulse->np[0].value = decPulse->np[1].value = 0; switch (dir) { case RA_INC_DIR: dirPulse = IUFindNumber(raPulse, "TIMED_GUIDE_W"); if (dirPulse == nullptr) return false; npulse = raPulse; break; case RA_DEC_DIR: dirPulse = IUFindNumber(raPulse, "TIMED_GUIDE_E"); if (dirPulse == nullptr) return false; npulse = raPulse; break; case DEC_INC_DIR: if (swapDEC) dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_S"); else dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_N"); if (dirPulse == nullptr) return false; npulse = decPulse; break; case DEC_DEC_DIR: if (swapDEC) dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_N"); else dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_S"); if (dirPulse == nullptr) return false; npulse = decPulse; break; default: return false; } dirPulse->value = msecs; clientManager->sendNewNumber(npulse); //qDebug() << "Sending pulse for " << npulse->name << " in direction " << dirPulse->name << " for " << msecs << " ms " << endl; return true; } } #ifndef KSTARS_LITE -QDBusArgument &operator<<(QDBusArgument &argument, const ISD::ParkStatus& source) +QDBusArgument &operator<<(QDBusArgument &argument, const ISD::ParkStatus &source) { argument.beginStructure(); argument << static_cast(source); argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::ParkStatus &dest) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); dest = static_cast(a); return argument; } #endif