/*****************************************************************************/

/*
 *	setbaycom.c  -- baycom radio modem driver setup utility.
 *
 *	Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program 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 General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 *
 * History:
 *  0.1  03.05.96  Renamed from stser12 0.5 and added support for par96
 *                 Fixed a bug in displaying compressed FlexNet headers
 *  0.2  13.05.96  use officially allocated major and new ioctls
 */

/*****************************************************************************/

#include <linux/baycom.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

/* ---------------------------------------------------------------------- */

#undef DISPLAY_RXBYTES

static int fd_baycom;
static unsigned char trace_stat = 0;
static char *progname;

/* ---------------------------------------------------------------------- */

void display_packet(unsigned char *bp, unsigned int len) {
	unsigned char v1=1,cmd=0;
	unsigned char i,j;

	if (!bp || !len) return;
	if (len < 8) return;
	if (bp[1] & 1) {
		/*
		 * FlexNet Header Compression
		 */
		v1 = 0;
		cmd = (bp[1] & 2) != 0;
		printf("fm ? to ");
		i = (bp[2] >> 2) & 0x3f;
		if (i) printf("%c",i+0x20);
		i = ((bp[2] << 4) | ((bp[3] >> 4) & 0xf)) & 0x3f;
		if (i) printf("%c",i+0x20);
		i = ((bp[3] << 2) | ((bp[4] >> 6) & 3)) & 0x3f;
		if (i) printf("%c",i+0x20);
		i = bp[4] & 0x3f;
		if (i) printf("%c",i+0x20);
		i = (bp[5] >> 2) & 0x3f;
		if (i) printf("%c",i+0x20);
		i = ((bp[5] << 4) | ((bp[6] >> 4) & 0xf)) & 0x3f;
		if (i) printf("%c",i+0x20);
		printf("-%u QSO Nr %u", bp[6] & 0xf, (bp[0] << 6) | 
		       (bp[1] >> 2));
		bp += 7;
		len -= 7;
	} else {
		/*
		 * normal header
		 */
		if (len < 15) return;
		if ((bp[6] & 0x80) != (bp[13] & 0x80)) {
			v1 = 0;
			cmd = (bp[6] & 0x80);
		}
		printf("fm ");
		for(i = 7; i < 13; i++) 
			if ((bp[i] &0xfe) != 0x40) 
				printf("%c",bp[i] >> 1);
		printf("-%u to ",(bp[13] >> 1) & 0xf);
		for(i = 0; i < 6; i++) 
			if ((bp[i] &0xfe) != 0x40) 
				printf("%c",bp[i] >> 1);
		printf("-%u",(bp[6] >> 1) & 0xf);
		bp += 14;
		len -= 14;
		if ((!(bp[-1] & 1)) && (len >= 7)) printf(" via ");
		while ((!(bp[-1] & 1)) && (len >= 7)) {
			for(i = 0; i < 6; i++) 
				if ((bp[i] &0xfe) != 0x40) 
					printf("%c",bp[i] >> 1);
			printf("-%u",(bp[6] >> 1) & 0xf);
			bp += 7;
			len -= 7;
			if ((!(bp[-1] & 1)) && (len >= 7)) 
				printf(",");
		}
	}
	if(!len) 
		return;
	i = *bp++;
	len--;
	j = v1 ? ((i & 0x10) ? '!' : ' ') : 
		((i & 0x10) ? (cmd ? '+' : '-') : (cmd ? '^' : 'v'));
	if (!(i & 1)) {
		/*
		 * Info frame
		 */
		printf(" I%u%u%c",(i >> 5) & 7,(i >> 1) & 7,j);
	} else if (i & 2) {
		/*
		 * U frame
		 */
		switch (i & (~0x10)) {
		case 0x03:
			printf(" UI%c",j);
			break;
		case 0x2f:
			printf(" SABM%c",j);
			break;
		case 0x43:
			printf(" DISC%c",j);
			break;
		case 0x0f:
			printf(" DM%c",j);
			break;
		case 0x63:
			printf(" UA%c",j);
			break;
		case 0x87:
			printf(" FRMR%c",j);
			break;
		default:
			printf(" unknown U (0x%x)%c",i & (~0x10),j);
			break;
		}
	} else {
		/*
		 * supervisory
		 */
		switch (i & 0xf) {
		case 0x1:
			printf(" RR%u%c",(i >> 5) & 7,j);
			break;
		case 0x5:
			printf(" RNR%u%c",(i >> 5) & 7,j);
			break;
		case 0x9:
			printf(" REJ%u%c",(i >> 5) & 7,j);
			break;
		default:
			printf(" unknown S (0x%x)%u%c", i & 0xf, 
			       (i >> 5) & 7, j);
			break;
		}
	}
	if (!len) {
		printf("\n");
		return;
	}
	printf(" pid=%02X\n", *bp++);
	len--;
	j = 0;
	while (len) {
		i = *bp++;
		if ((i >= 32) && (i < 128)) 
			printf("%c",i);
		else if (i == 13) {
			if (j) 
				printf("\n");
			j = 0;
		} else 
			printf(".");
		if (i >= 32) 
			j = 1;
		len--;
	}
	if (j) 
		printf("\n");
}

/* ---------------------------------------------------------------------- */

static void kiss_input(void) {
	static unsigned char inbuf[BAYCOM_MAXFLEN+1];
	static int inptr = 0;
	static unsigned char escaped = 0;
	static unsigned char valid = 0;
	unsigned char buffer[1024];
	unsigned char *bp = buffer;
	unsigned char cur;
	int len;
	
	len = read(fd_baycom, buffer, sizeof(buffer));
	if (len < 0) {
		if (errno != EAGAIN) {
			fprintf(stderr, "Error %s (%i) reading from KISS "
				"port\n", strerror(errno), errno);
			exit(-3);
		}
		return;
	}
	for(; len > 0; len--, bp++) {
		cur = *bp;
#ifdef DISPLAY_RXBYTES
		printf("0x%02x ",cur);
#endif /* DISPLAY_RXBYTES */
		if(cur == KISS_FEND) {
			if ((inptr > 0) && valid) {
				if ((inbuf[0] & 0xf0) == 0) {	
					/*
					 * only one channel supported 
					 */
					switch(inbuf[0] & 0xf) {
						case KISS_CMD_DATA:
							if (inptr < 8)
								break;
							display_packet(inbuf+1,
								       inptr-1);
							break;
						default:
							break;
					}
				}
			}
			valid = 1;
			inptr = 0;
			escaped = 0;
		} else if (cur == KISS_FESC) {
			escaped = 1;
		} else {
			if (inptr >= sizeof(inbuf)) valid = 0;
			if (valid) {
				if (escaped) {
					if (cur == KISS_TFEND) {
						inbuf[inptr++] = KISS_FEND;
					} else if (cur == KISS_TFESC) {
						inbuf[inptr++] = KISS_FESC;
					} else valid = 0;
					escaped = 0;
				} else inbuf[inptr++] = cur;
			}
		}
	}
}

/* ---------------------------------------------------------------------- */

void print_bits(unsigned int ctl)
{
	int ret;
	unsigned char bits;
	int i;
	
	for(;;) {
		ret = ioctl(fd_baycom, ctl, &bits);
		if (ret < 0) 
			return;
		for(i = 0; i < 8; i++) {
			printf("%c", bits & 0x80 ? '1':'0');
			bits <<= 1;
		}
	}
}

/* ---------------------------------------------------------------------- */

static void display_params(const struct baycom_params *par) 
{
	if (!par || par->modem_type == BAYCOM_MODEM_INVALID) {
		printf("off\n");
		return;
	}
	switch (par->modem_type) {
	case BAYCOM_MODEM_SER12:
		printf("ser12 iobase=0x%x irq=%u options=0x%x\n", 
		       par->iobase, par->irq, par->options);
		break;
	case BAYCOM_MODEM_PAR96:
		printf("par96 iobase=0x%x irq=%u options=0x%x\n", 
		       par->iobase, par->irq, par->options);
		break;
	default:
		printf("invalid parameter structure!\n");
		return;
	}
}

/* ---------------------------------------------------------------------- */

static void do_set_params(int argc, char **argv) 
{
	struct baycom_params par1, par2;
	char set = 1;
	int ret;

	ret = ioctl(fd_baycom, BAYCOMCTL_GETPARAMS, &par1);
	if (ret < 0) {
		fprintf(stderr, "%s: Error: ioctl (BAYCOMCTL_GETPARAMS) "
			"failed, %s (%i)\n", progname, strerror(errno), errno);
		exit(-1);
	}
	par2 = par1;
	par2.irq = par2.iobase = par2.options = 0;
	par2.modem_type = BAYCOM_MODEM_INVALID;
	if (argc < 1)
		set = 0;
	else {
		if (!strcasecmp(argv[0], "off") || 
		    !strcasecmp(argv[0], "none"))
			par2.modem_type = BAYCOM_MODEM_INVALID;
		else if (!strcasecmp(argv[0], "ser12"))
			par2.modem_type = BAYCOM_MODEM_SER12;
		else if (!strcasecmp(argv[0], "par96"))
			par2.modem_type = BAYCOM_MODEM_PAR96;
		else
			set = 0;
	}
	if (set && par2.modem_type != BAYCOM_MODEM_INVALID) {
		if (argc < 3)
			set = 0;
		else {
			par2.iobase = strtoul(argv[1], NULL, 0);
			par2.irq = strtoul(argv[2], NULL, 0);
			par2.options = 0;
		}
		if (argc >= 4)
			par2.options = strtoul(argv[3], NULL, 0);
	}
	printf("current parameters: ");
	display_params(&par1);
	if (set) {
		ret = ioctl(fd_baycom, BAYCOMCTL_SETPARAMS, &par2);
		if (ret < 0) {
			fprintf(stderr, "%s: Error: cannot set parameters, "
				"trying to restore old parameters; %s (%i)\n",
				progname, strerror(errno), errno);
			ret = ioctl(fd_baycom, BAYCOMCTL_SETPARAMS, &par1);
			if (ret < 0) 
				fprintf(stderr, "%s: Error: cannot restore "
					"old parameters; %s (%i)\n",
					progname, strerror(errno), errno);
		} else {
			printf("new parameters:     ");
			display_params(&par2);
		}
		exit(-1);
	}
}

/* ---------------------------------------------------------------------- */

static const char *usage_str = 
"[-b] [-i] [-d] [-i baycomif] [-h] [-c cal] [-p] [type iobase irq options]\n"
"  -b: trace demodulated bits\n"
"  -s: trace sampled input from tcm3105 (ser12 only)\n"
"  -d: trace dcd and ptt status on stdout\n"
"  -i: specify the name of the baycom kernel driver interface\n"
"  -c: calibrate (i.e. send calibration pattern) for cal seconds\n"
"  -p: set or display interface parameters\n"
"  -h: this help\n\n";

int main(int argc, char *argv[])
{
	int ret, stat;
	fd_set fds_read;
	fd_set fds_write;
	struct timeval tm;
	char name_baycom[256] = "/dev/bc0";
	struct baycom_statistics statistics;
#ifdef BAYCOM_DEBUG
	unsigned long debug1, debug2;
	long debug3;
#endif /* BAYCOM_DEBUG */
	char getsetparams = 0;
	char cal = 0;
	int cal_val = 0;

	progname = *argv;
	printf("%s: Version 0.2; (C) 1996 by Thomas Sailer HB9JNX\n", *argv);
	while ((ret = getopt(argc, argv, "bsdi:hpc:")) != -1) {
		switch (ret) {
		case 'b':
			trace_stat = 2;
			break;
		case 's':
			trace_stat = 3;
			break;
		case 'd':
			trace_stat = 1;
			break;
		case 'i':
			strcpy(name_baycom, optarg);
			break;
		case 'p':
			getsetparams = 1;
			break;
		case 'c':
			cal = 1;
			cal_val = strtoul(optarg, NULL, 0);
			break;
		default:
			printf("usage: %s %s", *argv, usage_str);
			exit(-1);
		}
	}
	if ((fd_baycom = open(name_baycom, O_RDWR)) < 0) {
		fprintf(stderr, "%s: Error %s (%i), cannot open %s\n", *argv, 
			strerror(errno), errno, name_baycom);
		exit(-1);
	}
	if (getsetparams) {
		do_set_params(argc - optind, argv+optind);
		exit(0);
	}
	if (cal) {
		ret = ioctl(fd_baycom, BAYCOMCTL_CALIBRATE, cal_val);
		if (ret < 0) 
			fprintf(stderr, "%s: Warning: ioctl "
				"(BAYCOMCTL_CALIBRATE) failed, %s (%i)\n", 
				*argv, strerror(errno), errno);
		else
			fprintf(stdout, "%s: calibrating for %i seconds\n",
				*argv, cal_val);
		exit(0);
	}
	for (;;) {
		FD_ZERO(&fds_read);
		FD_ZERO(&fds_write);
		FD_SET(fd_baycom, &fds_read);
		tm.tv_usec = 500000L;
		tm.tv_sec = 0L;
		ret = select(fd_baycom+1, &fds_read, &fds_write, NULL, &tm);
		if (ret < 0) {
			fprintf(stderr, "%s: Error %s (%i) in select\n", *argv,
				strerror(errno), errno);
			exit(-2);
		}
		if (FD_ISSET(fd_baycom, &fds_read)) kiss_input();
		switch (trace_stat) {
		case 1:
			ret = ioctl(fd_baycom, TIOCMGET, &stat);
			if (ret < 0)
				fprintf(stderr, "%s: Warning: ioctl "
					"(TIOCMGET) failed, %s (%i)\n", *argv,
					strerror(errno), errno);
			ret = ioctl(fd_baycom, BAYCOMCTL_GETSTAT, &statistics);
			if (ret < 0)
				fprintf(stderr, "%s: Warning: ioctl "
					"(BAYCOMCTL_GETSTAT) failed, "
					"%s (%i)\n", *argv, strerror(errno), 
					errno);
#ifdef BAYCOM_DEBUG
			ret = ioctl(fd_baycom, BAYCOMCTL_DEBUG1, &debug1);
			if (ret < 0)
				fprintf(stderr, "%s: Warning: ioctl "
					"(BAYCOMCTL_DEBUG1) failed, "
					"%s (%i)\n", *argv, strerror(errno),
					errno);
			ret = ioctl(fd_baycom, BAYCOMCTL_DEBUG2, &debug2);
			if (ret < 0)
				fprintf(stderr, "%s: Warning: ioctl "
					"(BAYCOMCTL_DEBUG2) failed, "
					"%s (%i)\n", *argv, strerror(errno),
					errno);
			ret = ioctl(fd_baycom, BAYCOMCTL_DEBUG3, &debug3);
			if (ret < 0)
				fprintf(stderr, "%s: Warning: ioctl "
					"(BAYCOMCTL_DEBUG3) failed, "
					"%s (%i)\n", *argv, strerror(errno),
					errno);
#endif /* BAYCOM_DEBUG */
			printf("%c%c ", (stat & TIOCM_CAR) ? 'D' : '-', 
			       (stat & TIOCM_RTS) ? 'P' : '-');
#ifdef BAYCOM_DEBUG
			printf("dbg1: %lu  dbg2: %lu  dbg3: %li  ", debug1, 
			       debug2, debug3);
#endif /* BAYCOM_DEBUG */
			printf("rx: %lu  tx: %lu  ptt: %lu  rxerr: %lu  "
			       "txerr: %lu\n", statistics.rx_packets, 
			       statistics.tx_packets, statistics.ptt_keyed,
			       statistics.rx_bufferoverrun, 
			       statistics.tx_bufferoverrun);
			break;
#ifdef BAYCOM_DEBUG
		case 2:
			print_bits(BAYCOMCTL_GETBITS);
			printf("\n");
			break;
		case 3:
			print_bits(BAYCOMCTL_GETSAMPLES);
			printf("\n");
			break;
#endif /* BAYCOM_DEBUG */
		default:
			break;
		}
	}
}

/* ---------------------------------------------------------------------- */

/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * Emacs will notice this stuff at the end of the file and automatically
 * adjust the settings for this buffer only.  This must remain at the end
 * of the file.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-indent-level: 8
 * c-brace-imaginary-offset: 0
 * c-brace-offset: -8
 * c-argdecl-indent: 8
 * c-label-offset: -8
 * c-continued-statement-offset: 8
 * c-continued-brace-offset: 0
 * End:
 */



