line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Audio::Xmpcr; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
$VERSION="1.02"; |
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
17767
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
170
|
|
6
|
1
|
|
|
1
|
|
575
|
use Audio::Xmpcr::Serial; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
38
|
|
7
|
1
|
|
|
1
|
|
664
|
use Audio::Xmpcr::Network; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
212
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
sub new { |
10
|
1
|
|
|
1
|
1
|
573
|
my($class,%args)=@_; |
11
|
1
|
50
|
|
|
|
10
|
if ($args{SERIALPORT}) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
12
|
0
|
|
|
|
|
0
|
return new Audio::Xmpcr::Serial($args{SERIALPORT}); |
13
|
|
|
|
|
|
|
} elsif ($args{NETHOST}) { |
14
|
0
|
|
|
|
|
0
|
return new Audio::Xmpcr::Network($args{NETHOST},$args{NETPORT}, |
15
|
|
|
|
|
|
|
$args{LOCKER}); |
16
|
|
|
|
|
|
|
} elsif ($args{LOADTEST}) { |
17
|
|
|
|
|
|
|
# just return a blessed hash, for loading/testing purposes |
18
|
1
|
|
|
|
|
3
|
my $ref={}; |
19
|
1
|
|
|
|
|
3
|
bless $ref, $class; |
20
|
1
|
|
|
|
|
4
|
return $ref; |
21
|
|
|
|
|
|
|
} else { |
22
|
0
|
|
|
|
|
|
die "Xmpcr: unknown API mode - must use either NET/SERIAL method.\n"; |
23
|
|
|
|
|
|
|
} |
24
|
|
|
|
|
|
|
} |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=pod |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 NAME |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
Audio::Xmpcr - control an XMPCR device for XM Radio |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 SYNOPSIS |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
use Audio::Xmpcr; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=head1 DESCRIPTION |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
The Audio::Xmpcr module allows you to control an XMPCR device, |
39
|
|
|
|
|
|
|
which is used to tune into the XM satellite radio network. |
40
|
|
|
|
|
|
|
More info can be found at http://www.xmradio.com. The device |
41
|
|
|
|
|
|
|
itself can only be purchased (as of this writing) at PCConnection |
42
|
|
|
|
|
|
|
http://www.pcconnection.com. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
The api operates in one of two modes. First, a direct SERIAL |
45
|
|
|
|
|
|
|
mode where the api communicates with the device directly. This is usually |
46
|
|
|
|
|
|
|
not desirable because polling the device for song data is time consuming. |
47
|
|
|
|
|
|
|
Time required to pull an entire channel/song/artist listing is upwards |
48
|
|
|
|
|
|
|
of 10-20 seconds. Also, the device may be shared by several users/programs. |
49
|
|
|
|
|
|
|
Protocol confusion may result if everyone is talking at the same time. |
50
|
|
|
|
|
|
|
Note that the serial mode will write a channel cache file into |
51
|
|
|
|
|
|
|
~/.xmpcrd-cache, so if the channel list changes, you'll need to delete |
52
|
|
|
|
|
|
|
this file and restart the program. (i.e., since the daemon uses serial |
53
|
|
|
|
|
|
|
mode, you'll need to restart the daemon) |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
The second mode of operation is NETWORK/DAEMON mode. Here, a daemon |
56
|
|
|
|
|
|
|
runs on the machine connected to the Pcr, and all communication with the |
57
|
|
|
|
|
|
|
daemon is done via sockets. This is preferable for most applications, as |
58
|
|
|
|
|
|
|
the daemon takes care of much of the busy work. In particular, the |
59
|
|
|
|
|
|
|
daemon continuously polls the device, and updates its internal channel |
60
|
|
|
|
|
|
|
listing. The default timing allows 4 channels to be updated each second. |
61
|
|
|
|
|
|
|
Also, every half second, the current channel is updated - since we always |
62
|
|
|
|
|
|
|
want to know when the channel data changes on the current channel. This |
63
|
|
|
|
|
|
|
means that it takes 100/4 or 25 seconds to refresh all channels. When |
64
|
|
|
|
|
|
|
retrieving a channel/song listing, a few channels may be out of date, |
65
|
|
|
|
|
|
|
but will most certainly be correct the next pass through. See the note |
66
|
|
|
|
|
|
|
about the cache file in the SERIAL mode paragraph, above. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
The mode is chosen when the device is instantiated - i.e., the constructor |
69
|
|
|
|
|
|
|
is an abstract factory for the two types of connections. Regardless of the |
70
|
|
|
|
|
|
|
mode chosen, the interface supports the same method calls and behaviour |
71
|
|
|
|
|
|
|
with few exceptions. That is, you won't care whether you're talking |
72
|
|
|
|
|
|
|
directly to the device or the daemon - the api will return the same |
73
|
|
|
|
|
|
|
results either way. The following is a list of exceptions to that rule: |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=over 1 |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item list() |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Via daemon, this call returns almost immediately; via SERIAL, dramatically slower (i.e., a full channel pull will take between 10-20 secs) |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=item events() and processEvents() |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
Channel events are not supported in the SERIAL api. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=back 1 |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=head1 METHOD CALLS |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=over 4 |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item new(KEY => VALUE,...) |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Creates a new Xmpcr object. Preferably, use the network mode; |
94
|
|
|
|
|
|
|
use the serial mode if you're unable to run a daemon. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
my $radio=new Xmpcr(SERIALPORT => "/dev/ttyUSB0") |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
or... |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
my $radio=new Xmpcr(NETHOST => "localhost",NETPORT => 32463,LOCKER => "appname"); |
101
|
|
|
|
|
|
|
(port and locker are optional) |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Since many users may use the device when the daemon is running, you have |
104
|
|
|
|
|
|
|
the option of locking the device to prevent channel changes/power off |
105
|
|
|
|
|
|
|
from occuring. When LOCKER is specified, no other networked API user |
106
|
|
|
|
|
|
|
may change the channel or power off if their appname is different. For |
107
|
|
|
|
|
|
|
example, you have a program with the LOCKER 'ripper', which is busily |
108
|
|
|
|
|
|
|
recording show data - you don't want the channel to be changed. Another |
109
|
|
|
|
|
|
|
API user whose appname is 'web-interface' may attempt to change the |
110
|
|
|
|
|
|
|
channel, but the call will be refused. When the locker powers the |
111
|
|
|
|
|
|
|
device off, it will be freed for use by other applications. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
It may take a few tens-of-seconds to power on the device, since a |
114
|
|
|
|
|
|
|
channel scan must be performed. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item power("on"/"off") |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Turns the power to the XmPCR on or off. While off, no commands may |
119
|
|
|
|
|
|
|
be executed (other than to turn the power on). |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Returns undef if successful, or an error string if call failed. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=item mute("on"/"off") |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Turns the mute control on or off. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Returns undef if successful, or an error string if call failed. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=item setchannel(integer) |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Sets the receiver channel. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Returns undef if successful, or an error string if call failed. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=item list() (pull entire channel list) |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item list(integer) (pull single channel list) |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Loads channel data. The "single channel" mode returns a reference |
140
|
|
|
|
|
|
|
to a single hash; the "entire list mode" returns an array of hashes. |
141
|
|
|
|
|
|
|
In serial mode (both entire and single list), the data is pulled |
142
|
|
|
|
|
|
|
directly from the device; the full channel listing takes some time. |
143
|
|
|
|
|
|
|
In network mode, a single channel is also pulled directly from the |
144
|
|
|
|
|
|
|
device (since it's fairly quick), but a full channel listing is |
145
|
|
|
|
|
|
|
pulled from a cache. This cache is continually refreshed in the |
146
|
|
|
|
|
|
|
background by the daemon, about every 20 seconds. Therefore, when |
147
|
|
|
|
|
|
|
the network/entire list is pulled, there's a chance that a few of the |
148
|
|
|
|
|
|
|
song titles will be incorrect, but they will be corrected shortly |
149
|
|
|
|
|
|
|
thereafter. (The currently selected channel is refreshed every |
150
|
|
|
|
|
|
|
.5 second, so it will always be accurate). A channel entry has the keys: |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=over 1 |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item NUM |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Channel number |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item NAME |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Name of channel |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=item CAT |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Category of channel |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=item SONG |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Title of song |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=item ARTIST |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Name of artist |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=back |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
returns an empty hash/array if the operation fails. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=item status() |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Returns status data. Returns a hash with the following keys: |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=over 1 |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item POWER |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
on/off |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=item ANTENNA |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
0-100 (percent) |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=item NUM |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
integer (channel num) |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=item NAME |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
string (channel name) |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=item CAT |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
string (channel category) |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=item SONG |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
string (currently playing song) |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=item ARTIST |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
string (currently playing artist) |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=item RADIOID |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
8-character string |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=back 1 |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Hash may have undefined/null values if operation failed - but POWER will |
219
|
|
|
|
|
|
|
always be defined. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=item events("on"/"off") |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
Turns event delivery on or off. Whenever a song changes, the API will |
224
|
|
|
|
|
|
|
automatically track channels that change songs, and deliver these |
225
|
|
|
|
|
|
|
changes to you. (see the processEvents() call) |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
This call is only supported in network mode, and always returns success. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=item processEvents() |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
If event delivery is enabled, you may use this call to see which |
232
|
|
|
|
|
|
|
channels have changed songs. It returns an array of hashes, each containing |
233
|
|
|
|
|
|
|
data about any channels that have changed. See the list() method for |
234
|
|
|
|
|
|
|
a hash key description. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Because the daemon broadcasts event changes to all interested parties, |
237
|
|
|
|
|
|
|
it is important that you call processEvents() periodically - perhaps |
238
|
|
|
|
|
|
|
every second - to avoid buffer overrun and data loss. (The alternative |
239
|
|
|
|
|
|
|
is to provide a separate thread to check the daemon, but not every environment |
240
|
|
|
|
|
|
|
is ready to run threads yet). You may also use the eventFd() call for |
241
|
|
|
|
|
|
|
use in select() statements (see eventFd()). |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
This call is only supported in network mode. An empty list will be |
244
|
|
|
|
|
|
|
returned if event deliver (via events()) is disabled. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=item eventFd() |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
Returns a single file descriptor (*not* a file handle) for use in |
249
|
|
|
|
|
|
|
select() calls. Useful when you need to monitor your own file handles |
250
|
|
|
|
|
|
|
for input/output, and also need to check for song events. When select |
251
|
|
|
|
|
|
|
indicates that the descriptor is ready for I/O, call processEvents to |
252
|
|
|
|
|
|
|
determine what was sent. Please do not try to send or receive data using |
253
|
|
|
|
|
|
|
the descriptor; let the API do that. |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=item forcelock() |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
If the PCR is locked by a program that has exited and was unable to |
258
|
|
|
|
|
|
|
turn off the radio, the daemon may remain locked, and no other programs |
259
|
|
|
|
|
|
|
will be able to use the device. This call removes the lock, allowing |
260
|
|
|
|
|
|
|
the radio to be turned off via the 'power' call. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=back |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=head1 AUTHOR AND COPYRIGHT |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
This code is modeled after a Perl module written by Chris Carlson |
267
|
|
|
|
|
|
|
(c@rlson.net). Only the low-level protocol communication was derived |
268
|
|
|
|
|
|
|
from his work (which was further derived from another author's Visual |
269
|
|
|
|
|
|
|
Basic project). |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
Copyright (c) 2003 Paul Bournival. All rights reserved. This program is |
272
|
|
|
|
|
|
|
free software; you can redistribute it and/or modify it under the terms |
273
|
|
|
|
|
|
|
of the Artistic License, distributed with Perl. |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=head1 SEE ALSO |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
The XMPCR module, available from http://xmpcr.sf.net |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=cut |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
1; |