Kstars

clientmanagerlite.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Artem Fedoskin <[email protected]>
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 
38 DeviceInfoLite::DeviceInfoLite(INDI::BaseDevice *dev) : device(dev)
39 {
40 }
41 
42 DeviceInfoLite::~DeviceInfoLite()
43 {
44 }
45 
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 
58 ClientManagerLite::~ClientManagerLite()
59 {
60 }
61 
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 
81 void ClientManagerLite::disconnectHost()
82 {
83  disconnectServer();
84  clearDevices();
85  setConnectedHost("");
86 }
87 
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)),
99  connect(webMProfilesReply.get(), &QNetworkReply::finished, this, &ClientManagerLite::webManagerReplyFinished);
100  setLastUsedServer(ip);
101  setLastUsedWebManagerPort(port);
102 }
103 
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)),
117  connect(webMStartProfileReply.get(), &QNetworkReply::finished,
119 }
120 
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)),
132  connect(webMStopProfileReply.get(), &QNetworkReply::finished,
134 }
135 
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 
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)),
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)),
268  connect(webMStatusReply.get(), &QNetworkReply::finished,
270  // Connect to the server automatically
271  QMetaObject::invokeMethod(indiControlPage, "connectIndiServer");
272  return;
273  }
274 }
275 
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 
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 
296 void ClientManagerLite::setConnected(bool connected)
297 {
298  m_connected = connected;
299  emit connectedChanged(connected);
300 }
301 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
821 void ClientManagerLite::connectNewDevice(const QString& device_name)
822 {
823  connectDevice(qPrintable(device_name));
824 }
825 
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 
850 void ClientManagerLite::removeDevice(BaseDevice *dp)
851 {
852  emit removeINDIDevice(QString(dp->getDeviceName()));
853 }
854 
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 
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 
958 void ClientManagerLite::newBLOB(IBLOB *bp)
959 {
960  processBLOBasCCD(bp);
961  emit newLEDState(bp->bvp->device, bp->name);
962 }
963 
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 
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 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QJsonObject toObject() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
Q_INVOKABLE void startWebManagerProfile(const QString &profile)
Start an INDI server with a Web Manager profile.
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
Q_INVOKABLE void stopWebManagerProfile()
Stop the INDI server with an active Web Manager profile.
int size() const const
QVariant fromValue(const T &value)
bool waitForFinished(int msecs)
bool remove()
Type type(const QSqlDatabase &db)
QDateTime currentDateTime()
bool isObject() const const
Q_INVOKABLE void webManagerReplyFinished()
Do actions when async Web Manager requests are finished.
void chop(int n)
static KStarsLite * Instance()
Definition: kstarslite.h:77
QWidget * activeWindow()
void setContextProperty(const QString &name, QObject *value)
QByteArray toLatin1() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QList< QByteArray > supportedImageFormats()
void notificationMessage(QString msg)
Once this signal is emitted, notification with text msg will appear on the screen.
QNetworkReply * get(const QNetworkRequest &request)
bool load(QIODevice *device, const char *format)
bool contains(const QString &key) const const
SkyMapLite * map() const
Definition: kstarslite.h:80
QString tempPath()
QString findExecutable(const QString &executableName, const QStringList &paths)
bool isArray() const const
Q_INVOKABLE void webManagerReplyError(QNetworkReply::NetworkError code)
Handle the errors of the async Web Manager requests.
void update()
Q_INVOKABLE QString syncLED(const QString &device, const QString &property, const QString &name="")
syncLED
QString i18n(const char *text, const TYPE &arg...)
QNetworkReply * post(const QNetworkRequest &request, QIODevice *data)
QString fromStdString(const std::string &str)
ImageProvider * imageProvider() const
Definition: kstarslite.h:89
void addImage(const QString &id, QImage image)
Add image to the list of images with the given id.
bool isEmpty() const const
std::string toStdString() const const
QByteArray toUtf8() const const
bool mkpath(const QString &dirPath) const const
QString filePath() const const
QString path() const const
QString & remove(int position, int n)
QJsonValue at(int i) const const
Q_INVOKABLE void getWebManagerProfiles(const QString &ip, unsigned int port)
Get the profiles from Web Manager.
QString label(StandardShortcut id)
bool setProperty(const char *name, const QVariant &value)
QString toLower() const const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
const char * constData() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString name(StandardShortcut id)
QJsonArray array() const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QVariant read(const QByteArray &data, int versionOverride=0)
Q_INVOKABLE bool saveDisplayImage()
saveDisplayImage
QString toString(Qt::DateFormat format) const const
bool save(const QString &fileName, const char *format, int quality) const const
char * data()
int size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Sep 25 2023 03:54:30 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.