File indexing completed on 2024-04-14 03:42:42

0001 /*
0002     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006     Handle INDI Standard properties.
0007 */
0008 
0009 #include "indistd.h"
0010 
0011 #include "clientmanager.h"
0012 #include "driverinfo.h"
0013 #include "deviceinfo.h"
0014 #include "imageviewer.h"
0015 #include "indi_debug.h"
0016 #include "kstars.h"
0017 #include "kstarsdata.h"
0018 #include "Options.h"
0019 #include "skymap.h"
0020 
0021 #include "indimount.h"
0022 #include "indicamera.h"
0023 #include "indiguider.h"
0024 #include "indifocuser.h"
0025 #include "indifilterwheel.h"
0026 #include "indidome.h"
0027 #include "indigps.h"
0028 #include "indiweather.h"
0029 #include "indiadaptiveoptics.h"
0030 #include "indidustcap.h"
0031 #include "indilightbox.h"
0032 #include "indidetector.h"
0033 #include "indirotator.h"
0034 #include "indispectrograph.h"
0035 #include "indicorrelator.h"
0036 #include "indiauxiliary.h"
0037 
0038 #include "genericdeviceadaptor.h"
0039 #include <indicom.h>
0040 #include <QImageReader>
0041 #include <QStatusBar>
0042 
0043 namespace ISD
0044 {
0045 
0046 GDSetCommand::GDSetCommand(INDI_PROPERTY_TYPE inPropertyType, const QString &inProperty, const QString &inElement,
0047                            QVariant qValue, QObject *parent)
0048     : QObject(parent), propType(inPropertyType), indiProperty((inProperty)), indiElement(inElement), elementValue(qValue)
0049 {
0050 }
0051 
0052 uint8_t GenericDevice::m_ID = 1;
0053 GenericDevice::GenericDevice(DeviceInfo &idv, ClientManager *cm, QObject *parent) : GDInterface(parent)
0054 {
0055     // Register DBus
0056     new GenericDeviceAdaptor(this);
0057     QDBusConnection::sessionBus().registerObject(QString("/KStars/INDI/GenericDevice/%1").arg(getID()), this);
0058 
0059     m_DeviceInfo    = &idv;
0060     m_DriverInfo    = idv.getDriverInfo();
0061     m_BaseDevice    = idv.getBaseDevice();
0062     m_ClientManager = cm;
0063 
0064     Q_ASSERT_X(m_BaseDevice, __FUNCTION__, "Base device is invalid.");
0065     Q_ASSERT_X(m_ClientManager, __FUNCTION__, "Client manager is invalid.");
0066 
0067     m_Name = m_BaseDevice.getDeviceName();
0068 
0069     setObjectName(m_Name);
0070 
0071     m_DriverInterface = m_BaseDevice.getDriverInterface();
0072     m_DriverVersion = m_BaseDevice.getDriverVersion();
0073 
0074     registerDBusType();
0075 
0076     // JM 2020-09-05: In case KStars time change, update driver time if applicable.
0077     connect(KStarsData::Instance()->clock(), &SimClock::timeChanged, this, [this]()
0078     {
0079         if (Options::useTimeUpdate() && Options::useKStarsSource())
0080         {
0081             if (isConnected())
0082             {
0083                 auto tvp = m_BaseDevice.getText("TIME_UTC");
0084                 if (tvp && tvp.getPermission() != IP_RO)
0085                     updateTime();
0086             }
0087         }
0088     });
0089 
0090     m_ReadyTimer = new QTimer(this);
0091     m_ReadyTimer->setInterval(250);
0092     m_ReadyTimer->setSingleShot(true);
0093     connect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout, Qt::UniqueConnection);
0094 
0095     m_TimeUpdateTimer = new QTimer(this);
0096     m_TimeUpdateTimer->setInterval(5000);
0097     m_TimeUpdateTimer->setSingleShot(true);
0098     connect(m_TimeUpdateTimer, &QTimer::timeout, this, &GenericDevice::checkTimeUpdate, Qt::UniqueConnection);
0099 
0100     m_LocationUpdateTimer = new QTimer(this);
0101     m_LocationUpdateTimer->setInterval(5000);
0102     m_LocationUpdateTimer->setSingleShot(true);
0103     connect(m_LocationUpdateTimer, &QTimer::timeout, this, &GenericDevice::checkLocationUpdate, Qt::UniqueConnection);
0104 
0105 }
0106 
0107 GenericDevice::~GenericDevice()
0108 {
0109     for (auto &metadata : streamFileMetadata)
0110         metadata.file->close();
0111 }
0112 
0113 void GenericDevice::handleTimeout()
0114 {
0115     generateDevices();
0116     // N.B. JM 2022.10.15: Do not disconnect timer.
0117     // It is possible that other properties can come later.
0118     // Even they do not make it in the 250ms window. Increasing timeout value alone
0119     // to 1000ms or more would improve the situation but is not sufficient to account for
0120     // unexpected delays. Therefore, the best solution is to keep the timer active.
0121     //m_ReadyTimer->disconnect(this);
0122     m_Ready = true;
0123     emit ready();
0124 }
0125 
0126 void GenericDevice::checkTimeUpdate()
0127 {
0128     auto tvp = getProperty("TIME_UTC");
0129     if (tvp)
0130     {
0131         auto timeTP = tvp.getText();
0132         // If time still empty, then force update.
0133         if (timeTP && timeTP->getPermission() != IP_RO && timeTP->getState() == IPS_IDLE)
0134             updateTime();
0135     }
0136 
0137 }
0138 
0139 void GenericDevice::checkLocationUpdate()
0140 {
0141     auto nvp = getProperty("GEOGRAPHIC_COORD");
0142     if (nvp)
0143     {
0144         auto locationNP = nvp.getNumber();
0145         // If time still empty, then force update.
0146         if (locationNP && locationNP->getPermission() != IP_RO && locationNP->getState() == IPS_IDLE)
0147             updateLocation();
0148     }
0149 }
0150 
0151 void GenericDevice::registerDBusType()
0152 {
0153 #ifndef KSTARS_LITE
0154     static bool isRegistered = false;
0155 
0156     if (isRegistered == false)
0157     {
0158         qRegisterMetaType<ISD::ParkStatus>("ISD::ParkStatus");
0159         qDBusRegisterMetaType<ISD::ParkStatus>();
0160         isRegistered = true;
0161     }
0162 #endif
0163 }
0164 
0165 const QString &GenericDevice::getDeviceName() const
0166 {
0167     return m_Name;
0168 }
0169 
0170 void GenericDevice::registerProperty(INDI::Property prop)
0171 {
0172     if (!prop.getRegistered())
0173         return;
0174 
0175     m_ReadyTimer->start();
0176 
0177     const QString name = prop.getName();
0178 
0179     // In case driver already started
0180     if (name == "CONNECTION")
0181     {
0182         auto svp = prop.getSwitch();
0183 
0184         // Still connecting/disconnecting...
0185         if (!svp || svp->getState() == IPS_BUSY)
0186             return;
0187 
0188         auto conSP = svp->findWidgetByName("CONNECT");
0189 
0190         if (!conSP)
0191             return;
0192 
0193         if (m_Connected == false && svp->getState() == IPS_OK && conSP->getState() == ISS_ON)
0194         {
0195             m_Connected = true;
0196             emit Connected();
0197             createDeviceInit();
0198         }
0199         else if (m_Connected && conSP->getState() == ISS_OFF)
0200         {
0201             m_Connected = false;
0202             emit Disconnected();
0203         }
0204         else
0205             m_Ready = (svp->s == IPS_OK && conSP->s == ISS_ON);
0206     }
0207     else if (name == "DRIVER_INFO")
0208     {
0209         auto tvp = prop.getText();
0210         if (tvp)
0211         {
0212             auto tp = tvp->findWidgetByName("DRIVER_INTERFACE");
0213             if (tp)
0214             {
0215                 m_DriverInterface = static_cast<uint32_t>(atoi(tp->getText()));
0216                 emit interfaceDefined();
0217             }
0218 
0219             tp = tvp->findWidgetByName("DRIVER_VERSION");
0220             if (tp)
0221             {
0222                 m_DriverVersion = QString(tp->getText());
0223             }
0224         }
0225     }
0226     else if (name == "SYSTEM_PORTS")
0227     {
0228         // Check if our current port is set to one of the system ports. This indicates that the port
0229         // is not mapped yet to a permenant designation
0230         auto svp = prop.getSwitch();
0231         auto port = m_BaseDevice.getText("DEVICE_PORT");
0232         if (svp && port)
0233         {
0234             for (const auto &it : *svp)
0235             {
0236                 if (it.isNameMatch(port.at(0)->getText()))
0237                 {
0238                     emit systemPortDetected();
0239                     break;
0240                 }
0241             }
0242         }
0243     }
0244     else if (name == "TIME_UTC" && Options::useTimeUpdate())
0245     {
0246         const auto &tvp = prop.getText();
0247 
0248         if (tvp)
0249         {
0250             if (Options::useKStarsSource() && tvp->getPermission() != IP_RO)
0251                 updateTime();
0252             else
0253                 m_TimeUpdateTimer->start();
0254         }
0255     }
0256     else if (name == "GEOGRAPHIC_COORD" && Options::useGeographicUpdate())
0257     {
0258         if (Options::useKStarsSource() && prop.getPermission() != IP_RO)
0259             updateLocation();
0260         else
0261             m_LocationUpdateTimer->start();
0262     }
0263     else if (name == "WATCHDOG_HEARTBEAT")
0264     {
0265         if (watchDogTimer == nullptr)
0266         {
0267             watchDogTimer = new QTimer(this);
0268             connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(resetWatchdog()));
0269         }
0270 
0271         if (m_Connected && prop.getNumber()->at(0)->getValue() > 0)
0272         {
0273             // Send immediately a heart beat
0274             m_ClientManager->sendNewProperty(prop);
0275         }
0276     }
0277 
0278     emit propertyDefined(prop);
0279 }
0280 
0281 void GenericDevice::updateProperty(INDI::Property prop)
0282 {
0283     switch (prop.getType())
0284     {
0285         case INDI_SWITCH:
0286             processSwitch(prop);
0287             break;
0288         case INDI_NUMBER:
0289             processNumber(prop);
0290             break;
0291         case INDI_TEXT:
0292             processText(prop);
0293             break;
0294         case INDI_LIGHT:
0295             processLight(prop);
0296             break;
0297         case INDI_BLOB:
0298             processBLOB(prop);
0299             break;
0300         default:
0301             break;
0302     }
0303 }
0304 
0305 void GenericDevice::removeProperty(INDI::Property prop)
0306 {
0307     emit propertyDeleted(prop);
0308 }
0309 
0310 void GenericDevice::processSwitch(INDI::Property prop)
0311 {
0312     if (prop.isNameMatch("CONNECTION"))
0313     {
0314         // Still connecting/disconnecting...
0315         if (prop.getState() == IPS_BUSY)
0316             return;
0317 
0318         auto connectionOn = prop.getSwitch()->findWidgetByName("CONNECT");
0319         if (m_Connected == false && prop.getState() == IPS_OK && connectionOn->getState() == ISS_ON)
0320         {
0321             m_Ready = false;
0322             connect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout, Qt::UniqueConnection);
0323 
0324             m_Connected = true;
0325             emit Connected();
0326             createDeviceInit();
0327 
0328             if (watchDogTimer != nullptr)
0329             {
0330                 auto nvp = m_BaseDevice.getNumber("WATCHDOG_HEARTBEAT");
0331                 if (nvp && nvp.at(0)->getValue() > 0)
0332                 {
0333                     // Send immediately
0334                     m_ClientManager->sendNewProperty(nvp);
0335                 }
0336             }
0337 
0338             m_ReadyTimer->start();
0339         }
0340         else if (m_Connected && connectionOn->getState() == ISS_OFF)
0341         {
0342             disconnect(m_ReadyTimer, &QTimer::timeout, this, &GenericDevice::handleTimeout);
0343             m_Connected = false;
0344             m_Ready = false;
0345             emit Disconnected();
0346         }
0347         else
0348             m_Ready = (prop.getState() == IPS_OK && connectionOn->getState() == ISS_ON);
0349     }
0350 
0351     emit propertyUpdated(prop);
0352 }
0353 
0354 void GenericDevice::processNumber(INDI::Property prop)
0355 {
0356     QString deviceName = getDeviceName();
0357     auto nvp = prop.getNumber();
0358 
0359     if (prop.isNameMatch("GEOGRAPHIC_COORD") && prop.getState() == IPS_OK &&
0360             ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) ||
0361               (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE))))
0362     {
0363         // Update KStars Location once we receive update from INDI, if the source is set to DEVICE
0364         dms lng, lat;
0365         double elev = 0;
0366 
0367         auto np = nvp->findWidgetByName("LONG");
0368         if (!np)
0369             return;
0370 
0371         // INDI Longitude convention is 0 to 360. We need to turn it back into 0 to 180 EAST, 0 to -180 WEST
0372         if (np->value < 180)
0373             lng.setD(np->value);
0374         else
0375             lng.setD(np->value - 360.0);
0376 
0377         np = nvp->findWidgetByName("LAT");
0378         if (!np)
0379             return;
0380 
0381         lat.setD(np->value);
0382 
0383         // Double check we have valid values
0384         if (lng.Degrees() == 0 && lat.Degrees() == 0)
0385         {
0386             qCWarning(KSTARS_INDI) << "Ignoring invalid device coordinates.";
0387             return;
0388         }
0389 
0390         np = nvp->findWidgetByName("ELEV");
0391         if (np)
0392             elev = np->value;
0393 
0394         auto geo = KStars::Instance()->data()->geo();
0395         std::unique_ptr<GeoLocation> tempGeo;
0396 
0397         QString newLocationName;
0398         if (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE)
0399             newLocationName = i18n("GPS Location");
0400         else
0401             newLocationName = i18n("Mount Location");
0402 
0403         if (geo->name() != newLocationName)
0404         {
0405             double TZ0 = geo->TZ0();
0406             TimeZoneRule *rule = geo->tzrule();
0407             tempGeo.reset(new GeoLocation(lng, lat, newLocationName, "", "", TZ0, rule, elev));
0408             geo = tempGeo.get();
0409         }
0410         else
0411         {
0412             geo->setLong(lng);
0413             geo->setLat(lat);
0414         }
0415 
0416         qCInfo(KSTARS_INDI) << "Setting location from device:" << deviceName << "Longitude:" << lng.toDMSString() << "Latitude:" <<
0417                             lat.toDMSString();
0418 
0419         KStars::Instance()->data()->setLocation(*geo);
0420     }
0421     else if (nvp->isNameMatch("WATCHDOG_HEARTBEAT"))
0422     {
0423         if (watchDogTimer == nullptr)
0424         {
0425             watchDogTimer = new QTimer(this);
0426             connect(watchDogTimer, &QTimer::timeout, this, &GenericDevice::resetWatchdog);
0427         }
0428 
0429         auto value = nvp->at(0)->getValue();
0430         if (m_Connected && value > 0)
0431         {
0432             // Reset timer 5 seconds before it is due
0433             // To account for any networking delays
0434             double nextMS = qMax(100.0, (value - 5) * 1000);
0435             watchDogTimer->start(nextMS);
0436         }
0437         else if (value == 0)
0438             watchDogTimer->stop();
0439     }
0440 
0441     emit propertyUpdated(prop);
0442 }
0443 
0444 void GenericDevice::processText(INDI::Property prop)
0445 {
0446     auto tvp = prop.getText();
0447     // If DRIVER_INFO is updated after being defined, make sure to re-generate concrete devices accordingly.
0448     if (tvp->isNameMatch("DRIVER_INFO"))
0449     {
0450         auto tp = tvp->findWidgetByName("DRIVER_INTERFACE");
0451         if (tp)
0452         {
0453             m_DriverInterface = static_cast<uint32_t>(atoi(tp->getText()));
0454             emit interfaceDefined();
0455 
0456             // If devices were already created but we receieved an update to DRIVER_INTERFACE
0457             // then we need to re-generate the concrete devices to account for the change.
0458             if (m_ConcreteDevices.isEmpty() == false)
0459             {
0460                 // If we generated ANY concrete device due to interface update, then we emit ready immediately.
0461                 if (generateDevices())
0462                     emit ready();
0463             }
0464         }
0465 
0466         tp = tvp->findWidgetByName("DRIVER_VERSION");
0467         if (tp)
0468         {
0469             m_DriverVersion = QString(tp->text);
0470         }
0471 
0472     }
0473     // Update KStars time once we receive update from INDI, if the source is set to DEVICE
0474     else if (tvp->isNameMatch("TIME_UTC") && tvp->s == IPS_OK &&
0475              ( (Options::useMountSource() && (getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) ||
0476                (Options::useGPSSource() && (getDriverInterface() & INDI::BaseDevice::GPS_INTERFACE))))
0477     {
0478         int d, m, y, min, sec, hour;
0479         float utcOffset;
0480         QDate indiDate;
0481         QTime indiTime;
0482 
0483         auto tp = tvp->findWidgetByName("UTC");
0484 
0485         if (!tp)
0486         {
0487             qCWarning(KSTARS_INDI) << "UTC property missing from TIME_UTC";
0488             return;
0489         }
0490 
0491         sscanf(tp->getText(), "%d%*[^0-9]%d%*[^0-9]%dT%d%*[^0-9]%d%*[^0-9]%d", &y, &m, &d, &hour, &min, &sec);
0492         indiDate.setDate(y, m, d);
0493         indiTime.setHMS(hour, min, sec);
0494 
0495         KStarsDateTime indiDateTime(QDateTime(indiDate, indiTime, Qt::UTC));
0496 
0497         tp = tvp->findWidgetByName("OFFSET");
0498 
0499         if (!tp)
0500         {
0501             qCWarning(KSTARS_INDI) << "Offset property missing from TIME_UTC";
0502             return;
0503         }
0504 
0505         sscanf(tp->getText(), "%f", &utcOffset);
0506 
0507         qCInfo(KSTARS_INDI) << "Setting UTC time from device:" << getDeviceName() << indiDateTime.toString();
0508 
0509         KStars::Instance()->data()->changeDateTime(indiDateTime);
0510         KStars::Instance()->data()->syncLST();
0511 
0512         auto geo = KStars::Instance()->data()->geo();
0513         if (geo->tzrule())
0514             utcOffset -= geo->tzrule()->deltaTZ();
0515 
0516         // TZ0 is the timezone WTIHOUT any DST offsets. Above, we take INDI UTC Offset (with DST already included)
0517         // and subtract from it the deltaTZ from the current TZ rule.
0518         geo->setTZ0(utcOffset);
0519     }
0520 
0521     emit propertyUpdated(prop);
0522 }
0523 
0524 void GenericDevice::processLight(INDI::Property prop)
0525 {
0526     emit propertyUpdated(prop);
0527 }
0528 
0529 void GenericDevice::processMessage(int messageID)
0530 {
0531     emit messageUpdated(messageID);
0532 }
0533 
0534 bool GenericDevice::processBLOB(INDI::Property prop)
0535 {
0536     // Ignore write-only BLOBs since we only receive it for state-change
0537     if (prop.getPermission() == IP_WO)
0538         return false;
0539 
0540     auto bvp = prop.getBLOB();
0541     auto bp = bvp->at(0);
0542 
0543     // If any concrete device processed the blob then we return
0544     for (auto oneConcreteDevice : m_ConcreteDevices)
0545     {
0546         if (oneConcreteDevice->processBLOB(prop))
0547             return true;
0548     }
0549 
0550     INDIDataTypes dataType;
0551 
0552     if (!strcmp(bp->getFormat(), ".ascii"))
0553         dataType = DATA_ASCII;
0554     else
0555         dataType = DATA_OTHER;
0556 
0557     QString currentDir = Options::fitsDir();
0558     int nr, n = 0;
0559 
0560     if (currentDir.endsWith('/'))
0561         currentDir.truncate(sizeof(currentDir) - 1);
0562 
0563     QString filename(currentDir + '/');
0564 
0565     QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
0566 
0567     filename += QString("%1_").arg(bp->getLabel()) + ts + QString(bp->getFormat()).trimmed();
0568 
0569     // Text Streaming
0570     if (dataType == DATA_ASCII)
0571     {
0572         // First time, create a data file to hold the stream.
0573 
0574         auto it = std::find_if(streamFileMetadata.begin(), streamFileMetadata.end(), [bvp, bp](const StreamFileMetadata & data)
0575         {
0576             return (bvp->getDeviceName() == data.device && bvp->getName() == data.property && bp->getName() == data.element);
0577         });
0578 
0579         QFile *streamDatafile = nullptr;
0580 
0581         // New stream data file
0582         if (it == streamFileMetadata.end())
0583         {
0584             StreamFileMetadata metadata;
0585             metadata.device = bvp->getDeviceName();
0586             metadata.property = bvp->getName();
0587             metadata.element = bp->getName();
0588 
0589             // Create new file instance
0590             // Since it's a child of this class, don't worry about deallocating it
0591             streamDatafile = new QFile(this);
0592             metadata.file = streamDatafile;
0593 
0594             streamFileMetadata.append(metadata);
0595         }
0596         else
0597             streamDatafile = (*it).file;
0598 
0599         // Try to get
0600 
0601         QDataStream out(streamDatafile);
0602         for (nr = 0; nr < bp->getSize(); nr += n)
0603             n = out.writeRawData(static_cast<char *>(bp->getBlob()) + nr, bp->getSize() - nr);
0604 
0605         out.writeRawData((const char *)"\n", 1);
0606         streamDatafile->flush();
0607 
0608     }
0609     else
0610     {
0611         QFile fits_temp_file(filename);
0612         if (!fits_temp_file.open(QIODevice::WriteOnly))
0613         {
0614             qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName();
0615             return false;
0616         }
0617 
0618         QDataStream out(&fits_temp_file);
0619 
0620         for (nr = 0; nr < bp->getSize(); nr += n)
0621             n = out.writeRawData(static_cast<char *>(bp->getBlob()) + nr, bp->getSize() - nr);
0622 
0623         fits_temp_file.flush();
0624         fits_temp_file.close();
0625 
0626         auto fmt = QString(bp->getFormat()).toLower().remove('.').toUtf8();
0627         if (QImageReader::supportedImageFormats().contains(fmt))
0628         {
0629             QUrl url(filename);
0630             url.setScheme("file");
0631             auto iv = new ImageViewer(url, QString(), KStars::Instance());
0632             if (iv)
0633                 iv->show();
0634         }
0635     }
0636 
0637     if (dataType == DATA_OTHER)
0638         KStars::Instance()->statusBar()->showMessage(i18n("Data file saved to %1", filename), 0);
0639 
0640     emit propertyUpdated(prop);
0641     return true;
0642 }
0643 
0644 bool GenericDevice::setConfig(INDIConfig tConfig)
0645 {
0646     auto svp = m_BaseDevice.getSwitch("CONFIG_PROCESS");
0647 
0648     if (!svp)
0649         return false;
0650 
0651     const char *strConfig = nullptr;
0652 
0653     switch (tConfig)
0654     {
0655         case LOAD_LAST_CONFIG:
0656             strConfig = "CONFIG_LOAD";
0657             break;
0658 
0659         case SAVE_CONFIG:
0660             strConfig = "CONFIG_SAVE";
0661             break;
0662 
0663         case LOAD_DEFAULT_CONFIG:
0664             strConfig = "CONFIG_DEFAULT";
0665             break;
0666 
0667         case PURGE_CONFIG:
0668             strConfig = "CONFIG_PURGE";
0669             break;
0670     }
0671 
0672     svp.reset();
0673     if (strConfig)
0674     {
0675         auto sp = svp.findWidgetByName(strConfig);
0676         if (!sp)
0677             return false;
0678         sp->setState(ISS_ON);
0679     }
0680 
0681     m_ClientManager->sendNewProperty(svp);
0682 
0683     return true;
0684 }
0685 
0686 void GenericDevice::createDeviceInit()
0687 {
0688     if (Options::showINDIMessages())
0689         KStars::Instance()->statusBar()->showMessage(i18n("%1 is online.", m_Name), 0);
0690 
0691     KStars::Instance()->map()->forceUpdateNow();
0692 }
0693 
0694 /*********************************************************************************/
0695 /* Update the Driver's Time                          */
0696 /*********************************************************************************/
0697 void GenericDevice::updateTime()
0698 {
0699     QString offset, isoTS;
0700 
0701     offset = QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2);
0702 
0703     //QTime newTime( KStars::Instance()->data()->ut().time());
0704     //QDate newDate( KStars::Instance()->data()->ut().date());
0705 
0706     //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());
0707 
0708     isoTS = KStars::Instance()->data()->ut().toString(Qt::ISODate).remove('Z');
0709 
0710     /* Update Date/Time */
0711     auto timeUTC = m_BaseDevice.getText("TIME_UTC");
0712 
0713     if (timeUTC)
0714     {
0715         auto timeEle = timeUTC.findWidgetByName("UTC");
0716         if (timeEle)
0717             timeEle->setText(isoTS.toLatin1().constData());
0718 
0719         auto offsetEle = timeUTC.findWidgetByName("OFFSET");
0720         if (offsetEle)
0721             offsetEle->setText(offset.toLatin1().constData());
0722 
0723         if (timeEle && offsetEle)
0724         {
0725             qCInfo(KSTARS_INDI) << "Updating" << getDeviceName() << "Time UTC:" << isoTS << "Offset:" << offset;
0726             m_ClientManager->sendNewProperty(timeUTC);
0727         }
0728     }
0729 }
0730 
0731 /*********************************************************************************/
0732 /* Update the Driver's Geographical Location                     */
0733 /*********************************************************************************/
0734 void GenericDevice::updateLocation()
0735 {
0736     GeoLocation *geo = KStars::Instance()->data()->geo();
0737     double longNP;
0738 
0739     if (geo->lng()->Degrees() >= 0)
0740         longNP = geo->lng()->Degrees();
0741     else
0742         longNP = dms(geo->lng()->Degrees() + 360.0).Degrees();
0743 
0744     auto nvp = m_BaseDevice.getNumber("GEOGRAPHIC_COORD");
0745 
0746     if (!nvp)
0747         return;
0748 
0749     auto np = nvp.findWidgetByName("LONG");
0750 
0751     if (!np)
0752         return;
0753 
0754     np->setValue(longNP);
0755 
0756     np = nvp.findWidgetByName("LAT");
0757     if (!np)
0758         return;
0759 
0760     np->setValue(geo->lat()->Degrees());
0761 
0762     np = nvp.findWidgetByName("ELEV");
0763     if (!np)
0764         return;
0765 
0766     np->setValue(geo->elevation());
0767 
0768     qCInfo(KSTARS_INDI) << "Updating" << getDeviceName() << "Location Longitude:" << longNP << "Latitude:" << geo->lat()->Degrees() << "Elevation:" << geo->elevation();
0769 
0770     m_ClientManager->sendNewProperty(nvp);
0771 }
0772 
0773 void GenericDevice::Connect()
0774 {
0775     m_ClientManager->connectDevice(m_Name.toLatin1().constData());
0776 }
0777 
0778 void GenericDevice::Disconnect()
0779 {
0780     m_ClientManager->disconnectDevice(m_Name.toLatin1().constData());
0781 }
0782 
0783 bool GenericDevice::setProperty(QObject *setPropCommand)
0784 {
0785     GDSetCommand *indiCommand = static_cast<GDSetCommand *>(setPropCommand);
0786 
0787     //qDebug() << Q_FUNC_INFO << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue;
0788 
0789     auto prop = m_BaseDevice.getProperty(indiCommand->indiProperty.toLatin1().constData());
0790 
0791     if (!prop)
0792         return false;
0793 
0794     switch (indiCommand->propType)
0795     {
0796         case INDI_SWITCH:
0797         {
0798             auto svp = prop.getSwitch();
0799 
0800             if (!svp)
0801                 return false;
0802 
0803             auto sp = svp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
0804 
0805             if (!sp)
0806                 return false;
0807 
0808             if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
0809                 svp->reset();
0810 
0811             sp->setState(indiCommand->elementValue.toInt() == 0 ? ISS_OFF : ISS_ON);
0812 
0813             //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
0814             m_ClientManager->sendNewProperty(svp);
0815 
0816             return true;
0817         }
0818 
0819         case INDI_NUMBER:
0820         {
0821             auto nvp = prop.getNumber();
0822 
0823             if (!nvp)
0824                 return false;
0825 
0826             auto np = nvp->findWidgetByName(indiCommand->indiElement.toLatin1().constData());
0827 
0828             if (!np)
0829                 return false;
0830 
0831             double value = indiCommand->elementValue.toDouble();
0832 
0833             if (value == np->getValue())
0834                 return true;
0835 
0836             np->setValue(value);
0837 
0838             //qDebug() << Q_FUNC_INFO << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
0839             m_ClientManager->sendNewProperty(nvp);
0840         }
0841         break;
0842         // TODO: Add set property for other types of properties
0843         default:
0844             break;
0845     }
0846 
0847     return true;
0848 }
0849 
0850 bool GenericDevice::getMinMaxStep(const QString &propName, const QString &elementName, double *min, double *max,
0851                                   double *step)
0852 {
0853     auto nvp = m_BaseDevice.getNumber(propName.toLatin1());
0854 
0855     if (!nvp)
0856         return false;
0857 
0858     auto np = nvp.findWidgetByName(elementName.toLatin1());
0859 
0860     if (!np)
0861         return false;
0862 
0863     *min  = np->getMin();
0864     *max  = np->getMax();
0865     *step = np->getStep();
0866 
0867     return true;
0868 }
0869 
0870 IPState GenericDevice::getState(const QString &propName)
0871 {
0872     return m_BaseDevice.getPropertyState(propName.toLatin1().constData());
0873 }
0874 
0875 IPerm GenericDevice::getPermission(const QString &propName)
0876 {
0877     return m_BaseDevice.getPropertyPermission(propName.toLatin1().constData());
0878 }
0879 
0880 INDI::Property GenericDevice::getProperty(const QString &propName)
0881 {
0882     return m_BaseDevice.getProperty(propName.toLatin1().constData());
0883 }
0884 
0885 bool GenericDevice::setJSONProperty(const QString &propName, const QJsonArray &propElements)
0886 {
0887     for (auto &oneProp : * (m_BaseDevice.getProperties()))
0888     {
0889         if (propName == QString(oneProp.getName()))
0890         {
0891             switch (oneProp.getType())
0892             {
0893                 case INDI_SWITCH:
0894                 {
0895                     auto svp = oneProp.getSwitch();
0896                     if (svp->getRule() == ISR_1OFMANY || svp->getRule() == ISR_ATMOST1)
0897                         svp->reset();
0898 
0899                     for (auto oneElement : propElements)
0900                     {
0901                         QJsonObject oneElementObject = oneElement.toObject();
0902                         auto sp = svp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
0903                         if (sp)
0904                         {
0905                             sp->setState(static_cast<ISState>(oneElementObject["state"].toInt()));
0906                         }
0907                     }
0908 
0909                     m_ClientManager->sendNewProperty(svp);
0910                     return true;
0911                 }
0912 
0913                 case INDI_NUMBER:
0914                 {
0915                     auto nvp = oneProp.getNumber();
0916                     for (const auto &oneElement : propElements)
0917                     {
0918                         QJsonObject oneElementObject = oneElement.toObject();
0919                         auto np = nvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
0920                         if (np)
0921                         {
0922                             double newValue = oneElementObject["value"].toDouble(std::numeric_limits<double>::quiet_NaN());
0923                             if (std::isnan(newValue))
0924                             {
0925                                 f_scansexa(oneElementObject["value"].toString().toLatin1().constData(), &newValue);
0926                             }
0927                             np->setValue(newValue);
0928                         }
0929                     }
0930 
0931                     m_ClientManager->sendNewProperty(nvp);
0932                     return true;
0933                 }
0934 
0935                 case INDI_TEXT:
0936                 {
0937                     auto tvp = oneProp.getText();
0938                     for (const auto &oneElement : propElements)
0939                     {
0940                         QJsonObject oneElementObject = oneElement.toObject();
0941                         auto tp = tvp->findWidgetByName(oneElementObject["name"].toString().toLatin1().constData());
0942                         if (tp)
0943                             tp->setText(oneElementObject["text"].toString().toLatin1().constData());
0944                     }
0945 
0946                     m_ClientManager->sendNewProperty(tvp);
0947                     return true;
0948                 }
0949 
0950                 case INDI_BLOB:
0951                     // TODO
0952                     break;
0953 
0954                 default:
0955                     break;
0956             }
0957         }
0958     }
0959 
0960     return false;
0961 }
0962 
0963 bool GenericDevice::getJSONProperty(const QString &propName, QJsonObject &propObject, bool compact)
0964 {
0965     for (auto oneProp : m_BaseDevice.getProperties())
0966     {
0967         if (propName == oneProp.getName())
0968         {
0969             switch (oneProp.getType())
0970             {
0971                 case INDI_SWITCH:
0972                     switchToJson(oneProp, propObject, compact);
0973                     return true;
0974 
0975                 case INDI_NUMBER:
0976                     numberToJson(oneProp, propObject, compact);
0977                     return true;
0978 
0979                 case INDI_TEXT:
0980                     textToJson(oneProp, propObject, compact);
0981                     return true;
0982 
0983                 case INDI_LIGHT:
0984                     lightToJson(oneProp, propObject, compact);
0985                     return true;
0986 
0987                 case INDI_BLOB:
0988                     // TODO
0989                     break;
0990 
0991                 default:
0992                     break;
0993             }
0994         }
0995     }
0996 
0997     return false;
0998 }
0999 
1000 bool GenericDevice::getJSONBLOB(const QString &propName, const QString &elementName, QJsonObject &blobObject)
1001 {
1002     auto blobProperty = m_BaseDevice.getProperty(propName.toLatin1().constData());
1003     if (!blobProperty.isValid())
1004         return false;
1005 
1006     auto oneBLOB = blobProperty.getBLOB()->findWidgetByName(elementName.toLatin1().constData());
1007     if (!oneBLOB)
1008         return false;
1009 
1010     // Now convert to base64 and send back.
1011     QByteArray data = QByteArray::fromRawData(static_cast<const char *>(oneBLOB->getBlob()), oneBLOB->getBlobLen());
1012 
1013     QString encoded = data.toBase64(QByteArray::Base64UrlEncoding);
1014     blobObject.insert("property", propName);
1015     blobObject.insert("element", elementName);
1016     blobObject.insert("size", encoded.size());
1017     blobObject.insert("data", encoded);
1018 
1019     return true;
1020 }
1021 
1022 void GenericDevice::resetWatchdog()
1023 {
1024     auto nvp = m_BaseDevice.getNumber("WATCHDOG_HEARTBEAT");
1025 
1026     if (nvp)
1027         // Send heartbeat to driver
1028         m_ClientManager->sendNewProperty(nvp);
1029 }
1030 
1031 bool GenericDevice::findConcreteDevice(uint32_t interface, QSharedPointer<ConcreteDevice> &device)
1032 {
1033     if (m_ConcreteDevices.contains(interface))
1034     {
1035         device = m_ConcreteDevices[interface];
1036         return true;
1037     }
1038     return false;
1039 }
1040 
1041 ISD::Mount *GenericDevice::getMount()
1042 {
1043     if (m_ConcreteDevices.contains(INDI::BaseDevice::TELESCOPE_INTERFACE))
1044         return dynamic_cast<ISD::Mount*>(m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].get());
1045     return nullptr;
1046 }
1047 
1048 ISD::Camera *GenericDevice::getCamera()
1049 {
1050     if (m_ConcreteDevices.contains(INDI::BaseDevice::CCD_INTERFACE))
1051         return dynamic_cast<ISD::Camera*>(m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].get());
1052     return nullptr;
1053 }
1054 
1055 ISD::Guider *GenericDevice::getGuider()
1056 {
1057     if (m_ConcreteDevices.contains(INDI::BaseDevice::GUIDER_INTERFACE))
1058         return dynamic_cast<ISD::Guider*>(m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].get());
1059     return nullptr;
1060 }
1061 
1062 ISD::Focuser *GenericDevice::getFocuser()
1063 {
1064     if (m_ConcreteDevices.contains(INDI::BaseDevice::FOCUSER_INTERFACE))
1065         return dynamic_cast<ISD::Focuser*>(m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].get());
1066     return nullptr;
1067 }
1068 
1069 ISD::FilterWheel *GenericDevice::getFilterWheel()
1070 {
1071     if (m_ConcreteDevices.contains(INDI::BaseDevice::FILTER_INTERFACE))
1072         return dynamic_cast<ISD::FilterWheel*>(m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].get());
1073     return nullptr;
1074 }
1075 
1076 ISD::Dome *GenericDevice::getDome()
1077 {
1078     if (m_ConcreteDevices.contains(INDI::BaseDevice::DOME_INTERFACE))
1079         return dynamic_cast<ISD::Dome*>(m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].get());
1080     return nullptr;
1081 }
1082 
1083 ISD::GPS *GenericDevice::getGPS()
1084 {
1085     if (m_ConcreteDevices.contains(INDI::BaseDevice::GPS_INTERFACE))
1086         return dynamic_cast<ISD::GPS*>(m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].get());
1087     return nullptr;
1088 }
1089 
1090 ISD::Weather *GenericDevice::getWeather()
1091 {
1092     if (m_ConcreteDevices.contains(INDI::BaseDevice::WEATHER_INTERFACE))
1093         return dynamic_cast<ISD::Weather*>(m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].get());
1094     return nullptr;
1095 }
1096 
1097 ISD::AdaptiveOptics *GenericDevice::getAdaptiveOptics()
1098 {
1099     if (m_ConcreteDevices.contains(INDI::BaseDevice::AO_INTERFACE))
1100         return dynamic_cast<ISD::AdaptiveOptics*>(m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].get());
1101     return nullptr;
1102 }
1103 
1104 ISD::DustCap *GenericDevice::getDustCap()
1105 {
1106     if (m_ConcreteDevices.contains(INDI::BaseDevice::DUSTCAP_INTERFACE))
1107         return dynamic_cast<ISD::DustCap*>(m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].get());
1108     return nullptr;
1109 }
1110 
1111 ISD::LightBox *GenericDevice::getLightBox()
1112 {
1113     if (m_ConcreteDevices.contains(INDI::BaseDevice::LIGHTBOX_INTERFACE))
1114         return dynamic_cast<ISD::LightBox*>(m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].get());
1115     return nullptr;
1116 }
1117 
1118 ISD::Detector *GenericDevice::getDetector()
1119 {
1120     if (m_ConcreteDevices.contains(INDI::BaseDevice::DETECTOR_INTERFACE))
1121         return dynamic_cast<ISD::Detector*>(m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].get());
1122     return nullptr;
1123 }
1124 
1125 ISD::Rotator *GenericDevice::getRotator()
1126 {
1127     if (m_ConcreteDevices.contains(INDI::BaseDevice::ROTATOR_INTERFACE))
1128         return dynamic_cast<ISD::Rotator*>(m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].get());
1129     return nullptr;
1130 }
1131 
1132 ISD::Spectrograph *GenericDevice::getSpectrograph()
1133 {
1134     if (m_ConcreteDevices.contains(INDI::BaseDevice::SPECTROGRAPH_INTERFACE))
1135         return dynamic_cast<ISD::Spectrograph*>(m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].get());
1136     return nullptr;
1137 }
1138 
1139 ISD::Correlator *GenericDevice::getCorrelator()
1140 {
1141     if (m_ConcreteDevices.contains(INDI::BaseDevice::CORRELATOR_INTERFACE))
1142         return dynamic_cast<ISD::Correlator*>(m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].get());
1143     return nullptr;
1144 }
1145 
1146 ISD::Auxiliary *GenericDevice::getAuxiliary()
1147 {
1148     if (m_ConcreteDevices.contains(INDI::BaseDevice::AUX_INTERFACE))
1149         return dynamic_cast<ISD::Auxiliary*>(m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].get());
1150     return nullptr;
1151 }
1152 
1153 bool GenericDevice::generateDevices()
1154 {
1155     auto generated = false;
1156     // Mount
1157     if (m_DriverInterface & INDI::BaseDevice::TELESCOPE_INTERFACE &&
1158             m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].isNull())
1159     {
1160         auto mount = new ISD::Mount(this);
1161         mount->setObjectName("Mount:" + objectName());
1162         generated = true;
1163         m_ConcreteDevices[INDI::BaseDevice::TELESCOPE_INTERFACE].reset(mount);
1164         mount->registeProperties();
1165         if (m_Connected)
1166         {
1167             mount->processProperties();
1168             emit newMount(mount);
1169         }
1170         else
1171         {
1172             connect(mount, &ISD::ConcreteDevice::ready, this, [this, mount]()
1173             {
1174                 emit newMount(mount);
1175             });
1176         }
1177     }
1178 
1179     // Camera
1180     if (m_DriverInterface & INDI::BaseDevice::CCD_INTERFACE &&
1181             m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].isNull())
1182     {
1183         auto camera = new ISD::Camera(this);
1184         camera->setObjectName("Camera:" + objectName());
1185         generated = true;
1186         m_ConcreteDevices[INDI::BaseDevice::CCD_INTERFACE].reset(camera);
1187         camera->registeProperties();
1188         if (m_Connected)
1189         {
1190             camera->processProperties();
1191             emit newCamera(camera);
1192             emit ready();
1193         }
1194         else
1195         {
1196             connect(camera, &ISD::ConcreteDevice::ready, this, [this, camera]()
1197             {
1198                 emit newCamera(camera);
1199             });
1200         }
1201     }
1202 
1203     // Guider
1204     if (m_DriverInterface & INDI::BaseDevice::GUIDER_INTERFACE &&
1205             m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].isNull())
1206     {
1207         auto guider = new ISD::Guider(this);
1208         guider->setObjectName("Guider:" + objectName());
1209         generated = true;
1210         m_ConcreteDevices[INDI::BaseDevice::GUIDER_INTERFACE].reset(guider);
1211         guider->registeProperties();
1212         if (m_Connected)
1213         {
1214             guider->processProperties();
1215             emit newGuider(guider);
1216         }
1217         else
1218         {
1219             connect(guider, &ISD::ConcreteDevice::ready, this, [this, guider]()
1220             {
1221                 emit newGuider(guider);
1222             });
1223         }
1224     }
1225 
1226     // Focuser
1227     if (m_DriverInterface & INDI::BaseDevice::FOCUSER_INTERFACE &&
1228             m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].isNull())
1229     {
1230         auto focuser = new ISD::Focuser(this);
1231         focuser->setObjectName("Focuser:" + objectName());
1232         generated = true;
1233         m_ConcreteDevices[INDI::BaseDevice::FOCUSER_INTERFACE].reset(focuser);
1234         focuser->registeProperties();
1235         if (m_Connected)
1236         {
1237             focuser->processProperties();
1238             emit newFocuser(focuser);
1239         }
1240         else
1241         {
1242             connect(focuser, &ISD::ConcreteDevice::ready, this, [this, focuser]()
1243             {
1244                 emit newFocuser(focuser);
1245             });
1246         }
1247     }
1248 
1249     // Filter Wheel
1250     if (m_DriverInterface & INDI::BaseDevice::FILTER_INTERFACE &&
1251             m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].isNull())
1252     {
1253         auto filterWheel = new ISD::FilterWheel(this);
1254         filterWheel->setObjectName("FilterWheel:" + objectName());
1255         generated = true;
1256         m_ConcreteDevices[INDI::BaseDevice::FILTER_INTERFACE].reset(filterWheel);
1257         filterWheel->registeProperties();
1258         if (m_Connected)
1259         {
1260             filterWheel->processProperties();
1261             emit newFilterWheel(filterWheel);
1262         }
1263         else
1264         {
1265             connect(filterWheel, &ISD::ConcreteDevice::ready, this, [this, filterWheel]()
1266             {
1267                 emit newFilterWheel(filterWheel);
1268             });
1269         }
1270     }
1271 
1272     // Dome
1273     if (m_DriverInterface & INDI::BaseDevice::DOME_INTERFACE &&
1274             m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].isNull())
1275     {
1276         auto dome = new ISD::Dome(this);
1277         dome->setObjectName("Dome:" + objectName());
1278         generated = true;
1279         m_ConcreteDevices[INDI::BaseDevice::DOME_INTERFACE].reset(dome);
1280         dome->registeProperties();
1281         if (m_Connected)
1282         {
1283             dome->processProperties();
1284             emit newDome(dome);
1285         }
1286         else
1287         {
1288             connect(dome, &ISD::ConcreteDevice::ready, this, [this, dome]()
1289             {
1290                 emit newDome(dome);
1291             });
1292         }
1293     }
1294 
1295     // GPS
1296     if (m_DriverInterface & INDI::BaseDevice::GPS_INTERFACE &&
1297             m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].isNull())
1298     {
1299         auto gps = new ISD::GPS(this);
1300         gps->setObjectName("GPS:" + objectName());
1301         generated = true;
1302         m_ConcreteDevices[INDI::BaseDevice::GPS_INTERFACE].reset(gps);
1303         gps->registeProperties();
1304         if (m_Connected)
1305         {
1306             gps->processProperties();
1307             emit newGPS(gps);
1308         }
1309         else
1310         {
1311             connect(gps, &ISD::ConcreteDevice::ready, this, [this, gps]()
1312             {
1313                 emit newGPS(gps);
1314             });
1315         }
1316     }
1317 
1318     // Weather
1319     if (m_DriverInterface & INDI::BaseDevice::WEATHER_INTERFACE &&
1320             m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].isNull())
1321     {
1322         auto weather = new ISD::Weather(this);
1323         weather->setObjectName("Weather:" + objectName());
1324         generated = true;
1325         m_ConcreteDevices[INDI::BaseDevice::WEATHER_INTERFACE].reset(weather);
1326         weather->registeProperties();
1327         if (m_Connected)
1328         {
1329             weather->processProperties();
1330             emit newWeather(weather);
1331         }
1332         else
1333         {
1334             connect(weather, &ISD::ConcreteDevice::ready, this, [this, weather]()
1335             {
1336                 emit newWeather(weather);
1337             });
1338         }
1339     }
1340 
1341     // Adaptive Optics
1342     if (m_DriverInterface & INDI::BaseDevice::AO_INTERFACE &&
1343             m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].isNull())
1344     {
1345         auto ao = new ISD::AdaptiveOptics(this);
1346         ao->setObjectName("AdaptiveOptics:" + objectName());
1347         generated = true;
1348         m_ConcreteDevices[INDI::BaseDevice::AO_INTERFACE].reset(ao);
1349         ao->registeProperties();
1350         if (m_Connected)
1351         {
1352             ao->processProperties();
1353             emit newAdaptiveOptics(ao);
1354         }
1355         else
1356         {
1357             connect(ao, &ISD::ConcreteDevice::ready, this, [this, ao]()
1358             {
1359                 emit newAdaptiveOptics(ao);
1360             });
1361         }
1362     }
1363 
1364     // Dust Cap
1365     if (m_DriverInterface & INDI::BaseDevice::DUSTCAP_INTERFACE &&
1366             m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].isNull())
1367     {
1368         auto dustCap = new ISD::DustCap(this);
1369         dustCap->setObjectName("DustCap:" + objectName());
1370         generated = true;
1371         m_ConcreteDevices[INDI::BaseDevice::DUSTCAP_INTERFACE].reset(dustCap);
1372         dustCap->registeProperties();
1373         if (m_Connected)
1374         {
1375             dustCap->processProperties();
1376             emit newDustCap(dustCap);
1377         }
1378         else
1379         {
1380             connect(dustCap, &ISD::ConcreteDevice::ready, this, [this, dustCap]()
1381             {
1382                 emit newDustCap(dustCap);
1383             });
1384         }
1385     }
1386 
1387     // Light box
1388     if (m_DriverInterface & INDI::BaseDevice::LIGHTBOX_INTERFACE &&
1389             m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].isNull())
1390     {
1391         auto lightBox = new ISD::LightBox(this);
1392         lightBox->setObjectName("LightBox:" + objectName());
1393         generated = true;
1394         m_ConcreteDevices[INDI::BaseDevice::LIGHTBOX_INTERFACE].reset(lightBox);
1395         lightBox->registeProperties();
1396         if (m_Connected)
1397         {
1398             lightBox->processProperties();
1399             emit newLightBox(lightBox);
1400         }
1401         else
1402         {
1403             connect(lightBox, &ISD::ConcreteDevice::ready, this, [this, lightBox]()
1404             {
1405                 emit newLightBox(lightBox);
1406             });
1407         }
1408     }
1409 
1410     // Rotator
1411     if (m_DriverInterface & INDI::BaseDevice::ROTATOR_INTERFACE &&
1412             m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].isNull())
1413     {
1414         auto rotator = new ISD::Rotator(this);
1415         rotator->setObjectName("Rotator:" + objectName());
1416         generated = true;
1417         m_ConcreteDevices[INDI::BaseDevice::ROTATOR_INTERFACE].reset(rotator);
1418         rotator->registeProperties();
1419         if (m_Connected)
1420         {
1421             rotator->processProperties();
1422             emit newRotator(rotator);
1423         }
1424         else
1425         {
1426             connect(rotator, &ISD::ConcreteDevice::ready, this, [this, rotator]()
1427             {
1428                 emit newRotator(rotator);
1429             });
1430         }
1431     }
1432 
1433     // Detector
1434     if (m_DriverInterface & INDI::BaseDevice::DETECTOR_INTERFACE &&
1435             m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].isNull())
1436     {
1437         auto detector = new ISD::Detector(this);
1438         detector->setObjectName("Detector:" + objectName());
1439         generated = true;
1440         m_ConcreteDevices[INDI::BaseDevice::DETECTOR_INTERFACE].reset(detector);
1441         detector->registeProperties();
1442         if (m_Connected)
1443         {
1444             detector->processProperties();
1445             emit newDetector(detector);
1446         }
1447         else
1448         {
1449             connect(detector, &ISD::ConcreteDevice::ready, this, [this, detector]()
1450             {
1451                 emit newDetector(detector);
1452             });
1453         }
1454     }
1455 
1456     // Spectrograph
1457     if (m_DriverInterface & INDI::BaseDevice::SPECTROGRAPH_INTERFACE &&
1458             m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].isNull())
1459     {
1460         auto spectrograph = new ISD::Spectrograph(this);
1461         spectrograph->setObjectName("Spectrograph:" + objectName());
1462         generated = true;
1463         m_ConcreteDevices[INDI::BaseDevice::SPECTROGRAPH_INTERFACE].reset(spectrograph);
1464         spectrograph->registeProperties();
1465         if (m_Connected)
1466         {
1467             spectrograph->processProperties();
1468             emit newSpectrograph(spectrograph);
1469         }
1470         else
1471         {
1472             connect(spectrograph, &ISD::ConcreteDevice::ready, this, [this, spectrograph]()
1473             {
1474                 emit newSpectrograph(spectrograph);
1475             });
1476         }
1477     }
1478 
1479     // Correlator
1480     if (m_DriverInterface & INDI::BaseDevice::CORRELATOR_INTERFACE &&
1481             m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].isNull())
1482     {
1483         auto correlator = new ISD::Correlator(this);
1484         correlator->setObjectName("Correlator:" + objectName());
1485         generated = true;
1486         m_ConcreteDevices[INDI::BaseDevice::CORRELATOR_INTERFACE].reset(correlator);
1487         correlator->registeProperties();
1488         if (m_Connected)
1489         {
1490             correlator->processProperties();
1491             emit newCorrelator(correlator);
1492         }
1493         else
1494         {
1495             connect(correlator, &ISD::ConcreteDevice::ready, this, [this, correlator]()
1496             {
1497                 emit newCorrelator(correlator);
1498             });
1499         }
1500     }
1501 
1502     // Auxiliary
1503     if (m_DriverInterface & INDI::BaseDevice::AUX_INTERFACE &&
1504             m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].isNull())
1505     {
1506         auto aux = new ISD::Auxiliary(this);
1507         aux->setObjectName("Auxiliary:" + objectName());
1508         generated = true;
1509         m_ConcreteDevices[INDI::BaseDevice::AUX_INTERFACE].reset(aux);
1510         aux->registeProperties();
1511         if (m_Connected)
1512         {
1513             aux->processProperties();
1514             emit newAuxiliary(aux);
1515         }
1516         else
1517         {
1518             connect(aux, &ISD::ConcreteDevice::ready, this, [this, aux]()
1519             {
1520                 emit newAuxiliary(aux);
1521             });
1522         }
1523     }
1524 
1525     return generated;
1526 }
1527 
1528 void GenericDevice::sendNewProperty(INDI::Property prop)
1529 {
1530     m_ClientManager->sendNewProperty(prop);
1531 }
1532 
1533 void switchToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1534 {
1535     auto svp = prop.getSwitch();
1536     QJsonArray switches;
1537     for (int i = 0; i < svp->count(); i++)
1538     {
1539         QJsonObject oneSwitch = {{"name", svp->at(i)->getName()}, {"state", svp->at(i)->getState()}};
1540         if (!compact)
1541             oneSwitch.insert("label", svp->at(i)->getLabel());
1542         switches.append(oneSwitch);
1543     }
1544 
1545     propObject = {{"device", svp->getDeviceName()}, {"name", svp->getName()}, {"state", svp->getState()}, {"switches", switches}};
1546     if (!compact)
1547     {
1548         propObject.insert("label", svp->getLabel());
1549         propObject.insert("group", svp->getGroupName());
1550         propObject.insert("perm", svp->getPermission());
1551         propObject.insert("rule", svp->getRule());
1552     }
1553 }
1554 
1555 void numberToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1556 {
1557     auto nvp = prop.getNumber();
1558     QJsonArray numbers;
1559     for (int i = 0; i < nvp->count(); i++)
1560     {
1561         QJsonObject oneNumber = {{"name", nvp->at(i)->getName()}, {"value", nvp->at(i)->getValue()}};
1562         if (!compact)
1563         {
1564             oneNumber.insert("label", nvp->at(i)->getLabel());
1565             oneNumber.insert("min", nvp->at(i)->getMin());
1566             oneNumber.insert("max", nvp->at(i)->getMax());
1567             oneNumber.insert("step", nvp->at(i)->getStep());
1568             oneNumber.insert("format", nvp->at(i)->getFormat());
1569         }
1570         numbers.append(oneNumber);
1571     }
1572 
1573     propObject = {{"device", nvp->getDeviceName()}, {"name", nvp->getName()}, {"state", nvp->getState()}, {"numbers", numbers}};
1574     if (!compact)
1575     {
1576         propObject.insert("label", nvp->getLabel());
1577         propObject.insert("group", nvp->getGroupName());
1578         propObject.insert("perm", nvp->getPermission());
1579     }
1580 }
1581 
1582 void textToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1583 {
1584     auto tvp = prop.getText();
1585     QJsonArray Texts;
1586     for (int i = 0; i < tvp->count(); i++)
1587     {
1588         QJsonObject oneText = {{"name", tvp->at(i)->getName()}, {"text", tvp->at(i)->getText()}};
1589         if (!compact)
1590         {
1591             oneText.insert("label", tvp->at(i)->getLabel());
1592         }
1593         Texts.append(oneText);
1594     }
1595 
1596     propObject = {{"device", tvp->getDeviceName()}, {"name", tvp->getName()}, {"state", tvp->getState()}, {"texts", Texts}};
1597     if (!compact)
1598     {
1599         propObject.insert("label", tvp->getLabel());
1600         propObject.insert("group", tvp->getGroupName());
1601         propObject.insert("perm", tvp->getPermission());
1602     }
1603 }
1604 
1605 void lightToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1606 {
1607     auto lvp = prop.getLight();
1608     QJsonArray Lights;
1609     for (int i = 0; i < lvp->count(); i++)
1610     {
1611         QJsonObject oneLight = {{"name", lvp->at(i)->getName()}, {"state", lvp->at(i)->getState()}};
1612         if (!compact)
1613         {
1614             oneLight.insert("label", lvp->at(i)->getLabel());
1615         }
1616         Lights.append(oneLight);
1617     }
1618 
1619     propObject = {{"device", lvp->getDeviceName()}, {"name", lvp->getName()}, {"state", lvp->getState()}, {"lights", Lights}};
1620     if (!compact)
1621     {
1622         propObject.insert("label", lvp->getLabel());
1623         propObject.insert("group", lvp->getGroupName());
1624     }
1625 }
1626 
1627 void propertyToJson(INDI::Property prop, QJsonObject &propObject, bool compact)
1628 {
1629     switch (prop.getType())
1630     {
1631         case INDI_SWITCH:
1632             switchToJson(prop, propObject, compact);
1633             break;
1634         case INDI_TEXT:
1635             textToJson(prop, propObject, compact);
1636             break;
1637         case INDI_NUMBER:
1638             numberToJson(prop, propObject, compact);
1639             break;
1640         case INDI_LIGHT:
1641             lightToJson(prop, propObject, compact);
1642             break;
1643         default:
1644             break;
1645     }
1646 }
1647 }
1648 
1649 #ifndef KSTARS_LITE
1650 QDBusArgument &operator<<(QDBusArgument &argument, const ISD::ParkStatus &source)
1651 {
1652     argument.beginStructure();
1653     argument << static_cast<int>(source);
1654     argument.endStructure();
1655     return argument;
1656 }
1657 
1658 const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::ParkStatus &dest)
1659 {
1660     int a;
1661     argument.beginStructure();
1662     argument >> a;
1663     argument.endStructure();
1664     dest = static_cast<ISD::ParkStatus>(a);
1665     return argument;
1666 }
1667 #endif