Kstars

message.cpp
1/* Ekos Live Message
2
3 SPDX-FileCopyrightText: 2018 Jasem Mutlaq <mutlaqja@ikarustech.com>
4
5 Message Channel
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "message.h"
11#include "commands.h"
12#include "profileinfo.h"
13#include "indi/drivermanager.h"
14#include "indi/indilistener.h"
15#include "auxiliary/ksmessagebox.h"
16#include "ekos/auxiliary/filtermanager.h"
17#include "ekos/auxiliary/opticaltrainmanager.h"
18#include "ekos/auxiliary/profilesettings.h"
19#include "ekos/capture/capture.h"
20#include "ekos/focus/focusmodule.h"
21#include "ekos/guide/guide.h"
22#include "ekos/mount/mount.h"
23#include "ekos/scheduler/scheduler.h"
24#include "ekos/scheduler/schedulermodulestate.h"
25#include "kstars.h"
26#include "kspaths.h"
27#include "kstarsdata.h"
28#include "ekos_debug.h"
29#include "ksalmanac.h"
30#include "skymapcomposite.h"
31#include "catalogobject.h"
32#include "fitsviewer/fitsviewer.h"
33#include "fitsviewer/fitstab.h"
34#include "ekos/auxiliary/darklibrary.h"
35#include "skymap.h"
36#include "Options.h"
37#include "version.h"
38
39#include <KActionCollection>
40#include <basedevice.h>
41#include <QUuid>
42#include <thread>
43
44namespace EkosLive
45{
46Message::Message(Ekos::Manager *manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
47 m_Manager(manager), m_NodeManagers(nodeManagers), m_DSOManager(CatalogsDB::dso_db_path())
48{
49 for (auto &nodeManager : m_NodeManagers)
50 {
51 connect(nodeManager->message(), &Node::connected, this, &Message::onConnected);
52 connect(nodeManager->message(), &Node::disconnected, this, &Message::onDisconnected);
53 connect(nodeManager->message(), &Node::onTextReceived, this, &Message::onTextReceived);
54 }
55
56 connect(manager, &Ekos::Manager::newModule, this, &Message::sendModuleState);
57 connect(INDIListener::Instance(), &INDIListener::deviceRemoved,
58 this, [this](const QSharedPointer<ISD::GenericDevice> &device)
59 {
60 // Clear any pending properties for this device
61 QMutableSetIterator<PendingProperty> it(m_PendingProperties);
62 while (it.hasNext())
63 {
64 const auto &pending = it.next();
65 if (pending.device == device->getDeviceName())
66 it.remove();
67 }
68 });
69
70 m_ThrottleTS = QDateTime::currentDateTime();
71
72 m_PendingPropertiesTimer.setInterval(500);
73 connect(&m_PendingPropertiesTimer, &QTimer::timeout, this, &Message::sendPendingProperties);
74
75 m_DebouncedSend.setInterval(500);
76 connect(&m_DebouncedSend, &QTimer::timeout, this, &Message::dispatchDebounceQueue);
77}
78
79///////////////////////////////////////////////////////////////////////////////////////////
80///
81///////////////////////////////////////////////////////////////////////////////////////////
82void Message::onConnected()
83{
84 auto node = qobject_cast<Node*>(sender());
85 if (!node)
86 return;
87
88 qCInfo(KSTARS_EKOS) << "Connected to Message Websocket server at" << node->url().toDisplayString();
89
90 m_PendingPropertiesTimer.start();
91 sendConnection();
92 sendProfiles();
93 emit connected();
94}
95
96///////////////////////////////////////////////////////////////////////////////////////////
97///
98///////////////////////////////////////////////////////////////////////////////////////////
99void Message::onDisconnected()
100{
101 auto node = qobject_cast<Node*>(sender());
102 if (!node)
103 return;
104
105 qCInfo(KSTARS_EKOS) << "Disconnected from Message Websocket server at" << node->url().toDisplayString();
106
107 if (isConnected() == false)
108 {
109 m_PendingPropertiesTimer.stop();
110 emit disconnected();
111 }
112}
113
114///////////////////////////////////////////////////////////////////////////////////////////
115///
116///////////////////////////////////////////////////////////////////////////////////////////
117void Message::onTextReceived(const QString &message)
118{
119 auto node = qobject_cast<Node*>(sender());
120 if (!node || message.isEmpty())
121 return;
122
123 qCInfo(KSTARS_EKOS) << "Websocket Message" << message;
125 auto serverMessage = QJsonDocument::fromJson(message.toUtf8(), &error);
126 if (error.error != QJsonParseError::NoError)
127 {
128 qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
129 return;
130 }
131
132 const QJsonObject msgObj = serverMessage.object();
133 const QString command = msgObj["type"].toString();
134 const QJsonObject payload = msgObj["payload"].toObject();
135
136 if (command == commands[GET_CONNECTION])
137 {
138 sendConnection();
139 }
140 else if (command == commands[LOGOUT] || command == commands[SESSION_EXPIRED])
141 {
142 emit expired(node->url());
143 return;
144 }
145 else if (command == commands[SET_CLIENT_STATE])
146 {
147 // If client is connected, make sure clock is ticking
148 if (payload["state"].toBool(false))
149 {
150 qCInfo(KSTARS_EKOS) << "EkosLive client is connected.";
151
152 // If the clock is PAUSED, run it now and sync time as well.
153 if (KStarsData::Instance()->clock()->isActive() == false)
154 {
155 qCInfo(KSTARS_EKOS) << "Resuming and syncing clock.";
156 KStarsData::Instance()->clock()->start();
157 QAction *a = KStars::Instance()->actionCollection()->action("time_to_now");
158 if (a)
159 a->trigger();
160 }
161 }
162 // Otherwise, if KStars was started in PAUSED state
163 // then we pause here as well to save power.
164 else
165 {
166 qCInfo(KSTARS_EKOS) << "EkosLive client is disconnected.";
167 // It was started with paused state, so let's pause IF Ekos is not running
168 if (KStars::Instance()->isStartedWithClockRunning() == false && m_Manager->ekosStatus() == Ekos::CommunicationStatus::Idle)
169 {
170 qCInfo(KSTARS_EKOS) << "Stopping the clock.";
171 KStarsData::Instance()->clock()->stop();
172 }
173 }
174 }
175 else if (command == commands[GET_DRIVERS])
176 sendDrivers();
177 else if (command == commands[GET_PROFILES])
178 sendProfiles();
179 else if (command == commands[GET_SCOPES])
180 sendScopes();
181 else if (command == commands[GET_DSLR_LENSES])
182 sendDSLRLenses();
183 else if(command == commands[INVOKE_METHOD])
184 {
185 auto object = findObject(payload["object"].toString());
186 if (object)
187 invokeMethod(object, payload);
188 }
189 else if(command == commands[SET_PROPERTY])
190 {
191 auto object = findObject(payload["object"].toString());
192 if (object)
193 object->setProperty(payload["name"].toString().toLatin1().constData(), payload["value"].toVariant());
194 }
195 else if(command == commands[GET_PROPERTY])
196 {
197 auto map = QVariantMap();
198 map["result"] = false;
199 auto object = findObject(payload["object"].toString());
200 if (object)
201 {
202 auto value = object->property(payload["name"].toString().toLatin1().constData());
203 if (value.isValid())
204 {
205 map["result"] = true;
206 map["value"] = value;
207 }
208 }
209 sendResponse(commands[GET_PROPERTY], QJsonObject::fromVariantMap(map));
210 }
211 else if (command == commands[TRAIN_GET_ALL])
212 sendTrains();
213 else if (command == commands[TRAIN_SETTINGS_GET])
214 {
215 auto id = payload["id"].toInt(-1);
216 if (id > 0)
217 {
218 Ekos::OpticalTrainSettings::Instance()->setOpticalTrainID(id);
219 auto settings = Ekos::OpticalTrainSettings::Instance()->getSettings();
220 if (!settings.isEmpty())
221 sendResponse(commands[TRAIN_SETTINGS_GET], QJsonObject::fromVariantMap(settings));
222 }
223 }
224 else if (command.startsWith("scope_"))
225 processScopeCommands(command, payload);
226 else if (command.startsWith("profile_"))
227 processProfileCommands(command, payload);
228 else if (command.startsWith("astro_"))
229 processAstronomyCommands(command, payload);
230 else if (command == commands[DIALOG_GET_RESPONSE])
231 processDialogResponse(payload);
232 else if (command.startsWith("option_"))
233 processOptionsCommands(command, payload);
234 else if (command.startsWith("scheduler"))
235 processSchedulerCommands(command, payload);
236 else if (command.startsWith("dslr_"))
237 processDSLRCommands(command, payload);
238 else if (command.startsWith("file_"))
239 processFileCommands(command, payload);
240
241 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
242 return;
243
244 if (command == commands[GET_STATES])
245 sendStates();
246 else if (command == commands[GET_STELLARSOLVER_PROFILES])
247 sendStellarSolverProfiles();
248 else if (command == commands[GET_DEVICES])
249 sendDevices();
250 else if (command.startsWith("capture_"))
251 processCaptureCommands(command, payload);
252 else if (command.startsWith("mount_"))
253 processMountCommands(command, payload);
254 else if (command.startsWith("focus_"))
255 processFocusCommands(command, payload);
256 else if (command.startsWith("guide_"))
257 processGuideCommands(command, payload);
258 else if (command.startsWith("align_"))
259 processAlignCommands(command, payload);
260 else if (command.startsWith("polar_"))
261 processPolarCommands(command, payload);
262 else if (command.startsWith("train_"))
263 processTrainCommands(command, payload);
264 else if (command.startsWith("fm_"))
265 processFilterManagerCommands(command, payload);
266 else if (command.startsWith("dark_library_"))
267 processDarkLibraryCommands(command, payload);
268 else if (command.startsWith("device_"))
269 processDeviceCommands(command, payload);
270
271}
272
273///////////////////////////////////////////////////////////////////////////////////////////
274///
275///////////////////////////////////////////////////////////////////////////////////////////
276bool Message::isConnected() const
277{
278 return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
279 {
280 return nodeManager->message()->isConnected();
281 });
282}
283
284///////////////////////////////////////////////////////////////////////////////////////////
285///
286///////////////////////////////////////////////////////////////////////////////////////////
287void Message::sendStellarSolverProfiles()
288{
289 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
290 return;
291
292 QJsonObject profiles;
293
294 if (m_Manager->focusModule())
295 profiles.insert("focus", QJsonArray::fromStringList(m_Manager->focusModule()->mainFocuser()->getStellarSolverProfiles()));
296 // TODO
297 // if (m_Manager->guideModule())
298 // profiles.insert("guide", QJsonArray::fromStringList(m_Manager->guideModule()->getStellarSolverProfiles()));
299 if (m_Manager->alignModule())
300 profiles.insert("align", QJsonArray::fromStringList(m_Manager->alignModule()->getStellarSolverProfiles()));
301
302
303 sendResponse(commands[GET_STELLARSOLVER_PROFILES], profiles);
304}
305
306///////////////////////////////////////////////////////////////////////////////////////////
307///
308///////////////////////////////////////////////////////////////////////////////////////////
309void Message::sendDrivers()
310{
311 sendResponse(commands[GET_DRIVERS], DriverManager::Instance()->getDriverList());
312}
313
314///////////////////////////////////////////////////////////////////////////////////////////
315///
316///////////////////////////////////////////////////////////////////////////////////////////
317void Message::sendDevices()
318{
319 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
320 return;
321
322 QJsonArray deviceList;
323
324 for(auto &gd : INDIListener::devices())
325 {
326 QJsonObject oneDevice =
327 {
328 {"name", gd->getDeviceName()},
329 {"connected", gd->isConnected()},
330 {"version", gd->getDriverVersion()},
331 {"interface", static_cast<int>(gd->getDriverInterface())},
332 };
333
334 deviceList.append(oneDevice);
335 }
336
337 sendResponse(commands[GET_DEVICES], deviceList);
338}
339
340///////////////////////////////////////////////////////////////////////////////////////////
341///
342///////////////////////////////////////////////////////////////////////////////////////////
343void Message::sendTrains()
344{
345 QJsonArray trains;
346
347 for(auto &train : Ekos::OpticalTrainManager::Instance()->getOpticalTrains())
348 trains.append(QJsonObject::fromVariantMap(train));
349
350 sendResponse(commands[TRAIN_GET_ALL], trains);
351}
352
353///////////////////////////////////////////////////////////////////////////////////////////
354///
355///////////////////////////////////////////////////////////////////////////////////////////
356void Message::sendTrainProfiles()
357{
358 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
359 return;
360
361 auto profiles = Ekos::ProfileSettings::Instance()->getSettings();
362
363 sendResponse(commands[TRAIN_GET_PROFILES], QJsonObject::fromVariantMap(profiles));
364}
365
366///////////////////////////////////////////////////////////////////////////////////////////
367///
368///////////////////////////////////////////////////////////////////////////////////////////
369void Message::requestOpticalTrains(bool show)
370{
371 sendResponse(commands[TRAIN_CONFIGURATION_REQUESTED], show);
372}
373
374///////////////////////////////////////////////////////////////////////////////////////////
375///
376///////////////////////////////////////////////////////////////////////////////////////////
377void Message::sendScopes()
378{
379 QJsonArray scopeList;
380
381 QList<OAL::Scope *> allScopes;
382 KStarsData::Instance()->userdb()->GetAllScopes(allScopes);
383
384 for (auto &scope : allScopes)
385 scopeList.append(scope->toJson());
386
387 sendResponse(commands[GET_SCOPES], scopeList);
388}
389
390///////////////////////////////////////////////////////////////////////////////////////////
391///
392///////////////////////////////////////////////////////////////////////////////////////////
393void Message::sendDSLRLenses()
394{
395 QJsonArray dslrList;
396
397 QList<OAL::DSLRLens *> allDslrLens;
398 KStarsData::Instance()->userdb()->GetAllDSLRLenses(allDslrLens);
399
400 for (auto &dslrLens : allDslrLens)
401 dslrList.append(dslrLens->toJson());
402
403 sendResponse(commands[GET_DSLR_LENSES], dslrList);
404}
405
406///////////////////////////////////////////////////////////////////////////////////////////
407///
408///////////////////////////////////////////////////////////////////////////////////////////
409void Message::sendTemperature(double value)
410{
411 ISD::Camera *oneCCD = dynamic_cast<ISD::Camera*>(sender());
412
413 if (oneCCD)
414 {
415 QJsonObject temperature =
416 {
417 {"name", oneCCD->getDeviceName()},
418 {"temperature", value}
419 };
420
421 sendResponse(commands[NEW_CAMERA_STATE], temperature);
422 }
423}
424
425///////////////////////////////////////////////////////////////////////////////////////////
426///
427///////////////////////////////////////////////////////////////////////////////////////////
428void Message::processCaptureCommands(const QString &command, const QJsonObject &payload)
429{
430 auto capture = m_Manager->captureModule();
431
432 if (capture == nullptr)
433 {
434 qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as capture module is not available";
435 return;
436 }
437
438 if (command == commands[CAPTURE_PREVIEW])
439 {
440 capture->mainCamera()->capturePreview();
441 }
442 else if (command == commands[CAPTURE_TOGGLE_VIDEO])
443 {
444 capture->setVideoLimits(payload["maxBufferSize"].toInt(512), payload["maxPreviewFPS"].toInt(10));
445 capture->toggleVideo(payload["enabled"].toBool());
446 }
447 else if (command == commands[CAPTURE_START])
448 capture->start();
449 else if (command == commands[CAPTURE_STOP])
450 capture->stop();
451 else if (command == commands[CAPTURE_LOOP])
452 {
453 capture->mainCamera()->startFraming();
454 }
455 else if (command == commands[CAPTURE_GET_SEQUENCES])
456 {
457 sendCaptureSequence(capture->getSequence());
458 }
459 else if (command == commands[CAPTURE_ADD_SEQUENCE])
460 {
461 // Now add job
462 capture->mainCamera()->createJob();
463 }
464 else if (command == commands[CAPTURE_REMOVE_SEQUENCE])
465 {
466 if (capture->mainCamera()->removeJob(payload["index"].toInt()) == false)
467 sendCaptureSequence(capture->getSequence());
468 }
469 else if (command == commands[CAPTURE_CLEAR_SEQUENCES])
470 {
471 capture->clearSequenceQueue();
472 }
473 else if (command == commands[CAPTURE_SAVE_SEQUENCE_FILE])
474 {
475 if (capture->saveSequenceQueue(payload["filepath"].toString()))
476 sendResponse(commands[CAPTURE_SAVE_SEQUENCE_FILE], QString::fromUtf8(QFile(payload["filepath"].toString()).readAll()));
477 }
478 else if (command == commands[CAPTURE_LOAD_SEQUENCE_FILE])
479 {
481 if (payload.contains("filedata"))
482 {
483 QTemporaryFile file;
484 if (file.open())
485 {
486 file.setAutoRemove(false);
487 path = file.fileName();
488 file.write(payload["filedata"].toString().toUtf8());
489 file.close();
490 }
491 }
492 else
493 path = payload["filepath"].toString();
494
495 if (!path.isEmpty())
496 {
497 auto result = capture->loadSequenceQueue(path);
498 QJsonObject response =
499 {
500 {"result", result},
501 {"path", path}
502 };
503 sendResponse(commands[CAPTURE_LOAD_SEQUENCE_FILE], response);
504 }
505 }
506 else if (command == commands[CAPTURE_GET_ALL_SETTINGS])
507 {
508 sendCaptureSettings(capture->mainCamera()->getAllSettings());
509 }
510 else if (command == commands[CAPTURE_SET_ALL_SETTINGS])
511 {
512 auto settings = payload.toVariantMap();
513 capture->mainCamera()->setAllSettings(settings);
514 KSUtils::setGlobalSettings(settings);
515 }
516 else if (command == commands[CAPTURE_GENERATE_DARK_FLATS])
517 {
518 capture->mainCamera()->generateDarkFlats();
519 }
520}
521
522///////////////////////////////////////////////////////////////////////////////////////////
523///
524///////////////////////////////////////////////////////////////////////////////////////////
525void Message::sendCaptureSequence(const QJsonArray &sequenceArray)
526{
527 sendResponse(commands[CAPTURE_GET_SEQUENCES], sequenceArray);
528}
529
530void Message::sendPreviewLabel(const QString &preview)
531{
532 const QJsonObject payload =
533 {
534 {"preview", preview}
535 };
536 sendResponse(commands[CAPTURE_GET_PREVIEW_LABEL], payload);
537}
538
539///////////////////////////////////////////////////////////////////////////////////////////
540///
541///////////////////////////////////////////////////////////////////////////////////////////
542void Message::sendCaptureSettings(const QVariantMap &settings)
543{
544 m_DebouncedSend.start();
545 m_DebouncedMap[commands[CAPTURE_GET_ALL_SETTINGS]] = settings;
546}
547
548///////////////////////////////////////////////////////////////////////////////////////////
549///
550///////////////////////////////////////////////////////////////////////////////////////////
551void Message::sendAlignSettings(const QVariantMap &settings)
552{
553 m_DebouncedSend.start();
554 m_DebouncedMap[commands[ALIGN_GET_ALL_SETTINGS]] = settings;
555}
556
557///////////////////////////////////////////////////////////////////////////////////////////
558///
559///////////////////////////////////////////////////////////////////////////////////////////
560void Message::sendGuideSettings(const QVariantMap &settings)
561{
562 m_DebouncedSend.start();
563 m_DebouncedMap[commands[GUIDE_GET_ALL_SETTINGS]] = settings;
564
565}
566
567///////////////////////////////////////////////////////////////////////////////////////////
568///
569///////////////////////////////////////////////////////////////////////////////////////////
570void Message::sendFocusSettings(const QVariantMap &settings)
571{
572 m_DebouncedSend.start();
573 m_DebouncedMap[commands[FOCUS_GET_ALL_SETTINGS]] = settings;
574}
575
576///////////////////////////////////////////////////////////////////////////////////////////
577///
578///////////////////////////////////////////////////////////////////////////////////////////
579void Message::sendMountSettings(const QVariantMap &settings)
580{
581 m_DebouncedSend.start();
582 m_DebouncedMap[commands[MOUNT_GET_ALL_SETTINGS]] = settings;
583}
584
585///////////////////////////////////////////////////////////////////////////////////////////
586///
587///////////////////////////////////////////////////////////////////////////////////////////
588void Message::sendDarkLibrarySettings(const QVariantMap &settings)
589{
590 m_DebouncedSend.start();
591 m_DebouncedMap[commands[DARK_LIBRARY_GET_ALL_SETTINGS]] = settings;
592}
593
594
595///////////////////////////////////////////////////////////////////////////////////////////
596///
597///////////////////////////////////////////////////////////////////////////////////////////
598void Message::sendSchedulerSettings(const QVariantMap &settings)
599{
600 m_DebouncedSend.start();
601 m_DebouncedMap[commands[SCHEDULER_GET_ALL_SETTINGS]] = settings;
602}
603
604///////////////////////////////////////////////////////////////////////////////////////////
605///
606///////////////////////////////////////////////////////////////////////////////////////////
607void Message::dispatchDebounceQueue()
608{
609 QMapIterator<QString, QVariantMap> i(m_DebouncedMap);
610 while (i.hasNext())
611 {
612 i.next();
613 sendResponse(i.key(), QJsonObject::fromVariantMap(i.value()));
614 }
615 m_DebouncedMap.clear();
616
617 // Save to disk
618 Options::self()->save();
619}
620
621///////////////////////////////////////////////////////////////////////////////////////////
622///
623///////////////////////////////////////////////////////////////////////////////////////////
624void Message::processGuideCommands(const QString &command, const QJsonObject &payload)
625{
626 Ekos::Guide *guide = m_Manager->guideModule();
627
628 if (guide == nullptr)
629 {
630 qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as guide module is not available";
631 return;
632 }
633
634 if (command == commands[GUIDE_START])
635 {
636 guide->guide();
637 }
638 else if (command == commands[GUIDE_CAPTURE])
639 guide->capture();
640 else if (command == commands[GUIDE_LOOP])
641 guide->loop();
642 else if (command == commands[GUIDE_STOP])
643 guide->abort();
644 else if (command == commands[GUIDE_CLEAR])
645 guide->clearCalibration();
646 else if (command == commands[GUIDE_SET_ALL_SETTINGS])
647 {
648 auto settings = payload.toVariantMap();
649 guide->setAllSettings(settings);
650 KSUtils::setGlobalSettings(settings);
651 }
652 else if (command == commands[GUIDE_GET_ALL_SETTINGS])
653 sendGuideSettings(guide->getAllSettings());
654 else if(command == commands[GUIDE_SET_CALIBRATION_SETTINGS])
655 {
656
657 Options::setCalibrationPulseDuration(payload["pulse"].toInt());
658 Options::setGuideCalibrationBacklash(payload["max_move"].toInt());
659 Options::setTwoAxisEnabled(payload["two_axis"].toBool());
660 Options::setGuideAutoSquareSizeEnabled(payload["square_size"].toBool());
661 Options::setGuideCalibrationBacklash(payload["calibrationBacklash"].toBool());
662 Options::setResetGuideCalibration(payload["resetCalibration"].toBool());
663 Options::setReuseGuideCalibration(payload["reuseCalibration"].toBool());
664 Options::setReverseDecOnPierSideChange(payload["reverseCalibration"].toBool());
665 sendGuideSettings(m_Manager->guideModule()->getAllSettings());
666 }
667}
668
669///////////////////////////////////////////////////////////////////////////////////////////
670///
671///////////////////////////////////////////////////////////////////////////////////////////
672void Message::processFocusCommands(const QString &command, const QJsonObject &payload)
673{
675 if (m_Manager->focusModule())
676 focus = m_Manager->focusModule()->mainFocuser();
677
678 if (focus.isNull())
679 {
680 qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as focus module is not available";
681 return;
682 }
683
684 if (command == commands[FOCUS_START])
685 focus->start();
686 else if (command == commands[FOCUS_CAPTURE])
687 {
688 focus->resetFrame();
689 focus->capture();
690 }
691 else if (command == commands[FOCUS_STOP])
692 focus->abort();
693 else if (command == commands[FOCUS_RESET])
694 focus->resetFrame();
695 else if (command == commands[FOCUS_IN])
696 focus->focusIn(payload["steps"].toInt());
697 else if (command == commands[FOCUS_OUT])
698 focus->focusOut(payload["steps"].toInt());
699 else if (command == commands[FOCUS_LOOP])
700 focus->startFraming();
701 else if (command == commands[FOCUS_SET_ALL_SETTINGS])
702 {
703 auto settings = payload.toVariantMap();
704 focus->setAllSettings(settings);
705 KSUtils::setGlobalSettings(settings);
706 }
707
708 else if (command == commands[FOCUS_GET_ALL_SETTINGS])
709 sendFocusSettings(focus->getAllSettings());
710 else if (command == commands[FOCUS_SET_CROSSHAIR])
711 {
712 double x = payload["x"].toDouble();
713 double y = payload["y"].toDouble();
714 focus->selectFocusStarFraction(x, y);
715 }
716}
717
718///////////////////////////////////////////////////////////////////////////////////////////
719///
720///////////////////////////////////////////////////////////////////////////////////////////
721void Message::processMountCommands(const QString &command, const QJsonObject &payload)
722{
723 Ekos::Mount *mount = m_Manager->mountModule();
724
725 if (mount == nullptr)
726 {
727 qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as mount module is not available";
728 return;
729 }
730
731 if (command == commands[MOUNT_ABORT])
732 mount->abort();
733 else if (command == commands[MOUNT_PARK])
734 mount->park();
735 else if (command == commands[MOUNT_UNPARK])
736 mount->unpark();
737 else if (command == commands[MOUNT_SET_TRACKING])
738 mount->setTrackEnabled(payload["enabled"].toBool());
739 else if (command == commands[MOUNT_SYNC_RADE])
740 {
741 mount->setJ2000Enabled(payload["isJ2000"].toBool());
742 auto ra = dms::fromString(payload["ra"].toString(), false);
743 auto de = dms::fromString(payload["de"].toString(), true);
744 mount->sync(ra.Hours(), de.Degrees());
745 }
746 else if (command == commands[MOUNT_SYNC_TARGET])
747 {
748 mount->syncTarget(payload["target"].toString());
749 }
750 else if (command == commands[MOUNT_GOTO_RADE])
751 {
752 mount->setJ2000Enabled(payload["isJ2000"].toBool());
753 auto ra = dms::fromString(payload["ra"].toString(), false);
754 auto de = dms::fromString(payload["de"].toString(), true);
755 mount->slew(ra.Hours(), de.Degrees());
756 }
757 else if (command == commands[MOUNT_GOTO_TARGET])
758 {
759 mount->gotoTarget(ki18n(payload["target"].toString().toLatin1()).toString());
760 }
761 else if (command == commands[MOUNT_SET_SLEW_RATE])
762 {
763 int rate = payload["rate"].toInt(-1);
764 if (rate >= 0)
765 mount->setSlewRate(rate);
766 }
767 else if (command == commands[MOUNT_SET_ALL_SETTINGS])
768 {
769 auto settings = payload.toVariantMap();
770 mount->setAllSettings(settings);
771 KSUtils::setGlobalSettings(settings);
772 }
773 else if (command == commands[MOUNT_GET_ALL_SETTINGS])
774 sendMountSettings(mount->getAllSettings());
775 else if (command == commands[MOUNT_SET_MOTION])
776 {
777 QString direction = payload["direction"].toString();
778 ISD::Mount::MotionCommand action = payload["action"].toBool(false) ?
779 ISD::Mount::MOTION_START : ISD::Mount::MOTION_STOP;
780
781 if (direction == "N")
782 mount->motionCommand(action, ISD::Mount::MOTION_NORTH, -1);
783 else if (direction == "S")
784 mount->motionCommand(action, ISD::Mount::MOTION_SOUTH, -1);
785 else if (direction == "E")
786 mount->motionCommand(action, -1, ISD::Mount::MOTION_EAST);
787 else if (direction == "W")
788 mount->motionCommand(action, -1, ISD::Mount::MOTION_WEST);
789 }
790 else if (command == commands[MOUNT_GOTO_PIXEL])
791 {
792 const auto name = payload["camera"].toString();
793 const auto xFactor = payload["x"].toDouble();
794 const auto yFactor = payload["y"].toDouble();
795
796 for(auto &oneDevice : INDIListener::devices())
797 {
798 auto camera = oneDevice->getCamera();
799 if (!camera || camera->getDeviceName() != name)
800 continue;
801
802 auto primaryChip = camera->getChip(ISD::CameraChip::PRIMARY_CCD);
803
804 if (!primaryChip)
805 break;
806
807 auto imageData = primaryChip->getImageData();
808 if (!imageData || imageData->hasWCS() == false)
809 break;
810
811 auto x = xFactor * imageData->width();
812 auto y = yFactor * imageData->height();
813
814 QPointF point(x, y);
815 SkyPoint coord;
816 if (imageData->pixelToWCS(point, coord))
817 {
818 // J2000 -> JNow
819 coord.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
820 mount->gotoTarget(coord);
821 break;
822 }
823 }
824 }
825 else if (command == commands[MOUNT_TOGGLE_AUTOPARK])
826 mount->setAutoParkEnabled(payload["toggled"].toBool());
827}
828
829///////////////////////////////////////////////////////////////////////////////////////////
830///
831///////////////////////////////////////////////////////////////////////////////////////////
832void Message::processAlignCommands(const QString &command, const QJsonObject &payload)
833{
834 Ekos::Align *align = m_Manager->alignModule();
835
836 if (align == nullptr)
837 {
838 qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as align module is not available";
839 return;
840 }
841
842 if (command == commands[ALIGN_SOLVE])
843 {
844 align->captureAndSolve();
845 }
846 else if (command == commands[ALIGN_SET_ALL_SETTINGS])
847 {
848 auto settings = payload.toVariantMap();
849 align->setAllSettings(settings);
850 KSUtils::setGlobalSettings(settings);
851 }
852 else if (command == commands[ALIGN_GET_ALL_SETTINGS])
853 sendAlignSettings(align->getAllSettings());
854 else if(command == commands[ALIGN_SET_ASTROMETRY_SETTINGS])
855 {
856 Options::setAstrometryRotatorThreshold(payload["threshold"].toInt());
857 Options::setAstrometryUseRotator(payload["rotator_control"].toBool());
858 Options::setAstrometryUseImageScale(payload["scale"].toBool());
859 Options::setAstrometryUsePosition(payload["position"].toBool());
860 }
861 else if (command == commands[ALIGN_STOP])
862 align->abort();
863 else if (command == commands[ALIGN_LOAD_AND_SLEW])
864 {
865 // Check if we have filename payload first
866 if (payload.contains("filename"))
867 {
868 align->loadAndSlew(payload["filename"].toString());
869 }
870 else
871 {
872 QString filename = QDir::tempPath() + QDir::separator() +
873 QString("XXXXXXloadslew.%1").arg(payload["ext"].toString("fits"));
874 QTemporaryFile file(filename);
875 file.setAutoRemove(false);
876 file.open();
877 file.write(QByteArray::fromBase64(payload["data"].toString().toLatin1()));
878 file.close();
879 align->loadAndSlew(file.fileName());
880 }
881 }
882 else if (command == commands[ALIGN_MANUAL_ROTATOR_TOGGLE])
883 {
884 align->toggleManualRotator(payload["toggled"].toBool());
885 }
886}
887
888///////////////////////////////////////////////////////////////////////////////////////////
889///
890///////////////////////////////////////////////////////////////////////////////////////////
891void Message::setAlignStatus(Ekos::AlignState newState)
892{
893 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
894 return;
895
896 QJsonObject alignState =
897 {
898 {"status", QString::fromLatin1(Ekos::alignStates[newState].untranslatedText())}
899 };
900
901 sendResponse(commands[NEW_ALIGN_STATE], alignState);
902}
903
904///////////////////////////////////////////////////////////////////////////////////////////
905///
906///////////////////////////////////////////////////////////////////////////////////////////
907void Message::setAlignSolution(const QVariantMap &solution)
908{
909 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
910 return;
911
912 QJsonObject alignState =
913 {
914 {"solution", QJsonObject::fromVariantMap(solution)},
915 };
916
917 sendResponse(commands[NEW_ALIGN_STATE], alignState);
918}
919
920///////////////////////////////////////////////////////////////////////////////////////////
921///
922///////////////////////////////////////////////////////////////////////////////////////////
923void Message::processSchedulerCommands(const QString &command, const QJsonObject &payload)
924{
925 Ekos::Scheduler *scheduler = m_Manager->schedulerModule();
926
927 if (command == commands[SCHEDULER_GET_JOBS])
928 {
929 sendSchedulerJobs();
930 }
931 else if (command == commands[SCHEDULER_ADD_JOBS])
932 {
933 scheduler->addJob();
934 }
935 else if(command == commands[SCHEDULER_REMOVE_JOBS])
936 {
937 int index = payload["index"].toInt();
938 scheduler->removeOneJob(index);
939 }
940 else if(command == commands[SCHEDULER_GET_ALL_SETTINGS])
941 {
942 sendSchedulerSettings(scheduler->getAllSettings());
943 }
944 else if(command == commands[SCHEDULER_SET_ALL_SETTINGS])
945 {
946 auto settings = payload.toVariantMap();
947 scheduler->setAllSettings(settings);
948 KSUtils::setGlobalSettings(settings);
949 }
950 else if (command == commands[SCHEDULER_SAVE_FILE])
951 {
952 if (scheduler->saveFile(QUrl::fromLocalFile(payload["filepath"].toString())))
953 sendResponse(commands[SCHEDULER_SAVE_FILE], QString::fromUtf8(QFile(payload["filepath"].toString()).readAll()));
954 }
955 else if (command == commands[SCHEDULER_SAVE_SEQUENCE_FILE])
956 {
958 bool result = false;
959 if (payload.contains("filedata"))
960 {
961 path = QDir::homePath() + QDir::separator() + payload["path"].toString();
962 QFile file(path);
963 if (file.open(QIODevice::WriteOnly))
964 {
965 result = true;
966 file.write(payload["filedata"].toString().toUtf8());
967 file.close();
968 }
969 }
970
971 QJsonObject response =
972 {
973 {"result", result},
974 {"path", path}
975 };
976 sendResponse(commands[SCHEDULER_SAVE_SEQUENCE_FILE], response);
977 }
978 else if (command == commands[SCHEDULER_LOAD_FILE])
979 {
980 QString path = payload["filepath"].toString();
981 bool success = true;
982
983 if (payload.contains("filedata"))
984 {
985 // Get path from temporary file if needed
986 if (path.isEmpty())
987 {
988 QTemporaryFile tempFile;
989 if (!tempFile.open())
990 {
991 success = false;
992 }
993 else
994 {
995 tempFile.setAutoRemove(false);
996 path = tempFile.fileName();
997 }
998 }
999 // Path for filedata is relative to home directory.
1000 else
1002
1003 // Write file data if we have a valid path
1004 if (success && !path.isEmpty())
1005 {
1006 QFile file(path);
1007 if (!file.open(QIODevice::WriteOnly) ||
1008 file.write(payload["filedata"].toString().toUtf8()) == -1)
1009 {
1010 success = false;
1011 }
1012 }
1013 }
1014
1015 // Load the file if we have a path
1016 if (success && !path.isEmpty())
1017 {
1018 success = scheduler->loadFile(QUrl::fromLocalFile(path));
1019 }
1020
1021 QJsonObject response
1022 {
1023 {"result", success}
1024 };
1025 if (success && !path.isEmpty())
1026 {
1027 response["path"] = path;
1028 }
1029
1030 sendResponse(commands[SCHEDULER_LOAD_FILE], response);
1031 }
1032 else if(command == commands[SCHEDULER_START_JOB])
1033 {
1034 scheduler->toggleScheduler();
1035 }
1036 else if(command == commands[SCHEDULER_IMPORT_MOSAIC])
1037 {
1038 if (scheduler->importMosaic(payload))
1039 sendSchedulerJobs();
1040 else
1041 sendEvent(i18n("Mosaic import failed."), KSNotification::Scheduler, KSNotification::Alert);
1042 }
1043}
1044
1045///////////////////////////////////////////////////////////////////////////////////////////
1046///
1047///////////////////////////////////////////////////////////////////////////////////////////
1048void Message::processPolarCommands(const QString &command, const QJsonObject &payload)
1049{
1050 Ekos::Align *align = m_Manager->alignModule();
1051 Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
1052
1053 if (!paa)
1054 return;
1055
1056 if (command == commands[PAH_START])
1057 {
1058 paa->startPAHProcess();
1059 }
1060 if (command == commands[PAH_STOP])
1061 {
1062 paa->stopPAHProcess();
1063 }
1064 else if (command == commands[PAH_REFRESH])
1065 {
1066 paa->setPAHRefreshDuration(payload["value"].toDouble(1));
1067 paa->startPAHRefreshProcess();
1068 }
1069 else if (command == commands[PAH_SET_ALGORITHM])
1070 {
1071 auto algorithmCombo = paa->findChild<QComboBox*>("PAHRefreshAlgorithmCombo");
1072 if (algorithmCombo)
1073 algorithmCombo->setCurrentIndex(static_cast<Ekos::PolarAlignmentAssistant::RefreshAlgorithm>(payload["value"].toInt(1)));
1074 }
1075 else if (command == commands[PAH_RESET_VIEW])
1076 {
1077 emit resetPolarView();
1078 }
1079 else if (command == commands[PAH_SET_CROSSHAIR])
1080 {
1081 double x = payload["x"].toDouble();
1082 double y = payload["y"].toDouble();
1083
1084 if (m_BoundingRect.isNull() == false)
1085 {
1086 // #1 Find actual dimension inside the bounding rectangle
1087 // since if we have bounding rectable then x,y fractions are INSIDE it
1088 double boundX = x * m_BoundingRect.width();
1089 double boundY = y * m_BoundingRect.height();
1090
1091 // #2 Find fraction of the dimensions above the full image size
1092 // Add to it the bounding rect top left offsets
1093 // factors in the change caused by zoom
1094 x = ((boundX + m_BoundingRect.x()) / (m_CurrentZoom / 100)) / m_ViewSize.width();
1095 y = ((boundY + m_BoundingRect.y()) / (m_CurrentZoom / 100)) / m_ViewSize.height();
1096
1097 }
1098
1099 paa->setPAHCorrectionOffsetPercentage(x, y);
1100 }
1101 else if (command == commands[PAH_SELECT_STAR_DONE])
1102 {
1103 // This button was removed from the desktop PAA scheme.
1104 // Nothing to do.
1105 // TODO: Make sure this works.
1106 }
1107 else if (command == commands[PAH_REFRESHING_DONE])
1108 {
1109 paa->stopPAHProcess();
1110 }
1111 else if (command == commands[PAH_SLEW_DONE])
1112 {
1113 paa->setPAHSlewDone();
1114 }
1115 else if (command == commands[PAH_PAH_SET_ZOOM])
1116 {
1117 double scale = payload["scale"].toDouble();
1118 align->setAlignZoom(scale);
1119 }
1120
1121}
1122
1123///////////////////////////////////////////////////////////////////////////////////////////
1124///
1125///////////////////////////////////////////////////////////////////////////////////////////
1126void Message::setPAHStage(Ekos::PolarAlignmentAssistant::Stage stage)
1127{
1128 if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1129 return;
1130
1131 Q_UNUSED(stage)
1132 Ekos::Align *align = m_Manager->alignModule();
1133
1134 Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
1135
1136 if (!paa)
1137 return;
1138
1139 QJsonObject polarState =
1140 {
1141 {"stage", paa->getPAHStageString(false)}
1142 };
1143
1144
1145 // Increase size when select star
1146 if (stage == Ekos::PolarAlignmentAssistant::PAH_STAR_SELECT)
1147 align->zoomAlignView();
1148
1149 sendResponse(commands[NEW_POLAR_STATE], polarState);
1150}
1151
1152///////////////////////////////////////////////////////////////////////////////////////////
1153///
1154///////////////////////////////////////////////////////////////////////////////////////////
1155void Message::setPAHMessage(const QString &message)
1156{
1157 if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1158 return;
1159
1160 QTextDocument doc;
1161 doc.setHtml(message);
1162 QJsonObject polarState =
1163 {
1164 {"message", doc.toPlainText()}
1165 };
1166
1167 sendResponse(commands[NEW_POLAR_STATE], polarState);
1168}
1169
1170///////////////////////////////////////////////////////////////////////////////////////////
1171///
1172///////////////////////////////////////////////////////////////////////////////////////////
1173void Message::setPolarResults(QLineF correctionVector, double polarError, double azError, double altError)
1174{
1175 if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1176 return;
1177
1178 this->correctionVector = correctionVector;
1179
1180 QPointF center = 0.5 * correctionVector.p1() + 0.5 * correctionVector.p2();
1181 QJsonObject vector =
1182 {
1183 {"center_x", center.x()},
1184 {"center_y", center.y()},
1185 {"mag", correctionVector.length()},
1186 {"pa", correctionVector.angle()},
1187 {"error", polarError},
1188 {"azError", azError},
1189 {"altError", altError}
1190 };
1191
1192 QJsonObject polarState =
1193 {
1194 {"vector", vector}
1195 };
1196
1197 sendResponse(commands[NEW_POLAR_STATE], polarState);
1198}
1199
1200///////////////////////////////////////////////////////////////////////////////////////////
1201///
1202///////////////////////////////////////////////////////////////////////////////////////////
1203void Message::setUpdatedErrors(double total, double az, double alt)
1204{
1205 if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1206 return;
1207
1209 {
1210 {"updatedError", total},
1211 {"updatedAZError", az},
1212 {"updatedALTError", alt}
1213 };
1214
1215 sendResponse(commands[NEW_POLAR_STATE], error);
1216}
1217
1218///////////////////////////////////////////////////////////////////////////////////////////
1219///
1220///////////////////////////////////////////////////////////////////////////////////////////
1221void Message::setPAHEnabled(bool enabled)
1222{
1223 if (m_Manager->getEkosStartingStatus() != Ekos::Success)
1224 return;
1225
1226 QJsonObject polarState =
1227 {
1228 {"enabled", enabled}
1229 };
1230
1231 sendResponse(commands[NEW_POLAR_STATE], polarState);
1232}
1233
1234///////////////////////////////////////////////////////////////////////////////////////////
1235///
1236///////////////////////////////////////////////////////////////////////////////////////////
1237void Message::processProfileCommands(const QString &command, const QJsonObject &payload)
1238{
1239 if (command == commands[START_PROFILE])
1240 {
1241 if (m_Manager->getEkosStartingStatus() != Ekos::Idle)
1242 m_Manager->stop();
1243
1244 m_Manager->setProfile(payload["name"].toString());
1245 // Always Sync time before we start
1246 KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
1247 m_Manager->start();
1248 }
1249 else if (command == commands[STOP_PROFILE])
1250 {
1251 m_Manager->stop();
1252
1253 // Close all FITS Viewers
1254 KStars::Instance()->clearAllViewers();
1255
1256 m_PropertySubscriptions.clear();
1257 }
1258 else if (command == commands[ADD_PROFILE])
1259 {
1260 m_Manager->addNamedProfile(payload);
1261 sendProfiles();
1262 }
1263 else if (command == commands[UPDATE_PROFILE])
1264 {
1265 m_Manager->editNamedProfile(payload);
1266 sendProfiles();
1267 }
1268 else if (command == commands[GET_PROFILE])
1269 {
1270 m_Manager->getNamedProfile(payload["name"].toString());
1271 }
1272 else if (command == commands[DELETE_PROFILE])
1273 {
1274 m_Manager->deleteNamedProfile(payload["name"].toString());
1275 sendProfiles();
1276 }
1277 else if (command == commands[SET_PROFILE_MAPPING])
1278 {
1279 m_Manager->setProfileMapping(payload);
1280 }
1281 else if (command == commands[SET_PROFILE_PORT_SELECTION])
1282 {
1283 requestPortSelection(false);
1284 m_Manager->acceptPortSelection();
1285 }
1286}
1287
1288///////////////////////////////////////////////////////////////////////////////////////////
1289///
1290///////////////////////////////////////////////////////////////////////////////////////////
1291void Message::sendProfiles()
1292{
1293 QJsonArray profileArray;
1294
1296 if (!m_Manager->getCurrentProfile(profile))
1297 return;
1298
1299 for (auto &oneProfile : m_Manager->profiles)
1300 profileArray.append(oneProfile->toJson());
1301
1302 QJsonObject profiles =
1303 {
1304 {"selectedProfile", profile->name},
1305 {"profiles", profileArray}
1306 };
1307 sendResponse(commands[GET_PROFILES], profiles);
1308}
1309
1310///////////////////////////////////////////////////////////////////////////////////////////
1311///
1312///////////////////////////////////////////////////////////////////////////////////////////
1313void Message::sendSchedulerJobs()
1314{
1315 QJsonObject jobs =
1316 {
1317 {"jobs", m_Manager->schedulerModule()->moduleState()->getJSONJobs()}
1318 };
1319 sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1320}
1321
1322///////////////////////////////////////////////////////////////////////////////////////////
1323///
1324///////////////////////////////////////////////////////////////////////////////////////////
1325void Message::sendSchedulerJobList(QJsonArray jobsList)
1326{
1327 QJsonObject jobs =
1328 {
1329 {"jobs", jobsList}
1330 };
1331 sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1332}
1333
1334///////////////////////////////////////////////////////////////////////////////////////////
1335///
1336///////////////////////////////////////////////////////////////////////////////////////////
1337void Message::sendSchedulerStatus(const QJsonObject &status)
1338{
1339 sendResponse(commands[NEW_SCHEDULER_STATE], status);
1340}
1341
1342
1343///////////////////////////////////////////////////////////////////////////////////////////
1344///
1345///////////////////////////////////////////////////////////////////////////////////////////
1346void Message::setEkosStatingStatus(Ekos::CommunicationStatus status)
1347{
1348 if (status == Ekos::Pending)
1349 return;
1350
1351 QJsonObject connectionState =
1352 {
1353 {"connected", true},
1354 {"online", status == Ekos::Success}
1355 };
1356 sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
1357}
1358
1359///////////////////////////////////////////////////////////////////////////////////////////
1360///
1361///////////////////////////////////////////////////////////////////////////////////////////
1362void Message::setINDIStatus(Ekos::CommunicationStatus status)
1363{
1364 QJsonObject connectionState =
1365 {
1366 {"status", status},
1367 };
1368
1369 sendResponse(commands[NEW_INDI_STATE], connectionState);
1370}
1371
1372///////////////////////////////////////////////////////////////////////////////////////////
1373///
1374///////////////////////////////////////////////////////////////////////////////////////////
1375void Message::processOptionsCommands(const QString &command, const QJsonObject &payload)
1376{
1377 if (command == commands[OPTION_SET])
1378 {
1379 const QJsonArray options = payload["options"].toArray();
1380 for (const auto &oneOption : options)
1381 Options::self()->setProperty(oneOption[QString("name")].toString().toLatin1(), oneOption[QString("value")].toVariant());
1382
1383 Options::self()->save();
1384 emit optionsUpdated();
1385 }
1386 else if (command == commands[OPTION_GET])
1387 {
1388 const QJsonArray options = payload[QString("options")].toArray();
1389 QJsonArray result;
1390 for (const auto &oneOption : options)
1391 {
1392 const auto name = oneOption[QString("name")].toString();
1393 QVariant value = Options::self()->property(name.toLatin1());
1394 QVariantMap map;
1395 map["name"] = name;
1396 map["value"] = value;
1398 }
1399 sendResponse(commands[OPTION_GET], result);
1400 }
1401}
1402
1403///////////////////////////////////////////////////////////////////////////////////////////
1404///
1405///////////////////////////////////////////////////////////////////////////////////////////
1406void Message::processScopeCommands(const QString &command, const QJsonObject &payload)
1407{
1408 if (command == commands[ADD_SCOPE])
1409 {
1410 KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1411 payload["type"].toString(), payload["aperture"].toDouble(), payload["focal_length"].toDouble());
1412 }
1413 else if (command == commands[UPDATE_SCOPE])
1414 {
1415 KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1416 payload["type"].toString(), payload["aperture"].toDouble(), payload["focal_length"].toDouble(), payload["id"].toString());
1417 }
1418 else if (command == commands[DELETE_SCOPE])
1419 {
1420 KStarsData::Instance()->userdb()->DeleteEquipment("telescope", payload["id"].toString());
1421 }
1422
1423 sendScopes();
1424}
1425
1426///////////////////////////////////////////////////////////////////////////////////////////
1427///
1428///////////////////////////////////////////////////////////////////////////////////////////
1429void Message::processDSLRCommands(const QString &command, const QJsonObject &payload)
1430{
1431 if (command == commands[DSLR_SET_INFO])
1432 {
1433 if (m_Manager->captureModule())
1434 m_Manager->captureModule()->mainCamera()->addDSLRInfo(
1435 payload["model"].toString(),
1436 payload["width"].toInt(),
1437 payload["height"].toInt(),
1438 payload["pixelw"].toDouble(),
1439 payload["pixelh"].toDouble());
1440
1441 }
1442 else if(command == commands[DSLR_ADD_LENS])
1443 {
1444 KStarsData::Instance()->userdb()->AddDSLRLens(payload["model"].toString(), payload["vendor"].toString(),
1445 payload["focal_length"].toDouble(), payload["focal_ratio"].toDouble());
1446 }
1447 else if (command == commands[DSLR_DELETE_LENS])
1448 {
1449 KStarsData::Instance()->userdb()->DeleteEquipment("dslrlens", payload["id"].toString());
1450 }
1451 else if (command == commands[DSLR_UPDATE_LENS])
1452 {
1453 KStarsData::Instance()->userdb()->AddDSLRLens(payload["model"].toString(), payload["vendor"].toString(),
1454 payload["focal_length"].toDouble(), payload["focal_ratio"].toDouble(), payload["id"].toString());
1455 }
1456
1457 sendDSLRLenses();
1458}
1459
1460///////////////////////////////////////////////////////////////////////////////////////////
1461///
1462///////////////////////////////////////////////////////////////////////////////////////////
1463void Message::processTrainCommands(const QString &command, const QJsonObject &payload)
1464{
1465 if (command == commands[TRAIN_GET_PROFILES])
1466 sendTrainProfiles();
1467 else if (command == commands[TRAIN_SET])
1468 {
1469 auto module = payload["module"].toString();
1470 auto name = payload["name"].toString();
1471
1472 if (module == "capture")
1473 {
1474 if (m_Manager->captureModule())
1475 m_Manager->captureModule()->setOpticalTrain(name);
1476 }
1477 else if (module == "focus")
1478 {
1479 if (m_Manager->focusModule())
1480 m_Manager->focusModule()->mainFocuser()->setOpticalTrain(name);
1481 }
1482 else if (module == "guide")
1483 {
1484 if (m_Manager->guideModule())
1485 m_Manager->guideModule()->setOpticalTrain(name);
1486 }
1487 else if (module == "align")
1488 {
1489 if (m_Manager->alignModule())
1490 m_Manager->alignModule()->setOpticalTrain(name);
1491 }
1492 else if (module == "mount")
1493 {
1494 if (m_Manager->mountModule())
1495 m_Manager->mountModule()->setOpticalTrain(name);
1496 }
1497 else if (module == "darklibrary")
1498 {
1499 Ekos::DarkLibrary::Instance()->setOpticalTrain(name);
1500 }
1501 }
1502 else if (command == commands[TRAIN_ADD])
1503 {
1504 Ekos::OpticalTrainManager::Instance()->addOpticalTrain(payload);
1505 }
1506 else if (command == commands[TRAIN_UPDATE])
1507 {
1508 Ekos::OpticalTrainManager::Instance()->setOpticalTrain(payload);
1509 }
1510 else if (command == commands[TRAIN_DELETE])
1511 {
1512 Ekos::OpticalTrainManager::Instance()->removeOpticalTrain(payload["name"].toString());
1513 }
1514 else if (command == commands[TRAIN_RESET])
1515 {
1516 Ekos::OpticalTrainManager::Instance()->reset();
1517 }
1518 else if (command == commands[TRAIN_ACCEPT])
1519 {
1520 requestOpticalTrains(false);
1521 Ekos::OpticalTrainManager::Instance()->accept();
1522 }
1523
1524}
1525
1526///////////////////////////////////////////////////////////////////////////////////////////
1527///
1528///////////////////////////////////////////////////////////////////////////////////////////
1529void Message::processFilterManagerCommands(const QString &command, const QJsonObject &payload)
1530{
1532 if (m_Manager->captureModule())
1533 manager = m_Manager->captureModule()->mainCamera()->filterManager();
1534
1535 if (manager.isNull())
1536 return;
1537
1538 if (command == commands[FM_GET_DATA])
1539 {
1540 QJsonObject data = manager->toJSON();
1541 sendResponse(commands[FM_GET_DATA], data);
1542 }
1543 else if (command == commands[FM_SET_DATA])
1544 {
1545 manager->setFilterData(payload);
1546 }
1547}
1548
1549///////////////////////////////////////////////////////////////////////////////////////////
1550///
1551///////////////////////////////////////////////////////////////////////////////////////////
1552void Message::processDarkLibraryCommands(const QString &command, const QJsonObject &payload)
1553{
1554 if (command == commands[DARK_LIBRARY_START])
1555 Ekos::DarkLibrary::Instance()->start();
1556 else if(command == commands[DARK_LIBRARY_SET_ALL_SETTINGS])
1557 {
1558 auto settings = payload.toVariantMap();
1559 Ekos::DarkLibrary::Instance()->setAllSettings(settings);
1560 KSUtils::setGlobalSettings(settings);
1561 }
1562 else if(command == commands[DARK_LIBRARY_GET_ALL_SETTINGS])
1563 sendDarkLibrarySettings(Ekos::DarkLibrary::Instance()->getAllSettings());
1564 else if(command == commands[DARK_LIBRARY_GET_DEFECT_SETTINGS])
1565 sendResponse(commands[DARK_LIBRARY_GET_DEFECT_SETTINGS], Ekos::DarkLibrary::Instance()->getDefectSettings());
1566 else if(command == commands[DARK_LIBRARY_SET_CAMERA_PRESETS])
1567 {
1568 Ekos::DarkLibrary::Instance()->setCameraPresets(payload);
1569 }
1570 else if (command == commands[DARK_LIBRARY_STOP])
1571 {
1572 Ekos::DarkLibrary::Instance()->stop();
1573 }
1574 else if (command == commands[DARK_LIBRARY_GET_MASTERS_IMAGE])
1575 {
1576 const int row = payload["row"].toInt();
1577 Ekos::DarkLibrary::Instance()->loadIndexInView(row);
1578 }
1579 else if (command == commands[DARK_LIBRARY_GET_CAMERA_PRESETS])
1580 {
1581 sendResponse(commands[DARK_LIBRARY_GET_CAMERA_PRESETS], Ekos::DarkLibrary::Instance()->getCameraPresets());
1582 }
1583 else if (command == commands[DARK_LIBRARY_SET_DEFECT_PIXELS])
1584 {
1585 Ekos::DarkLibrary::Instance()->setDefectPixels(payload);
1586 }
1587 else if (command == commands[DARK_LIBRARY_SAVE_MAP])
1588 {
1589 Ekos::DarkLibrary::Instance()->saveMapB->click();
1590 }
1591 else if (command == commands[DARK_LIBRARY_SET_DEFECT_FRAME])
1592 {
1593 Ekos::DarkLibrary::Instance()->setDefectMapEnabled(false);
1594 }
1595 else if (command == commands[DARK_LIBRARY_GET_VIEW_MASTERS])
1596 {
1597 sendResponse(commands[DARK_LIBRARY_GET_VIEW_MASTERS], Ekos::DarkLibrary::Instance()->getViewMasters());
1598 }
1599 else if (command == commands[DARK_LIBRARY_CLEAR_MASTERS_ROW])
1600 {
1601 const int rowIndex = payload["row"].toInt();
1602 Ekos::DarkLibrary::Instance()->clearRow(rowIndex);
1603 }
1604}
1605
1606///////////////////////////////////////////////////////////////////////////////////////////
1607///
1608///////////////////////////////////////////////////////////////////////////////////////////
1609void Message::processDeviceCommands(const QString &command, const QJsonObject &payload)
1610{
1611 QString device = payload["device"].toString();
1612
1613 // In case we want to UNSUBSCRIBE from all at once
1614 if (device.isEmpty() && command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1615 {
1616 m_PropertySubscriptions.clear();
1617 return;
1618 }
1619
1621 if (!INDIListener::findDevice(device, oneDevice))
1622 return;
1623
1624 // Get specific property
1625 if (command == commands[DEVICE_PROPERTY_GET])
1626 {
1627 QJsonObject propObject;
1628 if (oneDevice->getJSONProperty(payload["property"].toString(), propObject, payload["compact"].toBool(true)))
1629 sendResponse(commands[DEVICE_PROPERTY_GET], propObject);
1630 }
1631 // Set specific property
1632 else if (command == commands[DEVICE_PROPERTY_SET])
1633 {
1634 oneDevice->setJSONProperty(payload["property"].toString(), payload["elements"].toArray());
1635 }
1636 // Return ALL properties
1637 else if (command == commands[DEVICE_GET])
1638 {
1640 for (const auto &oneProp : *oneDevice->getProperties())
1641 {
1642 QJsonObject singleProp;
1643 if (oneDevice->getJSONProperty(oneProp.getName(), singleProp, payload["compact"].toBool(false)))
1644 properties.append(singleProp);
1645 }
1646
1647 QJsonObject response =
1648 {
1649 {"device", device},
1650 {"properties", properties}
1651 };
1652
1653 sendResponse(commands[DEVICE_GET], response);
1654 }
1655 // Subscribe to one or more properties
1656 // When subscribed, the updates are immediately pushed as soon as they are received.
1657 else if (command == commands[DEVICE_PROPERTY_SUBSCRIBE])
1658 {
1659 const QJsonArray properties = payload["properties"].toArray();
1660 const QJsonArray groups = payload["groups"].toArray();
1661
1662 // Get existing subscribed props for this device
1663 QSet<QString> props;
1664 if (m_PropertySubscriptions.contains(device))
1665 props = m_PropertySubscriptions[device];
1666
1667 // If it is just a single property, let's insert it to props.
1668 if (properties.isEmpty() == false)
1669 {
1670 for (const auto &oneProp : properties)
1671 props.insert(oneProp.toString());
1672 }
1673 // If group is specified, then we need to add ALL properties belonging to this group.
1674 else if (groups.isEmpty() == false)
1675 {
1676 QVariantList indiGroups = groups.toVariantList();
1677 for (auto &oneProp : *oneDevice->getProperties())
1678 {
1679 if (indiGroups.contains(oneProp.getGroupName()))
1680 props.insert(oneProp.getName());
1681 }
1682 }
1683 // Otherwise, subscribe to ALL property in this device
1684 else
1685 {
1686 for (auto &oneProp : *oneDevice->getProperties())
1687 props.insert(oneProp.getName());
1688 }
1689
1690 m_PropertySubscriptions[device] = props;
1691 }
1692 else if (command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1693 {
1694 const QJsonArray properties = payload["properties"].toArray();
1695 const QJsonArray groups = payload["groups"].toArray();
1696
1697 // Get existing subscribed props for this device
1698 QSet<QString> props;
1699 if (m_PropertySubscriptions.contains(device))
1700 props = m_PropertySubscriptions[device];
1701
1702 // If it is just a single property, let's insert it to props.
1703 // If it is just a single property, let's insert it to props.
1704 if (properties.isEmpty() == false)
1705 {
1706 for (const auto &oneProp : properties)
1707 props.remove(oneProp.toString());
1708 }
1709 // If group is specified, then we need to add ALL properties belonging to this group.
1710 else if (groups.isEmpty() == false)
1711 {
1712 QVariantList indiGroups = groups.toVariantList();
1713 for (auto &oneProp : *oneDevice->getProperties())
1714 {
1715 if (indiGroups.contains(oneProp.getGroupName()))
1716 props.remove(oneProp.getName());
1717 }
1718 }
1719 // Otherwise, subscribe to ALL property in this device
1720 else
1721 {
1722 for (auto &oneProp : *oneDevice->getProperties())
1723 props.remove(oneProp.getName());
1724 }
1725
1726 m_PropertySubscriptions[device] = props;
1727 }
1728}
1729
1730///////////////////////////////////////////////////////////////////////////////////////////
1731///
1732///////////////////////////////////////////////////////////////////////////////////////////
1733void Message::processAstronomyCommands(const QString &command, const QJsonObject &payload)
1734{
1735 if (command == commands[ASTRO_GET_ALMANC])
1736 {
1737 // Today's date
1738 const KStarsDateTime localTime = KStarsData::Instance()->lt();
1739 // Local Midnight
1740 const KStarsDateTime midnight = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
1741
1742 KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
1743
1744 QJsonObject response =
1745 {
1746 {"SunRise", almanac.getSunRise()},
1747 {"SunSet", almanac.getSunSet()},
1748 {"SunMaxAlt", almanac.getSunMaxAlt()},
1749 {"SunMinAlt", almanac.getSunMinAlt()},
1750 {"MoonRise", almanac.getMoonRise()},
1751 {"MoonSet", almanac.getMoonSet()},
1752 {"MoonPhase", almanac.getMoonPhase()},
1753 {"MoonIllum", almanac.getMoonIllum()},
1754 {"Dawn", almanac.getDawnAstronomicalTwilight()},
1755 {"Dusk", almanac.getDuskAstronomicalTwilight()},
1756
1757 };
1758
1759 sendResponse(commands[ASTRO_GET_ALMANC], response);
1760 }
1761 else if (command == commands[ASTRO_GET_NAMES])
1762 {
1763 auto composite = KStarsData::Instance()->skyComposite();
1764 QStringList all;
1766 CatalogsDB::CatalogObjectList dsoObjects;
1767
1768 allObjects.append(composite->objectLists(SkyObject::STAR));
1769 allObjects.append(composite->objectLists(SkyObject::CATALOG_STAR));
1770 allObjects.append(composite->objectLists(SkyObject::PLANET));
1771 allObjects.append(composite->objectLists(SkyObject::MOON));
1772 allObjects.append(composite->objectLists(SkyObject::COMET));
1773 allObjects.append(composite->objectLists(SkyObject::ASTEROID));
1774 allObjects.append(composite->objectLists(SkyObject::SUPERNOVA));
1775 allObjects.append(composite->objectLists(SkyObject::SATELLITE));
1776 dsoObjects = m_DSOManager.get_objects_all();
1777
1778 for (auto &oneObject : allObjects)
1779 all << oneObject.second->name() << oneObject.second->longname().split(", ");
1780
1781 for (auto &oneObject : dsoObjects)
1782 all << oneObject.name() << oneObject.longname().split(", ");
1783
1784 all.removeDuplicates();
1786 sendResponse(commands[ASTRO_GET_NAMES], QJsonArray::fromStringList(all));
1787 }
1788 else if (command == commands[ASTRO_GET_DESIGNATIONS])
1789 {
1790 QJsonArray designations;
1791
1792 for (auto &oneObject : m_DSOManager.get_objects_all())
1793 {
1794 QJsonObject oneDesignation =
1795 {
1796 {"primary", oneObject.name()},
1797 {"designations", QJsonArray::fromStringList(oneObject.longname().split(", "))}
1798 };
1799
1800 designations.append(oneDesignation);
1801 }
1802
1803 sendResponse(commands[ASTRO_GET_DESIGNATIONS], designations);
1804 }
1805 else if (command == commands[ASTRO_GET_LOCATION])
1806 {
1807 auto geo = KStarsData::Instance()->geo();
1809 {
1810 {"name", geo->name()},
1811 {"longitude", geo->lng()->Degrees()},
1812 {"latitude", geo->lat()->Degrees()},
1813 {"elevation", geo->elevation()},
1814 {"tz", geo->TZ()},
1815 {"tz0", geo->TZ0()}
1816 };
1817
1818 sendResponse(commands[ASTRO_GET_LOCATION], location);
1819 }
1820 // Get a list of object based on criteria
1821 else if (command == commands[ASTRO_SEARCH_OBJECTS])
1822 {
1823 // Set time if required only if Ekos profile is not running.
1824 if (payload.contains("jd") && m_Manager && m_Manager->getEkosStartingStatus() == Ekos::Idle)
1825 {
1826 auto jd = KStarsDateTime(payload["jd"].toDouble());
1827 KStarsData::Instance()->clock()->setManualMode(false);
1828 KStarsData::Instance()->clock()->setUTC(jd);
1829 }
1830
1831 // Search Criteria
1832 // Object Type
1833 auto objectType = static_cast<SkyObject::TYPE>(payload["type"].toInt(SkyObject::GALAXY));
1834 // Azimuth restriction
1835 auto objectDirection = static_cast<Direction>(payload["direction"].toInt(All));
1836 // Maximum Object Magnitude
1837 auto objectMaxMagnitude = payload["maxMagnitude"].toDouble(10);
1838 // Minimum Object Altitude
1839 auto objectMinAlt = payload["minAlt"].toDouble(15);
1840 // Minimum Duration that the object must be above the altitude (if any) seconds.
1841 auto objectMinDuration = payload["minDuration"].toInt(3600);
1842 // Minimum FOV in arcmins.
1843 auto objectMinFOV = payload["minFOV"].toDouble(0);
1844 // Data instance
1845 auto *data = KStarsData::Instance();
1846 // Geo Location
1847 auto *geo = KStarsData::Instance()->geo();
1848 // If we are before dawn, we check object altitude restrictions
1849 // Otherwise, all objects are welcome
1850 auto start = KStarsData::Instance()->lt();
1851 auto end = getNextDawn();
1852 if (start > end)
1853 // Add 1 day
1854 end = end.addDays(1);
1855
1857 CatalogsDB::CatalogObjectList dsoObjects;
1858 bool isDSO = false;
1859
1860 switch (objectType)
1861 {
1862 // Stars
1863 case SkyObject::STAR:
1864 case SkyObject::CATALOG_STAR:
1865 allObjects.append(data->skyComposite()->objectLists(SkyObject::STAR));
1866 allObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR));
1867 break;
1868 // Planets & Moon
1869 case SkyObject::PLANET:
1870 case SkyObject::MOON:
1871 allObjects.append(data->skyComposite()->objectLists(SkyObject::PLANET));
1872 allObjects.append(data->skyComposite()->objectLists(SkyObject::MOON));
1873 break;
1874 // Comets & Asteroids
1875 case SkyObject::COMET:
1876 allObjects.append(data->skyComposite()->objectLists(SkyObject::COMET));
1877 break;
1878 case SkyObject::ASTEROID:
1879 allObjects.append(data->skyComposite()->objectLists(SkyObject::ASTEROID));
1880 break;
1881 // Clusters
1882 case SkyObject::OPEN_CLUSTER:
1883 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::OPEN_CLUSTER, objectMaxMagnitude));
1884 isDSO = true;
1885 break;
1886 case SkyObject::GLOBULAR_CLUSTER:
1887 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GLOBULAR_CLUSTER, objectMaxMagnitude));
1888 isDSO = true;
1889 break;
1890 // Nebuale
1891 case SkyObject::GASEOUS_NEBULA:
1892 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GASEOUS_NEBULA, objectMaxMagnitude));
1893 isDSO = true;
1894 break;
1895 case SkyObject::PLANETARY_NEBULA:
1896 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::PLANETARY_NEBULA, objectMaxMagnitude));
1897 isDSO = true;
1898 break;
1899 case SkyObject::GALAXY:
1900 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GALAXY, objectMaxMagnitude));
1901 isDSO = true;
1902 break;
1903 case SkyObject::SUPERNOVA:
1904 {
1905 if (!Options::showSupernovae())
1906 {
1907 Options::setShowSupernovae(true);
1908 data->setFullTimeUpdate();
1910 }
1911 allObjects.append(data->skyComposite()->objectLists(SkyObject::SUPERNOVA));
1912 }
1913 break;
1914 case SkyObject::SATELLITE:
1915 {
1916 if (!Options::showSatellites())
1917 {
1918 Options::setShowSatellites(true);
1919 data->setFullTimeUpdate();
1921 }
1922 allObjects.append(data->skyComposite()->objectLists(SkyObject::SATELLITE));
1923 }
1924 break;
1925 default:
1926 break;
1927 }
1928
1929 // Sort by magnitude
1930 std::sort(allObjects.begin(), allObjects.end(), [](const auto & a, const auto & b)
1931 {
1932 return a.second->mag() < b.second->mag();
1933 });
1934
1935 QMutableVectorIterator<QPair<QString, const SkyObject *>> objectIterator(allObjects);
1936
1937 // Filter direction, if specified.
1938 if (objectDirection != All)
1939 {
1940 QPair<int, int> Quardent1(270, 360), Quardent2(0, 90), Quardent3(90, 180), Quardent4(180, 270);
1941 QPair<int, int> minAZ, maxAZ;
1942 switch (objectDirection)
1943 {
1944 case North:
1945 minAZ = Quardent1;
1946 maxAZ = Quardent2;
1947 break;
1948 case East:
1949 minAZ = Quardent2;
1950 maxAZ = Quardent3;
1951 break;
1952 case South:
1953 minAZ = Quardent3;
1954 maxAZ = Quardent4;
1955 break;
1956 case West:
1957 minAZ = Quardent4;
1958 maxAZ = Quardent1;
1959 break;
1960 default:
1961 break;
1962 }
1963
1964 if (isDSO)
1965 {
1966 CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1967 while (dsoIterator != dsoObjects.end())
1968 {
1969 // If there a more efficient way to do this?
1970 const double az = (*dsoIterator).recomputeHorizontalCoords(start, geo).az().Degrees();
1971 if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1972 dsoIterator = dsoObjects.erase(dsoIterator);
1973 else
1974 ++dsoIterator;
1975 }
1976 }
1977 else
1978 {
1979 while (objectIterator.hasNext())
1980 {
1981 const auto az = objectIterator.next().second->recomputeHorizontalCoords(start, geo).az().Degrees();
1982 if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1983 objectIterator.remove();
1984 }
1985 }
1986 }
1987
1988 // Maximum Magnitude
1989 if (!isDSO)
1990 {
1991 objectIterator.toFront();
1992 while (objectIterator.hasNext())
1993 {
1994 auto magnitude = objectIterator.next().second->mag();
1995 // Only filter for objects that have valid magnitude, otherwise, they're automatically included.
1996 if (magnitude != NaN::f && magnitude > objectMaxMagnitude)
1997 objectIterator.remove();
1998 }
1999 }
2000
2001 // Altitude
2002 if (isDSO)
2003 {
2004 CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
2005 while (dsoIterator != dsoObjects.end())
2006 {
2007 double duration = 0;
2008 for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
2009 {
2010 dms LST = geo->GSTtoLST(t.gst());
2011 (*dsoIterator).EquatorialToHorizontal(&LST, geo->lat());
2012 if ((*dsoIterator).alt().Degrees() >= objectMinAlt)
2013 duration += 3600;
2014 }
2015
2016 if (duration < objectMinDuration)
2017 dsoIterator = dsoObjects.erase(dsoIterator);
2018 else
2019 ++dsoIterator;
2020 }
2021 }
2022 else
2023 {
2024 objectIterator.toFront();
2025 while (objectIterator.hasNext())
2026 {
2027 auto oneObject = objectIterator.next().second;
2028 double duration = 0;
2029
2030 for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
2031 {
2032 auto LST = geo->GSTtoLST(t.gst());
2033 const_cast<SkyObject *>(oneObject)->EquatorialToHorizontal(&LST, geo->lat());
2034 if (oneObject->alt().Degrees() >= objectMinAlt)
2035 duration += 3600;
2036 }
2037
2038 if (duration < objectMinDuration)
2039 objectIterator.remove();
2040 }
2041 }
2042
2043 // For DSOs, check minimum required FOV, if any.
2044 if (isDSO && objectMinFOV > 0)
2045 {
2046 CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
2047 while (dsoIterator != dsoObjects.end())
2048 {
2049 if ((*dsoIterator).a() < objectMinFOV)
2050 dsoIterator = dsoObjects.erase(dsoIterator);
2051 else
2052 ++dsoIterator;
2053 }
2054 }
2055
2056 QStringList searchObjects;
2057 for (auto &oneObject : allObjects)
2058 searchObjects.append(oneObject.second->name());
2059 for (auto &oneObject : dsoObjects)
2060 searchObjects.append(oneObject.name());
2061
2062 searchObjects.removeDuplicates();
2063 QJsonArray response = QJsonArray::fromStringList(searchObjects);
2064
2065 sendResponse(commands[ASTRO_SEARCH_OBJECTS], response);
2066 }
2067 else if(command == commands[ASTRO_GET_OBJECT_INFO])
2068 {
2069 const auto name = payload["object"].toString();
2070 bool exact = payload["exact"].toBool(false);
2071 QJsonObject info;
2072 SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, exact);
2073 if(oneObject)
2074 {
2075 info =
2076 {
2077 {"name", exact ? name : oneObject->name()},
2078 {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
2079 {"magnitude", oneObject->mag()},
2080 {"ra0", oneObject->ra0().Hours()},
2081 {"de0", oneObject->dec0().Degrees()},
2082 {"ra", oneObject->ra().Hours()},
2083 {"de", oneObject->dec().Degrees()},
2084 {"object", true}
2085 };
2086 sendResponse(commands[ASTRO_GET_OBJECT_INFO], info);
2087 }
2088 else
2089 {
2090 info =
2091 {
2092 {"name", name},
2093 {"object", false},
2094 };
2095 sendResponse(commands[ASTRO_GET_OBJECT_INFO], info );
2096 }
2097
2098 }
2099 // Get a list of object based on criteria
2100 else if (command == commands[ASTRO_GET_OBJECTS_INFO])
2101 {
2102 // Set time if required only if Ekos profile is not running.
2103 if (payload.contains("jd") && m_Manager && m_Manager->getEkosStartingStatus() == Ekos::Idle)
2104 {
2105 auto jd = KStarsDateTime(payload["jd"].toDouble());
2106 KStarsData::Instance()->clock()->setManualMode(false);
2107 KStarsData::Instance()->clock()->setUTC(jd);
2108 }
2109
2110 // Object Names
2111 bool exact = payload["exact"].toBool(false);
2112 QVariantList objectNames = payload["names"].toArray().toVariantList();
2113 QJsonArray objectsArray;
2114
2115 for (auto &oneName : objectNames)
2116 {
2117 const QString name = oneName.toString();
2118 SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, exact);
2119 if (oneObject)
2120 {
2121 QJsonObject info =
2122 {
2123 {"name", exact ? name : oneObject->name()},
2124 {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
2125 {"magnitude", oneObject->mag()},
2126 {"ra0", oneObject->ra0().Hours()},
2127 {"de0", oneObject->dec0().Degrees()},
2128 {"ra", oneObject->ra().Hours()},
2129 {"de", oneObject->dec().Degrees()},
2130 };
2131
2132 // If DSO, add angular size.
2133 CatalogObject *dsoObject = dynamic_cast<CatalogObject*>(oneObject);
2134 if (dsoObject)
2135 {
2136 info["a"] = dsoObject->a();
2137 info["b"] = dsoObject->b();
2138 info["pa"] = dsoObject->pa();
2139 }
2140
2141 objectsArray.append(info);
2142 }
2143 }
2144
2145 sendResponse(commands[ASTRO_GET_OBJECTS_INFO], objectsArray);
2146 }
2147 // Get a object observability alt/az/ha
2148 else if (command == commands[ASTRO_GET_OBJECTS_OBSERVABILITY])
2149 {
2150 // Set time if required only if Ekos profile is not running.
2151 if (payload.contains("jd") && m_Manager && m_Manager->getEkosStartingStatus() == Ekos::Idle)
2152 {
2153 auto jd = KStarsDateTime(payload["jd"].toDouble());
2154 KStarsData::Instance()->clock()->setManualMode(false);
2155 KStarsData::Instance()->clock()->setUTC(jd);
2156 }
2157
2158 // Object Names
2159 QVariantList objectNames = payload["names"].toArray().toVariantList();
2160 QJsonArray objectsArray;
2161
2162 bool exact = payload["exact"].toBool(false);
2163 // Data instance
2164 auto *data = KStarsData::Instance();
2165 // Geo Location
2166 auto *geo = KStarsData::Instance()->geo();
2167 // UT
2168 auto ut = data->ut();
2169
2170 for (auto &oneName : objectNames)
2171 {
2172 const QString name = oneName.toString();
2173 SkyObject *oneObject = data->skyComposite()->findByName(name, exact);
2174 if (oneObject)
2175 {
2176 oneObject->EquatorialToHorizontal(data->lst(), geo->lat());
2177 dms ha(data->lst()->Degrees() - oneObject->ra().Degrees());
2178 QJsonObject info =
2179 {
2180 {"name", exact ? name : oneObject->name()},
2181 {"az", oneObject->az().Degrees()},
2182 {"alt", oneObject->alt().Degrees()},
2183 {"ha", ha.Hours()},
2184 };
2185
2186 objectsArray.append(info);
2187 }
2188 }
2189
2190 sendResponse(commands[ASTRO_GET_OBJECTS_OBSERVABILITY], objectsArray);
2191 }
2192 else if (command == commands[ASTRO_GET_OBJECTS_RISESET])
2193 {
2194 // Set time if required only if Ekos profile is not running.
2195 if (payload.contains("jd") && m_Manager && m_Manager->getEkosStartingStatus() == Ekos::Idle)
2196 {
2197 auto jd = KStarsDateTime(payload["jd"].toDouble());
2198 KStarsData::Instance()->clock()->setManualMode(false);
2199 KStarsData::Instance()->clock()->setUTC(jd);
2200 }
2201
2202 // Object Names
2203 QVariantList objectNames = payload["names"].toArray().toVariantList();
2204 QJsonArray objectsArray;
2205
2206 bool exact = payload["exact"].toBool(false);
2207 // Data instance
2208 auto *data = KStarsData::Instance();
2209 // Geo Location
2210 auto *geo = KStarsData::Instance()->geo();
2211 // UT
2212 QDateTime midnight = QDateTime(data->lt().date(), QTime());
2213 KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight));
2214
2215 int DayOffset = 0;
2216 if (data->lt().time().hour() > 12)
2217 DayOffset = 1;
2218
2219 for (auto &oneName : objectNames)
2220 {
2221 const QString name = oneName.toString();
2222 SkyObject *oneObject = data->skyComposite()->findByName(name, exact);
2223 if (oneObject)
2224 {
2225 QJsonObject info;
2226 //Prepare time/position variables
2227 //true = use rise time
2228 QTime riseTime = oneObject->riseSetTime(ut, geo, true);
2229
2230 //If transit time is before rise time, use transit time for tomorrow
2231 QTime transitTime = oneObject->transitTime(ut, geo);
2232 if (transitTime < riseTime)
2233 transitTime = oneObject->transitTime(ut.addDays(1), geo);
2234
2235 //If set time is before rise time, use set time for tomorrow
2236 //false = use set time
2237 QTime setTime = oneObject->riseSetTime(ut, geo, false);
2238 //false = use set time
2239 if (setTime < riseTime)
2240 setTime = oneObject->riseSetTime(ut.addDays(1), geo, false);
2241
2242 info["name"] = exact ? name : oneObject->name();
2243 if (riseTime.isValid())
2244 {
2245 info["rise"] = QString::asprintf("%02d:%02d", riseTime.hour(), riseTime.minute());
2246 info["set"] = QString::asprintf("%02d:%02d", setTime.hour(), setTime.minute());
2247 }
2248 else
2249 {
2250 if (oneObject->alt().Degrees() > 0.0)
2251 {
2252 info["rise"] = "Circumpolar";
2253 info["set"] = "Circumpolar";
2254 }
2255 else
2256 {
2257 info["rise"] = "Never rises";
2258 info["set"] = "Never rises";
2259 }
2260 }
2261
2262 info["transit"] = QString::asprintf("%02d:%02d", transitTime.hour(), transitTime.minute());
2263
2264 QJsonArray altitudes;
2265 for (double h = -12.0; h <= 12.0; h += 0.5)
2266 {
2267 double hour = h + (24.0 * DayOffset);
2268 KStarsDateTime offset = ut.addSecs(hour * 3600.0);
2269 CachingDms LST = geo->GSTtoLST(offset.gst());
2270 oneObject->EquatorialToHorizontal(&LST, geo->lat());
2271 altitudes.append(oneObject->alt().Degrees());
2272 }
2273
2274 info["altitudes"] = altitudes;
2275
2276 objectsArray.append(info);
2277 }
2278 }
2279
2280 sendResponse(commands[ASTRO_GET_OBJECTS_RISESET], objectsArray);
2281 }
2282}
2283
2284///////////////////////////////////////////////////////////////////////////////////////////
2285///
2286///////////////////////////////////////////////////////////////////////////////////////////
2287void Message::processFileCommands(const QString &command, const QJsonObject &payload)
2288{
2289 if (command == commands[FILE_DEFAULT_PATH])
2290 {
2291 sendResponse(commands[FILE_DEFAULT_PATH],
2292 KSPaths::writableLocation(static_cast<QStandardPaths::StandardLocation>(payload["type"].toInt())));
2293 }
2294 else if (command == commands[FILE_DIRECTORY_OPERATION])
2295 {
2296 auto path = payload["path"].toString();
2297 auto operation = payload["operation"].toString();
2298
2299 if (operation == "create")
2300 {
2301 QJsonObject info =
2302 {
2303 {"result", QDir().mkpath(path)},
2304 {"operation", operation}
2305 };
2306
2307 sendResponse(commands[FILE_DIRECTORY_OPERATION], info);
2308 }
2309 else if (operation == "remove")
2310 {
2311 QJsonObject info =
2312 {
2313 {"result", QDir(path).removeRecursively()},
2314 {"operation", operation}
2315 };
2316
2317 sendResponse(commands[FILE_DIRECTORY_OPERATION], info);
2318 }
2319 else if (operation == "list")
2320 {
2321 auto namedFilters = payload["namedFilters"].toString("*").split(",");
2322 auto filters = static_cast<QDir::Filters>(payload["filters"].toInt(QDir::NoFilter));
2323 auto sort = static_cast<QDir::SortFlags>(payload["sort"].toInt(QDir::NoSort));
2324 auto list = QDir(path).entryInfoList(namedFilters, filters, sort);
2325 auto entries = QJsonArray();
2326 for (auto &oneEntry : list)
2327 {
2328 QJsonObject info =
2329 {
2330 {"name", oneEntry.fileName()},
2331 {"path", oneEntry.absolutePath()},
2332 {"size", oneEntry.size()},
2333 {"isFile", oneEntry.isFile()},
2334 {"creation", oneEntry.birthTime().toSecsSinceEpoch()},
2335 {"modified", oneEntry.lastModified().toSecsSinceEpoch()}
2336 };
2337
2338 entries.push_back(info);
2339 }
2340
2341 QJsonObject info =
2342 {
2343 {"result", !entries.empty()},
2344 {"operation", operation},
2345 {"payload", entries}
2346 };
2347
2348 sendResponse(commands[FILE_DIRECTORY_OPERATION], info);
2349 }
2350 else if (operation == "exists")
2351 {
2352 QJsonObject info =
2353 {
2354 {"result", QDir(path).exists()},
2355 {"operation", operation}
2356 };
2357
2358 sendResponse(commands[FILE_DIRECTORY_OPERATION], info);
2359 }
2360 }
2361}
2362
2363///////////////////////////////////////////////////////////////////////////////////////////
2364///
2365///////////////////////////////////////////////////////////////////////////////////////////
2366KStarsDateTime Message::getNextDawn()
2367{
2368 // Today's date
2369 const KStarsDateTime localTime = KStarsData::Instance()->lt();
2370 // Local Midnight
2371 const KStarsDateTime midnight = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
2372 // Almanac
2373 KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
2374 // Next Dawn
2375 KStarsDateTime nextDawn = midnight.addSecs(almanac.getDawnAstronomicalTwilight() * 24.0 * 3600.0);
2376 // If dawn is earliar than now, add a day
2377 if (nextDawn < localTime)
2378 nextDawn.addDays(1);
2379
2380 return nextDawn;
2381}
2382
2383///////////////////////////////////////////////////////////////////////////////////////////
2384///
2385///////////////////////////////////////////////////////////////////////////////////////////
2386void Message::requestDSLRInfo(const QString &cameraName)
2387{
2388 sendResponse(commands[DSLR_GET_INFO], cameraName);
2389}
2390
2391///////////////////////////////////////////////////////////////////////////////////////////
2392///
2393///////////////////////////////////////////////////////////////////////////////////////////
2394void Message::requestPortSelection(bool show)
2395{
2396 sendResponse(commands[GET_PROFILE_PORT_SELECTION], show);
2397}
2398
2399///////////////////////////////////////////////////////////////////////////////////////////
2400///
2401///////////////////////////////////////////////////////////////////////////////////////////
2402void Message::sendDialog(const QJsonObject &message)
2403{
2404 sendResponse(commands[DIALOG_GET_INFO], message);
2405}
2406
2407///////////////////////////////////////////////////////////////////////////////////////////
2408///
2409///////////////////////////////////////////////////////////////////////////////////////////
2410void Message::sendResponse(const QString &command, const QJsonObject &payload)
2411{
2412 for (auto &nodeManager : m_NodeManagers)
2413 {
2414 nodeManager->message()->sendResponse(command, payload);
2415 }
2416}
2417
2418///////////////////////////////////////////////////////////////////////////////////////////
2419///
2420///////////////////////////////////////////////////////////////////////////////////////////
2421void Message::sendResponse(const QString &command, const QJsonArray &payload)
2422{
2423 for (auto &nodeManager : m_NodeManagers)
2424 {
2425 nodeManager->message()->sendResponse(command, payload);
2426 }
2427}
2428
2429///////////////////////////////////////////////////////////////////////////////////////////
2430///
2431///////////////////////////////////////////////////////////////////////////////////////////
2432void Message::sendResponse(const QString &command, const QString &payload)
2433{
2434 for (auto &nodeManager : m_NodeManagers)
2435 {
2436 nodeManager->message()->sendResponse(command, payload);
2437 }
2438}
2439
2440///////////////////////////////////////////////////////////////////////////////////////////
2441///
2442///////////////////////////////////////////////////////////////////////////////////////////
2443void Message::sendResponse(const QString &command, bool payload)
2444{
2445 for (auto &nodeManager : m_NodeManagers)
2446 {
2447 nodeManager->message()->sendResponse(command, payload);
2448 }
2449}
2450
2451///////////////////////////////////////////////////////////////////////////////////////////
2452///
2453///////////////////////////////////////////////////////////////////////////////////////////
2454void Message::autofocusAborted()
2455{
2456 QJsonObject cStatus =
2457 {
2458 {"status", "Aborted"}
2459 };
2460 sendResponse(commands[NEW_FOCUS_STATE], cStatus);
2461}
2462
2463///////////////////////////////////////////////////////////////////////////////////////////
2464///
2465///////////////////////////////////////////////////////////////////////////////////////////
2466void Message::updateMountStatus(const QJsonObject &status, bool throttle)
2467{
2468 if (throttle)
2469 {
2471 if (m_ThrottleTS.msecsTo(now) >= THROTTLE_INTERVAL)
2472 {
2473 m_ThrottleTS = now;
2474 sendResponse(commands[NEW_MOUNT_STATE], status);
2475 }
2476 }
2477 else
2478 sendResponse(commands[NEW_MOUNT_STATE], status);
2479}
2480
2481///////////////////////////////////////////////////////////////////////////////////////////
2482///
2483///////////////////////////////////////////////////////////////////////////////////////////
2484void Message::updateCaptureStatus(const QJsonObject &status)
2485{
2486 sendResponse(commands[NEW_CAPTURE_STATE], status);
2487}
2488
2489///////////////////////////////////////////////////////////////////////////////////////////
2490///
2491///////////////////////////////////////////////////////////////////////////////////////////
2492void Message::updateFocusStatus(const QJsonObject &status)
2493{
2494 sendResponse(commands[NEW_FOCUS_STATE], status);
2495}
2496
2497///////////////////////////////////////////////////////////////////////////////////////////
2498///
2499///////////////////////////////////////////////////////////////////////////////////////////
2500void Message::updateGuideStatus(const QJsonObject &status)
2501{
2502 sendResponse(commands[NEW_GUIDE_STATE], status);
2503}
2504
2505///////////////////////////////////////////////////////////////////////////////////////////
2506///
2507///////////////////////////////////////////////////////////////////////////////////////////
2508void Message::updateDomeStatus(const QJsonObject &status)
2509{
2510 sendResponse(commands[NEW_DOME_STATE], status);
2511}
2512
2513///////////////////////////////////////////////////////////////////////////////////////////
2514///
2515///////////////////////////////////////////////////////////////////////////////////////////
2516void Message::updateCapStatus(const QJsonObject &status)
2517{
2518 sendResponse(commands[NEW_CAP_STATE], status);
2519}
2520
2521///////////////////////////////////////////////////////////////////////////////////////////
2522///
2523///////////////////////////////////////////////////////////////////////////////////////////
2524void Message::updateAlignStatus(const QJsonObject &status)
2525{
2526 sendResponse(commands[NEW_ALIGN_STATE], status);
2527}
2528
2529///////////////////////////////////////////////////////////////////////////////////////////
2530///
2531///////////////////////////////////////////////////////////////////////////////////////////
2532void Message::sendConnection()
2533{
2534 QJsonObject connectionState =
2535 {
2536 {"connected", true},
2537 {"online", m_Manager->getEkosStartingStatus() == Ekos::Success}
2538 };
2539
2540 sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
2541}
2542
2543///////////////////////////////////////////////////////////////////////////////////////////
2544///
2545///////////////////////////////////////////////////////////////////////////////////////////
2546void Message::sendStates()
2547{
2548 // Send capture sequence if one exists
2549 if (m_Manager->captureModule())
2550 {
2551 QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2552 sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2553 sendCaptureSequence(m_Manager->captureModule()->getSequence());
2554 }
2555
2556 if (m_Manager->mountModule())
2557 {
2558 QJsonObject mountState =
2559 {
2560 {"status", m_Manager->mountModule()->statusString(false)},
2561 {"target", m_Manager->capturePreview->mountTarget->text()},
2562 {"slewRate", m_Manager->mountModule()->slewRate()},
2563 {"pierSide", m_Manager->mountModule()->pierSide()}
2564 };
2565
2566 sendResponse(commands[NEW_MOUNT_STATE], mountState);
2567 }
2568
2569 if (m_Manager->focusModule())
2570 {
2571 QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->mainFocuser()->status(), false)}};
2572 sendResponse(commands[NEW_FOCUS_STATE], focusState);
2573 }
2574
2575 if (m_Manager->guideModule())
2576 {
2577 QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2578 sendResponse(commands[NEW_GUIDE_STATE], guideState);
2579 }
2580
2581 if (m_Manager->alignModule())
2582 {
2583 // Align State
2584 QJsonObject alignState =
2585 {
2586 {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2587 };
2588 sendResponse(commands[NEW_ALIGN_STATE], alignState);
2589
2590 // Align settings
2591 sendAlignSettings(m_Manager->alignModule()->getAllSettings());
2592
2593 Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2594 if (paa)
2595 {
2596 // Polar State
2597 QTextDocument doc;
2598 doc.setHtml(paa->getPAHMessage());
2599 QJsonObject polarState =
2600 {
2601 {"stage", paa->getPAHStageString(false)},
2602 {"enabled", paa->isEnabled()},
2603 {"message", doc.toPlainText()},
2604 };
2605 sendResponse(commands[NEW_POLAR_STATE], polarState);
2606 }
2607 }
2608}
2609
2610///////////////////////////////////////////////////////////////////////////////////////////
2611///
2612///////////////////////////////////////////////////////////////////////////////////////////
2613void Message::sendEvent(const QString &message, KSNotification::EventSource source, KSNotification::EventType event)
2614{
2615 if (Options::ekosLiveNotifications() == false)
2616 return;
2617
2618 QJsonObject newEvent =
2619 {
2620 {"source", source},
2621 {"severity", event},
2622 {"message", message},
2623 {"uuid", QUuid::createUuid().toString()}
2624 };
2625
2626 sendResponse(commands[NEW_NOTIFICATION], newEvent);
2627}
2628
2629///////////////////////////////////////////////////////////////////////////////////////////
2630///
2631///////////////////////////////////////////////////////////////////////////////////////////
2632void Message::sendManualRotatorStatus(double currentPA, double targetPA, double threshold)
2633{
2634 QJsonObject request = {{ "currentPA", currentPA}, {"targetPA", targetPA}, {"threshold", threshold}};
2635 sendResponse(commands[ALIGN_MANUAL_ROTATOR_STATUS], request);
2636}
2637
2638///////////////////////////////////////////////////////////////////////////////////////////
2639///
2640///////////////////////////////////////////////////////////////////////////////////////////
2641void Message::setBoundingRect(QRect rect, QSize view, double currentZoom)
2642{
2643 m_BoundingRect = rect;
2644 m_ViewSize = view;
2645 m_CurrentZoom = currentZoom;
2646}
2647
2648///////////////////////////////////////////////////////////////////////////////////////////
2649///
2650///////////////////////////////////////////////////////////////////////////////////////////
2651void Message::processDialogResponse(const QJsonObject &payload)
2652{
2653 KSMessageBox::Instance()->selectResponse(payload["button"].toString());
2654}
2655
2656///////////////////////////////////////////////////////////////////////////////////////////
2657///
2658///////////////////////////////////////////////////////////////////////////////////////////
2659void Message::processNewProperty(INDI::Property prop)
2660{
2661 // Do not send new properties until all properties settle down
2662 // then send any properties that appears afterwards since the initial bunch
2663 // would cause a heavy message congestion.
2664 if (m_Manager->settleStatus() != Ekos::CommunicationStatus::Success)
2665 return;
2666
2667 QJsonObject propObject;
2668 ISD::propertyToJson(prop, propObject, false);
2669 sendResponse(commands[DEVICE_PROPERTY_ADD], propObject);
2670}
2671
2672///////////////////////////////////////////////////////////////////////////////////////////
2673///
2674///////////////////////////////////////////////////////////////////////////////////////////
2675void Message::processDeleteProperty(INDI::Property prop)
2676{
2677 QJsonObject payload =
2678 {
2679 {"device", prop.getDeviceName()},
2680 {"name", prop.getName()}
2681 };
2682
2683 sendResponse(commands[DEVICE_PROPERTY_REMOVE], payload);
2684}
2685
2686///////////////////////////////////////////////////////////////////////////////////////////
2687///
2688///////////////////////////////////////////////////////////////////////////////////////////
2689void Message::processMessage(const QSharedPointer<ISD::GenericDevice> &device, int id)
2690{
2691 if (Options::ekosLiveNotifications() == false)
2692 return;
2693
2694 auto message = QString::fromStdString(device->getBaseDevice().messageQueue(id));
2695 QJsonObject payload =
2696 {
2697 {"device", device->getDeviceName()},
2698 {"message", message}
2699 };
2700
2701 sendResponse(commands[DEVICE_MESSAGE], payload);
2702}
2703
2704///////////////////////////////////////////////////////////////////////////////////////////
2705///
2706///////////////////////////////////////////////////////////////////////////////////////////
2707void Message::processUpdateProperty(INDI::Property prop)
2708{
2709 if (m_PropertySubscriptions.contains(prop.getDeviceName()))
2710 {
2711 QSet<QString> subProps = m_PropertySubscriptions[prop.getDeviceName()];
2712 if (subProps.contains(prop.getName()))
2713 {
2714 PendingProperty pending{prop.getDeviceName(), prop.getName()};
2715 m_PendingProperties.remove(pending);
2716 m_PendingProperties.insert(pending);
2717 }
2718 }
2719}
2720
2721///////////////////////////////////////////////////////////////////////////////////////////
2722///
2723///////////////////////////////////////////////////////////////////////////////////////////
2724void Message::setPendingPropertiesEnabled(bool enabled)
2725{
2726 if (enabled)
2727 m_PendingPropertiesTimer.start();
2728 else
2729 {
2730 m_PendingProperties.clear();
2731 // Must stop timer and sleep for 500ms to enable any pending properties to finish
2732 if (m_PendingPropertiesTimer.isActive())
2733 {
2734 m_PendingPropertiesTimer.stop();
2735 std::this_thread::sleep_for(std::chrono::milliseconds(500));
2736 }
2737 }
2738}
2739
2740///////////////////////////////////////////////////////////////////////////////////////////
2741///
2742///////////////////////////////////////////////////////////////////////////////////////////
2743void Message::sendPendingProperties()
2744{
2745 // Group properties by device to minimize device lookups
2746 QMap<QString, QSet<QString>> deviceProperties;
2747
2748 // First pass - group by device
2749 for (const auto &pending : m_PendingProperties)
2750 deviceProperties[pending.device].insert(pending.name);
2751
2752 // Second pass - process each device's properties
2753 for (auto it = deviceProperties.constBegin(); it != deviceProperties.constEnd(); ++it)
2754 {
2756 // Only lookup device once for all its properties
2757 if (INDIListener::findDevice(it.key(), device))
2758 {
2759 // Process all properties for this device
2760 for (const auto &propName : it.value())
2761 {
2762 auto prop = device->getProperty(propName);
2763 if (prop)
2764 {
2765 QJsonObject propObject;
2766 ISD::propertyToJson(prop, propObject);
2767 sendResponse(commands[DEVICE_PROPERTY_GET], propObject);
2768 }
2769 }
2770 }
2771 }
2772
2773 // Clear all pending properties
2774 m_PendingProperties.clear();
2775}
2776
2777///////////////////////////////////////////////////////////////////////////////////////////
2778///
2779///////////////////////////////////////////////////////////////////////////////////////////
2780void Message::sendModuleState(const QString &name)
2781{
2782 if (name == "Capture")
2783 {
2784 QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2785 sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2786 sendCaptureSequence(m_Manager->captureModule()->getSequence());
2787 }
2788 else if (name == "Mount")
2789 {
2790 QJsonObject mountState =
2791 {
2792 {"status", m_Manager->mountStatus->getStatusText()},
2793 {"target", m_Manager->capturePreview->mountTarget->text()},
2794 {"slewRate", m_Manager->mountModule()->slewRate()},
2795 {"pierSide", m_Manager->mountModule()->pierSide()}
2796 };
2797
2798 sendResponse(commands[NEW_MOUNT_STATE], mountState);
2799 }
2800 else if (name == "Focus")
2801 {
2802 QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->mainFocuser()->status(), false)}};
2803 sendResponse(commands[NEW_FOCUS_STATE], focusState);
2804 }
2805 else if (name == "Guide")
2806 {
2807 QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2808 sendResponse(commands[NEW_GUIDE_STATE], guideState);
2809 }
2810 else if (name == "Align")
2811 {
2812 // Align State
2813 QJsonObject alignState =
2814 {
2815 {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2816 };
2817 sendResponse(commands[NEW_ALIGN_STATE], alignState);
2818
2819 // Align settings
2820 sendAlignSettings(m_Manager->alignModule()->getAllSettings());
2821
2822 Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2823 if (paa)
2824 {
2825 // Polar State
2826 QTextDocument doc;
2827 doc.setHtml(paa->getPAHMessage());
2828 QJsonObject polarState =
2829 {
2830 {"stage", paa->getPAHStageString(false)},
2831 {"enabled", paa->isEnabled()},
2832 {"message", doc.toPlainText()},
2833 };
2834 sendResponse(commands[NEW_POLAR_STATE], polarState);
2835 }
2836 }
2837}
2838
2839///////////////////////////////////////////////////////////////////////////////////////////
2840///
2841///////////////////////////////////////////////////////////////////////////////////////////
2842QObject *Message::findObject(const QString &name)
2843{
2844 QObject *object {nullptr};
2845 // Check for manager itself
2846 if (name == "Manager")
2847 return m_Manager;
2848 // Try Manager first
2849 object = m_Manager->findChild<QObject *>(name);
2850 if (object)
2851 return object;
2852 // Then INDI Listener
2853 object = INDIListener::Instance()->findChild<QObject *>(name);
2854 if (object)
2855 return object;
2856 // FITS Viewer. Search for any matching imageData
2857 // TODO Migrate to DBus
2858 for (auto &viewer : KStars::Instance()->getFITSViewers())
2859 {
2860 for (auto &tab : viewer->tabs())
2861 {
2862 if (tab->getView()->objectName() == name)
2863 return tab->getView().get();
2864 }
2865 }
2866
2867 // Finally KStars
2868 // N.B. This does not include indepdent objects with their parent set to null (e.g. FITSViewer)
2869 object = KStars::Instance()->findChild<QObject *>(name);
2870 return object;
2871}
2872
2873///////////////////////////////////////////////////////////////////////////////////////////
2874///
2875///////////////////////////////////////////////////////////////////////////////////////////
2876#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
2877bool Message::parseArgument(QVariant::Type type, const QVariant &arg, QMetaMethodArgument &genericArg, SimpleTypes &types)
2878#else
2879bool Message::parseArgument(QVariant::Type type, const QVariant &arg, QGenericArgument &genericArg, SimpleTypes &types)
2880#endif
2881{
2882 //QMetaMethodArgument genericArgument;
2883
2884 switch (type)
2885 {
2886 case QVariant::Type::Int:
2887 types.number_integer = arg.toInt();
2888 genericArg = Q_ARG(int, types.number_integer);
2889 return true;
2890 case QVariant::Type::UInt:
2891 types.number_unsigned_integer = arg.toUInt();
2892 genericArg = Q_ARG(uint, types.number_unsigned_integer);
2893 return true;
2894 case QVariant::Type::LongLong:
2895 types.number_integer = arg.toLongLong();
2896 genericArg = Q_ARG(int, types.number_integer);
2897 return true;
2898 case QVariant::Type::ULongLong:
2899 types.number_unsigned_integer = arg.toULongLong();
2900 genericArg = Q_ARG(uint, types.number_unsigned_integer);
2901 return true;
2902 case QVariant::Type::Double:
2903 types.number_double = arg.toDouble();
2904 genericArg = Q_ARG(double, types.number_double);
2905 return true;
2906 case QVariant::Type::Bool:
2907 types.boolean = arg.toBool();
2908 genericArg = Q_ARG(bool, types.boolean);
2909 return true;
2910 case QVariant::Type::String:
2911 types.text = arg.toString();
2912 genericArg = Q_ARG(QString, types.text);
2913 return true;
2914 case QVariant::Type::Url:
2915 types.url = arg.toUrl();
2916 genericArg = Q_ARG(QUrl, types.url);
2917 return true;
2918 default:
2919 break;
2920 }
2921
2922 return false;
2923}
2924
2925///////////////////////////////////////////////////////////////////////////////////////////
2926///
2927///////////////////////////////////////////////////////////////////////////////////////////
2928void Message::invokeMethod(QObject *context, const QJsonObject &payload)
2929{
2930#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
2932#else
2933 QList<QGenericArgument> argsList;
2934#endif
2935
2936 QList<SimpleTypes> typesList;
2937
2938 auto name = payload["name"].toString().toLatin1();
2939
2940 if (payload.contains("args"))
2941 {
2942 QJsonArray args = payload["args"].toArray();
2943
2944 for (auto oneArg : args)
2945 {
2946 auto argObject = oneArg.toObject();
2947#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
2948 QMetaMethodArgument genericArgument;
2949#else
2950 QGenericArgument genericArgument;
2951#endif
2952 SimpleTypes genericType;
2953 argsList.append(genericArgument);
2954 typesList.append(genericType);
2955 if (parseArgument(static_cast<QVariant::Type>(argObject["type"].toInt()), argObject["value"].toVariant(), argsList.back(),
2956 typesList.last()) == false)
2957 {
2958 argsList.pop_back();
2959 typesList.pop_back();
2960 }
2961 }
2962
2963 switch (argsList.size())
2964 {
2965 case 1:
2966 QMetaObject::invokeMethod(context, name, argsList[0]);
2967 break;
2968 case 2:
2969 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1]);
2970 break;
2971 case 3:
2972 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1], argsList[2]);
2973 break;
2974 case 4:
2975 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1], argsList[2], argsList[3]);
2976 break;
2977 default:
2978 break;
2979 }
2980 }
2981 else
2982 {
2983 QMetaObject::invokeMethod(context, name);
2984 }
2985}
2986
2987}
a dms subclass that caches its sine and cosine values every time the angle is changed.
Definition cachingdms.h:19
A simple container object to hold the minimum information for a Deep Sky Object to be drawn on the sk...
float a() const
double pa() const override
float b() const
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
Definition align.h:77
bool loadAndSlew(const QByteArray &image, const QString &extension)
DBUS interface function.
Definition align.cpp:3085
Performs calibration and autoguiding using an ST4 port or directly via the INDI driver.
Definition guide.h:51
Q_SCRIPTABLE bool capture()
DBUS interface function.
Definition guide.cpp:774
Q_SCRIPTABLE Q_NOREPLY void clearCalibration()
DBUS interface function.
Definition guide.cpp:1578
Q_SCRIPTABLE bool abort()
DBUS interface function.
Definition guide.cpp:894
Q_SCRIPTABLE Q_NOREPLY void loop()
DBUS interface function.
Definition guide.cpp:3085
Q_SCRIPTABLE bool guide()
DBUS interface function.
Definition guide.cpp:1340
Supports controlling INDI telescope devices including setting/retrieving mount properties,...
Definition mount.h:33
The PolarAlignmentAssistant class.
The Ekos scheduler is a simple scheduler class to orchestrate automated multi object observation jobs...
Definition scheduler.h:56
Q_INVOKABLE void addJob(SchedulerJob *job=nullptr)
addJob Add a new job from form values
bool importMosaic(const QJsonObject &payload)
importMosaic Import mosaic into planner and generate jobs for the scheduler.
bool loadFile(const QUrl &path)
loadFile Load scheduler jobs from disk
bool saveFile(const QUrl &path)
saveFile Save scheduler jobs to disk
void removeOneJob(int index)
Remove a job by selecting a table row.
Camera class controls an INDI Camera device.
Definition indicamera.h:45
Q_INVOKABLE QAction * action(const QString &name) const
A class that implements methods to find sun rise, sun set, twilight begin / end times,...
Definition ksalmanac.h:27
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
KStarsDateTime addDays(int nd) const
Modify the Date/Time by adding a number of days.
KStarsDateTime addSecs(double s) const
static KStarsDateTime currentDateTimeUtc()
SkyMap * map() const
Definition kstars.h:140
static KStars * Instance()
Definition kstars.h:122
virtual KActionCollection * actionCollection() const
void forceUpdate(bool now=false)
Recalculates the positions of objects in the sky, and then repaints the sky map.
Definition skymap.cpp:1184
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:50
virtual QString name(void) const
Definition skyobject.h:154
virtual QString longname(void) const
Definition skyobject.h:182
QTime transitTime(const KStarsDateTime &dt, const GeoLocation *geo) const
The same iteration technique described in riseSetTime() is used here.
QTime riseSetTime(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact=true) const
Determine the time at which the point will rise or set.
Definition skyobject.cpp:93
float mag() const
Definition skyobject.h:236
TYPE
The type classification of the SkyObject.
Definition skyobject.h:120
The sky coordinates of a point in the sky.
Definition skypoint.h:45
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
Definition skypoint.cpp:720
const CachingDms & dec() const
Definition skypoint.h:269
const CachingDms & ra0() const
Definition skypoint.h:251
const CachingDms & ra() const
Definition skypoint.h:263
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
Definition skypoint.cpp:77
const dms & az() const
Definition skypoint.h:275
const dms & alt() const
Definition skypoint.h:281
const CachingDms & dec0() const
Definition skypoint.h:257
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
Definition dms.cpp:429
double Hours() const
Definition dms.h:168
const double & Degrees() const
Definition dms.h:141
Q_SCRIPTABLE bool captureAndSolve(bool initialCall=true)
DBUS interface function.
Definition align.cpp:1416
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition align.h:402
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Generic record interfaces and implementations.
Definition cloud.cpp:22
AlignState
Definition ekos.h:145
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
GeoCoordinates geo(const QVariant &location)
QVariant location(const QVariant &res)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
KGuiItem properties()
const QList< QKeySequence > & end()
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void trigger()
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
void setCurrentIndex(int index)
QDateTime currentDateTime()
QDate date() const const
typedef Filters
typedef SortFlags
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const const
bool exists() const const
QString homePath()
bool mkpath(const QString &dirPath) const const
bool removeRecursively()
QChar separator()
QString tempPath()
virtual void close() override
qint64 write(const QByteArray &data)
void append(const QJsonValue &value)
QJsonArray fromStringList(const QStringList &list)
bool isEmpty() const const
QVariantList toVariantList() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool contains(QLatin1StringView key) const const
QJsonObject fromVariantMap(const QVariantMap &map)
iterator insert(QLatin1StringView key, const QJsonValue &value)
QVariantMap toVariantMap() const const
qreal angle() const const
qreal length() const const
QPointF p1() const const
QPointF p2() const const
void append(QList< T > &&value)
reference back()
iterator begin()
iterator end()
T & last()
void pop_back()
qsizetype size() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator insert(const Key &key, const T &value)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
T findChild(const QString &name, Qt::FindChildOptions options) const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
bool remove(const T &value)
bool isNull() const const
QString arg(Args &&... args) const const
QString asprintf(const char *cformat,...)
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
qsizetype removeDuplicates()
void sort(Qt::CaseSensitivity cs)
CaseInsensitive
LocalTime
QTextStream & center(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
virtual QString fileName() const const override
void setAutoRemove(bool b)
void setHtml(const QString &html)
QString toPlainText() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
int hour() const const
bool isValid(int h, int m, int s, int ms)
int minute() const const
void timeout()
QUrl fromLocalFile(const QString &localFile)
QString url(FormattingOptions options) const const
QUuid createUuid()
QString toString(StringFormat mode) const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const
uint toUInt(bool *ok) const const
qulonglong toULongLong(bool *ok) const const
QUrl toUrl() const const
bool isEnabled() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 11:53:46 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.