Kstars

indistd.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 
6  Handle INDI Standard properties.
7 */
8 
9 #include "indistd.h"
10 
11 #include "clientmanager.h"
12 #include "driverinfo.h"
13 #include "deviceinfo.h"
14 #include "imageviewer.h"
15 #include "indi_debug.h"
16 #include "kstars.h"
17 #include "kstarsdata.h"
18 #include "Options.h"
19 #include "skymap.h"
20 
21 #include "indimount.h"
22 #include "indicamera.h"
23 #include "indiguider.h"
24 #include "indifocuser.h"
25 #include "indifilterwheel.h"
26 #include "indidome.h"
27 #include "indigps.h"
28 #include "indiweather.h"
29 #include "indiadaptiveoptics.h"
30 #include "indidustcap.h"
31 #include "indilightbox.h"
32 #include "indidetector.h"
33 #include "indirotator.h"
34 #include "indispectrograph.h"
35 #include "indicorrelator.h"
36 #include "indiauxiliary.h"
37 
38 #include <indicom.h>
39 #include <QImageReader>
40 #include <QStatusBar>
41 
42 namespace ISD
43 {
44 
45 GDSetCommand::GDSetCommand(INDI_PROPERTY_TYPE inPropertyType, const QString &inProperty, const QString &inElement,
46  QVariant qValue, QObject *parent)
47  : QObject(parent), propType(inPropertyType), indiProperty((inProperty)), indiElement(inElement), elementValue(qValue)
48 {
49 }
50 
51 GenericDevice::GenericDevice(DeviceInfo &idv, ClientManager *cm, QObject *parent) : GDInterface(parent)
52 {
53  m_DeviceInfo = &idv;
54  m_DriverInfo = idv.getDriverInfo();
55  m_BaseDevice = idv.getBaseDevice();
56  m_ClientManager = cm;
57 
58  Q_ASSERT_X(m_BaseDevice, __FUNCTION__, "Base device is invalid.");
59  Q_ASSERT_X(m_ClientManager, __FUNCTION__, "Client manager is invalid.");
60 
61  m_Name = m_BaseDevice->getDeviceName();
62 
63  registerDBusType();
64 
65  // JM 2020-09-05: In case KStars time change, update driver time if applicable.
66  connect(KStarsData::Instance()->clock(), &SimClock::timeChanged, this, [this]()
67  {
68  if (Options::useTimeUpdate() && Options::useKStarsSource())
69  {
70  if (m_BaseDevice != nullptr && isConnected())
71  {
72  auto tvp = m_BaseDevice->getText("TIME_UTC");
73  if (tvp && tvp->getPermission() != IP_RO)
74  updateTime();
75  }
76  }
77  });
78 
79  m_ReadyTimer = new QTimer(this);
80  m_ReadyTimer->setInterval(250);
81  m_ReadyTimer->setSingleShot(true);
82  connect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::ready);
83  connect(this, &GenericDevice::ready, this, [this]()
84  {
85  generateDevices();
86  m_ReadyTimer->disconnect(this);
87  });
88 }
89 
90 GenericDevice::~GenericDevice()
91 {
92  for (auto &metadata : streamFileMetadata)
93  metadata.file->close();
94 }
95 
96 void GenericDevice::registerDBusType()
97 {
98 #ifndef KSTARS_LITE
99  static bool isRegistered = false;
100 
101  if (isRegistered == false)
102  {
103  qRegisterMetaType<ISD::ParkStatus>("ISD::ParkStatus");
104  qDBusRegisterMetaType<ISD::ParkStatus>();
105  isRegistered = true;
106  }
107 #endif
108 }
109 
110 const QString &GenericDevice::getDeviceName() const
111 {
112  return m_Name;
113 }
114 
115 void GenericDevice::registerProperty(INDI::Property prop)
116 {
117  if (!prop->getRegistered())
118  return;
119 
120  m_ReadyTimer->start();
121 
122  const QString name = prop->getName();
123 
124  // In case driver already started
125  if (name == "CONNECTION")
126  {
127  auto svp = prop->getSwitch();
128 
129  // Still connecting/disconnecting...
130  if (!svp || svp->getState() == IPS_BUSY)
131  return;
132 
133  auto conSP = svp->findWidgetByName("CONNECT");
134 
135  if (!conSP)
136  return;
137 
138  if (m_Connected == false && svp->getState() == IPS_OK && conSP->getState() == ISS_ON)
139  {
140  m_Connected = true;
141  emit Connected();
142  createDeviceInit();
143  }
144  else if (m_Connected && conSP->getState() == ISS_OFF)
145  {
146  m_Connected = false;
147  emit Disconnected();
148  }
149  }
150  else if (name == "DRIVER_INFO")
151  {
152  auto tvp = prop->getText();
153  if (tvp)
154  {
155  auto tp = tvp->findWidgetByName("DRIVER_INTERFACE");
156  if (tp)
157  {
158  m_DriverInterface = static_cast<uint32_t>(atoi(tp->getText()));
159  emit interfaceDefined();
160  }
161 
162  tp = tvp->findWidgetByName("DRIVER_VERSION");
163  if (tp)
164  {
165  m_DriverVersion = QString(tp->getText());
166  }
167  }
168  }
169  else if (name == "SYSTEM_PORTS")
170  {
171  // Check if our current port is set to one of the system ports. This indicates that the port
172  // is not mapped yet to a permenant designation
173  auto svp = prop->getSwitch();
174  auto port = m_BaseDevice->getText("DEVICE_PORT");
175  if (svp && port)
176  {
177  for (const auto &it : *svp)
178  {
179  if (it.isNameMatch(port->at(0)->getText()))
180  {
181  emit systemPortDetected();
182  break;
183  }
184  }
185  }
186  }
187  else if (name == "TIME_UTC" && Options::useTimeUpdate() && Options::useKStarsSource())
188  {
189  const auto &tvp = prop->getText();
190  if (tvp && tvp->getPermission() != IP_RO)
191  updateTime();
192  }
193  else if (name == "GEOGRAPHIC_COORD" && Options::useGeographicUpdate() && Options::useKStarsSource())
194  {
195  const auto &nvp = prop->getNumber();
196  if (nvp && nvp->getPermission() != IP_RO)
197  updateLocation();
198  }
199  else if (name == "WATCHDOG_HEARTBEAT")
200  {
201  const auto &nvp = prop->getNumber();
202  if (nvp)
203  {
204  if (watchDogTimer == nullptr)
205  {
206  watchDogTimer = new QTimer(this);
207  connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(resetWatchdog()));
208  }
209 
210  if (m_Connected && nvp->at(0)->getValue() > 0)
211  {
212  // Send immediately a heart beat
213  m_ClientManager->sendNewNumber(nvp);
214  }
215  }
216  }
217 
218  emit propertyDefined(prop);
219 }
220 
221 void GenericDevice::removeProperty(const QString &name)
222 {
223  emit propertyDeleted(name);
224 }
225 
226 void GenericDevice::processSwitch(ISwitchVectorProperty *svp)
227 {
228  if (!strcmp(svp->name, "CONNECTION"))
229  {
230  ISwitch *conSP = IUFindSwitch(svp, "CONNECT");
231 
232  if (conSP == nullptr)
233  return;
234 
235  // Still connecting/disconnecting...
236  if (svp->s == IPS_BUSY)
237  return;
238 
239  if (m_Connected == false && svp->s == IPS_OK && conSP->s == ISS_ON)
240  {
241  m_Connected = true;
242  emit Connected();
243  createDeviceInit();
244 
245  if (watchDogTimer != nullptr)
246  {
247  auto nvp = m_BaseDevice->getNumber("WATCHDOG_HEARTBEAT");
248  if (nvp && nvp->at(0)->getValue() > 0)
249  {
250  // Send immediately
251  m_ClientManager->sendNewNumber(nvp);
252  }
253  }
254  }
255  else if (m_Connected && conSP->s == ISS_OFF)
256  {
257  m_Connected = false;
258  emit Disconnected();
259  }
260  }
261 
262  emit switchUpdated(svp);
263 }
264 
265 void GenericDevice::processNumber(INumberVectorProperty *nvp)
266 {
267  QString deviceName = getDeviceName();
268  // uint32_t interface = getDriverInterface();
269  // Q_UNUSED(interface);
270 
271  if (!strcmp(nvp->name, "GEOGRAPHIC_COORD") && nvp->s == IPS_OK &&
272  ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) ||
273  (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE))))
274  {
275  // Update KStars Location once we receive update from INDI, if the source is set to DEVICE
276  dms lng, lat;
277  double elev = 0;
278 
279  INumber *np = IUFindNumber(nvp, "LONG");
280  if (!np)
281  return;
282 
283  // INDI Longitude convention is 0 to 360. We need to turn it back into 0 to 180 EAST, 0 to -180 WEST
284  if (np->value < 180)
285  lng.setD(np->value);
286  else
287  lng.setD(np->value - 360.0);
288 
289  np = IUFindNumber(nvp, "LAT");
290  if (!np)
291  return;
292 
293  lat.setD(np->value);
294 
295  // Double check we have valid values
296  if (lng.Degrees() == 0 && lat.Degrees() == 0)
297  {
298  qCWarning(KSTARS_INDI) << "Ignoring invalid device coordinates.";
299  return;
300  }
301 
302  np = IUFindNumber(nvp, "ELEV");
303  if (np)
304  elev = np->value;
305 
307  std::unique_ptr<GeoLocation> tempGeo;
308 
309  QString newLocationName;
310  if (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)
311  newLocationName = i18n("GPS Location");
312  else
313  newLocationName = i18n("Mount Location");
314 
315  if (geo->name() != newLocationName)
316  {
317  double TZ0 = geo->TZ0();
318  TimeZoneRule *rule = geo->tzrule();
319  tempGeo.reset(new GeoLocation(lng, lat, newLocationName, "", "", TZ0, rule, elev));
320  geo = tempGeo.get();
321  }
322  else
323  {
324  geo->setLong(lng);
325  geo->setLat(lat);
326  }
327 
328  qCInfo(KSTARS_INDI) << "Setting location from device:" << deviceName << "Longitude:" << lng.toDMSString() << "Latitude:" <<
329  lat.toDMSString();
330 
331  KStars::Instance()->data()->setLocation(*geo);
332  }
333  else if (!strcmp(nvp->name, "WATCHDOG_HEARTBEAT"))
334  {
335  if (watchDogTimer == nullptr)
336  {
337  watchDogTimer = new QTimer(this);
338  connect(watchDogTimer, &QTimer::timeout, this, &GenericDevice::resetWatchdog);
339  }
340 
341  if (m_Connected && nvp->np[0].value > 0)
342  {
343  // Reset timer 5 seconds before it is due
344  // To account for any networking delays
345  double nextMS = qMax(100.0, (nvp->np[0].value - 5) * 1000);
346  watchDogTimer->start(nextMS);
347  }
348  else if (nvp->np[0].value == 0)
349  watchDogTimer->stop();
350  }
351 
352  emit numberUpdated(nvp);
353 }
354 
355 void GenericDevice::processText(ITextVectorProperty *tvp)
356 {
357  // Update KStars time once we receive update from INDI, if the source is set to DEVICE
358  if (!strcmp(tvp->name, "TIME_UTC") && tvp->s == IPS_OK &&
359  ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) ||
360  (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE))))
361  {
362  int d, m, y, min, sec, hour;
363  float utcOffset;
364  QDate indiDate;
365  QTime indiTime;
366 
367  IText *tp = IUFindText(tvp, "UTC");
368 
369  if (!tp)
370  {
371  qCWarning(KSTARS_INDI) << "UTC property missing from TIME_UTC";
372  return;
373  }
374 
375  sscanf(tp->text, "%d%*[^0-9]%d%*[^0-9]%dT%d%*[^0-9]%d%*[^0-9]%d", &y, &m, &d, &hour, &min, &sec);
376  indiDate.setDate(y, m, d);
377  indiTime.setHMS(hour, min, sec);
378 
379  KStarsDateTime indiDateTime(QDateTime(indiDate, indiTime, Qt::UTC));
380 
381  tp = IUFindText(tvp, "OFFSET");
382 
383  if (!tp)
384  {
385  qCWarning(KSTARS_INDI) << "Offset property missing from TIME_UTC";
386  return;
387  }
388 
389  sscanf(tp->text, "%f", &utcOffset);
390 
391  qCInfo(KSTARS_INDI) << "Setting UTC time from device:" << getDeviceName() << indiDateTime.toString();
392 
393  KStars::Instance()->data()->changeDateTime(indiDateTime);
394  KStars::Instance()->data()->syncLST();
395 
397  if (geo->tzrule())
398  utcOffset -= geo->tzrule()->deltaTZ();
399 
400  // TZ0 is the timezone WTIHOUT any DST offsets. Above, we take INDI UTC Offset (with DST already included)
401  // and subtract from it the deltaTZ from the current TZ rule.
402  geo->setTZ0(utcOffset);
403  }
404 
405  emit textUpdated(tvp);
406 }
407 
408 void GenericDevice::processLight(ILightVectorProperty *lvp)
409 {
410  emit lightUpdated(lvp);
411 }
412 
413 void GenericDevice::processMessage(int messageID)
414 {
415  emit messageUpdated(messageID);
416 }
417 
419 {
420  // Ignore write-only BLOBs since we only receive it for state-change
421  if (bp->bvp->p == IP_WO)
422  return false;
423 
424  // If any concrete device processed the blob then we return
425  for (auto oneConcreteDevice : m_ConcreteDevices)
426  {
427  if (oneConcreteDevice->processBLOB(bp))
428  return true;
429  }
430 
431  INDIDataTypes dataType;
432 
433  if (!strcmp(bp->format, ".ascii"))
434  dataType = DATA_ASCII;
435  else
436  dataType = DATA_OTHER;
437 
438  QString currentDir = Options::fitsDir();
439  int nr, n = 0;
440 
441  if (currentDir.endsWith('/'))
442  currentDir.truncate(sizeof(currentDir) - 1);
443 
444  QString filename(currentDir + '/');
445 
446  QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
447 
448  filename += QString("%1_").arg(bp->label) + ts + QString(bp->format).trimmed();
449 
450  // strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
451  // bp->aux2 = BLOBFilename;
452 
453  // Text Streaming
454  if (dataType == DATA_ASCII)
455  {
456  // First time, create a data file to hold the stream.
457 
458  auto it = std::find_if(streamFileMetadata.begin(), streamFileMetadata.end(), [bp](const StreamFileMetadata & data)
459  {
460  return (bp->bvp->device == data.device && bp->bvp->name == data.property && bp->name == data.element);
461  });
462 
463  QFile *streamDatafile = nullptr;
464 
465  // New stream data file
466  if (it == streamFileMetadata.end())
467  {
468  StreamFileMetadata metadata;
469  metadata.device = bp->bvp->device;
470  metadata.property = bp->bvp->name;
471  metadata.element = bp->name;
472 
473  // Create new file instance
474  // Since it's a child of this class, don't worry about deallocating it
475  streamDatafile = new QFile(this);
476  metadata.file = streamDatafile;
477 
478  streamFileMetadata.append(metadata);
479  }
480  else
481  streamDatafile = (*it).file;
482 
483  // Try to get
484 
485  QDataStream out(streamDatafile);
486  for (nr = 0; nr < bp->size; nr += n)
487  n = out.writeRawData(static_cast<char *>(bp->blob) + nr, bp->size - nr);
488 
489  out.writeRawData((const char *)"\n", 1);
490  streamDatafile->flush();
491 
492  }
493  else
494  {
495  QFile fits_temp_file(filename);
496  if (!fits_temp_file.open(QIODevice::WriteOnly))
497  {
498  qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName();
499  return false;
500  }
501 
502  QDataStream out(&fits_temp_file);
503 
504  for (nr = 0; nr < (int)bp->size; nr += n)
505  n = out.writeRawData(static_cast<char *>(bp->blob) + nr, bp->size - nr);
506 
507  fits_temp_file.flush();
508  fits_temp_file.close();
509 
510  QByteArray fmt = QString(bp->format).toLower().remove('.').toUtf8();
511  if (QImageReader::supportedImageFormats().contains(fmt))
512  {
513  QUrl url(filename);
514  url.setScheme("file");
515  ImageViewer *iv = new ImageViewer(url, QString(), KStars::Instance());
516  if (iv)
517  iv->show();
518  }
519  }
520 
521  if (dataType == DATA_OTHER)
522  KStars::Instance()->statusBar()->showMessage(i18n("Data file saved to %1", filename), 0);
523 
524  emit BLOBUpdated(bp);
525  return true;
526 }
527 
528 bool GenericDevice::setConfig(INDIConfig tConfig)
529 {
530  auto svp = m_BaseDevice->getSwitch("CONFIG_PROCESS");
531 
532  if (!svp)
533  return false;
534 
535  const char *strConfig = nullptr;
536 
537  switch (tConfig)
538  {
539  case LOAD_LAST_CONFIG:
540  strConfig = "CONFIG_LOAD";
541  break;
542 
543  case SAVE_CONFIG:
544  strConfig = "CONFIG_SAVE";
545  break;
546 
547  case LOAD_DEFAULT_CONFIG:
548  strConfig = "CONFIG_DEFAULT";
549  break;
550 
551  case PURGE_CONFIG:
552  strConfig = "CONFIG_PURGE";
553  break;
554  }
555 
556  svp->reset();
557  if (strConfig)
558  {
559  auto sp = svp->findWidgetByName(strConfig);
560  if (!sp)
561  return false;
562  sp->setState(ISS_ON);
563  }
564 
565  m_ClientManager->sendNewSwitch(svp);
566 
567  return true;
568 }
569 
570 void GenericDevice::createDeviceInit()
571 {
572  if (Options::showINDIMessages())
573  KStars::Instance()->statusBar()->showMessage(i18n("%1 is online.", m_Name), 0);
574 
576 }
577 
578 /*********************************************************************************/
579 /* Update the Driver's Time */
580 /*********************************************************************************/
581 void GenericDevice::updateTime()
582 {
583  QString offset, isoTS;
584 
585  offset = QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2);
586 
587  //QTime newTime( KStars::Instance()->data()->ut().time());
588  //QDate newDate( KStars::Instance()->data()->ut().date());
589 
590  //isoTS = QString("%1-%2-%3T%4:%5:%6").arg(newDate.year()).arg(newDate.month()).arg(newDate.day()).arg(newTime.hour()).arg(newTime.minute()).arg(newTime.second());
591 
592  isoTS = KStars::Instance()->data()->ut().toString(Qt::ISODate).remove('Z');
593 
594  /* Update Date/Time */
595  auto timeUTC = m_BaseDevice->getText("TIME_UTC");
596 
597  if (timeUTC)
598  {
599  auto timeEle = timeUTC->findWidgetByName("UTC");
600  if (timeEle)
601  timeEle->setText(isoTS.toLatin1().constData());
602 
603  auto offsetEle = timeUTC->findWidgetByName("OFFSET");
604  if (offsetEle)
605  offsetEle->setText(offset.toLatin1().constData());
606 
607  if (timeEle && offsetEle)
608  m_ClientManager->sendNewText(timeUTC);
609  }
610 }
611 
612 /*********************************************************************************/
613 /* Update the Driver's Geographical Location */
614 /*********************************************************************************/
615 void GenericDevice::updateLocation()
616 {
618  double longNP;
619 
620  if (geo->lng()->Degrees() >= 0)
621  longNP = geo->lng()->Degrees();
622  else
623  longNP = dms(geo->lng()->Degrees() + 360.0).Degrees();
624 
625  auto nvp = m_BaseDevice->getNumber("GEOGRAPHIC_COORD");
626 
627  if (!nvp)
628  return;
629 
630  auto np = nvp->findWidgetByName("LONG");
631 
632  if (!np)
633  return;
634 
635  np->setValue(longNP);
636 
637  np = nvp->findWidgetByName("LAT");
638  if (!np)
639  return;
640 
641  np->setValue(geo->lat()->Degrees());
642 
643  np = nvp->findWidgetByName("ELEV");
644  if (!np)
645  return;
646 
647  np->setValue(geo->elevation());
648 
649  m_ClientManager->sendNewNumber(nvp);
650 }
651 
652 bool GenericDevice::Connect()
653 {
654  m_ClientManager->connectDevice(m_Name.toLatin1().constData());
655  return true;
656 }
657 
658 bool GenericDevice::Disconnect()
659 {
660  m_ClientManager->disconnectDevice(m_Name.toLatin1().constData());
661  return true;
662 }
663 
664 bool GenericDevice::setProperty(QObject *setPropCommand)
665 {
666  GDSetCommand *indiCommand = static_cast<GDSetCommand *>(setPropCommand);
667 
668  //qDebug() << Q_FUNC_INFO << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue;
669 
670  auto pp = m_BaseDevice->getProperty(indiCommand->indiProperty.toLatin1().constData());
671 
672  if (!pp)
673  return false;
674 
675  switch (indiCommand->propType)
676  {
677  case INDI_SWITCH:
678  {
679  auto svp = pp->getSwitch();
680 
681  if (!svp)
682  return false;
683 
684  auto sp = svp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
685 
686  if (!sp)
687  return false;
688 
689  if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
690  svp->reset();
691 
692  sp->setState(indiCommand->elementValue.toInt() == 0 ? ISS_OFF : ISS_ON);
693 
694  //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
695  m_ClientManager->sendNewSwitch(svp);
696 
697  return true;
698  }
699 
700  case INDI_NUMBER:
701  {
702  auto nvp = pp->getNumber();
703 
704  if (!nvp)
705  return false;
706 
707  auto np = nvp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
708 
709  if (!np)
710  return false;
711 
712  double value = indiCommand->elementValue.toDouble();
713 
714  if (value == np->getValue())
715  return true;
716 
717  np->setValue(value);
718 
719  //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
720  m_ClientManager->sendNewNumber(nvp);
721  }
722  break;
723  // TODO: Add set property for other types of properties
724  default:
725  break;
726  }
727 
728  return true;
729 }
730 
731 bool GenericDevice::getMinMaxStep(const QString &propName, const QString &elementName, double *min, double *max,
732  double *step)
733 {
734  auto nvp = m_BaseDevice->getNumber(propName.toLatin1());
735 
736  if (!nvp)
737  return false;
738 
739  auto np = nvp->findWidgetByName(elementName.toLatin1());
740 
741  if (!np)
742  return false;
743 
744  *min = np->getMin();
745  *max = np->getMax();
746  *step = np->getStep();
747 
748  return true;
749 }
750 
751 IPState GenericDevice::getState(const QString &propName)
752 {
753  return m_BaseDevice->getPropertyState(propName.toLatin1().constData());
754 }
755 
756 IPerm GenericDevice::getPermission(const QString &propName)
757 {
758  return m_BaseDevice->getPropertyPermission(propName.toLatin1().constData());
759 }
760 
761 INDI::Property GenericDevice::getProperty(const QString &propName)
762 {
763  return m_BaseDevice->getProperty(propName.toLatin1().constData());
764 }
765 
766 bool GenericDevice::setJSONProperty(const QString &propName, const QJsonArray &propElements)
767 {
768  for (auto &oneProp : * (m_BaseDevice->getProperties()))
769  {
770  if (propName == QString(oneProp->getName()))
771  {
772  switch (oneProp->getType())
773  {
774  case INDI_SWITCH:
775  {
776  auto svp = oneProp->getSwitch();
777  if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
778  svp->reset();
779 
780  for (auto oneElement : propElements)
781  {
782  QJsonObject oneElementObject = oneElement.toObject();
783  auto sp = svp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
784  if (sp)
785  {
786  sp->setState(static_cast<ISState>(oneElementObject["state"].toInt()));
787  }
788  }
789 
790  m_ClientManager->sendNewSwitch(svp);
791  return true;
792  }
793 
794  case INDI_NUMBER:
795  {
796  auto nvp = oneProp->getNumber();
797  for (const auto &oneElement : propElements)
798  {
799  QJsonObject oneElementObject = oneElement.toObject();
800  auto np = nvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
801  if (np)
802  {
803  double newValue = oneElementObject["value"].toDouble(std::numeric_limits<double>::quiet_NaN());
804  if (std::isnan(newValue))
805  {
806  f_scansexa(oneElementObject["value"].toString().toLatin1().constData(), &newValue);
807  }
808  np->setValue(newValue);
809  }
810  }
811 
812  m_ClientManager->sendNewNumber(nvp);
813  return true;
814  }
815 
816  case INDI_TEXT:
817  {
818  auto tvp = oneProp->getText();
819  for (const auto &oneElement : propElements)
820  {
821  QJsonObject oneElementObject = oneElement.toObject();
822  auto tp = tvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
823  if (tp)
824  tp->setText(oneElementObject["text"].toString().toLatin1().constData());
825  }
826 
827  m_ClientManager->sendNewText(tvp);
828  return true;
829  }
830 
831  case INDI_BLOB:
832  // TODO
833  break;
834 
835  default:
836  break;
837  }
838  }
839  }
840 
841  return false;
842 }
843 
844 bool GenericDevice::getJSONProperty(const QString &propName, QJsonObject &propObject, bool compact)
845 {
846  for (auto &oneProp : * (m_BaseDevice->getProperties()))
847  {
848  if (propName == QString(oneProp->getName()))
849  {
850  switch (oneProp->getType())
851  {
852  case INDI_SWITCH:
853  propertyToJson(oneProp->getSwitch(), propObject, compact);
854  return true;
855 
856  case INDI_NUMBER:
857  propertyToJson(oneProp->getNumber(), propObject, compact);
858  return true;
859 
860  case INDI_TEXT:
861  propertyToJson(oneProp->getText(), propObject, compact);
862  return true;
863 
864  case INDI_LIGHT:
865  propertyToJson(oneProp->getLight(), propObject, compact);
866  return true;
867 
868  case INDI_BLOB:
869  // TODO
870  break;
871 
872  default:
873  break;
874  }
875  }
876  }
877 
878  return false;
879 }
880 
881 bool GenericDevice::getJSONBLOB(const QString &propName, const QString &elementName, QJsonObject &blobObject)
882 {
883  auto blobProperty = m_BaseDevice->getProperty(propName.toLatin1().constData());
884  if (blobProperty == nullptr)
885  return false;
886 
887  auto oneBLOB = blobProperty->getBLOB()->findWidgetByName(elementName.toLatin1().constData());
888  if (!oneBLOB)
889  return false;
890 
891  // Now convert to base64 and send back.
892  QByteArray data = QByteArray::fromRawData(static_cast<const char *>(oneBLOB->getBlob()), oneBLOB->getBlobLen());
893 
895  blobObject.insert("property", propName);
896  blobObject.insert("element", elementName);
897  blobObject.insert("size", encoded.size());
898  blobObject.insert("data", encoded);
899 
900  return true;
901 }
902 
903 void GenericDevice::resetWatchdog()
904 {
905  auto nvp = m_BaseDevice->getNumber("WATCHDOG_HEARTBEAT");
906 
907  if (nvp)
908  // Send heartbeat to driver
909  m_ClientManager->sendNewNumber(nvp);
910 }
911 
912 ConcreteDevice *GenericDevice::getConcreteDevice(uint32_t interface)
913 {
914  if (m_ConcreteDevices.contains(interface))
915  return m_ConcreteDevices[interface].get();
916  return nullptr;
917 }
918 
919 void GenericDevice::generateDevices()
920 {
921  // Mount
922  if (m_DriverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE)
923  {
924  auto mount = new ISD::Mount(this);
925  m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].reset(mount);
926  mount->registeProperties();
927  if (m_Connected)
928  {
929  mount->processProperties();
930  emit newMount(mount);
931  }
932  else
933  {
934  connect(mount, &ISD::ConcreteDevice::ready, this, [this, mount]()
935  {
936  emit newMount(mount);
937  });
938  }
939 
940  }
941 
942  // Camera
943  if (m_DriverInterface & INDI::BaseDevice::CCD_INTERFACE)
944  {
945  auto camera = new ISD::Camera(this);
946  m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].reset(camera);
947  camera->registeProperties();
948  if (m_Connected)
949  {
950  camera->processProperties();
951  emit newCamera(camera);
952  }
953  else
954  {
955  connect(camera, &ISD::ConcreteDevice::ready, this, [this, camera]()
956  {
957  emit newCamera(camera);
958  });
959  }
960  }
961 
962  // Guider
963  if (m_DriverInterface & INDI::BaseDevice::GUIDER_INTERFACE)
964  {
965  auto guider = new ISD::Guider(this);
966  m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].reset(guider);
967  guider->registeProperties();
968  if (m_Connected)
969  {
970  guider->processProperties();
971  emit newGuider(guider);
972  }
973  else
974  {
975  connect(guider, &ISD::ConcreteDevice::ready, this, [this, guider]()
976  {
977  emit newGuider(guider);
978  });
979  }
980  }
981 
982  // Focuser
983  if (m_DriverInterface & INDI::BaseDevice::FOCUSER_INTERFACE)
984  {
985  auto focuser = new ISD::Focuser(this);
986  m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].reset(focuser);
987  focuser->registeProperties();
988  if (m_Connected)
989  {
990  focuser->processProperties();
991  emit newFocuser(focuser);
992  }
993  else
994  {
995  connect(focuser, &ISD::ConcreteDevice::ready, this, [this, focuser]()
996  {
997  emit newFocuser(focuser);
998  });
999  }
1000  }
1001 
1002  // Filter Wheel
1003  if (m_DriverInterface & INDI::BaseDevice::FILTER_INTERFACE)
1004  {
1005  auto filterWheel = new ISD::FilterWheel(this);
1006  m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].reset(filterWheel);
1007  filterWheel->registeProperties();
1008  if (m_Connected)
1009  {
1010  filterWheel->processProperties();
1011  emit newFilterWheel(filterWheel);
1012  }
1013  else
1014  {
1015  connect(filterWheel, &ISD::ConcreteDevice::ready, this, [this, filterWheel]()
1016  {
1017  emit newFilterWheel(filterWheel);
1018  });
1019  }
1020  }
1021 
1022  // Dome
1023  if (m_DriverInterface & INDI::BaseDevice::DOME_INTERFACE)
1024  {
1025  auto dome = new ISD::Dome(this);
1026  m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].reset(dome);
1027  dome->registeProperties();
1028  if (m_Connected)
1029  {
1030  dome->processProperties();
1031  emit newDome(dome);
1032  }
1033  else
1034  {
1035  connect(dome, &ISD::ConcreteDevice::ready, this, [this, dome]()
1036  {
1037  emit newDome(dome);
1038  });
1039  }
1040  }
1041 
1042  // GPS
1043  if (m_DriverInterface & INDI::BaseDevice::GPS_INTERFACE)
1044  {
1045  auto gps = new ISD::GPS(this);
1046  m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].reset(gps);
1047  gps->registeProperties();
1048  if (m_Connected)
1049  {
1050  gps->processProperties();
1051  emit newGPS(gps);
1052  }
1053  else
1054  {
1055  connect(gps, &ISD::ConcreteDevice::ready, this, [this, gps]()
1056  {
1057  emit newGPS(gps);
1058  });
1059  }
1060  }
1061 
1062  // Weather
1063  if (m_DriverInterface & INDI::BaseDevice::WEATHER_INTERFACE)
1064  {
1065  auto weather = new ISD::Weather(this);
1066  m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].reset(weather);
1067  weather->registeProperties();
1068  if (m_Connected)
1069  {
1070  weather->processProperties();
1071  emit newWeather(weather);
1072  }
1073  else
1074  {
1075  connect(weather, &ISD::ConcreteDevice::ready, this, [this, weather]()
1076  {
1077  emit newWeather(weather);
1078  });
1079  }
1080  }
1081 
1082  // Adaptive Optics
1083  if (m_DriverInterface & INDI::BaseDevice::AO_INTERFACE)
1084  {
1085  auto ao = new ISD::AdaptiveOptics(this);
1086  m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].reset(ao);
1087  ao->registeProperties();
1088  if (m_Connected)
1089  {
1090  ao->processProperties();
1091  emit newAdaptiveOptics(ao);
1092  }
1093  else
1094  {
1095  connect(ao, &ISD::ConcreteDevice::ready, this, [this, ao]()
1096  {
1097  emit newAdaptiveOptics(ao);
1098  });
1099  }
1100  }
1101 
1102  // Dust Cap
1103  if (m_DriverInterface & INDI::BaseDevice::DUSTCAP_INTERFACE)
1104  {
1105  auto dustCap = new ISD::DustCap(this);
1106  m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].reset(dustCap);
1107  dustCap->registeProperties();
1108  if (m_Connected)
1109  {
1110  dustCap->processProperties();
1111  emit newDustCap(dustCap);
1112  }
1113  else
1114  {
1115  connect(dustCap, &ISD::ConcreteDevice::ready, this, [this, dustCap]()
1116  {
1117  emit newDustCap(dustCap);
1118  });
1119  }
1120  }
1121 
1122  // Light box
1123  if (m_DriverInterface & INDI::BaseDevice::LIGHTBOX_INTERFACE)
1124  {
1125  auto lightBox = new ISD::LightBox(this);
1126  m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].reset(lightBox);
1127  lightBox->registeProperties();
1128  if (m_Connected)
1129  {
1130  lightBox->processProperties();
1131  emit newLightBox(lightBox);
1132  }
1133  else
1134  {
1135  connect(lightBox, &ISD::ConcreteDevice::ready, this, [this, lightBox]()
1136  {
1137  emit newLightBox(lightBox);
1138  });
1139  }
1140  }
1141 
1142  // Rotator
1143  if (m_DriverInterface & INDI::BaseDevice::ROTATOR_INTERFACE)
1144  {
1145  auto rotator = new ISD::Rotator(this);
1146  m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].reset(rotator);
1147  rotator->registeProperties();
1148  if (m_Connected)
1149  {
1150  rotator->processProperties();
1151  emit newRotator(rotator);
1152  }
1153  else
1154  {
1155  connect(rotator, &ISD::ConcreteDevice::ready, this, [this, rotator]()
1156  {
1157  emit newRotator(rotator);
1158  });
1159  }
1160  }
1161 
1162  // Detector
1163  if (m_DriverInterface & INDI::BaseDevice::DETECTOR_INTERFACE)
1164  {
1165  auto detector = new ISD::Detector(this);
1166  m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].reset(detector);
1167  detector->registeProperties();
1168  if (m_Connected)
1169  {
1170  detector->processProperties();
1171  emit newDetector(detector);
1172  }
1173  else
1174  {
1175  connect(detector, &ISD::ConcreteDevice::ready, this, [this, detector]()
1176  {
1177  emit newDetector(detector);
1178  });
1179  }
1180  }
1181 
1182  // Spectrograph
1183  if (m_DriverInterface & INDI::BaseDevice::SPECTROGRAPH_INTERFACE)
1184  {
1185  auto spectrograph = new ISD::Spectrograph(this);
1186  m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].reset(spectrograph);
1187  spectrograph->registeProperties();
1188  if (m_Connected)
1189  {
1190  spectrograph->processProperties();
1191  emit newSpectrograph(spectrograph);
1192  }
1193  else
1194  {
1195  connect(spectrograph, &ISD::ConcreteDevice::ready, this, [this, spectrograph]()
1196  {
1197  emit newSpectrograph(spectrograph);
1198  });
1199  }
1200  }
1201 
1202  // Correlator
1203  if (m_DriverInterface & INDI::BaseDevice::CORRELATOR_INTERFACE)
1204  {
1205  auto correlator = new ISD::Correlator(this);
1206  m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].reset(correlator);
1207  correlator->registeProperties();
1208  if (m_Connected)
1209  {
1210  correlator->processProperties();
1211  emit newCorrelator(correlator);
1212  }
1213  else
1214  {
1215  connect(correlator, &ISD::ConcreteDevice::ready, this, [this, correlator]()
1216  {
1217  emit newCorrelator(correlator);
1218  });
1219  }
1220  }
1221 
1222  // Auxiliary
1223  if (m_DriverInterface & INDI::BaseDevice::AUX_INTERFACE)
1224  {
1225  auto aux = new ISD::Auxiliary(this);
1226  m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].reset(aux);
1227  aux->registeProperties();
1228  if (m_Connected)
1229  {
1230  aux->processProperties();
1231  emit newAuxiliary(aux);
1232  }
1233  else
1234  {
1235  connect(aux, &ISD::ConcreteDevice::ready, this, [this, aux]()
1236  {
1237  emit newAuxiliary(aux);
1238  });
1239  }
1240  }
1241 }
1242 
1243 void propertyToJson(ISwitchVectorProperty *svp, QJsonObject &propObject, bool compact)
1244 {
1245  QJsonArray switches;
1246  for (int i = 0; i < svp->nsp; i++)
1247  {
1248  QJsonObject oneSwitch = {{"name", svp->sp[i].name}, {"state", svp->sp[i].s}};
1249  if (!compact)
1250  oneSwitch.insert("label", svp->sp[i].label);
1251  switches.append(oneSwitch);
1252  }
1253 
1254  propObject = {{"device", svp->device}, {"name", svp->name}, {"state", svp->s}, {"switches", switches}};
1255  if (!compact)
1256  {
1257  propObject.insert("label", svp->label);
1258  propObject.insert("group", svp->group);
1259  propObject.insert("perm", svp->p);
1260  propObject.insert("rule", svp->r);
1261  }
1262 }
1263 
1264 void propertyToJson(INumberVectorProperty *nvp, QJsonObject &propObject, bool compact)
1265 {
1266  QJsonArray numbers;
1267  for (int i = 0; i < nvp->nnp; i++)
1268  {
1269  QJsonObject oneNumber = {{"name", nvp->np[i].name}, {"value", nvp->np[i].value}};
1270  if (!compact)
1271  {
1272  oneNumber.insert("label", nvp->np[i].label);
1273  oneNumber.insert("min", nvp->np[i].min);
1274  oneNumber.insert("max", nvp->np[i].max);
1275  oneNumber.insert("step", nvp->np[i].step);
1276  oneNumber.insert("format", nvp->np[i].format);
1277  }
1278  numbers.append(oneNumber);
1279  }
1280 
1281  propObject = {{"device", nvp->device}, {"name", nvp->name}, {"state", nvp->s}, {"numbers", numbers}};
1282  if (!compact)
1283  {
1284  propObject.insert("label", nvp->label);
1285  propObject.insert("group", nvp->group);
1286  propObject.insert("perm", nvp->p);
1287  }
1288 }
1289 
1290 void propertyToJson(ITextVectorProperty *tvp, QJsonObject &propObject, bool compact)
1291 {
1292  QJsonArray Texts;
1293  for (int i = 0; i < tvp->ntp; i++)
1294  {
1295  QJsonObject oneText = {{"name", tvp->tp[i].name}, {"text", tvp->tp[i].text}};
1296  if (!compact)
1297  {
1298  oneText.insert("label", tvp->tp[i].label);
1299  }
1300  Texts.append(oneText);
1301  }
1302 
1303  propObject = {{"device", tvp->device}, {"name", tvp->name}, {"state", tvp->s}, {"texts", Texts}};
1304  if (!compact)
1305  {
1306  propObject.insert("label", tvp->label);
1307  propObject.insert("group", tvp->group);
1308  propObject.insert("perm", tvp->p);
1309  }
1310 }
1311 
1312 void propertyToJson(ILightVectorProperty *lvp, QJsonObject &propObject, bool compact)
1313 {
1314  QJsonArray Lights;
1315  for (int i = 0; i < lvp->nlp; i++)
1316  {
1317  QJsonObject oneLight = {{"name", lvp->lp[i].name}, {"state", lvp->lp[i].s}};
1318  if (!compact)
1319  {
1320  oneLight.insert("label", lvp->lp[i].label);
1321  }
1322  Lights.append(oneLight);
1323  }
1324 
1325  propObject = {{"device", lvp->device}, {"name", lvp->name}, {"state", lvp->s}, {"lights", Lights}};
1326  if (!compact)
1327  {
1328  propObject.insert("label", lvp->label);
1329  propObject.insert("group", lvp->group);
1330  }
1331 }
1332 
1333 void propertyToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1334 {
1335  switch (prop->getType())
1336  {
1337  case INDI_SWITCH:
1338  propertyToJson(prop->getSwitch(), propObject, compact);
1339  break;
1340  case INDI_TEXT:
1341  propertyToJson(prop->getText(), propObject, compact);
1342  break;
1343  case INDI_NUMBER:
1344  propertyToJson(prop->getNumber(), propObject, compact);
1345  break;
1346  case INDI_LIGHT:
1347  propertyToJson(prop->getLight(), propObject, compact);
1348  break;
1349  default:
1350  break;
1351  }
1352 }
1353 }
1354 
1355 #ifndef KSTARS_LITE
1356 QDBusArgument &operator<<(QDBusArgument &argument, const ISD::ParkStatus &source)
1357 {
1358  argument.beginStructure();
1359  argument << static_cast<int>(source);
1360  argument.endStructure();
1361  return argument;
1362 }
1363 
1364 const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::ParkStatus &dest)
1365 {
1366  int a;
1367  argument.beginStructure();
1368  argument >> a;
1369  argument.endStructure();
1370  dest = static_cast<ISD::ParkStatus>(a);
1371  return argument;
1372 }
1373 #endif
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
void truncate(int position)
void forceUpdateNow()
Convenience function; simply calls forceUpdate(true).
Definition: skymap.h:377
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
Definition: kstarsdata.cpp:403
virtual void setD(const double &x)
Sets floating-point value of angle, in degrees.
Definition: dms.h:179
int size() const const
QByteArray fromRawData(const char *data, int size)
virtual bool processBLOB(IBLOB *bp) override
processBLOB Process Binary BLOB
Definition: indistd.cpp:418
virtual bool open(QIODevice::OpenMode mode) override
QDateTime currentDateTime()
QString trimmed() const const
void timeChanged()
The time has changed (emitted by setUTC() )
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
SkyMap * map() const
Definition: kstars.h:143
QByteArray toLatin1() const const
QByteArray toBase64(QByteArray::Base64Options options) const const
QList< QByteArray > supportedImageFormats()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual QString fileName() const const override
void setScheme(const QString &scheme)
static KStars * Instance()
Definition: kstars.h:125
void showMessage(const QString &message, int timeout)
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
void start(int msec)
void changeDateTime(const KStarsDateTime &newDate)
Change the current simulation date/time to the KStarsDateTime argument.
Definition: kstarsdata.cpp:327
QString i18n(const char *text, const TYPE &arg...)
QString & setNum(short n, int base)
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
QJsonObject::iterator insert(const QString &key, const QJsonValue &value)
char * toString(const T &value)
void timeout()
GeoLocation * geo()
Definition: kstarsdata.h:229
int writeRawData(const char *s, int len)
QByteArray toUtf8() const const
bool setHMS(int h, int m, int s, int ms)
QJsonValue value(const QString &key) const const
GeoCoordinates geo(const QVariant &location)
virtual void close() override
QString & remove(int position, int n)
QStatusBar * statusBar() const const
void show()
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
const KStarsDateTime & ut() const
Definition: kstarsdata.h:156
QString toLower() const const
bool flush()
Image viewer window for KStars.
Definition: imageviewer.h:56
const char * constData() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QDebug operator<<(QDebug d, const QCPVector2D &vec)
Definition: qcustomplot.h:450
void stop()
const double & Degrees() const
Definition: dms.h:141
void beginStructure()
const char * name(StandardAction id)
void endStructure()
KStarsData * data() const
Definition: kstars.h:137
bool setDate(int year, int month, int day)
void append(const QJsonValue &value)
QString toString(Qt::DateFormat format) const const
void syncLST()
Sync the LST with the simulation clock.
Definition: kstarsdata.cpp:322
Relevant data about an observing location on Earth.
Definition: geolocation.h:27
QVariant property(const char *name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:54 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.