/* rc85prg: program an RC-85 compatible controller
	via the phone line, using a Hayes-compatible modem.

   USAGE: rc85prg [options] inputfile
        -or- rc85prg [options] < inputfile
   	-or- (for example) grep pattern inputfile | rc85prg [options]

   Input file format:		Usage
   S nn  call  number           Program number into autodial slot nn
   CL				Config Lock (after a CU)
   CU				Config unlock (to permit msg programming)
   CS string...			Send config string to RC85
   CO string...			Send control-op prefix followed by config string
   MP				Msg program (same as CS *0)
   MR				Msg readback (same as CS *2)
   MA				Msg abort (same as CS *4)
   # comment text		May appear anywhere on a line

   Written by James Dugal, N5KNX, Aug 1989.
   Ver 1.1 9/89: Improved abort handling. Added copyright greeting.
   Ver 1.2 10/89: Added inputfile as an argument, redid option parsing.

   Compile in Turbo C 1.5 + by: tcc -a -G -O rc85prg.c
   Requires AA4RE's MBBIOS com drivers be loaded before invocation.

   Copyright 1989 by James P. Dugal.
*/

#define VERSION "1.2"

#include <stdio.h>
#include <stdlib.h>
#include <bios.h>
#include <string.h>


/* Int 14H (serial i/o) defines */

int baudtab[8] = { 110, 150, 300, 600, 1200, 2400, 4800, 9600 };
char partab[3][2] = { 'N', 0, 'E', 0x18, 'O', 0x8 };

#define SENDST 0
#define SENDCH 1
#define GOODWRITE 0x8000
#define READCH 2
#define GOODREAD 0x9E00
#define READST 3
#define STDAV	0x0100	/* data available */
#define MBENQ  4
#define MBDRTS 5
#define MBARTS 6
#define MBSBRK 7
#define MBNDRD 8
#define MBOPTS 9
#define SHAKEBF 0x0	/* No xmit buffering, no hardware handshake, low-speed opt */

#define MBWBUF 10
#define MBRBUF 11	/* ES:DI->BUF, CX={BUFSIZ,NREAD}, AX=stats */


#define COMMENT	'#'	/* in input file */


/* MODEM-related commands */
#define DEFPORT		"COM1:1200,7E1"
#define USRHEAD		"ATDT"
#define USRTAIL		";"
#define HANGUP		"ATH0"
#define OK		"OK"
#define USRMAXSZ	42	/* 40 + AT (spaces and CR don't count) */

/* RC85 controller-related commands */
#define DIALRC85	"5551234"	/* RC85 phone number */
#define RCUNLOCK	"1234567890"	/* owner's unlock seq. */
#define RCLOCK		"#"		/* owner's lock seq. */

#define CTRLOP		"123"	/* control operator prefix */
#define RCHANGUP	"44"	/* RC85 on-hook command */
#define RCAU		"47"	/* autodial prog unlock */
#define RCAL		"48"	/* autodial prog lock */

#define RC1SLOT		"*520"	/* then digit */
#define RC2DFLT		"456"	/* then *nnxxxyyyy or nn */
#define RC3DFLT		"457"	/* then *nnxxxyyyy or nn */

char	*me;
char	baudetc;
char	*dialup_num = DIALRC85;
char	*owner_code = RCUNLOCK;
char	*ctrlop_code = CTRLOP;
char	*rc2slot = RC2DFLT;
char	*rc3slot = RC3DFLT;

int	comport;
int	unlock = 1;	/* 1 => we must unlock user autodialer */
int	aunlocked = 0;	/* 1 => we have unlocked the user autodialer */
int	cunlocked = 0;	/* 1 => we have unlocked for config commands */
int	verbose = 0;
int	waitsec = 25;
int	pausesec = 4;
int	test = 0;

char	progseq[100];	/* room for building programming sequences */

/* ANSI func defs */
int main(int, char**);
void programslot(int, char *, char *);
void dialup(void);
void hangup(void);
void enter_config(void);
void exit_config(void);
void send(char *);
int writecom(char *);
int waitfor(char *, int, char *, int);
void parse_args(int, char**);
int parse_comport(char *);
int hndlbrk(void);
int hat_break(void);


main(argc,argv)
int argc;
char **argv;
{
	int lineno,slot,i;
	char buffer[256],call[10],number[30],*cp;

	cprintf("Version %s. Copyright 1989 by James P. Dugal.  All rights reserved.\r\n", VERSION);
	parse_args(argc, argv);

	if ((unsigned)bioscom(MBENQ, 0, comport) != 0xAA55) {
		fprintf(stderr,"%s: MBBIOS not enabled for COM%d.\n",
			me, comport+1);
		exit(2);
	}

	(void)bioscom(SENDST, baudetc, comport);	/* set baud,parity,etc */
	(void)bioscom(MBOPTS, SHAKEBF, comport);	/* set handshaking etc */

	/* Just to ge safe we flush all input */
	while (bioscom(READST, 0, comport) & STDAV)
		(void)bioscom(READCH, 0, comport);


	dialup();
	ctrlbrk(hndlbrk);	/* we now take over if ^C typed */

	lineno = 0;
	while (gets(buffer) != NULL) {
		lineno++;
		if ((cp=strchr(buffer,COMMENT)) != NULL) *cp=0;	/* ignore trailing comments */
		cp = strupr(buffer);	/* map to uppercase */

		switch (*cp) {

		case 0:		/* full-line comment, ignore it */
			break;

		case 'S':	/* Slot:  ss  call  phonenumber */
			i = sscanf(++cp, "%d\t%s\t%s", &slot, call, number);
			if (i != 3) goto badfmt;
			else programslot(slot,call,number);
			break;

		case 'C':	/* Config command */
			if (*++cp == 'L') exit_config();
			else if (*cp == 'U') enter_config();
			else if (*cp == 'S' || *cp == 'O') {	/* send all after CS/CO */
				if (*cp == 'S') sprintf(progseq, "%s%s#%s", USRHEAD, ++cp, USRTAIL);
				else sprintf(progseq, "%s%s%s#%s", USRHEAD, ctrlop_code, ++cp, USRTAIL);
				send(progseq);
				/* Auto readback takes a while, so vary sleep time */
				i = pausesec + strlen(progseq)/3;
				if (i > 50) i = 50;	/* but not too long */
				sleep(i);
			}
			else goto badfmt;
			break;

		case 'M':	/* Voice message command */
			if (*++cp == 'P') *cp = '0';	/* program */
			else if (*cp == 'R') *cp = '2';	/* readback */
			else if (*cp == 'A') *cp = '4';	/* abort */
			else goto badfmt;

			sprintf(progseq, "%s*%c#%s", USRHEAD, *cp, USRTAIL);
			send(progseq);
			sleep(pausesec+5);
			if (*cp != '4') sleep(5);	/* longer unless abort */
			break;

		default:	/* Unknown cmd */
		badfmt:
			fprintf(stderr,"%s: line %d: unknown or malformed command %s (ignored)\n",
				me, lineno, cp);
			break;
		} /* end switch */
	} /* end while */


	hangup();


	return(0);
}

void programslot(slot,call,number)
int slot;
char *call,*number;
{
	char	progprefix[10];
	char	secure[2];	/* * secure code, else null */

	strcpy(secure, "*");	/* default is to NOT speak the programmed number */
	if (slot < 0 ) {
		secure[0]=0;
		slot = -slot;
	}
		
	else if (slot > 199) {
		fprintf(stderr,"%s: illegal slot number %d for %s (ignored)\n",
			me, slot, call);
		return;
	}

	if (slot < 10) {		/* emergency slot */
		if (!cunlocked) enter_config();

		sprintf(progseq, "%s%s%d%s#%s", USRHEAD, RC1SLOT, slot, number, USRTAIL);
		send(progseq);
		sleep(pausesec+5);
	}
	else {
		if (slot < 100) 	/* 2-digit slot */
			strcpy(progprefix,rc2slot);
		else 	/* 3-digit slot */
			strcpy(progprefix,rc3slot);

		/* First unlock as needed */
		if (cunlocked) exit_config();
		if (unlock && !aunlocked) {
			sprintf(progseq, "%s%s%s#%s", USRHEAD, ctrlop_code, RCAU, USRTAIL);
			send(progseq);
			sleep(pausesec);	/* await AU */
			aunlocked++;
		}

		sprintf(progseq, "%s%s%d#%s",
			USRHEAD, progprefix, slot, USRTAIL);
		send(progseq);
		sleep (pausesec);	/* await finish of 'autodial clear' */

		sprintf(progseq, "%s%s%s%d%s#%s",
			USRHEAD, progprefix, secure, slot, number, USRTAIL);
		send(progseq);
		sleep (pausesec);	/* await finish of 'autodial program' */
	}

	printf("%s: did %d\t%s\t%s\n", me, slot, call, number);
}

void dialup()
{
	int	errcode;
	char	dialstr[64];

	sprintf(dialstr, "%s%s%s", USRHEAD, dialup_num, USRTAIL);
	send(dialstr);
	if (!test) sleep (waitsec);	/* await finish of greeting msg */
}

void hangup()
{
	if (cunlocked) exit_config();	/* exit owner config mode */

	if (unlock && aunlocked) {	/* relock user autodialer? */
		sprintf(progseq, "%s%s%s#%s", USRHEAD, ctrlop_code, RCAL, USRTAIL);
		send(progseq);
		sleep(pausesec);	/* await AL */
		aunlocked = 0;
	}

	sprintf(progseq, "%s%s%s#%s", USRHEAD, ctrlop_code, RCHANGUP, USRTAIL);
	send(progseq);
	sleep(pausesec);	/* await 'call complete' */

	send(HANGUP);	/* tell modem to go onhook */
}


void enter_config (void)	/* enter owner config mode */
{
	sprintf(progseq, "%s%s#%s", USRHEAD, owner_code, USRTAIL);
	send(progseq);
	sleep(pausesec);	/* await UL */
	cunlocked = 1;
}


void exit_config(void)	/* exit owner config mode */
{
	sprintf(progseq, "%s%s#%s", USRHEAD, RCLOCK, USRTAIL);
	send(progseq);
	sleep(pausesec);	/* await LOCK */
	cunlocked = 0;
}


void send(s_str)	/* Send s_str to the comport, then read the OK */
char	*s_str;
{
	int	errcode;
	char	logstr[128];

	if (writecom(s_str)) {
		fprintf(stderr,"%s: Unable to write %s to COM%d (aborting)\n",
			me, s_str, comport+1);
		exit (1);
	}

	errcode = waitfor(OK,pausesec+strlen(s_str)/2,logstr,sizeof(logstr));
	if (errcode) {
		fprintf(stderr,"%s: Expected %s, got %s (aborting)\n",
			me, OK, logstr);
		exit(1);
	}
}


int writecom(w_str)		/* Write w_str to the comport, return 0 if OK */
char *w_str;
{
	int	retcode;
	char	*sp;
	#define CR	0x0D
	#define SP	0x20

	if (verbose) printf("Writing %s\n", w_str);
	for (retcode=0, sp=w_str; *sp; sp++)
		if (*sp != SP && *sp != '\t') retcode++;
	if (retcode > USRMAXSZ) fprintf(stderr,"%s: Warning: %s may exceed modem buffer capacity.\n",
					me, w_str);

	if (test) return(0);

	while (*w_str) {
		retcode = bioscom(SENDCH, *w_str++, comport);
		if (retcode & GOODWRITE) return (-1);
	}
	if (*--w_str != CR) {
		retcode = bioscom(SENDCH, CR, comport);
		if (retcode & GOODWRITE) return (-1);
	}
	return (0);
}


int waitfor(w_str, w_time, logstr, loglen)
char	*w_str;		/* search string */
int	w_time;		/* time limit (seconds) */
char	*logstr;	/* log string */
int	loglen;		/* sizeof logstr */

/* wait a max of "w_time" seconds for "w_str" to appear on comport.
   append all characters read to "logstr".
   Return 0 if w_str read successfully, -1 if timeout, else errorcode.
*/

{
#include <time.h>
	int strindx, strln, retval;
	char ch;
	long done;

	if (test) return(0);

	done = w_time + time((long *) 0);
	logstr[0] = '\0';
	strindx = 0;
	strln = strlen(w_str);
	while (done > time((long *) 0)) {
		if (bioscom(READST, 0, comport) & STDAV) {
			retval = bioscom(READCH, 0, comport);
			if (retval & GOODREAD)	/* error */
				return(retval);
			ch = retval & 0x7f;
			if (strlen(logstr) < loglen)
				strncat(logstr, &ch, 1);
			if (ch == w_str[strindx]) {
				strindx++;
				if (strindx >= strln) return(0);	/* all done */
			}
			else strindx=0;		/* start over (ie, ignore leading chs */
		}
	}

	return(-1);	/* timeout */
}

void parse_args(argc, argv)	/* scan command-line */
int argc;
char **argv;
{
	char	*cp;
	int	i;

	/* Get our entry name for use in error msgs */
	me = strrchr(*argv,'/');
	if (me == NULL) me = strrchr(*argv,'\\');
	if (me == NULL) me=*argv;
	else me++;
	if ((cp=strchr(me,'.')) != NULL) *cp=0;	/* drop trailing .EXE */

	argv++;

	if (parse_comport(DEFPORT)) {	/* init baudetc and comport */
		fprintf(stderr,"%s: Illegal default port spec: %s\n",
			me, DEFPORT);
		exit(1);
	}

	while (--argc) {
		if (**argv == '-' )
			switch (*(++*argv)) {
			case 'm':	/* -m modem_port_spec */
				++argv; --argc;
				if (!argc || parse_comport(*argv)) goto usage;
				++argv;
				break;

			case 'w':	/* -w Nsecs */
				++argv; --argc;
				if (!argc) goto usage;
				waitsec = atoi(*argv);
				++argv;
				break;

			case 'p':	/* -p Nsecs */
				++argv; --argc;
				if (!argc) goto usage;
				pausesec = atoi(*argv);
				++argv;
				break;

			case 'd':	/* -d phonenum */
				++argv; --argc;
				if (!argc) goto usage;
				dialup_num = *argv;
				++argv;
				break;

			case 'o':	/* -o owner_code */
				++argv; --argc;
				if (!argc) goto usage;
				owner_code = *argv;
				++argv;
				if (strlen(owner_code) != 10)
					fprintf(stderr,"%s: Warning: 10 digits expected, got %s\n",
						me, owner_code);
				break;

			case 'c':	/* -c ctrl_op_code */
				++argv; --argc;
				if (!argc) goto usage;
				ctrlop_code = *argv;
				++argv;
				i=strlen(ctrlop_code);
				if (i<1 || i>7) fprintf(stderr, "%s: Warning: ctrl op code length is illegal: %s\n",
							me, ctrlop_code);
				break;

			case '2':	/* -2 2digit_slot_code */
				++argv; --argc;
				if (!argc) goto usage;
				rc2slot = *argv;
				++argv;
				break;

			case '3':	/* -3 3digit_slot_code */
				++argv; --argc;
				if (!argc) goto usage;
				rc3slot = *argv;
				++argv;
				break;

			case 'v':
				verbose++;
				argv++;
				break;

			case 'u':
				unlock = 0;	/* already unlocked */
				argv++;
				break;

			case 't':
				test++;	/* enable test mode */
				argv++;
				break;

			default:
usage:			fprintf(stderr,"USAGE: %s [options] infile\n", me);
			fprintf(stderr,"where infile contains lines of the form:\n");
			fprintf(stderr,"S slotnum  call  phonenumber\nor commands CU,CL,CS,CO,MP,MA,MR\n\n");
			fprintf(stderr,"options are:\t-v for verbose mode\n");
			fprintf(stderr,"\t\t-t for test mode: simulate operations\n");
			fprintf(stderr,"\t\t-u if autodialer is kept unlocked\n");
			fprintf(stderr,"\t\t-w N to wait N secs after dialing the RC85\n");
			fprintf(stderr,"\t\t-p N to pause N secs after each command\n");
			fprintf(stderr,"\t\t-m COMn:baud,8N1 to select a modem port and parameters\n");
			fprintf(stderr,"\t\t-d phonenumber to override the default phone number\n");
			fprintf(stderr,"\t\t-o owner_code to override the default owner unlock code\n");
			fprintf(stderr,"\t\t-c ctrlop_code to override the default control op code\n");
			fprintf(stderr,"\t\t-2 code to override the default 2-digit slot programming code\n");
			fprintf(stderr,"\t\t-3 code to override the default 3-digit slot programming code\n");
			fprintf(stderr,"defaults are -w %d -p %d -m %s -d %s -o %s -c %s -2 %s -3 %s\n",
					waitsec, pausesec, DEFPORT, dialup_num, owner_code,
					ctrlop_code, rc2slot, rc3slot);
			exit (1);
			} /* end of if () switch */
		else {	/* not an option, so assume is an input file */
			if (freopen(*argv, "r", stdin) == NULL) {
				fprintf(stderr,"%s: %s: %s\n", me, *argv, strerror(errno));
				exit(2);
			}
		}
	} /* end while (--argc) */
}


int parse_comport(ap)	/* ap -> "COM1:9600,8N1" */
char *ap;
/* Sets global variables comport, baudetc */

{
	char *p, *s;
	int i,j;

	(void)strupr(ap);
	s = strchr(ap,':');
	if (s == NULL) {
		fprintf(stderr,"%s: Unknown port %s\n", me, ap);
		return (1);
	}
	*s++ = 0;		/* terminate COMx, hop to baud */

	if (strncmp(ap, "COM", 3)==0)
		comport=atoi(ap+3) - 1;	/* COMn */
	else {
		fprintf(stderr,"%s: Unknown port %s\n",me, ap);
		return (1);
	}

	p = strchr(s,',');  /* Where is comma separating baud from par? */
	if (p == NULL) return (1);
	*p++ = 0;	/* replace comma by EOS, point p to 8N1 string */
	j = atoi(s);  /* baud */

	for (i=0; i < (sizeof(baudtab)/sizeof(int)); i++)
		if (j == baudtab[i]) {
			baudetc = (i << 5);
			break;
		}

	if (i >= sizeof(baudtab)/sizeof(int)) {
		fprintf(stderr, "%s: Illegal baud %s\n", me, s);
		return (1);
	}

	if (*p < '5' || *p > '8') {
		fprintf(stderr,"%s: Illegal word length %c\n", me, *p);
		return (1);
	}
	baudetc |= (*p - '5');
	p++;
	for (i=0; i < 3; i++)
		if (*p == partab[i][0]) {
			baudetc |= partab[i][1];
			break;
		}
	if (i >= 3) {
		fprintf(stderr,"%s: Unknown parity %c\n",me, *p);
		return (1);
	}

	p++;
	if (*p < '1' || *p > '2') {
		fprintf(stderr,"%s: Illegal stop bit %c\n", *p);
		return (1);
	}
	baudetc |= (*p - '1') << 2;
	return (0);
}


int hndlbrk()
{
	char abort[] = "\rAT\r";	/* \r to terminate possible cmd */
	char i;

	/* cancel any modem cmd in progress */
	writecom(abort);	/* This MAY produce an OK, OR start a cmd going! */
	for (i=0; i<15; i++) {	/* try 15 times */
		if (!waitfor(OK, 2, "", 0)) break;	/* wait 2 secs for an OK */
		writecom(abort);	/* try to elicit an OK again */
	}
	hangup();	/* undo what we did so far */
	return(0);	/* 0 => abort */
}
