File indexing completed on 2024-04-21 14:45:04

0001 /* Ekos Live Message
0002 
0003     SPDX-FileCopyrightText: 2018 Jasem Mutlaq <mutlaqja@ikarustech.com>
0004 
0005     Message Channel
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "message.h"
0011 #include "commands.h"
0012 #include "profileinfo.h"
0013 #include "indi/drivermanager.h"
0014 #include "indi/indilistener.h"
0015 #include "auxiliary/ksmessagebox.h"
0016 #include "ekos/auxiliary/filtermanager.h"
0017 #include "ekos/auxiliary/opticaltrainmanager.h"
0018 #include "ekos/auxiliary/profilesettings.h"
0019 #include "ekos/capture/capture.h"
0020 #include "ekos/guide/guide.h"
0021 #include "ekos/mount/mount.h"
0022 #include "ekos/scheduler/scheduler.h"
0023 #include "ekos/scheduler/schedulermodulestate.h"
0024 #include "kstars.h"
0025 #include "kstarsdata.h"
0026 #include "ekos_debug.h"
0027 #include "ksalmanac.h"
0028 #include "skymapcomposite.h"
0029 #include "catalogobject.h"
0030 #include "ekos/auxiliary/darklibrary.h"
0031 #include "skymap.h"
0032 #include "Options.h"
0033 #include "version.h"
0034 
0035 #include <KActionCollection>
0036 #include <basedevice.h>
0037 #include <QUuid>
0038 
0039 namespace EkosLive
0040 {
0041 Message::Message(Ekos::Manager *manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
0042     m_Manager(manager), m_NodeManagers(nodeManagers), m_DSOManager(CatalogsDB::dso_db_path())
0043 {
0044     for (auto &nodeManager : m_NodeManagers)
0045     {
0046         connect(nodeManager->message(), &Node::connected, this, &Message::onConnected);
0047         connect(nodeManager->message(), &Node::disconnected, this, &Message::onDisconnected);
0048         connect(nodeManager->message(), &Node::onTextReceived, this, &Message::onTextReceived);
0049     }
0050 
0051     connect(manager, &Ekos::Manager::newModule, this, &Message::sendModuleState);
0052 
0053     m_ThrottleTS = QDateTime::currentDateTime();
0054 
0055     m_PendingPropertiesTimer.setInterval(500);
0056     connect(&m_PendingPropertiesTimer, &QTimer::timeout, this, &Message::sendPendingProperties);
0057 
0058     m_DebouncedSend.setInterval(500);
0059     connect(&m_DebouncedSend, &QTimer::timeout, this, &Message::dispatchDebounceQueue);
0060 }
0061 
0062 ///////////////////////////////////////////////////////////////////////////////////////////
0063 ///
0064 ///////////////////////////////////////////////////////////////////////////////////////////
0065 void Message::onConnected()
0066 {
0067     auto node = qobject_cast<Node*>(sender());
0068     if (!node)
0069         return;
0070 
0071     qCInfo(KSTARS_EKOS) << "Connected to Message Websocket server at" << node->url().toDisplayString();
0072 
0073     m_PendingPropertiesTimer.start();
0074     sendConnection();
0075     sendProfiles();
0076     emit connected();
0077 }
0078 
0079 ///////////////////////////////////////////////////////////////////////////////////////////
0080 ///
0081 ///////////////////////////////////////////////////////////////////////////////////////////
0082 void Message::onDisconnected()
0083 {
0084     auto node = qobject_cast<Node*>(sender());
0085     if (!node)
0086         return;
0087 
0088     qCInfo(KSTARS_EKOS) << "Disconnected from Message Websocket server at" << node->url().toDisplayString();
0089 
0090     if (isConnected() == false)
0091     {
0092         m_PendingPropertiesTimer.stop();
0093         emit disconnected();
0094     }
0095 }
0096 
0097 ///////////////////////////////////////////////////////////////////////////////////////////
0098 ///
0099 ///////////////////////////////////////////////////////////////////////////////////////////
0100 void Message::onTextReceived(const QString &message)
0101 {
0102     auto node = qobject_cast<Node*>(sender());
0103     if (!node || message.isEmpty())
0104         return;
0105 
0106     qCInfo(KSTARS_EKOS) << "Websocket Message" << message;
0107     QJsonParseError error;
0108     auto serverMessage = QJsonDocument::fromJson(message.toUtf8(), &error);
0109     if (error.error != QJsonParseError::NoError)
0110     {
0111         qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
0112         return;
0113     }
0114 
0115     const QJsonObject msgObj = serverMessage.object();
0116     const QString command = msgObj["type"].toString();
0117     const QJsonObject payload = msgObj["payload"].toObject();
0118 
0119     if (command == commands[GET_CONNECTION])
0120     {
0121         sendConnection();
0122     }
0123     else if (command == commands[LOGOUT])
0124     {
0125         emit expired(node->url());
0126         return;
0127     }
0128     else if (command == commands[SET_CLIENT_STATE])
0129     {
0130         // If client is connected, make sure clock is ticking
0131         if (payload["state"].toBool(false))
0132         {
0133             qCInfo(KSTARS_EKOS) << "EkosLive client is connected.";
0134 
0135             // If the clock is PAUSED, run it now and sync time as well.
0136             if (KStarsData::Instance()->clock()->isActive() == false)
0137             {
0138                 qCInfo(KSTARS_EKOS) << "Resuming and syncing clock.";
0139                 KStarsData::Instance()->clock()->start();
0140                 QAction *a = KStars::Instance()->actionCollection()->action("time_to_now");
0141                 if (a)
0142                     a->trigger();
0143             }
0144         }
0145         // Otherwise, if KStars was started in PAUSED state
0146         // then we pause here as well to save power.
0147         else
0148         {
0149             qCInfo(KSTARS_EKOS) << "EkosLive client is disconnected.";
0150             // It was started with paused state, so let's pause IF Ekos is not running
0151             if (KStars::Instance()->isStartedWithClockRunning() == false && m_Manager->ekosStatus() == Ekos::CommunicationStatus::Idle)
0152             {
0153                 qCInfo(KSTARS_EKOS) << "Stopping the clock.";
0154                 KStarsData::Instance()->clock()->stop();
0155             }
0156         }
0157     }
0158     else if (command == commands[GET_DRIVERS])
0159         sendDrivers();
0160     else if (command == commands[GET_PROFILES])
0161         sendProfiles();
0162     else if (command == commands[GET_SCOPES])
0163         sendScopes();
0164     else if (command == commands[GET_DSLR_LENSES])
0165         sendDSLRLenses();
0166     else if(command == commands[INVOKE_METHOD])
0167     {
0168         auto object = findObject(payload["object"].toString());
0169         if (object)
0170             invokeMethod(object, payload);
0171     }
0172     else if(command == commands[SET_PROPERTY])
0173     {
0174         auto object = findObject(payload["object"].toString());
0175         if (object)
0176             object->setProperty(payload["name"].toString().toLatin1().constData(), payload["value"].toVariant());
0177     }
0178     else if(command == commands[GET_PROPERTY])
0179     {
0180         auto map = QVariantMap();
0181         map["result"] = false;
0182         auto object = findObject(payload["object"].toString());
0183         if (object)
0184         {
0185             auto value = object->property(payload["name"].toString().toLatin1().constData());
0186             if (value.isValid())
0187             {
0188                 map["result"] = true;
0189                 map["value"] = value;
0190             }
0191         }
0192         sendResponse(commands[GET_PROPERTY], QJsonObject::fromVariantMap(map));
0193     }
0194     else if (command.startsWith("scope_"))
0195         processScopeCommands(command, payload);
0196     else if (command.startsWith("profile_"))
0197         processProfileCommands(command, payload);
0198     else if (command.startsWith("astro_"))
0199         processAstronomyCommands(command, payload);
0200     else if (command == commands[DIALOG_GET_RESPONSE])
0201         processDialogResponse(payload);
0202     else if (command.startsWith("option_"))
0203         processOptionsCommands(command, payload);
0204     else if (command.startsWith("scheduler"))
0205         processSchedulerCommands(command, payload);
0206     else if (command.startsWith("dslr_"))
0207         processDSLRCommands(command, payload);
0208 
0209     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0210         return;
0211 
0212     if (command == commands[GET_STATES])
0213         sendStates();
0214     else if (command == commands[GET_STELLARSOLVER_PROFILES])
0215         sendStellarSolverProfiles();
0216     else if (command == commands[GET_DEVICES])
0217         sendDevices();
0218     else if (command.startsWith("capture_"))
0219         processCaptureCommands(command, payload);
0220     else if (command.startsWith("mount_"))
0221         processMountCommands(command, payload);
0222     else if (command.startsWith("focus_"))
0223         processFocusCommands(command, payload);
0224     else if (command.startsWith("guide_"))
0225         processGuideCommands(command, payload);
0226     else if (command.startsWith("align_"))
0227         processAlignCommands(command, payload);
0228     else if (command.startsWith("polar_"))
0229         processPolarCommands(command, payload);
0230     else if (command.startsWith("train_"))
0231         processTrainCommands(command, payload);
0232     else if (command.startsWith("fm_"))
0233         processFilterManagerCommands(command, payload);
0234     else if (command.startsWith("dark_library_"))
0235         processDarkLibraryCommands(command, payload);
0236     else if (command.startsWith("device_"))
0237         processDeviceCommands(command, payload);
0238 
0239 }
0240 
0241 ///////////////////////////////////////////////////////////////////////////////////////////
0242 ///
0243 ///////////////////////////////////////////////////////////////////////////////////////////
0244 bool Message::isConnected() const
0245 {
0246     return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
0247     {
0248         return nodeManager->message()->isConnected();
0249     });
0250 }
0251 
0252 ///////////////////////////////////////////////////////////////////////////////////////////
0253 ///
0254 ///////////////////////////////////////////////////////////////////////////////////////////
0255 void Message::sendStellarSolverProfiles()
0256 {
0257     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0258         return;
0259 
0260     QJsonObject profiles;
0261 
0262     if (m_Manager->focusModule())
0263         profiles.insert("focus", QJsonArray::fromStringList(m_Manager->focusModule()->getStellarSolverProfiles()));
0264     // TODO
0265     //    if (m_Manager->guideModule())
0266     //        profiles.insert("guide", QJsonArray::fromStringList(m_Manager->guideModule()->getStellarSolverProfiles()));
0267     if (m_Manager->alignModule())
0268         profiles.insert("align", QJsonArray::fromStringList(m_Manager->alignModule()->getStellarSolverProfiles()));
0269 
0270 
0271     sendResponse(commands[GET_STELLARSOLVER_PROFILES], profiles);
0272 }
0273 
0274 ///////////////////////////////////////////////////////////////////////////////////////////
0275 ///
0276 ///////////////////////////////////////////////////////////////////////////////////////////
0277 void Message::sendDrivers()
0278 {
0279     sendResponse(commands[GET_DRIVERS], DriverManager::Instance()->getDriverList());
0280 }
0281 
0282 ///////////////////////////////////////////////////////////////////////////////////////////
0283 ///
0284 ///////////////////////////////////////////////////////////////////////////////////////////
0285 void Message::sendDevices()
0286 {
0287     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0288         return;
0289 
0290     QJsonArray deviceList;
0291 
0292     for(auto &gd : INDIListener::devices())
0293     {
0294         QJsonObject oneDevice =
0295         {
0296             {"name", gd->getDeviceName()},
0297             {"connected", gd->isConnected()},
0298             {"version", gd->getDriverVersion()},
0299             {"interface", static_cast<int>(gd->getDriverInterface())},
0300         };
0301 
0302         deviceList.append(oneDevice);
0303     }
0304 
0305     sendResponse(commands[GET_DEVICES], deviceList);
0306 }
0307 
0308 ///////////////////////////////////////////////////////////////////////////////////////////
0309 ///
0310 ///////////////////////////////////////////////////////////////////////////////////////////
0311 void Message::sendTrains()
0312 {
0313     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0314         return;
0315 
0316     QJsonArray trains;
0317 
0318     for(auto &train : Ekos::OpticalTrainManager::Instance()->getOpticalTrains())
0319         trains.append(QJsonObject::fromVariantMap(train));
0320 
0321     sendResponse(commands[TRAIN_GET_ALL], trains);
0322 }
0323 
0324 ///////////////////////////////////////////////////////////////////////////////////////////
0325 ///
0326 ///////////////////////////////////////////////////////////////////////////////////////////
0327 void Message::sendTrainProfiles()
0328 {
0329     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0330         return;
0331 
0332     auto profiles = Ekos::ProfileSettings::Instance()->getSettings();
0333 
0334     sendResponse(commands[TRAIN_GET_PROFILES], QJsonObject::fromVariantMap(profiles));
0335 }
0336 
0337 ///////////////////////////////////////////////////////////////////////////////////////////
0338 ///
0339 ///////////////////////////////////////////////////////////////////////////////////////////
0340 void Message::requestOpticalTrains(bool show)
0341 {
0342     sendResponse(commands[TRAIN_CONFIGURATION_REQUESTED], show);
0343 }
0344 
0345 ///////////////////////////////////////////////////////////////////////////////////////////
0346 ///
0347 ///////////////////////////////////////////////////////////////////////////////////////////
0348 void Message::sendScopes()
0349 {
0350     QJsonArray scopeList;
0351 
0352     QList<OAL::Scope *> allScopes;
0353     KStarsData::Instance()->userdb()->GetAllScopes(allScopes);
0354 
0355     for (auto &scope : allScopes)
0356         scopeList.append(scope->toJson());
0357 
0358     sendResponse(commands[GET_SCOPES], scopeList);
0359 }
0360 
0361 ///////////////////////////////////////////////////////////////////////////////////////////
0362 ///
0363 ///////////////////////////////////////////////////////////////////////////////////////////
0364 void Message::sendDSLRLenses()
0365 {
0366     QJsonArray dslrList;
0367 
0368     QList<OAL::DSLRLens *> allDslrLens;
0369     KStarsData::Instance()->userdb()->GetAllDSLRLenses(allDslrLens);
0370 
0371     for (auto &dslrLens : allDslrLens)
0372         dslrList.append(dslrLens->toJson());
0373 
0374     sendResponse(commands[GET_DSLR_LENSES], dslrList);
0375 }
0376 
0377 ///////////////////////////////////////////////////////////////////////////////////////////
0378 ///
0379 ///////////////////////////////////////////////////////////////////////////////////////////
0380 void Message::sendTemperature(double value)
0381 {
0382     ISD::Camera *oneCCD = dynamic_cast<ISD::Camera*>(sender());
0383 
0384     if (oneCCD)
0385     {
0386         QJsonObject temperature =
0387         {
0388             {"name", oneCCD->getDeviceName()},
0389             {"temperature", value}
0390         };
0391 
0392         sendResponse(commands[NEW_CAMERA_STATE], temperature);
0393     }
0394 }
0395 
0396 ///////////////////////////////////////////////////////////////////////////////////////////
0397 ///
0398 ///////////////////////////////////////////////////////////////////////////////////////////
0399 void Message::setCapturePresetSettings(const QJsonObject &settings)
0400 {
0401     m_Manager->captureModule()->setPresetSettings(settings);
0402 }
0403 
0404 ///////////////////////////////////////////////////////////////////////////////////////////
0405 ///
0406 ///////////////////////////////////////////////////////////////////////////////////////////
0407 void Message::processCaptureCommands(const QString &command, const QJsonObject &payload)
0408 {
0409     Ekos::Capture *capture = m_Manager->captureModule();
0410 
0411     if (capture == nullptr)
0412     {
0413         qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as capture module is not available";
0414         return;
0415     }
0416 
0417     if (command == commands[CAPTURE_PREVIEW])
0418     {
0419         setCapturePresetSettings(payload);
0420         capture->capturePreview();
0421     }
0422     else if (command == commands[CAPTURE_TOGGLE_VIDEO])
0423     {
0424         capture->setVideoLimits(payload["maxBufferSize"].toInt(512), payload["maxPreviewFPS"].toInt(10));
0425         capture->toggleVideo(payload["enabled"].toBool());
0426     }
0427     else if (command == commands[CAPTURE_START])
0428         capture->start();
0429     else if (command == commands[CAPTURE_STOP])
0430         capture->stop();
0431     else if (command == commands[CAPTURE_LOOP])
0432     {
0433         setCapturePresetSettings(payload);
0434         capture->startFraming();
0435     }
0436     else if (command == commands[CAPTURE_GET_SEQUENCES])
0437     {
0438         sendCaptureSequence(capture->getSequence());
0439     }
0440     else if(command == commands[CAPTURE_SET_FILE_SETTINGS])
0441     {
0442         m_Manager->captureModule()->setFileSettings(payload);
0443     }
0444     else if (command == commands[CAPTURE_ADD_SEQUENCE])
0445     {
0446         // Set capture settings first
0447         setCapturePresetSettings(payload["preset"].toObject());
0448 
0449         // Then sequence settings
0450         capture->setCount(static_cast<uint16_t>(payload["count"].toInt()));
0451         capture->setDelay(static_cast<uint16_t>(payload["delay"].toInt()));
0452 
0453         // File Settings
0454         m_Manager->captureModule()->setFileSettings(payload["file"].toObject());
0455 
0456         // Calibration Settings
0457         m_Manager->captureModule()->setCalibrationSettings(payload["calibration"].toObject());
0458 
0459         // Now add job
0460         capture->createJob();
0461     }
0462     else if (command == commands[CAPTURE_REMOVE_SEQUENCE])
0463     {
0464         if (capture->removeJob(payload["index"].toInt()) == false)
0465             sendCaptureSequence(capture->getSequence());
0466     }
0467     else if (command == commands[CAPTURE_CLEAR_SEQUENCES])
0468     {
0469         capture->clearSequenceQueue();
0470     }
0471     else if (command == commands[CAPTURE_SET_LIMITS])
0472     {
0473         capture->setLimitSettings(payload);
0474     }
0475     else if (command == commands[CAPTURE_GET_LIMITS])
0476     {
0477         sendResponse(commands[CAPTURE_GET_LIMITS], capture->getLimitSettings());
0478     }
0479     else if (command == commands[CAPTURE_SAVE_SEQUENCE_FILE])
0480     {
0481         capture->saveSequenceQueue(payload["filepath"].toString());
0482     }
0483     else if (command == commands[CAPTURE_LOAD_SEQUENCE_FILE])
0484     {
0485         capture->loadSequenceQueue(payload["filepath"].toString());
0486     }
0487     else if (command == commands[CAPTURE_GET_CALIBRATION_SETTINGS])
0488     {
0489         sendResponse(commands[CAPTURE_GET_CALIBRATION_SETTINGS], capture->getCalibrationSettings());
0490     }
0491     else if (command == commands[CAPTURE_GET_FILE_SETTINGS])
0492     {
0493         sendResponse(commands[CAPTURE_GET_FILE_SETTINGS], capture->getFileSettings());
0494     }
0495     else if (command == commands[CAPTURE_GENERATE_DARK_FLATS])
0496     {
0497         capture->generateDarkFlats();
0498     }
0499 }
0500 
0501 ///////////////////////////////////////////////////////////////////////////////////////////
0502 ///
0503 ///////////////////////////////////////////////////////////////////////////////////////////
0504 void Message::sendCaptureSequence(const QJsonArray &sequenceArray)
0505 {
0506     sendResponse(commands[CAPTURE_GET_SEQUENCES], sequenceArray);
0507 }
0508 
0509 void Message::sendPreviewLabel(const QString &preview)
0510 {
0511     const QJsonObject payload =
0512     {
0513         {"preview", preview}
0514     };
0515     sendResponse(commands[CAPTURE_GET_PREVIEW_LABEL], payload);
0516 }
0517 
0518 ///////////////////////////////////////////////////////////////////////////////////////////
0519 ///
0520 ///////////////////////////////////////////////////////////////////////////////////////////
0521 void Message::sendCaptureSettings(const QJsonObject &settings)
0522 {
0523     sendResponse(commands[CAPTURE_SET_SETTINGS], settings);
0524 }
0525 
0526 ///////////////////////////////////////////////////////////////////////////////////////////
0527 ///
0528 ///////////////////////////////////////////////////////////////////////////////////////////
0529 void Message::sendAlignSettings(const QVariantMap &settings)
0530 {
0531     m_DebouncedSend.start();
0532     m_DebouncedMap[commands[ALIGN_GET_ALL_SETTINGS]] = settings;
0533 }
0534 
0535 ///////////////////////////////////////////////////////////////////////////////////////////
0536 ///
0537 ///////////////////////////////////////////////////////////////////////////////////////////
0538 void Message::sendGuideSettings(const QVariantMap &settings)
0539 {
0540     m_DebouncedSend.start();
0541     m_DebouncedMap[commands[GUIDE_GET_ALL_SETTINGS]] = settings;
0542 
0543 }
0544 
0545 ///////////////////////////////////////////////////////////////////////////////////////////
0546 ///
0547 ///////////////////////////////////////////////////////////////////////////////////////////
0548 void Message::sendFocusSettings(const QVariantMap &settings)
0549 {
0550     m_DebouncedSend.start();
0551     m_DebouncedMap[commands[FOCUS_GET_ALL_SETTINGS]] = settings;
0552 }
0553 
0554 ///////////////////////////////////////////////////////////////////////////////////////////
0555 ///
0556 ///////////////////////////////////////////////////////////////////////////////////////////
0557 void Message::sendMountSettings(const QVariantMap &settings)
0558 {
0559     m_DebouncedSend.start();
0560     m_DebouncedMap[commands[MOUNT_GET_ALL_SETTINGS]] = settings;
0561 }
0562 
0563 ///////////////////////////////////////////////////////////////////////////////////////////
0564 ///
0565 ///////////////////////////////////////////////////////////////////////////////////////////
0566 void Message::sendDarkLibrarySettings(const QVariantMap &settings)
0567 {
0568     m_DebouncedSend.start();
0569     m_DebouncedMap[commands[DARK_LIBRARY_GET_ALL_SETTINGS]] = settings;
0570 }
0571 
0572 
0573 ///////////////////////////////////////////////////////////////////////////////////////////
0574 ///
0575 ///////////////////////////////////////////////////////////////////////////////////////////
0576 void Message::sendSchedulerSettings(const QVariantMap &settings)
0577 {
0578     m_DebouncedSend.start();
0579     m_DebouncedMap[commands[SCHEDULER_GET_ALL_SETTINGS]] = settings;
0580 }
0581 
0582 ///////////////////////////////////////////////////////////////////////////////////////////
0583 ///
0584 ///////////////////////////////////////////////////////////////////////////////////////////
0585 void Message::dispatchDebounceQueue()
0586 {
0587     QMapIterator<QString, QVariantMap> i(m_DebouncedMap);
0588     while (i.hasNext())
0589     {
0590         i.next();
0591         sendResponse(i.key(), QJsonObject::fromVariantMap(i.value()));
0592     }
0593     m_DebouncedMap.clear();
0594 
0595     // Save to disk
0596     Options::self()->save();
0597 }
0598 
0599 ///////////////////////////////////////////////////////////////////////////////////////////
0600 ///
0601 ///////////////////////////////////////////////////////////////////////////////////////////
0602 void Message::processGuideCommands(const QString &command, const QJsonObject &payload)
0603 {
0604     Ekos::Guide *guide = m_Manager->guideModule();
0605 
0606     if (guide == nullptr)
0607     {
0608         qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as guide module is not available";
0609         return;
0610     }
0611 
0612     if (command == commands[GUIDE_START])
0613     {
0614         guide->guide();
0615     }
0616     else if (command == commands[GUIDE_CAPTURE])
0617         guide->capture();
0618     else if (command == commands[GUIDE_LOOP])
0619         guide->loop();
0620     else if (command == commands[GUIDE_STOP])
0621         guide->abort();
0622     else if (command == commands[GUIDE_CLEAR])
0623         guide->clearCalibration();
0624     else if (command == commands[GUIDE_SET_ALL_SETTINGS])
0625     {
0626         auto settings = payload.toVariantMap();
0627         guide->setAllSettings(settings);
0628         KSUtils::setGlobalSettings(settings);
0629     }
0630     else if (command == commands[GUIDE_GET_ALL_SETTINGS])
0631         sendGuideSettings(guide->getAllSettings());
0632     else if(command == commands[GUIDE_SET_CALIBRATION_SETTINGS])
0633     {
0634 
0635         Options::setCalibrationPulseDuration(payload["pulse"].toInt());
0636         Options::setGuideCalibrationBacklash(payload["max_move"].toInt());
0637         Options::setTwoAxisEnabled(payload["two_axis"].toBool());
0638         Options::setGuideAutoSquareSizeEnabled(payload["square_size"].toBool());
0639         Options::setGuideCalibrationBacklash(payload["calibrationBacklash"].toBool());
0640         Options::setResetGuideCalibration(payload["resetCalibration"].toBool());
0641         Options::setReuseGuideCalibration(payload["reuseCalibration"].toBool());
0642         Options::setReverseDecOnPierSideChange(payload["reverseCalibration"].toBool());
0643         sendGuideSettings(m_Manager->guideModule()->getAllSettings());
0644     }
0645 }
0646 
0647 ///////////////////////////////////////////////////////////////////////////////////////////
0648 ///
0649 ///////////////////////////////////////////////////////////////////////////////////////////
0650 void Message::processFocusCommands(const QString &command, const QJsonObject &payload)
0651 {
0652     Ekos::Focus *focus = m_Manager->focusModule();
0653 
0654     if (focus == nullptr)
0655     {
0656         qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as focus module is not available";
0657         return;
0658     }
0659 
0660     if (command == commands[FOCUS_START])
0661         focus->start();
0662     else if (command == commands[FOCUS_CAPTURE])
0663     {
0664         focus->resetFrame();
0665         focus->capture();
0666     }
0667     else if (command == commands[FOCUS_STOP])
0668         focus->abort();
0669     else if (command == commands[FOCUS_RESET])
0670         focus->resetFrame();
0671     else if (command == commands[FOCUS_IN])
0672         focus->focusIn(payload["steps"].toInt());
0673     else if (command == commands[FOCUS_OUT])
0674         focus->focusOut(payload["steps"].toInt());
0675     else if (command == commands[FOCUS_LOOP])
0676         focus->startFraming();
0677     else if (command == commands[FOCUS_SET_ALL_SETTINGS])
0678     {
0679         auto settings = payload.toVariantMap();
0680         focus->setAllSettings(settings);
0681         KSUtils::setGlobalSettings(settings);
0682     }
0683 
0684     else if (command == commands[FOCUS_GET_ALL_SETTINGS])
0685         sendFocusSettings(focus->getAllSettings());
0686     else if (command == commands[FOCUS_SET_CROSSHAIR])
0687     {
0688         double x = payload["x"].toDouble();
0689         double y = payload["y"].toDouble();
0690         focus->selectFocusStarFraction(x, y);
0691     }
0692 }
0693 
0694 ///////////////////////////////////////////////////////////////////////////////////////////
0695 ///
0696 ///////////////////////////////////////////////////////////////////////////////////////////
0697 void Message::processMountCommands(const QString &command, const QJsonObject &payload)
0698 {
0699     Ekos::Mount *mount = m_Manager->mountModule();
0700 
0701     if (mount == nullptr)
0702     {
0703         qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as mount module is not available";
0704         return;
0705     }
0706 
0707     if (command == commands[MOUNT_ABORT])
0708         mount->abort();
0709     else if (command == commands[MOUNT_PARK])
0710         mount->park();
0711     else if (command == commands[MOUNT_UNPARK])
0712         mount->unpark();
0713     else if (command == commands[MOUNT_SET_TRACKING])
0714         mount->setTrackEnabled(payload["enabled"].toBool());
0715     else if (command == commands[MOUNT_SYNC_RADE])
0716     {
0717         mount->setJ2000Enabled(payload["isJ2000"].toBool());
0718         mount->sync(payload["ra"].toString(), payload["de"].toString());
0719     }
0720     else if (command == commands[MOUNT_SYNC_TARGET])
0721     {
0722         mount->syncTarget(payload["target"].toString());
0723     }
0724     else if (command == commands[MOUNT_GOTO_RADE])
0725     {
0726         mount->setJ2000Enabled(payload["isJ2000"].toBool());
0727         mount->slew(payload["ra"].toString(), payload["de"].toString());
0728     }
0729     else if (command == commands[MOUNT_GOTO_TARGET])
0730     {
0731         mount->gotoTarget(payload["target"].toString());
0732     }
0733     else if (command == commands[MOUNT_SET_SLEW_RATE])
0734     {
0735         int rate = payload["rate"].toInt(-1);
0736         if (rate >= 0)
0737             mount->setSlewRate(rate);
0738     }
0739     else if (command == commands[MOUNT_SET_ALL_SETTINGS])
0740     {
0741         auto settings = payload.toVariantMap();
0742         mount->setAllSettings(settings);
0743         KSUtils::setGlobalSettings(settings);
0744     }
0745     else if (command == commands[MOUNT_GET_ALL_SETTINGS])
0746         sendMountSettings(mount->getAllSettings());
0747     else if (command == commands[MOUNT_SET_MOTION])
0748     {
0749         QString direction = payload["direction"].toString();
0750         ISD::Mount::MotionCommand action = payload["action"].toBool(false) ?
0751                                            ISD::Mount::MOTION_START : ISD::Mount::MOTION_STOP;
0752 
0753         if (direction == "N")
0754             mount->motionCommand(action, ISD::Mount::MOTION_NORTH, -1);
0755         else if (direction == "S")
0756             mount->motionCommand(action, ISD::Mount::MOTION_SOUTH, -1);
0757         else if (direction == "E")
0758             mount->motionCommand(action, -1, ISD::Mount::MOTION_EAST);
0759         else if (direction == "W")
0760             mount->motionCommand(action, -1, ISD::Mount::MOTION_WEST);
0761     }
0762     else if (command == commands[MOUNT_GOTO_PIXEL])
0763     {
0764         const auto name = payload["camera"].toString();
0765         const auto xFactor = payload["x"].toDouble();
0766         const auto yFactor = payload["y"].toDouble();
0767 
0768         for(auto &oneDevice : INDIListener::devices())
0769         {
0770             auto camera = oneDevice->getCamera();
0771             if (!camera  || camera->getDeviceName() != name)
0772                 continue;
0773 
0774             auto primaryChip = camera->getChip(ISD::CameraChip::PRIMARY_CCD);
0775 
0776             if (!primaryChip)
0777                 break;
0778 
0779             auto imageData = primaryChip->getImageData();
0780             if (!imageData || imageData->hasWCS() == false)
0781                 break;
0782 
0783             auto x = xFactor * imageData->width();
0784             auto y = yFactor * imageData->height();
0785 
0786             QPointF point(x, y);
0787             SkyPoint coord;
0788             if (imageData->pixelToWCS(point, coord))
0789             {
0790                 // J2000 -> JNow
0791                 coord.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
0792                 mount->gotoTarget(coord);
0793                 break;
0794             }
0795         }
0796     }
0797     else if (command == commands[MOUNT_TOGGLE_AUTOPARK])
0798         mount->setAutoParkEnabled(payload["toggled"].toBool());
0799 }
0800 
0801 ///////////////////////////////////////////////////////////////////////////////////////////
0802 ///
0803 ///////////////////////////////////////////////////////////////////////////////////////////
0804 void Message::processAlignCommands(const QString &command, const QJsonObject &payload)
0805 {
0806     Ekos::Align *align = m_Manager->alignModule();
0807 
0808     if (align == nullptr)
0809     {
0810         qCWarning(KSTARS_EKOS) << "Ignoring command" << command << "as align module is not available";
0811         return;
0812     }
0813 
0814     if (command == commands[ALIGN_SOLVE])
0815     {
0816         align->captureAndSolve();
0817     }
0818     else if (command == commands[ALIGN_SET_ALL_SETTINGS])
0819     {
0820         auto settings = payload.toVariantMap();
0821         align->setAllSettings(settings);
0822         KSUtils::setGlobalSettings(settings);
0823     }
0824     else if (command == commands[ALIGN_GET_ALL_SETTINGS])
0825         sendAlignSettings(align->getAllSettings());
0826     else if(command == commands[ALIGN_SET_ASTROMETRY_SETTINGS])
0827     {
0828         Options::setAstrometryRotatorThreshold(payload["threshold"].toInt());
0829         Options::setAstrometryUseRotator(payload["rotator_control"].toBool());
0830         Options::setAstrometryUseImageScale(payload["scale"].toBool());
0831         Options::setAstrometryUsePosition(payload["position"].toBool());
0832     }
0833     else if (command == commands[ALIGN_STOP])
0834         align->abort();
0835     else if (command == commands[ALIGN_LOAD_AND_SLEW])
0836     {
0837         // Check if we have filename payload first
0838         if (payload.contains("filename"))
0839         {
0840             align->loadAndSlew(payload["filename"].toString());
0841         }
0842         else
0843         {
0844             QString filename = QDir::tempPath() + QDir::separator() +
0845                                QString("XXXXXXloadslew.%1").arg(payload["ext"].toString("fits"));
0846             QTemporaryFile file(filename);
0847             file.setAutoRemove(false);
0848             file.open();
0849             file.write(QByteArray::fromBase64(payload["data"].toString().toLatin1()));
0850             file.close();
0851             align->loadAndSlew(file.fileName());
0852         }
0853     }
0854     else if (command == commands[ALIGN_MANUAL_ROTATOR_TOGGLE])
0855     {
0856         align->toggleManualRotator(payload["toggled"].toBool());
0857     }
0858 }
0859 
0860 ///////////////////////////////////////////////////////////////////////////////////////////
0861 ///
0862 ///////////////////////////////////////////////////////////////////////////////////////////
0863 void Message::setAlignStatus(Ekos::AlignState newState)
0864 {
0865     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0866         return;
0867 
0868     QJsonObject alignState =
0869     {
0870         {"status", Ekos::alignStates[newState]}
0871     };
0872 
0873     sendResponse(commands[NEW_ALIGN_STATE], alignState);
0874 }
0875 
0876 ///////////////////////////////////////////////////////////////////////////////////////////
0877 ///
0878 ///////////////////////////////////////////////////////////////////////////////////////////
0879 void Message::setAlignSolution(const QVariantMap &solution)
0880 {
0881     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
0882         return;
0883 
0884     QJsonObject alignState =
0885     {
0886         {"solution", QJsonObject::fromVariantMap(solution)},
0887     };
0888 
0889     sendResponse(commands[NEW_ALIGN_STATE], alignState);
0890 }
0891 
0892 ///////////////////////////////////////////////////////////////////////////////////////////
0893 ///
0894 ///////////////////////////////////////////////////////////////////////////////////////////
0895 void Message::processSchedulerCommands(const QString &command, const QJsonObject &payload)
0896 {
0897     Ekos::Scheduler *scheduler = m_Manager->schedulerModule();
0898 
0899     if (command == commands[SCHEDULER_GET_JOBS])
0900     {
0901         sendSchedulerJobs();
0902     }
0903     else if (command == commands[SCHEDULER_ADD_JOBS])
0904     {
0905         scheduler->addJob();
0906     }
0907     else if(command == commands[SCHEDULER_REMOVE_JOBS])
0908     {
0909         int index = payload["index"].toInt();
0910         scheduler->removeOneJob(index);
0911     }
0912     else if(command == commands[SCHEDULER_GET_ALL_SETTINGS])
0913     {
0914         sendSchedulerSettings(scheduler->getAllSettings());
0915     }
0916     else if(command == commands[SCHEDULER_SET_ALL_SETTINGS])
0917     {
0918         auto settings = payload.toVariantMap();
0919         scheduler->setAllSettings(settings);
0920         KSUtils::setGlobalSettings(settings);
0921     }
0922     else if(command == commands[SCHEDULER_START_JOB])
0923     {
0924         scheduler->toggleScheduler();
0925     }
0926     else if(command == commands[SCHEDULER_IMPORT_MOSAIC])
0927     {
0928         if (scheduler->importMosaic(payload))
0929             sendSchedulerJobs();
0930         else
0931             sendEvent(i18n("Mosaic import failed."), KSNotification::Scheduler, KSNotification::Alert);
0932     }
0933 }
0934 
0935 ///////////////////////////////////////////////////////////////////////////////////////////
0936 ///
0937 ///////////////////////////////////////////////////////////////////////////////////////////
0938 void Message::processPolarCommands(const QString &command, const QJsonObject &payload)
0939 {
0940     Ekos::Align *align = m_Manager->alignModule();
0941     Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
0942 
0943     if (!paa)
0944         return;
0945 
0946     if (command == commands[PAH_START])
0947     {
0948         paa->startPAHProcess();
0949     }
0950     if (command == commands[PAH_STOP])
0951     {
0952         paa->stopPAHProcess();
0953     }
0954     else if (command == commands[PAH_REFRESH])
0955     {
0956         paa->setPAHRefreshDuration(payload["value"].toDouble(1));
0957         paa->startPAHRefreshProcess();
0958     }
0959     else if (command == commands[PAH_SET_ALGORITHM])
0960     {
0961         auto algorithmCombo = paa->findChild<QComboBox*>("PAHRefreshAlgorithmCombo");
0962         if (algorithmCombo)
0963             algorithmCombo->setCurrentIndex(static_cast<Ekos::PolarAlignmentAssistant::RefreshAlgorithm>(payload["value"].toInt(1)));
0964     }
0965     else if (command == commands[PAH_RESET_VIEW])
0966     {
0967         emit resetPolarView();
0968     }
0969     else if (command == commands[PAH_SET_CROSSHAIR])
0970     {
0971         double x = payload["x"].toDouble();
0972         double y = payload["y"].toDouble();
0973 
0974         if (m_BoundingRect.isNull() == false)
0975         {
0976             // #1 Find actual dimension inside the bounding rectangle
0977             // since if we have bounding rectable then x,y fractions are INSIDE it
0978             double boundX = x * m_BoundingRect.width();
0979             double boundY = y * m_BoundingRect.height();
0980 
0981             // #2 Find fraction of the dimensions above the full image size
0982             // Add to it the bounding rect top left offsets
0983             // factors in the change caused by zoom
0984             x = ((boundX + m_BoundingRect.x()) / (m_CurrentZoom / 100)) / m_ViewSize.width();
0985             y = ((boundY + m_BoundingRect.y()) / (m_CurrentZoom / 100)) / m_ViewSize.height();
0986 
0987         }
0988 
0989         paa->setPAHCorrectionOffsetPercentage(x, y);
0990     }
0991     else if (command == commands[PAH_SELECT_STAR_DONE])
0992     {
0993         // This button was removed from the desktop PAA scheme.
0994         // Nothing to do.
0995         // TODO: Make sure this works.
0996     }
0997     else if (command == commands[PAH_REFRESHING_DONE])
0998     {
0999         paa->stopPAHProcess();
1000     }
1001     else if (command == commands[PAH_SLEW_DONE])
1002     {
1003         paa->setPAHSlewDone();
1004     }
1005     else if (command == commands[PAH_PAH_SET_ZOOM])
1006     {
1007         double scale = payload["scale"].toDouble();
1008         align->setAlignZoom(scale);
1009     }
1010 
1011 }
1012 
1013 ///////////////////////////////////////////////////////////////////////////////////////////
1014 ///
1015 ///////////////////////////////////////////////////////////////////////////////////////////
1016 void Message::setPAHStage(Ekos::PolarAlignmentAssistant::Stage stage)
1017 {
1018     if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1019         return;
1020 
1021     Q_UNUSED(stage)
1022     Ekos::Align *align = m_Manager->alignModule();
1023 
1024     Ekos::PolarAlignmentAssistant *paa = align->polarAlignmentAssistant();
1025 
1026     if (!paa)
1027         return;
1028 
1029     QJsonObject polarState =
1030     {
1031         {"stage", paa->getPAHStageString(false)}
1032     };
1033 
1034 
1035     // Increase size when select star
1036     if (stage == Ekos::PolarAlignmentAssistant::PAH_STAR_SELECT)
1037         align->zoomAlignView();
1038 
1039     sendResponse(commands[NEW_POLAR_STATE], polarState);
1040 }
1041 
1042 ///////////////////////////////////////////////////////////////////////////////////////////
1043 ///
1044 ///////////////////////////////////////////////////////////////////////////////////////////
1045 void Message::setPAHMessage(const QString &message)
1046 {
1047     if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1048         return;
1049 
1050     QTextDocument doc;
1051     doc.setHtml(message);
1052     QJsonObject polarState =
1053     {
1054         {"message", doc.toPlainText()}
1055     };
1056 
1057     sendResponse(commands[NEW_POLAR_STATE], polarState);
1058 }
1059 
1060 ///////////////////////////////////////////////////////////////////////////////////////////
1061 ///
1062 ///////////////////////////////////////////////////////////////////////////////////////////
1063 void Message::setPolarResults(QLineF correctionVector, double polarError, double azError, double altError)
1064 {
1065     if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1066         return;
1067 
1068     this->correctionVector = correctionVector;
1069 
1070     QPointF center = 0.5 * correctionVector.p1() + 0.5 * correctionVector.p2();
1071     QJsonObject vector =
1072     {
1073         {"center_x", center.x()},
1074         {"center_y", center.y()},
1075         {"mag", correctionVector.length()},
1076         {"pa", correctionVector.angle()},
1077         {"error", polarError},
1078         {"azError", azError},
1079         {"altError", altError}
1080     };
1081 
1082     QJsonObject polarState =
1083     {
1084         {"vector", vector}
1085     };
1086 
1087     sendResponse(commands[NEW_POLAR_STATE], polarState);
1088 }
1089 
1090 ///////////////////////////////////////////////////////////////////////////////////////////
1091 ///
1092 ///////////////////////////////////////////////////////////////////////////////////////////
1093 void Message::setUpdatedErrors(double total, double az, double alt)
1094 {
1095     if (isConnected() == false || m_Manager->getEkosStartingStatus() != Ekos::Success)
1096         return;
1097 
1098     QJsonObject error =
1099     {
1100         {"updatedError", total},
1101         {"updatedAZError", az},
1102         {"updatedALTError", alt}
1103     };
1104 
1105     sendResponse(commands[NEW_POLAR_STATE], error);
1106 }
1107 
1108 ///////////////////////////////////////////////////////////////////////////////////////////
1109 ///
1110 ///////////////////////////////////////////////////////////////////////////////////////////
1111 void Message::setPAHEnabled(bool enabled)
1112 {
1113     if (m_Manager->getEkosStartingStatus() != Ekos::Success)
1114         return;
1115 
1116     QJsonObject polarState =
1117     {
1118         {"enabled", enabled}
1119     };
1120 
1121     sendResponse(commands[NEW_POLAR_STATE], polarState);
1122 }
1123 
1124 ///////////////////////////////////////////////////////////////////////////////////////////
1125 ///
1126 ///////////////////////////////////////////////////////////////////////////////////////////
1127 void Message::processProfileCommands(const QString &command, const QJsonObject &payload)
1128 {
1129     if (command == commands[START_PROFILE])
1130     {
1131         if (m_Manager->getEkosStartingStatus() != Ekos::Idle)
1132             m_Manager->stop();
1133 
1134         m_Manager->setProfile(payload["name"].toString());
1135         // Always Sync time before we start
1136         KStarsData::Instance()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
1137         m_Manager->start();
1138     }
1139     else if (command == commands[STOP_PROFILE])
1140     {
1141         m_Manager->stop();
1142 
1143         // Close all FITS Viewers
1144         KStars::Instance()->clearAllViewers();
1145 
1146         m_PropertySubscriptions.clear();
1147     }
1148     else if (command == commands[ADD_PROFILE])
1149     {
1150         m_Manager->addNamedProfile(payload);
1151         sendProfiles();
1152     }
1153     else if (command == commands[UPDATE_PROFILE])
1154     {
1155         m_Manager->editNamedProfile(payload);
1156         sendProfiles();
1157     }
1158     else if (command == commands[GET_PROFILE])
1159     {
1160         m_Manager->getNamedProfile(payload["name"].toString());
1161     }
1162     else if (command == commands[DELETE_PROFILE])
1163     {
1164         m_Manager->deleteNamedProfile(payload["name"].toString());
1165         sendProfiles();
1166     }
1167     else if (command == commands[SET_PROFILE_MAPPING])
1168     {
1169         m_Manager->setProfileMapping(payload);
1170     }
1171     else if (command == commands[SET_PROFILE_PORT_SELECTION])
1172     {
1173         requestPortSelection(false);
1174         m_Manager->acceptPortSelection();
1175     }
1176 }
1177 
1178 ///////////////////////////////////////////////////////////////////////////////////////////
1179 ///
1180 ///////////////////////////////////////////////////////////////////////////////////////////
1181 void Message::sendProfiles()
1182 {
1183     QJsonArray profileArray;
1184 
1185     QSharedPointer<ProfileInfo> profile;
1186     if (!m_Manager->getCurrentProfile(profile))
1187         return;
1188 
1189     for (auto &oneProfile : m_Manager->profiles)
1190         profileArray.append(oneProfile->toJson());
1191 
1192     QJsonObject profiles =
1193     {
1194         {"selectedProfile", profile->name},
1195         {"profiles", profileArray}
1196     };
1197     sendResponse(commands[GET_PROFILES], profiles);
1198 }
1199 
1200 ///////////////////////////////////////////////////////////////////////////////////////////
1201 ///
1202 ///////////////////////////////////////////////////////////////////////////////////////////
1203 void Message::sendSchedulerJobs()
1204 {
1205     QJsonObject jobs =
1206     {
1207         {"jobs", m_Manager->schedulerModule()->moduleState()->getJSONJobs()}
1208     };
1209     sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1210 }
1211 
1212 ///////////////////////////////////////////////////////////////////////////////////////////
1213 ///
1214 ///////////////////////////////////////////////////////////////////////////////////////////
1215 void Message::sendSchedulerJobList(QJsonArray jobsList)
1216 {
1217     QJsonObject jobs =
1218     {
1219         {"jobs", jobsList}
1220     };
1221     sendResponse(commands[SCHEDULER_GET_JOBS], jobs);
1222 }
1223 
1224 ///////////////////////////////////////////////////////////////////////////////////////////
1225 ///
1226 ///////////////////////////////////////////////////////////////////////////////////////////
1227 void Message::sendSchedulerStatus(const QJsonObject &status)
1228 {
1229     sendResponse(commands[NEW_SCHEDULER_STATE], status);
1230 }
1231 
1232 
1233 ///////////////////////////////////////////////////////////////////////////////////////////
1234 ///
1235 ///////////////////////////////////////////////////////////////////////////////////////////
1236 void Message::setEkosStatingStatus(Ekos::CommunicationStatus status)
1237 {
1238     if (status == Ekos::Pending)
1239         return;
1240 
1241     QJsonObject connectionState =
1242     {
1243         {"connected", true},
1244         {"online", status == Ekos::Success}
1245     };
1246     sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
1247 }
1248 
1249 ///////////////////////////////////////////////////////////////////////////////////////////
1250 ///
1251 ///////////////////////////////////////////////////////////////////////////////////////////
1252 void Message::setINDIStatus(Ekos::CommunicationStatus status)
1253 {
1254     QJsonObject connectionState =
1255     {
1256         {"status", status},
1257     };
1258 
1259     sendResponse(commands[NEW_INDI_STATE], connectionState);
1260 }
1261 
1262 ///////////////////////////////////////////////////////////////////////////////////////////
1263 ///
1264 ///////////////////////////////////////////////////////////////////////////////////////////
1265 void Message::processOptionsCommands(const QString &command, const QJsonObject &payload)
1266 {
1267     if (command == commands[OPTION_SET])
1268     {
1269         const QJsonArray options = payload["options"].toArray();
1270         for (const auto &oneOption : options)
1271             Options::self()->setProperty(oneOption["name"].toString().toLatin1(), oneOption["value"].toVariant());
1272 
1273         Options::self()->save();
1274         emit optionsUpdated();
1275     }
1276     else if (command == commands[OPTION_GET])
1277     {
1278         const QJsonArray options = payload["options"].toArray();
1279         QJsonArray result;
1280         for (const auto &oneOption : options)
1281         {
1282             const auto name = oneOption["name"].toString();
1283             QVariant value = Options::self()->property(name.toLatin1());
1284             QVariantMap map;
1285             map["name"] = name;
1286             map["value"] = value;
1287             result.append(QJsonObject::fromVariantMap(map));
1288         }
1289         sendResponse(commands[OPTION_GET], result);
1290     }
1291 }
1292 
1293 ///////////////////////////////////////////////////////////////////////////////////////////
1294 ///
1295 ///////////////////////////////////////////////////////////////////////////////////////////
1296 void Message::processScopeCommands(const QString &command, const QJsonObject &payload)
1297 {
1298     if (command == commands[ADD_SCOPE])
1299     {
1300         KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1301                 payload["type"].toString(), payload["aperture"].toDouble(), payload["focal_length"].toDouble());
1302     }
1303     else if (command == commands[UPDATE_SCOPE])
1304     {
1305         KStarsData::Instance()->userdb()->AddScope(payload["model"].toString(), payload["vendor"].toString(),
1306                 payload["type"].toString(), payload["aperture"].toDouble(), payload["focal_length"].toDouble(), payload["id"].toString());
1307     }
1308     else if (command == commands[DELETE_SCOPE])
1309     {
1310         KStarsData::Instance()->userdb()->DeleteEquipment("telescope", payload["id"].toString());
1311     }
1312 
1313     sendScopes();
1314 }
1315 
1316 ///////////////////////////////////////////////////////////////////////////////////////////
1317 ///
1318 ///////////////////////////////////////////////////////////////////////////////////////////
1319 void Message::processDSLRCommands(const QString &command, const QJsonObject &payload)
1320 {
1321     if (command == commands[DSLR_SET_INFO])
1322     {
1323         if (m_Manager->captureModule())
1324             m_Manager->captureModule()->addDSLRInfo(
1325                 payload["model"].toString(),
1326                 payload["width"].toInt(),
1327                 payload["height"].toInt(),
1328                 payload["pixelw"].toDouble(),
1329                 payload["pixelh"].toDouble());
1330 
1331     }
1332     else if(command == commands[DSLR_ADD_LENS])
1333     {
1334         KStarsData::Instance()->userdb()->AddDSLRLens(payload["model"].toString(), payload["vendor"].toString(),
1335                 payload["focal_length"].toDouble(), payload["focal_ratio"].toDouble());
1336     }
1337     else if (command == commands[DSLR_DELETE_LENS])
1338     {
1339         KStarsData::Instance()->userdb()->DeleteEquipment("dslrlens", payload["id"].toString());
1340     }
1341     else if (command == commands[DSLR_UPDATE_LENS])
1342     {
1343         KStarsData::Instance()->userdb()->AddDSLRLens(payload["model"].toString(), payload["vendor"].toString(),
1344                 payload["focal_length"].toDouble(), payload["focal_ratio"].toDouble(), payload["id"].toString());
1345     }
1346 
1347     sendDSLRLenses();
1348 }
1349 
1350 ///////////////////////////////////////////////////////////////////////////////////////////
1351 ///
1352 ///////////////////////////////////////////////////////////////////////////////////////////
1353 void Message::processTrainCommands(const QString &command, const QJsonObject &payload)
1354 {
1355     if (command == commands[TRAIN_GET_ALL])
1356         sendTrains();
1357     else if (command == commands[TRAIN_GET_PROFILES])
1358         sendTrainProfiles();
1359     else if (command == commands[TRAIN_SET])
1360     {
1361         auto module = payload["module"].toString();
1362         auto name = payload["name"].toString();
1363 
1364         if (module == "capture")
1365         {
1366             if (m_Manager->captureModule())
1367                 m_Manager->captureModule()->setOpticalTrain(name);
1368         }
1369         else if (module == "focus")
1370         {
1371             if (m_Manager->focusModule())
1372                 m_Manager->focusModule()->setOpticalTrain(name);
1373         }
1374         else if (module == "guide")
1375         {
1376             if (m_Manager->guideModule())
1377                 m_Manager->guideModule()->setOpticalTrain(name);
1378         }
1379         else if (module == "align")
1380         {
1381             if (m_Manager->alignModule())
1382                 m_Manager->alignModule()->setOpticalTrain(name);
1383         }
1384         else if (module == "mount")
1385         {
1386             if (m_Manager->mountModule())
1387                 m_Manager->mountModule()->setOpticalTrain(name);
1388         }
1389         else if (module == "darklibrary")
1390         {
1391             Ekos::DarkLibrary::Instance()->setOpticalTrain(name);
1392         }
1393     }
1394     else if (command == commands[TRAIN_ADD])
1395     {
1396         Ekos::OpticalTrainManager::Instance()->addOpticalTrain(payload);
1397     }
1398     else if (command == commands[TRAIN_UPDATE])
1399     {
1400         Ekos::OpticalTrainManager::Instance()->setOpticalTrain(payload);
1401     }
1402     else if (command == commands[TRAIN_DELETE])
1403     {
1404         Ekos::OpticalTrainManager::Instance()->removeOpticalTrain(payload["name"].toString());
1405     }
1406     else if (command == commands[TRAIN_RESET])
1407     {
1408         Ekos::OpticalTrainManager::Instance()->reset();
1409     }
1410     else if (command == commands[TRAIN_ACCEPT])
1411     {
1412         requestOpticalTrains(false);
1413         Ekos::OpticalTrainManager::Instance()->accept();
1414     }
1415 
1416 }
1417 
1418 ///////////////////////////////////////////////////////////////////////////////////////////
1419 ///
1420 ///////////////////////////////////////////////////////////////////////////////////////////
1421 void Message::processFilterManagerCommands(const QString &command, const QJsonObject &payload)
1422 {
1423     QSharedPointer<Ekos::FilterManager> manager;
1424     if (m_Manager->captureModule())
1425         manager = m_Manager->captureModule()->filterManager();
1426 
1427     if (manager.isNull())
1428         return;
1429 
1430     if (command == commands[FM_GET_DATA])
1431     {
1432         QJsonObject data = manager->toJSON();
1433         sendResponse(commands[FM_GET_DATA], data);
1434     }
1435     else if (command == commands[FM_SET_DATA])
1436     {
1437         manager->setFilterData(payload);
1438     }
1439 }
1440 
1441 ///////////////////////////////////////////////////////////////////////////////////////////
1442 ///
1443 ///////////////////////////////////////////////////////////////////////////////////////////
1444 void Message::processDarkLibraryCommands(const QString &command, const QJsonObject &payload)
1445 {
1446     if (command == commands[DARK_LIBRARY_START])
1447         Ekos::DarkLibrary::Instance()->start();
1448     else if(command == commands[DARK_LIBRARY_SET_ALL_SETTINGS])
1449     {
1450         auto settings = payload.toVariantMap();
1451         Ekos::DarkLibrary::Instance()->setAllSettings(settings);
1452         KSUtils::setGlobalSettings(settings);
1453     }
1454     else if(command == commands[DARK_LIBRARY_GET_ALL_SETTINGS])
1455         sendDarkLibrarySettings(Ekos::DarkLibrary::Instance()->getAllSettings());
1456     else if(command == commands[DARK_LIBRARY_GET_DEFECT_SETTINGS])
1457         sendResponse(commands[DARK_LIBRARY_GET_DEFECT_SETTINGS], Ekos::DarkLibrary::Instance()->getDefectSettings());
1458     else if(command == commands[DARK_LIBRARY_SET_CAMERA_PRESETS])
1459     {
1460         Ekos::DarkLibrary::Instance()->setCameraPresets(payload);
1461     }
1462     else if (command == commands[DARK_LIBRARY_STOP])
1463     {
1464         Ekos::DarkLibrary::Instance()->stop();
1465     }
1466     else if (command == commands[DARK_LIBRARY_GET_MASTERS_IMAGE])
1467     {
1468         const int row = payload["row"].toInt();
1469         Ekos::DarkLibrary::Instance()->loadIndexInView(row);
1470     }
1471     else if (command == commands[DARK_LIBRARY_GET_CAMERA_PRESETS])
1472     {
1473         sendResponse(commands[DARK_LIBRARY_GET_CAMERA_PRESETS], Ekos::DarkLibrary::Instance()->getCameraPresets());
1474     }
1475     else if (command == commands[DARK_LIBRARY_SET_DEFECT_PIXELS])
1476     {
1477         Ekos::DarkLibrary::Instance()->setDefectPixels(payload);
1478     }
1479     else if (command == commands[DARK_LIBRARY_SAVE_MAP])
1480     {
1481         Ekos::DarkLibrary::Instance()->saveMapB->click();
1482     }
1483     else if (command == commands[DARK_LIBRARY_SET_DEFECT_FRAME])
1484     {
1485         Ekos::DarkLibrary::Instance()->setDefectMapEnabled(false);
1486     }
1487     else if (command == commands[DARK_LIBRARY_GET_VIEW_MASTERS])
1488     {
1489         sendResponse(commands[DARK_LIBRARY_GET_VIEW_MASTERS], Ekos::DarkLibrary::Instance()->getViewMasters());
1490     }
1491     else if (command == commands[DARK_LIBRARY_CLEAR_MASTERS_ROW])
1492     {
1493         const int rowIndex = payload["row"].toInt();
1494         Ekos::DarkLibrary::Instance()->clearRow(rowIndex);
1495     }
1496 }
1497 
1498 ///////////////////////////////////////////////////////////////////////////////////////////
1499 ///
1500 ///////////////////////////////////////////////////////////////////////////////////////////
1501 void Message::processDeviceCommands(const QString &command, const QJsonObject &payload)
1502 {
1503     QString device = payload["device"].toString();
1504 
1505     // In case we want to UNSUBSCRIBE from all at once
1506     if (device.isEmpty() && command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1507     {
1508         m_PropertySubscriptions.clear();
1509         return;
1510     }
1511 
1512     QSharedPointer<ISD::GenericDevice> oneDevice;
1513     if (!INDIListener::findDevice(device, oneDevice))
1514         return;
1515 
1516     // Get specific property
1517     if (command == commands[DEVICE_PROPERTY_GET])
1518     {
1519         QJsonObject propObject;
1520         if (oneDevice->getJSONProperty(payload["property"].toString(), propObject, payload["compact"].toBool(true)))
1521             sendResponse(commands[DEVICE_PROPERTY_GET], propObject);
1522     }
1523     // Set specific property
1524     else if (command == commands[DEVICE_PROPERTY_SET])
1525     {
1526         oneDevice->setJSONProperty(payload["property"].toString(), payload["elements"].toArray());
1527     }
1528     // Return ALL properties
1529     else if (command == commands[DEVICE_GET])
1530     {
1531         QJsonArray properties;
1532         for (const auto &oneProp : *oneDevice->getProperties())
1533         {
1534             QJsonObject singleProp;
1535             if (oneDevice->getJSONProperty(oneProp.getName(), singleProp, payload["compact"].toBool(false)))
1536                 properties.append(singleProp);
1537         }
1538 
1539         QJsonObject response =
1540         {
1541             {"device", device},
1542             {"properties", properties}
1543         };
1544 
1545         sendResponse(commands[DEVICE_GET], response);
1546     }
1547     // Subscribe to one or more properties
1548     // When subscribed, the updates are immediately pushed as soon as they are received.
1549     else if (command == commands[DEVICE_PROPERTY_SUBSCRIBE])
1550     {
1551         const QJsonArray properties = payload["properties"].toArray();
1552         const QJsonArray groups = payload["groups"].toArray();
1553 
1554         // Get existing subscribed props for this device
1555         QSet<QString> props;
1556         if (m_PropertySubscriptions.contains(device))
1557             props = m_PropertySubscriptions[device];
1558 
1559         // If it is just a single property, let's insert it to props.
1560         if (properties.isEmpty() == false)
1561         {
1562             for (const auto &oneProp : properties)
1563                 props.insert(oneProp.toString());
1564         }
1565         // If group is specified, then we need to add ALL properties belonging to this group.
1566         else if (groups.isEmpty() == false)
1567         {
1568             QVariantList indiGroups = groups.toVariantList();
1569             for (auto &oneProp : *oneDevice->getProperties())
1570             {
1571                 if (indiGroups.contains(oneProp.getGroupName()))
1572                     props.insert(oneProp.getName());
1573             }
1574         }
1575         // Otherwise, subscribe to ALL property in this device
1576         else
1577         {
1578             for (auto &oneProp : *oneDevice->getProperties())
1579                 props.insert(oneProp.getName());
1580         }
1581 
1582         m_PropertySubscriptions[device] = props;
1583     }
1584     else if (command == commands[DEVICE_PROPERTY_UNSUBSCRIBE])
1585     {
1586         const QJsonArray properties = payload["properties"].toArray();
1587         const QJsonArray groups = payload["groups"].toArray();
1588 
1589         // Get existing subscribed props for this device
1590         QSet<QString> props;
1591         if (m_PropertySubscriptions.contains(device))
1592             props = m_PropertySubscriptions[device];
1593 
1594         // If it is just a single property, let's insert it to props.
1595         // If it is just a single property, let's insert it to props.
1596         if (properties.isEmpty() == false)
1597         {
1598             for (const auto &oneProp : properties)
1599                 props.remove(oneProp.toString());
1600         }
1601         // If group is specified, then we need to add ALL properties belonging to this group.
1602         else if (groups.isEmpty() == false)
1603         {
1604             QVariantList indiGroups = groups.toVariantList();
1605             for (auto &oneProp : *oneDevice->getProperties())
1606             {
1607                 if (indiGroups.contains(oneProp.getGroupName()))
1608                     props.remove(oneProp.getName());
1609             }
1610         }
1611         // Otherwise, subscribe to ALL property in this device
1612         else
1613         {
1614             for (auto &oneProp : *oneDevice->getProperties())
1615                 props.remove(oneProp.getName());
1616         }
1617 
1618         m_PropertySubscriptions[device] = props;
1619     }
1620 }
1621 
1622 ///////////////////////////////////////////////////////////////////////////////////////////
1623 ///
1624 ///////////////////////////////////////////////////////////////////////////////////////////
1625 void Message::processAstronomyCommands(const QString &command, const QJsonObject &payload)
1626 {
1627     if (command == commands[ASTRO_GET_ALMANC])
1628     {
1629         // Today's date
1630         const KStarsDateTime localTime  = KStarsData::Instance()->lt();
1631         // Local Midnight
1632         const KStarsDateTime midnight  = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
1633 
1634         KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
1635 
1636         QJsonObject response =
1637         {
1638             {"SunRise", almanac.getSunRise()},
1639             {"SunSet", almanac.getSunSet()},
1640             {"SunMaxAlt", almanac.getSunMaxAlt()},
1641             {"SunMinAlt", almanac.getSunMinAlt()},
1642             {"MoonRise", almanac.getMoonRise()},
1643             {"MoonSet", almanac.getMoonSet()},
1644             {"MoonPhase", almanac.getMoonPhase()},
1645             {"MoonIllum", almanac.getMoonIllum()},
1646             {"Dawn", almanac.getDawnAstronomicalTwilight()},
1647             {"Dusk", almanac.getDuskAstronomicalTwilight()},
1648 
1649         };
1650 
1651         sendResponse(commands[ASTRO_GET_ALMANC], response);
1652     }
1653     // Get a list of object based on criteria
1654     else if (command == commands[ASTRO_SEARCH_OBJECTS])
1655     {
1656         // Set time if required.
1657         if (payload.contains("jd"))
1658         {
1659             KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1660             KStarsData::Instance()->clock()->setUTC(jd);
1661         }
1662 
1663         // Search Criteria
1664         // Object Type
1665         auto objectType = static_cast<SkyObject::TYPE>(payload["type"].toInt(SkyObject::GALAXY));
1666         // Azimuth restriction
1667         auto objectDirection = static_cast<Direction>(payload["direction"].toInt(All));
1668         // Maximum Object Magnitude
1669         auto objectMaxMagnitude = payload["maxMagnitude"].toDouble(10);
1670         // Minimum Object Altitude
1671         auto objectMinAlt = payload["minAlt"].toDouble(15);
1672         // Minimum Duration that the object must be above the altitude (if any) seconds.
1673         auto objectMinDuration = payload["minDuration"].toInt(3600);
1674         // Minimum FOV in arcmins.
1675         auto objectMinFOV = payload["minFOV"].toDouble(0);
1676         // Data instance
1677         auto *data = KStarsData::Instance();
1678         // Geo Location
1679         auto *geo = KStarsData::Instance()->geo();
1680         // If we are before dawn, we check object altitude restrictions
1681         // Otherwise, all objects are welcome
1682         auto start = KStarsData::Instance()->lt();
1683         auto end = getNextDawn();
1684         if (start > end)
1685             // Add 1 day
1686             end = end.addDays(1);
1687 
1688         QVector<QPair<QString, const SkyObject *>> allObjects;
1689         CatalogsDB::CatalogObjectList dsoObjects;
1690         bool isDSO = false;
1691 
1692         switch (objectType)
1693         {
1694             // Stars
1695             case SkyObject::STAR:
1696             case SkyObject::CATALOG_STAR:
1697                 allObjects.append(data->skyComposite()->objectLists(SkyObject::STAR));
1698                 allObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR));
1699                 break;
1700             // Planets & Moon
1701             case SkyObject::PLANET:
1702             case SkyObject::MOON:
1703                 allObjects.append(data->skyComposite()->objectLists(SkyObject::PLANET));
1704                 allObjects.append(data->skyComposite()->objectLists(SkyObject::MOON));
1705                 break;
1706             // Comets & Asteroids
1707             case SkyObject::COMET:
1708                 allObjects.append(data->skyComposite()->objectLists(SkyObject::COMET));
1709                 break;
1710             case SkyObject::ASTEROID:
1711                 allObjects.append(data->skyComposite()->objectLists(SkyObject::ASTEROID));
1712                 break;
1713             // Clusters
1714             case SkyObject::OPEN_CLUSTER:
1715                 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::OPEN_CLUSTER, objectMaxMagnitude));
1716                 isDSO = true;
1717                 break;
1718             case SkyObject::GLOBULAR_CLUSTER:
1719                 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GLOBULAR_CLUSTER, objectMaxMagnitude));
1720                 isDSO = true;
1721                 break;
1722             // Nebuale
1723             case SkyObject::GASEOUS_NEBULA:
1724                 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GASEOUS_NEBULA, objectMaxMagnitude));
1725                 isDSO = true;
1726                 break;
1727             case SkyObject::PLANETARY_NEBULA:
1728                 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::PLANETARY_NEBULA, objectMaxMagnitude));
1729                 isDSO = true;
1730                 break;
1731             case SkyObject::GALAXY:
1732                 dsoObjects.splice(dsoObjects.end(), m_DSOManager.get_objects(SkyObject::GALAXY, objectMaxMagnitude));
1733                 isDSO = true;
1734                 break;
1735             case SkyObject::SUPERNOVA:
1736             {
1737                 if (!Options::showSupernovae())
1738                 {
1739                     Options::setShowSupernovae(true);
1740                     data->setFullTimeUpdate();
1741                     KStars::Instance()->map()->forceUpdate();
1742                 }
1743                 allObjects.append(data->skyComposite()->objectLists(SkyObject::SUPERNOVA));
1744             }
1745             break;
1746             case SkyObject::SATELLITE:
1747             {
1748                 if (!Options::showSatellites())
1749                 {
1750                     Options::setShowSatellites(true);
1751                     data->setFullTimeUpdate();
1752                     KStars::Instance()->map()->forceUpdate();
1753                 }
1754                 allObjects.append(data->skyComposite()->objectLists(SkyObject::SATELLITE));
1755             }
1756             break;
1757             default:
1758                 break;
1759         }
1760 
1761         // Sort by magnitude
1762         std::sort(allObjects.begin(), allObjects.end(), [](const auto & a, const auto & b)
1763         {
1764             return a.second->mag() < b.second->mag();
1765         });
1766 
1767         QMutableVectorIterator<QPair<QString, const SkyObject *>> objectIterator(allObjects);
1768 
1769         // Filter direction, if specified.
1770         if (objectDirection != All)
1771         {
1772             QPair<int, int> Quardent1(270, 360), Quardent2(0, 90), Quardent3(90, 180), Quardent4(180, 270);
1773             QPair<int, int> minAZ, maxAZ;
1774             switch (objectDirection)
1775             {
1776                 case North:
1777                     minAZ = Quardent1;
1778                     maxAZ = Quardent2;
1779                     break;
1780                 case East:
1781                     minAZ = Quardent2;
1782                     maxAZ = Quardent3;
1783                     break;
1784                 case South:
1785                     minAZ = Quardent3;
1786                     maxAZ = Quardent4;
1787                     break;
1788                 case West:
1789                     minAZ = Quardent4;
1790                     maxAZ = Quardent1;
1791                     break;
1792                 default:
1793                     break;
1794             }
1795 
1796             if (isDSO)
1797             {
1798                 CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1799                 while (dsoIterator != dsoObjects.end())
1800                 {
1801                     // If there a more efficient way to do this?
1802                     const double az = (*dsoIterator).recomputeHorizontalCoords(start, geo).az().Degrees();
1803                     if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1804                         dsoIterator = dsoObjects.erase(dsoIterator);
1805                     else
1806                         ++dsoIterator;
1807                 }
1808             }
1809             else
1810             {
1811                 while (objectIterator.hasNext())
1812                 {
1813                     const auto az = objectIterator.next().second->recomputeHorizontalCoords(start, geo).az().Degrees();
1814                     if (! ((minAZ.first <= az && az <= minAZ.second) || (maxAZ.first <= az && az <= maxAZ.second)))
1815                         objectIterator.remove();
1816                 }
1817             }
1818         }
1819 
1820         // Maximum Magnitude
1821         if (!isDSO)
1822         {
1823             objectIterator.toFront();
1824             while (objectIterator.hasNext())
1825             {
1826                 auto magnitude = objectIterator.next().second->mag();
1827                 // Only filter for objects that have valid magnitude, otherwise, they're automatically included.
1828                 if (magnitude != NaN::f && magnitude > objectMaxMagnitude)
1829                     objectIterator.remove();
1830             }
1831         }
1832 
1833         // Altitude
1834         if (isDSO)
1835         {
1836             CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1837             while (dsoIterator != dsoObjects.end())
1838             {
1839                 double duration = 0;
1840                 for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
1841                 {
1842                     dms LST = geo->GSTtoLST(t.gst());
1843                     (*dsoIterator).EquatorialToHorizontal(&LST, geo->lat());
1844                     if ((*dsoIterator).alt().Degrees() >= objectMinAlt)
1845                         duration += 3600;
1846                 }
1847 
1848                 if (duration < objectMinDuration)
1849                     dsoIterator = dsoObjects.erase(dsoIterator);
1850                 else
1851                     ++dsoIterator;
1852             }
1853         }
1854         else
1855         {
1856             objectIterator.toFront();
1857             while (objectIterator.hasNext())
1858             {
1859                 auto oneObject = objectIterator.next().second;
1860                 double duration = 0;
1861 
1862                 for (KStarsDateTime t = start; t < end; t = t.addSecs(3600.0))
1863                 {
1864                     auto LST = geo->GSTtoLST(t.gst());
1865                     const_cast<SkyObject *>(oneObject)->EquatorialToHorizontal(&LST, geo->lat());
1866                     if (oneObject->alt().Degrees() >= objectMinAlt)
1867                         duration += 3600;
1868                 }
1869 
1870                 if (duration < objectMinDuration)
1871                     objectIterator.remove();
1872             }
1873         }
1874 
1875         // For DSOs, check minimum required FOV, if any.
1876         if (isDSO && objectMinFOV > 0)
1877         {
1878             CatalogsDB::CatalogObjectList::iterator dsoIterator = dsoObjects.begin();
1879             while (dsoIterator != dsoObjects.end())
1880             {
1881                 if ((*dsoIterator).a() < objectMinFOV)
1882                     dsoIterator = dsoObjects.erase(dsoIterator);
1883                 else
1884                     ++dsoIterator;
1885             }
1886         }
1887 
1888         QStringList searchObjects;
1889         for (auto &oneObject : allObjects)
1890             searchObjects.append(oneObject.second->name());
1891         for (auto &oneObject : dsoObjects)
1892             searchObjects.append(oneObject.name());
1893 
1894         searchObjects.removeDuplicates();
1895         QJsonArray response = QJsonArray::fromStringList(searchObjects);
1896 
1897         sendResponse(commands[ASTRO_SEARCH_OBJECTS], response);
1898     }
1899     else if(command == commands[ASTRO_GET_OBJECT_INFO])
1900     {
1901         const auto name = payload["object"].toString();
1902         QJsonObject info;
1903         SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, false);
1904         if(oneObject)
1905         {
1906             info =
1907             {
1908                 {"name", name},
1909                 {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
1910                 {"magnitude", oneObject->mag()},
1911                 {"ra0", oneObject->ra0().Hours()},
1912                 {"de0", oneObject->dec0().Degrees()},
1913                 {"ra", oneObject->ra().Hours()},
1914                 {"de", oneObject->dec().Degrees()},
1915                 {"object", true}
1916             };
1917             sendResponse(commands[ASTRO_GET_OBJECT_INFO], info);
1918         }
1919         else
1920         {
1921             info =
1922             {
1923                 {"name", name},
1924                 {"object", false},
1925             };
1926             sendResponse(commands[ASTRO_GET_OBJECT_INFO], info );
1927         }
1928 
1929     }
1930     // Get a list of object based on criteria
1931     else if (command == commands[ASTRO_GET_OBJECTS_INFO])
1932     {
1933         // Set time if required.
1934         if (payload.contains("jd"))
1935         {
1936             KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1937             KStarsData::Instance()->clock()->setUTC(jd);
1938         }
1939 
1940         // Object Names
1941         QVariantList objectNames = payload["names"].toArray().toVariantList();
1942         QJsonArray objectsArray;
1943 
1944         for (auto &oneName : objectNames)
1945         {
1946             const QString name = oneName.toString();
1947             SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, false);
1948             if (oneObject)
1949             {
1950                 QJsonObject info =
1951                 {
1952                     {"name", name},
1953                     {"designations", QJsonArray::fromStringList(oneObject->longname().split(", "))},
1954                     {"magnitude", oneObject->mag()},
1955                     {"ra0", oneObject->ra0().Hours()},
1956                     {"de0", oneObject->dec0().Degrees()},
1957                     {"ra", oneObject->ra().Hours()},
1958                     {"de", oneObject->dec().Degrees()},
1959                 };
1960 
1961                 // If DSO, add angular size.
1962                 CatalogObject *dsoObject = dynamic_cast<CatalogObject*>(oneObject);
1963                 if (dsoObject)
1964                 {
1965                     info["a"] = dsoObject->a();
1966                     info["b"] = dsoObject->b();
1967                     info["pa"] = dsoObject->pa();
1968                 }
1969 
1970                 objectsArray.append(info);
1971             }
1972         }
1973 
1974         sendResponse(commands[ASTRO_GET_OBJECTS_INFO], objectsArray);
1975     }
1976     // Get a object observability alt/az/ha
1977     else if (command == commands[ASTRO_GET_OBJECTS_OBSERVABILITY])
1978     {
1979         // Set time if required.
1980         if (payload.contains("jd"))
1981         {
1982             KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
1983             KStarsData::Instance()->clock()->setUTC(jd);
1984         }
1985 
1986         // Object Names
1987         QVariantList objectNames = payload["names"].toArray().toVariantList();
1988         QJsonArray objectsArray;
1989 
1990         // Data instance
1991         auto *data = KStarsData::Instance();
1992         // Geo Location
1993         auto *geo = KStarsData::Instance()->geo();
1994         // UT
1995         auto ut = data->ut();
1996 
1997         for (auto &oneName : objectNames)
1998         {
1999             const QString name = oneName.toString();
2000             SkyObject *oneObject = data->skyComposite()->findByName(name, false);
2001             if (oneObject)
2002             {
2003                 oneObject->EquatorialToHorizontal(data->lst(), geo->lat());
2004                 dms ha(data->lst()->Degrees() - oneObject->ra().Degrees());
2005                 QJsonObject info =
2006                 {
2007                     {"name", name},
2008                     {"az", oneObject->az().Degrees()},
2009                     {"alt", oneObject->alt().Degrees()},
2010                     {"ha",  ha.Hours()},
2011                 };
2012 
2013                 objectsArray.append(info);
2014             }
2015         }
2016 
2017         sendResponse(commands[ASTRO_GET_OBJECTS_OBSERVABILITY], objectsArray);
2018     }
2019     else if (command == commands[ASTRO_GET_OBJECTS_RISESET])
2020     {
2021         // Set time if required.
2022         if (payload.contains("jd"))
2023         {
2024             KStarsDateTime jd = KStarsDateTime(payload["jd"].toDouble());
2025             KStarsData::Instance()->clock()->setUTC(jd);
2026         }
2027 
2028         // Object Names
2029         QVariantList objectNames = payload["names"].toArray().toVariantList();
2030         QJsonArray objectsArray;
2031 
2032         // Data instance
2033         auto *data = KStarsData::Instance();
2034         // Geo Location
2035         auto *geo = KStarsData::Instance()->geo();
2036         // UT
2037         QDateTime midnight = QDateTime(data->lt().date(), QTime());
2038         KStarsDateTime ut  = geo->LTtoUT(KStarsDateTime(midnight));
2039 
2040         int DayOffset = 0;
2041         if (data->lt().time().hour() > 12)
2042             DayOffset = 1;
2043 
2044         for (auto &oneName : objectNames)
2045         {
2046             const QString name = oneName.toString();
2047             SkyObject *oneObject = data->skyComposite()->findByName(name, false);
2048             if (oneObject)
2049             {
2050                 QJsonObject info;
2051                 //Prepare time/position variables
2052                 //true = use rise time
2053                 QTime riseTime = oneObject->riseSetTime(ut, geo, true);
2054 
2055                 //If transit time is before rise time, use transit time for tomorrow
2056                 QTime transitTime = oneObject->transitTime(ut, geo);
2057                 if (transitTime < riseTime)
2058                     transitTime   = oneObject->transitTime(ut.addDays(1), geo);
2059 
2060                 //If set time is before rise time, use set time for tomorrow
2061                 //false = use set time
2062                 QTime setTime = oneObject->riseSetTime(ut, geo, false);
2063                 //false = use set time
2064                 if (setTime < riseTime)
2065                     setTime  = oneObject->riseSetTime(ut.addDays(1), geo, false);
2066 
2067                 info["name"] = name;
2068                 if (riseTime.isValid())
2069                 {
2070                     info["rise"] = QString::asprintf("%02d:%02d", riseTime.hour(), riseTime.minute());
2071                     info["set"] = QString::asprintf("%02d:%02d", setTime.hour(), setTime.minute());
2072                 }
2073                 else
2074                 {
2075                     if (oneObject->alt().Degrees() > 0.0)
2076                     {
2077                         info["rise"] = "Circumpolar";
2078                         info["set"] = "Circumpolar";
2079                     }
2080                     else
2081                     {
2082                         info["rise"] = "Never rises";
2083                         info["set"] = "Never rises";
2084                     }
2085                 }
2086 
2087                 info["transit"] = QString::asprintf("%02d:%02d", transitTime.hour(), transitTime.minute());
2088 
2089                 QJsonArray altitudes;
2090                 for (double h = -12.0; h <= 12.0; h += 0.5)
2091                 {
2092                     double hour = h + (24.0 * DayOffset);
2093                     KStarsDateTime offset = ut.addSecs(hour * 3600.0);
2094                     CachingDms LST = geo->GSTtoLST(offset.gst());
2095                     oneObject->EquatorialToHorizontal(&LST, geo->lat());
2096                     altitudes.append(oneObject->alt().Degrees());
2097                 }
2098 
2099                 info["altitudes"] = altitudes;
2100 
2101                 objectsArray.append(info);
2102             }
2103         }
2104 
2105         sendResponse(commands[ASTRO_GET_OBJECTS_RISESET], objectsArray);
2106     }
2107 }
2108 
2109 ///////////////////////////////////////////////////////////////////////////////////////////
2110 ///
2111 ///////////////////////////////////////////////////////////////////////////////////////////
2112 KStarsDateTime Message::getNextDawn()
2113 {
2114     // Today's date
2115     const KStarsDateTime localTime  = KStarsData::Instance()->lt();
2116     // Local Midnight
2117     const KStarsDateTime midnight  = KStarsDateTime(localTime.date(), QTime(0, 0), Qt::LocalTime);
2118     // Almanac
2119     KSAlmanac almanac(midnight, KStarsData::Instance()->geo());
2120     // Next Dawn
2121     KStarsDateTime nextDawn = midnight.addSecs(almanac.getDawnAstronomicalTwilight() * 24.0 * 3600.0);
2122     // If dawn is earliar than now, add a day
2123     if (nextDawn < localTime)
2124         nextDawn.addDays(1);
2125 
2126     return nextDawn;
2127 }
2128 
2129 ///////////////////////////////////////////////////////////////////////////////////////////
2130 ///
2131 ///////////////////////////////////////////////////////////////////////////////////////////
2132 void Message::requestDSLRInfo(const QString &cameraName)
2133 {
2134     sendResponse(commands[DSLR_GET_INFO], cameraName);
2135 }
2136 
2137 ///////////////////////////////////////////////////////////////////////////////////////////
2138 ///
2139 ///////////////////////////////////////////////////////////////////////////////////////////
2140 void Message::requestPortSelection(bool show)
2141 {
2142     sendResponse(commands[GET_PROFILE_PORT_SELECTION], show);
2143 }
2144 
2145 ///////////////////////////////////////////////////////////////////////////////////////////
2146 ///
2147 ///////////////////////////////////////////////////////////////////////////////////////////
2148 void Message::sendDialog(const QJsonObject &message)
2149 {
2150     sendResponse(commands[DIALOG_GET_INFO], message);
2151 }
2152 
2153 ///////////////////////////////////////////////////////////////////////////////////////////
2154 ///
2155 ///////////////////////////////////////////////////////////////////////////////////////////
2156 void Message::sendResponse(const QString &command, const QJsonObject &payload)
2157 {
2158     for (auto &nodeManager : m_NodeManagers)
2159     {
2160         nodeManager->message()->sendResponse(command, payload);
2161     }
2162 }
2163 
2164 ///////////////////////////////////////////////////////////////////////////////////////////
2165 ///
2166 ///////////////////////////////////////////////////////////////////////////////////////////
2167 void Message::sendResponse(const QString &command, const QJsonArray &payload)
2168 {
2169     for (auto &nodeManager : m_NodeManagers)
2170     {
2171         nodeManager->message()->sendResponse(command, payload);
2172     }
2173 }
2174 
2175 ///////////////////////////////////////////////////////////////////////////////////////////
2176 ///
2177 ///////////////////////////////////////////////////////////////////////////////////////////
2178 void Message::sendResponse(const QString &command, const QString &payload)
2179 {
2180     for (auto &nodeManager : m_NodeManagers)
2181     {
2182         nodeManager->message()->sendResponse(command, payload);
2183     }
2184 }
2185 
2186 ///////////////////////////////////////////////////////////////////////////////////////////
2187 ///
2188 ///////////////////////////////////////////////////////////////////////////////////////////
2189 void Message::sendResponse(const QString &command, bool payload)
2190 {
2191     for (auto &nodeManager : m_NodeManagers)
2192     {
2193         nodeManager->message()->sendResponse(command, payload);
2194     }
2195 }
2196 
2197 ///////////////////////////////////////////////////////////////////////////////////////////
2198 ///
2199 ///////////////////////////////////////////////////////////////////////////////////////////
2200 void Message::autofocusAborted()
2201 {
2202     QJsonObject cStatus =
2203     {
2204         {"status", "Aborted"}
2205     };
2206     sendResponse(commands[NEW_FOCUS_STATE], cStatus);
2207 }
2208 
2209 ///////////////////////////////////////////////////////////////////////////////////////////
2210 ///
2211 ///////////////////////////////////////////////////////////////////////////////////////////
2212 void Message::updateMountStatus(const QJsonObject &status, bool throttle)
2213 {
2214     if (throttle)
2215     {
2216         QDateTime now = QDateTime::currentDateTime();
2217         if (m_ThrottleTS.msecsTo(now) >= THROTTLE_INTERVAL)
2218         {
2219             m_ThrottleTS = now;
2220             sendResponse(commands[NEW_MOUNT_STATE], status);
2221         }
2222     }
2223     else
2224         sendResponse(commands[NEW_MOUNT_STATE], status);
2225 }
2226 
2227 ///////////////////////////////////////////////////////////////////////////////////////////
2228 ///
2229 ///////////////////////////////////////////////////////////////////////////////////////////
2230 void Message::updateCaptureStatus(const QJsonObject &status)
2231 {
2232     sendResponse(commands[NEW_CAPTURE_STATE], status);
2233 }
2234 
2235 ///////////////////////////////////////////////////////////////////////////////////////////
2236 ///
2237 ///////////////////////////////////////////////////////////////////////////////////////////
2238 void Message::updateFocusStatus(const QJsonObject &status)
2239 {
2240     sendResponse(commands[NEW_FOCUS_STATE], status);
2241 }
2242 
2243 ///////////////////////////////////////////////////////////////////////////////////////////
2244 ///
2245 ///////////////////////////////////////////////////////////////////////////////////////////
2246 void Message::updateGuideStatus(const QJsonObject &status)
2247 {
2248     sendResponse(commands[NEW_GUIDE_STATE], status);
2249 }
2250 
2251 ///////////////////////////////////////////////////////////////////////////////////////////
2252 ///
2253 ///////////////////////////////////////////////////////////////////////////////////////////
2254 void Message::updateDomeStatus(const QJsonObject &status)
2255 {
2256     sendResponse(commands[NEW_DOME_STATE], status);
2257 }
2258 
2259 ///////////////////////////////////////////////////////////////////////////////////////////
2260 ///
2261 ///////////////////////////////////////////////////////////////////////////////////////////
2262 void Message::updateCapStatus(const QJsonObject &status)
2263 {
2264     sendResponse(commands[NEW_CAP_STATE], status);
2265 }
2266 
2267 ///////////////////////////////////////////////////////////////////////////////////////////
2268 ///
2269 ///////////////////////////////////////////////////////////////////////////////////////////
2270 void Message::sendConnection()
2271 {
2272     QJsonObject connectionState =
2273     {
2274         {"connected", true},
2275         {"online", m_Manager->getEkosStartingStatus() == Ekos::Success}
2276     };
2277 
2278     sendResponse(commands[NEW_CONNECTION_STATE], connectionState);
2279 }
2280 
2281 ///////////////////////////////////////////////////////////////////////////////////////////
2282 ///
2283 ///////////////////////////////////////////////////////////////////////////////////////////
2284 void Message::sendStates()
2285 {
2286     // Send capture sequence if one exists
2287     if (m_Manager->captureModule())
2288     {
2289         QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2290         sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2291         sendCaptureSequence(m_Manager->captureModule()->getSequence());
2292     }
2293 
2294     if (m_Manager->mountModule())
2295     {
2296         QJsonObject mountState =
2297         {
2298             {"status", m_Manager->mountModule()->statusString(false)},
2299             {"target", m_Manager->capturePreview->mountTarget->text()},
2300             {"slewRate", m_Manager->mountModule()->slewRate()},
2301             {"pierSide", m_Manager->mountModule()->pierSide()}
2302         };
2303 
2304         sendResponse(commands[NEW_MOUNT_STATE], mountState);
2305     }
2306 
2307     if (m_Manager->focusModule())
2308     {
2309         QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->status(), false)}};
2310         sendResponse(commands[NEW_FOCUS_STATE], focusState);
2311     }
2312 
2313     if (m_Manager->guideModule())
2314     {
2315         QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2316         sendResponse(commands[NEW_GUIDE_STATE], guideState);
2317     }
2318 
2319     if (m_Manager->alignModule())
2320     {
2321         // Align State
2322         QJsonObject alignState =
2323         {
2324             {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2325         };
2326         sendResponse(commands[NEW_ALIGN_STATE], alignState);
2327 
2328         // Align settings
2329         sendAlignSettings(m_Manager->alignModule()->getAllSettings());
2330 
2331         Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2332         if (paa)
2333         {
2334             // Polar State
2335             QTextDocument doc;
2336             doc.setHtml(paa->getPAHMessage());
2337             QJsonObject polarState =
2338             {
2339                 {"stage", paa->getPAHStageString(false)},
2340                 {"enabled", paa->isEnabled()},
2341                 {"message", doc.toPlainText()},
2342             };
2343             sendResponse(commands[NEW_POLAR_STATE], polarState);
2344         }
2345     }
2346 }
2347 
2348 ///////////////////////////////////////////////////////////////////////////////////////////
2349 ///
2350 ///////////////////////////////////////////////////////////////////////////////////////////
2351 void Message::sendEvent(const QString &message, KSNotification::EventSource source, KSNotification::EventType event)
2352 {
2353     if (Options::ekosLiveNotifications() == false)
2354         return;
2355 
2356     QJsonObject newEvent =
2357     {
2358         {"source", source},
2359         {"severity", event},
2360         {"message", message},
2361         {"uuid", QUuid::createUuid().toString()}
2362     };
2363 
2364     sendResponse(commands[NEW_NOTIFICATION], newEvent);
2365 }
2366 
2367 ///////////////////////////////////////////////////////////////////////////////////////////
2368 ///
2369 ///////////////////////////////////////////////////////////////////////////////////////////
2370 void Message::sendManualRotatorStatus(double currentPA, double targetPA, double threshold)
2371 {
2372     QJsonObject request = {{ "currentPA", currentPA}, {"targetPA", targetPA}, {"threshold", threshold}};
2373     sendResponse(commands[ALIGN_MANUAL_ROTATOR_STATUS], request);
2374 }
2375 
2376 ///////////////////////////////////////////////////////////////////////////////////////////
2377 ///
2378 ///////////////////////////////////////////////////////////////////////////////////////////
2379 void Message::setBoundingRect(QRect rect, QSize view, double currentZoom)
2380 {
2381     m_BoundingRect = rect;
2382     m_ViewSize = view;
2383     m_CurrentZoom = currentZoom;
2384 }
2385 
2386 ///////////////////////////////////////////////////////////////////////////////////////////
2387 ///
2388 ///////////////////////////////////////////////////////////////////////////////////////////
2389 void Message::processDialogResponse(const QJsonObject &payload)
2390 {
2391     KSMessageBox::Instance()->selectResponse(payload["button"].toString());
2392 }
2393 
2394 ///////////////////////////////////////////////////////////////////////////////////////////
2395 ///
2396 ///////////////////////////////////////////////////////////////////////////////////////////
2397 void Message::processNewProperty(INDI::Property prop)
2398 {
2399     // Do not send new properties until all properties settle down
2400     // then send any properties that appears afterwards since the initial bunch
2401     // would cause a heavy message congestion.
2402     if (m_Manager->settleStatus() != Ekos::CommunicationStatus::Success)
2403         return;
2404 
2405     QJsonObject propObject;
2406     ISD::propertyToJson(prop, propObject, false);
2407     sendResponse(commands[DEVICE_PROPERTY_ADD], propObject);
2408 }
2409 
2410 ///////////////////////////////////////////////////////////////////////////////////////////
2411 ///
2412 ///////////////////////////////////////////////////////////////////////////////////////////
2413 void Message::processDeleteProperty(INDI::Property prop)
2414 {
2415     QJsonObject payload =
2416     {
2417         {"device", prop.getDeviceName()},
2418         {"name", prop.getName()}
2419     };
2420 
2421     sendResponse(commands[DEVICE_PROPERTY_REMOVE], payload);
2422 }
2423 
2424 ///////////////////////////////////////////////////////////////////////////////////////////
2425 ///
2426 ///////////////////////////////////////////////////////////////////////////////////////////
2427 void Message::processMessage(const QSharedPointer<ISD::GenericDevice> &device, int id)
2428 {
2429     if (Options::ekosLiveNotifications() == false)
2430         return;
2431 
2432     auto message = QString::fromStdString(device->getBaseDevice().messageQueue(id));
2433     QJsonObject payload =
2434     {
2435         {"device", device->getDeviceName()},
2436         {"message", message}
2437     };
2438 
2439     sendResponse(commands[DEVICE_MESSAGE], payload);
2440 }
2441 
2442 ///////////////////////////////////////////////////////////////////////////////////////////
2443 ///
2444 ///////////////////////////////////////////////////////////////////////////////////////////
2445 void Message::processUpdateProperty(INDI::Property prop)
2446 {
2447     if (m_PropertySubscriptions.contains(prop.getDeviceName()))
2448     {
2449         QSet<QString> subProps = m_PropertySubscriptions[prop.getDeviceName()];
2450         if (subProps.contains(prop.getName()))
2451         {
2452             m_PendingProperties.remove(prop);
2453             m_PendingProperties.insert(prop);
2454         }
2455     }
2456 }
2457 
2458 ///////////////////////////////////////////////////////////////////////////////////////////
2459 ///
2460 ///////////////////////////////////////////////////////////////////////////////////////////
2461 void Message::clearPendingProperties()
2462 {
2463     m_PendingProperties.clear();
2464 }
2465 
2466 ///////////////////////////////////////////////////////////////////////////////////////////
2467 ///
2468 ///////////////////////////////////////////////////////////////////////////////////////////
2469 void Message::sendPendingProperties()
2470 {
2471     for (auto &prop : m_PendingProperties)
2472     {
2473         if (prop->isValid())
2474         {
2475             QJsonObject propObject;
2476             ISD::propertyToJson(*prop, propObject);
2477             sendResponse(commands[DEVICE_PROPERTY_GET], propObject);
2478         }
2479     }
2480 
2481     m_PendingProperties.clear();
2482 }
2483 
2484 ///////////////////////////////////////////////////////////////////////////////////////////
2485 ///
2486 ///////////////////////////////////////////////////////////////////////////////////////////
2487 void Message::sendModuleState(const QString &name)
2488 {
2489     if (name == "Capture")
2490     {
2491         QJsonObject captureState = {{ "status", getCaptureStatusString(m_Manager->captureModule()->status(), false)}};
2492         sendResponse(commands[NEW_CAPTURE_STATE], captureState);
2493         sendCaptureSequence(m_Manager->captureModule()->getSequence());
2494     }
2495     else if (name == "Mount")
2496     {
2497         QJsonObject mountState =
2498         {
2499             {"status", m_Manager->mountStatus->getStatusText()},
2500             {"target", m_Manager->capturePreview->mountTarget->text()},
2501             {"slewRate", m_Manager->mountModule()->slewRate()},
2502             {"pierSide", m_Manager->mountModule()->pierSide()}
2503         };
2504 
2505         sendResponse(commands[NEW_MOUNT_STATE], mountState);
2506     }
2507     else if (name == "Focus")
2508     {
2509         QJsonObject focusState = {{ "status", getFocusStatusString(m_Manager->focusModule()->status(), false)}};
2510         sendResponse(commands[NEW_FOCUS_STATE], focusState);
2511     }
2512     else if (name == "Guide")
2513     {
2514         QJsonObject guideState = {{ "status", getGuideStatusString(m_Manager->guideModule()->status(), false)}};
2515         sendResponse(commands[NEW_GUIDE_STATE], guideState);
2516     }
2517     else if (name == "Align")
2518     {
2519         // Align State
2520         QJsonObject alignState =
2521         {
2522             {"status", getAlignStatusString(m_Manager->alignModule()->status(), false)}
2523         };
2524         sendResponse(commands[NEW_ALIGN_STATE], alignState);
2525 
2526         // Align settings
2527         sendAlignSettings(m_Manager->alignModule()->getAllSettings());
2528 
2529         Ekos::PolarAlignmentAssistant *paa = m_Manager->alignModule()->polarAlignmentAssistant();
2530         if (paa)
2531         {
2532             // Polar State
2533             QTextDocument doc;
2534             doc.setHtml(paa->getPAHMessage());
2535             QJsonObject polarState =
2536             {
2537                 {"stage", paa->getPAHStageString(false)},
2538                 {"enabled", paa->isEnabled()},
2539                 {"message", doc.toPlainText()},
2540             };
2541             sendResponse(commands[NEW_POLAR_STATE], polarState);
2542         }
2543     }
2544 }
2545 
2546 ///////////////////////////////////////////////////////////////////////////////////////////
2547 ///
2548 ///////////////////////////////////////////////////////////////////////////////////////////
2549 QObject *Message::findObject(const QString &name)
2550 {
2551     QObject *object {nullptr};
2552     // Check for manager itself
2553     if (name == "Manager")
2554         return m_Manager;
2555     // Try Manager first
2556     object = m_Manager->findChild<QObject *>(name);
2557     if (object)
2558         return object;
2559     // Then INDI Listener
2560     object = INDIListener::Instance()->findChild<QObject *>(name);
2561     if (object)
2562         return object;
2563     // Finally KStars
2564     // N.B. This does not include indepdent objects with their parent set to null (e.g. FITSViewer)
2565     object = KStars::Instance()->findChild<QObject *>(name);
2566     return object;
2567 }
2568 
2569 ///////////////////////////////////////////////////////////////////////////////////////////
2570 ///
2571 ///////////////////////////////////////////////////////////////////////////////////////////
2572 bool Message::parseArgument(QVariant::Type type, const QVariant &arg, QGenericArgument &genericArg, SimpleTypes &types)
2573 {
2574     QGenericArgument genericArgument;
2575 
2576     switch (type)
2577     {
2578         case QVariant::Type::Int:
2579             types.number_integer = arg.toInt();
2580             genericArg = Q_ARG(int, types.number_integer);
2581             return true;
2582         case QVariant::Type::UInt:
2583             types.number_unsigned_integer = arg.toUInt();
2584             genericArg = Q_ARG(uint, types.number_unsigned_integer);
2585             return true;
2586         case QVariant::Type::LongLong:
2587             types.number_integer = arg.toLongLong();
2588             genericArg = Q_ARG(int, types.number_integer);
2589             return true;
2590         case QVariant::Type::ULongLong:
2591             types.number_unsigned_integer = arg.toULongLong();
2592             genericArg = Q_ARG(uint, types.number_unsigned_integer);
2593             return true;
2594         case QVariant::Type::Double:
2595             types.number_double = arg.toDouble();
2596             genericArg = Q_ARG(double, types.number_double);
2597             return true;
2598         case QVariant::Type::Bool:
2599             types.boolean = arg.toBool();
2600             genericArg = Q_ARG(bool, types.boolean);
2601             return true;
2602         case QVariant::Type::String:
2603             types.text = arg.toString();
2604             genericArg = Q_ARG(QString, types.text);
2605             return true;
2606         case QVariant::Type::Url:
2607             types.url = arg.toUrl();
2608             genericArg = Q_ARG(QUrl, types.url);
2609             return true;
2610         default:
2611             break;
2612     }
2613 
2614     return false;
2615 }
2616 
2617 ///////////////////////////////////////////////////////////////////////////////////////////
2618 ///
2619 ///////////////////////////////////////////////////////////////////////////////////////////
2620 void Message::invokeMethod(QObject *context, const QJsonObject &payload)
2621 {
2622     QList<QGenericArgument> argsList;
2623     QList<SimpleTypes> typesList;
2624 
2625     auto name = payload["name"].toString().toLatin1();
2626 
2627     if (payload.contains("args"))
2628     {
2629         QJsonArray args = payload["args"].toArray();
2630 
2631         for (auto oneArg : args)
2632         {
2633             auto argObject = oneArg.toObject();
2634             QGenericArgument genericArgument;
2635             SimpleTypes genericType;
2636             argsList.append(genericArgument);
2637             typesList.append(genericType);
2638             if (parseArgument(static_cast<QVariant::Type>(argObject["type"].toInt()), argObject["value"].toVariant(), argsList.back(),
2639                               typesList.last()) == false)
2640             {
2641                 argsList.pop_back();
2642                 typesList.pop_back();
2643             }
2644         }
2645 
2646         switch (argsList.size())
2647         {
2648             case 1:
2649                 QMetaObject::invokeMethod(context, name, argsList[0]);
2650                 break;
2651             case 2:
2652                 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1]);
2653                 break;
2654             case 3:
2655                 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1], argsList[2]);
2656                 break;
2657             case 4:
2658                 QMetaObject::invokeMethod(context, name, argsList[0], argsList[1], argsList[2], argsList[3]);
2659                 break;
2660             default:
2661                 break;
2662         }
2663     }
2664     else
2665     {
2666         QMetaObject::invokeMethod(context, name);
2667     }
2668 }
2669 
2670 }