Kstars

observatory.cpp
1/* Ekos Observatory Module
2 SPDX-FileCopyrightText: Wolfgang Reissenberger <sterne-jaeger@t-online.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "kstarsdata.h"
8
9#include "observatory.h"
10#include "observatoryadaptor.h"
11#include "Options.h"
12
13#include "ekos_observatory_debug.h"
14
15#include <QToolTip>
16#include <QButtonGroup>
17
18namespace Ekos
19{
20Observatory::Observatory()
21{
22 qRegisterMetaType<ISD::Weather::Status>("ISD::Weather::Status");
23 qDBusRegisterMetaType<ISD::Weather::Status>();
24
25 new ObservatoryAdaptor(this);
26 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Observatory", this);
27
28 setupUi(this);
29
30 // status control
31 //setObseratoryStatusControl(m_StatusControl);
32 // update UI for status control
33 connect(useDomeCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
34 connect(useShutterCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
35 connect(useWeatherCB, &QCheckBox::clicked, this, &Ekos::Observatory::statusControlSettingsChanged);
36 // ready button deactivated
37 // connect(statusReadyButton, &QPushButton::clicked, mObservatoryModel, &Ekos::ObservatoryModel::makeReady);
38 statusReadyButton->setEnabled(false);
39
40 // weather controls
41 connect(weatherWarningShutterCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged);
42 connect(weatherWarningDomeCB, &QCheckBox::clicked, this, &Observatory::weatherWarningSettingsChanged);
43 connect(weatherWarningDelaySB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int i)
44 {
45 Q_UNUSED(i)
46 weatherWarningSettingsChanged();
47 });
48
49 connect(weatherAlertShutterCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged);
50 connect(weatherAlertDomeCB, &QCheckBox::clicked, this, &Observatory::weatherAlertSettingsChanged);
51 connect(weatherAlertDelaySB, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int i)
52 {
53 Q_UNUSED(i)
54 weatherAlertSettingsChanged();
55 });
56
57 // read the default values
58 warningActionsActive = Options::warningActionsActive();
59 m_WarningActions.parkDome = Options::weatherWarningCloseDome();
60 m_WarningActions.closeShutter = Options::weatherWarningCloseShutter();
61 m_WarningActions.delay = Options::weatherWarningDelay();
62 alertActionsActive = Options::alertActionsActive();
63 m_AlertActions.parkDome = Options::weatherAlertCloseDome();
64 m_AlertActions.closeShutter = Options::weatherAlertCloseShutter();
65 m_AlertActions.delay = Options::weatherAlertDelay();
66 m_autoScaleValues = Options::weatherAutoScaleValues();
67
68 // not implemented yet
69 m_WarningActions.stopScheduler = false;
70 m_AlertActions.stopScheduler = false;
71
72 warningTimer.setInterval(static_cast<int>(m_WarningActions.delay * 1000));
73 warningTimer.setSingleShot(true);
74 alertTimer.setInterval(static_cast<int>(m_AlertActions.delay * 1000));
75 alertTimer.setSingleShot(true);
76
77 connect(&warningTimer, &QTimer::timeout, [this]()
78 {
79 execute(m_WarningActions);
80 });
81 connect(&alertTimer, &QTimer::timeout, [this]()
82 {
83 execute(m_AlertActions);
84 });
85
86 connect(weatherSourceCombo, &QComboBox::currentTextChanged, this, &Observatory::setWeatherSource);
87
88 // initialize the weather sensor data group box
89 sensorDataBoxLayout = new QGridLayout();
90 sensorData->setLayout(sensorDataBoxLayout);
91
92 initSensorGraphs();
93
94 connect(weatherWarningBox, &QGroupBox::clicked, this, &Observatory::setWarningActionsActive);
95 connect(weatherAlertBox, &QGroupBox::clicked, this, &Observatory::setAlertActionsActive);
96
97 connect(clearGraphHistory, &QPushButton::clicked, this, &Observatory::clearSensorDataHistory);
98 connect(autoscaleValuesCB, &QCheckBox::clicked, [this](bool checked)
99 {
100 setAutoScaleValues(checked);
101 refreshSensorGraph();
102 });
103 connect(&weatherStatusTimer, &QTimer::timeout, [this]()
104 {
105 weatherWarningStatusLabel->setText(getWarningActionsStatus());
106 weatherAlertStatusLabel->setText(getAlertActionsStatus());
107 });
108
109
110}
111
112bool Observatory::setDome(ISD::Dome *device)
113{
114 if (m_Dome == device)
115 return false;
116
117 if (m_Dome)
118 m_Dome->disconnect(this);
119
120 m_Dome = device;
121
122 domeBox->setEnabled(true);
123
124 connect(m_Dome, &ISD::Dome::Disconnected, this, &Ekos::Observatory::shutdownDome);
125 connect(m_Dome, &ISD::Dome::newStatus, this, &Ekos::Observatory::setDomeStatus);
126 connect(m_Dome, &ISD::Dome::newParkStatus, this, &Ekos::Observatory::setDomeParkStatus);
127 connect(m_Dome, &ISD::Dome::newShutterStatus, this, &Ekos::Observatory::setShutterStatus);
128 connect(m_Dome, &ISD::Dome::positionChanged, this, &Ekos::Observatory::domeAzimuthChanged);
129 connect(m_Dome, &ISD::Dome::newAutoSyncStatus, this, &Ekos::Observatory::showAutoSync);
130
131 // motion controls
132 connect(motionMoveAbsButton, &QCheckBox::clicked, [this]()
133 {
134 m_Dome->setPosition(absoluteMotionSB->value());
135 });
136
137 connect(motionMoveRelButton, &QCheckBox::clicked, [this]()
138 {
139 m_Dome->setRelativePosition(relativeMotionSB->value());
140 });
141
142 // abort button
143 connect(motionAbortButton, &QPushButton::clicked, m_Dome, &ISD::Dome::abort);
144
145
146 // dome motion buttons
147 connect(motionCWButton, &QPushButton::clicked, [ = ](bool checked)
148 {
149 m_Dome->moveDome(ISD::Dome::DOME_CW, checked ? ISD::Dome::MOTION_START : ISD::Dome::MOTION_STOP);
150 });
151 connect(motionCCWButton, &QPushButton::clicked, [ = ](bool checked)
152 {
153 m_Dome->moveDome(ISD::Dome::DOME_CCW, checked ? ISD::Dome::MOTION_START : ISD::Dome::MOTION_STOP);
154 });
155
156 if (m_Dome->canPark())
157 {
158 connect(domePark, &QPushButton::clicked, m_Dome, &ISD::Dome::park);
159 connect(domeUnpark, &QPushButton::clicked, m_Dome, &ISD::Dome::unpark);
160 domePark->setEnabled(true);
161 domeUnpark->setEnabled(true);
162 }
163 else
164 {
165 domePark->setEnabled(false);
166 domeUnpark->setEnabled(false);
167 }
168
169 enableMotionControl(true);
170
171 if (m_Dome->isRolloffRoof())
172 {
173 SlavingBox->setVisible(false);
174 domeAzimuthPosition->setText(i18nc("Not Applicable", "N/A"));
175 }
176 else
177 {
178 // initialize the dome motion controls
179 domeAzimuthChanged(m_Dome->position());
180
181 // slaving
182 showAutoSync(m_Dome->isAutoSync());
183 connect(slavingEnableButton, &QPushButton::clicked, this, [this]()
184 {
185 enableAutoSync(true);
186 });
187 connect(slavingDisableButton, &QPushButton::clicked, this, [this]()
188 {
189 enableAutoSync(false);
190 });
191 }
192
193 // shutter handling
194 if (m_Dome->hasShutter())
195 {
196 shutterBox->setVisible(true);
197 shutterBox->setEnabled(true);
198 connect(shutterOpen, &QPushButton::clicked, m_Dome, &ISD::Dome::openShutter);
199 connect(shutterClosed, &QPushButton::clicked, m_Dome, &ISD::Dome::closeShutter);
200 shutterClosed->setEnabled(true);
201 shutterOpen->setEnabled(true);
202 setShutterStatus(m_Dome->shutterStatus());
203 useShutterCB->setVisible(true);
204 }
205 else
206 {
207 shutterBox->setVisible(false);
208 weatherWarningShutterCB->setVisible(false);
209 weatherAlertShutterCB->setVisible(false);
210 useShutterCB->setVisible(false);
211 }
212
213 // abort button should always be available
214 motionAbortButton->setEnabled(true);
215
216 statusDefinitionBox->setVisible(true);
217 statusDefinitionBox->setEnabled(true);
218
219 // update the dome parking status
220 setDomeParkStatus(m_Dome->parkStatus());
221
222 // enable the UI controls for dome weather actions
223 initWeatherActions(m_Dome && m_WeatherSource);
224
225 return true;
226}
227
228void Observatory::shutdownDome()
229{
230 shutterBox->setEnabled(false);
231 shutterBox->setVisible(false);
232 domePark->setEnabled(false);
233 domeUnpark->setEnabled(false);
234 shutterClosed->setEnabled(false);
235 shutterOpen->setEnabled(false);
236
237 if (m_Dome)
238 disconnect(m_Dome);
239
240 // disable the UI controls for dome weather actions
241 initWeatherActions(false);
242 statusDefinitionBox->setVisible(false);
243 domeBox->setEnabled(false);
244}
245
246void Observatory::setDomeStatus(ISD::Dome::Status status)
247{
248 qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting dome status to " << status;
249
250 switch (status)
251 {
252 case ISD::Dome::DOME_ERROR:
253 appendLogText(i18n("%1 error. See INDI log for details.",
254 m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
255 motionCWButton->setChecked(false);
256 motionCCWButton->setChecked(false);
257 break;
258
259 case ISD::Dome::DOME_IDLE:
260 motionCWButton->setChecked(false);
261 motionCWButton->setEnabled(true);
262 motionCCWButton->setChecked(false);
263 motionCCWButton->setEnabled(true);
264
265 appendLogText(i18n("%1 is idle.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
266 break;
267
268 case ISD::Dome::DOME_MOVING_CW:
269 motionCWButton->setChecked(true);
270 motionCWButton->setEnabled(false);
271 motionCCWButton->setChecked(false);
272 motionCCWButton->setEnabled(true);
273 if (m_Dome->isRolloffRoof())
274 {
275 domeAzimuthPosition->setText(i18n("Opening"));
276 toggleButtons(domeUnpark, i18n("Unparking"), domePark, i18n("Park"));
277 appendLogText(i18n("Rolloff roof opening..."));
278 }
279 else
280 {
281 appendLogText(i18n("Dome is moving clockwise..."));
282 }
283 break;
284
285 case ISD::Dome::DOME_MOVING_CCW:
286 motionCWButton->setChecked(false);
287 motionCWButton->setEnabled(true);
288 motionCCWButton->setChecked(true);
289 motionCCWButton->setEnabled(false);
290 if (m_Dome->isRolloffRoof())
291 {
292 domeAzimuthPosition->setText(i18n("Closing"));
293 toggleButtons(domePark, i18n("Parking"), domeUnpark, i18n("Unpark"));
294 appendLogText(i18n("Rolloff roof is closing..."));
295 }
296 else
297 {
298 appendLogText(i18n("Dome is moving counter clockwise..."));
299 }
300 break;
301
302 case ISD::Dome::DOME_PARKED:
303 setDomeParkStatus(ISD::PARK_PARKED);
304
305 appendLogText(i18n("%1 is parked.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
306 break;
307
308 case ISD::Dome::DOME_PARKING:
309 toggleButtons(domePark, i18n("Parking"), domeUnpark, i18n("Unpark"));
310 motionCWButton->setEnabled(true);
311
312 if (m_Dome->isRolloffRoof())
313 domeAzimuthPosition->setText(i18n("Closing"));
314 else
315 enableMotionControl(false);
316
317 motionCWButton->setChecked(false);
318 motionCCWButton->setChecked(true);
319
320 appendLogText(i18n("%1 is parking...", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
321 break;
322
323 case ISD::Dome::DOME_UNPARKING:
324 toggleButtons(domeUnpark, i18n("Unparking"), domePark, i18n("Park"));
325 motionCCWButton->setEnabled(true);
326
327 if (m_Dome->isRolloffRoof())
328 domeAzimuthPosition->setText(i18n("Opening"));
329 else
330 enableMotionControl(false);
331
332 motionCWButton->setChecked(true);
333 motionCCWButton->setChecked(false);
334
335 appendLogText(i18n("%1 is unparking...", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
336 break;
337
338 case ISD::Dome::DOME_TRACKING:
339 enableMotionControl(true);
340 motionCWButton->setEnabled(true);
341 motionCCWButton->setChecked(true);
342 appendLogText(i18n("%1 is tracking.", m_Dome->isRolloffRoof() ? i18n("Rolloff roof") : i18n("Dome")));
343 break;
344 }
345}
346
347void Observatory::setDomeParkStatus(ISD::ParkStatus status)
348{
349 qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting dome park status to " << status;
350 switch (status)
351 {
352 case ISD::PARK_UNPARKED:
353 activateButton(domePark, i18n("Park"));
354 buttonPressed(domeUnpark, i18n("Unparked"));
355 motionCWButton->setChecked(false);
356 motionCWButton->setEnabled(true);
357 motionCCWButton->setChecked(false);
358
359 if (m_Dome->isRolloffRoof())
360 domeAzimuthPosition->setText(i18n("Open"));
361 else
362 enableMotionControl(true);
363 break;
364
365 case ISD::PARK_PARKED:
366 buttonPressed(domePark, i18n("Parked"));
367 activateButton(domeUnpark, i18n("Unpark"));
368 motionCWButton->setChecked(false);
369 motionCCWButton->setChecked(false);
370 motionCCWButton->setEnabled(false);
371
372 if (m_Dome->isRolloffRoof())
373 domeAzimuthPosition->setText(i18n("Closed"));
374 else
375 enableMotionControl(false);
376 break;
377
378 default:
379 break;
380 }
381}
382
383
384void Observatory::setShutterStatus(ISD::Dome::ShutterStatus status)
385{
386 qCDebug(KSTARS_EKOS_OBSERVATORY) << "Setting shutter status to " << status;
387
388 switch (status)
389 {
390 case ISD::Dome::SHUTTER_OPEN:
391 buttonPressed(shutterOpen, i18n("Opened"));
392 activateButton(shutterClosed, i18n("Close"));
393 appendLogText(i18n("Shutter is open."));
394 break;
395
396 case ISD::Dome::SHUTTER_OPENING:
397 toggleButtons(shutterOpen, i18n("Opening"), shutterClosed, i18n("Close"));
398 appendLogText(i18n("Shutter is opening..."));
399 break;
400
401 case ISD::Dome::SHUTTER_CLOSED:
402 buttonPressed(shutterClosed, i18n("Closed"));
403 activateButton(shutterOpen, i18n("Open"));
404 appendLogText(i18n("Shutter is closed."));
405 break;
406 case ISD::Dome::SHUTTER_CLOSING:
407 toggleButtons(shutterClosed, i18n("Closing"), shutterOpen, i18n("Open"));
408 appendLogText(i18n("Shutter is closing..."));
409 break;
410 default:
411 break;
412 }
413}
414
415void Observatory::enableWeather(bool enable)
416{
417 weatherBox->setEnabled(enable);
418 clearGraphHistory->setVisible(enable);
419 clearGraphHistory->setEnabled(enable);
420 autoscaleValuesCB->setVisible(enable);
421 sensorData->setVisible(enable);
422 sensorGraphs->setVisible(enable);
423}
424
425void Observatory::clearSensorDataHistory()
426{
427 std::map<QString, QVector<QCPGraphData>*>::iterator it;
428
429 for (it = sensorGraphData.begin(); it != sensorGraphData.end(); ++it)
430 {
431 QVector<QCPGraphData>* graphDataVector = it->second;
432 if (graphDataVector->size() > 0)
433 {
434 // we keep only the last one
435 QCPGraphData last = graphDataVector->last();
436 graphDataVector->clear();
437 QDateTime when = QDateTime();
438 when.setSecsSinceEpoch(static_cast<uint>(last.key));
439 updateSensorGraph(it->first, when, last.value);
440 }
441 }
442
443 // force an update to the current graph
444 if (!selectedSensorID.isEmpty())
445 selectedSensorChanged(selectedSensorID);
446}
447
448bool Observatory::addWeatherSource(ISD::Weather *device)
449{
450 // No duplicates
451 if (m_WeatherSources.contains(device))
452 return false;
453
454 // Disconnect all
455 for (auto &oneWeatherSource : m_WeatherSources)
456 oneWeatherSource->disconnect(this);
457
458 // If default source is empty or matches current device then let's set the current weather source to it
459 auto defaultSource = Options::defaultObservatoryWeatherSource();
460 if (m_WeatherSource == nullptr || defaultSource.isEmpty() || device->getDeviceName() == defaultSource)
461 m_WeatherSource = device;
462 m_WeatherSources.append(device);
463
464 weatherSourceCombo->blockSignals(true);
465 weatherSourceCombo->clear();
466 for (auto &oneSource : m_WeatherSources)
467 weatherSourceCombo->addItem(oneSource->getDeviceName());
468 if (defaultSource.isEmpty())
469 Options::setDefaultObservatoryWeatherSource(weatherSourceCombo->currentText());
470 else
471 weatherSourceCombo->setCurrentText(defaultSource);
472 weatherSourceCombo->blockSignals(false);
473
474 initWeather();
475
476 // make invisible, since not implemented yet
477 weatherWarningSchedulerCB->setVisible(false);
478 weatherAlertSchedulerCB->setVisible(false);
479
480 return true;
481}
482
483void Observatory::enableMotionControl(bool enabled)
484{
485 MotionBox->setEnabled(enabled);
486
487 // absolute motion controls
488 if (m_Dome->canAbsoluteMove())
489 {
490 motionMoveAbsButton->setEnabled(enabled);
491 absoluteMotionSB->setEnabled(enabled);
492 }
493 else
494 {
495 motionMoveAbsButton->setEnabled(false);
496 absoluteMotionSB->setEnabled(false);
497 }
498
499 // relative motion controls
500 if (m_Dome->canRelativeMove())
501 {
502 motionMoveRelButton->setEnabled(enabled);
503 relativeMotionSB->setEnabled(enabled);
504 motionCWButton->setEnabled(enabled);
505 motionCCWButton->setEnabled(enabled);
506 }
507 else
508 {
509 motionMoveRelButton->setEnabled(false);
510 relativeMotionSB->setEnabled(false);
511 motionCWButton->setEnabled(false);
512 motionCCWButton->setEnabled(false);
513 }
514
515 // special case for rolloff roofs
516 if (m_Dome->isRolloffRoof())
517 {
518 motionCWButton->setText(i18n("Open"));
519 motionCWButton->setToolTip(QString());
520 motionCCWButton->setText(i18n("Close"));
521 motionCCWButton->setToolTip(QString());
522 motionCWButton->setEnabled(enabled);
523 motionCCWButton->setEnabled(enabled);
524 motionMoveAbsButton->setVisible(false);
525 motionMoveRelButton->setVisible(false);
526 absoluteMotionSB->setVisible(false);
527 relativeMotionSB->setVisible(false);
528 }
529}
530
531void Observatory::enableAutoSync(bool enabled)
532{
533 if (m_Dome == nullptr)
534 showAutoSync(false);
535 else
536 {
537 m_Dome->setAutoSync(enabled);
538 showAutoSync(enabled);
539 }
540}
541
542void Observatory::showAutoSync(bool enabled)
543{
544 slavingEnableButton->setChecked(enabled);
545 slavingDisableButton->setChecked(! enabled);
546}
547
548void Observatory::initWeather()
549{
550 enableWeather(true);
551 weatherBox->setEnabled(true);
552
553 connect(m_WeatherSource, &ISD::Weather::newStatus, this, &Ekos::Observatory::setWeatherStatus);
554 connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::newWeatherData);
555 connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::updateSensorData);
556 connect(m_WeatherSource, &ISD::Weather::Disconnected, this, &Ekos::Observatory::shutdownWeather);
557
558 autoscaleValuesCB->setChecked(autoScaleValues());
559 weatherWarningBox->setChecked(getWarningActionsActive());
560 weatherAlertBox->setChecked(getAlertActionsActive());
561 setWeatherStatus(m_WeatherSource->status());
562 setWarningActions(getWarningActions());
563 setAlertActions(getAlertActions());
564 initWeatherActions(true);
565 weatherStatusTimer.start(1000);
566}
567
568void Observatory::shutdownWeather()
569{
570 weatherStatusTimer.stop();
571 setWeatherStatus(ISD::Weather::WEATHER_IDLE);
572 enableWeather(false);
573 // disable the UI controls for weather actions
574 initWeatherActions(false);
575}
576
577void Observatory::initWeatherActions(bool enabled)
578{
579 if (enabled && m_Dome != nullptr && m_Dome->isConnected())
580 {
581 // make the entire box visible
582 weatherActionsBox->setVisible(true);
583 weatherActionsBox->setEnabled(true);
584
585 // enable warning and alert action control
586 weatherAlertDomeCB->setEnabled(true);
587 weatherWarningDomeCB->setEnabled(true);
588
589 // only domes with shutters need shutter action controls
590 if (m_Dome->hasShutter())
591 {
592 weatherAlertShutterCB->setEnabled(true);
593 weatherWarningShutterCB->setEnabled(true);
594 }
595 else
596 {
597 weatherAlertShutterCB->setEnabled(false);
598 weatherWarningShutterCB->setEnabled(false);
599 }
600 }
601 else
602 {
603 weatherActionsBox->setVisible(false);
604 weatherActionsBox->setEnabled(false);
605 }
606}
607
608
609void Observatory::updateSensorGraph(const QString &sensor_label, QDateTime now, double value)
610{
611 // we assume that labels are unique and use the full label as identifier
612 QString id = sensor_label;
613
614 // lazy instantiation of the sensor data storage
615 if (sensorGraphData[id] == nullptr)
616 {
617 sensorGraphData[id] = new QVector<QCPGraphData>();
618 sensorRanges[id] = value > 0 ? 1 : (value < 0 ? -1 : 0);
619 }
620
621 // store the data
622 sensorGraphData[id]->append(QCPGraphData(static_cast<double>(now.toSecsSinceEpoch()), value));
623
624 // add data for the graphs we display
625 if (selectedSensorID == id)
626 {
627 // display first point in scattered style
628 if (sensorGraphData[id]->size() == 1)
629 sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::black, 0), QBrush(Qt::green),
630 5));
631 else
632 sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone));
633
634 // display data point
635 sensorGraphs->graph()->addData(sensorGraphData[id]->last().key, sensorGraphData[id]->last().value);
636
637 // determine where the x axis is relatively to the value ranges
638 if ((sensorRanges[id] > 0 && value < 0) || (sensorRanges[id] < 0 && value > 0))
639 sensorRanges[id] = 0;
640
641 refreshSensorGraph();
642 }
643}
644
645void Observatory::updateSensorData(const QJsonArray &data)
646{
647 if (data.empty())
648 return;
649
650 QDateTime now = KStarsData::Instance()->lt();
651
652 for (const auto &oneEntry : std::as_const(data))
653 {
654 auto label = oneEntry[QString("label")].toString();
655 auto value = oneEntry[QString("value")].toDouble();
656
657 auto id = oneEntry[QString("label")].toString();
658
659 if (sensorDataWidgets[id] == nullptr)
660 {
661 QPushButton* labelWidget = new QPushButton(label, this);
662 labelWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
663 labelWidget->setCheckable(true);
664 labelWidget->setStyleSheet("QPushButton:checked\n{\nbackground-color: maroon;\nborder: 1px outset;\nfont-weight:bold;\n}");
665 // we need the object name since the label may contain '&' for keyboard shortcuts
666 labelWidget->setObjectName(label);
667
668 QLineEdit* valueWidget = new QLineEdit(QString().setNum(value, 'f', 2), this);
669 // fix width to enable stretching of the graph
670 valueWidget->setMinimumWidth(96);
671 valueWidget->setMaximumWidth(96);
672 valueWidget->setReadOnly(true);
673 valueWidget->setAlignment(Qt::AlignRight);
674
675 sensorDataWidgets[id] = new QPair<QAbstractButton*, QLineEdit*>(labelWidget, valueWidget);
676
677 sensorDataBoxLayout->addWidget(labelWidget, sensorDataBoxLayout->rowCount(), 0);
678 sensorDataBoxLayout->addWidget(valueWidget, sensorDataBoxLayout->rowCount() - 1, 1);
679
680 // initial graph selection
681 if (!selectedSensorID.isEmpty() && id.indexOf('(') > 0 && id.indexOf('(') < id.indexOf(')'))
682 {
683 selectedSensorID = id;
684 labelWidget->setChecked(true);
685 }
686
687 sensorDataNamesGroup->addButton(labelWidget);
688 }
689 else
690 {
691 sensorDataWidgets[id]->first->setText(label);
692 sensorDataWidgets[id]->second->setText(QString().setNum(value, 'f', 2));
693 }
694
695 // store sensor data unit if necessary
696 updateSensorGraph(label, now, value);
697 }
698}
699
700void Observatory::mouseOverLine(QMouseEvent *event)
701{
702 double key = sensorGraphs->xAxis->pixelToCoord(event->localPos().x());
703 QCPGraph *graph = qobject_cast<QCPGraph *>(sensorGraphs->plottableAt(event->pos(), false));
704
705 if (graph)
706 {
707 int index = sensorGraphs->graph(0)->findBegin(key);
708 double value = sensorGraphs->graph(0)->dataMainValue(index);
709 QDateTime when = QDateTime::fromSecsSinceEpoch(sensorGraphs->graph(0)->dataMainKey(index));
710
712 event->globalPos(),
713 i18n("%1 = %2 @ %3", selectedSensorID, value, when.toString("hh:mm")));
714 }
715 else
716 {
718 }
719}
720
721
722void Observatory::refreshSensorGraph()
723{
724 sensorGraphs->rescaleAxes();
725
726 // restrict the y-Axis to the values range
727 if (autoScaleValues() == false)
728 {
729 if (sensorRanges[selectedSensorID] > 0)
730 sensorGraphs->yAxis->setRangeLower(0);
731 else if (sensorRanges[selectedSensorID] < 0)
732 sensorGraphs->yAxis->setRangeUpper(0);
733 }
734
735 sensorGraphs->replot();
736}
737
738void Observatory::selectedSensorChanged(QString id)
739{
740 QVector<QCPGraphData> *data = sensorGraphData[id];
741
742 if (data != nullptr)
743 {
744 // copy the graph data to the graph container
745 QCPGraphDataContainer *container = new QCPGraphDataContainer();
746 for (QVector<QCPGraphData>::iterator it = data->begin(); it != data->end(); ++it)
747 container->add(QCPGraphData(it->key, it->value));
748
749 sensorGraphs->graph()->setData(QSharedPointer<QCPGraphDataContainer>(container));
750 selectedSensorID = id;
751 refreshSensorGraph();
752 }
753}
754
755void Observatory::setWeatherStatus(ISD::Weather::Status status)
756{
757 QString label;
758 if (status != m_WeatherStatus)
759 {
760 switch (status)
761 {
762 case ISD::Weather::WEATHER_OK:
763 label = "security-high";
764 appendLogText(i18n("Weather is OK"));
765 warningTimer.stop();
766 alertTimer.stop();
767 break;
768 case ISD::Weather::WEATHER_WARNING:
769 label = "security-medium";
770 appendLogText(i18n("Weather Warning"));
771 alertTimer.stop();
772 startWarningTimer();
773 break;
774 case ISD::Weather::WEATHER_ALERT:
775 label = "security-low";
776 appendLogText(i18n("Weather Alert"));
777 warningTimer.stop();
778 startAlertTimer();
779 break;
780 default:
781 label = QString();
782 break;
783 }
784
785 weatherStatusLabel->setPixmap(QIcon::fromTheme(label).pixmap(QSize(28, 28)));
786 m_WeatherStatus = status;
787 emit newStatus(m_WeatherStatus);
788 }
789
790 // update weather sensor data
791 if (m_WeatherSource)
792 updateSensorData(m_WeatherSource->data());
793
794}
795
796void Observatory::initSensorGraphs()
797{
798 // set some pens, brushes and backgrounds:
799 sensorGraphs->xAxis->setBasePen(QPen(Qt::white, 1));
800 sensorGraphs->yAxis->setBasePen(QPen(Qt::white, 1));
801 sensorGraphs->xAxis->setTickPen(QPen(Qt::white, 1));
802 sensorGraphs->yAxis->setTickPen(QPen(Qt::white, 1));
803 sensorGraphs->xAxis->setSubTickPen(QPen(Qt::white, 1));
804 sensorGraphs->yAxis->setSubTickPen(QPen(Qt::white, 1));
805 sensorGraphs->xAxis->setTickLabelColor(Qt::white);
806 sensorGraphs->yAxis->setTickLabelColor(Qt::white);
807 sensorGraphs->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
808 sensorGraphs->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
809 sensorGraphs->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
810 sensorGraphs->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
811 sensorGraphs->xAxis->grid()->setSubGridVisible(true);
812 sensorGraphs->yAxis->grid()->setSubGridVisible(true);
813 sensorGraphs->xAxis->grid()->setZeroLinePen(Qt::NoPen);
814 sensorGraphs->yAxis->grid()->setZeroLinePen(Qt::NoPen);
815 sensorGraphs->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
816 sensorGraphs->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
817 QLinearGradient plotGradient;
818 plotGradient.setStart(0, 0);
819 plotGradient.setFinalStop(0, 350);
820 plotGradient.setColorAt(0, QColor(80, 80, 80));
821 plotGradient.setColorAt(1, QColor(50, 50, 50));
822 sensorGraphs->setBackground(plotGradient);
823 QLinearGradient axisRectGradient;
824 axisRectGradient.setStart(0, 0);
825 axisRectGradient.setFinalStop(0, 350);
826 axisRectGradient.setColorAt(0, QColor(80, 80, 80));
827 axisRectGradient.setColorAt(1, QColor(30, 30, 30));
828 sensorGraphs->axisRect()->setBackground(axisRectGradient);
829
830 QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
831 dateTicker->setDateTimeFormat("hh:mm");
832 dateTicker->setTickCount(2);
833 sensorGraphs->xAxis->setTicker(dateTicker);
834
835 // allow dragging in all directions
836 sensorGraphs->setInteraction(QCP::iRangeDrag, true);
837 sensorGraphs->setInteraction(QCP::iRangeZoom);
838
839 // create the universal graph
840 QCPGraph *graph = sensorGraphs->addGraph();
841 graph->setPen(QPen(Qt::darkGreen, 2));
842 graph->setBrush(QColor(10, 100, 50, 70));
843
844 // ensure that the 0-line is visible
845 sensorGraphs->yAxis->setRangeLower(0);
846
847 sensorDataNamesGroup = new QButtonGroup();
848 // enable changing the displayed sensor
849 connect(sensorDataNamesGroup, static_cast<void (QButtonGroup::*)(QAbstractButton*)>(&QButtonGroup::buttonClicked), [this](
850 QAbstractButton * button)
851 {
852 selectedSensorChanged(button->objectName());
853 });
854
855 // show current temperature below the mouse
856 connect(sensorGraphs, &QCustomPlot::mouseMove, this, &Ekos::Observatory::mouseOverLine);
857
858}
859
860void Observatory::weatherWarningSettingsChanged()
861{
862 struct WeatherActions actions;
863 actions.parkDome = weatherWarningDomeCB->isChecked();
864 actions.closeShutter = weatherWarningShutterCB->isChecked();
865 // Fixme: not implemented yet
866 actions.stopScheduler = false;
867 actions.delay = static_cast<unsigned int>(weatherWarningDelaySB->value());
868
869 setWarningActions(actions);
870}
871
872void Observatory::weatherAlertSettingsChanged()
873{
874 struct WeatherActions actions;
875 actions.parkDome = weatherAlertDomeCB->isChecked();
876 actions.closeShutter = weatherAlertShutterCB->isChecked();
877 // Fixme: not implemented yet
878 actions.stopScheduler = false;
879 actions.delay = static_cast<unsigned int>(weatherAlertDelaySB->value());
880
881 setAlertActions(actions);
882}
883
884void Observatory::domeAzimuthChanged(double position)
885{
886 domeAzimuthPosition->setText(QString::number(position, 'f', 2));
887}
888
889void Observatory::setWarningActions(WeatherActions actions)
890{
891 if (m_Dome != nullptr)
892 weatherWarningDomeCB->setChecked(actions.parkDome);
893 else
894 weatherWarningDomeCB->setChecked(actions.parkDome);
895
896 if (m_Dome != nullptr && m_Dome->hasShutter())
897 weatherWarningShutterCB->setChecked(actions.closeShutter);
898 else
899 weatherWarningShutterCB->setChecked(actions.closeShutter);
900
901 weatherWarningDelaySB->setValue(static_cast<int>(actions.delay));
902
903 if (m_WeatherSource)
904 {
905 m_WarningActions = actions;
906 Options::setWeatherWarningCloseDome(actions.parkDome);
907 Options::setWeatherWarningCloseShutter(actions.closeShutter);
908 Options::setWeatherWarningDelay(actions.delay);
909 if (!warningTimer.isActive())
910 warningTimer.setInterval(static_cast<int>(actions.delay * 1000));
911
912 if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
913 startWarningTimer();
914 }
915}
916
917void Observatory::setAlertActions(WeatherActions actions)
918{
919 if (m_Dome != nullptr)
920 weatherAlertDomeCB->setChecked(actions.parkDome);
921 else
922 weatherAlertDomeCB->setChecked(false);
923
924 if (m_Dome != nullptr && m_Dome->hasShutter())
925 weatherAlertShutterCB->setChecked(actions.closeShutter);
926 else
927 weatherAlertShutterCB->setChecked(false);
928
929 weatherAlertDelaySB->setValue(static_cast<int>(actions.delay));
930
931 if (m_WeatherSource)
932 {
933 m_AlertActions = actions;
934 Options::setWeatherAlertCloseDome(actions.parkDome);
935 Options::setWeatherAlertCloseShutter(actions.closeShutter);
936 Options::setWeatherAlertDelay(actions.delay);
937 if (!alertTimer.isActive())
938 alertTimer.setInterval(static_cast<int>(actions.delay * 1000));
939
940 if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
941 startAlertTimer();
942 }
943}
944
945void Observatory::toggleButtons(QPushButton *buttonPressed, QString titlePressed, QPushButton *buttonCounterpart,
946 QString titleCounterpart)
947{
948 buttonPressed->setEnabled(false);
949 buttonPressed->setText(titlePressed);
950
951 buttonCounterpart->setEnabled(true);
952 buttonCounterpart->setChecked(false);
953 buttonCounterpart->setCheckable(false);
954 buttonCounterpart->setText(titleCounterpart);
955}
956
957void Observatory::activateButton(QPushButton *button, QString title)
958{
959 button->setEnabled(true);
960 button->setCheckable(false);
961 button->setText(title);
962}
963
964void Observatory::buttonPressed(QPushButton *button, QString title)
965{
966 button->setEnabled(false);
967 button->setCheckable(true);
968 button->setChecked(true);
969 button->setText(title);
970
971}
972
973void Observatory::statusControlSettingsChanged()
974{
975 ObservatoryStatusControl control;
976 control.useDome = useDomeCB->isChecked();
977 control.useShutter = useShutterCB->isChecked();
978 control.useWeather = useWeatherCB->isChecked();
979 setStatusControl(control);
980}
981
982
983void Observatory::appendLogText(const QString &text)
984{
985 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
986 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
987
988 qCInfo(KSTARS_EKOS_OBSERVATORY) << text;
989
990 emit newLog(text);
991}
992
993void Observatory::clearLog()
994{
995 m_LogText.clear();
996 emit newLog(QString());
997}
998
999void Observatory::setWarningActionsActive(bool active)
1000{
1001 warningActionsActive = active;
1002 Options::setWarningActionsActive(active);
1003
1004 // stop warning actions if deactivated
1005 if (!active && warningTimer.isActive())
1006 warningTimer.stop();
1007 // start warning timer if activated
1008 else if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
1009 startWarningTimer();
1010}
1011
1012void Observatory::startWarningTimer()
1013{
1014 if (warningActionsActive && (m_WarningActions.parkDome || m_WarningActions.closeShutter || m_WarningActions.stopScheduler))
1015 {
1016 if (!warningTimer.isActive())
1017 warningTimer.start();
1018 }
1019 else if (warningTimer.isActive())
1020 warningTimer.stop();
1021}
1022
1023void Observatory::setAlertActionsActive(bool active)
1024{
1025 alertActionsActive = active;
1026 Options::setAlertActionsActive(active);
1027
1028 // stop alert actions if deactivated
1029 if (!active && alertTimer.isActive())
1030 alertTimer.stop();
1031 // start alert timer if activated
1032 else if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
1033 startAlertTimer();
1034}
1035
1036void Observatory::setAutoScaleValues(bool value)
1037{
1038 m_autoScaleValues = value;
1039 Options::setWeatherAutoScaleValues(value);
1040}
1041
1042void Observatory::startAlertTimer()
1043{
1044 if (alertActionsActive && (m_AlertActions.parkDome || m_AlertActions.closeShutter || m_AlertActions.stopScheduler))
1045 {
1046 if (!alertTimer.isActive())
1047 alertTimer.start();
1048 }
1049 else if (alertTimer.isActive())
1050 alertTimer.stop();
1051}
1052
1053QString Observatory::getWarningActionsStatus()
1054{
1055 if (warningTimer.isActive())
1056 {
1057 int remaining = warningTimer.remainingTime() / 1000;
1058 return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1059 }
1060
1061 return i18n("Status: inactive");
1062}
1063
1064QString Observatory::getAlertActionsStatus()
1065{
1066 if (alertTimer.isActive())
1067 {
1068 int remaining = alertTimer.remainingTime() / 1000;
1069 return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1070 }
1071
1072 return i18n("Status: inactive");
1073}
1074
1075void Observatory::execute(WeatherActions actions)
1076{
1077 if (!m_Dome)
1078 return;
1079
1080 if (m_Dome->hasShutter() && actions.closeShutter)
1081 m_Dome->closeShutter();
1082 if (actions.parkDome)
1083 m_Dome->park();
1084}
1085
1086
1087void Observatory::setStatusControl(ObservatoryStatusControl control)
1088{
1089 m_StatusControl = control;
1090 Options::setObservatoryStatusUseDome(control.useDome);
1091 Options::setObservatoryStatusUseShutter(control.useShutter);
1092 Options::setObservatoryStatusUseWeather(control.useWeather);
1093}
1094
1095void Observatory::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved)
1096{
1097 auto name = deviceRemoved->getDeviceName();
1098
1099 // Check in Dome
1100
1101 if (m_Dome && m_Dome->getDeviceName() == name)
1102 {
1103 m_Dome->disconnect(this);
1104 m_Dome = nullptr;
1105 shutdownDome();
1106 }
1107
1108 if (m_WeatherSource && m_WeatherSource->getDeviceName() == name)
1109 {
1110 m_WeatherSource->disconnect(this);
1111 m_WeatherSource = nullptr;
1112 shutdownWeather();
1113 }
1114
1115 // Check in Weather Sources.
1116 for (auto &oneSource : m_WeatherSources)
1117 {
1118 if (oneSource->getDeviceName() == name)
1119 {
1120 m_WeatherSources.removeAll(oneSource);
1121 weatherSourceCombo->removeItem(weatherSourceCombo->findText(name));
1122 }
1123 }
1124}
1125
1126void Observatory::setWeatherSource(const QString &name)
1127{
1128 Options::setDefaultObservatoryWeatherSource(name);
1129 for (auto &oneWeatherSource : m_WeatherSources)
1130 {
1131 if (oneWeatherSource->getDeviceName() == name)
1132 {
1133 // Same source, ignore and return
1134 if (m_WeatherSource == oneWeatherSource)
1135 return;
1136
1137 if (m_WeatherSource)
1138 m_WeatherSource->disconnect(this);
1139
1140 m_WeatherSource = oneWeatherSource;
1141
1142 // Must delete all the Buttons and Line-edits
1143 for (auto &oneWidget : sensorDataWidgets)
1144 {
1145 auto pair = oneWidget.second;
1146 sensorDataBoxLayout->removeWidget(pair->first);
1147 sensorDataBoxLayout->removeWidget(pair->second);
1148 pair->first->deleteLater();
1149 pair->second->deleteLater();
1150 }
1151 sensorDataWidgets.clear();
1152 initWeather();
1153 return;
1154 }
1155 }
1156}
1157
1158}
const KStarsDateTime & lt() const
Definition kstarsdata.h:153
void setBrush(const QBrush &brush)
void setPen(const QPen &pen)
void add(const QCPDataContainer< DataType > &data)
@ esSpikeArrow
A filled arrow head with an indented back.
@ ssCircle
\enumimage{ssCircle.png} a circle
@ ssNone
no scatter symbols are drawn (e.g. in QCPGraph, data only represented with lines)
void mouseMove(QMouseEvent *event)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
QString name(StandardAction id)
QString label(StandardShortcut id)
@ iRangeDrag
0x001 Axis ranges are draggable (see QCPAxisRect::setRangeDrag, QCPAxisRect::setRangeDragAxes)
@ iRangeZoom
0x002 Axis ranges are zoomable with the mouse wheel (see QCPAxisRect::setRangeZoom,...
void setCheckable(bool)
void setChecked(bool)
void clicked(bool checked)
void setText(const QString &text)
void buttonClicked(QAbstractButton *button)
void currentTextChanged(const QString &text)
QDateTime fromSecsSinceEpoch(qint64 secs)
void setSecsSinceEpoch(qint64 secs)
qint64 toSecsSinceEpoch() const const
QString toString(QStringView format, QCalendar cal) const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void setColorAt(qreal position, const QColor &color)
void clicked(bool checked)
QIcon fromTheme(const QString &name)
bool empty() const const
void setFinalStop(const QPointF &stop)
void setStart(const QPointF &start)
void setAlignment(Qt::Alignment flag)
void setReadOnly(bool)
iterator begin()
void clear()
iterator end()
T & last()
qsizetype size() const const
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
T qobject_cast(QObject *object)
void setObjectName(QAnyStringView name)
void valueChanged(int i)
QString & append(QChar ch)
QString number(double n, char format, int precision)
AlignRight
void timeout()
void hideText()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
QList< QAction * > actions() const const
void setMaximumWidth(int maxw)
void setMinimumWidth(int minw)
void setupUi(QWidget *widget)
void setSizePolicy(QSizePolicy)
void setStyleSheet(const QString &styleSheet)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:56:00 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.