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 setWeatherSource(weatherSourceCombo->currentText());
553
554 connect(m_WeatherSource, &ISD::Weather::newStatus, this, &Ekos::Observatory::setWeatherStatus);
555 connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::newWeatherData);
556 connect(m_WeatherSource, &ISD::Weather::newData, this, &Ekos::Observatory::updateSensorData);
557 connect(m_WeatherSource, &ISD::Weather::Disconnected, this, &Ekos::Observatory::shutdownWeather);
558
559 autoscaleValuesCB->setChecked(autoScaleValues());
560 weatherWarningBox->setChecked(getWarningActionsActive());
561 weatherAlertBox->setChecked(getAlertActionsActive());
562 setWeatherStatus(m_WeatherSource->status());
563 setWarningActions(getWarningActions());
564 setAlertActions(getAlertActions());
565 initWeatherActions(true);
566 weatherStatusTimer.start(1000);
567}
568
569void Observatory::shutdownWeather()
570{
571 weatherStatusTimer.stop();
572 setWeatherStatus(ISD::Weather::WEATHER_IDLE);
573 enableWeather(false);
574 // disable the UI controls for weather actions
575 initWeatherActions(false);
576}
577
578void Observatory::initWeatherActions(bool enabled)
579{
580 if (enabled && m_Dome != nullptr && m_Dome->isConnected())
581 {
582 // make the entire box visible
583 weatherActionsBox->setVisible(true);
584 weatherActionsBox->setEnabled(true);
585
586 // enable warning and alert action control
587 weatherAlertDomeCB->setEnabled(true);
588 weatherWarningDomeCB->setEnabled(true);
589
590 // only domes with shutters need shutter action controls
591 if (m_Dome->hasShutter())
592 {
593 weatherAlertShutterCB->setEnabled(true);
594 weatherWarningShutterCB->setEnabled(true);
595 }
596 else
597 {
598 weatherAlertShutterCB->setEnabled(false);
599 weatherWarningShutterCB->setEnabled(false);
600 }
601 }
602 else
603 {
604 weatherActionsBox->setVisible(false);
605 weatherActionsBox->setEnabled(false);
606 }
607}
608
609
610void Observatory::updateSensorGraph(const QString &sensor_label, QDateTime now, double value)
611{
612 // we assume that labels are unique and use the full label as identifier
613 QString id = sensor_label;
614
615 // lazy instantiation of the sensor data storage
616 if (sensorGraphData[id] == nullptr)
617 {
618 sensorGraphData[id] = new QVector<QCPGraphData>();
619 sensorRanges[id] = value > 0 ? 1 : (value < 0 ? -1 : 0);
620 }
621
622 // store the data
623 sensorGraphData[id]->append(QCPGraphData(static_cast<double>(now.toSecsSinceEpoch()), value));
624
625 // add data for the graphs we display
626 if (selectedSensorID == id)
627 {
628 // display first point in scattered style
629 if (sensorGraphData[id]->size() == 1)
630 sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::black, 0), QBrush(Qt::green),
631 5));
632 else
633 sensorGraphs->graph()->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone));
634
635 // display data point
636 sensorGraphs->graph()->addData(sensorGraphData[id]->last().key, sensorGraphData[id]->last().value);
637
638 // determine where the x axis is relatively to the value ranges
639 if ((sensorRanges[id] > 0 && value < 0) || (sensorRanges[id] < 0 && value > 0))
640 sensorRanges[id] = 0;
641
642 refreshSensorGraph();
643 }
644}
645
646void Observatory::updateSensorData(const QJsonArray &data)
647{
648 if (data.empty())
649 return;
650
651 QDateTime now = KStarsData::Instance()->lt();
652
653 for (const auto &oneEntry : std::as_const(data))
654 {
655 auto label = oneEntry[QString("label")].toString();
656 auto value = oneEntry[QString("value")].toDouble();
657
658 auto id = oneEntry[QString("label")].toString();
659
660 if (sensorDataWidgets[id] == nullptr)
661 {
662 QPushButton* labelWidget = new QPushButton(label, this);
663 labelWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
664 labelWidget->setCheckable(true);
665 labelWidget->setStyleSheet("QPushButton:checked\n{\nbackground-color: maroon;\nborder: 1px outset;\nfont-weight:bold;\n}");
666 // we need the object name since the label may contain '&' for keyboard shortcuts
667 labelWidget->setObjectName(label);
668
669 QLineEdit* valueWidget = new QLineEdit(QString().setNum(value, 'f', 2), this);
670 // fix width to enable stretching of the graph
671 valueWidget->setMinimumWidth(96);
672 valueWidget->setMaximumWidth(96);
673 valueWidget->setReadOnly(true);
674 valueWidget->setAlignment(Qt::AlignRight);
675
676 sensorDataWidgets[id] = new QPair<QAbstractButton*, QLineEdit*>(labelWidget, valueWidget);
677
678 sensorDataBoxLayout->addWidget(labelWidget, sensorDataBoxLayout->rowCount(), 0);
679 sensorDataBoxLayout->addWidget(valueWidget, sensorDataBoxLayout->rowCount() - 1, 1);
680
681 // initial graph selection
682 if (!selectedSensorID.isEmpty() && id.indexOf('(') > 0 && id.indexOf('(') < id.indexOf(')'))
683 {
684 selectedSensorID = id;
685 labelWidget->setChecked(true);
686 }
687
688 sensorDataNamesGroup->addButton(labelWidget);
689 }
690 else
691 {
692 sensorDataWidgets[id]->first->setText(label);
693 sensorDataWidgets[id]->second->setText(QString().setNum(value, 'f', 2));
694 }
695
696 // store sensor data unit if necessary
697 updateSensorGraph(label, now, value);
698 }
699}
700
701void Observatory::mouseOverLine(QMouseEvent *event)
702{
703 double key = sensorGraphs->xAxis->pixelToCoord(event->localPos().x());
704 QCPGraph *graph = qobject_cast<QCPGraph *>(sensorGraphs->plottableAt(event->pos(), false));
705
706 if (graph)
707 {
708 int index = sensorGraphs->graph(0)->findBegin(key);
709 double value = sensorGraphs->graph(0)->dataMainValue(index);
710 QDateTime when = QDateTime::fromSecsSinceEpoch(sensorGraphs->graph(0)->dataMainKey(index));
711
713 event->globalPos(),
714 i18n("%1 = %2 @ %3", selectedSensorID, value, when.toString("hh:mm")));
715 }
716 else
717 {
719 }
720}
721
722
723void Observatory::refreshSensorGraph()
724{
725 sensorGraphs->rescaleAxes();
726
727 // restrict the y-Axis to the values range
728 if (autoScaleValues() == false)
729 {
730 if (sensorRanges[selectedSensorID] > 0)
731 sensorGraphs->yAxis->setRangeLower(0);
732 else if (sensorRanges[selectedSensorID] < 0)
733 sensorGraphs->yAxis->setRangeUpper(0);
734 }
735
736 sensorGraphs->replot();
737}
738
739void Observatory::selectedSensorChanged(QString id)
740{
741 QVector<QCPGraphData> *data = sensorGraphData[id];
742
743 if (data != nullptr)
744 {
745 // copy the graph data to the graph container
746 QCPGraphDataContainer *container = new QCPGraphDataContainer();
747 for (QVector<QCPGraphData>::iterator it = data->begin(); it != data->end(); ++it)
748 container->add(QCPGraphData(it->key, it->value));
749
750 sensorGraphs->graph()->setData(QSharedPointer<QCPGraphDataContainer>(container));
751 selectedSensorID = id;
752 refreshSensorGraph();
753 }
754}
755
756void Observatory::setWeatherStatus(ISD::Weather::Status status)
757{
758 QString label;
759 if (status != m_WeatherStatus)
760 {
761 switch (status)
762 {
763 case ISD::Weather::WEATHER_OK:
764 label = "security-high";
765 appendLogText(i18n("Weather is OK"));
766 warningTimer.stop();
767 alertTimer.stop();
768 break;
769 case ISD::Weather::WEATHER_WARNING:
770 label = "security-medium";
771 appendLogText(i18n("Weather Warning"));
772 alertTimer.stop();
773 startWarningTimer();
774 break;
775 case ISD::Weather::WEATHER_ALERT:
776 label = "security-low";
777 appendLogText(i18n("Weather Alert"));
778 warningTimer.stop();
779 startAlertTimer();
780 break;
781 default:
782 label = QString();
783 break;
784 }
785
786 weatherStatusLabel->setPixmap(QIcon::fromTheme(label).pixmap(QSize(28, 28)));
787 m_WeatherStatus = status;
788 emit newStatus(m_WeatherStatus);
789 }
790
791 // update weather sensor data
792 if (m_WeatherSource)
793 updateSensorData(m_WeatherSource->data());
794
795}
796
797void Observatory::initSensorGraphs()
798{
799 // set some pens, brushes and backgrounds:
800 sensorGraphs->xAxis->setBasePen(QPen(Qt::white, 1));
801 sensorGraphs->yAxis->setBasePen(QPen(Qt::white, 1));
802 sensorGraphs->xAxis->setTickPen(QPen(Qt::white, 1));
803 sensorGraphs->yAxis->setTickPen(QPen(Qt::white, 1));
804 sensorGraphs->xAxis->setSubTickPen(QPen(Qt::white, 1));
805 sensorGraphs->yAxis->setSubTickPen(QPen(Qt::white, 1));
806 sensorGraphs->xAxis->setTickLabelColor(Qt::white);
807 sensorGraphs->yAxis->setTickLabelColor(Qt::white);
808 sensorGraphs->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
809 sensorGraphs->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
810 sensorGraphs->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
811 sensorGraphs->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
812 sensorGraphs->xAxis->grid()->setSubGridVisible(true);
813 sensorGraphs->yAxis->grid()->setSubGridVisible(true);
814 sensorGraphs->xAxis->grid()->setZeroLinePen(Qt::NoPen);
815 sensorGraphs->yAxis->grid()->setZeroLinePen(Qt::NoPen);
816 sensorGraphs->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
817 sensorGraphs->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
818 QLinearGradient plotGradient;
819 plotGradient.setStart(0, 0);
820 plotGradient.setFinalStop(0, 350);
821 plotGradient.setColorAt(0, QColor(80, 80, 80));
822 plotGradient.setColorAt(1, QColor(50, 50, 50));
823 sensorGraphs->setBackground(plotGradient);
824 QLinearGradient axisRectGradient;
825 axisRectGradient.setStart(0, 0);
826 axisRectGradient.setFinalStop(0, 350);
827 axisRectGradient.setColorAt(0, QColor(80, 80, 80));
828 axisRectGradient.setColorAt(1, QColor(30, 30, 30));
829 sensorGraphs->axisRect()->setBackground(axisRectGradient);
830
831 QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
832 dateTicker->setDateTimeFormat("hh:mm");
833 dateTicker->setTickCount(2);
834 sensorGraphs->xAxis->setTicker(dateTicker);
835
836 // allow dragging in all directions
837 sensorGraphs->setInteraction(QCP::iRangeDrag, true);
838 sensorGraphs->setInteraction(QCP::iRangeZoom);
839
840 // create the universal graph
841 QCPGraph *graph = sensorGraphs->addGraph();
842 graph->setPen(QPen(Qt::darkGreen, 2));
843 graph->setBrush(QColor(10, 100, 50, 70));
844
845 // ensure that the 0-line is visible
846 sensorGraphs->yAxis->setRangeLower(0);
847
848 sensorDataNamesGroup = new QButtonGroup();
849 // enable changing the displayed sensor
850 connect(sensorDataNamesGroup, static_cast<void (QButtonGroup::*)(QAbstractButton*)>(&QButtonGroup::buttonClicked), [this](
851 QAbstractButton * button)
852 {
853 selectedSensorChanged(button->objectName());
854 });
855
856 // show current temperature below the mouse
857 connect(sensorGraphs, &QCustomPlot::mouseMove, this, &Ekos::Observatory::mouseOverLine);
858
859}
860
861void Observatory::weatherWarningSettingsChanged()
862{
863 struct WeatherActions actions;
864 actions.parkDome = weatherWarningDomeCB->isChecked();
865 actions.closeShutter = weatherWarningShutterCB->isChecked();
866 // Fixme: not implemented yet
867 actions.stopScheduler = false;
868 actions.delay = static_cast<unsigned int>(weatherWarningDelaySB->value());
869
870 setWarningActions(actions);
871}
872
873void Observatory::weatherAlertSettingsChanged()
874{
875 struct WeatherActions actions;
876 actions.parkDome = weatherAlertDomeCB->isChecked();
877 actions.closeShutter = weatherAlertShutterCB->isChecked();
878 // Fixme: not implemented yet
879 actions.stopScheduler = false;
880 actions.delay = static_cast<unsigned int>(weatherAlertDelaySB->value());
881
882 setAlertActions(actions);
883}
884
885void Observatory::domeAzimuthChanged(double position)
886{
887 domeAzimuthPosition->setText(QString::number(position, 'f', 2));
888}
889
890void Observatory::setWarningActions(WeatherActions actions)
891{
892 if (m_Dome != nullptr)
893 weatherWarningDomeCB->setChecked(actions.parkDome);
894 else
895 weatherWarningDomeCB->setChecked(actions.parkDome);
896
897 if (m_Dome != nullptr && m_Dome->hasShutter())
898 weatherWarningShutterCB->setChecked(actions.closeShutter);
899 else
900 weatherWarningShutterCB->setChecked(actions.closeShutter);
901
902 weatherWarningDelaySB->setValue(static_cast<int>(actions.delay));
903
904 if (m_WeatherSource)
905 {
906 m_WarningActions = actions;
907 Options::setWeatherWarningCloseDome(actions.parkDome);
908 Options::setWeatherWarningCloseShutter(actions.closeShutter);
909 Options::setWeatherWarningDelay(actions.delay);
910 if (!warningTimer.isActive())
911 warningTimer.setInterval(static_cast<int>(actions.delay * 1000));
912
913 if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
914 startWarningTimer();
915 }
916}
917
918void Observatory::setAlertActions(WeatherActions actions)
919{
920 if (m_Dome != nullptr)
921 weatherAlertDomeCB->setChecked(actions.parkDome);
922 else
923 weatherAlertDomeCB->setChecked(false);
924
925 if (m_Dome != nullptr && m_Dome->hasShutter())
926 weatherAlertShutterCB->setChecked(actions.closeShutter);
927 else
928 weatherAlertShutterCB->setChecked(false);
929
930 weatherAlertDelaySB->setValue(static_cast<int>(actions.delay));
931
932 if (m_WeatherSource)
933 {
934 m_AlertActions = actions;
935 Options::setWeatherAlertCloseDome(actions.parkDome);
936 Options::setWeatherAlertCloseShutter(actions.closeShutter);
937 Options::setWeatherAlertDelay(actions.delay);
938 if (!alertTimer.isActive())
939 alertTimer.setInterval(static_cast<int>(actions.delay * 1000));
940
941 if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
942 startAlertTimer();
943 }
944}
945
946void Observatory::toggleButtons(QPushButton *buttonPressed, QString titlePressed, QPushButton *buttonCounterpart,
947 QString titleCounterpart)
948{
949 buttonPressed->setEnabled(false);
950 buttonPressed->setText(titlePressed);
951
952 buttonCounterpart->setEnabled(true);
953 buttonCounterpart->setChecked(false);
954 buttonCounterpart->setCheckable(false);
955 buttonCounterpart->setText(titleCounterpart);
956}
957
958void Observatory::activateButton(QPushButton *button, QString title)
959{
960 button->setEnabled(true);
961 button->setCheckable(false);
962 button->setText(title);
963}
964
965void Observatory::buttonPressed(QPushButton *button, QString title)
966{
967 button->setEnabled(false);
968 button->setCheckable(true);
969 button->setChecked(true);
970 button->setText(title);
971
972}
973
974void Observatory::statusControlSettingsChanged()
975{
976 ObservatoryStatusControl control;
977 control.useDome = useDomeCB->isChecked();
978 control.useShutter = useShutterCB->isChecked();
979 control.useWeather = useWeatherCB->isChecked();
980 setStatusControl(control);
981}
982
983
984void Observatory::appendLogText(const QString &text)
985{
986 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
987 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
988
989 qCInfo(KSTARS_EKOS_OBSERVATORY) << text;
990
991 emit newLog(text);
992}
993
994void Observatory::clearLog()
995{
996 m_LogText.clear();
997 emit newLog(QString());
998}
999
1000void Observatory::setWarningActionsActive(bool active)
1001{
1002 warningActionsActive = active;
1003 Options::setWarningActionsActive(active);
1004
1005 // stop warning actions if deactivated
1006 if (!active && warningTimer.isActive())
1007 warningTimer.stop();
1008 // start warning timer if activated
1009 else if (m_WeatherSource->status() == ISD::Weather::WEATHER_WARNING)
1010 startWarningTimer();
1011}
1012
1013void Observatory::startWarningTimer()
1014{
1015 if (warningActionsActive && (m_WarningActions.parkDome || m_WarningActions.closeShutter || m_WarningActions.stopScheduler))
1016 {
1017 if (!warningTimer.isActive())
1018 warningTimer.start();
1019 }
1020 else if (warningTimer.isActive())
1021 warningTimer.stop();
1022}
1023
1024void Observatory::setAlertActionsActive(bool active)
1025{
1026 alertActionsActive = active;
1027 Options::setAlertActionsActive(active);
1028
1029 // stop alert actions if deactivated
1030 if (!active && alertTimer.isActive())
1031 alertTimer.stop();
1032 // start alert timer if activated
1033 else if (m_WeatherSource->status() == ISD::Weather::WEATHER_ALERT)
1034 startAlertTimer();
1035}
1036
1037void Observatory::setAutoScaleValues(bool value)
1038{
1039 m_autoScaleValues = value;
1040 Options::setWeatherAutoScaleValues(value);
1041}
1042
1043void Observatory::startAlertTimer()
1044{
1045 if (alertActionsActive && (m_AlertActions.parkDome || m_AlertActions.closeShutter || m_AlertActions.stopScheduler))
1046 {
1047 if (!alertTimer.isActive())
1048 alertTimer.start();
1049 }
1050 else if (alertTimer.isActive())
1051 alertTimer.stop();
1052}
1053
1054QString Observatory::getWarningActionsStatus()
1055{
1056 if (warningTimer.isActive())
1057 {
1058 int remaining = warningTimer.remainingTime() / 1000;
1059 return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1060 }
1061
1062 return i18n("Status: inactive");
1063}
1064
1065QString Observatory::getAlertActionsStatus()
1066{
1067 if (alertTimer.isActive())
1068 {
1069 int remaining = alertTimer.remainingTime() / 1000;
1070 return i18np("%1 second remaining", "%1 seconds remaining", remaining);
1071 }
1072
1073 return i18n("Status: inactive");
1074}
1075
1076void Observatory::execute(WeatherActions actions)
1077{
1078 if (!m_Dome)
1079 return;
1080
1081 if (m_Dome->hasShutter() && actions.closeShutter)
1082 m_Dome->closeShutter();
1083 if (actions.parkDome)
1084 m_Dome->park();
1085}
1086
1087
1088void Observatory::setStatusControl(ObservatoryStatusControl control)
1089{
1090 m_StatusControl = control;
1091 Options::setObservatoryStatusUseDome(control.useDome);
1092 Options::setObservatoryStatusUseShutter(control.useShutter);
1093 Options::setObservatoryStatusUseWeather(control.useWeather);
1094}
1095
1096void Observatory::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved)
1097{
1098 auto name = deviceRemoved->getDeviceName();
1099
1100 // Check in Dome
1101
1102 if (m_Dome && m_Dome->getDeviceName() == name)
1103 {
1104 m_Dome->disconnect(this);
1105 m_Dome = nullptr;
1106 shutdownDome();
1107 }
1108
1109 if (m_WeatherSource && m_WeatherSource->getDeviceName() == name)
1110 {
1111 m_WeatherSource->disconnect(this);
1112 m_WeatherSource = nullptr;
1113 shutdownWeather();
1114 }
1115
1116 // Check in Weather Sources.
1117 for (auto &oneSource : m_WeatherSources)
1118 {
1119 if (oneSource->getDeviceName() == name)
1120 {
1121 m_WeatherSources.removeAll(oneSource);
1122 weatherSourceCombo->removeItem(weatherSourceCombo->findText(name));
1123 }
1124 }
1125}
1126
1127void Observatory::setWeatherSource(const QString &name)
1128{
1129 Options::setDefaultObservatoryWeatherSource(name);
1130 for (auto &oneWeatherSource : m_WeatherSources)
1131 {
1132 if (oneWeatherSource->getDeviceName() == name)
1133 {
1134 // Same source, ignore and return
1135 if (m_WeatherSource == oneWeatherSource)
1136 return;
1137
1138 if (m_WeatherSource)
1139 m_WeatherSource->disconnect(this);
1140
1141 m_WeatherSource = oneWeatherSource;
1142
1143 // Must delete all the Buttons and Line-edits
1144 for (auto &oneWidget : sensorDataWidgets)
1145 {
1146 auto pair = oneWidget.second;
1147 sensorDataBoxLayout->removeWidget(pair->first);
1148 sensorDataBoxLayout->removeWidget(pair->second);
1149 pair->first->deleteLater();
1150 pair->second->deleteLater();
1151 }
1152 sensorDataWidgets.clear();
1153 initWeather();
1154 return;
1155 }
1156 }
1157}
1158
1159}
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(const QVariant &location)
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 Mar 28 2025 11:57:24 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.