/*
 * Program to control ICOM radios
 * 
 * Subroutine library
 */
#include "icom.h"

/*
 * Local function prototypes
 */
static void doublefreq(double, u_char *, int); /* double to frequency */
static double freqdouble(u_char *, int); /* frequency to double */


/*
 * Initialize
 */
void
init()
{
	initpkt();		/* initialize (device dependent) */
}


/*
 * select_radio(ident) - select/initalize radio structure
 */
struct icom *
select_radio(			/* returns icom pointer or NULL */
	int ident		/* radio ID */
	)
{
	struct icom *rp;
	struct readbandmsg rspband;double	dtemp, dtemp1;
	int	i, j, temp1, temp2;
	char	s1[NAMMAX];
	u_char	cmdband[] = {V_RBAND, FI};
	u_char	cmdbank[] = {V_SETW, S_RBNK, 0, FI};
	u_char	rsp[BMAX];

	/*
	 * Find prototype. Bad tables if not found.
	 */
	for (i = 0; name[i].ident != R_ERR; i++) {
		if (name[i].ident == ident)
			break;
	}
	if (name[i].ident == R_ERR)
		return (NULL);

	if (name[i].ident == 0) {
		retry = 1;
		for (i = 1; i < 0xe0; i++)  {
			if (setcmda(i, cmdband, (u_char *)&rspband) !=
			    R_ERR) {
				for (j = 0; name[j].ident != R_ERR;
				    j++) {
					if (name[j].ident == i)
						break;
				}
				if (name[j].ident != R_ERR)
					printf("radio %s (%02x) found\n",
					    name[j].name,
					    name[j].ident);
				else
					printf("unknown radio (%02x) found\n",
					    i);
			}
		}
		retry = RETRY;
		return (NULL);
	}

	/*
	 * If already initialized, return structure pointer. Otherwise,
	 * allocate and initialized structure.
	 */
	if (name[i].radio != NULL)
		return (name[i].radio);

	/*
	 * Read band limits. Every radio and transceiver supports this
	 * command, so we use it to see if the radio is lit.
	 */
	retry = 1;
	if (setcmda(ident, cmdband, (u_char *)&rspband) == R_ERR) {
		retry = RETRY;
		return (NULL);
	}
	retry = RETRY;
	rp = malloc(sizeof(struct icom));
	memset(rp, 0, sizeof(struct icom));

	/*
	 * Initialize bits and pieces.
	 */
	strcpy(rp->name, name[i].name);
	rp->ident = ident;
	rp->maxch = name[i].maxch;
	rp->maxbk = name[i].maxbk;
	rp->probe = name[i].probe;
	flags = name[i].flags;
	rp->modetab = name[i].modetab;
	rp->lband = freqdouble(rspband.lband, 5) / 1e6;
	rp->uband = freqdouble(rspband.uband, 5) / 1e6;
	if (rp->lband > rp->uband) {
		dtemp = rp->lband;
		rp->lband = rp->uband;
		rp->uband = dtemp;
	}
	rp->lstep = rp->lband;
	rp->ustep = rp->uband;

	/*
	 * If the V_SETW, S_RBNK (read bank) command works, the radio
	 * supports multiple memory banks and we don't need further
	 * probing.
	 */
	if (setcmda(ident, cmdbank, rsp) != R_ERR) {
		flags |= F_BANK;
	} else {

		/*
		 * If the V_SMEM 0 (read channel 0) fails, the channel
		 * range starts at one.
		 */
		if (setcmd(ident, V_SMEM, 0) == R_ERR)
			rp->minch = 1;

		/*
		 * If the V_VFOM (memory -> vfo) command works, we have
		 * to use it every time the channel is changed.
		 */
		if (setcmd(ident, V_VFOM, FI) != R_ERR)
			flags |= F_VFO;

		/*
		 * If the V_ROFFS (read offset) command works, the radio
		 * supports transmit duplex.
		 */
		if (setcmd(ident, V_ROFFS, FI) != R_ERR)
			flags |= F_OFFSET;

		/*
		 * Determine whether the frequency needs to be reset
		 * after a mode change. We first write the channel with
		 * mode USB, then read the frequency back and remember
		 * it. Then we write the channel with mode LSB, read
		 * back the frequency and see if it changed. If so, we
		 * have to write the frequency every time the mode is
		 * changed. What a drag.
		 */
		if (*modetoa(M_USB, rp->modetab) != '\0' ||
		    *modetoa(M_LSB, rp->modetab) != '\0') {
			loadmode(ident, M_USB);
			readfreq(ident, &dtemp1);
			loadmode(ident, M_LSB);
			readfreq(ident, &dtemp);
			if (dtemp != dtemp1)
				flags |= F_RELD;
		}
	}

	/*
	 * Determine the frequency resolution. This depends on the radio
	 * and TS button. We first write the frequency and mode near the
	 * lower band edge. The value is chosen with '1' in each
	 * significant digit ending in the units digit. Then, we read
	 * the frequency back and determine the resolution corresponding
	 * to the first significant digit which contains '1'.
	 */
	loadfreq(ident, rp->lband + 11111e-6);
	readfreq(ident, &dtemp);
	sprintf(s1, "%8.6lf", dtemp - rp->lband);
	rp->minstep = 6;
	if (s1[7] == '1')
		rp->minstep = 0;
	else if (s1[6] == '1')
		rp->minstep = 3;
	else if (s1[5] == '1')
		rp->minstep = 6;
	else if (s1[4] != '1')
		printf("*** tuning step error\n");
	rp->rate = rp->minstep;
	rp->step = logtab[rp->rate];
	name[i].radio = rp;
	return (rp);
}


/*
 * setchan(ident, bank, chan) - select bank and channel
 */
int
setchan(			/* returns > 0 (ok), -1 (error) */
	int	ident,		/* radio ID */
	int	bank,		/* bank number */
	int	mchan		/* channel number */
	)
{
	u_char	cmd[] = {V_SMEM, 0, 0, FI};
	u_char	rsp[BMAX];
	u_char	s1[BMAX];
	int	temp;


	/*
	 * Older transceivers and radios without memory banks.
	 */
	sprintf(s1, "%02d", mchan);
	if (!(flags & F_BANK)) {
		cmd[1] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
		cmd[2] = FI;
		return (setcmda(ident, cmd, s1));
	}

	/*
	 * Set bank.
	 */
	sprintf(s1, "%02d", bank);
	cmd[1] = 0xa0;
	cmd[2] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	if (setcmda(ident, cmd, s1) == R_ERR)
		return (R_ERR);

	/*
	 * Set channel.
	 */
	sprintf(s1, "%04d", mchan);
	cmd[1] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	cmd[2] = ((s1[2] & 0xf) << 4) | (s1[3] & 0xf);
	return (setcmda(ident, cmd, s1));
}


/*
 * readchan(ident, chan) - read channel frequency and mode
 */
int
readchan(			/* returns > 0 (ok), 1 (error) */
	int	ident,		/* radio ID */
	struct chan *cp		/* channel structure */
	)
{
	struct modemsg rspmode;
	struct offsetmsg rspoff;
	u_char	cmdmode[] = {V_RMODE, FI};
	u_char	cmdoff[] = {V_ROFFS, FI};
	int temp;

	cp->freq = 0;
	cp->mode = 0;
	cp->duplex = 0;	
	if (flags & F_VFO)
		setcmd(ident, V_VFOM, FI);
	if (readfreq(ident, &cp->freq) == R_ERR)
		return (R_ERR);

	temp = setcmda(ident, cmdmode, (u_char *)&rspmode);
	if (temp == R_ERR)
		return (R_ERR);

	cp->mode = rspmode.mode[0];
	if (temp > 3)
		cp->mode |= (rspmode.mode[1] << 8) | 0x8000;
	if (!(flags & F_OFFSET))
		return (R_OK);

	if (setcmda(ident, cmdoff, (u_char *)&rspoff) <
	    sizeof(rspoff))
		return (R_ERR);

	cp->duplex = freqdouble(rspoff.offset, 3) / 10.;
	return (R_OK);
}


/*
 * readfreq(index, freq) - read vfo frequency
 */
int
readfreq(			/* returns > 0 (ok), -1 (error) */
	int	ident,		/* radio ID */
	double	*freq		/* frequency (MHz) */
	)
{
	struct freqmsg rsp;
	u_char	cmd[] = {V_RFREQ, FI};

	if (setcmda(ident, cmd, (u_char *)&rsp) < sizeof(rsp))
		return (R_ERR);

	*freq = freqdouble(rsp.freq, 5) / 1e6;
	return (R_OK);
}


/*
 * loadfreq(ident, freq) - write vfo frequency
 */
int
loadfreq(			/* returns > 0 (ok), -1 (error) */
	int	ident,		/* radio ID */
	double	freq		/* frequency (MHz) */
	)
{
	struct freqmsg cmd = {V_SFREQ, 0, 0, 0, 0, FI, FI};
	u_char rsp[BMAX];
	int temp;

	if (flags & F_735)
		temp = 4;
	else
		temp = 5;
	doublefreq(freq * 1e6, cmd.freq, temp);
	return (setcmda(ident, (u_char *)&cmd, rsp));
}

/*
 * loadmode(ident, mode) - write vfo mode
 */
int
loadmode(			/* returns > 0 (ok), -1 (error) */
	int	ident,		/* radio ID */
	int	mode		/* mode */
	)
{
	u_char	cmd[] = {V_SMODE, 0, FI, FI};
	u_char	rsp[BMAX];

	cmd[1] = mode & 0xff;
	if ((mode & 0x8000) != 0)
		cmd[2] = (mode >> 8) & 0x7f;
	return (setcmda(ident, cmd, rsp));
}


/*
 * loadoffset(ident, offset) - write transmit offset
 */
int
loadoffset(			/* returns > 0 (ok), 1 (error) */
	int	ident,		/* radio ID */
	double	freq		/* transmit offset (kHz) */
	)
{
	struct offsetmsg cmd = {V_SOFFS, 0, 0, 0, FI};
	u_char	rsp[BMAX];

	doublefreq(freq * 10, cmd.offset, sizeof(cmd.offset));
	return (setcmda(ident, (u_char *)&cmd, rsp));
}

/*
 * sendcw(ident, string) - send CW message (775)
 */
int
sendcw(				/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	char *string		/* ASCII string */
	)
{
	u_char cmd[BMAX], *ptr;
	struct cmd1msg rsp;
	int temp, i;

	ptr = cmd; *ptr++ = V_ASCII;
	for (i = 0; string[i] != '\0'; i++)
		*ptr++ = string[i];
	*ptr++ = FI;
	return (setcmda(ident, (u_char *)&cmd, (u_char *)&rsp));
}


/*
 * loaddial(ident, step) - write dial tuning step (775, R8500)
 */
int
loaddial(			/* returns > 0 (ok), -1 (error) */
	int	ident,		/* radio ID */
	int	step,		/* dial tuning step code */
	double	pstep		/* dial programmed tuning step (kHz) */
	)
{
	struct dialmsg cmd = {V_DIAL, 0, 0, 0, FI};
	u_char	s1[BMAX];

	sprintf(s1, "%02d", step);
	cmd.step = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	doublefreq(pstep, cmd.pstep, 2);

	/*
	 * Silly radio can't do programmed step. We can do this by
	 * hijacking a memory channel, writing it out and reading it
	 * back in with changes. Later.
	 */
	cmd.pstep[0] = FI;
	return (setcmda(ident, (u_char *)&cmd, s1));
}


/*
 * loadbank(ident, bank, name) - write bank name (R8500)
 */
int
loadbank(			/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	int bank,		/* bank number */
	char *name		/* bank name */
	)
{
	struct bankmsg cmd = {V_SETW, S_WBNK};
	struct bankmsg rsp;
	u_char s1[BMAX];
	int	temp;

	sprintf(s1, "%02d", bank);
	cmd.bank = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	memset(cmd.name, ' ', sizeof(cmd.name));
	temp = strlen(name);
	if (temp > sizeof(cmd.name))
		temp = sizeof(cmd.name);
	memcpy(cmd.name, name, temp);
	cmd.fd = FI;
	return (setcmda(ident, (u_char *)&cmd, (u_char *)&rsp));
}

/*
 * readbank(ident, bank, name) - read bank name (R8500)
 */
int
readbank(			/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	int bank,		/* bank number */
	char *name		/* bank name */
	)
{
	struct bankmsg cmd = {V_SETW, S_RBNK, 0, FI};
	struct bankmsg rsp;
	u_char s1[BMAX];
	int	temp;

	sprintf(s1, "%02d", bank);
	cmd.bank = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	temp = setcmda(ident, (u_char *)&cmd, (u_char *)&rsp);	
	if (temp != R_ERR) {
		rsp.name[sizeof(rsp.name)] = '\0';
		sscanf(rsp.name, "%s", name);
	}
	return (temp);
}

/*
 * write_chan(ident, chan) - write channel data (R8500)
 */
int
write_chan(			/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	struct chan *cp		/* channel structure */
	)
{
	struct chanmsg cmd = {V_SETW, S_WCHN};
	u_char	s1[BMAX];
	int	temp;
	double	dtemp;

	sprintf(s1, "%02d", cp->bank);
	cmd.bank = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	sprintf(s1, "%04d", cp->mchan);
	cmd.mchan[0] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	cmd.mchan[1] = ((s1[2] & 0xf) << 4) | (s1[3] & 0xf);
	doublefreq(cp->freq * 1e6, cmd.freq, 5);
	cmd.mode[0] = cp->mode & 0xff;
	cmd.mode[1] = (cp->mode >> 8) & 0x7f;
	cmd.step = (cp->step % 10) | (cp->step / 10) << 4;
	doublefreq(cp->pstep * 10, cmd.pstep, 2);
	cmd.atten = cp->atten;
	cmd.scan = cp->scan;
	memset(cmd.name, ' ', sizeof(cmd.name));
	temp = strlen(cp->name);
	if (temp > strlen(cmd.name))
		temp = strlen(cmd.name);
	memcpy(cmd.name, cp->name, temp);
	cmd.fd = FI;
	return (setcmda(ident, (u_char *)&cmd, s1));
}


/*
 * read_chan(ident, chan) - read channel data (R8500)
 */
int
read_chan(			/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	struct chan *cp		/* channel structure */
	)
{
	struct chanmsg rsp;
	u_char	cmd[] = {V_SETW, S_RCHN, 0, 0, 0, FI};
	u_char	s1[BMAX];
	int	temp;

	sprintf(s1, "%02d", cp->bank);
	cmd[2] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	sprintf(s1, "%04d", cp->mchan);
	cmd[3] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	cmd[4] = ((s1[2] & 0xf) << 4) | (s1[3] & 0xf);
	memset(&rsp, 0, sizeof(rsp));
	temp = setcmda(ident, cmd, (u_char *)&rsp);
	if (temp < sizeof(rsp))
		return (R_ERR);

	cp->freq = freqdouble(rsp.freq, 5) / 1e6;
	cp->duplex = 0;
	cp->mode = (rsp.mode[1] << 8) | rsp.mode[0] | 0x8000;
	cp->step = ((rsp.step >> 4) & 0xf) * 10 + (rsp.step & 0xf);
	cp->pstep = freqdouble(rsp.pstep, 2) / 10.;
	cp->atten = rsp.atten;
	cp->scan = rsp.scan;
	strncpy(cp->name, rsp.name, sizeof(rsp.name));
	cp->name[sizeof(rsp.name)] = '\0';
	return (temp);
}


/*
 * clear_chan(ident, chan) - clear channel data (R8500)
 */
int
clear_chan(			/* returns > 0 (ok), -1 (error) */
	int ident,		/* radio ID */
	struct chan *cp		/* channel structure */
	)
{
	struct chanmsg cmd = {V_SETW, S_WCHN};
	u_char	s1[BMAX];
	int	temp;
	double	dtemp;

	sprintf(s1, "%02d", cp->bank);
	cmd.bank = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	sprintf(s1, "%04d", cp->mchan);
	cmd.mchan[0] = ((s1[0] & 0xf) << 4) | (s1[1] & 0xf);
	cmd.mchan[1] = ((s1[2] & 0xf) << 4) | (s1[3] & 0xf);
	cmd.freq[0] = 0xff;
	cmd.freq[1] = FI;
	return (setcmda(ident, (u_char *)&cmd, s1));
}


/*
 * These low level routines send a command to the radio and receive the
 * reply. They return the length of the reply or -1 if error.
 *
 * setcmd(ident, cmd, subcmd) - send CI-V command.
 */
int
setcmd(				/* returns length or -1 (error) */
	int	ident,		/* radio ID */
	int	cmmd,		/* command */
	int	subcmd		/* subcommand */
	)
{
	u_char	cmd[] = {FI, FI, FI, FI};
	u_char	rsp[BMAX];

	cmd[0] = cmmd;
	cmd[1] = subcmd;
	if (subcmd & 0x8000)
		cmd[2] = (subcmd >> 8) & 0x7f;
	return (setcmda(ident, cmd, rsp));
}

/*
 * setcmda(ident, cmd, rsp) - send CI-V command and receive response.
 */
int
setcmda(			/* returns length or -1 (error) */
	int	ident,		/* radio ID */
	u_char	*cmd,		/* command */
	u_char	*rsp		/* response */
	)
{
	int	temp;

	temp = sndpkt(ident, cmd, rsp);
	if (temp < 2 || rsp[0] == NAK)
		return (R_ERR);

	return (temp);
}


/*
 * BCD conversion routines
 *
 * These routines convert between internal (floating point) format and
 * radio (BCD) format. BCD data are two digits per octet in big normal
 * or reversed order, depending on data type. These routines convert
 * between internal and reversed data types.
 *
 * 	data type	BCD	units	order
 * 	====================================================
 * 	bank		2	1	normal		
 * 	channel		2/4	1	normal (4 for R8500)
 * 	frequency	8/10	1/10 Hz	reversed (8 for 735)
 * 	transmit offset	6	100 Hz	reversed
 * 	dial tune step	4	100 Hz	reversed
 */
/*
 * doublefreq(freq, y, len) - double to ICOM frequency with padding
 */
void
doublefreq(
	double	freq,		/* frequency */
	u_char	*x,		/* radio frequency */
	int	len		/* length (octets) */
	)
{
	int	i;
	char	s1[11];
	char	*y;

	sprintf(s1, " %10.0lf", freq);
	y = s1 + 10;
	i = 0;
	while (*y != ' ') {
		x[i] = *y-- & 0xf;
		x[i] = x[i] | ((*y-- & 0xf) << 4);
		i++;
	}
	for (; i < len; i++)
		x[i] = 0;
	x[i] = FI;
}


/*
 * freqdouble(x, len) - ICOM frequency to double
 */
double
freqdouble(			/* returns frequency */
	u_char	*x,		/* radio frequency */
	int	len		/* length (octets) */
	)
{
	int	i;
	char	s[11];
	char	*y;
	double	dtemp;

	y = s + 2 * len;
	*y-- = '\0';
	for (i = 0; i < len && x[i] != FI; i++) {
		*y-- = (char)((x[i] & 0xf) + '0');
		*y-- = (char)(((x[i] >> 4) & 0xf) + '0');
	}
	sscanf(s, "%lf", &dtemp);
	return (dtemp);
}

/* end program */
