diff options
author | Ralf Ertzinger <rockbox@camperquake.de> | 2013-06-22 10:08:23 +0100 |
---|---|---|
committer | Frank Gevaerts <frank@gevaerts.be> | 2013-11-10 18:41:24 +0100 |
commit | b170c73f922e3457b923b4e7fcbec794a8885c77 (patch) | |
tree | 89fbdbd8c25af5101a29a1ede3b896332a4e205c /tools/iap/Device | |
parent | 500b137308a6ee5c2aba873734a8956d70472f56 (diff) | |
download | rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.gz rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.zip |
Updated IAP commands.
Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012.
They have been condensed into a single patch and some further additions
by Andy Potter.
Currently includes Authentication V2 support from iPod to Accessory,
RF/BlueTooth transmitter support, selecting a playlist and selecting a
track from the current playlist. Does not support uploading Album Art
or podcasts. Has been tested on the following iPods,
4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and
Video 5.5Gen.
Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e
Co-Authored by: Andy Potter <liveboxandy@gmail.com>
Reviewed-on: http://gerrit.rockbox.org/533
Reviewed-by: Frank Gevaerts <frank@gevaerts.be>
Diffstat (limited to 'tools/iap/Device')
-rw-r--r-- | tools/iap/Device/iPod.pm | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/tools/iap/Device/iPod.pm b/tools/iap/Device/iPod.pm new file mode 100644 index 0000000000..b2d686cce7 --- /dev/null +++ b/tools/iap/Device/iPod.pm | |||
@@ -0,0 +1,386 @@ | |||
1 | package Device::iPod; | ||
2 | |||
3 | use Device::SerialPort; | ||
4 | use POSIX qw(isgraph); | ||
5 | use strict; | ||
6 | |||
7 | sub new { | ||
8 | my $class = shift; | ||
9 | my $port = shift; | ||
10 | my $self = {}; | ||
11 | my $s; | ||
12 | |||
13 | $self->{-serial} = undef; | ||
14 | $self->{-inbuf} = ''; | ||
15 | $self->{-error} = undef; | ||
16 | $self->{-baudrate} = 57600; | ||
17 | $self->{-debug} = 0; | ||
18 | |||
19 | return bless($self, $class); | ||
20 | } | ||
21 | |||
22 | sub open { | ||
23 | my $self = shift; | ||
24 | my $port = shift; | ||
25 | |||
26 | $self->{-serial} = new Device::SerialPort($port); | ||
27 | unless(defined($self->{-serial})) { | ||
28 | $self->{-error} = $!; | ||
29 | return undef; | ||
30 | } | ||
31 | |||
32 | $self->{-serial}->parity('none'); | ||
33 | $self->{-serial}->databits(8); | ||
34 | $self->{-serial}->stopbits(1); | ||
35 | $self->{-serial}->handshake('none'); | ||
36 | return $self->baudrate($self->{-baudrate}); | ||
37 | } | ||
38 | |||
39 | sub baudrate { | ||
40 | my $self = shift; | ||
41 | my $baudrate = shift; | ||
42 | |||
43 | if ($baudrate < 1) { | ||
44 | $self->{-error} = "Invalid baudrate"; | ||
45 | return undef; | ||
46 | } | ||
47 | |||
48 | $self->{-baudrate} = $baudrate; | ||
49 | if (defined($self->{-serial})) { | ||
50 | $self->{-serial}->baudrate($baudrate); | ||
51 | } | ||
52 | |||
53 | return 1; | ||
54 | } | ||
55 | |||
56 | sub sendmsg { | ||
57 | my $self = shift; | ||
58 | my $lingo = shift; | ||
59 | my $command = shift; | ||
60 | my $data = shift || ''; | ||
61 | |||
62 | return $self->_nosetup() unless(defined($self->{-serial})); | ||
63 | |||
64 | if (($lingo < 0) || ($lingo > 255)) { | ||
65 | $self->{-error} = 'Invalid lingo'; | ||
66 | return undef; | ||
67 | } | ||
68 | |||
69 | if ($command < 0) { | ||
70 | $self->{-error} = 'Invalid command'; | ||
71 | return undef; | ||
72 | } | ||
73 | |||
74 | if ($lingo == 4) { | ||
75 | if ($command > 0xffff) { | ||
76 | $self->{-error} = 'Invalid command'; | ||
77 | return undef; | ||
78 | } | ||
79 | return $self->_send($self->_frame_cmd(pack("Cn", $lingo, $command) . $data)); | ||
80 | } else { | ||
81 | if ($command > 0xff) { | ||
82 | $self->{-error} = 'Invalid command'; | ||
83 | return undef; | ||
84 | } | ||
85 | return $self->_send($self->_frame_cmd(pack("CC", $lingo, $command) . $data)); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | sub sendraw { | ||
90 | my $self = shift; | ||
91 | my $data = shift; | ||
92 | |||
93 | return $self->_nosetup() unless(defined($self->{-serial})); | ||
94 | |||
95 | return $self->_send($data); | ||
96 | } | ||
97 | |||
98 | sub recvmsg { | ||
99 | my $self = shift; | ||
100 | my $m; | ||
101 | my @m; | ||
102 | |||
103 | return $self->_nosetup() unless(defined($self->{-serial})); | ||
104 | |||
105 | $m = $self->_fillbuf(); | ||
106 | unless(defined($m)) { | ||
107 | # Error was set by lower levels | ||
108 | return wantarray?():undef; | ||
109 | } | ||
110 | |||
111 | printf("Fetched %s\n", $self->_hexstring($m)) if $self->{-debug}; | ||
112 | |||
113 | @m = $self->_unframe_cmd($m); | ||
114 | |||
115 | unless(@m) { | ||
116 | return undef; | ||
117 | } | ||
118 | |||
119 | if (wantarray()) { | ||
120 | return @m; | ||
121 | } else { | ||
122 | return {-lingo => $m[0], -cmd => $m[1], -payload => $m[2]}; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | sub emptyrecv { | ||
127 | my $self = shift; | ||
128 | my $m; | ||
129 | |||
130 | while ($m = $self->_fillbuf()) { | ||
131 | printf("Discarded %s\n", $self->_hexstring($m)) if (defined($m) && $self->{-debug}); | ||
132 | } | ||
133 | } | ||
134 | |||
135 | sub error { | ||
136 | my $self = shift; | ||
137 | |||
138 | return $self->{-error}; | ||
139 | } | ||
140 | |||
141 | sub _nosetup { | ||
142 | my $self = shift; | ||
143 | |||
144 | $self->{-error} = 'Serial port not setup'; | ||
145 | return undef; | ||
146 | } | ||
147 | |||
148 | sub _frame_cmd { | ||
149 | my $self = shift; | ||
150 | my $data = shift; | ||
151 | my $l = length($data); | ||
152 | my $csum; | ||
153 | |||
154 | if ($l > 0xffff) { | ||
155 | $self->{-error} = 'Command too long'; | ||
156 | return undef; | ||
157 | } | ||
158 | |||
159 | if ($l > 255) { | ||
160 | $data = pack("Cn", 0, length($data)) . $data; | ||
161 | } else { | ||
162 | $data = pack("C", length($data)) . $data; | ||
163 | } | ||
164 | |||
165 | foreach (unpack("C" x length($data), $data)) { | ||
166 | $csum += $_; | ||
167 | } | ||
168 | $csum &= 0xFF; | ||
169 | $csum = 0x100 - $csum; | ||
170 | |||
171 | return "\xFF\x55" . $data . pack("C", $csum); | ||
172 | } | ||
173 | |||
174 | sub _unframe_cmd { | ||
175 | my $self = shift; | ||
176 | my $data = shift; | ||
177 | my $payload = ''; | ||
178 | my ($count, $length, $csum); | ||
179 | my $state = 0; | ||
180 | my $c; | ||
181 | my ($lingo, $cmd); | ||
182 | |||
183 | return () unless(defined($data)); | ||
184 | |||
185 | foreach $c (unpack("C" x length($data), $data)) { | ||
186 | if ($state == 0) { | ||
187 | # Wait for sync | ||
188 | next unless($c == 255); | ||
189 | $state = 1; | ||
190 | } elsif ($state == 1) { | ||
191 | # Wait for sop | ||
192 | next unless($c == 85); | ||
193 | $state = 2; | ||
194 | } elsif ($state == 2) { | ||
195 | # Length (short frame) | ||
196 | $csum = $c; | ||
197 | if ($c == 0) { | ||
198 | # Large frame | ||
199 | $state = 3; | ||
200 | } else { | ||
201 | $state = 5; | ||
202 | } | ||
203 | $length = $c; | ||
204 | $count = 0; | ||
205 | next; | ||
206 | } elsif ($state == 3) { | ||
207 | # Large frame, hi | ||
208 | $csum += $c; | ||
209 | $length = ($c << 8); | ||
210 | $state = 4; | ||
211 | next; | ||
212 | } elsif ($state == 4) { | ||
213 | # Large frame, lo | ||
214 | $csum += $c; | ||
215 | $length |= $c; | ||
216 | if ($length == 0) { | ||
217 | $self->{-error} = 'Length is 0'; | ||
218 | return (); | ||
219 | } | ||
220 | $state = 5; | ||
221 | next; | ||
222 | } elsif ($state == 5) { | ||
223 | # Data bytes | ||
224 | $csum += $c; | ||
225 | $payload .= chr($c); | ||
226 | $count += 1; | ||
227 | if ($count == $length) { | ||
228 | $state = 6; | ||
229 | } | ||
230 | } elsif ($state == 6) { | ||
231 | # Checksum byte | ||
232 | $csum += $c; | ||
233 | if (($csum & 0xFF) != 0) { | ||
234 | $self->{-error} = 'Invalid checksum'; | ||
235 | return (); | ||
236 | } | ||
237 | $state = 7; | ||
238 | last; | ||
239 | } else { | ||
240 | $self->{-error} = 'Invalid state'; | ||
241 | return (); | ||
242 | } | ||
243 | } | ||
244 | |||
245 | # If we get here, we either have data or not. Check. | ||
246 | if ($state != 7) { | ||
247 | $self->{-error} = 'Could not unframe data'; | ||
248 | return (); | ||
249 | } | ||
250 | |||
251 | $lingo = unpack("C", $payload); | ||
252 | if ($lingo == 4) { | ||
253 | return unpack("Cna*", $payload); | ||
254 | } else { | ||
255 | return unpack("CCa*", $payload); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | sub _send { | ||
260 | my $self = shift; | ||
261 | my $data = shift; | ||
262 | my $l = length($data); | ||
263 | my $c; | ||
264 | |||
265 | printf("Sending %s\n", $self->_hexstring($data)) if $self->{-debug}; | ||
266 | |||
267 | $c = $self->{-serial}->write($data); | ||
268 | unless(defined($c)) { | ||
269 | $self->{-error} = 'write failed'; | ||
270 | return undef; | ||
271 | } | ||
272 | |||
273 | if ($c != $l) { | ||
274 | $self->{-error} = 'incomplete write'; | ||
275 | return undef; | ||
276 | } | ||
277 | |||
278 | return 1; | ||
279 | } | ||
280 | |||
281 | sub _fillbuf { | ||
282 | my $self = shift; | ||
283 | my $timeout = shift || 2; | ||
284 | my $to; | ||
285 | |||
286 | # Read from the port until we have a complete message in the buffer, | ||
287 | # or until we haven't read any new data for $timeout seconds, whatever | ||
288 | # comes first. | ||
289 | |||
290 | $to = $timeout; | ||
291 | |||
292 | while(!$self->_message_in_buffer() && $to > 0) { | ||
293 | my ($c, $s) = $self->{-serial}->read(255); | ||
294 | if ($c == 0) { | ||
295 | # No data read | ||
296 | select(undef, undef, undef, 0.1); | ||
297 | $to -= 0.1; | ||
298 | } else { | ||
299 | $self->{-inbuf} .= $s; | ||
300 | $to = $timeout; | ||
301 | } | ||
302 | } | ||
303 | if ($self->_message_in_buffer()) { | ||
304 | # There is a complete message in the buffer | ||
305 | return $self->_message(); | ||
306 | } else { | ||
307 | # Timeout occured | ||
308 | $self->{-error} = 'Timeout reading from port'; | ||
309 | return undef; | ||
310 | } | ||
311 | } | ||
312 | |||
313 | sub _message_in_buffer { | ||
314 | my $self = shift; | ||
315 | my $sp = 0; | ||
316 | my $i; | ||
317 | |||
318 | $i = index($self->{-inbuf}, "\xFF\x55", $sp); | ||
319 | while ($i != -1) { | ||
320 | my $header; | ||
321 | my $len; | ||
322 | my $large = 0; | ||
323 | |||
324 | |||
325 | $header = substr($self->{-inbuf}, $i, 3); | ||
326 | if (length($header) != 3) { | ||
327 | # Runt frame | ||
328 | return (); | ||
329 | } | ||
330 | $len = unpack("x2C", $header); | ||
331 | if ($len == 0) { | ||
332 | # Possible large frame | ||
333 | $header = substr($self->{-inbuf}, $i, 5); | ||
334 | if (length($header) != 5) { | ||
335 | # Runt frame | ||
336 | return (); | ||
337 | } | ||
338 | $large = 1; | ||
339 | $len = unpack("x3n", $header); | ||
340 | } | ||
341 | |||
342 | # Add framing, checksum and length | ||
343 | $len = $len+3+($large?3:1); | ||
344 | |||
345 | if (length($self->{-inbuf}) < ($i+$len)) { | ||
346 | # Buffer too short to hold rest of frame. Try again. | ||
347 | $sp = $i+1; | ||
348 | $i = index($self->{-inbuf}, "\xFF\x55", $sp); | ||
349 | } else { | ||
350 | return ($i, $len); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | # No complete message found | ||
355 | return (); | ||
356 | } | ||
357 | |||
358 | |||
359 | sub _message { | ||
360 | my $self = shift; | ||
361 | my $start; | ||
362 | my $len; | ||
363 | my $m; | ||
364 | |||
365 | # Return the first complete message in the buffer, removing the message | ||
366 | # and everything before it from the buffer. | ||
367 | ($start, $len) = $self->_message_in_buffer(); | ||
368 | unless(defined($start)) { | ||
369 | $self->{-error} = 'No complete message in buffer'; | ||
370 | return undef; | ||
371 | } | ||
372 | $m = substr($self->{-inbuf}, $start, $len); | ||
373 | $self->{-inbuf} = substr($self->{-inbuf}, $start+$len); | ||
374 | |||
375 | return $m; | ||
376 | } | ||
377 | |||
378 | sub _hexstring { | ||
379 | my $self = shift; | ||
380 | my $s = shift; | ||
381 | |||
382 | return join("", map { (($_ == 0x20) || isgraph(chr($_)))?chr($_):sprintf("\\x%02x", $_) } | ||
383 | unpack("C" x length($s), $s)); | ||
384 | } | ||
385 | |||
386 | 1; | ||