Instrument Neutral Distributed Interface INDI 2.2.5
defaultdevice.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2011 Jasem Mutlaq. All rights reserved.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*******************************************************************************/
18
19#include "defaultdevice.h"
20#include "defaultdevice_p.h"
21
22#include "indicom.h"
23#include "indiapi.h"
24
27
28#include "indipropertyswitch.h"
29#include "indipropertytext.h"
30#include "indipropertynumber.h"
31#include "indipropertyblob.h"
32
33#include <algorithm>
34#include <assert.h>
35#include <cstdlib>
36#include <cstring>
37#include <pwd.h>
38
39const char *COMMUNICATION_TAB = "Communication";
40const char *MAIN_CONTROL_TAB = "Main Control";
41const char *CONNECTION_TAB = "Connection";
42const char *MOTION_TAB = "Motion Control";
43const char *DATETIME_TAB = "Date/Time";
44const char *SITE_TAB = "Site Management";
45const char *OPTIONS_TAB = "Options";
46const char *FILTER_TAB = "Filter Wheel";
47const char *FOCUS_TAB = "Focuser";
48const char *GUIDE_TAB = "Guide";
49const char *ALIGNMENT_TAB = "Alignment";
50const char *SATELLITE_TAB = "Satellite";
51const char *INFO_TAB = "General Info";
52
53std::list<INDI::DefaultDevicePrivate*> INDI::DefaultDevicePrivate::devices;
55
56extern "C"
57{
58
59 void ISGetProperties(const char *dev)
60 {
61 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
63 it->defaultDevice->ISGetProperties(dev);
64 }
65
66 void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
67 {
68 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
70 if (dev == nullptr || strcmp(dev, it->defaultDevice->getDeviceName()) == 0)
71 it->defaultDevice->ISNewSwitch(dev, name, states, names, n);
72 }
73
74 void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
75 {
76 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
78 if (dev == nullptr || strcmp(dev, it->defaultDevice->getDeviceName()) == 0)
79 it->defaultDevice->ISNewNumber(dev, name, values, names, n);
80 }
81
82 void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
83 {
84 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
86 if (dev == nullptr || strcmp(dev, it->defaultDevice->getDeviceName()) == 0)
87 it->defaultDevice->ISNewText(dev, name, texts, names, n);
88 }
89
90 void ISNewBLOB(const char *dev, const char *name,
91 int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n
92 )
93 {
94 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
96 if (dev == nullptr || strcmp(dev, it->defaultDevice->getDeviceName()) == 0)
97 it->defaultDevice->ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
98 }
99
101 {
102 const std::unique_lock<std::recursive_mutex> lock(INDI::DefaultDevicePrivate::devicesLock);
104 it->defaultDevice->ISSnoopDevice(root);
105 }
106
107} // extern "C"
108
109
110// For drivers, indiproperty need access to ID/IU functions
111extern void (*WeakIDSetTextVA)(const ITextVectorProperty *, const char *, va_list);
112extern void (*WeakIDDefTextVA)(const ITextVectorProperty *, const char *, va_list);
113extern void (*WeakIDSetNumberVA)(const INumberVectorProperty *, const char *, va_list);
114extern void (*WeakIDDefNumberVA)(const INumberVectorProperty *, const char *, va_list);
115extern void (*WeakIDSetSwitchVA)(const ISwitchVectorProperty *, const char *, va_list);
116extern void (*WeakIDDefSwitchVA)(const ISwitchVectorProperty *, const char *, va_list);
117extern void (*WeakIDSetLightVA)(const ILightVectorProperty *, const char *, va_list);
118extern void (*WeakIDDefLightVA)(const ILightVectorProperty *, const char *, va_list);
119extern void (*WeakIDSetBLOBVA)(const IBLOBVectorProperty *, const char *, va_list);
120extern void (*WeakIDDefBLOBVA)(const IBLOBVectorProperty *, const char *, va_list);
121extern int (*WeakIUUpdateText)(ITextVectorProperty *, char *[], char *[], int);
122extern int (*WeakIUUpdateNumber)(INumberVectorProperty *, double[], char *[], int n);
123extern int (*WeakIUUpdateSwitch)(ISwitchVectorProperty *, ISState *, char *[], int n);
124extern int (*WeakIUUpdateBLOB)(IBLOBVectorProperty *, int [], int [], char *[], char *[], char *[], int n);
125extern void (*WeakIUUpdateMinMax)(const INumberVectorProperty *);
126
127static struct WeakIDLoader
128{
129 WeakIDLoader()
130 {
146 }
147} weakLoader;
148
149void timerfunc(void *t)
150{
151 INDI::DefaultDevice *devPtr = static_cast<INDI::DefaultDevice *>(t);
152 if (devPtr != nullptr)
153 {
154 // this was for my device
155 // but we dont have a way of telling
156 // WHICH timer was hit :(
157 devPtr->TimerHit();
158 }
159 return;
160}
161
162
163namespace INDI
164{
165
166namespace
167{
168
169// trim from start (in place)
170static inline void ltrim(std::string &s)
171{
172 if (s.empty())
173 return;
174
175 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch)
176 {
177 return !std::isspace(ch);
178 }));
179}
180
181// trim from end (in place)
182static inline void rtrim(std::string &s)
183{
184 if (s.empty())
185 return;
186
187 s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch)
188 {
189 return !std::isspace(ch);
190 }).base(), s.end());
191}
192
193// trim from both ends (in place)
194static void trim(std::string &s)
195{
196 ltrim(s);
197 rtrim(s);
198}
199
200static std::string GetHomeDirectory()
201{
202 // Check first the HOME environmental variable
203 const char *HomeDir = getenv("HOME");
204
205 // ...otherwise get the home directory of the current user.
206 if (!HomeDir)
207 {
208 HomeDir = getpwuid(getuid())->pw_dir;
209 }
210 return (HomeDir ? std::string(HomeDir) : "");
211}
212
213} // namespace
214
243#define NICKNAME_FILE "/.indi/Nicknames.xml"
244#define NICK_TAG_ROOT "INDINicknames"
245#define NICK_TAG_DEVICE "device"
246#define NICK_ATTR_NAME "name"
247#define NICK_TAG_ENTRY "nickname"
248#define NICK_ATTR_ID "identifier"
249
251{
252 const std::string filename = GetHomeDirectory() + NICKNAME_FILE;
253 nicknameMap.clear();
254
255 LilXML *lp = newLilXML();
256 XMLEle *NickXmlRoot = nullptr;
257 XMLEle *devicexml = nullptr;
258 XMLEle *nickxml = nullptr;
259 static char errmsg[XML_ERROR_SIZE];
260 memset(errmsg, 0, sizeof(errmsg)); // Clear anything from prior run
261 FILE *fp = fopen(filename.c_str(), "r");
262 if (fp)
263 {
264 NickXmlRoot = readXMLFile(fp, lp, errmsg);
265 fclose(fp);
266 }
267 delLilXML(lp);
268
269 // NickXmlRoot is the root root tag, it's name is INDINickname
270 // iterating that one gives the other tags
271 if (!NickXmlRoot || errmsg[0])
272 return 1;
273
274 if (strcmp(tagXMLEle(NickXmlRoot), NICK_TAG_ROOT) != 0)
275 {
276 delXMLEle(NickXmlRoot);
277 return 1;
278 }
279
280 // children of top level INDINicknames
281 // This should be <device>
282 devicexml = nextXMLEle(NickXmlRoot, 1);
283 // find <device name= matching this driver
284 for (; devicexml != NULL; devicexml = nextXMLEle(NickXmlRoot, 0))
285 {
286 // Skip non <device> tags
287 if (strcmp(tagXMLEle(devicexml), NICK_TAG_DEVICE))
288 {
289 printf("Skipping XML Non-Device: %s\n", tagXMLEle(devicexml));
290 continue;
291 }
292
293 // find name= attr
294 const char *devname = findXMLAttValu(devicexml, NICK_ATTR_NAME);
295 // skip other drivers
296 if (devname && strcmp(devname, devicename) == 0)
297 {
298 // Found <device name= matching the current driver
299 nickxml = nextXMLEle(devicexml, 1);
300 break;
301 }
302 }
303
304 for (; nickxml != NULL; nickxml = nextXMLEle(devicexml, 0))
305 {
306 // Skip non <nickname> tags
307 if (strcmp(tagXMLEle(nickxml), NICK_TAG_ENTRY))
308 continue;
309
310 // find identifier= attr
311 const char *pId = findXMLAttValu(nickxml, NICK_ATTR_ID);
312 const char *pVal = pcdataXMLEle(nickxml);
313 if (pId && pVal)
314 {
315 std::string sId{pId}, sVal{pVal}; // deep copy
316 trim(sId);
317 trim(sVal);
318 if (!sId.empty() && !sVal.empty())
319 nicknameMap[sId] = sVal;
320 }
321 }
322
323 delXMLEle(nickxml);
324 delXMLEle(devicexml);
325 delXMLEle(NickXmlRoot);
326 nicknamesLoaded = true;
327
328 return 0;
329}
330
339{
340 const std::string filename = GetHomeDirectory() + NICKNAME_FILE;
341
342 LilXML *lp = newLilXML();
343 XMLEle *NickXmlRoot = nullptr;
344 XMLEle *devicexml = nullptr;
345 XMLEle *nickxml = nullptr;
346 static char errmsg[XML_ERROR_SIZE];
347 memset(errmsg, 0, sizeof(errmsg)); // Clear anything from prior run
348 FILE *fp = fopen(filename.c_str(), "r+");
349 if (fp)
350 {
351 NickXmlRoot = readXMLFile(fp, lp, errmsg);
352 }
353
354 // NickXmlRoot is the root <INDINickname> tag
355 if (!NickXmlRoot) // empty file, make new root node
356 NickXmlRoot = addXMLEle(nullptr, NICK_TAG_ROOT);
357
358 if (strcmp(tagXMLEle(NickXmlRoot), NICK_TAG_ROOT) != 0)
359 {
360 // Unexpected tag, clear and make new
361 delXMLEle(NickXmlRoot);
362 NickXmlRoot = addXMLEle(nullptr, NICK_TAG_ROOT);
363 }
364
365 // children of top level INDINicknames
366 // This should be <device>
367 devicexml = nextXMLEle(NickXmlRoot, 1);
368 for (; devicexml != NULL; devicexml = nextXMLEle(NickXmlRoot, 0))
369 {
370 // Skip non <device> tags
371 if (strcmp(tagXMLEle(devicexml), NICK_TAG_DEVICE))
372 continue;
373
374 // find name= attr
375 const char *devname = findXMLAttValu(devicexml, NICK_ATTR_NAME);
376 // skip other drivers
377 if (devname && strcmp(devname, devicename) == 0)
378 {
379 // Found <device name= matching the current driver
380 nickxml = nextXMLEle(devicexml, 1);
381 break;
382 }
383 }
384
385 if (!devicexml)
386 {
387 // No entry found for current driver, add empty
388 devicexml = addXMLEle(NickXmlRoot, NICK_TAG_DEVICE);
389 addXMLAtt(devicexml, NICK_ATTR_NAME, devicename);
390 }
391
392 // Remove all nicknames in current driver section
393 while ((nickxml = nextXMLEle(devicexml, 1)) != 0)
394 {
395 delXMLEle(nickxml);
396 nickxml = nullptr;
397 }
398
399 for (const auto &kv : nicknameMap)
400 {
401 nickxml = addXMLEle(devicexml, NICK_TAG_ENTRY);
402 addXMLAtt(nickxml, NICK_ATTR_ID, kv.first.c_str());
403 editXMLEle(nickxml, kv.second.c_str());
404 }
405
406 // Reopen for writing and truncate file.
407 // freopen requires a valid FILE* — if the file didn't exist, fp is null, so fall back to fopen.
408 if (fp)
409 fp = freopen(filename.c_str(), "w+", fp);
410 else
411 fp = fopen(filename.c_str(), "w+");
412 if (fp)
413 {
414 prXMLEle(fp, NickXmlRoot, 0);
415 fclose(fp);
416 }
417
418 delXMLEle(NickXmlRoot);
419 delLilXML(lp);
420 return 0;
421}
422
424 : defaultDevice(defaultDevice)
425{
426 const std::unique_lock<std::recursive_mutex> lock(DefaultDevicePrivate::devicesLock);
427 devices.push_back(this);
428}
429
431{
432 const std::unique_lock<std::recursive_mutex> lock(DefaultDevicePrivate::devicesLock);
433 devices.remove(this);
434}
435
438{
439 D_PTR(DefaultDevice);
440 d->m_MainLoopTimer.setSingleShot(true);
441 d->m_MainLoopTimer.setInterval(getPollingPeriod());
442 d->m_MainLoopTimer.callOnTimeout(std::bind(&DefaultDevice::TimerHit, this));
443}
444
446{
447 return loadConfig(true, property.getName());
448}
449
450bool DefaultDevice::loadConfig(bool silent, const char *property)
451{
452 D_PTR(DefaultDevice);
453 char errmsg[MAXRBUF] = {0};
454 d->isConfigLoading = true;
455 bool pResult = IUReadConfig(nullptr, getDeviceName(), property, silent ? 1 : 0, errmsg) == 0 ? true : false;
456 d->isConfigLoading = false;
457
458 if (!silent)
459 {
460 if (pResult)
461 {
462 LOG_DEBUG("Configuration successfully loaded.");
463 }
464 else
465 LOG_INFO("No previous configuration found. To save driver configuration, click Save Configuration in Options tab.");
466 }
467
468 // Determine default config file name
469 // Need to be done only once per device.
470 if (d->isDefaultConfigLoaded == false)
471 {
472 d->isDefaultConfigLoaded = IUSaveDefaultConfig(nullptr, nullptr, getDeviceName()) == 0;
473 }
474
475 return pResult;
476}
477
479{
480 D_PTR(DefaultDevice);
481 d->DebugSP.save(fp);
482 d->PollPeriodNP.save(fp);
483 if (!d->ConnectionModeSP.isEmpty())
484 d->ConnectionModeSP.save(fp);
485
486 if (d->activeConnection != nullptr)
487 d->activeConnection->saveConfigItems(fp);
488
490}
491
493{
494 for (const auto &oneProperty : getProperties())
495 {
496 if (oneProperty.getType() == INDI_SWITCH)
497 {
498 const auto &svp = oneProperty.getSwitch();
499 /* Never save CONNECTION property. Don't save switches with no switches on if the rule is one of many */
500 if (
501 (svp->isNameMatch(INDI::SP::CONNECTION)) ||
502 (svp->getRule() == ISR_1OFMANY && svp->findOnSwitch() == nullptr)
503 )
504 continue;
505 }
506 oneProperty.save(fp);
507 }
508 return true;
509}
510
512{
513 char errmsg[MAXRBUF];
514 if (IUPurgeConfig(nullptr, getDeviceName(), errmsg) == -1)
515 {
516 LOGF_WARN("%s", errmsg);
517 return false;
518 }
519
520 LOG_INFO("Configuration file successfully purged.");
521 return true;
522}
523
525{
526 return saveConfig(true, property.getName());
527}
528
529bool DefaultDevice::saveConfig(bool silent, const char *property)
530{
531 D_PTR(DefaultDevice);
532 if (d->isConfigLoading)
533 return false;
534 silent = false;
535 char errmsg[MAXRBUF] = {0};
536
537 FILE *fp = nullptr;
538
539 if (property == nullptr)
540 {
541 fp = IUGetConfigFP(nullptr, getDeviceName(), "w", errmsg);
542
543 if (fp == nullptr)
544 {
545 if (!silent)
546 LOGF_WARN("Failed to save configuration. %s", errmsg);
547 return false;
548 }
549
550 IUSaveConfigTag(fp, 0, getDeviceName(), silent ? 1 : 0);
551
552 saveConfigItems(fp);
553
554 IUSaveConfigTag(fp, 1, getDeviceName(), silent ? 1 : 0);
555
556 fflush(fp);
557 fclose(fp);
558
559 if (d->isDefaultConfigLoaded == false)
560 {
561 d->isDefaultConfigLoaded = IUSaveDefaultConfig(nullptr, nullptr, getDeviceName()) == 0;
562 }
563
564 LOG_DEBUG("Configuration successfully saved.");
565 }
566 else
567 {
568 fp = IUGetConfigFP(nullptr, getDeviceName(), "r", errmsg);
569
570 if (fp == nullptr)
571 {
572 //if (!silent)
573 // LOGF_ERROR("Error saving configuration. %s", errmsg);
574 //return false;
575 // If we don't have an existing file pointer, save all properties.
576 return saveConfig(silent);
577 }
578
579 LilXML *lp = newLilXML();
580 XMLEle *root = readXMLFile(fp, lp, errmsg);
581
582 fclose(fp);
583 delLilXML(lp);
584
585 if (root == nullptr)
586 return false;
587
588 XMLEle *ep = nullptr;
589 bool propertySaved = false;
590
591 // Check that the saved members match the current property members. If members were added or
592 // removed since the configuration was written (e.g. new baud rates), editing the saved members
593 // in place would drop the new ones, so the whole configuration must be saved instead.
594 auto savedLayoutMatches = [&ep](const auto &oneProperty)
595 {
596 size_t savedCount = 0;
597 for (XMLEle *oneElement = nextXMLEle(ep, 1); oneElement != nullptr; oneElement = nextXMLEle(ep, 0), savedCount++)
598 {
599 if (oneProperty.findWidgetByName(findXMLAttValu(oneElement, "name")) == nullptr)
600 return false;
601 }
602 return savedCount == oneProperty.count();
603 };
604
605 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
606 {
607 const char *elemName = findXMLAttValu(ep, "name");
608 const char *tagName = tagXMLEle(ep);
609
610 if (strcmp(elemName, property))
611 continue;
612
613 if (!strcmp(tagName, "newSwitchVector"))
614 {
615 auto svp = getSwitch(elemName);
616 if (!svp)
617 {
618 delXMLEle(root);
619 return false;
620 }
621
622 if (!savedLayoutMatches(svp))
623 {
624 delXMLEle(root);
625 return saveConfig(silent);
626 }
627
628 XMLEle *sw = nullptr;
629 for (sw = nextXMLEle(ep, 1); sw != nullptr; sw = nextXMLEle(ep, 0))
630 {
631 auto oneSwitch = svp.findWidgetByName(findXMLAttValu(sw, "name"));
632 if (!oneSwitch)
633 {
634 delXMLEle(root);
635 return false;
636 }
637 char formatString[MAXRBUF];
638 snprintf(formatString, MAXRBUF, " %s\n", oneSwitch->getStateAsString());
639 editXMLEle(sw, formatString);
640 }
641
642 propertySaved = true;
643 break;
644 }
645 else if (!strcmp(tagName, "newNumberVector"))
646 {
647 auto nvp = getNumber(elemName);
648 if (!nvp)
649 {
650 delXMLEle(root);
651 return false;
652 }
653
654 if (!savedLayoutMatches(nvp))
655 {
656 delXMLEle(root);
657 return saveConfig(silent);
658 }
659
660 XMLEle *np = nullptr;
661 for (np = nextXMLEle(ep, 1); np != nullptr; np = nextXMLEle(ep, 0))
662 {
663 auto oneNumber = nvp.findWidgetByName(findXMLAttValu(np, "name"));
664 if (!oneNumber)
665 return false;
666
667 char formatString[MAXRBUF];
668 snprintf(formatString, MAXRBUF, " %.20g\n", oneNumber->getValue());
669 editXMLEle(np, formatString);
670 }
671
672 propertySaved = true;
673 break;
674 }
675 else if (!strcmp(tagName, "newTextVector"))
676 {
677 auto tvp = getText(elemName);
678 if (!tvp)
679 {
680 delXMLEle(root);
681 return false;
682 }
683
684 if (!savedLayoutMatches(tvp))
685 {
686 delXMLEle(root);
687 return saveConfig(silent);
688 }
689
690 XMLEle *tp = nullptr;
691 for (tp = nextXMLEle(ep, 1); tp != nullptr; tp = nextXMLEle(ep, 0))
692 {
693 auto oneText = tvp.findWidgetByName(findXMLAttValu(tp, "name"));
694 if (!oneText)
695 return false;
696
697 char formatString[MAXRBUF];
698 snprintf(formatString, MAXRBUF, " %s\n", oneText->getText() ? oneText->getText() : "");
699 editXMLEle(tp, formatString);
700 }
701
702 propertySaved = true;
703 break;
704 }
705 }
706
707 if (propertySaved)
708 {
709 fp = IUGetConfigFP(nullptr, getDeviceName(), "w", errmsg);
710 prXMLEle(fp, root, 0);
711 fflush(fp);
712 fclose(fp);
713 delXMLEle(root);
714 LOGF_DEBUG("Configuration successfully saved for %s.", property);
715 return true;
716 }
717 else
718 {
719 delXMLEle(root);
720 // If property does not exist, save the whole thing
721 return saveConfig(silent);
722 }
723 }
724
725 return true;
726}
727
729{
730 char configDefaultFileName[MAXRBUF];
731 char errmsg[MAXRBUF];
732 bool pResult = false;
733
734 if (getenv("INDICONFIG"))
735 snprintf(configDefaultFileName, MAXRBUF, "%s.default", getenv("INDICONFIG"));
736 else
737 snprintf(configDefaultFileName, MAXRBUF, "%s/.indi/%s_config.xml.default", getenv("HOME"), getDeviceName());
738
739 LOGF_DEBUG("Requesting to load default config with: %s", configDefaultFileName);
740
741 pResult = IUReadConfig(configDefaultFileName, getDeviceName(), nullptr, 0, errmsg) == 0 ? true : false;
742
743 if (pResult)
744 LOG_INFO("Default configuration loaded.");
745 else
746 LOGF_INFO("Error loading default configuration. %s", errmsg);
747
748 return pResult;
749}
750
751bool DefaultDevice::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
752{
753 D_PTR(DefaultDevice);
754 // ignore if not ours //
755 if (strcmp(dev, getDeviceName()))
756 return false;
757
759
760 if (!property.isValid())
761 return false;
762
763 // #PS: TODO Remove
765 // Debugging and Logging Levels
767 if (
768 property.isNameMatch("DEBUG_LEVEL") ||
769 property.isNameMatch("LOGGING_LEVEL") ||
770 property.isNameMatch("LOG_OUTPUT"))
771 {
772 bool rc = Logger::ISNewSwitch(dev, name, states, names, n);
773
774 if (property.isNameMatch("LOG_OUTPUT"))
775 {
776 auto sw = property.findWidgetByName("FILE_DEBUG");
777 if (sw != nullptr && sw->getState() == ISS_ON)
778 DEBUGF(Logger::DBG_SESSION, "Session log file %s", Logger::getLogFile().c_str());
779 }
780
781 return rc;
782 }
783
784 property.update(states, names, n); // update and callback
785 if (property.hasUpdateCallback())
786 return true;
787
788 bool rc = false;
789 for (Connection::Interface *oneConnection : d->connections)
790 rc |= oneConnection->ISNewSwitch(dev, name, states, names, n);
791
792 return rc;
793}
794
795bool DefaultDevice::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
796{
797 D_PTR(DefaultDevice);
798
800
801 if (!property.isValid())
802 return false;
803
804 property.update(values, names, n); // update and callback
805 if (property.hasUpdateCallback())
806 return true;
807
808 for (Connection::Interface *oneConnection : d->connections)
809 oneConnection->ISNewNumber(dev, name, values, names, n);
810
811 return false;
812}
813
814bool DefaultDevice::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
815{
816 D_PTR(DefaultDevice);
817
818 INDI::PropertyText property = getProperty(name, INDI_TEXT);
819
820 if (!property.isValid())
821 return false;
822
823 property.update(texts, names, n); // update and callback
824 if (property.hasUpdateCallback())
825 return true;
826
827 for (Connection::Interface *oneConnection : d->connections)
828 oneConnection->ISNewText(dev, name, texts, names, n);
829
830 return false;
831}
832
833bool DefaultDevice::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
834 char *formats[], char *names[], int n)
835{
836 D_PTR(DefaultDevice);
837
838 INDI::PropertyBlob property = getProperty(name, INDI_BLOB);
839
840 if (!property.isValid())
841 return false;
842
843 property.update(sizes, blobsizes, blobs, formats, names, n); // update and callback
844 if (property.hasUpdateCallback())
845 return true;
846
847 for (Connection::Interface *oneConnection : d->connections)
848 oneConnection->ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
849
850 return false;
851}
852
854{
855 D_PTR(DefaultDevice);
856 char errmsg[MAXRBUF];
857 return d->watchDevice.processXml(INDI::LilXmlElement(root), errmsg) < 0;
858}
859
860void DefaultDevice::watchDevice(const char *name, const std::function<void (BaseDevice)> &callback)
861{
862 D_PTR(DefaultDevice);
863 d->watchDevice.watchDevice(name, callback);
864 IDSnoopDevice(name, nullptr);
865}
866
868{
869 D_PTR(DefaultDevice);
870 registerProperty(d->DebugSP);
871 d->isDebug = false;
872}
873
875{
876 D_PTR(DefaultDevice);
877 registerProperty(d->SimulationSP);
878 d->isSimulation = false;
879}
880
882{
883 D_PTR(DefaultDevice);
884 registerProperty(d->ConfigProcessSP);
885}
886
888{
889 D_PTR(DefaultDevice);
890 registerProperty(d->PollPeriodNP);
891}
892
894{
895 D_PTR(DefaultDevice);
896 registerProperty(d->NicknameTP);
897}
898
906
907void DefaultDevice::setDebug(bool enable)
908{
909 D_PTR(DefaultDevice);
910 if (d->isDebug == enable)
911 {
912 d->DebugSP.setState(IPS_OK);
913 d->DebugSP.apply();
914 return;
915 }
916
917 d->DebugSP.reset();
918
919 auto sp = d->DebugSP.findWidgetByName(enable ? "ENABLE" : "DISABLE");
920 if (sp)
921 {
922 sp->setState(ISS_ON);
923 LOGF_INFO("Debug is %s.", enable ? "enabled" : "disabled");
924 }
925
926 d->isDebug = enable;
927
928 // Inform logger
929 if (Logger::updateProperties(enable) == false)
930 DEBUG(Logger::DBG_WARNING, "setLogDebug: Logger error");
931
932 debugTriggered(enable);
933 d->DebugSP.setState(IPS_OK);
934 d->DebugSP.apply();
935}
936
938{
939 D_PTR(DefaultDevice);
940 if (d->isSimulation == enable)
941 {
942 d->SimulationSP.setState(IPS_OK);
943 d->SimulationSP.apply();
944 return;
945 }
946
947 d->SimulationSP.reset();
948
949 auto sp = d->SimulationSP.findWidgetByName(enable ? "ENABLE" : "DISABLE");
950 if (sp)
951 {
952 LOGF_INFO("Simulation is %s.", enable ? "enabled" : "disabled");
953 sp->setState(ISS_ON);
954 }
955
956 d->isSimulation = enable;
957 simulationTriggered(enable);
958 d->SimulationSP.setState(IPS_OK);
959 d->SimulationSP.apply();
960}
961
963{
964 D_PTR(const DefaultDevice);
965 return d->isDebug;
966}
967
969{
970 D_PTR(const DefaultDevice);
971 return d->isSimulation;
972}
973
975{
976 INDI_UNUSED(enable);
977}
978
980{
981 INDI_UNUSED(enable);
982}
983
984void DefaultDevice::nicknameSet(const char *nickname)
985{
986 INDI_UNUSED(nickname);
987}
988
990{
991 D_PTR(DefaultDevice);
992 if (d->isInit == false)
993 {
994 if (dev != nullptr)
995 setDeviceName(dev);
996 else if (*getDeviceName() == '\0')
997 {
998 char *envDev = getenv("INDIDEV");
999 if (envDev != nullptr)
1000 setDeviceName(envDev);
1001 else
1003 }
1004
1005 d->ConnectionSP.setDeviceName(getDeviceName());
1008
1009 // If we have no connections, move Driver Info to General Info tab
1010 if (d->connections.size() == 0)
1011 d->DriverInfoTP.setGroupName(INFO_TAB);
1012 }
1013
1014 for (const auto &oneProperty : *getProperties())
1015 {
1016 if (d->defineDynamicProperties == false && oneProperty.isDynamic())
1017 continue;
1018
1019 oneProperty.define();
1020 }
1021
1022 // Remember debug & logging settings
1023 if (d->isInit == false)
1024 {
1025 loadConfig(true, "DEBUG");
1026 loadConfig(true, "DEBUG_LEVEL");
1027 loadConfig(true, "LOGGING_LEVEL");
1028 loadConfig(true, "POLLING_PERIOD");
1029 loadConfig(true, "LOG_OUTPUT");
1030 }
1031
1032 if (d->ConnectionModeSP.isEmpty())
1033 {
1034 if (d->connections.size() > 0)
1035 {
1036 d->ConnectionModeSP.resize(d->connections.size());
1037 auto sp = &d->ConnectionModeSP[0];
1038 for (Connection::Interface *oneConnection : d->connections)
1039 {
1040 (sp++)->fill(oneConnection->name(), oneConnection->label(), ISS_OFF);
1041 }
1042 d->ConnectionModeSP.fill(getDeviceName(), "CONNECTION_MODE", "Connection Mode", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 60,
1043 IPS_IDLE);
1044
1045 // Try to read config first
1046 if (IUGetConfigOnSwitchIndex(getDeviceName(), d->ConnectionModeSP.getName(), &d->m_ConfigConnectionMode) == 0)
1047 {
1048 d->ConnectionModeSP[d->m_ConfigConnectionMode].setState(ISS_ON);
1049 d->activeConnection = d->connections[d->m_ConfigConnectionMode];
1050 }
1051 // Check if we already have an active connection set.
1052 else if (d->activeConnection != nullptr)
1053 {
1054 auto it = std::find(d->connections.begin(), d->connections.end(), d->activeConnection);
1055 if (it != d->connections.end())
1056 {
1057 int index = std::distance(d->connections.begin(), it);
1058 if (index >= 0)
1059 d->ConnectionModeSP[index].setState(ISS_ON);
1060 }
1061 }
1062 // Otherwise use connection 0
1063 else
1064 {
1065 d->ConnectionModeSP[0].setState(ISS_ON);
1066 d->activeConnection = d->connections[0];
1067 }
1068
1069 defineProperty(d->ConnectionModeSP);
1070 d->activeConnection->Activated();
1071 }
1072 }
1073
1074 d->isInit = true;
1075}
1076
1078{
1079 for (auto &oneProperty : getProperties())
1080 {
1081 oneProperty.setState(IPS_IDLE);
1082 oneProperty.apply();
1083 }
1084}
1085
1086void DefaultDevice::setConnected(bool status, IPState state, const char *msg)
1087{
1088 auto svp = getSwitch(INDI::SP::CONNECTION);
1089 if (!svp)
1090 return;
1091
1092 svp[INDI_ENABLED].setState(status ? ISS_ON : ISS_OFF);
1093 svp[INDI_DISABLED].setState(status ? ISS_OFF : ISS_ON);
1094 svp.setState(state);
1095
1096 if (msg == nullptr)
1097 svp.apply();
1098 else
1099 svp.apply("%s", msg);
1100}
1101
1102// Set the timeout for the TimerHit function.
1103// This is a single shot timer.
1105{
1106 D_PTR(DefaultDevice);
1107 d->m_MainLoopTimer.start(ms);
1108 return 1;
1109}
1110
1111// Remove main timer. ID is not used.
1112// Kept for backward compatibility
1114{
1115 INDI_UNUSED(id);
1116 D_PTR(DefaultDevice);
1117 d->m_MainLoopTimer.stop();
1118 return;
1119}
1120
1121// This is just a placeholder
1122// This function should be overridden by child classes if they use timers
1123// So we should never get here
1125{
1126 return;
1127}
1128
1130{
1131 // The base device has no properties to update
1132 return true;
1133}
1134
1135// Early access to properties.
1136// The `BaseDevice::getDriverInterface()` function will return a false value of 0
1137// when the `DriverInfoTP` property has not yet been registered.
1138//
1139// The situation occurs in the case of drivers where the class that inherits from `DefaultDevice`
1140// will call the `setDriverInterface` function in the constructor
1141
1143{
1144 D_PTR(const DefaultDevice);
1145 return atoi(d->DriverInfoTP[3 /* DRIVER_INTERFACE */].getText());
1146}
1147
1149{
1150 D_PTR(DefaultDevice);
1151 d->DriverInfoTP[3 /* DRIVER_INTERFACE */].setText(std::to_string(value));
1152}
1153
1155{
1156 D_PTR(DefaultDevice);
1157 d->DriverInfoTP.apply();
1158}
1159
1161{
1162 D_PTR(DefaultDevice);
1163 char versionStr[16];
1164 char interfaceStr[16];
1165
1166 snprintf(versionStr, 16, "%d.%d", d->majorVersion, d->minorVersion);
1167 snprintf(interfaceStr, 16, "%d", getDriverInterface());
1168
1169 // Connection Mode
1170 d->ConnectionModeSP.onUpdate([d]()
1171 {
1172 int activeConnectionMode = d->ConnectionModeSP.findOnSwitchIndex();
1173
1174 if (activeConnectionMode >= 0 && activeConnectionMode < static_cast<int>(d->connections.size()))
1175 {
1176 d->activeConnection = d->connections[activeConnectionMode];
1177 d->activeConnection->Activated();
1178
1179 for (Connection::Interface *oneConnection : d->connections)
1180 {
1181 if (oneConnection == d->activeConnection)
1182 continue;
1183
1184 oneConnection->Deactivated();
1185 }
1186
1187 d->ConnectionModeSP.setState(IPS_OK);
1188 }
1189 else
1190 d->ConnectionModeSP.setState(IPS_ALERT);
1191
1192 d->ConnectionModeSP.apply();
1193
1194 });
1195
1196 // Connection
1197 d->ConnectionSP[INDI_ENABLED ].fill("CONNECT", "Connect", ISS_OFF);
1198 d->ConnectionSP[INDI_DISABLED].fill("DISCONNECT", "Disconnect", ISS_ON);
1199 d->ConnectionSP.fill(getDeviceName(), INDI::SP::CONNECTION, "Connection", "Main Control", IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
1200 d->ConnectionSP.onNewValues([this](const INDI::PropertySwitch::NewValues & values)
1201 {
1202 if (values.contains("CONNECT", ISS_ON))
1203 {
1204 // If disconnected, try to connect.
1205 if (isConnected() == false)
1206 {
1207 if (Connect())
1208 {
1209 // Connection is successful, set it to OK and updateProperties.
1210 setConnected(true);
1211 updateProperties();
1212 }
1213 else
1214 setConnected(false, IPS_ALERT);
1215 }
1216 else
1217 // Already connected, tell client we're connected already.
1218 setConnected(true);
1219 }
1220
1221 if (values.contains("DISCONNECT", ISS_ON))
1222 {
1223 // If connected, try to disconnect.
1224 if (isConnected() == true)
1225 {
1226 // Disconnection is successful, set it IDLE and updateProperties.
1227 if (Disconnect())
1228 {
1229 setConnected(false, IPS_IDLE);
1230 updateProperties();
1231 }
1232 else
1233 setConnected(true, IPS_ALERT);
1234 }
1235 // Already disconnected, tell client we're disconnected already.
1236 else
1237 setConnected(false, IPS_IDLE);
1238 }
1239
1240 });
1241 registerProperty(d->ConnectionSP);
1242
1243 d->DriverInfoTP[0].fill("DRIVER_NAME", "Name", getDriverName());
1244 d->DriverInfoTP[1].fill("DRIVER_EXEC", "Exec", getDriverExec());
1245 d->DriverInfoTP[2].fill("DRIVER_VERSION", "Version", versionStr);
1246 d->DriverInfoTP[3].fill("DRIVER_INTERFACE", "Interface", interfaceStr);
1247 d->DriverInfoTP.fill(getDeviceName(), "DRIVER_INFO", "Driver Info", CONNECTION_TAB, IP_RO, 60, IPS_IDLE);
1248 registerProperty(d->DriverInfoTP);
1249
1250 // Debug
1251 d->DebugSP[INDI_ENABLED ].fill("ENABLE", "Enable", ISS_OFF);
1252 d->DebugSP[INDI_DISABLED].fill("DISABLE", "Disable", ISS_ON);
1253 d->DebugSP.fill(getDeviceName(), "DEBUG", "Debug", "Options", IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1254 d->DebugSP.onUpdate([this, d]()
1255 {
1256 auto sp = d->DebugSP.findOnSwitch();
1257 assert(sp != nullptr);
1258 setDebug(sp->isNameMatch("ENABLE") ? true : false);
1259 });
1260
1261 // Simulation
1262 d->SimulationSP[INDI_ENABLED ].fill("ENABLE", "Enable", ISS_OFF);
1263 d->SimulationSP[INDI_DISABLED].fill("DISABLE", "Disable", ISS_ON);
1264 d->SimulationSP.fill(getDeviceName(), "SIMULATION", "Simulation", "Options", IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
1265 d->SimulationSP.onUpdate([this, d]()
1266 {
1267 auto sp = d->SimulationSP.findOnSwitch();
1268 assert(sp != nullptr);
1269 setSimulation(sp->isNameMatch("ENABLE") ? true : false);
1270 });
1271
1272 // Configuration
1273 d->ConfigProcessSP[0].fill("CONFIG_LOAD", "Load", ISS_OFF);
1274 d->ConfigProcessSP[1].fill("CONFIG_SAVE", "Save", ISS_OFF);
1275 d->ConfigProcessSP[2].fill("CONFIG_DEFAULT", "Default", ISS_OFF);
1276 d->ConfigProcessSP[3].fill("CONFIG_PURGE", "Purge", ISS_OFF);
1277 d->ConfigProcessSP.fill(getDeviceName(), "CONFIG_PROCESS", "Configuration", "Options", IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
1278 d->ConfigProcessSP.onUpdate([this, d]()
1279 {
1280 auto svp = d->ConfigProcessSP;
1281 auto sp = svp.findOnSwitch();
1282 svp.reset();
1283 bool pResult = false;
1284
1285 // Not suppose to happen (all switches off) but let's handle it anyway
1286 if (sp == nullptr)
1287 {
1288 svp.setState(IPS_IDLE);
1289 svp.apply();
1290 return;
1291 }
1292
1293 if (sp->isNameMatch("CONFIG_LOAD"))
1294 pResult = loadConfig();
1295 else if (sp->isNameMatch("CONFIG_SAVE"))
1296 pResult = saveConfig();
1297 else if (sp->isNameMatch("CONFIG_DEFAULT"))
1298 pResult = loadDefaultConfig();
1299 else if (sp->isNameMatch("CONFIG_PURGE"))
1300 pResult = purgeConfig();
1301
1302 svp.setState(pResult ? IPS_OK : IPS_ALERT);
1303 svp.apply();
1304 });
1305
1306 // Polling Period
1307 d->PollPeriodNP[0].fill("PERIOD_MS", "Period (ms)", "%.f", 10, 600000, 1000, d->pollingPeriod);
1308 d->PollPeriodNP.fill(getDeviceName(), "POLLING_PERIOD", "Polling", "Options", IP_RW, 0, IPS_IDLE);
1309 d->PollPeriodNP.onUpdate([d]()
1310 {
1311 d->PollPeriodNP.setState(IPS_OK);
1312 d->pollingPeriod = static_cast<uint32_t>(d->PollPeriodNP[0].getValue());
1313 d->PollPeriodNP.apply();
1314 });
1315
1316 d->NicknameTP[0].fill("nickname", "nickname", d->deviceNickname.c_str());
1317 d->NicknameTP.fill(getDeviceName(), "NICKNAME", "Nickname", INFO_TAB, IP_RW, 60, IPS_IDLE);
1318 d->NicknameTP.onUpdate([d, this]()
1319 {
1320 setDeviceNickname(d->NicknameTP[0].getText());
1321 d->NicknameTP[0].setText(d->deviceNickname);
1322 d->NicknameTP.setState(IPS_OK);
1323 d->NicknameTP.apply();
1324 nicknameSet(d->deviceNickname.c_str());
1325 });
1326
1328
1329 // Ready the logger
1330 std::string logFile = getDriverExec();
1331
1333
1334 return true;
1335}
1336
1337bool DefaultDevice::deleteProperty(INDI::Property &property)
1338{
1339 return deleteProperty(property.getName());
1340}
1341
1342bool DefaultDevice::deleteProperty(const char *propertyName)
1343{
1344 D_PTR(DefaultDevice);
1345 char errmsg[MAXRBUF];
1346
1347 if (propertyName == nullptr)
1348 {
1349 //while(!pAll.empty()) delete bar.back(), bar.pop_back();
1350 IDDelete(getDeviceName(), nullptr, nullptr);
1351 return true;
1352 }
1353
1354 // Keep dynamic properties in existing property list so they can be reused
1355 if (d->deleteDynamicProperties == false)
1356 {
1357 INDI::Property prop = getProperty(propertyName);
1358 if (prop && prop.isDynamic())
1359 {
1360 IDDelete(getDeviceName(), propertyName, nullptr);
1361 return true;
1362 }
1363 }
1364
1365 if (removeProperty(propertyName, errmsg) == 0)
1366 {
1367 IDDelete(getDeviceName(), propertyName, nullptr);
1368 return true;
1369 }
1370 else
1371 return false;
1372}
1373
1374void DefaultDevice::defineProperty(INumberVectorProperty *property)
1375{
1376 registerProperty(INDI::Property(property));
1377 static_cast<PropertyViewNumber*>(property)->define();
1378}
1379
1380void DefaultDevice::defineProperty(ITextVectorProperty *property)
1381{
1382 registerProperty(INDI::Property(property));
1383 static_cast<PropertyViewText*>(property)->define();
1384}
1385
1386void DefaultDevice::defineProperty(ISwitchVectorProperty *property)
1387{
1388 registerProperty(INDI::Property(property));
1389 static_cast<PropertyViewSwitch*>(property)->define();
1390}
1391
1392void DefaultDevice::defineProperty(ILightVectorProperty *property)
1393{
1394 registerProperty(INDI::Property(property));
1395 static_cast<PropertyViewLight*>(property)->define();
1396}
1397
1398void DefaultDevice::defineProperty(IBLOBVectorProperty *property)
1399{
1400 registerProperty(INDI::Property(property));
1401 static_cast<PropertyViewBlob*>(property)->define();
1402}
1403
1404void DefaultDevice::defineProperty(INDI::Property &property)
1405{
1406 registerProperty(property);
1407 property.define();
1408}
1409
1410void DefaultDevice::defineNumber(INumberVectorProperty *nvp)
1411{
1412 defineProperty(nvp);
1413}
1414
1415void DefaultDevice::defineText(ITextVectorProperty *tvp)
1416{
1417 defineProperty(tvp);
1418}
1419
1420void DefaultDevice::defineSwitch(ISwitchVectorProperty *svp)
1421{
1422 defineProperty(svp);
1423}
1424
1425void DefaultDevice::defineLight(ILightVectorProperty *lvp)
1426{
1427 defineProperty(lvp);
1428}
1429
1430void DefaultDevice::defineBLOB(IBLOBVectorProperty *bvp)
1431{
1432 defineProperty(bvp);
1433}
1434
1435bool DefaultDevice::Connect()
1436{
1437 D_PTR(DefaultDevice);
1438 if (isConnected())
1439 return true;
1440
1441 if (d->activeConnection == nullptr)
1442 {
1443 LOG_ERROR("No active connection defined.");
1444 return false;
1445 }
1446
1447 bool rc = d->activeConnection->Connect();
1448
1449 if (rc)
1450 {
1451 if (d->ConnectionModeSP.findOnSwitchIndex() != d->m_ConfigConnectionMode)
1452 saveConfig(true, d->ConnectionModeSP.getName());
1453 if (d->pollingPeriod > 0)
1454 SetTimer(d->pollingPeriod);
1455 }
1456
1457 return rc;
1458}
1459
1460bool DefaultDevice::Disconnect()
1461{
1462 D_PTR(DefaultDevice);
1463 if (isSimulation())
1464 {
1465 DEBUGF(Logger::DBG_SESSION, "%s is offline.", getDeviceName());
1466 return true;
1467 }
1468
1469 if (d->activeConnection)
1470 {
1471 bool rc = d->activeConnection->Disconnect();
1472 if (rc)
1473 {
1474 DEBUGF(Logger::DBG_SESSION, "%s is offline.", getDeviceName());
1475 return true;
1476 }
1477 else
1478 return false;
1479 }
1480
1481 return false;
1482}
1483
1484void DefaultDevice::registerConnection(Connection::Interface *newConnection)
1485{
1486 D_PTR(DefaultDevice);
1487 d->connections.push_back(newConnection);
1488}
1489
1490bool DefaultDevice::unRegisterConnection(Connection::Interface *existingConnection)
1491{
1492 D_PTR(DefaultDevice);
1493
1494 auto i = std::begin(d->connections);
1495
1496 while (i != std::end(d->connections))
1497 {
1498 if (*i == existingConnection)
1499 {
1500 i = d->connections.erase(i);
1501 return true;
1502 }
1503 else
1504 ++i;
1505 }
1506
1507 return false;
1508}
1509
1510void DefaultDevice::setCurrentPollingPeriod(uint32_t msec)
1511{
1512 D_PTR(DefaultDevice);
1513 d->pollingPeriod = msec;
1514}
1515
1516uint32_t DefaultDevice::getCurrentPollingPeriod() const
1517{
1518 D_PTR(const DefaultDevice);
1519 return d->pollingPeriod;
1520}
1521
1522uint32_t &DefaultDevice::refCurrentPollingPeriod()
1523{
1524 D_PTR(DefaultDevice);
1525 return d->pollingPeriod;
1526}
1527
1528uint32_t DefaultDevice::refCurrentPollingPeriod() const
1529{
1530 D_PTR(const DefaultDevice);
1531 return d->pollingPeriod;
1532}
1533
1534void DefaultDevice::setDefaultPollingPeriod(uint32_t msec)
1535{
1536 D_PTR(DefaultDevice);
1537 d->PollPeriodNP[0].setValue(msec);
1538 d->pollingPeriod = msec;
1539}
1540
1541void DefaultDevice::setPollingPeriodRange(uint32_t minimum, uint32_t maximum)
1542{
1543 D_PTR(DefaultDevice);
1544
1545 d->PollPeriodNP[0].setMinMax(minimum, maximum);
1546 d->PollPeriodNP.updateMinMax();
1547}
1548
1549void DefaultDevice::setActiveConnection(Connection::Interface *existingConnection)
1550{
1551 D_PTR(DefaultDevice);
1552
1553 if (existingConnection == d->activeConnection)
1554 return;
1555
1556 for (Connection::Interface *oneConnection : d->connections)
1557 {
1558 if (oneConnection == d->activeConnection)
1559 {
1560 oneConnection->Deactivated();
1561 break;
1562 }
1563 }
1564
1565 d->activeConnection = existingConnection;
1566 if (!d->ConnectionModeSP.isEmpty())
1567 {
1568 auto it = std::find(d->connections.begin(), d->connections.end(), d->activeConnection);
1569 if (it != d->connections.end())
1570 {
1571 int index = std::distance(d->connections.begin(), it);
1572 if (index >= 0)
1573 {
1574 d->ConnectionModeSP.reset();
1575 d->ConnectionModeSP[index].setState(ISS_ON);
1576 d->ConnectionModeSP.setState(IPS_OK);
1577 // If property is registerned then send back response to client
1578 INDI::Property connectionProperty = getProperty(d->ConnectionModeSP.getName(), INDI_SWITCH);
1579 if (connectionProperty && connectionProperty.getRegistered())
1580 d->ConnectionModeSP.apply();
1581 }
1582 }
1583 }
1584}
1585
1586const char *DefaultDevice::getDriverExec()
1587{
1588 return me;
1589}
1590
1591const char *DefaultDevice::getDriverName()
1592{
1593 return getDefaultName();
1594}
1595
1596void DefaultDevice::setDeviceNickname(const char *nick)
1597{
1598 D_PTR(DefaultDevice);
1599 std::string def{getDefaultName()};
1600
1601 std::string n, full;
1602 if (nick)
1603 n = nick;
1604
1605 if (n.rfind(def, 0) == 0) // Remove prefix if nick is full name
1606 {
1607 n = n.substr(def.length());
1608 }
1609 // If prefix was removed, remove the space left over
1610 trim(n);
1611 d->deviceNickname = n;
1612
1613 // setDeviceName can only be set during initialization. The Nickname can be
1614 // set in NicknameTP and will take affect when restarted.
1615 if (!d->isInit)
1616 {
1617 if (d->deviceNickname.empty())
1618 full = def;
1619 else
1620 full = def + " " + d->deviceNickname;
1621 setDeviceName(full.c_str());
1622 }
1623}
1624
1625const char *DefaultDevice::getDeviceNickname()
1626{
1627 D_PTR(const DefaultDevice);
1628 return d->deviceNickname.c_str();
1629}
1630
1631const char *DefaultDevice::lookupDeviceNicknameFromId(const char *identifier, const char *device)
1632{
1633 D_PTR(DefaultDevice);
1634 if (!identifier)
1635 return nullptr;
1636
1637 if (device)
1638 {
1639 // NOT IMPLEMENTED
1640 // Any need to lookup nicknames for other devices? (maybe to help
1641 // migration?)
1642 return "";
1643 }
1644
1645 std::string id{identifier};
1646
1647 if (!d->nicknamesLoaded)
1648 d->loadINDINicknamesXML(getDefaultName());
1649
1650 auto search = d->nicknameMap.find(id);
1651 if (search != d->nicknameMap.end())
1652 return search->second.c_str();
1653
1654 return "";
1655}
1656
1657bool DefaultDevice::setDeviceNicknameFromId(const char *identifier)
1658{
1659 const char *nick = lookupDeviceNicknameFromId(identifier);
1660 bool hasNickname = (nick && *nick);
1661 setDeviceNickname(nick);
1662 return hasNickname;
1663}
1664
1665void DefaultDevice::saveNicknameId(const char *nickname, const char *identifier)
1666{
1667 D_PTR(DefaultDevice);
1668 if (!identifier)
1669 return;
1670
1671 std::string id{identifier};
1672 trim(id);
1673 std::string nick = (nickname) ? nickname : d->deviceNickname;
1674 if (nick.empty() || id.empty())
1675 return;
1676
1677 if (!d->nicknamesLoaded)
1678 d->loadINDINicknamesXML(getDefaultName());
1679
1680 d->nicknameMap[id] = nick;
1681 d->saveINDINicknamesXML(getDefaultName());
1682}
1683
1684void DefaultDevice::setVersion(uint16_t vMajor, uint16_t vMinor)
1685{
1686 D_PTR(DefaultDevice);
1687 d->majorVersion = vMajor;
1688 d->minorVersion = vMinor;
1689}
1690
1691uint16_t DefaultDevice::getMajorVersion() const
1692{
1693 D_PTR(const DefaultDevice);
1694 return d->majorVersion;
1695}
1696
1697uint16_t DefaultDevice::getMinorVersion() const
1698{
1699 D_PTR(const DefaultDevice);
1700 return d->minorVersion;
1701}
1702
1703void DefaultDevice::setDynamicPropertiesBehavior(bool defineEnabled, bool deleteEnabled)
1704{
1705 D_PTR(DefaultDevice);
1706 d->defineDynamicProperties = defineEnabled;
1707 d->deleteDynamicProperties = deleteEnabled;
1708}
1709
1710Connection::Interface *DefaultDevice::getActiveConnection()
1711{
1712 D_PTR(DefaultDevice);
1713 return d->activeConnection;
1714}
1715
1716uint32_t DefaultDevice::getPollingPeriod() const
1717{
1718 D_PTR(const DefaultDevice);
1719 return static_cast<uint32_t>(d->PollPeriodNP[0].getValue());
1720}
1721
1722bool DefaultDevice::isConfigLoading() const
1723{
1724 D_PTR(const DefaultDevice);
1725 return d->isConfigLoading;
1726}
1727
1728bool DefaultDevice::isInitializationComplete() const
1729{
1730 D_PTR(const DefaultDevice);
1731 return d->isInit;
1732}
1733
1734bool DefaultDevice::ISNewProperty(INDI::Property &property, const std::string &elementName, const std::any &value)
1735{
1736 if (!property.isValid() || elementName.empty())
1737 {
1738 LOGF_WARN("ISNewProperty: Invalid property or empty element name for device '%s'.", getDeviceName());
1739 return false;
1740 }
1741
1742 char mutableElementName[MAXINDINAME]; // Use MAXINDINAME from indicom.h
1743 strncpy(mutableElementName, elementName.c_str(), sizeof(mutableElementName) - 1);
1744 mutableElementName[sizeof(mutableElementName) - 1] = '\0'; // Ensure null termination
1745 char *names[] = { mutableElementName };
1746
1747 switch (property.getType())
1748 {
1749 case INDI_SWITCH:
1750 {
1751 try
1752 {
1753 ISState newState = std::any_cast<ISState>(value);
1754 ISState states[] = { newState };
1755 return ISNewSwitch(getDeviceName(), property.getName(), states, names, 1);
1756 }
1757 catch (const std::bad_any_cast &e)
1758 {
1759 LOGF_ERROR("ISNewProperty: Type mismatch for Switch property '%s'. Expected ISState. Got: %s",
1760 property.getName(), e.what());
1761 return false;
1762 }
1763 }
1764 case INDI_NUMBER:
1765 {
1766 try
1767 {
1768 double newValue = 0;
1769 if (value.type() == typeid(int))
1770 {
1771 newValue = static_cast<double>(std::any_cast<int>(value));
1772 }
1773 else if (value.type() == typeid(long))
1774 {
1775 newValue = static_cast<double>(std::any_cast<long>(value));
1776 }
1777 else if (value.type() == typeid(double))
1778 {
1779 newValue = std::any_cast<double>(value);
1780 }
1781 else
1782 {
1783 throw std::bad_any_cast();
1784 }
1785
1786 double values[] = { newValue };
1787 return ISNewNumber(getDeviceName(), property.getName(), values, names, 1);
1788 }
1789 catch (const std::bad_any_cast &e)
1790 {
1791 LOGF_ERROR("ISNewProperty: Type mismatch for Number property '%s'. Expected double, int, or long. Got: %s",
1792 property.getName(), e.what());
1793 return false;
1794 }
1795 }
1796 case INDI_TEXT:
1797 {
1798 try
1799 {
1800 char mutableTextValue[1024]; // Use 1024 for text buffer
1801 memset(mutableTextValue, 0, sizeof(mutableTextValue));
1802
1803 if (value.type() == typeid(const char *))
1804 {
1805 const char *text = std::any_cast<const char *>(value);
1806 if (text) strncpy(mutableTextValue, text, sizeof(mutableTextValue) - 1);
1807 }
1808 else if (value.type() == typeid(std::string))
1809 {
1810 const std::string &text = std::any_cast<std::string>(value);
1811 strncpy(mutableTextValue, text.c_str(), sizeof(mutableTextValue) - 1);
1812 }
1813 else
1814 {
1815 throw std::bad_any_cast();
1816 }
1817 mutableTextValue[sizeof(mutableTextValue) - 1] = '\0'; // Ensure null termination
1818
1819 char *texts[] = { mutableTextValue };
1820 return ISNewText(getDeviceName(), property.getName(), texts, names, 1);
1821 }
1822 catch (const std::bad_any_cast &e)
1823 {
1824 LOGF_ERROR("ISNewProperty: Type mismatch for Text property '%s'. Expected const char* or std::string. Got: %s",
1825 property.getName(), e.what());
1826 return false;
1827 }
1828 }
1829 default:
1830 LOGF_WARN("ISNewProperty: Unsupported property type for property '%s'.", property.getName());
1831 return false;
1832 }
1833 return false;
1834}
1835
1836}
hid_device * device
The Interface class is the base class for all INDI connection plugins.
Class to provide basic INDI device functionality.
Definition basedevice.h:52
INDI::PropertyNumber getNumber(const char *name) const
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
void setDeviceName(const char *dev)
Set the device name.
void registerProperty(const INDI::Property &property)
Register the property to be able to observe and update.
INDI::PropertyText getText(const char *name) const
Properties getProperties()
Return a list of all properties in the device.
int loadINDINicknamesXML(const char *devicename)
int saveINDINicknamesXML(const char *devicename)
Save Nickname data to XML.
static std::list< DefaultDevicePrivate * > devices
static std::recursive_mutex devicesLock
std::map< std::string, std::string > nicknameMap
DefaultDevicePrivate(DefaultDevice *defaultDevice)
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
void addPollPeriodControl()
Add Polling period control to the driver.
virtual bool purgeConfig()
purgeConfig Remove config file from disk.
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void setSimulation(bool enable)
Toggle driver simulation status A driver can run in simulation mode if Simulation option is enabled b...
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void addConfigurationControl()
Add Configuration control to the driver.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void addSimulationControl()
Add Simulation control to the driver.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual void setConnected(bool status, IPState state=IPS_OK, const char *msg=nullptr)
Set connection switch status in the client.
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
void watchDevice(const char *deviceName, const std::function< void(INDI::BaseDevice)> &callback)
Add a device to the watch list.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
virtual void simulationTriggered(bool enable)
Inform driver that the simulation option was triggered. This function is called after setSimulation i...
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual void TimerHit()
Callback function to be called once SetTimer duration elapses.
virtual const char * getDefaultName()=0
void setDebug(bool enable)
Toggle driver debug status A driver can be more verbose if Debug option is enabled by the client.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
bool isSimulation() const
void syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool saveAllConfigItems(FILE *fp)
saveAllConfigItems Save all the drivers' properties in the configuration file
uint32_t getDriverInterface() const
virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Process the client newBLOB command.
virtual bool loadDefaultConfig()
Load the default configuration file.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void resetProperties()
Set all properties to IDLE state.
virtual void debugTriggered(bool enable)
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
void RemoveTimer(int id)
Remove timer added with SetTimer.
void addNicknameControl()
Add Nickname text property to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual void nicknameSet(const char *nickname)
Inform driver that the nickname has been set.
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
The class is used to create device instances. Class copying is not allowed. When an object is destroy...
Provides generic container for INDI properties.
bool isValid() const
const char * getName() const
bool isDynamic() const
bool getRegistered() const
INDI_PROPERTY_TYPE getType() const
#define NICK_ATTR_NAME
void(* WeakIDSetBLOBVA)(const IBLOBVectorProperty *, const char *, va_list)
const char * GUIDE_TAB
GUIDE_TAB Where all the properties for guiding are located.
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
int(* WeakIUUpdateText)(ITextVectorProperty *, char *[], char *[], int)
int(* WeakIUUpdateNumber)(INumberVectorProperty *, double[], char *[], int n)
void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Update data of an existing blob vector property.
#define NICKNAME_FILE
int(* WeakIUUpdateBLOB)(IBLOBVectorProperty *, int[], int[], char *[], char *[], char *[], int n)
void(* WeakIDSetLightVA)(const ILightVectorProperty *, const char *, va_list)
void(* WeakIDDefSwitchVA)(const ISwitchVectorProperty *, const char *, va_list)
void(* WeakIDDefTextVA)(const ITextVectorProperty *, const char *, va_list)
const char * FILTER_TAB
FILTER_TAB Where all the properties for filter wheels are located.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
void(* WeakIDSetTextVA)(const ITextVectorProperty *, const char *, va_list)
void(* WeakIDDefBLOBVA)(const IBLOBVectorProperty *, const char *, va_list)
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Update the value of an existing switch vector property.
const char * DATETIME_TAB
DATETIME_TAB Where all date and time setting properties are located.
void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Update the value of an existing text vector property.
const char * SATELLITE_TAB
SATELLITE_TAB.
#define NICK_TAG_ENTRY
const char * COMMUNICATION_TAB
COMMUNICATION_TAB Where all the properties required to connect/disconnect from a device are located....
const char * INFO_TAB
INFO_TAB Where all the properties for general information are located.
#define NICK_TAG_ROOT
void ISGetProperties(const char *dev)
Get Device Properties.
void(* WeakIDDefNumberVA)(const INumberVectorProperty *, const char *, va_list)
#define NICK_ATTR_ID
void timerfunc(void *t)
void(* WeakIDDefLightVA)(const ILightVectorProperty *, const char *, va_list)
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
int(* WeakIUUpdateSwitch)(ISwitchVectorProperty *, ISState *, char *[], int n)
const char * ALIGNMENT_TAB
ALIGNMENT_TAB Where all the properties for guiding are located.
void ISSnoopDevice(XMLEle *root)
Function defined by Drivers that is called when another Driver it is snooping (by having previously c...
void(* WeakIDSetNumberVA)(const INumberVectorProperty *, const char *, va_list)
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
void(* WeakIDSetSwitchVA)(const ISwitchVectorProperty *, const char *, va_list)
void(* WeakIUUpdateMinMax)(const INumberVectorProperty *)
#define NICK_TAG_DEVICE
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * FOCUS_TAB
FOCUS_TAB Where all the properties for focuser are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
Constants and Data structure definitions for the interface to the reference INDI C API implementation...
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_ATMOST1
Definition indiapi.h:172
#define MAXINDINAME
Definition indiapi.h:189
Implementations for common driver routines.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int IUUpdateBLOB(IBLOBVectorProperty *bvp, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
Update all BLOB members in a BLOB vector property.
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetNumberVA(const INumberVectorProperty *nvp, const char *fmt, va_list ap)
void IDDefSwitchVA(const ISwitchVectorProperty *svp, const char *fmt, va_list ap)
void IDDefNumberVA(const INumberVectorProperty *nvp, const char *fmt, va_list ap)
FILE * IUGetConfigFP(const char *filename, const char *dev, const char *mode, char errmsg[])
Open a configuration file for writing and return a configuration file FILE pointer.
void IDDelete(const char *dev, const char *name, const char *fmt,...)
Definition indidriver.c:132
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
void IDDefTextVA(const ITextVectorProperty *tvp, const char *fmt, va_list ap)
void IDSetBLOBVA(const IBLOBVectorProperty *bvp, const char *fmt, va_list ap)
#define MAXRBUF
Definition indidriver.c:52
void IUSaveConfigTag(FILE *fp, int ctag, const char *dev, int silent)
Add opening or closing tag to a configuration file. A configuration file root XML element is <INDIDri...
@ INDI_TEXT
Definition indidriver.c:59
@ INDI_NUMBER
Definition indidriver.c:57
@ INDI_SWITCH
Definition indidriver.c:58
@ INDI_BLOB
Definition indidriver.c:61
void IDSetTextVA(const ITextVectorProperty *tvp, const char *fmt, va_list ap)
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition indidriver.c:143
int IUReadConfig(const char *filename, const char *dev, const char *property, int silent, char errmsg[])
Loads and processes a configuration file. Once a configuration file is successful loaded,...
Definition indidriver.c:485
void IDDefLightVA(const ILightVectorProperty *lvp, const char *fmt, va_list ap)
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
int IUPurgeConfig(const char *filename, const char *dev, char errmsg[])
Definition indidriver.c:991
void IDDefBLOBVA(const IBLOBVectorProperty *bvp, const char *fmt, va_list ap)
int IUSaveDefaultConfig(const char *source_config, const char *dest_config, const char *dev)
Copies an existing configuration file into a default configuration file. If no default configuration ...
Definition indidriver.c:542
int IUGetConfigOnSwitchIndex(const char *dev, const char *property, int *index)
IUGetConfigOnSwitchIndex Opens configuration file and reads single switch property to find ON switch ...
Definition indidriver.c:719
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
void IDSetSwitchVA(const ISwitchVectorProperty *svp, const char *fmt, va_list ap)
char * me
Definition indidriver.c:50
void IDSetLightVA(const ILightVectorProperty *lvp, const char *fmt, va_list ap)
#define DEBUG_CONF(outputFile, configuration, fileVerbosityLevel, screenVerbosityLevel)
Macro to configure the logger. Example of configuration of the Logger: DEBUG_CONF("outputfile",...
Definition indilogger.h:38
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define DEBUGF(priority, msg,...)
Definition indilogger.h:57
#define getDeviceName
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
LilXML * newLilXML()
Create a new lilxml parser.
Definition lilxml.cpp:150
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition lilxml.cpp:600
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
Definition lilxml.cpp:670
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
Definition lilxml.cpp:698
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition lilxml.cpp:844
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition lilxml.cpp:159
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition lilxml.cpp:706
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
Definition lilxml.cpp:653
#define XML_ERROR_SIZE
Definition lilxml.h:73
const char * CONNECTION
Connect to and disconnect from device.
Namespace to encapsulate INDI client, drivers, and mediator classes.
static const loggerConf screen_on
Definition indilogger.h:220
static const loggerConf file_off
Definition indilogger.h:219
static bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
static bool initProperties(INDI::DefaultDevice *device)
static bool updateProperties(bool enable)
static bool saveConfigItems(FILE *fp)
static std::string getLogFile()
Definition indilogger.h:225
static const unsigned int defaultlevel
Definition indilogger.h:210
bool contains(const std::string &key, const ISState &state) const
BLOB (Binary Large Object) vector property descriptor.
Definition indiapi.h:469
Light vector property descriptor.
Definition indiapi.h:415
Number vector property descriptor.
Definition indiapi.h:317
Switch vector property descriptor.
Definition indiapi.h:365
Text vector property descriptor.
Definition indiapi.h:244