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);
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
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{
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
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}
Class handles control of INDI dome devices.
Definition indidome.h:25
bool isRolloffRoof()
isRolloffRoof Do we have a roll off structure?
Definition indidome.h:155
Q_SCRIPTABLE bool setPosition(double position)
setPosition Set azimuth absolute position.
Definition indidome.cpp:363
Focuser class handles control of INDI Weather devices.
Definition indiweather.h:24
const KStarsDateTime & lt() const
Definition kstarsdata.h:153
void setBrush(const QBrush &brush)
void setPen(const QPen &pen)
Specialized axis ticker for calendar dates and times as axis ticks.
The generic data container for one-dimensional plottables.
void add(const QCPDataContainer< DataType > &data)
Holds the data of one single data point for QCPGraph.
A plottable representing a graph in a plot.
@ esSpikeArrow
A filled arrow head with an indented back.
Represents the visual appearance of scatter points.
@ 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)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
@ 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 addButton(QAbstractButton *button, int id)
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 addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment)
int rowCount() const const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
bool empty() const const
void removeWidget(QWidget *widget)
void setFinalStop(const QPointF &stop)
void setStart(const QPointF &start)
void setAlignment(Qt::Alignment flag)
void setReadOnly(bool)
iterator begin()
void clear()
bool contains(const AT &value) const const
iterator end()
iterator insert(const_iterator before, parameter_type value)
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)
T qobject_cast(QObject *object)
void setObjectName(QAnyStringView name)
void valueChanged(int i)
bool isEmpty() const const
QString number(double n, char format, int precision)
AlignRight
void setInterval(int msec)
bool isActive() const const
void setSingleShot(bool singleShot)
void start()
void stop()
void timeout()
void hideText()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
QList< QAction * > actions() const const
virtual bool event(QEvent *event) override
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-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:40 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.