Instrument Neutral Distributed Interface INDI 2.2.5
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
32extern int compileExpr(char *expr, char *errmsg);
33extern int evalExpr(double *vp, char *errmsg);
34extern int allOperandsSet();
35extern int getAllOperands(char ***ops);
36extern int getSetOperands(char ***ops);
37extern int getUnsetOperands(char ***ops);
38extern int setOperand(char *name, double valu);
39
40static void usage();
41static void compileINDI(char *expr);
42static FILE *openINDIServer();
43static void getProps(FILE *fp);
44static void initProps(FILE *fp);
45static int pstatestr(char *state);
46static time_t timestampINDI(char *ts);
47static int devcmp(char *op1, char *op2);
48static int runEval(FILE *fp);
49static int setOp(XMLEle *root);
50static XMLEle *nxtEle(FILE *fp);
51static int readServerChar(FILE *fp);
52static void onAlarm(int dummy);
53
54static char *me;
55static char host_def[] = "localhost"; /* default host name */
56static char *host = host_def; /* working host name */
57#define INDIPORT 7624 /* default port */
58static int port = INDIPORT; /* working port number */
59#define TIMEOUT 2 /* default timeout, secs */
60static int timeout = TIMEOUT; /* working timeout, secs */
61static LilXML *lillp; /* XML parser context */
62static int directfd = -1; /* direct filedes to server, if >= 0 */
63static int verbose; /* more tracing */
64static int eflag; /* print each updated expression value*/
65static int fflag; /* print final expression value */
66static int iflag; /* read expression from stdin */
67static int oflag; /* print operands as they change */
68static int wflag; /* wait for expression to be true */
69static int bflag; /* beep when true */
70
71int 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
206static 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 */
249static 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 */
279static 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 */
317static 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 */
345static 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 */
360static 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 snprintf(prop, sizeof(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 snprintf(prop, sizeof(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 snprintf(prop, sizeof(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 snprintf(prop, sizeof(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 snprintf(prop, sizeof(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 */
461static 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 */
494static 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 */
507static 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 */
524static 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 */
535static 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 */
559static 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 */
581static 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 setOperand(char *name, double valu)
Definition compiler.c:168
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
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
A little DOM-style library to handle parsing and processing an XML file.