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

KDE's Doxygen guidelines are available online.