Kstars

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

KDE's Doxygen guidelines are available online.