Instrument Neutral Distributed Interface INDI 2.2.5
setINDIproperty.c
Go to the documentation of this file.
1/* connect to an INDI server and set one or more device.property.element.
2 */
3
4#define _GNU_SOURCE // needed for fdopen
5
6#include "indiapi.h"
7#include "indidevapi.h"
8#include "lilxml.h"
9
10#include <errno.h>
11#include <math.h>
12#include <netdb.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <signal.h>
17#include <time.h>
18#include <unistd.h>
19#include <netinet/in.h>
20#include <sys/types.h>
21#include <sys/socket.h>
22
23/* table of INDI definition elements we can set
24 * N.B. do not change defs[] order, they are indexed via -x/-n/-s args
25 */
26typedef struct
27{
28 char *defType; /* defXXXVector name */
29 char *defOne; /* defXXX name */
30 char *newType; /* newXXXVector name */
31 char *oneType; /* oneXXX name */
32} INDIDef;
33static INDIDef defs[] = {
34 { "defTextVector", "defText", "newTextVector", "oneText" },
35 { "defNumberVector", "defNumber", "newNumberVector", "oneNumber" },
36 { "defSwitchVector", "defSwitch", "newSwitchVector", "oneSwitch" },
37};
38#define NDEFS (sizeof(defs) / sizeof(defs[0]))
39
40#define INDIPORT 7624 /* default port */
41static char host_def[] = "localhost"; /* default host name */
42
43static char *me; /* our name for usage message */
44static char *host = host_def; /* working host name */
45static int port = INDIPORT; /* working port number */
46static int verbose; /* report extra info */
47static int directfd = -1; /* direct filedes to server, if >= 0 */
48#define TIMEOUT 2 /* default timeout, secs */
49static int timeout = TIMEOUT; /* working timeout, secs */
50static LilXML *lillp; /* XML parser context */
51
52typedef struct
53{
54 char *e, *v; /* element name and value */
55 int ok; /* set when found */
56} SetEV;
57
58typedef struct
59{
60 char *d; /* device */
61 char *p; /* property */
62 SetEV *ev; /* elements */
63 int nev; /* n elements */
64 INDIDef *dp; /* one of defs if known, else NULL */
65} SetSpec;
66
67static SetSpec *sets; /* set of properties to set */
68static int nsets;
69
70static void usage(void);
71static int crackSpec(int *acp, char **avp[]);
72static void openINDIServer(FILE **rfpp, FILE **wfpp);
73static void listenINDI(FILE *rfp, FILE *wfp);
74static int finished(void);
75static void onAlarm(int dummy);
76static int readServerChar(FILE *fp);
77static void findSet(XMLEle *root, FILE *fp);
78static void scanEV(SetSpec *specp, char ev[]);
79static void scanEEVV(SetSpec *specp, char *ep, char ev[]);
80static void scanEVEV(SetSpec *specp, char ev[]);
81static void sendNew(FILE *fp, INDIDef *dp, SetSpec *sp);
82static void sendSpecs(FILE *wfp);
83
84int main(int ac, char *av[])
85{
86 FILE *rfp, *wfp;
87 int stop = 0;
88 int allspeced;
89
90 /* save our name */
91 me = av[0];
92
93 /* crack args */
94 while (!stop && --ac && **++av == '-')
95 {
96 char *s = *av;
97 while (*++s)
98 {
99 switch (*s)
100 {
101 case 'd':
102 if (ac < 2)
103 {
104 fprintf(stderr, "-d requires open fileno\n");
105 usage();
106 }
107 directfd = atoi(*++av);
108 ac--;
109 break;
110
111 case 'h':
112 if (directfd >= 0)
113 {
114 fprintf(stderr, "Can not combine -d and -h\n");
115 usage();
116 }
117 if (ac < 2)
118 {
119 fprintf(stderr, "-h requires host name\n");
120 usage();
121 }
122 host = *++av;
123 ac--;
124 break;
125
126 case 'p':
127 if (directfd >= 0)
128 {
129 fprintf(stderr, "Can not combine -d and -p\n");
130 usage();
131 }
132 if (ac < 2)
133 {
134 fprintf(stderr, "-p requires tcp port number\n");
135 usage();
136 }
137 port = atoi(*++av);
138 ac--;
139 break;
140
141 case 't':
142 if (ac < 2)
143 {
144 fprintf(stderr, "-t requires timeout\n");
145 usage();
146 }
147 timeout = atoi(*++av);
148 ac--;
149 break;
150
151 case 'v': /* verbose */
152 verbose++;
153 break;
154
155 case 'x': /* FALLTHRU */
156 case 'n': /* FALLTHRU */
157 case 's':
158 /* stop if see one of the property types */
159 stop = 1;
160 break;
161
162 default:
163 fprintf(stderr, "Unknown flag: %c\n", *s);
164 usage();
165 }
166 }
167 }
168
169 /* now ac args starting at av[0] */
170 if (ac < 1)
171 usage();
172
173 /* crack each property, add to sets[] */
174 allspeced = 1;
175 do
176 {
177 if (!crackSpec(&ac, &av))
178 allspeced = 0;
179 } while (ac > 0);
180
181 /* open connection */
182 if (directfd >= 0)
183 {
184 wfp = fdopen(directfd, "w");
185 rfp = fdopen(directfd, "r");
186 setbuf(rfp, NULL); /* don't absorb next guy's stuff */
187 if (!rfp || !wfp)
188 {
189 fprintf(stderr, "Direct fd %d: %s\n", directfd, strerror(errno));
190 exit(1);
191 }
192 if (verbose)
193 fprintf(stderr, "Using direct fd %d\n", directfd);
194 }
195 else
196 {
197 openINDIServer(&rfp, &wfp);
198 if (verbose)
199 fprintf(stderr, "Connected to %s on port %d\n", host, port);
200 }
201
202 /* build a parser context for cracking XML responses */
203 lillp = newLilXML();
204
205 /* just send it all speced, else check with server */
206 if (allspeced)
207 {
208 sendSpecs(wfp);
209 }
210 else
211 {
212 /* issue getProperties */
213 if (verbose)
214 fprintf(stderr, "Querying for properties\n");
215 fprintf(wfp, "<getProperties version='%g'/>\n", INDIV);
216 fflush(wfp);
217
218 /* listen for properties, set when see any we recognize */
219 listenINDI(rfp, wfp);
220 }
221
222 return (0);
223}
224
225static void usage()
226{
227 fprintf(stderr, "Purpose: set one or more writable INDI properties\n");
228 fprintf(stderr, "%s\n", GIT_TAG_STRING);
229 fprintf(stderr, "Usage: %s [options] {[type] spec} ...\n", me);
230 fprintf(stderr, "Options:\n");
231 fprintf(stderr, " -d f : use file descriptor f already open to server\n");
232 fprintf(stderr, " -h h : alternate host, default is %s\n", host_def);
233 fprintf(stderr, " -p p : alternate port, default is %d\n", INDIPORT);
234 fprintf(stderr, " -t t : max time to wait, default is %d secs\n", TIMEOUT);
235 fprintf(stderr, " -v : verbose (more are cumulative)\n");
236 fprintf(stderr, "Each spec optionally preceded by its type is sent without first confirming\n");
237 fprintf(stderr, "its structure. This is much more efficient but there is no error checking.\n");
238 fprintf(stderr, "Types are indicated with the following flags:\n");
239 fprintf(stderr, " -x : Text\n");
240 fprintf(stderr, " -n : Number\n");
241 fprintf(stderr, " -s : Switch\n");
242 fprintf(stderr, "Spec may be either:\n");
243 fprintf(stderr, " device.property.e1[;e2...]=v1[;v2...]\n");
244 fprintf(stderr, " or\n");
245 fprintf(stderr, " device.property.e1=v1[;e2=v2...]\n");
246 fprintf(stderr, "Exit status:\n");
247 fprintf(stderr, " 0: all settings successful\n");
248 fprintf(stderr, " 1: at least one setting was invalid\n");
249 fprintf(stderr, " 2: real trouble, try repeating with -v\n");
250
251 exit(2);
252}
253
254/* crack property set spec, add to sets [], move to next spec.
255 * return 1 if see a type
256 */
257static int crackSpec(int *acp, char **avp[])
258{
259 char d[128], p[128], ev[2048];
260 char *spec = *avp[0];
261 INDIDef *dp = NULL;
262
263 /* check if first arg is type indicator */
264 if ((*acp > 0) && (spec[0] == '-'))
265 {
266 switch (spec[1])
267 {
268 case 'x':
269 dp = &defs[0];
270 break;
271 case 'n':
272 dp = &defs[1];
273 break;
274 case 's':
275 dp = &defs[2];
276 break;
277 default:
278 fprintf(stderr, "Bad property type: %s\n", spec);
279 usage();
280 }
281 (*acp)--;
282 (*avp)++;
283 spec = *avp[0];
284 }
285 if (*acp <= 0)
286 {
287 fprintf(stderr, "Missing spec\n");
288 usage();
289 }
290
291 /* then scan arg for property spec */
292 //if (sscanf(spec, "%[^.].%[^.].%s", d, p, ev) != 3)
293 if (sscanf(spec, "%[^.].%[^.].%[^\n]", d, p, ev) != 3)
294 {
295 fprintf(stderr, "Malformed property spec: %s\n", spec);
296 usage();
297 }
298
299 /* add to list */
300 sets = (SetSpec *)realloc(sets, (nsets + 1) * sizeof(SetSpec));
301 sets[nsets].d = strdup(d);
302 sets[nsets].p = strdup(p);
303 sets[nsets].dp = dp;
304 sets[nsets].ev = NULL;
305 sets[nsets].nev = 0;
306 scanEV(&sets[nsets++], ev);
307
308 /* update caller's pointers */
309 (*acp)--;
310 (*avp)++;
311
312 /* return 1 if saw a spec */
313 return (dp ? 1 : 0);
314}
315
316/* open a read and write connection to host and port or die.
317 * exit if trouble.
318 */
319static void openINDIServer(FILE **rfpp, FILE **wfpp)
320{
321 struct sockaddr_in serv_addr;
322 struct hostent *hp;
323 int sockfd;
324
325 /* lookup host address */
326 hp = gethostbyname(host);
327 if (!hp)
328 {
329 perror("gethostbyname");
330 exit(2);
331 }
332
333 /* create a socket to the INDI server */
334 (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
335 serv_addr.sin_family = AF_INET;
336 serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
337 serv_addr.sin_port = htons(port);
338 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
339 {
340 perror("socket");
341 exit(2);
342 }
343
344 /* connect */
345 if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
346 {
347 perror("connect");
348 exit(2);
349 }
350
351 /* prepare for line-oriented i/o to client */
352 *rfpp = fdopen(sockfd, "r");
353 *wfpp = fdopen(sockfd, "w");
354}
355
356/* listen for property reports, send new sets if match */
357static void listenINDI(FILE *rfp, FILE *wfp)
358{
359 char msg[1024];
360
361 /* arrange to call onAlarm() if not seeing any more defXXX */
362 signal(SIGALRM, onAlarm);
363 alarm(timeout);
364
365 /* read from server, exit if find all properties */
366 while (1)
367 {
368 XMLEle *root = readXMLEle(lillp, readServerChar(rfp), msg);
369 if (root)
370 {
371 /* found a complete XML element */
372 if (verbose > 1)
373 prXMLEle(stderr, root, 0);
374 findSet(root, wfp);
375 if (finished() == 0)
376 {
377 shutdown(fileno(wfp), SHUT_WR); /* insure flush */
378 exit(0); /* found all we want */
379 }
380 delXMLEle(root); /* not yet, delete and continue */
381 }
382 else if (msg[0])
383 {
384 fprintf(stderr, "Bad XML from %s/%d: %s\n", host, port, msg);
385 exit(2);
386 }
387 }
388}
389
390/* return 0 if we are sure we set everything we wanted to, else -1 */
391static int finished()
392{
393 int i, j;
394
395 for (i = 0; i < nsets; i++)
396 for (j = 0; j < sets[i].nev; j++)
397 if (!sets[i].ev[j].ok)
398 return (-1);
399 return (0);
400}
401
402/* called after timeout seconds because we did not find something we trying
403 * to set.
404 */
405static void onAlarm(int dummy)
406{
407 INDI_UNUSED(dummy);
408 int i, j;
409
410 for (i = 0; i < nsets; i++)
411 for (j = 0; j < sets[i].nev; j++)
412 if (!sets[i].ev[j].ok)
413 fprintf(stderr, "No %s.%s.%s from %s:%d\n", sets[i].d, sets[i].p, sets[i].ev[j].e, host, port);
414
415 exit(1);
416}
417
418static int readServerChar(FILE *fp)
419{
420 int c = fgetc(fp);
421
422 if (c == EOF)
423 {
424 if (ferror(fp))
425 perror("read");
426 else
427 fprintf(stderr, "INDI server %s:%d disconnected\n", host, port);
428 exit(2);
429 }
430
431 if (verbose > 2)
432 fprintf(stderr, "Read %c\n", c);
433
434 return (c);
435}
436
437/* issue a set command if it matches the given property */
438static void findSet(XMLEle *root, FILE *fp)
439{
440 char *rtype, *rdev, *rprop;
441 XMLEle *ep;
442 int t, s, i;
443
444 /* check type */
445 rtype = tagXMLEle(root);
446 for (t = 0; t < (int)NDEFS; t++)
447 {
448 if (strcmp(rtype, defs[t].defType) == 0)
449 break;
450 }
451 if (t == NDEFS)
452 return;
453
454 alarm(timeout); /* reset timeout */
455
456 /* check each set for matching device and property name, send if ok */
457 rdev = (char *)findXMLAttValu(root, "device");
458 rprop = (char *)findXMLAttValu(root, "name");
459 if (verbose > 1)
460 fprintf(stderr, "Read definition for %s.%s\n", rdev, rprop);
461
462 for (s = 0; s < nsets; s++)
463 {
464 if (!strcmp(rdev, sets[s].d) && !strcmp(rprop, sets[s].p))
465 {
466 /* found device and name, check perm */
467 if (!strchr(findXMLAttValu(root, "perm"), 'w'))
468 {
469 if (verbose)
470 fprintf(stderr, "%s.%s is read-only\n", rdev, rprop);
471 exit(1);
472 }
473 /* check matching elements */
474 for (i = 0; i < sets[s].nev; i++)
475 {
476 for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0))
477 {
478 if (!strcmp(findXMLAttValu(ep, "name"), sets[s].ev[i].e) && !strcmp(tagXMLEle(ep), defs[t].defOne))
479 {
480 sets[s].ev[i].ok = 1;
481 break;
482 }
483 }
484 if (!ep)
485 return; /* not in this msg, maybe later */
486 }
487 /* all element names found, send new values */
488 sendNew(fp, &defs[t], &sets[s]);
489 }
490 }
491}
492
493/* send the given set specification of the given INDI type to channel on fp */
494static void sendNew(FILE *fp, INDIDef *dp, SetSpec *sp)
495{
496 int i;
497
498 fprintf(fp, "<%s device='%s' name='%s'>\n", dp->newType, sp->d, sp->p);
499 for (i = 0; i < sp->nev; i++)
500 {
501 if (verbose)
502 fprintf(stderr, " %s.%s.%s <- %s\n", sp->d, sp->p, sp->ev[i].e, sp->ev[i].v);
503 fprintf(fp, " <%s name='%s'>%s</%s>\n", dp->oneType, sp->ev[i].e, sp->ev[i].v, dp->oneType);
504 }
505 fprintf(fp, "</%s>\n", dp->newType);
506 fflush(fp);
507 if (feof(fp) || ferror(fp))
508 {
509 fprintf(stderr, "Send error\n");
510 exit(2);
511 }
512}
513
514/* scan ev for element definitions in either of two forms and add to sp:
515 * e1[;e2...]=v1[;v2...]
516 * or
517 * e1=v1[;e2=v2...]
518 * exit if nothing sensible found.
519 */
520static void scanEV(SetSpec *specp, char ev[])
521{
522 char *ep, *sp; /* pointers to = and ; */
523
524 if (verbose > 1)
525 fprintf(stderr, "Scanning assignments %s\n", ev);
526
527 ep = strchr(ev, '=');
528 sp = strchr(ev, ';');
529
530 if (!ep)
531 {
532 fprintf(stderr, "Malformed assignment: %s\n", ev);
533 usage();
534 }
535
536 if (sp < ep)
537 scanEEVV(specp, ep, ev); /* including just one E=V */
538 else
539 scanEVEV(specp, ev);
540}
541
542/* add specs of the form e1[;e2...]=v1[;v2...] to sp.
543 * v is pointer to equal sign.
544 * exit if trouble.
545 * N.B. e[] and v[] are modified in place.
546 */
547static void scanEEVV(SetSpec *sp, char *v, char *e)
548{
549 static char sep[] = ";";
550 char *ec, *vc;
551
552 *v++ = '\0';
553
554 while (1)
555 {
556 char *e0 = strtok_r(e, sep, &ec);
557 char *v0 = strtok_r(v, sep, &vc);
558
559 if (!e0 && !v0)
560 break;
561 if (!e0)
562 {
563 fprintf(stderr, "More values than elements for %s.%s\n", sp->d, sp->p);
564 exit(2);
565 }
566 if (!v0)
567 {
568 fprintf(stderr, "More elements than values for %s.%s\n", sp->d, sp->p);
569 exit(2);
570 }
571
572 sp->ev = (SetEV *)realloc(sp->ev, (sp->nev + 1) * sizeof(SetEV));
573 sp->ev[sp->nev].e = strdup(e0);
574 sp->ev[sp->nev].v = strdup(v0);
575 if (verbose > 1)
576 fprintf(stderr, "Found assignment %s=%s\n", sp->ev[sp->nev].e, sp->ev[sp->nev].v);
577 sp->nev++;
578
579 e = NULL;
580 v = NULL;
581 }
582}
583
584/* add specs of the form e1=v1[;e2=v2...] to sp.
585 * exit if trouble.
586 * N.B. ev[] is modified in place.
587 */
588static void scanEVEV(SetSpec *sp, char ev[])
589{
590 char *s, *e;
591 int last = 0;
592
593 do
594 {
595 s = strchr(ev, ';');
596 if (s)
597 *s++ = '\0';
598 else
599 {
600 s = ev + strlen(ev);
601 last = 1;
602 }
603 e = strchr(ev, '=');
604 if (e)
605 *e++ = '\0';
606 else
607 {
608 fprintf(stderr, "Malformed assignment: %s\n", ev);
609 usage();
610 }
611
612 sp->ev = (SetEV *)realloc(sp->ev, (sp->nev + 1) * sizeof(SetEV));
613 sp->ev[sp->nev].e = strdup(ev);
614 sp->ev[sp->nev].v = strdup(e);
615 if (verbose > 1)
616 fprintf(stderr, "Found assignment %s=%s\n", sp->ev[sp->nev].e, sp->ev[sp->nev].v);
617 sp->nev++;
618
619 ev = s;
620
621 } while (!last);
622}
623
624/* send each SetSpec, all of which have a known type, to wfp
625 */
626static void sendSpecs(FILE *wfp)
627{
628 int i;
629
630 for (i = 0; i < nsets; i++)
631 sendNew(wfp, sets[i].dp, &sets[i]);
632}
int main()
int errno
Constants and Data structure definitions for the interface to the reference INDI C API implementation...
#define INDIV
Definition indiapi.h:134
Interface to the reference INDI C API device implementation on the Device Driver side.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int verbose
Definition indidriver.c:49
char * me
Definition indidriver.c:50
void usage(void)
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
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition lilxml.cpp:844
XMLEle * readXMLEle(LilXML *lp, int newc, char ynot[])
Process an XML one char at a time.
Definition lilxml.cpp:385
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
A little DOM-style library to handle parsing and processing an XML file.
#define TIMEOUT
#define NDEFS
#define INDIPORT
char * newType
char * defType
char * defOne
char * oneType
char * e
char * v
SetEV * ev
INDIDef * dp