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

KDE's Doxygen guidelines are available online.