Instrument Neutral Distributed Interface INDI  2.0.2
evalINDI.c
Go to the documentation of this file.
1 /* evaluate an expression of INDI operands
2  */
3 
4 /* Overall design:
5  * compile expression, building operand table, if trouble exit 2
6  * open INDI connection, if trouble exit 2
7  * send getProperties as required to get operands flowing
8  * watch for messages until get initial values of each operand
9  * evaluate expression, repeat if -w each time an op arrives until true
10  * exit val==0
11  */
12 
13 #define _GNU_SOURCE // needed for fdopen
14 
15 #include "indiapi.h"
16 #include "indidevapi.h"
17 #include "lilxml.h"
18 
19 #include <errno.h>
20 #include <math.h>
21 #include <netdb.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <unistd.h>
28 #include <netinet/in.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 
32 extern int compileExpr(char *expr, char *errmsg);
33 extern int evalExpr(double *vp, char *errmsg);
34 extern int allOperandsSet();
35 extern int getAllOperands(char ***ops);
36 extern int getSetOperands(char ***ops);
37 extern int getUnsetOperands(char ***ops);
38 extern int setOperand(char *name, double valu);
39 
40 static void usage();
41 static void compileINDI(char *expr);
42 static FILE *openINDIServer();
43 static void getProps(FILE *fp);
44 static void initProps(FILE *fp);
45 static int pstatestr(char *state);
46 static time_t timestampINDI(char *ts);
47 static int devcmp(char *op1, char *op2);
48 static int runEval(FILE *fp);
49 static int setOp(XMLEle *root);
50 static XMLEle *nxtEle(FILE *fp);
51 static int readServerChar(FILE *fp);
52 static void onAlarm(int dummy);
53 
54 static char *me;
55 static char host_def[] = "localhost"; /* default host name */
56 static char *host = host_def; /* working host name */
57 #define INDIPORT 7624 /* default port */
58 static int port = INDIPORT; /* working port number */
59 #define TIMEOUT 2 /* default timeout, secs */
60 static int timeout = TIMEOUT; /* working timeout, secs */
61 static LilXML *lillp; /* XML parser context */
62 static int directfd = -1; /* direct filedes to server, if >= 0 */
63 static int verbose; /* more tracing */
64 static int eflag; /* print each updated expression value*/
65 static int fflag; /* print final expression value */
66 static int iflag; /* read expresion from stdin */
67 static int oflag; /* print operands as they change */
68 static int wflag; /* wait for expression to be true */
69 static int bflag; /* beep when true */
70 
71 int main(int ac, char *av[])
72 {
73  FILE *fp;
74 
75  /* save our name for usage() */
76  me = av[0];
77 
78  /* crack args */
79  while (--ac && **++av == '-')
80  {
81  char *s = *av;
82  while (*++s)
83  {
84  switch (*s)
85  {
86  case 'b': /* beep when true */
87  bflag++;
88  break;
89  case 'd':
90  if (ac < 2)
91  {
92  fprintf(stderr, "-d requires open fileno\n");
93  usage();
94  }
95  directfd = atoi(*++av);
96  ac--;
97  break;
98  case 'e': /* print each updated expression value */
99  eflag++;
100  break;
101  case 'f': /* print final expression value */
102  fflag++;
103  break;
104  case 'h':
105  if (directfd >= 0)
106  {
107  fprintf(stderr, "Can not combine -d and -h\n");
108  usage();
109  }
110  if (ac < 2)
111  {
112  fprintf(stderr, "-h requires host name\n");
113  usage();
114  }
115  host = *++av;
116  ac--;
117  break;
118  case 'i': /* read expression from stdin */
119  iflag++;
120  break;
121  case 'o': /* print operands as they change */
122  oflag++;
123  break;
124  case 'p':
125  if (directfd >= 0)
126  {
127  fprintf(stderr, "Can not combine -d and -p\n");
128  usage();
129  }
130  if (ac < 2)
131  {
132  fprintf(stderr, "-p requires tcp port number\n");
133  usage();
134  }
135  port = atoi(*++av);
136  ac--;
137  break;
138  case 't':
139  if (ac < 2)
140  {
141  fprintf(stderr, "-t requires timeout\n");
142  usage();
143  }
144  timeout = atoi(*++av);
145  ac--;
146  break;
147  case 'v': /* verbose */
148  verbose++;
149  break;
150  case 'w': /* wait for expression to be true */
151  wflag++;
152  break;
153  default:
154  fprintf(stderr, "Unknown flag: %c\n", *s);
155  usage();
156  }
157  }
158  }
159 
160  /* now there are ac args starting with av[0] */
161 
162  /* compile expression from av[0] or stdin */
163  if (ac == 0)
164  compileINDI(NULL);
165  else if (ac == 1)
166  compileINDI(av[0]);
167  else
168  usage();
169 
170  /* open connection */
171  if (directfd >= 0)
172  {
173  fp = fdopen(directfd, "r+");
174  setbuf(fp, NULL); /* don't absorb next guy's stuff */
175  if (!fp)
176  {
177  fprintf(stderr, "Direct fd %d: %s\n", directfd, strerror(errno));
178  exit(1);
179  }
180  if (verbose)
181  fprintf(stderr, "Using direct fd %d\n", directfd);
182  }
183  else
184  {
185  fp = openINDIServer();
186  if (verbose)
187  fprintf(stderr, "Connected to %s on port %d\n", host, port);
188  }
189 
190  /* build a parser context for cracking XML responses */
191  lillp = newLilXML();
192 
193  /* set up to catch an io timeout function */
194  signal(SIGALRM, onAlarm);
195 
196  /* send getProperties */
197  getProps(fp);
198 
199  /* initialize all properties */
200  initProps(fp);
201 
202  /* evaluate expression, return depending on flags */
203  return (runEval(fp));
204 }
205 
206 static void usage()
207 {
208  fprintf(stderr, "Usage: %s [options] [exp]\n", me);
209  fprintf(stderr, "Purpose: evaluate an expression of INDI operands\n");
210  fprintf(stderr, "Version: $Revision: 1.5 $\n");
211  fprintf(stderr, "Options:\n");
212  fprintf(stderr, " -b : beep when expression evaluates as true\n");
213  fprintf(stderr, " -d f : use file descriptor f already open to server\n");
214 
215  fprintf(stderr, " -e : print each updated expression value\n");
216  fprintf(stderr, " -f : print final expression value\n");
217  fprintf(stderr, " -h h : alternate host, default is %s\n", host_def);
218  fprintf(stderr, " -i : read expression from stdin\n");
219  fprintf(stderr, " -o : print operands as they change\n");
220  fprintf(stderr, " -p p : alternate port, default is %d\n", INDIPORT);
221  fprintf(stderr, " -t t : max secs to wait, 0 is forever, default is %d\n", TIMEOUT);
222  fprintf(stderr, " -v : verbose (cumulative)\n");
223  fprintf(stderr, " -w : wait for expression to evaluate as true\n");
224  fprintf(stderr, "[exp] is an arith expression built from the following operators and functions:\n");
225  fprintf(stderr, " ! + - * / && || > >= == != < <=\n");
226  fprintf(stderr, " pi sin(rad) cos(rad) tan(rad) asin(x) acos(x) atan(x) atan2(y,x) abs(x)\n");
227  fprintf(stderr, " degrad(deg) raddeg(rad) floor(x) log(x) log10(x) exp(x) sqrt(x) pow(x,exp)\n");
228  fprintf(stderr, " operands are of the form \"device.name.element\" (including quotes), where\n");
229  fprintf(stderr, " element may be:\n");
230  fprintf(stderr, " _STATE evaluated to 0,1,2,3 from Idle,Ok,Busy,Alert.\n");
231  fprintf(stderr, " _TS evaluated to UNIX seconds from epoch.\n");
232  fprintf(stderr, " Switch vectors are evaluated to 0,1 from Off,On.\n");
233  fprintf(stderr, " Light vectors are evaluated to 0-3 as per _STATE.\n");
234  fprintf(stderr, "Examples:\n");
235  fprintf(stderr, " To print 0/1 whether Security.Doors.Front or .Rear are in Alert:\n");
236  fprintf(stderr, " evalINDI -f '\"Security.Doors.Front\"==3 || \"Security.Doors.Rear\"==3'\n");
237  fprintf(stderr, " To exit 0 if the Security property as a whole is in a state of Ok:\n");
238  fprintf(stderr, " evalINDI '\"Security.Security._STATE\"==1'\n");
239  fprintf(stderr, " To wait for RA and Dec to be near zero and watch their values as they change:\n");
240  fprintf(stderr, " evalINDI -t 0 -wo 'abs(\"Mount.EqJ2K.RA\")<.01 && abs(\"Mount.EqJ2K.Dec\")<.01'\n");
241  fprintf(stderr, "Exit 0 if expression evaluates to non-0, 1 if 0, else 2\n");
242 
243  exit(1);
244 }
245 
246 /* compile the given expression else read from stdin.
247  * exit(2) if trouble.
248  */
249 static void compileINDI(char *expr)
250 {
251  char errmsg[1024];
252  char *exp = expr;
253 
254  if (!exp)
255  {
256  /* read expression from stdin */
257  int nr, nexp = 0;
258  exp = malloc(1024);
259  while ((nr = fread(exp + nexp, 1, 1024, stdin)) > 0)
260  exp = realloc(exp, (nexp += nr) + 1024);
261  exp[nexp] = '\0';
262  }
263 
264  if (verbose)
265  fprintf(stderr, "Compiling: %s\n", exp);
266  if (compileExpr(exp, errmsg) < 0)
267  {
268  fprintf(stderr, "Compile err: %s\n", errmsg);
269  exit(2);
270  }
271 
272  if (exp != expr)
273  free(exp);
274 }
275 
276 /* open a connection to the given host and port or die.
277  * return FILE pointer to socket.
278  */
279 static FILE *openINDIServer()
280 {
281  struct sockaddr_in serv_addr;
282  struct hostent *hp;
283  int sockfd;
284 
285  /* lookup host address */
286  hp = gethostbyname(host);
287  if (!hp)
288  {
289  perror("gethostbyname");
290  exit(2);
291  }
292 
293  /* create a socket to the INDI server */
294  (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
295  serv_addr.sin_family = AF_INET;
296  serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
297  serv_addr.sin_port = htons(port);
298  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
299  {
300  perror("socket");
301  exit(2);
302  }
303 
304  /* connect */
305  if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
306  {
307  perror("connect");
308  exit(2);
309  }
310 
311  /* prepare for line-oriented i/o with client */
312  return (fdopen(sockfd, "r+"));
313 }
314 
315 /* invite each device referenced in the expression to report its properties.
316  */
317 static void getProps(FILE *fp)
318 {
319  char **ops;
320  int nops;
321  int i, j;
322 
323  /* get each operand used in the expression */
324  nops = getAllOperands(&ops);
325 
326  /* send getProperties for each unique device referenced */
327  for (i = 0; i < nops; i++)
328  {
329  for (j = 0; j < i; j++)
330  if (devcmp(ops[i], ops[j]) == 0)
331  break;
332  if (j < i)
333  continue;
334  if (verbose)
335  fprintf(stderr, "sending getProperties for %.*s\n", (int)(strchr(ops[i], '.') - ops[i]), ops[i]);
336  fprintf(fp, "<getProperties version='%g' device='%.*s'/>\n", INDIV, (int)(strchr(ops[i], '.') - ops[i]),
337  ops[i]);
338  }
339 }
340 
341 /* wait for defXXX or setXXX for each property in the expression.
342  * return when find all operands are found or
343  * exit(2) if time out waiting for all known operands.
344  */
345 static void initProps(FILE *fp)
346 {
347  alarm(timeout);
348  while (allOperandsSet() < 0)
349  {
350  if (setOp(nxtEle(fp)) == 0)
351  alarm(timeout);
352  }
353  alarm(0);
354 }
355 
356 /* pull apart the name and value from the given message, and set operand value.
357  * ignore any other messages.
358  * return 0 if found a recognized operand else -1
359  */
360 static int setOp(XMLEle *root)
361 {
362  char *t = tagXMLEle(root);
363  const char *d = findXMLAttValu(root, "device");
364  const char *n = findXMLAttValu(root, "name");
365  int nset = 0;
366  double v;
367  char prop[1024];
368  XMLEle *ep;
369 
370  /* check values */
371  if (!strcmp(t, "defNumberVector") || !strcmp(t, "setNumberVector"))
372  {
373  for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0))
374  {
375  char *et = tagXMLEle(ep);
376  if (!strcmp(et, "defNumber") || !strcmp(et, "oneNumber"))
377  {
378  sprintf(prop, "%s.%s.%s", d, n, findXMLAttValu(ep, "name"));
379  v = atof(pcdataXMLEle(ep));
380  if (setOperand(prop, v) == 0)
381  {
382  nset++;
383  if (oflag)
384  fprintf(stderr, "%s=%g\n", prop, v);
385  }
386  }
387  }
388  }
389  else if (!strcmp(t, "defSwitchVector") || !strcmp(t, "setSwitchVector"))
390  {
391  for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0))
392  {
393  char *et = tagXMLEle(ep);
394 
395  if (!strcmp(et, "defSwitch") || !strcmp(et, "oneSwitch"))
396  {
397  sprintf(prop, "%s.%s.%s", d, n, findXMLAttValu(ep, "name"));
398  v = (double)!strncmp(pcdataXMLEle(ep), "On", 2);
399  if (setOperand(prop, v) == 0)
400  {
401  nset++;
402  if (oflag)
403  fprintf(stderr, "%s=%g\n", prop, v);
404  }
405  }
406  }
407  }
408  else if (!strcmp(t, "defLightVector") || !strcmp(t, "setLightVector"))
409  {
410  for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0))
411  {
412  char *et = tagXMLEle(ep);
413  if (!strcmp(et, "defLight") || !strcmp(et, "oneLight"))
414  {
415  sprintf(prop, "%s.%s.%s", d, n, findXMLAttValu(ep, "name"));
416  v = (double)pstatestr(pcdataXMLEle(ep));
417  if (setOperand(prop, v) == 0)
418  {
419  nset++;
420  if (oflag)
421  fprintf(stderr, "%s=%g\n", prop, v);
422  }
423  }
424  }
425  }
426 
427  /* check special elements */
428  t = (char *)findXMLAttValu(root, "state");
429  if (t[0])
430  {
431  sprintf(prop, "%s.%s._STATE", d, n);
432  v = (double)pstatestr(t);
433  if (setOperand(prop, v) == 0)
434  {
435  nset++;
436  if (oflag)
437  fprintf(stderr, "%s=%g\n", prop, v);
438  }
439  }
440  t = (char *)findXMLAttValu(root, "timestamp");
441  if (t[0])
442  {
443  sprintf(prop, "%s.%s._TS", d, n);
444  v = (double)timestampINDI(t);
445  if (setOperand(prop, v) == 0)
446  {
447  nset++;
448  if (oflag)
449  fprintf(stderr, "%s=%g\n", prop, v);
450  }
451  }
452 
453  /* return whether any were set */
454  return (nset > 0 ? 0 : -1);
455 }
456 
457 /* evaluate the expression after seeing any operand change.
458  * return whether expression evaluated to 0.
459  * exit(2) is trouble or timeout waiting for operands we expect.
460  */
461 static int runEval(FILE *fp)
462 {
463  char errmsg[1024];
464  double v;
465 
466  alarm(timeout);
467  while (1)
468  {
469  if (evalExpr(&v, errmsg) < 0)
470  {
471  fprintf(stderr, "Eval: %s\n", errmsg);
472  exit(2);
473  }
474  if (bflag && v)
475  fprintf(stderr, "\a");
476  if (eflag)
477  fprintf(stderr, "%g\n", v);
478  if (!wflag || v != 0)
479  break;
480  while (setOp(nxtEle(fp)) < 0)
481  continue;
482  alarm(timeout);
483  }
484  alarm(0);
485 
486  if (!eflag && fflag)
487  fprintf(stderr, "%g\n", v);
488 
489  return (v == 0);
490 }
491 
492 /* return 0|1|2|3 depending on whether state is Idle|Ok|Busy|<other>.
493  */
494 static int pstatestr(char *state)
495 {
496  if (!strcmp(state, "Idle"))
497  return (0);
498  if (!strncmp(state, "Ok", 2))
499  return (1);
500  if (!strcmp(state, "Busy"))
501  return (2);
502  return (3);
503 }
504 
505 /* return UNIX time for the given ISO 8601 time string
506  */
507 static time_t timestampINDI(char *ts)
508 {
509  struct tm tm;
510 
511  if (6 == sscanf(ts, "%d-%d-%dT%d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec))
512  {
513  tm.tm_mon -= 1; /* want 0..11 */
514  tm.tm_year -= 1900; /* want years since 1900 */
515  tm.tm_isdst = 0;
516  return (mktime(&tm));
517  }
518  else
519  return ((time_t)-1);
520 }
521 
522 /* return 0 if the device portion of the two given property specs match, else 1
523  */
524 static int devcmp(char *op1, char *op2)
525 {
526  int n1 = strchr(op1, '.') - op1;
527  int n2 = strchr(op2, '.') - op2;
528  return (n1 != n2 || strncmp(op1, op2, n1));
529 }
530 
531 /* monitor server and return the next complete XML message.
532  * exit(2) if time out.
533  * N.B. caller must call delXMLEle()
534  */
535 static XMLEle *nxtEle(FILE *fp)
536 {
537  char msg[1024];
538 
539  /* read from server, exit if trouble or see malformed XML */
540  while (1)
541  {
542  XMLEle *root = readXMLEle(lillp, readServerChar(fp), msg);
543  if (root)
544  {
545  /* found a complete XML element */
546  if (verbose > 1)
547  prXMLEle(stderr, root, 0);
548  return (root);
549  }
550  else if (msg[0])
551  {
552  fprintf(stderr, "Bad XML from %s/%d: %s\n", host, port, msg);
553  exit(2);
554  }
555  }
556 }
557 
558 /* read next char from the INDI server connected by fp */
559 static int readServerChar(FILE *fp)
560 {
561  int c = fgetc(fp);
562 
563  if (c == EOF)
564  {
565  if (ferror(fp))
566  perror("read");
567  else
568  fprintf(stderr, "INDI server %s/%d disconnected\n", host, port);
569  exit(2);
570  }
571 
572  if (verbose > 2)
573  fprintf(stderr, "Read %c\n", c);
574 
575  return (c);
576 }
577 
578 /* called after timeout seconds waiting to hear from server.
579  * print reason for trouble and exit(2).
580  */
581 static void onAlarm(int dummy)
582 {
583  char **ops;
584  int nops;
585 
586  INDI_UNUSED(dummy);
587 
588  /* report any unseen operands if any, else just say timed out */
589  if ((nops = getUnsetOperands(&ops)) > 0)
590  {
591  fprintf(stderr, "No values seen for");
592  while (nops-- > 0)
593  fprintf(stderr, " %s", ops[nops]);
594  fprintf(stderr, "\n");
595  }
596  else
597  fprintf(stderr, "Timed out waiting for new values\n");
598 
599  exit(2);
600 }
int getAllOperands(char ***ops)
Definition: compiler.c:199
#define TIMEOUT
Definition: evalINDI.c:59
int allOperandsSet()
Definition: compiler.c:186
int evalExpr(double *vp, char *errmsg)
Definition: compiler.c:157
int getSetOperands(char ***ops)
Definition: compiler.c:214
int compileExpr(char *expr, char *errmsg)
Definition: compiler.c:127
int getUnsetOperands(char ***ops)
Definition: compiler.c:230
#define INDIPORT
Definition: evalINDI.c:57
int main(int ac, char *av[])
Definition: evalINDI.c:71
int setOperand(char *name, double valu)
Definition: compiler.c:168
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 * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
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
A little DOM-style library to handle parsing and processing an XML file.