/*
 * Program to control ICOM radios
 *
 * Main program
 */
#include "icom.h"

#ifndef MSDOS			/* include for Unix */
#include <sys/inttypes.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#endif /* MSDOS */
#ifdef AUDIO			/* include for Sun audio */
#include <sys/audioio.h>
#endif /* AUDIO */

/*
 * Module definitions
 */
#define ARGMAX 20		/* maximum number of command args */

/*
 * External functions
 */
extern FILE *fopen();
extern char *strtok(), *strcpy();
extern char *optarg;
extern int optind, opterr;

/*
 * Local function prototypes
 */
static int scanarg(struct icom *, char *, struct cmdtable *, int);
static int getline(char *);
static int argchan(struct icom *, struct chan *, char *);
static int argbank(struct icom *, struct chan *, char *);
static int loadsub(struct icom *, struct chan *, int);
static int setswitch(struct icom *, char *, struct cmdtable *, int);
static int setctrl(struct icom *, char *, int);
static int sw_keypad(void);
static int sw_keybd(void);
static void reset(struct icom *);
static void update(struct icom *, struct chan *);
char *modetoa(int, struct cmdtable *);
double fabs(double);

/*
 * Local variables
 */
int flags;			/* radio flags */
int pflags;			/* program flags */
static char *argv[ARGMAX];	/* command line args */
static int argn;		/* number of command line args */
static FILE *fp_cmd;		/* command input file */
static FILE *fp_in;		/* data input file */
static FILE *fp_out;		/* data output file */
double logtab[] = {		/* tuning rate table */
	1., 2., 5.,		/* 0.000000 */
	10., 25., 50.,		/* 0.00000 */
	1e2, 2.5e2, 5e2,	/* 0.0000 */
	1e3, 2.5e3, 5e3,	/* 0.000 */
	1e4, 2.5e4, 5e4,	/* 0.00 */
	1e5, 2.5e5, 5e5,	/* 0.0 */
	1e6, 2.5e6, 5e6		/* 0. */
};
int sigtab[] = {		/* significant digits table */
	6, 6, 6,		/* 0.000000 */
	5, 6, 5,		/* 0.00000 */
	4, 5, 4,		/* 0.0000 */
	3, 4, 3,		/* 0.000 */
	2, 3, 2,		/* 0.00 */
	1, 2, 1,		/* 0.0 */
	0, 0, 0			/* 0. */
};
struct metertab {
	int smeter;		/* S meter reading */
	char pip[NAMMAX];	/* meter translation */
};
struct metertab mtab[] = {
	{0,	"S0"},		/* 0.4-1.3 uv */
	{12,	"S1"},
	{32,	"S2"},
	{46,	"S3"},		/* 0.79-2.0 uV */
	{64,	"S4"},
	{78,	"S5"},		/* 3.2 uV */
	{94,	"S6"}, 
	{110,	"S7"},		/* 13 uV */
	{126,	"S8"},
	{140,	"S9"},		/* 50 uV */
	{156,	"S9+10"},
	{174,	"S9+20"},	/* 0.5 mv */
	{190,	"S9+30"},
	{208,	"S9+40"},	/* 5 mv */
	{230,	"S9+50"},
	{999,	"S9+60"}	/* 50 mV */
};

#ifndef MSDOS
static int fd;			/* terminal control file descriptor */
static struct termios terma, termb; /* Unix terminal interface */
extern int errno;
#endif /* MSDOS */
#ifdef AUDIO
static struct audio_device device; /* audio device ident */
static struct audio_info info;	/* audio device info */
static int ctl_fd;		/* audio control file descriptor */
#endif /* AUDIO */

/*
 * Main program
 */
int
main(
	int	argc,		/* number of arguments */
	char	**argcv		/* vector of argument pointers */
	)
{
	char	s1[VALMAX], s2[VALMAX]; /* parameter strings */
	char	chr, *ptr;	/* char temps */
	int	i, temp, sw;	/* int temps */
	double	freq, step, dtemp; /* double temps */
	int	par1, par2;	/* integer parameters */
	struct icom *rp;	/* radio pointer */
	struct icom *rp1;	/* temporary radio structure pointer */
	struct chan *cp;	/* memory channel pointer */
	struct chan *vp;	/* vfo channel pointer */

	u_char cmdwrite[] = {V_WRITE, FI};
	u_char cmdclear[] = {V_CLEAR, FI};
	u_char cmdsig[] = {V_RDCTL, S_RDSG, FI};
	u_char cmdsqu[] = {V_RDCTL, S_RDSQ, FI};
	struct metermsg rspsig;
	struct modelmsg rspsqu;
	int	ident;

	/*
	 * Initialize
	 */
	flags = 0;
	rp = NULL;
	init();

#ifndef MSDOS
	/*
	 * Process command-line arguments
	 */
	while ((temp = getopt(argc, argcv, "ac:df:g:km:o:r:")) != -1) {
		switch (temp) {

		/*
		 * -d - debug trace
		 */
		case 'd':
			pflags |= P_TRACE | P_ERMSG;
			continue;

		/*
		 * -a - open audio device
		 */
		case 'a':
#ifdef AUDIO
			if ((ctl_fd = open("/dev/audioctl", O_RDWR)) <
			    0)
				printf("*** audio not available\n");
			continue;
#else /* AUDIO */
			printf("*** audio not configured\n");
			continue;
#endif /* AUDIO */

		/*
		 * -f <file> - open command file
		 */
		case 'f':
			if ((fp_cmd = fopen(optarg, "r")) == NULL) {
				printf("*** file not found\n");
				exit(1);
			}
			continue;

		/*
		 * -k - specify keypad mode
		 */
		case 'k':
			pflags |= P_PAD;
			continue;

		/*
		 * -r <radio> - select radio
		 */
		case 'r':
			temp = argsx(optarg, identab);
			if (temp == R_ERR)
				exit(1);

			rp = select_radio(temp);
			if (rp == NULL) {
				printf("*** initialization failure\n");
				exit(1);
			}
			continue;
		}

		/*
		 * The remaining options are valid only after a valid
		 * radio has been selected. If any are present, the
		 * program exits after executing the command line
		 * options.
		 */
		if (rp == NULL) {
			printf("*** radio not specified\n");
			exit(1);
		}
		ident = rp->ident;
		vp = &rp->vfo;
		switch (temp) {

		/*
		 * -c <chan> - set bank, channel
		 */
		case 'c':
			pflags |= P_EXIT;
			if (argchan(rp, vp, optarg) == R_ERR)
				continue;

			setchan(ident, vp->bank, vp->mchan);
			if (readchan(ident, cp) == R_ERR)
				printf("*** empty channel\n");
			continue;

		/*
		 * -g <freq> - set frequency
		 */
		case 'g':
			pflags |= P_EXIT;
			if (sscanf(optarg, "%lf", &freq) != 1) {
				printf("*** bad frequency format\n");
				continue;
			}
			if (freq > 1000)
				freq /= 1000;
			if (loadfreq(ident, freq) == R_ERR)
				printf("*** bad frequency\n");
			continue;

		/*
		 * -m <mode> - set mode. Note, this option must precede
		 * the -f option for older radios.
		 */
		case 'm':
			pflags |= P_EXIT;
			temp = argsx(optarg, rp->modetab);
			if (temp == R_ERR)
				continue;

			loadmode(ident, temp);
			continue;

		/*
		 * -o <offset> - set duplex offset
		 */
		case 'o':
			pflags |= P_EXIT;
			if (sscanf(optarg, "%lf", &freq) != 1) {
				printf("*** bad offset format\n");
				continue;
			}
			if (loadoffset(ident, freq / 1000) == R_ERR)
				printf("*** bad offset\n");
			continue;
		}
	}

	/*
	 * If a radio was found, initialize it. If its settings were
	 * changed and a command file is not open, assume this is run
	 * from a script and nothing more needs to be done.
	 */
	if (rp != NULL)
		reset(rp);
	if (pflags & P_EXIT)
		exit(1);
	if (pflags & P_PAD) {
		if (sw_keypad())
			pflags &= ~P_PAD;
	}
#endif /* MSDOS */

	/*
	 * Main loop
	 */
	while (1) {
		pflags &= ~(P_DISP | P_KEYP | P_ESC);
		if (pflags & P_PAD) {

			/*
			 * Keypad mode. Keypad commands begin with a
			 * sequence of digits and '+', '-', '.' and end
			 * with a single letter or ANSI escape sequence,
			 * which specifies the command, or \n, which is
			 * mapped to 'f'. Escape sequences consist of
			 * ESC followed by '[' and either a letter or
			 * sequence of digits followed by '~', which is
			 * not part of the command. Help '?' displays a
			 * list of command names and descriptions.
			 */
			printf(">");
			ptr = s1;
			for (i = 0; i < 6; i++)
				*ptr++ = ' ';
			while (1) {
				chr = (char)getchar();
				if (chr == ESC) {
					pflags |= P_ESC;
					*ptr = '\0';
					ptr = s1;
					continue;
				}
				if (pflags & P_ESC) {
					if (chr == '~')
						break;

					*ptr++ = chr;
					if (isalpha(chr) || chr == '\n')
						break;
					continue;
				}
				if (isdigit(chr) || chr == '.' ||
				    chr == '+' || chr == '-') {
					pflags |= P_KEYP;
					*ptr++ = chr;
 					putchar(chr);
					continue;

				} else if (chr != '\n') {
					pflags |= P_KEYP;
					putchar(chr);
				}
				if (pflags & P_KEYP)
					putchar('\n');
				*ptr = '\0';
				*s1 = chr;
				break;
			}
			if ((argn = getline(s1)) == 0)
				sw = C_FREQ;
			else
				sw = argsx(argv[0], key);
		} else {

			/*
			 * Keyboard mode. Get the next command, convert
			 * to lower case and parse the tokens. Select
			 * the command based on the first token. Ignore
			 * '#' and the rest of the line. This is for
			 * command scripts.
			 */
			if (fp_cmd != NULL) {
				if (fgets(s1, VALMAX, fp_cmd) == NULL)
					exit(0);

				printf("%s", s1);
			} else {
				printf("icom>");
				if (gets(s1) == NULL)
					exit(0);
			}
			if (*s1 == '#')
				continue;

			if ((argn = getline(s1)) == 0)
				sw = C_FREQ;
			else
				sw = argsx(argv[0], cmd);
		}
		switch (sw) {

		/*
		 * radio [ <name> ]
		 *
		 * Select the <name> radio for further commands and
		 * display its description and band limits. If name is
		 * missing and the radio has not been previously
		 * defined, the buss is probed for all known radios,
		 * which takes some time. If previouslh defined, its
		 * description and band limits of the are displayed.
		 */
		case C_RADIO:
			if (argn < 2) {
				if (rp != NULL) {
					printf("radio %s\n",
					    getcap("radio", rp->cap));
					continue;
				}
				for (i = 0; name[i].name[0] != '\0';
				    i++) {
					rp1 =
					    select_radio(name[i].ident);
					if (rp1 != NULL) {
						rp = rp1;
						reset(rp);
						printf("radio %s\n",
						    getcap("radio",
						    rp->cap));
					}
				}
				continue;
			}
			temp = argsx(argv[1], identab);
			if (temp == R_ERR)
				continue;

			rp = select_radio(temp);
			if (rp == NULL) {
				printf("*** radio not found\n");
				continue;
			}
			reset(rp);
			printf("radio %s\n", getcap("radio", rp->cap));
			break;
 
		/*
		 * quit
		 *
		 * Quit the dance
		 */
		case C_QUIT:
			exit(0);
 
		/*
		 * verbose off | on
		 *
		 * Set verbose mode
		 */
		case C_VERB:
			if (argn < 2) {
				printf("*** missing argument\n");
				continue;
			}
			temp = argsx(argv[1], verbx);
			if (temp != R_ERR)
				pflags = (pflags & ~P_VERB) | temp;
			continue;

		/*
		 * trace [ all | none | bus | pkt ]
		 *
		 * Set debug flags
		 */
		case C_DEBUG:
			if (argn < 2) {
				printf("*** missing argument\n");
				continue;
			}
			temp = argsx(argv[1], dbx);
			if (temp != R_ERR)
				pflags = (pflags & ~(P_TRACE |
				    P_ERMSG)) | temp;
			continue;

		/*
		 * pad
		 *
		 * Switch to keypad mode.
		 */
		case C_KEYPAD:
			if (!sw_keypad())
				pflags |= P_PAD;
			continue;

		/*
		 * # (keypad mode)
		 *
		 * Erase input
		 */
		case R_ERR:
		case C_ERASE:
			continue;

		/*
		 * q (keypad mode)
		 *
		 * Switch to keyboard mode.
		 */
		case C_KEYBD:
			if (!sw_keybd())
				pflags &= ~P_PAD;
			continue;
#ifdef AUDIO
		/*
		 * gain [ <gain> ]
		 *
		 * Adjust record gain in 16 levels.
		 */
		case C_GAIN:
			if (ctl_fd <= 0) {
				printf("*** audio not available\n");
			} else if (argn < 2) {
				printf("audio gain %d\n",
				    info.record.gain);
			} else if (sscanf(argv[1], "%d", &temp) != 1) {
				printf("*** bad gain format\n");
			} else {
				if (temp > 0)
					temp = (temp << 4) - 1;
				if (temp > AUDIO_MAX_GAIN)
					temp = AUDIO_MAX_GAIN;
				else if (temp < AUDIO_MIN_GAIN)
					temp = AUDIO_MIN_GAIN;
				ioctl(ctl_fd, (int)AUDIO_GETINFO,
				    &info);
				info.record.gain = temp;
				ioctl(ctl_fd, (int)AUDIO_SETINFO,
				    &info);
			}
			continue;

		/*
		 * mute
		 *
		 * Mute output (toggle)
		 */
		case C_MUTE:
			if (ctl_fd <= 0) {
				printf("*** audio not available\n");
				continue;
			}
			ioctl(ctl_fd, (int)AUDIO_GETINFO, &info);
			if (info.output_muted) {
				info.output_muted = 0;
			} else {
				info.output_muted = 1;
				printf("audio muted\n");
			}
			ioctl(ctl_fd, (int)AUDIO_SETINFO, &info);
			continue;

		/*
		 * port <port>
		 *
		 * Select input port (1 = mike, 2 = line in)
		 */
		case C_PORT:
			if (ctl_fd <= 0) {
				printf("*** audio not available\n");
			} else if (argn < 2) {
				printf("audio port %d\n",
				    info.record.port);
			} else if (sscanf(argv[1], "%d", &temp) != 1) {
				printf("*** bad port format\n");
			} else if (temp < 1 || temp > 2) {
				printf("*** invalid port number\n");
			} else {
				ioctl(ctl_fd, (int)AUDIO_GETINFO,
				    &info);
				info.record.port = temp;
				ioctl(ctl_fd, (int)AUDIO_SETINFO,
				    &info);
			}
			continue;
#endif /* AUDIO */
		}

		/*
		 * The remaining commands are valid only after a radio
		 * has been selected.
		 */
		if (rp == NULL) {
			printf("*** radio not specified\n");
			continue;
		}
		ident = rp->ident;
		vp = &rp->vfo;
		cp = &rp->chan;
		switch (sw) {

		/*
		 * cap [ <args> ]
		 */
		case C_PROBE:
			if (argn < 2) {
				(void)getcap("?", rp->cap);
				break;
			}
			printf("%s %s\n", argv[1], getcap(argv[1],
			    rp->cap));
			break;

		/*
		 * restore file [ first ] [ last ]
		 *
		 * Restore memory channels from from a file to a range
		 * of channels numbered first through last in sequence.
		 * The default is to restore them all to the original
		 * channels.
		 */
		case C_RESTORE:
			if (argn < 2) {
				printf(
				"usage: restore <file> [first] [last]\n");
				break;
			}
			if ((fp_in = fopen(argv[1], "r")) == NULL) {
				printf("*** input file error\n");
				break;
			}
			if (argn > 3) {
				if (argchan(rp, vp, argv[3]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
				if (argchan(rp, vp, argv[2]) == R_ERR)
					break;

			} else if (argn == 3) {
				if (argchan(rp, vp, argv[2]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
			} else {
				vp->bank = 0;
				vp->mchan = rp->minch;
				par1 = rp->maxbk;
				par2 = rp->maxch;
			}
			while (1) {
				if (fgets(s1, VALMAX, fp_in) == NULL)
					break;

				printf("%s", s1);
				argn = getline(s1);
				if (argn == 0)
					continue;

				if (argchan(rp, vp, argv[0]) == R_ERR)
					continue;

				if (argn == 1)
					continue;

				if (sscanf(argv[1], "%lf", &freq) != 1)
				    {
					printf("*** bad frequency format\n");
					continue;
				}
				if (freq > 1000.)
					freq /= 1000.;
				vp->freq = freq;
				if (argn == 3) {
					temp = argsx(argv[2],
					    rp->modetab);
					if (temp == R_ERR)
						continue;

					vp->mode = temp;
				}
				if (argn > 3) {
					if (loadsub(rp, vp, 2) == R_ERR)
						continue;
				}
				if (flags & F_BANK) {
					write_chan(ident, vp);
				} else {
					setchan(ident, vp->bank,
					    vp->mchan);
					if (loadmode(ident, vp->mode) ==
					    R_ERR)
						printf("*** bad mode\n");
					if (loadfreq(ident, vp->freq) ==
					    R_ERR)
						printf("*** bad frequency\n");;
					setcmd(ident, V_WRITE, FI);
				}
				if (vp->bank == par1 && vp->mchan ==
				    par2)
					break;

			}
			close(fp_in);
			break;

		/*
		 * save file [ first ] [ last ]
		  Save memory channels to a file from a range of
		 * channels numbered first through last in sequence,
		 * skipping empty channels. The default is to save them
		 * all.
		 */
		case C_SAVE:
			if (argn < 2) {
				printf(
				"usage: save <file> [first] [last]\n");
				break;
			}
			if ((fp_out = fopen(argv[1], "w")) == NULL) {
				printf(
				"*** output file error\n");
				break;
			}
			if (argn > 3) {
				if (argchan(rp, vp, argv[3]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
				if (argchan(rp, vp, argv[2]) == R_ERR)
					break;

			} else if (argn > 2) {
				if (argchan(rp, vp, argv[2]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
			} else {
				vp->bank = 0;
				vp->mchan = rp->minch;
				par1 = rp->maxbk;
				par2 = rp->maxch;
			}
			while (1) {
				setchan(ident, vp->bank, vp->mchan);
				if (readchan(ident, vp) == R_ERR) {
					printf("%d.%d empty channel\n",
					    vp->bank, vp->mchan);
				} else if (flags & F_OFFSET) {
 					sprintf(s1,
					    "%2d.%-2d %10.*lf %s %.0lf",
					    vp->bank, vp->mchan,
					    sigtab[rp->rate], vp->freq,
					    modetoa(vp->mode,
					    rp->modetab), vp->duplex);
					fprintf(fp_out, "%s\n", s1);
					puts(s1);
				} else {
					sprintf(s1,
					    "%2d.%-2d %10.*lf %s",
					    vp->bank, vp->mchan,
					    sigtab[rp->rate], vp->freq,
					    modetoa(vp->mode,
					    rp->modetab));
					fprintf(fp_out, "%s\n", s1);
					puts(s1);
				}
				if (vp->bank == par1 && vp->mchan ==
				    par2)
					break;

				if (argchan(rp, vp, "+") == R_ERR)
					break;
			}
			fclose(fp_out);
			break;

		/*
		 * bank [ bank ] [ name ] [...]
		 *
		 * Read/write bank name (R8500).
		 */
		case C_BANK:
			if (argn < 2) {
				temp = cp->bank;
			} else if (sscanf(argv[1], "%d", &temp) != 1) {
				printf("*** bad bank number\n");
				break;
			}
			if (loadsub(rp, cp, 2) == R_ERR)
				break;

			if (argn > 2)
				loadbank(ident, temp, cp->name);
			if (readbank(ident, temp, s1) == R_ERR) {
				printf("*** radio %s can't do that\n",
				    rp->name);
				break;
			}
			cp->bank = temp;
			printf("bank %d %s\n", temp, s1);
			break;

		/*
		 * chan [ chan ]
		 *
		 * Read frequency, mode and other data from a memory
		 * channel.
		 */
		case C_CHAN:
			if (argn < 2) {
				pflags |= P_DISP | P_DSPCH;
				break;

			}
			if (argchan(rp, vp, argv[1]) == R_ERR)
				break;

			setchan(ident, vp->bank, vp->mchan);
			if (readchan(ident, vp) == R_ERR) {
				printf("*** empty channel\n");
				break;
			}
			
			pflags |= P_DISP | P_DSPCH;
			break;

		/*
		 * write [ chan ]
		 *
		 * Write frequency, mode and other data to a memory
		 * channel.
		 */
		case C_WRITE:
			if (argn > 1) {
				if (argchan(rp, vp, argv[1]) == R_ERR)
					break;
			}
			if (vp->freq == 0) {
				printf("*** empty channel\n");
				break;
			}
			if (setcmda(ident, cmdwrite, s1) == R_ERR) {
				printf("*** radio %s can't do that\n",
				    rp->name);
				break;
			}
			break;

		/*
		 * clear [ chan ] [ chan ]
		 *
		 * Clear memory channel. If no arguments, clear current
		 * channel; if one argument, clear that; if two
		 * argments, clear the block.
		 */
		case C_CLEAR:
			if (argn > 2) {
				if (argchan(rp, vp, argv[2]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
				if (argchan(rp, vp, argv[1]) == R_ERR)
					break;

			} else if (argn > 1) {
				if (argchan(rp, vp, argv[1]) == R_ERR)
					break;

				par1 = vp->bank;
				par2 = vp->mchan;
			} else {
				par1 = vp->bank;
				par2 = vp->mchan;
			}
			while (1) {
				setchan(ident, vp->bank, vp->mchan);
				if (setcmda(ident, cmdclear, s1) ==
				    R_ERR) {
					printf("*** radio %s can't do that\n",
				 	   rp->name);
					break;
				}
				if (vp->bank == par1 && vp->mchan ==
				    par2)
					break;

				if (argchan(rp, vp, "+") == R_ERR)
					break;
			}
			vp->freq = 0;
			break;

		/*
		 * freq [ freq ] [ mode ]
		 *
		 * Set current operating frequency (kHz or MHz) and
		 * optional mode. The default is to display the current
		 * frequency and mode. Note that the mode has to be set
		 * first, since some radios shift only the BFO and don't
		 * compensate the local oscillator. Watch out for
		 * ham/general-coverage switch; some radios won't let
		 * you set the frequency outside the ham bands if the
		 * switch is in the ham position. This holds also when
		 * reading a memory channel, which can be stored either
		 * way. 
		 */
		case C_FREQ:
			if (argn < 2) {
				pflags |= P_DISP;
				break;
			}
			if (argn > 2) {
				temp = argsx(argv[2], rp->modetab);
				if (temp == R_ERR)
					break;

				if (loadmode(ident, temp) == R_ERR) {
					printf("*** bad mode\n");
					break;
				}
				vp->mode = temp;
			}
			if (sscanf(argv[1], "%lf", &freq) != 1) {
				printf("*** bad frequency format\n");
				break;
			}
			if (freq > 1000.)
				freq /= 1000.;
			if (loadfreq(ident, freq) == R_ERR) {
				printf("*** bad frequency\n");
				break;
			}	
			vp->freq = freq;
			pflags |= P_DISP;
			break;

		/*
		 * load chan freq [...]
		 *
		 * Read/write channel data (R8500).
		 *
		 * This command loads the memory channel directly
		 * without affecting the working registers. Unless
		 * overriden, the name, mode, dial tuning step and
		 * attenuator apply to all subsequent loads. These
		 * functions can be set in the [...] list and will apply
		 * to the current and subsequent loads.
		 *
		 * The [...] options are in pairs
		 *
		 * name     up to eight characters     
		 * mode     as in the mode command
		 * dial     as in the dial command
		 * atten    as in the atten command
		 * duplex   as in the duplex command
		 */
		case C_LOAD:
			if (argn < 2) {
				printf("*** argument required\n");
				break;
			}
			if (argchan(rp, cp, argv[1]) == R_ERR)
				break;

			if (argn < 3) {
				if (read_chan(ident, cp) == R_ERR)
					printf("*** radio %s can't do that\n",
					    rp->name);
				break;
			}
			if (sscanf(argv[2], "%lf", &freq) != 1) {
				printf("*** bad frequency format\n");
				break;
			}
			if (freq > 1000)
				freq /= 1000;
			cp->freq = freq;
			if (loadsub(rp, cp, 3) == R_ERR)
				break;

			if (cp->pstep < .5)
				cp->pstep = .5;
			if (write_chan(ident, cp) == R_ERR) {
				printf("*** radio %s can't do that\n",
				    rp->name);
				break;
			}
			if (pflags & P_VERB) {
				printf("bank %d chan %d", cp->bank,
				    cp->mchan);
				printf(" freq %lf MHz mode %04x",
			   	    cp->freq, cp->mode);
				printf(" step %02x pstep %.1lf atten %02x scan %02x name %s\n",
			    	    cp->step, cp->pstep, cp->atten,
				    cp->scan, cp->name);
			}
			break;

		/*
		 * (command not found)
		 *
		 * We get here if the first argument matches no valid
		 * command name. If it has valid floating point format,
		 * set the frequency as given. If so and there is an
		 * additional argument, set the mode as given.
		 */
		case C_FREQX:
			if (argn > 1) {
				temp = argsx(argv[1], rp->modetab);
				if (temp == R_ERR)
					break;

				if (loadmode(ident, temp) == R_ERR)
					break;

				vp->mode = temp;
			}
			if (sscanf(argv[0], "%lf", &freq) != 1) {
				printf("*** bad frequency format\n");
				break;
			}
			if (freq > 1000.)
				freq /= 1000;
			if (loadfreq(ident, freq) == R_ERR) {
				printf("*** bad frequency\n");
				break;
			}
			vp->freq = freq;
			pflags |= P_DISP;
			break;

		/*
		 * offset [ <offset> ]
		 *
		 * Set VFO frequency offset.
		 */
		case C_OFFSET:
			if (argn < 2) {
				printf("VFO offset %.0lf Hz\n",
				    rp->offset);
				break;
			}
			if (sscanf(argv[1], "%lf", &freq) != 1) {
				printf("*** bad offset format\n");
				break;
			}
			rp->offset = freq;
			break;

		/*
		 * comp [ <offset> ]
		 *
		 * Compensate VFO frequency offset.
		 */
		case C_VCOMP:
			if (argn < 2) {
				printf("VFO compensation %.3lf PPM\n",
				    rp->freq_comp * 1e6);
				break;
			}
			if (sscanf(argv[1], "%lf", &freq) != 1) {
				printf("*** bad compensation format\n");
				break;
			}
			rp->freq_comp = freq / 1e6;
			break;

		/*
		 * band [ <low> ] [ <high> ]
		 *
		 * Set band scan limits.
		 */
		case C_BAND:
			if (argn < 2) {
				printf("band %s\n", getcap("band",
				    rp->cap));
				break;
			}
			if (argn < 3) {
				printf("*** two arguments required\n");
				break;
			}
			if (sscanf(argv[1], "%lf", &freq) != 1) {
				printf("*** bad format arg 1\n");
				break;
			}
			if (sscanf(argv[2], "%lf", &step) != 1) {
				printf("*** bad format arg 2\n");
				break;
			}
			if (freq > step) {
				dtemp = freq;
				freq = step;
				step = dtemp;
			}
			if (freq < rp->lband)
				freq = rp->lband;
			rp->lstep = freq;
			if (step > rp->uband)
				step = rp->uband;
			rp->ustep = step;
			break;

		/*
		 * Some radios synthesize BFO frequencies separately for
		 * each mode. The BFO offset command is used to correct
		 * for the intrinsic frequency errors (Hz) in each mode.
		 * Other radios synthesize all frequencies from a single
		 * crystal, so the BFO offsets should all be zero. The
		 * VFO compensation command is used to correct for the
		 * intrinsic frequency error (PPM) of the main
		 * synthesizer.
		 *
		 * mode [ mode ] [ offset ]
		 *
		 * Set current operating mode and BFO offset.
		 */
		case C_MODE:
			if (argn < 2) {
				freq = rp->bfo[vp->mode & 0x7];
				printf("mode %s BFO offset %.0lf Hz\n",
				    modetoa(vp->mode, rp->modetab),
				    freq);
				break;
			}
			temp = argsx(argv[1], rp->modetab);
			if (temp == R_ERR)
				break;

			if (loadmode(ident, temp) == R_ERR)
				break;

			vp->mode = temp;
			if (flags & F_RELD)
				loadfreq(ident, vp->freq);
			if (argn > 2) {
				if (sscanf(argv[2], "%lf", &freq) != 1)
				    {
					printf(
					    "*** bad offset format\n");
					break;
				}
				rp->bfo[vp->mode & 0x7] = freq;
			}
			pflags |= P_DISP;
			break;

		/*
		 * Set the operating mode.
		 */
		case C_MODEG:
			temp = argsx(argv[0], rp->modetab);
			if (temp == R_ERR)
				break;

			if (loadmode(ident, temp) == R_ERR)
				break;

			vp->mode = temp;
			if (flags & F_RELD)
				loadfreq(ident, vp->freq);
			pflags |= P_DISP;
			break;

		/*
		 * The following commands require the transmit duplex
		 * offset feature in most VHF/UHV transceivers.
		 *
		 * duplex [ duplex ]
		 *
		 * Set transmit offset for FM duplex.
		 */
		case C_DUPLEX:
			if (!(flags & F_OFFSET)) {
				printf("*** radio %s can't do that\n",
				    rp->name);
				break;
			}
			if (argn < 2) {
				printf("duplex %+.0lf kHz\n",
				    vp->duplex);
				break;
			}
			flags &= ~F_SMPLX;
			if (sscanf(argv[1], "%lf", &freq) != 1) {
				printf("*** bad duplex format\n");
				break;
			}
			if (loadoffset(ident, freq))
				break;

			vp->duplex = freq;
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * simplex (toggle)
		 *
		 * Receive on transmit frequency.
		 */
		case C_SMPLX:
			if (!(flags & F_OFFSET)) {
				printf("*** radio %s can't do that\n",
				    rp->name);
				break;
			}
			if (flags & F_SMPLX) {
				freq = (vp->freq -
				    rp->oldplex) * 1000.;
				if (loadoffset(ident, freq))
					break;

				if (loadfreq(ident, rp->oldplex))
					break;

				flags &= ~F_SMPLX;
				vp->duplex = freq;
			} else {
				freq = vp->freq +
				    vp->duplex / 1000.;
				rp->oldplex = vp->freq;
				if (loadoffset(ident, 0))
					break;

				if (loadfreq(ident, freq))
					break;

				vp->duplex = freq;
				flags |= F_SMPLX;
			}
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * The following commands should work in all receivers
		 * and transceivers.
		 *
		 * Tuning rate commands. These adjust the tuning steps
		 * in 1-2.5-5 sequence from the minimum step usable with
		 * the radio to 5 MHz per step(!). Each time the tuning
		 * step is changed, the vfo frequency is aligned to the
		 * current least significant digit padded by zeros.
		 *
		 * rate [ <rate> ]
		 *
		 * Set tuning rate. The values of <rate> from 0 through
		 * 20 select the rate values in a 1-2.5-5-10 sequence.
		 */
		case C_RATE:
			if (argn > 1) {
				if (sscanf(argv[1], "%d", &temp) != 1) {
					printf("*** bad rate format\n");
					break;
				}
				if (temp > 20)
					temp = 20;
				else if (temp < rp->minstep)
					temp = rp->minstep;
				rp->rate = temp;
				rp->step = logtab[rp->rate];
			}
			printf("rate %d step %.0lf Hz\n", rp->rate,
			    rp->step);
			break;

		/*
		 * rate up (keypad)
		 *
		 * Set tuning rate up one notch.
		 */
		case C_RUP:
			if (rp->rate < 20)
				rp->rate++;
			rp->step = logtab[rp->rate];
			step = modf(vp->freq / (rp->step * 1e-6),
			    &freq);
			freq *= rp->step * 1e-6;
			if (loadfreq(ident, freq) != R_ERR)
				printf("step %.0lf Hz\n", rp->step);
			break;

		/*
		 * rate down (keypad)
		 *
		 * Set tuning rate down one notch.
		 */
		case C_RDOWN:
			if (rp->rate > rp->minstep)
				rp->rate--;
			rp->step = logtab[rp->rate];
			step = modf(vp->freq / (rp->step * 1e-6),
			    &freq);
			freq *= rp->step * 1e-6;
			if (loadfreq(ident, freq) != R_ERR)
				printf("step %.0lf Hz\n", rp->step);
			break;

		/*
		 * Tuning step commands. The step command sets the
		 * tuning step directly to an arbitrary value. The up
		 * and down commands shift the frequency up or down by
		 * the value of the tuning step.
		 *
		 * step [ <step> ]
		 *
		 * Set tuning step directly in kHz. This is useful when
		 * scanning odd channel spacings, such as aviation and
		 * marine radio channels. Note that the tuning rate is
		 * set to minimum here, since otherwise the rounding
		 * process would violate the principle of least
		 * astonishment.
		 */
		case C_STEP:
			if (argn < 2) {
				printf("rate %d step %.0lf Hz\n",
				    rp->rate, rp->step);
				break;
			} else {
				if (sscanf(argv[1], "%lf", &step) != 1)
				    {
					printf("*** bad step format\n");
					break;
				}
			}
			rp->rate = rp->minstep;
			if (step < logtab[rp->minstep])
				step = logtab[rp->minstep];
			rp->step = step;
			break;

		/*
		 * up (keypad)
		 *
		 * Tune up one step.
		 */
		case C_UP:
			freq = vp->freq + rp->step / 1e6;
			if (freq > rp->ustep + rp->step / 2e6)
				freq = rp->lstep;
			if (loadfreq(ident, freq) != R_ERR)
				vp->freq = freq;
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * down (keypad)
		 *
		 * Tune down one step.
		 */
		case C_DOWN:
			freq = vp->freq - rp->step / 1e6;
			if (freq < rp->lstep - rp->step / 2e6)
				freq = rp->ustep;
			if (loadfreq(ident, freq) != R_ERR)
				vp->freq = freq;
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * The following commands are known to work in the 775
		 * transceiver and probably the 781.
		 *
		 * vfo [ <command> ]
		 *
		 * Set vfo (V_VFO) subcommands.
		 */
		case C_VFO:
			if (argn < 2) {
				printf("vfo %s\n",
				    getcap("vfo", rp->cap));
				break;
			}
			temp = scanarg(rp, argv[1], vfo, V_SVFO);
			if (temp == R_ERR)
				break;

			setcap("vfo", rp->cap, argv[1]);
			switch (temp) {

			case S_XCHNG:
				freq = rp->sub;
				rp->sub = vp->freq;
				vp->freq = freq;
				break;

			case S_EQUAL:
				rp->sub = vp->freq;
				break;
			}
			break;

		/*
		 * The following commands require the transmit split
		 * feature in some HF transceivers.
		 *
		 * split [ <offset> ]
		 *
		 * Set the transmit frequency relative to the receive
		 * frequency and turn the split on. If no arguments,
		 * just toggle the split on and off.
		 */
		case C_SPLIT:
			if (argn < 2 && rp->sub != 0) {
				if (flags & F_SPLIT) {
					setcmd(ident, V_SPLIT, S_OFF);
					flags &=~F_SPLIT;
				} else {
					setcmd(ident, V_SPLIT, S_ON);
					flags |= F_SPLIT;
				}
				pflags |= P_DISP;
				break;
			}
			setcmd(ident, V_SPLIT, S_ON);
			if (argn < 2) {
				step = 0;
			} else {
				if (sscanf(argv[1], "%lf", &step) != 1)
				    {
					printf("*** bad format\n");
					break;
				}
			}
			if (step == 0 || *argv[1] == '+' || *argv[1] ==
			    '-')
				step = vp->freq + step / 1000.;
			else if (step > 1000.)
				step /= 1000.;
			freq = vp->freq;
			setcmd(ident, V_SVFO, S_EQUAL);
			loadfreq(ident, step);
			setcmd(ident, V_SVFO, S_XCHNG);
			vp->freq = freq;
			rp->sub = step;
			flags |= F_SPLIT;
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * change (toggle)
		 *
		 * Swap main and sub VFOs.
		 */
		case C_CHANGE:
			if (!(flags & F_SPLIT)) {
				printf(
				    "*** transmit split must be on\n");
				break;
			}
			if (setcmd(ident, V_SVFO, S_XCHNG) == R_ERR) {
				printf("*** radio %s can't do that",
				    rp->name);
				break;
			}
			freq = vp->freq;
			vp->freq = rp->sub;
			rp->sub = freq;
			if (pflags & P_VERB)
				pflags |= P_DISP;
			break;

		/*
		 * key <string>
		 *
		 * Transmit quoted ASCII string as CW (775)
		 */
		case C_KEY:
			ptr = getcap("key", rp->cap);
			if (argn < 2) {
				if (*ptr == '\0') {
					printf("key undefined\n");
					break;
				}
			} else {
				ptr = argv[1];
			}
			if (sendcw(ident, ptr) == R_ERR)
				printf("*** radio %s can't do that\n",
				    rp->name);
			else
				setcap("key", rp->cap, ptr);
			break;

		/*
		 * dial [ <step> ]
		 *
		 * Set dial tuning step. This command works with both
		 * the 775 and R8500; however, the allowable arguments
		 * are different. Note that in the R8500 the allowable
		 * steps are constrained to multiples of 0.5 kHz.
		 */
		case C_DIAL:
			ptr = getcap("dial", rp->cap);
			if (argn < 2) {
				if (*ptr == '\0')
					printf("dial undefined\n");
				else
					printf("dial %s\n", ptr);
				break;
			}
			if (flags & F_BANK)
				temp = argsx(argv[1], diala);
			else
				temp = argsx(argv[1], dialb);
			if (temp == R_ERR) {
				break;

			} else if (sscanf(argv[1], "%lf", &dtemp) !=
			    1) {
				printf("*** invalid format\n");
			} else if (loaddial(ident, temp, dtemp) ==
			    R_ERR) {
				printf("*** radio %s can't do that\n",
				    rp->name);
			} else {
				cp->step = temp;
				if (step == 13)
					cp->pstep = dtemp;
				setcap("dial", rp->cap, argv[1]);
			}
			break;

		/*
		 * The control commands read or write a internal
		 * register associated with a front panel control. The
		 * 706MKIIG can read these registers but cannot write
		 * them. The R8500 can write them but not read them. For
		 * the R8500 a write command disables the front panel
		 * control until the control is set to zero, then the
		 * control is reenabled.
		 */
		/*
		 * meter
		 *
		 * Read S meter and squelch. Note that the S meter is
		 * decoded in S units and dB above S9.
		 */
		case C_METER:
			temp = setcmda(ident, cmdsig,
			    (u_char *)&rspsig);
			if (temp == R_ERR) {
				printf("*** radio %s can't do that\n",
			  	    rp->name);
				break;
			}
			temp = rspsig.meter[0] & 0xf;
			temp = temp * 10 + (rspsig.meter[1] >> 4);
			temp = temp * 10 + (rspsig.meter[1] & 0xf);
			for (i = 0; temp > mtab[i].smeter; i++);
			setcmda(ident, cmdsqu, (u_char *)&rspsqu);
			if (rspsqu.id == 1)
				printf("meter %s squelch open\n",
				    mtab[i].pip);
			else
				printf("meter %s squelch closed\n",
				    mtab[i].pip);
			break;
		/*
		 * volume [ value ]
		 *
		 * Set volume control range 0 to 255.
		 */
		case C_VOLUME:
			setctrl(rp, "vol", S_WRAF);
			break;

		/*
		 * squelch [ value ]
		 *
		 * Set squelch control range 0 to 255.
		 */
		case C_SQUELCH:
			setctrl(rp, "squelch", S_WRSQ);
			break;

		/*
		 * shift [ value ]
		 *
		 * Set IF shift control range -128 to 127.
		 */
		case C_SHIFT:
			setctrl(rp, "shift", S_WRSH);
			break;

		/*
		 * peak [ value ]
		 *
		 * Set audio peak filter (APF) control range -128 to
		 * 127. The APF function is operative only if enabled by
		 * the apf command.
		 */
		case C_APFC:
			setctrl(rp, "peak", S_WRAP);
			break;

		/*
		 * The switch commands activate and deactivate functions
		 * associated with front panel radio buttons. A button
		 * toggles an associated function on or off, while a
		 * command explicitly sets the function on or off.
		 * 
		 * ant [ 1 | 2 ]
		 *
		 * Select antenna 1/2.
		 */
		case C_ANT:
			setswitch(rp, "ant", ant, V_SANT);
			break;

		/*
		 * power [ off | on ]
		 *
		 * Set power on/off. The radio will be powered off after
		 * the sleep interval, but it will still listen for a
		 * power on command.
		 */
		case C_POWER:
			setswitch(rp, "power", power, V_POWER);
			break;

		/*
		 * agc [ slow | fast ]
		 *
		 * Set AGC time constant slow/fast.
		 */
		case C_AGC:
			if (flags & F_BANK)
				setswitch(rp, "agc", agc, V_TOGL);
			else
				setswitch(rp, "agc", agc1, V_TOGL);
			break;

		/*
		 * nb [ off | on ]
		 *
		 * Set noise blanker off/on.
		 */
		case C_NB:
			if (flags & F_BANK)
				setswitch(rp, "nb", nb, V_TOGL);
			else
				setswitch(rp, "nb", nb1, V_TOGL);
			break;

		/*
		 * apf [ off | on ]
		 *
		 * Set audio peak filter (APF) off/on. Note that there
		 * are two modes for the APF, narrow and wide. The mode
		 * alternates when the APF button is held down for one
		 * second, but cannot be controlled by the program.
		 */
		case C_APF:
			setswitch(rp, "apf", apf, V_TOGL);
			break;

		/*
		 * atten [ 0 | 10 | 20 | 30 ]
		 *
		 * Select attenuator 0/10/20/30 dB.
		 */
		case C_ATTEN:
			temp = setswitch(rp, "atten",
			    atten, V_ATTEN);
			if (temp != R_ERR)
				rp->atten = temp;
			break;

		/*
		 * say [ off | on ]
		 *
		 * Set announce control off/on. This requires the UT-102
		 * Voice Synthesizer Unit.
		 */
		case C_ANNC:
			setswitch(rp, "say", annc, V_ANNC);
			break;

		/*
		 * preamp [ off | on ]
		 *
		 * Preamp off/on.
		 */
		case C_PAMP:
			setswitch(rp, "preamp", preamp, V_TOGL);
			break;

		/*
		 * tone [ off | on | nosquelch | squelch ]
		 *
		 * Repeater tone off/on.
		 */
		case C_TONE:
			setswitch(rp, "tone", tone, V_TOGL);
			break;

		/*
		 * comp [ off | on ]
		 *
		 * Speech compressor  off/on.
		 */
		case C_COMP:
			setswitch(rp, "comp", comp, V_TOGL);
			break;

		/*
		 * vox [ off | on ]
		 *
		 * Voice control off/on.
		 */
		case C_VOX:
			setswitch(rp, "vox", vox, V_TOGL);
			break;

		/*
		 * qsk [ off | on ]
		 *
		 * QSK break in off/on.
		 */
		case C_QSK:
			setswitch(rp, "qsk", qsk, V_TOGL);
			break;

		/*
		 * scan [ <command> ]
		 *
		 * Scan control (V_SCAN) subcommands.
		 */
		case C_SCAN:
			setswitch(rp, "scan", scan, V_SCAN);
			if (strcasecmp(argv[1], "stop") == 0) {
				readfreq(ident, &vp->freq);
				pflags |= P_DISP;
			}
			break;

		/*
		 * Miscellaneous control (S_CTRL) subcommands.
		 */
		case C_MISC:
			setswitch(rp, "misc", misc, V_CTRL);
			break;
		}
		update(rp, vp);
	}
	return (R_OK);
}


/*
 * reset(radio) - initialize radio
 */
static void
reset(
	struct icom *rp		/* radio structure */
	)
{
	struct cmd2msg cmdmodel = {V_RDID, 0, FI};
	struct cmdvec *table;
	char	s1[VALMAX];
	u_char	rsp[CMDMAX];
	int	temp, i;

	/*
	 * Initialize radio capabilities
	 */
	table = rp->probe;
	sprintf(s1, "%s (%02x) %g-%g MHz %d chan %d bank",
	    getcap(rp->name, identab), rp->ident, rp->lband, rp->uband,
	    rp->maxch + 1, rp->maxbk + 1);
	setcap("radio", rp->cap, s1);
	update(rp, &rp->vfo);

	/*
	 * Scan through capabilities list.
	 */
	if (table == NULL)
		return;

	for (i = 0; table[i].name[0] != '\0'; i++) {
		temp = setcmda(rp->ident, table[i].cmd, rsp);
		if (temp != R_ERR)
			setcap(table[i].name, rp->cap, table[i].descr);
	}
}


/*
 * update(radio, chan) - update capabilities
 */
static void
update(
	struct icom *rp,	/* radio structure */
	struct chan *vp		/* channel structure */
	)
{
	char	s1[VALMAX];
	int	temp;

	/*
	 * Update chan, freq, mode, duplex, split capabilities.
	 */
	temp = sigtab[rp->rate];
	sprintf(s1, "%.*lf MHz %s", temp, vp->freq, modetoa(vp->mode,
	    rp->modetab));
	setcap("chan", rp->cap, s1);
	sprintf(s1,"%.*lf-%.*lf MHz step %.0lf Hz", temp, rp->lstep,
	    temp, rp->ustep, rp->step);
	setcap("band", rp->cap, s1);
	if (flags & F_SPLIT) {
 		sprintf(s1, "%.*lf MHz", temp, rp->sub);
		setcap("split", rp->cap, s1);
	}
	if (flags & F_OFFSET && vp->duplex != 0) {
		sprintf(s1, "%+.0lf kHz", vp->duplex);
		setcap("duplex", rp->cap, s1);
	}
	if (!(pflags & P_DISP))
		return;

	if (pflags & P_DSPCH)
		printf("chan %d.%d ", vp->bank, vp->mchan);
	printf("%.*lf %s", temp, vp->freq, modetoa(vp->mode,
	    rp->modetab));
	if (pflags & F_SPLIT)
		printf(" %.*lf", temp, rp->sub);
	if (vp->duplex != 0)
		printf(" %+.0lf", vp->duplex);
	if (flags & F_SMPLX)
		printf(" S");
	printf("\n");
	pflags &= ~(P_DISP | P_DSPCH); 
}


/*
 * modetoa(ident, table) - returns capability name
 */
char *				/* capability name, "" (not found) */
modetoa(
	int	ident,		/* capability key */
	struct cmdtable *table	/* capability table */
	)
{
	int i;

	for (i = 0; table[i].name[0] != '\0'; i++) {
		if (table[i].ident == ident)
			return (table[i].name);
	}
	return ("");
}


/*
 * argsx(name, table) - returns capability key
 */
int				/* capability key, -1 (not found) */
argsx(
	char	*name,		/* capability name */
	struct cmdtable *table	/* capability table */
	)
{
	int i, temp;

	if (*name == '?') {
		for (i = 0; table[i].name[0] != '\0'; i++)
			printf("%10s %s\n", table[i].name,
			    table[i].descr);
		return (R_ERR);
	}
	for (i = 0; table[i].name[0] != '\0'; i++) {
		if (strcasecmp(name, table[i].name) == 0 ||
		    *table[i].name == '*')
			break;
	}
	if (table[i].ident == R_ERR)
		printf("*** %s\n", table[i].descr);
	return (table[i].ident);
}


/*
 * getcap(name, table) - return pointer to capability string
 */
char *				/* capability string, "" (not found") */
getcap(
	char	*name,		/* capability name */
	struct cmdtable *table	/* capability table */
	)
{
	int i;

	if (*name == '?') {
		for (i = 0; table[i].name[0] != '\0'; i++)
			printf("%10s %s\n", table[i].name,
			    table[i].descr);
		return ("");
	}
	for (i = 0; table[i].name[0] != '\0'; i++) {
		if (strcasecmp(name, table[i].name) == 0)
			break;
	}
	return (table[i].descr);
}


/*
 * setcap(name, table, string) - insert capability string
 */
void
setcap(
	char *name,		/* capability name */
	struct cmdtable *table,	/* capability table */
	char *string		/* capability string */
	)
{
	int i;

	for (i = 0; table[i].name[0] != '\0'; i++) {
		if (strcasecmp(name, table[i].name) == 0) {
			strcpy(table[i].descr, string);
			return;
		}
	}
	strcpy(table[i].name, name);
	strcpy(table[i].descr, string);
	table[i + 1].name[0]  = '\0';
	table[i + 1].ident = R_ERR;
	table[i + 1].descr[0] = '\0';
}


/*
 * scanarg(radio, arg, table, sub) - execute capability
 */
static int			/* opcode, -1 (error) */
scanarg(
	struct icom *rp,	/* radio structure */
	char	*name,		/* subcommand */
	struct cmdtable *table, /* subcommand table */
	int	cmd		/* command */
	)
{
	int	temp;		/* temp */

	temp = argsx(name, table);
	if (temp != R_ERR) {
		if (setcmd(rp->ident, cmd, temp) == R_ERR) {
			printf("*** radio %s can't do that\n",
			    rp->name);
			temp = R_ERR;
		}
	}
	return (temp);
}


/*
 * setswitch(radio, name, op) - set switches.
 */
static int			/* opcode, -1 (error) */
setswitch(
	struct icom *rp,	/* radio structure pointer */
	char	*name,		/* command name */
	struct cmdtable *cmmd,	/* command pointer */
	int	op		/* operation code */
	)
{
	int	temp, i;
	char	*s1;
	u_char	cmd[] = {op, FI, FI};
	u_char	rsp[CMDMAX];
	int	rval;

	if (argn < 2) {
		if (op & 0x8000)
			cmd[1] = cmmd->ident & 0x7f;
		rval = setcmda(rp->ident, cmd, rsp);
		if (rval == R_ERR) {
			printf("*** radio %s can't do that\n",
			    rp->name);
			return (rval);;
		}
		temp = rsp[1];
		if (op & 0x8000)
			temp |= 0x8000 | (rsp[2] << 8);
		for (i = 0; cmmd[i].ident != R_ERR; i++) {
			if (temp == cmmd[i].ident)
				break;
		}
		if (cmmd[i].ident == R_ERR)
			printf("*** radio %s can't do that\n",
			    rp->name);
		else
			printf("%s %s\n", name, cmmd[i].name);
		return (rval);
	}
	return (scanarg(rp, argv[1], cmmd, op));
}


/*
 * setctrl(radio, name, op) - twirl controls.
 *
 * +	current value plus 8
 * -	current value minus 8
 * num	specified value 
 *
 * Argument is clamped at low and high limits of valid range.
 */
static int			/* opcode, -1 (error) */
setctrl(
	struct icom *rp,	/* radio structure */
	char	*name,		/* command name */
	int	op		/* operation code */
	)
{
	struct metermsg msgctl = {V_WRCTL, 0, 0, 0, FI};
	u_char	ctlmsg[] = {V_RDCTL, 0, FI};
	u_char	rsp[NAMMAX];
	char	*s1, s2[VALMAX];
	int	temp, incr;

	if (argn < 2) {

		/*
		 * Known radios can't read anything but S meter and
		 * squelch. This is a half-baked effort until some radio
		 * is found that can do more than that.
		 */
		ctlmsg[1] = op;
		temp = setcmda(rp->ident, ctlmsg, (u_char *)&rsp);
		if (temp == R_ERR) {
			printf("*** radio %s can't do that\n",
		  	    rp->name);
		}
		return (temp);
	}
	sscanf(s1, "%d", &temp);
	incr = 0;
	if (*argv[1] == '+') {
		incr = 8;
	} else if (*argv[1] == '-') {
		incr = -8;
	} else if (sscanf(argv[1], "%d", &temp) != 1) {
		printf("*** invalid argument\n");
		return (R_ERR);
	}
	if (op == S_WRSH || op == S_WRAP)
		temp += 128;
	temp += incr;
	if (temp < 0)
		temp = 0;
	else if (temp > 255)
		temp = 255;
	sprintf(s1, "%04d", temp);
	msgctl.subcmd = op;
	msgctl.meter[0] = ((s1[0] & 0x0f) << 4) | (s1[1] &
	   0x0f);
	msgctl.meter[1] = ((s1[2] & 0x0f) << 4) | (s1[3] &
	   0x0f);
	incr = setcmda(rp->ident, (u_char *)&msgctl, rsp);
	if (incr == R_ERR) {
		printf("*** radio %s can't do that\n", rp->name);
		return (R_ERR);
	}
	if (op == S_WRSH || op == S_WRAP)
		temp -= 128;
	sprintf(s2, "%d", temp);
	return (temp);
}


/*
 * Getline(str) - process input line and extract tokens
 *
 * Blank lines and comments beginning with '#' are ignored and the
 * string converted to lower case. The resulting tokens are saved in the
 * *argv[] array. The number of tokens found is returned to the caller.
 */
static int			/* number of tokens */
getline(
	char	*str		/* pointer to input string */
	)
{
	char	*ptr;
	char	xbreak[] = " ,\t\n\0";
	char	sbreak[] = "\"\n\0";

	int i, j, temp;

	ptr = strchr(str, '\r');
	if (ptr != NULL)
		*ptr = '\0';
	ptr = strchr(str, '#');
	if (ptr != NULL)
		*ptr = '\0';
	ptr = str;
	for (i = 0; i < ARGMAX;) {
		temp = strspn(ptr, xbreak);
		ptr += temp;
		if (*ptr == '\0')
			break;

		if (*ptr == '"') {
			argv[i++] = ++ptr;
			temp = strcspn(ptr, sbreak);
		} else {
			argv[i++] = ptr;
			temp = strcspn(ptr, xbreak);
		}
		ptr += temp;
		if (*ptr == '\0')
			break;

		*ptr++ = '\0';
	}
	return (i);
}


/*
 * argchan(radio, chan, sptr) - decode channel argument
 *
 * NULL	current bank/channel
 * $	highest bank/channel
 * +	current bank/channel plus 1
 * -	current bank/channel minus 1
 * bank	specified bank
 * chan	specified channel
 *
 * Channel argument formats are in the form of a single '$', '+' or '-'
 * character, a single number (channel) or two numbers (bank.channel)
 * separated by '.'. Channel increments wrap around the valid channel
 * range and increment/decrement bank number, which wraps around its
 * range. Values specified are checked for format and range.
 */
static int			/* > 0 (ok), -1 (error) */
argchan(
	struct icom *rp,	/* radio structure */
	struct chan *cp,	/* channel structure */
	char	*sptr		/* ascii argument pointer */
	)
{
	int	bank, mchan;

	bank = cp->bank;
	mchan = cp->mchan;
	if (sptr == NULL) {
		return (R_OK);

	} else if (*sptr == '$') {
		bank = rp->maxbk;
		mchan = rp->maxch;
	} else if (*sptr == '+') {
		mchan++;
		if (mchan > rp->maxch) {
			mchan = rp->minch;
			bank++;
			if (bank > rp->maxbk)
				bank = 0;
		}
	} else if (*sptr == '-') {
		mchan--;
		if (mchan < rp->minch) {
			mchan = rp->maxch;
			bank--;
			if (bank < 0)
				bank = rp->maxbk;
		}
	} else if (sscanf(sptr, "%d.%d", &bank, &mchan) == 2) {
		if (bank < 0 || bank > rp->maxbk || mchan < rp->minch ||
		    mchan > rp->maxch) {
			printf("*** bad channel number\n");
			return (R_ERR);
		}
	} else if (sscanf(sptr, "%d", &mchan) == 1) {
		bank = cp->bank;
		if (mchan < rp->minch || mchan > rp->maxch) {
			printf("*** bad channel number\n");
			return (R_ERR);
		}
	} else {
		printf("*** bad channel format\n");
		return (R_ERR);
	}
	cp->bank = bank;
	cp->mchan = mchan;
	return (R_OK);
}


/*
 * loadsub() - called by the bank, restore and load commands
 */
int
loadsub(
	struct icom *rp,	/* radio structure */
	struct chan *cp,	/* channel pointer */
	int	index		/* args index */
	)
{
	double	dtemp;
	int	i, temp;

	i = index;
	temp= R_OK;
	for (i += 1; i < argn; i += 2) {
		temp = argsx(argv[i - 1], loadtab);
		switch (temp) {

		/*
		 * name
		 */
		case D_NAME:
			strncpy(cp->name, argv[i], 8);
			break;

		/*
		 * mode
		 */
		case D_MODE:
			temp = argsx(argv[i], rp->modetab);
			if (temp != R_ERR)
				cp->mode = temp;
			break;

		/*
		 * dial
		 */
		case D_DIAL:
			temp = argsx(argv[i], diala);
			if (temp == R_ERR)
				break;

			if (temp == 13) {
				if (sscanf(argv[i], "%lf", &dtemp) ==
				    1) {
					cp->step = temp;
					cp->pstep = dtemp;
				} else {
					printf("*** invalid format\n");
					temp = R_ERR;
				}
			} else {
				cp->step = temp;
			}
			break;

		/*
		 * atten
		 */
		case D_ATTEN:
			temp = argsx(argv[i], atten);
			if (temp != R_ERR)
				cp->atten = temp;
			break;

		/*
		 * duplex
		 */
		case D_DUPLEX:
			if (sscanf(argv[i], "%lf", &dtemp) != 1) {
				printf("*** invalid format\n");
				temp = R_ERR;
			} else {
				cp->duplex = dtemp;
			}
			break;

		/*
		 * unknown
		 */
		default:
			printf("*** radio %s can't do that\n",
			    rp->name);
		}
		if (temp == R_ERR)
			break;
	}
	return (temp);
}


/*
 * sw_keypad() - switch to keypad mode
 */
static int			/* 0 (ok), -1 (system error) */
sw_keypad()
{
	fd = open("/dev/tty", O_RDONLY);
	if (fd < 0) {
		printf("*** open error %d\n", errno);
		return (R_ERR);
	}
	if (tcgetattr(fd, &terma) < 0) {
		printf("*** tcgetattr error %d\n", errno);
		return (R_ERR);
	}
	tcgetattr(fd, &termb);
	termb.c_lflag &= ~(ICANON | ECHO);
	termb.c_cc[VMIN] = 1;
	termb.c_cc[VTIME] = 0;
	if (tcsetattr(fd, TCSADRAIN, &termb) < 0) {
		printf("*** tcsetattr error %d\n", errno);
		return (R_ERR);
	}
	return (R_OK);
}


/*
 * sw_keybd() - switch to keyboard mode
 */
static int			/* 0 (ok), -1 (system error) */
sw_keybd()
{
	if (tcsetattr(fd, TCSADRAIN, &terma) < 0) {
		printf("*** tcsetattr error %d\n", errno);
		return (R_ERR);
	}
	return (R_OK);
}

/* end program */
