1 /*
2     SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "clientmanagerlite.h"
7 
8 #include "basedevice.h"
9 #include "indicom.h"
10 #include "inditelescopelite.h"
11 #include "kspaths.h"
12 #include "kstarslite.h"
13 #include "Options.h"
14 #include "skymaplite.h"
15 #include "fitsviewer/fitsdata.h"
16 #include "kstarslite/imageprovider.h"
17 #include "kstarslite/skyitems/telescopesymbolsitem.h"
18 
19 #include <KLocalizedString>
20 
21 #include <QApplication>
22 #include <QDebug>
23 #include <QFileDialog>
24 #include <QImageReader>
25 #include <QJsonArray>
26 #include <QJsonDocument>
27 #include <QProcess>
28 #include <QQmlApplicationEngine>
29 #include <QQmlContext>
30 #include <QTemporaryFile>
31 
32 const char *libindi_strings_context = "string from libindi, used in the config dialog";
33 
34 #ifdef Q_OS_ANDROID
35 #include "libraw/libraw.h"
36 #endif
37 
DeviceInfoLite(INDI::BaseDevice * dev)38 DeviceInfoLite::DeviceInfoLite(INDI::BaseDevice *dev) : device(dev)
39 {
40 }
41 
~DeviceInfoLite()42 DeviceInfoLite::~DeviceInfoLite()
43 {
44 }
45 
ClientManagerLite(QQmlContext & main_context)46 ClientManagerLite::ClientManagerLite(QQmlContext& main_context) : context(main_context)
47 {
48 #ifdef ANDROID
49     defaultImageType      = ".jpeg";
50     defaultImagesLocation = QDir(KSPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + qAppName()).path();
51 #endif
52     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeNS");
53     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeWE");
54     qmlRegisterType<TelescopeLite>("TelescopeLiteEnums", 1, 0, "TelescopeCommand");
55     context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
56 }
57 
~ClientManagerLite()58 ClientManagerLite::~ClientManagerLite()
59 {
60 }
61 
setHost(const QString & ip,unsigned int port)62 bool ClientManagerLite::setHost(const QString &ip, unsigned int port)
63 {
64     if (!isConnected())
65     {
66         setServer(ip.toStdString().c_str(), port);
67         qDebug() << ip << port;
68         if (connectServer())
69         {
70             setConnectedHost(ip + ':' + QString::number(port));
71             //Update last used server and port
72             setLastUsedServer(ip);
73             setLastUsedPort(port);
74 
75             return true;
76         }
77     }
78     return false;
79 }
80 
disconnectHost()81 void ClientManagerLite::disconnectHost()
82 {
83     disconnectServer();
84     clearDevices();
85     setConnectedHost("");
86 }
87 
getWebManagerProfiles(const QString & ip,unsigned int port)88 void ClientManagerLite::getWebManagerProfiles(const QString &ip, unsigned int port)
89 {
90     if (webMProfilesReply.get() != nullptr)
91         return;
92 
93     QString urlStr(QString("http://%1:%2/api/profiles").arg(ip).arg(port));
94     QNetworkRequest request { QUrl(urlStr) };
95 
96     webMProfilesReply.reset(manager.get(request));
97     connect(webMProfilesReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
98             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
99     connect(webMProfilesReply.get(), &QNetworkReply::finished, this, &ClientManagerLite::webManagerReplyFinished);
100     setLastUsedServer(ip);
101     setLastUsedWebManagerPort(port);
102 }
103 
startWebManagerProfile(const QString & profile)104 void ClientManagerLite::startWebManagerProfile(const QString &profile)
105 {
106     if (webMStartProfileReply.get() != nullptr)
107         return;
108 
109     QString urlStr("http://%1:%2/api/server/start/%3");
110 
111     urlStr = urlStr.arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()).arg(profile);
112     QNetworkRequest request { QUrl(urlStr) };
113 
114     webMStartProfileReply.reset(manager.post(request, QByteArray()));
115     connect(webMStartProfileReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
116             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
117     connect(webMStartProfileReply.get(), &QNetworkReply::finished,
118             this, &ClientManagerLite::webManagerReplyFinished);
119 }
120 
stopWebManagerProfile()121 void ClientManagerLite::stopWebManagerProfile()
122 {
123     if (webMStopProfileReply.get() != nullptr)
124         return;
125 
126     QString urlStr(QString("http://%1:%2/api/server/stop").arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()));
127     QNetworkRequest request { QUrl(urlStr) };
128 
129     webMStopProfileReply.reset(manager.post(request, QByteArray()));
130     connect(webMStopProfileReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
131             this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
132     connect(webMStopProfileReply.get(), &QNetworkReply::finished,
133             this, &ClientManagerLite::webManagerReplyFinished);
134 }
135 
webManagerReplyError(QNetworkReply::NetworkError code)136 void ClientManagerLite::webManagerReplyError(QNetworkReply::NetworkError code)
137 {
138     if (webMProfilesReply.get() != nullptr)
139     {
140         qWarning("Web Manager profile query error: %d", (int)code);
141         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
142         webMProfilesReply.release()->deleteLater();
143         return;
144     }
145     if (webMStatusReply.get() != nullptr)
146     {
147         qWarning("Web Manager status query error: %d", (int)code);
148         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
149         webMStatusReply.release()->deleteLater();
150         return;
151     }
152     if (webMStopProfileReply.get() != nullptr)
153     {
154         qWarning("Web Manager stop active profile error: %d", (int)code);
155         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
156         webMStopProfileReply.release()->deleteLater();
157         return;
158     }
159     if (webMStartProfileReply.get() != nullptr)
160     {
161         qWarning("Web Manager start active profile error: %d", (int)code);
162         KStarsLite::Instance()->notificationMessage(i18n("Could not connect to the Web Manager"));
163         webMStartProfileReply.release()->deleteLater();
164         return;
165     }
166 }
167 
webManagerReplyFinished()168 void ClientManagerLite::webManagerReplyFinished()
169 {
170     // Web Manager profile query
171     if (webMProfilesReply.get() != nullptr)
172     {
173         QByteArray responseData = webMProfilesReply->readAll();
174         QJsonDocument json = QJsonDocument::fromJson(responseData);
175 
176         webMProfilesReply.release()->deleteLater();
177         if (!json.isArray())
178         {
179             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
180             return;
181         }
182         QJsonArray array = json.array();
183 
184         webMProfiles.clear();
185         for (int i = 0; i < array.size(); ++i)
186         {
187             if (array.at(i).isObject() && array.at(i).toObject().contains("name"))
188             {
189                 webMProfiles += array.at(i).toObject()["name"].toString();
190             }
191         }
192         // Send a query for the network status
193         QString urlStr(QString("http://%1:%2/api/server/status").arg(getLastUsedServer()).arg(getLastUsedWebManagerPort()));
194         QNetworkRequest request { QUrl(urlStr) };
195 
196         webMStatusReply.reset(manager.get(request));
197         connect(webMStatusReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
198                 this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
199         connect(webMStatusReply.get(), &QNetworkReply::finished, this, &ClientManagerLite::webManagerReplyFinished);
200         return;
201     }
202     // Web Manager status query
203     if (webMStatusReply.get() != nullptr)
204     {
205         QByteArray responseData = webMStatusReply->readAll();
206         QJsonDocument json = QJsonDocument::fromJson(responseData);
207 
208         webMStatusReply.release()->deleteLater();
209         if (!json.isArray() || json.array().size() != 1 || !json.array().at(0).isObject())
210         {
211             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
212             return;
213         }
214         QJsonObject object = json.array().at(0).toObject();
215 
216         // Check the response
217         if (!object.contains("status") || !object.contains("active_profile"))
218         {
219             KStarsLite::Instance()->notificationMessage(i18n("Invalid response from Web Manager"));
220             return;
221         }
222         QString statusStr = object["status"].toString();
223         QString activeProfileStr = object["active_profile"].toString();
224 
225         indiControlPage->setProperty("webMBrowserButtonVisible", true);
226         indiControlPage->setProperty("webMStatusTextVisible", true);
227         if (statusStr == "True")
228         {
229             // INDI Server is running (online)
230             indiControlPage->setProperty("webMStatusText", i18n("Web Manager Status: Online"));
231             indiControlPage->setProperty("webMActiveProfileText",
232                                          i18n("Active Profile: %1", activeProfileStr));
233             indiControlPage->setProperty("webMActiveProfileLayoutVisible", true);
234             indiControlPage->setProperty("webMProfileListVisible", false);
235         } else {
236             // INDI Server is not running (offline)
237             indiControlPage->setProperty("webMStatusText", i18n("Web Manager Status: Offline"));
238             indiControlPage->setProperty("webMActiveProfileLayoutVisible", false);
239             context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
240             indiControlPage->setProperty("webMProfileListVisible", true);
241         }
242         return;
243     }
244     // Web Manager stop active profile
245     if (webMStopProfileReply.get() != nullptr)
246     {
247         webMStopProfileReply.release()->deleteLater();
248         indiControlPage->setProperty("webMStatusText", QString(i18n("Web Manager Status:")+' '+i18n("Offline")));
249         indiControlPage->setProperty("webMStatusTextVisible", true);
250         indiControlPage->setProperty("webMActiveProfileLayoutVisible", false);
251         context.setContextProperty("webMProfileModel", QVariant::fromValue(webMProfiles));
252         indiControlPage->setProperty("webMProfileListVisible", true);
253         return;
254     }
255     // Web Manager start active profile
256     if (webMStartProfileReply.get() != nullptr)
257     {
258         webMStartProfileReply.release()->deleteLater();
259         // Send a query for the network status
260         QString urlStr("http://%1:%2/api/server/status");
261 
262         urlStr = urlStr.arg(getLastUsedServer()).arg(getLastUsedWebManagerPort());
263         QNetworkRequest request { QUrl(urlStr) };
264 
265         webMStatusReply.reset(manager.get(request));
266         connect(webMStatusReply.get(), SIGNAL(error(QNetworkReply::NetworkError)),
267                 this, SLOT(webManagerReplyError(QNetworkReply::NetworkError)));
268         connect(webMStatusReply.get(), &QNetworkReply::finished,
269                 this, &ClientManagerLite::webManagerReplyFinished);
270         // Connect to the server automatically
271         QMetaObject::invokeMethod(indiControlPage, "connectIndiServer");
272         return;
273     }
274 }
275 
getTelescope()276 TelescopeLite *ClientManagerLite::getTelescope()
277 {
278     for (auto& devInfo : m_devices)
279     {
280         if (devInfo->telescope.get())
281         {
282             return devInfo->telescope.get();
283         }
284     }
285     return nullptr;
286 }
287 
setConnectedHost(const QString & connectedHost)288 void ClientManagerLite::setConnectedHost(const QString &connectedHost)
289 {
290     m_connectedHost = connectedHost;
291     setConnected(m_connectedHost.size() > 0);
292 
293     emit connectedHostChanged(connectedHost);
294 }
295 
setConnected(bool connected)296 void ClientManagerLite::setConnected(bool connected)
297 {
298     m_connected = connected;
299     emit connectedChanged(connected);
300 }
301 
syncLED(const QString & device,const QString & property,const QString & name)302 QString ClientManagerLite::syncLED(const QString &device, const QString &property, const QString &name)
303 {
304     foreach (DeviceInfoLite *devInfo, m_devices)
305     {
306         if (devInfo->device->getDeviceName() == device)
307         {
308             INDI::Property prop = devInfo->device->getProperty(property.toLatin1());
309             if (prop)
310             {
311                 IPState state = prop->getState();
312                 if (!name.isEmpty())
313                 {
314                     ILight *lights = prop->getLight()->lp;
315                     for (int i = 0; i < prop->getLight()->nlp; i++)
316                     {
317                         if (lights[i].name == name)
318                         {
319                             state = lights[i].s;
320                             break;
321                         }
322                         if (i == prop->getLight()->nlp - 1)
323                             return ""; // no Light with name "name" found so return empty string
324                     }
325                 }
326                 switch (state)
327                 {
328                     case IPS_IDLE:
329                         return "grey";
330                         break;
331 
332                     case IPS_OK:
333                         return "green";
334                         break;
335 
336                     case IPS_BUSY:
337                         return "yellow";
338                         break;
339 
340                     case IPS_ALERT:
341                         return "red";
342                         break;
343 
344                     default:
345                         return "grey";
346                         break;
347                 }
348             }
349         }
350     }
351     return "grey";
352 }
353 
buildTextGUI(Property * property)354 void ClientManagerLite::buildTextGUI(Property *property)
355 {
356     auto tvp = property->getText();
357     if (!tvp)
358         return;
359 
360     for (const auto &it: *tvp)
361     {
362         QString name  = it.getName();
363         QString label = it.getLabel();
364         QString text  = it.getText();
365         bool read     = false;
366         bool write    = false;
367         /*if (tp->label[0])
368                 label = i18nc(libindi_strings_context, itp->label);
369 
370             if (label == "(I18N_EMPTY_MESSAGE)")
371                 label = itp->label;*/
372 
373         if (label.isEmpty())
374             label = tvp->getName(); // #PS: it should be it.getName() ?
375         /*label = i18nc(libindi_strings_context, itp->name);
376 
377             if (label == "(I18N_EMPTY_MESSAGE)")*/
378 
379         //setupElementLabel();
380 
381         /*if (tp->text[0])
382                 text = i18nc(libindi_strings_context, tp->text);*/
383 
384         switch (property->getPermission())
385         {
386             case IP_RW:
387                 read  = true;
388                 write = true;
389                 break;
390 
391             case IP_RO:
392                 read  = true;
393                 write = false;
394                 break;
395 
396             case IP_WO:
397                 read  = false;
398                 write = true;
399                 break;
400         }
401         emit createINDIText(property->getDeviceName(), property->getName(), label, name, text, read, write);
402     }
403 }
404 
buildNumberGUI(Property * property)405 void ClientManagerLite::buildNumberGUI(Property *property)
406 {
407     auto nvp = property->getNumber();
408     if (!nvp)
409         return;
410 
411     //for (int i = 0; i < nvp->nnp; i++)
412     for (const auto &it: nvp)
413     {
414         bool scale = false;
415         char iNumber[MAXINDIFORMAT];
416 
417         QString name  = it.getName();
418         QString label = it.getLabel();
419         QString text;
420         bool read  = false;
421         bool write = false;
422         /*if (tp->label[0])
423                 label = i18nc(libindi_strings_context, itp->label);
424 
425             if (label == "(I18N_EMPTY_MESSAGE)")
426                 label = itp->label;*/
427 
428         if (label.isEmpty())
429             label = np->getName();
430 
431         numberFormat(iNumber, np.getFormat(), np.getValue());
432         text = iNumber;
433 
434         /*label = i18nc(libindi_strings_context, itp->name);
435 
436             if (label == "(I18N_EMPTY_MESSAGE)")*/
437 
438         //setupElementLabel();
439 
440         /*if (tp->text[0])
441                 text = i18nc(libindi_strings_context, tp->text);*/
442 
443         if (it.getStep() != 0 && (it.getMax() - it.getMin()) / it.getStep() <= 100)
444             scale = true;
445 
446         switch (property->getPermission())
447         {
448             case IP_RW:
449                 read  = true;
450                 write = true;
451                 break;
452 
453             case IP_RO:
454                 read  = true;
455                 write = false;
456                 break;
457 
458             case IP_WO:
459                 read  = false;
460                 write = true;
461                 break;
462         }
463         emit createINDINumber(property->getDeviceName(), property->getName(), label, name, text, read, write,
464                                 scale);
465     }
466 }
467 
buildMenuGUI(INDI::Property property)468 void ClientManagerLite::buildMenuGUI(INDI::Property property)
469 {
470     /*QStringList menuOptions;
471     QString oneOption;
472     int onItem=-1;*/
473     auto svp = property->getSwitch();
474 
475     if (!svp)
476         return;
477 
478     for (auto &it: *svp)
479     {
480         buildSwitch(false, &it, property);
481 
482         /*if (tp->s == ISS_ON)
483             onItem = i;
484 
485         lp = new INDI_E(this, dataProp);
486 
487         lp->buildMenuItem(tp);
488 
489         oneOption = i18nc(libindi_strings_context, lp->getLabel().toUtf8());
490 
491         if (oneOption == "(I18N_EMPTY_MESSAGE)")
492             oneOption =  lp->getLabel().toUtf8();
493 
494         menuOptions.append(oneOption);
495 
496         elementList.append(lp);*/
497     }
498 }
499 
buildSwitchGUI(INDI::Property property,PGui guiType)500 void ClientManagerLite::buildSwitchGUI(INDI::Property property, PGui guiType)
501 {
502     auto svp = property->getSwitch();
503     bool exclusive = false;
504 
505     if (!svp)
506         return;
507 
508     if (guiType == PG_BUTTONS)
509     {
510         if (svp->getRule() == ISR_1OFMANY)
511             exclusive = true;
512         else
513             exclusive = false;
514     }
515     else if (guiType == PG_RADIO)
516         exclusive = false;
517 
518     /*if (svp->p != IP_RO)
519         QObject::connect(groupB, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(newSwitch(QAbstractButton*)));*/
520 
521     for (auto &it: *svp)
522     {
523         buildSwitch(true, &it, property, exclusive, guiType);
524     }
525 }
526 
buildSwitch(bool buttonGroup,ISwitch * sw,INDI::Property property,bool exclusive,PGui guiType)527 void ClientManagerLite::buildSwitch(bool buttonGroup, ISwitch *sw, INDI::Property property, bool exclusive,
528                                     PGui guiType)
529 {
530     QString name  = sw->name;
531     QString label = sw->label; //i18nc(libindi_strings_context, sw->label);
532 
533     if (label == "(I18N_EMPTY_MESSAGE)")
534         label = sw->label;
535 
536     if (label.isEmpty())
537         label = sw->name;
538     //label = i18nc(libindi_strings_context, sw->name);
539 
540     if (label == "(I18N_EMPTY_MESSAGE)")
541         label = sw->name;
542 
543     if (!buttonGroup)
544     {
545         bool isSelected = false;
546         if (sw->s == ISS_ON)
547             isSelected = true;
548         emit createINDIMenu(property->getDeviceName(), property->getName(), label, sw->name, isSelected);
549         return;
550     }
551 
552     bool enabled = true;
553 
554     if (sw->svp->p == IP_RO)
555         enabled = (sw->s == ISS_ON);
556 
557     switch (guiType)
558     {
559         case PG_BUTTONS:
560             emit createINDIButton(property->getDeviceName(), property->getName(), label, name, true, true, exclusive,
561                                   sw->s == ISS_ON, enabled);
562             break;
563 
564         case PG_RADIO:
565             emit createINDIRadio(property->getDeviceName(), property->getName(), label, name, true, true, exclusive,
566                                  sw->s == ISS_ON, enabled);
567         /*check_w = new QCheckBox(label, guiProp->getGroup()->getContainer());
568         groupB->addButton(check_w);
569 
570         syncSwitch();
571 
572         guiProp->addWidget(check_w);
573 
574         check_w->show();
575 
576         if (sw->svp->p == IP_RO)
577             check_w->setEnabled(sw->s == ISS_ON);
578 
579         break;*/
580 
581         default:
582             break;
583     }
584 }
585 
buildLightGUI(INDI::Property property)586 void ClientManagerLite::buildLightGUI(INDI::Property property)
587 {
588     auto lvp = property->getLight();
589 
590     if (!lvp)
591         return;
592 
593     for (auto &it: *lvp)
594     {
595         QString name  = it.getName();
596         QString label = i18nc(libindi_strings_context, it.getLabel());
597 
598         if (label == "(I18N_EMPTY_MESSAGE)")
599             label = it.getLabel();
600 
601         if (label.isEmpty())
602             label = i18nc(libindi_strings_context, it.getName());
603 
604         if (label == "(I18N_EMPTY_MESSAGE)")
605             label = it.getName();;
606 
607         emit createINDILight(property->getDeviceName(), property->getName(), label, name);
608     }
609 }
610 
611 /*void ClientManagerLite::buildBLOBGUI(INDI::Property property) {
612     IBLOBVectorProperty *ibp = property->getBLOB();
613 
614     QString name  = ibp->name;
615     QString label = i18nc(libindi_strings_context, ibp->label);
616 
617     if (label == "(I18N_EMPTY_MESSAGE)")
618         label = ibp->label;
619 
620     if (label.isEmpty())
621         label = i18nc(libindi_strings_context, ibp->name);
622 
623     if (label == "(I18N_EMPTY_MESSAGE)")
624         label = ibp->name;
625 
626     text = i18n("INDI DATA STREAM");
627 
628     switch (property->getPermission())
629     {
630     case IP_RW:
631         setupElementRead(ELEMENT_READ_WIDTH);
632         setupElementWrite(ELEMENT_WRITE_WIDTH);
633         setupBrowseButton();
634         break;
635 
636     case IP_RO:
637         setupElementRead(ELEMENT_FULL_WIDTH);
638         break;
639 
640     case IP_WO:
641         setupElementWrite(ELEMENT_FULL_WIDTH);
642         setupBrowseButton();
643         break;
644     }
645 
646     guiProp->addLayout(EHBox);
647 }*/
648 
sendNewINDISwitch(const QString & deviceName,const QString & propName,const QString & name)649 void ClientManagerLite::sendNewINDISwitch(const QString &deviceName, const QString &propName, const QString &name)
650 {
651     foreach (DeviceInfoLite *devInfo, m_devices)
652     {
653         INDI::BaseDevice *device = devInfo->device;
654         if (device->getDeviceName() == deviceName)
655         {
656             auto property = device->getProperty(propName.toLatin1());
657             if (property)
658             {
659                 auto svp = property->getSwitch();
660 
661                 if (!svp)
662                     return;
663 
664                 auto sp = svp->findWidgetByName(name.toLatin1().constData());
665 
666                 if (!sp)
667                     return;
668 
669                 if (sp->isNameMatch("CONNECT"))
670                 {
671                     svp->reset();
672                     sp->setState(ISS_ON);
673                 }
674 
675                 if (svp->getRule() == ISR_1OFMANY)
676                 {
677                     svp->reset();
678                     sp->setState(ISS_ON);
679                 }
680                 else
681                 {
682                     if (svp->getRule() == ISR_ATMOST1)
683                     {
684                         ISState prev_state = sp->getState();
685                         svp->reset();
686                         sp->setState(prev_state);
687                     }
688 
689                     sp->setState(sp->getState() == ISS_ON ? ISS_OFF : ISS_ON);
690                 }
691                 sendNewSwitch(svp);
692             }
693         }
694     }
695 }
696 
sendNewINDINumber(const QString & deviceName,const QString & propName,const QString & numberName,double value)697 void ClientManagerLite::sendNewINDINumber(const QString &deviceName, const QString &propName, const QString &numberName,
698                                           double value)
699 {
700     foreach (DeviceInfoLite *devInfo, m_devices)
701     {
702         INDI::BaseDevice *device = devInfo->device;
703         if (device->getDeviceName() == deviceName)
704         {
705             auto np = device->getNumber(propName.toLatin1());
706             if (np)
707             {
708                 auto n = np->findWIdgetByName(numberName.toLatin1());
709                 if (n)
710                 {
711                     n->setValue(value);
712                     sendNewNumber(np);
713                     return;
714                 }
715 
716                 qDebug() << "Could not find property: " << deviceName << "." << propName << "." << numberName;
717                 return;
718             }
719 
720             qDebug() << "Could not find property: " << deviceName << "." << propName << "." << numberName;
721             return;
722         }
723     }
724 }
725 
sendNewINDIText(const QString & deviceName,const QString & propName,const QString & fieldName,const QString & text)726 void ClientManagerLite::sendNewINDIText(const QString &deviceName, const QString &propName, const QString &fieldName,
727                                         const QString &text)
728 {
729     foreach (DeviceInfoLite *devInfo, m_devices)
730     {
731         INDI::BaseDevice *device = devInfo->device;
732         if (device->getDeviceName() == deviceName)
733         {
734             auto tp = device->getText(propName.toLatin1());
735             if (tp)
736             {
737                 auto t = tp->findWidgetByName(fieldName.toLatin1());
738                 if (t)
739                 {
740                     t.setText(text.toLatin1().data());
741                     sendNewText(tp);
742                     return;
743                 }
744 
745                 qDebug() << "Could not find property: " << deviceName << "." << propName << "." << fieldName;
746                 return;
747             }
748 
749             qDebug() << "Could not find property: " << deviceName << "." << propName << "." << fieldName;
750             return;
751         }
752     }
753 }
754 
sendNewINDISwitch(const QString & deviceName,const QString & propName,int index)755 void ClientManagerLite::sendNewINDISwitch(const QString &deviceName, const QString &propName, int index)
756 {
757     if (index >= 0)
758     {
759         foreach (DeviceInfoLite *devInfo, m_devices)
760         {
761             INDI::BaseDevice *device = devInfo->device;
762             if (device->getDeviceName() == deviceName)
763             {
764                 auto property = device->getProperty(propName.toStdString().c_str());
765                 if (property)
766                 {
767                     auto svp = property->getSwitch();
768 
769                     if (!svp)
770                         return;
771 
772                     if (index >= svp->count())
773                         return;
774 
775                     auto sp = svp->at(index);
776 
777                     sp->reset();
778                     sp->setState(ISS_ON);
779 
780                     sendNewSwitch(svp);
781                 }
782             }
783         }
784     }
785 }
786 
saveDisplayImage()787 bool ClientManagerLite::saveDisplayImage()
788 {
789     QString const dateTime   = QDateTime::currentDateTime().toString("dd-MM-yyyy-hh-mm-ss");
790     QString const fileEnding = "kstars-lite-" + dateTime;
791     QDir const dir(KSPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + qAppName()).path();
792     QFileInfo const file(QString("%1/%2.jpeg").arg(dir.path()).arg(fileEnding));
793 
794     QString const filename = QFileDialog::getSaveFileName(
795                 QApplication::activeWindow(), i18nc("@title:window", "Save Image"), file.filePath(),
796                 i18n("JPEG (*.jpeg);;JPG (*.jpg);;PNG (*.png);;BMP (*.bmp)"));
797 
798     if (!filename.isEmpty())
799     {
800         if (displayImage.save(filename))
801         {
802             emit newINDIMessage("File " + filename + " was successfully saved");
803             return true;
804         }
805     }
806     emit newINDIMessage("Couldn't save file " + filename);
807     return false;
808 }
809 
isDeviceConnected(const QString & deviceName)810 bool ClientManagerLite::isDeviceConnected(const QString &deviceName)
811 {
812     INDI::BaseDevice *device = getDevice(deviceName.toStdString().c_str());
813 
814     if (device != nullptr)
815     {
816         return device->isConnected();
817     }
818     return false;
819 }
820 
connectNewDevice(const QString & device_name)821 void ClientManagerLite::connectNewDevice(const QString& device_name)
822 {
823     connectDevice(qPrintable(device_name));
824 }
825 
newDevice(INDI::BaseDevice * dp)826 void ClientManagerLite::newDevice(INDI::BaseDevice *dp)
827 {
828     setBLOBMode(B_ALSO, dp->getDeviceName());
829 
830     QString deviceName = dp->getDeviceName();
831 
832     if (deviceName.isEmpty())
833     {
834         qWarning() << "Received invalid device with empty name! Ignoring the device...";
835         return;
836     }
837 
838     if (Options::verboseLogging())
839         qDebug() << "Received new device " << deviceName;
840     emit newINDIDevice(deviceName);
841 
842     DeviceInfoLite *devInfo = new DeviceInfoLite(dp);
843     //Think about it!
844     //devInfo->telescope.reset(new TelescopeLite(dp));
845     m_devices.append(devInfo);
846     // Connect the device automatically
847     QTimer::singleShot(2000, [=]() { connectNewDevice(deviceName); });
848 }
849 
removeDevice(BaseDevice * dp)850 void ClientManagerLite::removeDevice(BaseDevice *dp)
851 {
852     emit removeINDIDevice(QString(dp->getDeviceName()));
853 }
854 
newProperty(INDI::Property property)855 void ClientManagerLite::newProperty(INDI::Property property)
856 {
857     QString deviceName      = property->getDeviceName();
858     QString name            = property->getName();
859     QString groupName       = property->getGroupName();
860     QString type            = QString(property->getType());
861     QString label           = property->getLabel();
862     DeviceInfoLite *devInfo = nullptr;
863 
864     foreach (DeviceInfoLite *di, m_devices)
865     {
866         if (di->device->getDeviceName() == deviceName)
867         {
868             devInfo = di;
869         }
870     }
871 
872     if (devInfo)
873     {
874         if ((!strcmp(property->getName(), "EQUATORIAL_EOD_COORD") ||
875              !strcmp(property->getName(), "EQUATORIAL_COORD") ||
876              !strcmp(property->getName(), "HORIZONTAL_COORD")))
877         {
878             devInfo->telescope.reset(new TelescopeLite(devInfo->device));
879             m_telescope = devInfo->telescope.get();
880             emit telescopeAdded(m_telescope);
881             // The connected signal must be emitted for already connected scopes otherwise
882             // the motion control page remains disabled.
883             if (devInfo->telescope->isConnected())
884             {
885                 emit deviceConnected(devInfo->telescope->getDeviceName(), true);
886                 emit telescopeConnected(devInfo->telescope.get());
887             }
888         }
889     }
890 
891     emit newINDIProperty(deviceName, name, groupName, type, label);
892     PGui guiType;
893     switch (property->getType())
894     {
895         case INDI_SWITCH:
896             if (property->getSwitch()->r == ISR_NOFMANY)
897                 guiType = PG_RADIO;
898             else if (property->getSwitch()->nsp > 4)
899                 guiType = PG_MENU;
900             else
901                 guiType = PG_BUTTONS;
902 
903             if (guiType == PG_MENU)
904                 buildMenuGUI(property);
905             else
906                 buildSwitchGUI(property, guiType);
907             break;
908 
909         case INDI_TEXT:
910             buildTextGUI(property);
911             break;
912         case INDI_NUMBER:
913             buildNumberGUI(property);
914             break;
915 
916         case INDI_LIGHT:
917             buildLightGUI(property);
918             break;
919 
920         case INDI_BLOB:
921             //buildBLOBGUI();
922             break;
923 
924         default:
925             break;
926     }
927 }
928 
removeProperty(INDI::Property property)929 void ClientManagerLite::removeProperty(INDI::Property property)
930 {
931     if (property == nullptr)
932         return;
933 
934     emit removeINDIProperty(property->getDeviceName(), property->getGroupName(), property->getName());
935 
936     DeviceInfoLite *devInfo = nullptr;
937     foreach (DeviceInfoLite *di, m_devices)
938     {
939         if (di->device == property->getBaseDevice())
940         {
941             devInfo = di;
942         }
943     }
944 
945     if (devInfo)
946     {
947         if ((!strcmp(property->getName(), "EQUATORIAL_EOD_COORD") || !strcmp(property->getName(), "HORIZONTAL_COORD")))
948         {
949             if (devInfo->telescope.get() != nullptr)
950             {
951                 emit telescopeRemoved(devInfo->telescope.get());
952             }
953             KStarsLite::Instance()->map()->update(); // Update SkyMap if position of telescope is changed
954         }
955     }
956 }
957 
newBLOB(IBLOB * bp)958 void ClientManagerLite::newBLOB(IBLOB *bp)
959 {
960     processBLOBasCCD(bp);
961     emit newLEDState(bp->bvp->device, bp->name);
962 }
963 
processBLOBasCCD(IBLOB * bp)964 bool ClientManagerLite::processBLOBasCCD(IBLOB *bp)
965 {
966     enum blobType
967     {
968         BLOB_IMAGE,
969         BLOB_FITS,
970         BLOB_CR2,
971         BLOB_OTHER
972     } BType;
973 
974     BType = BLOB_OTHER;
975 
976     QString format(bp->format);
977     QString deviceName = bp->bvp->device;
978 
979     QByteArray fmt = QString(bp->format).toLower().remove('.').toUtf8();
980 
981     // If it's not FITS or an image, don't process it.
982     if ((QImageReader::supportedImageFormats().contains(fmt)))
983         BType = BLOB_IMAGE;
984     else if (format.contains("fits"))
985         BType = BLOB_FITS;
986     else if (format.contains("cr2"))
987         BType = BLOB_CR2;
988 
989     if (BType == BLOB_OTHER)
990     {
991         return false;
992     }
993 
994     QString currentDir = QDir(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/" + qAppName()).path();
995 
996     int nr, n = 0;
997     QTemporaryFile tmpFile(QDir::tempPath() + "/fitsXXXXXX");
998 
999     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 
newSwitch(ISwitchVectorProperty * svp)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 
newNumber(INumberVectorProperty * nvp)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 
newText(ITextVectorProperty * tvp)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 
newLight(ILightVectorProperty * lvp)1184 void ClientManagerLite::newLight(ILightVectorProperty *lvp)
1185 {
1186     emit newINDILight(lvp->device, lvp->name);
1187     emit newLEDState(lvp->device, lvp->name);
1188 }
1189 
newMessage(INDI::BaseDevice * dp,int messageID)1190 void ClientManagerLite::newMessage(INDI::BaseDevice *dp, int messageID)
1191 {
1192     emit newINDIMessage(QString::fromStdString(dp->messageQueue(messageID)));
1193 }
1194 
serverDisconnected(int exit_code)1195 void ClientManagerLite::serverDisconnected(int exit_code)
1196 {
1197     Q_UNUSED(exit_code)
1198     clearDevices();
1199     setConnected(false);
1200 }
1201 
clearDevices()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 
getLastUsedServer()1216 QString ClientManagerLite::getLastUsedServer()
1217 {
1218     return Options::lastServer();
1219 }
1220 
setLastUsedServer(const QString & server)1221 void ClientManagerLite::setLastUsedServer(const QString &server)
1222 {
1223     if (getLastUsedServer() != server)
1224     {
1225         Options::setLastServer(server);
1226         lastUsedServerChanged();
1227     }
1228 }
1229 
getLastUsedPort()1230 int ClientManagerLite::getLastUsedPort()
1231 {
1232     return Options::lastServerPort();
1233 }
1234 
setLastUsedPort(int port)1235 void ClientManagerLite::setLastUsedPort(int port)
1236 {
1237     if (getLastUsedPort() != port)
1238     {
1239         Options::setLastServerPort(port);
1240         lastUsedPortChanged();
1241     }
1242 }
1243 
getLastUsedWebManagerPort()1244 int ClientManagerLite::getLastUsedWebManagerPort()
1245 {
1246     return Options::lastWebManagerPort();
1247 }
1248 
setLastUsedWebManagerPort(int port)1249 void ClientManagerLite::setLastUsedWebManagerPort(int port)
1250 {
1251     if (getLastUsedWebManagerPort() != port)
1252     {
1253         Options::setLastWebManagerPort(port);
1254         lastUsedWebManagerPortChanged();
1255     }
1256 }
1257