diff --git a/kstars/CMakeLists.txt b/kstars/CMakeLists.txt --- a/kstars/CMakeLists.txt +++ b/kstars/CMakeLists.txt @@ -127,6 +127,7 @@ ekos/guide/opscalibration.ui ekos/guide/opsguide.ui ekos/guide/manualdither.ui + ekos/observatory/observatory.ui #TODO remove from GIT #ekos/guide/guider.ui #ekos/guide/rcalibration.ui @@ -203,6 +204,12 @@ ekos/guide/externalguide/phd2.cpp ekos/guide/externalguide/linguider.cpp + #Observatory + ekos/observatory/observatory.cpp + ekos/observatory/observatorymodel.cpp + ekos/observatory/observatorydomemodel.cpp + ekos/observatory/observatoryweathermodel.cpp + # Ekos Live ekos/ekoslive/ekosliveclient.cpp ekos/ekoslive/message.cpp @@ -939,6 +946,7 @@ ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_guide_debug.h IDENTIFIER KSTARS_EKOS_GUIDE CATEGORY_NAME org.kde.kstars.ekos.guide) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_mount_debug.h IDENTIFIER KSTARS_EKOS_MOUNT CATEGORY_NAME org.kde.kstars.ekos.mount) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_scheduler_debug.h IDENTIFIER KSTARS_EKOS_SCHEDULER CATEGORY_NAME org.kde.kstars.ekos.scheduler) +ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_observatory_debug.h IDENTIFIER KSTARS_EKOS_OBSERVATORY CATEGORY_NAME org.kde.kstars.ekos.observatory) kconfig_add_kcfg_files(kstars_SRCS ${kstars_KCFG_SRCS}) @@ -972,6 +980,7 @@ qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Weather.xml ekos/auxiliary/weather.h Ekos::Weather) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.DustCap.xml ekos/auxiliary/dustcap.h Ekos::DustCap) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Scheduler.xml ekos/scheduler/scheduler.h Ekos::Scheduler) + qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Observatory.xml ekos/observatory/observatory.h Ekos::Observatory) ENDIF () ki18n_wrap_ui(kstars_SRCS diff --git a/kstars/data/icons/ekos_observatory.png b/kstars/data/icons/ekos_observatory.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@icons/kstars_satellites_invisible.svg icons/kstars_satellites_visible.svg icons/cloud-online.svg + icons/ekos_observatory.png kstars.knsrc diff --git a/kstars/ekos/auxiliary/dome.h b/kstars/ekos/auxiliary/dome.h --- a/kstars/ekos/auxiliary/dome.h +++ b/kstars/ekos/auxiliary/dome.h @@ -91,6 +91,9 @@ Q_SCRIPTABLE double azimuthPosition(); Q_SCRIPTABLE void setAzimuthPosition(double position); + Q_SCRIPTABLE bool hasShutter(); + Q_SCRIPTABLE bool controlShutter(bool open); + /** @}*/ /** @@ -106,18 +109,23 @@ void setTelescope(ISD::GDInterface *newTelescope); ISD::Dome::Status status() { return currentDome->status(); } + ISD::Dome::ShutterStatus shutterStatus() { return currentDome->shutterStatus(); } ISD::ParkStatus parkStatus() { return m_ParkStatus; } - signals: +signals: void newStatus(ISD::Dome::Status status); void newParkStatus(ISD::ParkStatus status); + void newShutterStatus(ISD::Dome::ShutterStatus status); void azimuthPositionChanged(double position); void ready(); + // Signal when the underlying ISD::Dome signals a Disconnected() + void disconnected(); private: // Devices needed for Dome operation ISD::Dome *currentDome { nullptr }; ISD::ParkStatus m_ParkStatus { ISD::PARK_UNKNOWN }; + ISD::Dome::ShutterStatus m_ShutterStatus { ISD::Dome::SHUTTER_UNKNOWN }; }; } diff --git a/kstars/ekos/auxiliary/dome.cpp b/kstars/ekos/auxiliary/dome.cpp --- a/kstars/ekos/auxiliary/dome.cpp +++ b/kstars/ekos/auxiliary/dome.cpp @@ -39,8 +39,11 @@ connect(currentDome, &ISD::Dome::newStatus, this, &Dome::newStatus); connect(currentDome, &ISD::Dome::newParkStatus, this, &Dome::newParkStatus); connect(currentDome, &ISD::Dome::newParkStatus, [&](ISD::ParkStatus status) {m_ParkStatus = status;}); + connect(currentDome, &ISD::Dome::newShutterStatus, this, &Dome::newShutterStatus); + connect(currentDome, &ISD::Dome::newShutterStatus, [&](ISD::Dome::ShutterStatus status) {m_ShutterStatus = status;}); connect(currentDome, &ISD::Dome::azimuthPositionChanged, this, &Dome::azimuthPositionChanged); connect(currentDome, &ISD::Dome::ready, this, &Dome::ready); + connect(currentDome, &ISD::Dome::Disconnected, this, &Dome::disconnected); } void Dome::setTelescope(ISD::GDInterface *newTelescope) @@ -121,6 +124,23 @@ currentDome->setAzimuthPosition(position); } +bool Dome::hasShutter() +{ + if (currentDome) + return currentDome->hasShutter(); + // no dome, no shutter + return false; +} + +bool Dome::controlShutter(bool open) +{ + + if (currentDome) + return currentDome->ControlShutter(open); + // no dome, no shutter control + return false; +} + #if 0 Dome::ParkingStatus Dome::getParkingStatus() { diff --git a/kstars/ekos/auxiliary/weather.h b/kstars/ekos/auxiliary/weather.h --- a/kstars/ekos/auxiliary/weather.h +++ b/kstars/ekos/auxiliary/weather.h @@ -70,6 +70,8 @@ */ void newStatus(ISD::Weather::Status status); void ready(); + // Signal when the underlying ISD::Weather signals a Disconnected() + void disconnected(); private: // Devices needed for Weather operation diff --git a/kstars/ekos/auxiliary/weather.cpp b/kstars/ekos/auxiliary/weather.cpp --- a/kstars/ekos/auxiliary/weather.cpp +++ b/kstars/ekos/auxiliary/weather.cpp @@ -35,6 +35,8 @@ currentWeather->disconnect(this); connect(currentWeather, &ISD::Weather::newStatus, this, &Weather::newStatus); connect(currentWeather, &ISD::Weather::ready, this, &Weather::ready); + connect(currentWeather, &ISD::Weather::Connected, this, &Weather::ready); + connect(currentWeather, &ISD::Weather::Disconnected, this, &Weather::disconnected); } ISD::Weather::Status Weather::status() diff --git a/kstars/ekos/manager.h b/kstars/ekos/manager.h --- a/kstars/ekos/manager.h +++ b/kstars/ekos/manager.h @@ -28,6 +28,7 @@ #include "indi/indistd.h" #include "mount/mount.h" #include "scheduler/scheduler.h" +#include "observatory/observatory.h" #include "auxiliary/filtermanager.h" #include "auxiliary/serialportassistant.h" #include "ksnotification.h" @@ -395,6 +396,7 @@ void initMount(); void initDome(); void initWeather(); + void initObservatory(Weather *weather, Dome *dome); void initDustCap(); void loadDrivers(); @@ -443,6 +445,7 @@ std::unique_ptr alignProcess; std::unique_ptr mountProcess; std::unique_ptr schedulerProcess; + std::unique_ptr observatoryProcess; std::unique_ptr domeProcess; std::unique_ptr weatherProcess; std::unique_ptr dustCapProcess; diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp --- a/kstars/ekos/manager.cpp +++ b/kstars/ekos/manager.cpp @@ -431,6 +431,7 @@ alignProcess.reset(); mountProcess.reset(); weatherProcess.reset(); + observatoryProcess.reset(); dustCapProcess.reset(); Ekos::CommunicationStatus previousStatus = m_ekosStatus; @@ -1870,8 +1871,10 @@ ekosLogOut->setPlainText(guideProcess->getLogText()); else if (currentWidget == mountProcess.get()) ekosLogOut->setPlainText(mountProcess->getLogText()); - if (currentWidget == schedulerProcess.get()) + else if (currentWidget == schedulerProcess.get()) ekosLogOut->setPlainText(schedulerProcess->getLogText()); + else if (currentWidget == observatoryProcess.get()) + ekosLogOut->setPlainText(observatoryProcess->getLogText()); #ifdef Q_OS_OSX repaint(); //This is a band-aid for a bug in QT 5.10.0 @@ -2222,6 +2225,7 @@ ekosLiveClient.get()->message()->updateDomeStatus(status); }); + initObservatory(nullptr, domeProcess.get()); emit newModule("Dome"); ekosLiveClient->message()->sendDomes(); @@ -2233,10 +2237,32 @@ return; weatherProcess.reset(new Ekos::Weather()); + initObservatory(weatherProcess.get(), nullptr); emit newModule("Weather"); } +void Manager::initObservatory(Weather *weather, Dome *dome) +{ + if (observatoryProcess.get() == nullptr) + { + // Initialize the Observatory Module + observatoryProcess.reset(new Ekos::Observatory()); + int index = toolsWidget->addTab(observatoryProcess.get(), QIcon(":/icons/ekos_observatory.png"), ""); + toolsWidget->tabBar()->setTabToolTip(index, i18n("Observatory")); + connect(observatoryProcess.get(), &Ekos::Observatory::newLog, this, &Ekos::Manager::updateLog); + } + + Observatory *obs = observatoryProcess.get(); + if (weather != nullptr) + obs->getWeatherModel()->initModel(weather); + if (dome != nullptr) + obs->getDomeModel()->initModel(dome); + + emit newModule("Observatory"); + +} + void Manager::initDustCap() { if (dustCapProcess.get() != nullptr) @@ -2293,6 +2319,7 @@ mountProcess.reset(); domeProcess.reset(); weatherProcess.reset(); + observatoryProcess.reset(); dustCapProcess.reset(); managedDevices.clear(); diff --git a/kstars/ekos/observatory/observatory.h b/kstars/ekos/observatory/observatory.h new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatory.h @@ -0,0 +1,90 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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_observatory.h" +#include "observatorymodel.h" +#include "observatorydomemodel.h" +#include "observatoryweathermodel.h" +#include "indiweather.h" + +#include +#include +#include "klocalizedstring.h" + + +namespace Ekos +{ + +class Observatory : public QWidget, public Ui::Observatory +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Observatory") + Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) + +public: + Observatory(); + ObservatoryDomeModel *getDomeModel() { return mObservatoryModel->getDomeModel(); } + ObservatoryWeatherModel *getWeatherModel() { return mObservatoryModel->getWeatherModel(); } + + // Logging + QStringList logText() { return m_LogText; } + QString getLogText() { return m_LogText.join("\n"); } + +signals: + Q_SCRIPTABLE void newLog(const QString &text); + + /** + * @brief Signal a new observatory status + */ + Q_SCRIPTABLE void newStatus(bool isReady); + +private: + ObservatoryModel *mObservatoryModel = nullptr; + + void setDomeModel(ObservatoryDomeModel *model); + void setWeatherModel(ObservatoryWeatherModel *model); + + // Logging + QStringList m_LogText; + void appendLogText(const QString &); + void clearLog(); + + // timer for refreshing the observatory status + QTimer weatherStatusTimer; + + // reacting on weather changes + void setWarningActions(WeatherActions actions); + void setAlertActions(WeatherActions actions); + +private slots: + // observatory status handling + void setObseratoryStatusControl(ObservatoryStatusControl control); + void statusControlSettingsChanged(); + + void initWeather(); + void shutdownWeather(); + void setWeatherStatus(ISD::Weather::Status status); + + + // reacting on weather changes + void weatherWarningSettingsChanged(); + void weatherAlertSettingsChanged(); + + // reacting on observatory status changes + void observatoryStatusChanged(bool ready); + + void initDome(); + void shutdownDome(); + + void setDomeStatus(ISD::Dome::Status status); + void setShutterStatus(ISD::Dome::ShutterStatus status); +}; +} diff --git a/kstars/ekos/observatory/observatory.cpp b/kstars/ekos/observatory/observatory.cpp new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatory.cpp @@ -0,0 +1,375 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "observatory.h" + +#include "ekos_observatory_debug.h" + +namespace Ekos +{ +Observatory::Observatory() +{ + setupUi(this); + + // status control + mObservatoryModel = new ObservatoryModel(); + setObseratoryStatusControl(mObservatoryModel->statusControl()); + // update UI for status control + connect(useDomeCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged); + connect(useShutterCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged); + connect(useWeatherCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged); + connect(mObservatoryModel, &Ekos::ObservatoryModel::newStatus, this, &Ekos::Observatory::observatoryStatusChanged); + // ready button deactivated + // connect(statusReadyButton, &QPushButton::clicked, mObservatoryModel, &Ekos::ObservatoryModel::makeReady); + statusReadyButton->setEnabled(false); + + setDomeModel(new ObservatoryDomeModel()); + setWeatherModel(new ObservatoryWeatherModel()); + statusDefinitionBox->setVisible(true); + statusDefinitionBox->setEnabled(true); + // make invisible, since not implemented yet + angleLabel->setVisible(false); + domeAngleSpinBox->setVisible(false); + setDomeAngleButton->setVisible(false); + weatherWarningSchedulerCB->setVisible(false); + weatherAlertSchedulerCB->setVisible(false); +} + +void Observatory::setObseratoryStatusControl(ObservatoryStatusControl control) +{ + if (mObservatoryModel != nullptr) + { + useDomeCB->setChecked(control.useDome); + useShutterCB->setChecked(control.useShutter); + useWeatherCB->setChecked(control.useWeather); + } +} + + +void Observatory::setDomeModel(ObservatoryDomeModel *model) +{ + mObservatoryModel->setDomeModel(model); + if (model != nullptr) + { + connect(model, &Ekos::ObservatoryDomeModel::ready, this, &Ekos::Observatory::initDome); + connect(model, &Ekos::ObservatoryDomeModel::disconnected, this, &Ekos::Observatory::shutdownDome); + connect(model, &Ekos::ObservatoryDomeModel::newStatus, this, &Ekos::Observatory::setDomeStatus); + connect(model, &Ekos::ObservatoryDomeModel::newShutterStatus, this, &Ekos::Observatory::setShutterStatus); + + connect(weatherWarningShutterCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged); + connect(weatherWarningDomeCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged); + connect(weatherWarningDelaySB, static_cast(&QSpinBox::valueChanged), [this](int i) { Q_UNUSED(i); weatherWarningSettingsChanged(); }); + + connect(weatherAlertShutterCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged); + connect(weatherAlertDomeCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged); + connect(weatherAlertDelaySB, static_cast(&QSpinBox::valueChanged), [this](int i) { Q_UNUSED(i); weatherAlertSettingsChanged(); }); + } + else + { + shutdownDome(); + disconnect(model, &Ekos::ObservatoryDomeModel::newShutterStatus, this, &Ekos::Observatory::setShutterStatus); + disconnect(model, &Ekos::ObservatoryDomeModel::newStatus, this, &Ekos::Observatory::setDomeStatus); + disconnect(model, &Ekos::ObservatoryDomeModel::ready, this, &Ekos::Observatory::initDome); + disconnect(model, &Ekos::ObservatoryDomeModel::disconnected, this, &Ekos::Observatory::shutdownDome); + + disconnect(weatherWarningShutterCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged); + disconnect(weatherWarningDomeCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged); + connect(weatherWarningDelaySB, static_cast(&QSpinBox::valueChanged), [this](int i) { Q_UNUSED(i); weatherWarningSettingsChanged(); }); + + disconnect(weatherAlertShutterCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged); + disconnect(weatherAlertDomeCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged); + connect(weatherAlertDelaySB, static_cast(&QSpinBox::valueChanged), [this](int i) { Q_UNUSED(i); weatherWarningSettingsChanged(); }); + } +} + +void Observatory::initDome() +{ + domeBox->setEnabled(true); + + if (getDomeModel() != nullptr) + { + connect(getDomeModel(), &Ekos::ObservatoryDomeModel::newLog, this, &Ekos::Observatory::appendLogText); + + if (getDomeModel()->canPark()) + { + connect(domePark, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::park); + connect(domeUnpark, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::unpark); + domePark->setEnabled(true); + domeUnpark->setEnabled(true); + } + else + { + domePark->setEnabled(false); + domeUnpark->setEnabled(false); + } + + if (getDomeModel()->hasShutter()) + { + shutterBox->setVisible(true); + shutterBox->setEnabled(true); + connect(shutterOpen, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::openShutter); + connect(shutterClosed, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::closeShutter); + shutterClosed->setEnabled(true); + shutterOpen->setEnabled(true); + } + else + { + shutterBox->setVisible(false); + weatherWarningShutterCB->setVisible(false); + weatherAlertShutterCB->setVisible(false); + } + + setDomeStatus(getDomeModel()->status()); + setShutterStatus(getDomeModel()->shutterStatus()); + } + +} + +void Observatory::shutdownDome() +{ + domeBox->setEnabled(false); + shutterBox->setEnabled(false); + shutterBox->setVisible(false); + domePark->setEnabled(false); + domeUnpark->setEnabled(false); + shutterClosed->setEnabled(false); + shutterOpen->setEnabled(false); + angleLabel->setEnabled(false); + domeAngleSpinBox->setEnabled(false); + setDomeAngleButton->setEnabled(false); + + disconnect(domePark, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::park); + disconnect(domeUnpark, &QPushButton::clicked, getDomeModel(), &Ekos::ObservatoryDomeModel::unpark); +} + +void Observatory::setDomeStatus(ISD::Dome::Status status) +{ + switch (status) { + case ISD::Dome::DOME_ERROR: + break; + case ISD::Dome::DOME_IDLE: + domePark->setChecked(false); + domePark->setText("PARK"); + domeUnpark->setChecked(true); + domeUnpark->setText("UNPARKED"); + appendLogText("Dome is unparked."); + break; + case ISD::Dome::DOME_MOVING: + appendLogText("Dome is moving..."); + break; + case ISD::Dome::DOME_PARKED: + domePark->setChecked(true); + domePark->setText("PARKED"); + domeUnpark->setChecked(false); + domeUnpark->setText("UNPARK"); + appendLogText("Dome is parked."); + break; + case ISD::Dome::DOME_PARKING: + domePark->setText("PARKING"); + domeUnpark->setText("UNPARK"); + appendLogText("Dome is parking..."); + break; + case ISD::Dome::DOME_UNPARKING: + domePark->setText("PARK"); + domeUnpark->setText("UNPARKING"); + appendLogText("Dome is unparking..."); + break; + case ISD::Dome::DOME_TRACKING: + appendLogText("Dome is tracking."); + break; + default: + break; + } +} + + +void Observatory::setShutterStatus(ISD::Dome::ShutterStatus status) +{ + switch (status) { + case ISD::Dome::SHUTTER_OPEN: + shutterOpen->setChecked(true); + shutterClosed->setChecked(false); + shutterOpen->setText("OPEN"); + shutterClosed->setText("CLOSE"); + appendLogText("Shutter is open."); + break; + case ISD::Dome::SHUTTER_OPENING: + shutterOpen->setText("OPENING"); + shutterClosed->setText("CLOSED"); + appendLogText("Shutter is opening..."); + break; + case ISD::Dome::SHUTTER_CLOSED: + shutterOpen->setChecked(false); + shutterClosed->setChecked(true); + shutterOpen->setText("OPEN"); + shutterClosed->setText("CLOSED"); + appendLogText("Shutter is closed."); + break; + case ISD::Dome::SHUTTER_CLOSING: + shutterOpen->setText("OPEN"); + shutterClosed->setText("CLOSING"); + appendLogText("Shutter is closing..."); + break; + default: + break; + } +} + + + + +void Observatory::setWeatherModel(ObservatoryWeatherModel *model) +{ + mObservatoryModel->setWeatherModel(model); + + if (model != nullptr) + { + connect(weatherWarningBox, &QGroupBox::clicked, model, &ObservatoryWeatherModel::setWarningActionsActive); + connect(weatherAlertBox, &QGroupBox::clicked, model, &ObservatoryWeatherModel::setAlertActionsActive); + + connect(model, &Ekos::ObservatoryWeatherModel::ready, this, &Ekos::Observatory::initWeather); + connect(model, &Ekos::ObservatoryWeatherModel::newStatus, this, &Ekos::Observatory::setWeatherStatus); + connect(model, &Ekos::ObservatoryWeatherModel::disconnected, this, &Ekos::Observatory::shutdownWeather); + connect(&weatherStatusTimer, &QTimer::timeout, [this]() + { + weatherWarningStatusLabel->setText(getWeatherModel()->getWarningActionsStatus()); + weatherAlertStatusLabel->setText(getWeatherModel()->getAlertActionsStatus()); + }); + } + else + { + shutdownWeather(); + disconnect(model, &Ekos::ObservatoryWeatherModel::newStatus, this, &Ekos::Observatory::setWeatherStatus); + disconnect(model, &Ekos::ObservatoryWeatherModel::disconnected, this, &Ekos::Observatory::shutdownWeather); + disconnect(model, &Ekos::ObservatoryWeatherModel::ready, this, &Ekos::Observatory::initWeather); + + disconnect(weatherWarningBox, &QGroupBox::clicked, model, &ObservatoryWeatherModel::setWarningActionsActive); + disconnect(weatherAlertBox, &QGroupBox::clicked, model, &ObservatoryWeatherModel::setAlertActionsActive); + } +} + +void Observatory::initWeather() +{ + weatherBox->setEnabled(true); + weatherLabel->setEnabled(true); + weatherActionsBox->setVisible(true); + weatherActionsBox->setEnabled(true); + weatherWarningBox->setChecked(getWeatherModel()->getWarningActionsActive()); + weatherAlertBox->setChecked(getWeatherModel()->getAlertActionsActive()); + setWeatherStatus(getWeatherModel()->status()); + setWarningActions(getWeatherModel()->getWarningActions()); + setAlertActions(getWeatherModel()->getAlertActions()); + weatherStatusTimer.start(1000); +} + +void Observatory::shutdownWeather() +{ + weatherBox->setEnabled(false); + weatherLabel->setEnabled(false); + setWeatherStatus(ISD::Weather::WEATHER_IDLE); + weatherStatusTimer.stop(); +} + + +void Observatory::setWeatherStatus(ISD::Weather::Status status) +{ + std::string label; + switch (status) { + case ISD::Weather::WEATHER_OK: + label = "security-high"; + appendLogText("Weather is OK."); + break; + case ISD::Weather::WEATHER_WARNING: + label = "security-medium"; + appendLogText("Weather WARNING!"); + break; + case ISD::Weather::WEATHER_ALERT: + label = "security-low"; + appendLogText("!! WEATHER ALERT !!"); + break; + default: + label = ""; + break; + } + + weatherStatusLabel->setPixmap(QIcon::fromTheme(label.c_str()) + .pixmap(QSize(48, 48))); +} + + +void Observatory::weatherWarningSettingsChanged() +{ + struct WeatherActions actions; + actions.parkDome = weatherWarningDomeCB->isChecked(); + actions.closeShutter = weatherWarningShutterCB->isChecked(); + actions.delay = weatherWarningDelaySB->value(); + + getWeatherModel()->setWarningActions(actions); +} + +void Observatory::weatherAlertSettingsChanged() +{ + struct WeatherActions actions; + actions.parkDome = weatherAlertDomeCB->isChecked(); + actions.closeShutter = weatherAlertShutterCB->isChecked(); + actions.delay = weatherAlertDelaySB->value(); + + getWeatherModel()->setAlertActions(actions); +} + +void Observatory::observatoryStatusChanged(bool ready) +{ + // statusReadyButton->setEnabled(!ready); + statusReadyButton->setChecked(ready); + emit newStatus(ready); +} + + +void Observatory::setWarningActions(WeatherActions actions) +{ + weatherWarningDomeCB->setChecked(actions.parkDome); + weatherWarningShutterCB->setChecked(actions.closeShutter); + weatherWarningDelaySB->setValue(actions.delay); +} + + +void Observatory::setAlertActions(WeatherActions actions) +{ + weatherAlertDomeCB->setChecked(actions.parkDome); + weatherAlertShutterCB->setChecked(actions.closeShutter); + weatherAlertDelaySB->setValue(actions.delay); +} + +void Observatory::statusControlSettingsChanged() +{ + ObservatoryStatusControl control; + control.useDome = useDomeCB->isChecked(); + control.useShutter = useShutterCB->isChecked(); + control.useWeather = useWeatherCB->isChecked(); + mObservatoryModel->setStatusControl(control); +} + + +void Observatory::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_OBSERVATORY) << text; + + emit newLog(text); +} + +void Observatory::clearLog() +{ + m_LogText.clear(); + emit newLog(QString()); +} + +} diff --git a/kstars/ekos/observatory/observatory.ui b/kstars/ekos/observatory/observatory.ui new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatory.ui @@ -0,0 +1,649 @@ + + + Observatory + + + + 0 + 0 + 904 + 536 + + + + Observatory + + + Status of the Observatory + + + + + + false + + + Observatory Status + + + + 6 + + + + + <html><head/><body><p>If selected, the shutter needs to be open for the observatory status being &quot;READY&quot;.</p></body></html> + + + Shutter + + + + + + + <html><head/><body><p>If selected, the weather needs to be OK for the observatory status being &quot;READY&quot;.</p></body></html> + + + Weather + + + + + + + <html><head/><body><p>If selected, the dome needs to be unparked for the observatory status being &quot;READY&quot;.</p></body></html> + + + Dome + + + + + + + false + + + + 96 + 36 + + + + + 72 + 36 + + + + <html><head/><body><p>Close the shutter of the dome. For advanced control of the dome please use the INDI tab.</p></body></html> + + + QPushButton:checked +{ +background-color: maroon; +border: 1px outset; +font-weight:bold; +} + + + READY + + + + 32 + 16 + + + + true + + + false + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Dome + + + + + + false + + + + 96 + 36 + + + + + 96 + 36 + + + + <html><head/><body><p>Park the dome. For advanced control of the dome please use the INDI tab.</p></body></html> + + + QPushButton:checked +{ +background-color: maroon; +border: 1px outset; +font-weight:bold; +} + + + PARK + + + + 32 + 16 + + + + true + + + + + + + false + + + + 96 + 36 + + + + + 96 + 36 + + + + <html><head/><body><p>Unpark the dome. For advanced control of the dome please use the INDI tab.</p></body></html> + + + QPushButton:checked +{ +background-color: maroon; +border: 1px outset; +font-weight:bold; +} + + + UNPARKED + + + true + + + false + + + + + + + + + false + + + Angle + + + + + + + false + + + + + + + false + + + + 72 + 24 + + + + + 72 + 24 + + + + Set + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + false + + + Weather + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Display the weather status. The warning and alert limits are set in the INDI tab. + + + Weather Status: + + + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + + + + + false + + + Actions + + + + + + War&ning + + + true + + + + 0 + + + 0 + + + + + park dome + + + + + + + close shutter + + + + + + + stop scheduler + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">Status: inactive</span></p></body></html> + + + Qt::AutoText + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + delay (sec): + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9999 + + + + + + + + + + + + Ale&rt + + + true + + + + 0 + + + 0 + + + + + close shutter + + + + + + + park dome + + + + + + + stop scheduler + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">Status: inactive</span></p></body></html> + + + Qt::AutoText + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + delay (sec): + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9999 + + + + + + + + + + + + + + + + + + false + + + Shutter + + + + + + false + + + + 96 + 36 + + + + + 72 + 36 + + + + <html><head/><body><p>Close the shutter of the dome. For advanced control of the dome please use the INDI tab.</p></body></html> + + + QPushButton:checked +{ +background-color: maroon; +border: 1px outset; +font-weight:bold; +} + + + CLOSED + + + + 32 + 16 + + + + true + + + + + + + false + + + + 96 + 36 + + + + + 72 + 36 + + + + <html><head/><body><p>Open the shutter of the dome. For advanced control of the dome please use the INDI tab.</p></body></html> + + + QPushButton:checked +{ +background-color: maroon; +border: 1px outset; +font-weight:bold; +} + + + OPEN + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/kstars/ekos/observatory/observatorydomemodel.h b/kstars/ekos/observatory/observatorydomemodel.h new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatorydomemodel.h @@ -0,0 +1,57 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "../auxiliary/dome.h" +#include "observatoryweathermodel.h" + +#include + + +namespace Ekos +{ + +class ObservatoryDomeModel: public QObject +{ + + Q_OBJECT + +public: + ObservatoryDomeModel() = default; + + void initModel(Dome *dome); + + ISD::Dome::Status status(); + ISD::Dome::ShutterStatus shutterStatus(); + + // proxies to the underlying dome object + bool canPark() { return (mDome != nullptr && mDome->canPark()); } + void park(); + void unpark(); + bool hasShutter() { return (mDome != nullptr && mDome->hasShutter()); } + void openShutter(); + void closeShutter(); + +public slots: + void execute(WeatherActions actions); + + +private: + Dome *mDome; + +signals: + void newStatus(ISD::Dome::Status state); + void newShutterStatus(ISD::Dome::ShutterStatus status); + void ready(); + void disconnected(); + void newLog(const QString &text); +}; + +} diff --git a/kstars/ekos/observatory/observatorydomemodel.cpp b/kstars/ekos/observatory/observatorydomemodel.cpp new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatorydomemodel.cpp @@ -0,0 +1,88 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "observatorydomemodel.h" + +namespace Ekos +{ + +void ObservatoryDomeModel::initModel(Dome *dome) +{ + mDome = dome; + + connect(mDome, &Dome::ready, this, &ObservatoryDomeModel::ready); + connect(mDome, &Dome::disconnected, this, &ObservatoryDomeModel::disconnected); + connect(mDome, &Dome::newStatus, this, &ObservatoryDomeModel::newStatus); + connect(mDome, &Dome::newShutterStatus, this, &ObservatoryDomeModel::newShutterStatus); + +} + + +ISD::Dome::Status ObservatoryDomeModel::status() +{ + if (mDome == nullptr) + return ISD::Dome::DOME_IDLE; + + return mDome->status(); +} + +ISD::Dome::ShutterStatus ObservatoryDomeModel::shutterStatus() +{ + if (mDome == nullptr) + return ISD::Dome::SHUTTER_UNKNOWN; + + return mDome->shutterStatus(); +} + +void ObservatoryDomeModel::park() +{ + if (mDome == nullptr) + return; + + emit newLog("Parking dome..."); + mDome->park(); +} + + +void ObservatoryDomeModel::unpark() +{ + if (mDome == nullptr) + return; + + emit newLog("Unparking dome..."); + mDome->unpark(); +} + +void ObservatoryDomeModel::openShutter() +{ + if (mDome == nullptr) + return; + + emit newLog("Opening shutter..."); + mDome->controlShutter(true); +} + +void ObservatoryDomeModel::closeShutter() +{ + if (mDome == nullptr) + return; + + emit newLog("Closing shutter..."); + mDome->controlShutter(false); +} + +void ObservatoryDomeModel::execute(WeatherActions actions) +{ + if (hasShutter() && actions.closeShutter) + closeShutter(); + if (actions.parkDome) + park(); +} + +} diff --git a/kstars/ekos/observatory/observatorymodel.h b/kstars/ekos/observatory/observatorymodel.h new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatorymodel.h @@ -0,0 +1,78 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "observatorydomemodel.h" +#include "observatoryweathermodel.h" + +#include + +namespace Ekos +{ + +struct ObservatoryStatusControl +{ + bool useDome, useShutter, useWeather; +}; + +class ObservatoryModel : public QObject +{ + + Q_OBJECT + +public: + ObservatoryModel(); + + ObservatoryDomeModel *getDomeModel() { return mDomeModel; } + ObservatoryWeatherModel *getWeatherModel() { return mWeatherModel; } + + void setDomeModel(ObservatoryDomeModel *model); + void setWeatherModel(ObservatoryWeatherModel *model); + + /** + * @brief Retrieve the settings that define, from which states the + * "ready" state of the observatory is derived from. + */ + ObservatoryStatusControl statusControl() { return mStatusControl; } + void setStatusControl(ObservatoryStatusControl control); + + /** + * @brief Is the observatory ready? This depends upon the states of the weather, + * dome etc and upon whether these settings are relevant (see status control). + */ + bool isReady(); + +public slots: + // call this slot in case that the weather or dome status has changed + void updateStatus(); + + /** + * @brief Depending on the status control settings execute everything so + * that the status reaches the state "READY". + */ + void makeReady(); + +signals: + /** + * @brief Signal a new observatory status + * @param isReady + */ + void newStatus(bool isReady); + + +private: + ObservatoryStatusControl mStatusControl; + + ObservatoryDomeModel *mDomeModel = nullptr; + ObservatoryWeatherModel *mWeatherModel = nullptr; + +}; + +} diff --git a/kstars/ekos/observatory/observatorymodel.cpp b/kstars/ekos/observatory/observatorymodel.cpp new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatorymodel.cpp @@ -0,0 +1,111 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "observatorymodel.h" +#include "Options.h" + +namespace Ekos +{ + +ObservatoryModel::ObservatoryModel() +{ + + mStatusControl.useDome = Options::observatoryStatusUseDome(); + mStatusControl.useShutter = Options::observatoryStatusUseShutter(); + mStatusControl.useWeather = Options::observatoryStatusUseWeather(); + + setDomeModel(new ObservatoryDomeModel()); + setWeatherModel(new ObservatoryWeatherModel()); +} + +void ObservatoryModel::setDomeModel(ObservatoryDomeModel *model) { + mDomeModel = model; + if (model != nullptr) + { + connect(mDomeModel, &ObservatoryDomeModel::newStatus, [this](ISD::Dome::Status s) { Q_UNUSED(s); updateStatus(); }); + connect(mDomeModel, &ObservatoryDomeModel::newShutterStatus, [this](ISD::Dome::ShutterStatus s) { Q_UNUSED(s); updateStatus(); }); + if (mWeatherModel != nullptr) + connect(mWeatherModel, &ObservatoryWeatherModel::execute, mDomeModel, &ObservatoryDomeModel::execute); + } + else + { + if (mWeatherModel != nullptr) + disconnect(mWeatherModel, &ObservatoryWeatherModel::execute, mDomeModel, &ObservatoryDomeModel::execute); + } + + updateStatus(); +} + +void ObservatoryModel::setWeatherModel(ObservatoryWeatherModel *model) { + mWeatherModel = model; + if (model != nullptr) + { + connect(mWeatherModel, &ObservatoryWeatherModel::newStatus, [this](ISD::Weather::Status s) { Q_UNUSED(s); updateStatus(); }); + if (mDomeModel != nullptr) + { + connect(mWeatherModel, &ObservatoryWeatherModel::execute, mDomeModel, &ObservatoryDomeModel::execute); + } + } + else + { + if (mDomeModel != nullptr) + disconnect(mWeatherModel, &ObservatoryWeatherModel::execute, mDomeModel, &ObservatoryDomeModel::execute); + } + updateStatus(); +} + + +void ObservatoryModel::setStatusControl(ObservatoryStatusControl control) +{ + mStatusControl = control; + Options::setObservatoryStatusUseDome(control.useDome); + Options::setObservatoryStatusUseShutter(control.useShutter); + Options::setObservatoryStatusUseWeather(control.useWeather); + updateStatus(); +} + +bool ObservatoryModel::isReady() +{ + // dome relevant for the status and dome is ready + if (mStatusControl.useDome && (getDomeModel() == nullptr || getDomeModel()->status() != ISD::Dome::DOME_IDLE)) + return false; + + // shutter relevant for the status and shutter open + if (mStatusControl.useShutter && (getDomeModel() == nullptr || + (getDomeModel()->hasShutter() && getDomeModel()->shutterStatus() != ISD::Dome::SHUTTER_OPEN))) + return false; + + // weather relevant for the status and weather is OK + if (mStatusControl.useWeather && (getWeatherModel() == nullptr || getWeatherModel()->status() != ISD::Weather::WEATHER_OK)) + return false; + + return true; +} + +void ObservatoryModel::updateStatus() +{ + emit newStatus(isReady()); +} + +void ObservatoryModel::makeReady() +{ + // dome relevant for the status and dome is ready + if (mStatusControl.useDome && (getDomeModel() == nullptr || getDomeModel()->status() != ISD::Dome::DOME_IDLE)) + getDomeModel()->unpark(); + + // shutter relevant for the status and shutter open + if (mStatusControl.useShutter && (getDomeModel() == nullptr || + (getDomeModel()->hasShutter() && getDomeModel()->shutterStatus() != ISD::Dome::SHUTTER_OPEN))) + getDomeModel()->openShutter(); + + // weather relevant for the status and weather is OK + // Haha, weather we can't change +} + +} diff --git a/kstars/ekos/observatory/observatoryweathermodel.h b/kstars/ekos/observatory/observatoryweathermodel.h new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatoryweathermodel.h @@ -0,0 +1,83 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "../auxiliary/weather.h" +#include "indiweather.h" + +#include + +namespace Ekos +{ + +struct WeatherActions { + bool parkDome, closeShutter, stopScheduler; + int delay; +}; + +class ObservatoryWeatherModel : public QObject +{ + + Q_OBJECT + +public: + ObservatoryWeatherModel() = default; + + void initModel(Weather *weather); + ISD::Weather::Status status(); + + /** + * @brief Actions to be taken when a weather warning occurs + */ + WeatherActions getWarningActions() { return warningActions; } + QString getWarningActionsStatus(); + void setWarningActions(WeatherActions actions); + bool getWarningActionsActive() { return warningActionsActive; } + + /** + * @brief Actions to be taken when a weather alert occurs + */ + WeatherActions getAlertActions() { return alertActions; } + QString getAlertActionsStatus(); + void setAlertActions(WeatherActions actions); + bool getAlertActionsActive() { return alertActionsActive; } + +public slots: + /** + * @brief Activate or deactivate the weather warning actions + */ + void setWarningActionsActive(bool active); + /** + * @brief Activate or deactivate the weather alert actions + */ + void setAlertActionsActive(bool active); + +private: + Weather *mWeather; + QTimer warningTimer, alertTimer; + struct WeatherActions warningActions, alertActions; + bool warningActionsActive, alertActionsActive; + +private slots: + void weatherChanged(ISD::Weather::Status status); + void updateWeatherStatus(); + +signals: + void newStatus(ISD::Weather::Status status); + void ready(); + void disconnected(); + /** + * @brief signal that actions need to be taken due to weather conditions + */ + void execute(WeatherActions actions); + +}; + +} diff --git a/kstars/ekos/observatory/observatoryweathermodel.cpp b/kstars/ekos/observatory/observatoryweathermodel.cpp new file mode 100644 --- /dev/null +++ b/kstars/ekos/observatory/observatoryweathermodel.cpp @@ -0,0 +1,156 @@ +/* Ekos Observatory Module + Copyright (C) Wolfgang Reissenberger + + 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 "observatoryweathermodel.h" +#include "Options.h" + +namespace Ekos +{ + +void ObservatoryWeatherModel::initModel(Weather *weather) +{ + mWeather = weather; + + // ensure that we start the timers if required + weatherChanged(status()); + + connect(mWeather, &Weather::ready, this, &ObservatoryWeatherModel::updateWeatherStatus); + connect(mWeather, &Weather::newStatus, this, &ObservatoryWeatherModel::weatherChanged); + connect(mWeather, &Weather::disconnected, this, &ObservatoryWeatherModel::disconnected); + + // read the default values + warningActionsActive = Options::warningActionsActive(); + warningActions.parkDome = Options::weatherWarningCloseDome(); + warningActions.closeShutter = Options::weatherWarningCloseShutter(); + warningActions.delay = Options::weatherWarningDelay(); + warningActions.stopScheduler = Options::weatherAlertStopScheduler(); + alertActionsActive = Options::alertActionsActive(); + alertActions.parkDome = Options::weatherAlertCloseDome(); + alertActions.closeShutter = Options::weatherAlertCloseShutter(); + alertActions.stopScheduler = Options::weatherAlertStopScheduler(); + alertActions.delay = Options::weatherAlertDelay(); + + warningTimer.setInterval(warningActions.delay * 1000); + warningTimer.setSingleShot(true); + alertTimer.setInterval(alertActions.delay * 1000); + alertTimer.setSingleShot(true); + + connect(&warningTimer, &QTimer::timeout, [this]() { execute(warningActions); }); + connect(&alertTimer, &QTimer::timeout, [this]() { execute(alertActions); }); + + if (mWeather->status() != ISD::Weather::WEATHER_IDLE) + emit ready(); +} + +ISD::Weather::Status ObservatoryWeatherModel::status() +{ + if (mWeather == nullptr) + return ISD::Weather::WEATHER_IDLE; + + return mWeather->status(); +} + +void ObservatoryWeatherModel::setWarningActionsActive(bool active) +{ + warningActionsActive = active; + Options::setWarningActionsActive(active); + + // stop warning actions if deactivated + if (!active && warningTimer.isActive()) + warningTimer.stop(); + // start warning timer if activated + else if (active && !warningTimer.isActive() && mWeather->status() == ISD::Weather::WEATHER_WARNING) + warningTimer.start(); +} + +void ObservatoryWeatherModel::setAlertActionsActive(bool active) +{ + alertActionsActive = active; + Options::setAlertActionsActive(active); + + // stop alert actions if deactivated + if (!active && alertTimer.isActive()) + alertTimer.stop(); + // start alert timer if activated + else if (active && !alertTimer.isActive() && mWeather->status() == ISD::Weather::WEATHER_ALERT) + alertTimer.start(); +} + +void ObservatoryWeatherModel::setWarningActions(WeatherActions actions) { + warningActions = actions; + Options::setWeatherWarningCloseDome(actions.parkDome); + Options::setWeatherWarningCloseShutter(actions.closeShutter); + Options::setWeatherWarningDelay(actions.delay); + warningTimer.setInterval(actions.delay * 1000); +} + + +QString ObservatoryWeatherModel::getWarningActionsStatus() +{ + if (warningTimer.isActive()) + { + QString status; + int remaining = warningTimer.remainingTime()/1000; + return status.sprintf("%02ds remaining", remaining); + } + + return "Status: inactive"; +} + +void ObservatoryWeatherModel::setAlertActions(WeatherActions actions) { + alertActions = actions; + Options::setWeatherAlertCloseDome(actions.parkDome); + Options::setWeatherAlertCloseShutter(actions.closeShutter); + Options::setWeatherAlertDelay(actions.delay); + alertTimer.setInterval(actions.delay * 1000); +} + +QString ObservatoryWeatherModel::getAlertActionsStatus() +{ + if (alertTimer.isActive()) + { + QString status; + int remaining = alertTimer.remainingTime()/1000; + return status.sprintf("%02ds remaining", remaining); + } + + return "Status: inactive"; +} + +void ObservatoryWeatherModel::updateWeatherStatus() +{ + weatherChanged(status()); + emit ready(); +} + + +void ObservatoryWeatherModel::weatherChanged(ISD::Weather::Status status) +{ + switch (status) { + case ISD::Weather::WEATHER_OK: + warningTimer.stop(); + alertTimer.stop(); + break; + case ISD::Weather::WEATHER_WARNING: + if (warningActionsActive) + warningTimer.start(); + alertTimer.stop(); + break; + case ISD::Weather::WEATHER_ALERT: + warningTimer.stop(); + if (alertActionsActive) + alertTimer.start(); + break; + default: + break; + } + emit newStatus(status); +} + +} // Ekos diff --git a/kstars/indi/indidome.h b/kstars/indi/indidome.h --- a/kstars/indi/indidome.h +++ b/kstars/indi/indidome.h @@ -26,18 +26,28 @@ { Q_OBJECT - public: - explicit Dome(GDInterface *iPtr); - typedef enum - { - DOME_IDLE, - DOME_MOVING, - DOME_TRACKING, - DOME_PARKING, - DOME_UNPARKING, - DOME_PARKED, - DOME_ERROR - } Status; +public: + explicit Dome(GDInterface *iPtr); + typedef enum + { + DOME_IDLE, + DOME_MOVING, + DOME_TRACKING, + DOME_PARKING, + DOME_UNPARKING, + DOME_PARKED, + DOME_ERROR + } Status; + + typedef enum + { + SHUTTER_UNKNOWN, + SHUTTER_OPEN, + SHUTTER_CLOSED, + SHUTTER_OPENING, + SHUTTER_CLOSING, + SHUTTER_ERROR + } ShutterStatus; void processSwitch(ISwitchVectorProperty *svp) override; void processText(ITextVectorProperty *tvp) override; @@ -71,29 +81,41 @@ double azimuthPosition() const; bool setAzimuthPosition(double position); + + bool hasShutter() const + { + return m_HasShutter; + } Status status() const { return m_Status; } static const QString getStatusString (Status status); - public slots: + ShutterStatus shutterStatus(); + ShutterStatus shutterStatus(ISwitchVectorProperty *svp); + +public slots: bool Abort(); bool Park(); bool UnPark(); + bool ControlShutter(bool open); signals: void newStatus(Status status); void newParkStatus(ParkStatus status); + void newShutterStatus(ShutterStatus status); void azimuthPositionChanged(double Az); void ready(); private: ParkStatus m_ParkStatus { PARK_UNKNOWN }; + ShutterStatus m_ShutterStatus { SHUTTER_UNKNOWN }; Status m_Status { DOME_IDLE }; bool m_CanAbsMove { false }; bool m_CanPark { false }; bool m_CanAbort { false }; + bool m_HasShutter { false }; std::unique_ptr readyTimer; }; } diff --git a/kstars/indi/indidome.cpp b/kstars/indi/indidome.cpp --- a/kstars/indi/indidome.cpp +++ b/kstars/indi/indidome.cpp @@ -51,6 +51,7 @@ if ((sp->s == ISS_ON) && svp->s == IPS_OK) { m_ParkStatus = PARK_PARKED; + m_Status = DOME_PARKED; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("dome_park"); @@ -63,6 +64,7 @@ else if ((sp->s == ISS_OFF) && svp->s == IPS_OK) { m_ParkStatus = PARK_UNPARKED; + m_Status = DOME_IDLE; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("dome_park"); @@ -83,6 +85,10 @@ { m_CanAbort = true; } + else if (!strcmp(prop->getName(), "DOME_SHUTTER")) + { + m_HasShutter = true; + } DeviceDecorator::registerProperty(prop); } @@ -225,7 +231,64 @@ emit newStatus(m_Status); } } + else if (!strcmp(svp->name, "DOME_SHUTTER")) + { + if (svp->s == IPS_ALERT) + { + emit newShutterStatus(SHUTTER_ERROR); + + // If alert, set shutter status to whatever it was opposite to. That is, if it was opening and failed + // then we set status to closed since it did not successfully complete opening. + if (m_ShutterStatus == SHUTTER_CLOSING) + m_ShutterStatus = SHUTTER_OPEN; + else if (m_ShutterStatus == SHUTTER_CLOSING) + m_ShutterStatus = SHUTTER_CLOSED; + + emit newShutterStatus(m_ShutterStatus); + } + + ShutterStatus status = shutterStatus(svp); + + switch (status) { + case SHUTTER_CLOSING: + if (m_ShutterStatus != SHUTTER_CLOSING) + { + m_ShutterStatus = SHUTTER_CLOSING; + KNotification::event(QLatin1String("ShutterClosing"), i18n("Shutter closing is in progress")); + emit newShutterStatus(m_ShutterStatus); + } + break; + case SHUTTER_OPENING: + if (m_ShutterStatus != SHUTTER_OPENING) + { + m_ShutterStatus = SHUTTER_OPENING; + KNotification::event(QLatin1String("ShutterOpening"), i18n("Shutter opening is in progress")); + emit newShutterStatus(m_ShutterStatus); + } + break; + case SHUTTER_CLOSED: + if (m_ShutterStatus != SHUTTER_CLOSED) + { + m_ShutterStatus = SHUTTER_CLOSED; + KNotification::event(QLatin1String("ShutterClosed"), i18n("Shutter closed")); + emit newShutterStatus(m_ShutterStatus); + } + break; + case SHUTTER_OPEN: + if (m_ShutterStatus != SHUTTER_OPEN) + { + m_ShutterStatus = SHUTTER_OPEN; + KNotification::event(QLatin1String("ShutterOpened"), i18n("Shutter opened")); + emit newShutterStatus(m_ShutterStatus); + } + break; + default: + break; + } + + return; + } DeviceDecorator::processSwitch(svp); } @@ -325,6 +388,53 @@ return true; } +bool Dome::ControlShutter(bool open) +{ + ISwitchVectorProperty *shutterSP = baseDevice->getSwitch("DOME_SHUTTER"); + + if (shutterSP == nullptr) + return false; + + ISwitch *shutterSW = IUFindSwitch(shutterSP, open ? "SHUTTER_OPEN" : "SHUTTER_CLOSE"); + + if (shutterSW == nullptr) + return false; + + IUResetSwitch(shutterSP); + shutterSW->s = ISS_ON; + clientManager->sendNewSwitch(shutterSP); + + return true; +} + +Dome::ShutterStatus Dome::shutterStatus() +{ + ISwitchVectorProperty *shutterSP = baseDevice->getSwitch("DOME_SHUTTER"); + + return shutterStatus(shutterSP); + +} + +Dome::ShutterStatus Dome::shutterStatus(ISwitchVectorProperty *svp) +{ + if (svp == nullptr) + return SHUTTER_UNKNOWN; + + ISwitch *sp = IUFindSwitch(svp, "SHUTTER_OPEN"); + if (sp == nullptr) + return SHUTTER_UNKNOWN; + + if (svp->s == IPS_ALERT) + return SHUTTER_ERROR; + else if (svp->s == IPS_BUSY) + return (sp->s == ISS_ON) ? SHUTTER_OPENING : SHUTTER_CLOSING; + else if (svp->s == IPS_OK) + return (sp->s == ISS_ON) ? SHUTTER_OPEN : SHUTTER_CLOSED; + + // this should not happen + return SHUTTER_UNKNOWN; +} + const QString Dome::getStatusString(Dome::Status status) { switch (status) diff --git a/kstars/indi/indilistener.cpp b/kstars/indi/indilistener.cpp --- a/kstars/indi/indilistener.cpp +++ b/kstars/indi/indilistener.cpp @@ -288,7 +288,8 @@ emit newFocuser(gd); } - else if (!strcmp(prop->getName(), "DOME_MOTION")) + else if (!strcmp(prop->getName(), "DOME_SHUTTER") || + !strcmp(prop->getName(), "DOME_MOTION")) { if (gd->getType() == KSTARS_UNKNOWN) { diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg --- a/kstars/kstars.kcfg +++ b/kstars/kstars.kcfg @@ -2306,4 +2306,58 @@ false + + + + true + + + + true + + + + false + + + + false + + + + true + + + + 600 + + + + true + + + + true + + + + true + + + + 30 + + + + true + + + + true + + + + true + + diff --git a/kstars/org.kde.kstars.Ekos.Observatory.xml b/kstars/org.kde.kstars.Ekos.Observatory.xml new file mode 100644 --- /dev/null +++ b/kstars/org.kde.kstars.Ekos.Observatory.xml @@ -0,0 +1,9 @@ + + + + + + + + +