Instrument Neutral Distributed Interface INDI 2.2.5
abstractbaseclient.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2016 Jasem Mutlaq. All rights reserved.
3 Copyright(c) 2022 Pawel Soja <[email protected]>
4
5 INDI Qt Client
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License version 2 as published by the Free Software Foundation.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*******************************************************************************/
21
22#include "abstractbaseclient.h"
24
25#include "basedevice.h"
26#include "basedevice_p.h"
27
28#include "indiuserio.h"
29#include "locale_compat.h"
31
32#if defined(_MSC_VER)
33#define snprintf _snprintf
34#pragma warning(push)
36#pragma warning(disable : 4996)
37#endif
38
39namespace INDI
40{
41
43
44// AbstractBaseClientPrivate
45
47 : parent(parent)
48{
49 io.write = [](void *user, const void * ptr, size_t count) -> ssize_t
50 {
51 auto self = static_cast<AbstractBaseClientPrivate *>(user);
52 return self->sendData(ptr, count);
53 };
54
55 io.vprintf = [](void *user, const char * format, va_list ap) -> int
56 {
57 auto self = static_cast<AbstractBaseClientPrivate *>(user);
58 char message[MAXRBUF];
59 vsnprintf(message, MAXRBUF, format, ap);
60 return int(self->sendData(message, strlen(message)));
61 };
62}
63
69
71{
72 // Ignore echoed newXXX
73 if (root.tagName().find("new") == 0)
74 {
75 return 0;
76 }
77
78 if (root.tagName() == "pingRequest")
79 {
80 parent->sendPingReply(root.getAttribute("uid"));
81 return 0;
82 }
83
84 if (root.tagName() == "pingReply")
85 {
87 return 0;
88 }
89
90 if (root.tagName() == "message")
91 {
92 return messageCmd(root, errmsg);
93 }
94
95 if (root.tagName() == "delProperty")
96 {
97 return delPropertyCmd(root, errmsg);
98 }
99
100 // Just ignore any getProperties we might get
101 if (root.tagName() == "getProperties")
102 {
104 }
105
106 // If device is set to BLOB_ONLY, we ignore everything else
107 // not related to blobs
108 if (
109 parent->getBLOBMode(root.getAttribute("device")) == B_ONLY &&
110 root.tagName() != "defBLOBVector" &&
111 root.tagName() != "setBLOBVector"
112 )
113 {
114 return 0;
115 }
116
117 return watchDevice.processXml(root, errmsg, [this] // create new device if nessesery
118 {
120 device.setMediator(parent);
121 return device;
122 });
123}
124
125int AbstractBaseClientPrivate::deleteDevice(const char *devName, char *errmsg)
126{
127 if (auto device = watchDevice.getDeviceByName(devName))
128 {
130 device.detach();
131 return 0;
132 }
133 snprintf(errmsg, MAXRBUF, "Device %s not found", devName);
135}
136
137/* delete the property in the given device, including widgets and data structs.
138 * when last property is deleted, delete the device too.
139 * if no property name attribute at all, delete the whole device regardless.
140 * return 0 if ok, else -1 with reason in errmsg[].
141 */
143{
144 /* dig out device and optional property name */
146
147 if (!dp.isValid())
149
150 dp.checkMessage(root.handle());
151
152 const auto propertyName = root.getAttribute("name");
153
154 // Delete the whole device if propertyName does not exists
155 if (!propertyName.isValid())
156 {
157 return deleteDevice(dp.getDeviceName(), errmsg);
158 }
159
160 // Delete property if it exists
161 if (auto property = dp.getProperty(propertyName))
162 {
163 if (sConnected)
164 {
165 dp.d_ptr->mediateRemoveProperty(property);
166 }
167 return dp.removeProperty(propertyName, errmsg);
168 }
169
170 // Silently ignore B_ONLY clients.
171 if (blobModes.empty() || blobModes.front().blobMode == B_ONLY)
172 return 0;
173 snprintf(errmsg, MAXRBUF, "Cannot delete property %s as it is not defined yet. Check driver.", propertyName.toCString());
174 return -1;
175}
176
177/* a general message command received from the device.
178 * return 0 if ok, else -1 with reason in errmsg[].
179 */
181{
182 INDI_UNUSED(errmsg);
183
185
186 if (dp.isValid())
187 {
188 dp.checkMessage(root.handle());
189 return 0;
190 }
191
192 char msgBuffer[MAXRBUF];
193
194 auto timestamp = root.getAttribute("timestamp");
195 auto message = root.getAttribute("message");
196
197 if (!message.isValid())
198 {
199 strncpy(errmsg, "No message content found.", MAXRBUF);
200 return -1;
201 }
202
203 if (timestamp.isValid())
204 {
205 snprintf(msgBuffer, MAXRBUF, "%s: %s", timestamp.toCString(), message.toCString());
206 }
207 else
208 {
209 char ts[32];
210 struct tm *tp;
211 time_t t;
212 time(&t);
213 tp = gmtime(&t);
214 strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", tp);
215 snprintf(msgBuffer, MAXRBUF, "%s: %s", ts, message.toCString());
216 }
217
218 parent->newUniversalMessage(msgBuffer);
219
220 return 0;
221}
222
224{
225 if (watchDevice.isEmpty())
226 {
227 IUUserIOGetProperties(&io, this, nullptr, nullptr);
228 if (verbose)
229 IUUserIOGetProperties(userio_file(), stderr, nullptr, nullptr);
230 }
231 else
232 {
233 for (const auto &deviceInfo : watchDevice /* first: device name, second: device info */)
234 {
235 // If there are no specific properties to watch, we watch the complete device
236 if (deviceInfo.second.properties.size() == 0)
237 {
238 IUUserIOGetProperties(&io, this, deviceInfo.first.c_str(), nullptr);
239 if (verbose)
240 IUUserIOGetProperties(userio_file(), stderr, deviceInfo.first.c_str(), nullptr);
241 }
242 else
243 {
244 for (const auto &oneProperty : deviceInfo.second.properties)
245 {
246 IUUserIOGetProperties(&io, this, deviceInfo.first.c_str(), oneProperty.c_str());
247 if (verbose)
248 IUUserIOGetProperties(userio_file(), stderr, deviceInfo.first.c_str(), oneProperty.c_str());
249 }
250 }
251 }
252 }
253}
254
255
256void AbstractBaseClientPrivate::setDriverConnection(bool status, const char *deviceName)
257{
258 BaseDevice drv = parent->getDevice(deviceName);
259
260 if (!drv.isValid())
261 {
262 IDLog("BaseClientQt: Error. Unable to find driver %s\n", deviceName);
263 return;
264 }
265
266 auto drv_connection = drv.getSwitch(SP::CONNECTION);
267
268 if (!drv_connection.isValid())
269 return;
270
271 // If we need to connect
272 if (status)
273 {
274 // If there is no need to do anything, i.e. already connected.
275 if (drv_connection[0].getState() == ISS_ON)
276 return;
277
278 drv_connection.reset();
279 drv_connection.setState(IPS_BUSY);
280 drv_connection[0].setState(ISS_ON);
281 drv_connection[1].setState(ISS_OFF);
282
283 parent->sendNewSwitch(drv_connection);
284 }
285 else
286 {
287 // If there is no need to do anything, i.e. already disconnected.
288 if (drv_connection[1].getState() == ISS_ON)
289 return;
290
291 drv_connection.reset();
292 drv_connection.setState(IPS_BUSY);
293 drv_connection[0].setState(ISS_OFF);
294 drv_connection[1].setState(ISS_ON);
295
296 parent->sendNewSwitch(drv_connection);
297 }
298}
299
300BLOBMode *AbstractBaseClientPrivate::findBLOBMode(const std::string &device, const std::string &property)
301{
302 for (auto &blob : blobModes)
303 {
304 if (blob.device == device && (property.empty() || blob.property == property))
305 return &blob;
306 }
307
308 return nullptr;
309}
310
311// AbstractBaseClient
312
313AbstractBaseClient::AbstractBaseClient(std::unique_ptr<AbstractBaseClientPrivate> &&d)
314 : d_ptr_indi(std::move(d))
315{ }
316
319
320void AbstractBaseClient::setServer(const char *hostname, unsigned int port)
321{
322 D_PTR(AbstractBaseClient);
323 d->cServer = hostname;
324 d->cPort = port;
325}
326
328{
329 D_PTR(const AbstractBaseClient);
330 return d->cServer.c_str();
331}
332
334{
335 D_PTR(const AbstractBaseClient);
336 return d->cPort;
337}
338
340{
341 D_PTR(const AbstractBaseClient);
342 return d->sConnected;
343}
344
345void AbstractBaseClient::setConnectionTimeout(uint32_t seconds, uint32_t microseconds)
346{
347 D_PTR(AbstractBaseClient);
348 d->timeout_sec = seconds;
349 d->timeout_us = microseconds;
350}
351
353{
354 D_PTR(AbstractBaseClient);
355 d->verbose = enable;
356}
357
359{
360 D_PTR(const AbstractBaseClient);
361 return d->verbose;
362}
363
364void AbstractBaseClient::watchDevice(const char *deviceName)
365{
366 D_PTR(AbstractBaseClient);
367 d->watchDevice.watchDevice(deviceName);
368}
369
370void AbstractBaseClient::watchDevice(const char *deviceName, const std::function<void (BaseDevice)> &callback)
371{
372 D_PTR(AbstractBaseClient);
373 d->watchDevice.watchDevice(deviceName, callback);
374}
375
376void AbstractBaseClient::watchProperty(const char *deviceName, const char *propertyName)
377{
378 D_PTR(AbstractBaseClient);
379 d->watchDevice.watchProperty(deviceName, propertyName);
380}
381
382void AbstractBaseClient::connectDevice(const char *deviceName)
383{
384 D_PTR(AbstractBaseClient);
385 d->setDriverConnection(true, deviceName);
386}
387
388void AbstractBaseClient::disconnectDevice(const char *deviceName)
389{
390 D_PTR(AbstractBaseClient);
391 d->setDriverConnection(false, deviceName);
392}
393
395{
396 D_PTR(AbstractBaseClient);
397 return d->watchDevice.getDeviceByName(deviceName);
398}
399
400std::vector<BaseDevice> AbstractBaseClient::getDevices() const
401{
402 D_PTR(const AbstractBaseClient);
403 return d->watchDevice.getDevices();
404}
405
406bool AbstractBaseClient::getDevices(std::vector<BaseDevice> &deviceList, uint16_t driverInterface )
407{
408 D_PTR(AbstractBaseClient);
409 for (auto &it : d->watchDevice)
410 {
411 if (it.second.device.getDriverInterface() & driverInterface)
412 deviceList.push_back(it.second.device);
413 }
414
415 return (deviceList.size() > 0);
416}
417
418
419void AbstractBaseClient::setBLOBMode(BLOBHandling blobH, const char *dev, const char *prop)
420{
421 D_PTR(AbstractBaseClient);
422 if (!dev[0])
423 return;
424
425 auto *bMode = d->findBLOBMode(std::string(dev), prop ? std::string(prop) : std::string());
426
427 if (bMode == nullptr)
428 {
429 BLOBMode newMode;
430 newMode.device = std::string(dev);
431 newMode.property = (prop ? std::string(prop) : std::string());
432 newMode.blobMode = blobH;
433 d->blobModes.push_back(std::move(newMode));
434 }
435 else
436 {
437 // If nothing changed, nothing to to do
438 if (bMode->blobMode == blobH)
439 return;
440
441 bMode->blobMode = blobH;
442 }
443
444 IUUserIOEnableBLOB(&d->io, d, dev, prop, blobH);
445}
446
447BLOBHandling AbstractBaseClient::getBLOBMode(const char *dev, const char *prop)
448{
449 D_PTR(AbstractBaseClient);
450 BLOBHandling bHandle = B_ALSO;
451
452 auto *bMode = d->findBLOBMode(dev, (prop ? std::string(prop) : std::string()));
453
454 if (bMode)
455 bHandle = bMode->blobMode;
456
457 return bHandle;
458}
459
461{
462 D_PTR(AbstractBaseClient);
463 pp.setState(IPS_BUSY);
464 // #PS: TODO more generic
465 switch (pp.getType())
466 {
467 case INDI_NUMBER:
468 IUUserIONewNumber(&d->io, d, pp.getNumber()->cast());
469 break;
470 case INDI_SWITCH:
471 IUUserIONewSwitch(&d->io, d, pp.getSwitch()->cast());
472 break;
473 case INDI_TEXT:
474 IUUserIONewText(&d->io, d, pp.getText()->cast());
475 break;
476 case INDI_LIGHT:
477 IDLog("Light type is not supported to send\n");
478 break;
479 case INDI_BLOB:
480 IUUserIONewBLOB(&d->io, d, pp.getBLOB()->cast());
481 break;
482 case INDI_UNKNOWN:
483 IDLog("Unknown type of property to send\n");
484 break;
485 }
486}
487
489{
490 D_PTR(AbstractBaseClient);
491 AutoCNumeric locale;
492
493 pp.setState(IPS_BUSY);
494 IUUserIONewText(&d->io, d, pp.getText()->cast());
495}
496
497void AbstractBaseClient::sendNewText(const char *deviceName, const char *propertyName, const char *elementName,
498 const char *text)
499{
500 auto tvp = getDevice(deviceName).getText(propertyName);
501
502 if (!tvp.isValid())
503 return;
504
505 auto tp = tvp.findWidgetByName(elementName);
506
507 if (!tp)
508 return;
509
510 tp->setText(text);
511
512 sendNewText(tvp);
513}
514
516{
517 D_PTR(AbstractBaseClient);
518 AutoCNumeric locale;
519 pp.setState(IPS_BUSY);
520 IUUserIONewNumber(&d->io, d, pp.getNumber()->cast());
521}
522
523void AbstractBaseClient::sendNewNumber(const char *deviceName, const char *propertyName, const char *elementName,
524 double value)
525{
526 auto nvp = getDevice(deviceName).getNumber(propertyName);
527
528 if (!nvp.isValid())
529 return;
530
531 auto np = nvp.findWidgetByName(elementName);
532
533 if (!np)
534 return;
535
536 np->setValue(value);
537
538 sendNewNumber(nvp);
539}
540
547
548void AbstractBaseClient::sendNewSwitch(const char *deviceName, const char *propertyName, const char *elementName)
549{
550 auto svp = getDevice(deviceName).getSwitch(propertyName);
551
552 if (!svp.isValid())
553 return;
554
555 auto sp = svp.findWidgetByName(elementName);
556
557 if (!sp)
558 return;
559
560 sp->setState(ISS_ON);
561
562 sendNewSwitch(svp);
563}
564
565void AbstractBaseClient::startBlob(const char *devName, const char *propName, const char *timestamp)
566{
567 D_PTR(AbstractBaseClient);
568 IUUserIONewBLOBStart(&d->io, d, devName, propName, timestamp);
569}
570
572{
573 D_PTR(AbstractBaseClient);
575 &d->io, d,
576 blob->getName(), blob->getSize(), blob->getBlobLen(), blob->getBlob(), blob->getFormat()
577 );
578}
579
584
585void AbstractBaseClient::sendOneBlob(const char *blobName, unsigned int blobSize, const char *blobFormat,
586 void *blobBuffer)
587{
588 D_PTR(AbstractBaseClient);
590 &d->io, d,
591 blobName, blobSize, blobSize, blobBuffer, blobFormat
592 );
593}
594
600
602{
603 D_PTR(AbstractBaseClient);
604 IUUserIOPingRequest(&d->io, d, uuid);
605}
606
608{
609 D_PTR(AbstractBaseClient);
610 IUUserIOPingReply(&d->io, d, uuid);
611}
612
614{
615 IDLog("Ping reply %s\n", uid.c_str());
616}
617
619{
620 INDI_UNUSED(message);
621 // Do not log message to stderr, let child decide what to do with it.
622 //IDLog("%s\n", message.c_str());
623}
624
625}
626
627#if defined(_MSC_VER)
628#undef snprintf
629#pragma warning(pop)
630#endif
hid_device * device
int dispatchCommand(const INDI::LilXmlElement &root, char *errmsg)
Dispatch command received from INDI server to respective devices handled by the client.
int messageCmd(const INDI::LilXmlElement &root, char *errmsg)
AbstractBaseClientPrivate(AbstractBaseClient *parent)
void setDriverConnection(bool status, const char *deviceName)
Connect/Disconnect to INDI driver.
int deleteDevice(const char *devName, char *errmsg)
Remove device.
int delPropertyCmd(const INDI::LilXmlElement &root, char *errmsg)
Delete property command.
virtual ssize_t sendData(const void *data, size_t size)=0
BLOBMode * findBLOBMode(const std::string &device, const std::string &property)
int getPort() const
Get the port number.
void setServer(const char *hostname, unsigned int port)
Set the server host name and port.
bool isVerbose() const
isVerbose Is client in verbose mode?
INDI::BaseDevice getDevice(const char *deviceName)
virtual void newUniversalMessage(std::string message)
newUniversalMessage Universal messages are sent from INDI server without a specific device....
void sendNewSwitch(INDI::Property pp)
Send new Switch command to server.
void setBLOBMode(BLOBHandling blobH, const char *dev, const char *prop=nullptr)
Set Binary Large Object policy mode.
void watchProperty(const char *deviceName, const char *propertyName)
watchProperties Add a property to the watch list. When communicating with INDI server.
void sendNewProperty(INDI::Property pp)
Send new Property command to server.
void setConnectionTimeout(uint32_t seconds, uint32_t microseconds)
setConnectionTimeout Set connection timeout. By default it is 3 seconds.
void finishBlob()
Send closing tag for BLOB command to server.
void disconnectDevice(const char *deviceName)
Disconnect INDI driver.
void setVerbose(bool enable)
setVerbose Set verbose mode
void sendPingReply(const char *uid)
Send a ping reply for the given uuid.
void sendOneBlob(IBLOB *bp)
Send ONE blob content to server. The BLOB data in raw binary format and will be converted to base64 a...
virtual void newPingReply(std::string uid)
pingReply are sent by the server on response to pingReply (see above).
bool isServerConnected() const
Get status of the connection.
void sendPingRequest(const char *uid)
Send one ping request, the server will answer back with the same uuid.
void connectDevice(const char *deviceName)
Disconnect INDI driver.
void watchDevice(const char *deviceName)
Add a device to the watch list.
const char * getHost() const
Get the server host name.
std::vector< INDI::BaseDevice > getDevices() const
void sendNewNumber(INDI::Property pp)
Send new Number command to server.
BLOBHandling getBLOBMode(const char *dev, const char *prop=nullptr)
getBLOBMode Get Binary Large Object policy mode IF set previously by setBLOBMode
void sendNewText(INDI::Property pp)
Send new Text command to server.
void startBlob(const char *devName, const char *propName, const char *timestamp)
Send opening tag for BLOB command to server.
Class to provide basic INDI device functionality.
Definition basedevice.h:52
INDI::PropertyNumber getNumber(const char *name) const
void checkMessage(XMLEle *root)
const char * getDeviceName() const
Property getProperty(const char *name, INDI_PROPERTY_TYPE type=INDI_UNKNOWN) const
Return a property and its type given its name.
INDI::PropertySwitch getSwitch(const char *name) const
int removeProperty(const char *name, char *errmsg)
Remove a property.
bool isValid() const
std::shared_ptr< BaseDevicePrivate > d_ptr
Definition basedevice.h:319
INDI::PropertyText getText(const char *name) const
XMLEle * handle() const
Definition indililxml.h:367
std::string tagName() const
Definition indililxml.h:372
LilXmlAttribute getAttribute(const char *name) const
Definition indililxml.h:405
std::string toString() const
Definition indililxml.h:265
The class is used to create device instances. Class copying is not allowed. When an object is destroy...
WidgetView< T > * findWidgetByName(const char *name) const
Provides generic container for INDI properties.
INDI::PropertyViewSwitch * getSwitch() const
INDI::PropertyViewBlob * getBLOB() const
INDI::PropertyViewText * getText() const
INDI::PropertyViewNumber * getNumber() const
INDI_PROPERTY_TYPE getType() const
void setState(IPState state)
BaseDevice getDeviceByName(const char *name)
int processXml(const INDI::LilXmlElement &root, char *errmsg, const std::function< ParentDevice()> &constructor=[] { return ParentDevice(ParentDevice::Valid);})
bool deleteDevice(const BaseDevice &device)
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IPS_BUSY
Definition indiapi.h:161
@ INDI_PROPERTY_DUPLICATED
@ INDI_DEVICE_NOT_FOUND
void IDLog(const char *fmt,...)
Definition indicom.c:305
BLOBHandling
How drivers handle BLOBs incoming from snooping drivers.
Definition indidevapi.h:267
@ B_ONLY
Definition indidevapi.h:270
@ B_ALSO
Definition indidevapi.h:269
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
@ INDI_LIGHT
Definition indidriver.c:60
@ INDI_TEXT
Definition indidriver.c:59
@ INDI_UNKNOWN
Definition indidriver.c:62
@ INDI_NUMBER
Definition indidriver.c:57
@ INDI_SWITCH
Definition indidriver.c:58
@ INDI_BLOB
Definition indidriver.c:61
void IUUserIOBLOBContextOne(const userio *io, void *user, const char *name, unsigned int size, unsigned int bloblen, const void *blob, const char *format)
Definition indiuserio.c:101
void IUUserIONewBLOBFinish(const userio *io, void *user)
Definition indiuserio.c:281
void IUUserIONewBLOB(const userio *io, void *user, const IBLOBVectorProperty *bvp)
Definition indiuserio.c:251
void IUUserIONewBLOBStart(const userio *io, void *user, const char *dev, const char *name, const char *timestamp)
Definition indiuserio.c:260
void IUUserIONewText(const userio *io, void *user, const ITextVectorProperty *tvp)
Definition indiuserio.c:216
void IUUserIOEnableBLOB(const userio *io, void *user, const char *dev, const char *name, BLOBHandling blobH)
Definition indiuserio.c:341
void IUUserIOPingRequest(const userio *io, void *user, const char *pingUid)
Definition indiuserio.c:787
void IUUserIONewSwitch(const userio *io, void *user, const ISwitchVectorProperty *svp)
Definition indiuserio.c:240
void IUUserIONewNumber(const userio *io, void *user, const INumberVectorProperty *nvp)
Definition indiuserio.c:199
void IUUserIOGetProperties(const userio *io, void *user, const char *dev, const char *name)
Definition indiuserio.c:307
void IUUserIOPingReply(const userio *io, void *user, const char *pingUid)
Definition indiuserio.c:794
const char * CONNECTION
Connect to and disconnect from device.
Namespace to encapsulate INDI client, drivers, and mediator classes.
One Blob (Binary Large Object) descriptor.
static PropertyView< T > * cast(PropertyType *raw)
const char * getName() const
const char * getFormat() const
void setState(const ISState &state)
void setText(const char *text, size_t size)
int(* vprintf)(void *user, const char *format, va_list arg)
Definition userio.h:31
ssize_t(* write)(void *user, const void *ptr, size_t count)
Definition userio.h:30
const struct userio * userio_file()
Definition userio.c:39