File indexing completed on 2024-07-21 06:28:17

0001 /*
0002     SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "clientmanagerlite.h"
0007 
0008 #include "basedevice.h"
0009 #include "indicom.h"
0010 #include "inditelescopelite.h"
0011 #include "kspaths.h"
0012 #include "kstarslite.h"
0013 #include "Options.h"
0014 #include "skymaplite.h"
0015 #include "fitsviewer/fitsdata.h"
0016 #include "kstarslite/imageprovider.h"
0017 #include "kstarslite/skyitems/telescopesymbolsitem.h"
0018 
0019 #include <KLocalizedString>
0020 
0021 #include <QApplication>
0022 #include <QDebug>
0023 #include <QFileDialog>
0024 #include <QImageReader>
0025 #include <QJsonArray>
0026 #include <QJsonDocument>
0027 #include <QProcess>
0028 #include <QQmlApplicationEngine>
0029 #include <QQmlContext>
0030 #include <QTemporaryFile>
0031 
0032 const char *libindi_strings_context = "string from libindi, used in the config dialog";
0033 
0034 #ifdef Q_OS_ANDROID
0035 #include "libraw/libraw.h"
0036 #endif
0037 
0038 DeviceInfoLite::DeviceInfoLite(INDI::BaseDevice *dev) : device(dev)
0039 {
0040 }
0041 
0042 DeviceInfoLite::~DeviceInfoLite()
0043 {
0044 }
0045 
0046 ClientManagerLite::ClientManagerLite(QQmlContext& main_context) : context(main_context)
0047 {
0048 #ifdef ANDROID
0049     defaultImageType      = ".jpeg";
0050     defaultImagesLocation = QDir(KSPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + qAppName()).path();
0051 #endif
0052     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeNS");
0053     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeWE");
0054     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeCommand");
0055     context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
0056 }
0057 
0058 ClientManagerLite::~ClientManagerLite()
0059 {
0060 }
0061 
0062 bool ClientManagerLite::setHost(const QString &ip, unsigned int port)
0063 {
0064     if (!isConnected())
0065     {
0066         setServer(ip.toStdString().c_str(), port);
0067         qDebug() << ip << port;
0068         if (connectServer())
0069         {
0070             setConnectedHost(ip + ':' + QString::number(port));
0071             //Update last used server and port
0072             setLastUsedServer(ip);
0073             setLastUsedPort(port);
0074 
0075             return true;
0076         }
0077     }
0078     return false;
0079 }
0080 
0081 void ClientManagerLite::disconnectHost()
0082 {
0083     disconnectServer();
0084     clearDevices();
0085     setConnectedHost("");
0086 }
0087 
0088 void ClientManagerLite::getWebManagerProfiles(const QString &ip, unsigned int port)
0089 {
0090     if (webMProfilesReply.get() != nullptr)
0091         return;
0092 
0093     QString urlStr(QString("http://%1:%2/api/profiles").arg(ip).arg(port));
0094     QNetworkRequest request { QUrl(urlStr) };
0095 
0096     webMProfilesReply.reset(manager.get(request));
0097     connect(webMProfilesReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
0098             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
0099     connect(webMProfilesReply.get(), &QNetworkReply::finished, this, &ClientManagerLite::webManagerReplyFinished);
0100     setLastUsedServer(ip);
0101     setLastUsedWebManagerPort(port);
0102 }
0103 
0104 void ClientManagerLite::startWebManagerProfile(const QString &profile)
0105 {
0106     if (webMStartProfileReply.get() != nullptr)
0107         return;
0108 
0109     QString urlStr("http://%1:%2/api/server/start/%3");
0110 
0111     urlStr = urlStr.arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()).arg(profile);
0112     QNetworkRequest request { QUrl(urlStr) };
0113 
0114     webMStartProfileReply.reset(manager.post(request, QByteArray()));
0115     connect(webMStartProfileReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
0116             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
0117     connect(webMStartProfileReply.get(), &QNetworkReply::finished,
0118             this, &ClientManagerLite::webManagerReplyFinished);
0119 }
0120 
0121 void ClientManagerLite::stopWebManagerProfile()
0122 {
0123     if (webMStopProfileReply.get() != nullptr)
0124         return;
0125 
0126     QString urlStr(QString("http://%1:%2/api/server/stop").arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()));
0127     QNetworkRequest request { QUrl(urlStr) };
0128 
0129     webMStopProfileReply.reset(manager.post(request, QByteArray()));
0130     connect(webMStopProfileReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
0131             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
0132     connect(webMStopProfileReply.get(), &QNetworkReply::finished,
0133             this, &ClientManagerLite::webManagerReplyFinished);
0134 }
0135 
0136 void ClientManagerLite::webManagerReplyError(QNetworkReply::NetworkError code)
0137 {
0138     if (webMProfilesReply.get() != nullptr)
0139     {
0140         qWarning("Web Manager profile query error: %d", (int)code);
0141         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
0142         webMProfilesReply.release()->deleteLater();
0143         return;
0144     }
0145     if (webMStatusReply.get() != nullptr)
0146     {
0147         qWarning("Web Manager status query error: %d", (int)code);
0148         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
0149         webMStatusReply.release()->deleteLater();
0150         return;
0151     }
0152     if (webMStopProfileReply.get() != nullptr)
0153     {
0154         qWarning("Web Manager stop active profile error: %d", (int)code);
0155         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
0156         webMStopProfileReply.release()->deleteLater();
0157         return;
0158     }
0159     if (webMStartProfileReply.get() != nullptr)
0160     {
0161         qWarning("Web Manager start active profile error: %d", (int)code);
0162         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
0163         webMStartProfileReply.release()->deleteLater();
0164         return;
0165     }
0166 }
0167 
0168 void ClientManagerLite::webManagerReplyFinished()
0169 {
0170     // Web Manager profile query
0171     if (webMProfilesReply.get() != nullptr)
0172     {
0173         QByteArray responseData = webMProfilesReply->readAll();
0174         QJsonDocument json = QJsonDocument::fromJson(responseData);
0175 
0176         webMProfilesReply.release()->deleteLater();
0177         if (!json.isArray())
0178         {
0179             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
0180             return;
0181         }
0182         QJsonArray array = json.array();
0183 
0184         webMProfiles.clear();
0185         for (int i = 0; i < array.size(); ++i)
0186         {
0187             if (array.at(i).isObject() && array.at(i).toObject().contains("name"))
0188             {
0189                 webMProfiles += array.at(i).toObject()["name"].toString();
0190             }
0191         }
0192         // Send a query for the network status
0193         QString urlStr(QString("http://%1:%2/api/server/status").arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()));
0194         QNetworkRequest request { QUrl(urlStr) };
0195 
0196         webMStatusReply.reset(manager.get(request));
0197         connect(webMStatusReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
0198                 this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
0199         connect(webMStatusReply.get(), &QNetworkReply::finished, this, &ClientManagerLite::webManagerReplyFinished);
0200         return;
0201     }
0202     // Web Manager status query
0203     if (webMStatusReply.get() != nullptr)
0204     {
0205         QByteArray responseData = webMStatusReply->readAll();
0206         QJsonDocument json = QJsonDocument::fromJson(responseData);
0207 
0208         webMStatusReply.release()->deleteLater();
0209         if (!json.isArray() || json.array().size() != 1 || !json.array().at(0).isObject())
0210         {
0211             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
0212             return;
0213         }
0214         QJsonObject object = json.array().at(0).toObject();
0215 
0216         // Check the response
0217         if (!object.contains("status") || !object.contains("active_profile"))
0218         {
0219             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
0220             return;
0221         }
0222         QString statusStr = object["status"].toString();
0223         QString activeProfileStr = object["active_profile"].toString();
0224 
0225         indiControlPage->setProperty("webMBrowserButtonVisible", true);
0226         indiControlPage->setProperty("webMStatusTextVisible", true);
0227         if (statusStr == "True")
0228         {
0229             // INDI Server is running (online)
0230             indiControlPage->setProperty("webMStatusText", i18n("Web Manager Status: Online"));
0231             indiControlPage->setProperty("webMActiveProfileText",
0232                                          i18n("Active Profile: %1", activeProfileStr));
0233             indiControlPage->setProperty("webMActiveProfileLayoutVisible", true);
0234             indiControlPage->setProperty("webMProfileListVisible", false);
0235         } else {
0236             // INDI Server is not running (offline)
0237             indiControlPage->setProperty("webMStatusText", i18n("Web Manager Status: Offline"));
0238             indiControlPage->setProperty("webMActiveProfileLayoutVisible", false);
0239             context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
0240             indiControlPage->setProperty("webMProfileListVisible", true);
0241         }
0242         return;
0243     }
0244     // Web Manager stop active profile
0245     if (webMStopProfileReply.get() != nullptr)
0246     {
0247         webMStopProfileReply.release()->deleteLater();
0248         indiControlPage->setProperty("webMStatusText", QString(i18n("Web Manager Status:")+' '+i18n("Offline")));
0249         indiControlPage->setProperty("webMStatusTextVisible", true);
0250         indiControlPage->setProperty("webMActiveProfileLayoutVisible", false);
0251         context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
0252         indiControlPage->setProperty("webMProfileListVisible", true);
0253         return;
0254     }
0255     // Web Manager start active profile
0256     if (webMStartProfileReply.get() != nullptr)
0257     {
0258         webMStartProfileReply.release()->deleteLater();
0259         // Send a query for the network status
0260         QString urlStr("http://%1:%2/api/server/status");
0261 
0262         urlStr = urlStr.arg(getLastUsedServer()).arg(getLastUsedWebManagerPort());
0263         QNetworkRequest request { QUrl(urlStr) };
0264 
0265         webMStatusReply.reset(manager.get(request));
0266         connect(webMStatusReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
0267                 this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
0268         connect(webMStatusReply.get(), &QNetworkReply::finished,
0269                 this, &ClientManagerLite::webManagerReplyFinished);
0270         // Connect to the server automatically
0271         QMetaObject::invokeMethod(indiControlPage, "connectIndiServer");
0272         return;
0273     }
0274 }
0275 
0276 TelescopeLite *ClientManagerLite::getTelescope()
0277 {
0278     for (auto& devInfo : m_devices)
0279     {
0280         if (devInfo->telescope.get())
0281         {
0282             return devInfo->telescope.get();
0283         }
0284     }
0285     return nullptr;
0286 }
0287 
0288 void ClientManagerLite::setConnectedHost(const QString &connectedHost)
0289 {
0290     m_connectedHost = connectedHost;
0291     setConnected(m_connectedHost.size() > 0);
0292 
0293     emit connectedHostChanged(connectedHost);
0294 }
0295 
0296 void ClientManagerLite::setConnected(bool connected)
0297 {
0298     m_connected = connected;
0299     emit connectedChanged(connected);
0300 }
0301 
0302 QString ClientManagerLite::syncLED(const QString &device, const QString &property, const QString &name)
0303 {
0304     foreach (DeviceInfoLite *devInfo, m_devices)
0305     {
0306         if (devInfo->device->getDeviceName() == device)
0307         {
0308             INDI::Property prop = devInfo->device->getProperty(property.toLatin1());
0309             if (prop)
0310             {
0311                 IPState state = prop->getState();
0312                 if (!name.isEmpty())
0313                 {
0314                     ILight *lights = prop->getLight()->lp;
0315                     for (int i = 0; i < prop->getLight()->nlp; i++)
0316                     {
0317                         if (lights[i].name == name)
0318                         {
0319                             state = lights[i].s;
0320                             break;
0321                         }
0322                         if (i == prop->getLight()->nlp - 1)
0323                             return ""; // no Light with name "name" found so return empty string
0324                     }
0325                 }
0326                 switch (state)
0327                 {
0328                     case IPS_IDLE:
0329                         return "grey";
0330                         break;
0331 
0332                     case IPS_OK:
0333                         return "green";
0334                         break;
0335 
0336                     case IPS_BUSY:
0337                         return "yellow";
0338                         break;
0339 
0340                     case IPS_ALERT:
0341                         return "red";
0342                         break;
0343 
0344                     default:
0345                         return "grey";
0346                         break;
0347                 }
0348             }
0349         }
0350     }
0351     return "grey";
0352 }
0353 
0354 void ClientManagerLite::buildTextGUI(Property *property)
0355 {
0356     auto tvp = property->getText();
0357     if (!tvp)
0358         return;
0359 
0360     for (const auto &it: *tvp)
0361     {
0362         QString name  = it.getName();
0363         QString label = it.getLabel();
0364         QString text  = it.getText();
0365         bool read     = false;
0366         bool write    = false;
0367         /*if (tp->label[0])
0368                 label = i18nc(libindi_strings_context, itp->label);
0369 
0370             if (label == "(I18N_EMPTY_MESSAGE)")
0371                 label = itp->label;*/
0372 
0373         if (label.isEmpty())
0374             label = tvp->getName(); // #PS: it should be it.getName() ?
0375         /*label = i18nc(libindi_strings_context, itp->name);
0376 
0377             if (label == "(I18N_EMPTY_MESSAGE)")*/
0378 
0379         //setupElementLabel();
0380 
0381         /*if (tp->text[0])
0382                 text = i18nc(libindi_strings_context, tp->text);*/
0383 
0384         switch (property->getPermission())
0385         {
0386             case IP_RW:
0387                 read  = true;
0388                 write = true;
0389                 break;
0390 
0391             case IP_RO:
0392                 read  = true;
0393                 write = false;
0394                 break;
0395 
0396             case IP_WO:
0397                 read  = false;
0398                 write = true;
0399                 break;
0400         }
0401         emit createINDIText(property->getDeviceName(), property->getName(), label, name, text, read, write);
0402     }
0403 }
0404 
0405 void ClientManagerLite::buildNumberGUI(Property *property)
0406 {
0407     auto nvp = property->getNumber();
0408     if (!nvp)
0409         return;
0410 
0411     //for (int i = 0; i < nvp->nnp; i++)
0412     for (const auto &it: nvp)
0413     {
0414         bool scale = false;
0415         char iNumber[MAXINDIFORMAT];
0416 
0417         QString name  = it.getName();
0418         QString label = it.getLabel();
0419         QString text;
0420         bool read  = false;
0421         bool write = false;
0422         /*if (tp->label[0])
0423                 label = i18nc(libindi_strings_context, itp->label);
0424 
0425             if (label == "(I18N_EMPTY_MESSAGE)")
0426                 label = itp->label;*/
0427 
0428         if (label.isEmpty())
0429             label = np->getName();
0430 
0431         numberFormat(iNumber, np.getFormat(), np.getValue());
0432         text = iNumber;
0433 
0434         /*label = i18nc(libindi_strings_context, itp->name);
0435 
0436             if (label == "(I18N_EMPTY_MESSAGE)")*/
0437 
0438         //setupElementLabel();
0439 
0440         /*if (tp->text[0])
0441                 text = i18nc(libindi_strings_context, tp->text);*/
0442 
0443         if (it.getStep() != 0 && (it.getMax() - it.getMin()) / it.getStep() <= 100)
0444             scale = true;
0445 
0446         switch (property->getPermission())
0447         {
0448             case IP_RW:
0449                 read  = true;
0450                 write = true;
0451                 break;
0452 
0453             case IP_RO:
0454                 read  = true;
0455                 write = false;
0456                 break;
0457 
0458             case IP_WO:
0459                 read  = false;
0460                 write = true;
0461                 break;
0462         }
0463         emit createINDINumber(property->getDeviceName(), property->getName(), label, name, text, read, write,
0464                                 scale);
0465     }
0466 }
0467 
0468 void ClientManagerLite::buildMenuGUI(INDI::Property property)
0469 {
0470     /*QStringList menuOptions;
0471     QString oneOption;
0472     int onItem=-1;*/
0473     auto svp = property->getSwitch();
0474 
0475     if (!svp)
0476         return;
0477 
0478     for (auto &it: *svp)
0479     {
0480         buildSwitch(false, &it, property);
0481 
0482         /*if (tp->s == ISS_ON)
0483             onItem = i;
0484 
0485         lp = new INDI_E(this, dataProp);
0486 
0487         lp->buildMenuItem(tp);
0488 
0489         oneOption = i18nc(libindi_strings_context, lp->getLabel().toUtf8());
0490 
0491         if (oneOption == "(I18N_EMPTY_MESSAGE)")
0492             oneOption =  lp->getLabel().toUtf8();
0493 
0494         menuOptions.append(oneOption);
0495 
0496         elementList.append(lp);*/
0497     }
0498 }
0499 
0500 void ClientManagerLite::buildSwitchGUI(INDI::Property property, PGui guiType)
0501 {
0502     auto svp = property->getSwitch();
0503     bool exclusive = false;
0504 
0505     if (!svp)
0506         return;
0507 
0508     if (guiType == PG_BUTTONS)
0509     {
0510         if (svp->getRule() == ISR_1OFMANY)
0511             exclusive = true;
0512         else
0513             exclusive = false;
0514     }
0515     else if (guiType == PG_RADIO)
0516         exclusive = false;
0517 
0518     /*if (svp->p != IP_RO)
0519         QObject::connect(groupB, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(newSwitch(QAbstractButton*)));*/
0520 
0521     for (auto &it: *svp)
0522     {
0523         buildSwitch(true, &it, property, exclusive, guiType);
0524     }
0525 }
0526 
0527 void ClientManagerLite::buildSwitch(bool buttonGroup, ISwitch *sw, INDI::Property property, bool exclusive,
0528                                     PGui guiType)
0529 {
0530     QString name  = sw->name;
0531     QString label = sw->label; //i18nc(libindi_strings_context, sw->label);
0532 
0533     if (label == "(I18N_EMPTY_MESSAGE)")
0534         label = sw->label;
0535 
0536     if (label.isEmpty())
0537         label = sw->name;
0538     //label = i18nc(libindi_strings_context, sw->name);
0539 
0540     if (label == "(I18N_EMPTY_MESSAGE)")
0541         label = sw->name;
0542 
0543     if (!buttonGroup)
0544     {
0545         bool isSelected = false;
0546         if (sw->s == ISS_ON)
0547             isSelected = true;
0548         emit createINDIMenu(property->getDeviceName(), property->getName(), label, sw->name, isSelected);
0549         return;
0550     }
0551 
0552     bool enabled = true;
0553 
0554     if (sw->svp->p == IP_RO)
0555         enabled = (sw->s == ISS_ON);
0556 
0557     switch (guiType)
0558     {
0559         case PG_BUTTONS:
0560             emit createINDIButton(property->getDeviceName(), property->getName(), label, name, true, true, exclusive,
0561                                   sw->s == ISS_ON, enabled);
0562             break;
0563 
0564         case PG_RADIO:
0565             emit createINDIRadio(property->getDeviceName(), property->getName(), label, name, true, true, exclusive,
0566                                  sw->s == ISS_ON, enabled);
0567         /*check_w = new QCheckBox(label, guiProp->getGroup()->getContainer());
0568         groupB->addButton(check_w);
0569 
0570         syncSwitch();
0571 
0572         guiProp->addWidget(check_w);
0573 
0574         check_w->show();
0575 
0576         if (sw->svp->p == IP_RO)
0577             check_w->setEnabled(sw->s == ISS_ON);
0578 
0579         break;*/
0580 
0581         default:
0582             break;
0583     }
0584 }
0585 
0586 void ClientManagerLite::buildLightGUI(INDI::Property property)
0587 {
0588     auto lvp = property->getLight();
0589 
0590     if (!lvp)
0591         return;
0592 
0593     for (auto &it: *lvp)
0594     {
0595         QString name  = it.getName();
0596         QString label = i18nc(libindi_strings_context, it.getLabel());
0597 
0598         if (label == "(I18N_EMPTY_MESSAGE)")
0599             label = it.getLabel();
0600 
0601         if (label.isEmpty())
0602             label = i18nc(libindi_strings_context, it.getName());
0603 
0604         if (label == "(I18N_EMPTY_MESSAGE)")
0605             label = it.getName();;
0606 
0607         emit createINDILight(property->getDeviceName(), property->getName(), label, name);
0608     }
0609 }
0610 
0611 /*void ClientManagerLite::buildBLOBGUI(INDI::Property property) {
0612     IBLOBVectorProperty *ibp = property->getBLOB();
0613 
0614     QString name  = ibp->name;
0615     QString label = i18nc(libindi_strings_context, ibp->label);
0616 
0617     if (label == "(I18N_EMPTY_MESSAGE)")
0618         label = ibp->label;
0619 
0620     if (label.isEmpty())
0621         label = i18nc(libindi_strings_context, ibp->name);
0622 
0623     if (label == "(I18N_EMPTY_MESSAGE)")
0624         label = ibp->name;
0625 
0626     text = i18n("INDI DATA STREAM");
0627 
0628     switch (property->getPermission())
0629     {
0630     case IP_RW:
0631         setupElementRead(ELEMENT_READ_WIDTH);
0632         setupElementWrite(ELEMENT_WRITE_WIDTH);
0633         setupBrowseButton();
0634         break;
0635 
0636     case IP_RO:
0637         setupElementRead(ELEMENT_FULL_WIDTH);
0638         break;
0639 
0640     case IP_WO:
0641         setupElementWrite(ELEMENT_FULL_WIDTH);
0642         setupBrowseButton();
0643         break;
0644     }
0645 
0646     guiProp->addLayout(EHBox);
0647 }*/
0648 
0649 void ClientManagerLite::sendNewINDISwitch(const QString &deviceName, const QString &propName, const QString &name)
0650 {
0651     foreach (DeviceInfoLite *devInfo, m_devices)
0652     {
0653         INDI::BaseDevice *device = devInfo->device;
0654         if (device->getDeviceName() == deviceName)
0655         {
0656             auto property = device->getProperty(propName.toLatin1());
0657             if (property)
0658             {
0659                 auto svp = property->getSwitch();
0660 
0661                 if (!svp)
0662                     return;
0663 
0664                 auto sp = svp->findWidgetByName(name.toLatin1().constData());
0665 
0666                 if (!sp)
0667                     return;
0668 
0669                 if (sp->isNameMatch("CONNECT"))
0670                 {
0671                     svp->reset();
0672                     sp->setState(ISS_ON);
0673                 }
0674 
0675                 if (svp->getRule() == ISR_1OFMANY)
0676                 {
0677                     svp->reset();
0678                     sp->setState(ISS_ON);
0679                 }
0680                 else
0681                 {
0682                     if (svp->getRule() == ISR_ATMOST1)
0683                     {
0684                         ISState prev_state = sp->getState();
0685                         svp->reset();
0686                         sp->setState(prev_state);
0687                     }
0688 
0689                     sp->setState(sp->getState() == ISS_ON ? ISS_OFF : ISS_ON);
0690                 }
0691                 sendNewSwitch(svp);
0692             }
0693         }
0694     }
0695 }
0696 
0697 void ClientManagerLite::sendNewINDINumber(const QString &deviceName, const QString &propName, const QString &numberName,
0698                                           double value)
0699 {
0700     foreach (DeviceInfoLite *devInfo, m_devices)
0701     {
0702         INDI::BaseDevice *device = devInfo->device;
0703         if (device->getDeviceName() == deviceName)
0704         {
0705             auto np = device->getNumber(propName.toLatin1());
0706             if (np)
0707             {
0708                 auto n = np->findWIdgetByName(numberName.toLatin1());
0709                 if (n)
0710                 {
0711                     n->setValue(value);
0712                     sendNewNumber(np);
0713                     return;
0714                 }
0715 
0716                 qDebug() << "Could not find property: " << deviceName << "." << propName << "." << numberName;
0717                 return;
0718             }
0719 
0720             qDebug() << "Could not find property: " << deviceName << "." << propName << "." << numberName;
0721             return;
0722         }
0723     }
0724 }
0725 
0726 void ClientManagerLite::sendNewINDIText(const QString &deviceName, const QString &propName, const QString &fieldName,
0727                                         const QString &text)
0728 {
0729     foreach (DeviceInfoLite *devInfo, m_devices)
0730     {
0731         INDI::BaseDevice *device = devInfo->device;
0732         if (device->getDeviceName() == deviceName)
0733         {
0734             auto tp = device->getText(propName.toLatin1());
0735             if (tp)
0736             {
0737                 auto t = tp->findWidgetByName(fieldName.toLatin1());
0738                 if (t)
0739                 {
0740                     t.setText(text.toLatin1().data());
0741                     sendNewText(tp);
0742                     return;
0743                 }
0744 
0745                 qDebug() << "Could not find property: " << deviceName << "." << propName << "." << fieldName;
0746                 return;
0747             }
0748 
0749             qDebug() << "Could not find property: " << deviceName << "." << propName << "." << fieldName;
0750             return;
0751         }
0752     }
0753 }
0754 
0755 void ClientManagerLite::sendNewINDISwitch(const QString &deviceName, const QString &propName, int index)
0756 {
0757     if (index >= 0)
0758     {
0759         foreach (DeviceInfoLite *devInfo, m_devices)
0760         {
0761             INDI::BaseDevice *device = devInfo->device;
0762             if (device->getDeviceName() == deviceName)
0763             {
0764                 auto property = device->getProperty(propName.toStdString().c_str());
0765                 if (property)
0766                 {
0767                     auto svp = property->getSwitch();
0768 
0769                     if (!svp)
0770                         return;
0771 
0772                     if (index >= svp->count())
0773                         return;
0774 
0775                     auto sp = svp->at(index);
0776 
0777                     sp->reset();
0778                     sp->setState(ISS_ON);
0779 
0780                     sendNewSwitch(svp);
0781                 }
0782             }
0783         }
0784     }
0785 }
0786 
0787 bool ClientManagerLite::saveDisplayImage()
0788 {
0789     QString const dateTime   = QDateTime::currentDateTime().toString("dd-MM-yyyy-hh-mm-ss");
0790     QString const fileEnding = "kstars-lite-" + dateTime;
0791     QDir const dir(KSPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + qAppName()).path();
0792     QFileInfo const file(QString("%1/%2.jpeg").arg(dir.path()).arg(fileEnding));
0793 
0794     QString const filename = QFileDialog::getSaveFileName(
0795                 QApplication::activeWindow(), i18nc("@title:window", "Save Image"), file.filePath(),
0796                 i18n("JPEG (*.jpeg);;JPG (*.jpg);;PNG (*.png);;BMP (*.bmp)"));
0797 
0798     if (!filename.isEmpty())
0799     {
0800         if (displayImage.save(filename))
0801         {
0802             emit newINDIMessage("File " + filename + " was successfully saved");
0803             return true;
0804         }
0805     }
0806     emit newINDIMessage("Couldn't save file " + filename);
0807     return false;
0808 }
0809 
0810 bool ClientManagerLite::isDeviceConnected(const QString &deviceName)
0811 {
0812     INDI::BaseDevice *device = getDevice(deviceName.toStdString().c_str());
0813 
0814     if (device != nullptr)
0815     {
0816         return device->isConnected();
0817     }
0818     return false;
0819 }
0820 
0821 void ClientManagerLite::connectNewDevice(const QString& device_name)
0822 {
0823     connectDevice(qPrintable(device_name));
0824 }
0825 
0826 void ClientManagerLite::newDevice(INDI::BaseDevice *dp)
0827 {
0828     setBLOBMode(B_ALSO, dp->getDeviceName());
0829 
0830     QString deviceName = dp->getDeviceName();
0831 
0832     if (deviceName.isEmpty())
0833     {
0834         qWarning() << "Received invalid device with empty name! Ignoring the device...";
0835         return;
0836     }
0837 
0838     if (Options::verboseLogging())
0839         qDebug() << "Received new device " << deviceName;
0840     emit newINDIDevice(deviceName);
0841 
0842     DeviceInfoLite *devInfo = new DeviceInfoLite(dp);
0843     //Think about it!
0844     //devInfo->telescope.reset(new TelescopeLite(dp));
0845     m_devices.append(devInfo);
0846     // Connect the device automatically
0847     QTimer::singleShot(2000, [=]() { connectNewDevice(deviceName); });
0848 }
0849 
0850 void ClientManagerLite::removeDevice(BaseDevice *dp)
0851 {
0852     emit removeINDIDevice(QString(dp->getDeviceName()));
0853 }
0854 
0855 void ClientManagerLite::newProperty(INDI::Property property)
0856 {
0857     QString deviceName      = property->getDeviceName();
0858     QString name            = property->getName();
0859     QString groupName       = property->getGroupName();
0860     QString type            = QString(property->getType());
0861     QString label           = property->getLabel();
0862     DeviceInfoLite *devInfo = nullptr;
0863 
0864     foreach (DeviceInfoLite *di, m_devices)
0865     {
0866         if (di->device->getDeviceName() == deviceName)
0867         {
0868             devInfo = di;
0869         }
0870     }
0871 
0872     if (devInfo)
0873     {
0874         if ((!strcmp(property->getName(), "EQUATORIAL_EOD_COORD") ||
0875              !strcmp(property->getName(), "EQUATORIAL_COORD") ||
0876              !strcmp(property->getName(), "HORIZONTAL_COORD")))
0877         {
0878             devInfo->telescope.reset(new TelescopeLite(devInfo->device));
0879             m_telescope = devInfo->telescope.get();
0880             emit telescopeAdded(m_telescope);
0881             // The connected signal must be emitted for already connected scopes otherwise
0882             // the motion control page remains disabled.
0883             if (devInfo->telescope->isConnected())
0884             {
0885                 emit deviceConnected(devInfo->telescope->getDeviceName(), true);
0886                 emit telescopeConnected(devInfo->telescope.get());
0887             }
0888         }
0889     }
0890 
0891     emit newINDIProperty(deviceName, name, groupName, type, label);
0892     PGui guiType;
0893     switch (property->getType())
0894     {
0895         case INDI_SWITCH:
0896             if (property->getSwitch()->r == ISR_NOFMANY)
0897                 guiType = PG_RADIO;
0898             else if (property->getSwitch()->nsp > 4)
0899                 guiType = PG_MENU;
0900             else
0901                 guiType = PG_BUTTONS;
0902 
0903             if (guiType == PG_MENU)
0904                 buildMenuGUI(property);
0905             else
0906                 buildSwitchGUI(property, guiType);
0907             break;
0908 
0909         case INDI_TEXT:
0910             buildTextGUI(property);
0911             break;
0912         case INDI_NUMBER:
0913             buildNumberGUI(property);
0914             break;
0915 
0916         case INDI_LIGHT:
0917             buildLightGUI(property);
0918             break;
0919 
0920         case INDI_BLOB:
0921             //buildBLOBGUI();
0922             break;
0923 
0924         default:
0925             break;
0926     }
0927 }
0928 
0929 void ClientManagerLite::removeProperty(INDI::Property property)
0930 {
0931     if (property == nullptr)
0932         return;
0933 
0934     emit removeINDIProperty(property->getDeviceName(), property->getGroupName(), property->getName());
0935 
0936     DeviceInfoLite *devInfo = nullptr;
0937     foreach (DeviceInfoLite *di, m_devices)
0938     {
0939         if (di->device == property->getBaseDevice())
0940         {
0941             devInfo = di;
0942         }
0943     }
0944 
0945     if (devInfo)
0946     {
0947         if ((!strcmp(property->getName(), "EQUATORIAL_EOD_COORD") || !strcmp(property->getName(), "HORIZONTAL_COORD")))
0948         {
0949             if (devInfo->telescope.get() != nullptr)
0950             {
0951                 emit telescopeRemoved(devInfo->telescope.get());
0952             }
0953             KStarsLite::Instance()->map()->update(); // Update SkyMap if position of telescope is changed
0954         }
0955     }
0956 }
0957 
0958 void ClientManagerLite::newBLOB(IBLOB *bp)
0959 {
0960     processBLOBasCCD(bp);
0961     emit newLEDState(bp->bvp->device, bp->name);
0962 }
0963 
0964 bool ClientManagerLite::processBLOBasCCD(IBLOB *bp)
0965 {
0966     enum blobType
0967     {
0968         BLOB_IMAGE,
0969         BLOB_FITS,
0970         BLOB_CR2,
0971         BLOB_OTHER
0972     } BType;
0973 
0974     BType = BLOB_OTHER;
0975 
0976     QString format(bp->format);
0977     QString deviceName = bp->bvp->device;
0978 
0979     QByteArray fmt = QString(bp->format).toLower().remove('.').toUtf8();
0980 
0981     // If it's not FITS or an image, don't process it.
0982     if ((QImageReader::supportedImageFormats().contains(fmt)))
0983         BType = BLOB_IMAGE;
0984     else if (format.contains("fits"))
0985         BType = BLOB_FITS;
0986     else if (format.contains("cr2"))
0987         BType = BLOB_CR2;
0988 
0989     if (BType == BLOB_OTHER)
0990     {
0991         return false;
0992     }
0993 
0994     QString currentDir = QDir(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/" + qAppName()).path();
0995 
0996     int nr, n = 0;
0997     QTemporaryFile tmpFile(QDir::tempPath() + "/fitsXXXXXX");
0998 
0999     if (currentDir.endsWith('/'))
1000         currentDir.chop(1);
1001 
1002     if (QDir(currentDir).exists() == false)
1003         QDir().mkpath(currentDir);
1004 
1005     QString filename(currentDir + '/');
1006 
1007     if (true)
1008     {
1009         tmpFile.setAutoRemove(false);
1010 
1011         if (!tmpFile.open())
1012         {
1013             qDebug() << "ISD:CCD Error: Unable to open " << filename << endl;
1014             //emit BLOBUpdated(nullptr);
1015             return false;
1016         }
1017 
1018         QDataStream out(&tmpFile);
1019 
1020         for (nr = 0; nr < (int)bp->size; nr += n)
1021             n = out.writeRawData(static_cast<char *>(bp->blob) + nr, bp->size - nr);
1022 
1023         tmpFile.close();
1024 
1025         filename = tmpFile.fileName();
1026     }
1027     else
1028     {
1029         //Add support for batch mode
1030     }
1031 
1032     strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
1033     bp->aux2 = BLOBFilename;
1034 
1035     /* Test images
1036     BType = BLOB_IMAGE;
1037     filename = "/home/polaris/Pictures/351181_0.jpeg";
1038     */
1039     /*Test CR2
1040     BType = BLOB_CR2;
1041 
1042     filename = "/home/polaris/test.CR2";
1043     filename = "/storage/emulated/0/test.CR2";*/
1044 
1045     if (BType == BLOB_IMAGE || BType == BLOB_CR2)
1046     {
1047         if (BType == BLOB_CR2)
1048         {
1049 #ifdef Q_OS_ANDROID
1050             LibRaw RawProcessor;
1051 #define OUT RawProcessor.imgdata.params
1052             OUT.user_qual     = 0; // -q
1053             OUT.use_camera_wb = 1; // -w
1054             OUT.highlight     = 5; // -H
1055             OUT.bright        = 8; // -b
1056 #undef OUT
1057 
1058             QString rawFileName  = filename;
1059             rawFileName          = rawFileName.remove(0, rawFileName.lastIndexOf(QLatin1String("/")));
1060             QString templateName = QString("%1/%2.XXXXXX").arg(QDir::tempPath()).arg(rawFileName);
1061             QTemporaryFile jpgPreview(templateName);
1062             jpgPreview.setAutoRemove(false);
1063             jpgPreview.open();
1064             jpgPreview.close();
1065             QString jpeg_filename = jpgPreview.fileName();
1066 
1067             RawProcessor.open_file(filename.toLatin1());
1068             RawProcessor.unpack();
1069             RawProcessor.dcraw_process();
1070             RawProcessor.dcraw_ppm_tiff_writer(jpeg_filename.toLatin1());
1071             QFile::remove(filename);
1072             filename = jpeg_filename;
1073 #else
1074             if (QStandardPaths::findExecutable("dcraw").isEmpty() == false &&
1075                 QStandardPaths::findExecutable("cjpeg").isEmpty() == false)
1076             {
1077                 QProcess dcraw;
1078                 QString rawFileName  = filename;
1079                 rawFileName          = rawFileName.remove(0, rawFileName.lastIndexOf(QLatin1String("/")));
1080                 QString templateName = QString("%1/%2.XXXXXX").arg(QDir::tempPath()).arg(rawFileName);
1081                 QTemporaryFile jpgPreview(templateName);
1082                 jpgPreview.setAutoRemove(false);
1083                 jpgPreview.open();
1084                 jpgPreview.close();
1085                 QString jpeg_filename = jpgPreview.fileName();
1086 
1087                 QString cmd = QString("/bin/sh -c \"dcraw -c -q 0 -w -H 5 -b 8 %1 | cjpeg -quality 80 > %2\"")
1088                                   .arg(filename)
1089                                   .arg(jpeg_filename);
1090                 dcraw.start(cmd);
1091                 dcraw.waitForFinished();
1092                 QFile::remove(filename); //Delete raw
1093                 filename = jpeg_filename;
1094             }
1095             else
1096             {
1097                 emit newINDIMessage(
1098                     i18n("Unable to find dcraw and cjpeg. Please install the required tools to convert CR2 to JPEG."));
1099                 emit newINDIBLOBImage(deviceName, false);
1100                 return false;
1101             }
1102 #endif
1103         }
1104 
1105         displayImage.load(filename);
1106         QFile::remove(filename);
1107         KStarsLite::Instance()->imageProvider()->addImage("ccdPreview", displayImage);
1108         emit newINDIBLOBImage(deviceName, true);
1109         return true;
1110     }
1111     else if (BType == BLOB_FITS)
1112     {
1113         displayImage = FITSData::FITSToImage(filename);
1114         QFile::remove(filename);
1115         KStarsLite::Instance()->imageProvider()->addImage("ccdPreview", displayImage);
1116         emit newINDIBLOBImage(deviceName, true);
1117         return true;
1118     }
1119     emit newINDIBLOBImage(deviceName, false);
1120     return false;
1121 }
1122 
1123 void ClientManagerLite::newSwitch(ISwitchVectorProperty *svp)
1124 {
1125     for (int i = 0; i < svp->nsp; ++i)
1126     {
1127         ISwitch *sw = &(svp->sp[i]);
1128         if (QString(sw->name) == QString("CONNECT"))
1129         {
1130             emit deviceConnected(svp->device, sw->s == ISS_ON);
1131             if (m_telescope && m_telescope->getDeviceName() == svp->device)
1132             {
1133                 if (sw->s == ISS_ON)
1134                 {
1135                     emit telescopeConnected(m_telescope);
1136                 } else {
1137                     emit telescopeDisconnected();
1138                 }
1139             }
1140         }
1141         if (sw != nullptr)
1142         {
1143             emit newINDISwitch(svp->device, svp->name, sw->name, sw->s == ISS_ON);
1144             emit newLEDState(svp->device, svp->name);
1145         }
1146     }
1147 }
1148 
1149 void ClientManagerLite::newNumber(INumberVectorProperty *nvp)
1150 {
1151     if ((!strcmp(nvp->name, "EQUATORIAL_EOD_COORD") || !strcmp(nvp->name, "HORIZONTAL_COORD")))
1152     {
1153         KStarsLite::Instance()->map()->update(); // Update SkyMap if position of telescope is changed
1154     }
1155 
1156     QString deviceName = nvp->device;
1157     QString propName   = nvp->name;
1158     for (int i = 0; i < nvp->nnp; ++i)
1159     {
1160         INumber num = nvp->np[i];
1161         char buf[MAXINDIFORMAT];
1162         numberFormat(buf, num.format, num.value);
1163         QString numberName = num.name;
1164 
1165         emit newINDINumber(deviceName, propName, numberName, QString(buf).trimmed());
1166         emit newLEDState(deviceName, propName);
1167     }
1168 }
1169 
1170 void ClientManagerLite::newText(ITextVectorProperty *tvp)
1171 {
1172     QString deviceName = tvp->device;
1173     QString propName   = tvp->name;
1174     for (int i = 0; i < tvp->ntp; ++i)
1175     {
1176         IText text        = tvp->tp[i];
1177         QString fieldName = text.name;
1178 
1179         emit newINDIText(deviceName, propName, fieldName, text.text);
1180         emit newLEDState(deviceName, propName);
1181     }
1182 }
1183 
1184 void ClientManagerLite::newLight(ILightVectorProperty *lvp)
1185 {
1186     emit newINDILight(lvp->device, lvp->name);
1187     emit newLEDState(lvp->device, lvp->name);
1188 }
1189 
1190 void ClientManagerLite::newMessage(INDI::BaseDevice *dp, int messageID)
1191 {
1192     emit newINDIMessage(QString::fromStdString(dp->messageQueue(messageID)));
1193 }
1194 
1195 void ClientManagerLite::serverDisconnected(int exit_code)
1196 {
1197     Q_UNUSED(exit_code)
1198     clearDevices();
1199     setConnected(false);
1200 }
1201 
1202 void ClientManagerLite::clearDevices()
1203 {
1204     //Delete all created devices
1205     foreach (DeviceInfoLite *devInfo, m_devices)
1206     {
1207         if (devInfo->telescope.get() != nullptr)
1208         {
1209             emit telescopeRemoved(devInfo->telescope.get());
1210         }
1211         delete devInfo;
1212     }
1213     m_devices.clear();
1214 }
1215 
1216 QString ClientManagerLite::getLastUsedServer()
1217 {
1218     return Options::lastServer();
1219 }
1220 
1221 void ClientManagerLite::setLastUsedServer(const QString &server)
1222 {
1223     if (getLastUsedServer() != server)
1224     {
1225         Options::setLastServer(server);
1226         lastUsedServerChanged();
1227     }
1228 }
1229 
1230 int ClientManagerLite::getLastUsedPort()
1231 {
1232     return Options::lastServerPort();
1233 }
1234 
1235 void ClientManagerLite::setLastUsedPort(int port)
1236 {
1237     if (getLastUsedPort() != port)
1238     {
1239         Options::setLastServerPort(port);
1240         lastUsedPortChanged();
1241     }
1242 }
1243 
1244 int ClientManagerLite::getLastUsedWebManagerPort()
1245 {
1246     return Options::lastWebManagerPort();
1247 }
1248 
1249 void ClientManagerLite::setLastUsedWebManagerPort(int port)
1250 {
1251     if (getLastUsedWebManagerPort() != port)
1252     {
1253         Options::setLastWebManagerPort(port);
1254         lastUsedWebManagerPortChanged();
1255     }
1256 }