/*
x10rfd.c (c) Copyright Daniel D. Lanciani 1995-2000
All rights reserved.

x10rfd.c is licensed free of charge for non-commercial distribution and for
personal and internal business use only.  x10rfd.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, x10rfd.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 x10rfd.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 x10rfd.c.  Modified versions of x10rfd.c must be
marked as such.

ddl@danlan.com
*/

#include <sys/types.h>
#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>

#define LOGFILE "/usr/local/lib/hcs/x10rf.log"

#ifdef __linux__
#include <string.h>
#include <termios.h>
 
struct termios oldsb, newsb;
#else
struct sgttyb sgttyb;
#endif

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

char *ctime(), *memchr(), *malloc(), *realloc();

struct clients {
	int s, cnt, flags, unit;
	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"
};


#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, lastcnt = 0;
	struct servent *sp;
	fd_set rset, probe;
	u_long now, lasttime = 0;
	u_char msg[5];

#ifndef	DEBUG
	if(fork())
		exit(0);
	close(0);
	close(1);
	close(2);
	if(open("/", 0)) {
		syslog(LOG_CRIT, "x10rfd: open / not 0: %m");
		exit(1);
	}
	dup2(0, 1);
	dup2(0, 2);
#endif
	signal(SIGPIPE, SIG_IGN);
#ifdef	LOG_LOCAL0
	openlog("x10rfd", LOG_PID, LOG_LOCAL0);
#else
	openlog("x10rfd", 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("hamx10rf", "tcp"))) {
		syslog(LOG_ERR, "hamx10rf: 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/cm17", 2)) < 0) {
		syslog(LOG_ERR, "/dev/cm17: %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 = B9600;
	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);
	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/x10rfd.access", "r")) {
				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].unit = 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));
			if(c < 0) {
				if(errno != EWOULDBLOCK) {
					syslog(LOG_ERR, "read x10rf: %m");
					sleep(1);
				}
				continue;
			}
			if(c == 0) {
				syslog(LOG_ERR, "read x10rf: EOF");
				sleep(1);
				continue;
			}
			time(&now);
			if(now - lasttime > 2)
				lastcnt = 0;
			lasttime = now;
			p = rbuf;
			if(lastcnt) {
				if(lastcnt + c <= 5) {
					bcopy(p, msg + lastcnt, c);
					if((lastcnt += c) == 5) {
						decodemsg(msg);
						lastcnt = 0;
					}
					continue;
				}
				bcopy(p, msg + lastcnt, i = 5 - lastcnt);
				p += i;
				c -= i;
				decodemsg(msg);
			}
			while(c >= 5) {
				decodemsg(p);
				p += 5;
				c -= 5;
			}
			if(lastcnt = c)
				bcopy(p, msg, c);
		}
	}
}

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};
static char flip[] = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };

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(LOGFILE, "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);
	}
}

processb(b, r)
{
	register int i, c;
	char buf[128];
	FILE *m;
	u_long t;

	if((m = fopen(LOGFILE, "a"))) {
		time(&t);
		fprintf(m, "B %u %u %s", b, r, ctime(&t));
		fclose(m);
	}
	sprintf(buf, "B %u %u\r\n", b, r);
	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);
	}
}

decodemsg(msg)
register u_char *msg;
{
	register int i;
	int house;

	if(msg[0] != 0xd5 || msg[1] != 0xaa || msg[4] != 0xad) {
		syslog(LOG_ERR, "decodemsg: bad msg %02x %02x %02x %02x %02x",
			msg[0], msg[1], msg[2], msg[3], msg[4]);
		return;
	}
	if(msg[2] == 0xee) {
		processb(msg[3], 0);
		return;
	}
	if(msg[2] & 0x03) {
	unrecog:
		syslog(LOG_ERR, "decodemsg: unrecognized msg %02x %02x",
			msg[2], msg[3]);
		return;
	}
	house = fmap[flip[(msg[2] >> 4) & 0xf]] + 'A' - 1;
	switch(msg[3]) {

		case 0x80:
			if(msg[2] & 0x0c)
				goto unrecog;
			processx(house, X10_ALL_UNITS_OFF, 0, 0, 0, 0, 2);
			return;

		case 0x90:
			if(msg[2] & 0x0c)
				goto unrecog;
			processx(house, X10_ALL_LIGHTS_ON, 0, 0, 0, 0, 2);
			return;

		case 0x88:
			if(msg[2] & 0x0c)
				goto unrecog;
			processx(house, X10_BRIGHT, 0, 0, 0, 0, 2);
			return;

		case 0x98:
			if(msg[2] & 0x0c)
				goto unrecog;
			processx(house, X10_DIM, 0, 0, 0, 0, 2);
			return;
	}
	if(msg[3] & 0x87)
		goto unrecog;
	i = 0;
	if(msg[2] & 4)
		i |= 8;
	if(msg[3] & 0x40)
		i |= 4;
	if(msg[3] & 8)
		i |= 2;
	if(msg[3] & 0x10)
		i |= 1;
	if((msg[2] & 8) && i < 12)
		i += 4;
	processx(house, i + 1, 0, 0, 0, 0, 2);
	processx(house, (msg[3] & 0x20) ? X10_OFF : X10_ON, 0, 0, 0, 0, 2);
}

parse(i, c)
register char *i;
{
	register struct x10aliases *x;
	register int code;
	int repeat = 0;
	static struct timeval tv;
	u_char msg[5];

	msg[0] = 0xd5;
	msg[1] = 0xaa;
	msg[2] = 0;
	msg[3] = 0;
	msg[4] = 0xad;
	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;
			continue;
		}
		if(*i < 'A' || *i > 'P') {
		syntax:
			write(clients[c].s, "E Syntax\r\n", 10);
			return;
		}
		msg[2] = (flip[tmap[*i++ - 'A' + 1]] << 4) | (msg[2] & 0xf);
		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(code <= 16) {
			clients[c].unit = code - 1;
			continue;
		}
		switch(code) {

			case X10_OFF:
				msg[3] = 0x20;
				goto onoff;
			case X10_ON:
				msg[3] = 0;
			onoff:
				msg[2] &= 0xf0;
				if(clients[c].unit & 8)
					msg[2] |= 4;
				if(clients[c].unit & 4)
					msg[3] |= 0x40;
				if(clients[c].unit & 2)
					msg[3] |= 8;
				if(clients[c].unit & 1)
					msg[3] |= 0x10;
				break;

			case X10_ALL_UNITS_OFF:
				msg[2] &= 0xf0;
				msg[3] = 0x80;
				break;

			case X10_ALL_LIGHTS_ON:
				msg[2] &= 0xf0;
				msg[3] = 0x90;
				break;

			case X10_BRIGHT:
				msg[2] &= 0xf0;
				msg[3] = 0x88;
				break;

			case X10_DIM:
				msg[2] &= 0xf0;
				msg[3] = 0x98;
				break;

			default:
				goto syntax;
		}
		if(!(code = repeat))
			code = 1;
		while(code--)
			cm17send(msg);
	}
	write(clients[c].s, "A\r\n", 3);
}

unitdelay()
{
	static struct timeval tv;

	tv.tv_usec = 600;
	select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv);
}

send1()
{
	int arg = TIOCM_DTR;

	ioctl(n, TIOCMBIC, &arg);
	unitdelay();
	ioctl(n, TIOCMBIS, &arg);
	unitdelay();
}

send0()
{
	int arg = TIOCM_RTS;

	ioctl(n, TIOCMBIC, &arg);
	unitdelay();
	ioctl(n, TIOCMBIS, &arg);
	unitdelay();
}

cm17send(p)
register char *p;
{
	register int i, j, k = 5;

	while(k--) {
		j = *p++;
		i = 8;
		while(i--) {
			if(j & 0x80)
				send1();
			else
				send0();
			j <<= 1;
		}
	}
}
