/usr/local/CPAN/Xmpcr/Audio/Xmpcr/Serial.pm


# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Audio::Xmpcr::Serial
# Copyright Paul Bournival 2003
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

package Audio::Xmpcr::Serial;

$VERSION="1.02";

use strict;
use Device::SerialPort;
use bytes;


sub new {
  my($class,$port)=@_;
  my $self={};
	$self->{port}=$port;	
	$self->{sdev} = new Device::SerialPort ("$self->{port}")
           || die "Can't open USB Port! ($self->{port} $!\n";
  $self->{sdev}->baudrate(9600);
  $self->{sdev}->parity('none');
  $self->{sdev}->databits(8);
  $self->{sdev}->stopbits(1);

	$self->{_state}={
		power => 0,	     
		channel => 0,
		radioId => "",
		channels => [],
	};

	bless $self,$class;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# a general send/receive method.
# if called in a scalar context, returns STATUS: undef=success || errmsg=failed
# if called in an array context, returns (STATUS (above),PORTREADSTR)
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub _doop {
	my($self,$op,$cmd,$wcnt,$rcnt)=@_;
	my($readstr,$retval,$cnt)=("",undef,0);
	return("$op: Power isn't on!")
					if $cmd ne "5AA500050010101001EDED" and ! $self->{_state}{power};
	$self->{sdev}->write(pack("H*",$cmd));
  $self->{sdev}->read_const_time($wcnt) if defined $wcnt;
	if ($rcnt) {
		while($cnt<$rcnt) {
			($cnt,$readstr)=$self->{sdev}->read($rcnt);
			$readstr=join("",unpack("H*",$readstr));
		}
		$retval=substr($readstr,0,6) eq "5aa500" ? undef : "$op failed";
		$self->{_state}{radioId}=pack("H*",substr($readstr, 46, 16))
																	if $cmd eq "5AA500050010101001EDED";
	}
	wantarray ? ($retval,$readstr) : $retval;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# turn on/off power
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub power {
  my($self,$status)=@_;
	defined($status) || die "power called improperly\n";
	my $res=$status eq "on" ?
		$self->_doop("power on","5AA500050010101001EDED",100,40) :
		$self->_doop("power off","5AA500020100EDED",0,0);

	$self->{_state}{power}=($status eq "on" ? 1 : 0) if ! $res;

	# if powering up, load the channels from the device.
	if ($status eq "on" and ! $res) {
		sleep(8);
		$self->_buildChannelList;
		$self->setchannel(1);
	}
	$res;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# turn on/off mute
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub mute {
  my($self,$status)=@_;
	defined($status) || die "mute called improperly\n";
	$self->_doop("mute $status",$status eq "on" ?
					"5AA500021301EDED" : "5AA500021300EDED", 0,10);
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# change channel
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub setchannel {
  my($self,$chan)=@_;
	defined($chan) || die "setchannel called improperly\n";
	$self->{_state}{channel}=$chan;
	$self->_doop("setchannel $chan",
			"5AA500061002@{[sprintf('%02X',$chan)]}000001EDED",3000,12);
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# list 1 or all channels
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub list {
  my($self,$chan)=@_;
  my(@ret,$err,$res);
	my @ch=$chan ? ($chan) : @{ $self->{_state}{channels} };
	for my $ch (@ch) {
		($err,$res)=$self->_doop("channel $ch info",
			"5AA500042508@{[sprintf('%02X',$ch)]}00EDED",100,83);
		last if $err;
		push(@ret,{
			NUM => $ch,
			NAME => $self->_prune(pack("H*", substr($res, 20, 32))),
			CAT => $self->_prune(pack("H*", substr($res, 52, 32))),
			ARTIST => $self->_prune(pack("H*", substr($res, 88, 32))),
			SONG => $self->_prune(pack("H*", substr($res, 122, 32))),
		});
	}
	$chan ? $ret[0] : @ret;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# remove extra spaces and control characters
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub _prune {
	my($self,$str)=@_;
	$str =~ s/[^[:graph:] ]//gs;
	$str =~ s/^\s+//;
	$str =~ s/\s+$//;
	$str =~ s#/#-#g;   # embedded forward slashes - yuk!
	$str;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# builds a list of channels on the radio
# this should probably write the list to a file somewhere...
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# to be used at power up only!!!
sub _buildChannelList {
  my($self)=@_;
  my($ch,$lasterr,$res)=("00",undef);
# NOTE: PAULB GET RID OF ME LATER! - for debugging only!!!!!!!!!!!!
#	$self->{_state}{channels}=[1,4,5,6,7,8,9,10,11,12,13,14,15,20,21,22,23,24,25,26,27,28,29,30,31,32,40,41,42,43,44,45,46,47,48,50,51,52,60,61,62,63,64,65,66,67,70,71,72,73,74,75,76,80,81,82,83,90,91,92,93,94,100,101,102,103,104,110,112,113,115,116,121,122,123,124,125,127,129,130,131,132,134,140,141,142,143,144,150,151,152,161,162,163,164,165,166,168,169,170,171];
#return;
	$self->{_state}{channels}=[];

  # build a cache file if none is present
  if (! -f "$ENV{HOME}/.xmpcrd-cache") {
    open(F,">$ENV{HOME}/.xmpcrd-cache") or die "Can't write cache file: $!";
	  while(1) {
	 		($lasterr,$res)=$self->_doop("channel $ch info",
																		"5AA500042509${ch}00EDED",100,83);
			$ch=substr($res,14,2);
			last if $ch eq "00" or $lasterr;
      print F hex($ch) . "\n";
	  }
    close(F);
  }

  my($line);
  open(F,"$ENV{HOME}/.xmpcrd-cache") or die "Can't read cache file: $!";
  while($line=<F>) {
    chop $line;
		push(@{ $self->{_state}{channels} },$line);
  }
  close(F);
  
	$lasterr;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# obtain general radio status
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub status {
  my($self)=@_;
	
	my %cur;
	if ($self->{_state}{power}) {
		%cur=%{ $self->list($self->{_state}{channel}) };
		$cur{RADIOID}= $self->{_state}{radioId};
 		my($err,$ti)=$self->_doop("tech info","5AA5000143EDED",100,32);
		$cur{ANTENNA}=int(1+(substr($ti, 16,2) || 0)*33.3);
	}
	$cur{POWER}=$self->{_state}{power} ? "on" : "off";
	%cur;
}

# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# event support (i.e., song changing)
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub events {
	die "Whoops! events aren't supported on the serial interface!\n";
}
sub processEvents {
	die "Whoops! events aren't supported on the serial interface!\n";
}
sub eventFd {
	die "Whoops! events aren't supported on the serial interface!\n";
}
sub forcelock {
	die "Whoops! locks aren't supported on the serial interface!\n";
}



1;