line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Audio::Radio::Sirius;
|
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
1545735
|
use 5.008;
|
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
147
|
|
4
|
|
|
|
|
|
|
|
5
|
3
|
|
|
3
|
|
96
|
use warnings;
|
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
97
|
|
6
|
3
|
|
|
3
|
|
18
|
use strict;
|
|
3
|
|
|
|
|
19
|
|
|
3
|
|
|
|
|
116
|
|
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
17
|
use Carp;
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
255
|
|
9
|
3
|
|
|
3
|
|
8473
|
use Time::HiRes qw(sleep); # need to sleep for milliseconds in some receive loops
|
|
3
|
|
|
|
|
7507
|
|
|
3
|
|
|
|
|
18
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Audio::Radio::Sirius - Control a Sirius satellite radio tuner
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 VERSION
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Version 0.03
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=cut
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
our $VERSION = '0.03';
|
22
|
|
|
|
|
|
|
our $AUTOLOAD;
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
our %DEFAULTS = (
|
25
|
|
|
|
|
|
|
power => 0,
|
26
|
|
|
|
|
|
|
connected => 0,
|
27
|
|
|
|
|
|
|
channel => 0,
|
28
|
|
|
|
|
|
|
gain => 0,
|
29
|
|
|
|
|
|
|
debug => 0,
|
30
|
|
|
|
|
|
|
mute => 0,
|
31
|
|
|
|
|
|
|
verbosity => 0,
|
32
|
|
|
|
|
|
|
_sequence => 0,
|
33
|
|
|
|
|
|
|
_serial => undef,
|
34
|
|
|
|
|
|
|
_lastack => -1,
|
35
|
|
|
|
|
|
|
_lastreq => -1,
|
36
|
|
|
|
|
|
|
_callbacks => {
|
37
|
|
|
|
|
|
|
'channel_update' => undef,
|
38
|
|
|
|
|
|
|
},
|
39
|
|
|
|
|
|
|
_buffer => '',
|
40
|
|
|
|
|
|
|
);
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
our %SETTABLE = (
|
43
|
|
|
|
|
|
|
debug => 1,
|
44
|
|
|
|
|
|
|
);
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
our %COMMANDS = (
|
47
|
|
|
|
|
|
|
poweroff => '000800',
|
48
|
|
|
|
|
|
|
reset => '0009',
|
49
|
|
|
|
|
|
|
poweron => '000803',
|
50
|
|
|
|
|
|
|
volume => '0002',
|
51
|
|
|
|
|
|
|
mute => '0003',
|
52
|
|
|
|
|
|
|
channel => '000a', channel_suffix => '000b',
|
53
|
|
|
|
|
|
|
request_signal => '4018',
|
54
|
|
|
|
|
|
|
request_unkn1 => '4017',
|
55
|
|
|
|
|
|
|
request_sid => '4011',
|
56
|
|
|
|
|
|
|
verbosity => '000d000000'
|
57
|
|
|
|
|
|
|
);
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
our %UPDATES = (
|
60
|
|
|
|
|
|
|
'2008' => {
|
61
|
|
|
|
|
|
|
name => 'power',
|
62
|
|
|
|
|
|
|
handler => undef
|
63
|
|
|
|
|
|
|
},
|
64
|
|
|
|
|
|
|
'2002' => {
|
65
|
|
|
|
|
|
|
name => 'volume',
|
66
|
|
|
|
|
|
|
handler => undef
|
67
|
|
|
|
|
|
|
},
|
68
|
|
|
|
|
|
|
'2003' => {
|
69
|
|
|
|
|
|
|
name => 'mute',
|
70
|
|
|
|
|
|
|
handler => undef
|
71
|
|
|
|
|
|
|
},
|
72
|
|
|
|
|
|
|
'200a' => {
|
73
|
|
|
|
|
|
|
name => 'channel',
|
74
|
|
|
|
|
|
|
handler => \&_channel_update,
|
75
|
|
|
|
|
|
|
removefirst => 4
|
76
|
|
|
|
|
|
|
},
|
77
|
|
|
|
|
|
|
'200d' => {
|
78
|
|
|
|
|
|
|
name => 'verbosity',
|
79
|
|
|
|
|
|
|
handler => undef
|
80
|
|
|
|
|
|
|
},
|
81
|
|
|
|
|
|
|
'6011' => {
|
82
|
|
|
|
|
|
|
name => 'reply_sid',
|
83
|
|
|
|
|
|
|
handler => undef
|
84
|
|
|
|
|
|
|
},
|
85
|
|
|
|
|
|
|
'6017' => {
|
86
|
|
|
|
|
|
|
name => 'reply_unkn1',
|
87
|
|
|
|
|
|
|
handler => undef
|
88
|
|
|
|
|
|
|
},
|
89
|
|
|
|
|
|
|
'6018' => {
|
90
|
|
|
|
|
|
|
name => 'reply_signal',
|
91
|
|
|
|
|
|
|
handler => undef
|
92
|
|
|
|
|
|
|
},
|
93
|
|
|
|
|
|
|
'8001' => {
|
94
|
|
|
|
|
|
|
name => 'channel_info',
|
95
|
|
|
|
|
|
|
handler => \&_channel_item_update,
|
96
|
|
|
|
|
|
|
removefirst => 2
|
97
|
|
|
|
|
|
|
},
|
98
|
|
|
|
|
|
|
'8002' => {
|
99
|
|
|
|
|
|
|
# The way verbosity works now, we won't see PID info. Verbosity must not include channel updates or it only sends those
|
100
|
|
|
|
|
|
|
# (mostly because PIDs are part of channel updates).
|
101
|
|
|
|
|
|
|
name => 'pid_info',
|
102
|
|
|
|
|
|
|
handler => undef,
|
103
|
|
|
|
|
|
|
},
|
104
|
|
|
|
|
|
|
'8003' => {
|
105
|
|
|
|
|
|
|
name => 'time_info',
|
106
|
|
|
|
|
|
|
handler => \&_time_update,
|
107
|
|
|
|
|
|
|
removefirst => 2
|
108
|
|
|
|
|
|
|
},
|
109
|
|
|
|
|
|
|
'8004' => {
|
110
|
|
|
|
|
|
|
# 1 1 0 - acquiring signal
|
111
|
|
|
|
|
|
|
# 1 0 0 - all's well
|
112
|
|
|
|
|
|
|
# 2 1 0 - antenna disconnected
|
113
|
|
|
|
|
|
|
# 2 0 1 - antenna back
|
114
|
|
|
|
|
|
|
name => 'tuner_info',
|
115
|
|
|
|
|
|
|
handler => undef,
|
116
|
|
|
|
|
|
|
removefirst => 2
|
117
|
|
|
|
|
|
|
},
|
118
|
|
|
|
|
|
|
'8005' => {
|
119
|
|
|
|
|
|
|
name => 'signal_info',
|
120
|
|
|
|
|
|
|
handler => \&_signal_update,
|
121
|
|
|
|
|
|
|
removefirst => 2
|
122
|
|
|
|
|
|
|
},
|
123
|
|
|
|
|
|
|
);
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
our %TYPES = (
|
126
|
|
|
|
|
|
|
command => '00',
|
127
|
|
|
|
|
|
|
ack => '80',
|
128
|
|
|
|
|
|
|
e_busy => '82',
|
129
|
|
|
|
|
|
|
e_checksum => '83'
|
130
|
|
|
|
|
|
|
);
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
our %ITEM_TYPES = (
|
133
|
|
|
|
|
|
|
0x1 => 'artist',
|
134
|
|
|
|
|
|
|
0x2 => 'title',
|
135
|
|
|
|
|
|
|
0x6 => 'composer',
|
136
|
|
|
|
|
|
|
0x86 => 'pid'
|
137
|
|
|
|
|
|
|
);
|
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
our $START = 'a40300'; # Const that prefaces each command
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
Sirius satellite radio (L) is a US based satellite radio serice. While none of the tuners they make have serial or USB connectors,
|
145
|
|
|
|
|
|
|
it has been found that generation 2.5 tuners (Sportster, Starmate, * Replay, Sirius Connect, and others) have a common tuner module. Furthermore
|
146
|
|
|
|
|
|
|
this tuner module generally has a serial interface. Presently only one commercial site is offering a modification for adding a serial port to a
|
147
|
|
|
|
|
|
|
Sirius tuner: L. Google should reveal schematics and parts needed for adding ports to other tuners.
|
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Once your tuner is connected to your system and accessible via a serial port like device, you can use this module to access it:
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
use Audio::Radio::Sirius;
|
152
|
|
|
|
|
|
|
use Win32::SerialPort; # or Device::SerialPort on Linux
|
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
my $serial = new Win32::SerialPort('com1');
|
155
|
|
|
|
|
|
|
my $tuner = new Audio::Radio::Sirius;
|
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
$tuner->connect($serial);
|
158
|
|
|
|
|
|
|
$tuner->power(1);
|
159
|
|
|
|
|
|
|
$tuner->channel(184); # tune in the preview channel
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 CONSTRUCTOR
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 new
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Call new to create an instance of the Sirius radio object. Once the object is created, you will probably want to L to it.
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=cut
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub new {
|
170
|
2
|
|
|
2
|
1
|
20
|
my $class = shift;
|
171
|
2
|
|
|
|
|
18
|
my $self = { %DEFAULTS };
|
172
|
2
|
|
|
|
|
34
|
bless $self, $class;
|
173
|
2
|
|
|
|
|
8
|
return $self;
|
174
|
|
|
|
|
|
|
}
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub AUTOLOAD {
|
177
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
178
|
0
|
0
|
|
|
|
0
|
my $type = ref($self) or croak "$self is not an object";
|
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
0
|
my $name = $AUTOLOAD;
|
181
|
0
|
|
|
|
|
0
|
$name =~ s/.*://; # Remove Audio::Radio::Sirius:: bit
|
182
|
|
|
|
|
|
|
|
183
|
0
|
0
|
|
|
|
0
|
unless (exists $self->{$name}) { croak "$name is not a field in class $type"; }
|
|
0
|
|
|
|
|
0
|
|
184
|
|
|
|
|
|
|
|
185
|
0
|
0
|
|
|
|
0
|
if (@_) {
|
186
|
|
|
|
|
|
|
# setter
|
187
|
0
|
0
|
|
|
|
0
|
if (defined($SETTABLE{$name}) ) { return $self->{$name} = shift; }
|
|
0
|
|
|
|
|
0
|
|
188
|
0
|
|
|
|
|
0
|
else { croak "$name cannot be changed."; }
|
189
|
|
|
|
|
|
|
} else {
|
190
|
0
|
|
|
|
|
0
|
return $self->{$name};
|
191
|
|
|
|
|
|
|
}
|
192
|
|
|
|
|
|
|
}
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub DESTROY {
|
195
|
2
|
|
|
2
|
|
3279
|
my $self = shift;
|
196
|
|
|
|
|
|
|
|
197
|
2
|
50
|
|
|
|
7856
|
if (defined($self->{_serial} )) {
|
198
|
0
|
|
|
|
|
0
|
$self->{_serial}->close;
|
199
|
0
|
|
|
|
|
0
|
undef $self->{_serial};
|
200
|
|
|
|
|
|
|
}
|
201
|
|
|
|
|
|
|
}
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head1 OBJECT METHODS
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 connect (serialport object)
|
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Connect establishes a connection between the tuner object and the SerialPort object. The SerialPort
|
208
|
|
|
|
|
|
|
object must be a Win32::SerialPort or a Device::SerialPort.
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
require Win32::SerialPort;
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
my $serial_port = new Win32::SerialPort('com1');
|
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$tuner->connect($serial_port);
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=cut
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub connect {
|
219
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
220
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
221
|
0
|
|
|
|
|
0
|
my ($connection) = @_;
|
222
|
0
|
|
|
|
|
0
|
my $connectiontype = ref($connection);
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
### TODO: switch to isa() here to allow derived classes
|
225
|
0
|
0
|
0
|
|
|
0
|
if (($connectiontype eq "Win32::SerialPort")
|
226
|
|
|
|
|
|
|
|| ($connectiontype eq "Device::SerialPort")) {
|
227
|
0
|
|
|
|
|
0
|
$connection->baudrate(57600);
|
228
|
0
|
|
|
|
|
0
|
$connection->parity('none');
|
229
|
0
|
|
|
|
|
0
|
$connection->databits(8);
|
230
|
0
|
|
|
|
|
0
|
$connection->stopbits(1);
|
231
|
0
|
|
|
|
|
0
|
$connection->handshake('none');
|
232
|
|
|
|
|
|
|
# $connection->read_const_time(150);
|
233
|
|
|
|
|
|
|
# $connection->read_interval(50);
|
234
|
|
|
|
|
|
|
# $connection->read_char_time(10);
|
235
|
|
|
|
|
|
|
# $connection->write_char_time(10);
|
236
|
|
|
|
|
|
|
# $connection->read_const_time(1000);
|
237
|
|
|
|
|
|
|
# $connection->read_interval(5);
|
238
|
|
|
|
|
|
|
# $connection->read_char_time(50);
|
239
|
|
|
|
|
|
|
# $connection->write_char_time(0);
|
240
|
0
|
0
|
|
|
|
0
|
if (!$connection->write_settings) {
|
241
|
0
|
|
|
|
|
0
|
carp "Couldn't open connection: $_";
|
242
|
0
|
|
|
|
|
0
|
return 0;
|
243
|
|
|
|
|
|
|
}
|
244
|
0
|
|
|
|
|
0
|
$self->{_serial} = $connection;
|
245
|
|
|
|
|
|
|
# $self->_send_command($COMMANDS{'reset'});
|
246
|
|
|
|
|
|
|
# if ( !$self->_send_command($COMMANDS{'poweroff'}) ) {
|
247
|
|
|
|
|
|
|
# carp "Tuner didn't respond to poweroff command";
|
248
|
|
|
|
|
|
|
# return 0;
|
249
|
|
|
|
|
|
|
# }
|
250
|
0
|
|
|
|
|
0
|
$self->{connected} = 1; # we're live
|
251
|
0
|
|
|
|
|
0
|
return 1;
|
252
|
|
|
|
|
|
|
} else {
|
253
|
0
|
|
|
|
|
0
|
croak "Connect needs a Win32::SerialPort or a Device::SerialPort, got a $connectiontype";
|
254
|
|
|
|
|
|
|
}
|
255
|
|
|
|
|
|
|
}
|
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head2 power (state)
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Use to turn the radio on (1) or off (0). Returns true if succeeded.
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
$tuner->power(1); # Power on tuner.
|
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=cut
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub power {
|
266
|
|
|
|
|
|
|
### TODO: Needs accessor and turn off method
|
267
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
268
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
269
|
0
|
|
|
|
|
0
|
my ($powerreq) = @_;
|
270
|
|
|
|
|
|
|
|
271
|
0
|
0
|
|
|
|
0
|
if (!defined($powerreq)) { return $self->{'power'}; }
|
|
0
|
|
|
|
|
0
|
|
272
|
0
|
0
|
|
|
|
0
|
if ($powerreq == 1) {
|
273
|
0
|
|
|
|
|
0
|
my $current_gain = $self->{gain};
|
274
|
0
|
|
|
|
|
0
|
my $current_mute = $self->{mute};
|
275
|
0
|
0
|
0
|
|
|
0
|
if (!(
|
276
|
|
|
|
|
|
|
$self->_send_command($COMMANDS{'reset'}) &&
|
277
|
|
|
|
|
|
|
$self->_send_command($COMMANDS{'poweroff'}) &&
|
278
|
|
|
|
|
|
|
$self->_send_command($COMMANDS{'poweron'}) &&
|
279
|
|
|
|
|
|
|
# $self->_send_command('000c0000001700') && #useless
|
280
|
|
|
|
|
|
|
$self->gain($current_gain) &&
|
281
|
|
|
|
|
|
|
$self->_send_command($COMMANDS{'request_signal'}) &&
|
282
|
|
|
|
|
|
|
$self->_send_command($COMMANDS{'request_sid'}) &&
|
283
|
|
|
|
|
|
|
$self->mute($current_mute)
|
284
|
|
|
|
|
|
|
# $self->{'power'} = 1
|
285
|
|
|
|
|
|
|
)) {
|
286
|
0
|
|
|
|
|
0
|
carp "Error - tuner failed to respond to power-up sequence.";
|
287
|
0
|
|
|
|
|
0
|
return 0;
|
288
|
|
|
|
|
|
|
}
|
289
|
|
|
|
|
|
|
} else {
|
290
|
0
|
|
|
|
|
0
|
$self->_send_command($COMMANDS{'poweroff'});
|
291
|
0
|
|
|
|
|
0
|
$self->{'power'} = 0;
|
292
|
|
|
|
|
|
|
}
|
293
|
|
|
|
|
|
|
}
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head2 gain (db)
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Gain ranges from -9db to 0db. It defaults to 0. When called with a parameter, gain returns
|
298
|
|
|
|
|
|
|
false on failure and true on success. When called without a parameter, gain returns the current gain
|
299
|
|
|
|
|
|
|
setting.
|
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
$tuner->gain(-6); # Mom's on the phone, turn down Howard Stern
|
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
my $current_gain = $tuner->gain;
|
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=cut
|
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub gain {
|
308
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
309
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
my ($gainreq) = @_;
|
312
|
|
|
|
|
|
|
|
313
|
0
|
0
|
|
|
|
0
|
if (!defined($gainreq)) { return $self->{gain}; } # accessor
|
|
0
|
|
|
|
|
0
|
|
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# mutator
|
316
|
0
|
0
|
0
|
|
|
0
|
if (!($gainreq <= 0) && ($gainreq >= -9)) {
|
317
|
0
|
|
|
|
|
0
|
carp "Requested gain out of range: $gainreq. Must be between -9 and 0.";
|
318
|
0
|
|
|
|
|
0
|
return 0;
|
319
|
|
|
|
|
|
|
}
|
320
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
0
|
my $gainhex = $self->_num_to_signed_hex($gainreq);
|
322
|
0
|
|
|
|
|
0
|
my $cmd = $COMMANDS{volume}.$gainhex;
|
323
|
|
|
|
|
|
|
|
324
|
0
|
0
|
|
|
|
0
|
if (!$self->_send_command($cmd)) {
|
325
|
0
|
|
|
|
|
0
|
carp "Tuner did not respond to gain setting.";
|
326
|
0
|
|
|
|
|
0
|
return 0;
|
327
|
|
|
|
|
|
|
}
|
328
|
0
|
|
|
|
|
0
|
return 1;
|
329
|
|
|
|
|
|
|
}
|
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=head2 mute (mute setting)
|
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
When called with a parameter, you can set it to 1 to mute and 0 to unmute. Called without a parameter
|
334
|
|
|
|
|
|
|
retrieves the current setting.
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
my $result = $tuner->mute(0); # Unmute the tuner
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
my $muted = $tuner->mute;
|
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub mute {
|
343
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
344
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
0
|
my ($mutereq) = @_;
|
347
|
|
|
|
|
|
|
|
348
|
0
|
0
|
|
|
|
0
|
if (!defined($mutereq)) { return $self->{mute}; } # accessor
|
|
0
|
|
|
|
|
0
|
|
349
|
|
|
|
|
|
|
|
350
|
0
|
0
|
0
|
|
|
0
|
if (!( ($mutereq == 0) || ($mutereq == 1) ) ) {
|
351
|
0
|
|
|
|
|
0
|
carp "Mute must be either 0 or 1.";
|
352
|
0
|
|
|
|
|
0
|
return 0;
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
my $mutehex = $self->_num_to_signed_hex($mutereq);
|
356
|
0
|
|
|
|
|
0
|
my $cmd = $COMMANDS{'mute'} . $mutehex;
|
357
|
0
|
0
|
|
|
|
0
|
if (!$self->_send_command($cmd)) {
|
358
|
0
|
|
|
|
|
0
|
carp "Tuner did not respond to mute command.";
|
359
|
0
|
|
|
|
|
0
|
return 0;
|
360
|
|
|
|
|
|
|
}
|
361
|
|
|
|
|
|
|
|
362
|
0
|
|
|
|
|
0
|
return 1;
|
363
|
|
|
|
|
|
|
}
|
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head2 channel (channel number, offset)
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
Can be used without a parameter to get the current channel number or with a parameter to change channels. When used with a parameter, returns true
|
368
|
|
|
|
|
|
|
on success and false on failure. Offset is -1 to select the channel before the specified number, 1 to select the channel above the specified number,
|
369
|
|
|
|
|
|
|
or 0 (default) to simply go to the specified channel.
|
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
my $current_channel = $tuner->channel;
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
my $result = $tuner->channel(6, 1); # Tune to channel 7
|
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
$tuner->channel(100); # Tune directly to channel 100
|
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=cut
|
378
|
|
|
|
|
|
|
sub channel {
|
379
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
380
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
381
|
|
|
|
|
|
|
|
382
|
0
|
|
|
|
|
0
|
my ($chanreq, $offsetreq) = @_;
|
383
|
0
|
|
|
|
|
0
|
my $offset = 0;
|
384
|
|
|
|
|
|
|
|
385
|
0
|
0
|
|
|
|
0
|
if (!defined($chanreq)) { return $self->{channel}; } # accessor
|
|
0
|
|
|
|
|
0
|
|
386
|
0
|
0
|
0
|
|
|
0
|
if (defined($offsetreq) && ($offsetreq =~ /0|1|-1/) ) { $offset = $offsetreq; }
|
|
0
|
|
|
|
|
0
|
|
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
### TODO: Channel validation.
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# channel command: $COMMAND, channel, [0,1,-1], $COMMAND suffix
|
391
|
0
|
|
|
|
|
0
|
my $chanhex = $self->_num_to_unsigned_hex($chanreq);
|
392
|
0
|
|
|
|
|
0
|
my $offsethex = $self->_num_to_signed_hex($offset);
|
393
|
0
|
|
|
|
|
0
|
my $cmd = $COMMANDS{channel} . $chanhex . $offsethex . $COMMANDS{channel_suffix};
|
394
|
0
|
|
|
|
|
0
|
return $self->_send_command($cmd);
|
395
|
|
|
|
|
|
|
}
|
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head2 monitor (cycles)
|
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
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,
|
400
|
|
|
|
|
|
|
PIDs, signal strength, and other information. Calling monitor initiates reads of this data.
|
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Reads happen automatically when commands are executed (for example changing the channel or muting the tuner). Still, monitor generally needs
|
403
|
|
|
|
|
|
|
to be called as often as possible to gather the latest data from the Tuner.
|
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
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.
|
406
|
|
|
|
|
|
|
The amount of time monitor takes will depend on the C of the tuner.
|
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
If no number of cycles is specified, monitor runs one cycle.
|
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
B 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.
|
411
|
|
|
|
|
|
|
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.
|
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
$tuner->monitor(5); # spin 5 times
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=cut
|
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
sub monitor {
|
418
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
419
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
420
|
|
|
|
|
|
|
|
421
|
0
|
|
|
|
|
0
|
my ($spins) = @_;
|
422
|
|
|
|
|
|
|
|
423
|
0
|
0
|
|
|
|
0
|
if (!defined($spins)) { $spins = 1; }
|
|
0
|
|
|
|
|
0
|
|
424
|
0
|
|
|
|
|
0
|
$spins = $spins * 20;
|
425
|
0
|
|
|
|
|
0
|
foreach (1..$spins) {
|
426
|
0
|
|
|
|
|
0
|
$self->_receive_if_waiting;
|
427
|
0
|
|
|
|
|
0
|
sleep (.05); # chill .05 second
|
428
|
|
|
|
|
|
|
}
|
429
|
|
|
|
|
|
|
}
|
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=head2 set_callback (callback type, function reference)
|
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
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
|
434
|
|
|
|
|
|
|
event. To accomidate this, you may define function callbacks activated when each event occurs. Note that some of the parameters below are marked with
|
435
|
|
|
|
|
|
|
an asterisk. This indicates that they may be undefined when your function is called. You should account for this in your callback function.
|
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=head3 channel_update (channel, *pid, *artist, *title, *composer)
|
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
$tuner->set_callback ('channel_update', \&channel);
|
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
sub channel {
|
442
|
|
|
|
|
|
|
my ($channel, $pid, $artist, $title, $composer) = @_;
|
443
|
|
|
|
|
|
|
print "Channel $channel is now playing $title.\n";
|
444
|
|
|
|
|
|
|
}
|
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head3 signal_update
|
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
Not yet implemented.
|
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=head3 time_update
|
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
Not yet implemented.
|
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=head3 status_update
|
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
Not yet implemented.
|
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=cut
|
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub set_callback {
|
461
|
1
|
|
|
1
|
1
|
7
|
my $self = shift;
|
462
|
1
|
50
|
|
|
|
7
|
if (!ref($self) eq 'CODE') { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
463
|
1
|
|
|
|
|
2
|
my ($reqtype, $funcref) = @_;
|
464
|
1
|
50
|
|
|
|
5
|
if (!ref $funcref) { croak "$funcref must be a reference to a function"; }
|
|
0
|
|
|
|
|
0
|
|
465
|
1
|
50
|
|
|
|
7
|
if (!exists($DEFAULTS{'_callbacks'}{$reqtype}) ) { croak "$reqtype is not a supported update type"; }
|
|
0
|
|
|
|
|
0
|
|
466
|
|
|
|
|
|
|
# validated enough for 'ya??
|
467
|
|
|
|
|
|
|
|
468
|
1
|
|
|
|
|
12
|
$self->{'_callbacks'}{$reqtype} = $funcref;
|
469
|
|
|
|
|
|
|
}
|
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head2 verbosity (level)
|
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
Not to be confused with C, verbosity changes the updates the tuner sends. By default, the tuner only sends updates for artist/title/PID
|
474
|
|
|
|
|
|
|
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
|
475
|
|
|
|
|
|
|
channels.
|
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
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
|
478
|
|
|
|
|
|
|
as a boolean. 0 (default) requests that no updates be sent. 1 requests that all of the following updates are sent:
|
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=over
|
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=item *
|
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
Artist/Title information for every channel
|
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=item *
|
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
PID information for every channel
|
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=item *
|
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Signal strength
|
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=item *
|
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
Current time
|
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=back
|
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
$tuner->verbosity(1); #request all of these updates
|
501
|
|
|
|
|
|
|
$current_verbosity=$tuner->verbosity;
|
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=cut
|
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
sub verbosity {
|
506
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
507
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
508
|
0
|
|
|
|
|
0
|
my ($verbreq) = @_;
|
509
|
|
|
|
|
|
|
|
510
|
0
|
0
|
|
|
|
0
|
if (!defined($verbreq)) { return $self->{verbosity}; } # accessor
|
|
0
|
|
|
|
|
0
|
|
511
|
0
|
0
|
|
|
|
0
|
if ($verbreq == 0) {
|
512
|
|
|
|
|
|
|
# 0 = no verbosity, 1b = full verbosity
|
513
|
0
|
|
|
|
|
0
|
my $cmd = $COMMANDS{verbosity}.'0000';
|
514
|
0
|
|
|
|
|
0
|
$self->_send_command($cmd);
|
515
|
0
|
|
|
|
|
0
|
$self->{verbosity} = $verbreq;
|
516
|
|
|
|
|
|
|
}
|
517
|
0
|
0
|
|
|
|
0
|
if ($verbreq == 1) {
|
518
|
|
|
|
|
|
|
# 0 = no verbosity, 1b = full verbosity
|
519
|
|
|
|
|
|
|
# my $cmd = $COMMANDS{verbosity}.'1b00';
|
520
|
0
|
|
|
|
|
0
|
my $cmd = $COMMANDS{verbosity}.'1f00';
|
521
|
0
|
|
|
|
|
0
|
$self->_send_command($cmd);
|
522
|
0
|
|
|
|
|
0
|
$self->{verbosity} = $verbreq;
|
523
|
|
|
|
|
|
|
}
|
524
|
|
|
|
|
|
|
}
|
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub _read {
|
527
|
|
|
|
|
|
|
# _read works like read from $serial. except better.
|
528
|
|
|
|
|
|
|
# returns ($count, $data)
|
529
|
|
|
|
|
|
|
# the tests for > 200000 check for the get_tick_count function wrapping
|
530
|
|
|
|
|
|
|
# (happens every 43 days or something)
|
531
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
532
|
0
|
|
|
|
|
0
|
my ($count) = @_;
|
533
|
0
|
|
|
|
|
0
|
my $debug = $self->{debug};
|
534
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
535
|
0
|
|
|
|
|
0
|
my $buffer = $self->{_buffer};
|
536
|
0
|
|
|
|
|
0
|
my $buffer_count = length($buffer);
|
537
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
0
|
my $data = '';
|
539
|
0
|
|
|
|
|
0
|
my $data_count = 0;
|
540
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
0
|
my $timeout = 100;
|
542
|
0
|
|
|
|
|
0
|
my $start_ticks = $serial->get_tick_count;
|
543
|
0
|
|
|
|
|
0
|
my $end_ticks = $start_ticks + $timeout;
|
544
|
0
|
|
0
|
|
|
0
|
WAIT: while ( (($serial->status)[1] == 0) && ($buffer_count==0) ) { # loop while nothing is waiting
|
545
|
0
|
0
|
0
|
|
|
0
|
if (($serial->get_tick_count > $end_ticks) || (($end_ticks - $serial->get_tick_count) > 200000)) {
|
546
|
|
|
|
|
|
|
# last WAIT;
|
547
|
0
|
|
|
|
|
0
|
return 0, $data;
|
548
|
|
|
|
|
|
|
}
|
549
|
0
|
|
|
|
|
0
|
sleep .005;
|
550
|
|
|
|
|
|
|
#print "hi $buffer_count\n";
|
551
|
|
|
|
|
|
|
}
|
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
# READ: while (($serial->status)[1] > 0) { # loop while data is waiting
|
554
|
0
|
|
0
|
|
|
0
|
do {
|
|
|
|
0
|
|
|
|
|
555
|
0
|
|
|
|
|
0
|
my $input = '';
|
556
|
0
|
0
|
|
|
|
0
|
if ($buffer_count > 0) {
|
557
|
0
|
|
|
|
|
0
|
$input = $buffer;
|
558
|
0
|
|
|
|
|
0
|
$self->{_buffer} = '';
|
559
|
0
|
|
|
|
|
0
|
$buffer_count = 0;
|
560
|
|
|
|
|
|
|
}
|
561
|
0
|
|
|
|
|
0
|
$input .= $serial->input;
|
562
|
0
|
|
|
|
|
0
|
my $input_count = length($input);
|
563
|
0
|
0
|
|
|
|
0
|
if ($input_count > 0) {
|
564
|
0
|
|
|
|
|
0
|
$data .= $input;
|
565
|
0
|
|
|
|
|
0
|
$data_count += $input_count;
|
566
|
0
|
|
|
|
|
0
|
$end_ticks += 6; # bonus delay because we got something
|
567
|
|
|
|
|
|
|
}
|
568
|
0
|
|
|
|
|
0
|
sleep .001;
|
569
|
|
|
|
|
|
|
#print "$data_count: $count\n";
|
570
|
|
|
|
|
|
|
} until (($data_count >= $count) || ($serial->get_tick_count > $end_ticks) ||
|
571
|
|
|
|
|
|
|
(($end_ticks - $serial->get_tick_count) > 200000));
|
572
|
|
|
|
|
|
|
|
573
|
0
|
0
|
|
|
|
0
|
if ($data_count > $count) {
|
574
|
0
|
|
|
|
|
0
|
$self->{_buffer} = substr($data, $count);
|
575
|
0
|
|
|
|
|
0
|
return $count, substr($data, 0, $count);
|
576
|
|
|
|
|
|
|
}
|
577
|
|
|
|
|
|
|
#print "returning: $data\n";
|
578
|
0
|
|
|
|
|
0
|
return $data_count, $data;
|
579
|
|
|
|
|
|
|
}
|
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
sub _receive_if_waiting {
|
582
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
583
|
0
|
0
|
|
|
|
0
|
if (!ref($self)) { croak "$self isn't an object"; }
|
|
0
|
|
|
|
|
0
|
|
584
|
|
|
|
|
|
|
|
585
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
586
|
0
|
|
|
|
|
0
|
my $waiting = ($serial->status)[1];
|
587
|
0
|
0
|
0
|
|
|
0
|
if (defined($waiting) && $waiting > 6) { $self->_receive; }
|
|
0
|
|
|
|
|
0
|
|
588
|
|
|
|
|
|
|
}
|
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub _receive {
|
591
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
592
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
593
|
0
|
|
|
|
|
0
|
my $debug = $self->{debug};
|
594
|
0
|
|
|
|
|
0
|
READ: while (1) {
|
595
|
|
|
|
|
|
|
#my ($headercount, $header) = $serial->read(6);
|
596
|
0
|
|
|
|
|
0
|
my ($headercount, $header) = $self->_read(6);
|
597
|
0
|
0
|
|
|
|
0
|
last READ if ($headercount == 0);
|
598
|
0
|
0
|
|
|
|
0
|
if ($headercount < 6) {
|
599
|
0
|
0
|
|
|
|
0
|
if ($debug) {
|
600
|
0
|
|
|
|
|
0
|
my $hexheader = $self->_pformat($header);
|
601
|
0
|
|
|
|
|
0
|
print "Read error: headercount is $headercount: $hexheader\n";
|
602
|
|
|
|
|
|
|
}
|
603
|
0
|
|
|
|
|
0
|
next READ;
|
604
|
|
|
|
|
|
|
}
|
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# handle escape escape in header (mostly)
|
607
|
0
|
|
|
|
|
0
|
my $headerescapes = $header =~ s/\x1b\x1b/\x1b/g;
|
608
|
0
|
0
|
|
|
|
0
|
if ($headerescapes) {
|
609
|
|
|
|
|
|
|
# read even more
|
610
|
0
|
0
|
|
|
|
0
|
if ($debug) { print "Fixing $headerescapes escape characters in header.\n"; }
|
|
0
|
|
|
|
|
0
|
|
611
|
|
|
|
|
|
|
#my ($headercount2, $header2) = $serial->read($headerescapes);
|
612
|
0
|
|
|
|
|
0
|
my ($headercount2, $header2) = $self->_read($headerescapes);
|
613
|
0
|
0
|
|
|
|
0
|
next READ if ($headercount2 < $headerescapes); # :(
|
614
|
0
|
|
|
|
|
0
|
$header .= $header2;
|
615
|
|
|
|
|
|
|
}
|
616
|
|
|
|
|
|
|
|
617
|
0
|
|
|
|
|
0
|
my ($start, $seq, $type, $length) = unpack('H6C1H2C1', $header);
|
618
|
|
|
|
|
|
|
|
619
|
0
|
0
|
|
|
|
0
|
next READ if ($start ne $START); # oy
|
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
# there's a special case that happens if length = 1b (the escape character). we need to read 1 just to flush it.
|
622
|
0
|
0
|
|
|
|
0
|
if ($length == 0x1b) {
|
623
|
0
|
0
|
|
|
|
0
|
if ($debug) { print "Length 1b. Flushing 1 character.\n"; }
|
|
0
|
|
|
|
|
0
|
|
624
|
|
|
|
|
|
|
#$serial->read(1);
|
625
|
0
|
|
|
|
|
0
|
$self->_read(1);
|
626
|
|
|
|
|
|
|
}
|
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
#my ($datacount, $data) = $serial->read($length+1); # read data and checksum
|
629
|
0
|
|
|
|
|
0
|
my ($datacount, $data) = $self->_read($length+1); # read data and checksum
|
630
|
0
|
0
|
|
|
|
0
|
next READ if ($datacount < $length + 1); # shouldn't happen
|
631
|
|
|
|
|
|
|
# everything was read.
|
632
|
|
|
|
|
|
|
# handle the escape character in the data sequence. must be done before checksum.
|
633
|
0
|
|
|
|
|
0
|
my $escapecount = $data =~ s/\x1b\x1b/\x1b/g;
|
634
|
0
|
0
|
|
|
|
0
|
FIXESC: if ($escapecount) {
|
635
|
|
|
|
|
|
|
# read even more
|
636
|
0
|
0
|
|
|
|
0
|
if ($debug) { print "Fixing $escapecount escape characters.\n"; }
|
|
0
|
|
|
|
|
0
|
|
637
|
|
|
|
|
|
|
#my ($datacount2, $data2) = $serial->read($escapecount);
|
638
|
0
|
|
|
|
|
0
|
my ($datacount2, $data2) = $self->_read($escapecount);
|
639
|
0
|
0
|
|
|
|
0
|
next READ if ($datacount2 < $escapecount); # :(
|
640
|
0
|
|
|
|
|
0
|
$data .= $data2;
|
641
|
0
|
|
|
|
|
0
|
$escapecount = $data =~ s/\x1b\x1b/\x1b/g;
|
642
|
0
|
0
|
|
|
|
0
|
if ($escapecount) { redo FIXESC; } # for the special times when we read more data due to escape chars and the data we read contains them... ugh
|
|
0
|
|
|
|
|
0
|
|
643
|
|
|
|
|
|
|
}
|
644
|
0
|
0
|
|
|
|
0
|
if ($debug >= 3) {print '<< '.$self->_pformat($header . $data)."\n"; }
|
|
0
|
|
|
|
|
0
|
|
645
|
0
|
|
|
|
|
0
|
my $checksum = chop $data;
|
646
|
0
|
|
|
|
|
0
|
my $calculated = $self->_checksum($header . $data);
|
647
|
0
|
0
|
|
|
|
0
|
if ($calculated ne $checksum) {
|
648
|
0
|
|
|
|
|
0
|
my ($calcval, $realval) = (ord($calculated), ord($checksum) );
|
649
|
0
|
0
|
|
|
|
0
|
if ($debug) { print "Checksum didn't match - calc: $calcval act: $realval\n"; }
|
|
0
|
|
|
|
|
0
|
|
650
|
0
|
|
|
|
|
0
|
$self->_send_checksum_error($seq);
|
651
|
0
|
|
|
|
|
0
|
next READ; # this is also bad news :(
|
652
|
|
|
|
|
|
|
}
|
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# start processing for real
|
655
|
0
|
0
|
|
|
|
0
|
if ($type eq $TYPES{ack}) {
|
656
|
0
|
|
|
|
|
0
|
$self->{_lastack} = $seq;
|
657
|
0
|
0
|
|
|
|
0
|
if ($debug) { print "Got an ack for seq: $seq\n"; }
|
|
0
|
|
|
|
|
0
|
|
658
|
0
|
|
|
|
|
0
|
next READ;
|
659
|
|
|
|
|
|
|
}
|
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
# ack it now before we go further. the tuner is impatient.
|
662
|
0
|
|
|
|
|
0
|
$self->_send_ack($seq);
|
663
|
|
|
|
|
|
|
|
664
|
0
|
0
|
|
|
|
0
|
if ($type eq $TYPES{command}) {
|
665
|
|
|
|
|
|
|
# did we get this already?
|
666
|
0
|
0
|
|
|
|
0
|
if ($seq == $self->{_lastreq}) {
|
667
|
|
|
|
|
|
|
# Tuner is repeating itself... This is bad.
|
668
|
0
|
0
|
|
|
|
0
|
if ($debug > 2) { print "Not handling duplicate update seq $seq\n"; }
|
|
0
|
|
|
|
|
0
|
|
669
|
0
|
|
|
|
|
0
|
next READ;
|
670
|
|
|
|
|
|
|
}
|
671
|
0
|
|
|
|
|
0
|
$self->{_lastreq} = $seq;
|
672
|
|
|
|
|
|
|
# handle the update, then send an ack
|
673
|
0
|
|
|
|
|
0
|
my $updatetype = unpack ('H4', $data);
|
674
|
0
|
0
|
|
|
|
0
|
if (defined($UPDATES{$updatetype})) {
|
675
|
|
|
|
|
|
|
# OK... I recognize this update.
|
676
|
0
|
|
|
|
|
0
|
my $updatename = $UPDATES{$updatetype}{name};
|
677
|
0
|
|
|
|
|
0
|
my $updatehandler = $UPDATES{$updatetype}{handler};
|
678
|
0
|
0
|
|
|
|
0
|
if ($debug) {
|
679
|
0
|
|
|
|
|
0
|
print "Received an update: $updatename\n";
|
680
|
|
|
|
|
|
|
}
|
681
|
0
|
0
|
|
|
|
0
|
if (defined($updatehandler)) {
|
682
|
|
|
|
|
|
|
# some responses are identical but the identical part starts
|
683
|
|
|
|
|
|
|
# somewhere after the command... chop it off to the identical bits
|
684
|
0
|
|
|
|
|
0
|
my $removefirst = $UPDATES{$updatetype}{removefirst};
|
685
|
0
|
|
|
|
|
0
|
$data=substr($data,$removefirst);
|
686
|
0
|
|
|
|
|
0
|
$self->$updatehandler($data);
|
687
|
|
|
|
|
|
|
}
|
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
} else {
|
690
|
|
|
|
|
|
|
# unknown command.
|
691
|
0
|
0
|
|
|
|
0
|
if ($debug) {
|
692
|
0
|
|
|
|
|
0
|
my $datahex = $self->_pformat($data);
|
693
|
0
|
|
|
|
|
0
|
print "Unknown update: $updatetype data: $datahex\n";
|
694
|
|
|
|
|
|
|
}
|
695
|
|
|
|
|
|
|
}
|
696
|
|
|
|
|
|
|
}
|
697
|
|
|
|
|
|
|
}
|
698
|
|
|
|
|
|
|
}
|
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
sub _channel_update {
|
701
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
702
|
0
|
|
|
|
|
0
|
my ($data) = @_;
|
703
|
|
|
|
|
|
|
|
704
|
0
|
|
|
|
|
0
|
my ($channel, $categorynum, $shortchan, $longchan, $shortcat, $longcat);
|
705
|
0
|
|
|
|
|
0
|
($channel, $categorynum, $shortchan, $longchan, $shortcat, $longcat, $data) = unpack ('C1xC1xxC1/aC/aC/aC/aa*', $data);
|
706
|
|
|
|
|
|
|
|
707
|
0
|
|
|
|
|
0
|
$self->{channel} = $channel;
|
708
|
|
|
|
|
|
|
|
709
|
0
|
|
|
|
|
0
|
$self->{categories}{$categorynum}{longname} = $longcat;
|
710
|
0
|
|
|
|
|
0
|
$self->{categories}{$categorynum}{shortname} = $shortcat;
|
711
|
0
|
|
|
|
|
0
|
$self->{channels}{$channel}{longname} = $longchan;
|
712
|
0
|
|
|
|
|
0
|
$self->{channels}{$channel}{shortname} = $shortchan;
|
713
|
|
|
|
|
|
|
|
714
|
0
|
|
|
|
|
0
|
$self->{channels}{$channel}{category} = $self->{categories}{$categorynum};
|
715
|
0
|
|
|
|
|
0
|
$self->{categories}{$categorynum}{channels}{$channel} = $self->{channels}{$channel};
|
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
# process left over items
|
718
|
0
|
|
|
|
|
0
|
$self->_channel_items($channel, $data);
|
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
# call handler
|
721
|
0
|
|
|
|
|
0
|
$self->_call_channel_handler($channel);
|
722
|
|
|
|
|
|
|
}
|
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
sub _call_channel_handler {
|
725
|
2
|
|
|
2
|
|
4
|
my $self = shift;
|
726
|
2
|
|
|
|
|
4
|
my ($channel) = @_;
|
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
# update handler: ($channel, $pid, $artist, $title, $composer)
|
729
|
2
|
|
|
|
|
5
|
my $handler = $self->{'_callbacks'}{'channel_update'};
|
730
|
2
|
50
|
|
|
|
8
|
if (ref($handler)) {
|
731
|
2
|
|
|
|
|
13
|
&$handler (
|
732
|
|
|
|
|
|
|
$channel,
|
733
|
|
|
|
|
|
|
$self->{'channels'}{$channel}{'pid'},
|
734
|
|
|
|
|
|
|
$self->{'channels'}{$channel}{'artist'},
|
735
|
|
|
|
|
|
|
$self->{'channels'}{$channel}{'title'},
|
736
|
|
|
|
|
|
|
$self->{'channels'}{$channel}{'composer'}
|
737
|
|
|
|
|
|
|
);
|
738
|
|
|
|
|
|
|
}
|
739
|
|
|
|
|
|
|
}
|
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
sub _signal_update {
|
742
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
743
|
0
|
|
|
|
|
0
|
my ($data) = @_;
|
744
|
0
|
|
|
|
|
0
|
my $debug = $self->{debug};
|
745
|
|
|
|
|
|
|
|
746
|
0
|
|
|
|
|
0
|
my ($overall, $sat, $terrestrial) = unpack ('CCC', $data);
|
747
|
|
|
|
|
|
|
|
748
|
0
|
|
|
|
|
0
|
foreach my $signal ($overall, $sat, $terrestrial) {
|
749
|
0
|
|
|
|
|
0
|
$signal = $signal * .33;
|
750
|
|
|
|
|
|
|
}
|
751
|
0
|
0
|
|
|
|
0
|
if ($debug>1) { print "Signal overall: $overall Sat: $sat Terrestrial: $terrestrial\n"; }
|
|
0
|
|
|
|
|
0
|
|
752
|
0
|
|
|
|
|
0
|
$self->{signal}{overall} = $overall;
|
753
|
0
|
|
|
|
|
0
|
$self->{signal}{sat} = $sat;
|
754
|
0
|
|
|
|
|
0
|
$self->{signal}{terrestrial} = $terrestrial;
|
755
|
|
|
|
|
|
|
}
|
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
sub _time_update {
|
758
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
759
|
0
|
|
|
|
|
0
|
my ($data) = @_;
|
760
|
0
|
|
|
|
|
0
|
my $debug = $self->{debug};
|
761
|
|
|
|
|
|
|
|
762
|
0
|
|
|
|
|
0
|
my ($year, $month, $day, $hour, $minute, $second) = unpack ('nCCCCC', $data);
|
763
|
0
|
0
|
|
|
|
0
|
if ($debug>1) { print "Time update: $year-$month-$day $hour:$minute:$second\n"; }
|
|
0
|
|
|
|
|
0
|
|
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
# send to user functions as reverse list to conform with perl custom
|
766
|
|
|
|
|
|
|
}
|
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
sub _channel_item_update {
|
769
|
2
|
|
|
2
|
|
6119
|
my $self = shift;
|
770
|
2
|
|
|
|
|
4
|
my ($data) = @_;
|
771
|
|
|
|
|
|
|
|
772
|
2
|
|
|
|
|
3
|
my $channel;
|
773
|
2
|
|
|
|
|
24
|
($channel, $data) = unpack ('C1a*', $data);
|
774
|
2
|
|
|
|
|
8
|
$self->_channel_items($channel, $data);
|
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
# call handler
|
777
|
2
|
|
|
|
|
6
|
$self->_call_channel_handler($channel);
|
778
|
|
|
|
|
|
|
}
|
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
sub _channel_items {
|
781
|
|
|
|
|
|
|
# multiple updates contain this stuff. call this with $chan and $data.
|
782
|
2
|
|
|
2
|
|
3
|
my $self = shift;
|
783
|
2
|
|
|
|
|
5
|
my ($channel, $data) = @_;
|
784
|
2
|
|
|
|
|
5
|
my $debug=$self->{debug};
|
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
|
787
|
2
|
|
|
|
|
2
|
my $numitems;
|
788
|
2
|
|
|
|
|
6
|
($numitems, $data) = unpack ('C1a*', $data);
|
789
|
2
|
50
|
|
|
|
8
|
if ($numitems>0) {
|
790
|
|
|
|
|
|
|
# there be items here
|
791
|
|
|
|
|
|
|
# step 1 - clean out the old items
|
792
|
2
|
|
|
|
|
6
|
foreach my $clean (values %ITEM_TYPES) {
|
793
|
8
|
|
|
|
|
23
|
$self->{channels}{$channel}{$clean} = undef;
|
794
|
|
|
|
|
|
|
}
|
795
|
|
|
|
|
|
|
|
796
|
2
|
|
|
|
|
7
|
ITEM: foreach (1..$numitems) {
|
797
|
9
|
|
|
|
|
12
|
my ($itemtype, $item, $typevar);
|
798
|
9
|
|
|
|
|
37
|
($itemtype, $item, $data) = unpack ('C1C1/aa*', $data);
|
799
|
9
|
|
|
|
|
20
|
$typevar = $ITEM_TYPES{$itemtype};
|
800
|
9
|
50
|
|
|
|
21
|
if ($debug > 1) { print "Item type: $itemtype Info: $item\n"; }
|
|
0
|
|
|
|
|
0
|
|
801
|
9
|
100
|
|
|
|
22
|
if (!defined($typevar)) {
|
802
|
2
|
50
|
|
|
|
7
|
if ($debug) { print "Channel update contained unrecognized item: $itemtype: $item\n"; }
|
|
0
|
|
|
|
|
0
|
|
803
|
2
|
|
|
|
|
8
|
next ITEM;
|
804
|
|
|
|
|
|
|
}
|
805
|
|
|
|
|
|
|
# store item
|
806
|
7
|
|
|
|
|
19
|
$self->{channels}{$channel}{$typevar} = $item;
|
807
|
|
|
|
|
|
|
}
|
808
|
|
|
|
|
|
|
}
|
809
|
2
|
|
|
|
|
4
|
my $remainder = length($data);
|
810
|
2
|
50
|
|
|
|
8
|
if ($remainder > 0) { warn "Got a remainder when reading channel update."; }
|
|
0
|
|
|
|
|
0
|
|
811
|
|
|
|
|
|
|
}
|
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub _send_ack {
|
814
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
815
|
0
|
|
|
|
|
0
|
my ($seq) = @_;
|
816
|
|
|
|
|
|
|
|
817
|
0
|
|
|
|
|
0
|
my $rawdata = pack('H6C1H2C1', $START, $seq, $TYPES{ack}, 0);
|
818
|
0
|
|
|
|
|
0
|
my $checksum = $self->_checksum($rawdata);
|
819
|
0
|
|
|
|
|
0
|
my $data = $rawdata.$checksum;
|
820
|
0
|
0
|
|
|
|
0
|
if ($self->debug >= 3) {print '>> '.$self->_pformat($data)."\n"; }
|
|
0
|
|
|
|
|
0
|
|
821
|
|
|
|
|
|
|
|
822
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
823
|
0
|
|
|
|
|
0
|
my $count_out = $serial->write($data);
|
824
|
0
|
0
|
|
|
|
0
|
warn "Not enough data written" unless ($count_out == length($data));
|
825
|
|
|
|
|
|
|
}
|
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
sub _send_checksum_error {
|
828
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
829
|
0
|
|
|
|
|
0
|
my ($seq) = @_;
|
830
|
|
|
|
|
|
|
|
831
|
0
|
|
|
|
|
0
|
my $rawdata = pack('H6C1H2C1', $START, $seq, $TYPES{e_checksum}, 0);
|
832
|
0
|
|
|
|
|
0
|
my $checksum = $self->_checksum($rawdata);
|
833
|
0
|
|
|
|
|
0
|
my $data = $rawdata.$checksum;
|
834
|
0
|
0
|
|
|
|
0
|
if ($self->debug >= 3) {print '>> '.$self->_pformat($data)."\n"; }
|
|
0
|
|
|
|
|
0
|
|
835
|
|
|
|
|
|
|
|
836
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
837
|
0
|
|
|
|
|
0
|
my $count_out = $serial->write($data);
|
838
|
0
|
0
|
|
|
|
0
|
warn "Not enough data written" unless ($count_out == length($data));
|
839
|
|
|
|
|
|
|
}
|
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
sub _send_command {
|
842
|
|
|
|
|
|
|
### TODO: Handle escape char (1B)
|
843
|
|
|
|
|
|
|
# returns true/false results
|
844
|
0
|
|
|
0
|
|
0
|
my $self = shift;
|
845
|
0
|
|
|
|
|
0
|
my ($hexcommand) = @_;
|
846
|
0
|
|
|
|
|
0
|
my $command = pack('H*', $hexcommand);
|
847
|
0
|
|
|
|
|
0
|
my $cmdlength = length($command);
|
848
|
0
|
|
|
|
|
0
|
my $sequence = $self->{_sequence};
|
849
|
|
|
|
|
|
|
|
850
|
0
|
|
|
|
|
0
|
my $rawdata = pack('H6C1H2C1a*', $START, $sequence, $TYPES{command}, $cmdlength, $command);
|
851
|
0
|
|
|
|
|
0
|
my $checksum = $self->_checksum($rawdata);
|
852
|
|
|
|
|
|
|
# oddly enough the double escapes don't count as length. don't change original length.
|
853
|
0
|
|
|
|
|
0
|
my $data = pack('H6C1H2C1a*a1', $START, $sequence, $TYPES{command}, $cmdlength, $command, $checksum);
|
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
# handle the escape character anywhere in the sent data. must be done after checksum.
|
856
|
0
|
|
|
|
|
0
|
$data =~ s/\x1b/\x1b\x1b/g;
|
857
|
|
|
|
|
|
|
|
858
|
0
|
|
|
|
|
0
|
my $serial = $self->{_serial};
|
859
|
|
|
|
|
|
|
|
860
|
0
|
|
|
|
|
0
|
my $attempts=0;
|
861
|
0
|
|
|
|
|
0
|
SEND: foreach $attempts (1..5) {
|
862
|
|
|
|
|
|
|
# send/retry logic
|
863
|
0
|
0
|
|
|
|
0
|
if ($self->{debug}) { print "Sending command: $hexcommand sequence: $sequence\n"; }
|
|
0
|
|
|
|
|
0
|
|
864
|
0
|
0
|
|
|
|
0
|
if ($self->debug >= 3) {print '>> '.$self->_pformat($data)."\n"; }
|
|
0
|
|
|
|
|
0
|
|
865
|
0
|
|
|
|
|
0
|
$serial->write($data);
|
866
|
0
|
|
|
|
|
0
|
$self->_receive;
|
867
|
0
|
0
|
|
|
|
0
|
last SEND if ($self->{_lastack} == $sequence );
|
868
|
|
|
|
|
|
|
# we're still here... receiver is probably busy. give it a bit.
|
869
|
0
|
|
|
|
|
0
|
sleep(3);
|
870
|
|
|
|
|
|
|
}
|
871
|
|
|
|
|
|
|
|
872
|
0
|
|
|
|
|
0
|
$self->{_sequence} = ($self->{_sequence} + 1);
|
873
|
0
|
0
|
|
|
|
0
|
if ($self->{_sequence} > 255) { $self->{_sequence} = 0; }
|
|
0
|
|
|
|
|
0
|
|
874
|
|
|
|
|
|
|
|
875
|
0
|
0
|
0
|
|
|
0
|
if (($attempts == 3) && ($self->{lastack} != $sequence) ) {
|
876
|
0
|
|
|
|
|
0
|
carp "Command not acknowledged by tuner after 3 attempts.";
|
877
|
0
|
|
|
|
|
0
|
return 0;
|
878
|
|
|
|
|
|
|
}
|
879
|
0
|
|
|
|
|
0
|
return 1;
|
880
|
|
|
|
|
|
|
}
|
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
sub _checksum {
|
884
|
|
|
|
|
|
|
# returns 1 byte (char) of checksum data
|
885
|
|
|
|
|
|
|
# i can replace this with unpack. just need to do the 256-result thing.
|
886
|
|
|
|
|
|
|
# is there a bug here when $sum % 256 = 0?
|
887
|
4
|
|
|
4
|
|
5066
|
my $self = shift;
|
888
|
4
|
|
|
|
|
7
|
my ($data) = @_;
|
889
|
|
|
|
|
|
|
|
890
|
4
|
|
|
|
|
4
|
my $char;
|
891
|
4
|
|
|
|
|
5
|
my $sum = 0;
|
892
|
4
|
|
|
|
|
33
|
foreach $char (split(//, $data)) {
|
893
|
161
|
|
|
|
|
189
|
$sum += ord($char);
|
894
|
|
|
|
|
|
|
}
|
895
|
4
|
100
|
|
|
|
28
|
if ( ($sum % 0x100) == 0) { return chr(0); }
|
|
1
|
|
|
|
|
3
|
|
896
|
3
|
|
|
|
|
5
|
my $cs = 0x100 - ($sum % 0x100);
|
897
|
3
|
|
|
|
|
10
|
return chr($cs);
|
898
|
|
|
|
|
|
|
}
|
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
sub _pformat {
|
901
|
0
|
|
|
0
|
|
|
my $self = shift;
|
902
|
0
|
|
|
|
|
|
my ($data) = @_;
|
903
|
0
|
|
|
|
|
|
my $buffer = '';
|
904
|
|
|
|
|
|
|
|
905
|
0
|
|
|
|
|
|
my $char;
|
906
|
|
|
|
|
|
|
|
907
|
0
|
|
|
|
|
|
foreach $char (split(//, $data)) {
|
908
|
0
|
|
|
|
|
|
$char = ord($char);
|
909
|
0
|
0
|
0
|
|
|
|
if (($char >= 32) && ($char <= 126)) {
|
910
|
|
|
|
|
|
|
# $buffer .= chr($char);
|
911
|
0
|
|
|
|
|
|
$buffer .= sprintf ("0x%02x ", $char);
|
912
|
|
|
|
|
|
|
} else {
|
913
|
0
|
|
|
|
|
|
$buffer .= sprintf ("0x%02x ", $char);
|
914
|
|
|
|
|
|
|
}
|
915
|
|
|
|
|
|
|
}
|
916
|
0
|
|
|
|
|
|
return $buffer;
|
917
|
|
|
|
|
|
|
}
|
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
sub _num_to_signed_hex {
|
920
|
0
|
|
|
0
|
|
|
my $self = shift;
|
921
|
0
|
|
|
|
|
|
my ($data) = @_;
|
922
|
|
|
|
|
|
|
|
923
|
0
|
|
|
|
|
|
return (unpack('H2', pack ('c1', $data) ) );
|
924
|
|
|
|
|
|
|
}
|
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
sub _num_to_unsigned_hex {
|
927
|
0
|
|
|
0
|
|
|
my $self = shift;
|
928
|
0
|
|
|
|
|
|
my ($data) = @_;
|
929
|
|
|
|
|
|
|
|
930
|
0
|
|
|
|
|
|
return (unpack('H2', pack ('C1', $data) ) );
|
931
|
|
|
|
|
|
|
}
|
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
=head1 DEPENDENCIES
|
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
None yet.
|
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
=head1 AUTHOR
|
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
Jamie Tatum, L, C<< >>
|
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
=head1 BUGS
|
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=over
|
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
=item *
|
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
You should be able to submit a function reference to be called when the various updates (channel info, time, signal, pid) occur. This is not yet
|
948
|
|
|
|
|
|
|
implemented.
|
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=item *
|
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
The power system needs to be revisited. Currently C turns the radio off - it should probably preserve state between sessions.
|
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=item *
|
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
The channel property isn't being set (correctly anyway).
|
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
=item *
|
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
Various public properties need to be documented.
|
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=back
|
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
Please report any bugs or feature requests to
|
965
|
|
|
|
|
|
|
C, or through the web interface at
|
966
|
|
|
|
|
|
|
L.
|
967
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on
|
968
|
|
|
|
|
|
|
your bug as I make changes.
|
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS
|
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
Thanks to Mitch and Dale at L Thanks to everyone who reversed a little bit of the tuner protocol
|
973
|
|
|
|
|
|
|
- too many to list. :) You know who you are.
|
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE
|
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
Copyright 2005 Jamie Tatum, all rights reserved.
|
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
Sirius and related marks are trademarks of SIRIUS Satellite Radio Inc. Use of this module is at your own risk and may be subject to the SIRIUS terms and
|
980
|
|
|
|
|
|
|
conditions located at L.
|
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
983
|
|
|
|
|
|
|
under the same terms as Perl itself.
|
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=cut
|
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
1; # End of Audio::Radio::Sirius
|