Kstars

devicemanager.cpp
1/*
2 SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
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
35const 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*******************************************************************/
45DeviceManager::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
55DeviceManager::~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
73void 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_MACOS
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()));
110 }
111
112 serverProcess->start();
113
114 serverProcess->waitForStarted();
115
116 if (mode == DeviceManager::M_LOCAL)
117 connectToServer();
118}
119
120void DeviceManager::stopServer()
121{
122 serverProcess->terminate();
123}
124
125void 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
145void 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
170void 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
181void 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
192void 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
204void DeviceManager::processStandardError()
205{
206 if (serverProcess == nullptr)
207 return;
208
209 serverBuffer.append(serverProcess->readAllStandardError());
210 emit newServerInput();
211}
212
213void 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
252int 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 */
294int 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
324int 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
347INDI_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- */
364INDI_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
416INDI_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 */
447int 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 */
456void 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 */
469void 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
507void 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
528void 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
551void 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
566void 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
576void 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
592void DeviceManager::finishBlob()
593{
594 QTextStream serverFP(&serverSocket);
595
596 serverFP << QString("</newBLOBVector>\n");
597}
598
599#include "devicemanager.moc"
INDI_D represents an INDI GUI Device.
Definition indidevice.h:35
INDI_E represents an INDI GUI element (Number, Text, Switch, Light, or BLOB) within an INDI property.
Definition indielement.h:42
INDI_P represents a single INDI property (Switch, Text, Number, Light, or BLOB).
void start()
void setOutputChannelMode(OutputChannelMode mode)
static KStarsDateTime currentDateTime()
char * toString(const EngineQuery &query)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
virtual void close() override
void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode)
virtual bool waitForConnected(int msecs)
QString applicationDirPath()
QString errorString() const const
void append(QList< T > &&value)
bool isEmpty() const const
qsizetype size() const const
T takeAt(qsizetype i)
value_type takeFirst()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
virtual void close() override
QByteArray readAllStandardError()
void setReadChannel(ProcessChannel channel)
void terminate()
bool waitForStarted(int msecs)
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool isEmpty() const const
qsizetype length() const const
QString number(double n, char format, int precision)
QueuedConnection
QTextStream & endl(QTextStream &stream)
bool movePosition(MoveOperation operation, MoveMode mode, int n)
void insertPlainText(const QString &text)
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:04:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.