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

KDE's Doxygen guidelines are available online.