File: evalINDI.c

package info (click to toggle)
libindi 0.9.8.1-5.1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 4,600 kB
  • ctags: 7,283
  • sloc: cpp: 34,410; ansic: 20,227; xml: 294; makefile: 13
file content (561 lines) | stat: -rw-r--r-- 15,306 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
/* evaluate an expression of INDI operands
 */

/* Overall design:
 * compile expression, building operand table, if trouble exit 2
 * open INDI connection, if trouble exit 2
 * send getProperties as required to get operands flowing
 * watch for messages until get initial values of each operand
 * evaluate expression, repeat if -w each time an op arrives until true
 * exit val==0
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include "indiapi.h"
#include "lilxml.h"

extern int compileExpr (char *expr, char *errmsg);
extern int evalExpr (double *vp, char *errmsg);
extern int allOperandsSet (void);
extern int getAllOperands (char ***ops);
extern int getSetOperands (char ***ops);
extern int getUnsetOperands (char ***ops);
extern int setOperand (char *name, double valu);

static void usage (void);
static void compile (char *expr);
static FILE *openINDIServer (void);
static void getProps(FILE *fp);
static void initProps (FILE *fp);
static int pstatestr (char *state);
static time_t timestamp (char *ts);
static int devcmp (char *op1, char *op2);
static int runEval (FILE *fp);
static int setOp (XMLEle *root);
static XMLEle *nxtEle (FILE *fp);
static int readServerChar (FILE *fp);
static void onAlarm (int dummy);

static char *me;
static char host_def[] = "localhost";   /* default host name */
static char *host = host_def;           /* working host name */
#define INDIPORT        7624            /* default port */
static int port = INDIPORT;             /* working port number */
#define TIMEOUT         2               /* default timeout, secs */
static int timeout = TIMEOUT;           /* working timeout, secs */
static LilXML *lillp;			/* XML parser context */
static int directfd = -1;		/* direct filedes to server, if >= 0 */
static int verbose;			/* more tracing */
static int eflag;			/* print each updated expression value*/
static int fflag;			/* print final expression value */
static int iflag;			/* read expresion from stdin */
static int oflag;			/* print operands as they change */
static int wflag;			/* wait for expression to be true */
static int bflag;			/* beep when true */

int
main (int ac, char *av[])
{
	FILE *fp;

	/* save our name for usage() */
	me = av[0];

	/* crack args */
	while (--ac && **++av == '-') {
	    char *s = *av;
	    while (*++s) {
		switch (*s) {
		case 'b':	/* beep when true */
		    bflag++;
		    break;
		case 'd':
		    if (ac < 2) {
			fprintf (stderr, "-d requires open fileno\n");
			usage();
		    }
		    directfd = atoi(*++av);
		    ac--;
		    break;
		case 'e':	/* print each updated expression value */
		    eflag++;
		    break;
		case 'f':	/* print final expression value */
		    fflag++;
		    break;
		case 'h':
		    if (directfd >= 0) {
			fprintf (stderr, "Can not combine -d and -h\n");
			usage();
		    }
		    if (ac < 2) {
			fprintf (stderr, "-h requires host name\n");
			usage();
		    }
		    host = *++av;
		    ac--;
		    break;
		case 'i':	/* read expression from stdin */
		    iflag++;
		    break;
		case 'o':	/* print operands as they change */
		    oflag++;
		    break;
		case 'p':
		    if (directfd >= 0) {
			fprintf (stderr, "Can not combine -d and -p\n");
			usage();
		    }
		    if (ac < 2) {
			fprintf (stderr, "-p requires tcp port number\n");
			usage();
		    }
		    port = atoi(*++av);
		    ac--;
		    break;
		case 't':
		    if (ac < 2) {
			fprintf (stderr, "-t requires timeout\n");
			usage();
		    }
		    timeout = atoi(*++av);
		    ac--;
		    break;
		case 'v':	/* verbose */
		    verbose++;
		    break;
		case 'w':	/* wait for expression to be true */
		    wflag++;
		    break;
		default:
		    fprintf (stderr, "Unknown flag: %c\n", *s);
		    usage();
		}
	    }
	}

	/* now there are ac args starting with av[0] */

	/* compile expression from av[0] or stdin */
	if (ac == 0)
	    compile (NULL);
	else if (ac == 1)
	    compile (av[0]);
	else
	    usage();

        /* open connection */
	if (directfd >= 0) {
	    fp = fdopen (directfd, "r+");
	    setbuf (fp, NULL);		/* don't absorb next guy's stuff */
	    if (!fp) {
		fprintf (stderr, "Direct fd %d: %s\n",directfd,strerror(errno));
		exit(1);
	    }
	    if (verbose)
		fprintf (stderr, "Using direct fd %d\n", directfd);
	} else {
	    fp = openINDIServer();
	    if (verbose)
		fprintf (stderr, "Connected to %s on port %d\n", host, port);
	}

	/* build a parser context for cracking XML responses */
	lillp = newLilXML();

	/* set up to catch an io timeout function */
	signal (SIGALRM, onAlarm);

	/* send getProperties */
	getProps(fp);

	/* initialize all properties */
	initProps(fp);

	/* evaluate expression, return depending on flags */
	return (runEval(fp));
}

static void
usage()
{
	fprintf (stderr, "Usage: %s [options] [exp]\n", me);
	fprintf (stderr, "Purpose: evaluate an expression of INDI operands\n");
	fprintf (stderr, "Version: $Revision: 1.5 $\n");
	fprintf (stderr, "Options:\n");
	fprintf (stderr, "   -b   : beep when expression evaluates as true\n");
	fprintf (stderr, "   -d f : use file descriptor f already open to server\n");

	fprintf (stderr, "   -e   : print each updated expression value\n");
	fprintf (stderr, "   -f   : print final expression value\n");
        fprintf (stderr, "   -h h : alternate host, default is %s\n", host_def);
	fprintf (stderr, "   -i   : read expression from stdin\n");
	fprintf (stderr, "   -o   : print operands as they change\n");
	fprintf (stderr, "   -p p : alternate port, default is %d\n", INDIPORT);
	fprintf (stderr, "   -t t : max secs to wait, 0 is forever, default is %d\n",TIMEOUT);
	fprintf (stderr, "   -v   : verbose (cummulative)\n");
	fprintf (stderr, "   -w   : wait for expression to evaluate as true\n");
	fprintf (stderr, "[exp] is an arith expression built from the following operators and functons:\n");
	fprintf (stderr, "     ! + - * / && || > >= == != < <=\n");
	fprintf (stderr, "     pi sin(rad) cos(rad) tan(rad) asin(x) acos(x) atan(x) atan2(y,x) abs(x)\n");
	fprintf (stderr, "     degrad(deg) raddeg(rad) floor(x) log(x) log10(x) exp(x) sqrt(x) pow(x,exp)\n");
	fprintf (stderr, "   operands are of the form \"device.name.element\" (including quotes), where\n");
	fprintf (stderr, "   element may be:\n");
	fprintf (stderr, "     _STATE evaluated to 0,1,2,3 from Idle,Ok,Busy,Alert.\n");
	fprintf (stderr, "     _TS evaluated to UNIX seconds from epoch.\n");
	fprintf (stderr, "   Switch vectors are evaluated to 0,1 from Off,On.\n");
	fprintf (stderr, "   Light vectors are evaluated to 0-3 as per _STATE.\n");
	fprintf (stderr, "Examples:\n");
	fprintf (stderr, "   To print 0/1 whether Security.Doors.Front or .Rear are in Alert:\n");
	fprintf (stderr, "     evalINDI -f '\"Security.Doors.Front\"==3 || \"Security.Doors.Rear\"==3'\n");
	fprintf (stderr, "   To exit 0 if the Security property as a whole is in a state of Ok:\n");
	fprintf (stderr, "     evalINDI '\"Security.Security._STATE\"==1'\n");
	fprintf (stderr, "   To wait for RA and Dec to be near zero and watch their values as they change:\n");
	fprintf (stderr, "     evalINDI -t 0 -wo 'abs(\"Mount.EqJ2K.RA\")<.01 && abs(\"Mount.EqJ2K.Dec\")<.01'\n");
	fprintf (stderr, "Exit 0 if expression evaluates to non-0, 1 if 0, else 2\n");

	exit (1);
}

/* compile the given expression else read from stdin.
 * exit(2) if trouble.
 */
static void
compile (char *expr)
{
	char errmsg[1024];
	char *exp = expr;

	if (!exp) {
	    /* read expression from stdin */
	    int nr, nexp = 0;
	    exp = malloc(1024);
	    while ((nr = fread (exp+nexp, 1, 1024, stdin)) > 0)
		exp = realloc (exp, (nexp+=nr)+1024);
	    exp[nexp] = '\0';
	}

	if (verbose)
	    fprintf (stderr, "Compiling: %s\n", exp);
	if (compileExpr (exp, errmsg) < 0) {
	    fprintf (stderr, "Compile err: %s\n", errmsg);
	    exit(2);
	}

	if (exp != expr)
	    free (exp);
}

/* open a connection to the given host and port or die.
 * return FILE pointer to socket.
 */
static FILE *
openINDIServer (void)
{
	struct sockaddr_in serv_addr;
	struct hostent *hp;
	int sockfd;

	/* lookup host address */
	hp = gethostbyname (host);
	if (!hp) {
	    perror ("gethostbyname");
	    exit (2);
	}

	/* create a socket to the INDI server */
	(void) memset ((char *)&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr =
			    ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
	serv_addr.sin_port = htons(port);
	if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
	    perror ("socket");
	    exit(2);
	}

	/* connect */
	if (connect (sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0){
	    perror ("connect");
	    exit(2);
	}

	/* prepare for line-oriented i/o with client */
	return (fdopen (sockfd, "r+"));
}

/* invite each device referenced in the expression to report its properties.
 */
static void
getProps(FILE *fp)
{
	char **ops;
	int nops;
	int i, j;

	/* get each operand used in the expression */
	nops = getAllOperands (&ops);

	/* send getProperties for each unique device referenced */
	for (i = 0; i < nops; i++) {
	    for (j = 0; j < i; j++)
		if (devcmp (ops[i], ops[j]) == 0)
		    break;
	    if (j < i)
		continue;
	    if (verbose)
		fprintf (stderr, "sending getProperties for %.*s\n",
                    (int)(strchr (ops[i],'.')-ops[i]), ops[i]);
	    fprintf (fp, "<getProperties version='%g' device='%.*s'/>\n", INDIV,
                    (int)(strchr (ops[i],'.')-ops[i]), ops[i]);
	}
}

/* wait for defXXX or setXXX for each property in the expression.
 * return when find all operands are found or
 * exit(2) if time out waiting for all known operands.
 */
static void
initProps (FILE *fp)
{
	alarm (timeout);
	while (allOperandsSet() < 0) {
	    if (setOp (nxtEle (fp)) == 0)
		alarm(timeout);
	}
	alarm (0);
}

/* pull apart the name and value from the given message, and set operand value.
 * ignore any other messages.
 * return 0 if found a recognized operand else -1
 */
static int
setOp (XMLEle *root)
{
	char *t = tagXMLEle (root);
        const char *d = findXMLAttValu (root, "device");
        const char *n = findXMLAttValu (root, "name");
	int nset = 0;
	double v;
	char prop[1024];
	XMLEle *ep;

	/* check values */
	if (!strcmp (t,"defNumberVector") || !strcmp (t,"setNumberVector")) {
	    for (ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		char *et = tagXMLEle(ep);
		if (!strcmp (et,"defNumber") || !strcmp (et,"oneNumber")) {
		    sprintf (prop, "%s.%s.%s", d, n, findXMLAttValu(ep,"name"));
		    v = atof(pcdataXMLEle(ep));
		    if (setOperand (prop, v) == 0) {
			nset++;
			if (oflag)
			    fprintf (stderr, "%s=%g\n", prop, v);
		    }
		}
	    }
	} else if(!strcmp(t,"defSwitchVector") || !strcmp(t,"setSwitchVector")){
	    for (ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		char *et = tagXMLEle(ep);
		if (!strcmp (et,"defSwitch") || !strcmp (et,"oneSwitch")) {
		    sprintf (prop, "%s.%s.%s", d, n, findXMLAttValu(ep,"name"));
		    v = (double)!strcmp(pcdataXMLEle(ep),"On");
		    if (setOperand (prop, v) == 0) {
			nset++;
			if (oflag)
			    fprintf (stderr, "%s=%g\n", prop, v);
		    }
		}
	    }
	} else if(!strcmp(t,"defLightVector") || !strcmp(t,"setLightVector")){
	    for (ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		char *et = tagXMLEle(ep);
		if (!strcmp (et,"defLight") || !strcmp (et,"oneLight")) {
		    sprintf (prop, "%s.%s.%s", d, n, findXMLAttValu(ep,"name"));
		    v = (double)pstatestr(pcdataXMLEle(ep));
		    if (setOperand (prop, v) == 0) {
			nset++;
			if (oflag)
			    fprintf (stderr, "%s=%g\n", prop, v);
		    }
		}
	    }
	}

	/* check special elements */
        t = (char *) findXMLAttValu (root, "state");
	if (t[0]) {
	    sprintf (prop, "%s.%s._STATE", d, n);
	    v = (double)pstatestr(t);
	    if (setOperand (prop, v) == 0) {
		nset++;
		if (oflag)
		    fprintf (stderr, "%s=%g\n", prop, v);
	    }
	}
        t = (char *) findXMLAttValu (root, "timestamp");
	if (t[0]) {
	    sprintf (prop, "%s.%s._TS", d, n);
	    v = (double)timestamp(t);
	    if (setOperand (prop, v) == 0) {
		nset++;
		if (oflag)
		    fprintf (stderr, "%s=%g\n", prop, v);
	    }
	}

	/* return whether any were set */
	return (nset > 0 ? 0 : -1);
}

/* evaluate the expression after seeing any operand change.
 * return whether expression evaluated to 0.
 * exit(2) is trouble or timeout waiting for operands we expect.
 */
static int
runEval (FILE *fp)
{
	char errmsg[1024];
	double v;

	alarm(timeout);
	while (1) {
	    if (evalExpr (&v, errmsg) < 0) {
		fprintf (stderr, "Eval: %s\n", errmsg);
		exit(2);
	    }
	    if (bflag && v)
		fprintf (stderr, "\a");
	    if (eflag)
		fprintf (stderr, "%g\n", v);
	    if (!wflag || v != 0)
		break;
	    while (setOp (nxtEle (fp)) < 0)
		continue;
	    alarm(timeout);
	}
	alarm(0);

	if (!eflag && fflag)
	    fprintf (stderr, "%g\n", v);

	return (v == 0);
}

/* return 0|1|2|3 depending on whether state is Idle|Ok|Busy|<other>.
 */
static int
pstatestr (char *state)
{
	if (!strcmp (state, "Idle"))
	    return (0);
	if (!strcmp (state, "Ok"))
	    return (1);
	if (!strcmp (state, "Busy"))
	    return (2);
	return (3);
}

/* return UNIX time for the given ISO 8601 time string
 */
static time_t
timestamp (char *ts)
{
	struct tm tm;

	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)) {
	    tm.tm_mon -= 1;		/* want 0..11 */
	    tm.tm_year -= 1900;		/* want years since 1900 */
	    return (mktime (&tm));
	} else
	    return ((time_t)-1);
}

/* return 0 if the device portion of the two given property specs match, else 1
 */
static int
devcmp (char *op1, char *op2)
{
	int n1 = strchr(op1,'.') - op1;
	int n2 = strchr(op2,'.') - op2;
	return (n1 != n2 || strncmp (op1,op2,n1));
}

/* monitor server and return the next complete XML message.
 * exit(2) if time out.
 * N.B. caller must call delXMLEle()
 */
static XMLEle *
nxtEle (FILE *fp)
{
	char msg[1024];

	/* read from server, exit if trouble or see malformed XML */
	while(1) {
	    XMLEle *root = readXMLEle (lillp, readServerChar(fp), msg);
	    if (root) {
		/* found a complete XML element */
		if (verbose > 1)
		    prXMLEle (stderr, root, 0);
		return (root);
	    } else if (msg[0]) {
		fprintf (stderr, "Bad XML from %s/%d: %s\n", host, port, msg);
		exit(2);
	    }
	}
}

/* read next char from the INDI server connected by fp */
static int
readServerChar (FILE *fp)
{
	int c = fgetc (fp);

	if (c == EOF) {
	    if (ferror(fp))
		perror ("read");
	    else
		fprintf (stderr,"INDI server %s/%d disconnected\n", host, port);
	    exit (2);
	}

	if (verbose > 2)
	    fprintf (stderr, "Read %c\n", c);

	return (c);
}

/* called after timeout seconds waiting to hear from server.
 * print reason for trouble and exit(2).
 */
static void
onAlarm (int dummy)
{
	char **ops;
	int nops;

	/* report any unseen operands if any, else just say timed out */
	if ((nops = getUnsetOperands (&ops)) > 0) {
	    fprintf (stderr, "No values seen for");
	    while (nops-- > 0)
		fprintf (stderr, " %s", ops[nops]);
	    fprintf (stderr, "\n");
	} else 
	    fprintf (stderr, "Timed out waiting for new values\n");

	exit (2);
}