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

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

import libindi (JEC)

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