Instrument Neutral Distributed Interface INDI  2.0.2
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  */
26 typedef struct
27 {
28  char *defType; /* defXXXVector name */
29  char *defOne; /* defXXX name */
30  char *newType; /* newXXXVector name */
31  char *oneType; /* oneXXX name */
32 } INDIDef;
33 static 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 */
41 static char host_def[] = "localhost"; /* default host name */
42 
43 static char *me; /* our name for usage message */
44 static char *host = host_def; /* working host name */
45 static int port = INDIPORT; /* working port number */
46 static int verbose; /* report extra info */
47 static int directfd = -1; /* direct filedes to server, if >= 0 */
48 #define TIMEOUT 2 /* default timeout, secs */
49 static int timeout = TIMEOUT; /* working timeout, secs */
50 static LilXML *lillp; /* XML parser context */
51 
52 typedef struct
53 {
54  char *e, *v; /* element name and value */
55  int ok; /* set when found */
56 } SetEV;
57 
58 typedef 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 
67 static SetSpec *sets; /* set of properties to set */
68 static int nsets;
69 
70 static void usage(void);
71 static int crackSpec(int *acp, char **avp[]);
72 static void openINDIServer(FILE **rfpp, FILE **wfpp);
73 static void listenINDI(FILE *rfp, FILE *wfp);
74 static int finished(void);
75 static void onAlarm(int dummy);
76 static int readServerChar(FILE *fp);
77 static void findSet(XMLEle *root, FILE *fp);
78 static void scanEV(SetSpec *specp, char ev[]);
79 static void scanEEVV(SetSpec *specp, char *ep, char ev[]);
80 static void scanEVEV(SetSpec *specp, char ev[]);
81 static void sendNew(FILE *fp, INDIDef *dp, SetSpec *sp);
82 static void sendSpecs(FILE *wfp);
83 
84 int 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 
225 static 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  */
257 static 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  */
319 static 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 */
357 static 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 */
391 static 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  */
405 static 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 
418 static 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 */
438 static 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 */
494 static 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  */
520 static 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  */
547 static 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  */
588 static 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  */
626 static 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 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:131
int verbose
Definition: indidriver.c:49
char * me
Definition: indidriver.c:50
LilXML * newLilXML()
Create a new lilxml parser.
Definition: lilxml.cpp:150
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.cpp:644
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
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
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
int main(int ac, char *av[])
char * newType
char * defType
char * defOne
char * oneType
char * e
char * v
SetEV * ev
char * d
INDIDef * dp
char * p