File indexing completed on 2024-04-28 03:43:40

0001 /*  Ekos Observatory Module
0002     SPDX-FileCopyrightText: Wolfgang Reissenberger <sterne-jaeger@t-online.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kstarsdata.h"
0008 
0009 #include "observatory.h"
0010 #include "Options.h"
0011 
0012 #include "ekos_observatory_debug.h"
0013 
0014 namespace Ekos
0015 {
0016 Observatory::Observatory()
0017 {
0018     setupUi(this);
0019 
0020     // status control
0021     //setObseratoryStatusControl(m_StatusControl);
0022     // update UI for status control
0023     connect(useDomeCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
0024     connect(useShutterCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
0025     connect(useWeatherCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
0026     // ready button deactivated
0027     // connect(statusReadyButton, &QPushButton::clicked, mObservatoryModel, &Ekos::ObservatoryModel::makeReady);
0028     statusReadyButton->setEnabled(false);
0029 
0030     // weather controls
0031     connect(weatherWarningShutterCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged);
0032     connect(weatherWarningDomeCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged);
0033     connect(weatherWarningDelaySB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int i)
0034     {
0035         Q_UNUSED(i)
0036         weatherWarningSettingsChanged();
0037     });
0038 
0039     connect(weatherAlertShutterCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged);
0040     connect(weatherAlertDomeCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged);
0041     connect(weatherAlertDelaySB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int i)
0042     {
0043         Q_UNUSED(i)
0044         weatherAlertSettingsChanged();
0045     });
0046 
0047     // read the default values
0048     warningActionsActive = Options::warningActionsActive();
0049     m_WarningActions.parkDome = Options::weatherWarningCloseDome();
0050     m_WarningActions.closeShutter = Options::weatherWarningCloseShutter();
0051     m_WarningActions.delay = Options::weatherWarningDelay();
0052     alertActionsActive = Options::alertActionsActive();
0053     m_AlertActions.parkDome = Options::weatherAlertCloseDome();
0054     m_AlertActions.closeShutter = Options::weatherAlertCloseShutter();
0055     m_AlertActions.delay = Options::weatherAlertDelay();
0056     m_autoScaleValues = Options::weatherAutoScaleValues();
0057 
0058     // not implemented yet
0059     m_WarningActions.stopScheduler = false;
0060     m_AlertActions.stopScheduler = false;
0061 
0062     warningTimer.setInterval(static_cast<int>(m_WarningActions.delay * 1000));
0063     warningTimer.setSingleShot(true);
0064     alertTimer.setInterval(static_cast<int>(m_AlertActions.delay * 1000));
0065     alertTimer.setSingleShot(true);
0066 
0067     connect(&warningTimer, &QTimer::timeout, [this]()
0068     {
0069         execute(m_WarningActions);
0070     });
0071     connect(&alertTimer, &QTimer::timeout, [this]()
0072     {
0073         execute(m_AlertActions);
0074     });
0075 
0076     connect(weatherSourceCombo, &QComboBox::currentTextChanged, this, &Observatory::setWeatherSource);
0077 
0078     // initialize the weather sensor data group box
0079     sensorDataBoxLayout = new QGridLayout();
0080     sensorData->setLayout(sensorDataBoxLayout);
0081 
0082     initSensorGraphs();
0083 
0084     connect(weatherWarningBox, &QGroupBox::clicked, this, &Observatory::setWarningActionsActive);
0085     connect(weatherAlertBox, &QGroupBox::clicked, this, &Observatory::setAlertActionsActive);
0086 
0087     connect(clearGraphHistory, &QPushButton::clicked, this, &Observatory::clearSensorDataHistory);
0088     connect(autoscaleValuesCB, &QCheckBox::clicked, [this](bool checked)
0089     {
0090         setAutoScaleValues(checked);
0091         refreshSensorGraph();
0092     });
0093     connect(&weatherStatusTimer, &QTimer::timeout, [this]()
0094     {
0095         weatherWarningStatusLabel->setText(getWarningActionsStatus());
0096         weatherAlertStatusLabel->setText(getAlertActionsStatus());
0097     });
0098 
0099 
0100 }
0101 
0102 bool Observatory::setDome(ISD::Dome *device)
0103 {
0104     if (m_Dome == device)
0105         return false;
0106 
0107     if (m_Dome)
0108         m_Dome->disconnect(this);
0109 
0110     m_Dome = device;
0111 
0112     domeBox->setEnabled(true);
0113 
0114     connect(m_Dome, &ISD::Dome::Disconnected, this, &Ekos::Observatory::shutdownDome);
0115     connect(m_Dome, &ISD::Dome::newStatus, this, &Ekos::Observatory::setDomeStatus);
0116     connect(m_Dome, &ISD::Dome::newParkStatus, this, &Ekos::Observatory::setDomeParkStatus);
0117     connect(m_Dome, &ISD::Dome::newShutterStatus, this, &Ekos::Observatory::setShutterStatus);
0118     connect(m_Dome, &ISD::Dome::positionChanged, this, &Ekos::Observatory::domeAzimuthChanged);
0119     connect(m_Dome, &ISD::Dome::newAutoSyncStatus, this, &Ekos::Observatory::showAutoSync);
0120 
0121     // motion controls
0122     connect(motionMoveAbsButton, &QCheckBox::clicked, [this]()
0123     {
0124         m_Dome->setPosition(absoluteMotionSB->value());
0125     });
0126 
0127     connect(motionMoveRelButton, &QCheckBox::clicked, [this]()
0128     {
0129         m_Dome->setRelativePosition(relativeMotionSB->value());
0130     });
0131 
0132     // abort button
0133     connect(motionAbortButton, &QPushButton::clicked, m_Dome, &ISD::Dome::abort);
0134 
0135 
0136     // dome motion buttons
0137     connect(motionCWButton, &QPushButton::clicked, [ = ](bool checked)
0138     {
0139         m_Dome->moveDome(ISD::Dome::DOME_CW, checked ? ISD::Dome::MOTION_START : ISD::Dome::MOTION_STOP);
0140     });
0141     connect(motionCCWButton, &QPushButton::clicked, [ = ](bool checked)
0142     {
0143         m_Dome->moveDome(ISD::Dome::DOME_CCW, checked ? ISD::Dome::MOTION_START : ISD::Dome::MOTION_STOP);
0144     });
0145 
0146     if (m_Dome->canPark())
0147     {
0148         connect(domePark, &QPushButton::clicked, m_Dome, &ISD::Dome::park);
0149         connect(domeUnpark, &QPushButton::clicked, m_Dome, &ISD::Dome::unpark);
0150         domePark->setEnabled(true);
0151         domeUnpark->setEnabled(true);
0152     }
0153     else
0154     {
0155         domePark->setEnabled(false);
0156         domeUnpark->setEnabled(false);
0157     }
0158 
0159     enableMotionControl(true);
0160 
0161     if (m_Dome->isRolloffRoof())
0162     {
0163         SlavingBox->setVisible(false);
0164         domeAzimuthPosition->setText(i18nc("Not Applicable", "N/A"));
0165     }
0166     else
0167     {
0168         // initialize the dome motion controls
0169         domeAzimuthChanged(m_Dome->position());
0170 
0171         // slaving
0172         showAutoSync(m_Dome->isAutoSync());
0173         connect(slavingEnableButton, &QPushButton::clicked, this, [this]()
0174         {
0175             enableAutoSync(true);
0176         });
0177         connect(slavingDisableButton, &QPushButton::clicked, this, [this]()
0178         {
0179             enableAutoSync(false);
0180         });
0181     }
0182 
0183     // shutter handling
0184     if (m_Dome->hasShutter())
0185     {
0186         shutterBox->setVisible(true);
0187         shutterBox->setEnabled(true);
0188         connect(shutterOpen, &QPushButton::clicked, m_Dome, &ISD::Dome::openShutter);
0189         connect(shutterClosed, &QPushButton::clicked, m_Dome, &ISD::Dome::closeShutter);
0190         shutterClosed->setEnabled(true);
0191         shutterOpen->setEnabled(true);
0192         setShutterStatus(m_Dome->shutterStatus());
0193         useShutterCB->setVisible(true);
0194     }
0195     else
0196     {
0197         shutterBox->setVisible(false);
0198         weatherWarningShutterCB->setVisible(false);
0199         weatherAlertShutterCB->setVisible(false);
0200         useShutterCB->setVisible(false);
0201     }
0202 
0203     // abort button should always be available
0204     motionAbortButton->setEnabled(true);
0205 
0206     statusDefinitionBox->setVisible(true);
0207     statusDefinitionBox->setEnabled(true);
0208 
0209     // update the dome parking status
0210     setDomeParkStatus(m_Dome->parkStatus());
0211 
0212     // enable the UI controls for dome weather actions
0213     initWeatherActions(m_Dome && m_WeatherSource);
0214 
0215     return true;
0216 }
0217 
0218 void Observatory::shutdownDome()
0219 {
0220     shutterBox->setEnabled(false);
0221     shutterBox->setVisible(false);
0222     domePark->setEnabled(false);
0223     domeUnpark->setEnabled(false);
0224     shutterClosed->setEnabled(false);
0225     shutterOpen->setEnabled(false);
0226 
0227     if (m_Dome)
0228         disconnect(m_Dome);
0229 
0230     // disable the UI controls for dome weather actions
0231     initWeatherActions(false);
0232     statusDefinitionBox->setVisible(false);
0233     domeBox->setEnabled(false);
0234 }
0235 
0236 void Observatory::setDomeStatus(ISD::Dome::Status status)
0237 {
0238     qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting dome status to " << status;
0239 
0240     switch (status)
0241     {
0242         case ISD::Dome::DOME_ERROR:
0243             appendLogText(i18n("%1 error. See INDI log for details.",
0244                                m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0245             motionCWButton->setChecked(false);
0246             motionCCWButton->setChecked(false);
0247             break;
0248 
0249         case ISD::Dome::DOME_IDLE:
0250             motionCWButton->setChecked(false);
0251             motionCWButton->setEnabled(true);
0252             motionCCWButton->setChecked(false);
0253             motionCCWButton->setEnabled(true);
0254 
0255             appendLogText(i18n("%1 is idle.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0256             break;
0257 
0258         case ISD::Dome::DOME_MOVING_CW:
0259             motionCWButton->setChecked(true);
0260             motionCWButton->setEnabled(false);
0261             motionCCWButton->setChecked(false);
0262             motionCCWButton->setEnabled(true);
0263             if (m_Dome->isRolloffRoof())
0264             {
0265                 domeAzimuthPosition->setText(i18n("Opening"));
0266                 toggleButtons(domeUnpark, i18n("Unparking"), domePark, i18n("Park"));
0267                 appendLogText(i18n("Rolloff roof opening..."));
0268             }
0269             else
0270             {
0271                 appendLogText(i18n("Dome is moving clockwise..."));
0272             }
0273             break;
0274 
0275         case ISD::Dome::DOME_MOVING_CCW:
0276             motionCWButton->setChecked(false);
0277             motionCWButton->setEnabled(true);
0278             motionCCWButton->setChecked(true);
0279             motionCCWButton->setEnabled(false);
0280             if (m_Dome->isRolloffRoof())
0281             {
0282                 domeAzimuthPosition->setText(i18n("Closing"));
0283                 toggleButtons(domePark, i18n("Parking"), domeUnpark, i18n("Unpark"));
0284                 appendLogText(i18n("Rolloff roof is closing..."));
0285             }
0286             else
0287             {
0288                 appendLogText(i18n("Dome is moving counter clockwise..."));
0289             }
0290             break;
0291 
0292         case ISD::Dome::DOME_PARKED:
0293             setDomeParkStatus(ISD::PARK_PARKED);
0294 
0295             appendLogText(i18n("%1 is parked.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0296             break;
0297 
0298         case ISD::Dome::DOME_PARKING:
0299             toggleButtons(domePark, i18n("Parking"), domeUnpark, i18n("Unpark"));
0300             motionCWButton->setEnabled(true);
0301 
0302             if (m_Dome->isRolloffRoof())
0303                 domeAzimuthPosition->setText(i18n("Closing"));
0304             else
0305                 enableMotionControl(false);
0306 
0307             motionCWButton->setChecked(false);
0308             motionCCWButton->setChecked(true);
0309 
0310             appendLogText(i18n("%1 is parking...", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0311             break;
0312 
0313         case ISD::Dome::DOME_UNPARKING:
0314             toggleButtons(domeUnpark, i18n("Unparking"), domePark, i18n("Park"));
0315             motionCCWButton->setEnabled(true);
0316 
0317             if (m_Dome->isRolloffRoof())
0318                 domeAzimuthPosition->setText(i18n("Opening"));
0319             else
0320                 enableMotionControl(false);
0321 
0322             motionCWButton->setChecked(true);
0323             motionCCWButton->setChecked(false);
0324 
0325             appendLogText(i18n("%1 is unparking...", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0326             break;
0327 
0328         case ISD::Dome::DOME_TRACKING:
0329             enableMotionControl(true);
0330             motionCWButton->setEnabled(true);
0331             motionCCWButton->setChecked(true);
0332             appendLogText(i18n("%1 is tracking.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
0333             break;
0334     }
0335 }
0336 
0337 void Observatory::setDomeParkStatus(ISD::ParkStatus status)
0338 {
0339     qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting dome park status to " << status;
0340     switch (status)
0341     {
0342         case ISD::PARK_UNPARKED:
0343             activateButton(domePark, i18n("Park"));
0344             buttonPressed(domeUnpark, i18n("Unparked"));
0345             motionCWButton->setChecked(false);
0346             motionCWButton->setEnabled(true);
0347             motionCCWButton->setChecked(false);
0348 
0349             if (m_Dome->isRolloffRoof())
0350                 domeAzimuthPosition->setText(i18n("Open"));
0351             else
0352                 enableMotionControl(true);
0353             break;
0354 
0355         case ISD::PARK_PARKED:
0356             buttonPressed(domePark, i18n("Parked"));
0357             activateButton(domeUnpark, i18n("Unpark"));
0358             motionCWButton->setChecked(false);
0359             motionCCWButton->setChecked(false);
0360             motionCCWButton->setEnabled(false);
0361 
0362             if (m_Dome->isRolloffRoof())
0363                 domeAzimuthPosition->setText(i18n("Closed"));
0364             else
0365                 enableMotionControl(false);
0366             break;
0367 
0368         default:
0369             break;
0370     }
0371 }
0372 
0373 
0374 void Observatory::setShutterStatus(ISD::Dome::ShutterStatus status)
0375 {
0376     qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting shutter status to " << status;
0377 
0378     switch (status)
0379     {
0380         case ISD::Dome::SHUTTER_OPEN:
0381             buttonPressed(shutterOpen, i18n("Opened"));
0382             activateButton(shutterClosed, i18n("Close"));
0383             appendLogText(i18n("Shutter is open."));
0384             break;
0385 
0386         case ISD::Dome::SHUTTER_OPENING:
0387             toggleButtons(shutterOpen, i18n("Opening"), shutterClosed, i18n("Close"));
0388             appendLogText(i18n("Shutter is opening..."));
0389             break;
0390 
0391         case ISD::Dome::SHUTTER_CLOSED:
0392             buttonPressed(shutterClosed, i18n("Closed"));
0393             activateButton(shutterOpen, i18n("Open"));
0394             appendLogText(i18n("Shutter is closed."));
0395             break;
0396         case ISD::Dome::SHUTTER_CLOSING:
0397             toggleButtons(shutterClosed, i18n("Closing"), shutterOpen, i18n("Open"));
0398             appendLogText(i18n("Shutter is closing..."));
0399             break;
0400         default:
0401             break;
0402     }
0403 }
0404 
0405 void Observatory::enableWeather(bool enable)
0406 {
0407     weatherBox->setEnabled(enable);
0408     clearGraphHistory->setVisible(enable);
0409     clearGraphHistory->setEnabled(enable);
0410     autoscaleValuesCB->setVisible(enable);
0411     sensorData->setVisible(enable);
0412     sensorGraphs->setVisible(enable);
0413 }
0414 
0415 void Observatory::clearSensorDataHistory()
0416 {
0417     std::map<QString, QVector<QCPGraphData>*>::iterator it;
0418 
0419     for (it = sensorGraphData.begin(); it != sensorGraphData.end(); ++it)
0420     {
0421         QVector<QCPGraphData>* graphDataVector = it->second;
0422         if (graphDataVector->size() > 0)
0423         {
0424             // we keep only the last one
0425             QCPGraphData last = graphDataVector->last();
0426             graphDataVector->clear();
0427             QDateTime when = QDateTime();
0428             when.setTime_t(static_cast<uint>(last.key));
0429             updateSensorGraph(it->first, when, last.value);
0430         }
0431     }
0432 
0433     // force an update to the current graph
0434     if (!selectedSensorID.isEmpty())
0435         selectedSensorChanged(selectedSensorID);
0436 }
0437 
0438 bool Observatory::addWeatherSource(ISD::Weather *device)
0439 {
0440     // No duplicates
0441     if (m_WeatherSources.contains(device))
0442         return false;
0443 
0444     // Disconnect all
0445     for (auto &oneWeatherSource : m_WeatherSources)
0446         oneWeatherSource->disconnect(this);
0447 
0448     m_WeatherSource = device;
0449     m_WeatherSources.append(device);
0450     weatherSourceCombo->addItem(device->getDeviceName());
0451 
0452     initWeather();
0453 
0454     // make invisible, since not implemented yet
0455     weatherWarningSchedulerCB->setVisible(false);
0456     weatherAlertSchedulerCB->setVisible(false);
0457 
0458     return true;
0459 }
0460 
0461 void Observatory::enableMotionControl(bool enabled)
0462 {
0463     MotionBox->setEnabled(enabled);
0464 
0465     // absolute motion controls
0466     if (m_Dome->canAbsoluteMove())
0467     {
0468         motionMoveAbsButton->setEnabled(enabled);
0469         absoluteMotionSB->setEnabled(enabled);
0470     }
0471     else
0472     {
0473         motionMoveAbsButton->setEnabled(false);
0474         absoluteMotionSB->setEnabled(false);
0475     }
0476 
0477     // relative motion controls
0478     if (m_Dome->canRelativeMove())
0479     {
0480         motionMoveRelButton->setEnabled(enabled);
0481         relativeMotionSB->setEnabled(enabled);
0482         motionCWButton->setEnabled(enabled);
0483         motionCCWButton->setEnabled(enabled);
0484     }
0485     else
0486     {
0487         motionMoveRelButton->setEnabled(false);
0488         relativeMotionSB->setEnabled(false);
0489         motionCWButton->setEnabled(false);
0490         motionCCWButton->setEnabled(false);
0491     }
0492 
0493     // special case for rolloff roofs
0494     if (m_Dome->isRolloffRoof())
0495     {
0496         motionCWButton->setText(i18n("Open"));
0497         motionCCWButton->setText(i18n("Close"));
0498         motionCWButton->setEnabled(enabled);
0499         motionCCWButton->setEnabled(enabled);
0500         motionMoveAbsButton->setVisible(false);
0501         motionMoveRelButton->setVisible(false);
0502         absoluteMotionSB->setVisible(false);
0503         relativeMotionSB->setVisible(false);
0504     }
0505 }
0506 
0507 void Observatory::enableAutoSync(bool enabled)
0508 {
0509     if (m_Dome == nullptr)
0510         showAutoSync(false);
0511     else
0512     {
0513         m_Dome->setAutoSync(enabled);
0514         showAutoSync(enabled);
0515     }
0516 }
0517 
0518 void Observatory::showAutoSync(bool enabled)
0519 {
0520     slavingEnableButton->setChecked(enabled);
0521     slavingDisableButton->setChecked(! enabled);
0522 }
0523 
0524 void Observatory::initWeather()
0525 {
0526     enableWeather(true);
0527     weatherBox->setEnabled(true);
0528 
0529     connect(m_WeatherSource, &ISD::Weather::newStatus, this, &Ekos::Observatory::setWeatherStatus);
0530     connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::newWeatherData);
0531     connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::updateSensorData);
0532     connect(m_WeatherSource, &ISD::Weather::Disconnected, this, &Ekos::Observatory::shutdownWeather);
0533 
0534     autoscaleValuesCB->setChecked(autoScaleValues());
0535     weatherWarningBox->setChecked(getWarningActionsActive());
0536     weatherAlertBox->setChecked(getAlertActionsActive());
0537     setWeatherStatus(m_WeatherSource->status());
0538     setWarningActions(getWarningActions());
0539     setAlertActions(getAlertActions());
0540     initWeatherActions(true);
0541     weatherStatusTimer.start(1000);
0542 }
0543 
0544 void Observatory::shutdownWeather()
0545 {
0546     weatherStatusTimer.stop();
0547     setWeatherStatus(ISD::Weather::WEATHER_IDLE);
0548     enableWeather(false);
0549     // disable the UI controls for weather actions
0550     initWeatherActions(false);
0551 }
0552 
0553 void Observatory::initWeatherActions(bool enabled)
0554 {
0555     if (enabled && m_Dome != nullptr && m_Dome->isConnected())
0556     {
0557         // make the entire box visible
0558         weatherActionsBox->setVisible(true);
0559         weatherActionsBox->setEnabled(true);
0560 
0561         // enable warning and alert action control
0562         weatherAlertDomeCB->setEnabled(true);
0563         weatherWarningDomeCB->setEnabled(true);
0564 
0565         // only domes with shutters need shutter action controls
0566         if (m_Dome->hasShutter())
0567         {
0568             weatherAlertShutterCB->setEnabled(true);
0569             weatherWarningShutterCB->setEnabled(true);
0570         }
0571         else
0572         {
0573             weatherAlertShutterCB->setEnabled(false);
0574             weatherWarningShutterCB->setEnabled(false);
0575         }
0576     }
0577     else
0578     {
0579         weatherActionsBox->setVisible(false);
0580         weatherActionsBox->setEnabled(false);
0581     }
0582 }
0583 
0584 
0585 void Observatory::updateSensorGraph(const QString &sensor_label, QDateTime now, double value)
0586 {
0587     // we assume that labels are unique and use the full label as identifier
0588     QString id = sensor_label;
0589 
0590     // lazy instantiation of the sensor data storage
0591     if (sensorGraphData[id] == nullptr)
0592     {
0593         sensorGraphData[id] = new QVector<QCPGraphData>();
0594         sensorRanges[id] = value > 0 ? 1 : (value < 0 ? -1 : 0);
0595     }
0596 
0597     // store the data
0598     sensorGraphData[id]->append(QCPGraphData(static_cast<double>(now.toTime_t()), value));
0599 
0600     // add data for the graphs we display
0601     if (selectedSensorID == id)
0602     {
0603         // display first point in scattered style
0604         if (sensorGraphData[id]->size() == 1)
0605             sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::black, 0), QBrush(Qt::green),
0606                                                    5));
0607         else
0608             sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone));
0609 
0610         // display data point
0611         sensorGraphs->graph()->addData(sensorGraphData[id]->last().key, sensorGraphData[id]->last().value);
0612 
0613         // determine where the x axis is relatively to the value ranges
0614         if ((sensorRanges[id] > 0 && value < 0) || (sensorRanges[id] < 0 && value > 0))
0615             sensorRanges[id] = 0;
0616 
0617         refreshSensorGraph();
0618     }
0619 }
0620 
0621 void Observatory::updateSensorData(const QJsonArray &data)
0622 {
0623     QDateTime now = KStarsData::Instance()->lt();
0624 
0625     for (const auto &oneEntry : qAsConst(data))
0626     {
0627         auto label = oneEntry["label"].toString();
0628         auto value = oneEntry["value"].toDouble();
0629 
0630         auto id = oneEntry["label"].toString();
0631 
0632         if (sensorDataWidgets[id] == nullptr)
0633         {
0634             QPushButton* labelWidget = new QPushButton(label);
0635             labelWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0636             labelWidget->setCheckable(true);
0637             labelWidget->setStyleSheet("QPushButton:checked\n{\nbackground-color: maroon;\nborder: 1px outset;\nfont-weight:bold;\n}");
0638             // we need the object name since the label may contain '&' for keyboard shortcuts
0639             labelWidget->setObjectName(label);
0640 
0641             QLineEdit* valueWidget = new QLineEdit(QString().setNum(value, 'f', 2));
0642             // fix width to enable stretching of the graph
0643             valueWidget->setMinimumWidth(96);
0644             valueWidget->setMaximumWidth(96);
0645             valueWidget->setReadOnly(true);
0646             valueWidget->setAlignment(Qt::AlignRight);
0647 
0648             sensorDataWidgets[id] = new QPair<QAbstractButton*, QLineEdit*>(labelWidget, valueWidget);
0649 
0650             sensorDataBoxLayout->addWidget(labelWidget, sensorDataBoxLayout->rowCount(), 0);
0651             sensorDataBoxLayout->addWidget(valueWidget, sensorDataBoxLayout->rowCount() - 1, 1);
0652 
0653             // initial graph selection
0654             if (!selectedSensorID.isEmpty() && id.indexOf('(') > 0 && id.indexOf('(') < id.indexOf(')'))
0655             {
0656                 selectedSensorID = id;
0657                 labelWidget->setChecked(true);
0658             }
0659 
0660             sensorDataNamesGroup->addButton(labelWidget);
0661         }
0662         else
0663         {
0664             sensorDataWidgets[id]->first->setText(label);
0665             sensorDataWidgets[id]->second->setText(QString().setNum(value, 'f', 2));
0666         }
0667 
0668         // store sensor data unit if necessary
0669         updateSensorGraph(label, now, value);
0670     }
0671 }
0672 
0673 void Observatory::mouseOverLine(QMouseEvent *event)
0674 {
0675     double key = sensorGraphs->xAxis->pixelToCoord(event->localPos().x());
0676     QCPGraph *graph = qobject_cast<QCPGraph *>(sensorGraphs->plottableAt(event->pos(), false));
0677 
0678     if (graph)
0679     {
0680         int index = sensorGraphs->graph(0)->findBegin(key);
0681         double value   = sensorGraphs->graph(0)->dataMainValue(index);
0682         QDateTime when = QDateTime::fromTime_t(sensorGraphs->graph(0)->dataMainKey(index));
0683 
0684         QToolTip::showText(
0685             event->globalPos(),
0686             i18n("%1 = %2 @ %3", selectedSensorID, value, when.toString("hh:mm")));
0687     }
0688     else
0689     {
0690         QToolTip::hideText();
0691     }
0692 }
0693 
0694 
0695 void Observatory::refreshSensorGraph()
0696 {
0697     sensorGraphs->rescaleAxes();
0698 
0699     // restrict the y-Axis to the values range
0700     if (autoScaleValues() == false)
0701     {
0702         if (sensorRanges[selectedSensorID] > 0)
0703             sensorGraphs->yAxis->setRangeLower(0);
0704         else if (sensorRanges[selectedSensorID] < 0)
0705             sensorGraphs->yAxis->setRangeUpper(0);
0706     }
0707 
0708     sensorGraphs->replot();
0709 }
0710 
0711 void Observatory::selectedSensorChanged(QString id)
0712 {
0713     QVector<QCPGraphData> *data = sensorGraphData[id];
0714 
0715     if (data != nullptr)
0716     {
0717         // copy the graph data to the graph container
0718         QCPGraphDataContainer *container = new QCPGraphDataContainer();
0719         for (QVector<QCPGraphData>::iterator it = data->begin(); it != data->end(); ++it)
0720             container->add(QCPGraphData(it->key, it->value));
0721 
0722         sensorGraphs->graph()->setData(QSharedPointer<QCPGraphDataContainer>(container));
0723         selectedSensorID = id;
0724         refreshSensorGraph();
0725     }
0726 }
0727 
0728 void Observatory::setWeatherStatus(ISD::Weather::Status status)
0729 {
0730     QString label;
0731     if (status != m_WeatherStatus)
0732     {
0733         switch (status)
0734         {
0735             case ISD::Weather::WEATHER_OK:
0736                 label = "security-high";
0737                 appendLogText(i18n("Weather is OK"));
0738                 break;
0739             case ISD::Weather::WEATHER_WARNING:
0740                 label = "security-medium";
0741                 appendLogText(i18n("Weather Warning"));
0742                 break;
0743             case ISD::Weather::WEATHER_ALERT:
0744                 label = "security-low";
0745                 appendLogText(i18n("Weather Alert"));
0746                 break;
0747             default:
0748                 label = "";
0749                 break;
0750         }
0751 
0752         weatherStatusLabel->setPixmap(QIcon::fromTheme(label).pixmap(QSize(28, 28)));
0753         m_WeatherStatus = status;
0754     }
0755 
0756     // update weather sensor data
0757     if (m_WeatherSource)
0758         updateSensorData(m_WeatherSource->data());
0759 
0760 }
0761 
0762 void Observatory::initSensorGraphs()
0763 {
0764     // set some pens, brushes and backgrounds:
0765     sensorGraphs->xAxis->setBasePen(QPen(Qt::white, 1));
0766     sensorGraphs->yAxis->setBasePen(QPen(Qt::white, 1));
0767     sensorGraphs->xAxis->setTickPen(QPen(Qt::white, 1));
0768     sensorGraphs->yAxis->setTickPen(QPen(Qt::white, 1));
0769     sensorGraphs->xAxis->setSubTickPen(QPen(Qt::white, 1));
0770     sensorGraphs->yAxis->setSubTickPen(QPen(Qt::white, 1));
0771     sensorGraphs->xAxis->setTickLabelColor(Qt::white);
0772     sensorGraphs->yAxis->setTickLabelColor(Qt::white);
0773     sensorGraphs->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0774     sensorGraphs->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0775     sensorGraphs->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0776     sensorGraphs->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0777     sensorGraphs->xAxis->grid()->setSubGridVisible(true);
0778     sensorGraphs->yAxis->grid()->setSubGridVisible(true);
0779     sensorGraphs->xAxis->grid()->setZeroLinePen(Qt::NoPen);
0780     sensorGraphs->yAxis->grid()->setZeroLinePen(Qt::NoPen);
0781     sensorGraphs->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
0782     sensorGraphs->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
0783     QLinearGradient plotGradient;
0784     plotGradient.setStart(0, 0);
0785     plotGradient.setFinalStop(0, 350);
0786     plotGradient.setColorAt(0, QColor(80, 80, 80));
0787     plotGradient.setColorAt(1, QColor(50, 50, 50));
0788     sensorGraphs->setBackground(plotGradient);
0789     QLinearGradient axisRectGradient;
0790     axisRectGradient.setStart(0, 0);
0791     axisRectGradient.setFinalStop(0, 350);
0792     axisRectGradient.setColorAt(0, QColor(80, 80, 80));
0793     axisRectGradient.setColorAt(1, QColor(30, 30, 30));
0794     sensorGraphs->axisRect()->setBackground(axisRectGradient);
0795 
0796     QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
0797     dateTicker->setDateTimeFormat("hh:mm");
0798     dateTicker->setTickCount(2);
0799     sensorGraphs->xAxis->setTicker(dateTicker);
0800 
0801     // allow dragging in all directions
0802     sensorGraphs->setInteraction(QCP::iRangeDrag, true);
0803     sensorGraphs->setInteraction(QCP::iRangeZoom);
0804 
0805     // create the universal graph
0806     QCPGraph *graph = sensorGraphs->addGraph();
0807     graph->setPen(QPen(Qt::darkGreen, 2));
0808     graph->setBrush(QColor(10, 100, 50, 70));
0809 
0810     // ensure that the 0-line is visible
0811     sensorGraphs->yAxis->setRangeLower(0);
0812 
0813     sensorDataNamesGroup = new QButtonGroup();
0814     // enable changing the displayed sensor
0815     connect(sensorDataNamesGroup, static_cast<void (QButtonGroup::*)(QAbstractButton*)>(&QButtonGroup::buttonClicked), [this](
0816                 QAbstractButton * button)
0817     {
0818         selectedSensorChanged(button->objectName());
0819     });
0820 
0821     // show current temperature below the mouse
0822     connect(sensorGraphs, &QCustomPlot::mouseMove, this, &Ekos::Observatory::mouseOverLine);
0823 
0824 }
0825 
0826 void Observatory::weatherWarningSettingsChanged()
0827 {
0828     struct WeatherActions actions;
0829     actions.parkDome = weatherWarningDomeCB->isChecked();
0830     actions.closeShutter = weatherWarningShutterCB->isChecked();
0831     // Fixme: not implemented yet
0832     actions.stopScheduler = false;
0833     actions.delay = static_cast<unsigned int>(weatherWarningDelaySB->value());
0834 
0835     setWarningActions(actions);
0836 }
0837 
0838 void Observatory::weatherAlertSettingsChanged()
0839 {
0840     struct WeatherActions actions;
0841     actions.parkDome = weatherAlertDomeCB->isChecked();
0842     actions.closeShutter = weatherAlertShutterCB->isChecked();
0843     // Fixme: not implemented yet
0844     actions.stopScheduler = false;
0845     actions.delay = static_cast<unsigned int>(weatherAlertDelaySB->value());
0846 
0847     setAlertActions(actions);
0848 }
0849 
0850 void Observatory::domeAzimuthChanged(double position)
0851 {
0852     domeAzimuthPosition->setText(QString::number(position, 'f', 2));
0853 }
0854 
0855 void Observatory::setWarningActions(WeatherActions actions)
0856 {
0857     if (m_Dome != nullptr)
0858         weatherWarningDomeCB->setChecked(actions.parkDome);
0859     else
0860         weatherWarningDomeCB->setChecked(actions.parkDome);
0861 
0862     if (m_Dome != nullptr && m_Dome->hasShutter())
0863         weatherWarningShutterCB->setChecked(actions.closeShutter);
0864     else
0865         weatherWarningShutterCB->setChecked(actions.closeShutter);
0866 
0867     weatherWarningDelaySB->setValue(static_cast<int>(actions.delay));
0868 
0869     if (m_WeatherSource)
0870     {
0871         m_WarningActions = actions;
0872         Options::setWeatherWarningCloseDome(actions.parkDome);
0873         Options::setWeatherWarningCloseShutter(actions.closeShutter);
0874         Options::setWeatherWarningDelay(actions.delay);
0875         if (!warningTimer.isActive())
0876             warningTimer.setInterval(static_cast<int>(actions.delay * 1000));
0877 
0878         if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
0879             startWarningTimer();
0880     }
0881 }
0882 
0883 
0884 void Observatory::setAlertActions(WeatherActions actions)
0885 {
0886     if (m_Dome != nullptr)
0887         weatherAlertDomeCB->setChecked(actions.parkDome);
0888     else
0889         weatherAlertDomeCB->setChecked(false);
0890 
0891     if (m_Dome != nullptr && m_Dome->hasShutter())
0892         weatherAlertShutterCB->setChecked(actions.closeShutter);
0893     else
0894         weatherAlertShutterCB->setChecked(false);
0895 
0896     weatherAlertDelaySB->setValue(static_cast<int>(actions.delay));
0897 
0898     if (m_WeatherSource)
0899     {
0900         m_AlertActions = actions;
0901         Options::setWeatherAlertCloseDome(actions.parkDome);
0902         Options::setWeatherAlertCloseShutter(actions.closeShutter);
0903         Options::setWeatherAlertDelay(actions.delay);
0904         if (!alertTimer.isActive())
0905             alertTimer.setInterval(static_cast<int>(actions.delay * 1000));
0906 
0907         if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
0908             startAlertTimer();
0909     }
0910 }
0911 
0912 void Observatory::toggleButtons(QPushButton *buttonPressed, QString titlePressed, QPushButton *buttonCounterpart,
0913                                 QString titleCounterpart)
0914 {
0915     buttonPressed->setEnabled(false);
0916     buttonPressed->setText(titlePressed);
0917 
0918     buttonCounterpart->setEnabled(true);
0919     buttonCounterpart->setChecked(false);
0920     buttonCounterpart->setCheckable(false);
0921     buttonCounterpart->setText(titleCounterpart);
0922 }
0923 
0924 void Observatory::activateButton(QPushButton *button, QString title)
0925 {
0926     button->setEnabled(true);
0927     button->setCheckable(false);
0928     button->setText(title);
0929 }
0930 
0931 void Observatory::buttonPressed(QPushButton *button, QString title)
0932 {
0933     button->setEnabled(false);
0934     button->setCheckable(true);
0935     button->setChecked(true);
0936     button->setText(title);
0937 
0938 }
0939 
0940 void Observatory::statusControlSettingsChanged()
0941 {
0942     ObservatoryStatusControl control;
0943     control.useDome = useDomeCB->isChecked();
0944     control.useShutter = useShutterCB->isChecked();
0945     control.useWeather = useWeatherCB->isChecked();
0946     setStatusControl(control);
0947 }
0948 
0949 
0950 void Observatory::appendLogText(const QString &text)
0951 {
0952     m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
0953                               KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
0954 
0955     qCInfo(KSTARS_EKOS_OBSERVATORY) << text;
0956 
0957     emit newLog(text);
0958 }
0959 
0960 void Observatory::clearLog()
0961 {
0962     m_LogText.clear();
0963     emit newLog(QString());
0964 }
0965 
0966 void Observatory::setWarningActionsActive(bool active)
0967 {
0968     warningActionsActive = active;
0969     Options::setWarningActionsActive(active);
0970 
0971     // stop warning actions if deactivated
0972     if (!active && warningTimer.isActive())
0973         warningTimer.stop();
0974     // start warning timer if activated
0975     else if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
0976         startWarningTimer();
0977 }
0978 
0979 void Observatory::startWarningTimer()
0980 {
0981     if (warningActionsActive && (m_WarningActions.parkDome || m_WarningActions.closeShutter || m_WarningActions.stopScheduler))
0982     {
0983         if (!warningTimer.isActive())
0984             warningTimer.start();
0985     }
0986     else if (warningTimer.isActive())
0987         warningTimer.stop();
0988 }
0989 
0990 void Observatory::setAlertActionsActive(bool active)
0991 {
0992     alertActionsActive = active;
0993     Options::setAlertActionsActive(active);
0994 
0995     // stop alert actions if deactivated
0996     if (!active && alertTimer.isActive())
0997         alertTimer.stop();
0998     // start alert timer if activated
0999     else if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
1000         startAlertTimer();
1001 }
1002 
1003 void Observatory::setAutoScaleValues(bool value)
1004 {
1005     m_autoScaleValues = value;
1006     Options::setWeatherAutoScaleValues(value);
1007 }
1008 
1009 void Observatory::startAlertTimer()
1010 {
1011     if (alertActionsActive && (m_AlertActions.parkDome || m_AlertActions.closeShutter || m_AlertActions.stopScheduler))
1012     {
1013         if (!alertTimer.isActive())
1014             alertTimer.start();
1015     }
1016     else if (alertTimer.isActive())
1017         alertTimer.stop();
1018 }
1019 
1020 QString Observatory::getWarningActionsStatus()
1021 {
1022     if (warningTimer.isActive())
1023     {
1024         int remaining = warningTimer.remainingTime() / 1000;
1025         return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1026     }
1027 
1028     return i18n("Status: inactive");
1029 }
1030 
1031 QString Observatory::getAlertActionsStatus()
1032 {
1033     if (alertTimer.isActive())
1034     {
1035         int remaining = alertTimer.remainingTime() / 1000;
1036         return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1037     }
1038 
1039     return i18n("Status: inactive");
1040 }
1041 
1042 void Observatory::weatherChanged(ISD::Weather::Status status)
1043 {
1044     switch (status)
1045     {
1046         case ISD::Weather::WEATHER_OK:
1047             warningTimer.stop();
1048             alertTimer.stop();
1049             break;
1050         case ISD::Weather::WEATHER_WARNING:
1051             alertTimer.stop();
1052             startWarningTimer();
1053             break;
1054         case ISD::Weather::WEATHER_ALERT:
1055             warningTimer.stop();
1056             startAlertTimer();
1057             break;
1058         default:
1059             break;
1060     }
1061     //emit newStatus(status);
1062 }
1063 
1064 void Observatory::execute(WeatherActions actions)
1065 {
1066     if (!m_Dome)
1067         return;
1068 
1069     if (m_Dome->hasShutter() && actions.closeShutter)
1070         m_Dome->closeShutter();
1071     if (actions.parkDome)
1072         m_Dome->park();
1073 }
1074 
1075 
1076 void Observatory::setStatusControl(ObservatoryStatusControl control)
1077 {
1078     m_StatusControl = control;
1079     Options::setObservatoryStatusUseDome(control.useDome);
1080     Options::setObservatoryStatusUseShutter(control.useShutter);
1081     Options::setObservatoryStatusUseWeather(control.useWeather);
1082 }
1083 
1084 void Observatory::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved)
1085 {
1086     auto name = deviceRemoved->getDeviceName();
1087 
1088     // Check in Dome
1089 
1090     if (m_Dome && m_Dome->getDeviceName() == name)
1091     {
1092         m_Dome->disconnect(this);
1093         m_Dome = nullptr;
1094         shutdownDome();
1095     }
1096 
1097     if (m_WeatherSource && m_WeatherSource->getDeviceName() == name)
1098     {
1099         m_WeatherSource->disconnect(this);
1100         m_WeatherSource = nullptr;
1101         shutdownWeather();
1102     }
1103 
1104     // Check in Weather Sources.
1105     for (auto &oneSource : m_WeatherSources)
1106     {
1107         if (oneSource->getDeviceName() == name)
1108         {
1109             m_WeatherSources.removeAll(oneSource);
1110             weatherSourceCombo->removeItem(weatherSourceCombo->findText(name));
1111         }
1112     }
1113 }
1114 
1115 void Observatory::setWeatherSource(const QString &name)
1116 {
1117     Options::setDefaultObservatoryWeatherSource(name);
1118     for (auto &oneWeatherSource : m_WeatherSources)
1119     {
1120         if (oneWeatherSource->getDeviceName() == name)
1121         {
1122             // Same source, ignore and return
1123             if (m_WeatherSource == oneWeatherSource)
1124                 return;
1125 
1126             if (m_WeatherSource)
1127                 m_WeatherSource->disconnect(this);
1128 
1129             m_WeatherSource = oneWeatherSource;
1130 
1131             connect(m_WeatherSource, &ISD::Weather::newStatus, this, &Ekos::Observatory::setWeatherStatus);
1132             connect(m_WeatherSource, &ISD::Weather::Disconnected, this, &Ekos::Observatory::shutdownWeather);
1133 
1134             return;
1135         }
1136     }
1137 
1138 
1139 }
1140 
1141 }