1 /* INDI frontend for KStars
2 Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com)
5 This application is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
11 2003-04-28 Used indimenu.c as a template. C --> C++, Xm --> KDE/Qt
12 2003-05-01 Added tab for devices and a group feature
13 2003-05-02 Added scrolling area. Most things are rewritten
14 2003-05-05 Device/Group seperation
15 2003-05-29 Replaced raw INDI time with KStars's timedialog
16 2003-08-02 Upgrading to INDI v 1.11
17 2003-08-09 Initial support for non-sidereal tracking
18 2004-01-15 redesigning the GUI to support INDI v1.2 and fix previous GUI bugs
19 and problems. The new GUI can easily incoperate extensions to the INDI
24 #include "indiproperty.h"
25 #include "indigroup.h"
26 #include "indidevice.h"
27 #include "devicemanager.h"
29 #include "indidriver.h"
31 #include "indi/indicom.h"
33 #include "skyobject.h"
34 #include "timedialog.h"
35 #include "geolocation.h"
36 #include "indi/base64.h"
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
47 #include <qlineedit.h>
48 #include <qtextedit.h>
50 #include <qtabwidget.h>
51 #include <qcheckbox.h>
53 #include <qpushbutton.h>
56 #include <qwhatsthis.h>
57 #include <qbuttongroup.h>
58 #include <qscrollview.h>
59 #include <qsocketnotifier.h>
61 #include <qdatetime.h>
67 #include <klineedit.h>
68 #include <kpushbutton.h>
69 #include <kapplication.h>
71 #include <kmessagebox.h>
72 #include <klistview.h>
74 #include <kcombobox.h>
75 #include <knuminput.h>
76 #include <kdialogbase.h>
77 #include <kstatusbar.h>
78 #include <kpopupmenu.h>
81 /* INDI standard property used across all clients to enable interoperability. */
82 const char * indi_std
[NINDI_STD
] =
83 {"CONNECTION", "EQUATORIAL_COORD", "EQUATORIAL_EOD_COORD", "ON_COORD_SET", "ABORT_MOTION", "SOLAR_SYSTEM",
84 "GEOGRAPHIC_COORD", "HORIZONTAL_COORD", "TIME", "EXPOSE_DURATION", "DEVICE_PORT", "PARK", "MOVEMENT", "SDTIME", "DATA_CHANNEL", "VIDEO_STREAM", "IMAGE_SIZE", "FILTER_CONF"};
86 /*******************************************************************
87 ** INDI Device: The work-horse. Responsible for handling its
88 ** child properties and managing signal and changes.
89 *******************************************************************/
90 INDI_D::INDI_D(INDIMenu
*menuParent
, DeviceManager
*parentManager
, QString inName
, QString inLabel
)
95 parentMgr
= parentManager
;
97 gl
.setAutoDelete(true);
99 deviceVBox
= menuParent
->addVBoxPage(inLabel
);
100 groupContainer
= new QTabWidget(deviceVBox
);
102 msgST_w
= new QTextEdit(deviceVBox
);
103 msgST_w
->setReadOnly(true);
104 msgST_w
->setMaximumHeight(100);
106 dataBuffer
= (unsigned char *) malloc (1);
108 stdDev
= new INDIStdDevice(this, parent
->ksw
);
112 INDIStdSupport
= false;
127 void INDI_D::registerProperty(INDI_P
*pp
)
131 pp
->pg
->dp
->INDIStdSupport
= true;
133 stdDev
->registerProperty(pp
);
137 bool INDI_D::isINDIStd(INDI_P
*pp
)
139 for (uint i
=0; i
< NINDI_STD
; i
++)
140 if (!strcmp(pp
->name
.ascii(), indi_std
[i
]))
149 /* Remove a property from a group, if there are no more properties
150 * left in the group, then delete the group as well */
151 int INDI_D::removeProperty(INDI_P
*pp
)
153 for (unsigned int i
=0; i
< gl
.count(); i
++)
154 if (gl
.at(i
)->removeProperty(pp
))
156 if (gl
.at(i
)->pl
.count() == 0)
162 kdDebug() << "INDI: Device " << name
<< " has no property named " << pp
->name
<< endl
;
166 /* implement any <set???> received from the device.
167 * return 0 if ok, else -1 with reason in errmsg[]
169 int INDI_D::setAnyCmd (XMLEle
*root
, char errmsg
[])
174 ap
= findAtt (root
, "name", errmsg
);
178 pp
= findProp (valuXMLAtt(ap
));
181 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.32s> device %.32s has no property named %.64s",
182 tagXMLEle(root
), name
.ascii(), valuXMLAtt(ap
));
186 parentMgr
->checkMsg (root
, this);
188 return (setValue (pp
, root
, errmsg
));
191 /* set the given GUI property according to the XML command.
192 * return 0 if ok else -1 with reason in errmsg
194 int INDI_D::setValue (INDI_P
*pp
, XMLEle
*root
, char errmsg
[])
198 /* set overall property state, if any */
199 ap
= findXMLAtt (root
, "state");
202 if (crackLightState (valuXMLAtt(ap
), &pp
->state
) == 0)
203 pp
->drawLt (pp
->state
);
206 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> bogus state %.64s for %.64s %.64s",
207 tagXMLEle(root
), valuXMLAtt(ap
), name
.ascii(), pp
->name
.ascii());
212 /* allow changing the timeout */
213 ap
= findXMLAtt (root
, "timeout");
215 pp
->timeout
= atof(valuXMLAtt(ap
));
217 /* process specific GUI features */
223 case PG_NUMERIC
: /* FALLTHRU */
225 return (setTextValue (pp
, root
, errmsg
));
232 return (setLabelState (pp
, root
, errmsg
));
236 return (setBLOB(pp
, root
, errmsg
));
247 /* set the given TEXT or NUMERIC property from the given element.
248 * root should have <text> or <number> child.
249 * return 0 if ok else -1 with reason in errmsg
251 int INDI_D::setTextValue (INDI_P
*pp
, XMLEle
*root
, char errmsg
[])
260 for (ep
= nextXMLEle (root
, 1); ep
!= NULL
; ep
= nextXMLEle (root
, 0))
262 if (strcmp (tagXMLEle(ep
), "oneText") && strcmp(tagXMLEle(ep
), "oneNumber"))
265 ap
= findXMLAtt(ep
, "name");
268 kdDebug() << "Error: unable to find attribute 'name' for property " << pp
->name
<< endl
;
272 elementName
= valuXMLAtt(ap
);
274 lp
= pp
->findElement(elementName
);
278 snprintf(errmsg
, ERRMSG_SIZE
, "Error: unable to find element '%.64s' in property '%.64s'", elementName
.ascii(), pp
->name
.ascii());
282 //fprintf(stderr, "tag okay, getting perm\n");
285 case PP_RW
: // FALLTHRU
287 if (pp
->guitype
== PG_TEXT
)
289 lp
->text
= QString(pcdataXMLEle(ep
));
290 lp
->read_w
->setText(lp
->text
);
292 else if (pp
->guitype
== PG_NUMERIC
)
294 lp
->value
= atof(pcdataXMLEle(ep
));
295 numberFormat(iNumber
, lp
->format
.ascii(), lp
->value
);
297 lp
->read_w
->setText(lp
->text
);
299 ap
= findXMLAtt (ep
, "min");
300 if (ap
) { min
= atof(valuXMLAtt(ap
)); lp
->setMin(min
); }
301 ap
= findXMLAtt (ep
, "max");
302 if (ap
) { max
= atof(valuXMLAtt(ap
)); lp
->setMax(max
); }
306 lp->spin_w->setValue(lp->value);
307 lp->spinChanged(lp->value);
314 if (pp
->guitype
== PG_TEXT
)
315 lp
->write_w
->setText(QString(pcdataXMLEle(ep
)));
316 else if (pp
->guitype
== PG_NUMERIC
)
318 lp
->value
= atof(pcdataXMLEle(ep
));
319 numberFormat(iNumber
, lp
->format
.ascii(), lp
->value
);
323 lp
->spin_w
->setValue(lp
->value
);
325 lp
->write_w
->setText(lp
->text
);
327 ap
= findXMLAtt (ep
, "min");
328 if (ap
) { min
= (int) atof(valuXMLAtt(ap
)); lp
->setMin(min
); }
329 ap
= findXMLAtt (ep
, "max");
330 if (ap
) { max
= (int) atof(valuXMLAtt(ap
)); lp
->setMax(max
); }
337 /* handle standard cases if needed */
338 stdDev
->setTextValue(pp
);
346 /* set the given BUTTONS or LIGHTS property from the given element.
347 * root should have some <switch> or <light> children.
348 * return 0 if ok else -1 with reason in errmsg
350 int INDI_D::setLabelState (INDI_P
*pp
, XMLEle
*root
, char errmsg
[])
360 /* for each child element */
361 for (ep
= nextXMLEle (root
, 1), i
=0; ep
!= NULL
; ep
= nextXMLEle (root
, 0), i
++)
364 /* only using light and switch */
365 islight
= !strcmp (tagXMLEle(ep
), "oneLight");
366 if (!islight
&& strcmp (tagXMLEle(ep
), "oneSwitch"))
369 ap
= findXMLAtt (ep
, "name");
373 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> %.64s %.64s %.64s requires name",
374 tagXMLEle(root
), name
.ascii(), pp
->name
.ascii(), tagXMLEle(ep
));
378 if ((islight
&& crackLightState (pcdataXMLEle(ep
), &state
) < 0)
379 || (!islight
&& crackSwitchState (pcdataXMLEle(ep
), &state
) < 0))
381 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> unknown state %.64s for %.64s %.64s %.64s",
382 tagXMLEle(root
), pcdataXMLEle(ep
), name
.ascii(), pp
->name
.ascii(), tagXMLEle(ep
));
386 /* find matching label */
387 //fprintf(stderr, "Find matching label. Name from XML is %s\n", valuXMLAtt(ap));
388 lp
= pp
->findElement(QString(valuXMLAtt(ap
)));
392 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> %.64s %.64s has no choice named %.64s",
393 tagXMLEle(root
), name
.ascii(), pp
->name
.ascii(), valuXMLAtt(ap
));
398 /* engage new state */
407 lp
->push_w
->setDown(state
== PS_ON
? true : false);
408 buttonFont
= lp
->push_w
->font();
409 buttonFont
.setBold(state
== PS_ON
? TRUE
: FALSE
);
410 lp
->push_w
->setFont(buttonFont
);
415 lp
->check_w
->setChecked(state
== PS_ON
? true : false);
422 snprintf(errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> %.64s %.64s has multiple ON states", tagXMLEle(root
), name
.ascii(), pp
->name
.ascii());
426 pp
->om_w
->setCurrentItem(i
);
440 stdDev
->setLabelState(pp
);
445 /* Set BLOB vector. Process incoming data stream
446 * Return 0 if okay, -1 if error
448 int INDI_D::setBLOB(INDI_P
*pp
, XMLEle
* root
, char errmsg
[])
454 for (ep
= nextXMLEle(root
,1); ep
; ep
= nextXMLEle(root
,0))
457 if (strcmp(tagXMLEle(ep
), "oneBLOB") == 0)
460 blobEL
= pp
->findElement(QString(findXMLAttValu (ep
, "name")));
463 return processBlob(blobEL
, ep
, errmsg
);
466 sprintf (errmsg
, "INDI: set %64s.%64s.%64s not found", name
.ascii(), pp
->name
.ascii(), findXMLAttValu(ep
, "name"));
476 /* Process incoming data stream
477 * Return 0 if okay, -1 if error
479 int INDI_D::processBlob(INDI_E
*blobEL
, XMLEle
*ep
, char errmsg
[])
482 int blobSize
=0, r
=0, dataType
=0;
486 unsigned char *blobBuffer(NULL
);
489 ap
= findXMLAtt(ep
, "size");
492 sprintf (errmsg
, "INDI: set %64s size not found", blobEL
->name
.ascii());
496 dataSize
= atoi(valuXMLAtt(ap
));
498 ap
= findXMLAtt(ep
, "format");
501 sprintf (errmsg
, "INDI: set %64s format not found", blobEL
->name
.ascii());
505 dataFormat
= QString(valuXMLAtt(ap
));
507 baseBuffer
= (char *) malloc ( (3*pcdatalenXMLEle(ep
)/4) * sizeof (char));
508 blobSize
= from64tobits (baseBuffer
, pcdataXMLEle(ep
));
509 blobBuffer
= (unsigned char *) baseBuffer
;
511 /* Blob size = 0 when only state changes */
517 else if (blobSize
< 0)
520 sprintf (errmsg
, "INDI: %64s.%64s.%64s bad base64", name
.ascii(), blobEL
->pp
->name
.ascii(), blobEL
->name
.ascii());
524 iscomp
= (dataFormat
.find(".z") != -1);
526 dataFormat
.remove(".z");
528 if (dataFormat
== ".fits") dataType
= DATA_FITS
;
529 else if (dataFormat
== ".stream") dataType
= DATA_STREAM
;
530 else dataType
= DATA_OTHER
;
532 //kdDebug() << "We're getting data with size " << dataSize << endl;
533 //kdDebug() << "data format " << dataFormat << endl;
538 dataBuffer
= (unsigned char *) realloc (dataBuffer
, (dataSize
* sizeof(unsigned char)));
539 r
= uncompress(dataBuffer
, &dataSize
, blobBuffer
, (uLong
) blobSize
);
542 sprintf(errmsg
, "INDI: %64s.%64s.%64s compression error: %d", name
.ascii(), blobEL
->pp
->name
.ascii(), blobEL
->name
.ascii(), r
);
547 //kdDebug() << "compressed" << endl;
551 //kdDebug() << "uncompressed!!" << endl;
552 dataBuffer
= (unsigned char *) realloc (dataBuffer
, (dataSize
* sizeof(unsigned char)));
553 memcpy(dataBuffer
, blobBuffer
, dataSize
);
556 stdDev
->handleBLOB(dataBuffer
, dataSize
, dataType
);
569 prop
= findProp(QString("CONNECTION"));
573 return (prop
->isOn(QString("CONNECT")));
576 INDI_P
* INDI_D::addProperty (XMLEle
*root
, char errmsg
[])
582 // Search for group tag
583 ap
= findAtt (root
, "group", errmsg
);
586 kdDebug() << QString(errmsg
) << endl
;
589 // Find an existing group, if none found, create one
590 pg
= findGroup(QString(valuXMLAtt(ap
)), 1, errmsg
);
595 /* get property name and add new property to dp */
596 ap
= findAtt (root
, "name", errmsg
);
600 if (findProp (valuXMLAtt(ap
)))
602 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s %.64s %.64s> already exists.\n", tagXMLEle(root
),
603 name
.ascii(), valuXMLAtt(ap
));
607 /* Remove Vertical spacer from group layout, this is done everytime
608 * a new property arrives. The spacer is then appended to the end of the
610 pg
->propertyLayout
->removeItem(pg
->VerticalSpacer
);
612 pp
= new INDI_P(pg
, QString(valuXMLAtt(ap
)));
615 ap
= findAtt (root
, "state", errmsg
);
622 if (crackLightState (valuXMLAtt(ap
), &pp
->state
) < 0)
624 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> bogus state %.64s for %.64s %.64s",
625 tagXMLEle(root
), valuXMLAtt(ap
), pp
->pg
->dp
->name
.ascii(), pp
->name
.ascii());
631 ap
= findAtt (root
, "timeout", NULL
);
633 pp
->timeout
= ap
? atof(valuXMLAtt(ap
)) : 0;
635 /* log any messages */
636 parentMgr
->checkMsg (root
, this);
644 INDI_P
* INDI_D::findProp (QString name
)
646 for (unsigned int i
= 0; i
< gl
.count(); i
++)
647 for (unsigned int j
= 0; j
< gl
.at(i
)->pl
.count(); j
++)
648 if (name
== gl
.at(i
)->pl
.at(j
)->name
)
649 return (gl
.at(i
)->pl
.at(j
));
654 INDI_G
* INDI_D::findGroup (QString grouptag
, int create
, char errmsg
[])
658 for (ig
= gl
.first(); ig
!= NULL
; ig
= gl
.next() )
659 if (ig
->name
== grouptag
)
665 /* couldn't find an existing group, create a new one if create is 1*/
668 if (grouptag
.isEmpty())
669 grouptag
= "Group_1";
671 curGroup
= new INDI_G(this, grouptag
);
676 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: group %.64s not found in %.64s", grouptag
.ascii(), name
.ascii());
681 /* find "perm" attribute in root, crack and set *pp.
682 * return 0 if ok else -1 with excuse in errmsg[]
685 int INDI_D::findPerm (INDI_P
*pp
, XMLEle
*root
, PPerm
*permp
, char errmsg
[])
689 ap
= findXMLAtt(root
, "perm");
691 snprintf (errmsg
, ERRMSG_SIZE
,"INDI: <%.64s %.64s %.64s> missing attribute 'perm'",
692 tagXMLEle(root
), pp
->pg
->dp
->name
.ascii(), pp
->name
.ascii());
695 if (!strcmp(valuXMLAtt(ap
), "ro") || !strcmp(valuXMLAtt(ap
), "r"))
697 else if (!strcmp(valuXMLAtt(ap
), "wo"))
699 else if (!strcmp(valuXMLAtt(ap
), "rw") || !strcmp(valuXMLAtt(ap
), "w"))
702 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> unknown perm %.64s for %.64s %.64s",
703 tagXMLEle(root
), valuXMLAtt(ap
), pp
->pg
->dp
->name
.ascii(), pp
->name
.ascii());
710 /* convert the given light/property state string to the PState at psp.
711 * return 0 if successful, else -1 and leave *psp unchanged.
713 int INDI_D::crackLightState (char *name
, PState
*psp
)
729 for (int i
= 0; i
< 4; i
++)
730 if (!strcmp (psmap
[i
].name
, name
)) {
738 /* convert the given switch state string to the PState at psp.
739 * return 0 if successful, else -1 and leave *psp unchanged.
741 int INDI_D::crackSwitchState (char *name
, PState
*psp
)
756 for (int i
= 0; i
< 2; i
++)
757 if (!strcmp (psmap
[i
].name
, name
))
766 int INDI_D::buildTextGUI(XMLEle
*root
, char errmsg
[])
771 /* build a new property */
772 pp
= addProperty (root
, errmsg
);
777 /* get the permission, it will determine layout issues */
778 if (findPerm (pp
, root
, &p
, errmsg
))
784 /* we know it will be a general text GUI */
785 pp
->guitype
= PG_TEXT
;
788 if (pp
->buildTextGUI(root
, errmsg
) < 0)
794 pp
->pg
->addProperty(pp
);
799 /* build GUI for a number property.
800 * return 0 if ok, else -1 with reason in errmsg[]
802 int INDI_D::buildNumberGUI (XMLEle
*root
, char *errmsg
)
807 /* build a new property */
808 pp
= addProperty (root
, errmsg
);
813 /* get the permission, it will determine layout issues */
814 if (findPerm (pp
, root
, &p
, errmsg
))
820 /* we know it will be a number GUI */
821 pp
->guitype
= PG_NUMERIC
;
824 if (pp
->buildNumberGUI(root
, errmsg
) < 0)
830 pp
->pg
->addProperty(pp
);
835 /* build GUI for switches property.
836 * rule and number of will determine exactly how the GUI is built.
837 * return 0 if ok, else -1 with reason in errmsg[]
839 int INDI_D::buildSwitchesGUI (XMLEle
*root
, char errmsg
[])
846 /* build a new property */
847 pp
= addProperty (root
, errmsg
);
851 ap
= findAtt (root
, "rule", errmsg
);
858 /* decide GUI. might use MENU if OneOf but too many for button array */
859 if (!strcmp (valuXMLAtt(ap
), "OneOfMany") || !strcmp (valuXMLAtt(ap
), "AtMostOne"))
861 /* count number of switches -- make menu if too many */
862 for ( ep
= nextXMLEle(root
, 1) , n
= 0 ; ep
!= NULL
; ep
= nextXMLEle(root
, 0))
863 if (!strcmp (tagXMLEle(ep
), "defSwitch"))
868 pp
->guitype
= PG_MENU
;
869 err
= pp
->buildMenuGUI (root
, errmsg
);
873 pp
->pg
->addProperty(pp
);
877 /* otherwise, build 1-4 button layout */
878 pp
->guitype
= PG_BUTTONS
;
880 err
= pp
->buildSwitchesGUI(root
, errmsg
);
884 pp
->pg
->addProperty(pp
);
888 else if (!strcmp (valuXMLAtt(ap
), "AnyOfMany"))
890 /* 1-4 checkboxes layout */
891 pp
->guitype
= PG_RADIO
;
893 err
= pp
->buildSwitchesGUI(root
, errmsg
);
897 pp
->pg
->addProperty(pp
);
901 snprintf (errmsg
, ERRMSG_SIZE
, "INDI: <%.64s> unknown rule %.64s for %.64s %.64s",
902 tagXMLEle(root
), valuXMLAtt(ap
), name
.ascii(), pp
->name
.ascii());
910 /* build GUI for a lights GUI.
911 * return 0 if ok, else -1 with reason in errmsg[] */
912 int INDI_D::buildLightsGUI (XMLEle
*root
, char errmsg
[])
916 // build a new property
917 pp
= addProperty (root
, errmsg
);
921 pp
->guitype
= PG_LIGHTS
;
923 if (pp
->buildLightsGUI(root
, errmsg
) < 0)
929 pp
->pg
->addProperty(pp
);
933 /* build GUI for a BLOB GUI.
934 * return 0 if ok, else -1 with reason in errmsg[] */
935 int INDI_D::buildBLOBGUI (XMLEle
*root
, char errmsg
[])
940 // build a new property
941 pp
= addProperty (root
, errmsg
);
945 /* get the permission, it will determine layout issues */
946 if (findPerm (pp
, root
, &p
, errmsg
))
952 /* we know it will be a number GUI */
954 pp
->guitype
= PG_BLOB
;
956 if (pp
->buildBLOBGUI(root
, errmsg
) < 0)
962 pp
->pg
->addProperty(pp
);
966 INDI_E
* INDI_D::findElem(QString name
)
972 for (grp
= gl
.first(); grp
!= NULL
; grp
= gl
.next())
974 for (prop
= grp
->pl
.first(); prop
!= NULL
; prop
= grp
->pl
.next())
976 el
= prop
->findElement(name
);
977 if (el
!= NULL
) return el
;
986 #include "indidevice.moc"