#if 0
    INDI
    Copyright (C) 2003 Elwood C. Downey

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#endif

/* main() for one INDI driver process.
 * Drivers define IS*() functions we call to deliver INDI XML arriving on stdin.
 * Drivers call ID*() functions to send INDI XML commands to stdout.
 * Drivers call IE*() functions to build an event-driver program.
 * Drivers call IU*() functions to perform various common utility tasks.
 * Troubles are reported on stderr then we exit.
 *
 * This requires liblilxml.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "lilxml.h"
#include "base64.h"
#include "eventloop.h"
#include "indidevapi.h"
#include "astro.h"

static void usage(void);
static void clientMsgCB(int fd, void *arg);
static int dispatch (XMLEle *root, char msg[]);
static int crackDN (XMLEle *root, char **dev, char **name, char msg[]);
static char *pstateStr(IPState s);
static char *sstateStr(ISState s);
static char *ruleStr(ISRule r);
static char *permStr(IPerm p);
static char *timestamp (void);
static void xmlv1(void);

static int verbose;			/* chatty */
static char *me;			/* a.out name */
static LilXML *clixml;			/* XML parser context */

int
main (int ac, char *av[])
{
	/* save handy pointer to our base name */
	for (me = av[0]; av[0][0]; av[0]++)
	    if (av[0][0] == '/')
		me = &av[0][1];

	/* crack args */
	while (--ac && (*++av)[0] == '-')
	    while (*++(*av))
		switch (*(*av)) {
		case 'v':	/* verbose */
		    verbose++;
		    break;
		default:
		    usage();
		}

	/* ac remaining args starting at av[0] */
	if (ac > 0)
	    usage();

	/* init */
	clixml =  newLilXML();
	addCallback (0, clientMsgCB, NULL);

	/* service client */
	eventLoop();

	/* eh?? */
	fprintf (stderr, "%s: inf loop ended\n", me);
	return (1);
}

/* functions we define that drivers may call */

/* tell client to create a text vector property */
void
IDDefText (const ITextVectorProperty *tvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<defTextVector\n");
	printf ("  device='%s'\n", tvp->device);
	printf ("  name='%s'\n", tvp->name);
	printf ("  label='%s'\n", tvp->label);
	printf ("  group='%s'\n", tvp->group);
	printf ("  state='%s'\n", pstateStr(tvp->s));
	printf ("  perm='%s'\n", permStr(tvp->p));
	printf ("  timeout='%g'\n", tvp->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < tvp->ntp; i++) {
	    IText *tp = &tvp->tp[i];
	    printf ("  <defText\n");
	    printf ("    name='%s'\n", tp->name);
	    printf ("    label='%s'>\n", tp->label);
	    printf ("      %s\n", tp->text ? tp->text : "");
	    printf ("  </defText>\n");
	}

	printf ("</defTextVector>\n");
	fflush (stdout);
}

/* tell client to create a new numeric vector property */
void
IDDefNumber (const INumberVectorProperty *n, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<defNumberVector\n");
	printf ("  device='%s'\n", n->device);
	printf ("  name='%s'\n", n->name);
	printf ("  label='%s'\n", n->label);
	printf ("  group='%s'\n", n->group);
	printf ("  state='%s'\n", pstateStr(n->s));
	printf ("  perm='%s'\n", permStr(n->p));
	printf ("  timeout='%g'\n", n->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < n->nnp; i++) {
	    INumber *np = &n->np[i];
	    printf ("  <defNumber\n");
	    printf ("    name='%s'\n", np->name);
	    printf ("    label='%s'\n", np->label);
	    printf ("    format='%s'\n", np->format);
	    printf ("    min='%.20g'\n", np->min);
	    printf ("    max='%.20g'\n", np->max);
	    printf ("    step='%.20g'>\n", np->step);
	    printf ("      %.20g\n", np->value);
	    printf ("  </defNumber>\n");
	}

	printf ("</defNumberVector>\n");
	fflush (stdout);
}

/* tell client to create a new switch vector property */
void
IDDefSwitch (const ISwitchVectorProperty *s, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<defSwitchVector\n");
	printf ("  device='%s'\n", s->device);
	printf ("  name='%s'\n", s->name);
	printf ("  label='%s'\n", s->label);
	printf ("  group='%s'\n", s->group);
	printf ("  state='%s'\n", pstateStr(s->s));
	printf ("  perm='%s'\n", permStr(s->p));
	printf ("  rule='%s'\n", ruleStr (s->r));
	printf ("  timeout='%g'\n", s->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < s->nsp; i++) {
	    ISwitch *sp = &s->sp[i];
	    printf ("  <defSwitch\n");
	    printf ("    name='%s'\n", sp->name);
	    printf ("    label='%s'>\n", sp->label);
	    printf ("      %s\n", sstateStr(sp->s));
	    printf ("  </defSwitch>\n");
	}

	printf ("</defSwitchVector>\n");
	fflush (stdout);
}

/* tell client to create a new lights vector property */
void
IDDefLight (const ILightVectorProperty *lvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<defLightVector\n");
	printf ("  device='%s'\n", lvp->device);
	printf ("  name='%s'\n", lvp->name);
	printf ("  label='%s'\n", lvp->label);
	printf ("  group='%s'\n", lvp->group);
	printf ("  state='%s'\n", pstateStr(lvp->s));
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < lvp->nlp; i++) {
	    ILight *lp = &lvp->lp[i];
	    printf ("  <defLight\n");
	    printf ("    name='%s'\n", lp->name);
	    printf ("    label='%s'>\n", lp->label);
	    printf ("      %s\n", pstateStr(lp->s));
	    printf ("  </defLight>\n");
	}

	printf ("</defLightVector>\n");
	fflush (stdout);
}

/* tell client to create a new BLOB vector property */
void
IDDefBLOB (const IBLOBVectorProperty *b, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<defBLOBVector\n");
	printf ("  device='%s'\n", b->device);
	printf ("  name='%s'\n", b->name);
	printf ("  label='%s'\n", b->label);
	printf ("  group='%s'\n", b->group);
	printf ("  state='%s'\n", pstateStr(b->s));
	printf ("  perm='%s'\n", permStr(b->p));
	printf ("  timeout='%g'\n", b->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < b->nbp; i++) {
	    IBLOB *bp = &b->bp[i];
	    printf ("  <defBLOB\n");
	    printf ("    name='%s'\n", bp->name);
	    printf ("    label='%s'\n", bp->label);
	    printf ("  />\n");
	}

	printf ("</defBLOBVector>\n");
	fflush (stdout);
}

/* tell client to update an existing text vector property */
void
IDSetText (const ITextVectorProperty *tvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<setTextVector\n");
	printf ("  device='%s'\n", tvp->device);
	printf ("  name='%s'\n", tvp->name);
	printf ("  state='%s'\n", pstateStr(tvp->s));
	printf ("  timeout='%g'\n", tvp->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < tvp->ntp; i++) {
	    IText *tp = &tvp->tp[i];
	    printf ("  <oneText name='%s'>\n", tp->name);
	    printf ("      %s\n", tp->text ? tp->text : "");
	    printf ("  </oneText>\n");
	}

	printf ("</setTextVector>\n");
	fflush (stdout);
}

/* tell client to update an existing numeric vector property */
void
IDSetNumber (const INumberVectorProperty *nvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<setNumberVector\n");
	printf ("  device='%s'\n", nvp->device);
	printf ("  name='%s'\n", nvp->name);
	printf ("  state='%s'\n", pstateStr(nvp->s));
	printf ("  timeout='%g'\n", nvp->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < nvp->nnp; i++) {
	    INumber *np = &nvp->np[i];
	    printf ("  <oneNumber name='%s'>\n", np->name);
	    printf ("      %.20g\n", np->value);
	    printf ("  </oneNumber>\n");
	}

	printf ("</setNumberVector>\n");
	fflush (stdout);
}

/* tell client to update an existing switch vector property */
void
IDSetSwitch (const ISwitchVectorProperty *svp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<setSwitchVector\n");
	printf ("  device='%s'\n", svp->device);
	printf ("  name='%s'\n", svp->name);
	printf ("  state='%s'\n", pstateStr(svp->s));
	printf ("  timeout='%g'\n", svp->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < svp->nsp; i++) {
	    ISwitch *sp = &svp->sp[i];
	    printf ("  <oneSwitch name='%s'>\n", sp->name);
	    printf ("      %s\n", sstateStr(sp->s));
	    printf ("  </oneSwitch>\n");
	}

	printf ("</setSwitchVector>\n");
	fflush (stdout);
}

/* tell client to update an existing lights vector property */
void
IDSetLight (const ILightVectorProperty *lvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<setLightVector\n");
	printf ("  device='%s'\n", lvp->device);
	printf ("  name='%s'\n", lvp->name);
	printf ("  state='%s'\n", pstateStr(lvp->s));
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < lvp->nlp; i++) {
	    ILight *lp = &lvp->lp[i];
	    printf ("  <oneLight name='%s'>\n", lp->name);
	    printf ("      %s\n", pstateStr(lp->s));
	    printf ("  </oneLight>\n");
	}

	printf ("</setLightVector>\n");
	fflush (stdout);
}

/* tell client to update an existing BLOB vector property */
void
IDSetBLOB (const IBLOBVectorProperty *bvp, const char *fmt, ...)
{
	int i;

	xmlv1();
	printf ("<setBLOBVector\n");
	printf ("  device='%s'\n", bvp->device);
	printf ("  name='%s'\n", bvp->name);
	printf ("  state='%s'\n", pstateStr(bvp->s));
	printf ("  timeout='%g'\n", bvp->timeout);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf (">\n");

	for (i = 0; i < bvp->nbp; i++) {
	    IBLOB *bp = &bvp->bp[i];
	    unsigned char *encblob;
	    int j, l;

	    printf ("  <oneBLOB\n");
	    printf ("    name='%s'\n", bp->name);
	    printf ("    size='%d'\n", bp->size);
	    printf ("    format='%s'>\n", bp->format);

	    encblob = malloc (4*bp->bloblen/3+4);
	    l = to64frombits(encblob, bp->blob, bp->bloblen);
	    for (j = 0; j < l; j += 72)
		printf ("%.72s\n", encblob+j);
	    free (encblob);

	    printf ("  </oneBLOB>\n");
	}

	printf ("</setBLOBVector>\n");
	fflush (stdout);
}

/* send client a message for a specific device or at large if !dev */
void
IDMessage (const char *dev, const char *fmt, ...)
{
	xmlv1();
	printf ("<message\n");
	if (dev)
	    printf (" device='%s'\n", dev);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf ("/>\n");
	fflush (stdout);
}

/* tell Client to delete the property with given name on given device, or
 * entire device if !name
 */
void
IDDelete (const char *dev, const char *name, const char *fmt, ...)
{
	xmlv1();
	printf ("<delProperty\n  device='%s'\n", dev);
	if (name)
	    printf (" name='%s'\n", name);
	printf ("  timestamp='%s'\n", timestamp());
	if (fmt) {
	    va_list ap;
	    va_start (ap, fmt);
	    printf ("  message='");
	    vprintf (fmt, ap);
	    printf ("'\n");
	    va_end (ap);
	}
	printf ("/>\n");
	fflush (stdout);
}

/* log message locally.
 * this has nothing to do with XML or any Clients.
 */
void
IDLog (const char *fmt, ...)
{
  	va_list ap;
	fprintf (stderr, "%s ", timestamp());
	va_start (ap, fmt);
	vfprintf (stderr, fmt, ap);
	va_end (ap);
}

/* "INDI" wrappers to the more generic eventloop facility. */

int
IEAddCallback (int readfiledes, IE_CBF *fp, void *p)
{
	return (addCallback (readfiledes, (CBF*)fp, p));
}

void
IERmCallback (int callbackid)
{
	rmCallback (callbackid);
}

int
IEAddTimer (int millisecs, IE_TCF *fp, void *p)
{
	return (addTimer (millisecs, (TCF*)fp, p));
}

void
IERmTimer (int timerid)
{
	rmTimer (timerid);
}

int
IEAddWorkProc (IE_WPF *fp, void *p)
{
	return (addWorkProc ((WPF*)fp, p));
}

void
IERmWorkProc (int workprocid)
{
	rmWorkProc (workprocid);
}

int
IEDeferLoop (int maxms, int *flagp)
{
	return (deferLoop (maxms, flagp));
}

/* find a member of an IText vector, else NULL */
IText *
IUFindText  (const ITextVectorProperty *tvp, const char *name)
{
	int i;

	for (i = 0; i < tvp->ntp; i++)
	    if (strcmp (tvp->tp[i].name, name) == 0)
		return (&tvp->tp[i]);
	fprintf (stderr, "No IText '%s' in %s.%s\n",name,tvp->device,tvp->name);
	return (NULL);
}

/* convenience function for use in your implementation of ISNewText().
 * given a candidate Text and the args from ISNewText, fill in the
 * elements and return 0 else return -1 if it's the wrong candidate or not
 * all the elements are present.
 */
int
IUCrackText (ITextVectorProperty *tvp, const char *dev, const char *name,
char *texts[], char *names[], int n)
{
	int i, j;

	if (strcmp(dev,tvp->device) || strcmp(name,tvp->name))
	    return (-1);	/* not this property */

	for (i = 0; i < tvp->ntp; i++) {
	    for (j = 0; j < n; j++) {
		if (!strcmp(tvp->tp[i].name, names[j])) {
		    IUSaveText (&tvp->tp[i], texts[j]);
		    break;
		}
	    }
	    if (j == n)
		return (-1);	/* missing element */
	}
	return (0);
}

/* find a member of an INumber vector, else NULL */
INumber *
IUFindNumber(const INumberVectorProperty *nvp, const char *name)
{
	int i;

	for (i = 0; i < nvp->nnp; i++)
	    if (strcmp (nvp->np[i].name, name) == 0)
		return (&nvp->np[i]);
	fprintf(stderr,"No INumber '%s' in %s.%s\n",name,nvp->device,nvp->name);
	return (NULL);
}

/* convenience function for use in your implementation of ISNewNumber().
 * given a candidate Number and the args from ISNewNumber, fill in the
 * elements and return 0 else return -1 if it's the wrong candidate or not
 * all the elements are present.
 */
int
IUCrackNumber (INumberVectorProperty *nvp, const char *dev, const char *name,
double *doubles, char *names[], int n)
{
	int i, j;

	if (strcmp(dev,nvp->device) || strcmp(name,nvp->name))
	    return (-1);	/* not this property */

	for (i = 0; i < nvp->nnp; i++) {
	    for (j = 0; j < n; j++) {
		if (!strcmp(nvp->np[i].name, names[j])) {
		    nvp->np[i].value = doubles[j];
		    break;
		}
	    }
	    if (j == n)
		return (-1);	/* missing element */
	}
	return (0);
}

/* find a member of an ISwitch vector, else NULL */
ISwitch *
IUFindSwitch(const ISwitchVectorProperty *svp, const char *name)
{
	int i;

	for (i = 0; i < svp->nsp; i++)
	    if (strcmp (svp->sp[i].name, name) == 0)
		return (&svp->sp[i]);
	fprintf(stderr,"No ISwitch '%s' in %s.%s\n",name,svp->device,svp->name);
	return (NULL);
}

/* find an ON member of an ISwitch vector, else NULL.
 * N.B. user must make sense of result with ISRule in mind.
 */
ISwitch *
IUFindOnSwitch(const ISwitchVectorProperty *svp)
{
	int i;

	for (i = 0; i < svp->nsp; i++)
	    if (svp->sp[i].s == ISS_ON)
		return (&svp->sp[i]);
	fprintf(stderr, "No ISwitch On in %s.%s\n", svp->device, svp->name);
	return (NULL);
}

/* Set all switches to off */
void 
IUResetSwitches(const ISwitchVectorProperty *svp)
{
	int i;
    
	for (i = 0; i < svp->nsp; i++)
	    svp->sp[i].s = ISS_OFF;
}

/* use this to set the text value of an IText.
 * save malloced copy of newtext in tp->text, reusing if not first time.
 * N.B. don't mix using this with setting tp->text directly!
 */
void
IUSaveText (IText *tp, const char *newtext)
{
	/* seed for realloc */
	if (tp->text == NULL)
	    tp->text = malloc (1);

	/* copy in fresh string, or free and reset if NULL */
	if (newtext == NULL)
	    newtext = "";
	tp->text = strcpy (realloc (tp->text, strlen(newtext)+1), newtext);
}


/* print usage message and exit (1) */
static void
usage(void)
{
	fprintf (stderr, "Usage: %s [options]\n", me);
	fprintf (stderr, "Purpose: INDI Device driver framework.\n");
	fprintf (stderr, "Options:\n");
	fprintf (stderr, " -v    : more verbose to stderr\n");

	exit (1);
}

/* callback when INDI client message arrives on stdin.
 * collect and dispatch when see outter element closure.
 * exit if OS trouble or see incompatable INDI version.
 * arg is not used.
 */
static void
clientMsgCB (int fd, void *arg)
{
	char buf[1024], msg[1024], *bp;
	int nr;

	/* one read */
	nr = read (fd, buf, sizeof(buf));
	if (nr < 0) {
	    fprintf (stderr, "%s: %s\n", me, strerror(errno));
	    exit(1);
	}
	if (nr == 0) {
	    fprintf (stderr, "%s: EOF\n", me);
	    exit(1);
	}

	/* crack and dispatch when complete */
	for (bp = buf; nr-- > 0; bp++) {
	    XMLEle *root = readXMLEle (clixml, *bp, msg);
	    if (root) {
		if (dispatch (root, msg) < 0)
		    fprintf (stderr, "%s dispatch error: %s\n", me, msg);
		delXMLEle (root);
	    } else if (msg[0])
		fprintf (stderr, "%s XML error: %s\n", me, msg);
	}
}

/* crack the given INDI XML element and call driver's IS* entry points as they
 *   are recognized.
 * return 0 if ok else -1 with reason in msg[].
 * N.B. exit if getProperties does not proclaim a compatible version.
 */
static int
dispatch (XMLEle *root, char msg[])
{
	XMLEle *ep;
	int n;

	if (verbose)
	    prXMLEle (stderr, root, 0);

	/* check tag in surmised decreasing order of likelyhood */

	if (!strcmp (tagXMLEle(root), "newNumberVector")) {
	    static double *doubles;
	    static char **names;
	    static int maxn;
	    char *dev, *name;

	    /* pull out device and name */
	    if (crackDN (root, &dev, &name, msg) < 0)
		return (-1);

	    /* seed for reallocs */
	    if (!doubles) {
		doubles = (double *) malloc (1);
		names = (char **) malloc (1);
	    }

	    /* pull out each name/value pair */
	    for (n = 0, ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		if (strcmp (tagXMLEle(ep), "oneNumber") == 0) {
		    XMLAtt *na = findXMLAtt (ep, "name");
		    if (na) {
			if (n >= maxn) {
			    /* grow for this and another */
			    int newsz = (maxn=n+1)*sizeof(double);
			    doubles = (double *) realloc(doubles,newsz);
			    newsz = maxn*sizeof(char *);
			    names = (char **) realloc (names, newsz);
			}
			if (f_scansexa (pcdataXMLEle(ep), &doubles[n]) < 0)
			    IDMessage (dev,"%s: Bad format %s", name,
							    pcdataXMLEle(ep));
			else
			    names[n++] = valuXMLAtt(na);
		    }
		}
	    }

	    /* invoke driver if something to do, but not an error if not */
	    if (n > 0)
		ISNewNumber (dev, name, doubles, names, n);
	    else
		IDMessage(dev,"%s: newNumberVector with no valid members",name);
	    return (0);
	}

	if (!strcmp (tagXMLEle(root), "newSwitchVector")) {
	    static ISState *states;
	    static char **names;
	    static int maxn;
	    char *dev, *name;
	    XMLEle *ep;

	    /* pull out device and name */
	    if (crackDN (root, &dev, &name, msg) < 0)
		return (-1);

	    /* seed for reallocs */
	    if (!states) {
		states = (ISState *) malloc (1);
		names = (char **) malloc (1);
	    }

	    /* pull out each name/state pair */
	    for (n = 0, ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		if (strcmp (tagXMLEle(ep), "oneSwitch") == 0) {
		    XMLAtt *na = findXMLAtt (ep, "name");
		    if (na) {
			if (n >= maxn) {
			    int newsz = (maxn=n+1)*sizeof(ISState);
			    states = (ISState *) realloc(states, newsz);
			    newsz = maxn*sizeof(char *);
			    names = (char **) realloc (names, newsz);
			}
			if (strcmp (pcdataXMLEle(ep),"On") == 0) {
			    states[n] = ISS_ON;
			    names[n] = valuXMLAtt(na);
			    n++;
			} else if (strcmp (pcdataXMLEle(ep),"Off") == 0) {
			    states[n] = ISS_OFF;
			    names[n] = valuXMLAtt(na);
			    n++;
			} else 
			    IDMessage (dev, "%s: must be On or Off: %s", name,
							    pcdataXMLEle(ep));
		    }
		}
	    }

	    /* invoke driver if something to do, but not an error if not */
	    if (n > 0)
		ISNewSwitch (dev, name, states, names, n);
	    else
		IDMessage(dev,"%s: newSwitchVector with no valid members",name);
	    return (0);
	}

	if (!strcmp (tagXMLEle(root), "newTextVector")) {
	    static char **texts;
	    static char **names;
	    static int maxn;
	    char *dev, *name;

	    /* pull out device and name */
	    if (crackDN (root, &dev, &name, msg) < 0)
		return (-1);

	    /* seed for reallocs */
	    if (!texts) {
		texts = (char **) malloc (1);
		names = (char **) malloc (1);
	    }

	    /* pull out each name/text pair */
	    for (n = 0, ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		if (strcmp (tagXMLEle(ep), "oneText") == 0) {
		    XMLAtt *na = findXMLAtt (ep, "name");
		    if (na) {
			if (n >= maxn) {
			    int newsz = (maxn=n+1)*sizeof(char *);
			    texts = (char **) realloc (texts, newsz);
			    names = (char **) realloc (names, newsz);
			}
			texts[n] = pcdataXMLEle(ep);
			names[n] = valuXMLAtt(na);
			n++;
		    }
		}
	    }

	    /* invoke driver if something to do, but not an error if not */
	    if (n > 0)
		ISNewText (dev, name, texts, names, n);
	    else
		IDMessage (dev, "%s: newTextVector with no valid members",name);
	    return (0);
	}

	if (!strcmp (tagXMLEle(root), "newBLOBVector")) {
	    static char **blobs;
	    static char **names;
	    static char **formats;
	    static int *blobsizes;
	    static int *sizes;
	    static int maxn;
	    char *dev, *name;
	    int i;

	    /* pull out device and name */
	    if (crackDN (root, &dev, &name, msg) < 0)
		return (-1);

	    /* seed for reallocs */
	    if (!blobs) {
		blobs = (char **) malloc (1);
		names = (char **) malloc (1);
		formats = (char **) malloc (1);
		blobsizes = (int *) malloc (1);
		sizes = (int *) malloc (1);
	    }

	    /* pull out each name/BLOB pair, decode */
	    for (n = 0, ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0)) {
		if (strcmp (tagXMLEle(ep), "oneBLOB") == 0) {
		    XMLAtt *na = findXMLAtt (ep, "name");
		    XMLAtt *fa = findXMLAtt (ep, "format");
		    XMLAtt *sa = findXMLAtt (ep, "size");
		    if (na && fa && sa) {
			if (n >= maxn) {
			    int newsz = (maxn=n+1)*sizeof(char *);
			    blobs = (char **) realloc (blobs, newsz);
			    names = (char **) realloc (names, newsz);
			    formats = (char **) realloc(formats,newsz);
			    newsz = maxn*sizeof(int);
			    sizes = (int *) realloc(sizes,newsz);
			    blobsizes = (int *) realloc(blobsizes,newsz);
			}
			blobs[n] = malloc (3*pcdatalenXMLEle(ep)/4);
			blobsizes[n] = from64tobits(blobs[n], pcdataXMLEle(ep));
			names[n] = valuXMLAtt(na);
			formats[n] = valuXMLAtt(fa);
			sizes[n] = atoi(valuXMLAtt(sa));
			n++;
		    }
		}
	    }

	    /* invoke driver if something to do, but not an error if not */
	    if (n > 0) {
		ISNewBLOB (dev, name, sizes, blobsizes, blobs, formats,names,n);
		for (i = 0; i < n; i++)
		    free (blobs[i]);
	    } else
		IDMessage (dev, "%s: newBLOBVector with no valid members",name);
	    return (0);
	}

	if (!strcmp (tagXMLEle(root), "getProperties")) {
	    XMLAtt *ap;
	    double v;

	    /* check version */
	    ap = findXMLAtt (root, "version");
	    if (!ap) {
		fprintf (stderr, "%s: getProperties missing version\n", me);
		exit(1);
	    }
	    v = atof (valuXMLAtt(ap));
	    if (v > INDIV) {
		fprintf (stderr, "%s: client version %g > %g\n", me, v, INDIV);
		exit(1);
	    }

	    /* ok */
	    ap = findXMLAtt (root, "device");
	    ISGetProperties (ap ? valuXMLAtt(ap) : NULL);
	    return (0);
	}

	sprintf (msg, "Unknown command: %s", tagXMLEle(root));
	return(1);
}

/* pull out device and name attributes from root.
 * return 0 if ok else -1 with reason in msg[].
 */
static int
crackDN (XMLEle *root, char **dev, char **name, char msg[])
{
	XMLAtt *ap;

	ap = findXMLAtt (root, "device");
	if (!ap) {
	    sprintf (msg, "%s requires 'device' attribute", tagXMLEle(root));
	    return (-1);
	}
	*dev = valuXMLAtt(ap);

	ap = findXMLAtt (root, "name");
	if (!ap) {
	    sprintf (msg, "%s requires 'name' attribute", tagXMLEle(root));
	    return (-1);
	}
	*name = valuXMLAtt(ap);

	return (0);
}

/* return static string corresponding to the given property or light state */
static char *
pstateStr (IPState s)
{
	switch (s) {
	case IPS_IDLE:  return ("Idle");
	case IPS_OK:    return ("Ok");
	case IPS_BUSY:  return ("Busy");
	case IPS_ALERT: return ("Alert");
	default:
	    fprintf (stderr, "Impossible IPState %d\n", s);
	    exit(1);
	}
}

/* return static string corresponding to the given switch state */
static char *
sstateStr (ISState s)
{
	switch (s) {
	case ISS_ON:  return ("On");
	case ISS_OFF: return ("Off");
	default:
	    fprintf (stderr, "Impossible ISState %d\n", s);
	    exit(1);
	}
}

/* return static string corresponding to the given Rule */
static char *
ruleStr (ISRule r)
{
	switch (r) {
	case ISR_1OFMANY: return ("OneOfMany");
	case ISR_ATMOST1: return ("AtMostOne");
	case ISR_NOFMANY: return ("AnyOfMany");
	default:
	    fprintf (stderr, "Impossible ISRule %d\n", r);
	    exit(1);
	}
}

/* return static string corresponding to the given IPerm */
static char *
permStr (IPerm p)
{
	switch (p) {
	case IP_RO: return ("ro");
	case IP_WO: return ("wo");
	case IP_RW: return ("rw");
	default:
	    fprintf (stderr, "Impossible IPerm %d\n", p);
	    exit(1);
	}
}

/* return current system time in message format */
static char *
timestamp()
{
	static char ts[32];
	struct tm *tp;
	time_t t;

	time (&t);
	tp = gmtime (&t);
	strftime (ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", tp);
	return (ts);
}

/* print the boilerplate comment introducing xml */
static void
xmlv1()
{
	printf ("<?xml version='1.0'?>\n");
}

/* For RCS Only -- Do Not Edit */
static char *rcsid[2] = {(char *)rcsid, "@(#) $RCSfile: indidrivermain.c,v $ $Date: 2006/06/29 11:09:36 $ $Revision: 1.35 $ $Name:  $"};
