Audio-Radio-Sirius
view release on metacpan or search on metacpan
lib/Audio/Radio/Sirius.pm view on Meta::CPAN
package Audio::Radio::Sirius;
use 5.008;
use warnings;
use strict;
use Carp;
use Time::HiRes qw(sleep); # need to sleep for milliseconds in some receive loops
=head1 NAME
Audio::Radio::Sirius - Control a Sirius satellite radio tuner
=head1 VERSION
Version 0.03
=cut
our $VERSION = '0.03';
our $AUTOLOAD;
our %DEFAULTS = (
power => 0,
connected => 0,
channel => 0,
gain => 0,
debug => 0,
mute => 0,
verbosity => 0,
_sequence => 0,
_serial => undef,
_lastack => -1,
_lastreq => -1,
_callbacks => {
'channel_update' => undef,
},
_buffer => '',
);
our %SETTABLE = (
debug => 1,
);
our %COMMANDS = (
poweroff => '000800',
reset => '0009',
poweron => '000803',
volume => '0002',
mute => '0003',
channel => '000a', channel_suffix => '000b',
request_signal => '4018',
request_unkn1 => '4017',
request_sid => '4011',
verbosity => '000d000000'
);
our %UPDATES = (
'2008' => {
name => 'power',
handler => undef
},
'2002' => {
name => 'volume',
handler => undef
},
'2003' => {
name => 'mute',
handler => undef
},
'200a' => {
name => 'channel',
handler => \&_channel_update,
removefirst => 4
},
'200d' => {
name => 'verbosity',
handler => undef
},
'6011' => {
name => 'reply_sid',
handler => undef
},
'6017' => {
name => 'reply_unkn1',
handler => undef
},
'6018' => {
name => 'reply_signal',
handler => undef
},
'8001' => {
name => 'channel_info',
handler => \&_channel_item_update,
removefirst => 2
lib/Audio/Radio/Sirius.pm view on Meta::CPAN
$tuner->channel(100); # Tune directly to channel 100
=cut
sub channel {
my $self = shift;
if (!ref($self)) { croak "$self isn't an object"; }
my ($chanreq, $offsetreq) = @_;
my $offset = 0;
if (!defined($chanreq)) { return $self->{channel}; } # accessor
if (defined($offsetreq) && ($offsetreq =~ /0|1|-1/) ) { $offset = $offsetreq; }
### TODO: Channel validation.
# channel command: $COMMAND, channel, [0,1,-1], $COMMAND suffix
my $chanhex = $self->_num_to_unsigned_hex($chanreq);
my $offsethex = $self->_num_to_signed_hex($offset);
my $cmd = $COMMANDS{channel} . $chanhex . $offsethex . $COMMANDS{channel_suffix};
return $self->_send_command($cmd);
}
=head2 monitor (cycles)
Monitor is called to watch for updates from the tuner. The Sirius tuner is pretty chatty and sends relevant data, such as Artist/Title updates,
PIDs, signal strength, and other information. Calling monitor initiates reads of this data.
Reads happen automatically when commands are executed (for example changing the channel or muting the tuner). Still, monitor generally needs
to be called as often as possible to gather the latest data from the Tuner.
A monitor cycle will take a minimum of one second. If data is received, this timer resets. In other words, monitor may take longer than you anticipate.
The amount of time monitor takes will depend on the C<verbosity> of the tuner.
If no number of cycles is specified, monitor runs one cycle.
B<Note:> As of version 0.02, the cycle parameter is no longer a true count of the number of cycles. The number specified is multiplied by 20.
Each cycle now sleeps 50 msec so the result is roughly the same, although this may increase the drift of cycles vs. seconds even more.
$tuner->monitor(5); # spin 5 times
=cut
sub monitor {
my $self = shift;
if (!ref($self)) { croak "$self isn't an object"; }
my ($spins) = @_;
if (!defined($spins)) { $spins = 1; }
$spins = $spins * 20;
foreach (1..$spins) {
$self->_receive_if_waiting;
sleep (.05); # chill .05 second
}
}
=head2 set_callback (callback type, function reference)
When the tuner sends an update, such as new artist/title information on the current channel, it may be helpful to execute some code which handles this
event. To accomidate this, you may define function callbacks activated when each event occurs. Note that some of the parameters below are marked with
an asterisk. This indicates that they may be undefined when your function is called. You should account for this in your callback function.
=head3 channel_update (channel, *pid, *artist, *title, *composer)
$tuner->set_callback ('channel_update', \&channel);
sub channel {
my ($channel, $pid, $artist, $title, $composer) = @_;
print "Channel $channel is now playing $title.\n";
}
=head3 signal_update
Not yet implemented.
=head3 time_update
Not yet implemented.
=head3 status_update
Not yet implemented.
=cut
sub set_callback {
my $self = shift;
if (!ref($self) eq 'CODE') { croak "$self isn't an object"; }
my ($reqtype, $funcref) = @_;
if (!ref $funcref) { croak "$funcref must be a reference to a function"; }
if (!exists($DEFAULTS{'_callbacks'}{$reqtype}) ) { croak "$reqtype is not a supported update type"; }
# validated enough for 'ya??
$self->{'_callbacks'}{$reqtype} = $funcref;
}
=head2 verbosity (level)
Not to be confused with C<debug>, verbosity changes the updates the tuner sends. By default, the tuner only sends updates for artist/title/PID
on the current channel. The Generation 2.5 tuners can send artist/title on all channels, the current time, signal strength, and PID information on all
channels.
Internally the tuner treats verbosity as a bitmap allowing you to control each type of update you are interested in. For now, this module treats it
as a boolean. 0 (default) requests that no updates be sent. 1 requests that all of the following updates are sent:
=over
=item *
Artist/Title information for every channel
=item *
PID information for every channel
=item *
Signal strength
=item *
Current time
=back
$tuner->verbosity(1); #request all of these updates
$current_verbosity=$tuner->verbosity;
=cut
sub verbosity {
my $self = shift;
if (!ref($self)) { croak "$self isn't an object"; }
my ($verbreq) = @_;
if (!defined($verbreq)) { return $self->{verbosity}; } # accessor
if ($verbreq == 0) {
# 0 = no verbosity, 1b = full verbosity
my $cmd = $COMMANDS{verbosity}.'0000';
$self->_send_command($cmd);
$self->{verbosity} = $verbreq;
}
if ($verbreq == 1) {
# 0 = no verbosity, 1b = full verbosity
# my $cmd = $COMMANDS{verbosity}.'1b00';
my $cmd = $COMMANDS{verbosity}.'1f00';
$self->_send_command($cmd);
$self->{verbosity} = $verbreq;
}
}
sub _read {
# _read works like read from $serial. except better.
# returns ($count, $data)
lib/Audio/Radio/Sirius.pm view on Meta::CPAN
next READ;
}
$self->{_lastreq} = $seq;
# handle the update, then send an ack
my $updatetype = unpack ('H4', $data);
if (defined($UPDATES{$updatetype})) {
# OK... I recognize this update.
my $updatename = $UPDATES{$updatetype}{name};
my $updatehandler = $UPDATES{$updatetype}{handler};
if ($debug) {
print "Received an update: $updatename\n";
}
if (defined($updatehandler)) {
# some responses are identical but the identical part starts
# somewhere after the command... chop it off to the identical bits
my $removefirst = $UPDATES{$updatetype}{removefirst};
$data=substr($data,$removefirst);
$self->$updatehandler($data);
}
} else {
# unknown command.
if ($debug) {
my $datahex = $self->_pformat($data);
print "Unknown update: $updatetype data: $datahex\n";
}
}
}
}
}
sub _channel_update {
my $self = shift;
my ($data) = @_;
my ($channel, $categorynum, $shortchan, $longchan, $shortcat, $longcat);
($channel, $categorynum, $shortchan, $longchan, $shortcat, $longcat, $data) = unpack ('C1xC1xxC1/aC/aC/aC/aa*', $data);
$self->{channel} = $channel;
$self->{categories}{$categorynum}{longname} = $longcat;
$self->{categories}{$categorynum}{shortname} = $shortcat;
$self->{channels}{$channel}{longname} = $longchan;
$self->{channels}{$channel}{shortname} = $shortchan;
$self->{channels}{$channel}{category} = $self->{categories}{$categorynum};
$self->{categories}{$categorynum}{channels}{$channel} = $self->{channels}{$channel};
# process left over items
$self->_channel_items($channel, $data);
# call handler
$self->_call_channel_handler($channel);
}
sub _call_channel_handler {
my $self = shift;
my ($channel) = @_;
# update handler: ($channel, $pid, $artist, $title, $composer)
my $handler = $self->{'_callbacks'}{'channel_update'};
if (ref($handler)) {
&$handler (
$channel,
$self->{'channels'}{$channel}{'pid'},
$self->{'channels'}{$channel}{'artist'},
$self->{'channels'}{$channel}{'title'},
$self->{'channels'}{$channel}{'composer'}
);
}
}
sub _signal_update {
my $self = shift;
my ($data) = @_;
my $debug = $self->{debug};
my ($overall, $sat, $terrestrial) = unpack ('CCC', $data);
foreach my $signal ($overall, $sat, $terrestrial) {
$signal = $signal * .33;
}
if ($debug>1) { print "Signal overall: $overall Sat: $sat Terrestrial: $terrestrial\n"; }
$self->{signal}{overall} = $overall;
$self->{signal}{sat} = $sat;
$self->{signal}{terrestrial} = $terrestrial;
}
sub _time_update {
my $self = shift;
my ($data) = @_;
my $debug = $self->{debug};
my ($year, $month, $day, $hour, $minute, $second) = unpack ('nCCCCC', $data);
if ($debug>1) { print "Time update: $year-$month-$day $hour:$minute:$second\n"; }
# send to user functions as reverse list to conform with perl custom
}
sub _channel_item_update {
my $self = shift;
my ($data) = @_;
my $channel;
($channel, $data) = unpack ('C1a*', $data);
$self->_channel_items($channel, $data);
# call handler
$self->_call_channel_handler($channel);
}
sub _channel_items {
# multiple updates contain this stuff. call this with $chan and $data.
my $self = shift;
my ($channel, $data) = @_;
my $debug=$self->{debug};
my $numitems;
($numitems, $data) = unpack ('C1a*', $data);
if ($numitems>0) {
( run in 1.662 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )