Kstars

indistd.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
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 "indilistener.h"
22#include "indimount.h"
23#include "indicamera.h"
24#include "indiguider.h"
25#include "indifocuser.h"
26#include "indifilterwheel.h"
27#include "indidome.h"
28#include "indigps.h"
29#include "indiweather.h"
30#include "indiadaptiveoptics.h"
31#include "indidustcap.h"
32#include "indilightbox.h"
33#include "indidetector.h"
34#include "indirotator.h"
35#include "indispectrograph.h"
36#include "indicorrelator.h"
37#include "indiauxiliary.h"
38
39#include "genericdeviceadaptor.h"
40#include <indicom.h>
41#include <QImageReader>
42#include <QStatusBar>
43
44namespace ISD
45{
46
47GDSetCommand::GDSetCommand(INDI_PROPERTY_TYPE inPropertyType, const QString &inProperty, const QString &inElement,
48 QVariant qValue, QObject *parent)
49 : QObject(parent), propType(inPropertyType), indiProperty((inProperty)), indiElement(inElement), elementValue(qValue)
50{
51}
52
53uint8_t GenericDevice::m_ID = 1;
54GenericDevice::GenericDevice(DeviceInfo &idv, ClientManager *cm, QObject *parent) : GDInterface(parent)
55{
56 // Register DBus
57 new GenericDeviceAdaptor(this);
58 QDBusConnection::sessionBus().registerObject(QString("/KStars/INDI/GenericDevice/%1").arg(getID()), this);
59
60 m_DeviceInfo = &idv;
61 m_DriverInfo = idv.getDriverInfo();
62 m_BaseDevice = idv.getBaseDevice();
63 m_ClientManager = cm;
64
65 Q_ASSERT_X(m_BaseDevice, __FUNCTION__, "Base device is invalid.");
66 Q_ASSERT_X(m_ClientManager, __FUNCTION__, "Client manager is invalid.");
67
68 m_Name = m_BaseDevice.getDeviceName();
69
70 setObjectName(m_Name);
71
72 m_DriverInterface = m_BaseDevice.getDriverInterface();
73 m_DriverVersion = m_BaseDevice.getDriverVersion();
74
75 registerDBusType();
76
77 // JM 2020-09-05: In case KStars time change, update driver time if applicable.
78 connect(KStarsData::Instance()->clock(), &SimClock::timeChanged, this, [this]()
79 {
80 if (Options::useTimeUpdate() && Options::timeSource() == "KStars")
81 {
82 if (isConnected())
83 {
84 auto tvp = m_BaseDevice.getText("TIME_UTC");
85 if (tvp && tvp.getPermission() != IP_RO)
86 updateTime();
87 }
88 }
89 });
90
91 m_ReadyTimer = new QTimer(this);
92 m_ReadyTimer->setInterval(250);
93 m_ReadyTimer->setSingleShot(true);
94 connect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout, Qt::UniqueConnection);
95
96 m_TimeUpdateTimer = new QTimer(this);
97 m_TimeUpdateTimer->setInterval(5000);
98 m_TimeUpdateTimer->setSingleShot(true);
99 connect(m_TimeUpdateTimer, &QTimer::timeout, this, &GenericDevice::checkTimeUpdate, Qt::UniqueConnection);
100
101 m_LocationUpdateTimer = new QTimer(this);
102 m_LocationUpdateTimer->setInterval(5000);
103 m_LocationUpdateTimer->setSingleShot(true);
104 connect(m_LocationUpdateTimer, &QTimer::timeout, this, &GenericDevice::checkLocationUpdate, Qt::UniqueConnection);
105
106}
107
108GenericDevice::~GenericDevice()
109{
110 for (auto &metadata : streamFileMetadata)
111 metadata.file->close();
112}
113
114void GenericDevice::handleTimeout()
115{
116 generateDevices();
117 // N.B. JM 2022.10.15: Do not disconnect timer.
118 // It is possible that other properties can come later.
119 // Even they do not make it in the 250ms window. Increasing timeout value alone
120 // to 1000ms or more would improve the situation but is not sufficient to account for
121 // unexpected delays. Therefore, the best solution is to keep the timer active.
122 //m_ReadyTimer->disconnect(this);
123 m_Ready = true;
124 emit ready();
125}
126
127void GenericDevice::checkTimeUpdate()
128{
129 auto tvp = getProperty("TIME_UTC");
130 if (tvp)
131 {
132 auto timeTP = tvp.getText();
133 // If time still empty, then force update.
134 if (timeTP && timeTP->getPermission() != IP_RO && timeTP->getState() == IPS_IDLE)
135 updateTime();
136 }
137
138}
139
140void GenericDevice::checkLocationUpdate()
141{
142 auto nvp = getProperty("GEOGRAPHIC_COORD");
143 if (nvp)
144 {
145 auto locationNP = nvp.getNumber();
146 // If time still empty, then force update.
147 if (locationNP && locationNP->getPermission() != IP_RO && locationNP->getState() == IPS_IDLE)
148 updateLocation();
149 }
150}
151
152void GenericDevice::registerDBusType()
153{
154#ifndef KSTARS_LITE
155 static bool isRegistered = false;
156
157 if (isRegistered == false)
158 {
159 qRegisterMetaType<ISD::ParkStatus>("ISD::ParkStatus");
160 qDBusRegisterMetaType<ISD::ParkStatus>();
161 isRegistered = true;
162 }
163#endif
164}
165
166const QString &GenericDevice::getDeviceName() const
167{
168 return m_Name;
169}
170
171void GenericDevice::registerProperty(INDI::Property prop)
172{
173 if (!prop.getRegistered())
174 return;
175
176 m_ReadyTimer->start();
177
178 const QString name = prop.getName();
179
180 // In case driver already started
181 if (name == "CONNECTION")
182 {
183 auto svp = prop.getSwitch();
184
185 // Still connecting/disconnecting...
186 if (!svp || svp->getState() == IPS_BUSY)
187 return;
188
189 auto conSP = svp->findWidgetByName("CONNECT");
190
191 if (!conSP)
192 return;
193
194 if (m_Connected == false && svp->getState() == IPS_OK && conSP->getState() == ISS_ON)
195 {
196 m_Connected = true;
197 emit Connected();
198 createDeviceInit();
199 }
200 else if (m_Connected && conSP->getState() == ISS_OFF)
201 {
202 m_Connected = false;
203 emit Disconnected();
204 }
205 else
206 m_Ready = (svp->s == IPS_OK && conSP->s == ISS_ON);
207 }
208 else if (name == "DRIVER_INFO")
209 {
210 auto tvp = prop.getText();
211 if (tvp)
212 {
213 auto tp = tvp->findWidgetByName("DRIVER_INTERFACE");
214 if (tp)
215 {
216 m_DriverInterface = static_cast<uint32_t>(atoi(tp->getText()));
217 emit interfaceDefined();
218 }
219
220 tp = tvp->findWidgetByName("DRIVER_VERSION");
221 if (tp)
222 {
223 m_DriverVersion = QString(tp->getText());
224 }
225 }
226 }
227 else if (name == "SYSTEM_PORTS")
228 {
229 // Check if our current port is set to one of the system ports. This indicates that the port
230 // is not mapped yet to a permenant designation
231 auto svp = prop.getSwitch();
232 auto port = m_BaseDevice.getText("DEVICE_PORT");
233 if (svp && port)
234 {
235 for (const auto &it : *svp)
236 {
237 if (it.isNameMatch(port.at(0)->getText()))
238 {
239 emit systemPortDetected();
240 break;
241 }
242 }
243 }
244 }
245 else if (name == "TIME_UTC" && Options::useTimeUpdate())
246 {
247 const auto &tvp = prop.getText();
248
249 if (tvp)
250 {
251 if (Options::timeSource() == "KStars" && tvp->getPermission() != IP_RO)
252 updateTime();
253 else
254 m_TimeUpdateTimer->start();
255 }
256 }
257 else if (name == "GEOGRAPHIC_COORD" && Options::useGeographicUpdate())
258 {
259 if (Options::locationSource() == "KStars" && prop.getPermission() != IP_RO)
260 updateLocation();
261 else
262 m_LocationUpdateTimer->start();
263 }
264 else if (name == "WATCHDOG_HEARTBEAT")
265 {
266 if (watchDogTimer == nullptr)
267 {
268 watchDogTimer = new QTimer(this);
269 connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(resetWatchdog()));
270 }
271
272 if (m_Connected && prop.getNumber()->at(0)->getValue() > 0)
273 {
274 // Send immediately a heart beat
275 m_ClientManager->sendNewProperty(prop);
276 }
277 }
278
279 emit propertyDefined(prop);
280}
281
282void GenericDevice::updateProperty(INDI::Property prop)
283{
284 switch (prop.getType())
285 {
286 case INDI_SWITCH:
287 processSwitch(prop);
288 break;
289 case INDI_NUMBER:
290 processNumber(prop);
291 break;
292 case INDI_TEXT:
293 processText(prop);
294 break;
295 case INDI_LIGHT:
296 processLight(prop);
297 break;
298 case INDI_BLOB:
299 processBLOB(prop);
300 break;
301 default:
302 break;
303 }
304}
305
306void GenericDevice::removeProperty(INDI::Property prop)
307{
308 emit propertyDeleted(prop);
309}
310
311void GenericDevice::processSwitch(INDI::Property prop)
312{
313 if (prop.isNameMatch("CONNECTION"))
314 {
315 // Still connecting/disconnecting...
316 if (prop.getState() == IPS_BUSY)
317 return;
318
319 auto connectionOn = prop.getSwitch()->findWidgetByName("CONNECT");
320 if (m_Connected == false && prop.getState() == IPS_OK && connectionOn->getState() == ISS_ON)
321 {
322 m_Ready = false;
323 connect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout, Qt::UniqueConnection);
324
325 m_Connected = true;
326 emit Connected();
327 createDeviceInit();
328
329 if (watchDogTimer != nullptr)
330 {
331 auto nvp = m_BaseDevice.getNumber("WATCHDOG_HEARTBEAT");
332 if (nvp && nvp.at(0)->getValue() > 0)
333 {
334 // Send immediately
335 m_ClientManager->sendNewProperty(nvp);
336 }
337 }
338
339 m_ReadyTimer->start();
340 }
341 else if (m_Connected && connectionOn->getState() == ISS_OFF)
342 {
343 disconnect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout);
344 m_Connected = false;
345 m_Ready = false;
346 emit Disconnected();
347 }
348 else
349 m_Ready = (prop.getState() == IPS_OK && connectionOn->getState() == ISS_ON);
350 }
351
352 emit propertyUpdated(prop);
353}
354
355void GenericDevice::processNumber(INDI::Property prop)
356{
357 QString deviceName = getDeviceName();
358 auto nvp = prop.getNumber();
359
360 if (prop.isNameMatch("GEOGRAPHIC_COORD") && prop.getState() == IPS_OK && Options::locationSource() == deviceName)
361 {
362 // Update KStars Location once we receive update from INDI, if the source is set to DEVICE
363 dms lng, lat;
364 double elev = 0;
365
366 auto np = nvp->findWidgetByName("LONG");
367 if (!np)
368 return;
369
370 // INDI Longitude convention is 0 to 360. We need to turn it back into 0 to 180 EAST, 0 to -180 WEST
371 if (np->value < 180)
372 lng.setD(np->value);
373 else
374 lng.setD(np->value - 360.0);
375
376 np = nvp->findWidgetByName("LAT");
377 if (!np)
378 return;
379
380 lat.setD(np->value);
381
382 // Double check we have valid values
383 if (lng.Degrees() == 0 && lat.Degrees() == 0)
384 {
385 qCWarning(KSTARS_INDI) << "Ignoring invalid device coordinates.";
386 return;
387 }
388
389 np = nvp->findWidgetByName("ELEV");
390 if (np)
391 elev = np->value;
392
393 // Update all other INDI devices
394 for (auto &oneDevice : INDIListener::devices())
395 {
396 // Skip updating the device itself
397 if (oneDevice->getDeviceName() == getDeviceName())
398 continue;
399
400 oneDevice->updateLocation(lng.Degrees(), lat.Degrees(), elev);
401 }
402
403 auto geo = KStars::Instance()->data()->geo();
404 std::unique_ptr<GeoLocation> tempGeo;
405
406 QString newLocationName;
407 if (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)
408 newLocationName = i18n("GPS Location");
409 else
410 newLocationName = i18n("Mount Location");
411
412 if (geo->name() != newLocationName)
413 {
414 double TZ0 = geo->TZ0();
415 TimeZoneRule *rule = geo->tzrule();
416 tempGeo.reset(new GeoLocation(lng, lat, newLocationName, "", "", TZ0, rule, elev));
417 geo = tempGeo.get();
418 }
419 else
420 {
421 geo->setLong(lng);
422 geo->setLat(lat);
423 }
424
425 qCInfo(KSTARS_INDI) << "Setting location from device:" << deviceName << "Longitude:" << lng.toDMSString() << "Latitude:" <<
426 lat.toDMSString();
427
429 }
430 else if (nvp->isNameMatch("WATCHDOG_HEARTBEAT"))
431 {
432 if (watchDogTimer == nullptr)
433 {
434 watchDogTimer = new QTimer(this);
435 connect(watchDogTimer, &QTimer::timeout, this, &GenericDevice::resetWatchdog);
436 }
437
438 auto value = nvp->at(0)->getValue();
439 if (m_Connected && value > 0)
440 {
441 // Reset timer 5 seconds before it is due
442 // To account for any networking delays
443 double nextMS = qMax(100.0, (value - 5) * 1000);
444 watchDogTimer->start(nextMS);
445 }
446 else if (value == 0)
447 watchDogTimer->stop();
448 }
449
450 emit propertyUpdated(prop);
451}
452
453void GenericDevice::processText(INDI::Property prop)
454{
455 auto tvp = prop.getText();
456 // If DRIVER_INFO is updated after being defined, make sure to re-generate concrete devices accordingly.
457 if (tvp->isNameMatch("DRIVER_INFO"))
458 {
459 auto tp = tvp->findWidgetByName("DRIVER_INTERFACE");
460 if (tp)
461 {
462 m_DriverInterface = static_cast<uint32_t>(atoi(tp->getText()));
463 emit interfaceDefined();
464
465 // If devices were already created but we receieved an update to DRIVER_INTERFACE
466 // then we need to re-generate the concrete devices to account for the change.
467 if (m_ConcreteDevices.isEmpty() == false)
468 {
469 // If we generated ANY concrete device due to interface update, then we emit ready immediately.
470 if (generateDevices())
471 emit ready();
472 }
473 }
474
475 tp = tvp->findWidgetByName("DRIVER_VERSION");
476 if (tp)
477 {
478 m_DriverVersion = QString(tp->text);
479 }
480
481 }
482 // Update KStars time once we receive update from INDI, if the source is set to DEVICE
483 else if (tvp->isNameMatch("TIME_UTC") && tvp->s == IPS_OK && Options::timeSource() == getDeviceName())
484 {
485 int d, m, y, min, sec, hour;
486 float utcOffset;
487 QDate indiDate;
488 QTime indiTime;
489
490 auto tp = tvp->findWidgetByName("UTC");
491
492 if (!tp)
493 {
494 qCWarning(KSTARS_INDI) << "UTC property missing from TIME_UTC";
495 return;
496 }
497
498 sscanf(tp->getText(), "%d%*[^0-9]%d%*[^0-9]%dT%d%*[^0-9]%d%*[^0-9]%d", &y, &m, &d, &hour, &min, &sec);
499 indiDate.setDate(y, m, d);
500 indiTime.setHMS(hour, min, sec);
501
502 KStarsDateTime indiDateTime(QDateTime(indiDate, indiTime, Qt::UTC));
503
504 tp = tvp->findWidgetByName("OFFSET");
505
506 if (!tp)
507 {
508 qCWarning(KSTARS_INDI) << "Offset property missing from TIME_UTC";
509 return;
510 }
511
512 sscanf(tp->getText(), "%f", &utcOffset);
513
514 // Update all other INDI devices
515 for (auto &oneDevice : INDIListener::devices())
516 {
517 // Skip updating the device itself
518 if (oneDevice->getDeviceName() == getDeviceName())
519 continue;
520
521 oneDevice->updateTime(tvp->tp[0].text, tvp->tp[1].text);
522 }
523
524 qCInfo(KSTARS_INDI) << "Setting UTC time from device:" << getDeviceName() << indiDateTime.toString();
525
526 KStars::Instance()->data()->changeDateTime(indiDateTime);
528
529 auto geo = KStars::Instance()->data()->geo();
530 if (geo->tzrule())
531 utcOffset -= geo->tzrule()->deltaTZ();
532
533 // TZ0 is the timezone WTIHOUT any DST offsets. Above, we take INDI UTC Offset (with DST already included)
534 // and subtract from it the deltaTZ from the current TZ rule.
535 geo->setTZ0(utcOffset);
536 }
537
538 emit propertyUpdated(prop);
539}
540
541void GenericDevice::processLight(INDI::Property prop)
542{
543 emit propertyUpdated(prop);
544}
545
546void GenericDevice::processMessage(int messageID)
547{
548 emit messageUpdated(messageID);
549}
550
551bool GenericDevice::processBLOB(INDI::Property prop)
552{
553 // Ignore write-only BLOBs since we only receive it for state-change
554 if (prop.getPermission() == IP_WO)
555 return false;
556
557 auto bvp = prop.getBLOB();
558 auto bp = bvp->at(0);
559
560 // If any concrete device processed the blob then we return
561 for (auto &oneConcreteDevice : m_ConcreteDevices)
562 {
563 if (!oneConcreteDevice.isNull() && oneConcreteDevice->processBLOB(prop))
564 return true;
565 }
566
567 INDIDataTypes dataType;
568
569 if (!strcmp(bp->getFormat(), ".ascii"))
570 dataType = DATA_ASCII;
571 else
572 dataType = DATA_OTHER;
573
574 QString currentDir = Options::fitsDir();
575 int nr, n = 0;
576
577 if (currentDir.endsWith('/'))
578 currentDir.truncate(sizeof(currentDir) - 1);
579
580 QString filename(currentDir + '/');
581
582 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
583
584 filename += QString("%1_").arg(bp->getLabel()) + ts + QString(bp->getFormat()).trimmed();
585
586 // Text Streaming
587 if (dataType == DATA_ASCII)
588 {
589 // First time, create a data file to hold the stream.
590
591 auto it = std::find_if(streamFileMetadata.begin(), streamFileMetadata.end(), [bvp, bp](const StreamFileMetadata & data)
592 {
593 return (bvp->getDeviceName() == data.device && bvp->getName() == data.property && bp->getName() == data.element);
594 });
595
596 QFile *streamDatafile = nullptr;
597
598 // New stream data file
599 if (it == streamFileMetadata.end())
600 {
601 StreamFileMetadata metadata;
602 metadata.device = bvp->getDeviceName();
603 metadata.property = bvp->getName();
604 metadata.element = bp->getName();
605
606 // Create new file instance
607 // Since it's a child of this class, don't worry about deallocating it
608 streamDatafile = new QFile(this);
609 metadata.file = streamDatafile;
610
611 streamFileMetadata.append(metadata);
612 }
613 else
614 streamDatafile = (*it).file;
615
616 // Try to get
617
618 QDataStream out(streamDatafile);
619 for (nr = 0; nr < bp->getSize(); nr += n)
620 n = out.writeRawData(static_cast<char *>(bp->getBlob()) + nr, bp->getSize() - nr);
621
622 out.writeRawData((const char *)"\n", 1);
623 streamDatafile->flush();
624
625 }
626 else
627 {
628 QFile fits_temp_file(filename);
629 if (!fits_temp_file.open(QIODevice::WriteOnly))
630 {
631 qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName();
632 return false;
633 }
634
635 QDataStream out(&fits_temp_file);
636
637 for (nr = 0; nr < bp->getSize(); nr += n)
638 n = out.writeRawData(static_cast<char *>(bp->getBlob()) + nr, bp->getSize() - nr);
639
640 fits_temp_file.flush();
641 fits_temp_file.close();
642
643 auto fmt = QString(bp->getFormat()).toLower().remove('.').toUtf8();
644 if (QImageReader::supportedImageFormats().contains(fmt))
645 {
646 QUrl url(filename);
647 url.setScheme("file");
648 auto iv = new ImageViewer(url, QString(), KStars::Instance());
649 if (iv)
650 iv->show();
651 }
652 }
653
654 if (dataType == DATA_OTHER)
655 KStars::Instance()->statusBar()->showMessage(i18n("Data file saved to %1", filename), 0);
656
657 emit propertyUpdated(prop);
658 return true;
659}
660
661bool GenericDevice::setConfig(INDIConfig tConfig)
662{
663 auto svp = m_BaseDevice.getSwitch("CONFIG_PROCESS");
664
665 if (!svp)
666 return false;
667
668 const char *strConfig = nullptr;
669
670 switch (tConfig)
671 {
672 case LOAD_LAST_CONFIG:
673 strConfig = "CONFIG_LOAD";
674 break;
675
676 case SAVE_CONFIG:
677 strConfig = "CONFIG_SAVE";
678 break;
679
680 case LOAD_DEFAULT_CONFIG:
681 strConfig = "CONFIG_DEFAULT";
682 break;
683
684 case PURGE_CONFIG:
685 strConfig = "CONFIG_PURGE";
686 break;
687 }
688
689 svp.reset();
690 if (strConfig)
691 {
692 auto sp = svp.findWidgetByName(strConfig);
693 if (!sp)
694 return false;
695 sp->setState(ISS_ON);
696 }
697
698 m_ClientManager->sendNewProperty(svp);
699
700 return true;
701}
702
703void GenericDevice::createDeviceInit()
704{
705 if (Options::showINDIMessages())
706 KStars::Instance()->statusBar()->showMessage(i18n("%1 is online.", m_Name), 0);
707
709}
710
711/*********************************************************************************/
712/* Update the Driver's Time */
713/*********************************************************************************/
714void GenericDevice::updateTime(const QString &iso8601, const QString &utcOffset)
715{
716 /* Update Date/Time */
717 auto timeUTC = m_BaseDevice.getText("TIME_UTC");
718 if (!timeUTC)
719 return;
720
721 QString offset, isoTS;
722
723 if (iso8601.isEmpty())
724 {
725 isoTS = KStars::Instance()->data()->ut().toString(Qt::ISODate).remove('Z');
726 offset = QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2);
727 }
728 else
729 {
730 isoTS = iso8601;
731 offset = utcOffset;
732 }
733
734 auto timeEle = timeUTC.findWidgetByName("UTC");
735 if (timeEle)
736 timeEle->setText(isoTS.toLatin1().constData());
737
738 auto offsetEle = timeUTC.findWidgetByName("OFFSET");
739 if (offsetEle)
740 offsetEle->setText(offset.toLatin1().constData());
741
742 if (timeEle && offsetEle)
743 {
744 qCInfo(KSTARS_INDI) << "Updating" << getDeviceName() << "Time UTC:" << isoTS << "Offset:" << offset;
745 m_ClientManager->sendNewProperty(timeUTC);
746 }
747}
748
749/*********************************************************************************/
750/* Update the Driver's Geographical Location */
751/*********************************************************************************/
752void GenericDevice::updateLocation(double longitude, double latitude, double elevation)
753{
754 auto nvp = m_BaseDevice.getNumber("GEOGRAPHIC_COORD");
755
756 if (!nvp)
757 return;
758
760
761 double longitude_degrees, latitude_degrees, elevation_meters;
762
763 if (longitude == -1 && latitude == -1 && elevation == -1)
764 {
765 longitude_degrees = geo->lng()->Degrees();
766 latitude_degrees = geo->lat()->Degrees();
767 elevation_meters = geo->elevation();
768 }
769 else
770 {
771 longitude_degrees = longitude;
772 latitude_degrees = latitude;
773 elevation_meters = elevation;
774 }
775
776 if (longitude_degrees < 0)
777 longitude_degrees = dms(longitude_degrees + 360.0).Degrees();
778
779 auto np = nvp.findWidgetByName("LONG");
780
781 if (!np)
782 return;
783
784 np->setValue(longitude_degrees);
785
786 np = nvp.findWidgetByName("LAT");
787 if (!np)
788 return;
789
790 np->setValue(latitude_degrees);
791
792 np = nvp.findWidgetByName("ELEV");
793 if (!np)
794 return;
795
796 np->setValue(elevation_meters);
797
798 qCInfo(KSTARS_INDI) << "Updating" << getDeviceName() << "Location Longitude:" << longitude_degrees << "Latitude:" <<
799 latitude_degrees << "Elevation:" << elevation_meters;
800
801 m_ClientManager->sendNewProperty(nvp);
802}
803
804void GenericDevice::Connect()
805{
806 m_ClientManager->connectDevice(m_Name.toLatin1().constData());
807}
808
809void GenericDevice::Disconnect()
810{
811 m_ClientManager->disconnectDevice(m_Name.toLatin1().constData());
812}
813
814bool GenericDevice::setProperty(QObject *setPropCommand)
815{
816 GDSetCommand *indiCommand = static_cast<GDSetCommand *>(setPropCommand);
817
818 //qDebug() << Q_FUNC_INFO << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue;
819
820 auto prop = m_BaseDevice.getProperty(indiCommand->indiProperty.toLatin1().constData());
821
822 if (!prop)
823 return false;
824
825 switch (indiCommand->propType)
826 {
827 case INDI_SWITCH:
828 {
829 auto svp = prop.getSwitch();
830
831 if (!svp)
832 return false;
833
834 auto sp = svp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
835
836 if (!sp)
837 return false;
838
839 if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
840 svp->reset();
841
842 sp->setState(indiCommand->elementValue.toInt() == 0 ? ISS_OFF : ISS_ON);
843
844 //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
845 m_ClientManager->sendNewProperty(svp);
846
847 return true;
848 }
849
850 case INDI_NUMBER:
851 {
852 auto nvp = prop.getNumber();
853
854 if (!nvp)
855 return false;
856
857 auto np = nvp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
858
859 if (!np)
860 return false;
861
862 double value = indiCommand->elementValue.toDouble();
863
864 if (value == np->getValue())
865 return true;
866
867 np->setValue(value);
868
869 //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
870 m_ClientManager->sendNewProperty(nvp);
871 }
872 break;
873 // TODO: Add set property for other types of properties
874 default:
875 break;
876 }
877
878 return true;
879}
880
881bool GenericDevice::getMinMaxStep(const QString &propName, const QString &elementName, double *min, double *max,
882 double *step)
883{
884 auto nvp = m_BaseDevice.getNumber(propName.toLatin1());
885
886 if (!nvp)
887 return false;
888
889 auto np = nvp.findWidgetByName(elementName.toLatin1());
890
891 if (!np)
892 return false;
893
894 *min = np->getMin();
895 *max = np->getMax();
896 *step = np->getStep();
897
898 return true;
899}
900
901IPState GenericDevice::getState(const QString &propName)
902{
903 return m_BaseDevice.getPropertyState(propName.toLatin1().constData());
904}
905
906IPerm GenericDevice::getPermission(const QString &propName)
907{
908 return m_BaseDevice.getPropertyPermission(propName.toLatin1().constData());
909}
910
911INDI::Property GenericDevice::getProperty(const QString &propName)
912{
913 return m_BaseDevice.getProperty(propName.toLatin1().constData());
914}
915
916bool GenericDevice::setJSONProperty(const QString &propName, const QJsonArray &propElements)
917{
918 for (auto &oneProp : * (m_BaseDevice.getProperties()))
919 {
920 if (propName == QString(oneProp.getName()))
921 {
922 switch (oneProp.getType())
923 {
924 case INDI_SWITCH:
925 {
926 auto svp = oneProp.getSwitch();
927 if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
928 svp->reset();
929
930 for (auto oneElement : propElements)
931 {
932 QJsonObject oneElementObject = oneElement.toObject();
933 auto sp = svp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
934 if (sp)
935 {
936 sp->setState(static_cast<ISState>(oneElementObject["state"].toInt()));
937 }
938 }
939
940 m_ClientManager->sendNewProperty(svp);
941 return true;
942 }
943
944 case INDI_NUMBER:
945 {
946 auto nvp = oneProp.getNumber();
947 for (const auto &oneElement : propElements)
948 {
949 QJsonObject oneElementObject = oneElement.toObject();
950 auto np = nvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
951 if (np)
952 {
953 double newValue = oneElementObject["value"].toDouble(std::numeric_limits<double>::quiet_NaN());
954 if (std::isnan(newValue))
955 {
956 f_scansexa(oneElementObject["value"].toString().toLatin1().constData(), &newValue);
957 }
958 np->setValue(newValue);
959 }
960 }
961
962 m_ClientManager->sendNewProperty(nvp);
963 return true;
964 }
965
966 case INDI_TEXT:
967 {
968 auto tvp = oneProp.getText();
969 for (const auto &oneElement : propElements)
970 {
971 QJsonObject oneElementObject = oneElement.toObject();
972 auto tp = tvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
973 if (tp)
974 tp->setText(oneElementObject["text"].toString().toLatin1().constData());
975 }
976
977 m_ClientManager->sendNewProperty(tvp);
978 return true;
979 }
980
981 case INDI_BLOB:
982 // TODO
983 break;
984
985 default:
986 break;
987 }
988 }
989 }
990
991 return false;
992}
993
994bool GenericDevice::getJSONProperty(const QString &propName, QJsonObject &propObject, bool compact)
995{
996 for (auto oneProp : m_BaseDevice.getProperties())
997 {
998 if (propName == oneProp.getName())
999 {
1000 switch (oneProp.getType())
1001 {
1002 case INDI_SWITCH:
1003 switchToJson(oneProp, propObject, compact);
1004 return true;
1005
1006 case INDI_NUMBER:
1007 numberToJson(oneProp, propObject, compact);
1008 return true;
1009
1010 case INDI_TEXT:
1011 textToJson(oneProp, propObject, compact);
1012 return true;
1013
1014 case INDI_LIGHT:
1015 lightToJson(oneProp, propObject, compact);
1016 return true;
1017
1018 case INDI_BLOB:
1019 // TODO
1020 break;
1021
1022 default:
1023 break;
1024 }
1025 }
1026 }
1027
1028 return false;
1029}
1030
1031bool GenericDevice::getJSONBLOB(const QString &propName, const QString &elementName, QJsonObject &blobObject)
1032{
1033 auto blobProperty = m_BaseDevice.getProperty(propName.toLatin1().constData());
1034 if (!blobProperty.isValid())
1035 return false;
1036
1037 auto oneBLOB = blobProperty.getBLOB()->findWidgetByName(elementName.toLatin1().constData());
1038 if (!oneBLOB)
1039 return false;
1040
1041 // Now convert to base64 and send back.
1042 QByteArray data = QByteArray::fromRawData(static_cast<const char *>(oneBLOB->getBlob()), oneBLOB->getBlobLen());
1043
1045 blobObject.insert("property", propName);
1046 blobObject.insert("element", elementName);
1047 blobObject.insert("size", encoded.size());
1048 blobObject.insert("data", encoded);
1049
1050 return true;
1051}
1052
1053void GenericDevice::resetWatchdog()
1054{
1055 auto nvp = m_BaseDevice.getNumber("WATCHDOG_HEARTBEAT");
1056
1057 if (nvp)
1058 // Send heartbeat to driver
1059 m_ClientManager->sendNewProperty(nvp);
1060}
1061
1062bool GenericDevice::findConcreteDevice(uint32_t interface, QSharedPointer<ConcreteDevice> &device)
1063{
1064 if (m_ConcreteDevices.contains(interface))
1065 {
1066 device = m_ConcreteDevices[interface];
1067 return true;
1068 }
1069 return false;
1070}
1071
1072ISD::Mount *GenericDevice::getMount()
1073{
1074 if (m_ConcreteDevices.contains(INDI::BaseDevice::TELESCOPE_INTERFACE))
1075 return dynamic_cast<ISD::Mount*>(m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].get());
1076 return nullptr;
1077}
1078
1079ISD::Camera *GenericDevice::getCamera()
1080{
1081 if (m_ConcreteDevices.contains(INDI::BaseDevice::CCD_INTERFACE))
1082 return dynamic_cast<ISD::Camera*>(m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].get());
1083 return nullptr;
1084}
1085
1086ISD::Guider *GenericDevice::getGuider()
1087{
1088 if (m_ConcreteDevices.contains(INDI::BaseDevice::GUIDER_INTERFACE))
1089 return dynamic_cast<ISD::Guider*>(m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].get());
1090 return nullptr;
1091}
1092
1093ISD::Focuser *GenericDevice::getFocuser()
1094{
1095 if (m_ConcreteDevices.contains(INDI::BaseDevice::FOCUSER_INTERFACE))
1096 return dynamic_cast<ISD::Focuser*>(m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].get());
1097 return nullptr;
1098}
1099
1100ISD::FilterWheel *GenericDevice::getFilterWheel()
1101{
1102 if (m_ConcreteDevices.contains(INDI::BaseDevice::FILTER_INTERFACE))
1103 return dynamic_cast<ISD::FilterWheel*>(m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].get());
1104 return nullptr;
1105}
1106
1107ISD::Dome *GenericDevice::getDome()
1108{
1109 if (m_ConcreteDevices.contains(INDI::BaseDevice::DOME_INTERFACE))
1110 return dynamic_cast<ISD::Dome*>(m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].get());
1111 return nullptr;
1112}
1113
1114ISD::GPS *GenericDevice::getGPS()
1115{
1116 if (m_ConcreteDevices.contains(INDI::BaseDevice::GPS_INTERFACE))
1117 return dynamic_cast<ISD::GPS*>(m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].get());
1118 return nullptr;
1119}
1120
1121ISD::Weather *GenericDevice::getWeather()
1122{
1123 if (m_ConcreteDevices.contains(INDI::BaseDevice::WEATHER_INTERFACE))
1124 return dynamic_cast<ISD::Weather*>(m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].get());
1125 return nullptr;
1126}
1127
1128ISD::AdaptiveOptics *GenericDevice::getAdaptiveOptics()
1129{
1130 if (m_ConcreteDevices.contains(INDI::BaseDevice::AO_INTERFACE))
1131 return dynamic_cast<ISD::AdaptiveOptics*>(m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].get());
1132 return nullptr;
1133}
1134
1135ISD::DustCap *GenericDevice::getDustCap()
1136{
1137 if (m_ConcreteDevices.contains(INDI::BaseDevice::DUSTCAP_INTERFACE))
1138 return dynamic_cast<ISD::DustCap*>(m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].get());
1139 return nullptr;
1140}
1141
1142ISD::LightBox *GenericDevice::getLightBox()
1143{
1144 if (m_ConcreteDevices.contains(INDI::BaseDevice::LIGHTBOX_INTERFACE))
1145 return dynamic_cast<ISD::LightBox*>(m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].get());
1146 return nullptr;
1147}
1148
1149ISD::Detector *GenericDevice::getDetector()
1150{
1151 if (m_ConcreteDevices.contains(INDI::BaseDevice::DETECTOR_INTERFACE))
1152 return dynamic_cast<ISD::Detector*>(m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].get());
1153 return nullptr;
1154}
1155
1156ISD::Rotator *GenericDevice::getRotator()
1157{
1158 if (m_ConcreteDevices.contains(INDI::BaseDevice::ROTATOR_INTERFACE))
1159 return dynamic_cast<ISD::Rotator*>(m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].get());
1160 return nullptr;
1161}
1162
1163ISD::Spectrograph *GenericDevice::getSpectrograph()
1164{
1165 if (m_ConcreteDevices.contains(INDI::BaseDevice::SPECTROGRAPH_INTERFACE))
1166 return dynamic_cast<ISD::Spectrograph*>(m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].get());
1167 return nullptr;
1168}
1169
1170ISD::Correlator *GenericDevice::getCorrelator()
1171{
1172 if (m_ConcreteDevices.contains(INDI::BaseDevice::CORRELATOR_INTERFACE))
1173 return dynamic_cast<ISD::Correlator*>(m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].get());
1174 return nullptr;
1175}
1176
1177ISD::Auxiliary *GenericDevice::getAuxiliary()
1178{
1179 if (m_ConcreteDevices.contains(INDI::BaseDevice::AUX_INTERFACE))
1180 return dynamic_cast<ISD::Auxiliary*>(m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].get());
1181 return nullptr;
1182}
1183
1184bool GenericDevice::generateDevices()
1185{
1186 auto generated = false;
1187 // Mount
1188 if (m_DriverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE &&
1189 m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].isNull())
1190 {
1191 auto mount = new ISD::Mount(this);
1192 mount->setObjectName("Mount:" + objectName());
1193 generated = true;
1194 m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].reset(mount);
1195 mount->registeProperties();
1196 if (m_Connected)
1197 {
1198 mount->processProperties();
1199 mount->setProperty("dispatched", true);
1200 emit newMount(mount);
1201 }
1202 else
1203 {
1204 connect(mount, &ISD::ConcreteDevice::ready, this, [this, mount]()
1205 {
1206 if (!mount->property("dispatched").isValid())
1207 {
1208 mount->setProperty("dispatched", true);
1209 emit newMount(mount);
1210 }
1211 });
1212 }
1213 }
1214
1215 // Camera
1216 if (m_DriverInterface & INDI::BaseDevice::CCD_INTERFACE &&
1217 m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].isNull())
1218 {
1219 auto camera = new ISD::Camera(this);
1220 camera->setObjectName("Camera:" + objectName());
1221 generated = true;
1222 m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].reset(camera);
1223 camera->registeProperties();
1224 if (m_Connected)
1225 {
1226 camera->processProperties();
1227 camera->setProperty("dispatched", true);
1228 emit newCamera(camera);
1229 emit ready();
1230 }
1231 else
1232 {
1233 connect(camera, &ISD::ConcreteDevice::ready, this, [this, camera]()
1234 {
1235 if (!camera->property("dispatched").isValid())
1236 {
1237 camera->setProperty("dispatched", true);
1238 emit newCamera(camera);
1239 }
1240 });
1241 }
1242 }
1243
1244 // Guider
1245 if (m_DriverInterface & INDI::BaseDevice::GUIDER_INTERFACE &&
1246 m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].isNull())
1247 {
1248 auto guider = new ISD::Guider(this);
1249 guider->setObjectName("Guider:" + objectName());
1250 generated = true;
1251 m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].reset(guider);
1252 guider->registeProperties();
1253 if (m_Connected)
1254 {
1255 guider->processProperties();
1256 guider->setProperty("dispatched", true);
1257 emit newGuider(guider);
1258 }
1259 else
1260 {
1261 connect(guider, &ISD::ConcreteDevice::ready, this, [this, guider]()
1262 {
1263 if (!guider->property("dispatched").isValid())
1264 {
1265 guider->setProperty("dispatched", true);
1266 emit newGuider(guider);
1267 }
1268 });
1269 }
1270 }
1271
1272 // Focuser
1273 if (m_DriverInterface & INDI::BaseDevice::FOCUSER_INTERFACE &&
1274 m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].isNull())
1275 {
1276 auto focuser = new ISD::Focuser(this);
1277 focuser->setObjectName("Focuser:" + objectName());
1278 generated = true;
1279 m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].reset(focuser);
1280 focuser->registeProperties();
1281 if (m_Connected)
1282 {
1283 focuser->processProperties();
1284 focuser->setProperty("dispatched", true);
1285 emit newFocuser(focuser);
1286 }
1287 else
1288 {
1289 connect(focuser, &ISD::ConcreteDevice::ready, this, [this, focuser]()
1290 {
1291 if (!focuser->property("dispatched").isValid())
1292 {
1293 focuser->setProperty("dispatched", true);
1294 emit newFocuser(focuser);
1295 }
1296 });
1297 }
1298 }
1299
1300 // Filter Wheel
1301 if (m_DriverInterface & INDI::BaseDevice::FILTER_INTERFACE &&
1302 m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].isNull())
1303 {
1304 auto filterWheel = new ISD::FilterWheel(this);
1305 filterWheel->setObjectName("FilterWheel:" + objectName());
1306 generated = true;
1307 m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].reset(filterWheel);
1308 filterWheel->registeProperties();
1309 if (m_Connected)
1310 {
1311 filterWheel->processProperties();
1312 filterWheel->setProperty("dispatched", true);
1313 emit newFilterWheel(filterWheel);
1314 }
1315 else
1316 {
1317 connect(filterWheel, &ISD::ConcreteDevice::ready, this, [this, filterWheel]()
1318 {
1319 if (!filterWheel->property("dispatched").isValid())
1320 {
1321 filterWheel->setProperty("dispatched", true);
1322 emit newFilterWheel(filterWheel);
1323 }
1324 });
1325 }
1326 }
1327
1328 // Dome
1329 if (m_DriverInterface & INDI::BaseDevice::DOME_INTERFACE &&
1330 m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].isNull())
1331 {
1332 auto dome = new ISD::Dome(this);
1333 dome->setObjectName("Dome:" + objectName());
1334 generated = true;
1335 m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].reset(dome);
1336 dome->registeProperties();
1337 if (m_Connected)
1338 {
1339 dome->processProperties();
1340 dome->setProperty("dispatched", true);
1341 emit newDome(dome);
1342 }
1343 else
1344 {
1345 connect(dome, &ISD::ConcreteDevice::ready, this, [this, dome]()
1346 {
1347 if (!dome->property("dispatched").isValid())
1348 {
1349 dome->setProperty("dispatched", true);
1350 emit newDome(dome);
1351 }
1352 });
1353 }
1354 }
1355
1356 // GPS
1357 if (m_DriverInterface & INDI::BaseDevice::GPS_INTERFACE &&
1358 m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].isNull())
1359 {
1360 auto gps = new ISD::GPS(this);
1361 gps->setObjectName("GPS:" + objectName());
1362 generated = true;
1363 m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].reset(gps);
1364 gps->registeProperties();
1365 if (m_Connected)
1366 {
1367 gps->processProperties();
1368 gps->setProperty("dispatched", true);
1369 emit newGPS(gps);
1370 }
1371 else
1372 {
1373 connect(gps, &ISD::ConcreteDevice::ready, this, [this, gps]()
1374 {
1375 if (!gps->property("dispatched").isValid())
1376 {
1377 gps->setProperty("dispatched", true);
1378 emit newGPS(gps);
1379 }
1380 });
1381 }
1382 }
1383
1384 // Weather
1385 if (m_DriverInterface & INDI::BaseDevice::WEATHER_INTERFACE &&
1386 m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].isNull())
1387 {
1388 auto weather = new ISD::Weather(this);
1389 weather->setObjectName("Weather:" + objectName());
1390 generated = true;
1391 m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].reset(weather);
1392 weather->registeProperties();
1393 if (m_Connected)
1394 {
1395 weather->processProperties();
1396 weather->setProperty("dispatched", true);
1397 emit newWeather(weather);
1398 }
1399 else
1400 {
1401 connect(weather, &ISD::ConcreteDevice::ready, this, [this, weather]()
1402 {
1403 if (!weather->property("dispatched").isValid())
1404 {
1405 weather->setProperty("dispatched", true);
1406 emit newWeather(weather);
1407 }
1408 });
1409 }
1410 }
1411
1412 // Adaptive Optics
1413 if (m_DriverInterface & INDI::BaseDevice::AO_INTERFACE &&
1414 m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].isNull())
1415 {
1416 auto ao = new ISD::AdaptiveOptics(this);
1417 ao->setObjectName("AdaptiveOptics:" + objectName());
1418 generated = true;
1419 m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].reset(ao);
1420 ao->registeProperties();
1421 if (m_Connected)
1422 {
1423 ao->processProperties();
1424 ao->setProperty("dispatched", true);
1425 emit newAdaptiveOptics(ao);
1426 }
1427 else
1428 {
1429 connect(ao, &ISD::ConcreteDevice::ready, this, [this, ao]()
1430 {
1431 if (!ao->property("dispatched").isValid())
1432 {
1433 ao->setProperty("dispatched", true);
1434 emit newAdaptiveOptics(ao);
1435 }
1436 });
1437 }
1438 }
1439
1440 // Dust Cap
1441 if (m_DriverInterface & INDI::BaseDevice::DUSTCAP_INTERFACE &&
1442 m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].isNull())
1443 {
1444 auto dustCap = new ISD::DustCap(this);
1445 dustCap->setObjectName("DustCap:" + objectName());
1446 generated = true;
1447 m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].reset(dustCap);
1448 dustCap->registeProperties();
1449 if (m_Connected)
1450 {
1451 dustCap->processProperties();
1452 dustCap->setProperty("dispatched", true);
1453 emit newDustCap(dustCap);
1454 }
1455 else
1456 {
1457 connect(dustCap, &ISD::ConcreteDevice::ready, this, [this, dustCap]()
1458 {
1459 if (!dustCap->property("dispatched").isValid())
1460 {
1461 dustCap->setProperty("dispatched", true);
1462 emit newDustCap(dustCap);
1463 }
1464 });
1465 }
1466 }
1467
1468 // Light box
1469 if (m_DriverInterface & INDI::BaseDevice::LIGHTBOX_INTERFACE &&
1470 m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].isNull())
1471 {
1472 auto lightBox = new ISD::LightBox(this);
1473 lightBox->setObjectName("LightBox:" + objectName());
1474 generated = true;
1475 m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].reset(lightBox);
1476 lightBox->registeProperties();
1477 if (m_Connected)
1478 {
1479 lightBox->processProperties();
1480 lightBox->setProperty("dispatched", true);
1481 emit newLightBox(lightBox);
1482 }
1483 else
1484 {
1485 connect(lightBox, &ISD::ConcreteDevice::ready, this, [this, lightBox]()
1486 {
1487 if (!lightBox->property("dispatched").isValid())
1488 {
1489 lightBox->setProperty("dispatched", true);
1490 emit newLightBox(lightBox);
1491 }
1492 });
1493 }
1494 }
1495
1496 // Rotator
1497 if (m_DriverInterface & INDI::BaseDevice::ROTATOR_INTERFACE &&
1498 m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].isNull())
1499 {
1500 auto rotator = new ISD::Rotator(this);
1501 rotator->setObjectName("Rotator:" + objectName());
1502 generated = true;
1503 m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].reset(rotator);
1504 rotator->registeProperties();
1505 if (m_Connected)
1506 {
1507 rotator->processProperties();
1508 rotator->setProperty("dispatched", true);
1509 emit newRotator(rotator);
1510 }
1511 else
1512 {
1513 connect(rotator, &ISD::ConcreteDevice::ready, this, [this, rotator]()
1514 {
1515 if (!rotator->property("dispatched").isValid())
1516 {
1517 rotator->setProperty("dispatched", true);
1518 emit newRotator(rotator);
1519 }
1520 });
1521 }
1522 }
1523
1524 // Detector
1525 if (m_DriverInterface & INDI::BaseDevice::DETECTOR_INTERFACE &&
1526 m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].isNull())
1527 {
1528 auto detector = new ISD::Detector(this);
1529 detector->setObjectName("Detector:" + objectName());
1530 generated = true;
1531 m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].reset(detector);
1532 detector->registeProperties();
1533 if (m_Connected)
1534 {
1535 detector->processProperties();
1536 detector->setProperty("dispatched", true);
1537 emit newDetector(detector);
1538 }
1539 else
1540 {
1541 connect(detector, &ISD::ConcreteDevice::ready, this, [this, detector]()
1542 {
1543 if (!detector->property("dispatched").isValid())
1544 {
1545 detector->setProperty("dispatched", true);
1546 emit newDetector(detector);
1547 }
1548 });
1549 }
1550 }
1551
1552 // Spectrograph
1553 if (m_DriverInterface & INDI::BaseDevice::SPECTROGRAPH_INTERFACE &&
1554 m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].isNull())
1555 {
1556 auto spectrograph = new ISD::Spectrograph(this);
1557 spectrograph->setObjectName("Spectrograph:" + objectName());
1558 generated = true;
1559 m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].reset(spectrograph);
1560 spectrograph->registeProperties();
1561 if (m_Connected)
1562 {
1563 spectrograph->processProperties();
1564 spectrograph->setProperty("dispatched", true);
1565 emit newSpectrograph(spectrograph);
1566 }
1567 else
1568 {
1569 connect(spectrograph, &ISD::ConcreteDevice::ready, this, [this, spectrograph]()
1570 {
1571 if (!spectrograph->property("dispatched").isValid())
1572 {
1573 spectrograph->setProperty("dispatched", true);
1574 emit newSpectrograph(spectrograph);
1575 }
1576 });
1577 }
1578 }
1579
1580 // Correlator
1581 if (m_DriverInterface & INDI::BaseDevice::CORRELATOR_INTERFACE &&
1582 m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].isNull())
1583 {
1584 auto correlator = new ISD::Correlator(this);
1585 correlator->setObjectName("Correlator:" + objectName());
1586 generated = true;
1587 m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].reset(correlator);
1588 correlator->registeProperties();
1589 if (m_Connected)
1590 {
1591 correlator->processProperties();
1592 correlator->setProperty("dispatched", true);
1593 emit newCorrelator(correlator);
1594 }
1595 else
1596 {
1597 connect(correlator, &ISD::ConcreteDevice::ready, this, [this, correlator]()
1598 {
1599 if (!correlator->property("dispatched").isValid())
1600 {
1601 correlator->setProperty("dispatched", true);
1602 emit newCorrelator(correlator);
1603 }
1604 });
1605 }
1606 }
1607
1608 // Auxiliary
1609 if (m_DriverInterface & INDI::BaseDevice::AUX_INTERFACE &&
1610 m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].isNull())
1611 {
1612 auto aux = new ISD::Auxiliary(this);
1613 aux->setObjectName("Auxiliary:" + objectName());
1614 generated = true;
1615 m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].reset(aux);
1616 aux->registeProperties();
1617 if (m_Connected)
1618 {
1619 aux->processProperties();
1620 aux->setProperty("dispatched", true);
1621 emit newAuxiliary(aux);
1622 }
1623 else
1624 {
1625 connect(aux, &ISD::ConcreteDevice::ready, this, [this, aux]()
1626 {
1627 if (!aux->property("dispatched").isValid())
1628 {
1629 aux->setProperty("dispatched", true);
1630 emit newAuxiliary(aux);
1631 }
1632 });
1633 }
1634 }
1635
1636 return generated;
1637}
1638
1639void GenericDevice::sendNewProperty(INDI::Property prop)
1640{
1641 m_ClientManager->sendNewProperty(prop);
1642}
1643
1644void switchToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1645{
1646 auto svp = prop.getSwitch();
1647 QJsonArray switches;
1648 for (int i = 0; i < svp->count(); i++)
1649 {
1650 QJsonObject oneSwitch = {{"name", svp->at(i)->getName()}, {"state", svp->at(i)->getState()}};
1651 if (!compact)
1652 oneSwitch.insert("label", svp->at(i)->getLabel());
1653 switches.append(oneSwitch);
1654 }
1655
1656 propObject = {{"device", svp->getDeviceName()}, {"name", svp->getName()}, {"state", svp->getState()}, {"switches", switches}};
1657 if (!compact)
1658 {
1659 propObject.insert("label", svp->getLabel());
1660 propObject.insert("group", svp->getGroupName());
1661 propObject.insert("perm", svp->getPermission());
1662 propObject.insert("rule", svp->getRule());
1663 }
1664}
1665
1666void numberToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1667{
1668 auto nvp = prop.getNumber();
1669 QJsonArray numbers;
1670 for (int i = 0; i < nvp->count(); i++)
1671 {
1672 QJsonObject oneNumber = {{"name", nvp->at(i)->getName()}, {"value", nvp->at(i)->getValue()}};
1673 if (!compact)
1674 {
1675 oneNumber.insert("label", nvp->at(i)->getLabel());
1676 oneNumber.insert("min", nvp->at(i)->getMin());
1677 oneNumber.insert("max", nvp->at(i)->getMax());
1678 oneNumber.insert("step", nvp->at(i)->getStep());
1679 oneNumber.insert("format", nvp->at(i)->getFormat());
1680 }
1681 numbers.append(oneNumber);
1682 }
1683
1684 propObject = {{"device", nvp->getDeviceName()}, {"name", nvp->getName()}, {"state", nvp->getState()}, {"numbers", numbers}};
1685 if (!compact)
1686 {
1687 propObject.insert("label", nvp->getLabel());
1688 propObject.insert("group", nvp->getGroupName());
1689 propObject.insert("perm", nvp->getPermission());
1690 }
1691}
1692
1693void textToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1694{
1695 auto tvp = prop.getText();
1696 QJsonArray Texts;
1697 for (int i = 0; i < tvp->count(); i++)
1698 {
1699 QJsonObject oneText = {{"name", tvp->at(i)->getName()}, {"text", tvp->at(i)->getText()}};
1700 if (!compact)
1701 {
1702 oneText.insert("label", tvp->at(i)->getLabel());
1703 }
1704 Texts.append(oneText);
1705 }
1706
1707 propObject = {{"device", tvp->getDeviceName()}, {"name", tvp->getName()}, {"state", tvp->getState()}, {"texts", Texts}};
1708 if (!compact)
1709 {
1710 propObject.insert("label", tvp->getLabel());
1711 propObject.insert("group", tvp->getGroupName());
1712 propObject.insert("perm", tvp->getPermission());
1713 }
1714}
1715
1716void lightToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1717{
1718 auto lvp = prop.getLight();
1719 QJsonArray Lights;
1720 for (int i = 0; i < lvp->count(); i++)
1721 {
1722 QJsonObject oneLight = {{"name", lvp->at(i)->getName()}, {"state", lvp->at(i)->getState()}};
1723 if (!compact)
1724 {
1725 oneLight.insert("label", lvp->at(i)->getLabel());
1726 }
1727 Lights.append(oneLight);
1728 }
1729
1730 propObject = {{"device", lvp->getDeviceName()}, {"name", lvp->getName()}, {"state", lvp->getState()}, {"lights", Lights}};
1731 if (!compact)
1732 {
1733 propObject.insert("label", lvp->getLabel());
1734 propObject.insert("group", lvp->getGroupName());
1735 }
1736}
1737
1738void propertyToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1739{
1740 switch (prop.getType())
1741 {
1742 case INDI_SWITCH:
1743 switchToJson(prop, propObject, compact);
1744 break;
1745 case INDI_TEXT:
1746 textToJson(prop, propObject, compact);
1747 break;
1748 case INDI_NUMBER:
1749 numberToJson(prop, propObject, compact);
1750 break;
1751 case INDI_LIGHT:
1752 lightToJson(prop, propObject, compact);
1753 break;
1754 default:
1755 break;
1756 }
1757}
1758}
1759
1760#ifndef KSTARS_LITE
1761QDBusArgument &operator<<(QDBusArgument &argument, const ISD::ParkStatus &source)
1762{
1763 argument.beginStructure();
1764 argument << static_cast<int>(source);
1765 argument.endStructure();
1766 return argument;
1767}
1768
1769const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::ParkStatus &dest)
1770{
1771 int a;
1772 argument.beginStructure();
1773 argument >> a;
1774 argument.endStructure();
1775 dest = static_cast<ISD::ParkStatus>(a);
1776 return argument;
1777}
1778#endif
ClientManager manages connection to INDI server, creation of devices, and receiving/sending propertie...
DeviceInfo is simple class to hold DriverInfo and INDI::BaseDevice associated with a particular devic...
Definition deviceinfo.h:21
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
AdaptiveOptics class handles control of INDI AdaptiveOptics devices.
Auxiliary class handles control of INDI Auxiliary devices.
Camera class controls an INDI Camera device.
Definition indicamera.h:45
void sendNewProperty(INDI::Property prop)
Send new property command to server.
Correlator class handles control of INDI Correlator devices.
Detector class handles control of INDI Detector devices.
Class handles control of INDI dome devices.
Definition indidome.h:25
Handles operation of a remotely controlled dust cover cap.
Definition indidustcap.h:25
Focuser class handles control of INDI focuser devices.
Definition indifocuser.h:21
Handles operation of a remotely controlled light box.
device handle controlling Mounts.
Definition indimount.h:29
Rotator class handles control of INDI Rotator devices.
Definition indirotator.h:20
Spectrograph class handles control of INDI Spectrograph devices.
Focuser class handles control of INDI Weather devices.
Definition indiweather.h:24
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
void changeDateTime(const KStarsDateTime &newDate)
Change the current simulation date/time to the KStarsDateTime argument.
void syncLST()
Sync the LST with the simulation clock.
const KStarsDateTime & ut() const
Definition kstarsdata.h:157
GeoLocation * geo()
Definition kstarsdata.h:230
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
SkyMap * map() const
Definition kstars.h:139
static KStars * Instance()
Definition kstars.h:121
KStarsData * data() const
Definition kstars.h:133
void timeChanged()
The time has changed (emitted by setUTC() )
void forceUpdateNow()
Convenience function; simply calls forceUpdate(true).
Definition skymap.h:378
This class provides the information needed to determine whether Daylight Savings Time (DST; a....
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition dms.cpp:287
virtual void setD(const double &x)
Sets floating-point value of angle, in degrees.
Definition dms.h:179
const double & Degrees() const
Definition dms.h:141
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
ISD is a collection of INDI Standard Devices.
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
QString name(GameStandardAction id)
GeoCoordinates geo(const QVariant &location)
QCA_EXPORT QVariant getProperty(const QString &name)
const char * constData() const const
QByteArray fromRawData(const char *data, qsizetype size)
QByteArray toBase64(Base64Options options) const const
int writeRawData(const char *s, int len)
bool setDate(int year, int month, int day)
QDateTime currentDateTime()
QString toString(QStringView format, QCalendar cal) const const
void beginStructure()
void endStructure()
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual void close() override
bool flush()
QList< QByteArray > supportedImageFormats()
void append(const QJsonValue &value)
iterator insert(QLatin1StringView key, const QJsonValue &value)
QStatusBar * statusBar() const const
QVariant property(const char *name) const const
void setObjectName(QAnyStringView name)
bool setProperty(const char *name, QVariant &&value)
void showMessage(const QString &message, int timeout)
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & setNum(double n, char format, int precision)
qsizetype size() const const
QByteArray toLatin1() const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
void truncate(qsizetype position)
UniqueConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool setHMS(int h, int m, int s, int ms)
void timeout()
void setScheme(const QString &scheme)
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.