source: BAORadio/libindi/v1.0.1/tools/setINDIproperty.c@ 608

Last change on this file since 608 was 490, checked in by campagne, 15 years ago

import libindi (JEC)

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