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 }