#!/usr/bin/perl
# cfgsa828, Boone, 06/29/14
# Configure a NiceRF SA828 radio
#
# Modifications:
# 06/29/14 Boone      Initial coding
# End Modifications

# Libraries

	use Getopt::Long;

# Initialize

	$usage = <<"EOF";

usage: $0 --sport serialport --pfile programfile

    serialport is the device where the SA828 is connected
    programfile is the file containing frequencies and squelche settings

Additional documentation is at the bottom of the script.

EOF

	$bsig = 'SA828_VER1.0';

	# Default config values, overridden by program file

	%pconf =
	(
		#	ch => [ tx,	rx ]
		0 => [ 150.0000, 150.0000 ],
		1 => [ 150.0000, 150.0000 ],
		2 => [ 150.0000, 150.0000 ],
		3 => [ 150.0000, 150.0000 ],
		4 => [ 150.0000, 150.0000 ],
		5 => [ 150.0000, 150.0000 ],
		6 => [ 150.0000, 150.0000 ],
		7 => [ 150.0000, 150.0000 ],
		8 => [ 150.0000, 150.0000 ],
		9 => [ 150.0000, 150.0000 ],
		10 => [ 150.0000, 150.0000 ],
		11 => [ 150.0000, 150.0000 ],
		12 => [ 150.0000, 150.0000 ],
		13 => [ 150.0000, 150.0000 ],
		14 => [ 150.0000, 150.0000 ],
		15 => [ 150.0000, 150.0000 ],
		pl => [ 000, 000 ],
		sq => 0,
	);

# Command line

	$help = 0;
	$sport = "";
	$pfile = "";
	$verbose = 1;
	%glopts =
	(
		"h|help" => \$help,
		"sport=s" => \$sport,
		"pfile=s" => \$pfile,
		"v|verbose" => \$verbose,
	);
	GetOptions(%glopts) ||
		exit(22);

	if ($help)
	{
		print STDERR $usage;
		exit(0);
	}

	if (! $sport) { $err = 1; push(@errs, "!!! serial port not specified"); }
	if (! $pfile) { $err = 1; push(@errs, "!!! program file not specified"); }

	if ($err)
	{
		print "\n", join("\n", @errs), "\n";
		print STDERR $usage;
		exit(22);
	}

# Parse program file

	$cfgstr = parse($pfile, \%pconf);
	die "invalid program file" unless ($cfgstr);

# Set up and open serial port

	$src = system("stty 9600 -cstopb cs8 -parenb cooked -hupcl -icrnl -imaxbel -inlcr -ixon -ocrnl -onlcr -onlret -opost -echo -echoe -echoke -isig -iexten -echok -echoctl -cooked -icanon -istrip < $sport");
	$rc = (($src >> 8) & 0xff);
	die "serial port setup failed with $rc" if ($rc);

	open(COM, '+<', $sport) ||
		die 'unable to open port: ' . $!;

# Program

	# Board version

	$ver = cmd("AAFAA\r\n");
	if ($ver !~ /$bsig/)
	{
		chomp($ver);
		die "I don't recognize the version stamp of this radio. ($ver)";
	}

	# Read existing config (use verbose to see this)

	cmd("AAFA1\r\n");

	# Set new config

	cmd("$cfgstr\r\n");

	# Make sure it took (use verbose to see this)

	cmd("AAFA1\r\n");

# Done

	close(COM);

	exit(0);

###############################################################################

	sub cmd
	{
		my $cmd = shift;

		print '>> ', $cmd if ($verbose);
		print COM $cmd;
		$_ = <COM>;
		print "<< ", $_ if ($verbose);

		print "=============\n" if ($verbose);

		return($_);
	}

###############################################################################

	sub parse
	{
		my ($pfile, $pcref) = @_;
		my $line;
		my $cfgstr;
		my $i;

		open(PF, '<', $pfile) ||
			die "unable to open $pfile: $!";

		$line = 0;
		while (<PF>)
		{
			$line++;
			s/#.*$//;
			s/^\s+|\s+$//g;
			s/\s+/ /g;
			next if (/^$/);
			y/A-Z/a-z/;
			($verb, @args) = split(/ /);
			if		($verb eq "channel")
			{
				if (@args != 3)
				{
					die "not exactly 3 parameters in line $line";
				}
				($cno, $txf, $rxf) = @args;
				die "bad channel number in line $line"
					if (($cno < 0) || ($cno > 15));
				$pcref -> {$cno} = [ $txf, $rxf ];
			}
			elsif	($verb eq "tone")
			{
				if (@args != 2)
				{
					die "not exactly 2 parameters in line $line";
				}
				($txpl, $rxpl) = @args;
				if (($txpl < 0) || ($txpl > 204) ||
					($rxpl < 0) || ($rxpl > 204))
				{
					die "bad tone value in line $line";
				}
				$pcref -> {pl} = [ $txpl, $rxpl ];
			}
			elsif	($verb eq "squelch")
			{
				if (@args != 1)
				{
					die "not exactly 1 parameter in line $line";
				}
				$squelch = $args[0];
				die "bad squelch value in line $line"
					if (($squelch < 0) || ($squelch > 8));
				$pcref -> {sq} = $squelch;
			}
			else
			{
				die "unrecognized verb $verb in line $line";
			}
		}

		$cfgstr = 'AAFA3';
		for ($i = 0; $i < 16; $i++)
		{
			$cfgstr .= sprintf("%08.4f,", $pcref -> {$i}[0]);
			$cfgstr .= sprintf("%08.4f,", $pcref -> {$i}[1]);
		}
		$cfgstr .= sprintf("%03d,", $pcref -> {pl}[0]);
		$cfgstr .= sprintf("%03d,", $pcref -> {pl}[1]);
		$cfgstr .= sprintf("%1d", $pcref -> {sq});

		return($cfgstr);
	}

###############################################################################

__END__
Program file

Comments starting with # are ignored
Leading zeros before decimal and trailing zeros after are optional
Frequencies are not checked by this program

Statements are:

	channel 0 150.0000 150.0000
	tone 000 000
	squelch 0

	channel numbers are 0-15
	valid frequencies depend on the model of radio, tx first, rx second
	tone/code squelch values are 000-204, tx first, rx second
		see the SA828 programming manual for index values
		the CTCSS frequency for a given index is not defined in the manual
		the DCSS code for a given index is given in the table in the manual
	squelch values are 0-8, 0 is open squelch

Example:

# Mid-Michigan fox transmit channels
channel  0 146.565 146.565	# US standard ARDF
channel  1 146.400 146.400	# Usual 2m simplex...
channel  2 146.430 146.430
channel  3 146.460 146.460
channel  4 146.490 146.490
# We skip the call channel 146.520 so we don't annoy everyone by mistake
channel  5 146.550 146.550
channel  6 146.580 146.580
channel  7 147.420 147.420
channel  8 147.450 147.450
channel  9 147.480 147.480
channel 10 147.510 147.510
channel 11 147.540 147.540
channel 12 147.570 147.570
channel 13 147.570 147.570	# Unused, default is 150.0, avoid accidentaly
channel 14 147.570 147.570	# out-of-band xmit by filling these in...
channel 15 147.570 147.570
tone 0 0					# No tone sent or received
squelch 0					# Avoid issues by leaving fox squelch open
