Kstars

devicemanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2003 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "devicemanager.h"
8 
9 #include "Options.h"
10 #include "indimenu.h"
11 #include "indiproperty.h"
12 #include "indigroup.h"
13 #include "indidevice.h"
14 #include "indistd.h"
15 #include "indidriver.h"
16 #include "kstars.h"
17 #include "kstarsdatetime.h"
18 
19 #include <indicom.h>
20 
21 #include <config-kstars.h>
22 
23 //#include <stdlib.h>
24 //#include <unistd.h>
25 
26 #include <QTcpSocket>
27 #include <QTextEdit>
28 
29 #include <KProcess>
30 #include <KLocale>
31 #include <KDebug>
32 #include <KMessageBox>
33 #include <KStatusBar>
34 
35 const int INDI_MAX_TRIES = 3;
36 
37 /*******************************************************************
38 ** The device manager contain devices running from one indiserver
39 ** This allow KStars to control multiple devices distributed across
40 ** multiple servers seemingly in a way that is completely transparent
41 ** to devices and drivers alike.
42 ** The device Manager can be thought of as the 'networking' parent
43 ** of devices, while indimenu is 'GUI' parent of devices
44 *******************************************************************/
45 DeviceManager::DeviceManager(INDIMenu *INDIparent, QString inHost, uint inPort, ManagerMode inMode)
46 {
47  parent = INDIparent;
48  serverProcess = nullptr;
49  XMLParser = nullptr;
50  host = inHost;
51  port = inPort;
52  mode = inMode;
53 }
54 
55 DeviceManager::~DeviceManager()
56 {
57  serverSocket.close();
58 
59  if (serverProcess)
60  serverProcess->close();
61 
62  delete (serverProcess);
63 
64  if (XMLParser)
65  delLilXML(XMLParser);
66 
67  XMLParser = nullptr;
68 
69  while (!indi_dev.isEmpty())
70  delete indi_dev.takeFirst();
71 }
72 
73 void DeviceManager::startServer()
74 {
75  serverProcess = new KProcess;
76 
77  if (managed_devices.isEmpty())
78  {
79  kWarning() << "managed_devices was not set! Cannot start server!";
80  return;
81  }
82 #ifdef Q_OS_OSX
83  if (Options::indiServerIsInternal())
84  *serverProcess << QCoreApplication::applicationDirPath() + "/indiserver";
85  else
86 #endif
87  *serverProcess << Options::indiServer();
88  *serverProcess << "-v"
89  << "-p" << QString::number(port);
90 
91  foreach (IDevice *device, managed_devices)
92  {
93  // JM: Temporary workaround for indiserver limit of client BLOBs for CCDs.
94  if (device->type == KSTARS_CCD)
95  {
96  *serverProcess << "-m"
97  << "100";
98  break;
99  }
100  }
101 
102  foreach (IDevice *device, managed_devices)
103  *serverProcess << device->driver;
104 
105  if (mode == DeviceManager::M_LOCAL)
106  {
107  connect(serverProcess, SIGNAL(readyReadStandardError()), this, SLOT(processStandardError()));
108  serverProcess->setOutputChannelMode(KProcess::SeparateChannels);
109  serverProcess->setReadChannel(QProcess::StandardError);
110  }
111 
112  serverProcess->start();
113 
114  serverProcess->waitForStarted();
115 
116  if (mode == DeviceManager::M_LOCAL)
117  connectToServer();
118 }
119 
120 void DeviceManager::stopServer()
121 {
122  serverProcess->terminate();
123 }
124 
125 void DeviceManager::connectToServer()
126 {
127  connect(&serverSocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));
128 
129  for (int i = 0; i < INDI_MAX_TRIES; i++)
130  {
131  serverSocket.connectToHost(host, port);
132  if (serverSocket.waitForConnected(1000))
133  {
134  connect(&serverSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError()));
135  connectionSuccess();
136  return;
137  }
138 
139  usleep(100000);
140  }
141 
142  connectionError();
143 }
144 
145 void DeviceManager::enableBLOB(bool enable, QString device, QString property)
146 {
147  QTextStream serverFP(&serverSocket);
148  QString openTag;
149 
150  if (device.isEmpty())
151  return;
152 
153  if (property.isEmpty() == false)
154  openTag = QString("<enableBLOB device='%1' name='%2'>").arg(device).arg(property);
155  else
156  openTag = QString("<enableBLOB device='%1'>").arg(device);
157 
158  if (enable)
159  {
160  serverFP << QString("%1Also</enableBLOB>\n").arg(openTag);
161  kDebug() << QString("%1Also</enableBLOB>\n").arg(openTag);
162  }
163  else
164  {
165  serverFP << QString("%1Never</enableBLOB>\n").arg(openTag);
166  kDebug() << QString("%1Never</enableBLOB>\n").arg(openTag);
167  }
168 }
169 
170 void DeviceManager::connectionSuccess()
171 {
172  QTextStream serverFP(&serverSocket);
173 
174  if (XMLParser)
175  delLilXML(XMLParser);
176  XMLParser = newLilXML();
177 
178  serverFP << QString("<getProperties version='%1'/>\n").arg(INDIVERSION);
179 }
180 
181 void DeviceManager::connectionError()
182 {
183  QString errMsg = QString("Connection to INDI host at %1 on port %2 encountered an error: %3.")
184  .arg(host)
185  .arg(port)
186  .arg(serverSocket.errorString());
187  KMessageBox::error(nullptr, errMsg);
188 
189  emit deviceManagerError(this);
190 }
191 
192 void DeviceManager::appendManagedDevices(QList<IDevice *> &processed_devices)
193 {
194  managed_devices = processed_devices;
195 
196  foreach (IDevice *device, managed_devices)
197  {
198  device->unique_label = parent->getUniqueDeviceLabel(device->tree_label);
199  //device->mode = mode;
200  device->deviceManager = this;
201  }
202 }
203 
204 void DeviceManager::processStandardError()
205 {
206  if (serverProcess == nullptr)
207  return;
208 
209  serverBuffer.append(serverProcess->readAllStandardError());
210  emit newServerInput();
211 }
212 
213 void DeviceManager::dataReceived()
214 {
215  char errmsg[ERRMSG_SIZE];
216  int nr = 0, err_code = 0;
217  QTextStream serverFP(&serverSocket);
218  QString ibuf, err_cmd;
219 
220  ibuf = serverFP.readAll();
221  nr = ibuf.length();
222 
223  /* process each char */
224  for (int i = 0; i < nr; i++)
225  {
226  if (!XMLParser)
227  return;
228 
229  XMLEle *root = readXMLEle(XMLParser, ibuf[i].toAscii(), errmsg);
230  if (root)
231  {
232  if ((err_code = dispatchCommand(root, err_cmd)) < 0)
233  {
234  // Silently ignore property duplication errors
235  if (err_code != INDI_PROPERTY_DUPLICATED)
236  {
237  //kDebug() << "Dispatch command error: " << err_cmd << endl;
238  fprintf(stderr, "Dispatch command error: %d for command %s\n", err_code, qPrintable(err_cmd));
239  prXMLEle(stderr, root, 0);
240  }
241  }
242 
243  delXMLEle(root);
244  }
245  else if (*errmsg)
246  {
247  kDebug() << "XML Root Error: " << errmsg;
248  }
249  }
250 }
251 
252 int DeviceManager::dispatchCommand(XMLEle *root, QString &errmsg)
253 {
254  if (!strcmp(tagXMLEle(root), "message"))
255  return messageCmd(root, errmsg);
256  else if (!strcmp(tagXMLEle(root), "delProperty"))
257  return delPropertyCmd(root, errmsg);
258 
259  /* Get the device, if not available, create it */
260  INDI_D *dp = findDev(root, 1, errmsg);
261 
262  if (dp == nullptr)
263  {
264  errmsg = "No device available and none was created";
265  return INDI_DEVICE_NOT_FOUND;
266  }
267 
268  if (!strcmp(tagXMLEle(root), "defTextVector"))
269  return dp->buildTextGUI(root, errmsg);
270  else if (!strcmp(tagXMLEle(root), "defNumberVector"))
271  return dp->buildNumberGUI(root, errmsg);
272  else if (!strcmp(tagXMLEle(root), "defSwitchVector"))
273  return dp->buildSwitchesGUI(root, errmsg);
274  else if (!strcmp(tagXMLEle(root), "defLightVector"))
275  return dp->buildLightsGUI(root, errmsg);
276  else if (!strcmp(tagXMLEle(root), "defBLOBVector"))
277  return dp->buildBLOBGUI(root, errmsg);
278  else if (!strcmp(tagXMLEle(root), "setTextVector") || !strcmp(tagXMLEle(root), "setNumberVector") ||
279  !strcmp(tagXMLEle(root), "setSwitchVector") || !strcmp(tagXMLEle(root), "setLightVector") ||
280  !strcmp(tagXMLEle(root), "setBLOBVector"))
281  return dp->setAnyCmd(root, errmsg);
282  // Ignore if we get NewXXX commands
283  else if (QString(tagXMLEle(root)).startsWith("new"))
284  return 0;
285 
286  return INDI_DISPATCH_ERROR;
287 }
288 
289 /* delete the property in the given device, including widgets and data structs.
290  * when last property is deleted, delete the device too.
291  * if no property name attribute at all, delete the whole device regardless.
292  * return 0 if ok, else -1 with reason in errmsg[].
293  */
294 int DeviceManager::delPropertyCmd(XMLEle *root, QString &errmsg)
295 {
296  XMLAtt *ap;
297  INDI_D *dp;
298  INDI_P *pp;
299 
300  /* dig out device and optional property name */
301  dp = findDev(root, 0, errmsg);
302  if (!dp)
303  return INDI_DEVICE_NOT_FOUND;
304 
305  checkMsg(root, dp);
306 
307  ap = findXMLAtt(root, "name");
308 
309  /* Delete property if it exists, otherwise, delete the whole device */
310  if (ap)
311  {
312  pp = dp->findProp(QString(valuXMLAtt(ap)));
313 
314  if (pp)
315  return dp->removeProperty(pp);
316  else
317  return INDI_PROPERTY_INVALID;
318  }
319  // delete the whole device
320  else
321  return removeDevice(dp->name, errmsg);
322 }
323 
324 int DeviceManager::removeDevice(const QString &devName, QString &errmsg)
325 {
326  // remove all devices if devName == nullptr
327  if (devName == nullptr)
328  {
329  while (!indi_dev.isEmpty())
330  delete indi_dev.takeFirst();
331  return (0);
332  }
333 
334  for (int i = 0; i < indi_dev.size(); i++)
335  {
336  if (indi_dev[i]->name == devName)
337  {
338  delete indi_dev.takeAt(i);
339  return (0);
340  }
341  }
342 
343  errmsg = QString("Device %1 not found").arg(devName);
344  return INDI_DEVICE_NOT_FOUND;
345 }
346 
347 INDI_D *DeviceManager::findDev(const QString &devName, QString &errmsg)
348 {
349  /* search for existing */
350  for (int i = 0; i < indi_dev.size(); i++)
351  {
352  if (indi_dev[i]->name == devName)
353  return indi_dev[i];
354  }
355 
356  errmsg = QString("INDI: no such device %1").arg(devName);
357 
358  return nullptr;
359 }
360 
361 /* add new device to mainrc_w using info in dep.
362 - * if trouble return nullptr with reason in errmsg[]
363 - */
364 INDI_D *DeviceManager::addDevice(XMLEle *dep, QString &errmsg)
365 {
366  INDI_D *dp;
367  XMLAtt *ap;
368  QString device_name, unique_label;
369  IDevice *targetDevice = nullptr;
370 
371  /* allocate new INDI_D on indi_dev */
372  ap = findAtt(dep, "device", errmsg);
373  if (!ap)
374  {
375  errmsg = QString("Unable to find device attribute in XML tree. Cannot add device.");
376  kDebug() << errmsg << endl;
377  return nullptr;
378  }
379 
380  device_name = QString(valuXMLAtt(ap));
381 
382  if (mode != M_CLIENT)
383  foreach (IDevice *device, managed_devices)
384  {
385  // Each device manager has a list of managed_devices (IDevice). Each IDevice has the original constant name of the driver (driver_class)
386  // 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
387  // 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
388  // 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
389 
390  if (device->name == device_name && device->state == IDevice::DEV_TERMINATE)
391  {
392  device->state = IDevice::DEV_START;
393  unique_label = device->unique_label = parent->getUniqueDeviceLabel(device->tree_label);
394  targetDevice = device;
395  break;
396  }
397  }
398 
399  // For remote INDI drivers with no label attributes
400  if (unique_label.isEmpty())
401  unique_label = parent->getUniqueDeviceLabel(device_name);
402 
403  dp = new INDI_D(parent, this, device_name, unique_label, targetDevice);
404  indi_dev.append(dp);
405  emit newDevice(dp);
406 
407  enableBLOB(true, device_name);
408 
409  connect(dp->stdDev, SIGNAL(newTelescope()), parent->ksw->indiDriver(), SLOT(newTelescopeDiscovered()),
411 
412  /* ok */
413  return dp;
414 }
415 
416 INDI_D *DeviceManager::findDev(XMLEle *root, int create, QString &errmsg)
417 {
418  XMLAtt *ap = findAtt(root, "device", errmsg);
419  char *dn = nullptr;
420 
421  /* get device name */
422  if (!ap)
423  {
424  errmsg = QString("No device attribute found in element %1").arg(tagXMLEle(root));
425  return nullptr;
426  }
427  dn = valuXMLAtt(ap);
428 
429  /* search for existing */
430  for (int i = 0; i < indi_dev.size(); i++)
431  {
432  if (indi_dev[i]->name == QString(dn))
433  return indi_dev[i];
434  }
435 
436  /* not found, create if ok */
437  if (create)
438  return (addDevice(root, errmsg));
439 
440  errmsg = QString("INDI: <%1> no such device %2").arg(tagXMLEle(root)).arg(dn);
441  return nullptr;
442 }
443 
444 /* a general message command received from the device.
445  * return 0 if ok, else -1 with reason in errmsg[].
446  */
447 int DeviceManager::messageCmd(XMLEle *root, QString &errmsg)
448 {
449  checkMsg(root, findDev(root, 0, errmsg));
450  return (0);
451 }
452 
453 /* display message attribute.
454  * N.B. don't put carriage control in msg, we take care of that.
455  */
456 void DeviceManager::checkMsg(XMLEle *root, INDI_D *dp)
457 {
458  XMLAtt *ap;
459  ap = findXMLAtt(root, "message");
460 
461  if (ap)
462  doMsg(root, dp);
463 }
464 
465 /* display valu of message and timestamp in dp's scrolled area, if any, else general.
466  * prefix our time stamp if not included.
467  * N.B. don't put carriage control in msg, we take care of that.
468  */
469 void DeviceManager::doMsg(XMLEle *msg, INDI_D *dp)
470 {
471  QTextEdit *txt_w;
472  XMLAtt *message;
473  XMLAtt *timestamp;
474 
475  if (dp == nullptr)
476  {
477  kDebug() << "Warning: dp is null.";
478  return;
479  }
480 
481  txt_w = dp->msgST_w;
482 
483  /* prefix our timestamp if not with msg */
484  timestamp = findXMLAtt(msg, "timestamp");
485 
486  if (timestamp)
487  txt_w->insertPlainText(QString(valuXMLAtt(timestamp)) + QString(" "));
488  else
489  txt_w->insertPlainText(KStarsDateTime::currentDateTime().toString("yyyy/mm/dd - h:m:s ap "));
490 
491  /* finally! the msg */
492  message = findXMLAtt(msg, "message");
493 
494  if (!message)
495  return;
496 
497  // Prepend to the log viewer
498  txt_w->insertPlainText(QString(valuXMLAtt(message)) + QString("\n"));
499  QTextCursor c = txt_w->textCursor();
501  txt_w->setTextCursor(c);
502 
503  if (Options::showINDIMessages())
504  parent->ksw->statusBar()->changeItem(QString(valuXMLAtt(message)), 0);
505 }
506 
507 void DeviceManager::sendNewText(INDI_P *pp)
508 {
509  INDI_E *lp;
510 
511  QTextStream serverFP(&serverSocket);
512 
513  serverFP << QString("<newTextVector\n");
514  serverFP << QString(" device='%1'\n").arg(qPrintable(pp->pg->dp->name));
515  serverFP << QString(" name='%1'\n>").arg(qPrintable(pp->name));
516 
517  //for (lp = pp->el.first(); lp != nullptr; lp = pp->el.next())
518  foreach (lp, pp->el)
519  {
520  serverFP << QString(" <oneText\n");
521  serverFP << QString(" name='%1'>\n").arg(qPrintable(lp->name));
522  serverFP << QString(" %1\n").arg(qPrintable(lp->text));
523  serverFP << QString(" </oneText>\n");
524  }
525  serverFP << QString("</newTextVector>\n");
526 }
527 
528 void DeviceManager::sendNewNumber(INDI_P *pp)
529 {
530  INDI_E *lp;
531 
532  QTextStream serverFP(&serverSocket);
533 
534  serverFP << QString("<newNumberVector\n");
535  serverFP << QString(" device='%1'\n").arg(qPrintable(pp->pg->dp->name));
536  serverFP << QString(" name='%1'\n>").arg(qPrintable(pp->name));
537 
538  foreach (lp, pp->el)
539  {
540  serverFP << QString(" <oneNumber\n");
541  serverFP << QString(" name='%1'>\n").arg(qPrintable(lp->name));
542  if (lp->text.isEmpty() || lp->spin_w)
543  serverFP << QString(" %1\n").arg(lp->targetValue);
544  else
545  serverFP << QString(" %1\n").arg(lp->text);
546  serverFP << QString(" </oneNumber>\n");
547  }
548  serverFP << QString("</newNumberVector>\n");
549 }
550 
551 void DeviceManager::sendNewSwitch(INDI_P *pp, INDI_E *lp)
552 {
553  QTextStream serverFP(&serverSocket);
554 
555  serverFP << QString("<newSwitchVector\n");
556  serverFP << QString(" device='%1'\n").arg(qPrintable(pp->pg->dp->name));
557  serverFP << QString(" name='%1'>\n").arg(qPrintable(pp->name));
558  serverFP << QString(" <oneSwitch\n");
559  serverFP << QString(" name='%1'>\n").arg(qPrintable(lp->name));
560  serverFP << QString(" %1\n").arg(lp->switch_state == ISS_ON ? "On" : "Off");
561  serverFP << QString(" </oneSwitch>\n");
562 
563  serverFP << QString("</newSwitchVector>\n");
564 }
565 
566 void DeviceManager::startBlob(const QString &devName, const QString &propName, const QString &timestamp)
567 {
568  QTextStream serverFP(&serverSocket);
569 
570  serverFP << QString("<newBLOBVector\n");
571  serverFP << QString(" device='%1'\n").arg(qPrintable(devName));
572  serverFP << QString(" name='%1'\n").arg(qPrintable(propName));
573  serverFP << QString(" timestamp='%1'>\n").arg(qPrintable(timestamp));
574 }
575 
576 void DeviceManager::sendOneBlob(const QString &blobName, unsigned int blobSize, const QString &blobFormat,
577  unsigned char *blobBuffer)
578 {
579  QTextStream serverFP(&serverSocket);
580 
581  serverFP << QString(" <oneBLOB\n");
582  serverFP << QString(" name='%1'\n").arg(qPrintable(blobName));
583  serverFP << QString(" size='%1'\n").arg(blobSize);
584  serverFP << QString(" format='%1'>\n").arg(qPrintable(blobFormat));
585 
586  for (unsigned i = 0; i < blobSize; i += 72)
587  serverFP << QString().sprintf(" %.72s\n", blobBuffer + i);
588 
589  serverFP << QString(" </oneBLOB>\n");
590 }
591 
592 void DeviceManager::finishBlob()
593 {
594  QTextStream serverFP(&serverSocket);
595 
596  serverFP << QString("</newBLOBVector>\n");
597 }
598 
599 #include "devicemanager.moc"
void append(const T &value)
static KStarsDateTime currentDateTime()
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
QTextCursor textCursor() const const
void insertPlainText(const QString &text)
QString applicationDirPath()
void setTextCursor(const QTextCursor &cursor)
QString & sprintf(const char *cformat,...)
char * toString(const T &value)
bool isEmpty() const const
int length() const const
QueuedConnection
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ScriptableExtension * host() const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
QString message
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.