/*
x10d.c (c) Copyright Daniel D. Lanciani 1995-1999
All rights reserved.

x10d.c is licensed free of charge for non-commercial distribution and for
personal and internal business use only.  x10d.c may not be distributed
for profit, nor may it be included in products or otherwise distributed by
commercial entities to their clients or customers without the prior written
permission of the author.

TO THE EXTENT ALLOWED BY APPLICABLE LAW, x10d.c IS PROVIDED
"AS IS", WITH NO EXPRESS OR IMPLIED WARRANTY, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL THE AUTHOR BE
LIABLE FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO
USE x10d.c EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.

These copyright, license, and disclaimer notices must be included
with all copies of x10d.c.  Modified versions of x10d.c must be
marked as such.

ddl@danlan.com
*/

#define REPEATER		/* repeater in use */

#include <sys/types.h>

#ifdef __linux__
  #include <string.h> 		// memchar()
  #include <time.h>             // ctime()
  #include <stdlib.h>		// malloc(), realloc()
#endif

#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <signal.h>
#include <syslog.h>

#ifdef __linux__
  #include <termios.h>
 
  struct termios oldsb, newsb;
#else
  struct sgttyb sgttyb;
  char *ctime(), *memchr(), *malloc(), *realloc();
#endif

char rbuf[128], buf[128];
int lsocket, n, nclient, maxclient = 20, on = 1, off = 0, firmware;
static struct sockaddr_in sin = { AF_INET };
struct linger linger = { 1, 0 };
static fd_set xprobe;

struct clients {
	int s, cnt, flags;
	char buf[128];
} *clients;

#define C_QUIET 0x0001

#define X10_ALL_UNITS_OFF 17
#define X10_ALL_LIGHTS_ON 18
#define X10_ON 19
#define X10_OFF 20
#define X10_DIM 21
#define X10_BRIGHT 22
#define X10_ALL_LIGHTS_OFF 23
#define X10_EXTENDED 24
#define X10_STATUS_ON 30
#define X10_STATUS_OFF 31
#define X10_STATUS_REQUEST 32

struct x10aliases {
	char *name;
	int code;
} x10aliases[] = {
	{ "ALLUNITSOFF", X10_ALL_UNITS_OFF },
	{ "ALLLIGHTSON", X10_ALL_LIGHTS_ON },
	{ "ON", X10_ON },
	{ "OFF", X10_OFF },
	{ "DIM", X10_DIM },
	{ "BRIGHT", X10_BRIGHT },
	{ 0, 0 }
};

char *x10longnames[] = {
	"",
	"Unit 1",
	"Unit 2",
	"Unit 3",
	"Unit 4",
	"Unit 5",
	"Unit 6",
	"Unit 7",
	"Unit 8",
	"Unit 9",
	"Unit 10",
	"Unit 11",
	"Unit 12",
	"Unit 13",
	"Unit 14",
	"Unit 15",
	"Unit 16",
	"All Units Off",
	"All Lights On",
	"On",
	"Off",
	"Dim",
	"Bright",
	"All Lights Off",
	"Extended Code",
	"Hail Request",
	"Hail Acknowledge",
	"Preset Dim 0",
	"Preset Dim 1",
	"Extended Data",
	"Status is On",
	"Status is Off",
	"Status Request"
};

struct cm11_setclock {
	char cmd;	/* 0x9b */
	char seconds;
	char minutes;	/* 0-119 */
	char hours;	/* 0-11 (hours/2) */
	char yearday;	/* really 9 bits */
	char daymask;	/* really 7 bits */
	char house;	/* 0:timer purge, 1:monitor clear, 3:battery clear */
};

struct cm11_status {
	short battery;
	char seconds;
	char minutes;
	char hours;
	char yearday;
	char daymask;
	char housefirm;
	short mondev;
	short statdev;
	short dimstat;
};

#ifndef __linux__
#define FD_NULL		(struct fd_set *) 0
#define TIMEVAL_NULL	(struct timeval *) 0
#else
#define FD_NULL		(fd_set *) 0
#define TIMEVAL_NULL	(struct timeval *) 0
#endif

main(argc, argv)
char **argv;
{
	register FILE *m;
	register int i, c;
	register char *p;
	int l;
	struct servent *sp;
	fd_set rset, probe;

#ifndef	DEBUG
	if(fork())
		exit(0);
	close(0);
	close(1);
	close(2);
	if(open("/", 0)) {
		syslog(LOG_CRIT, "x10d: open / not 0: %m");
		exit(1);
	}
	dup2(0, 1);
	dup2(0, 2);
#endif
	signal(SIGPIPE, SIG_IGN);
#ifdef	LOG_LOCAL0
	openlog("x10d", LOG_PID, LOG_LOCAL0);
#else
	openlog("x10d", LOG_PID, 0);
#endif
	syslog(LOG_INFO, "starting");
	if(!(clients = (struct clients *)
		malloc(maxclient * sizeof(struct clients)))) {
		syslog(LOG_CRIT, "no memory for clients");
		exit(1);
	}
	FD_ZERO(&rset);
	FD_ZERO(&xprobe);
	if(!(sp = getservbyname("hamx10", "tcp"))) {
		syslog(LOG_ERR, "hamx10: unknown service");
		exit(1);
	}
	if((lsocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog(LOG_ERR, "socket: %m");
		exit(1);
	}
	sin.sin_port = sp->s_port;
	if(bind(lsocket, &sin, sizeof(sin))) {
		syslog(LOG_ERR, "bind: %m");
		exit(1);
	}
	listen(lsocket, 5);
	ioctl(lsocket, FIONBIO, &on);
	FD_SET(lsocket, &rset);
	if((n = open("/dev/cm11", 2)) < 0) {
		syslog(LOG_ERR, "/dev/cm11: %m");
		exit(1);
	}
#ifndef	DEBUG
	if((c = open("/dev/tty", 2)) >= 0) {
		ioctl(c, TIOCNOTTY, 0);
		close(c);
	}
#endif

#ifndef __linux__
	ioctl(n, TIOCGETP, &sgttyb);
	sgttyb.sg_ispeed = sgttyb.sg_ospeed = B4800;
	sgttyb.sg_flags = RAW;
	ioctl(n, TIOCSETP, &sgttyb);
#else
        /*
        ** This works for heyu (NJC)
        */
 
        i = tcgetattr(n, &oldsb);
 
        if (i < 0) {
        	p = strerror(errno);
                syslog(LOG_CRIT, p);
        	exit(errno);
        }
 
        newsb = oldsb;
          
        newsb.c_iflag = IGNBRK | IGNPAR;
        /*newsb.c_oflag = 0;*/
        newsb.c_oflag = ONLRET;
        newsb.c_lflag = 0;
        /*newsb.c_lflag = ISIG;*/
        newsb.c_cflag = (CLOCAL | B4800 | CS8 | CREAD);

        for (i = 0; i < NCC; i++)
        	newsb.c_cc[i] = 0;

        newsb.c_cc[VMIN]   = 1;
        newsb.c_cc[VTIME]  = 0;
 
        tcsetattr(n, TCSADRAIN, &newsb);

#endif
	ioctl(n, FIONBIO, &on);
	FD_SET(n, &rset);
	setcm11clock();
	while(1) {
		probe = rset;
		if(select(FD_SETSIZE, &probe, FD_NULL,
			FD_NULL, (struct timeval *)0) <= 0) {
			syslog(LOG_ERR, "select: %m");
			sleep(2);
			continue;
		}
		if(FD_ISSET(lsocket, &probe)) {
			l = sizeof(sin);
			if((c = accept(lsocket, &sin, &l)) < 0)
				goto badaccept;
			ioctl(c, FIONBIO, &on);
			if(m = fopen("/usr/local/lib/hcs/x10d.access", "r")) {
                        	strcpy(buf,  "");	/* these are used elsewhere so clear them */
                                strcpy(rbuf, "");
				while(fscanf(m, "%s %s\n", buf, rbuf) == 2)
					if((sin.sin_addr.s_addr &
						inet_addr(rbuf)) ==
						inet_addr(buf))
						break;
				fclose(m);
				if((sin.sin_addr.s_addr & inet_addr(rbuf)) !=
					inet_addr(buf)) {
					close(c);
					goto badaccept;
				}
			}
			if(nclient >= maxclient) {
				maxclient += 20;
				if(!(clients = (struct clients *)realloc(
					(char *)clients, maxclient *
					sizeof(struct clients)))) {
					syslog(LOG_CRIT,"out of client memory");
					exit(1);
				}
			}
			setsockopt(c, SOL_SOCKET, SO_LINGER, &linger,
				sizeof(linger));
			clients[nclient].flags = clients[nclient].cnt = 0;
			clients[nclient++].s = c;
			FD_SET(c, &rset);
		badaccept:;
		}
		for(i = 0; i < nclient; i++)
			if(FD_ISSET(clients[i].s, &probe)) {
				c = sizeof(clients[i].buf) - clients[i].cnt - 1;
				if(c <= 0)
					goto badclient;
				c = read(clients[i].s,
					clients[i].buf + clients[i].cnt, c);
				if(c < 0) {
					if(errno != EWOULDBLOCK)
						goto badclient;
					continue;
				}
				if(c == 0) {
				badclient:
					close(clients[i].s);
					FD_CLR(clients[i].s, &rset);
					nclient--;
					for(c = i; c < nclient; c++)
						clients[c] = clients[c + 1];
					i--;
					continue;
				}
				clients[i].cnt += c;
				while(p = memchr(clients[i].buf, '\n',
					clients[i].cnt)) {
				c = p - clients[i].buf + 1;
				bcopy(clients[i].buf, rbuf, c);
				if(clients[i].cnt -= c)
					bcopy(clients[i].buf + c,
						clients[i].buf, clients[i].cnt);
				for(p = buf, l = c, c = 0; c < l; c++) {
					if(rbuf[c] < ' ') {
						*p = 0;
						break;
					}
					if(rbuf[c] == ' ')
						continue;
					if(rbuf[c] >= 'a' && rbuf[c] <= 'z')
						*p++ = rbuf[c] - 'a' + 'A';
					else
						*p++ = rbuf[c];
				}
				parse(buf, i);
				}
			}
		if(FD_ISSET(n, &probe)) {
			c = read(n, rbuf, sizeof(rbuf) - 2);
			if(c < 0) {
				if(errno != EWOULDBLOCK) {
					syslog(LOG_ERR, "read x10: %m");
					sleep(1);
				}
				goto badread;
			}
			if(c == 0) {
				syslog(LOG_ERR, "read x10: EOF");
				sleep(1);
				goto badread;
			}
			for(i = 0; i < c; i++)
				if((rbuf[i] & 0xff) == 0x5b) {
					addrreceive(rbuf + i + 1, c - i - 1);
					if((c -= i + 3) <= 0)
						goto badread;
					bcopy(rbuf + i + 3, rbuf, c);
					i = -1;
				}
			switch(rbuf[c - 1] & 0xff) {

				case 0x5a:	/* receive poll */
					pollreceive();
					break;

				case 0xf3:	/* filter fail */
					write(n, "\363", 1);
					syslog(LOG_WARNING, "filter failure");
					break;

				case 0xa5:	/* power fail */
					setcm11clock();
					syslog(LOG_NOTICE, "power failed");
					break;

				case 0x5b:	/* eeprom address */
				case 0xa6:	/* CP10 power fail */

				default:
					syslog(LOG_ERR, "unexpected poll: %x",
						*rbuf & 0xff);
					break;
			}
		badread:;
		}
	}
}

static char fmap[] = { 13, 5, 3, 11, 15, 7, 1, 9, 14, 6, 4, 12, 16, 8, 2, 10 };
static char tmap[] = { 12, 6, 14, 2, 10, 1, 9, 5, 13, 7, 15, 3, 11, 0, 8, 4,12};

addrreceive(buf, cnt)
register int cnt;
char *buf;
{
	register int c;
	static struct timeval tv;

	while(cnt < 2) {
		FD_SET(n, &xprobe);
		tv.tv_sec = 2;
		c = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
		if(c < 0) {
			syslog(LOG_ERR, "select addrreceive: %m");
			return;
		}
		if(c == 0) {
			syslog(LOG_ERR, "select addrreceive timeout");
			return;
		}
		c = read(n, buf + cnt, 2 - cnt);
		if(c <= 0) {
			if(c) {
				if(errno == EWOULDBLOCK)
					continue;
				syslog(LOG_ERR, "read addrreceive: %m");
			}
			else
				syslog(LOG_ERR, "read addrreceive: EOF");
			return;
		}
		cnt += c;
	}
}

pollreceive()
{
	register int c, i, j, r;
	int ecnt;
	static struct timeval tv;

top:
	ecnt = 0;
	write(n, "\303", 1);
again:
	FD_SET(n, &xprobe);
	tv.tv_sec = 2;
	c = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
	if(c < 0) {
		syslog(LOG_ERR, "select pollreceive: %m");
		return;
	}
	if(c == 0) {
		syslog(LOG_ERR, "select pollreceive timeout");
		return;
	}
	c = read(n, rbuf, 3);
	if(c <= 0) {
		if(c) {
			if(errno == EWOULDBLOCK)
				goto again;
			syslog(LOG_ERR, "read pollreceive: %m");
		}
		else
			syslog(LOG_ERR, "read pollreceive: EOF");
		return;
	}
retrysize:
	i = *rbuf & 0xff;
	if(i > 9 || i < 2) {
		if(i == 0x5a) {
			syslog(LOG_INFO, "pollreceive saw extra 0x5a (%d/%d)",
				c, ecnt);
			if(c == 1) {
				if(ecnt++)
					goto top;
				goto again;
			}
			c--;
			bcopy(rbuf + 1, rbuf, c);
			goto retrysize;
		}
		syslog(LOG_ERR, "pollreceive unreasonable upload size: %d", i);
		return;
	}
	i -= (j = c) - 1;
	while(i > 0) {
		FD_SET(n, &xprobe);
		tv.tv_sec = 2;
		c = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
		if(c < 0) {
			syslog(LOG_ERR, "select2 pollreceive: %m");
			return;
		}
		if(c == 0) {
			syslog(LOG_ERR, "select2 pollreceive timeout");
			return;
		}
		c = read(n, rbuf + j, i);
		if(c <= 0) {
			if(c) {
				if(errno == EWOULDBLOCK)
					continue;
				syslog(LOG_ERR, "read2 pollreceive: %m");
			}
			else
				syslog(LOG_ERR, "read2 pollreceive: EOF");
			return;
		}
		i -= c;
		j += c;
	}
	j = (*rbuf & 0xff) - 1;
	for(i = 0; i < j; i++) {
		c = rbuf[i + 2] & 0xf;
		if(rbuf[1] & (1 << i))
			c += 17;
		else
			c = fmap[c];
		if(c == X10_DIM || c == X10_BRIGHT)
			r = ((rbuf[i + 3] & 0xff) + 14) / 11;
		else
			r = 2;
		processx(fmap[(rbuf[i + 2] >> 4) & 0xf] + 'A' - 1, c, 0,
			rbuf[i + 3] & 0xff, rbuf[i + 4] & 0xff,
			rbuf[i + 5] & 0xff, r);
		if(c == X10_DIM || c == X10_BRIGHT)
			i++;
		else if(c == X10_EXTENDED)
			i += 3;
	}
}

setcm11clock()
{
	register struct tm *tm;
	register int c;
	u_long t;
	u_char sum;
	struct cm11_setclock sc;
	struct cm11_status st;
	static struct timeval tv;

	time(&t);
	tm = localtime(&t);
	sc.cmd = 0x9b;
	sc.seconds = tm->tm_sec;
	sc.minutes = tm->tm_min + 60 * (tm->tm_hour & 1);
	sc.hours   = tm->tm_hour >> 1;
	sc.yearday = tm->tm_yday;
	sc.daymask = 1 << tm->tm_wday;
	if(tm->tm_yday & 0x100)
		sc.daymask |= 0x80;
	sc.house = 0x60;
	for(c = 1, sum = 0; c < sizeof(sc); c++)
		sum += ((u_char *)&sc)[c];
	write(n, (char *)&sc, sizeof(sc));
again:
	FD_SET(n, &xprobe);
	tv.tv_sec = 2;
	c = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
	if(c < 0) {
		syslog(LOG_ERR, "select setcm11clock: %m");
		sleep(2);
		goto again;
	}
	if(c == 0) {
		syslog(LOG_ERR, "select setcm11clock timeout");
		return(-1);
	}
	c = read(n, rbuf, sizeof(rbuf));
	if(c <= 0) {
		if(c) {
			if(errno == EWOULDBLOCK)
				goto again;
			syslog(LOG_ERR, "read setcm11clock: %m");
		}
		else
			syslog(LOG_ERR, "read setcm11clock: EOF");
		sleep(1);
		return(-1);
	}
	if(c > 1)
		syslog(LOG_INFO, "setcm11clock %d byte response", c);
	c = rbuf[c - 1] & 0xff;
	if(c != sum) {
		syslog(LOG_ERR, "setcm11clock bad checksum %x expected %x",
			c, sum);
		if(c == 0x5a) {
			pollreceive();
			write(n, (char *)&sc, sizeof(sc));
			goto again;
		}
		return(-1);
	}
	write(n, "", 1);
again2:
	FD_SET(n, &xprobe);
	tv.tv_sec = 2;
	c = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
	if(c < 0) {
		syslog(LOG_ERR, "select2 setcm11clock: %m");
		sleep(2);
		goto again2;
	}
	if(c == 0) {
		syslog(LOG_ERR, "select2 setcm11clock timeout");
		return(-1);
	}
	c = read(n, rbuf, sizeof(rbuf));
	if(c <= 0) {
		if(c) {
			if(errno == EWOULDBLOCK)
				goto again2;
			syslog(LOG_ERR, "read2 setcm11clock: %m");
		}
		else
			syslog(LOG_ERR, "read2 setcm11clock: EOF");
		sleep(1);
		return(-1);
	}
	if(c > 1)
		syslog(LOG_INFO, "setcm11clock %d byte response2", c);
	c = *rbuf & 0xff;
	if(c != 0x55) {
		syslog(LOG_ERR, "setcm11clock bad ready %x", c);
		if(c == 0x5a) {
			pollreceive();
			write(n, (char *)&sc, sizeof(sc));
			goto again;
		}
		return(-1);
	}
	write(n, "\213", 1);
	sleep(2);
	if((c = read(n, (char *)&st, sizeof(st))) < sizeof(st)) {
		syslog(LOG_ERR, "read3 setcm11clock: %m (%d)", c);
		return(-1);
	}
	firmware = st.housefirm & 0x0f;
	syslog(LOG_INFO, "firmware version %d", firmware);
	return(0);
}

char modstate[16][16];

processx(house, func, flag, x1, x2, x3, r)
unsigned x1, x2, x3;
{
	register int i, c;
	register char *s = modstate[house - 'A'];
	char buf[128], xbuf[128];
	FILE *m;
	u_long t;

	if(func <= 16) {
		for(i = 0; i < 16; i++)
			if(s[i] == 2)
				s[i] = 0;
		s[func - 1] = 1;
	}
	else switch(func) {

		case X10_ALL_UNITS_OFF:
			bzero(s, 16);
			break;

		default:
			for(i = 0; i < 16; i++)
				if(s[i] == 1)
					s[i] = 2;
			break;
	}
	for(i = c = 0; i < 16; i++)
		if(s[i])
			c |= (1 << i);
	if(func == X10_EXTENDED) {
		x1 = fmap[x1 & 0xf];
		switch(x3) {
		case 0x30:
			switch(x2 & 0x20) {
			case 0x00:
				sprintf(xbuf, "Include in group %d", x2 >> 6);
				break;
			case 0x20:
				sprintf(xbuf, "Include in group %d@%d", x2 >> 6,
					x2 & 0x0f);
				break;
			}
			break;
		case 0x31:
			sprintf(xbuf, "Preset level %d", x2 & 0x3f);
			break;
		case 0x32:
			sprintf(xbuf, "Include in group %d at level %d",
				x2 >> 6, x2 & 0x3f);
			break;
		case 0x33:
			sprintf(xbuf, "Extended all units on");
			break;
		case 0x34:
			sprintf(xbuf, "Extended all units off");
			break;
		case 0x35:
			switch(x2 & 0xf0) {
			case 0x00:
				sprintf(xbuf, "Remove unit from group(s) %x",
					x2 & 0x0f);
				break;
			case 0xf0:
				sprintf(xbuf, "Remove house from group(s) %x",
					x2 & 0x0f);
				break;
			default:
				goto def;
			}
			break;
		case 0x36:
			switch(x2 & 0x30) {
			case 0x00:
				sprintf(xbuf, "Execute function in group %d",
					x2 >> 6);
				break;
			case 0x20:
				sprintf(xbuf, "Execute function in group %d@%d",
					x2 >> 6, x2 & 0x0f);
				break;
			case 0x10:
				sprintf(xbuf, "Shut off group %d", x2 >> 6);
				break;
			case 0x30:
				sprintf(xbuf, "Shut off group %d@%d",
					x2 >> 6, x2 & 0x0f);
				break;
			default:
				goto def;
			}
			break;
		case 0x37:
			switch(x2 & 0x30) {
			case 0x00:
				sprintf(xbuf, "Request output status");
				break;
			case 0x10:
				sprintf(xbuf, "Report module powerup");
				break;
			case 0x20:
				sprintf(xbuf, "Request status group %d",
					x2 >> 6);
				break;
			case 0x30:
				sprintf(xbuf, "Request status group %d@%d",
					x2 >> 6, x2 & 0x0f);
				break;
			}
			break;
		case 0x38:
			sprintf(xbuf, "Output status ACK %d, load %d, appl %d",
				x2 & 0x3f, x2 >> 7, (x2 >> 6) & 1);
			break;
		case 0x39:
			sprintf(xbuf, "Group status ACK %d, group %d",
				x2 & 0x3f, x2 >> 6);
			break;
		case 0x3a:
			sprintf(xbuf, "Group status NAK, data %02x", x2);
			break;
		case 0x3b:
			sprintf(xbuf, "Configure module SACK %d, EACK %d",
				(x2 >> 1) & 1, x2 & 1);
			break;
		default:
		def:
			sprintf(xbuf, "Unknown extended command %02x:%02x",
				x3, x2);
			break;
		}
	}
	if((m = fopen("/usr/local/lib/hcs/x10.log", "a"))) {
		time(&t);
		if(func == X10_EXTENDED)
		fprintf(m, "%c %c%02d %u %u %u %u %u Unit: %d %s %s",
			flag ? 'X' : 'R', house, func, c, r, x1, x2, x3,
			x1, xbuf, ctime(&t));
		else
		fprintf(m, "%c %c%02d %u %u %s %s", flag ? 'X' : 'R',
			house, func, c, r, x10longnames[func], ctime(&t));
		fclose(m);
	}
	if(func == X10_EXTENDED)
		sprintf(buf, "R %c%02d %u %u %u %u %u Unit: %d %s\r\n",
			house, func, c, r, x1, x2, x3, x1, xbuf);
	else
		sprintf(buf, "R %c%02d %u %u %s\r\n", house, func,
			c, r, x10longnames[func]);
	c = strlen(buf);
	for(i = 0; i < nclient; i++) {
		if(clients[i].flags & C_QUIET)
			continue;
		if(write(clients[i].s, buf, c) != c)
			shutdown(clients[i].s, 2);
	}
}

parse(i, c)
register char *i;
{
	register struct x10aliases *x;
	register int code, j;
	int repeat = 0, stry = 0, m, avoidloop, pollsoon = 0;
	static struct timeval tv;
	u_char msg[5], sum;
	char dbuf[50], *si = 0, *ss = 0;

	msg[2] = tmap[1];	/* default to preset on unit 1 */
	msg[3] = 0x3f;
	msg[4] = 0x31;
top:
	while(*i) {
		if(*i == 'Q') {
			i++;
			clients[c].flags ^= C_QUIET;
			continue;
		}
		if(*i == 'R') {
			i++;
			if(*i > '9' || *i < '0')
				goto syntax;
			repeat = 0;
			while(*i <= '9' && *i >= '0')
				repeat = 10 * repeat + *i++ - '0';
			if((unsigned)repeat > 31)
				repeat = 31;
#ifdef	REPEATER
			if(firmware == 7 && ss)
				repeat = 0;
#endif
			continue;
		}
		if(*i == 'S') {
			if(ss)
				stry = 0;
			ss = i++;
			if(*i == 'Q') {
				i++;
				ss = 0;
				continue;
			}
#ifdef	REPEATER
			if(firmware == 7)
				repeat = 0;
#endif
			if(*i < 'A' || *i > 'P')
				goto syntax;
			code = *i++ - 'A';
			sum = 0;
			for(j = m = 0; j < 16; j++)
				if(modstate[code][j]) {
					m |= 1 << j;
					sum = modstate[code][j];
				}
			if(*i > '9' || *i < '0')
				goto syntax;
			j = 0;
			while(*i <= '9' && *i >= '0')
				j = 10 * j + *i++ - '0';
			if((j &= 0xffff) == m || !j)
				continue;
			code += 'A';
			si = dbuf;
			if(sum == 1) {
				if(m & ~j) {
					sprintf(si, "%c26", code);
					si += strlen(si);
				}
				else
					j &= ~m;
			}
			m = j;
			for(j = 0; j < 16; j++)
				if(m & (1 << j)) {
					sprintf(si, "%c%d", code, j + 1);
					si += strlen(si);
				}
			si = i;
			i = dbuf;
			continue;
		}
		if(*i == 'T') {
			i++;
			if(*i > '9' || *i < '0')
				goto syntax;
			j = 0;
			while(*i <= '9' && *i >= '0')
				j = 10 * j + *i++ - '0';
			if(((unsigned)j) > 255)
				goto syntax;
			msg[4] = j;
			continue;
		}
		if(*i == 'U') {
			i++;
			if(*i > '9' || *i < '0')
				goto syntax;
			j = 0;
			while(*i <= '9' && *i >= '0')
				j = 10 * j + *i++ - '0';
			if(!j || ((unsigned)j) > 16)
				goto syntax;
			msg[2] = tmap[j];
			continue;
		}
		if(*i == 'V') {
			i++;
			if(*i > '9' || *i < '0')
				goto syntax;
			j = 0;
			while(*i <= '9' && *i >= '0')
				j = 10 * j + *i++ - '0';
			if(((unsigned)j) > 255)
				goto syntax;
			msg[3] = j;
			continue;
		}
		if(*i == 'X') {
			i++;
			if(strlen(i) < 6)
				goto syntax;
			sscanf(i, "%6x", &m);
			msg[2] = m >> 16;
			msg[3] = m >> 8;
			msg[4] = m;
			i += 6;
			continue;
		}
		if(*i < 'A' || *i > 'P') {
		syntax:
			write(clients[c].s, "E Syntax\r\n", 10);
			return;
		}
		msg[0] = (repeat << 3) | 4;
		msg[1] = tmap[*i++ - 'A' + 1] << 4;
		if(*i <= '9' && *i >= '0') {
			code = 0;
			while(*i <= '9' && *i >= '0')
				code = 10 * code + *i++ - '0';
			if(!code || code > 32)
				goto syntax;
		}
		else {
			for(x = x10aliases; x->name; x++)
				if(!memcmp(i, x->name, strlen(x->name)))
					break;
			if(!x->name)
				goto syntax;
			code = x->code;
			i += strlen(x->name);
		}
		if(firmware != 7 && code == X10_STATUS_REQUEST)
			avoidloop = 1;
		else
			avoidloop = 0;
		if(code == X10_EXTENDED) {
			msg[0] |= 1;
			m = 5;
		}
		else
			m = 2;
		if(code <= 16)
			msg[1] |= tmap[code];
		else {
			msg[0] |= 2;
			msg[1] |= code - 17;
		}
		for(sum = j = 0; j < m; j++)
			sum += msg[j];
		write(n, msg, m);
	again:
		FD_SET(n, &xprobe);
		tv.tv_sec = 2;
		code = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
		if(code < 0) {
			syslog(LOG_ERR, "select parse: %m");
			sleep(2);
			goto again;
		}
		if(code == 0) {
			syslog(LOG_ERR, "select parse timeout");
			write(clients[c].s, "E device timeout\r\n", 18);
			return;
		}
		code = read(n, rbuf, sizeof(rbuf));
		if(code <= 0) {
			if(code) {
				if(errno == EWOULDBLOCK)
					goto again;
				syslog(LOG_ERR, "read parse: %m");
			}
			else
				syslog(LOG_ERR, "read parse: EOF");
			write(clients[c].s, "E bad device read\r\n", 19);
			sleep(1);
			return;
		}
		if(code > 1)
			syslog(LOG_INFO, "parse %d byte response", code);
		code = rbuf[code - 1] & 0xff;
		if(code != sum) {
			syslog(LOG_INFO, "parse bad checksum %x expected %x",
				code, sum);
			if(code == 0xf3 || code == 0xa5) {
				write(clients[c].s, "E transient fail\r\n", 18);
				return;
			}
			if(code == 0x5a) {
				pollreceive();
				if(ss && stry < 20) {
					stry++;
					si = 0;
					i = ss;
					ss = 0;
					continue;
				}
			}
			else
				sleep(1);
			write(n, msg, m);
			goto again;
		}
		write(n, "", 1);
	again2:
		FD_SET(n, &xprobe);
		tv.tv_sec = 4 + 2 * repeat;
		code = select(n + 1, &xprobe, FD_NULL, FD_NULL, &tv);
		if(code < 0) {
			syslog(LOG_ERR, "select2 parse: %m");
			sleep(2);
			goto again2;
		}
		if(code == 0) {
			syslog(LOG_ERR, "select2 parse timeout");
			write(clients[c].s, "E device timeout\r\n", 18);
			return;
		}
		code = read(n, rbuf, sizeof(rbuf));
		if(code <= 0) {
			if(code) {
				if(errno == EWOULDBLOCK)
					goto again2;
				syslog(LOG_ERR, "read2 parse: %m");
			}
			else
				syslog(LOG_ERR, "read2 parse: EOF");
			write(clients[c].s, "E bad device read\r\n", 19);
			sleep(1);
			return;
		}
		if(code > 1)
			syslog(LOG_INFO, "parse %d byte response2", code);
		code = *rbuf & 0xff;
		if(code != 0x55) {
			if(!avoidloop || code != 0x5a)
				syslog(LOG_INFO, "parse bad ready %x", code);
			if(code == 0xf3 || code == 0xa5) {
				write(clients[c].s, "E transient fail\r\n", 18);
				return;
			}
			if(code == 0x5a) {
				if(avoidloop) {
					if(*i || si)
						pollreceive();
					else
						pollsoon = 1;
				}
				else {
					pollreceive();
					if(ss && stry < 20) {
						stry++;
						si = 0;
						i = ss;
						ss = 0;
						continue;
					}
					write(n, msg, m);
					goto again;
				}
			}
			else {
				sleep(1);
				write(n, msg, m);
				goto again;
			}
		}
		code = msg[1] & 0xf;
		if(msg[0] & 2)
			code += 17;
		else
			code = fmap[code];
		if(!(j = (msg[0] >> 3) & 0x1f))
			j = 2;
#ifdef	REPEATER
		if(firmware == 7) {
			if(code == X10_DIM || code == X10_BRIGHT) {
				if(j == 1)
					j = 0;
			}
			else {
				if(j & 1)
					j--;
			}
		}
		if(j)
#endif
		processx(fmap[(msg[1] >> 4) & 0xf] + 'A' - 1, code, 1,
			msg[2], msg[3], msg[4], j);
	}
	if(si) {
		i = si;
		si = 0;
		goto top;
	}
	write(clients[c].s, "A\r\n", 3);
	if(pollsoon)
		pollreceive();
}
