source: BAORadio/libindi/libindi/tools/evalINDI.c @ 490

Last change on this file since 490 was 490, checked in by campagne, 14 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.