File indexing completed on 2024-04-21 14:46:02

0001 /*
0002     SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "devicemanager.h"
0008 
0009 #include "Options.h"
0010 #include "indimenu.h"
0011 #include "indiproperty.h"
0012 #include "indigroup.h"
0013 #include "indidevice.h"
0014 #include "indistd.h"
0015 #include "indidriver.h"
0016 #include "kstars.h"
0017 #include "kstarsdatetime.h"
0018 
0019 #include <indicom.h>
0020 
0021 #include <config-kstars.h>
0022 
0023 //#include <stdlib.h>
0024 //#include <unistd.h>
0025 
0026 #include <QTcpSocket>
0027 #include <QTextEdit>
0028 
0029 #include <KProcess>
0030 #include <KLocale>
0031 #include <KDebug>
0032 #include <KMessageBox>
0033 #include <KStatusBar>
0034 
0035 const int INDI_MAX_TRIES = 3;
0036 
0037 /*******************************************************************
0038 ** The device manager contain devices running from one indiserver
0039 ** This allow KStars to control multiple devices distributed across
0040 ** multiple servers seemingly in a way that is completely transparent
0041 ** to devices and drivers alike.
0042 ** The device Manager can be thought of as the 'networking' parent
0043 ** of devices, while indimenu is 'GUI' parent of devices
0044 *******************************************************************/
0045 DeviceManager::DeviceManager(INDIMenu *INDIparent, QString inHost, uint inPort, ManagerMode inMode)
0046 {
0047     parent        = INDIparent;
0048     serverProcess = nullptr;
0049     XMLParser     = nullptr;
0050     host          = inHost;
0051     port          = inPort;
0052     mode          = inMode;
0053 }
0054 
0055 DeviceManager::~DeviceManager()
0056 {
0057     serverSocket.close();
0058 
0059     if (serverProcess)
0060         serverProcess->close();
0061 
0062     delete (serverProcess);
0063 
0064     if (XMLParser)
0065         delLilXML(XMLParser);
0066 
0067     XMLParser = nullptr;
0068 
0069     while (!indi_dev.isEmpty())
0070         delete indi_dev.takeFirst();
0071 }
0072 
0073 void DeviceManager::startServer()
0074 {
0075     serverProcess = new KProcess;
0076 
0077     if (managed_devices.isEmpty())
0078     {
0079         kWarning() << "managed_devices was not set! Cannot start server!";
0080         return;
0081     }
0082 #ifdef Q_OS_OSX
0083     if (Options::indiServerIsInternal())
0084         *serverProcess << QCoreApplication::applicationDirPath() + "/indiserver";
0085     else
0086 #endif
0087         *serverProcess << Options::indiServer();
0088     *serverProcess << "-v"
0089                    << "-p" << QString::number(port);
0090 
0091     foreach (IDevice *device, managed_devices)
0092     {
0093         // JM: Temporary workaround for indiserver limit of client BLOBs for CCDs.
0094         if (device->type == KSTARS_CCD)
0095         {
0096             *serverProcess << "-m"
0097                            << "100";
0098             break;
0099         }
0100     }
0101 
0102     foreach (IDevice *device, managed_devices)
0103         *serverProcess << device->driver;
0104 
0105     if (mode == DeviceManager::M_LOCAL)
0106     {
0107         connect(serverProcess, SIGNAL(readyReadStandardError()), this, SLOT(processStandardError()));
0108         serverProcess->setOutputChannelMode(KProcess::SeparateChannels);
0109         serverProcess->setReadChannel(QProcess::StandardError);
0110     }
0111 
0112     serverProcess->start();
0113 
0114     serverProcess->waitForStarted();
0115 
0116     if (mode == DeviceManager::M_LOCAL)
0117         connectToServer();
0118 }
0119 
0120 void DeviceManager::stopServer()
0121 {
0122     serverProcess->terminate();
0123 }
0124 
0125 void DeviceManager::connectToServer()
0126 {
0127     connect(&serverSocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));
0128 
0129     for (int i = 0; i < INDI_MAX_TRIES; i++)
0130     {
0131         serverSocket.connectToHost(host, port);
0132         if (serverSocket.waitForConnected(1000))
0133         {
0134             connect(&serverSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError()));
0135             connectionSuccess();
0136             return;
0137         }
0138 
0139         usleep(100000);
0140     }
0141 
0142     connectionError();
0143 }
0144 
0145 void DeviceManager::enableBLOB(bool enable, QString device, QString property)
0146 {
0147     QTextStream serverFP(&serverSocket);
0148     QString openTag;
0149 
0150     if (device.isEmpty())
0151         return;
0152 
0153     if (property.isEmpty() == false)
0154         openTag = QString("<enableBLOB device='%1' name='%2'>").arg(device).arg(property);
0155     else
0156         openTag = QString("<enableBLOB device='%1'>").arg(device);
0157 
0158     if (enable)
0159     {
0160         serverFP << QString("%1Also</enableBLOB>\n").arg(openTag);
0161         kDebug() << QString("%1Also</enableBLOB>\n").arg(openTag);
0162     }
0163     else
0164     {
0165         serverFP << QString("%1Never</enableBLOB>\n").arg(openTag);
0166         kDebug() << QString("%1Never</enableBLOB>\n").arg(openTag);
0167     }
0168 }
0169 
0170 void DeviceManager::connectionSuccess()
0171 {
0172     QTextStream serverFP(&serverSocket);
0173 
0174     if (XMLParser)
0175         delLilXML(XMLParser);
0176     XMLParser = newLilXML();
0177 
0178     serverFP << QString("<getProperties version='%1'/>\n").arg(INDIVERSION);
0179 }
0180 
0181 void DeviceManager::connectionError()
0182 {
0183     QString errMsg = QString("Connection to INDI host at %1 on port %2 encountered an error: %3.")
0184                          .arg(host)
0185                          .arg(port)
0186                          .arg(serverSocket.errorString());
0187     KMessageBox::error(nullptr, errMsg);
0188 
0189     emit deviceManagerError(this);
0190 }
0191 
0192 void DeviceManager::appendManagedDevices(QList<IDevice *> &processed_devices)
0193 {
0194     managed_devices = processed_devices;
0195 
0196     foreach (IDevice *device, managed_devices)
0197     {
0198         device->unique_label = parent->getUniqueDeviceLabel(device->tree_label);
0199         //device->mode      = mode;
0200         device->deviceManager = this;
0201     }
0202 }
0203 
0204 void DeviceManager::processStandardError()
0205 {
0206     if (serverProcess == nullptr)
0207         return;
0208 
0209     serverBuffer.append(serverProcess->readAllStandardError());
0210     emit newServerInput();
0211 }
0212 
0213 void DeviceManager::dataReceived()
0214 {
0215     char errmsg[ERRMSG_SIZE];
0216     int nr = 0, err_code = 0;
0217     QTextStream serverFP(&serverSocket);
0218     QString ibuf, err_cmd;
0219 
0220     ibuf = serverFP.readAll();
0221     nr   = ibuf.length();
0222 
0223     /* process each char */
0224     for (int i = 0; i < nr; i++)
0225     {
0226         if (!XMLParser)
0227             return;
0228 
0229         XMLEle *root = readXMLEle(XMLParser, ibuf[i].toAscii(), errmsg);
0230         if (root)
0231         {
0232             if ((err_code = dispatchCommand(root, err_cmd)) < 0)
0233             {
0234                 // Silently ignore property duplication errors
0235                 if (err_code != INDI_PROPERTY_DUPLICATED)
0236                 {
0237                     //kDebug() << "Dispatch command error: " << err_cmd << endl;
0238                     fprintf(stderr, "Dispatch command error: %d for command %s\n", err_code, qPrintable(err_cmd));
0239                     prXMLEle(stderr, root, 0);
0240                 }
0241             }
0242 
0243             delXMLEle(root);
0244         }
0245         else if (*errmsg)
0246         {
0247             kDebug() << "XML Root Error: " << errmsg;
0248         }
0249     }
0250 }
0251 
0252 int DeviceManager::dispatchCommand(XMLEle *root, QString &errmsg)
0253 {
0254     if (!strcmp(tagXMLEle(root), "message"))
0255         return messageCmd(root, errmsg);
0256     else if (!strcmp(tagXMLEle(root), "delProperty"))
0257         return delPropertyCmd(root, errmsg);
0258 
0259     /* Get the device, if not available, create it */
0260     INDI_D *dp = findDev(root, 1, errmsg);
0261 
0262     if (dp == nullptr)
0263     {
0264         errmsg = "No device available and none was created";
0265         return INDI_DEVICE_NOT_FOUND;
0266     }
0267 
0268     if (!strcmp(tagXMLEle(root), "defTextVector"))
0269         return dp->buildTextGUI(root, errmsg);
0270     else if (!strcmp(tagXMLEle(root), "defNumberVector"))
0271         return dp->buildNumberGUI(root, errmsg);
0272     else if (!strcmp(tagXMLEle(root), "defSwitchVector"))
0273         return dp->buildSwitchesGUI(root, errmsg);
0274     else if (!strcmp(tagXMLEle(root), "defLightVector"))
0275         return dp->buildLightsGUI(root, errmsg);
0276     else if (!strcmp(tagXMLEle(root), "defBLOBVector"))
0277         return dp->buildBLOBGUI(root, errmsg);
0278     else if (!strcmp(tagXMLEle(root), "setTextVector") || !strcmp(tagXMLEle(root), "setNumberVector") ||
0279              !strcmp(tagXMLEle(root), "setSwitchVector") || !strcmp(tagXMLEle(root), "setLightVector") ||
0280              !strcmp(tagXMLEle(root), "setBLOBVector"))
0281         return dp->setAnyCmd(root, errmsg);
0282     // Ignore if we get NewXXX commands
0283     else if (QString(tagXMLEle(root)).startsWith("new"))
0284         return 0;
0285 
0286     return INDI_DISPATCH_ERROR;
0287 }
0288 
0289 /* delete the property in the given device, including widgets and data structs.
0290  * when last property is deleted, delete the device too.
0291  * if no property name attribute at all, delete the whole device regardless.
0292  * return 0 if ok, else -1 with reason in errmsg[].
0293  */
0294 int DeviceManager::delPropertyCmd(XMLEle *root, QString &errmsg)
0295 {
0296     XMLAtt *ap;
0297     INDI_D *dp;
0298     INDI_P *pp;
0299 
0300     /* dig out device and optional property name */
0301     dp = findDev(root, 0, errmsg);
0302     if (!dp)
0303         return INDI_DEVICE_NOT_FOUND;
0304 
0305     checkMsg(root, dp);
0306 
0307     ap = findXMLAtt(root, "name");
0308 
0309     /* Delete property if it exists, otherwise, delete the whole device */
0310     if (ap)
0311     {
0312         pp = dp->findProp(QString(valuXMLAtt(ap)));
0313 
0314         if (pp)
0315             return dp->removeProperty(pp);
0316         else
0317             return INDI_PROPERTY_INVALID;
0318     }
0319     // delete the whole device
0320     else
0321         return removeDevice(dp->name, errmsg);
0322 }
0323 
0324 int DeviceManager::removeDevice(const QString &devName, QString &errmsg)
0325 {
0326     // remove all devices if devName == nullptr
0327     if (devName == nullptr)
0328     {
0329         while (!indi_dev.isEmpty())
0330             delete indi_dev.takeFirst();
0331         return (0);
0332     }
0333 
0334     for (int i = 0; i < indi_dev.size(); i++)
0335     {
0336         if (indi_dev[i]->name == devName)
0337         {
0338             delete indi_dev.takeAt(i);
0339             return (0);
0340         }
0341     }
0342 
0343     errmsg = QString("Device %1 not found").arg(devName);
0344     return INDI_DEVICE_NOT_FOUND;
0345 }
0346 
0347 INDI_D *DeviceManager::findDev(const QString &devName, QString &errmsg)
0348 {
0349     /* search for existing */
0350     for (int i = 0; i < indi_dev.size(); i++)
0351     {
0352         if (indi_dev[i]->name == devName)
0353             return indi_dev[i];
0354     }
0355 
0356     errmsg = QString("INDI: no such device %1").arg(devName);
0357 
0358     return nullptr;
0359 }
0360 
0361 /* add new device to mainrc_w using info in dep.
0362 - * if trouble return nullptr with reason in errmsg[]
0363 - */
0364 INDI_D *DeviceManager::addDevice(XMLEle *dep, QString &errmsg)
0365 {
0366     INDI_D *dp;
0367     XMLAtt *ap;
0368     QString device_name, unique_label;
0369     IDevice *targetDevice = nullptr;
0370 
0371     /* allocate new INDI_D on indi_dev */
0372     ap = findAtt(dep, "device", errmsg);
0373     if (!ap)
0374     {
0375         errmsg = QString("Unable to find device attribute in XML tree. Cannot add device.");
0376         kDebug() << errmsg << endl;
0377         return nullptr;
0378     }
0379 
0380     device_name = QString(valuXMLAtt(ap));
0381 
0382     if (mode != M_CLIENT)
0383         foreach (IDevice *device, managed_devices)
0384         {
0385             // Each device manager has a list of managed_devices (IDevice). Each IDevice has the original constant name of the driver (driver_class)
0386             // Therefore, when a new device is discovered, we match the driver name (which never changes, it's always static from indiserver) against the driver_class
0387             // of IDevice because IDevice can have several names. It can have the tree_label which is the name it has in the local tree widget. Finally, the name that shows
0388             // up in the INDI control panel is the unique name of the driver, which is for most cases tree_label, but if that exists already then we get tree_label_1..etc
0389 
0390             if (device->name == device_name && device->state == IDevice::DEV_TERMINATE)
0391             {
0392                 device->state = IDevice::DEV_START;
0393                 unique_label = device->unique_label = parent->getUniqueDeviceLabel(device->tree_label);
0394                 targetDevice                        = device;
0395                 break;
0396             }
0397         }
0398 
0399     // For remote INDI drivers with no label attributes
0400     if (unique_label.isEmpty())
0401         unique_label = parent->getUniqueDeviceLabel(device_name);
0402 
0403     dp = new INDI_D(parent, this, device_name, unique_label, targetDevice);
0404     indi_dev.append(dp);
0405     emit newDevice(dp);
0406 
0407     enableBLOB(true, device_name);
0408 
0409     connect(dp->stdDev, SIGNAL(newTelescope()), parent->ksw->indiDriver(), SLOT(newTelescopeDiscovered()),
0410             Qt::QueuedConnection);
0411 
0412     /* ok */
0413     return dp;
0414 }
0415 
0416 INDI_D *DeviceManager::findDev(XMLEle *root, int create, QString &errmsg)
0417 {
0418     XMLAtt *ap = findAtt(root, "device", errmsg);
0419     char *dn   = nullptr;
0420 
0421     /* get device name */
0422     if (!ap)
0423     {
0424         errmsg = QString("No device attribute found in element %1").arg(tagXMLEle(root));
0425         return nullptr;
0426     }
0427     dn = valuXMLAtt(ap);
0428 
0429     /* search for existing */
0430     for (int i = 0; i < indi_dev.size(); i++)
0431     {
0432         if (indi_dev[i]->name == QString(dn))
0433             return indi_dev[i];
0434     }
0435 
0436     /* not found, create if ok */
0437     if (create)
0438         return (addDevice(root, errmsg));
0439 
0440     errmsg = QString("INDI: <%1> no such device %2").arg(tagXMLEle(root)).arg(dn);
0441     return nullptr;
0442 }
0443 
0444 /* a general message command received from the device.
0445  * return 0 if ok, else -1 with reason in errmsg[].
0446  */
0447 int DeviceManager::messageCmd(XMLEle *root, QString &errmsg)
0448 {
0449     checkMsg(root, findDev(root, 0, errmsg));
0450     return (0);
0451 }
0452 
0453 /* display message attribute.
0454  * N.B. don't put carriage control in msg, we take care of that.
0455  */
0456 void DeviceManager::checkMsg(XMLEle *root, INDI_D *dp)
0457 {
0458     XMLAtt *ap;
0459     ap = findXMLAtt(root, "message");
0460 
0461     if (ap)
0462         doMsg(root, dp);
0463 }
0464 
0465 /* display valu of message and timestamp in dp's scrolled area, if any, else general.
0466  * prefix our time stamp if not included.
0467  * N.B. don't put carriage control in msg, we take care of that.
0468  */
0469 void DeviceManager::doMsg(XMLEle *msg, INDI_D *dp)
0470 {
0471     QTextEdit *txt_w;
0472     XMLAtt *message;
0473     XMLAtt *timestamp;
0474 
0475     if (dp == nullptr)
0476     {
0477         kDebug() << "Warning: dp is null.";
0478         return;
0479     }
0480 
0481     txt_w = dp->msgST_w;
0482 
0483     /* prefix our timestamp if not with msg */
0484     timestamp = findXMLAtt(msg, "timestamp");
0485 
0486     if (timestamp)
0487         txt_w->insertPlainText(QString(valuXMLAtt(timestamp)) + QString(" "));
0488     else
0489         txt_w->insertPlainText(KStarsDateTime::currentDateTime().toString("yyyy/mm/dd - h:m:s ap "));
0490 
0491     /* finally! the msg */
0492     message = findXMLAtt(msg, "message");
0493 
0494     if (!message)
0495         return;
0496 
0497     // Prepend to the log viewer
0498     txt_w->insertPlainText(QString(valuXMLAtt(message)) + QString("\n"));
0499     QTextCursor c = txt_w->textCursor();
0500     c.movePosition(QTextCursor::Start);
0501     txt_w->setTextCursor(c);
0502 
0503     if (Options::showINDIMessages())
0504         parent->ksw->statusBar()->changeItem(QString(valuXMLAtt(message)), 0);
0505 }
0506 
0507 void DeviceManager::sendNewText(INDI_P *pp)
0508 {
0509     INDI_E *lp;
0510 
0511     QTextStream serverFP(&serverSocket);
0512 
0513     serverFP << QString("<newTextVector\n");
0514     serverFP << QString("  device='%1'\n").arg(qPrintable(pp->pg->dp->name));
0515     serverFP << QString("  name='%1'\n>").arg(qPrintable(pp->name));
0516 
0517     //for (lp = pp->el.first(); lp != nullptr; lp = pp->el.next())
0518     foreach (lp, pp->el)
0519     {
0520         serverFP << QString("  <oneText\n");
0521         serverFP << QString("    name='%1'>\n").arg(qPrintable(lp->name));
0522         serverFP << QString("      %1\n").arg(qPrintable(lp->text));
0523         serverFP << QString("  </oneText>\n");
0524     }
0525     serverFP << QString("</newTextVector>\n");
0526 }
0527 
0528 void DeviceManager::sendNewNumber(INDI_P *pp)
0529 {
0530     INDI_E *lp;
0531 
0532     QTextStream serverFP(&serverSocket);
0533 
0534     serverFP << QString("<newNumberVector\n");
0535     serverFP << QString("  device='%1'\n").arg(qPrintable(pp->pg->dp->name));
0536     serverFP << QString("  name='%1'\n>").arg(qPrintable(pp->name));
0537 
0538     foreach (lp, pp->el)
0539     {
0540         serverFP << QString("  <oneNumber\n");
0541         serverFP << QString("    name='%1'>\n").arg(qPrintable(lp->name));
0542         if (lp->text.isEmpty() || lp->spin_w)
0543             serverFP << QString("      %1\n").arg(lp->targetValue);
0544         else
0545             serverFP << QString("      %1\n").arg(lp->text);
0546         serverFP << QString("  </oneNumber>\n");
0547     }
0548     serverFP << QString("</newNumberVector>\n");
0549 }
0550 
0551 void DeviceManager::sendNewSwitch(INDI_P *pp, INDI_E *lp)
0552 {
0553     QTextStream serverFP(&serverSocket);
0554 
0555     serverFP << QString("<newSwitchVector\n");
0556     serverFP << QString("  device='%1'\n").arg(qPrintable(pp->pg->dp->name));
0557     serverFP << QString("  name='%1'>\n").arg(qPrintable(pp->name));
0558     serverFP << QString("  <oneSwitch\n");
0559     serverFP << QString("    name='%1'>\n").arg(qPrintable(lp->name));
0560     serverFP << QString("      %1\n").arg(lp->switch_state == ISS_ON ? "On" : "Off");
0561     serverFP << QString("  </oneSwitch>\n");
0562 
0563     serverFP << QString("</newSwitchVector>\n");
0564 }
0565 
0566 void DeviceManager::startBlob(const QString &devName, const QString &propName, const QString &timestamp)
0567 {
0568     QTextStream serverFP(&serverSocket);
0569 
0570     serverFP << QString("<newBLOBVector\n");
0571     serverFP << QString("  device='%1'\n").arg(qPrintable(devName));
0572     serverFP << QString("  name='%1'\n").arg(qPrintable(propName));
0573     serverFP << QString("  timestamp='%1'>\n").arg(qPrintable(timestamp));
0574 }
0575 
0576 void DeviceManager::sendOneBlob(const QString &blobName, unsigned int blobSize, const QString &blobFormat,
0577                                 unsigned char *blobBuffer)
0578 {
0579     QTextStream serverFP(&serverSocket);
0580 
0581     serverFP << QString("  <oneBLOB\n");
0582     serverFP << QString("    name='%1'\n").arg(qPrintable(blobName));
0583     serverFP << QString("    size='%1'\n").arg(blobSize);
0584     serverFP << QString("    format='%1'>\n").arg(qPrintable(blobFormat));
0585 
0586     for (unsigned i = 0; i < blobSize; i += 72)
0587         serverFP << QString().sprintf("    %.72s\n", blobBuffer + i);
0588 
0589     serverFP << QString("   </oneBLOB>\n");
0590 }
0591 
0592 void DeviceManager::finishBlob()
0593 {
0594     QTextStream serverFP(&serverSocket);
0595 
0596     serverFP << QString("</newBLOBVector>\n");
0597 }
0598 
0599 #include "devicemanager.moc"