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 | |
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>
-rw-r--r-- | apps/SOURCES | 6 | ||||
-rw-r--r-- | apps/iap.c | 1110 | ||||
-rw-r--r-- | apps/iap/iap-core.c | 1392 | ||||
-rw-r--r-- | apps/iap/iap-core.h | 250 | ||||
-rw-r--r-- | apps/iap/iap-lingo.h | 23 | ||||
-rw-r--r-- | apps/iap/iap-lingo0.c | 1035 | ||||
-rw-r--r-- | apps/iap/iap-lingo2.c | 278 | ||||
-rw-r--r-- | apps/iap/iap-lingo3.c | 1508 | ||||
-rw-r--r-- | apps/iap/iap-lingo4.c | 3153 | ||||
-rw-r--r-- | apps/misc.c | 8 | ||||
-rw-r--r-- | firmware/export/iap.h | 4 | ||||
-rw-r--r-- | firmware/export/kernel.h | 2 | ||||
-rw-r--r-- | firmware/target/arm/pp/debug-pp.c | 9 | ||||
-rw-r--r-- | tools/iap/Device/iPod.pm | 386 | ||||
-rw-r--r-- | tools/iap/Makefile | 7 | ||||
-rw-r--r-- | tools/iap/README | 23 | ||||
-rw-r--r-- | tools/iap/device-ipod.t | 74 | ||||
-rw-r--r-- | tools/iap/iap-verbose.pl | 1856 | ||||
-rw-r--r-- | tools/iap/ipod-001-general.t | 133 | ||||
-rw-r--r-- | tools/iap/ipod-002-lingo0.t | 277 | ||||
-rw-r--r-- | tools/iap/ipod-003-lingo2.t | 220 |
21 files changed, 10629 insertions, 1125 deletions
diff --git a/apps/SOURCES b/apps/SOURCES index 8fa1a7ed40..3968666d98 100644 --- a/apps/SOURCES +++ b/apps/SOURCES | |||
@@ -62,7 +62,11 @@ tagtree.c | |||
62 | filetree.c | 62 | filetree.c |
63 | scrobbler.c | 63 | scrobbler.c |
64 | #ifdef IPOD_ACCESSORY_PROTOCOL | 64 | #ifdef IPOD_ACCESSORY_PROTOCOL |
65 | iap.c | 65 | iap/iap-core.c |
66 | iap/iap-lingo0.c | ||
67 | iap/iap-lingo2.c | ||
68 | iap/iap-lingo3.c | ||
69 | iap/iap-lingo4.c | ||
66 | #endif | 70 | #endif |
67 | 71 | ||
68 | screen_access.c | 72 | screen_access.c |
diff --git a/apps/iap.c b/apps/iap.c deleted file mode 100644 index 6fe0a03281..0000000000 --- a/apps/iap.c +++ /dev/null | |||
@@ -1,1110 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #include <stdio.h> | ||
20 | #include <stdlib.h> | ||
21 | #include <stdarg.h> | ||
22 | #include <string.h> | ||
23 | |||
24 | #include "panic.h" | ||
25 | #include "iap.h" | ||
26 | #include "button.h" | ||
27 | #include "config.h" | ||
28 | #include "cpu.h" | ||
29 | #include "system.h" | ||
30 | #include "kernel.h" | ||
31 | #include "serial.h" | ||
32 | #include "appevents.h" | ||
33 | |||
34 | #include "playlist.h" | ||
35 | #include "playback.h" | ||
36 | #include "audio.h" | ||
37 | #include "settings.h" | ||
38 | #include "metadata.h" | ||
39 | #include "wps.h" | ||
40 | #include "sound.h" | ||
41 | #include "action.h" | ||
42 | #include "powermgmt.h" | ||
43 | |||
44 | #include "tuner.h" | ||
45 | #include "ipod_remote_tuner.h" | ||
46 | |||
47 | #include "filetree.h" | ||
48 | #include "dir.h" | ||
49 | |||
50 | static volatile int iap_pollspeed = 0; | ||
51 | static volatile bool iap_remotetick = true; | ||
52 | static bool iap_setupflag = false, iap_updateflag = false; | ||
53 | static int iap_changedctr = 0; | ||
54 | |||
55 | static unsigned long iap_remotebtn = 0; | ||
56 | static int iap_repeatbtn = 0; | ||
57 | static bool iap_btnrepeat = false, iap_btnshuffle = false; | ||
58 | |||
59 | static unsigned char serbuf[RX_BUFLEN]; | ||
60 | |||
61 | static unsigned char response[TX_BUFLEN]; | ||
62 | |||
63 | static char cur_dbrecord[5] = {0}; | ||
64 | |||
65 | /* states of the iap de-framing state machine */ | ||
66 | enum fsm_state { | ||
67 | ST_SYNC, /* wait for 0xFF sync byte */ | ||
68 | ST_SOF, /* wait for 0x55 start-of-frame byte */ | ||
69 | ST_LEN, /* receive length byte (small packet) */ | ||
70 | ST_LENH, /* receive length high byte (large packet) */ | ||
71 | ST_LENL, /* receive length low byte (large packet) */ | ||
72 | ST_DATA, /* receive data */ | ||
73 | ST_CHECK /* verify checksum */ | ||
74 | }; | ||
75 | |||
76 | static struct state_t { | ||
77 | enum fsm_state state; /* current fsm state */ | ||
78 | unsigned int len; /* payload data length */ | ||
79 | unsigned char *payload; /* payload data pointer */ | ||
80 | unsigned int check; /* running checksum over [len,payload,check] */ | ||
81 | unsigned int count; /* playload bytes counter */ | ||
82 | } frame_state = { | ||
83 | .state = ST_SYNC | ||
84 | }; | ||
85 | |||
86 | static void put_u32(unsigned char *buf, uint32_t data) | ||
87 | { | ||
88 | buf[0] = (data >> 24) & 0xFF; | ||
89 | buf[1] = (data >> 16) & 0xFF; | ||
90 | buf[2] = (data >> 8) & 0xFF; | ||
91 | buf[3] = (data >> 0) & 0xFF; | ||
92 | } | ||
93 | |||
94 | static uint32_t get_u32(const unsigned char *buf) | ||
95 | { | ||
96 | return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; | ||
97 | } | ||
98 | |||
99 | static void iap_task(void) | ||
100 | { | ||
101 | static int count = 0; | ||
102 | |||
103 | count += iap_pollspeed; | ||
104 | if (count < (500/10)) return; | ||
105 | |||
106 | /* exec every 500ms if pollspeed == 1 */ | ||
107 | count = 0; | ||
108 | queue_post(&button_queue, SYS_IAP_PERIODIC, 0); | ||
109 | } | ||
110 | |||
111 | /* called by playback when the next track starts */ | ||
112 | static void iap_track_changed(void *ignored) | ||
113 | { | ||
114 | (void)ignored; | ||
115 | iap_changedctr = 1; | ||
116 | } | ||
117 | |||
118 | void iap_setup(int ratenum) | ||
119 | { | ||
120 | iap_bitrate_set(ratenum); | ||
121 | iap_pollspeed = 0; | ||
122 | iap_remotetick = true; | ||
123 | iap_updateflag = false; | ||
124 | iap_changedctr = 0; | ||
125 | iap_setupflag = true; | ||
126 | iap_remotebtn = BUTTON_NONE; | ||
127 | tick_add_task(iap_task); | ||
128 | add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed); | ||
129 | } | ||
130 | |||
131 | void iap_bitrate_set(int ratenum) | ||
132 | { | ||
133 | switch(ratenum) | ||
134 | { | ||
135 | case 0: | ||
136 | serial_bitrate(0); | ||
137 | break; | ||
138 | case 1: | ||
139 | serial_bitrate(9600); | ||
140 | break; | ||
141 | case 2: | ||
142 | serial_bitrate(19200); | ||
143 | break; | ||
144 | case 3: | ||
145 | serial_bitrate(38400); | ||
146 | break; | ||
147 | case 4: | ||
148 | serial_bitrate(57600); | ||
149 | break; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | /* Message format: | ||
154 | 0xff | ||
155 | 0x55 | ||
156 | length | ||
157 | mode | ||
158 | command (2 bytes) | ||
159 | parameters (0-n bytes) | ||
160 | checksum (length+mode+parameters+checksum == 0) | ||
161 | */ | ||
162 | |||
163 | void iap_send_pkt(const unsigned char * data, int len) | ||
164 | { | ||
165 | int i, chksum, responselen; | ||
166 | |||
167 | if(len > TX_BUFLEN-4) len = TX_BUFLEN-4; | ||
168 | responselen = len + 4; | ||
169 | |||
170 | response[0] = 0xFF; | ||
171 | response[1] = 0x55; | ||
172 | |||
173 | chksum = response[2] = len; | ||
174 | for(i = 0; i < len; i ++) | ||
175 | { | ||
176 | chksum += data[i]; | ||
177 | response[i+3] = data[i]; | ||
178 | } | ||
179 | |||
180 | response[i+3] = 0x100 - (chksum & 0xFF); | ||
181 | |||
182 | for(i = 0; i < responselen; i ++) | ||
183 | { | ||
184 | while (!tx_rdy()) ; | ||
185 | tx_writec(response[i]); | ||
186 | } | ||
187 | } | ||
188 | |||
189 | bool iap_getc(unsigned char x) | ||
190 | { | ||
191 | struct state_t *s = &frame_state; | ||
192 | |||
193 | /* run state machine to detect and extract a valid frame */ | ||
194 | switch (s->state) { | ||
195 | case ST_SYNC: | ||
196 | if (x == 0xFF) { | ||
197 | s->state = ST_SOF; | ||
198 | } | ||
199 | break; | ||
200 | case ST_SOF: | ||
201 | if (x == 0x55) { | ||
202 | /* received a valid sync/SOF pair */ | ||
203 | s->state = ST_LEN; | ||
204 | } else { | ||
205 | s->state = ST_SYNC; | ||
206 | return iap_getc(x); | ||
207 | } | ||
208 | break; | ||
209 | case ST_LEN: | ||
210 | s->check = x; | ||
211 | s->count = 0; | ||
212 | s->payload = serbuf; | ||
213 | if (x == 0) { | ||
214 | /* large packet */ | ||
215 | s->state = ST_LENH; | ||
216 | } else { | ||
217 | /* small packet */ | ||
218 | s->len = x; | ||
219 | s->state = ST_DATA; | ||
220 | } | ||
221 | break; | ||
222 | case ST_LENH: | ||
223 | s->check += x; | ||
224 | s->len = x << 8; | ||
225 | s->state = ST_LENL; | ||
226 | break; | ||
227 | case ST_LENL: | ||
228 | s->check += x; | ||
229 | s->len += x; | ||
230 | if ((s->len == 0) || (s->len > RX_BUFLEN)) { | ||
231 | /* invalid length */ | ||
232 | s->state = ST_SYNC; | ||
233 | return iap_getc(x); | ||
234 | } else { | ||
235 | s->state = ST_DATA; | ||
236 | } | ||
237 | break; | ||
238 | case ST_DATA: | ||
239 | s->check += x; | ||
240 | s->payload[s->count++] = x; | ||
241 | if (s->count == s->len) { | ||
242 | s->state = ST_CHECK; | ||
243 | } | ||
244 | break; | ||
245 | case ST_CHECK: | ||
246 | s->check += x; | ||
247 | if ((s->check & 0xFF) == 0) { | ||
248 | /* done, received a valid frame */ | ||
249 | queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0); | ||
250 | } | ||
251 | s->state = ST_SYNC; | ||
252 | break; | ||
253 | default: | ||
254 | panicf("Unhandled iap state %d", (int) s->state); | ||
255 | break; | ||
256 | } | ||
257 | |||
258 | /* return true while still hunting for the sync and start-of-frame byte */ | ||
259 | return (s->state == ST_SYNC) || (s->state == ST_SOF); | ||
260 | } | ||
261 | |||
262 | void iap_periodic(void) | ||
263 | { | ||
264 | if(!iap_setupflag) return; | ||
265 | if(!iap_pollspeed) return; | ||
266 | |||
267 | /* PlayStatusChangeNotification */ | ||
268 | unsigned char data[] = {0x04, 0x00, 0x27, 0x04, 0x00, 0x00, 0x00, 0x00}; | ||
269 | unsigned long time_elapsed = audio_current_track()->elapsed; | ||
270 | |||
271 | time_elapsed += wps_get_ff_rewind_count(); | ||
272 | |||
273 | data[3] = 0x04; /* playing */ | ||
274 | |||
275 | /* If info has changed, don't flag it right away */ | ||
276 | if(iap_changedctr && iap_changedctr++ >= iap_pollspeed * 2) | ||
277 | { | ||
278 | /* track info has changed */ | ||
279 | iap_changedctr = 0; | ||
280 | data[3] = 0x01; /* 0x02 has same effect? */ | ||
281 | iap_updateflag = true; | ||
282 | } | ||
283 | |||
284 | put_u32(&data[4], time_elapsed); | ||
285 | iap_send_pkt(data, sizeof(data)); | ||
286 | } | ||
287 | |||
288 | static void iap_set_remote_volume(void) | ||
289 | { | ||
290 | unsigned char data[] = {0x03, 0x0D, 0x04, 0x00, 0x00}; | ||
291 | data[4] = (char)((global_settings.volume+58) * 4); | ||
292 | iap_send_pkt(data, sizeof(data)); | ||
293 | } | ||
294 | |||
295 | static void cmd_ok_mode0(unsigned char cmd) | ||
296 | { | ||
297 | unsigned char data[] = {0x00, 0x02, 0x00, 0x00}; | ||
298 | data[3] = cmd; /* respond with cmd */ | ||
299 | iap_send_pkt(data, sizeof(data)); | ||
300 | } | ||
301 | |||
302 | static void iap_handlepkt_mode0(unsigned int len, const unsigned char *buf) | ||
303 | { | ||
304 | (void)len; /* len currently unused */ | ||
305 | |||
306 | unsigned int cmd = buf[1]; | ||
307 | switch (cmd) { | ||
308 | /* Identify */ | ||
309 | case 0x01: | ||
310 | { | ||
311 | /* FM transmitter sends this: */ | ||
312 | /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */ | ||
313 | if(buf[2] == 0x05) | ||
314 | { | ||
315 | sleep(HZ/3); | ||
316 | /* RF Transmitter: Begin transmission */ | ||
317 | unsigned char data[] = {0x05, 0x02}; | ||
318 | iap_send_pkt(data, sizeof(data)); | ||
319 | } | ||
320 | /* FM remote sends this: */ | ||
321 | /* FF 55 03 00 01 02 FA (1st thing sent) */ | ||
322 | else if (buf[2] == 0x02) | ||
323 | { | ||
324 | /* useful only for apple firmware */ | ||
325 | } | ||
326 | break; | ||
327 | } | ||
328 | |||
329 | /* EnterRemoteUIMode, FM transmitter sends FF 55 02 00 05 F9 */ | ||
330 | case 0x05: | ||
331 | { | ||
332 | /* ACK Pending (3000 ms) */ | ||
333 | unsigned char data[] = {0x00, 0x02, 0x06, | ||
334 | 0x05, 0x00, 0x00, 0x0B, 0xB8}; | ||
335 | iap_send_pkt(data, sizeof(data)); | ||
336 | cmd_ok_mode0(cmd); | ||
337 | break; | ||
338 | } | ||
339 | |||
340 | /* ExitRemoteUIMode */ | ||
341 | case 0x06: | ||
342 | { | ||
343 | audio_stop(); | ||
344 | cmd_ok_mode0(cmd); | ||
345 | break; | ||
346 | } | ||
347 | |||
348 | /* RequestiPodSoftwareVersion, Ipod FM remote sends FF 55 02 00 09 F5 */ | ||
349 | case 0x09: | ||
350 | { | ||
351 | /* ReturniPodSoftwareVersion, ipod5G firmware version */ | ||
352 | unsigned char data[] = {0x00, 0x0A, 0x01, 0x02, 0x01}; | ||
353 | iap_send_pkt(data, sizeof(data)); | ||
354 | break; | ||
355 | } | ||
356 | |||
357 | /* RequestiPodModelNum */ | ||
358 | case 0x0D: | ||
359 | { | ||
360 | /* ipod is supposed to work only with 5G and nano 2G */ | ||
361 | /*{0x00, 0x0E, 0x00, 0x0B, 0x00, 0x05, 0x50, 0x41, 0x31, 0x34, | ||
362 | 0x37, 0x4C, 0x4C, 0x00}; PA147LL (IPOD 5G 60 GO) */ | ||
363 | /* ReturniPodModelNum */ | ||
364 | unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10, | ||
365 | 'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00}; | ||
366 | iap_send_pkt(data, sizeof(data)); | ||
367 | break; | ||
368 | } | ||
369 | |||
370 | /* RequestLingoProtocolVersion */ | ||
371 | case 0x0F: | ||
372 | { | ||
373 | /* ReturnLingoProtocolVersion */ | ||
374 | unsigned char data[] = {0x00, 0x10, 0x00, 0x01, 0x05}; | ||
375 | data[2] = buf[2]; | ||
376 | iap_send_pkt(data, sizeof(data)); | ||
377 | break; | ||
378 | } | ||
379 | |||
380 | /* IdentifyDeviceLingoes */ | ||
381 | case 0x13: | ||
382 | { | ||
383 | cmd_ok_mode0(cmd); | ||
384 | |||
385 | uint32_t lingoes = get_u32(&buf[2]); | ||
386 | |||
387 | if (lingoes == 0x35) | ||
388 | /* FM transmitter sends this: */ | ||
389 | /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/ | ||
390 | { | ||
391 | /* GetAccessoryInfo */ | ||
392 | unsigned char data2[] = {0x00, 0x27, 0x00}; | ||
393 | iap_send_pkt(data2, sizeof(data2)); | ||
394 | /* RF Transmitter: Begin transmission */ | ||
395 | unsigned char data3[] = {0x05, 0x02}; | ||
396 | iap_send_pkt(data3, sizeof(data3)); | ||
397 | } | ||
398 | else | ||
399 | { | ||
400 | /* ipod fm remote sends this: */ | ||
401 | /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */ | ||
402 | if (lingoes & (1 << 7)) /* bit 7 = RF tuner lingo */ | ||
403 | radio_present = 1; | ||
404 | /* GetDevAuthenticationInfo */ | ||
405 | unsigned char data4[] = {0x00, 0x14}; | ||
406 | iap_send_pkt(data4, sizeof(data4)); | ||
407 | } | ||
408 | break; | ||
409 | } | ||
410 | |||
411 | /* RetDevAuthenticationInfo */ | ||
412 | case 0x15: | ||
413 | { | ||
414 | /* AckDevAuthenticationInfo */ | ||
415 | unsigned char data0[] = {0x00, 0x16, 0x00}; | ||
416 | iap_send_pkt(data0, sizeof(data0)); | ||
417 | /* GetAccessoryInfo */ | ||
418 | unsigned char data1[] = {0x00, 0x27, 0x00}; | ||
419 | iap_send_pkt(data1, sizeof(data1)); | ||
420 | /* AckDevAuthenticationStatus, mandatory to enable some hardware */ | ||
421 | unsigned char data2[] = {0x00, 0x19, 0x00}; | ||
422 | iap_send_pkt(data2, sizeof(data2)); | ||
423 | if (radio_present == 1) | ||
424 | { | ||
425 | /* GetTunerCaps */ | ||
426 | unsigned char data3[] = {0x07, 0x01}; | ||
427 | iap_send_pkt(data3, sizeof(data3)); | ||
428 | } | ||
429 | iap_set_remote_volume(); | ||
430 | break; | ||
431 | } | ||
432 | |||
433 | /* RetDevAuthenticationSignature */ | ||
434 | case 0x18: | ||
435 | { | ||
436 | /* Isn't used since we don't send the 0x00 0x17 command */ | ||
437 | break; | ||
438 | } | ||
439 | |||
440 | /* GetIpodOptions */ | ||
441 | case 0x24: | ||
442 | { | ||
443 | /* RetIpodOptions (ipod video send this) */ | ||
444 | unsigned char data[] = {0x00, 0x25, 0x00, 0x00, 0x00, | ||
445 | 0x00, 0x00, 0x00, 0x00, 0x01}; | ||
446 | iap_send_pkt(data, sizeof(data)); | ||
447 | break; | ||
448 | } | ||
449 | |||
450 | /* default response is with cmd ok packet */ | ||
451 | default: | ||
452 | { | ||
453 | cmd_ok_mode0(cmd); | ||
454 | break; | ||
455 | } | ||
456 | } | ||
457 | } | ||
458 | |||
459 | static void iap_handlepkt_mode2(unsigned int len, const unsigned char *buf) | ||
460 | { | ||
461 | if(buf[1] != 0) return; | ||
462 | iap_remotebtn = BUTTON_NONE; | ||
463 | iap_remotetick = false; | ||
464 | |||
465 | if(len >= 3 && buf[2] != 0) | ||
466 | { | ||
467 | if(buf[2] & 1) | ||
468 | iap_remotebtn |= BUTTON_RC_PLAY; | ||
469 | if(buf[2] & 2) | ||
470 | iap_remotebtn |= BUTTON_RC_VOL_UP; | ||
471 | if(buf[2] & 4) | ||
472 | iap_remotebtn |= BUTTON_RC_VOL_DOWN; | ||
473 | if(buf[2] & 8) | ||
474 | iap_remotebtn |= BUTTON_RC_RIGHT; | ||
475 | if(buf[2] & 16) | ||
476 | iap_remotebtn |= BUTTON_RC_LEFT; | ||
477 | } | ||
478 | else if(len >= 4 && buf[3] != 0) | ||
479 | { | ||
480 | if(buf[3] & 1) /* play */ | ||
481 | { | ||
482 | if (audio_status() != AUDIO_STATUS_PLAY) | ||
483 | { | ||
484 | iap_remotebtn |= BUTTON_RC_PLAY; | ||
485 | iap_repeatbtn = 2; | ||
486 | iap_remotetick = false; | ||
487 | iap_changedctr = 1; | ||
488 | } | ||
489 | } | ||
490 | if(buf[3] & 2) /* pause */ | ||
491 | { | ||
492 | if (audio_status() == AUDIO_STATUS_PLAY) | ||
493 | { | ||
494 | iap_remotebtn |= BUTTON_RC_PLAY; | ||
495 | iap_repeatbtn = 2; | ||
496 | iap_remotetick = false; | ||
497 | iap_changedctr = 1; | ||
498 | } | ||
499 | } | ||
500 | if((buf[3] & 128) && !iap_btnshuffle) /* shuffle */ | ||
501 | { | ||
502 | iap_btnshuffle = true; | ||
503 | if(!global_settings.playlist_shuffle) | ||
504 | { | ||
505 | global_settings.playlist_shuffle = 1; | ||
506 | settings_save(); | ||
507 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
508 | playlist_randomise(NULL, current_tick, true); | ||
509 | } | ||
510 | else if(global_settings.playlist_shuffle) | ||
511 | { | ||
512 | global_settings.playlist_shuffle = 0; | ||
513 | settings_save(); | ||
514 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
515 | playlist_sort(NULL, true); | ||
516 | } | ||
517 | } | ||
518 | else | ||
519 | iap_btnshuffle = false; | ||
520 | } | ||
521 | else if(len >= 5 && buf[4] != 0) | ||
522 | { | ||
523 | if((buf[4] & 1) && !iap_btnrepeat) /* repeat */ | ||
524 | { | ||
525 | int oldmode = global_settings.repeat_mode; | ||
526 | iap_btnrepeat = true; | ||
527 | |||
528 | if (oldmode == REPEAT_ONE) | ||
529 | global_settings.repeat_mode = REPEAT_OFF; | ||
530 | else if (oldmode == REPEAT_ALL) | ||
531 | global_settings.repeat_mode = REPEAT_ONE; | ||
532 | else if (oldmode == REPEAT_OFF) | ||
533 | global_settings.repeat_mode = REPEAT_ALL; | ||
534 | |||
535 | settings_save(); | ||
536 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
537 | audio_flush_and_reload_tracks(); | ||
538 | } | ||
539 | else | ||
540 | iap_btnrepeat = false; | ||
541 | |||
542 | if(buf[4] & 16) /* ffwd */ | ||
543 | { | ||
544 | iap_remotebtn |= BUTTON_RC_RIGHT; | ||
545 | } | ||
546 | if(buf[4] & 32) /* frwd */ | ||
547 | { | ||
548 | iap_remotebtn |= BUTTON_RC_LEFT; | ||
549 | } | ||
550 | } | ||
551 | } | ||
552 | |||
553 | static void iap_handlepkt_mode3(unsigned int len, const unsigned char *buf) | ||
554 | { | ||
555 | (void)len; /* len currently unused */ | ||
556 | |||
557 | unsigned int cmd = buf[1]; | ||
558 | switch (cmd) | ||
559 | { | ||
560 | /* GetCurrentEQProfileIndex */ | ||
561 | case 0x01: | ||
562 | { | ||
563 | /* RetCurrentEQProfileIndex */ | ||
564 | unsigned char data[] = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00}; | ||
565 | iap_send_pkt(data, sizeof(data)); | ||
566 | break; | ||
567 | } | ||
568 | |||
569 | /* SetRemoteEventNotification */ | ||
570 | case 0x08: | ||
571 | { | ||
572 | /* ACK */ | ||
573 | unsigned char data[] = {0x03, 0x00, 0x00, 0x08}; | ||
574 | iap_send_pkt(data, sizeof(data)); | ||
575 | break; | ||
576 | } | ||
577 | |||
578 | /* GetiPodStateInfo */ | ||
579 | case 0x0C: | ||
580 | { | ||
581 | /* request ipod volume */ | ||
582 | if (buf[2] == 0x04) | ||
583 | { | ||
584 | iap_set_remote_volume(); | ||
585 | } | ||
586 | break; | ||
587 | } | ||
588 | |||
589 | /* SetiPodStateInfo */ | ||
590 | case 0x0E: | ||
591 | { | ||
592 | if (buf[2] == 0x04) | ||
593 | global_settings.volume = (-58)+((int)buf[4]+1)/4; | ||
594 | sound_set_volume(global_settings.volume); /* indent BUG? */ | ||
595 | break; | ||
596 | } | ||
597 | } | ||
598 | } | ||
599 | |||
600 | static void cmd_ok_mode4(unsigned int cmd) | ||
601 | { | ||
602 | unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x00}; | ||
603 | data[4] = (cmd >> 8) & 0xFF; | ||
604 | data[5] = (cmd >> 0) & 0xFF; | ||
605 | iap_send_pkt(data, sizeof(data)); | ||
606 | } | ||
607 | |||
608 | static void get_playlist_name(unsigned char *dest, | ||
609 | unsigned long item_offset, | ||
610 | size_t max_length) | ||
611 | { | ||
612 | if (item_offset == 0) return; | ||
613 | DIR* dp; | ||
614 | struct dirent* playlist_file = NULL; | ||
615 | |||
616 | dp = opendir(global_settings.playlist_catalog_dir); | ||
617 | |||
618 | char *extension; | ||
619 | unsigned long nbr = 0; | ||
620 | while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL)) | ||
621 | { | ||
622 | /*Increment only if there is a playlist extension*/ | ||
623 | if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){ | ||
624 | if ((strcmp(extension, ".m3u") == 0 || | ||
625 | strcmp(extension, ".m3u8") == 0)) | ||
626 | nbr++; | ||
627 | } | ||
628 | } | ||
629 | if (playlist_file != NULL) { | ||
630 | strlcpy(dest, playlist_file->d_name, max_length); | ||
631 | } | ||
632 | closedir(dp); | ||
633 | } | ||
634 | |||
635 | static void iap_handlepkt_mode4(unsigned int len, const unsigned char *buf) | ||
636 | { | ||
637 | (void)len; /* len currently unused */ | ||
638 | |||
639 | unsigned int cmd = (buf[1] << 8) | buf[2]; | ||
640 | switch (cmd) | ||
641 | { | ||
642 | /* GetAudioBookSpeed */ | ||
643 | case 0x0009: | ||
644 | { | ||
645 | /* ReturnAudioBookSpeed */ | ||
646 | unsigned char data[] = {0x04, 0x00, 0x0A, 0x00}; | ||
647 | data[3] = iap_updateflag ? 0 : 1; | ||
648 | iap_send_pkt(data, sizeof(data)); | ||
649 | break; | ||
650 | } | ||
651 | |||
652 | /* SetAudioBookSpeed */ | ||
653 | case 0x000B: | ||
654 | { | ||
655 | iap_updateflag = buf[3] ? 0 : 1; | ||
656 | /* respond with cmd ok packet */ | ||
657 | cmd_ok_mode4(cmd); | ||
658 | break; | ||
659 | } | ||
660 | |||
661 | /* RequestProtocolVersion */ | ||
662 | case 0x0012: | ||
663 | { | ||
664 | /* ReturnProtocolVersion */ | ||
665 | unsigned char data[] = {0x04, 0x00, 0x13, 0x01, 0x0B}; | ||
666 | iap_send_pkt(data, sizeof(data)); | ||
667 | break; | ||
668 | } | ||
669 | |||
670 | /* SelectDBRecord */ | ||
671 | case 0x0017: | ||
672 | { | ||
673 | memcpy(cur_dbrecord, buf + 3, 5); | ||
674 | cmd_ok_mode4(cmd); | ||
675 | break; | ||
676 | } | ||
677 | |||
678 | /* GetNumberCategorizedDBRecords */ | ||
679 | case 0x0018: | ||
680 | { | ||
681 | /* ReturnNumberCategorizedDBRecords */ | ||
682 | unsigned char data[] = {0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00}; | ||
683 | unsigned long num = 0; | ||
684 | |||
685 | DIR* dp; | ||
686 | unsigned long nbr_total_playlists = 0; | ||
687 | struct dirent* playlist_file = NULL; | ||
688 | char *extension; | ||
689 | |||
690 | switch(buf[3]) /* type number */ | ||
691 | { | ||
692 | case 0x01: /* total number of playlists */ | ||
693 | dp = opendir(global_settings.playlist_catalog_dir); | ||
694 | while ((playlist_file = readdir(dp)) != NULL) | ||
695 | { | ||
696 | /*Increment only if there is a playlist extension*/ | ||
697 | if ((extension=strrchr(playlist_file->d_name, '.')) | ||
698 | != NULL) { | ||
699 | if ((strcmp(extension, ".m3u") == 0 || | ||
700 | strcmp(extension, ".m3u8") == 0)) | ||
701 | nbr_total_playlists++; | ||
702 | } | ||
703 | } | ||
704 | closedir(dp); | ||
705 | /*Add 1 for the main playlist*/ | ||
706 | num = nbr_total_playlists + 1; | ||
707 | break; | ||
708 | case 0x05: /* total number of songs */ | ||
709 | num = 1; | ||
710 | break; | ||
711 | } | ||
712 | put_u32(&data[3], num); | ||
713 | iap_send_pkt(data, sizeof(data)); | ||
714 | break; | ||
715 | } | ||
716 | |||
717 | /* RetrieveCategorizedDatabaseRecords */ | ||
718 | case 0x001A: | ||
719 | { | ||
720 | /* ReturnCategorizedDatabaseRecord */ | ||
721 | unsigned char data[7 + MAX_PATH] = | ||
722 | {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, | ||
723 | 'R', 'O', 'C', 'K', 'B', 'O', 'X', '\0'}; | ||
724 | |||
725 | unsigned long item_offset = get_u32(&buf[4]); | ||
726 | |||
727 | get_playlist_name(data + 7, item_offset, MAX_PATH); | ||
728 | /*Remove file extension*/ | ||
729 | char *dot=NULL; | ||
730 | dot = (strrchr(data+7, '.')); | ||
731 | if (dot != NULL) | ||
732 | *dot = '\0'; | ||
733 | iap_send_pkt(data, 7 + strlen(data+7) + 1); | ||
734 | break; | ||
735 | } | ||
736 | |||
737 | /* GetPlayStatus */ | ||
738 | case 0x001C: | ||
739 | { | ||
740 | /* ReturnPlayStatus */ | ||
741 | unsigned char data[] = {0x04, 0x00, 0x1D, 0x00, 0x00, 0x00, | ||
742 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
743 | struct mp3entry *id3 = audio_current_track(); | ||
744 | unsigned long time_total = id3->length; | ||
745 | unsigned long time_elapsed = id3->elapsed; | ||
746 | int status = audio_status(); | ||
747 | put_u32(&data[3], time_total); | ||
748 | put_u32(&data[7], time_elapsed); | ||
749 | if (status == AUDIO_STATUS_PLAY) | ||
750 | data[11] = 0x01; /* play */ | ||
751 | else if (status & AUDIO_STATUS_PAUSE) | ||
752 | data[11] = 0x02; /* pause */ | ||
753 | iap_send_pkt(data, sizeof(data)); | ||
754 | break; | ||
755 | } | ||
756 | |||
757 | /* GetCurrentPlayingTrackIndex */ | ||
758 | case 0x001E: | ||
759 | { | ||
760 | /* ReturnCurrentPlayingTrackIndex */ | ||
761 | unsigned char data[] = {0x04, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00}; | ||
762 | long playlist_pos = playlist_next(0); | ||
763 | playlist_pos -= playlist_get_first_index(NULL); | ||
764 | if(playlist_pos < 0) | ||
765 | playlist_pos += playlist_amount(); | ||
766 | put_u32(&data[3], playlist_pos); | ||
767 | iap_send_pkt(data, sizeof(data)); | ||
768 | break; | ||
769 | } | ||
770 | |||
771 | /* GetIndexedPlayingTrackTitle */ | ||
772 | case 0x0020: | ||
773 | /* GetIndexedPlayingTrackArtistName */ | ||
774 | case 0x0022: | ||
775 | /* GetIndexedPlayingTrackAlbumName */ | ||
776 | case 0x0024: | ||
777 | { | ||
778 | unsigned char data[70] = {0x04, 0x00, 0xFF}; | ||
779 | struct mp3entry id3; | ||
780 | int fd; | ||
781 | size_t len; | ||
782 | long tracknum = get_u32(&buf[3]); | ||
783 | |||
784 | data[2] = cmd + 1; | ||
785 | memcpy(&id3, audio_current_track(), sizeof(id3)); | ||
786 | tracknum += playlist_get_first_index(NULL); | ||
787 | if(tracknum >= playlist_amount()) | ||
788 | tracknum -= playlist_amount(); | ||
789 | |||
790 | /* If the tracknumber is not the current one, | ||
791 | read id3 from disk */ | ||
792 | if(playlist_next(0) != tracknum) | ||
793 | { | ||
794 | struct playlist_track_info info; | ||
795 | playlist_get_track_info(NULL, tracknum, &info); | ||
796 | fd = open(info.filename, O_RDONLY); | ||
797 | memset(&id3, 0, sizeof(struct mp3entry)); | ||
798 | get_metadata(&id3, fd, info.filename); | ||
799 | close(fd); | ||
800 | } | ||
801 | |||
802 | /* Return the requested track data */ | ||
803 | switch(cmd) | ||
804 | { | ||
805 | case 0x20: | ||
806 | len = strlcpy((char *)&data[3], id3.title, 64); | ||
807 | iap_send_pkt(data, 4+len); | ||
808 | break; | ||
809 | case 0x22: | ||
810 | len = strlcpy((char *)&data[3], id3.artist, 64); | ||
811 | iap_send_pkt(data, 4+len); | ||
812 | break; | ||
813 | case 0x24: | ||
814 | len = strlcpy((char *)&data[3], id3.album, 64); | ||
815 | iap_send_pkt(data, 4+len); | ||
816 | break; | ||
817 | } | ||
818 | break; | ||
819 | } | ||
820 | |||
821 | /* SetPlayStatusChangeNotification */ | ||
822 | case 0x0026: | ||
823 | { | ||
824 | iap_pollspeed = buf[3] ? 1 : 0; | ||
825 | /* respond with cmd ok packet */ | ||
826 | cmd_ok_mode4(cmd); | ||
827 | break; | ||
828 | } | ||
829 | |||
830 | /* PlayCurrentSelection */ | ||
831 | case 0x0028: | ||
832 | { | ||
833 | switch (cur_dbrecord[0]) | ||
834 | { | ||
835 | case 0x01: | ||
836 | {/*Playlist*/ | ||
837 | unsigned long item_offset = get_u32(&cur_dbrecord[1]); | ||
838 | |||
839 | unsigned char selected_playlist | ||
840 | [sizeof(global_settings.playlist_catalog_dir) | ||
841 | + 1 | ||
842 | + MAX_PATH] = {0}; | ||
843 | |||
844 | strcpy(selected_playlist, | ||
845 | global_settings.playlist_catalog_dir); | ||
846 | int len = strlen(selected_playlist); | ||
847 | selected_playlist[len] = '/'; | ||
848 | get_playlist_name (selected_playlist + len + 1, | ||
849 | item_offset, | ||
850 | MAX_PATH); | ||
851 | ft_play_playlist(selected_playlist, | ||
852 | global_settings.playlist_catalog_dir, | ||
853 | strrchr(selected_playlist, '/') + 1); | ||
854 | break; | ||
855 | } | ||
856 | } | ||
857 | cmd_ok_mode4(cmd); | ||
858 | break; | ||
859 | } | ||
860 | |||
861 | /* PlayControl */ | ||
862 | case 0x0029: | ||
863 | { | ||
864 | switch(buf[3]) | ||
865 | { | ||
866 | case 0x01: /* play/pause */ | ||
867 | iap_remotebtn = BUTTON_RC_PLAY; | ||
868 | iap_repeatbtn = 2; | ||
869 | iap_remotetick = false; | ||
870 | iap_changedctr = 1; | ||
871 | break; | ||
872 | case 0x02: /* stop */ | ||
873 | iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT; | ||
874 | iap_repeatbtn = 2; | ||
875 | iap_remotetick = false; | ||
876 | iap_changedctr = 1; | ||
877 | break; | ||
878 | case 0x03: /* skip++ */ | ||
879 | iap_remotebtn = BUTTON_RC_RIGHT; | ||
880 | iap_repeatbtn = 2; | ||
881 | iap_remotetick = false; | ||
882 | break; | ||
883 | case 0x04: /* skip-- */ | ||
884 | iap_remotebtn = BUTTON_RC_LEFT; | ||
885 | iap_repeatbtn = 2; | ||
886 | iap_remotetick = false; | ||
887 | break; | ||
888 | case 0x05: /* ffwd */ | ||
889 | iap_remotebtn = BUTTON_RC_RIGHT; | ||
890 | iap_remotetick = false; | ||
891 | if(iap_pollspeed) iap_pollspeed = 5; | ||
892 | break; | ||
893 | case 0x06: /* frwd */ | ||
894 | iap_remotebtn = BUTTON_RC_LEFT; | ||
895 | iap_remotetick = false; | ||
896 | if(iap_pollspeed) iap_pollspeed = 5; | ||
897 | break; | ||
898 | case 0x07: /* end ffwd/frwd */ | ||
899 | iap_remotebtn = BUTTON_NONE; | ||
900 | iap_remotetick = false; | ||
901 | if(iap_pollspeed) iap_pollspeed = 1; | ||
902 | break; | ||
903 | } | ||
904 | /* respond with cmd ok packet */ | ||
905 | cmd_ok_mode4(cmd); | ||
906 | break; | ||
907 | } | ||
908 | |||
909 | /* GetShuffle */ | ||
910 | case 0x002C: | ||
911 | { | ||
912 | /* ReturnShuffle */ | ||
913 | unsigned char data[] = {0x04, 0x00, 0x2D, 0x00}; | ||
914 | data[3] = global_settings.playlist_shuffle ? 1 : 0; | ||
915 | iap_send_pkt(data, sizeof(data)); | ||
916 | break; | ||
917 | } | ||
918 | |||
919 | /* SetShuffle */ | ||
920 | case 0x002E: | ||
921 | { | ||
922 | if(buf[3] && !global_settings.playlist_shuffle) | ||
923 | { | ||
924 | global_settings.playlist_shuffle = 1; | ||
925 | settings_save(); | ||
926 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
927 | playlist_randomise(NULL, current_tick, true); | ||
928 | } | ||
929 | else if(!buf[3] && global_settings.playlist_shuffle) | ||
930 | { | ||
931 | global_settings.playlist_shuffle = 0; | ||
932 | settings_save(); | ||
933 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
934 | playlist_sort(NULL, true); | ||
935 | } | ||
936 | |||
937 | /* respond with cmd ok packet */ | ||
938 | cmd_ok_mode4(cmd); | ||
939 | break; | ||
940 | } | ||
941 | |||
942 | /* GetRepeat */ | ||
943 | case 0x002F: | ||
944 | { | ||
945 | /* ReturnRepeat */ | ||
946 | unsigned char data[] = {0x04, 0x00, 0x30, 0x00}; | ||
947 | if(global_settings.repeat_mode == REPEAT_OFF) | ||
948 | data[3] = 0; | ||
949 | else if(global_settings.repeat_mode == REPEAT_ONE) | ||
950 | data[3] = 1; | ||
951 | else | ||
952 | data[3] = 2; | ||
953 | iap_send_pkt(data, sizeof(data)); | ||
954 | break; | ||
955 | } | ||
956 | |||
957 | /* SetRepeat */ | ||
958 | case 0x0031: | ||
959 | { | ||
960 | int oldmode = global_settings.repeat_mode; | ||
961 | if (buf[3] == 0) | ||
962 | global_settings.repeat_mode = REPEAT_OFF; | ||
963 | else if (buf[3] == 1) | ||
964 | global_settings.repeat_mode = REPEAT_ONE; | ||
965 | else if (buf[3] == 2) | ||
966 | global_settings.repeat_mode = REPEAT_ALL; | ||
967 | |||
968 | if (oldmode != global_settings.repeat_mode) | ||
969 | { | ||
970 | settings_save(); | ||
971 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
972 | audio_flush_and_reload_tracks(); | ||
973 | } | ||
974 | |||
975 | /* respond with cmd ok packet */ | ||
976 | cmd_ok_mode4(cmd); | ||
977 | break; | ||
978 | } | ||
979 | |||
980 | /* GetMonoDisplayImageLimits */ | ||
981 | case 0x0033: | ||
982 | { | ||
983 | /* ReturnMonoDisplayImageLimits */ | ||
984 | unsigned char data[] = {0x04, 0x00, 0x34, | ||
985 | LCD_WIDTH >> 8, LCD_WIDTH & 0xff, | ||
986 | LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff, | ||
987 | 0x01}; | ||
988 | iap_send_pkt(data, sizeof(data)); | ||
989 | break; | ||
990 | } | ||
991 | |||
992 | /* GetNumPlayingTracks */ | ||
993 | case 0x0035: | ||
994 | { | ||
995 | /* ReturnNumPlayingTracks */ | ||
996 | unsigned char data[] = {0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00}; | ||
997 | unsigned long playlist_amt = playlist_amount(); | ||
998 | put_u32(&data[3], playlist_amt); | ||
999 | iap_send_pkt(data, sizeof(data)); | ||
1000 | break; | ||
1001 | } | ||
1002 | |||
1003 | /* SetCurrentPlayingTrack */ | ||
1004 | case 0x0037: | ||
1005 | { | ||
1006 | int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE)); | ||
1007 | long tracknum = get_u32(&buf[3]); | ||
1008 | |||
1009 | audio_pause(); | ||
1010 | audio_skip(tracknum - playlist_next(0)); | ||
1011 | if (!paused) | ||
1012 | audio_resume(); | ||
1013 | |||
1014 | /* respond with cmd ok packet */ | ||
1015 | cmd_ok_mode4(cmd); | ||
1016 | break; | ||
1017 | } | ||
1018 | |||
1019 | default: | ||
1020 | { | ||
1021 | /* default response is with cmd ok packet */ | ||
1022 | cmd_ok_mode4(cmd); | ||
1023 | break; | ||
1024 | } | ||
1025 | } | ||
1026 | } | ||
1027 | |||
1028 | static void iap_handlepkt_mode7(unsigned int len, const unsigned char *buf) | ||
1029 | { | ||
1030 | unsigned int cmd = buf[1]; | ||
1031 | switch (cmd) | ||
1032 | { | ||
1033 | /* RetTunerCaps */ | ||
1034 | case 0x02: | ||
1035 | { | ||
1036 | /* do nothing */ | ||
1037 | |||
1038 | /* GetAccessoryInfo */ | ||
1039 | unsigned char data[] = {0x00, 0x27, 0x00}; | ||
1040 | iap_send_pkt(data, sizeof(data)); | ||
1041 | break; | ||
1042 | } | ||
1043 | |||
1044 | /* RetTunerFreq */ | ||
1045 | case 0x0A: | ||
1046 | /* fall through */ | ||
1047 | /* TunerSeekDone */ | ||
1048 | case 0x13: | ||
1049 | { | ||
1050 | rmt_tuner_freq(len, buf); | ||
1051 | break; | ||
1052 | } | ||
1053 | |||
1054 | /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/ | ||
1055 | case 0x21: | ||
1056 | { | ||
1057 | rmt_tuner_rds_data(len, buf); | ||
1058 | break; | ||
1059 | } | ||
1060 | } | ||
1061 | } | ||
1062 | |||
1063 | void iap_handlepkt(void) | ||
1064 | { | ||
1065 | struct state_t *s = &frame_state; | ||
1066 | |||
1067 | if(!iap_setupflag) return; | ||
1068 | |||
1069 | /* if we are waiting for a remote button to go out, | ||
1070 | delay the handling of the new packet */ | ||
1071 | if(!iap_remotetick) | ||
1072 | { | ||
1073 | queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0); | ||
1074 | return; | ||
1075 | } | ||
1076 | |||
1077 | /* handle command by mode */ | ||
1078 | unsigned char mode = s->payload[0]; | ||
1079 | switch (mode) { | ||
1080 | case 0: iap_handlepkt_mode0(s->len, s->payload); break; | ||
1081 | case 2: iap_handlepkt_mode2(s->len, s->payload); break; | ||
1082 | case 3: iap_handlepkt_mode3(s->len, s->payload); break; | ||
1083 | case 4: iap_handlepkt_mode4(s->len, s->payload); break; | ||
1084 | case 7: iap_handlepkt_mode7(s->len, s->payload); break; | ||
1085 | } | ||
1086 | } | ||
1087 | |||
1088 | int remote_control_rx(void) | ||
1089 | { | ||
1090 | int btn = iap_remotebtn; | ||
1091 | if(iap_repeatbtn) | ||
1092 | { | ||
1093 | iap_repeatbtn--; | ||
1094 | if(!iap_repeatbtn) | ||
1095 | { | ||
1096 | iap_remotebtn = BUTTON_NONE; | ||
1097 | iap_remotetick = true; | ||
1098 | } | ||
1099 | } | ||
1100 | else | ||
1101 | iap_remotetick = true; | ||
1102 | |||
1103 | return btn; | ||
1104 | } | ||
1105 | |||
1106 | const unsigned char *iap_get_serbuf(void) | ||
1107 | { | ||
1108 | return serbuf; | ||
1109 | } | ||
1110 | |||
diff --git a/apps/iap/iap-core.c b/apps/iap/iap-core.c new file mode 100644 index 0000000000..ddcb22853a --- /dev/null +++ b/apps/iap/iap-core.c | |||
@@ -0,0 +1,1392 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #include <stdio.h> | ||
20 | #include <stdlib.h> | ||
21 | #include <stdarg.h> | ||
22 | #include <string.h> | ||
23 | |||
24 | #include "panic.h" | ||
25 | #include "iap-core.h" | ||
26 | #include "iap-lingo.h" | ||
27 | #include "button.h" | ||
28 | #include "config.h" | ||
29 | #include "cpu.h" | ||
30 | #include "system.h" | ||
31 | #include "kernel.h" | ||
32 | #include "thread.h" | ||
33 | #include "serial.h" | ||
34 | #include "appevents.h" | ||
35 | #include "core_alloc.h" | ||
36 | |||
37 | #include "playlist.h" | ||
38 | #include "playback.h" | ||
39 | #include "audio.h" | ||
40 | #include "settings.h" | ||
41 | #include "metadata.h" | ||
42 | #include "sound.h" | ||
43 | #include "action.h" | ||
44 | #include "powermgmt.h" | ||
45 | |||
46 | #include "tuner.h" | ||
47 | #include "ipod_remote_tuner.h" | ||
48 | |||
49 | |||
50 | /* MS_TO_TICKS converts a milisecond time period into the | ||
51 | * corresponding amount of ticks. If the time period cannot | ||
52 | * be accurately measured in ticks it will round up. | ||
53 | */ | ||
54 | #if (HZ>1000) | ||
55 | #error "HZ is >1000, please fix MS_TO_TICKS" | ||
56 | #endif | ||
57 | #define MS_PER_HZ (1000/HZ) | ||
58 | #define MS_TO_TICKS(x) (((x)+MS_PER_HZ-1)/MS_PER_HZ) | ||
59 | /* IAP specifies a timeout of 25ms for traffic from a device to the iPod. | ||
60 | * Depending on HZ this cannot be accurately measured. Find out the next | ||
61 | * best thing. | ||
62 | */ | ||
63 | #define IAP_PKT_TIMEOUT (MS_TO_TICKS(25)) | ||
64 | |||
65 | /* Events in the iap_queue */ | ||
66 | #define IAP_EV_TICK (1) /* The regular task timeout */ | ||
67 | #define IAP_EV_MSG_RCVD (2) /* A complete message has been received from the device */ | ||
68 | #define IAP_EV_MALLOC (3) /* Allocate memory for the RX/TX buffers */ | ||
69 | |||
70 | static bool iap_started = false; | ||
71 | static bool iap_setupflag = false, iap_running = false; | ||
72 | /* This is set to true if a SYS_POWEROFF message is received, | ||
73 | * signalling impending power off | ||
74 | */ | ||
75 | static bool iap_shutdown = false; | ||
76 | static struct timeout iap_task_tmo; | ||
77 | |||
78 | unsigned long iap_remotebtn = 0; | ||
79 | /* Used to make sure a button press is delivered to the processing | ||
80 | * backend. While this is !0, no new incoming messasges are processed. | ||
81 | * Counted down by remote_control_rx() | ||
82 | */ | ||
83 | int iap_repeatbtn = 0; | ||
84 | /* Used to time out button down events in case we miss the button up event | ||
85 | * from the device somehow. | ||
86 | * If a device sends a button down event it's required to repeat that event | ||
87 | * every 30 to 100ms as long as the button is pressed, and send an explicit | ||
88 | * button up event if the button is released. | ||
89 | * In case the button up event is lost any down events will time out after | ||
90 | * ~200ms. | ||
91 | * iap_periodic() will count down this variable and reset all buttons if | ||
92 | * it reaches 0 | ||
93 | */ | ||
94 | unsigned int iap_timeoutbtn = 0; | ||
95 | bool iap_btnrepeat = false, iap_btnshuffle = false; | ||
96 | |||
97 | static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)]; | ||
98 | static struct event_queue iap_queue; | ||
99 | |||
100 | /* These are pointer used to manage a dynamically allocated buffer which | ||
101 | * will hold both the RX and TX side of things. | ||
102 | * | ||
103 | * iap_buffer_handle is the handle returned from core_alloc() | ||
104 | * iap_buffers points to the start of the complete buffer | ||
105 | * | ||
106 | * The buffer is partitioned as follows: | ||
107 | * - TX_BUFLEN+6 bytes for the TX buffer | ||
108 | * The 6 extra bytes are for the sync byte, the SOP byte, the length indicators | ||
109 | * (3 bytes) and the checksum byte. | ||
110 | * iap_txstart points to the beginning of the TX buffer | ||
111 | * iap_txpayload points to the beginning of the payload portion of the TX buffer | ||
112 | * iap_txnext points to the position where the next byte will be placed | ||
113 | * | ||
114 | * - RX_BUFLEN+2 bytes for the RX buffer | ||
115 | * The RX buffer can hold multiple packets at once, up to it's | ||
116 | * maximum capacity. Every packet consists of a two byte length | ||
117 | * indicator followed by the actual payload. The length indicator | ||
118 | * is two bytes for every length, even for packets with a length <256 | ||
119 | * bytes. | ||
120 | * | ||
121 | * Once a packet has been processed from the RX buffer the rest | ||
122 | * of the buffer (and the pointers below) are shifted to the front | ||
123 | * so that the next packet again starts at the beginning of the | ||
124 | * buffer. This happens with interrupts disabled, to prevent | ||
125 | * writing into the buffer during the move. | ||
126 | * | ||
127 | * iap_rxstart points to the beginning of the RX buffer | ||
128 | * iap_rxpayload starts to the beginning of the currently recieved | ||
129 | * packet | ||
130 | * iap_rxnext points to the position where the next incoming byte | ||
131 | * will be placed | ||
132 | * iap_rxlen is not a pointer, but an indicator of the free | ||
133 | * space left in the RX buffer. | ||
134 | * | ||
135 | * The RX buffer is placed behind the TX buffer so that an eventual TX | ||
136 | * buffer overflow has some place to spill into where it will not cause | ||
137 | * immediate damage. See the comments for IAP_TX_* and iap_send_tx() | ||
138 | */ | ||
139 | #define IAP_MALLOC_SIZE (TX_BUFLEN+6+RX_BUFLEN+2) | ||
140 | #ifdef IAP_MALLOC_DYNAMIC | ||
141 | static int iap_buffer_handle; | ||
142 | #endif | ||
143 | static unsigned char* iap_buffers; | ||
144 | static unsigned char* iap_rxstart; | ||
145 | static unsigned char* iap_rxpayload; | ||
146 | static unsigned char* iap_rxnext; | ||
147 | static uint32_t iap_rxlen; | ||
148 | static unsigned char* iap_txstart; | ||
149 | unsigned char* iap_txpayload; | ||
150 | unsigned char* iap_txnext; | ||
151 | |||
152 | /* The versions of the various Lingoes we support. A major version | ||
153 | * of 0 means unsupported | ||
154 | */ | ||
155 | unsigned char lingo_versions[32][2] = { | ||
156 | {1, 9}, /* General lingo, 0x00 */ | ||
157 | {0, 0}, /* Microphone lingo, 0x01, unsupported */ | ||
158 | {1, 2}, /* Simple remote lingo, 0x02 */ | ||
159 | {1, 5}, /* Display remote lingo, 0x03 */ | ||
160 | {1, 12}, /* Extended Interface lingo, 0x04 */ | ||
161 | {1, 1}, /* RF/BT Transmitter lingo, 0x05 */ | ||
162 | {} /* All others are unsupported */ | ||
163 | }; | ||
164 | |||
165 | /* states of the iap de-framing state machine */ | ||
166 | enum fsm_state { | ||
167 | ST_SYNC, /* wait for 0xFF sync byte */ | ||
168 | ST_SOF, /* wait for 0x55 start-of-frame byte */ | ||
169 | ST_LEN, /* receive length byte (small packet) */ | ||
170 | ST_LENH, /* receive length high byte (large packet) */ | ||
171 | ST_LENL, /* receive length low byte (large packet) */ | ||
172 | ST_DATA, /* receive data */ | ||
173 | ST_CHECK /* verify checksum */ | ||
174 | }; | ||
175 | |||
176 | static struct state_t { | ||
177 | enum fsm_state state; /* current fsm state */ | ||
178 | unsigned int len; /* payload data length */ | ||
179 | unsigned int check; /* running checksum over [len,payload,check] */ | ||
180 | unsigned int count; /* playload bytes counter */ | ||
181 | } frame_state = { | ||
182 | .state = ST_SYNC | ||
183 | }; | ||
184 | |||
185 | enum interface_state interface_state = IST_STANDARD; | ||
186 | |||
187 | struct device_t device; | ||
188 | |||
189 | #ifdef IAP_MALLOC_DYNAMIC | ||
190 | static int iap_move_callback(int handle, void* current, void* new); | ||
191 | |||
192 | static struct buflib_callbacks iap_buflib_callbacks = { | ||
193 | iap_move_callback, | ||
194 | NULL | ||
195 | }; | ||
196 | #endif | ||
197 | |||
198 | static void iap_malloc(void); | ||
199 | |||
200 | void put_u16(unsigned char *buf, const uint16_t data) | ||
201 | { | ||
202 | buf[0] = (data >> 8) & 0xFF; | ||
203 | buf[1] = (data >> 0) & 0xFF; | ||
204 | } | ||
205 | |||
206 | void put_u32(unsigned char *buf, const uint32_t data) | ||
207 | { | ||
208 | buf[0] = (data >> 24) & 0xFF; | ||
209 | buf[1] = (data >> 16) & 0xFF; | ||
210 | buf[2] = (data >> 8) & 0xFF; | ||
211 | buf[3] = (data >> 0) & 0xFF; | ||
212 | } | ||
213 | |||
214 | uint32_t get_u32(const unsigned char *buf) | ||
215 | { | ||
216 | return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; | ||
217 | } | ||
218 | |||
219 | uint16_t get_u16(const unsigned char *buf) | ||
220 | { | ||
221 | return (buf[0] << 8) | buf[1]; | ||
222 | } | ||
223 | |||
224 | #if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF) | ||
225 | /* Convert a buffer into a printable string, perl style | ||
226 | * buf contains the data to be converted, len is the length | ||
227 | * of the buffer. | ||
228 | * | ||
229 | * This will convert at most 1024 bytes from buf | ||
230 | */ | ||
231 | static char* hexstring(const unsigned char *buf, unsigned int len) { | ||
232 | static char hexbuf[4097]; | ||
233 | unsigned int l; | ||
234 | const unsigned char* p; | ||
235 | unsigned char* out; | ||
236 | unsigned char h[] = {'0', '1', '2', '3', '4', '5', '6', '7', | ||
237 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; | ||
238 | |||
239 | if (len > 1024) { | ||
240 | l = 1024; | ||
241 | } else { | ||
242 | l = len; | ||
243 | } | ||
244 | p = buf; | ||
245 | out = hexbuf; | ||
246 | do { | ||
247 | *out++ = h[(*p)>>4]; | ||
248 | *out++ = h[*p & 0x0F]; | ||
249 | } while(--l && p++); | ||
250 | |||
251 | *out = 0x00; | ||
252 | |||
253 | return hexbuf; | ||
254 | } | ||
255 | #endif | ||
256 | |||
257 | |||
258 | void iap_tx_strlcpy(const unsigned char *str) | ||
259 | { | ||
260 | ptrdiff_t txfree; | ||
261 | int r; | ||
262 | |||
263 | txfree = TX_BUFLEN - (iap_txnext - iap_txstart); | ||
264 | r = strlcpy(iap_txnext, str, txfree); | ||
265 | |||
266 | if (r < txfree) | ||
267 | { | ||
268 | /* No truncation occured | ||
269 | * Account for the terminating \0 | ||
270 | */ | ||
271 | iap_txnext += (r+1); | ||
272 | } else { | ||
273 | /* Truncation occured, the TX buffer is now full. */ | ||
274 | iap_txnext = iap_txstart + TX_BUFLEN; | ||
275 | } | ||
276 | } | ||
277 | |||
278 | void iap_reset_auth(struct auth_t* auth) | ||
279 | { | ||
280 | auth->state = AUST_NONE; | ||
281 | auth->max_section = 0; | ||
282 | auth->next_section = 0; | ||
283 | } | ||
284 | |||
285 | void iap_reset_device(struct device_t* device) | ||
286 | { | ||
287 | iap_reset_auth(&(device->auth)); | ||
288 | device->lingoes = 0; | ||
289 | device->notifications = 0; | ||
290 | device->changed_notifications = 0; | ||
291 | device->do_notify = false; | ||
292 | device->do_power_notify = false; | ||
293 | device->accinfo = ACCST_NONE; | ||
294 | device->capabilities = 0; | ||
295 | device->capabilities_queried = 0; | ||
296 | } | ||
297 | |||
298 | static int iap_task(struct timeout *tmo) | ||
299 | { | ||
300 | (void) tmo; | ||
301 | |||
302 | queue_post(&iap_queue, IAP_EV_TICK, 0); | ||
303 | return MS_TO_TICKS(100); | ||
304 | } | ||
305 | |||
306 | /* This thread is waiting for events posted to iap_queue and calls | ||
307 | * the appropriate subroutines in response | ||
308 | */ | ||
309 | static void iap_thread(void) | ||
310 | { | ||
311 | struct queue_event ev; | ||
312 | while(1) { | ||
313 | queue_wait(&iap_queue, &ev); | ||
314 | switch (ev.id) | ||
315 | { | ||
316 | /* Handle the regular 100ms tick used for driving the | ||
317 | * authentication state machine and notifications | ||
318 | */ | ||
319 | case IAP_EV_TICK: | ||
320 | { | ||
321 | iap_periodic(); | ||
322 | break; | ||
323 | } | ||
324 | |||
325 | /* Handle a newly received message from the device */ | ||
326 | case IAP_EV_MSG_RCVD: | ||
327 | { | ||
328 | iap_handlepkt(); | ||
329 | break; | ||
330 | } | ||
331 | |||
332 | /* Handle memory allocation. This is used only once, during | ||
333 | * startup | ||
334 | */ | ||
335 | case IAP_EV_MALLOC: | ||
336 | { | ||
337 | iap_malloc(); | ||
338 | break; | ||
339 | } | ||
340 | |||
341 | /* Handle poweroff message */ | ||
342 | case SYS_POWEROFF: | ||
343 | { | ||
344 | iap_shutdown = true; | ||
345 | break; | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /* called by playback when the next track starts */ | ||
352 | static void iap_track_changed(void *ignored) | ||
353 | { | ||
354 | (void)ignored; | ||
355 | if ((interface_state == IST_EXTENDED) && device.do_notify) { | ||
356 | long playlist_pos = playlist_next(0); | ||
357 | playlist_pos -= playlist_get_first_index(NULL); | ||
358 | if(playlist_pos < 0) | ||
359 | playlist_pos += playlist_amount(); | ||
360 | |||
361 | IAP_TX_INIT4(0x04, 0x0027); | ||
362 | IAP_TX_PUT(0x01); | ||
363 | IAP_TX_PUT_U32(playlist_pos); | ||
364 | |||
365 | iap_send_tx(); | ||
366 | return; | ||
367 | } | ||
368 | } | ||
369 | |||
370 | /* Do general setup of the needed infrastructure. | ||
371 | * | ||
372 | * Please note that a lot of additional work is done by iap_start() | ||
373 | */ | ||
374 | void iap_setup(const int ratenum) | ||
375 | { | ||
376 | iap_bitrate_set(ratenum); | ||
377 | iap_remotebtn = BUTTON_NONE; | ||
378 | iap_setupflag = true; | ||
379 | iap_started = false; | ||
380 | iap_running = false; | ||
381 | } | ||
382 | |||
383 | /* Actually bring up the message queue, message handler thread and | ||
384 | * notification timer | ||
385 | * | ||
386 | * NOTE: This is running in interrupt context | ||
387 | */ | ||
388 | static void iap_start(void) | ||
389 | { | ||
390 | unsigned int tid; | ||
391 | |||
392 | if (iap_started) | ||
393 | return; | ||
394 | |||
395 | iap_reset_device(&device); | ||
396 | queue_init(&iap_queue, true); | ||
397 | tid = create_thread(iap_thread, thread_stack, sizeof(thread_stack), | ||
398 | 0, "iap" | ||
399 | IF_PRIO(, PRIORITY_SYSTEM) | ||
400 | IF_COP(, CPU)); | ||
401 | if (!tid) | ||
402 | panicf("Could not create iap thread"); | ||
403 | timeout_register(&iap_task_tmo, iap_task, MS_TO_TICKS(100), (intptr_t)NULL); | ||
404 | add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed); | ||
405 | |||
406 | /* Since we cannot allocate memory while in interrupt context | ||
407 | * post a message to our own queue to get that done | ||
408 | */ | ||
409 | queue_post(&iap_queue, IAP_EV_MALLOC, 0); | ||
410 | iap_started = true; | ||
411 | } | ||
412 | |||
413 | static void iap_malloc(void) | ||
414 | { | ||
415 | #ifndef IAP_MALLOC_DYNAMIC | ||
416 | static unsigned char serbuf[IAP_MALLOC_SIZE]; | ||
417 | #endif | ||
418 | |||
419 | if (iap_running) | ||
420 | return; | ||
421 | |||
422 | #ifdef IAP_MALLOC_DYNAMIC | ||
423 | iap_buffer_handle = core_alloc_ex("iap", IAP_MALLOC_SIZE, &iap_buflib_callbacks); | ||
424 | if (iap_buffer_handle < 0) | ||
425 | panicf("Could not allocate buffer memory"); | ||
426 | iap_buffers = core_get_data(iap_buffer_handle); | ||
427 | #else | ||
428 | iap_buffers = serbuf; | ||
429 | #endif | ||
430 | iap_txstart = iap_buffers; | ||
431 | iap_txpayload = iap_txstart+5; | ||
432 | iap_txnext = iap_txpayload; | ||
433 | iap_rxstart = iap_buffers+(TX_BUFLEN+6); | ||
434 | iap_rxpayload = iap_rxstart; | ||
435 | iap_rxnext = iap_rxpayload; | ||
436 | iap_rxlen = RX_BUFLEN+2; | ||
437 | iap_running = true; | ||
438 | } | ||
439 | |||
440 | void iap_bitrate_set(const int ratenum) | ||
441 | { | ||
442 | switch(ratenum) | ||
443 | { | ||
444 | case 0: | ||
445 | serial_bitrate(0); | ||
446 | break; | ||
447 | case 1: | ||
448 | serial_bitrate(9600); | ||
449 | break; | ||
450 | case 2: | ||
451 | serial_bitrate(19200); | ||
452 | break; | ||
453 | case 3: | ||
454 | serial_bitrate(38400); | ||
455 | break; | ||
456 | case 4: | ||
457 | serial_bitrate(57600); | ||
458 | break; | ||
459 | } | ||
460 | } | ||
461 | |||
462 | /* Message format: | ||
463 | 0xff | ||
464 | 0x55 | ||
465 | length | ||
466 | mode | ||
467 | command (2 bytes) | ||
468 | parameters (0-n bytes) | ||
469 | checksum (length+mode+parameters+checksum == 0) | ||
470 | */ | ||
471 | |||
472 | /* Send the current content of the TX buffer. | ||
473 | * This will check for TX buffer overflow and panic, but it might | ||
474 | * be too late by then (although one would have to overflow the complete | ||
475 | * RX buffer as well) | ||
476 | */ | ||
477 | void iap_send_tx(void) | ||
478 | { | ||
479 | int i, chksum; | ||
480 | ptrdiff_t txlen; | ||
481 | unsigned char* txstart; | ||
482 | |||
483 | txlen = iap_txnext - iap_txpayload; | ||
484 | |||
485 | if (txlen <= 0) | ||
486 | return; | ||
487 | |||
488 | if (txlen > TX_BUFLEN) | ||
489 | panicf("IAP: TX buffer overflow"); | ||
490 | |||
491 | if (txlen < 256) | ||
492 | { | ||
493 | /* Short packet */ | ||
494 | txstart = iap_txstart+2; | ||
495 | *(txstart+2) = txlen; | ||
496 | chksum = txlen; | ||
497 | } else { | ||
498 | /* Long packet */ | ||
499 | txstart = iap_txstart; | ||
500 | *(txstart+2) = 0x00; | ||
501 | *(txstart+3) = (txlen >> 8) & 0xFF; | ||
502 | *(txstart+4) = (txlen) & 0xFF; | ||
503 | chksum = *(txstart+3) + *(txstart+4); | ||
504 | } | ||
505 | *(txstart) = 0xFF; | ||
506 | *(txstart+1) = 0x55; | ||
507 | |||
508 | for (i=0; i<txlen; i++) | ||
509 | { | ||
510 | chksum += iap_txpayload[i]; | ||
511 | } | ||
512 | *(iap_txnext) = 0x100 - (chksum & 0xFF); | ||
513 | |||
514 | #ifdef LOGF_ENABLE | ||
515 | logf("T: %s", hexstring(txstart+3, (iap_txnext - txstart)-3)); | ||
516 | #endif | ||
517 | for (i=0; i <= (iap_txnext - txstart); i++) | ||
518 | { | ||
519 | while(!tx_rdy()) ; | ||
520 | tx_writec(txstart[i]); | ||
521 | } | ||
522 | } | ||
523 | |||
524 | /* This is just a compatibility wrapper around the new TX buffer | ||
525 | * infrastructure | ||
526 | */ | ||
527 | void iap_send_pkt(const unsigned char * data, const int len) | ||
528 | { | ||
529 | if (!iap_running) | ||
530 | return; | ||
531 | |||
532 | iap_txnext = iap_txpayload; | ||
533 | IAP_TX_PUT_DATA(data, len); | ||
534 | iap_send_tx(); | ||
535 | } | ||
536 | |||
537 | bool iap_getc(const unsigned char x) | ||
538 | { | ||
539 | struct state_t *s = &frame_state; | ||
540 | static long pkt_timeout; | ||
541 | |||
542 | if (!iap_setupflag) | ||
543 | return false; | ||
544 | |||
545 | /* Check the time since the last packet arrived. */ | ||
546 | if ((s->state != ST_SYNC) && TIME_AFTER(current_tick, pkt_timeout)) { | ||
547 | /* Packet timeouts only make sense while not waiting for the | ||
548 | * sync byte */ | ||
549 | s->state = ST_SYNC; | ||
550 | return iap_getc(x); | ||
551 | } | ||
552 | |||
553 | |||
554 | /* run state machine to detect and extract a valid frame */ | ||
555 | switch (s->state) { | ||
556 | case ST_SYNC: | ||
557 | if (x == 0xFF) { | ||
558 | /* The IAP infrastructure is started by the first received sync | ||
559 | * byte. It takes a while to spin up, so do not advance the state | ||
560 | * machine until it has started. | ||
561 | */ | ||
562 | if (!iap_running) | ||
563 | { | ||
564 | iap_start(); | ||
565 | break; | ||
566 | } | ||
567 | iap_rxnext = iap_rxpayload; | ||
568 | s->state = ST_SOF; | ||
569 | } | ||
570 | break; | ||
571 | case ST_SOF: | ||
572 | if (x == 0x55) { | ||
573 | /* received a valid sync/SOF pair */ | ||
574 | s->state = ST_LEN; | ||
575 | } else { | ||
576 | s->state = ST_SYNC; | ||
577 | return iap_getc(x); | ||
578 | } | ||
579 | break; | ||
580 | case ST_LEN: | ||
581 | s->check = x; | ||
582 | s->count = 0; | ||
583 | if (x == 0) { | ||
584 | /* large packet */ | ||
585 | s->state = ST_LENH; | ||
586 | } else { | ||
587 | /* small packet */ | ||
588 | if (x > (iap_rxlen-2)) | ||
589 | { | ||
590 | /* Packet too long for buffer */ | ||
591 | s->state = ST_SYNC; | ||
592 | break; | ||
593 | } | ||
594 | s->len = x; | ||
595 | s->state = ST_DATA; | ||
596 | put_u16(iap_rxnext, s->len); | ||
597 | iap_rxnext += 2; | ||
598 | } | ||
599 | break; | ||
600 | case ST_LENH: | ||
601 | s->check += x; | ||
602 | s->len = x << 8; | ||
603 | s->state = ST_LENL; | ||
604 | break; | ||
605 | case ST_LENL: | ||
606 | s->check += x; | ||
607 | s->len += x; | ||
608 | if ((s->len == 0) || (s->len > (iap_rxlen-2))) { | ||
609 | /* invalid length */ | ||
610 | s->state = ST_SYNC; | ||
611 | break; | ||
612 | } else { | ||
613 | s->state = ST_DATA; | ||
614 | put_u16(iap_rxnext, s->len); | ||
615 | iap_rxnext += 2; | ||
616 | } | ||
617 | break; | ||
618 | case ST_DATA: | ||
619 | s->check += x; | ||
620 | *(iap_rxnext++) = x; | ||
621 | s->count += 1; | ||
622 | if (s->count == s->len) { | ||
623 | s->state = ST_CHECK; | ||
624 | } | ||
625 | break; | ||
626 | case ST_CHECK: | ||
627 | s->check += x; | ||
628 | if ((s->check & 0xFF) == 0) { | ||
629 | /* done, received a valid frame */ | ||
630 | iap_rxpayload = iap_rxnext; | ||
631 | queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0); | ||
632 | } else { | ||
633 | /* Invalid frame */ | ||
634 | } | ||
635 | s->state = ST_SYNC; | ||
636 | break; | ||
637 | default: | ||
638 | #ifdef LOGF_ENABLE | ||
639 | logf("Unhandled iap state %d", (int) s->state); | ||
640 | #else | ||
641 | panicf("Unhandled iap state %d", (int) s->state); | ||
642 | #endif | ||
643 | break; | ||
644 | } | ||
645 | |||
646 | pkt_timeout = current_tick + IAP_PKT_TIMEOUT; | ||
647 | |||
648 | /* return true while still hunting for the sync and start-of-frame byte */ | ||
649 | return (s->state == ST_SYNC) || (s->state == ST_SOF); | ||
650 | } | ||
651 | |||
652 | void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3) | ||
653 | { | ||
654 | int tracknum; | ||
655 | int fd; | ||
656 | struct playlist_track_info info; | ||
657 | |||
658 | tracknum = track; | ||
659 | |||
660 | tracknum += playlist_get_first_index(NULL); | ||
661 | if(tracknum >= playlist_amount()) | ||
662 | tracknum -= playlist_amount(); | ||
663 | |||
664 | /* If the tracknumber is not the current one, | ||
665 | read id3 from disk */ | ||
666 | if(playlist_next(0) != tracknum) | ||
667 | { | ||
668 | playlist_get_track_info(NULL, tracknum, &info); | ||
669 | fd = open(info.filename, O_RDONLY); | ||
670 | memset(id3, 0, sizeof(*id3)); | ||
671 | get_metadata(id3, fd, info.filename); | ||
672 | close(fd); | ||
673 | } else { | ||
674 | memcpy(id3, audio_current_track(), sizeof(*id3)); | ||
675 | } | ||
676 | } | ||
677 | |||
678 | uint32_t iap_get_trackpos(void) | ||
679 | { | ||
680 | struct mp3entry *id3 = audio_current_track(); | ||
681 | |||
682 | return id3->elapsed; | ||
683 | } | ||
684 | |||
685 | uint32_t iap_get_trackindex(void) | ||
686 | { | ||
687 | struct playlist_info* playlist = playlist_get_current(); | ||
688 | |||
689 | return (playlist->index - playlist->first_index); | ||
690 | } | ||
691 | |||
692 | void iap_periodic(void) | ||
693 | { | ||
694 | static int count; | ||
695 | |||
696 | if(!iap_setupflag) return; | ||
697 | |||
698 | /* Handle pending authentication tasks */ | ||
699 | switch (device.auth.state) | ||
700 | { | ||
701 | case AUST_INIT: | ||
702 | { | ||
703 | /* Send out GetDevAuthenticationInfo */ | ||
704 | IAP_TX_INIT(0x00, 0x14); | ||
705 | |||
706 | iap_send_tx(); | ||
707 | device.auth.state = AUST_CERTREQ; | ||
708 | break; | ||
709 | } | ||
710 | |||
711 | case AUST_CERTDONE: | ||
712 | { | ||
713 | /* Send out GetDevAuthenticationSignature, with | ||
714 | * 20 bytes of challenge and a retry counter of 1. | ||
715 | * Since we do not really care about the content of the | ||
716 | * challenge we just use the first 20 bytes of whatever | ||
717 | * is in the RX buffer right now. | ||
718 | */ | ||
719 | IAP_TX_INIT(0x00, 0x17); | ||
720 | IAP_TX_PUT_DATA(iap_rxstart, 20); | ||
721 | IAP_TX_PUT(0x01); | ||
722 | |||
723 | iap_send_tx(); | ||
724 | device.auth.state = AUST_CHASENT; | ||
725 | break; | ||
726 | } | ||
727 | |||
728 | default: | ||
729 | { | ||
730 | break; | ||
731 | } | ||
732 | } | ||
733 | |||
734 | /* Time out button down events */ | ||
735 | if (iap_timeoutbtn) | ||
736 | iap_timeoutbtn -= 1; | ||
737 | |||
738 | if (!iap_timeoutbtn) | ||
739 | { | ||
740 | iap_remotebtn = BUTTON_NONE; | ||
741 | iap_repeatbtn = 0; | ||
742 | iap_btnshuffle = false; | ||
743 | iap_btnrepeat = false; | ||
744 | } | ||
745 | |||
746 | /* Handle power down messages. */ | ||
747 | if (iap_shutdown && device.do_power_notify) | ||
748 | { | ||
749 | /* NotifyiPodStateChange */ | ||
750 | IAP_TX_INIT(0x00, 0x23); | ||
751 | IAP_TX_PUT(0x01); | ||
752 | |||
753 | iap_send_tx(); | ||
754 | |||
755 | /* No further actions, we're going down */ | ||
756 | iap_reset_device(&device); | ||
757 | return; | ||
758 | } | ||
759 | |||
760 | /* Handle GetAccessoryInfo messages */ | ||
761 | if (device.accinfo == ACCST_INIT) | ||
762 | { | ||
763 | /* GetAccessoryInfo */ | ||
764 | IAP_TX_INIT(0x00, 0x27); | ||
765 | IAP_TX_PUT(0x00); | ||
766 | |||
767 | iap_send_tx(); | ||
768 | device.accinfo = ACCST_SENT; | ||
769 | } | ||
770 | |||
771 | /* Do not send requests for device information while | ||
772 | * an authentication is still running, this seems to | ||
773 | * confuse some devices | ||
774 | */ | ||
775 | if (!DEVICE_AUTH_RUNNING && (device.accinfo == ACCST_DATA)) | ||
776 | { | ||
777 | int first_set; | ||
778 | |||
779 | /* Find the first bit set in the capabilities field, | ||
780 | * ignoring those we already asked for | ||
781 | */ | ||
782 | first_set = find_first_set_bit(device.capabilities & (~device.capabilities_queried)); | ||
783 | |||
784 | if (first_set != 32) | ||
785 | { | ||
786 | /* Add bit to queried cababilities */ | ||
787 | device.capabilities_queried |= BIT_N(first_set); | ||
788 | |||
789 | switch (first_set) | ||
790 | { | ||
791 | /* Name */ | ||
792 | case 0x01: | ||
793 | /* Firmware version */ | ||
794 | case 0x04: | ||
795 | /* Hardware version */ | ||
796 | case 0x05: | ||
797 | /* Manufacturer */ | ||
798 | case 0x06: | ||
799 | /* Model number */ | ||
800 | case 0x07: | ||
801 | /* Serial number */ | ||
802 | case 0x08: | ||
803 | /* Maximum payload size */ | ||
804 | case 0x09: | ||
805 | { | ||
806 | IAP_TX_INIT(0x00, 0x27); | ||
807 | IAP_TX_PUT(first_set); | ||
808 | |||
809 | iap_send_tx(); | ||
810 | break; | ||
811 | } | ||
812 | |||
813 | /* Minimum supported iPod firmware version */ | ||
814 | case 0x02: | ||
815 | { | ||
816 | IAP_TX_INIT(0x00, 0x27); | ||
817 | IAP_TX_PUT(2); | ||
818 | IAP_TX_PUT_U32(IAP_IPOD_MODEL); | ||
819 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR); | ||
820 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR); | ||
821 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV); | ||
822 | |||
823 | iap_send_tx(); | ||
824 | break; | ||
825 | } | ||
826 | |||
827 | /* Minimum supported lingo version. Queries Lingo 0 */ | ||
828 | case 0x03: | ||
829 | { | ||
830 | IAP_TX_INIT(0x00, 0x27); | ||
831 | IAP_TX_PUT(3); | ||
832 | IAP_TX_PUT(0); | ||
833 | |||
834 | iap_send_tx(); | ||
835 | break; | ||
836 | } | ||
837 | } | ||
838 | |||
839 | device.accinfo = ACCST_SENT; | ||
840 | } | ||
841 | } | ||
842 | |||
843 | if (!device.do_notify) return; | ||
844 | if (device.notifications == 0) return; | ||
845 | |||
846 | /* Volume change notifications are sent every 100ms */ | ||
847 | if (device.notifications & (BIT_N(4) | BIT_N(16))) { | ||
848 | /* Currently we do not track volume changes, so this is | ||
849 | * never sent. | ||
850 | * | ||
851 | * TODO: Fix volume tracking | ||
852 | */ | ||
853 | } | ||
854 | |||
855 | /* All other events are sent every 500ms */ | ||
856 | count += 1; | ||
857 | if (count < 5) return; | ||
858 | |||
859 | count = 0; | ||
860 | |||
861 | /* RemoteEventNotification */ | ||
862 | |||
863 | /* Mode 04 PlayStatusChangeNotification */ | ||
864 | /* Are we in Extended Mode */ | ||
865 | if (interface_state == IST_EXTENDED) { | ||
866 | /* Return Track Position */ | ||
867 | struct mp3entry *id3 = audio_current_track(); | ||
868 | unsigned long time_elapsed = id3->elapsed; | ||
869 | IAP_TX_INIT4(0x04, 0x0027); | ||
870 | IAP_TX_PUT(0x04); | ||
871 | IAP_TX_PUT_U32(time_elapsed); | ||
872 | |||
873 | iap_send_tx(); | ||
874 | } | ||
875 | |||
876 | /* Track position (ms) or Track position (s) */ | ||
877 | if (device.notifications & (BIT_N(0) | BIT_N(15))) | ||
878 | { | ||
879 | uint32_t t; | ||
880 | uint16_t ts; | ||
881 | bool changed; | ||
882 | |||
883 | t = iap_get_trackpos(); | ||
884 | ts = (t / 1000) & 0xFFFF; | ||
885 | |||
886 | if ((device.notifications & BIT_N(0)) && (device.trackpos_ms != t)) | ||
887 | { | ||
888 | IAP_TX_INIT(0x03, 0x09); | ||
889 | IAP_TX_PUT(0x00); | ||
890 | IAP_TX_PUT_U32(t); | ||
891 | device.changed_notifications |= BIT_N(0); | ||
892 | changed = true; | ||
893 | |||
894 | iap_send_tx(); | ||
895 | } | ||
896 | |||
897 | if ((device.notifications & BIT_N(15)) && (device.trackpos_s != ts)) { | ||
898 | IAP_TX_INIT(0x03, 0x09); | ||
899 | IAP_TX_PUT(0x0F); | ||
900 | IAP_TX_PUT_U16(ts); | ||
901 | device.changed_notifications |= BIT_N(15); | ||
902 | changed = true; | ||
903 | |||
904 | iap_send_tx(); | ||
905 | } | ||
906 | |||
907 | if (changed) | ||
908 | { | ||
909 | device.trackpos_ms = t; | ||
910 | device.trackpos_s = ts; | ||
911 | } | ||
912 | } | ||
913 | |||
914 | /* Track index */ | ||
915 | if (device.notifications & BIT_N(1)) | ||
916 | { | ||
917 | uint32_t index; | ||
918 | |||
919 | index = iap_get_trackindex(); | ||
920 | |||
921 | if (device.track_index != index) { | ||
922 | IAP_TX_INIT(0x03, 0x09); | ||
923 | IAP_TX_PUT(0x01); | ||
924 | IAP_TX_PUT_U32(index); | ||
925 | device.changed_notifications |= BIT_N(1); | ||
926 | |||
927 | iap_send_tx(); | ||
928 | |||
929 | device.track_index = index; | ||
930 | } | ||
931 | } | ||
932 | |||
933 | /* Chapter index */ | ||
934 | if (device.notifications & BIT_N(2)) | ||
935 | { | ||
936 | uint32_t index; | ||
937 | |||
938 | index = iap_get_trackindex(); | ||
939 | |||
940 | if (device.track_index != index) | ||
941 | { | ||
942 | IAP_TX_INIT(0x03, 0x09); | ||
943 | IAP_TX_PUT(0x02); | ||
944 | IAP_TX_PUT_U32(index); | ||
945 | IAP_TX_PUT_U16(0); | ||
946 | IAP_TX_PUT_U16(0xFFFF); | ||
947 | device.changed_notifications |= BIT_N(2); | ||
948 | |||
949 | iap_send_tx(); | ||
950 | |||
951 | device.track_index = index; | ||
952 | } | ||
953 | } | ||
954 | |||
955 | /* Play status */ | ||
956 | if (device.notifications & BIT_N(3)) | ||
957 | { | ||
958 | unsigned char play_status; | ||
959 | |||
960 | play_status = audio_status(); | ||
961 | |||
962 | if (device.play_status != play_status) | ||
963 | { | ||
964 | IAP_TX_INIT(0x03, 0x09); | ||
965 | IAP_TX_PUT(0x03); | ||
966 | if (play_status & AUDIO_STATUS_PLAY) { | ||
967 | /* Playing or paused */ | ||
968 | if (play_status & AUDIO_STATUS_PAUSE) { | ||
969 | /* Paused */ | ||
970 | IAP_TX_PUT(0x02); | ||
971 | } else { | ||
972 | /* Playing */ | ||
973 | IAP_TX_PUT(0x01); | ||
974 | } | ||
975 | } else { | ||
976 | IAP_TX_PUT(0x00); | ||
977 | } | ||
978 | device.changed_notifications |= BIT_N(3); | ||
979 | |||
980 | iap_send_tx(); | ||
981 | |||
982 | device.play_status = play_status; | ||
983 | } | ||
984 | } | ||
985 | |||
986 | /* Power/Battery */ | ||
987 | if (device.notifications & BIT_N(5)) | ||
988 | { | ||
989 | unsigned char power_state; | ||
990 | unsigned char battery_l; | ||
991 | |||
992 | power_state = charger_input_state; | ||
993 | battery_l = battery_level(); | ||
994 | |||
995 | if ((device.power_state != power_state) || (device.battery_level != battery_l)) | ||
996 | { | ||
997 | IAP_TX_INIT(0x03, 0x09); | ||
998 | IAP_TX_PUT(0x05); | ||
999 | |||
1000 | iap_fill_power_state(); | ||
1001 | device.changed_notifications |= BIT_N(5); | ||
1002 | |||
1003 | iap_send_tx(); | ||
1004 | |||
1005 | device.power_state = power_state; | ||
1006 | device.battery_level = battery_l; | ||
1007 | } | ||
1008 | } | ||
1009 | |||
1010 | /* Equalizer state | ||
1011 | * This is not handled yet. | ||
1012 | * | ||
1013 | * TODO: Fix equalizer handling | ||
1014 | */ | ||
1015 | |||
1016 | /* Shuffle */ | ||
1017 | if (device.notifications & BIT_N(7)) | ||
1018 | { | ||
1019 | unsigned char shuffle; | ||
1020 | |||
1021 | shuffle = global_settings.playlist_shuffle; | ||
1022 | |||
1023 | if (device.shuffle != shuffle) | ||
1024 | { | ||
1025 | IAP_TX_INIT(0x03, 0x09); | ||
1026 | IAP_TX_PUT(0x07); | ||
1027 | IAP_TX_PUT(shuffle?0x01:0x00); | ||
1028 | device.changed_notifications |= BIT_N(7); | ||
1029 | |||
1030 | iap_send_tx(); | ||
1031 | |||
1032 | device.shuffle = shuffle; | ||
1033 | } | ||
1034 | } | ||
1035 | |||
1036 | /* Repeat */ | ||
1037 | if (device.notifications & BIT_N(8)) | ||
1038 | { | ||
1039 | unsigned char repeat; | ||
1040 | |||
1041 | repeat = global_settings.repeat_mode; | ||
1042 | |||
1043 | if (device.repeat != repeat) | ||
1044 | { | ||
1045 | IAP_TX_INIT(0x03, 0x09); | ||
1046 | IAP_TX_PUT(0x08); | ||
1047 | switch (repeat) | ||
1048 | { | ||
1049 | case REPEAT_OFF: | ||
1050 | { | ||
1051 | IAP_TX_PUT(0x00); | ||
1052 | break; | ||
1053 | } | ||
1054 | |||
1055 | case REPEAT_ONE: | ||
1056 | { | ||
1057 | IAP_TX_PUT(0x01); | ||
1058 | break; | ||
1059 | } | ||
1060 | |||
1061 | case REPEAT_ALL: | ||
1062 | { | ||
1063 | IAP_TX_PUT(0x02); | ||
1064 | break; | ||
1065 | } | ||
1066 | } | ||
1067 | device.changed_notifications |= BIT_N(8); | ||
1068 | |||
1069 | iap_send_tx(); | ||
1070 | |||
1071 | device.repeat = repeat; | ||
1072 | } | ||
1073 | } | ||
1074 | |||
1075 | /* Date/Time */ | ||
1076 | if (device.notifications & BIT_N(9)) | ||
1077 | { | ||
1078 | struct tm* tm; | ||
1079 | |||
1080 | tm = get_time(); | ||
1081 | |||
1082 | if (memcmp(tm, &(device.datetime), sizeof(struct tm))) | ||
1083 | { | ||
1084 | IAP_TX_INIT(0x03, 0x09); | ||
1085 | IAP_TX_PUT(0x09); | ||
1086 | IAP_TX_PUT_U16(tm->tm_year); | ||
1087 | |||
1088 | /* Month */ | ||
1089 | IAP_TX_PUT(tm->tm_mon+1); | ||
1090 | |||
1091 | /* Day */ | ||
1092 | IAP_TX_PUT(tm->tm_mday); | ||
1093 | |||
1094 | /* Hour */ | ||
1095 | IAP_TX_PUT(tm->tm_hour); | ||
1096 | |||
1097 | /* Minute */ | ||
1098 | IAP_TX_PUT(tm->tm_min); | ||
1099 | |||
1100 | device.changed_notifications |= BIT_N(9); | ||
1101 | |||
1102 | iap_send_tx(); | ||
1103 | |||
1104 | memcpy(&(device.datetime), tm, sizeof(struct tm)); | ||
1105 | } | ||
1106 | } | ||
1107 | |||
1108 | /* Alarm | ||
1109 | * This is not supported yet. | ||
1110 | * | ||
1111 | * TODO: Fix alarm handling | ||
1112 | */ | ||
1113 | |||
1114 | /* Backlight | ||
1115 | * This is not supported yet. | ||
1116 | * | ||
1117 | * TODO: Fix backlight handling | ||
1118 | */ | ||
1119 | |||
1120 | /* Hold switch */ | ||
1121 | if (device.notifications & BIT_N(0x0C)) | ||
1122 | { | ||
1123 | unsigned char hold; | ||
1124 | |||
1125 | hold = button_hold(); | ||
1126 | if (device.hold != hold) { | ||
1127 | IAP_TX_INIT(0x03, 0x09); | ||
1128 | IAP_TX_PUT(0x0C); | ||
1129 | IAP_TX_PUT(hold?0x01:0x00); | ||
1130 | |||
1131 | device.changed_notifications |= BIT_N(0x0C); | ||
1132 | |||
1133 | iap_send_tx(); | ||
1134 | |||
1135 | device.hold = hold; | ||
1136 | } | ||
1137 | } | ||
1138 | |||
1139 | /* Sound check | ||
1140 | * This is not supported yet. | ||
1141 | * | ||
1142 | * TODO: Fix sound check handling | ||
1143 | */ | ||
1144 | |||
1145 | /* Audiobook check | ||
1146 | * This is not supported yet. | ||
1147 | * | ||
1148 | * TODO: Fix audiobook handling | ||
1149 | */ | ||
1150 | } | ||
1151 | |||
1152 | /* Change the current interface state. | ||
1153 | * On a change from IST_EXTENDED to IST_STANDARD, or from IST_STANDARD | ||
1154 | * to IST_EXTENDED, pause playback, if playing | ||
1155 | */ | ||
1156 | void iap_interface_state_change(const enum interface_state new) | ||
1157 | { | ||
1158 | if (((interface_state == IST_EXTENDED) && (new == IST_STANDARD)) || | ||
1159 | ((interface_state == IST_STANDARD) && (new == IST_EXTENDED))) { | ||
1160 | if (audio_status() == AUDIO_STATUS_PLAY) | ||
1161 | { | ||
1162 | REMOTE_BUTTON(BUTTON_RC_PLAY); | ||
1163 | } | ||
1164 | } | ||
1165 | |||
1166 | interface_state = new; | ||
1167 | } | ||
1168 | |||
1169 | static void iap_handlepkt_mode5(const unsigned int len, const unsigned char *buf) | ||
1170 | { | ||
1171 | (void) len; | ||
1172 | unsigned int cmd = buf[1]; | ||
1173 | switch (cmd) | ||
1174 | { | ||
1175 | /* Sent from iPod Begin Transmission */ | ||
1176 | case 0x02: | ||
1177 | { | ||
1178 | /* RF Transmitter: Begin High Power transmission */ | ||
1179 | unsigned char data0[] = {0x05, 0x02}; | ||
1180 | iap_send_pkt(data0, sizeof(data0)); | ||
1181 | break; | ||
1182 | } | ||
1183 | |||
1184 | /* Sent from iPod End High Power Transmission */ | ||
1185 | case 0x03: | ||
1186 | { | ||
1187 | /* RF Transmitter: End High Power transmission */ | ||
1188 | unsigned char data1[] = {0x05, 0x03}; | ||
1189 | iap_send_pkt(data1, sizeof(data1)); | ||
1190 | break; | ||
1191 | } | ||
1192 | /* Return Version Number ?*/ | ||
1193 | case 0x04: | ||
1194 | { | ||
1195 | /* do nothing */ | ||
1196 | break; | ||
1197 | } | ||
1198 | } | ||
1199 | } | ||
1200 | |||
1201 | #if 0 | ||
1202 | static void iap_handlepkt_mode7(const unsigned int len, const unsigned char *buf) | ||
1203 | { | ||
1204 | unsigned int cmd = buf[1]; | ||
1205 | switch (cmd) | ||
1206 | { | ||
1207 | /* RetTunerCaps */ | ||
1208 | case 0x02: | ||
1209 | { | ||
1210 | /* do nothing */ | ||
1211 | |||
1212 | /* GetAccessoryInfo */ | ||
1213 | unsigned char data[] = {0x00, 0x27, 0x00}; | ||
1214 | iap_send_pkt(data, sizeof(data)); | ||
1215 | break; | ||
1216 | } | ||
1217 | |||
1218 | /* RetTunerFreq */ | ||
1219 | case 0x0A: | ||
1220 | /* fall through */ | ||
1221 | /* TunerSeekDone */ | ||
1222 | case 0x13: | ||
1223 | { | ||
1224 | rmt_tuner_freq(len, buf); | ||
1225 | break; | ||
1226 | } | ||
1227 | |||
1228 | /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/ | ||
1229 | case 0x21: | ||
1230 | { | ||
1231 | rmt_tuner_rds_data(len, buf); | ||
1232 | break; | ||
1233 | } | ||
1234 | } | ||
1235 | } | ||
1236 | #endif | ||
1237 | |||
1238 | void iap_handlepkt(void) | ||
1239 | { | ||
1240 | int level; | ||
1241 | int length; | ||
1242 | |||
1243 | if(!iap_setupflag) return; | ||
1244 | |||
1245 | /* if we are waiting for a remote button to go out, | ||
1246 | delay the handling of the new packet */ | ||
1247 | if(iap_repeatbtn) | ||
1248 | { | ||
1249 | queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0); | ||
1250 | sleep(1); | ||
1251 | return; | ||
1252 | } | ||
1253 | |||
1254 | /* handle command by mode */ | ||
1255 | length = get_u16(iap_rxstart); | ||
1256 | #ifdef LOGF_ENABLE | ||
1257 | logf("R: %s", hexstring(iap_rxstart+2, (length))); | ||
1258 | #endif | ||
1259 | |||
1260 | unsigned char mode = *(iap_rxstart+2); | ||
1261 | switch (mode) { | ||
1262 | case 0: iap_handlepkt_mode0(length, iap_rxstart+2); break; | ||
1263 | case 2: iap_handlepkt_mode2(length, iap_rxstart+2); break; | ||
1264 | case 3: iap_handlepkt_mode3(length, iap_rxstart+2); break; | ||
1265 | case 4: iap_handlepkt_mode4(length, iap_rxstart+2); break; | ||
1266 | case 5: iap_handlepkt_mode5(length, iap_rxstart+2); break; | ||
1267 | /* case 7: iap_handlepkt_mode7(length, iap_rxstart+2); break; */ | ||
1268 | } | ||
1269 | |||
1270 | /* Remove the handled packet from the RX buffer | ||
1271 | * This needs to be done with interrupts disabled, to make | ||
1272 | * sure the buffer and the pointers into it are handled | ||
1273 | * cleanly | ||
1274 | */ | ||
1275 | level = disable_irq_save(); | ||
1276 | memmove(iap_rxstart, iap_rxstart+(length+2), (RX_BUFLEN+2)-(length+2)); | ||
1277 | iap_rxnext -= (length+2); | ||
1278 | iap_rxpayload -= (length+2); | ||
1279 | iap_rxlen += (length+2); | ||
1280 | restore_irq(level); | ||
1281 | |||
1282 | /* poke the poweroff timer */ | ||
1283 | reset_poweroff_timer(); | ||
1284 | } | ||
1285 | |||
1286 | int remote_control_rx(void) | ||
1287 | { | ||
1288 | int btn = iap_remotebtn; | ||
1289 | if(iap_repeatbtn) | ||
1290 | iap_repeatbtn--; | ||
1291 | |||
1292 | return btn; | ||
1293 | } | ||
1294 | |||
1295 | const unsigned char *iap_get_serbuf(void) | ||
1296 | { | ||
1297 | return iap_rxstart; | ||
1298 | } | ||
1299 | |||
1300 | #ifdef IAP_MALLOC_DYNAMIC | ||
1301 | static int iap_move_callback(int handle, void* current, void* new) | ||
1302 | { | ||
1303 | (void) handle; | ||
1304 | (void) current; | ||
1305 | |||
1306 | iap_txstart = new; | ||
1307 | iap_txpayload = iap_txstart+5; | ||
1308 | iap_txnext = iap_txpayload; | ||
1309 | iap_rxstart = iap_buffers+(TX_BUFLEN+6); | ||
1310 | |||
1311 | return BUFLIB_CB_OK; | ||
1312 | } | ||
1313 | #endif | ||
1314 | |||
1315 | /* Change the shuffle state */ | ||
1316 | void iap_shuffle_state(const bool state) | ||
1317 | { | ||
1318 | /* Set shuffle to enabled */ | ||
1319 | if(state && !global_settings.playlist_shuffle) | ||
1320 | { | ||
1321 | global_settings.playlist_shuffle = 1; | ||
1322 | settings_save(); | ||
1323 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
1324 | playlist_randomise(NULL, current_tick, true); | ||
1325 | } | ||
1326 | /* Set shuffle to disabled */ | ||
1327 | else if(!state && global_settings.playlist_shuffle) | ||
1328 | { | ||
1329 | global_settings.playlist_shuffle = 0; | ||
1330 | settings_save(); | ||
1331 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
1332 | playlist_sort(NULL, true); | ||
1333 | } | ||
1334 | } | ||
1335 | |||
1336 | /* Change the repeat state */ | ||
1337 | void iap_repeat_state(const unsigned char state) | ||
1338 | { | ||
1339 | if (state != global_settings.repeat_mode) | ||
1340 | { | ||
1341 | global_settings.repeat_mode = state; | ||
1342 | settings_save(); | ||
1343 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
1344 | audio_flush_and_reload_tracks(); | ||
1345 | } | ||
1346 | } | ||
1347 | |||
1348 | void iap_repeat_next(void) | ||
1349 | { | ||
1350 | switch (global_settings.repeat_mode) | ||
1351 | { | ||
1352 | case REPEAT_OFF: | ||
1353 | { | ||
1354 | iap_repeat_state(REPEAT_ALL); | ||
1355 | break; | ||
1356 | } | ||
1357 | case REPEAT_ALL: | ||
1358 | { | ||
1359 | iap_repeat_state(REPEAT_ONE); | ||
1360 | break; | ||
1361 | } | ||
1362 | case REPEAT_ONE: | ||
1363 | { | ||
1364 | iap_repeat_state(REPEAT_OFF); | ||
1365 | break; | ||
1366 | } | ||
1367 | } | ||
1368 | } | ||
1369 | |||
1370 | /* This function puts the current power/battery state | ||
1371 | * into the TX buffer. The buffer is assumed to be initialized | ||
1372 | */ | ||
1373 | void iap_fill_power_state(void) | ||
1374 | { | ||
1375 | unsigned char power_state; | ||
1376 | unsigned char battery_l; | ||
1377 | |||
1378 | power_state = charger_input_state; | ||
1379 | battery_l = battery_level(); | ||
1380 | |||
1381 | if (power_state == NO_CHARGER) { | ||
1382 | if (battery_l < 30) { | ||
1383 | IAP_TX_PUT(0x00); | ||
1384 | } else { | ||
1385 | IAP_TX_PUT(0x01); | ||
1386 | } | ||
1387 | IAP_TX_PUT((char)((battery_l * 255)/100)); | ||
1388 | } else { | ||
1389 | IAP_TX_PUT(0x04); | ||
1390 | IAP_TX_PUT(0x00); | ||
1391 | } | ||
1392 | } | ||
diff --git a/apps/iap/iap-core.h b/apps/iap/iap-core.h new file mode 100644 index 0000000000..d06e3c300c --- /dev/null +++ b/apps/iap/iap-core.h | |||
@@ -0,0 +1,250 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #ifndef _IAP_CORE_H | ||
20 | #define _IAP_CORE_H | ||
21 | |||
22 | #include <stdint.h> | ||
23 | #include <string.h> | ||
24 | #include "timefuncs.h" | ||
25 | #include "metadata.h" | ||
26 | #include "playlist.h" | ||
27 | #include "iap.h" | ||
28 | |||
29 | #define LOGF_ENABLE | ||
30 | /* #undef LOGF_ENABLE */ | ||
31 | #ifdef LOGF_ENABLE | ||
32 | #include "logf.h" | ||
33 | #endif | ||
34 | |||
35 | /* The Model ID of the iPod we emulate. Currently a 160GB classic */ | ||
36 | #define IAP_IPOD_MODEL (0x00130200U) | ||
37 | |||
38 | /* The firmware version we emulate. Currently 2.0.3 */ | ||
39 | #define IAP_IPOD_FIRMWARE_MAJOR (2) | ||
40 | #define IAP_IPOD_FIRMWARE_MINOR (0) | ||
41 | #define IAP_IPOD_FIRMWARE_REV (3) | ||
42 | |||
43 | /* Status code for IAP ack messages */ | ||
44 | #define IAP_ACK_OK (0x00) /* Success */ | ||
45 | #define IAP_ACK_UNKNOWN_DB (0x01) /* Unknown Database Category */ | ||
46 | #define IAP_ACK_CMD_FAILED (0x02) /* Command failed */ | ||
47 | #define IAP_ACK_NO_RESOURCE (0x03) /* Out of resources */ | ||
48 | #define IAP_ACK_BAD_PARAM (0x04) /* Bad parameter */ | ||
49 | #define IAP_ACK_UNKNOWN_ID (0x05) /* Unknown ID */ | ||
50 | #define IAP_ACK_PENDING (0x06) /* Command pending */ | ||
51 | #define IAP_ACK_NO_AUTHEN (0x07) /* Not authenticated */ | ||
52 | #define IAP_ACK_BAD_AUTHEN (0x08) /* Bad authentication version */ | ||
53 | /* 0x09 reserved */ | ||
54 | #define IAP_ACK_CERT_INVAL (0x0A) /* Certificate invalid */ | ||
55 | #define IAP_ACK_CERT_PERM (0x0B) /* Certificate permissions invalid */ | ||
56 | /* 0x0C-0x10 reserved */ | ||
57 | #define IAP_ACK_RES_INVAL (0x11) /* Invalid accessory resistor value */ | ||
58 | |||
59 | /* Add a button to the remote button bitfield. Also set iap_repeatbtn=1 | ||
60 | * to ensure a button press is at least delivered once. | ||
61 | */ | ||
62 | #define REMOTE_BUTTON(x) do { \ | ||
63 | iap_remotebtn |= (x); \ | ||
64 | iap_timeoutbtn = 3; \ | ||
65 | iap_repeatbtn = 2; \ | ||
66 | } while(0) | ||
67 | |||
68 | /* States of the extended command support */ | ||
69 | enum interface_state { | ||
70 | IST_STANDARD, /* General state, support lingo 0x00 commands */ | ||
71 | IST_EXTENDED, /* Extended Interface lingo (0x04) negotiated */ | ||
72 | }; | ||
73 | |||
74 | /* States of the authentication state machine */ | ||
75 | enum authen_state { | ||
76 | AUST_NONE, /* Initial state, no message sent */ | ||
77 | AUST_INIT, /* Remote side has requested authentication */ | ||
78 | AUST_CERTREQ, /* Remote certificate requested */ | ||
79 | AUST_CERTBEG, /* Certificate is being received */ | ||
80 | AUST_CERTDONE, /* Certificate received */ | ||
81 | AUST_CHASENT, /* Challenge sent */ | ||
82 | AUST_CHADONE, /* Challenge response received */ | ||
83 | AUST_AUTH, /* Authentication complete */ | ||
84 | }; | ||
85 | |||
86 | /* State of authentication */ | ||
87 | struct auth_t { | ||
88 | enum authen_state state; /* Current state of authentication */ | ||
89 | unsigned char max_section; /* The maximum number of certificate sections */ | ||
90 | unsigned char next_section; /* The next expected section number */ | ||
91 | }; | ||
92 | |||
93 | /* State of GetAccessoryInfo */ | ||
94 | enum accinfo_state { | ||
95 | ACCST_NONE, /* Initial state, no message sent */ | ||
96 | ACCST_INIT, /* Send out initial GetAccessoryInfo */ | ||
97 | ACCST_SENT, /* Wait for initial RetAccessoryInfo */ | ||
98 | ACCST_DATA, /* Query device information, according to capabilities */ | ||
99 | }; | ||
100 | |||
101 | /* A struct describing an attached device and it's current | ||
102 | * state | ||
103 | */ | ||
104 | struct device_t { | ||
105 | struct auth_t auth; /* Authentication state */ | ||
106 | enum accinfo_state accinfo; /* Accessory information state */ | ||
107 | uint32_t lingoes; /* Negotiated lingoes */ | ||
108 | uint32_t notifications; /* Requested notifications. These are just the | ||
109 | * notifications explicitly requested by the | ||
110 | * device | ||
111 | */ | ||
112 | uint32_t changed_notifications; /* Tracks notifications that changed since the last | ||
113 | * call to SetRemoteEventNotification or GetRemoteEventStatus | ||
114 | */ | ||
115 | bool do_notify; /* Notifications enabled */ | ||
116 | bool do_power_notify; /* Whether to send power change notifications. | ||
117 | * These are sent automatically to all devices | ||
118 | * that used IdentifyDeviceLingoes to identify | ||
119 | * themselves, independent of other notifications | ||
120 | */ | ||
121 | |||
122 | uint32_t trackpos_ms; /* These fields are to save the current state */ | ||
123 | uint32_t track_index; /* of various fields so we can send a notification */ | ||
124 | uint32_t chapter_index; /* if they change */ | ||
125 | unsigned char play_status; | ||
126 | bool mute; | ||
127 | unsigned char volume; | ||
128 | unsigned char power_state; | ||
129 | unsigned char battery_level; | ||
130 | uint32_t equalizer_index; | ||
131 | unsigned char shuffle; | ||
132 | unsigned char repeat; | ||
133 | struct tm datetime; | ||
134 | unsigned char alarm_state; | ||
135 | unsigned char alarm_hour; | ||
136 | unsigned char alarm_minute; | ||
137 | unsigned char backlight; | ||
138 | bool hold; | ||
139 | unsigned char soundcheck; | ||
140 | unsigned char audiobook; | ||
141 | uint16_t trackpos_s; | ||
142 | uint32_t capabilities; /* Capabilities of the device, as returned by type 0 | ||
143 | * of GetAccessoryInfo | ||
144 | */ | ||
145 | uint32_t capabilities_queried; /* Capabilities already queried */ | ||
146 | }; | ||
147 | |||
148 | extern struct device_t device; | ||
149 | #define DEVICE_AUTHENTICATED (device.auth.state == AUST_AUTH) | ||
150 | #define DEVICE_AUTH_RUNNING ((device.auth.state != AUST_NONE) && (device.auth.state != AUST_AUTH)) | ||
151 | #define DEVICE_LINGO_SUPPORTED(x) (device.lingoes & BIT_N((x)&0x1f)) | ||
152 | |||
153 | extern unsigned long iap_remotebtn; | ||
154 | extern unsigned int iap_timeoutbtn; | ||
155 | extern int iap_repeatbtn; | ||
156 | |||
157 | extern unsigned char* iap_txpayload; | ||
158 | extern unsigned char* iap_txnext; | ||
159 | |||
160 | /* These are a number of helper macros to manage the dynamic TX buffer content | ||
161 | * These macros DO NOT CHECK for buffer overflow. iap_send_tx() will, but | ||
162 | * it might be too late at that point. See the current size of TX_BUFLEN | ||
163 | */ | ||
164 | |||
165 | /* Initialize the TX buffer with a lingo and command ID. This will reset the | ||
166 | * data pointer, effectively invalidating unsent information in the TX buffer. | ||
167 | * There are two versions of this, one for 1 byte command IDs (all Lingoes except | ||
168 | * 0x04) and one for two byte command IDs (Lingo 0x04) | ||
169 | */ | ||
170 | #define IAP_TX_INIT(lingo, command) do { \ | ||
171 | iap_txnext = iap_txpayload; \ | ||
172 | IAP_TX_PUT((lingo)); \ | ||
173 | IAP_TX_PUT((command)); \ | ||
174 | } while (0) | ||
175 | |||
176 | #define IAP_TX_INIT4(lingo, command) do { \ | ||
177 | iap_txnext = iap_txpayload; \ | ||
178 | IAP_TX_PUT((lingo)); \ | ||
179 | IAP_TX_PUT_U16((command)); \ | ||
180 | } while (0) | ||
181 | |||
182 | /* Put an unsigned char into the TX buffer */ | ||
183 | #define IAP_TX_PUT(data) *(iap_txnext++) = (data) | ||
184 | |||
185 | /* Put a 16bit unsigned quantity into the TX buffer */ | ||
186 | #define IAP_TX_PUT_U16(data) do { \ | ||
187 | put_u16(iap_txnext, (data)); \ | ||
188 | iap_txnext += 2; \ | ||
189 | } while (0) | ||
190 | |||
191 | /* Put a 32bit unsigned quantity into the TX buffer */ | ||
192 | #define IAP_TX_PUT_U32(data) do { \ | ||
193 | put_u32(iap_txnext, (data)); \ | ||
194 | iap_txnext += 4; \ | ||
195 | } while (0) | ||
196 | |||
197 | /* Put an arbitrary amount of data (identified by a char pointer and | ||
198 | * a length) into the TX buffer | ||
199 | */ | ||
200 | #define IAP_TX_PUT_DATA(data, len) do { \ | ||
201 | memcpy(iap_txnext, (unsigned char *)(data), (len)); \ | ||
202 | iap_txnext += (len); \ | ||
203 | } while(0) | ||
204 | |||
205 | /* Put a NULL terminated string into the TX buffer, including the | ||
206 | * NULL byte | ||
207 | */ | ||
208 | #define IAP_TX_PUT_STRING(str) IAP_TX_PUT_DATA((str), strlen((str))+1) | ||
209 | |||
210 | /* Put a NULL terminated string into the TX buffer, taking care not to | ||
211 | * overflow the buffer. If the string does not fit into the TX buffer | ||
212 | * it will be truncated, but always NULL terminated. | ||
213 | * | ||
214 | * This function is expensive compared to the other IAP_TX_PUT_* | ||
215 | * functions | ||
216 | */ | ||
217 | #define IAP_TX_PUT_STRLCPY(str) iap_tx_strlcpy(str) | ||
218 | |||
219 | extern unsigned char lingo_versions[32][2]; | ||
220 | #define LINGO_SUPPORTED(x) (LINGO_MAJOR((x)&0x1f) > 0) | ||
221 | #define LINGO_MAJOR(x) (lingo_versions[(x)&0x1f][0]) | ||
222 | #define LINGO_MINOR(x) (lingo_versions[(x)&0x1f][1]) | ||
223 | |||
224 | void put_u16(unsigned char *buf, const uint16_t data); | ||
225 | void put_u32(unsigned char *buf, const uint32_t data); | ||
226 | uint32_t get_u32(const unsigned char *buf); | ||
227 | uint16_t get_u16(const unsigned char *buf); | ||
228 | void iap_tx_strlcpy(const unsigned char *str); | ||
229 | |||
230 | void iap_reset_auth(struct auth_t* auth); | ||
231 | void iap_reset_device(struct device_t* device); | ||
232 | |||
233 | void iap_shuffle_state(bool state); | ||
234 | void iap_repeat_state(unsigned char state); | ||
235 | void iap_repeat_next(void); | ||
236 | void iap_fill_power_state(void); | ||
237 | |||
238 | void iap_send_tx(void); | ||
239 | |||
240 | extern enum interface_state interface_state; | ||
241 | void iap_interface_state_change(const enum interface_state new); | ||
242 | |||
243 | extern bool iap_btnrepeat; | ||
244 | extern bool iap_btnshuffle; | ||
245 | |||
246 | uint32_t iap_get_trackpos(void); | ||
247 | uint32_t iap_get_trackindex(void); | ||
248 | void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3); | ||
249 | |||
250 | #endif | ||
diff --git a/apps/iap/iap-lingo.h b/apps/iap/iap-lingo.h new file mode 100644 index 0000000000..0c0a9e633d --- /dev/null +++ b/apps/iap/iap-lingo.h | |||
@@ -0,0 +1,23 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf); | ||
21 | void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf); | ||
22 | void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf); | ||
23 | void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf); | ||
diff --git a/apps/iap/iap-lingo0.c b/apps/iap/iap-lingo0.c new file mode 100644 index 0000000000..9e0355cb3f --- /dev/null +++ b/apps/iap/iap-lingo0.c | |||
@@ -0,0 +1,1035 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include "iap-core.h" | ||
21 | #include "iap-lingo.h" | ||
22 | #include "kernel.h" | ||
23 | #include "system.h" | ||
24 | |||
25 | /* | ||
26 | * This macro is meant to be used inside an IAP mode message handler. | ||
27 | * It is passed the expected minimum length of the message buffer. | ||
28 | * If the buffer does not have the required lenght an ACK | ||
29 | * packet with a Bad Parameter error is generated. | ||
30 | */ | ||
31 | #define CHECKLEN(x) do { \ | ||
32 | if (len < (x)) { \ | ||
33 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); \ | ||
34 | return; \ | ||
35 | }} while(0) | ||
36 | |||
37 | /* Check for authenticated state, and return an ACK Not | ||
38 | * Authenticated on failure. | ||
39 | */ | ||
40 | #define CHECKAUTH do { \ | ||
41 | if (!DEVICE_AUTHENTICATED) { \ | ||
42 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \ | ||
43 | return; \ | ||
44 | }} while(0) | ||
45 | |||
46 | static void cmd_ack(const unsigned char cmd, const unsigned char status) | ||
47 | { | ||
48 | IAP_TX_INIT(0x00, 0x02); | ||
49 | IAP_TX_PUT(status); | ||
50 | IAP_TX_PUT(cmd); | ||
51 | iap_send_tx(); | ||
52 | } | ||
53 | |||
54 | #define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK) | ||
55 | |||
56 | static void cmd_pending(const unsigned char cmd, const uint32_t msdelay) | ||
57 | { | ||
58 | IAP_TX_INIT(0x00, 0x02); | ||
59 | IAP_TX_PUT(0x06); | ||
60 | IAP_TX_PUT(cmd); | ||
61 | IAP_TX_PUT_U32(msdelay); | ||
62 | iap_send_tx(); | ||
63 | } | ||
64 | |||
65 | void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf) | ||
66 | { | ||
67 | unsigned int cmd = buf[1]; | ||
68 | |||
69 | /* We expect at least two bytes in the buffer, one for the | ||
70 | * lingo, one for the command | ||
71 | */ | ||
72 | CHECKLEN(2); | ||
73 | |||
74 | switch (cmd) { | ||
75 | /* RequestIdentify (0x00) | ||
76 | * | ||
77 | * Sent from the iPod to the device | ||
78 | */ | ||
79 | |||
80 | /* Identify (0x01) | ||
81 | * This command is deprecated. | ||
82 | * | ||
83 | * It is used by a device to inform the iPod of the devices | ||
84 | * presence and of the lingo the device supports. | ||
85 | * | ||
86 | * Also, it is used to negotiate power for RF transmitters | ||
87 | * | ||
88 | * Packet format (offset in buf[]: Description) | ||
89 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
90 | * 0x01: Command, always 0x01 | ||
91 | * 0x02: Lingo supported by the device | ||
92 | * | ||
93 | * Some RF transmitters use an extended version of this | ||
94 | * command: | ||
95 | * | ||
96 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
97 | * 0x01: Command, always 0x01 | ||
98 | * 0x02: Lingo supported by the device, always 0x05 (RF Transmitter) | ||
99 | * 0x03: Reserved, always 0x00 | ||
100 | * 0x04: Number of valid bits in the following fields | ||
101 | * 0x05-N: Datafields holding the number of bits specified in 0x04 | ||
102 | * | ||
103 | * Returns: (none) | ||
104 | * | ||
105 | * TODO: | ||
106 | * BeginHighPower/EndHighPower should be send in the periodic handler, | ||
107 | * depending on the current play status | ||
108 | */ | ||
109 | case 0x01: | ||
110 | { | ||
111 | unsigned char lingo = buf[2]; | ||
112 | |||
113 | /* This is sufficient even for Lingo 0x05, as we are | ||
114 | * not actually reading from the extended bits for now | ||
115 | */ | ||
116 | CHECKLEN(3); | ||
117 | |||
118 | /* Issuing this command exits any extended interface states | ||
119 | * and resets authentication | ||
120 | */ | ||
121 | iap_interface_state_change(IST_STANDARD); | ||
122 | iap_reset_device(&device); | ||
123 | |||
124 | switch (lingo) { | ||
125 | case 0x04: | ||
126 | { | ||
127 | /* A single lingo device negotiating the | ||
128 | * extended interface lingo. This causes an interface | ||
129 | * state change. | ||
130 | */ | ||
131 | iap_interface_state_change(IST_EXTENDED); | ||
132 | break; | ||
133 | } | ||
134 | |||
135 | case 0x05: | ||
136 | { | ||
137 | /* FM transmitter sends this: */ | ||
138 | /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */ | ||
139 | sleep(HZ/3); | ||
140 | /* RF Transmitter: Begin transmission */ | ||
141 | IAP_TX_INIT(0x05, 0x02); | ||
142 | |||
143 | iap_send_tx(); | ||
144 | break; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | if (lingo < 32) { | ||
149 | /* All devices that Identify get access to Lingoes 0x00 and 0x02 */ | ||
150 | device.lingoes = BIT_N(0x00) | BIT_N(0x02); | ||
151 | |||
152 | device.lingoes |= BIT_N(lingo); | ||
153 | |||
154 | /* Devices that Identify with Lingo 0x04 also gain access | ||
155 | * to Lingo 0x03 | ||
156 | */ | ||
157 | if (lingo == 0x04) | ||
158 | device.lingoes |= BIT_N(0x03); | ||
159 | } else { | ||
160 | device.lingoes = 0; | ||
161 | } | ||
162 | break; | ||
163 | } | ||
164 | |||
165 | /* ACK (0x02) | ||
166 | * | ||
167 | * Sent from the iPod to the device | ||
168 | */ | ||
169 | |||
170 | /* RequestRemoteUIMode (0x03) | ||
171 | * | ||
172 | * Request the current Extended Interface Mode state | ||
173 | * This command may be used only if the accessory requests Lingo 0x04 | ||
174 | * during its identification process. | ||
175 | * | ||
176 | * Packet format (offset in buf[]: Description) | ||
177 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
178 | * 0x01: Command, always 0x03 | ||
179 | * | ||
180 | * Returns on success: | ||
181 | * ReturnRemoteUIMode | ||
182 | * | ||
183 | * Packet format (offset in data[]: Description) | ||
184 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
185 | * 0x01: Command, always 0x04 | ||
186 | * 0x02: Current Extended Interface Mode (zero: false, non-zero: true) | ||
187 | * | ||
188 | * Returns on failure: | ||
189 | * IAP_ACK_BAD_PARAM | ||
190 | */ | ||
191 | case 0x03: | ||
192 | { | ||
193 | if (!DEVICE_LINGO_SUPPORTED(0x04)) { | ||
194 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
195 | break; | ||
196 | } | ||
197 | |||
198 | IAP_TX_INIT(0x00, 0x04); | ||
199 | if (interface_state == IST_EXTENDED) | ||
200 | IAP_TX_PUT(0x01); | ||
201 | else | ||
202 | IAP_TX_PUT(0x00); | ||
203 | |||
204 | iap_send_tx(); | ||
205 | break; | ||
206 | } | ||
207 | |||
208 | /* ReturnRemoteUIMode (0x04) | ||
209 | * | ||
210 | * Sent from the iPod to the device | ||
211 | */ | ||
212 | |||
213 | /* EnterRemoteUIMode (0x05) | ||
214 | * | ||
215 | * Request Extended Interface Mode | ||
216 | * This command may be used only if the accessory requests Lingo 0x04 | ||
217 | * during its identification process. | ||
218 | * | ||
219 | * Packet format (offset in buf[]: Description) | ||
220 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
221 | * 0x01: Command, always 0x05 | ||
222 | * | ||
223 | * Returns on success: | ||
224 | * IAP_ACK_PENDING | ||
225 | * IAP_ACK_OK | ||
226 | * | ||
227 | * Returns on failure: | ||
228 | * IAP_ACK_BAD_PARAM | ||
229 | */ | ||
230 | case 0x05: | ||
231 | { | ||
232 | if (!DEVICE_LINGO_SUPPORTED(0x04)) { | ||
233 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
234 | break; | ||
235 | } | ||
236 | |||
237 | cmd_pending(cmd, 1000); | ||
238 | iap_interface_state_change(IST_EXTENDED); | ||
239 | cmd_ok(cmd); | ||
240 | break; | ||
241 | } | ||
242 | |||
243 | /* ExitRemoteUIMode (0x06) | ||
244 | * | ||
245 | * Leave Extended Interface Mode | ||
246 | * This command may be used only if the accessory requests Lingo 0x04 | ||
247 | * during its identification process. | ||
248 | * | ||
249 | * Packet format (offset in buf[]: Description) | ||
250 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
251 | * 0x01: Command, always 0x06 | ||
252 | * | ||
253 | * Returns on success: | ||
254 | * IAP_ACK_PENDING | ||
255 | * IAP_ACK_OK | ||
256 | * | ||
257 | * Returns on failure: | ||
258 | * IAP_ACK_BAD_PARAM | ||
259 | */ | ||
260 | case 0x06: | ||
261 | { | ||
262 | if (!DEVICE_LINGO_SUPPORTED(0x04)) { | ||
263 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
264 | break; | ||
265 | } | ||
266 | |||
267 | cmd_pending(cmd, 1000); | ||
268 | iap_interface_state_change(IST_STANDARD); | ||
269 | cmd_ok(cmd); | ||
270 | break; | ||
271 | } | ||
272 | |||
273 | /* RequestiPodName (0x07) | ||
274 | * | ||
275 | * Retrieves the name of the iPod | ||
276 | * | ||
277 | * Packet format (offset in buf[]: Description) | ||
278 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
279 | * 0x01: Command, always 0x07 | ||
280 | * | ||
281 | * Returns: | ||
282 | * ReturniPodName | ||
283 | * | ||
284 | * Packet format (offset in data[]: Description) | ||
285 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
286 | * 0x01: Command, always 0x08 | ||
287 | * 0x02-0xNN: iPod name as NULL-terminated UTF8 string | ||
288 | */ | ||
289 | case 0x07: | ||
290 | { | ||
291 | IAP_TX_INIT(0x00, 0x08); | ||
292 | IAP_TX_PUT_STRING("ROCKBOX"); | ||
293 | |||
294 | iap_send_tx(); | ||
295 | break; | ||
296 | } | ||
297 | |||
298 | /* ReturniPodName (0x08) | ||
299 | * | ||
300 | * Sent from the iPod to the device | ||
301 | */ | ||
302 | |||
303 | /* RequestiPodSoftwareVersion (0x09) | ||
304 | * | ||
305 | * Returns the major, minor and revision numbers of the iPod | ||
306 | * software version. This not any Lingo protocol version. | ||
307 | * | ||
308 | * Packet format (offset in buf[]: Description) | ||
309 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
310 | * 0x01: Command, always 0x09 | ||
311 | * | ||
312 | * Returns: | ||
313 | * ReturniPodSoftwareVersion | ||
314 | * | ||
315 | * Packet format (offset in data[]: Description) | ||
316 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
317 | * 0x01: Command, always 0x0A | ||
318 | * 0x02: iPod major software version | ||
319 | * 0x03: iPod minor software version | ||
320 | * 0x04: iPod revision software version | ||
321 | */ | ||
322 | case 0x09: | ||
323 | { | ||
324 | IAP_TX_INIT(0x00, 0x0A); | ||
325 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR); | ||
326 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR); | ||
327 | IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV); | ||
328 | |||
329 | iap_send_tx(); | ||
330 | break; | ||
331 | } | ||
332 | |||
333 | /* ReturniPodSoftwareVersion (0x0A) | ||
334 | * | ||
335 | * Sent from the iPod to the device | ||
336 | */ | ||
337 | |||
338 | /* RequestiPodSerialNum (0x0B) | ||
339 | * | ||
340 | * Returns the iPod serial number | ||
341 | * | ||
342 | * Packet format (offset in buf[]: Description) | ||
343 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
344 | * 0x01: Command, always 0x0B | ||
345 | * | ||
346 | * Returns: | ||
347 | * ReturniPodSerialNumber | ||
348 | * | ||
349 | * Packet format (offset in data[]: Description) | ||
350 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
351 | * 0x01: Command, always 0x0C | ||
352 | * 0x02-0xNN: Serial number as NULL-terminated UTF8 string | ||
353 | */ | ||
354 | case 0x0B: | ||
355 | { | ||
356 | IAP_TX_INIT(0x00, 0x0C); | ||
357 | IAP_TX_PUT_STRING("0123456789"); | ||
358 | |||
359 | iap_send_tx(); | ||
360 | break; | ||
361 | } | ||
362 | |||
363 | /* ReturniPodSerialNum (0x0C) | ||
364 | * | ||
365 | * Sent from the iPod to the device | ||
366 | */ | ||
367 | |||
368 | /* RequestiPodModelNum (0x0D) | ||
369 | * | ||
370 | * Returns the model number as a 32bit unsigned integer and | ||
371 | * as a string. | ||
372 | * | ||
373 | * Packet format (offset in buf[]: Description) | ||
374 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
375 | * 0x01: Command, always 0x0D | ||
376 | * | ||
377 | * Returns: | ||
378 | * ReturniPodModelNum | ||
379 | * | ||
380 | * Packet format (offset in data[]: Description) | ||
381 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
382 | * 0x01: Command, always 0x0E | ||
383 | * 0x02-0x05: Model number as 32bit integer | ||
384 | * 0x06-0xNN: Model number as NULL-terminated UTF8 string | ||
385 | */ | ||
386 | case 0x0D: | ||
387 | { | ||
388 | IAP_TX_INIT(0x00, 0x0E); | ||
389 | IAP_TX_PUT_U32(IAP_IPOD_MODEL); | ||
390 | IAP_TX_PUT_STRING("ROCKBOX"); | ||
391 | |||
392 | iap_send_tx(); | ||
393 | break; | ||
394 | } | ||
395 | |||
396 | /* ReturniPodSerialNum (0x0E) | ||
397 | * | ||
398 | * Sent from the iPod to the device | ||
399 | */ | ||
400 | |||
401 | /* RequestLingoProtocolVersion (0x0F) | ||
402 | * | ||
403 | * Packet format (offset in buf[]: Description) | ||
404 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
405 | * 0x01: Command, always 0x0F | ||
406 | * 0x02: Lingo for which to request version information | ||
407 | * | ||
408 | * Returns on success: | ||
409 | * ReturnLingoProtocolVersion | ||
410 | * | ||
411 | * Packet format (offset in data[]: Description) | ||
412 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
413 | * 0x01: Command, always 0x10 | ||
414 | * 0x02: Lingo for which version information is returned | ||
415 | * 0x03: Major protocol version for the given lingo | ||
416 | * 0x04: Minor protocol version for the given lingo | ||
417 | * | ||
418 | * Returns on failure: | ||
419 | * IAP_ACK_BAD_PARAM | ||
420 | */ | ||
421 | case 0x0F: | ||
422 | { | ||
423 | unsigned char lingo = buf[2]; | ||
424 | |||
425 | CHECKLEN(3); | ||
426 | |||
427 | /* Supported lingos and versions are read from the lingo_versions | ||
428 | * array | ||
429 | */ | ||
430 | if (LINGO_SUPPORTED(lingo)) { | ||
431 | IAP_TX_INIT(0x00, 0x10); | ||
432 | IAP_TX_PUT(lingo); | ||
433 | IAP_TX_PUT(LINGO_MAJOR(lingo)); | ||
434 | IAP_TX_PUT(LINGO_MINOR(lingo)); | ||
435 | |||
436 | iap_send_tx(); | ||
437 | } else { | ||
438 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
439 | } | ||
440 | break; | ||
441 | } | ||
442 | |||
443 | /* ReturnLingoProtocolVersion (0x10) | ||
444 | * | ||
445 | * Sent from the iPod to the device | ||
446 | */ | ||
447 | |||
448 | /* IdentifyDeviceLingoes (0x13); | ||
449 | * | ||
450 | * Used by a device to inform the iPod of the devices | ||
451 | * presence and of the lingoes the device supports. | ||
452 | * | ||
453 | * Packet format (offset in buf[]: Description) | ||
454 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
455 | * 0x01: Command, always 0x13 | ||
456 | * 0x02-0x05: Device lingoes spoken | ||
457 | * 0x06-0x09: Device options | ||
458 | * 0x0A-0x0D: Device ID. Only important for authentication | ||
459 | * | ||
460 | * Returns on success: | ||
461 | * IAP_ACK_OK | ||
462 | * | ||
463 | * Returns on failure: | ||
464 | * IAP_ACK_CMD_FAILED | ||
465 | */ | ||
466 | case 0x13: | ||
467 | { | ||
468 | uint32_t lingoes = get_u32(&buf[2]); | ||
469 | uint32_t options = get_u32(&buf[6]); | ||
470 | uint32_t deviceid = get_u32(&buf[0x0A]); | ||
471 | bool seen_unsupported = false; | ||
472 | unsigned char i; | ||
473 | |||
474 | CHECKLEN(14); | ||
475 | |||
476 | /* Issuing this command exits any extended interface states */ | ||
477 | iap_interface_state_change(IST_STANDARD); | ||
478 | |||
479 | /* Loop through the lingoes advertised by the device. | ||
480 | * If it tries to use a lingo we do not support, return | ||
481 | * a Command Failed ACK. | ||
482 | */ | ||
483 | for(i=0; i<32; i++) { | ||
484 | if (lingoes & BIT_N(i)) { | ||
485 | /* Bit set by device */ | ||
486 | if (!LINGO_SUPPORTED(i)) { | ||
487 | seen_unsupported = true; | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | |||
492 | /* Bit 0 _must_ be set by the device */ | ||
493 | if (!(lingoes & 1)) { | ||
494 | seen_unsupported = true; | ||
495 | } | ||
496 | |||
497 | /* Specifying a deviceid without requesting authentication is | ||
498 | * an error | ||
499 | */ | ||
500 | if (deviceid && !(options & 0x03)) | ||
501 | seen_unsupported = true; | ||
502 | |||
503 | /* Specifying authentication without a deviceid is an error */ | ||
504 | if (!deviceid && (options & 0x03)) | ||
505 | seen_unsupported = true; | ||
506 | |||
507 | device.lingoes = 0; | ||
508 | if (seen_unsupported) { | ||
509 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
510 | break; | ||
511 | } | ||
512 | iap_reset_device(&device); | ||
513 | device.lingoes = lingoes; | ||
514 | |||
515 | /* Devices using IdentifyDeviceLingoes get power off notifications */ | ||
516 | device.do_power_notify = true; | ||
517 | |||
518 | /* If a new authentication is requested, start the auth | ||
519 | * process. | ||
520 | * The periodic handler will take care of sending out the | ||
521 | * GetDevAuthenticationInfo packet | ||
522 | * | ||
523 | * If no authentication is requested, schedule the start of | ||
524 | * GetAccessoryInfo | ||
525 | */ | ||
526 | if (deviceid && (options & 0x03) && !DEVICE_AUTH_RUNNING) { | ||
527 | device.auth.state = AUST_INIT; | ||
528 | } else { | ||
529 | device.accinfo = ACCST_INIT; | ||
530 | } | ||
531 | |||
532 | cmd_ok(cmd); | ||
533 | |||
534 | /* Bit 5: RF Transmitter lingo */ | ||
535 | if (lingoes & (1 << 5)) | ||
536 | { | ||
537 | /* FM transmitter sends this: */ | ||
538 | /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/ | ||
539 | |||
540 | /* GetAccessoryInfo */ | ||
541 | unsigned char data2[] = {0x00, 0x27, 0x00}; | ||
542 | iap_send_pkt(data2, sizeof(data2)); | ||
543 | /* RF Transmitter: Begin transmission */ | ||
544 | unsigned char data3[] = {0x05, 0x02}; | ||
545 | iap_send_pkt(data3, sizeof(data3)); | ||
546 | } | ||
547 | |||
548 | |||
549 | #if 0 | ||
550 | /* Bit 7: RF Tuner lingo */ | ||
551 | if (lingoes & (1 << 7)) | ||
552 | { | ||
553 | /* ipod fm remote sends this: */ | ||
554 | /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */ | ||
555 | radio_present = 1; | ||
556 | /* GetDevAuthenticationInfo */ | ||
557 | unsigned char data4[] = {0x00, 0x14}; | ||
558 | iap_send_pkt(data4, sizeof(data4)); | ||
559 | } | ||
560 | #endif | ||
561 | break; | ||
562 | } | ||
563 | |||
564 | /* GetDevAuthenticationInfo (0x14) | ||
565 | * | ||
566 | * Sent from the iPod to the device | ||
567 | */ | ||
568 | |||
569 | /* RetDevAuthenticationInfo (0x15) | ||
570 | * | ||
571 | * Send certificate information from the device to the iPod. | ||
572 | * The certificate may come in multiple parts and has | ||
573 | * to be reassembled. | ||
574 | * | ||
575 | * Packet format (offset in buf[]: Description) | ||
576 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
577 | * 0x01: Command, always 0x15 | ||
578 | * 0x02: Authentication major version | ||
579 | * 0x03: Authentication minor version | ||
580 | * 0x04: Certificate current section index | ||
581 | * 0x05: Certificate maximum section index | ||
582 | * 0x06-0xNN: Certificate data | ||
583 | * | ||
584 | * Returns on success: | ||
585 | * IAP_ACK_OK for intermediate sections | ||
586 | * AckDevAuthenticationInfo for the last section | ||
587 | * | ||
588 | * Returns on failure: | ||
589 | * IAP_ACK_BAD_PARAMETER | ||
590 | * AckDevAuthenticationInfo for version mismatches | ||
591 | * | ||
592 | */ | ||
593 | case 0x15: | ||
594 | { | ||
595 | /* There are two formats of this packet. One with only | ||
596 | * the version information bytes (for Auth version 1.0) | ||
597 | * and the long form shown above | ||
598 | */ | ||
599 | CHECKLEN(4); | ||
600 | |||
601 | if (!DEVICE_AUTH_RUNNING) { | ||
602 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
603 | break; | ||
604 | } | ||
605 | |||
606 | /* We only support version 2.0 */ | ||
607 | if ((buf[2] != 2) || (buf[3] != 0)) { | ||
608 | /* Version mismatches are signalled by AckDevAuthenticationInfo | ||
609 | * with the status set to Authentication Information unsupported | ||
610 | */ | ||
611 | iap_reset_auth(&(device.auth)); | ||
612 | |||
613 | IAP_TX_INIT(0x00, 0x16); | ||
614 | IAP_TX_PUT(0x08); | ||
615 | |||
616 | iap_send_tx(); | ||
617 | break; | ||
618 | } | ||
619 | |||
620 | /* There must be at least one byte of certificate data | ||
621 | * in the packet | ||
622 | */ | ||
623 | CHECKLEN(7); | ||
624 | |||
625 | switch (device.auth.state) | ||
626 | { | ||
627 | /* This is the first packet. Note the maximum section number | ||
628 | * so we can check it later. | ||
629 | */ | ||
630 | case AUST_CERTREQ: | ||
631 | { | ||
632 | device.auth.max_section = buf[5]; | ||
633 | device.auth.state = AUST_CERTBEG; | ||
634 | |||
635 | /* Intentional fall-through */ | ||
636 | } | ||
637 | /* All following packets */ | ||
638 | case AUST_CERTBEG: | ||
639 | { | ||
640 | /* Check if this is the expected section */ | ||
641 | if (buf[4] != device.auth.next_section) { | ||
642 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
643 | break; | ||
644 | } | ||
645 | |||
646 | /* Is this the last section? */ | ||
647 | if (device.auth.next_section == device.auth.max_section) { | ||
648 | /* If we could really do authentication we'd have to | ||
649 | * check the certificate here. Since we can't, just acknowledge | ||
650 | * the packet with an "everything OK" AckDevAuthenticationInfo | ||
651 | * | ||
652 | * Also, start GetAccessoryInfo process | ||
653 | */ | ||
654 | IAP_TX_INIT(0x00, 0x16); | ||
655 | IAP_TX_PUT(0x00); | ||
656 | |||
657 | iap_send_tx(); | ||
658 | device.auth.state = AUST_CERTDONE; | ||
659 | device.accinfo = ACCST_INIT; | ||
660 | } else { | ||
661 | device.auth.next_section++; | ||
662 | cmd_ok(cmd); | ||
663 | } | ||
664 | break; | ||
665 | } | ||
666 | |||
667 | default: | ||
668 | { | ||
669 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
670 | break; | ||
671 | } | ||
672 | } | ||
673 | |||
674 | break; | ||
675 | } | ||
676 | |||
677 | /* AckDevAuthenticationInfo (0x16) | ||
678 | * | ||
679 | * Sent from the iPod to the device | ||
680 | */ | ||
681 | |||
682 | /* GetDevAuthenticationSignature (0x17) | ||
683 | * | ||
684 | * Sent from the iPod to the device | ||
685 | */ | ||
686 | |||
687 | /* RetDevAuthenticationSignature (0x18) | ||
688 | * | ||
689 | * Return a calculated signature based on the device certificate | ||
690 | * and the challenge sent with GetDevAuthenticationSignature | ||
691 | * | ||
692 | * Packet format (offset in buf[]: Description) | ||
693 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
694 | * 0x01: Command, always 0x17 | ||
695 | * 0x02-0xNN: Certificate data | ||
696 | * | ||
697 | * Returns on success: | ||
698 | * AckDevAuthenticationStatus | ||
699 | * | ||
700 | * Packet format (offset in data[]: Description) | ||
701 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
702 | * 0x01: Command, always 0x19 | ||
703 | * 0x02: Status (0x00: OK) | ||
704 | * | ||
705 | * Returns on failure: | ||
706 | * IAP_ACK_BAD_PARAM | ||
707 | * | ||
708 | * TODO: | ||
709 | * There is a timeout of 75 seconds between GetDevAuthenticationSignature | ||
710 | * and RetDevAuthenticationSignature for Auth 2.0. This is currently not | ||
711 | * checked. | ||
712 | */ | ||
713 | case 0x18: | ||
714 | { | ||
715 | if (device.auth.state != AUST_CHASENT) { | ||
716 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
717 | break; | ||
718 | } | ||
719 | |||
720 | /* Here we could check the signature. Since we can't, just | ||
721 | * acknowledge and go to authenticated status | ||
722 | */ | ||
723 | IAP_TX_INIT(0x00, 0x19); | ||
724 | IAP_TX_PUT(0x00); | ||
725 | |||
726 | iap_send_tx(); | ||
727 | device.auth.state = AUST_AUTH; | ||
728 | break; | ||
729 | } | ||
730 | |||
731 | /* AckDevAuthenticationStatus (0x19) | ||
732 | * | ||
733 | * Sent from the iPod to the device | ||
734 | */ | ||
735 | |||
736 | /* GetiPodAuthenticationInfo (0x1A) | ||
737 | * | ||
738 | * Obtain authentication information from the iPod. | ||
739 | * This cannot be implemented without posessing an Apple signed | ||
740 | * certificate and the corresponding private key. | ||
741 | * | ||
742 | * Packet format (offset in buf[]: Description) | ||
743 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
744 | * 0x01: Command, always 0x1A | ||
745 | * | ||
746 | * This command requires authentication | ||
747 | * | ||
748 | * Returns: | ||
749 | * IAP_ACK_CMD_FAILED | ||
750 | */ | ||
751 | case 0x1A: | ||
752 | { | ||
753 | CHECKAUTH; | ||
754 | |||
755 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
756 | break; | ||
757 | } | ||
758 | |||
759 | /* RetiPodAuthenticationInfo (0x1B) | ||
760 | * | ||
761 | * Sent from the iPod to the device | ||
762 | */ | ||
763 | |||
764 | /* AckiPodAuthenticationInfo (0x1C) | ||
765 | * | ||
766 | * Confirm authentication information from the iPod. | ||
767 | * This cannot be implemented without posessing an Apple signed | ||
768 | * certificate and the corresponding private key. | ||
769 | * | ||
770 | * Packet format (offset in buf[]: Description) | ||
771 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
772 | * 0x01: Command, always 0x1C | ||
773 | * 0x02: Authentication state (0x00: OK) | ||
774 | * | ||
775 | * This command requires authentication | ||
776 | * | ||
777 | * Returns: (none) | ||
778 | */ | ||
779 | case 0x1C: | ||
780 | { | ||
781 | CHECKAUTH; | ||
782 | |||
783 | break; | ||
784 | } | ||
785 | |||
786 | /* GetiPodAuthenticationSignature (0x1D) | ||
787 | * | ||
788 | * Send challenge information to the iPod. | ||
789 | * This cannot be implemented without posessing an Apple signed | ||
790 | * certificate and the corresponding private key. | ||
791 | * | ||
792 | * Packet format (offset in buf[]: Description) | ||
793 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
794 | * 0x01: Command, always 0x1D | ||
795 | * 0x02-0x15: Challenge | ||
796 | * | ||
797 | * This command requires authentication | ||
798 | * | ||
799 | * Returns: | ||
800 | * IAP_ACK_CMD_FAILED | ||
801 | */ | ||
802 | case 0x1D: | ||
803 | { | ||
804 | CHECKAUTH; | ||
805 | |||
806 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
807 | break; | ||
808 | } | ||
809 | |||
810 | /* RetiPodAuthenticationSignature (0x1E) | ||
811 | * | ||
812 | * Sent from the iPod to the device | ||
813 | */ | ||
814 | |||
815 | /* AckiPodAuthenticationStatus (0x1F) | ||
816 | * | ||
817 | * Confirm chellenge information from the iPod. | ||
818 | * This cannot be implemented without posessing an Apple signed | ||
819 | * certificate and the corresponding private key. | ||
820 | * | ||
821 | * Packet format (offset in buf[]: Description) | ||
822 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
823 | * 0x01: Command, always 0x1C | ||
824 | * 0x02: Challenge state (0x00: OK) | ||
825 | * | ||
826 | * This command requires authentication | ||
827 | * | ||
828 | * Returns: (none) | ||
829 | */ | ||
830 | case 0x1F: | ||
831 | { | ||
832 | CHECKAUTH; | ||
833 | |||
834 | break; | ||
835 | } | ||
836 | |||
837 | /* NotifyiPodStateChange (0x23) | ||
838 | * | ||
839 | * Sent from the iPod to the device | ||
840 | */ | ||
841 | |||
842 | /* GetIpodOptions (0x24) | ||
843 | * | ||
844 | * Request supported features of the iPod | ||
845 | * | ||
846 | * Packet format (offset in buf[]: Description) | ||
847 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
848 | * 0x01: Command, always 0x24 | ||
849 | * | ||
850 | * Retuns: | ||
851 | * RetiPodOptions | ||
852 | * | ||
853 | * Packet format (offset in data[]: Description) | ||
854 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
855 | * 0x01: Command, always 0x25 | ||
856 | * 0x02-0x09: Options as a bitfield | ||
857 | */ | ||
858 | case 0x24: | ||
859 | { | ||
860 | /* There are only two features that can be communicated via this | ||
861 | * function, video support and the ability to control line-out usage. | ||
862 | * Rockbox supports neither | ||
863 | */ | ||
864 | IAP_TX_INIT(0x00, 0x25); | ||
865 | IAP_TX_PUT_U32(0x00); | ||
866 | IAP_TX_PUT_U32(0x00); | ||
867 | |||
868 | iap_send_tx(); | ||
869 | break; | ||
870 | } | ||
871 | |||
872 | /* RetiPodOptions (0x25) | ||
873 | * | ||
874 | * Sent from the iPod to the device | ||
875 | */ | ||
876 | |||
877 | /* GetAccessoryInfo (0x27) | ||
878 | * | ||
879 | * Sent from the iPod to the device | ||
880 | */ | ||
881 | |||
882 | /* RetAccessoryInfo (0x28) | ||
883 | * | ||
884 | * Send information about the device | ||
885 | * | ||
886 | * Packet format (offset in buf[]: Description) | ||
887 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
888 | * 0x01: Command, always 0x28 | ||
889 | * 0x02: Accessory info type | ||
890 | * 0x03-0xNN: Accessory information (depends on 0x02) | ||
891 | * | ||
892 | * Returns: (none) | ||
893 | * | ||
894 | * TODO: Actually do something with the information received here. | ||
895 | * Some devices actually expect us to request the data they | ||
896 | * offer, so completely ignoring this does not work, either. | ||
897 | */ | ||
898 | case 0x28: | ||
899 | { | ||
900 | CHECKLEN(3); | ||
901 | |||
902 | switch (buf[0x02]) | ||
903 | { | ||
904 | /* Info capabilities */ | ||
905 | case 0x00: | ||
906 | { | ||
907 | CHECKLEN(7); | ||
908 | |||
909 | device.capabilities = get_u32(&buf[0x03]); | ||
910 | /* Type 0x00 was already queried, that's where this information comes from */ | ||
911 | device.capabilities_queried = 0x01; | ||
912 | device.capabilities &= ~0x01; | ||
913 | break; | ||
914 | } | ||
915 | |||
916 | /* For now, ignore all other information */ | ||
917 | default: | ||
918 | { | ||
919 | break; | ||
920 | } | ||
921 | } | ||
922 | |||
923 | /* If there are any unqueried capabilities left, do so */ | ||
924 | if (device.capabilities) | ||
925 | device.accinfo = ACCST_DATA; | ||
926 | |||
927 | break; | ||
928 | } | ||
929 | |||
930 | /* GetiPodPreferences (0x29) | ||
931 | * | ||
932 | * Retrieve information about the current state of the | ||
933 | * iPod. | ||
934 | * | ||
935 | * Packet format (offset in buf[]: Description) | ||
936 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
937 | * 0x01: Command, always 0x29 | ||
938 | * 0x02: Information class requested | ||
939 | * | ||
940 | * This command requires authentication | ||
941 | * | ||
942 | * Returns on success: | ||
943 | * RetiPodPreferences | ||
944 | * | ||
945 | * Packet format (offset in data[]: Description) | ||
946 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
947 | * 0x01: Command, always 0x2A | ||
948 | * 0x02: Information class provided | ||
949 | * 0x03: Information | ||
950 | * | ||
951 | * Returns on failure: | ||
952 | * IAP_ACK_BAD_PARAM | ||
953 | */ | ||
954 | case 0x29: | ||
955 | { | ||
956 | CHECKLEN(3); | ||
957 | CHECKAUTH; | ||
958 | |||
959 | IAP_TX_INIT(0x00, 0x2A); | ||
960 | /* The only information really supported is 0x03, Line-out usage. | ||
961 | * All others are video related | ||
962 | */ | ||
963 | if (buf[2] == 0x03) { | ||
964 | IAP_TX_PUT(0x03); | ||
965 | IAP_TX_PUT(0x01); /* Line-out enabled */ | ||
966 | |||
967 | iap_send_tx(); | ||
968 | } else { | ||
969 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
970 | } | ||
971 | |||
972 | break; | ||
973 | } | ||
974 | |||
975 | /* RetiPodPreference (0x2A) | ||
976 | * | ||
977 | * Sent from the iPod to the device | ||
978 | */ | ||
979 | |||
980 | /* SetiPodPreferences (0x2B) | ||
981 | * | ||
982 | * Set preferences on the iPod | ||
983 | * | ||
984 | * Packet format (offset in buf[]: Description) | ||
985 | * 0x00: Lingo ID: General Lingo, always 0x00 | ||
986 | * 0x01: Command, always 0x29 | ||
987 | * 0x02: Prefecence class requested | ||
988 | * 0x03: Preference setting | ||
989 | * 0x04: Restore on exit | ||
990 | * | ||
991 | * This command requires authentication | ||
992 | * | ||
993 | * Returns on success: | ||
994 | * IAP_ACK_OK | ||
995 | * | ||
996 | * Returns on failure: | ||
997 | * IAP_ACK_BAD_PARAM | ||
998 | * IAP_ACK_CMD_FAILED | ||
999 | */ | ||
1000 | case 0x2B: | ||
1001 | { | ||
1002 | CHECKLEN(5); | ||
1003 | CHECKAUTH; | ||
1004 | |||
1005 | /* The only information really supported is 0x03, Line-out usage. | ||
1006 | * All others are video related | ||
1007 | */ | ||
1008 | if (buf[2] == 0x03) { | ||
1009 | /* If line-out disabled is requested, reply with IAP_ACK_CMD_FAILED, | ||
1010 | * otherwise with IAP_ACK_CMD_OK | ||
1011 | */ | ||
1012 | if (buf[3] == 0x00) { | ||
1013 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
1014 | } else { | ||
1015 | cmd_ok(cmd); | ||
1016 | } | ||
1017 | } else { | ||
1018 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1019 | } | ||
1020 | |||
1021 | break; | ||
1022 | } | ||
1023 | |||
1024 | /* The default response is IAP_ACK_BAD_PARAM */ | ||
1025 | default: | ||
1026 | { | ||
1027 | #ifdef LOGF_ENABLE | ||
1028 | logf("iap: Unsupported Mode00 Command"); | ||
1029 | #else | ||
1030 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1031 | #endif | ||
1032 | break; | ||
1033 | } | ||
1034 | } | ||
1035 | } | ||
diff --git a/apps/iap/iap-lingo2.c b/apps/iap/iap-lingo2.c new file mode 100644 index 0000000000..4fbf730192 --- /dev/null +++ b/apps/iap/iap-lingo2.c | |||
@@ -0,0 +1,278 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | /* Lingo 0x02, Simple Remote Lingo | ||
21 | * | ||
22 | * TODO: | ||
23 | * - Fix cmd 0x00 handling, there has to be a more elegant way of doing | ||
24 | * this | ||
25 | */ | ||
26 | |||
27 | #include "iap-core.h" | ||
28 | #include "iap-lingo.h" | ||
29 | #include "system.h" | ||
30 | #include "button.h" | ||
31 | #include "audio.h" | ||
32 | #include "settings.h" | ||
33 | |||
34 | /* | ||
35 | * This macro is meant to be used inside an IAP mode message handler. | ||
36 | * It is passed the expected minimum length of the message buffer. | ||
37 | * If the buffer does not have the required lenght an ACK | ||
38 | * packet with a Bad Parameter error is generated. | ||
39 | */ | ||
40 | #define CHECKLEN(x) do { \ | ||
41 | if (len < (x)) { \ | ||
42 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); \ | ||
43 | return; \ | ||
44 | }} while(0) | ||
45 | |||
46 | static void cmd_ack(const unsigned char cmd, const unsigned char status) | ||
47 | { | ||
48 | IAP_TX_INIT(0x02, 0x01); | ||
49 | IAP_TX_PUT(status); | ||
50 | IAP_TX_PUT(cmd); | ||
51 | |||
52 | iap_send_tx(); | ||
53 | } | ||
54 | |||
55 | #define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK) | ||
56 | |||
57 | void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf) | ||
58 | { | ||
59 | unsigned int cmd = buf[1]; | ||
60 | |||
61 | /* We expect at least three bytes in the buffer, one for the | ||
62 | * lingo, one for the command, and one for the first button | ||
63 | * state bits. | ||
64 | */ | ||
65 | CHECKLEN(3); | ||
66 | |||
67 | /* Lingo 0x02 must have been negotiated */ | ||
68 | if (!DEVICE_LINGO_SUPPORTED(0x02)) { | ||
69 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
70 | return; | ||
71 | } | ||
72 | |||
73 | switch (cmd) | ||
74 | { | ||
75 | /* ContextButtonStatus (0x00) | ||
76 | * | ||
77 | * Transmit button events from the device to the iPod | ||
78 | * | ||
79 | * Packet format (offset in buf[]: Description) | ||
80 | * 0x00: Lingo ID: Simple Remote Lingo, always 0x02 | ||
81 | * 0x01: Command, always 0x00 | ||
82 | * 0x02: Button states 0:7 | ||
83 | * 0x03: Button states 8:15 (optional) | ||
84 | * 0x04: Button states 16:23 (optional) | ||
85 | * 0x05: Button states 24:31 (optional) | ||
86 | * | ||
87 | * Returns: (none) | ||
88 | */ | ||
89 | case 0x00: | ||
90 | { | ||
91 | iap_remotebtn = BUTTON_NONE; | ||
92 | iap_timeoutbtn = 0; | ||
93 | |||
94 | if(buf[2] != 0) | ||
95 | { | ||
96 | if(buf[2] & 1) | ||
97 | REMOTE_BUTTON(BUTTON_RC_PLAY); | ||
98 | if(buf[2] & 2) | ||
99 | REMOTE_BUTTON(BUTTON_RC_VOL_UP); | ||
100 | if(buf[2] & 4) | ||
101 | REMOTE_BUTTON(BUTTON_RC_VOL_DOWN); | ||
102 | if(buf[2] & 8) | ||
103 | REMOTE_BUTTON(BUTTON_RC_RIGHT); | ||
104 | if(buf[2] & 16) | ||
105 | REMOTE_BUTTON(BUTTON_RC_LEFT); | ||
106 | } | ||
107 | else if(len >= 4 && buf[3] != 0) | ||
108 | { | ||
109 | if(buf[3] & 1) /* play */ | ||
110 | { | ||
111 | if (audio_status() != AUDIO_STATUS_PLAY) | ||
112 | REMOTE_BUTTON(BUTTON_RC_PLAY); | ||
113 | } | ||
114 | if(buf[3] & 2) /* pause */ | ||
115 | { | ||
116 | if (audio_status() == AUDIO_STATUS_PLAY) | ||
117 | REMOTE_BUTTON(BUTTON_RC_PLAY); | ||
118 | } | ||
119 | if(buf[3] & 128) /* Shuffle */ | ||
120 | { | ||
121 | if (!iap_btnshuffle) | ||
122 | { | ||
123 | iap_shuffle_state(!global_settings.playlist_shuffle); | ||
124 | iap_btnshuffle = true; | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | else if(len >= 5 && buf[4] != 0) | ||
129 | { | ||
130 | if(buf[4] & 1) /* repeat */ | ||
131 | { | ||
132 | if (!iap_btnrepeat) | ||
133 | { | ||
134 | iap_repeat_next(); | ||
135 | iap_btnrepeat = true; | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /* Power off | ||
140 | * Not quite sure how to react to this, but stopping playback | ||
141 | * is a good start. | ||
142 | */ | ||
143 | if (buf[4] & 0x04) | ||
144 | { | ||
145 | if (audio_status() == AUDIO_STATUS_PLAY) | ||
146 | REMOTE_BUTTON(BUTTON_RC_PLAY); | ||
147 | } | ||
148 | |||
149 | if(buf[4] & 16) /* ffwd */ | ||
150 | REMOTE_BUTTON(BUTTON_RC_RIGHT); | ||
151 | if(buf[4] & 32) /* frwd */ | ||
152 | REMOTE_BUTTON(BUTTON_RC_LEFT); | ||
153 | } | ||
154 | |||
155 | break; | ||
156 | } | ||
157 | /* ACK (0x01) | ||
158 | * | ||
159 | * Sent from the iPod to the device | ||
160 | */ | ||
161 | |||
162 | /* ImageButtonStatus (0x02) | ||
163 | * | ||
164 | * Transmit image button events from the device to the iPod | ||
165 | * | ||
166 | * Packet format (offset in buf[]: Description) | ||
167 | * 0x00: Lingo ID: Simple Remote Lingo, always 0x02 | ||
168 | * 0x01: Command, always 0x02 | ||
169 | * 0x02: Button states 0:7 | ||
170 | * 0x03: Button states 8:15 (optional) | ||
171 | * 0x04: Button states 16:23 (optional) | ||
172 | * 0x05: Button states 24:31 (optional) | ||
173 | * | ||
174 | * This command requires authentication | ||
175 | * | ||
176 | * Returns on success: | ||
177 | * IAP_ACK_OK | ||
178 | * | ||
179 | * Returns on failure: | ||
180 | * IAP_ACK_* | ||
181 | */ | ||
182 | case 0x02: | ||
183 | { | ||
184 | if (!DEVICE_AUTHENTICATED) { | ||
185 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); | ||
186 | break; | ||
187 | } | ||
188 | |||
189 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
190 | break; | ||
191 | } | ||
192 | |||
193 | /* VideoButtonStatus (0x03) | ||
194 | * | ||
195 | * Transmit video button events from the device to the iPod | ||
196 | * | ||
197 | * Packet format (offset in buf[]: Description) | ||
198 | * 0x00: Lingo ID: Simple Remote Lingo, always 0x02 | ||
199 | * 0x01: Command, always 0x03 | ||
200 | * 0x02: Button states 0:7 | ||
201 | * 0x03: Button states 8:15 (optional) | ||
202 | * 0x04: Button states 16:23 (optional) | ||
203 | * 0x05: Button states 24:31 (optional) | ||
204 | * | ||
205 | * This command requires authentication | ||
206 | * | ||
207 | * Returns on success: | ||
208 | * IAP_ACK_OK | ||
209 | * | ||
210 | * Returns on failure: | ||
211 | * IAP_ACK_* | ||
212 | */ | ||
213 | case 0x03: | ||
214 | { | ||
215 | if (!DEVICE_AUTHENTICATED) { | ||
216 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); | ||
217 | break; | ||
218 | } | ||
219 | |||
220 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
221 | break; | ||
222 | } | ||
223 | |||
224 | /* AudioButtonStatus (0x04) | ||
225 | * | ||
226 | * Transmit audio button events from the device to the iPod | ||
227 | * | ||
228 | * Packet format (offset in buf[]: Description) | ||
229 | * 0x00: Lingo ID: Simple Remote Lingo, always 0x02 | ||
230 | * 0x01: Command, always 0x04 | ||
231 | * 0x02: Button states 0:7 | ||
232 | * 0x03: Button states 8:15 (optional) | ||
233 | * 0x04: Button states 16:23 (optional) | ||
234 | * 0x05: Button states 24:31 (optional) | ||
235 | * | ||
236 | * This command requires authentication | ||
237 | * | ||
238 | * Returns on success: | ||
239 | * IAP_ACK_OK | ||
240 | * | ||
241 | * Returns on failure: | ||
242 | * IAP_ACK_* | ||
243 | */ | ||
244 | case 0x04: | ||
245 | { | ||
246 | unsigned char repeatbuf[6]; | ||
247 | |||
248 | if (!DEVICE_AUTHENTICATED) { | ||
249 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); | ||
250 | break; | ||
251 | } | ||
252 | |||
253 | /* This is basically the same command as ContextButtonStatus (0x00), | ||
254 | * with the difference that it requires authentication and that | ||
255 | * it returns an ACK packet to the device. | ||
256 | * So just route it through the handler again, with 0x00 as the | ||
257 | * command | ||
258 | */ | ||
259 | memcpy(repeatbuf, buf, 6); | ||
260 | repeatbuf[1] = 0x00; | ||
261 | iap_handlepkt_mode2((len<6)?len:6, repeatbuf); | ||
262 | |||
263 | cmd_ok(cmd); | ||
264 | break; | ||
265 | } | ||
266 | |||
267 | /* The default response is IAP_ACK_BAD_PARAM */ | ||
268 | default: | ||
269 | { | ||
270 | #ifdef LOGF_ENABLE | ||
271 | logf("iap: Unsupported Mode02 Command"); | ||
272 | #else | ||
273 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
274 | #endif | ||
275 | break; | ||
276 | } | ||
277 | } | ||
278 | } | ||
diff --git a/apps/iap/iap-lingo3.c b/apps/iap/iap-lingo3.c new file mode 100644 index 0000000000..0ed3df118e --- /dev/null +++ b/apps/iap/iap-lingo3.c | |||
@@ -0,0 +1,1508 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | /* Lingo 0x03: Display Remote Lingo | ||
21 | * | ||
22 | * A bit of a hodgepogde of odds and ends. | ||
23 | * | ||
24 | * Used to control the equalizer in version 1.00 of the Lingo, but later | ||
25 | * grew functions to control album art transfer and check the player | ||
26 | * status. | ||
27 | * | ||
28 | * TODO: | ||
29 | * - Actually support multiple equalizer profiles, currently only the | ||
30 | * profile 0 (equalizer disabled) is supported | ||
31 | */ | ||
32 | |||
33 | #include "iap-core.h" | ||
34 | #include "iap-lingo.h" | ||
35 | #include "system.h" | ||
36 | #include "audio.h" | ||
37 | #include "powermgmt.h" | ||
38 | #include "settings.h" | ||
39 | #include "metadata.h" | ||
40 | #include "playback.h" | ||
41 | |||
42 | /* | ||
43 | * This macro is meant to be used inside an IAP mode message handler. | ||
44 | * It is passed the expected minimum length of the message buffer. | ||
45 | * If the buffer does not have the required lenght an ACK | ||
46 | * packet with a Bad Parameter error is generated. | ||
47 | */ | ||
48 | #define CHECKLEN(x) do { \ | ||
49 | if (len < (x)) { \ | ||
50 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); \ | ||
51 | return; \ | ||
52 | }} while(0) | ||
53 | |||
54 | /* Check for authenticated state, and return an ACK Not | ||
55 | * Authenticated on failure. | ||
56 | */ | ||
57 | #define CHECKAUTH do { \ | ||
58 | if (!DEVICE_AUTHENTICATED) { \ | ||
59 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \ | ||
60 | return; \ | ||
61 | }} while(0) | ||
62 | |||
63 | static void cmd_ack(const unsigned char cmd, const unsigned char status) | ||
64 | { | ||
65 | IAP_TX_INIT(0x03, 0x00); | ||
66 | IAP_TX_PUT(status); | ||
67 | IAP_TX_PUT(cmd); | ||
68 | |||
69 | iap_send_tx(); | ||
70 | } | ||
71 | |||
72 | #define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK) | ||
73 | |||
74 | void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf) | ||
75 | { | ||
76 | unsigned int cmd = buf[1]; | ||
77 | |||
78 | /* We expect at least two bytes in the buffer, one for the | ||
79 | * state bits. | ||
80 | */ | ||
81 | CHECKLEN(2); | ||
82 | |||
83 | /* Lingo 0x03 must have been negotiated */ | ||
84 | if (!DEVICE_LINGO_SUPPORTED(0x03)) { | ||
85 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
86 | return; | ||
87 | } | ||
88 | |||
89 | switch (cmd) | ||
90 | { | ||
91 | /* ACK (0x00) | ||
92 | * | ||
93 | * Sent from the iPod to the device | ||
94 | */ | ||
95 | |||
96 | /* GetCurrentEQProfileIndex (0x01) | ||
97 | * | ||
98 | * Return the index of the current equalizer profile. | ||
99 | * | ||
100 | * Packet format (offset in buf[]: Description) | ||
101 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
102 | * 0x01: Command, always 0x01 | ||
103 | * | ||
104 | * Returns: | ||
105 | * RetCurrentEQProfileIndex | ||
106 | * | ||
107 | * Packet format (offset in data[]: Description) | ||
108 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
109 | * 0x01: Command, always 0x02 | ||
110 | * 0x02-0x05: Index as an unsigned 32bit integer | ||
111 | */ | ||
112 | case 0x01: | ||
113 | { | ||
114 | IAP_TX_INIT(0x03, 0x02); | ||
115 | IAP_TX_PUT_U32(0x00); | ||
116 | |||
117 | iap_send_tx(); | ||
118 | break; | ||
119 | } | ||
120 | |||
121 | /* RetCurrentEQProfileIndex (0x02) | ||
122 | * | ||
123 | * Sent from the iPod to the device | ||
124 | */ | ||
125 | |||
126 | /* SetCurrentEQProfileIndex (0x03) | ||
127 | * | ||
128 | * Set the active equalizer profile | ||
129 | * | ||
130 | * Packet format (offset in buf[]: Description) | ||
131 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
132 | * 0x01: Command, always 0x03 | ||
133 | * 0x02-0x05: Profile index to activate | ||
134 | * 0x06: Whether to restore the previous profile on detach | ||
135 | * | ||
136 | * Returns on success: | ||
137 | * IAP_ACK_OK | ||
138 | * | ||
139 | * Returns on failure: | ||
140 | * IAP_ACK_CMD_FAILED | ||
141 | * | ||
142 | * TODO: Figure out return code for invalid index | ||
143 | */ | ||
144 | case 0x03: | ||
145 | { | ||
146 | uint32_t index; | ||
147 | |||
148 | CHECKLEN(7); | ||
149 | |||
150 | index = get_u32(&buf[2]); | ||
151 | |||
152 | if (index > 0) { | ||
153 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
154 | break; | ||
155 | } | ||
156 | |||
157 | /* Currently, we just ignore the command and acknowledge it */ | ||
158 | cmd_ok(cmd); | ||
159 | break; | ||
160 | } | ||
161 | |||
162 | /* GetNumEQProfiles (0x04) | ||
163 | * | ||
164 | * Get the number of available equalizer profiles | ||
165 | * | ||
166 | * Packet format (offset in buf[]: Description) | ||
167 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
168 | * 0x01: Command, always 0x04 | ||
169 | * | ||
170 | * Returns: | ||
171 | * RetNumEQProfiles | ||
172 | * | ||
173 | * Packet format (offset in data[]: Description) | ||
174 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
175 | * 0x01: Command, always 0x05 | ||
176 | * 0x02-0x05: Number as an unsigned 32bit integer | ||
177 | */ | ||
178 | case 0x04: | ||
179 | { | ||
180 | IAP_TX_INIT(0x03, 0x05); | ||
181 | /* Return one profile (0, the disabled profile) */ | ||
182 | IAP_TX_PUT_U32(0x01); | ||
183 | |||
184 | iap_send_tx(); | ||
185 | break; | ||
186 | } | ||
187 | |||
188 | /* RetNumEQProfiles (0x05) | ||
189 | * | ||
190 | * Sent from the iPod to the device | ||
191 | */ | ||
192 | |||
193 | /* GetIndexedEQProfileName (0x06) | ||
194 | * | ||
195 | * Return the name of the indexed equalizer profile | ||
196 | * | ||
197 | * Packet format (offset in buf[]: Description) | ||
198 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
199 | * 0x01: Command, always 0x06 | ||
200 | * 0x02-0x05: Profile index to get the name of | ||
201 | * | ||
202 | * Returns on success: | ||
203 | * RetIndexedEQProfileName | ||
204 | * | ||
205 | * Packet format (offset in data[]: Description) | ||
206 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
207 | * 0x01: Command, always 0x06 | ||
208 | * 0x02-0xNN: Name as an UTF-8 null terminated string | ||
209 | * | ||
210 | * Returns on failure: | ||
211 | * IAP_ACK_BAD_PARAM | ||
212 | * | ||
213 | * TODO: Figure out return code for out of range index | ||
214 | */ | ||
215 | case 0x06: | ||
216 | { | ||
217 | uint32_t index; | ||
218 | |||
219 | CHECKLEN(6); | ||
220 | |||
221 | index = get_u32(&buf[2]); | ||
222 | |||
223 | if (index > 0) { | ||
224 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
225 | break; | ||
226 | } | ||
227 | IAP_TX_INIT(0x03, 0x07); | ||
228 | IAP_TX_PUT_STRING("Default"); | ||
229 | |||
230 | iap_send_tx(); | ||
231 | break; | ||
232 | } | ||
233 | |||
234 | /* RetIndexedQUProfileName (0x07) | ||
235 | * | ||
236 | * Sent from the iPod to the device | ||
237 | */ | ||
238 | |||
239 | /* SetRemoteEventNotification (0x08) | ||
240 | * | ||
241 | * Set events the device would like to be notified about | ||
242 | * | ||
243 | * Packet format (offset in buf[]: Description) | ||
244 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
245 | * 0x01: Command, always 0x08 | ||
246 | * 0x02-0x05: Event bitmask | ||
247 | * | ||
248 | * Returns: | ||
249 | * IAP_ACK_OK | ||
250 | */ | ||
251 | case 0x08: | ||
252 | { | ||
253 | struct tm* tm; | ||
254 | |||
255 | CHECKLEN(6); | ||
256 | CHECKAUTH; | ||
257 | |||
258 | /* Save the current state of the various attributes we track */ | ||
259 | device.trackpos_ms = iap_get_trackpos(); | ||
260 | device.track_index = iap_get_trackindex(); | ||
261 | device.chapter_index = 0; | ||
262 | device.play_status = audio_status(); | ||
263 | /* TODO: Fix this */ | ||
264 | device.mute = false; | ||
265 | device.volume = 0x80; | ||
266 | device.power_state = charger_input_state; | ||
267 | device.battery_level = battery_level(); | ||
268 | /* TODO: Fix this */ | ||
269 | device.equalizer_index = 0; | ||
270 | device.shuffle = global_settings.playlist_shuffle; | ||
271 | device.repeat = global_settings.repeat_mode; | ||
272 | tm = get_time(); | ||
273 | memcpy(&(device.datetime), tm, sizeof(struct tm)); | ||
274 | device.alarm_state = 0; | ||
275 | device.alarm_hour = 0; | ||
276 | device.alarm_minute = 0; | ||
277 | /* TODO: Fix this */ | ||
278 | device.backlight = 0; | ||
279 | device.hold = button_hold(); | ||
280 | device.soundcheck = 0; | ||
281 | device.audiobook = 0; | ||
282 | device.trackpos_s = (device.trackpos_ms/1000) & 0xFFFF; | ||
283 | |||
284 | /* Get the notification bits */ | ||
285 | device.do_notify = false; | ||
286 | device.changed_notifications = 0; | ||
287 | device.notifications = get_u32(&buf[0x02]); | ||
288 | if (device.notifications) | ||
289 | device.do_notify = true; | ||
290 | |||
291 | cmd_ok(cmd); | ||
292 | break; | ||
293 | } | ||
294 | |||
295 | /* RemoteEventNotification (0x09) | ||
296 | * | ||
297 | * Sent from the iPod to the device | ||
298 | */ | ||
299 | |||
300 | /* GetRemoteEventStatus (0x0A) | ||
301 | * | ||
302 | * Request the events changed since the last call to GetREmoteEventStatus | ||
303 | * or SetRemoteEventNotification | ||
304 | * | ||
305 | * Packet format (offset in buf[]: Description) | ||
306 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
307 | * 0x01: Command, always 0x0A | ||
308 | * | ||
309 | * This command requires authentication | ||
310 | * | ||
311 | * Returns: | ||
312 | * RetRemoteEventNotification | ||
313 | * | ||
314 | * Packet format (offset in data[]: Description) | ||
315 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
316 | * 0x01: Command, always 0x0B | ||
317 | * 0x02-0x05: Event status bits | ||
318 | */ | ||
319 | case 0x0A: | ||
320 | { | ||
321 | CHECKAUTH; | ||
322 | IAP_TX_INIT(0x03, 0x0B); | ||
323 | IAP_TX_PUT_U32(device.changed_notifications); | ||
324 | |||
325 | iap_send_tx(); | ||
326 | |||
327 | device.changed_notifications = 0; | ||
328 | break; | ||
329 | } | ||
330 | |||
331 | /* RetRemoteEventStatus (0x0B) | ||
332 | * | ||
333 | * Sent from the iPod to the device | ||
334 | */ | ||
335 | |||
336 | /* GetiPodStateInfo (0x0C) | ||
337 | * | ||
338 | * Request state information from the iPod | ||
339 | * | ||
340 | * Packet format (offset in buf[]: Description) | ||
341 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
342 | * 0x01: Command, always 0x0C | ||
343 | * 0x02: Type information | ||
344 | * | ||
345 | * This command requires authentication | ||
346 | * | ||
347 | * Returns: | ||
348 | * RetiPodStateInfo | ||
349 | * | ||
350 | * Packet format (offset in data[]: Description) | ||
351 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
352 | * 0x01: Command, always 0x0D | ||
353 | * 0x02: Type information | ||
354 | * 0x03-0xNN: State information | ||
355 | */ | ||
356 | case 0x0C: | ||
357 | { | ||
358 | struct mp3entry* id3; | ||
359 | struct playlist_info* playlist; | ||
360 | int play_status; | ||
361 | struct tm* tm; | ||
362 | |||
363 | CHECKLEN(3); | ||
364 | CHECKAUTH; | ||
365 | |||
366 | IAP_TX_INIT(0x03, 0x0D); | ||
367 | IAP_TX_PUT(buf[0x02]); | ||
368 | |||
369 | switch (buf[0x02]) | ||
370 | { | ||
371 | /* 0x00: Track position | ||
372 | * Data length: 4 | ||
373 | */ | ||
374 | case 0x00: | ||
375 | { | ||
376 | id3 = audio_current_track(); | ||
377 | IAP_TX_PUT_U32(id3->elapsed); | ||
378 | |||
379 | iap_send_tx(); | ||
380 | break; | ||
381 | } | ||
382 | |||
383 | /* 0x01: Track index | ||
384 | * Data length: 4 | ||
385 | */ | ||
386 | case 0x01: | ||
387 | { | ||
388 | playlist = playlist_get_current(); | ||
389 | IAP_TX_PUT_U32(playlist->index - playlist->first_index); | ||
390 | |||
391 | iap_send_tx(); | ||
392 | break; | ||
393 | } | ||
394 | |||
395 | /* 0x02: Chapter information | ||
396 | * Data length: 8 | ||
397 | */ | ||
398 | case 0x02: | ||
399 | { | ||
400 | playlist = playlist_get_current(); | ||
401 | IAP_TX_PUT_U32(playlist->index - playlist->first_index); | ||
402 | /* Indicate that track does not have chapters */ | ||
403 | IAP_TX_PUT_U16(0x0000); | ||
404 | IAP_TX_PUT_U16(0xFFFF); | ||
405 | |||
406 | iap_send_tx(); | ||
407 | break; | ||
408 | } | ||
409 | |||
410 | /* 0x03: Play status | ||
411 | * Data length: 1 | ||
412 | */ | ||
413 | case 0x03: | ||
414 | { | ||
415 | /* TODO: Handle FF/REW | ||
416 | */ | ||
417 | play_status = audio_status(); | ||
418 | if (play_status & AUDIO_STATUS_PLAY) { | ||
419 | if (play_status & AUDIO_STATUS_PAUSE) { | ||
420 | IAP_TX_PUT(0x02); | ||
421 | } else { | ||
422 | IAP_TX_PUT(0x01); | ||
423 | } | ||
424 | } else { | ||
425 | IAP_TX_PUT(0x00); | ||
426 | } | ||
427 | |||
428 | iap_send_tx(); | ||
429 | break; | ||
430 | } | ||
431 | |||
432 | /* 0x04: Mute/UI/Volume | ||
433 | * Data length: 2 | ||
434 | */ | ||
435 | case 0x04: | ||
436 | { | ||
437 | /* Figuring out what the current volume is | ||
438 | * seems to be tricky. | ||
439 | * TODO: Fix. | ||
440 | */ | ||
441 | |||
442 | /* Mute status */ | ||
443 | IAP_TX_PUT(0x00); | ||
444 | /* Volume */ | ||
445 | IAP_TX_PUT(0x80); | ||
446 | |||
447 | iap_send_tx(); | ||
448 | break; | ||
449 | } | ||
450 | |||
451 | /* 0x05: Power/Battery | ||
452 | * Data length: 2 | ||
453 | */ | ||
454 | case 0x05: | ||
455 | { | ||
456 | iap_fill_power_state(); | ||
457 | |||
458 | iap_send_tx(); | ||
459 | break; | ||
460 | } | ||
461 | |||
462 | /* 0x06: Equalizer state | ||
463 | * Data length: 4 | ||
464 | */ | ||
465 | case 0x06: | ||
466 | { | ||
467 | /* Currently only one equalizer setting supported, 0 */ | ||
468 | IAP_TX_PUT_U32(0x00); | ||
469 | |||
470 | iap_send_tx(); | ||
471 | break; | ||
472 | } | ||
473 | |||
474 | /* 0x07: Shuffle | ||
475 | * Data length: 1 | ||
476 | */ | ||
477 | case 0x07: | ||
478 | { | ||
479 | IAP_TX_PUT(global_settings.playlist_shuffle?0x01:0x00); | ||
480 | |||
481 | iap_send_tx(); | ||
482 | break; | ||
483 | } | ||
484 | |||
485 | /* 0x08: Repeat | ||
486 | * Data length: 1 | ||
487 | */ | ||
488 | case 0x08: | ||
489 | { | ||
490 | switch (global_settings.repeat_mode) | ||
491 | { | ||
492 | case REPEAT_OFF: | ||
493 | { | ||
494 | IAP_TX_PUT(0x00); | ||
495 | break; | ||
496 | } | ||
497 | |||
498 | case REPEAT_ONE: | ||
499 | { | ||
500 | IAP_TX_PUT(0x01); | ||
501 | break; | ||
502 | } | ||
503 | |||
504 | case REPEAT_ALL: | ||
505 | { | ||
506 | IAP_TX_PUT(0x02); | ||
507 | break; | ||
508 | } | ||
509 | } | ||
510 | |||
511 | iap_send_tx(); | ||
512 | break; | ||
513 | } | ||
514 | |||
515 | /* 0x09: Data/Time | ||
516 | * Data length: 6 | ||
517 | */ | ||
518 | case 0x09: | ||
519 | { | ||
520 | tm = get_time(); | ||
521 | |||
522 | /* Year */ | ||
523 | IAP_TX_PUT_U16(tm->tm_year); | ||
524 | |||
525 | /* Month */ | ||
526 | IAP_TX_PUT(tm->tm_mon+1); | ||
527 | |||
528 | /* Day */ | ||
529 | IAP_TX_PUT(tm->tm_mday); | ||
530 | |||
531 | /* Hour */ | ||
532 | IAP_TX_PUT(tm->tm_hour); | ||
533 | |||
534 | /* Minute */ | ||
535 | IAP_TX_PUT(tm->tm_min); | ||
536 | |||
537 | iap_send_tx(); | ||
538 | break; | ||
539 | } | ||
540 | |||
541 | /* 0x0A: Alarm | ||
542 | * Data length: 3 | ||
543 | */ | ||
544 | case 0x0A: | ||
545 | { | ||
546 | /* Alarm not supported, always off */ | ||
547 | IAP_TX_PUT(0x00); | ||
548 | IAP_TX_PUT(0x00); | ||
549 | IAP_TX_PUT(0x00); | ||
550 | |||
551 | iap_send_tx(); | ||
552 | break; | ||
553 | } | ||
554 | |||
555 | /* 0x0B: Backlight | ||
556 | * Data length: 1 | ||
557 | */ | ||
558 | case 0x0B: | ||
559 | { | ||
560 | /* TOOD: Find out how to do this */ | ||
561 | IAP_TX_PUT(0x00); | ||
562 | |||
563 | iap_send_tx(); | ||
564 | break; | ||
565 | } | ||
566 | |||
567 | /* 0x0C: Hold switch | ||
568 | * Data length: 1 | ||
569 | */ | ||
570 | case 0x0C: | ||
571 | { | ||
572 | IAP_TX_PUT(button_hold()?0x01:0x00); | ||
573 | |||
574 | iap_send_tx(); | ||
575 | break; | ||
576 | } | ||
577 | |||
578 | /* 0x0D: Sound check | ||
579 | * Data length: 1 | ||
580 | */ | ||
581 | case 0x0D: | ||
582 | { | ||
583 | /* TODO: Find out what the hell this is. Default to off */ | ||
584 | IAP_TX_PUT(0x00); | ||
585 | |||
586 | iap_send_tx(); | ||
587 | break; | ||
588 | } | ||
589 | |||
590 | /* 0x0E: Audiobook | ||
591 | * Data length: 1 | ||
592 | */ | ||
593 | case 0x0E: | ||
594 | { | ||
595 | /* Default to normal */ | ||
596 | IAP_TX_PUT(0x00); | ||
597 | |||
598 | iap_send_tx(); | ||
599 | break; | ||
600 | } | ||
601 | |||
602 | /* 0x0F: Track position in seconds | ||
603 | * Data length: 2 | ||
604 | */ | ||
605 | case 0x0F: | ||
606 | { | ||
607 | unsigned int pos; | ||
608 | |||
609 | id3 = audio_current_track(); | ||
610 | pos = id3->elapsed/1000; | ||
611 | |||
612 | IAP_TX_PUT_U16(pos); | ||
613 | |||
614 | iap_send_tx(); | ||
615 | break; | ||
616 | } | ||
617 | |||
618 | /* 0x10: Mute/UI/Absolute volume | ||
619 | * Data length: 3 | ||
620 | */ | ||
621 | case 0x10: | ||
622 | { | ||
623 | /* TODO: See volume above */ | ||
624 | IAP_TX_PUT(0x00); | ||
625 | IAP_TX_PUT(0x80); | ||
626 | IAP_TX_PUT(0x80); | ||
627 | |||
628 | iap_send_tx(); | ||
629 | break; | ||
630 | } | ||
631 | |||
632 | default: | ||
633 | { | ||
634 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
635 | break; | ||
636 | } | ||
637 | } | ||
638 | break; | ||
639 | } | ||
640 | |||
641 | /* RetiPodStateInfo (0x0D) | ||
642 | * | ||
643 | * Sent from the iPod to the device | ||
644 | */ | ||
645 | |||
646 | /* SetiPodStateInfo (0x0E) | ||
647 | * | ||
648 | * Set status information to new values | ||
649 | * | ||
650 | * Packet format (offset in buf[]: Description) | ||
651 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
652 | * 0x01: Command, always 0x0E | ||
653 | * 0x02: Type of information to change | ||
654 | * 0x03-0xNN: New information | ||
655 | * | ||
656 | * This command requires authentication | ||
657 | * | ||
658 | * Returns on success: | ||
659 | * IAP_ACK_OK | ||
660 | * | ||
661 | * Returns on failure: | ||
662 | * IAP_ACK_CMD_FAILED | ||
663 | * IAP_ACK_BAD_PARAM | ||
664 | */ | ||
665 | case 0x0E: | ||
666 | { | ||
667 | CHECKLEN(3); | ||
668 | CHECKAUTH; | ||
669 | switch (buf[0x02]) | ||
670 | { | ||
671 | /* Track position (ms) | ||
672 | * Data length: 4 | ||
673 | */ | ||
674 | case 0x00: | ||
675 | { | ||
676 | uint32_t pos; | ||
677 | |||
678 | CHECKLEN(7); | ||
679 | pos = get_u32(&buf[0x03]); | ||
680 | audio_ff_rewind(pos); | ||
681 | |||
682 | cmd_ok(cmd); | ||
683 | break; | ||
684 | } | ||
685 | |||
686 | /* Track index | ||
687 | * Data length: 4 | ||
688 | */ | ||
689 | case 0x01: | ||
690 | { | ||
691 | uint32_t index; | ||
692 | |||
693 | CHECKLEN(7); | ||
694 | index = get_u32(&buf[0x03]); | ||
695 | audio_skip(index-iap_get_trackindex()); | ||
696 | |||
697 | cmd_ok(cmd); | ||
698 | break; | ||
699 | } | ||
700 | |||
701 | /* Chapter index | ||
702 | * Data length: 2 | ||
703 | */ | ||
704 | case 0x02: | ||
705 | { | ||
706 | /* This is not supported */ | ||
707 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
708 | break; | ||
709 | } | ||
710 | |||
711 | /* Play status | ||
712 | * Data length: 1 | ||
713 | */ | ||
714 | case 0x03: | ||
715 | { | ||
716 | CHECKLEN(4); | ||
717 | switch(buf[0x03]) | ||
718 | { | ||
719 | case 0x00: | ||
720 | { | ||
721 | audio_stop(); | ||
722 | cmd_ok(cmd); | ||
723 | break; | ||
724 | } | ||
725 | |||
726 | case 0x01: | ||
727 | { | ||
728 | audio_resume(); | ||
729 | cmd_ok(cmd); | ||
730 | break; | ||
731 | } | ||
732 | |||
733 | case 0x02: | ||
734 | { | ||
735 | audio_pause(); | ||
736 | cmd_ok(cmd); | ||
737 | break; | ||
738 | } | ||
739 | |||
740 | default: | ||
741 | { | ||
742 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
743 | break; | ||
744 | } | ||
745 | } | ||
746 | break; | ||
747 | } | ||
748 | |||
749 | /* Volume/Mute | ||
750 | * Data length: 2 | ||
751 | * TODO: Fix this | ||
752 | */ | ||
753 | case 0x04: | ||
754 | { | ||
755 | CHECKLEN(5); | ||
756 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
757 | break; | ||
758 | } | ||
759 | |||
760 | /* Equalizer | ||
761 | * Data length: 5 | ||
762 | */ | ||
763 | case 0x06: | ||
764 | { | ||
765 | uint32_t index; | ||
766 | |||
767 | CHECKLEN(8); | ||
768 | index = get_u32(&buf[0x03]); | ||
769 | if (index == 0) { | ||
770 | cmd_ok(cmd); | ||
771 | } else { | ||
772 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
773 | } | ||
774 | break; | ||
775 | } | ||
776 | |||
777 | /* Shuffle | ||
778 | * Data length: 2 | ||
779 | */ | ||
780 | case 0x07: | ||
781 | { | ||
782 | CHECKLEN(5); | ||
783 | |||
784 | switch(buf[0x03]) | ||
785 | { | ||
786 | case 0x00: | ||
787 | { | ||
788 | iap_shuffle_state(false); | ||
789 | cmd_ok(cmd); | ||
790 | break; | ||
791 | } | ||
792 | case 0x01: | ||
793 | case 0x02: | ||
794 | { | ||
795 | iap_shuffle_state(true); | ||
796 | cmd_ok(cmd); | ||
797 | break; | ||
798 | } | ||
799 | |||
800 | default: | ||
801 | { | ||
802 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
803 | break; | ||
804 | } | ||
805 | } | ||
806 | break; | ||
807 | } | ||
808 | |||
809 | /* Repeat | ||
810 | * Data length: 2 | ||
811 | */ | ||
812 | case 0x08: | ||
813 | { | ||
814 | CHECKLEN(5); | ||
815 | |||
816 | switch(buf[0x03]) | ||
817 | { | ||
818 | case 0x00: | ||
819 | { | ||
820 | iap_repeat_state(REPEAT_OFF); | ||
821 | cmd_ok(cmd); | ||
822 | break; | ||
823 | } | ||
824 | case 0x01: | ||
825 | { | ||
826 | iap_repeat_state(REPEAT_ONE); | ||
827 | cmd_ok(cmd); | ||
828 | break; | ||
829 | } | ||
830 | case 0x02: | ||
831 | { | ||
832 | iap_repeat_state(REPEAT_ALL); | ||
833 | cmd_ok(cmd); | ||
834 | break; | ||
835 | } | ||
836 | default: | ||
837 | { | ||
838 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
839 | break; | ||
840 | } | ||
841 | } | ||
842 | break; | ||
843 | } | ||
844 | |||
845 | /* Date/Time | ||
846 | * Data length: 6 | ||
847 | */ | ||
848 | case 0x09: | ||
849 | { | ||
850 | CHECKLEN(9); | ||
851 | |||
852 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
853 | break; | ||
854 | } | ||
855 | |||
856 | /* Alarm | ||
857 | * Data length: 4 | ||
858 | */ | ||
859 | case 0x0A: | ||
860 | { | ||
861 | CHECKLEN(7); | ||
862 | |||
863 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
864 | break; | ||
865 | } | ||
866 | |||
867 | /* Backlight | ||
868 | * Data length: 2 | ||
869 | */ | ||
870 | case 0x0B: | ||
871 | { | ||
872 | CHECKLEN(5); | ||
873 | |||
874 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
875 | break; | ||
876 | } | ||
877 | |||
878 | /* Sound check | ||
879 | * Data length: 2 | ||
880 | */ | ||
881 | case 0x0D: | ||
882 | { | ||
883 | CHECKLEN(5); | ||
884 | |||
885 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
886 | break; | ||
887 | } | ||
888 | |||
889 | /* Audio book speed | ||
890 | * Data length: 1 | ||
891 | */ | ||
892 | case 0x0E: | ||
893 | { | ||
894 | CHECKLEN(4); | ||
895 | |||
896 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
897 | break; | ||
898 | } | ||
899 | |||
900 | /* Track position (s) | ||
901 | * Data length: 2 | ||
902 | */ | ||
903 | case 0x0F: | ||
904 | { | ||
905 | uint16_t pos; | ||
906 | |||
907 | CHECKLEN(5); | ||
908 | pos = get_u16(&buf[0x03]); | ||
909 | audio_ff_rewind(1000L * pos); | ||
910 | |||
911 | cmd_ok(cmd); | ||
912 | break; | ||
913 | } | ||
914 | |||
915 | /* Volume/Mute/Absolute | ||
916 | * Data length: 4 | ||
917 | * TODO: Fix this | ||
918 | */ | ||
919 | case 0x10: | ||
920 | { | ||
921 | CHECKLEN(7); | ||
922 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
923 | break; | ||
924 | } | ||
925 | |||
926 | default: | ||
927 | { | ||
928 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
929 | break; | ||
930 | } | ||
931 | } | ||
932 | |||
933 | break; | ||
934 | } | ||
935 | |||
936 | /* GetPlayStatus (0x0F) | ||
937 | * | ||
938 | * Request the current play status information | ||
939 | * | ||
940 | * Packet format (offset in buf[]: Description) | ||
941 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
942 | * 0x01: Command, always 0x0F | ||
943 | * | ||
944 | * This command requires authentication | ||
945 | * | ||
946 | * Returns: | ||
947 | * RetPlayStatus | ||
948 | * | ||
949 | * Packet format (offset in data[]: Description) | ||
950 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
951 | * 0x01: Command, always 0x10 | ||
952 | * 0x02: Play state | ||
953 | * 0x03-0x06: Current track index | ||
954 | * 0x07-0x0A: Current track length (ms) | ||
955 | * 0x0B-0x0E: Current track position (ms) | ||
956 | */ | ||
957 | case 0x0F: | ||
958 | { | ||
959 | int play_status; | ||
960 | struct mp3entry* id3; | ||
961 | struct playlist_info* playlist; | ||
962 | |||
963 | CHECKAUTH; | ||
964 | |||
965 | IAP_TX_INIT(0x03, 0x10); | ||
966 | |||
967 | play_status = audio_status(); | ||
968 | |||
969 | if (play_status & AUDIO_STATUS_PLAY) { | ||
970 | /* Playing or paused */ | ||
971 | if (play_status & AUDIO_STATUS_PAUSE) { | ||
972 | /* Paused */ | ||
973 | IAP_TX_PUT(0x02); | ||
974 | } else { | ||
975 | /* Playing */ | ||
976 | IAP_TX_PUT(0x01); | ||
977 | } | ||
978 | playlist = playlist_get_current(); | ||
979 | IAP_TX_PUT_U32(playlist->index - playlist->first_index); | ||
980 | id3 = audio_current_track(); | ||
981 | IAP_TX_PUT_U32(id3->length); | ||
982 | IAP_TX_PUT_U32(id3->elapsed); | ||
983 | } else { | ||
984 | /* Stopped, all values are 0x00 */ | ||
985 | IAP_TX_PUT(0x00); | ||
986 | IAP_TX_PUT_U32(0x00); | ||
987 | IAP_TX_PUT_U32(0x00); | ||
988 | IAP_TX_PUT_U32(0x00); | ||
989 | } | ||
990 | |||
991 | iap_send_tx(); | ||
992 | break; | ||
993 | } | ||
994 | |||
995 | /* RetPlayStatus (0x10) | ||
996 | * | ||
997 | * Sent from the iPod to the device | ||
998 | */ | ||
999 | |||
1000 | /* SetCurrentPlayingTrack (0x11) | ||
1001 | * | ||
1002 | * Set the current playing track | ||
1003 | * | ||
1004 | * Packet format (offset in buf[]: Description) | ||
1005 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1006 | * 0x01: Command, always 0x11 | ||
1007 | * 0x02-0x05: Index of track to play | ||
1008 | * | ||
1009 | * This command requires authentication | ||
1010 | * | ||
1011 | * Returns on success: | ||
1012 | * IAP_ACK_OK | ||
1013 | * | ||
1014 | * Returns on failure: | ||
1015 | * IAP_ACK_BAD_PARAM | ||
1016 | */ | ||
1017 | case 0x11: | ||
1018 | { | ||
1019 | uint32_t index; | ||
1020 | uint32_t trackcount; | ||
1021 | |||
1022 | CHECKAUTH; | ||
1023 | CHECKLEN(6); | ||
1024 | |||
1025 | index = get_u32(&buf[0x02]); | ||
1026 | trackcount = playlist_amount(); | ||
1027 | |||
1028 | if (index >= trackcount) | ||
1029 | { | ||
1030 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1031 | break; | ||
1032 | } | ||
1033 | audio_skip(index-iap_get_trackindex()); | ||
1034 | cmd_ok(cmd); | ||
1035 | |||
1036 | break; | ||
1037 | } | ||
1038 | |||
1039 | /* GetIndexedPlayingTrackInfo (0x12) | ||
1040 | * | ||
1041 | * Request information about a given track | ||
1042 | * | ||
1043 | * Packet format (offset in buf[]: Description) | ||
1044 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1045 | * 0x01: Command, always 0x12 | ||
1046 | * 0x02: Type of information to retrieve | ||
1047 | * 0x03-0x06: Track index | ||
1048 | * 0x07-0x08: Chapter index | ||
1049 | * | ||
1050 | * This command requires authentication. | ||
1051 | * | ||
1052 | * Returns: | ||
1053 | * RetIndexedPlayingTrackInfo | ||
1054 | * | ||
1055 | * Packet format (offset in data[]: Description) | ||
1056 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1057 | * 0x01: Command, always 0x13 | ||
1058 | * 0x02: Type of information returned | ||
1059 | * 0x03-0xNN: Information | ||
1060 | */ | ||
1061 | case 0x12: | ||
1062 | { | ||
1063 | /* NOTE: | ||
1064 | * | ||
1065 | * Retrieving the track information from a track which is not | ||
1066 | * the currently playing track can take a seriously long time, | ||
1067 | * in the order of several seconds. | ||
1068 | * | ||
1069 | * This most certainly violates the IAP spec, but there's no way | ||
1070 | * around this for now. | ||
1071 | */ | ||
1072 | uint32_t track_index; | ||
1073 | struct playlist_track_info track; | ||
1074 | struct mp3entry id3; | ||
1075 | |||
1076 | CHECKLEN(0x09); | ||
1077 | CHECKAUTH; | ||
1078 | |||
1079 | track_index = get_u32(&buf[0x03]); | ||
1080 | if (-1 == playlist_get_track_info(NULL, track_index, &track)) { | ||
1081 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1082 | break; | ||
1083 | } | ||
1084 | |||
1085 | IAP_TX_INIT(0x03, 0x13); | ||
1086 | IAP_TX_PUT(buf[2]); | ||
1087 | switch (buf[2]) | ||
1088 | { | ||
1089 | /* 0x00: Track caps/info | ||
1090 | * Information length: 10 bytes | ||
1091 | */ | ||
1092 | case 0x00: | ||
1093 | { | ||
1094 | iap_get_trackinfo(track_index, &id3); | ||
1095 | /* Track capabilities. None of these are supported, yet */ | ||
1096 | IAP_TX_PUT_U32(0x00); | ||
1097 | |||
1098 | /* Track length in ms */ | ||
1099 | IAP_TX_PUT_U32(id3.length); | ||
1100 | |||
1101 | /* Chapter count, stays at 0 */ | ||
1102 | IAP_TX_PUT_U16(0x00); | ||
1103 | |||
1104 | iap_send_tx(); | ||
1105 | break; | ||
1106 | } | ||
1107 | |||
1108 | /* 0x01: Chapter time/name | ||
1109 | * Information length: 4+variable | ||
1110 | */ | ||
1111 | case 0x01: | ||
1112 | { | ||
1113 | /* Chapter length, set at 0 (no chapters) */ | ||
1114 | IAP_TX_PUT_U32(0x00); | ||
1115 | |||
1116 | /* Chapter name, empty */ | ||
1117 | IAP_TX_PUT_STRING(""); | ||
1118 | |||
1119 | iap_send_tx(); | ||
1120 | break; | ||
1121 | } | ||
1122 | |||
1123 | /* 0x02, Artist name | ||
1124 | * Information length: variable | ||
1125 | */ | ||
1126 | case 0x02: | ||
1127 | { | ||
1128 | /* Artist name */ | ||
1129 | iap_get_trackinfo(track_index, &id3); | ||
1130 | IAP_TX_PUT_STRLCPY(id3.artist); | ||
1131 | |||
1132 | iap_send_tx(); | ||
1133 | break; | ||
1134 | } | ||
1135 | |||
1136 | /* 0x03, Album name | ||
1137 | * Information length: variable | ||
1138 | */ | ||
1139 | case 0x03: | ||
1140 | { | ||
1141 | /* Album name */ | ||
1142 | iap_get_trackinfo(track_index, &id3); | ||
1143 | IAP_TX_PUT_STRLCPY(id3.album); | ||
1144 | |||
1145 | iap_send_tx(); | ||
1146 | break; | ||
1147 | } | ||
1148 | |||
1149 | /* 0x04, Genre name | ||
1150 | * Information length: variable | ||
1151 | */ | ||
1152 | case 0x04: | ||
1153 | { | ||
1154 | /* Genre name */ | ||
1155 | iap_get_trackinfo(track_index, &id3); | ||
1156 | IAP_TX_PUT_STRLCPY(id3.genre_string); | ||
1157 | |||
1158 | iap_send_tx(); | ||
1159 | break; | ||
1160 | } | ||
1161 | |||
1162 | /* 0x05, Track title | ||
1163 | * Information length: variable | ||
1164 | */ | ||
1165 | case 0x05: | ||
1166 | { | ||
1167 | /* Track title */ | ||
1168 | iap_get_trackinfo(track_index, &id3); | ||
1169 | IAP_TX_PUT_STRLCPY(id3.title); | ||
1170 | |||
1171 | iap_send_tx(); | ||
1172 | break; | ||
1173 | } | ||
1174 | |||
1175 | /* 0x06, Composer name | ||
1176 | * Information length: variable | ||
1177 | */ | ||
1178 | case 0x06: | ||
1179 | { | ||
1180 | /* Track Composer */ | ||
1181 | iap_get_trackinfo(track_index, &id3); | ||
1182 | IAP_TX_PUT_STRLCPY(id3.composer); | ||
1183 | |||
1184 | iap_send_tx(); | ||
1185 | break; | ||
1186 | } | ||
1187 | |||
1188 | /* 0x07, Lyrics | ||
1189 | * Information length: variable | ||
1190 | */ | ||
1191 | case 0x07: | ||
1192 | { | ||
1193 | /* Packet information bits. All 0 (single packet) */ | ||
1194 | IAP_TX_PUT(0x00); | ||
1195 | |||
1196 | /* Packet index */ | ||
1197 | IAP_TX_PUT_U16(0x00); | ||
1198 | |||
1199 | /* Lyrics */ | ||
1200 | IAP_TX_PUT_STRING(""); | ||
1201 | |||
1202 | iap_send_tx(); | ||
1203 | break; | ||
1204 | } | ||
1205 | |||
1206 | /* 0x08, Artwork count | ||
1207 | * Information length: variable | ||
1208 | */ | ||
1209 | case 0x08: | ||
1210 | { | ||
1211 | /* No artwork, return packet containing just the type byte */ | ||
1212 | iap_send_tx(); | ||
1213 | break; | ||
1214 | } | ||
1215 | |||
1216 | default: | ||
1217 | { | ||
1218 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1219 | break; | ||
1220 | } | ||
1221 | } | ||
1222 | |||
1223 | break; | ||
1224 | } | ||
1225 | |||
1226 | /* RetIndexedPlayingTrackInfo (0x13) | ||
1227 | * | ||
1228 | * Sent from the iPod to the device | ||
1229 | */ | ||
1230 | |||
1231 | /* GetNumPlayingTracks (0x14) | ||
1232 | * | ||
1233 | * Request the number of tracks in the current playlist | ||
1234 | * | ||
1235 | * Packet format (offset in buf[]: Description) | ||
1236 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1237 | * 0x01: Command, always 0x14 | ||
1238 | * | ||
1239 | * This command requires authentication. | ||
1240 | * | ||
1241 | * Returns: | ||
1242 | * RetNumPlayingTracks | ||
1243 | * | ||
1244 | * Packet format (offset in data[]: Description) | ||
1245 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1246 | * 0x01: Command, always 0x15 | ||
1247 | * 0x02-0xNN: Number of tracks | ||
1248 | */ | ||
1249 | case 0x14: | ||
1250 | { | ||
1251 | CHECKAUTH; | ||
1252 | |||
1253 | IAP_TX_INIT(0x03, 0x15); | ||
1254 | IAP_TX_PUT_U32(playlist_amount()); | ||
1255 | |||
1256 | iap_send_tx(); | ||
1257 | } | ||
1258 | |||
1259 | /* RetNumPlayingTracks (0x15) | ||
1260 | * | ||
1261 | * Sent from the iPod to the device | ||
1262 | */ | ||
1263 | |||
1264 | /* GetArtworkFormats (0x16) | ||
1265 | * | ||
1266 | * Request a list of supported artwork formats | ||
1267 | * | ||
1268 | * Packet format (offset in buf[]: Description) | ||
1269 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1270 | * 0x01: Command, always 0x16 | ||
1271 | * | ||
1272 | * This command requires authentication. | ||
1273 | * | ||
1274 | * Returns: | ||
1275 | * RetArtworkFormats | ||
1276 | * | ||
1277 | * Packet format (offset in data[]: Description) | ||
1278 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1279 | * 0x01: Command, always 0x17 | ||
1280 | * 0x02-0xNN: list of 7 byte format descriptors | ||
1281 | */ | ||
1282 | case 0x16: | ||
1283 | { | ||
1284 | CHECKAUTH; | ||
1285 | |||
1286 | /* We return the empty list, meaning no artwork | ||
1287 | * TODO: Fix to return actual artwork formats | ||
1288 | */ | ||
1289 | IAP_TX_INIT(0x03, 0x17); | ||
1290 | |||
1291 | iap_send_tx(); | ||
1292 | break; | ||
1293 | } | ||
1294 | |||
1295 | /* RetArtworkFormats (0x17) | ||
1296 | * | ||
1297 | * Sent from the iPod to the device | ||
1298 | */ | ||
1299 | |||
1300 | /* GetTrackArtworkData (0x18) | ||
1301 | * | ||
1302 | * Request artwork for the given track | ||
1303 | * | ||
1304 | * Packet format (offset in buf[]: Description) | ||
1305 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1306 | * 0x01: Command, always 0x18 | ||
1307 | * 0x02-0x05: Track index | ||
1308 | * 0x06-0x07: Format ID | ||
1309 | * 0x08-0x0B: Track offset in ms | ||
1310 | * | ||
1311 | * This command requires authentication. | ||
1312 | * | ||
1313 | * Returns: | ||
1314 | * RetTrackArtworkData | ||
1315 | * | ||
1316 | * Packet format (offset in data[]: Description) | ||
1317 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1318 | * 0x01: Command, always 0x19 | ||
1319 | * 0x02-0x03: Descriptor index | ||
1320 | * 0x04: Pixel format code | ||
1321 | * 0x05-0x06: Image width in pixels | ||
1322 | * 0x07-0x08: Image height in pixels | ||
1323 | * 0x09-0x0A: Inset rectangle, top left x | ||
1324 | * 0x0B-0x0C: Inset rectangle, top left y | ||
1325 | * 0x0D-0x0E: Inset rectangle, bottom right x | ||
1326 | * 0x0F-0x10: Inset rectangle, bottom right y | ||
1327 | * 0x11-0x14: Row size in bytes | ||
1328 | * 0x15-0xNN: Image data | ||
1329 | * | ||
1330 | * If the image data does not fit in a single packet, subsequent | ||
1331 | * packets omit bytes 0x04-0x14. | ||
1332 | */ | ||
1333 | case 0x18: | ||
1334 | { | ||
1335 | CHECKAUTH; | ||
1336 | CHECKLEN(0x0C); | ||
1337 | |||
1338 | /* No artwork support currently */ | ||
1339 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1340 | break; | ||
1341 | } | ||
1342 | |||
1343 | /* RetTrackArtworkFormat (0x19) | ||
1344 | * | ||
1345 | * Sent from the iPod to the device | ||
1346 | */ | ||
1347 | |||
1348 | /* GetPowerBatteryState (0x1A) | ||
1349 | * | ||
1350 | * Request the current power state | ||
1351 | * | ||
1352 | * Packet format (offset in buf[]: Description) | ||
1353 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1354 | * 0x01: Command, always 0x1A | ||
1355 | * | ||
1356 | * This command requires authentication. | ||
1357 | * | ||
1358 | * Returns: | ||
1359 | * RetPowerBatteryState | ||
1360 | * | ||
1361 | * Packet format (offset in data[]: Description) | ||
1362 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1363 | * 0x01: Command, always 0x1B | ||
1364 | * 0x02: Power state | ||
1365 | * 0x03: Battery state | ||
1366 | */ | ||
1367 | case 0x1A: | ||
1368 | { | ||
1369 | IAP_TX_INIT(0x03, 0x1B); | ||
1370 | |||
1371 | iap_fill_power_state(); | ||
1372 | iap_send_tx(); | ||
1373 | break; | ||
1374 | } | ||
1375 | |||
1376 | /* RetPowerBatteryState (0x1B) | ||
1377 | * | ||
1378 | * Sent from the iPod to the device | ||
1379 | */ | ||
1380 | |||
1381 | /* GetSoundCheckState (0x1C) | ||
1382 | * | ||
1383 | * Request the current sound check state | ||
1384 | * | ||
1385 | * Packet format (offset in buf[]: Description) | ||
1386 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1387 | * 0x01: Command, always 0x1C | ||
1388 | * | ||
1389 | * This command requires authentication. | ||
1390 | * | ||
1391 | * Returns: | ||
1392 | * RetSoundCheckState | ||
1393 | * | ||
1394 | * Packet format (offset in data[]: Description) | ||
1395 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1396 | * 0x01: Command, always 0x1D | ||
1397 | * 0x02: Sound check state | ||
1398 | */ | ||
1399 | case 0x1C: | ||
1400 | { | ||
1401 | CHECKAUTH; | ||
1402 | |||
1403 | IAP_TX_INIT(0x03, 0x1D); | ||
1404 | IAP_TX_PUT(0x00); /* Always off */ | ||
1405 | |||
1406 | iap_send_tx(); | ||
1407 | break; | ||
1408 | } | ||
1409 | |||
1410 | /* RetSoundCheckState (0x1D) | ||
1411 | * | ||
1412 | * Sent from the iPod to the device | ||
1413 | */ | ||
1414 | |||
1415 | /* SetSoundCheckState (0x1E) | ||
1416 | * | ||
1417 | * Set the sound check state | ||
1418 | * | ||
1419 | * Packet format (offset in buf[]: Description) | ||
1420 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1421 | * 0x01: Command, always 0x1E | ||
1422 | * 0x02: Sound check state | ||
1423 | * 0x03: Restore on exit | ||
1424 | * | ||
1425 | * This command requires authentication. | ||
1426 | * | ||
1427 | * Returns on success | ||
1428 | * IAP_ACK_OK | ||
1429 | * | ||
1430 | * Returns on failure | ||
1431 | * IAP_ACK_CMD_FAILED | ||
1432 | */ | ||
1433 | case 0x1E: | ||
1434 | { | ||
1435 | CHECKAUTH; | ||
1436 | CHECKLEN(4); | ||
1437 | |||
1438 | /* Sound check is not supported right now | ||
1439 | * TODO: Fix | ||
1440 | */ | ||
1441 | |||
1442 | cmd_ack(cmd, IAP_ACK_CMD_FAILED); | ||
1443 | break; | ||
1444 | } | ||
1445 | |||
1446 | /* GetTrackArtworkTimes (0x1F) | ||
1447 | * | ||
1448 | * Request a list of timestamps at which artwork exists in a track | ||
1449 | * | ||
1450 | * Packet format (offset in buf[]: Description) | ||
1451 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1452 | * 0x01: Command, always 0x1F | ||
1453 | * 0x02-0x05: Track index | ||
1454 | * 0x06-0x07: Format ID | ||
1455 | * 0x08-0x09: Artwork Index | ||
1456 | * 0x0A-0x0B: Artwork count | ||
1457 | * | ||
1458 | * This command requires authentication. | ||
1459 | * | ||
1460 | * Returns: | ||
1461 | * RetTrackArtworkTimes | ||
1462 | * | ||
1463 | * Packet format (offset in data[]: Description) | ||
1464 | * 0x00: Lingo ID: Display Remote Lingo, always 0x03 | ||
1465 | * 0x01: Command, always 0x20 | ||
1466 | * 0x02-0x05: Offset in ms | ||
1467 | * | ||
1468 | * Bytes 0x02-0x05 can be repeated multiple times | ||
1469 | */ | ||
1470 | case 0x1F: | ||
1471 | { | ||
1472 | uint32_t index; | ||
1473 | uint32_t trackcount; | ||
1474 | |||
1475 | CHECKAUTH; | ||
1476 | CHECKLEN(0x0C); | ||
1477 | |||
1478 | /* Artwork is currently unsuported, just check for a valid | ||
1479 | * track index | ||
1480 | */ | ||
1481 | index = get_u32(&buf[0x02]); | ||
1482 | trackcount = playlist_amount(); | ||
1483 | |||
1484 | if (index >= trackcount) | ||
1485 | { | ||
1486 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1487 | break; | ||
1488 | } | ||
1489 | |||
1490 | /* Send an empty list */ | ||
1491 | IAP_TX_INIT(0x03, 0x20); | ||
1492 | |||
1493 | iap_send_tx(); | ||
1494 | break; | ||
1495 | } | ||
1496 | |||
1497 | /* The default response is IAP_ACK_BAD_PARAM */ | ||
1498 | default: | ||
1499 | { | ||
1500 | #ifdef LOGF_ENABLE | ||
1501 | logf("iap: Unsupported Mode03 Command"); | ||
1502 | #else | ||
1503 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1504 | #endif | ||
1505 | break; | ||
1506 | } | ||
1507 | } | ||
1508 | } | ||
diff --git a/apps/iap/iap-lingo4.c b/apps/iap/iap-lingo4.c new file mode 100644 index 0000000000..fa0196645b --- /dev/null +++ b/apps/iap/iap-lingo4.c | |||
@@ -0,0 +1,3153 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Alan Korr & Nick Robinson | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ******************************************************************************/ | ||
19 | |||
20 | #include "iap-core.h" | ||
21 | #include "iap-lingo.h" | ||
22 | #include "dir.h" | ||
23 | #include "settings.h" | ||
24 | #include "filetree.h" | ||
25 | #include "wps.h" | ||
26 | #include "playback.h" | ||
27 | |||
28 | /* | ||
29 | * This macro is meant to be used inside an IAP mode message handler. | ||
30 | * It is passed the expected minimum length of the message buffer. | ||
31 | * If the buffer does not have the required lenght an ACK | ||
32 | * packet with a Bad Parameter error is generated. | ||
33 | */ | ||
34 | #define CHECKLEN(x) do { \ | ||
35 | if (len < (x)) { \ | ||
36 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); \ | ||
37 | return; \ | ||
38 | }} while(0) | ||
39 | |||
40 | /* Check for authenticated state, and return an ACK Not | ||
41 | * Authenticated on failure. | ||
42 | */ | ||
43 | #define CHECKAUTH do { \ | ||
44 | if (!DEVICE_AUTHENTICATED) { \ | ||
45 | cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \ | ||
46 | return; \ | ||
47 | }} while(0) | ||
48 | |||
49 | /* Used to remember the last Type and Record requested */ | ||
50 | static char cur_dbrecord[5] = {0}; | ||
51 | |||
52 | /* Used to remember the total number of filtered database records */ | ||
53 | static unsigned long dbrecordcount = 0; | ||
54 | |||
55 | /* Used to remember the LAST playlist selected */ | ||
56 | static unsigned long last_selected_playlist = 0; | ||
57 | |||
58 | static void cmd_ack(const unsigned int cmd, const unsigned char status) | ||
59 | { | ||
60 | IAP_TX_INIT4(0x04, 0x0001); | ||
61 | IAP_TX_PUT(status); | ||
62 | IAP_TX_PUT_U16(cmd); | ||
63 | iap_send_tx(); | ||
64 | } | ||
65 | |||
66 | #define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK) | ||
67 | |||
68 | static void get_playlist_name(unsigned char *dest, | ||
69 | unsigned long item_offset, | ||
70 | size_t max_length) | ||
71 | { | ||
72 | if (item_offset == 0) return; | ||
73 | DIR* dp; | ||
74 | struct dirent* playlist_file = NULL; | ||
75 | |||
76 | dp = opendir(global_settings.playlist_catalog_dir); | ||
77 | |||
78 | char *extension; | ||
79 | unsigned long nbr = 0; | ||
80 | while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL)) | ||
81 | { | ||
82 | /*Increment only if there is a playlist extension*/ | ||
83 | if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){ | ||
84 | if ((strcmp(extension, ".m3u") == 0 || | ||
85 | strcmp(extension, ".m3u8") == 0)) | ||
86 | nbr++; | ||
87 | } | ||
88 | } | ||
89 | if (playlist_file != NULL) { | ||
90 | strlcpy(dest, playlist_file->d_name, max_length); | ||
91 | } | ||
92 | closedir(dp); | ||
93 | } | ||
94 | |||
95 | static void seek_to_playlist(unsigned long index) | ||
96 | { | ||
97 | unsigned char selected_playlist | ||
98 | [sizeof(global_settings.playlist_catalog_dir) | ||
99 | + 1 | ||
100 | + MAX_PATH] = {0}; | ||
101 | |||
102 | strcpy(selected_playlist, | ||
103 | global_settings.playlist_catalog_dir); | ||
104 | int len = strlen(selected_playlist); | ||
105 | selected_playlist[len] = '/'; | ||
106 | get_playlist_name (selected_playlist + len + 1, | ||
107 | index, | ||
108 | MAX_PATH); | ||
109 | ft_play_playlist(selected_playlist, | ||
110 | global_settings.playlist_catalog_dir, | ||
111 | strrchr(selected_playlist, '/') + 1); | ||
112 | |||
113 | } | ||
114 | |||
115 | static unsigned long nbr_total_playlists(void) | ||
116 | { | ||
117 | DIR* dp; | ||
118 | unsigned long nbr_total_playlists = 0; | ||
119 | struct dirent* playlist_file = NULL; | ||
120 | char *extension; | ||
121 | dp = opendir(global_settings.playlist_catalog_dir); | ||
122 | while ((playlist_file = readdir(dp)) != NULL) | ||
123 | { | ||
124 | /*Increment only if there is a playlist extension*/ | ||
125 | if ((extension=strrchr(playlist_file->d_name, '.')) != NULL) | ||
126 | { | ||
127 | if ((strcmp(extension, ".m3u") == 0 || | ||
128 | strcmp(extension, ".m3u8") == 0)) | ||
129 | { | ||
130 | nbr_total_playlists++; | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | closedir(dp); | ||
135 | return nbr_total_playlists; | ||
136 | } | ||
137 | |||
138 | void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf) | ||
139 | { | ||
140 | unsigned int cmd = (buf[1] << 8) | buf[2]; | ||
141 | /* Lingo 0x04 commands are at least 3 bytes in length */ | ||
142 | CHECKLEN(3); | ||
143 | |||
144 | /* Lingo 0x04 must have been negotiated */ | ||
145 | if (!DEVICE_LINGO_SUPPORTED(0x04)) { | ||
146 | #ifdef LOGF_ENABLE | ||
147 | logf("iap: Mode04 Not Negotiated"); | ||
148 | #endif | ||
149 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
150 | return; | ||
151 | } | ||
152 | |||
153 | /* All these commands require extended interface mode */ | ||
154 | if (interface_state != IST_EXTENDED) { | ||
155 | #ifdef LOGF_ENABLE | ||
156 | logf("iap: Not in Mode04 Extended Mode"); | ||
157 | #endif | ||
158 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
159 | return; | ||
160 | } | ||
161 | switch (cmd) | ||
162 | { | ||
163 | case 0x0001: /* CmdAck. See above cmd_ack() */ | ||
164 | /* | ||
165 | * The following is the description for the Apple Firmware | ||
166 | * The iPod sends this telegram to acknowledge the receipt of a | ||
167 | * command and return the command status. The command ID field | ||
168 | * indicates the device command for which the response is being | ||
169 | * sent. The command status indicates the results of the command | ||
170 | * (success or failure). | ||
171 | * | ||
172 | * Byte Value Meaning | ||
173 | * 0 0xFF Sync byte (required only for UART serial) | ||
174 | * 1 0x55 Start of telegram | ||
175 | * 2 0x06 Telegram payload length | ||
176 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
177 | * 4 0x00 Command ID (bits 15:8) | ||
178 | * 5 0x01 Command ID (bits 7:0) | ||
179 | * 6 0xNN Command result status. Possible values are: | ||
180 | * 0x00 = Success (OK) | ||
181 | * 0x01 = ERROR: Unknown database category | ||
182 | * 0x02 = ERROR: Command failed | ||
183 | * 0x03 = ERROR: Out of resources | ||
184 | * 0x04 = ERROR: Bad parameter | ||
185 | * 0x05 = ERROR: Unknown ID | ||
186 | * 0x06 = Reserved | ||
187 | * 0x07 = Accessory not authenticated | ||
188 | * 0x08 - 0xFF = Reserved | ||
189 | * 7 0xNN The ID of the command being acknowledged (bits 15:8). | ||
190 | * 8 0xNN The ID of the command being acknowledged (bits 7:0). | ||
191 | * 9 0xNN Telegram payload checksum byte | ||
192 | */ | ||
193 | { | ||
194 | /* We should NEVER receive this command so ERROR if we do */ | ||
195 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
196 | break; | ||
197 | } | ||
198 | case 0x0002: /* GetCurrentPlayingTrackChapterInfo */ | ||
199 | /* The following is the description for the Apple Firmware | ||
200 | * Requests the chapter information of the currently playing track. | ||
201 | * In response, the iPod sends a | ||
202 | * Command 0x0003: ReturnCurrentPlayingTrackChapterInfo | ||
203 | * telegram to the device. | ||
204 | * Note: The returned track index is valid only when there is a | ||
205 | * currently playing or paused track. | ||
206 | * | ||
207 | * Byte Value Meaning | ||
208 | * 0 0xFF Sync byte (required only for UART serial) | ||
209 | * 1 0x55 Start of telegram | ||
210 | * 2 0x03 Telegram payload length | ||
211 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
212 | * 4 0x00 Command ID (bits 15:8) | ||
213 | * 5 0x02 Command ID (bits 7:0) | ||
214 | * 6 0xF7 Telegram payload checksum byte | ||
215 | * | ||
216 | * We Return that the track does not have chapter information by | ||
217 | * returning chapter index -1 (0xFFFFFFFF) and chapter count 0 | ||
218 | * (0x00000000) | ||
219 | */ | ||
220 | { | ||
221 | unsigned char data[] = {0x04, 0x00, 0x03, | ||
222 | 0xFF, 0xFF, 0xFF, 0xFF, | ||
223 | 0x00, 0x00, 0x00, 0x00}; | ||
224 | iap_send_pkt(data, sizeof(data)); | ||
225 | break; | ||
226 | } | ||
227 | case 0x0003: /* ReturnCurrentPlayingTrackChapterInfo. See Above */ | ||
228 | /* The following is the description for the Apple Firmware | ||
229 | * | ||
230 | * Returns the chapter information of the currently playing track. | ||
231 | * The iPod sends this telegramin response to the | ||
232 | * Command 0x0002: GetCurrentPlayingTrackChapterInfo | ||
233 | * telegram from the device. The track chapter information includes | ||
234 | * the currently playingtrack's chapter index,as well as the | ||
235 | * total number of chapters in the track. The track chapter and the | ||
236 | * total number of chapters are 32-bit signed integers. The chapter | ||
237 | * index of the firstchapter is always 0x00000000. If the track does | ||
238 | * not have chapter information, a chapter index of -1(0xFFFFFFFF) | ||
239 | * and a chapter count of 0 (0x00000000) are returned. | ||
240 | * | ||
241 | * Byte Value Meaning | ||
242 | * 0 0xFF Sync byte (required only for UART serial) | ||
243 | * 1 0x55 Start of telegram | ||
244 | * 2 0x0B Telegram payload length | ||
245 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
246 | * 4 0x00 Command ID (bits 15:8) | ||
247 | * 5 0x03 Command ID (bits 7:0) | ||
248 | * 6 0xNN Current chapter index (bits 31:24) | ||
249 | * 7 0xNN Current chapter index (bits 23:16) | ||
250 | * 8 0xNN Current chapter index (bits 15:8) | ||
251 | * 9 0xNN Current chapter index (bits 7:0) | ||
252 | * 10 0xNN Chapter count (bits 31:24) | ||
253 | * 11 0xNN Chapter count (bits 23:16) | ||
254 | * 12 0xNN Chapter count (bits 15:8) | ||
255 | * 13 0xNN Chapter count (bits 7:0) | ||
256 | * 14 0xNN Telegram payload checksum byte | ||
257 | */ | ||
258 | { | ||
259 | /* We should NEVER receive this command so ERROR if we do */ | ||
260 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
261 | break; | ||
262 | } | ||
263 | case 0x0004: /* SetCurrentPlayingTrackChapter */ | ||
264 | /* The following is the description for the Apple Firmware | ||
265 | * | ||
266 | * Sets the currently playing track chapter.You can send the Command | ||
267 | * 0x0002: GetCurrentPlayingTrackChapterInfo telegram to get the | ||
268 | * chapter count and the index of the currently playing chapter in | ||
269 | * the current track. In response to the command | ||
270 | * SetCurrentPlayingTrackChapter, the iPod sends an ACK telegram | ||
271 | * with the command status. | ||
272 | * | ||
273 | * Note: This command should be used only when the iPod is in a | ||
274 | * playing or paused state. The command fails if the iPod is stopped | ||
275 | * or if the track does not contain chapter information. | ||
276 | * | ||
277 | * Byte Value Meaning | ||
278 | * 0 0xFF Sync byte (required only for UART serial) | ||
279 | * 1 0x55 Start of telegram | ||
280 | * 2 0x07 Telegram payload length | ||
281 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
282 | * 4 0x00 Command ID (bits 15:8) | ||
283 | * 5 0x04 Command ID (bits 7:0) | ||
284 | * 6 0xNN Chapter index (bits 31:24) | ||
285 | * 7 0xNN Chapter index (bits 23:16) | ||
286 | * 8 0xNN Chapter index (bits 15:8) | ||
287 | * 9 0xNN Chapter index (bits 7:0) | ||
288 | * 10 0xNN Telegram payload checksum byte | ||
289 | * | ||
290 | * We don't do anything with this as we don't support chapters, | ||
291 | * so just return BAD_PARAM | ||
292 | */ | ||
293 | { | ||
294 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
295 | break; | ||
296 | } | ||
297 | case 0x0005: /* GetCurrentPlayingTrackChapterPlayStatus */ | ||
298 | /* The following is the description for the Apple Firmware | ||
299 | * | ||
300 | * Requests the chapter playtime status of the currently playing | ||
301 | * track. The status includes the chapter length and the time | ||
302 | * elapsed within that chapter. In response to a valid telegram, the | ||
303 | * iPod sends a Command 0x0006: | ||
304 | * ReturnCurrentPlayingTrackChapterPlayStatus telegram to the | ||
305 | * device. | ||
306 | * | ||
307 | * Note: If the telegram length or chapter index is invalid for | ||
308 | * instance, if the track does not contain chapter information the | ||
309 | * iPod responds with an ACK telegram including the specific error | ||
310 | * status. | ||
311 | * | ||
312 | * Byte Value Meaning | ||
313 | * 0 0xFF Sync byte (required only for UART serial) | ||
314 | * 1 0x55 Start of telegram | ||
315 | * 2 0x07 Telegram payload length | ||
316 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
317 | * 4 0x00 Command ID (bits 15:8) | ||
318 | * 5 0x05 Command ID (bits 7:0) | ||
319 | * 6 0xNN Currently playingchapter index (bits31:24) | ||
320 | * 7 0xNN Currently playingchapter index (bits23:16) | ||
321 | * 8 0xNN Currently playing chapter index (bits 15:8) | ||
322 | * 9 0xNN Currently playing chapter index (bits 7:0) | ||
323 | * 10 0xNN Telegram payload checksum byte | ||
324 | * | ||
325 | * The returned data includes 4 bytes for Chapter Length followed | ||
326 | * by 4 bytes of elapsed time If there is no currently playing | ||
327 | * chapter, length and elapsed are 0 | ||
328 | * We don't do anything with this as we don't support chapters, | ||
329 | * so just return BAD_PARAM | ||
330 | */ | ||
331 | { | ||
332 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
333 | break; | ||
334 | } | ||
335 | case 0x0006: /* ReturnCurrentPlayingTrackChapterPlayStatus. See Above */ | ||
336 | /* The following is the description for the Apple Firmware | ||
337 | * | ||
338 | * Returns the play status of the currently playing track chapter. | ||
339 | * The iPod sends this telegram in response to the Command 0x0005: | ||
340 | * GetCurrentPlayingTrackChapterPlayStatus telegram from the device. | ||
341 | * The returned information includes the chapter length and elapsed | ||
342 | * time, in milliseconds. If there is no currently playing chapter, | ||
343 | * the chapter length and elapsed time are zero. | ||
344 | * | ||
345 | * Byte Value Meaning | ||
346 | * 0 0xFF Sync byte (required only for UART serial) | ||
347 | * 1 0x55 Start of telegram | ||
348 | * 2 0x0B Telegram payload length | ||
349 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
350 | * 4 0x00 Command ID (bits 15:8) | ||
351 | * 5 0x06 Command ID (bits 7:0) | ||
352 | * 6 0xNN Chapter length in milliseconds (bits 31:24) | ||
353 | * 7 0xNN Chapter length in milliseconds (bits 23:16) | ||
354 | * 8 0xNN Chapter length in milliseconds (bits 15:8) | ||
355 | * 9 0xNN Chapter length in milliseconds (bits 7:0) | ||
356 | * 10 0xNN Elapsed time in chapter, in milliseconds (bits 31:24) | ||
357 | * 11 0xNN Elapsed time in chapter, in milliseconds (bits 23:16) | ||
358 | * 12 0xNN Elapsed time in chapter, in milliseconds (bits 15:8) | ||
359 | * 13 0xNN Elapsed time in chapter, in milliseconds (bits 7:0) | ||
360 | * 14 0xNN Telegram payload checksum byte | ||
361 | */ | ||
362 | { | ||
363 | /* We should NEVER receive this command so ERROR if we do */ | ||
364 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
365 | break; | ||
366 | } | ||
367 | case 0x0007: /* GetCurrentPlayingTrackChapterName */ | ||
368 | /* The following is the description for the Apple Firmware | ||
369 | * | ||
370 | * Requests a chapter name in the currently playing track. In | ||
371 | * response to a valid telegram, the iPod sends a Command 0x0008: | ||
372 | * ReturnCurrentPlayingTrackChapterName telegram to the device. | ||
373 | * | ||
374 | * Note: If the received telegram length or track index is invalid | ||
375 | * for instance, if the track does not have chapter information or | ||
376 | * is not a part of the Audiobook category, the iPod responds with | ||
377 | * an ACK telegram including the specific error status. | ||
378 | * | ||
379 | * Byte Value Meaning | ||
380 | * 0 0xFF Sync byte (required only for UART serial) | ||
381 | * 1 0x55 Start of telegram | ||
382 | * 2 0x07 Telegram payload length | ||
383 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
384 | * 4 0x00 Command ID (bits 15:8) | ||
385 | * 5 0x07 Command ID (bits 7:0) | ||
386 | * 6 0xNN Chapter index (bits 31:24) | ||
387 | * 7 0xNN Chapter index (bits 23:16) | ||
388 | * 8 0xNN Chapter index (bits 15:8) | ||
389 | * 9 0xNN Chapter index (bits 7:0) | ||
390 | * 10 0xNN Telegram payload checksum byte | ||
391 | * | ||
392 | * We don't do anything with this as we don't support chapters, | ||
393 | * so just return BAD_PARAM | ||
394 | */ | ||
395 | { | ||
396 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
397 | break; | ||
398 | } | ||
399 | case 0x0008: /* ReturnCurrentPlayingTrackChapterName. See Above */ | ||
400 | /* The following is the description for the Apple Firmware | ||
401 | * | ||
402 | * Returns a chapter name in the currently playing track. The iPod | ||
403 | * sends this telegram in response to a valid Command 0x0007: | ||
404 | * GetCurrentPlayingTrackChapterName telegram from the | ||
405 | * device. The chapter name is encoded as a null-terminated UTF-8 | ||
406 | * character array. | ||
407 | * | ||
408 | * Note: The chapter name string is not limited to 252 characters; | ||
409 | * it may be sent in small or large telegram format depending on | ||
410 | * the string length. The small telegram format is shown. | ||
411 | * | ||
412 | * Byte Value Meaning | ||
413 | * 0 0xFF Sync byte (required only for UART serial) | ||
414 | * 1 0x55 Start of telegram | ||
415 | * 2 0xNN Telegram payload length | ||
416 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
417 | * 4 0x00 Command ID (bits 15:8) | ||
418 | * 5 0x08 Command ID (bits 7:0) | ||
419 | * 6-N 0xNN Chapter name as UTF-8 character array | ||
420 | *(last byte) 0xNN Telegram payload checksum byte | ||
421 | * | ||
422 | */ | ||
423 | { | ||
424 | /* We should NEVER receive this command so ERROR if we do */ | ||
425 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
426 | break; | ||
427 | } | ||
428 | case 0x0009: /* GetAudioBookSpeed */ | ||
429 | /* The following is the description for the Apple Firmware | ||
430 | * | ||
431 | * Requests the current iPod audiobook speed state. The iPod | ||
432 | * responds with the “Command 0x000A: ReturnAudiobookSpeed” | ||
433 | * telegram indicating the current audiobook speed. | ||
434 | * | ||
435 | * Byte Value Meaning | ||
436 | * 0 0xFF Sync byte (required only for UART serial) | ||
437 | * 1 0x55 Start of telegram | ||
438 | * 2 0x03 Telegram payload length | ||
439 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
440 | * 4 0x00 Command ID (bits 15:8) | ||
441 | * 5 0x09 Command ID (bits 7:0) | ||
442 | * 6 0xF0 Telegram payload checksum byte | ||
443 | * | ||
444 | * ReturnAudioBookSpeed | ||
445 | * 0x00 = Normal, 0xFF = Slow, 0x01 = Fast | ||
446 | * We always respond with Normal speed | ||
447 | */ | ||
448 | { | ||
449 | unsigned char data[] = {0x04, 0x00, 0x0A, 0x00}; | ||
450 | iap_send_pkt(data, sizeof(data)); | ||
451 | break; | ||
452 | } | ||
453 | case 0x000A: /* ReturnAudioBookSpeed. See Above */ | ||
454 | /* The following is the description for the Apple Firmware | ||
455 | * | ||
456 | * Returns the current audiobook speed setting. The iPod sends | ||
457 | * this telegram in response to the Command 0x0009: | ||
458 | * GetAudiobookSpeed command from the device. | ||
459 | * | ||
460 | * Byte Value Meaning | ||
461 | * 0 0xFF Sync byte (required only for UART serial) | ||
462 | * 1 0x55 Start of telegram | ||
463 | * 2 0x04 Telegram payload length | ||
464 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
465 | * 4 0x00 Command ID (bits 15:8) | ||
466 | * 5 0x0A Command ID (bits 7:0) | ||
467 | * 6 0xNN Audiobook speed status code. | ||
468 | * 0xFF Slow (-1) | ||
469 | * 0x00 Normal | ||
470 | * 0x01 Fast (+1) | ||
471 | * 7 0xNN Telegram payload checksum byte | ||
472 | * | ||
473 | */ | ||
474 | { | ||
475 | /* We should NEVER receive this command so ERROR if we do */ | ||
476 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
477 | break; | ||
478 | } | ||
479 | case 0x000B: /* SetAudioBookSpeed */ | ||
480 | /* The following is the description for the Apple Firmware | ||
481 | * Sets the speed of audiobook playback. The iPod audiobook speed | ||
482 | * states are listed above. This telegram has two modes: one to | ||
483 | * set the speed of the currently playing audiobook and a second | ||
484 | * to set the audiobook speed for all audiobooks. Byte number 7 | ||
485 | * is an optional byte; devices should not send this byte if they | ||
486 | * want to set the speed only of the currently playing audiobook. | ||
487 | * If devices want to set the global audiobook speed setting then | ||
488 | * they must use byte number 7. This is the Restore on Exit byte; | ||
489 | * a nonzero value restores the original audiobook speed setting | ||
490 | * when the accessory is detached. If this byte is zero, the | ||
491 | * audiobook speed setting set by the accessory is saved and | ||
492 | * persists after the accessory is detached from the iPod. See below | ||
493 | * for the telegram format used when including the Restore on Exit | ||
494 | * byte. | ||
495 | * In response to this command, the iPod sends an ACK telegram with | ||
496 | * the command status. | ||
497 | * | ||
498 | * Note: Accessory developers are encouraged to always use the | ||
499 | * Restore on Exit byte with a nonzero value, to restore any of the | ||
500 | * user's iPod settings that were modified by the accessory. | ||
501 | * | ||
502 | * Byte Value Meaning (Cuurent audiobook only | ||
503 | * 0 0xFF Sync byte (required only for UART serial) | ||
504 | * 1 0x55 Start of telegram | ||
505 | * 2 0x04 Telegram payload length | ||
506 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
507 | * 4 0x00 Command ID (bits 15:8) | ||
508 | * 5 0x0B Command ID (bits 7:0) | ||
509 | * 6 0xNN New audiobook speed code. | ||
510 | * 7 0xNN Telegram payload checksum byte | ||
511 | * | ||
512 | * | ||
513 | * SetAudiobookSpeed telegram to set the global audiobook speed | ||
514 | * Byte Value Meaning | ||
515 | * 0 0xFF Sync byte (required only for UART serial) | ||
516 | * 1 0x55 Start of telegram | ||
517 | * 2 0x05 Telegram payload length | ||
518 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
519 | * 4 0x00 Command ID (bits 15:8) | ||
520 | * 5 0x0B Command ID (bits 7:0) | ||
521 | * 6 0xNN Global audiobook speed code. | ||
522 | * 7 0xNN Restore on Exit byte. | ||
523 | * If 1, the original setting is restored on detach; | ||
524 | * if 0, the newsetting persists after accessory detach. | ||
525 | * 8 0xNN Telegram payload checksum byte | ||
526 | * | ||
527 | * | ||
528 | * We don't do anything with this as we don't support chapters, | ||
529 | * so just return BAD_PARAM | ||
530 | */ | ||
531 | { | ||
532 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
533 | break; | ||
534 | } | ||
535 | case 0x000C: /* GetIndexedPlayingTrackInfo */ | ||
536 | /* The following is the description for the Apple Firmware | ||
537 | * | ||
538 | * Gets track information for the track at the specified index. | ||
539 | * The track info type field specifies the type of information to be | ||
540 | * returned, such as song lyrics, podcast name, episode date, and | ||
541 | * episode description. In response, the iPod sends the Command | ||
542 | * 0x000D: ReturnIndexedPlayingTrackInfo command with the requested | ||
543 | * track information. If the information type is invalid or does not | ||
544 | * apply to the selected track, the iPod returns an ACK with an | ||
545 | * error status. | ||
546 | * | ||
547 | * Byte Value Meaning | ||
548 | * 0 0xFF Sync byte (required only for UART serial) | ||
549 | * 1 0x55 Start of telegram | ||
550 | * 2 0x0A Telegram payload length | ||
551 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
552 | * 4 0x00 Command ID (bits 15:8) | ||
553 | * 5 0x0C Command ID (bits 7:0) | ||
554 | * 6 0xNN Track info type. See Below | ||
555 | * 7 0xNN Track index (bits 31:24) | ||
556 | * 8 0xNN Track index (bits 23:16) | ||
557 | * 9 0xNN Track index (bits 15:8) | ||
558 | * 10 0xNN Track index (bits 7:0) | ||
559 | * 11 0xNN Chapter index (bits 15:8) | ||
560 | * 12 0xNN Chapter index (bits 7:0) | ||
561 | * (last byte)0xNN Telegram payload checksum byte | ||
562 | * | ||
563 | * Track Info Types: Return Data | ||
564 | * 0x00 Track Capabilities. 10byte data | ||
565 | * 0x01 Podcast Name UTF-8 String | ||
566 | * 0x02 Track Release Date 7Byte Data All 0 if invalid | ||
567 | * 0x03 Track Description UTF-8 String | ||
568 | * 0x04 Track Song Lyrics UTF-8 String | ||
569 | * 0x05 Track Genre UTF-8 String | ||
570 | * 0x06 Track Composer UTF-8 String | ||
571 | * 0x07 Track Artwork Count 2byte formatID and 2byte count of images | ||
572 | * 0x08-0xff Reserved | ||
573 | * | ||
574 | * Track capabilities | ||
575 | * 0x00-0x03 | ||
576 | * Bit0 is Audiobook | ||
577 | * Bit1 has chapters | ||
578 | * Bit2 has album art | ||
579 | * Bit3 has lyrics | ||
580 | * Bit4 is podcast episode | ||
581 | * Bit5 has Release Date | ||
582 | * Bit6 has Decription | ||
583 | * Bit7 Contains Video | ||
584 | * Bit8 Play as Video | ||
585 | * Bit9+ Reserved | ||
586 | * 0x04-0x07 Total Track Length in ms | ||
587 | * 0x08-0x09 Chapter Count | ||
588 | * | ||
589 | * Track Release Date Encoding | ||
590 | * 0x00 Seconds 0-59 | ||
591 | * 0x01 Minutes 0-59 | ||
592 | * 0x02 Hours 0-23 | ||
593 | * 0x03 Day Of Month 1-31 | ||
594 | * 0x04 Month 1-12 | ||
595 | * 0x05 Year Bits 15:8 2006 = 2006AD | ||
596 | * 0x06 Year Bits 7:0 | ||
597 | * 0x07 Weekday 0-6 where 0=Sunday 6=Saturday | ||
598 | * | ||
599 | * | ||
600 | */ | ||
601 | { | ||
602 | if (len < (10)) | ||
603 | { | ||
604 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
605 | break; | ||
606 | } | ||
607 | struct mp3entry *id3 = audio_current_track(); | ||
608 | |||
609 | switch(buf[3]) | ||
610 | { | ||
611 | case 0x01: /* Podcast Not Supported */ | ||
612 | case 0x04: /* Lyrics Not Supported */ | ||
613 | case 0x03: /* Description */ | ||
614 | case 0x05: /* Genre */ | ||
615 | case 0x06: /* Composer */ | ||
616 | { | ||
617 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
618 | break; | ||
619 | } | ||
620 | default: | ||
621 | { | ||
622 | IAP_TX_INIT4(0x04, 0x000D); | ||
623 | IAP_TX_PUT(buf[3]); | ||
624 | switch(buf[3]) | ||
625 | { | ||
626 | case 0x00: | ||
627 | { | ||
628 | /* Track Capabilities 10Bytes Data */ | ||
629 | IAP_TX_PUT_U32(0); /* Track Capabilities. */ | ||
630 | /* Currently None Supported*/ | ||
631 | IAP_TX_PUT_U32(id3->length); /* Track Length */ | ||
632 | IAP_TX_PUT_U16(0x00); /* Chapter Count */ | ||
633 | break; | ||
634 | } | ||
635 | case 0x02: | ||
636 | { | ||
637 | /* Track Release Date 7 Bytes Data | ||
638 | * Currently only returns a fixed value, | ||
639 | * Sunday 1st Feb 2011 3Hr 4Min 5Secs | ||
640 | */ | ||
641 | IAP_TX_PUT(5); /* Seconds 0-59 */ | ||
642 | IAP_TX_PUT(4); /* Minutes 0-59 */ | ||
643 | IAP_TX_PUT(3); /* Hours 0-23 */ | ||
644 | IAP_TX_PUT(1); /* Day Of Month 1-31 */ | ||
645 | IAP_TX_PUT(2); /* Month 1-12 */ | ||
646 | IAP_TX_PUT_U16(2011); /* Year */ | ||
647 | IAP_TX_PUT(0); /* Day 0=Sunday */ | ||
648 | break; | ||
649 | } | ||
650 | case 0x07: | ||
651 | { | ||
652 | /* Track Artwork Count */ | ||
653 | /* Currently not supported */ | ||
654 | IAP_TX_PUT_U16(0x00); /* Format ID */ | ||
655 | IAP_TX_PUT_U16(0x00); /* Image Count */ | ||
656 | break; | ||
657 | } | ||
658 | } | ||
659 | iap_send_tx(); | ||
660 | break; | ||
661 | } | ||
662 | } | ||
663 | break; | ||
664 | } | ||
665 | case 0x000D: /* ReturnIndexedPlayingTrackInfo. See Above */ | ||
666 | /* The following is the description for the Apple Firmware | ||
667 | * | ||
668 | * Returns the requested track information type and data. The iPod | ||
669 | * sends this command in response to the Command 0x000C: | ||
670 | * GetIndexedPlayingTrackInfo command. | ||
671 | * Data returned as strings are encoded as null-terminated UTF-8 | ||
672 | * character arrays. | ||
673 | * If the track information string does not exist, a null UTF-8 | ||
674 | * string is returned. If the track has no release date, then the | ||
675 | * returned release date has all bytes zeros. Track song lyrics and | ||
676 | * the track description are sent in a large or small telegram | ||
677 | * format with an incrementing packet index field, spanning | ||
678 | * multiple packets if needed. | ||
679 | * | ||
680 | * Below is the packet format for the | ||
681 | * ReturnIndexedPlayingTrackInfo telegram sent in response to a | ||
682 | * request for information types 0x00 to 0x02. | ||
683 | * | ||
684 | * Byte Value Meaning | ||
685 | * 0 0xFF Sync byte | ||
686 | * 1 0x55 Start of telegram | ||
687 | * 2 0xNN Telegram payload length | ||
688 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
689 | * 4 0x00 Command ID (bits 15:8) | ||
690 | * 5 0x0D Command ID (bits 7:0) | ||
691 | * 6 0xNN Track info type. See | ||
692 | * 7-N 0xNN Track information. The data format is specific | ||
693 | * to the track info type. | ||
694 | * NN 0xNN Telegram payload checksum byte | ||
695 | * | ||
696 | * Below is the large packet format for the | ||
697 | * ReturnIndexedPlayingTrackInfo telegram sent in response to a | ||
698 | * request for information types 0x03 to 0x04. The small telegram | ||
699 | * format is not shown. | ||
700 | * | ||
701 | * Byte Value Meaning | ||
702 | * 0 0xFF Sync byte | ||
703 | * 1 0x55 Start of telegram | ||
704 | * 2 0x00 Telegram payload marker (large format) | ||
705 | * 3 0xNN Large telegram payload length (bits 15:8) | ||
706 | * 4 0xNN Large telegram payload length (bits 7:0) | ||
707 | * 5 0x04 Lingo ID (Extended Interface lingo) | ||
708 | * 6 0x00 Command ID (bits 15:8) | ||
709 | * 7 0x0D Command ID (bits 7:0) | ||
710 | * 8 0xNN Track info type. | ||
711 | * 9 0xNN Packet information bits. If set, | ||
712 | * these bits have the following meanings: | ||
713 | * Bit 0: Indicates that there are multiple packets. | ||
714 | * Bit 1: This is the last packet. Applicable only if | ||
715 | * bit 0 is set. | ||
716 | * Bit 31:2 Reserved | ||
717 | * 10 0xNN Packet Index (bits 15:8) | ||
718 | * 11 0xNN Packet Index (bits 7:0) | ||
719 | * 12-N 0xNN Track information as a UTF-8 string. | ||
720 | * NN 0xNN Telegram payload checksum byte | ||
721 | * | ||
722 | * Track info types and return data | ||
723 | * Info Type Code Data Format | ||
724 | * 0x00 Track Capabilities and Information 10-byte data. | ||
725 | * 0x01 PodcastName UTF-8 string | ||
726 | * 0x02 Track Release Date 7-byte data. | ||
727 | * 0x03 Track Description UTF-8 string | ||
728 | * 0x04 Track Song Lyrics UTF-8 string | ||
729 | * 0x05 TrackGenre UTF-8 string | ||
730 | * 0x06 Track Composer UTF-8 string | ||
731 | * 0x07 Track Artwork Count Artwork count data. The | ||
732 | * artwork count is a sequence of 4-byte records; each record | ||
733 | * consists of a 2-byte format ID value followed by a 2-byte | ||
734 | * count of images in that format for this track. For more | ||
735 | * information about formatID and chapter index values, see | ||
736 | * commands 0x000E-0x0011 and 0x002A-0x002B. | ||
737 | * 0x08-0xFF Reserved | ||
738 | * | ||
739 | * Track Capabilities and Information encoding | ||
740 | * Byte Code | ||
741 | * 0x00-0x03 Track Capability bits. If set, these bits have the | ||
742 | * following meanings: | ||
743 | * Bit 0: Track is audiobook | ||
744 | * Bit 1: Track has chapters | ||
745 | * Bit 2: Track has album artwork | ||
746 | * Bit 3: Track has song lyrics | ||
747 | * Bit 4: Track is a podcast episode | ||
748 | * Bit 5: Track has release date | ||
749 | * Bit 6: Track has description | ||
750 | * Bit 7: Track contains video (a video podcast, music | ||
751 | * video, movie, or TV show) | ||
752 | * Bit 8: Track is currently queued to play as a video | ||
753 | * Bit 31:9: Reserved | ||
754 | * 0x04 Total track length, in milliseconds (bits 31:24) | ||
755 | * 0x05 Total track length, in milliseconds (bits 23:16) | ||
756 | * 0x06 Total track length, in milliseconds (bits 15:8) | ||
757 | * 0x07 Total track length, in milliseconds (bits 7:0) | ||
758 | * 0x08 Chapter count (bits 15:8) | ||
759 | * 0x09 Chapter count (bits 7:0) | ||
760 | * | ||
761 | * Track Release Date encoding | ||
762 | * Byte Code | ||
763 | * 0x00 Seconds (0-59) | ||
764 | * 0x01 Minutes (0-59) | ||
765 | * 0x02 Hours (0-23) | ||
766 | * 0x03 Day of themonth(1-31) | ||
767 | * 0x04 Month (1-12) | ||
768 | * 0x05 Year (bits 15:8). For example, 2006 signifies the year 2006 | ||
769 | * 0x06 Year (bits 7:0) | ||
770 | * 0x07 Weekday (0-6, where 0 = Sunday and 6 = Saturday) | ||
771 | * | ||
772 | */ | ||
773 | { | ||
774 | /* We should NEVER receive this command so ERROR if we do */ | ||
775 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
776 | break; | ||
777 | } | ||
778 | case 0x000E: /* GetArtworkFormats */ | ||
779 | /* The following is the description for the Apple Firmware | ||
780 | * | ||
781 | * The device sends this command to obtain the list of supported | ||
782 | * artwork formats on the iPod. No parameters are sent. | ||
783 | * | ||
784 | * Byte Value Comment | ||
785 | * 0 0xFF Sync byte (required only for UART serial) | ||
786 | * 1 0x55 Start of telegram | ||
787 | * 2 0x03 Length of packet | ||
788 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
789 | * 4 0x00 Command ID (bits 15:8) | ||
790 | * 5 0x0E Command ID (bits 7:0) | ||
791 | * 6 0xEB Checksum/ | ||
792 | * | ||
793 | * Returned Artwork Formats are a 7byte record. | ||
794 | * formatID:2 | ||
795 | * pixelFormat:1 | ||
796 | * width:2 | ||
797 | * height:2 | ||
798 | */ | ||
799 | { | ||
800 | unsigned char data[] = {0x04, 0x00, 0x0F, | ||
801 | 0x04, 0x04, /* FormatID */ | ||
802 | 0x02, /* PixelFormat*/ | ||
803 | 0x00, 0x64, /* 100 pixels */ | ||
804 | 0x00, 0x64, /* 100 pixels */ | ||
805 | 0x04, 0x05, /* FormatID */ | ||
806 | 0x02, /* PixelFormat*/ | ||
807 | 0x00, 0xC8, /* 200 pixels */ | ||
808 | 0x00, 0xC8 /* 200 pixels */ | ||
809 | }; | ||
810 | iap_send_pkt(data, sizeof(data)); | ||
811 | break; | ||
812 | } | ||
813 | case 0x000F: /* RetArtworkFormats. See Above */ | ||
814 | /* The following is the description for the Apple Firmware | ||
815 | * | ||
816 | * The iPod sends this command to the device, giving it the list | ||
817 | * of supported artwork formats. Each format is described in a | ||
818 | * 7-byte record (formatID:2, pixelFormat:1, width:2, height:2). | ||
819 | * The formatID is used when sending GetTrackArtworkTimes. | ||
820 | * The device may return zero records. | ||
821 | * | ||
822 | * Byte Value Comment | ||
823 | * 0 0xFF Sync byte (required only for UART serial) | ||
824 | * 1 0x55 Start of telegram | ||
825 | * 2 0xNN Length of packet | ||
826 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
827 | * 4 0x00 Command ID (bits 15:8) | ||
828 | * 5 0x0F Command ID (bits 7:0) | ||
829 | * NN 0xNN formatID (15:8) iPod-assigned value for this format | ||
830 | * NN 0xNN formatID (7:0) | ||
831 | * NN 0xNN pixelFormat. Same as from SetDisplayImage | ||
832 | * NN 0xNN imageWidth(15:8).Number of pixels widefor eachimage. | ||
833 | * NN 0xNN imageWidth (7:0) | ||
834 | * NN 0xNN imageHeight (15:8). Number of pixels high for each | ||
835 | * NN 0xNN imageHeight (7:0). image | ||
836 | * Previous 7 bytes may be repeated NN times | ||
837 | * NN 0xNN Checksum | ||
838 | * | ||
839 | */ | ||
840 | { | ||
841 | /* We should NEVER receive this command so ERROR if we do */ | ||
842 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
843 | break; | ||
844 | } | ||
845 | case 0x0010: /* GetTrackArtworkData */ | ||
846 | /* The following is the description for the Apple Firmware | ||
847 | * The device sends this command to the iPod to request data for a | ||
848 | * given trackIndex, formatID, and artworkIndex. The time offset | ||
849 | * from track start is the value returned by GetTrackArtworkTimes | ||
850 | * | ||
851 | * Byte Value Comment | ||
852 | * 0 0xFF Sync byte (required only for UART serial) | ||
853 | * 1 0x55 Start of telegram | ||
854 | * 2 0x0D Length of packet | ||
855 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
856 | * 4 0x00 Command ID (bits 15:8) | ||
857 | * 5 0x10 Command ID (bits 7:0) | ||
858 | * 6 0xNN trackIndex (31:24). | ||
859 | * 7 0xNN trackIndex(23:16) | ||
860 | * 8 0xNN trackIndex (15:8) | ||
861 | * 9 0xNN trackIndex (7:0) | ||
862 | * 10 0xNN formatID (15:8) | ||
863 | * 11 0xNN formatID (7:0) | ||
864 | * 12 0xNN time offset from track start, in ms (31:24) | ||
865 | * 13 0xNN time offset from track start, in ms (23:16) | ||
866 | * 14 0xNN time offset from track start, in ms (15:8) | ||
867 | * 15 0xNN time offset from track start, in ms (7:0) | ||
868 | * 16 0xNN Checksum | ||
869 | * | ||
870 | * Returned data is | ||
871 | * DescriptorTelegramIndex: 2 | ||
872 | * pixelformatcode: 1 | ||
873 | * ImageWidthPixels: 2 | ||
874 | * ImageHeightPixels: 2 | ||
875 | * InsetRectangleTopLeftX: 2 | ||
876 | * InsetRectangleTopLeftY: 2 | ||
877 | * InsetRectangleBotRightX: 2 | ||
878 | * InsetRectangleBotRightY: 2 | ||
879 | * RowSizeInBytes: 4 | ||
880 | * ImagePixelData:VariableLength | ||
881 | * Subsequent packets omit bytes 8 - 24 | ||
882 | */ | ||
883 | { | ||
884 | unsigned char data[] = {0x04, 0x00, 0x11, | ||
885 | 0x00, 0x00, /* DescriptorIndex */ | ||
886 | 0x00, /* PixelFormat */ | ||
887 | 0x00, 0x00, /* ImageWidthPixels*/ | ||
888 | 0x00, 0x00, /* ImageHeightPixels*/ | ||
889 | 0x00, 0x00, /* InsetRectangleTopLeftX*/ | ||
890 | 0x00, 0x00, /* InsetRectangleTopLeftY*/ | ||
891 | 0x00, 0x00, /* InsetRectangleBotRightX*/ | ||
892 | 0x00, 0x00, /* InsetRectangleBotRoghtY*/ | ||
893 | 0x00, 0x00, 0x00, 0x00,/* RowSize*/ | ||
894 | 0x00 /* ImagePixelData Var Length*/ | ||
895 | }; | ||
896 | iap_send_pkt(data, sizeof(data)); | ||
897 | break; | ||
898 | } | ||
899 | case 0x0011: /* RetTrackArtworkData. See Abobe */ | ||
900 | /* The following is the description for the Apple Firmware | ||
901 | * | ||
902 | * The iPod sends the requested artwork to the accessory. Multiple | ||
903 | * RetTrackArtworkData commands may be necessary to transfer all | ||
904 | * the data because it will be too much to fit into a single packet. | ||
905 | * This command uses nearly the same format as the SetDisplayImage | ||
906 | * command (command 0x0032). The only difference is the addition | ||
907 | * of 2 coordinates; they define an inset rectangle that describes | ||
908 | * any padding that may have been added to the image. The | ||
909 | * coordinates consist of two x,y pairs. Each x or y value is 2 | ||
910 | * bytes, so the total size of the coordinate set is 8 bytes. | ||
911 | * | ||
912 | * Byte Value Comment | ||
913 | * 0 0xFF Sync byte (required only for UART serial) | ||
914 | * 1 0x55 Start of telegram | ||
915 | * 2 0xNN Length of packet | ||
916 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
917 | * 4 0x00 Command ID (bits 15:8) | ||
918 | * 5 0x11 Command ID (bits 7:0) | ||
919 | * 6 0x00 Descriptor telegram index (15:8). | ||
920 | * These fields uniquely identify each packet in the | ||
921 | * RetTrackArtworkData transaction. The first telegram | ||
922 | * is the descriptor telegram, which always starts with | ||
923 | * an index of 0x0000. | ||
924 | * 7 0x00 Descriptor telegram index (7:0) | ||
925 | * 8 0xNN Display pixel format code. | ||
926 | * 9 0xNN Imagewidth in pixels (15:8) | ||
927 | * 10 0xNN Image width in pixels (7:0) | ||
928 | * 11 0xNN Image height in pixels (15:8) | ||
929 | * 12 0xNN Image height in pixels (7:0) | ||
930 | * 13 0xNN Inset rectangle, top-left point, x value (15:8) | ||
931 | * 14 0xNN Inset rectangle, top-left point, x value (7:0) | ||
932 | * 15 0xNN Inset rectangle, top-left point, y value (15:8) | ||
933 | * 16 0xNN Inset rectangle, top-left point, y value (7:0) | ||
934 | * 17 0xNN Inset rectangle,bottom-rightpoint,xvalue(15:8) | ||
935 | * 18 0xNN Inset rectangle,bottom-rightpoint, x value(7:0) | ||
936 | * 19 0xNN Inset rectangle,bottom-rightpoint,y value(15:8) | ||
937 | * 20 0xNN Inset rectangle, bottom-right point, y value(7:0) | ||
938 | * 21 0xNN Rowsize in bytes (31:24) | ||
939 | * 22 0xNN Rowsize in bytes (23:16) | ||
940 | * 23 0xNN Row size in bytes (15:8) | ||
941 | * 24 0xNN Row size in bytes (7:0) | ||
942 | * 25-NN 0xNN Image pixel data (variable length) | ||
943 | * NN 0xNN Checksum | ||
944 | * | ||
945 | * In subsequent packets in the sequence, bytes 8 through 24 are | ||
946 | * omitted. | ||
947 | */ | ||
948 | { | ||
949 | /* We should NEVER receive this command so ERROR if we do */ | ||
950 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
951 | break; | ||
952 | } | ||
953 | case 0x0012: /* RequestProtocolVersion */ | ||
954 | /* The following is the description for the Apple Firmware | ||
955 | * | ||
956 | * This command is deprecated. | ||
957 | * | ||
958 | * Requests the version of the running protocol from the iPod. | ||
959 | * The iPod responds with a Command 0x0013:ReturnProtocolVersion | ||
960 | * command. | ||
961 | * | ||
962 | * Note: This command requests the Extended Interface protocol | ||
963 | * version, not the iPod software version. | ||
964 | * | ||
965 | * Byte Value Meaning | ||
966 | * 0 0xFF Sync byte (required only for UART serial) | ||
967 | * 1 0x55 Start of telegram | ||
968 | * 2 0x03 Telegram payload length | ||
969 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
970 | * 4 0x00 Command ID (bits 15:8) | ||
971 | * 5 0x12 Command ID (bits 7:0) | ||
972 | * 6 0xE7 Telegram payload checksum byte | ||
973 | * | ||
974 | */ | ||
975 | { | ||
976 | IAP_TX_INIT4(0x04, 0x0013); | ||
977 | IAP_TX_PUT(LINGO_MAJOR(0x04)); | ||
978 | IAP_TX_PUT(LINGO_MINOR(0x04)); | ||
979 | iap_send_tx(); | ||
980 | break; | ||
981 | } | ||
982 | case 0x0013: /* ReturnProtocolVersion. See Above */ | ||
983 | /* The following is the description for the Apple Firmware | ||
984 | * This command is deprecated. | ||
985 | * Sent from the iPod to the device | ||
986 | * Returns the iPod Extended Interface protocol version number. | ||
987 | * The iPod sends this command in response to the Command 0x0012: | ||
988 | * RequestProtocolVersion command from the device. The major | ||
989 | * version number specifies the protocol version digits to the left | ||
990 | * of the decimal point; the minor version number specifies the | ||
991 | * digits to the right of the decimal point. For example, a major | ||
992 | * version number of 0x01 and a minor version number of 0x08 | ||
993 | * represents an Extended Interface protocol version of 1.08. This | ||
994 | * protocol information is also available through the General lingo | ||
995 | * (lingo 0x00) command RequestLingoProtocolVersion when passing | ||
996 | * lingo 0x04 as the lingo parameter. | ||
997 | * | ||
998 | * Note: This command returns the Extended Interface protocol | ||
999 | * version, not the iPod software version. | ||
1000 | * | ||
1001 | * Byte Value Meaning | ||
1002 | * 0 0xFF Sync byte (required only for UART serial) | ||
1003 | * 1 0x55 Start of telegram | ||
1004 | * 2 0x05 Telegram payload length | ||
1005 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1006 | * 4 0x00 Command ID (bits 15:8) | ||
1007 | * 5 0x13 Command ID (bits 7:0) | ||
1008 | * 6 0xNN Protocol major version number | ||
1009 | * 7 0xNN Protocol minor version number | ||
1010 | * 8 0xNN Telegram payload checksum byte | ||
1011 | * | ||
1012 | */ | ||
1013 | { | ||
1014 | /* We should NEVER receive this command so ERROR if we do */ | ||
1015 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1016 | break; | ||
1017 | } | ||
1018 | case 0x0014: /* RequestiPodName */ | ||
1019 | /* The following is the description for the Apple Firmware | ||
1020 | * This command is deprecated. | ||
1021 | * Retrieves the name of the iPod | ||
1022 | * | ||
1023 | * Returns the name of the user's iPod or 'iPod' if the iPod name | ||
1024 | * is undefined. This allows the iPod name to be shown in the | ||
1025 | * human-machineinterface(HMI) of the interfacingbody. The iPod | ||
1026 | * responds with the Command 0x0015: ReturniPodName command | ||
1027 | * containing the iPod name text string. | ||
1028 | * | ||
1029 | * Byte Value Meaning | ||
1030 | * 0 0xFF Sync byte (required only for UART serial) | ||
1031 | * 1 0x55 Start of telegram | ||
1032 | * 2 0x03 Telegram payload length | ||
1033 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1034 | * 4 0x00 Command ID (bits 15:8) | ||
1035 | * 5 0x14 Command ID (bits 7:0) | ||
1036 | * 6 0xE5 Telegram payload checksum byte | ||
1037 | * | ||
1038 | * We return ROCKBOX, should this be definable? | ||
1039 | * Should it be Volume Name? | ||
1040 | */ | ||
1041 | { | ||
1042 | IAP_TX_INIT4(0x04, 0x0015); | ||
1043 | IAP_TX_PUT_STRING("ROCKBOX"); | ||
1044 | iap_send_tx(); | ||
1045 | break; | ||
1046 | } | ||
1047 | case 0x0015: /* ReturniPodName. See Above */ | ||
1048 | /* The following is the description for the Apple Firmware | ||
1049 | * This command is deprecated. | ||
1050 | * Sent from the iPod to the device | ||
1051 | * | ||
1052 | * The iPod sends this command in response to the Command 0x0014: | ||
1053 | * RequestiPodName telegram from the device. The iPod name is | ||
1054 | * encoded as a null-terminated UTF-8 character array. The iPod | ||
1055 | * name string is not limited to 252 characters; it may be sent | ||
1056 | * in small or large telegram format. The small telegram format | ||
1057 | * is shown. | ||
1058 | * | ||
1059 | * Note: Starting with version 1.07 of the Extended Interface lingo, | ||
1060 | * the ReturniPodName command on Windows-formatted iPods returns the | ||
1061 | * iTunes name of the iPod instead of the Windows volume name. | ||
1062 | * | ||
1063 | * Byte Value Meaning | ||
1064 | * 0 0xFF Sync byte (required only for UART serial) | ||
1065 | * 1 0x55 Start of telegram | ||
1066 | * 2 0xNN Telegram payload length | ||
1067 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1068 | * 4 0x00 Command ID (bits 15:8) | ||
1069 | * 5 0x15 Command ID (bits 7:0) | ||
1070 | * 6-N 0xNN iPod name as UTF-8 character array | ||
1071 | * NN 0xNN Telegram payload checksum byte | ||
1072 | * | ||
1073 | */ | ||
1074 | { | ||
1075 | /* We should NEVER receive this command so ERROR if we do */ | ||
1076 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1077 | break; | ||
1078 | } | ||
1079 | case 0x0016: /* ResetDBSelection */ | ||
1080 | /* The following is the description for the Apple Firmware | ||
1081 | * | ||
1082 | * Resets the current database selection to an empty state and | ||
1083 | * invalidates the category entry count that is, sets the count | ||
1084 | * to 0, for all categories except the playlist category. This is | ||
1085 | * analogous to pressing the Menu button repeatedly to get to the | ||
1086 | * topmost iPod HMI menu. Any previously selected database items | ||
1087 | * are deselected. The command has no effect on the playback engine | ||
1088 | * In response, the iPod sends an ACK telegram with the command | ||
1089 | * status. Once the accessory has reset the database selection, | ||
1090 | * it must initialize the category count before it can select | ||
1091 | * database records. Please refer to Command 0x0018: | ||
1092 | * GetNumberCategorizedDBRecords and Command 0x0017: | ||
1093 | * SelectDBRecord for details. | ||
1094 | * | ||
1095 | * Note: Starting with protocol version 1.07, the ResetDBSelection | ||
1096 | * command clears the sort order. | ||
1097 | * | ||
1098 | * Byte Value Meaning | ||
1099 | * 0 0xFF Sync byte (required only for UART serial) | ||
1100 | * 1 0x55 Start of telegram | ||
1101 | * 2 0x03 Telegram payload length | ||
1102 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1103 | * 4 0x00 Command ID (bits 15:8) | ||
1104 | * 5 0x16 Command ID (bits 7:0) | ||
1105 | * 6 0xE3 Telegram payload checksum byte | ||
1106 | * | ||
1107 | * | ||
1108 | * Reset the DB Record Type | ||
1109 | * Hierarchy is as follows | ||
1110 | * All = 0 | ||
1111 | * Playlist = 1 | ||
1112 | * Artist = 2 | ||
1113 | * Album = 3 | ||
1114 | * Genre = 4 | ||
1115 | * Tracks = 5 | ||
1116 | * Composers = 6 | ||
1117 | * Audiobooks = 7 | ||
1118 | * Podcasts = 8 | ||
1119 | * | ||
1120 | */ | ||
1121 | { | ||
1122 | cur_dbrecord[0] = 0; | ||
1123 | put_u32(&cur_dbrecord[1],0); | ||
1124 | /* respond with cmd ok packet */ | ||
1125 | cmd_ok(cmd); | ||
1126 | break; | ||
1127 | } | ||
1128 | case 0x0017: /* SelectDBRecord */ | ||
1129 | /* The following is the description for the Apple Firmware | ||
1130 | * Selects one or more records in the Database Engine, based on a | ||
1131 | * category relative index. For example, selecting category two | ||
1132 | * (artist) and record index one results in a list of selected | ||
1133 | * tracks (or database records) from the second artist in the | ||
1134 | * artist list. | ||
1135 | * Selections are additive and limited by the category hierarchy; | ||
1136 | * Subsequent selections are made based on the subset of records | ||
1137 | * resulting from the previous selections and not from the entire | ||
1138 | * database. | ||
1139 | * Note that the selection of a single record automatically passes | ||
1140 | * it to the Playback Engine and starts its playback. Record indices | ||
1141 | * consist of a 32-bit signed integer. To select database records | ||
1142 | * with a specific sort order, use | ||
1143 | * Command 0x0038: SelectSortDBRecord | ||
1144 | * SelectDBRecord should be called only once a category count has | ||
1145 | * been initialized through a call to Command 0x0018: | ||
1146 | * GetNumberCategorizedDBRecords. Without a valid category count, | ||
1147 | * the SelectDBRecord call cannot select a database record and will | ||
1148 | * fail with a command failed ACK. Accessories that make use of | ||
1149 | * Command 0x0016: ResetDBSelection must always initialize the | ||
1150 | * category count before selecting a new database record using | ||
1151 | * SelectDBRecord. | ||
1152 | * Accessories should pay close attention to the ACK returned by the | ||
1153 | * SelectDBRecord command. Ignoring errors may cause unexpected | ||
1154 | * behavior. | ||
1155 | * To undo a database selection, send the SelectDBRecord telegram | ||
1156 | * with the current category selected in theDatabase Engine and a | ||
1157 | * record index of -1 (0xFFFFFFFF). This has the same effect as | ||
1158 | * pressing the iPod Menu button once and moves the database | ||
1159 | * selection up to the next highest menu level. For example, if a | ||
1160 | * device selected artist number three and then album number one, | ||
1161 | * it could use the SelectDBRecord(Album, -1) telegram to return | ||
1162 | * to the database selection of artist number three. If multiple | ||
1163 | * database selections have been made, devices can use any of the | ||
1164 | * previously used categories to return to the next highest database | ||
1165 | * selection. If the category used in one of these SelectDBRecord | ||
1166 | * telegrams has not been used in a previous database selection | ||
1167 | * then the command is treated as a no-op. | ||
1168 | * Sending a SelectDBRecord telegram with the Track category and a | ||
1169 | * record index of -1 is invalid, because the previous database | ||
1170 | * selection made with the Track category and a valid index passes | ||
1171 | * the database selection to the Playback Engine. | ||
1172 | * Sending a SelectDBRecord(Track, -1) telegram returns a parameter | ||
1173 | * error. The iPod also returns a bad parameter error ACK when | ||
1174 | * devices send the SelectDBRecord telegram with an invalid category | ||
1175 | * type, or with the Track category and an index greater than the | ||
1176 | * total number of tracks available on the iPod. | ||
1177 | * | ||
1178 | * Note: Selecting a podcast always selects from the main podcast | ||
1179 | * library regardless of the current category context of the iPod. | ||
1180 | * | ||
1181 | * To immediately go to the topmost iPod menu level and reset all | ||
1182 | * database selections, send the ResetDBSelection telegram to the | ||
1183 | * iPod. | ||
1184 | * | ||
1185 | * Byte Value Meaning | ||
1186 | * 0 0xFF Sync byte (required only for UART serial) | ||
1187 | * 1 0x55 Start of telegram | ||
1188 | * 2 0x08 Telegram payload length | ||
1189 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1190 | * 4 0x00 Command ID (bits 15:8) | ||
1191 | * 5 0x17 Command ID (bits 7:0) | ||
1192 | * 6 0xNN Database category type. See | ||
1193 | * 7 0xNN Database record index (bits 31:24) | ||
1194 | * 8 0xNN Database record index (bits 23:16) | ||
1195 | * 9 0xNN Database record index (bits 15:8) | ||
1196 | * 10 0xNN Database record index (bits 7:0) | ||
1197 | * 11 0xNN Telegram payload checksum byte | ||
1198 | * | ||
1199 | * The valid database categories are listed below | ||
1200 | * | ||
1201 | * Category Code Protocol version | ||
1202 | * Reserved 0x00 N/A | ||
1203 | * Playlist 0x01 1.00 | ||
1204 | * Artist 0x02 1.00 | ||
1205 | * Album 0x03 1.00 | ||
1206 | * Genre 0x04 1.00 | ||
1207 | * Track 0x05 1.00 | ||
1208 | * Composer 0x06 1.00 | ||
1209 | * Audiobook0x07 1.06 | ||
1210 | * Podcast 0x08 1.08 | ||
1211 | * Reserved 0x09+ N/A | ||
1212 | * | ||
1213 | * cur_dbrecord[0] is the record type | ||
1214 | * cur_dbrecord[1-4] is the u32 of the record number requested | ||
1215 | * which might be a playlist or a track number depending on | ||
1216 | * the value of cur_dbrecord[0] | ||
1217 | */ | ||
1218 | { | ||
1219 | memcpy(cur_dbrecord, buf + 3, 5); | ||
1220 | |||
1221 | int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE)); | ||
1222 | uint32_t index; | ||
1223 | uint32_t trackcount; | ||
1224 | index = get_u32(&cur_dbrecord[1]); | ||
1225 | trackcount = playlist_amount(); | ||
1226 | if ((cur_dbrecord[0] == 5) && (index > trackcount)) | ||
1227 | { | ||
1228 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1229 | break; | ||
1230 | } | ||
1231 | if ((cur_dbrecord[0] == 1) && (index > (nbr_total_playlists() + 1))) | ||
1232 | { | ||
1233 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1234 | break; | ||
1235 | } | ||
1236 | audio_pause(); | ||
1237 | switch (cur_dbrecord[0]) | ||
1238 | { | ||
1239 | case 0x01: /* Playlist*/ | ||
1240 | { | ||
1241 | if (index != 0x00) /* 0x00 is the On-The-Go Playlist and | ||
1242 | we do nothing with it */ | ||
1243 | { | ||
1244 | last_selected_playlist = index; | ||
1245 | audio_skip(-iap_get_trackindex()); | ||
1246 | seek_to_playlist(last_selected_playlist); | ||
1247 | } | ||
1248 | break; | ||
1249 | } | ||
1250 | case 0x02: /* Artist */ | ||
1251 | case 0x03: /* Album */ | ||
1252 | case 0x04: /* Genre */ | ||
1253 | case 0x05: /* Track */ | ||
1254 | case 0x06: /* Composer */ | ||
1255 | { | ||
1256 | audio_skip(index - playlist_next(0)); | ||
1257 | break; | ||
1258 | } | ||
1259 | default: | ||
1260 | { | ||
1261 | /* We don't do anything with the other selections. | ||
1262 | * YET. | ||
1263 | */ | ||
1264 | break; | ||
1265 | } | ||
1266 | } | ||
1267 | if (!paused) | ||
1268 | audio_resume(); | ||
1269 | /* respond with cmd ok packet */ | ||
1270 | cmd_ok(cmd); | ||
1271 | break; | ||
1272 | } | ||
1273 | case 0x0018: /* GetNumberCategorizedDBRecords */ | ||
1274 | /* The following is the description for the Apple Firmware | ||
1275 | * | ||
1276 | * Retrieves the number of records in a particular database | ||
1277 | * category. | ||
1278 | * For example, a device can get the number of artists or albums | ||
1279 | * present in the database. The category types are described above. | ||
1280 | * The iPod responds with a Command 0x0019: | ||
1281 | * ReturnNumberCategorizedDBRecords telegram indicating the number | ||
1282 | * of records present for this category. | ||
1283 | * GetNumberCategorizedDBRecords must be called to initialize the | ||
1284 | * category count before selecting a database record using Command | ||
1285 | * 0x0017: SelectDBRecord or Command 0x0038: SelectSortDBRecord | ||
1286 | * commands. A category’s record count can change based on the prior | ||
1287 | * categories selected and the database hierarchy. The accessory | ||
1288 | * is expected to call GetNumberCategorizedDBRecords in order to | ||
1289 | * get the valid range of category entries before selecting a | ||
1290 | * record in that category. | ||
1291 | * | ||
1292 | * Note: The record count returned by this command depends on the | ||
1293 | * database state before this command is sent. If the database has | ||
1294 | * been reset using Command 0x0016: ResetDBSelection this command | ||
1295 | * returns the total number of records for a given category. | ||
1296 | * However, if this command is sent after one or more categories | ||
1297 | * are selected, the record count is the subset of records that are | ||
1298 | * members of all the categories selected prior to this command. | ||
1299 | * | ||
1300 | * Byte Value Meaning | ||
1301 | * 0 0xFF Sync byte (required only for UART serial) | ||
1302 | * 1 0x55 Start of telegram | ||
1303 | * 2 0x04 Telegram payload length | ||
1304 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1305 | * 4 0x00 Command ID (bits 15:8) | ||
1306 | * 5 0x18 Command ID (bits 7:0) | ||
1307 | * 6 0xNN Database category type. See above | ||
1308 | * 7 0xNN Telegram payload checksum byte | ||
1309 | * | ||
1310 | * This is the actual number of available records for that category | ||
1311 | * some head units (Alpine CDE-103BT) use this command before | ||
1312 | * requesting records and then hang if not enough records are | ||
1313 | * returned. | ||
1314 | */ | ||
1315 | { | ||
1316 | unsigned char data[] = {0x04, 0x00, 0x19, | ||
1317 | 0x00, 0x00, 0x00, 0x00}; | ||
1318 | switch(buf[3]) /* type number */ | ||
1319 | { | ||
1320 | case 0x01: /* total number of playlists */ | ||
1321 | dbrecordcount = nbr_total_playlists() + 1; | ||
1322 | break; | ||
1323 | case 0x05: /* total number of Tracks */ | ||
1324 | case 0x02: /* total number of Artists */ | ||
1325 | case 0x03: /* total number of Albums */ | ||
1326 | /* We don't sort on the above but some Head Units | ||
1327 | * require at least one to exist so we just return | ||
1328 | * the number of tracks in the playlist. */ | ||
1329 | dbrecordcount = playlist_amount(); | ||
1330 | break; | ||
1331 | case 0x04: /* total number of Genres */ | ||
1332 | case 0x06: /* total number of Composers */ | ||
1333 | case 0x07: /* total number of AudioBooks */ | ||
1334 | case 0x08: /* total number of Podcasts */ | ||
1335 | /* We don't support the above so just return that | ||
1336 | there are none available. */ | ||
1337 | dbrecordcount = 0; | ||
1338 | break; | ||
1339 | } | ||
1340 | put_u32(&data[3], dbrecordcount); | ||
1341 | iap_send_pkt(data, sizeof(data)); | ||
1342 | break; | ||
1343 | } | ||
1344 | case 0x0019: /* ReturnNumberCategorizedDBRecords. See Above */ | ||
1345 | /* The following is the description for the Apple Firmware | ||
1346 | * | ||
1347 | * Returns the number of database records matching the specified | ||
1348 | * database category. The iPod sends this telegram in response to | ||
1349 | * the Command 0x0018: GetNumberCategorizedDBRecords telegram from | ||
1350 | * the device. Individual records can then be extracted by sending | ||
1351 | * Command 0x001A: RetrieveCategorizedDatabaseRecords to the iPod. | ||
1352 | * If no matching database records are found, a record count of | ||
1353 | * zero is returned. Category types are described above. | ||
1354 | * After selecting the podcast category, the number of artist, | ||
1355 | * album, composer, genre, and audiobook records is always zero. | ||
1356 | * | ||
1357 | * Byte Value Meaning | ||
1358 | * 0 0xFF Sync byte (required only for UART serial) | ||
1359 | * 1 0x55 Start of telegram | ||
1360 | * 2 0x07 Telegram payload length | ||
1361 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1362 | * 4 0x00 Command ID (bits 15:8) | ||
1363 | * 5 0x19 Command ID (bits 7:0) | ||
1364 | * 6 0xNN Database record count (bits 31:24) | ||
1365 | * 7 0xNN Database record count (bits 23:16) | ||
1366 | * 8 0xNN Database record count (bits 15:8) | ||
1367 | * 9 0xNN Database record count (bits 7:0) | ||
1368 | * 10 0xNN Telegram payload checksum byte | ||
1369 | * | ||
1370 | */ | ||
1371 | { | ||
1372 | /* We should NEVER receive this command so ERROR if we do */ | ||
1373 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1374 | break; | ||
1375 | } | ||
1376 | case 0x001A: /* RetrieveCategorizedDatabaseRecords */ | ||
1377 | /* The following is the description for the Apple Firmware | ||
1378 | * | ||
1379 | * Retrieves one or more database records from the iPod, | ||
1380 | * typically based on the results from the Command 0x0018: | ||
1381 | * GetNumberCategorizedDBRecords query. The database | ||
1382 | * category types are described above. This telegram | ||
1383 | * specifies the starting record index and the number of | ||
1384 | * records to retrieve (the record count). This allows a device | ||
1385 | * to retrieve an individual record or the entire set of records | ||
1386 | * for a category. The record start index and record count consist | ||
1387 | * of 32-bit signed integers. To retrieve all records from a given | ||
1388 | * starting record index, set the record count to -1 (0xFFFFFFFF). | ||
1389 | * The iPod responds to this telegram with a separate Command | ||
1390 | * 0x001B: ReturnCategorizedDatabaseRecord telegram FOR EACH record | ||
1391 | * matching the specified criteria (category and record index | ||
1392 | * range). | ||
1393 | * | ||
1394 | * Byte Value Meaning | ||
1395 | * 0 0xFF Sync byte (required only for UART serial) | ||
1396 | * 1 0x55 Start of telegram | ||
1397 | * 2 0x0C Telegram payload length | ||
1398 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1399 | * 4 0x00 Command ID (bits 15:8) | ||
1400 | * 5 0x1A Command ID (bits 7:0) | ||
1401 | * 6 0xNN Database category type. See above | ||
1402 | * 7 0xNN Database record start index (bits 31:24) | ||
1403 | * 8 0xNN Database record start index (bits 23:16) | ||
1404 | * 9 0xNN Database record start index (bits 15:8) | ||
1405 | * 10 0xNN Database record start index (bits 7:0) | ||
1406 | * 11 0xNN Database record read count (bits 31:24) | ||
1407 | * 12 0xNN Database record read count (bits 23:16) | ||
1408 | * 13 0xNN Database record read count (bits 15:8) | ||
1409 | * 14 0xNN Database record read count (bits 7:0) | ||
1410 | * 15 0xNN Telegram payload checksum byte | ||
1411 | |||
1412 | * The returned data | ||
1413 | * contains information for a single database record. The iPod sends | ||
1414 | * one or more of these telegrams in response to the Command 0x001A: | ||
1415 | * RetrieveCategorizedDatabaseRecords telegram from the device. The | ||
1416 | * category record index is included to allow the device to | ||
1417 | * determine which record has been sent. The record data is sent as | ||
1418 | * a null-terminated UTF-8 encoded data array. | ||
1419 | */ | ||
1420 | { | ||
1421 | unsigned char data[7 + MAX_PATH] = | ||
1422 | {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, | ||
1423 | 'O','n','-','T','h','e','-','G','o','\0'}; | ||
1424 | struct playlist_track_info track; | ||
1425 | struct mp3entry id3; | ||
1426 | |||
1427 | unsigned long start_index = get_u32(&buf[4]); | ||
1428 | unsigned long read_count = get_u32(&buf[8]); | ||
1429 | unsigned long counter = 0; | ||
1430 | unsigned int number_of_playlists = nbr_total_playlists(); | ||
1431 | uint32_t trackcount; | ||
1432 | trackcount = playlist_amount(); | ||
1433 | size_t len; | ||
1434 | |||
1435 | if ((buf[3] == 0x05) && ((start_index + read_count ) > trackcount)) | ||
1436 | { | ||
1437 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1438 | break; | ||
1439 | } | ||
1440 | if ((buf[3] == 0x01) && ((start_index + read_count) > (number_of_playlists + 1))) | ||
1441 | { | ||
1442 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1443 | break; | ||
1444 | } | ||
1445 | for (counter=0;counter<read_count;counter++) | ||
1446 | { | ||
1447 | switch(buf[3]) /* type number */ | ||
1448 | { | ||
1449 | case 0x01: /* Playlists */ | ||
1450 | get_playlist_name(data +7,start_index+counter, MAX_PATH); | ||
1451 | /*Remove file extension*/ | ||
1452 | char *dot=NULL; | ||
1453 | dot = (strrchr(data+7, '.')); | ||
1454 | if (dot != NULL) | ||
1455 | *dot = '\0'; | ||
1456 | break; | ||
1457 | case 0x05: /* Tracks */ | ||
1458 | case 0x02: /* Artists */ | ||
1459 | case 0x03: /* Albums */ | ||
1460 | case 0x04: /* Genre */ | ||
1461 | case 0x06: /* Composer */ | ||
1462 | playlist_get_track_info(NULL, start_index + counter, | ||
1463 | &track); | ||
1464 | iap_get_trackinfo(start_index + counter, &id3); | ||
1465 | switch(buf[3]) | ||
1466 | { | ||
1467 | case 0x05: | ||
1468 | len = strlcpy((char *)&data[7], id3.title,64); | ||
1469 | break; | ||
1470 | case 0x02: | ||
1471 | len = strlcpy((char *)&data[7], id3.artist,64); | ||
1472 | break; | ||
1473 | case 0x03: | ||
1474 | len = strlcpy((char *)&data[7], id3.album,64); | ||
1475 | break; | ||
1476 | case 0x04: | ||
1477 | case 0x06: | ||
1478 | len = strlcpy((char *)&data[7], "Not Supported",14); | ||
1479 | break; | ||
1480 | } | ||
1481 | break; | ||
1482 | } | ||
1483 | put_u32(&data[3], start_index+counter); | ||
1484 | iap_send_pkt(data, 7 + strlen(data+7) + 1); | ||
1485 | yield(); | ||
1486 | } | ||
1487 | break; | ||
1488 | } | ||
1489 | case 0x001B: /* ReturnCategorizedDatabaseRecord. See Above */ | ||
1490 | /* The following is the description for the Apple Firmware | ||
1491 | * | ||
1492 | * Contains information for a single database record. The iPod sends | ||
1493 | * ONE OR MORE of these telegrams in response to the Command 0x001A: | ||
1494 | * RetrieveCategorizedDatabaseRecords telegram from the device. The | ||
1495 | * category record index is included to allow the device to | ||
1496 | * determine which record has been sent. The record data is sent | ||
1497 | * as a null-terminated UTF-8 encoded data array. | ||
1498 | * | ||
1499 | * Note: The database record string is not limited to 252 characters | ||
1500 | * it may be sent in small or large telegram format, depending on | ||
1501 | * the record size. The small telegram format is shown. | ||
1502 | * | ||
1503 | * Byte Value Meaning | ||
1504 | * 0 0xFF Sync byte (required only for UART serial) | ||
1505 | * 1 0x55 Start of telegram | ||
1506 | * 2 0xNN Telegram payload length | ||
1507 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1508 | * 4 0x00 Command ID (bits 15:8) | ||
1509 | * 5 0x1B Command ID (bits 7:0) | ||
1510 | * 6 0xNN Database record category index (bits 31:24) | ||
1511 | * 7 0xNN Database record category index (bits 23:16) | ||
1512 | * 8 0xNN Database record category index (bits 15:8) | ||
1513 | * 9 0xNN Database record category index (bits 7:0) | ||
1514 | * 10-N 0xNN Database record as a UTF-8 character array. | ||
1515 | * NN 0xNN Telegram payload checksum byte | ||
1516 | * | ||
1517 | */ | ||
1518 | { | ||
1519 | /* We should NEVER receive this command so ERROR if we do */ | ||
1520 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1521 | break; | ||
1522 | } | ||
1523 | case 0x001C: /* GetPlayStatus */ | ||
1524 | /* The following is the description for the Apple Firmware | ||
1525 | * | ||
1526 | * Requests the current iPod playback status, allowing the | ||
1527 | * device to display feedback to the user. In response, the | ||
1528 | * iPod sends a Command 0x001D: ReturnPlayStatus telegram | ||
1529 | * with the current playback status. | ||
1530 | * | ||
1531 | * Byte Value Meaning | ||
1532 | * 0 0xFF Sync byte (required only for UART serial) | ||
1533 | * 1 0x55 Start of telegram | ||
1534 | * 2 0x03 Telegram payload length | ||
1535 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1536 | * 4 0x00 Command ID (bits 15:8) | ||
1537 | * 5 0x1C Command ID (bits 7:0) | ||
1538 | * 6 0xDD Telegram payload checksum byte | ||
1539 | * | ||
1540 | */ | ||
1541 | { | ||
1542 | unsigned char data[] = {0x04, 0x00, 0x1D, | ||
1543 | 0x00, 0x00, 0x00, 0x00, | ||
1544 | 0x00, 0x00, 0x00, 0x00, | ||
1545 | 0x00}; | ||
1546 | struct mp3entry *id3 = audio_current_track(); | ||
1547 | unsigned long time_total = id3->length; | ||
1548 | unsigned long time_elapsed = id3->elapsed; | ||
1549 | int status = audio_status(); | ||
1550 | put_u32(&data[3], time_total); | ||
1551 | put_u32(&data[7], time_elapsed); | ||
1552 | if (status == AUDIO_STATUS_PLAY) | ||
1553 | data[11] = 0x01; /* play */ | ||
1554 | else if (status & AUDIO_STATUS_PAUSE) | ||
1555 | data[11] = 0x02; /* pause */ | ||
1556 | iap_send_pkt(data, sizeof(data)); | ||
1557 | break; | ||
1558 | } | ||
1559 | case 0x001D: /* ReturnPlayStatus. See Above */ | ||
1560 | /* The following is the description for the Apple Firmware | ||
1561 | * | ||
1562 | * Returns the current iPod playback status. The iPod sends this | ||
1563 | * telegram in response to the Command 0x001C: GetPlayStatus | ||
1564 | * telegram from the device. The information returned includes the | ||
1565 | * current track length, track position, and player state. | ||
1566 | * | ||
1567 | * Note: The track length and track position fields are valid only | ||
1568 | * if the player state is Playing or Paused. For other player | ||
1569 | * states, these fields should be ignored. | ||
1570 | * | ||
1571 | * Byte Value Meaning | ||
1572 | * 0 0xFF Sync byte (required only for UART serial) | ||
1573 | * 1 0x55 Start of telegram | ||
1574 | * 2 0x0C Telegram payload length | ||
1575 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1576 | * 4 0x00 Command ID (bits 15:8) | ||
1577 | * 5 0x1D Command ID (bits 7:0) | ||
1578 | * 6 0xNN Track length in milliseconds (bits 31:24) | ||
1579 | * 7 0xNN Track length in milliseconds (bits 23:16) | ||
1580 | * 8 0xNN Track length in milliseconds (bits 15:8) | ||
1581 | * 9 0xNN Track length in milliseconds (bits 7:0) | ||
1582 | * 10 0xNN Track position in milliseconds (bits 31:24) | ||
1583 | * 11 0xNN Track position in milliseconds (bits 23:16) | ||
1584 | * 12 0xNN Track position in milliseconds (bits 15:8) | ||
1585 | * 13 0xNN Track position in milliseconds (bits 7:0) | ||
1586 | * 14 0xNN Player state. Possible values are: | ||
1587 | * 0x00 = Stopped | ||
1588 | * 0x01 = Playing | ||
1589 | * 0x02 = Paused | ||
1590 | * 0x03 - 0xFE = Reserved | ||
1591 | * 0xFF = Error | ||
1592 | * 15 0xNN Telegram payload checksum byte | ||
1593 | * | ||
1594 | */ | ||
1595 | { | ||
1596 | /* We should NEVER receive this command so ERROR if we do */ | ||
1597 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1598 | break; | ||
1599 | } | ||
1600 | case 0x001E: /* GetCurrentPlayingTrackIndex */ | ||
1601 | /* The following is the description for the Apple Firmware | ||
1602 | * | ||
1603 | * Requests the playback engine index of the currently playing | ||
1604 | * track. In response, the iPod sends a Command 0x001F: | ||
1605 | * ReturnCurrentPlayingTrackIndex telegram to the device. | ||
1606 | * | ||
1607 | * Note: The track index returned is valid only if there is | ||
1608 | * currently a track playing or paused. | ||
1609 | * | ||
1610 | * Byte Value Meaning | ||
1611 | * 0 0xFF Sync byte (required only for UART serial) | ||
1612 | * 1 0x55 Start of telegram | ||
1613 | * 2 0x03 Telegram payload length | ||
1614 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1615 | * 4 0x00 Command ID (bits 15:8) | ||
1616 | * 5 0x1E Command ID (bits 7:0) | ||
1617 | * 6 0xDB Telegram payload checksum byte | ||
1618 | * | ||
1619 | */ | ||
1620 | { | ||
1621 | unsigned char data[] = {0x04, 0x00, 0x1F, | ||
1622 | 0xFF, 0xFF, 0xFF, 0xFF}; | ||
1623 | long playlist_pos = playlist_next(0); | ||
1624 | int status = audio_status(); | ||
1625 | playlist_pos -= playlist_get_first_index(NULL); | ||
1626 | if(playlist_pos < 0) | ||
1627 | playlist_pos += playlist_amount(); | ||
1628 | if ((status == AUDIO_STATUS_PLAY) || (status & AUDIO_STATUS_PAUSE)) | ||
1629 | put_u32(&data[3], playlist_pos); | ||
1630 | iap_send_pkt(data, sizeof(data)); | ||
1631 | break; | ||
1632 | } | ||
1633 | case 0x001F: /* ReturnCurrentPlayingTrackIndex. See Above */ | ||
1634 | /* The following is the description for the Apple Firmware | ||
1635 | * | ||
1636 | * Returns the playback engine index of the current playing track in | ||
1637 | * response to the Command 0x001E: GetCurrentPlayingTrackIndex | ||
1638 | * telegram from the device. The track index is a 32-bit signed | ||
1639 | * integer. | ||
1640 | * If there is no track currently playing or paused, an index of -1 | ||
1641 | * (0xFFFFFFFF) is returned. | ||
1642 | * | ||
1643 | * Byte Value Meaning | ||
1644 | * 0 0xFF Sync byte (required only for UART serial) | ||
1645 | * 1 0x55 Start of telegram | ||
1646 | * 2 0x07 Telegram payload length | ||
1647 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1648 | * 4 0x00 Command ID (bits 15:8) | ||
1649 | * 5 0x1F Command ID (bits 7:0) | ||
1650 | * 6 0xNN Playback track index (bits 31:24) | ||
1651 | * 7 0xNN Playback track index (bits 23:16) | ||
1652 | * 8 0xNN Playback track index (bits 15:8) | ||
1653 | * 9 0xNN Playback track index (bits 7:0) | ||
1654 | * 10 0xNN Telegram payload checksum byte | ||
1655 | * | ||
1656 | */ | ||
1657 | { | ||
1658 | /* We should NEVER receive this command so ERROR if we do */ | ||
1659 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1660 | break; | ||
1661 | } | ||
1662 | case 0x0020: /* GetIndexedPlayingTrackTitle. See 0x0024 below */ | ||
1663 | /* The following is the description for the Apple Firmware | ||
1664 | * | ||
1665 | * Requests the title name of the indexed playing track from the | ||
1666 | * iPod. In response to a valid telegram, the iPod sends a | ||
1667 | * Command 0x0021: ReturnIndexedPlayingTrackTitle telegram to the | ||
1668 | * device. | ||
1669 | * | ||
1670 | * Note: If the telegram length or playing track index is invalid, | ||
1671 | * the iPod responds with an ACK telegram including the specific | ||
1672 | * error status. | ||
1673 | * | ||
1674 | * Byte Value Meaning | ||
1675 | * 0 0xFF Sync byte (required only for UART serial) | ||
1676 | * 1 0x55 Start of telegram | ||
1677 | * 2 0x07 Telegram payload length | ||
1678 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1679 | * 4 0x00 Command ID (bits 15:8) | ||
1680 | * 5 0x20 Command ID (bits 7:0) | ||
1681 | * 6 0xNN Playback track index (bits 31:24) | ||
1682 | * 7 0xNN Playback track index (bits 23:16) | ||
1683 | * 8 0xNN Playback track index (bits 15:8) | ||
1684 | * 9 0xNN Playback track index (bits 7:0) | ||
1685 | * 10 0xNN Telegram payload checksum byte | ||
1686 | * | ||
1687 | */ | ||
1688 | case 0x0021: /* ReturnIndexedPlayingTrackTitle. See 0x0024 Below */ | ||
1689 | /* The following is the description for the Apple Firmware | ||
1690 | * | ||
1691 | * Returns the title of the indexed playing track in response to | ||
1692 | * a valid Command 0x0020 GetIndexedPlayingTrackTitle telegram from | ||
1693 | * the device. The track title is encoded as a null-terminated UTF-8 | ||
1694 | * character array. | ||
1695 | * | ||
1696 | * Note: The track title string is not limited to 252 characters; | ||
1697 | * it may be sent in small or large telegram format, depending on | ||
1698 | * the string length. The small telegram format is shown. | ||
1699 | * | ||
1700 | * Byte Value Meaning | ||
1701 | * 0 0xFF Sync byte (required only for UART serial) | ||
1702 | * 1 0x55 Start of telegram | ||
1703 | * 2 0xNN Telegram payload length | ||
1704 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1705 | * 4 0x00 Command ID (bits 15:8) | ||
1706 | * 5 0x21 Command ID (bits 7:0) | ||
1707 | * 6-N 0xNN Track title as a UTF-8 character array | ||
1708 | * NN 0xNN Telegram payload checksum byte | ||
1709 | * | ||
1710 | */ | ||
1711 | case 0x0022: /* GetIndexedPlayingTrackArtistName. See 0x0024 Below */ | ||
1712 | /* The following is the description for the Apple Firmware | ||
1713 | * | ||
1714 | * Requests the name of the artist of the indexed playing track | ||
1715 | * In response to a valid telegram, the iPod sends a | ||
1716 | * Command 0x0023: ReturnIndexedPlayingTrackArtistName telegram to | ||
1717 | * the device. | ||
1718 | * | ||
1719 | * Note: If the telegram length or playing track index is invalid, | ||
1720 | * the iPod responds with an ACK telegram including the specific | ||
1721 | * error status. | ||
1722 | * | ||
1723 | * Byte Value Meaning | ||
1724 | * 0 0xFF Sync byte (required only for UART serial) | ||
1725 | * 1 0x55 Start of telegram | ||
1726 | * 2 0x07 Telegram payload length | ||
1727 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1728 | * 4 0x00 Command ID (bits 15:8) | ||
1729 | * 5 0x22 Command ID (bits 7:0) | ||
1730 | * 6 0xNN Playback track index (bits 31:24) | ||
1731 | * 7 0xNN Playback track index (bits 23:16) | ||
1732 | * 8 0xNN Playback track index (bits 15:8) | ||
1733 | * 9 0xNN Playback track index (bits 7:0) | ||
1734 | * 10 0xNN Telegram payload checksum byte | ||
1735 | * | ||
1736 | */ | ||
1737 | case 0x0023: /* ReturnIndexedPlayingTrackArtistName. See 0x0024 Below */ | ||
1738 | /* The following is the description for the Apple Firmware | ||
1739 | * | ||
1740 | * Returns the artist name of the indexed playing track in response | ||
1741 | * to a valid Command 0x0022 GetIndexedPlayingTrackArtistName | ||
1742 | * telegram from the device. The track artist name is encoded as a | ||
1743 | * null-terminated UTF-8 character array. | ||
1744 | * | ||
1745 | * Note: The artist name string is not limited to 252 characters; | ||
1746 | * it may be sent in small or large telegram format, depending on | ||
1747 | * the string length. The small telegram format is shown. | ||
1748 | * | ||
1749 | * Byte Value Meaning | ||
1750 | * 0 0xFF Sync byte (required only for UART serial) | ||
1751 | * 1 0x55 Start of telegram | ||
1752 | * 2 0xNN Telegram payload length | ||
1753 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1754 | * 4 0x00 Command ID (bits 15:8) | ||
1755 | * 5 0x23 Command ID (bits 7:0) | ||
1756 | * 6-N 0xNN Track Artist as a UTF-8 character array | ||
1757 | * NN 0xNN Telegram payload checksum byte | ||
1758 | * | ||
1759 | */ | ||
1760 | case 0x0024: /* GetIndexedPlayingTrackAlbumName AND | ||
1761 | * GetIndexedPlayingTrackTitle AND | ||
1762 | * AND GetIndexedPlayingTrackArtistName. */ | ||
1763 | /* The following is the description for the Apple Firmware | ||
1764 | * | ||
1765 | * Requests the album name of the indexed playing track | ||
1766 | * In response to a valid telegram, the iPod sends a | ||
1767 | * Command 0x0025: ReturnIndexedPlayingTrackAlbumName telegram to | ||
1768 | * the device. | ||
1769 | * | ||
1770 | * Note: If the telegram length or playing track index is invalid, | ||
1771 | * the iPod responds with an ACK telegram including the specific | ||
1772 | * error status. | ||
1773 | * | ||
1774 | * Byte Value Meaning | ||
1775 | * 0 0xFF Sync byte (required only for UART serial) | ||
1776 | * 1 0x55 Start of telegram | ||
1777 | * 2 0x07 Telegram payload length | ||
1778 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1779 | * 4 0x00 Command ID (bits 15:8) | ||
1780 | * 5 0x24 Command ID (bits 7:0) | ||
1781 | * 6 0xNN Playback track index (bits 31:24) | ||
1782 | * 7 0xNN Playback track index (bits 23:16) | ||
1783 | * 8 0xNN Playback track index (bits 15:8) | ||
1784 | * 9 0xNN Playback track index (bits 7:0) | ||
1785 | * 10 0xNN Telegram payload checksum byte | ||
1786 | * | ||
1787 | */ | ||
1788 | { | ||
1789 | unsigned char data[70] = {0x04, 0x00, 0xFF}; | ||
1790 | struct mp3entry id3; | ||
1791 | int fd; | ||
1792 | size_t len; | ||
1793 | long tracknum = get_u32(&buf[3]); | ||
1794 | |||
1795 | data[2] = cmd + 1; | ||
1796 | memcpy(&id3, audio_current_track(), sizeof(id3)); | ||
1797 | tracknum += playlist_get_first_index(NULL); | ||
1798 | if(tracknum >= playlist_amount()) | ||
1799 | tracknum -= playlist_amount(); | ||
1800 | /* If the tracknumber is not the current one, | ||
1801 | read id3 from disk */ | ||
1802 | if(playlist_next(0) != tracknum) | ||
1803 | { | ||
1804 | struct playlist_track_info info; | ||
1805 | playlist_get_track_info(NULL, tracknum, &info); | ||
1806 | fd = open(info.filename, O_RDONLY); | ||
1807 | memset(&id3, 0, sizeof(struct mp3entry)); | ||
1808 | get_metadata(&id3, fd, info.filename); | ||
1809 | close(fd); | ||
1810 | } | ||
1811 | /* Return the requested track data */ | ||
1812 | switch(cmd) | ||
1813 | { | ||
1814 | case 0x20: | ||
1815 | len = strlcpy((char *)&data[3], id3.title, 64); | ||
1816 | iap_send_pkt(data, 4+len); | ||
1817 | break; | ||
1818 | case 0x22: | ||
1819 | len = strlcpy((char *)&data[3], id3.artist, 64); | ||
1820 | iap_send_pkt(data, 4+len); | ||
1821 | break; | ||
1822 | case 0x24: | ||
1823 | len = strlcpy((char *)&data[3], id3.album, 64); | ||
1824 | iap_send_pkt(data, 4+len); | ||
1825 | break; | ||
1826 | } | ||
1827 | break; | ||
1828 | } | ||
1829 | case 0x0025: /* ReturnIndexedPlayingTrackAlbumName. See 0x0024 Above */ | ||
1830 | /* The following is the description for the Apple Firmware | ||
1831 | * | ||
1832 | * Returns the album name of the indexed playing track in response | ||
1833 | * to a valid Command 0x0024 GetIndexedPlayingTrackAlbumName | ||
1834 | * telegram from the device. The track artist name is encoded as a | ||
1835 | * null-terminated UTF-8 character array. | ||
1836 | * | ||
1837 | * Note: The album name string is not limited to 252 characters; | ||
1838 | * it may be sent in small or large telegram format, depending on | ||
1839 | * the string length. The small telegram format is shown. | ||
1840 | * | ||
1841 | * Byte Value Meaning | ||
1842 | * 0 0xFF Sync byte (required only for UART serial) | ||
1843 | * 1 0x55 Start of telegram | ||
1844 | * 2 0xNN Telegram payload length | ||
1845 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1846 | * 4 0x00 Command ID (bits 15:8) | ||
1847 | * 5 0x25 Command ID (bits 7:0) | ||
1848 | * 6-N 0xNN Track Album as a UTF-8 character array | ||
1849 | * NN 0xNN Telegram payload checksum byte | ||
1850 | * | ||
1851 | */ | ||
1852 | { | ||
1853 | /* We should NEVER receive this command so ERROR if we do */ | ||
1854 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
1855 | break; | ||
1856 | } | ||
1857 | case 0x0026: /* SetPlayStatusChangeNotification */ | ||
1858 | /* The following is the description for the Apple Firmware | ||
1859 | * Sets the state of play status change notifications from the iPod | ||
1860 | * to the device. Notification of play status changes can be | ||
1861 | * globally enabled or disabled. If notifications are enabled, the | ||
1862 | * iPod sends a Command 0x0027: PlayStatusChangeNotification | ||
1863 | * telegram to the device each time the play status changes, until | ||
1864 | * the device sends this telegram again with the disable | ||
1865 | * notification option. In response, the iPod sends an ACK telegram | ||
1866 | * indicating the status of the SetPlayStatusChangeNotification | ||
1867 | * command. | ||
1868 | * | ||
1869 | * Byte Value Meaning | ||
1870 | * 0 0xFF Sync byte (required only for UART serial) | ||
1871 | * 1 0x55 Start of telegram | ||
1872 | * 2 0x04 Telegram payload length | ||
1873 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1874 | * 4 0x00 Command ID (bits 15:8) | ||
1875 | * 5 0x26 Command ID (bits 7:0) | ||
1876 | * 6 0xNN The state of play status change notifications. | ||
1877 | * Possible values are: | ||
1878 | * 0x00 = Disable all change notification | ||
1879 | * 0x01 = Enable all change notification | ||
1880 | * 0x02 - 0xFF = Reserved | ||
1881 | * 7 0xNN Telegram payload checksum byte | ||
1882 | */ | ||
1883 | { | ||
1884 | device.do_notify = buf[3] ? true : false; | ||
1885 | /* respond with cmd ok packet */ | ||
1886 | cmd_ok(cmd); | ||
1887 | break; | ||
1888 | } | ||
1889 | case 0x0027: /* PlayStatusChangeNotification */ | ||
1890 | /* This response is handled by iap_track_changed() and iap_periodic() | ||
1891 | * within iap-core.c | ||
1892 | * The following is the description for the Apple Firmware | ||
1893 | * | ||
1894 | * The iPod sends this telegram to the device when the iPod play status | ||
1895 | * changes, if the device has previously enabled notifications using | ||
1896 | * Command 0x0026: SetPlayStatusChangeNotification . This telegram | ||
1897 | * contains details about the new play status. Notification telegrams | ||
1898 | * for changes in track position occur approximately every 500 | ||
1899 | * milliseconds while the iPod is playing. Notification telegrams are | ||
1900 | * sent from the iPod until the device sends the | ||
1901 | * SetPlayStatusChangeNotification telegram with the option to disable | ||
1902 | * all notifications. | ||
1903 | * Some notifications include additional data about the new play status. | ||
1904 | * | ||
1905 | * PlayStatusChangeNotification telegram: notifications 0x00, 0x02, 0x03 | ||
1906 | * Byte Value Meaning | ||
1907 | * 0 0xFF Sync byte (required only for UART serial) | ||
1908 | * 1 0x55 Start of telegram | ||
1909 | * 2 0x04 Telegram payload length | ||
1910 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1911 | * 4 0x00 Command ID (bits 15:8) | ||
1912 | * 5 0x27 Command ID (bits 7:0) | ||
1913 | * 6 0xNN New play status. See Below. | ||
1914 | * 7 0xNN Telegram payload checksum byte | ||
1915 | * | ||
1916 | * Play Status Change Code Extended Status Data (if | ||
1917 | * any) | ||
1918 | * Playback stopped 0x00 None | ||
1919 | * Playback track changed 0x01 New track record index | ||
1920 | * (32 bits) | ||
1921 | * Playback forward seek stop 0x02 None | ||
1922 | * Playback backward seek stop 0x03 None | ||
1923 | * Playback track position 0x04 New track position in | ||
1924 | * milliseconds (32 bits) | ||
1925 | * Playback chapter changed 0x05 New chapter index (32 | ||
1926 | * bits) | ||
1927 | * Reserved 0x06 - 0xFF N/A | ||
1928 | * | ||
1929 | * Playback track changed (code 0x01) | ||
1930 | * Byte Value Meaning | ||
1931 | * 0 0xFF Sync byte (required only for UART serial) | ||
1932 | * 1 0x55 Start of telegram | ||
1933 | * 2 0x08 Telegram payload length | ||
1934 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1935 | * 4 0x00 Command ID (bits 15:8) | ||
1936 | * 5 0x27 Command ID (bits 7:0) | ||
1937 | * 6 0x01 New status: Playback track changed | ||
1938 | * 7 0xNN New playback track index (bits 31:24) | ||
1939 | * 8 0xNN New playback track index (bits 23:16) | ||
1940 | * 9 0xNN New playback track index (bits 15:8) | ||
1941 | * 10 0xNN New playback track index (bits 7:0) | ||
1942 | * 11 0xNN Telegram payload checksum byte | ||
1943 | * | ||
1944 | * Playback track position changed (code 0x04) | ||
1945 | * Byte Value Meaning | ||
1946 | * 0 0xFF Sync byte (required only for UART serial) | ||
1947 | * 1 0x55 Start of telegram | ||
1948 | * 2 0x08 Telegram payload length | ||
1949 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1950 | * 4 0x00 Command ID (bits 15:8) | ||
1951 | * 5 0x27 Command ID (bits 7:0) | ||
1952 | * 6 0x04 New status: Playback track position changed | ||
1953 | * 7 0xNN New track position in milliseconds (bits 31:24) | ||
1954 | * 8 0xNN New track position in milliseconds (bits 23:16) | ||
1955 | * 9 0xNN New track position in milliseconds (bits 15:8) | ||
1956 | * 10 0xNN New track position in milliseconds (bits 7:0) | ||
1957 | * 11 0xNN Telegram payload checksum byte | ||
1958 | * | ||
1959 | * Playback chapter changed (code 0x05) | ||
1960 | * Byte Value Meaning | ||
1961 | * 0 0xFF Sync byte (required only for UART serial) | ||
1962 | * 1 0x55 Start of telegram | ||
1963 | * 2 0x08 Telegram payload length | ||
1964 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1965 | * 4 0x00 Command ID (bits 15:8) | ||
1966 | * 5 0x27 Command ID (bits 7:0) | ||
1967 | * 6 0x05 New status: Playback chapter changed | ||
1968 | * 7 0xNN New track chapter index (bits 31:24) | ||
1969 | * 8 0xNN New track chapter index (bits 23:16) | ||
1970 | * 9 0xNN New track chapter index (bits 15:8) | ||
1971 | * 10 0xNN New track chapter index (bits 7:0) | ||
1972 | * 11 0xNN Telegram payload checksum byte | ||
1973 | * | ||
1974 | */ | ||
1975 | case 0x0028: /* PlayCurrentSelection */ | ||
1976 | /* The following is the description for the Apple Firmware | ||
1977 | * | ||
1978 | * Requests playback of the currently selected track or list of | ||
1979 | * tracks. The currently selected tracks are placed in the Now | ||
1980 | * Playing playlist, where they are optionally shuffled (if the | ||
1981 | * shuffle feature is enabled). Finally, the specified track record | ||
1982 | * index is passed to the player to play. Note that if the track | ||
1983 | * index is -1(0xFFFFFFFF), the first track after the shuffle is | ||
1984 | * complete is played first. If a track index of n is sent, the nth | ||
1985 | * track of the selected tracks is played first, regardless of | ||
1986 | * where it is located in the Now Playing playlist after the shuffle | ||
1987 | * is performed (assuming that shuffle is on). In response, the iPod | ||
1988 | * sends an ACK telegram indicating the status of the command. | ||
1989 | * | ||
1990 | * Byte Value Meaning | ||
1991 | * 0 0xFF Sync byte (required only for UART serial) | ||
1992 | * 1 0x55 Start of telegram | ||
1993 | * 2 0x07 Telegram payload length | ||
1994 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
1995 | * 4 0x00 Command ID (bits 15:8) | ||
1996 | * 5 0x28 Command ID (bits 7:0) | ||
1997 | * 6 0xNN Selection track record index (bits 31:24) | ||
1998 | * 7 0xNN Selection track record index (bits 23:16) | ||
1999 | * 8 0xNN Selection track record index (bits 15:8) | ||
2000 | * 9 0xNN Selection track record index (bits 7:0) | ||
2001 | * 10 0xNN Telegram payload checksum byte | ||
2002 | * | ||
2003 | */ | ||
2004 | { | ||
2005 | int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE)); | ||
2006 | uint32_t index; | ||
2007 | uint32_t trackcount; | ||
2008 | index = get_u32(&buf[3]); | ||
2009 | trackcount = playlist_amount(); | ||
2010 | if (index >= trackcount) | ||
2011 | { | ||
2012 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2013 | break; | ||
2014 | } | ||
2015 | audio_pause(); | ||
2016 | if(global_settings.playlist_shuffle) | ||
2017 | { | ||
2018 | playlist_randomise(NULL, current_tick, true); | ||
2019 | } | ||
2020 | else | ||
2021 | { | ||
2022 | playlist_sort(NULL, true); | ||
2023 | } | ||
2024 | audio_skip(index - playlist_next(0)); | ||
2025 | if (!paused) | ||
2026 | audio_resume(); | ||
2027 | /* respond with cmd ok packet */ | ||
2028 | cmd_ok(cmd); | ||
2029 | break; | ||
2030 | } | ||
2031 | case 0x0029: /* PlayControl */ | ||
2032 | { | ||
2033 | /* The following is the description for the Apple Firmware | ||
2034 | * | ||
2035 | * Sets the new play state of the iPod. The play control command | ||
2036 | * codes are shown below. In response, the iPod sends an ACK | ||
2037 | * telegram indicating the status of the command. | ||
2038 | * | ||
2039 | * Note: If a remote device requests the Next or Previous Chapter | ||
2040 | * for a track that does not contain chapters, the iPod returns a | ||
2041 | * command failed ACK. | ||
2042 | * | ||
2043 | * Byte Value Meaning | ||
2044 | * 0 0xFF Sync byte (required only for UART serial) | ||
2045 | * 1 0x55 Start of telegram | ||
2046 | * 2 0x04 Telegram payload length | ||
2047 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2048 | * 4 0x00 Command ID (bits 15:8) | ||
2049 | * 5 0x29 Command ID (bits 7:0) | ||
2050 | * 6 0xNN Play control command code. | ||
2051 | * 7 0xNN Telegram payload checksum byte | ||
2052 | * | ||
2053 | * Play Control Command Code Protocol version | ||
2054 | * Reserved 0x00 N/A | ||
2055 | * Toggle Play/Pause 0x01 1.00 | ||
2056 | * Stop 0x02 1.00 | ||
2057 | * Next Track 0x03 1.00 | ||
2058 | * Previous Track 0x04 1.00 | ||
2059 | * StartFF 0x05 1.00 | ||
2060 | * StartRew 0x06 1.00 | ||
2061 | * EndFFRew 0x07 1.00 | ||
2062 | * NextChapter 0x08 1.06 | ||
2063 | * Previous Chapter 0x09 1.06 | ||
2064 | * Reserved 0x0A - 0xFF | ||
2065 | * | ||
2066 | */ | ||
2067 | switch(buf[3]) | ||
2068 | { | ||
2069 | case 0x01: /* play/pause */ | ||
2070 | iap_remotebtn = BUTTON_RC_PLAY; | ||
2071 | iap_repeatbtn = 2; | ||
2072 | break; | ||
2073 | case 0x02: /* stop */ | ||
2074 | iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT; | ||
2075 | iap_repeatbtn = 2; | ||
2076 | break; | ||
2077 | case 0x03: /* skip++ */ | ||
2078 | iap_remotebtn = BUTTON_RC_RIGHT; | ||
2079 | iap_repeatbtn = 2; | ||
2080 | break; | ||
2081 | case 0x04: /* skip-- */ | ||
2082 | iap_remotebtn = BUTTON_RC_LEFT; | ||
2083 | iap_repeatbtn = 2; | ||
2084 | break; | ||
2085 | case 0x05: /* ffwd */ | ||
2086 | iap_remotebtn = BUTTON_RC_RIGHT; | ||
2087 | break; | ||
2088 | case 0x06: /* frwd */ | ||
2089 | iap_remotebtn = BUTTON_RC_LEFT; | ||
2090 | break; | ||
2091 | case 0x07: /* end ffwd/frwd */ | ||
2092 | iap_remotebtn = BUTTON_NONE; | ||
2093 | break; | ||
2094 | } | ||
2095 | /* respond with cmd ok packet */ | ||
2096 | cmd_ok(cmd); | ||
2097 | break; | ||
2098 | } | ||
2099 | case 0x002A: /* GetTrackArtworkTimes */ | ||
2100 | /* The following is the description for the Apple Firmware | ||
2101 | * | ||
2102 | * The device sends this command to the iPod to request the list of | ||
2103 | * artwork time locations for a track. A 4-byte track Index | ||
2104 | * specifies which track from the Playback Engine is to be selected. | ||
2105 | * A 2-byte formatID indicates which type of artwork is desired. | ||
2106 | * The 2-byte artworkIndex specifies at which index to begin | ||
2107 | * searching for artwork. A value of 0 indicates that the iPod | ||
2108 | * should start with the first available artwork. | ||
2109 | * The 2-byte artworkCount specifies the maximum number of times | ||
2110 | * (artwork locations) to be returned. A value of -1 (0xFFFF) | ||
2111 | * indicates that there is no preferred limit. Note that podcasts | ||
2112 | * may have a large number of associated images. | ||
2113 | * | ||
2114 | * Byte Value Comment | ||
2115 | * 0 0xFF Sync byte (required only for UART serial) | ||
2116 | * 1 0x55 Start of telegram | ||
2117 | * 2 0x0D Length of packet | ||
2118 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2119 | * 4 0x00 Command ID (bits 15:8) | ||
2120 | * 5 0x2A Command ID (bits 7:0) | ||
2121 | * 6 0xNN trackIndex(31:24) | ||
2122 | * 7 0xNN trackIndex(23:16) | ||
2123 | * 8 0xNN trackIndex (15:8) | ||
2124 | * 9 0xNN trackIndex (7:0) | ||
2125 | * 10 0xNN formatID (15:8) | ||
2126 | * 11 0xNN formatID (7:0) | ||
2127 | * 12 0xNN artworkIndex (15:8) | ||
2128 | * 13 0xNN artworkIndex (7:0) | ||
2129 | * 14 0xNN artworkCount (15:8) | ||
2130 | * 15 0xNN artworkCount (7:0) | ||
2131 | * 16 0xNN Checksum | ||
2132 | * | ||
2133 | */ | ||
2134 | { | ||
2135 | unsigned char data[] = {0x04, 0x00, 0x2B, | ||
2136 | 0x00, 0x00, 0x00, 0x00}; | ||
2137 | iap_send_pkt(data, sizeof(data)); | ||
2138 | break; | ||
2139 | } | ||
2140 | case 0x002B: /* ReturnTrackArtworkTimes. See Above */ | ||
2141 | /* The following is the description for the Apple Firmware | ||
2142 | * | ||
2143 | * The iPod sends this command to the device to return the list of | ||
2144 | * artwork times for a given track. The iPod returns zero or more | ||
2145 | * 4-byte times, one for each piece of artwork associated with the | ||
2146 | * track and format specified by GetTrackArtworkTimes. | ||
2147 | * The number of records returned will be no greater than the number | ||
2148 | * specified in the GetTrackArtworkTimes command. It may however, be | ||
2149 | * less than requested. This can happen if there are fewer pieces of | ||
2150 | * artwork available than were requested, or if the iPod is unable | ||
2151 | * to place the full number in a single packet. Check the number of | ||
2152 | * records returned against the results of | ||
2153 | * RetIndexedPlayingTrackInfo with infoType 7 to ensure that all | ||
2154 | * artwork has been received. | ||
2155 | * | ||
2156 | * Byte Value Comment | ||
2157 | * 0 0xFF Sync byte (required only for UART serial) | ||
2158 | * 1 0x55 Start of telegram | ||
2159 | * 2 0xNN Length of packet | ||
2160 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2161 | * 4 0x00 Command ID (bits 15:8) | ||
2162 | * 5 0x2B Command ID (bits 7:0) | ||
2163 | * 6 0xNN time offset from track start in ms (31:24) | ||
2164 | * 7 0xNN time offset from track start in ms (23:16) | ||
2165 | * 8 0xNN time offset from track start in ms (15:8) | ||
2166 | * 9 0xNN time offset from track start in ms (7:0) | ||
2167 | * Preceding 4 bytes may be repeated NN times | ||
2168 | * NN 0xNN Checksum | ||
2169 | * | ||
2170 | */ | ||
2171 | { | ||
2172 | /* We should NEVER receive this command so ERROR if we do */ | ||
2173 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2174 | break; | ||
2175 | } | ||
2176 | case 0x002C: /* GetShuffle */ | ||
2177 | { | ||
2178 | /* The following is the description for the Apple Firmware | ||
2179 | * | ||
2180 | * Requests the current state of the iPod shuffle setting. The iPod | ||
2181 | * responds with the Command0x002D: ReturnShuffle telegram. | ||
2182 | * | ||
2183 | * Byte Value Meaning | ||
2184 | * 0 0xFF Sync byte (required only for UART serial) | ||
2185 | * 1 0x55 Start of telegram | ||
2186 | * 2 0x03 Telegram payload length | ||
2187 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2188 | * 4 0x00 Command ID (bits 15:8) | ||
2189 | * 5 0x2C Command ID (bits 7:0) | ||
2190 | * 6 0xCD Telegram payload checksum byte | ||
2191 | * | ||
2192 | */ | ||
2193 | unsigned char data[] = {0x04, 0x00, 0x2D, | ||
2194 | 0x00}; | ||
2195 | data[3] = global_settings.playlist_shuffle ? 1 : 0; | ||
2196 | iap_send_pkt(data, sizeof(data)); | ||
2197 | break; | ||
2198 | } | ||
2199 | case 0x002D: /* ReturnShuffle. See Above */ | ||
2200 | /* The following is the description for the Apple Firmware | ||
2201 | * | ||
2202 | * Returns the current state of the shuffle setting. The iPod sends | ||
2203 | * this telegram in response to the Command 0x002C: GetShuffle | ||
2204 | * telegram from the device. | ||
2205 | * | ||
2206 | * Byte Value Meaning | ||
2207 | * 0 0xFF Sync byte (required only for UART serial) | ||
2208 | * 1 0x55 Start of telegram | ||
2209 | * 2 0x04 Telegram payload length | ||
2210 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2211 | * 4 0x00 Command ID (bits 15:8) | ||
2212 | * 5 0x2D Command ID (bits 7:0) | ||
2213 | * 6 0xNN Shuffle mode. See Below. | ||
2214 | * 7 0xNN Telegram payload checksum byte | ||
2215 | * | ||
2216 | * Possible values of the shufflemode. | ||
2217 | * Value Meaning | ||
2218 | * 0x00 Shuffle off | ||
2219 | * 0x01 Shuffle tracks | ||
2220 | * 0x02 Shuffle albums | ||
2221 | * 0x03 – 0xFF Reserved | ||
2222 | * | ||
2223 | */ | ||
2224 | { | ||
2225 | /* We should NEVER receive this command so ERROR if we do */ | ||
2226 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2227 | break; | ||
2228 | } | ||
2229 | case 0x002E: /* SetShuffle */ | ||
2230 | { | ||
2231 | /* The following is the description for the Apple Firmware | ||
2232 | * | ||
2233 | * Sets the iPod shuffle mode. The iPod shuffle modes are listed | ||
2234 | * below. In response, the iPod sends an ACK telegram with the | ||
2235 | * command status. | ||
2236 | * This telegram has an optional byte, byte 0x07, called the | ||
2237 | * RestoreonExit byte. This byte can be used to restore the | ||
2238 | * original shuffle setting in use when the accessory was attached | ||
2239 | * to the iPod. A non zero value restores the original shuffle | ||
2240 | * setting of the iPod when the accessory is detached. If this byte | ||
2241 | * is zero, the shuffle setting set by the accessory overwrites the | ||
2242 | * original setting and persists after the accessory is detached | ||
2243 | * from the iPod. | ||
2244 | * Accessory engineers should note that the shuffle mode affects | ||
2245 | * items only in the playback engine. The shuffle setting does not | ||
2246 | * affect the order of tracks in the database engine, so calling | ||
2247 | * Command 0x001A: RetrieveCategorizedDatabaseRecords on a database | ||
2248 | * selection with the shuffle mode set returns a list of unshuffled | ||
2249 | * tracks. To get the shuffled playlist, an accessory must query the | ||
2250 | * playback engine by calling Command 0x0020 | ||
2251 | * GetIndexedPlayingTrackTitle. | ||
2252 | * Shuffling tracks does not affect the track index, just the track | ||
2253 | * at that index. If an unshuffled track at playback index 1 is | ||
2254 | * shuffled, a new track is placed into index 1. The playback | ||
2255 | * indexes themselves are not shuffled. | ||
2256 | * When shuffle mode is enabled, tracks that are marked 'skip when | ||
2257 | * shuffling' are filtered from the database selection. This affects | ||
2258 | * all audiobooks and all tracks that the user has marked in iTunes. | ||
2259 | * It also affects all podcasts unless their default 'skip when | ||
2260 | * shuffling' markings have been deliberately removed. To apply the | ||
2261 | * filter to the playback engine, the accessory should send | ||
2262 | * Command 0x0017: SelectDBRecord or | ||
2263 | * Command 0x0028: PlayCurrentSelection after enabling shuffle mode. | ||
2264 | * | ||
2265 | * Note: Accessory developers are encouraged to always use the | ||
2266 | * Restore on Exit byte with a nonzero value to restore any settings | ||
2267 | * modified by the accessory upon detach. | ||
2268 | * | ||
2269 | * SetShuffle telegram with Restore on Exit byte | ||
2270 | * Byte Value Meaning | ||
2271 | * 0 0xFF Sync byte (required only for UART serial) | ||
2272 | * 1 0x55 Start of telegram | ||
2273 | * 2 0x05 Telegram payload length | ||
2274 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2275 | * 4 0x00 Command ID (bits 15:8) | ||
2276 | * 5 0x2E Command ID (bits 7:0) | ||
2277 | * 6 0xNN New shuffle mode. See above . | ||
2278 | * 7 0xNN Restore on Exit byte. If 1, the orig setting is | ||
2279 | * restored on detach; if 0, the newsetting persists | ||
2280 | * after accessory detach. | ||
2281 | * 8 0xNN Telegram payload checksum byte | ||
2282 | * | ||
2283 | * SetShuffle setting persistent after the accessory detach. | ||
2284 | * Byte Value Meaning | ||
2285 | * 0 0xFF Sync byte (required only for UART serial) | ||
2286 | * 1 0x55 Start of telegram | ||
2287 | * 2 0x04 Telegram payload length | ||
2288 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2289 | * 4 0x00 Command ID (bits 15:8) | ||
2290 | * 5 0x2E Command ID (bits 7:0) | ||
2291 | * 6 0xNN New shuffle mode. See above. | ||
2292 | * 7 0xNN Telegram payload checksum byte | ||
2293 | * | ||
2294 | */ | ||
2295 | if(buf[3] && !global_settings.playlist_shuffle) | ||
2296 | { | ||
2297 | global_settings.playlist_shuffle = 1; | ||
2298 | settings_save(); | ||
2299 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
2300 | playlist_randomise(NULL, current_tick, true); | ||
2301 | } | ||
2302 | else if(!buf[3] && global_settings.playlist_shuffle) | ||
2303 | { | ||
2304 | global_settings.playlist_shuffle = 0; | ||
2305 | settings_save(); | ||
2306 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
2307 | playlist_sort(NULL, true); | ||
2308 | } | ||
2309 | |||
2310 | /* respond with cmd ok packet */ | ||
2311 | cmd_ok(cmd); | ||
2312 | break; | ||
2313 | } | ||
2314 | case 0x002F: /* GetRepeat */ | ||
2315 | { | ||
2316 | /* The following is the description for the Apple Firmware | ||
2317 | * | ||
2318 | * Requests the track repeat state of the iPod. In response, the | ||
2319 | * iPod sends a Command 0x0030: ReturnRepeat telegram | ||
2320 | * to the device. | ||
2321 | * | ||
2322 | * Byte Value Meaning | ||
2323 | * 0 0xFF Sync byte (required only for UART serial) | ||
2324 | * 1 0x55 Start of telegram | ||
2325 | * 2 0x03 Telegram payload length | ||
2326 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2327 | * 4 0x00 Command ID (bits 15:8) | ||
2328 | * 5 0x2F Command ID (bits 7:0) | ||
2329 | * 6 0xCA Telegram payload checksum byte | ||
2330 | * | ||
2331 | */ | ||
2332 | unsigned char data[] = {0x04, 0x00, 0x30, 0x00}; | ||
2333 | if(global_settings.repeat_mode == REPEAT_OFF) | ||
2334 | data[3] = 0; | ||
2335 | else if(global_settings.repeat_mode == REPEAT_ONE) | ||
2336 | data[3] = 1; | ||
2337 | else | ||
2338 | data[3] = 2; | ||
2339 | iap_send_pkt(data, sizeof(data)); | ||
2340 | break; | ||
2341 | } | ||
2342 | case 0x0030: /* ReturnRepeat. See Above */ | ||
2343 | /* The following is the description for the Apple Firmware | ||
2344 | * | ||
2345 | * Returns the current iPod track repeat state to the device. | ||
2346 | * The iPod sends this telegram in response to the Command | ||
2347 | * 0x002F:GetRepeat command. | ||
2348 | * | ||
2349 | * Byte Value Meaning | ||
2350 | * 0 0xFF Sync byte (required only for UART serial) | ||
2351 | * 1 0x55 Start of telegram | ||
2352 | * 2 0x04 Telegram payload length | ||
2353 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2354 | * 4 0x00 Command ID (bits 15:8) | ||
2355 | * 5 0x30 Command ID (bits 7:0) | ||
2356 | * 6 0xNN Repeat state. See Below. | ||
2357 | * 7 0xNN Telegram payload checksum byte | ||
2358 | * | ||
2359 | * Repeat state values | ||
2360 | * Value Meaning | ||
2361 | * 0x00 Repeat off | ||
2362 | * 0x01 Repeat one track | ||
2363 | * 0x02 Repeat all tracks | ||
2364 | * 0x03 - 0xFF Reserved | ||
2365 | * | ||
2366 | */ | ||
2367 | { | ||
2368 | /* We should NEVER receive this command so ERROR if we do */ | ||
2369 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2370 | break; | ||
2371 | } | ||
2372 | case 0x0031: /* SetRepeat */ | ||
2373 | { | ||
2374 | /* The following is the description for the Apple Firmware | ||
2375 | * | ||
2376 | * Sets the repeat state of the iPod. The iPod track repeat modes | ||
2377 | * are listed above. In response, the iPod sends an ACK telegram | ||
2378 | * with the command status. | ||
2379 | * | ||
2380 | * This telegram has an optional byte, byte 0x07, called the | ||
2381 | * RestoreonExitbyte. This byte can be used to restore the original | ||
2382 | * repeat setting in use when the accessory was attached to the | ||
2383 | * iPod. A nonzero value restores the original repeat setting of | ||
2384 | * the iPod when the accessory is detached. If this byte is zero, | ||
2385 | * the repeat setting set by the accessory overwrites the original | ||
2386 | * setting and persists after the accessory is detached from the | ||
2387 | * iPod. | ||
2388 | * | ||
2389 | * Note: Accessory developers are encouraged to always use the | ||
2390 | * Restore on Exit byte with a nonzero value to restore any | ||
2391 | * settings modified by the accessory upon detach. | ||
2392 | * | ||
2393 | * SetRepeat telegram with Restore on Exit byte | ||
2394 | * Byte Value Meaning | ||
2395 | * 0 0xFF Sync byte (required only for UART serial) | ||
2396 | * 1 0x55 Start of telegram | ||
2397 | * 2 0x05 Telegram payload length | ||
2398 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2399 | * 4 0x00 Command ID (bits 15:8) | ||
2400 | * 5 0x31 Command ID (bits 7:0) | ||
2401 | * 6 0xNN New repeat state. See above. | ||
2402 | * 7 0xNN Restore on Exit byte. If 1, the original setting is | ||
2403 | * restored on detach; if 0, the newsetting persists | ||
2404 | * after accessory detach. | ||
2405 | * 8 0xNN Telegram payload checksum byte | ||
2406 | * | ||
2407 | * SetRepeat setting persistent after the accessory detach. | ||
2408 | * Byte Value Meaning | ||
2409 | * 0 0xFF Sync byte (required only for UART serial) | ||
2410 | * 1 0x55 Start of telegram | ||
2411 | * 2 0x04 Telegram payload length | ||
2412 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2413 | * 4 0x00 Command ID (bits 15:8) | ||
2414 | * 5 0x31 Command ID (bits 7:0) | ||
2415 | * 6 0xNN New repeat state. See above. | ||
2416 | * 7 0xNN Telegram payload checksum byte | ||
2417 | * | ||
2418 | */ | ||
2419 | int oldmode = global_settings.repeat_mode; | ||
2420 | if (buf[3] == 0) | ||
2421 | global_settings.repeat_mode = REPEAT_OFF; | ||
2422 | else if (buf[3] == 1) | ||
2423 | global_settings.repeat_mode = REPEAT_ONE; | ||
2424 | else if (buf[3] == 2) | ||
2425 | global_settings.repeat_mode = REPEAT_ALL; | ||
2426 | |||
2427 | if (oldmode != global_settings.repeat_mode) | ||
2428 | { | ||
2429 | settings_save(); | ||
2430 | if (audio_status() & AUDIO_STATUS_PLAY) | ||
2431 | audio_flush_and_reload_tracks(); | ||
2432 | } | ||
2433 | /* respond with cmd ok packet */ | ||
2434 | cmd_ok(cmd); | ||
2435 | break; | ||
2436 | } | ||
2437 | case 0x0032: /* SetDisplayImage */ | ||
2438 | { | ||
2439 | /* The following is the description for the Apple Firmware | ||
2440 | * This sets a bitmap image | ||
2441 | * that is displayed on the iPod display when connected to the | ||
2442 | * device. It replaces the default checkmark bitmap image that is | ||
2443 | * displayed when iPod is connected to an external device | ||
2444 | * | ||
2445 | * Sets a bitmap image that is shown on the iPod display when it is | ||
2446 | * connected to the device. The intent is to allow third party | ||
2447 | * branding when the iPod is communicating with an external device. | ||
2448 | * An image downloaded using this mechanism replaces the default | ||
2449 | * checkmark bitmap image that is displayed when iPod is connected | ||
2450 | * to an external device. The new bitmap is retained in RAM for as | ||
2451 | * long as the iPod remains powered. After a system reset or deep | ||
2452 | * sleep state, the new bitmap is lost and the checkmark image | ||
2453 | * restored as the default when the iPod next enters Extended | ||
2454 | * Interface mode after power-up. | ||
2455 | * Before setting a monochrome display image, the device can send | ||
2456 | * the Command 0x0033: GetMonoDisplayImageLimits telegram to obtain | ||
2457 | * the current iPod display width, height and pixel format.The | ||
2458 | * monochrome display information returned in the Command 0x0034: | ||
2459 | * ReturnMonoDisplayImageLimits telegram can be useful to the | ||
2460 | * device in deciding which type of display image format is suitable | ||
2461 | * for downloading to the iPod. | ||
2462 | * On iPods withcolor displays, devices can send the Command 0x0039: | ||
2463 | * GetColorDisplayImageLimits telegram to obtain the iPod color | ||
2464 | * display width,height,and pixel formats. The color display | ||
2465 | * information is returned in the Command 0x003A: | ||
2466 | * ReturnColorDisplayImageLimits” telegram. | ||
2467 | * To set a display image, the device must successfully send | ||
2468 | * SetDisplayImage descriptor and data telegrams to the iPod. The | ||
2469 | * SetDisplayImage descriptor telegram (telegram index 0x0000) must | ||
2470 | * be sent first, as it gives the iPod a description of the image | ||
2471 | * to be downloaded. This telegram is shown in below. The image | ||
2472 | * descriptor telegram includes image pixel format, image width and | ||
2473 | * height, and display row size (stride) in bytes. Optionally, the | ||
2474 | * descriptor telegram may also contain the beginning data of the | ||
2475 | * display image, as long as it remains within the maximum length | ||
2476 | * limits of the telegram. | ||
2477 | * Following the descriptor telegram, the SetDisplayImage data | ||
2478 | * telegrams (telegram index 0x0001 - 0xNNNN) should be sent using | ||
2479 | * sequential telegram indices until the entire image has been sent | ||
2480 | * to the iPod. | ||
2481 | * | ||
2482 | * Note: The SetDisplayImage telegram payload length is limited to | ||
2483 | * 500 bytes. This telegram length limit and the size and format of | ||
2484 | * the display image generally determine the minimum number of | ||
2485 | * telegrams that are required to set a display image. | ||
2486 | * | ||
2487 | * Note: Starting with the second generation iPod nano with version | ||
2488 | * 1.1.2 firmware, the use of the SetDisplayImage command is | ||
2489 | * limited to once every 15 seconds over USB transport. The iPod | ||
2490 | * classic and iPod 3G nano apply this restriction to both USB and | ||
2491 | * UART transports. Calls made to SetDisplayImage more frequently | ||
2492 | * than every 15 seconds will return a successful ACK command, but | ||
2493 | * the bitmap will not be displayed on the iPod’s screen. Hence use | ||
2494 | * of the SetDisplayImage command should be limited to drawing one | ||
2495 | * bitmap image per accessory connect. The iPod touch will accept | ||
2496 | * the SetDisplayImage command but will not draw it on the iPod’s | ||
2497 | * screen. | ||
2498 | * | ||
2499 | * Below shows the format of a descriptor telegram. This example | ||
2500 | * assumes the display image descriptor data exceeds the small | ||
2501 | * telegram payload capacity; a large telegram format is shown. | ||
2502 | * | ||
2503 | * SetDisplayImage descriptor telegram (telegram index = 0x0000) | ||
2504 | * Byte Value Meaning | ||
2505 | * 0 0xFF Sync byte (required only for UART serial) | ||
2506 | * 1 0x55 Start of telegram | ||
2507 | * 2 0x00 Telegram payload marker (large format) | ||
2508 | * 3 0xNN Large telegram payload length (bits 15:8) | ||
2509 | * 4 0xNN Large telegram payload length (bits 7:0) | ||
2510 | * 5 0x04 Lingo ID: Extended Interface lingo | ||
2511 | * 6 0x00 Command ID (bits 15:8) | ||
2512 | * 7 0x32 Command ID (bits 7:0) | ||
2513 | * 8 0x00 Descriptor telegram index (bits 15:8). These fields | ||
2514 | * uniquely identify each packet in the SetDisplayImage | ||
2515 | * transaction. The first telegram is the Descriptor | ||
2516 | * telegram and always starts with an index of 0x0000. | ||
2517 | * 9 0x00 Descriptor telegram index (bits 7:0) | ||
2518 | * 10 0xNN Display pixel format code. See Below. | ||
2519 | * 11 0xNN Imagewidth in pixels (bits 15:8). The number of | ||
2520 | * pixels, from left to right, per row. | ||
2521 | * 12 0xNN Image width in pixels (bits 7:0) | ||
2522 | * 13 0xNN Image height in pixels (bits 15:8). The number of | ||
2523 | * rows, from top to bottom, in the image. | ||
2524 | * 14 0xNN Image height in pixels (bits 7:0) | ||
2525 | * 15 0xNN Row size (stride) in bytes (bits 31:24). The number of | ||
2526 | * bytes representing one row of pixels. Each row is | ||
2527 | * zero-padded to end on a 32-bit boundary. The | ||
2528 | * cumulative size, in bytes, of the image data, | ||
2529 | * transferred across all telegrams in this transaction | ||
2530 | * is effectively (Row Size * Image Height). | ||
2531 | * 16 0xNN Row size (stride) in bytes (bits 23:16) | ||
2532 | * 17 0xNN Row size (stride) in bytes (bits 15:8) | ||
2533 | * 18 0xNN Row size (stride) in bytes (bits 7:0) | ||
2534 | * 19–N 0xNN Display image pixel data | ||
2535 | * NN 0xNN Telegram payload checksum byte | ||
2536 | * | ||
2537 | * SetDisplayImage data telegram (telegram index = 0x0001 - 0xNNNN) | ||
2538 | * Byte Value Meaning | ||
2539 | * 0 0xFF Sync byte (required only for UART serial) | ||
2540 | * 1 0x55 Start of telegram | ||
2541 | * 2 0x00 Telegram payload marker (large format) | ||
2542 | * 3 0xNN Large telegram payload length (bits 15:8) | ||
2543 | * 4 0xNN Large telegram payload length (bits 7:0) | ||
2544 | * 5 0x04 Lingo ID: Extended Interface lingo | ||
2545 | * 6 0x00 Command ID (bits 15:8) | ||
2546 | * 7 0x32 Command ID (bits 7:0) | ||
2547 | * 8 0xNN Descriptor telegram index (bits 15:8). These fields | ||
2548 | * uniquely identify each packet in the SetDisplayImage | ||
2549 | * transaction. The first telegram is the descriptor | ||
2550 | * telegram, shown in Table 6-68 (page 97). The | ||
2551 | * remaining n-1 telegrams are simply data telegrams, | ||
2552 | * where n is determined by the size of the image. | ||
2553 | * 9 0xNN Descriptor telegram index (bits 7:0) | ||
2554 | * 10–N 0xNN Display image pixel data | ||
2555 | * NN 0xNN Telegram payload checksum byte | ||
2556 | * | ||
2557 | * Note: A known issue causes SetDisplayImage data telegram | ||
2558 | * lengths less than 11 bytes to return a bad parameter error (0x04) | ||
2559 | * ACK on 3G iPods. | ||
2560 | * | ||
2561 | * The iPod display is oriented as a rectangular grid of pixels. In | ||
2562 | * the horizontal direction (x-coordinate), the pixel columns are | ||
2563 | * numbered, left to right, from 0 to Cmax. In the vertical | ||
2564 | * direction (y-coordinate), the pixel rows are numbered, top to | ||
2565 | * bottom, from 0 to Rmax. Therefore, an (x,y) coordinate of (0,0) | ||
2566 | * represents the upper-leftmost pixel on the display and | ||
2567 | * (Cmax,Rmax) represents the lower-rightmost pixel on the display. | ||
2568 | * A portion of the iPod display pixel layout is shown below, where | ||
2569 | * x is the column number, y is the row number, and (Cmax,Rmax) | ||
2570 | * represents the maximum row and column numbers. | ||
2571 | * | ||
2572 | * Pixel layout | ||
2573 | * x | ||
2574 | * y 0,0 1,0 2,0 3,0 4,0 5,0 6,0 7,0 - Cmax,0 | ||
2575 | * 0,1 1,1 2,1 3,1 4,1 5,1 6,1 7,1 - Cmax,1 | ||
2576 | * 0,2 1,2 2,2 3,2 4,2 5,2 6,2 7,2 - Cmax,2 | ||
2577 | * 0,3 1,3 2,3 3,3 4,3 5,3 6,3 7,3 - Cmax,3 | ||
2578 | * 0,4 1,4 2,4 3,4 4,4 5,4 6,4 7,4 - Cmax,4 | ||
2579 | * 0,5 1,5 2,5 3,5 4,5 5,5 6,5 7,5 - Cmax,5 | ||
2580 | * 0,6 1,6 2,6 3,6 4,6 5,6 6,6 7,6 - Cmax,6 | ||
2581 | * 0,7 1,7 2,7 3,7 4,7 5,7 6,7 7,7 - Cmax,7 | ||
2582 | * " " " " " " " " " " | ||
2583 | * 0, 1, 2, 3, 4, 5, 6, 7, - Cmax, | ||
2584 | * RmaxRmaxRmaxRmaxRmaxRmaxRmaxRmax Rmax | ||
2585 | * | ||
2586 | * Display pixel format codes | ||
2587 | * Display pixel format Code Protocol version | ||
2588 | * Reserved 0x00 N/A | ||
2589 | * Monochrome, 2 bits per pixel 0x01 1.01 | ||
2590 | * RGB 565 color, little-endian, 16 bpp 0x02 1.09 | ||
2591 | * RGB 565 color, big-endian, 16 bpp 0x03 1.09 | ||
2592 | * Reserved 0x04-0xFF N/A | ||
2593 | * | ||
2594 | * iPods with color screens support all three image formats. All | ||
2595 | * other iPods support only display pixel format 0x01 (monochrome, | ||
2596 | * 2 bpp). | ||
2597 | * | ||
2598 | * Display Pixel Format 0x01 | ||
2599 | * Display pixel format 0x01 (monochrome, 2 bits per pixel) is the | ||
2600 | * pixel format supported by all iPods. Each pixel consists of 2 | ||
2601 | * bits that control the pixel intensity. The pixel intensities and | ||
2602 | * associated binary codes are listed below. | ||
2603 | * | ||
2604 | * 2 bpp monochrome pixel intensities | ||
2605 | * Pixel Intensity Binary Code | ||
2606 | * Pixel off (not visible) 00b | ||
2607 | * Pixel on 25% (light grey) 01b | ||
2608 | * Pixel on 50% (dark grey) 10b | ||
2609 | * Pixel on 100% (black) 11b | ||
2610 | * | ||
2611 | * Each byte of image data contains four packed pixels. The pixel | ||
2612 | * ordering within bytes and the byte ordering within 32 bits is | ||
2613 | * shown below. | ||
2614 | * | ||
2615 | * Image Data Byte 0x0000 | ||
2616 | * Pixel0 Pixel1 Pixel2 Pixel3 | ||
2617 | * 7 6 5 4 3 2 1 0 | ||
2618 | * | ||
2619 | * Image Data Byte 0x0001 | ||
2620 | * Pixel4 Pixel5 Pixel6 Pixel7 | ||
2621 | * 7 6 5 4 3 2 1 0 | ||
2622 | * | ||
2623 | * Image Data Byte 0x0002 | ||
2624 | * Pixel8 Pixel9 Pixel10 Pixel11 | ||
2625 | * 7 6 5 4 3 2 1 0 | ||
2626 | * | ||
2627 | * Image Data Byte 0x0003 | ||
2628 | * Pixel12 Pixel13 Pixel14 Pixel15 | ||
2629 | * 7 6 5 4 3 2 1 0 | ||
2630 | * | ||
2631 | * Image Data Byte 0xNNNN | ||
2632 | * PixelN PixelN+1 PixelN+2 PixelN+3 | ||
2633 | * 7 6 5 4 3 2 1 0 | ||
2634 | * | ||
2635 | * Display Pixel Formats 0x02 and 0x03 | ||
2636 | * Display pixel format 0x02 (RGB 565, little-endian) and display | ||
2637 | * pixel format 0x03 (RGB 565, big-endian) are available for use | ||
2638 | * in all iPods with color screens. Each pixel consists of 16 bits | ||
2639 | * that control the pixel intensity for the colors red, green, and | ||
2640 | * blue. | ||
2641 | * It takes two bytes to represent a single pixel. Red is | ||
2642 | * represented by 5 bits, green is represented by 6 bits, and blue | ||
2643 | * by the final 5 bits. A 32-bit sequence represents 2 pixels. The | ||
2644 | * pixel ordering within bytes and the byte ordering within 32 bits | ||
2645 | * for display format 0x02 (RGB 565, little-endian) is shown below. | ||
2646 | * | ||
2647 | * Image Data Byte 0x0000 | ||
2648 | * Pixel 0, lower 3 bits of green Pixel 0, all 5 bits of blue | ||
2649 | * 7 6 5 4 3 2 1 0 | ||
2650 | * Image Data Byte 0x0001 | ||
2651 | * Pixel 0, all 5 bits of red Pixel 0,upper 3 bits of green | ||
2652 | * 7 6 5 4 3 2 1 0 | ||
2653 | * | ||
2654 | * Image Data Byte 0x0002 | ||
2655 | * Pixel 1, lower 3 bits of green Pixel 1, all 5 bits of blue | ||
2656 | * 7 6 5 4 3 2 1 0 | ||
2657 | * | ||
2658 | * Image Data Byte 0x0003 | ||
2659 | * Pixel 1, all 5 bits of red Pixel 1, upper 3 bits of green | ||
2660 | * 7 6 5 4 3 2 1 0 | ||
2661 | * | ||
2662 | * The format for display pixel format 0x03 (RGB 565, big-endian, 16 | ||
2663 | * bpp) is almost identical, with the exception that bytes 0 and 1 | ||
2664 | * are swapped and bytes 2 and 3 are swapped. | ||
2665 | * | ||
2666 | */ | ||
2667 | cmd_ok(cmd); | ||
2668 | break; | ||
2669 | } | ||
2670 | case 0x0033: /* GetMonoDisplayImageLimits */ | ||
2671 | |||
2672 | { | ||
2673 | /* The following is the description for the Apple Firmware | ||
2674 | * | ||
2675 | * Requests the limiting characteristics of the monochrome image | ||
2676 | * that can be sent to the iPod for display while it is connected | ||
2677 | * to the device. It can be used to determine the display pixel | ||
2678 | * format and maximum width and height of a monochrome image to be | ||
2679 | * set using the Command 0x0032: SetDisplayImage telegram. In | ||
2680 | * response, the iPod sends a Command 0x0034: | ||
2681 | * ReturnMonoDisplayImageLimits telegram to the device with the | ||
2682 | * requested display information. The GetMonoDisplayImageLimits | ||
2683 | * command is supported by iPods with either monochrome or color | ||
2684 | * displays. To obtain color display image limits, use Command | ||
2685 | * 0x0039: GetColorDisplayImageLimits. | ||
2686 | * | ||
2687 | * Byte Value Meaning | ||
2688 | * 0 0xFF Sync byte (required only for UART serial) | ||
2689 | * 1 0x55 Start of telegram | ||
2690 | * 2 0x03 Telegram payload length | ||
2691 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2692 | * 4 0x00 Command ID (bits 15:8) | ||
2693 | * 5 0x33 Command ID (bits 7:0) | ||
2694 | * 6 0xC6 Telegram payload checksum byte | ||
2695 | * | ||
2696 | */ | ||
2697 | unsigned char data[] = {0x04, 0x00, 0x34, | ||
2698 | LCD_WIDTH >> 8, LCD_WIDTH & 0xff, | ||
2699 | LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff, | ||
2700 | 0x01}; | ||
2701 | iap_send_pkt(data, sizeof(data)); | ||
2702 | break; | ||
2703 | } | ||
2704 | case 0x0034: /* ReturnMonoDisplayImageLimits. See Above*/ | ||
2705 | /* The following is the description for the Apple Firmware | ||
2706 | * | ||
2707 | * Returns the limiting characteristics of the monochrome image that | ||
2708 | * can be sent to the iPod for display while it is connected to the | ||
2709 | * device. The iPod sends this telegram in response to the Command | ||
2710 | * 0x0033: GetMonoDisplayImageLimits telegram. Monochrome display | ||
2711 | * characteristics include maximum image width and height and the | ||
2712 | * display pixel format. | ||
2713 | * | ||
2714 | * Byte Value Meaning | ||
2715 | * 0 0xFF Sync byte (required only for UART serial) | ||
2716 | * 1 0x55 Start of telegram | ||
2717 | * 2 0xNN Telegram payload length | ||
2718 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2719 | * 4 0x00 Command ID (bits 15:8) | ||
2720 | * 5 0x34 Command ID (bits 7:0) | ||
2721 | * 6 0xNN Maximum image width in pixels (bits 15:8) | ||
2722 | * 7 0xNN Maximum image width in pixels (bits 7:0) | ||
2723 | * 8 0xNN Maximum image height in pixels (bits 15:8) | ||
2724 | * 9 0xNN Maximumimage height in pixels (bits 7:0) | ||
2725 | * 10 0xNN Display pixel format (see Table 6-70 (page 99)). | ||
2726 | * 11 0xNN Telegram payload checksum byte | ||
2727 | * | ||
2728 | */ | ||
2729 | { | ||
2730 | /* We should NEVER receive this command so ERROR if we do */ | ||
2731 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2732 | break; | ||
2733 | } | ||
2734 | case 0x0035: /* GetNumPlayingTracks */ | ||
2735 | { | ||
2736 | /* The following is the description for the Apple Firmware | ||
2737 | * | ||
2738 | * Requests the number of tracks in the list of tracks queued to | ||
2739 | * play on the iPod. In response, the iPod sends a Command 0x0036: | ||
2740 | * ReturnNumPlayingTracks telegram with the count of tracks queued | ||
2741 | * to play. | ||
2742 | * | ||
2743 | * Byte Value Meaning | ||
2744 | * 0 0xFF Sync byte (required only for UART serial) | ||
2745 | * 1 0x55 Start of telegram | ||
2746 | * 2 0x03 Telegram payload length | ||
2747 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2748 | * 4 0x00 Command ID (bits 15:8) | ||
2749 | * 5 0x35 Command ID (bits 7:0) | ||
2750 | * 6 0xC4 Telegram payload checksum byte | ||
2751 | * | ||
2752 | */ | ||
2753 | unsigned char data[] = {0x04, 0x00, 0x36, | ||
2754 | 0x00, 0x00, 0x00, 0x00}; | ||
2755 | unsigned long playlist_amt = playlist_amount(); | ||
2756 | put_u32(&data[3], playlist_amt); | ||
2757 | iap_send_pkt(data, sizeof(data)); | ||
2758 | break; | ||
2759 | } | ||
2760 | case 0x0036: /* ReturnNumPlayingTracks. See Above */ | ||
2761 | /* The following is the description for the Apple Firmware | ||
2762 | * | ||
2763 | * Returns the number of tracks in the actual list of tracks queued | ||
2764 | * to play, including the currently playing track (if any). The | ||
2765 | * iPod sends this telegram in response to the Command 0x0035: | ||
2766 | * GetNumPlayingTracks telegram. | ||
2767 | * | ||
2768 | * Byte Value Meaning | ||
2769 | * 0 0xFF Sync byte (required only for UART serial) | ||
2770 | * 1 0x55 Start of telegram | ||
2771 | * 2 0x07 Telegram payload length | ||
2772 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2773 | * 4 0x00 Command ID (bits 15:8) | ||
2774 | * 5 0x36 Command ID (bits 7:0) | ||
2775 | * 6 0xNN Number of tracks playing(bits 31:24) | ||
2776 | * 7 0xNN Number of tracks playing(bits 23:16) | ||
2777 | * 8 0xNN Number of tracks playing (bits 15:8) | ||
2778 | * 9 0xNN Number of tracks playing (bits 7:0) | ||
2779 | * 10 0xNN Telegram payload checksum byte | ||
2780 | * | ||
2781 | */ | ||
2782 | { | ||
2783 | /* We should NEVER receive this command so ERROR if we do */ | ||
2784 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2785 | break; | ||
2786 | } | ||
2787 | case 0x0037: /* SetCurrentPlayingTrack */ | ||
2788 | /* The following is the description for the Apple Firmware | ||
2789 | * | ||
2790 | * Sets the index of the track to play in the Now Playing playlist | ||
2791 | * on the iPod. The index that is specified here is obtained by | ||
2792 | * sending the Command 0x0035: GetNumPlayingTracks and Command | ||
2793 | * 0x001E: GetCurrentPlayingTrackIndex telegrams to obtain the | ||
2794 | * number of playing tracks and the current playing track index, | ||
2795 | * respectively. In response, the iPod sends an ACK telegram | ||
2796 | * indicating the status of the command. | ||
2797 | * | ||
2798 | * Note: The behavior of this command has changed. Before the | ||
2799 | * 2G nano, if this command was sent with the current playing track | ||
2800 | * index the iPod would pause playback momentarily and then resume. | ||
2801 | * Starting with the 2G nano, the iPod restarts playback of the | ||
2802 | * current track from the beginning. Older iPods will not be | ||
2803 | * updated to the new behavior. | ||
2804 | * | ||
2805 | * Note: This command is usable only when the iPod is in a playing | ||
2806 | * or paused state. If the iPod is stopped, this command fails. | ||
2807 | * | ||
2808 | * Byte Value Meaning | ||
2809 | * 0 0xFF Sync byte (required only for UART serial) | ||
2810 | * 1 0x55 Start of telegram | ||
2811 | * 2 0x07 Telegram payload length | ||
2812 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2813 | * 4 0x00 Command ID (bits 15:8) | ||
2814 | * 5 0x37 Command ID (bits 7:0) | ||
2815 | * 6 0xNN New current playing track index (bits 31:24) | ||
2816 | * 7 0xNN New current playing track index (bits 23:16) | ||
2817 | * 8 0xNN New current playing track index (bits 15:8) | ||
2818 | * 9 0xNN New current playing track index (bits 7:0) | ||
2819 | * 10 0xNN Telegram payload checksum byte | ||
2820 | * | ||
2821 | */ | ||
2822 | { | ||
2823 | int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE)); | ||
2824 | long tracknum = get_u32(&buf[3]); | ||
2825 | |||
2826 | audio_pause(); | ||
2827 | audio_skip(tracknum - playlist_next(0)); | ||
2828 | if (!paused) | ||
2829 | audio_resume(); | ||
2830 | |||
2831 | /* respond with cmd ok packet */ | ||
2832 | cmd_ok(cmd); | ||
2833 | break; | ||
2834 | } | ||
2835 | case 0x0038: /* SelectSortDBRecord */ | ||
2836 | /* The following is the description for the Apple Firmware | ||
2837 | * | ||
2838 | * Selects one or more records in the iPod database, based on a | ||
2839 | * category-relative index. For example, selecting category 2 | ||
2840 | * (Artist), record index 1, and sort order 3 (Album) results in a | ||
2841 | * list of selected tracks (records) from the second artist in the | ||
2842 | * artist list, sorted by album name. Selections are additive and | ||
2843 | * limited by the category hierarchy. Subsequent selections are | ||
2844 | * made based on the subset of records resulting from previous | ||
2845 | * selections and not from the entire database. The database | ||
2846 | * category types are shown above. The sort order options and codes | ||
2847 | * are shown below. | ||
2848 | * | ||
2849 | * SelectSortDBRecord telegram | ||
2850 | * Byte Value Meaning | ||
2851 | * 0 0xFF Sync byte (required only for UART serial) | ||
2852 | * 1 0x55 Start of telegram | ||
2853 | * 2 0x09 Telegram payload length | ||
2854 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
2855 | * 4 0x00 Command ID (bits 15:8) | ||
2856 | * 5 0x38 Command ID (bits 7:0) | ||
2857 | * 6 0xNN Database category type. | ||
2858 | * 7 0xNN Category record index (bits 31:24) | ||
2859 | * 8 0xNN Category record index (bits 23:16) | ||
2860 | * 9 0xNN Category record index (bits 15:8) | ||
2861 | * 10 0xNN Category record index (bits 7:0) | ||
2862 | * 11 0xNN Database sort type. | ||
2863 | * 12 0xNN Telegram payload checksum byte | ||
2864 | * | ||
2865 | * Database sort order options | ||
2866 | * Sort Order Code Protocol version | ||
2867 | * Sort by genre 0x00 1.00 | ||
2868 | * Sort by artist 0x01 1.00 | ||
2869 | * Sort by composer 0x02 1.00 | ||
2870 | * Sort by album 0x03 1.00 | ||
2871 | * Sort by name 0x04 1.00 | ||
2872 | * Sort by playlist 0x05 1.00 | ||
2873 | * Sort by release date 0x06 1.08 | ||
2874 | * Reserved 0x07 - 0xFE N/A | ||
2875 | * Use default sort type 0xFF 1.00 | ||
2876 | * | ||
2877 | * The default order of song and audiobook tracks on the iPod is | ||
2878 | * alphabetical by artist, then alphabetical by that artist's | ||
2879 | * albums, then ordered according to the order of the tracks on the | ||
2880 | * album. | ||
2881 | * For podcasts, the default order of episode tracks is reverse | ||
2882 | * chronological. That is, the newest ones are first,then | ||
2883 | * alphabetical by podcast name. | ||
2884 | * The SelectSortDBRecord command can be used to sort all the song | ||
2885 | * and audiobook tracks on the iPod alphabetically as follows: | ||
2886 | * 1. Command 0x0016: ResetDBSelection | ||
2887 | * 2. Command 0x0018: GetNumberCategorizedDBRecords for the Playlist | ||
2888 | * category. | ||
2889 | * 3. SelectSortDBRecord based on the Playlist category, using a | ||
2890 | * record index of 0 to select the All Tracks | ||
2891 | * playlist and the sort by name (0x04) sort | ||
2892 | * order. | ||
2893 | * 4. GetNumberCategorizedDBRecords for the Track category. | ||
2894 | * 5. Command 0x001A :RetrieveCategorizedDatabaseRecords based on | ||
2895 | * the Track category, using a start index of 0 | ||
2896 | * and an end index of the number of records | ||
2897 | * returned by the call to | ||
2898 | * GetNumberCategorizedDBRecords in step 4. | ||
2899 | * | ||
2900 | * The sort order of artist names ignores certain articles such | ||
2901 | * that the artist “The Doors” is sorted under the letter ‘D’ and | ||
2902 | * not ‘T’; this matches the behavior of iTunes. The sort order is | ||
2903 | * different depending on the language setting used in the iPod. | ||
2904 | * The list of ignored articles may change in the future without | ||
2905 | * notice. | ||
2906 | * The SelectDBRecord command may also be used to select a database | ||
2907 | * record with the default sort order. | ||
2908 | * | ||
2909 | * Note: The sort order field is ignored for the Audiobook category. | ||
2910 | * Audiobooks are automatically sorted by track title. The sort | ||
2911 | * order for podcast tracks defaults to release date, with the | ||
2912 | * newest track coming first. | ||
2913 | * | ||
2914 | * Selects one or more records in the iPod database, based on a | ||
2915 | * category-relative index. This appears to be hardcoded hierarchy | ||
2916 | * decided by Apple that the external devices follow. | ||
2917 | * This is as follows for all except podcasts, | ||
2918 | * | ||
2919 | * All (highest level), | ||
2920 | * Playlist, | ||
2921 | * Genre or Media Kind, | ||
2922 | * Artist or Composer, | ||
2923 | * Album, | ||
2924 | * Track or Audiobook (lowest) | ||
2925 | * | ||
2926 | * for Podcasts, the order is | ||
2927 | * | ||
2928 | * All (highest), | ||
2929 | * Podcast, | ||
2930 | * Episode | ||
2931 | * Track (lowest) | ||
2932 | * | ||
2933 | * Categories are | ||
2934 | * | ||
2935 | * 0x00 Reserved | ||
2936 | * 0x01 Playlist | ||
2937 | * 0x02 Artist | ||
2938 | * 0x03 Album | ||
2939 | * 0x04 Genre | ||
2940 | * 0x05 Track | ||
2941 | * 0x06 Composer | ||
2942 | * 0x07 Audiobook | ||
2943 | * 0x08 Podcast | ||
2944 | * 0x09 - 0xff Reserved | ||
2945 | * | ||
2946 | * Sort Order optiona and codes are | ||
2947 | * | ||
2948 | * 0x00 Sort by Genre | ||
2949 | * 0x01 Sort by Artist | ||
2950 | * 0x02 Sort by Composer | ||
2951 | * 0x03 Sort by Album | ||
2952 | * 0x04 Sort by Name (Song Title) | ||
2953 | * 0x05 Sort by Playlist | ||
2954 | * 0x06 Sort by Release Date | ||
2955 | * 0x07 - 0xfe Reserved | ||
2956 | * 0xff Use default Sort Type | ||
2957 | * | ||
2958 | * Packet format (offset in data[]: Description) | ||
2959 | * 0x00: Lingo ID: Extended Interface Protocol Lingo, always 0x04 | ||
2960 | * 0x01-0x02: Command, always 0x0038 | ||
2961 | * 0x03: Database Category Type | ||
2962 | * 0x04-0x07: Category Record Index | ||
2963 | * 0x08 Database Sort Type | ||
2964 | * | ||
2965 | * On Rockbox, if the recordtype is playlist, we load the selected | ||
2966 | * playlist and start playing from the first track. | ||
2967 | * If the recordtype is track, we play that track from the current | ||
2968 | * playlist. | ||
2969 | * On anything else we just play the current track from the current | ||
2970 | * playlist. | ||
2971 | * cur_dbrecord[0] is the recordtype | ||
2972 | * cur_dbrecord[1-4] is the u32 of the record number requested | ||
2973 | * which might be a playlist or a track number depending on | ||
2974 | * the value of cur_dbrecord[0] | ||
2975 | */ | ||
2976 | { | ||
2977 | memcpy(cur_dbrecord, buf + 3, 5); | ||
2978 | |||
2979 | int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE)); | ||
2980 | unsigned int number_of_playlists = nbr_total_playlists(); | ||
2981 | uint32_t index; | ||
2982 | uint32_t trackcount; | ||
2983 | index = get_u32(&cur_dbrecord[1]); | ||
2984 | trackcount = playlist_amount(); | ||
2985 | if ((cur_dbrecord[0] == 0x05) && (index > trackcount)) | ||
2986 | { | ||
2987 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2988 | break; | ||
2989 | } | ||
2990 | if ((cur_dbrecord[0] == 0x01) && (index > (number_of_playlists + 1))) | ||
2991 | { | ||
2992 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
2993 | break; | ||
2994 | } | ||
2995 | switch (cur_dbrecord[0]) | ||
2996 | { | ||
2997 | case 0x01: /* Playlist*/ | ||
2998 | { | ||
2999 | if (index != 0x00) /* 0x00 is the On-The-Go Playlist and | ||
3000 | we do nothing with it */ | ||
3001 | { | ||
3002 | audio_pause(); | ||
3003 | audio_skip(-iap_get_trackindex()); | ||
3004 | playlist_sort(NULL, true); | ||
3005 | last_selected_playlist = index; | ||
3006 | seek_to_playlist(last_selected_playlist); | ||
3007 | } | ||
3008 | break; | ||
3009 | } | ||
3010 | case 0x02: /* Artist Do Nothing */ | ||
3011 | case 0x03: /* Album Do Nothing */ | ||
3012 | case 0x04: /* Genre Do Nothing */ | ||
3013 | case 0x06: /* Composer Do Nothing */ | ||
3014 | break; | ||
3015 | case 0x05: /* Track*/ | ||
3016 | { | ||
3017 | audio_pause(); | ||
3018 | audio_skip(-iap_get_trackindex()); | ||
3019 | playlist_sort(NULL, true); | ||
3020 | audio_skip(index - playlist_next(0)); | ||
3021 | break; | ||
3022 | } | ||
3023 | } | ||
3024 | if (!paused) | ||
3025 | audio_resume(); | ||
3026 | /* respond with cmd ok packet */ | ||
3027 | cmd_ok(cmd); | ||
3028 | break; | ||
3029 | } | ||
3030 | case 0x0039: /* GetColorDisplayImageLimits */ | ||
3031 | /* The following is the description for the Apple Firmware | ||
3032 | * | ||
3033 | * Requests the limiting characteristics of the color image that | ||
3034 | * can be sent to the iPod for display while it is connected to | ||
3035 | * the device. It can be used to determine the display pixel format | ||
3036 | * and maximum width and height of a color image to be set using | ||
3037 | * the Command 0x0032: SetDisplayImage telegram. In response, the | ||
3038 | * iPod sends a Command 0x003A: ReturnColorDisplayImageLimits | ||
3039 | * telegram to the device with the requested display information. | ||
3040 | * This command is supported only by iPods with color displays. | ||
3041 | * | ||
3042 | * Byte Value Meaning | ||
3043 | * 0 0xFF Sync byte (required only for UART serial) | ||
3044 | * 1 0x55 Start of telegram | ||
3045 | * 2 0x03 Telegram payload length | ||
3046 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
3047 | * 4 0x00 Command ID (bits 15:8) | ||
3048 | * 5 0x39 Command ID (bits 7:0) | ||
3049 | * 6 0xC0 Telegram payload checksum byte | ||
3050 | * | ||
3051 | */ | ||
3052 | { | ||
3053 | /* Set the same as the ReturnMonoDisplayImageLimits */ | ||
3054 | unsigned char data[] = {0x04, 0x00, 0x3A, | ||
3055 | LCD_WIDTH >> 8, LCD_WIDTH & 0xff, | ||
3056 | LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff, | ||
3057 | 0x01}; | ||
3058 | iap_send_pkt(data, sizeof(data)); | ||
3059 | break; | ||
3060 | } | ||
3061 | case 0x003A: /* ReturnColorDisplayImageLimits See Above */ | ||
3062 | /* The following is the description for the Apple Firmware | ||
3063 | * | ||
3064 | * Returns the limiting characteristics of the color image that can | ||
3065 | * be sent to the iPod for display while it is connected to the | ||
3066 | * device. The iPod sends this telegram in response to the Command | ||
3067 | * 0x0039: GetColorDisplayImageLimits telegram. Display | ||
3068 | * characteristics include maximum image width and height and the | ||
3069 | * display pixel format. | ||
3070 | * | ||
3071 | * Note: If the iPod supports multiple display image formats, a five | ||
3072 | * byte block of additional image width, height, and pixel format | ||
3073 | * information is appended to the payload for each supported display | ||
3074 | * format. The list of supported color display image formats | ||
3075 | * returned by the iPod may change in future software versions. | ||
3076 | * Devices must be able to parse a variable length list of supported | ||
3077 | * color display formats to search for compatible formats. | ||
3078 | * | ||
3079 | * Byte Value Meaning | ||
3080 | * 0 0xFF Sync byte (required only for UART serial) | ||
3081 | * 1 0x55 Start of telegram | ||
3082 | * 2 0xNN Telegram payload length | ||
3083 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
3084 | * 4 0x00 Command ID (bits 15:8) | ||
3085 | * 5 0x3A Command ID (bits 7:0) | ||
3086 | * 6 0xNN Maximum image width in pixels (bits 15:8) | ||
3087 | * 7 0xNN Maximum image width in pixels (bits 7:0) | ||
3088 | * 8 0xNN Maximum image height in pixels (bits 15:8) | ||
3089 | * 9 0xNN Maximum image height in pixels (bits 7:0) | ||
3090 | * 10 0xNN Display pixel format (see Table 6-70 (page 99)). | ||
3091 | * 11-N 0xNN Optional display image width, height, and pixel format | ||
3092 | * for the second to nth supported display formats, | ||
3093 | * if present. | ||
3094 | * NN 0xNN Telegram payload checksum byte | ||
3095 | * | ||
3096 | */ | ||
3097 | { | ||
3098 | /* We should NEVER receive this command so ERROR if we do */ | ||
3099 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
3100 | break; | ||
3101 | } | ||
3102 | case 0x003B: /* ResetDBSelectionHierarchy */ | ||
3103 | { | ||
3104 | /* The following is the description for the Apple Firmware | ||
3105 | * | ||
3106 | * This command carries a single byte in its payload (byte 6). A | ||
3107 | * hierarchy selection value of 0x01 means that the accessory wants | ||
3108 | * to navigate the music hierarchy; a hierarchy selection value of | ||
3109 | * 0x02 means that the accessory wants to navigate the video | ||
3110 | * hierarchy. | ||
3111 | * | ||
3112 | * Byte Value Meaning | ||
3113 | * 0 0xFF Sync byte (required only for UART serial) | ||
3114 | * 1 0x55 Start of telegram | ||
3115 | * 2 0x04 Telegram payload length | ||
3116 | * 3 0x04 Lingo ID: Extended Interface lingo | ||
3117 | * 4 0x00 Command ID (bits 15:8) | ||
3118 | * 5 0x3B Command ID (bits 7:0) | ||
3119 | * 6 0x01 or 0x02 Hierarchy selection | ||
3120 | * 7 0xNN Telegram payload checksum byte | ||
3121 | * | ||
3122 | * Note: The iPod will return an error if a device attempts to | ||
3123 | * enable an unsupported hierarchy, such as a video hierarchy on an | ||
3124 | * iPod model that does not support video. | ||
3125 | */ | ||
3126 | dbrecordcount = 0; | ||
3127 | cur_dbrecord[0] = 0; | ||
3128 | put_u32(&cur_dbrecord[1],0); | ||
3129 | switch (buf[3]) | ||
3130 | { | ||
3131 | case 0x01: /* Music */ | ||
3132 | { | ||
3133 | cmd_ok(cmd); | ||
3134 | break; | ||
3135 | } | ||
3136 | default: /* Anything else */ | ||
3137 | { | ||
3138 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
3139 | break; | ||
3140 | } | ||
3141 | } | ||
3142 | } | ||
3143 | default: | ||
3144 | { | ||
3145 | #ifdef LOGF_ENABLE | ||
3146 | logf("iap: Unsupported Mode04 Command"); | ||
3147 | #endif | ||
3148 | /* The default response is IAP_ACK_BAD_PARAM */ | ||
3149 | cmd_ack(cmd, IAP_ACK_BAD_PARAM); | ||
3150 | break; | ||
3151 | } | ||
3152 | } | ||
3153 | } | ||
diff --git a/apps/misc.c b/apps/misc.c index 8dff227bc1..e746c432e6 100644 --- a/apps/misc.c +++ b/apps/misc.c | |||
@@ -607,14 +607,6 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame | |||
607 | unplug_change(false); | 607 | unplug_change(false); |
608 | return SYS_PHONE_UNPLUGGED; | 608 | return SYS_PHONE_UNPLUGGED; |
609 | #endif | 609 | #endif |
610 | #ifdef IPOD_ACCESSORY_PROTOCOL | ||
611 | case SYS_IAP_PERIODIC: | ||
612 | iap_periodic(); | ||
613 | return SYS_IAP_PERIODIC; | ||
614 | case SYS_IAP_HANDLEPKT: | ||
615 | iap_handlepkt(); | ||
616 | return SYS_IAP_HANDLEPKT; | ||
617 | #endif | ||
618 | #if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO) | 610 | #if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO) |
619 | /* stop playback if we receive a call */ | 611 | /* stop playback if we receive a call */ |
620 | case SYS_CALL_INCOMING: | 612 | case SYS_CALL_INCOMING: |
diff --git a/firmware/export/iap.h b/firmware/export/iap.h index 8ee26cbe9f..22f36083d1 100644 --- a/firmware/export/iap.h +++ b/firmware/export/iap.h | |||
@@ -22,7 +22,9 @@ | |||
22 | 22 | ||
23 | #include <stdbool.h> | 23 | #include <stdbool.h> |
24 | 24 | ||
25 | #define RX_BUFLEN 512 | 25 | /* This is just the payload size, without sync, length and checksum */ |
26 | #define RX_BUFLEN (64*1024) | ||
27 | /* This is the entire frame length, sync, length, payload and checksum */ | ||
26 | #define TX_BUFLEN 128 | 28 | #define TX_BUFLEN 128 |
27 | 29 | ||
28 | extern bool iap_getc(unsigned char x); | 30 | extern bool iap_getc(unsigned char x); |
diff --git a/firmware/export/kernel.h b/firmware/export/kernel.h index 76ed96bd14..3cadefdf68 100644 --- a/firmware/export/kernel.h +++ b/firmware/export/kernel.h | |||
@@ -79,8 +79,6 @@ | |||
79 | #define SYS_REMOTE_PLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 4) | 79 | #define SYS_REMOTE_PLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 4) |
80 | #define SYS_REMOTE_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 5) | 80 | #define SYS_REMOTE_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 5) |
81 | #define SYS_CAR_ADAPTER_RESUME MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 0) | 81 | #define SYS_CAR_ADAPTER_RESUME MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 0) |
82 | #define SYS_IAP_PERIODIC MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 1) | ||
83 | #define SYS_IAP_HANDLEPKT MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 2) | ||
84 | #define SYS_CALL_INCOMING MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 3) | 82 | #define SYS_CALL_INCOMING MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 3) |
85 | #define SYS_CALL_HUNG_UP MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 4) | 83 | #define SYS_CALL_HUNG_UP MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 4) |
86 | #define SYS_VOLUME_CHANGED MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 5) | 84 | #define SYS_VOLUME_CHANGED MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 5) |
diff --git a/firmware/target/arm/pp/debug-pp.c b/firmware/target/arm/pp/debug-pp.c index f4ba61cd39..2f57e1ef14 100644 --- a/firmware/target/arm/pp/debug-pp.c +++ b/firmware/target/arm/pp/debug-pp.c | |||
@@ -155,9 +155,12 @@ bool dbg_ports(void) | |||
155 | 155 | ||
156 | #if defined(IPOD_ACCESSORY_PROTOCOL) | 156 | #if defined(IPOD_ACCESSORY_PROTOCOL) |
157 | const unsigned char *serbuf = iap_get_serbuf(); | 157 | const unsigned char *serbuf = iap_get_serbuf(); |
158 | lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x", | 158 | if (serbuf) |
159 | serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5], | 159 | { |
160 | serbuf[6], serbuf[7]); | 160 | lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x", |
161 | serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5], | ||
162 | serbuf[6], serbuf[7]); | ||
163 | } | ||
161 | #endif | 164 | #endif |
162 | 165 | ||
163 | #if defined(IRIVER_H10) || defined(IRIVER_H10_5GB) | 166 | #if defined(IRIVER_H10) || defined(IRIVER_H10_5GB) |
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; | ||
diff --git a/tools/iap/Makefile b/tools/iap/Makefile new file mode 100644 index 0000000000..86f3760de6 --- /dev/null +++ b/tools/iap/Makefile | |||
@@ -0,0 +1,7 @@ | |||
1 | default: test | ||
2 | |||
3 | test: | ||
4 | perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV' ipod*.t | ||
5 | |||
6 | moduletest: | ||
7 | perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV' device-ipod.t | ||
diff --git a/tools/iap/README b/tools/iap/README new file mode 100644 index 0000000000..dbd050feb4 --- /dev/null +++ b/tools/iap/README | |||
@@ -0,0 +1,23 @@ | |||
1 | These are perl test scripts for validating the IAP implementation. | ||
2 | Also included is a perl class for talking to an iPod via the serial | ||
3 | port. You will probably need Linux to use this. | ||
4 | |||
5 | Run "make moduletest" to test the perl module itself. This will not | ||
6 | require any serial connection, or even an iPod, for that matter. | ||
7 | |||
8 | Run "make test" to run the iPod communication tests themselves. | ||
9 | |||
10 | In order to test make sure | ||
11 | |||
12 | - the iPod is connected to a serial port | ||
13 | - the test scripts assume that this port is /dev/ttyUSB0. Change | ||
14 | as neccessary | ||
15 | |||
16 | Sometimes, tests will time out instead of giving the desired result. | ||
17 | As long as the timeouts are not reproducable this is usually not a | ||
18 | problem. The serial port is known to be unreliable, and devices will | ||
19 | retransmit. This happens even with the OF. | ||
20 | |||
21 | The tests were designed against an iPod Touch 2G as a reference device. | ||
22 | Some older iPods fail some of the test, even with the OF, because of | ||
23 | behaviour changes in later firmware releases by Apple. | ||
diff --git a/tools/iap/device-ipod.t b/tools/iap/device-ipod.t new file mode 100644 index 0000000000..607184e331 --- /dev/null +++ b/tools/iap/device-ipod.t | |||
@@ -0,0 +1,74 @@ | |||
1 | use Test::More qw( no_plan ); | ||
2 | use strict; | ||
3 | |||
4 | BEGIN { use_ok('Device::iPod'); } | ||
5 | require_ok('Device::iPod'); | ||
6 | |||
7 | my $ipod = Device::iPod->new(); | ||
8 | my $m; | ||
9 | my ($l, $c, $p); | ||
10 | |||
11 | isa_ok($ipod, 'Device::iPod'); | ||
12 | |||
13 | # Frame a short command | ||
14 | $m = $ipod->_frame_cmd("\x00\x02\x00\x06"); | ||
15 | ok(defined($m) && ($m eq "\xFF\x55\x04\x00\x02\x00\x06\xF4"), "Framed command valid"); | ||
16 | |||
17 | # Frame a long command | ||
18 | $m = $ipod->_frame_cmd("\x00" x 1024); | ||
19 | ok(defined($m) && ($m eq "\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . "\xFC"), "Long framed command valid"); | ||
20 | |||
21 | # Frame an overly long command | ||
22 | $m = $ipod->_frame_cmd("\x00" x 65537); | ||
23 | ok(!defined($m) && ($ipod->error() =~ 'Command too long'), "Overly long command failed"); | ||
24 | |||
25 | # Unframe a short command | ||
26 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x04\x00\x02\x00\x06\xF4"); | ||
27 | ok(defined($l) && ($l == 0x00) && ($c == 0x02) && ($p eq "\x00\x06"), "Unframed short command"); | ||
28 | |||
29 | # Unframe a long command | ||
30 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . "\xFC"); | ||
31 | ok(defined($l) && ($l == 0x00) && ($c == 0x00) && ($p eq "\x00" x 1022), "Unframed long command"); | ||
32 | |||
33 | # Frame without sync byte | ||
34 | ($l, $c, $p) = $ipod->_unframe_cmd("\x00"); | ||
35 | ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without sync byte failed"); | ||
36 | |||
37 | # Frame without SOP byte | ||
38 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF"); | ||
39 | ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without SOP byte failed"); | ||
40 | |||
41 | # Frame with length 0 | ||
42 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x00\x00"); | ||
43 | ok(!defined($l) && ($ipod->error() =~ /Length is 0/), "Frame with length 0 failed"); | ||
44 | |||
45 | # Too short frame | ||
46 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00"); | ||
47 | ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Too short frame failed"); | ||
48 | |||
49 | # Invalid checksum | ||
50 | ($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00\x00\x00"); | ||
51 | ok(!defined($l) && ($ipod->error() =~ /Invalid checksum/), "Invalid checksum failed"); | ||
52 | |||
53 | # Find a message in a string | ||
54 | $ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4"; | ||
55 | ($c, $l) = $ipod->_message_in_buffer(); | ||
56 | ok(defined($l) && ($c == 6) && ($l == 8), "Found message in buffer"); | ||
57 | |||
58 | # Return message from a string | ||
59 | $ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4\x00"; | ||
60 | $m = $ipod->_message(); | ||
61 | ok(defined($m) && ($ipod->{-inbuf} eq "\x00"), "Retrieved message from buffer"); | ||
62 | |||
63 | # Return two messages from buffer | ||
64 | $ipod->{-inbuf} = "\xffU\x04\x00\x02\x00\x13\xe7\xffU\x02\x00\x14\xea"; | ||
65 | $m = $ipod->_message(); | ||
66 | subtest "First message" => sub { | ||
67 | ok(defined($m), "Message received"); | ||
68 | is($m, "\xffU\x04\x00\x02\x00\x13\xe7"); | ||
69 | }; | ||
70 | $m = $ipod->_message(); | ||
71 | subtest "Second message" => sub { | ||
72 | ok(defined($m), "Message received"); | ||
73 | is($m, "\xffU\x02\x00\x14\xea"); | ||
74 | }; | ||
diff --git a/tools/iap/iap-verbose.pl b/tools/iap/iap-verbose.pl new file mode 100644 index 0000000000..ed25de7548 --- /dev/null +++ b/tools/iap/iap-verbose.pl | |||
@@ -0,0 +1,1856 @@ | |||
1 | #!/usr/bin/perl -w | ||
2 | |||
3 | package iap::decode; | ||
4 | |||
5 | use Device::iPod; | ||
6 | use Data::Dumper; | ||
7 | use strict; | ||
8 | |||
9 | sub new { | ||
10 | my $class = shift; | ||
11 | my $self = {-state => {}}; | ||
12 | |||
13 | return bless($self, $class); | ||
14 | } | ||
15 | |||
16 | sub display { | ||
17 | my $self = shift; | ||
18 | my $lingo = shift; | ||
19 | my $command = shift; | ||
20 | my $data = shift; | ||
21 | my $name; | ||
22 | my $handler; | ||
23 | my $r; | ||
24 | |||
25 | |||
26 | $name = sprintf("_h_%02x_%04x", $lingo, $command); | ||
27 | $handler = $self->can($name); | ||
28 | if ($handler) { | ||
29 | unless(exists($self->{-state}->{$name})) { | ||
30 | $self->{-state}->{$name} = {}; | ||
31 | } | ||
32 | $r = $handler->($self, $data, $self->{-state}->{$name}); | ||
33 | } else { | ||
34 | $r = $self->generic($lingo, $command, $data); | ||
35 | } | ||
36 | |||
37 | printf("\n"); | ||
38 | return $r; | ||
39 | } | ||
40 | |||
41 | sub generic { | ||
42 | my $self = shift; | ||
43 | my $lingo = shift; | ||
44 | my $command = shift; | ||
45 | my $data = shift; | ||
46 | |||
47 | printf("Unknown command\n"); | ||
48 | printf(" Lingo: 0x%02x\n", $lingo); | ||
49 | printf(" Command 0x%04x\n", $command); | ||
50 | printf(" Data: %s\n", Device::iPod->_hexstring($data)); | ||
51 | |||
52 | exit(1); | ||
53 | |||
54 | return 1; | ||
55 | } | ||
56 | |||
57 | sub _h_00_0001 { | ||
58 | my $self = shift; | ||
59 | my $data = shift; | ||
60 | my $state = shift; | ||
61 | my $lingo = shift; | ||
62 | |||
63 | $lingo = unpack("C", $data); | ||
64 | |||
65 | printf("Identify (0x00, 0x01) D->I\n"); | ||
66 | printf(" Lingo: 0x%02x\n", $lingo); | ||
67 | |||
68 | return 1; | ||
69 | } | ||
70 | |||
71 | sub _h_00_0002 { | ||
72 | my $self = shift; | ||
73 | my $data = shift; | ||
74 | my $state = shift; | ||
75 | my $res; | ||
76 | my $cmd; | ||
77 | my $delay; | ||
78 | |||
79 | ($res, $cmd, $delay) = unpack("CCN", $data); | ||
80 | |||
81 | printf("ACK (0x00, 0x02) I->D\n"); | ||
82 | printf(" Acknowledged command: 0x%02x\n", $cmd); | ||
83 | printf(" Status: %s (%d)\n", | ||
84 | ("Success", | ||
85 | "ERROR: Unknown Database Category", | ||
86 | "ERROR: Command Failed", | ||
87 | "ERROR: Out Of Resource", | ||
88 | "ERROR: Bad Parameter", | ||
89 | "ERROR: Unknown ID", | ||
90 | "Command Pending", | ||
91 | "ERROR: Not Authenticated", | ||
92 | "ERROR: Bad Authentication Version", | ||
93 | "ERROR: Accessory Power Mode Request Failed", | ||
94 | "ERROR: Certificate Invalid", | ||
95 | "ERROR: Certificate permissions invalid", | ||
96 | "ERROR: File is in use", | ||
97 | "ERROR: Invalid file handle", | ||
98 | "ERROR: Directory not empty", | ||
99 | "ERROR: Operation timed out", | ||
100 | "ERROR: Command unavailable in this iPod mode", | ||
101 | "ERROR: Invalid accessory resistor ID", | ||
102 | "Reserved", | ||
103 | "Reserved", | ||
104 | "Reserved", | ||
105 | "ERROR: Maximum number of accessory connections already reached")[$res], $res); | ||
106 | if ($res == 6) { | ||
107 | $delay = unpack("xxN", $data); | ||
108 | printf(" Delay: %d ms\n", $delay); | ||
109 | } | ||
110 | |||
111 | return 1; | ||
112 | } | ||
113 | |||
114 | sub _h_00_0005 { | ||
115 | my $self = shift; | ||
116 | my $data = shift; | ||
117 | my $state = shift; | ||
118 | |||
119 | printf("EnterRemoteUIMode (0x00, 0x05) D->I\n"); | ||
120 | |||
121 | return 1; | ||
122 | } | ||
123 | |||
124 | sub _h_00_000d { | ||
125 | my $self = shift; | ||
126 | my $data = shift; | ||
127 | my $state = shift; | ||
128 | |||
129 | printf("RequestiPodModelNum (0x00, 0x0D) D->I\n"); | ||
130 | |||
131 | return 1; | ||
132 | } | ||
133 | |||
134 | sub _h_00_000e { | ||
135 | my $self = shift; | ||
136 | my $data = shift; | ||
137 | my $state = shift; | ||
138 | my ($modelnum, $name); | ||
139 | |||
140 | ($modelnum, $name) = unpack("NZ*", $data); | ||
141 | |||
142 | printf("ReturniPodModelNum (0x00, 0x0E) I->D\n"); | ||
143 | printf(" Model number: %08x\n", $modelnum); | ||
144 | printf(" Model name: %s\n", $name); | ||
145 | |||
146 | return 1; | ||
147 | } | ||
148 | |||
149 | sub _h_00_000f { | ||
150 | my $self = shift; | ||
151 | my $data = shift; | ||
152 | my $state = shift; | ||
153 | my $lingo; | ||
154 | |||
155 | $lingo = unpack("C", $data); | ||
156 | |||
157 | printf("RequestLingoProtocolVersion (0x00, 0x0F) D->I\n"); | ||
158 | printf(" Lingo: 0x%02x\n", $lingo); | ||
159 | |||
160 | return 1; | ||
161 | } | ||
162 | |||
163 | sub _h_00_0010 { | ||
164 | my $self = shift; | ||
165 | my $data = shift; | ||
166 | my $state = shift; | ||
167 | my ($lingo, $maj, $min); | ||
168 | |||
169 | ($lingo, $maj, $min) = unpack("CCC", $data); | ||
170 | |||
171 | printf("ReturnLingoProtocolVersion (0x00, 0x10) I->D\n"); | ||
172 | printf(" Lingo: 0x%02x\n", $lingo); | ||
173 | printf(" Version: %d.%02d\n", $maj, $min); | ||
174 | |||
175 | return 1; | ||
176 | } | ||
177 | |||
178 | |||
179 | sub _h_00_0013 { | ||
180 | my $self = shift; | ||
181 | my $data = shift; | ||
182 | my $state = shift; | ||
183 | my @lingolist; | ||
184 | my ($lingoes, $options, $devid); | ||
185 | |||
186 | ($lingoes, $options, $devid) = unpack("N3", $data); | ||
187 | |||
188 | foreach (0..31) { | ||
189 | push(@lingolist, $_) if ($lingoes & (1 << $_)); | ||
190 | } | ||
191 | |||
192 | printf("IdentifyDeviceLingoes (0x00, 0x13) D->I\n"); | ||
193 | printf(" Supported lingoes: %s\n", join(", ", @lingolist)); | ||
194 | printf(" Options:\n"); | ||
195 | printf(" Authentication: %s\n", ("None", "Defer (1.0)", "Immediate (2.0)", "Reserved")[$options & 0x03]); | ||
196 | printf(" Power: %s\n", ("Low", "Intermittent high", "Reserved", "Constant high")[($options & 0x0C) >> 2]); | ||
197 | printf(" Device ID: 0x%08x\n", $devid); | ||
198 | |||
199 | delete($self->{-state}->{'_h_00_0015'}); | ||
200 | |||
201 | return 1; | ||
202 | } | ||
203 | |||
204 | sub _h_00_0014 { | ||
205 | my $self = shift; | ||
206 | my $data = shift; | ||
207 | my $state = shift; | ||
208 | |||
209 | printf("GetDevAuthenticationInfo (0x00, 0x14) I->D\n"); | ||
210 | |||
211 | return 1; | ||
212 | } | ||
213 | |||
214 | sub _h_00_0015 { | ||
215 | my $self = shift; | ||
216 | my $data = shift; | ||
217 | my $state = shift; | ||
218 | my ($amaj, $amin, $curidx, $maxidx, $certdata); | ||
219 | |||
220 | $state->{-curidx} = -1 unless(exists($state->{-curidx})); | ||
221 | $state->{-maxidx} = -1 unless(exists($state->{-maxidx})); | ||
222 | $state->{-cert} = '' unless(exists($state->{-cert})); | ||
223 | |||
224 | ($amaj, $amin, $curidx, $maxidx, $certdata) = unpack("CCCCa*", $data); | ||
225 | |||
226 | printf("RetDevAuthenticationInfo (0x00, 0x15) D->I\n"); | ||
227 | printf(" Authentication version: %d.%d\n", $amaj, $amin); | ||
228 | printf(" Segment: %d of %d\n", $curidx, $maxidx); | ||
229 | |||
230 | if ($curidx-1 != $state->{-curidx}) { | ||
231 | printf(" WARNING! Out of order segment\n"); | ||
232 | return 0; | ||
233 | } | ||
234 | |||
235 | if (($maxidx != $state->{-maxidx}) && ($state->{-maxidx} != -1)) { | ||
236 | printf(" WARNING! maxidx changed midstream\n"); | ||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | if ($curidx > $maxidx) { | ||
241 | printf(" WARNING! Too many segments\n"); | ||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | $state->{-curidx} = $curidx; | ||
246 | $state->{-maxidx} = $maxidx; | ||
247 | $state->{-cert} .= $certdata; | ||
248 | |||
249 | if ($curidx == $maxidx) { | ||
250 | printf(" Certificate: %s\n", Device::iPod->_hexstring($state->{-cert})); | ||
251 | } | ||
252 | |||
253 | return 1; | ||
254 | } | ||
255 | |||
256 | sub _h_00_0016 { | ||
257 | my $self = shift; | ||
258 | my $data = shift; | ||
259 | my $state = shift; | ||
260 | my $res; | ||
261 | |||
262 | $res = unpack("C", $data); | ||
263 | |||
264 | printf("AckDevAuthenticationInfo (0x00, 0x16) I->D\n"); | ||
265 | printf(" Result: "); | ||
266 | if ($res == 0x00) { | ||
267 | printf("Authentication information supported\n"); | ||
268 | } elsif ($res == 0x08) { | ||
269 | printf("Authentication information unpported\n"); | ||
270 | } elsif ($res == 0x0A) { | ||
271 | printf("Certificate invalid\n"); | ||
272 | } elsif ($res == 0x0B) { | ||
273 | printf("Certificate permissions are invalid\n"); | ||
274 | } else { | ||
275 | printf("Unknown result 0x%02x\n", $res); | ||
276 | } | ||
277 | |||
278 | return 1; | ||
279 | } | ||
280 | |||
281 | sub _h_00_0017 { | ||
282 | my $self = shift; | ||
283 | my $data = shift; | ||
284 | my $state = shift; | ||
285 | my ($challenge, $retry); | ||
286 | |||
287 | printf("GetDevAuthenticationSignature (0x00, 0x17) I->D\n"); | ||
288 | |||
289 | if (length($data) == 17) { | ||
290 | ($challenge, $retry) = unpack("a16C", $data); | ||
291 | } elsif (length($data) == 21) { | ||
292 | ($challenge, $retry) = unpack("a20C", $data); | ||
293 | } else { | ||
294 | printf(" WARNING! Unsupported data length: %d\n", length($data)); | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | printf(" Challenge: %s (%d bytes)\n", Device::iPod->_hexstring($challenge), length($challenge)); | ||
299 | printf(" Retry counter: %d\n", $retry); | ||
300 | |||
301 | return 1; | ||
302 | } | ||
303 | |||
304 | sub _h_00_0018 { | ||
305 | my $self = shift; | ||
306 | my $data = shift; | ||
307 | my $state = shift; | ||
308 | my $reply; | ||
309 | |||
310 | printf("RetDevAuthenticationSignature (0x00, 0x18) D->I\n"); | ||
311 | printf(" Data: %s\n", Device::iPod->_hexstring($data)); | ||
312 | |||
313 | return 1; | ||
314 | } | ||
315 | |||
316 | sub _h_00_0019 { | ||
317 | my $self = shift; | ||
318 | my $data = shift; | ||
319 | my $state = shift; | ||
320 | my $res; | ||
321 | |||
322 | $res = unpack("C", $data); | ||
323 | |||
324 | printf("AckiPodAuthenticationInfo (0x00, 0x19) I->D\n"); | ||
325 | printf(" Status: %s (%d)\n", ( | ||
326 | "OK")[$res], $res); | ||
327 | |||
328 | return 1; | ||
329 | } | ||
330 | |||
331 | sub _h_00_0024 { | ||
332 | my $self = shift; | ||
333 | my $data = shift; | ||
334 | my $state = shift; | ||
335 | |||
336 | printf("GetiPodOptions (0x00, 0x24) D->I\n"); | ||
337 | |||
338 | return 1; | ||
339 | } | ||
340 | |||
341 | sub _h_00_0025 { | ||
342 | my $self = shift; | ||
343 | my $data = shift; | ||
344 | my $state = shift; | ||
345 | my ($ophi, $oplo); | ||
346 | |||
347 | ($ophi, $oplo) = unpack("NN", $data); | ||
348 | |||
349 | printf("RetiPodOptions (0x00, 0x25) I->D\n"); | ||
350 | printf(" Options:\n"); | ||
351 | printf(" iPod supports SetiPodPreferences\n") if ($oplo & 0x02); | ||
352 | printf(" iPod supports video\n") if ($oplo & 0x01); | ||
353 | |||
354 | return 1; | ||
355 | } | ||
356 | |||
357 | |||
358 | sub _h_00_0027 { | ||
359 | my $self = shift; | ||
360 | my $data = shift; | ||
361 | my $state = shift; | ||
362 | my $acctype; | ||
363 | my $accparam; | ||
364 | |||
365 | $acctype = unpack("C", $data); | ||
366 | |||
367 | printf("GetAccessoryInfo (0x00, 0x27) I->D\n"); | ||
368 | printf(" Accessory Info Type: %s (%d)\n", ( | ||
369 | "Info capabilities", | ||
370 | "Name", | ||
371 | "Minimum supported iPod firmware version", | ||
372 | "Minimum supported lingo version", | ||
373 | "Firmware version", | ||
374 | "Hardware version", | ||
375 | "Manufacturer", | ||
376 | "Model Number", | ||
377 | "Serial Number", | ||
378 | "Maximum payload size")[$acctype], $acctype); | ||
379 | if ($acctype == 0x02) { | ||
380 | my ($modelid, $maj, $min, $rev); | ||
381 | |||
382 | ($modelid, $maj, $min, $rev) = unpack("xNCCC", $data); | ||
383 | printf(" Model ID: 0x%04x\n", $modelid); | ||
384 | printf(" iPod Firmware: %d.%d.%d\n", $maj, $min, $rev); | ||
385 | } elsif ($acctype == 0x03) { | ||
386 | my $lingo; | ||
387 | |||
388 | $lingo = unpack("xC", $data); | ||
389 | printf(" Lingo: 0x%02x\n", $lingo); | ||
390 | } | ||
391 | |||
392 | return 1; | ||
393 | } | ||
394 | |||
395 | sub _h_00_0028 { | ||
396 | my $self = shift; | ||
397 | my $data = shift; | ||
398 | my $state = shift; | ||
399 | my $acctype; | ||
400 | my $accparam; | ||
401 | |||
402 | $acctype = unpack("C", $data); | ||
403 | |||
404 | printf("RetAccessoryInfo (0x00, 0x28) D->I\n"); | ||
405 | printf(" Accessory Info Type: %s (%d)\n", ( | ||
406 | "Info capabilities", | ||
407 | "Name", | ||
408 | "Minimum supported iPod firmware version", | ||
409 | "Minimum supported lingo version", | ||
410 | "Firmware version", | ||
411 | "Hardware version", | ||
412 | "Manufacturer", | ||
413 | "Model Number", | ||
414 | "Serial Number", | ||
415 | "Maximum payload size")[$acctype], $acctype); | ||
416 | |||
417 | if ($acctype == 0x00) { | ||
418 | $accparam = unpack("xN", $data); | ||
419 | printf(" Accessory Info capabilities\n") if ($accparam & 0x01); | ||
420 | printf(" Accessory name\n") if ($accparam & 0x02); | ||
421 | printf(" Accessory minimum supported iPod firmware\n") if ($accparam & 0x04); | ||
422 | printf(" Accessory minimum supported lingo version\n") if ($accparam & 0x08); | ||
423 | printf(" Accessory firmware version\n") if ($accparam & 0x10); | ||
424 | printf(" Accessory hardware version\n") if ($accparam & 0x20); | ||
425 | printf(" Accessory manufacturer\n") if ($accparam & 0x40); | ||
426 | printf(" Accessory model number\n") if ($accparam & 0x80); | ||
427 | printf(" Accessory serial number\n") if ($accparam & 0x100); | ||
428 | printf(" Accessory incoming max packet size\n") if ($accparam & 0x200); | ||
429 | } | ||
430 | |||
431 | if ($acctype ~~ [0x01, 0x06, 0x07, 0x08]) { | ||
432 | $accparam = unpack("xZ*", $data); | ||
433 | printf(" Data: %s\n", $accparam); | ||
434 | } | ||
435 | |||
436 | if ($acctype == 0x02) { | ||
437 | $accparam = [ unpack("xNCCC", $data) ]; | ||
438 | printf(" Model ID: %08x\n", $accparam->[0]); | ||
439 | printf(" Firmware version: %d.%02d.%02d\n", $accparam->[1], $accparam->[2], $accparam->[3]); | ||
440 | } | ||
441 | |||
442 | if ($acctype == 0x03) { | ||
443 | $accparam = [ unpack("xCCC", $data) ]; | ||
444 | printf(" Lingo: %02x\n", $accparam->[0]); | ||
445 | printf(" Version: %d.%02d\n", $accparam->[1], $accparam->[2]); | ||
446 | } | ||
447 | |||
448 | if ($acctype ~~ [0x04, 0x05]) { | ||
449 | $accparam = [ unpack("xCCC", $data) ]; | ||
450 | printf(" Version: %d.%02d.%02d\n", @{$accparam}); | ||
451 | } | ||
452 | |||
453 | return 1; | ||
454 | } | ||
455 | |||
456 | sub _h_00_0029 { | ||
457 | my $self = shift; | ||
458 | my $data = shift; | ||
459 | my $state = shift; | ||
460 | my $class; | ||
461 | |||
462 | $class = unpack("C", $data); | ||
463 | |||
464 | printf("GetiPodPreferences (0x00, 0x29) D->I\n"); | ||
465 | printf(" Class: %s (%d)\n", ( | ||
466 | "Video out setting", | ||
467 | "Screen configuration", | ||
468 | "Video signal format", | ||
469 | "Line Out usage", | ||
470 | "(Reserved)", | ||
471 | "(Reserved)", | ||
472 | "(Reserved)", | ||
473 | "(Reserved)", | ||
474 | "Video out connection", | ||
475 | "Closed captioning", | ||
476 | "Video aspect ratio", | ||
477 | "(Reserved)", | ||
478 | "Subtitles", | ||
479 | "Video alternate audio channel")[$class], $class); | ||
480 | |||
481 | return 1; | ||
482 | } | ||
483 | |||
484 | sub _h_00_002b { | ||
485 | my $self = shift; | ||
486 | my $data = shift; | ||
487 | my $state = shift; | ||
488 | my ($class, $setting); | ||
489 | |||
490 | ($class, $setting) = unpack("CC", $data); | ||
491 | |||
492 | printf("SetiPodPreferences (0x00, 0x2B) D->I\n"); | ||
493 | printf(" Class: %s (%d)\n", ( | ||
494 | "Video out setting", | ||
495 | "Screen configuration", | ||
496 | "Video signal format", | ||
497 | "Line out usage", | ||
498 | "Reserved", | ||
499 | "Reserved", | ||
500 | "Reserved", | ||
501 | "Reserved", | ||
502 | "Video-out connection", | ||
503 | "Closed captioning", | ||
504 | "Video monitor aspect ratio", | ||
505 | "Reserved", | ||
506 | "Subtitles", | ||
507 | "Video alternate audio channel")[$class], $class); | ||
508 | printf(" Setting: %s (%d)\n", ( | ||
509 | ["Off", | ||
510 | "On", | ||
511 | "Ask user"], | ||
512 | ["Fill screen", | ||
513 | "Fit to screen edge"], | ||
514 | ["NTSC", | ||
515 | "PAL"], | ||
516 | ["Not used", | ||
517 | "Used"], | ||
518 | [], | ||
519 | [], | ||
520 | [], | ||
521 | [], | ||
522 | ["None", | ||
523 | "Composite", | ||
524 | "S-video", | ||
525 | "Component"], | ||
526 | ["Off", | ||
527 | "On"], | ||
528 | ["4:3", | ||
529 | "16:9"], | ||
530 | [], | ||
531 | ["Off", | ||
532 | "On"], | ||
533 | ["Off", | ||
534 | "On"])[$class][$setting], $setting); | ||
535 | |||
536 | return 1; | ||
537 | } | ||
538 | |||
539 | sub _h_00_0038 { | ||
540 | my $self = shift; | ||
541 | my $data = shift; | ||
542 | my $state = shift; | ||
543 | my $transid; | ||
544 | |||
545 | $transid = unpack("n", $data); | ||
546 | |||
547 | printf("StartIDPS (0x00, 0x38) D->I\n"); | ||
548 | printf(" TransID: %d\n", $transid); | ||
549 | |||
550 | return 1; | ||
551 | } | ||
552 | |||
553 | sub _h_00_003b { | ||
554 | my $self = shift; | ||
555 | my $data = shift; | ||
556 | my $state = shift; | ||
557 | my ($transid, $status); | ||
558 | |||
559 | ($transid, $status) = unpack("nC", $data); | ||
560 | |||
561 | printf("EndIDPS (0x00, 0x3B) D->I\n"); | ||
562 | printf(" TransID: %d\n", $transid); | ||
563 | printf(" Action: %s (%d)\n", ( | ||
564 | "Finished", | ||
565 | "Reset")[$status], $status); | ||
566 | |||
567 | return 1; | ||
568 | } | ||
569 | |||
570 | sub _h_00_004b { | ||
571 | my $self = shift; | ||
572 | my $data = shift; | ||
573 | my $state = shift; | ||
574 | my $lingo; | ||
575 | |||
576 | $lingo = unpack("C", $data); | ||
577 | |||
578 | printf("GetiPodOptionsForLingo (0x00, 0x4B) D->I\n"); | ||
579 | printf(" Lingo: 0x%02x\n", $lingo); | ||
580 | |||
581 | return 1; | ||
582 | } | ||
583 | |||
584 | sub _h_02_0000 { | ||
585 | my $self = shift; | ||
586 | my $data = shift; | ||
587 | my $state = shift; | ||
588 | my @keys; | ||
589 | |||
590 | @keys = unpack("CCCC", $data); | ||
591 | |||
592 | printf("ContextButtonStatus (0x02, 0x00) D->I\n"); | ||
593 | printf(" Buttons:\n"); | ||
594 | printf(" Play/Pause\n") if ($keys[0] & 0x01); | ||
595 | printf(" Volume Up\n") if ($keys[0] & 0x02); | ||
596 | printf(" Volume Down\n") if ($keys[0] & 0x04); | ||
597 | printf(" Next Track\n") if ($keys[0] & 0x08); | ||
598 | printf(" Previous Track\n") if ($keys[0] & 0x10); | ||
599 | printf(" Next Album\n") if ($keys[0] & 0x20); | ||
600 | printf(" Previous Album\n") if ($keys[0] & 0x40); | ||
601 | printf(" Stop\n") if ($keys[0] & 0x80); | ||
602 | |||
603 | if (exists($keys[1])) { | ||
604 | printf(" Play/Resume\n") if ($keys[1] & 0x01); | ||
605 | printf(" Pause\n") if ($keys[1] & 0x02); | ||
606 | printf(" Mute toggle\n") if ($keys[1] & 0x04); | ||
607 | printf(" Next Chapter\n") if ($keys[1] & 0x08); | ||
608 | printf(" Previous Chapter\n") if ($keys[1] & 0x10); | ||
609 | printf(" Next Playlist\n") if ($keys[1] & 0x20); | ||
610 | printf(" Previous Playlist\n") if ($keys[1] & 0x40); | ||
611 | printf(" Shuffle Setting Advance\n") if ($keys[1] & 0x80); | ||
612 | } | ||
613 | |||
614 | if (exists($keys[2])) { | ||
615 | printf(" Repeat Setting Advance\n") if ($keys[2] & 0x01); | ||
616 | printf(" Power On\n") if ($keys[2] & 0x02); | ||
617 | printf(" Power Off\n") if ($keys[2] & 0x04); | ||
618 | printf(" Backlight for 30 seconds\n") if ($keys[2] & 0x08); | ||
619 | printf(" Begin FF\n") if ($keys[2] & 0x10); | ||
620 | printf(" Begin REW\n") if ($keys[2] & 0x20); | ||
621 | printf(" Menu\n") if ($keys[2] & 0x40); | ||
622 | printf(" Select\n") if ($keys[2] & 0x80); | ||
623 | } | ||
624 | |||
625 | if (exists($keys[3])) { | ||
626 | printf(" Up Arrow\n") if ($keys[3] & 0x01); | ||
627 | printf(" Down Arrow\n") if ($keys[3] & 0x02); | ||
628 | printf(" Backlight off\n") if ($keys[3] & 0x04); | ||
629 | } | ||
630 | |||
631 | return 1; | ||
632 | } | ||
633 | |||
634 | sub _h_02_0001 { | ||
635 | my $self = shift; | ||
636 | my $data = shift; | ||
637 | my $state = shift; | ||
638 | my $res; | ||
639 | my $cmd; | ||
640 | |||
641 | ($res, $cmd) = unpack("CC", $data); | ||
642 | |||
643 | printf("ACK (0x02, 0x01) I->D\n"); | ||
644 | printf(" Acknowledged command: 0x%02x\n", $cmd); | ||
645 | printf(" Status: %s (%d)\n", | ||
646 | ("Success", | ||
647 | "ERROR: Unknown Database Category", | ||
648 | "ERROR: Command Failed", | ||
649 | "ERROR: Out Of Resource", | ||
650 | "ERROR: Bad Parameter", | ||
651 | "ERROR: Unknown ID", | ||
652 | "Command Pending", | ||
653 | "ERROR: Not Authenticated", | ||
654 | "ERROR: Bad Authentication Version", | ||
655 | "ERROR: Accessory Power Mode Request Failed", | ||
656 | "ERROR: Certificate Invalid", | ||
657 | "ERROR: Certificate permissions invalid", | ||
658 | "ERROR: File is in use", | ||
659 | "ERROR: Invalid file handle", | ||
660 | "ERROR: Directory not empty", | ||
661 | "ERROR: Operation timed out", | ||
662 | "ERROR: Command unavailable in this iPod mode", | ||
663 | "ERROR: Invalid accessory resistor ID", | ||
664 | "Reserved", | ||
665 | "Reserved", | ||
666 | "Reserved", | ||
667 | "ERROR: Maximum number of accessory connections already reached")[$res], $res); | ||
668 | |||
669 | return 1; | ||
670 | } | ||
671 | |||
672 | sub _h_03_0000 { | ||
673 | my $self = shift; | ||
674 | my $data = shift; | ||
675 | my $state = shift; | ||
676 | my $res; | ||
677 | my $cmd; | ||
678 | |||
679 | ($res, $cmd) = unpack("CC", $data); | ||
680 | |||
681 | printf("ACK (0x03, 0x00) I->D\n"); | ||
682 | printf(" Acknowledged command: 0x%02x\n", $cmd); | ||
683 | printf(" Status: %s (%d)\n", | ||
684 | ("Success", | ||
685 | "ERROR: Unknown Database Category", | ||
686 | "ERROR: Command Failed", | ||
687 | "ERROR: Out Of Resource", | ||
688 | "ERROR: Bad Parameter", | ||
689 | "ERROR: Unknown ID", | ||
690 | "Command Pending", | ||
691 | "ERROR: Not Authenticated", | ||
692 | "ERROR: Bad Authentication Version", | ||
693 | "ERROR: Accessory Power Mode Request Failed", | ||
694 | "ERROR: Certificate Invalid", | ||
695 | "ERROR: Certificate permissions invalid", | ||
696 | "ERROR: File is in use", | ||
697 | "ERROR: Invalid file handle", | ||
698 | "ERROR: Directory not empty", | ||
699 | "ERROR: Operation timed out", | ||
700 | "ERROR: Command unavailable in this iPod mode", | ||
701 | "ERROR: Invalid accessory resistor ID", | ||
702 | "Reserved", | ||
703 | "Reserved", | ||
704 | "Reserved", | ||
705 | "ERROR: Maximum number of accessory connections already reached")[$res], $res); | ||
706 | |||
707 | return 1; | ||
708 | } | ||
709 | |||
710 | sub _h_03_0008 { | ||
711 | my $self = shift; | ||
712 | my $data = shift; | ||
713 | my $state = shift; | ||
714 | my $events;; | ||
715 | |||
716 | $events = unpack("N", $data); | ||
717 | |||
718 | printf("SetRemoteEventsNotification (0x03, 0x08) D->I\n"); | ||
719 | printf(" Events:\n"); | ||
720 | printf(" Track position in ms\n") if ($events & 0x00000001); | ||
721 | printf(" Track playback index\n") if ($events & 0x00000002); | ||
722 | printf(" Chapter index\n") if ($events & 0x00000004); | ||
723 | printf(" Play status\n") if ($events & 0x00000008); | ||
724 | printf(" Mute/UI volume\n") if ($events & 0x00000010); | ||
725 | printf(" Power/Battery\n") if ($events & 0x00000020); | ||
726 | printf(" Equalizer setting\n") if ($events & 0x00000040); | ||
727 | printf(" Shuffle setting\n") if ($events & 0x00000080); | ||
728 | printf(" Repeat setting\n") if ($events & 0x00000100); | ||
729 | printf(" Date and time setting\n") if ($events & 0x00000200); | ||
730 | printf(" Alarm setting\n") if ($events & 0x00000400); | ||
731 | printf(" Backlight state\n") if ($events & 0x00000800); | ||
732 | printf(" Hold switch state\n") if ($events & 0x00001000); | ||
733 | printf(" Sound check state\n") if ($events & 0x00002000); | ||
734 | printf(" Audiobook speed\n") if ($events & 0x00004000); | ||
735 | printf(" Track position in s\n") if ($events & 0x00008000); | ||
736 | printf(" Mute/UI/Absolute volume\n") if ($events & 0x00010000); | ||
737 | |||
738 | return 1; | ||
739 | } | ||
740 | |||
741 | sub _h_03_0009 { | ||
742 | my $self = shift; | ||
743 | my $data = shift; | ||
744 | my $state = shift; | ||
745 | my $event; | ||
746 | my $eventdata; | ||
747 | |||
748 | $event = unpack("C", $data); | ||
749 | |||
750 | printf("RemoteEventNotification (0x03, 0x09) I->D\n"); | ||
751 | printf(" Event: %s (%d)\n", ( | ||
752 | "Track position in ms", | ||
753 | "Track playback index", | ||
754 | "Chapter index", | ||
755 | "Play status", | ||
756 | "Mute/UI volume", | ||
757 | "Power/Battery", | ||
758 | "Equalizer setting", | ||
759 | "Shuffle setting", | ||
760 | "Repeat setting", | ||
761 | "Date and time setting", | ||
762 | "Alarm setting", | ||
763 | "Backlight state", | ||
764 | "Hold switch state", | ||
765 | "Sound check state", | ||
766 | "Audiobook speed", | ||
767 | "Track position in s", | ||
768 | "Mute/UI/Absolute volume")[$event], $event); | ||
769 | |||
770 | if ($event == 0x00) { | ||
771 | $eventdata = unpack("xN", $data); | ||
772 | printf(" Position: %d ms\n", $eventdata); | ||
773 | } elsif ($event == 0x01) { | ||
774 | $eventdata = unpack("xN", $data); | ||
775 | printf(" Track: %d\n", $eventdata); | ||
776 | } elsif ($event == 0x02) { | ||
777 | $eventdata = [ unpack("xNnn", $data) ]; | ||
778 | printf(" Track: %d\n", $eventdata->[0]); | ||
779 | printf(" Chapter count: %d\n", $eventdata->[1]); | ||
780 | printf(" Chapter index: %d\n", $eventdata->[2]); | ||
781 | } elsif ($event == 0x03) { | ||
782 | $eventdata = unpack("xC", $data); | ||
783 | printf(" Status: %s (%d)\n", ( | ||
784 | "Stopped", | ||
785 | "Playing", | ||
786 | "Paused", | ||
787 | "FF", | ||
788 | "REW", | ||
789 | "End FF/REW")[$eventdata], $eventdata); | ||
790 | } elsif ($event == 0x04) { | ||
791 | $eventdata = [ unpack("xCC") ]; | ||
792 | printf(" Mute: %s\n", $eventdata->[0]?"Off":"On"); | ||
793 | printf(" Volume: %d\n", $eventdata->[1]); | ||
794 | } elsif ($event == 0x0F) { | ||
795 | $eventdata = unpack("xn", $data); | ||
796 | printf(" Position: %d s\n", $eventdata); | ||
797 | } | ||
798 | |||
799 | return 1; | ||
800 | } | ||
801 | |||
802 | sub _h_03_000c { | ||
803 | my $self = shift; | ||
804 | my $data = shift; | ||
805 | my $state = shift; | ||
806 | my $info; | ||
807 | |||
808 | $info = unpack("C", $data); | ||
809 | |||
810 | printf("GetiPodStateInfo (0x03, 0x0C) D->I\n"); | ||
811 | printf(" Info to get: %s (%d)\n", ( | ||
812 | "Track time in ms", | ||
813 | "Track playback index", | ||
814 | "Chapter information", | ||
815 | "Play status", | ||
816 | "Mute and volume information", | ||
817 | "Power and battery status", | ||
818 | "Equalizer setting", | ||
819 | "Shuffle setting", | ||
820 | "Repeat setting", | ||
821 | "Date and time", | ||
822 | "Alarm state and time", | ||
823 | "Backlight state", | ||
824 | "Hold switch state", | ||
825 | "Audiobook speed", | ||
826 | "Track time in seconds", | ||
827 | "Mute/UI/Absolute volume")[$info], $info); | ||
828 | |||
829 | return 1; | ||
830 | } | ||
831 | |||
832 | sub _h_03_000d { | ||
833 | my $self = shift; | ||
834 | my $data = shift; | ||
835 | my $state = shift; | ||
836 | my $type; | ||
837 | my $info; | ||
838 | |||
839 | $type = unpack("C", $data); | ||
840 | |||
841 | printf("RetiPodStateInfo (0x03, 0x0E) D->I\n"); | ||
842 | |||
843 | if ($type == 0x00) { | ||
844 | $info = unpack("xN", $data); | ||
845 | printf(" Type: Track position\n"); | ||
846 | printf(" Position: %d ms\n", $info); | ||
847 | } elsif ($type == 0x01) { | ||
848 | $info = unpack("xN", $data); | ||
849 | printf(" Type: Track index\n"); | ||
850 | printf(" Index: %d\n", $info); | ||
851 | } elsif ($type == 0x02) { | ||
852 | $info = [ unpack("xNnn", $data) ]; | ||
853 | printf(" Type: Chapter Information\n"); | ||
854 | printf(" Playing Track: %d\n", $info->[0]); | ||
855 | printf(" Chapter count: %d\n", $info->[1]); | ||
856 | printf(" Chapter index: %d\n", $info->[2]); | ||
857 | } elsif ($type == 0x03) { | ||
858 | $info = unpack("xC", $data); | ||
859 | printf(" Type: Play status\n"); | ||
860 | printf(" Status: %s (%d)\n", ( | ||
861 | "Stopped", | ||
862 | "Playing", | ||
863 | "Paused", | ||
864 | "FF", | ||
865 | "REW", | ||
866 | "End FF/REW")[$info], $info); | ||
867 | } elsif ($type == 0x04) { | ||
868 | $info = [unpack("xCC", $data)]; | ||
869 | printf(" Type: Mute/Volume\n"); | ||
870 | printf(" Mute State: %s\n", $info->[0]?"On":"Off"); | ||
871 | printf(" Volume level: %d\n", $info->[1]); | ||
872 | } elsif ($type == 0x05) { | ||
873 | $info = [unpack("xCC", $data)]; | ||
874 | printf(" Type: Battery Information\n"); | ||
875 | printf(" Power state: %s (%d)\n", ( | ||
876 | "Internal, low power (<30%)", | ||
877 | "Internal", | ||
878 | "External battery pack, no charging", | ||
879 | "External power, no charging", | ||
880 | "External power, charging", | ||
881 | "External power, charged")[$info->[0]], $info->[0]); | ||
882 | printf(" Battery level: %d%%\n", $info->[1]*100/255); | ||
883 | } elsif ($type == 0x06) { | ||
884 | $info = [unpack("xN", $data)]; | ||
885 | printf(" Type: Equalizer\n"); | ||
886 | printf(" Index: %d\n", $info->[0]); | ||
887 | } elsif ($type == 0x07) { | ||
888 | $info = [unpack("xC", $data)]; | ||
889 | printf(" Type: Shuffle\n"); | ||
890 | printf(" Shuffle State: %s\n", $info->[0]?"On":"Off"); | ||
891 | } elsif ($type == 0x08) { | ||
892 | $info = [unpack("xC", $data)]; | ||
893 | printf(" Type: Repeat\n"); | ||
894 | printf(" Repeat State: %s\n", $info->[0]?"On":"Off"); | ||
895 | } elsif ($type == 0x09) { | ||
896 | $info = [unpack("xnCCCC", $data)]; | ||
897 | printf(" Type: Date\n"); | ||
898 | printf(" Date: %02d.%02d.%04d %02d:%02d\n", $info->[2], $info->[1], $info->[0], $info->[3], $info->[4]); | ||
899 | } elsif ($type == 0x0A) { | ||
900 | $info = [unpack("xCCC", $data)]; | ||
901 | printf(" Type: Alarm\n"); | ||
902 | printf(" Alarm State: %s\n", $info->[0]?"On":"Off"); | ||
903 | printf(" Time: %02d:%02d\n", $info->[1], $info->[2]); | ||
904 | } elsif ($type == 0x0B) { | ||
905 | $info = [unpack("xC", $data)]; | ||
906 | printf(" Type: Backlight\n"); | ||
907 | printf(" Backlight State: %s\n", $info->[0]?"On":"Off"); | ||
908 | } elsif ($type == 0x0C) { | ||
909 | $info = [unpack("xC", $data)]; | ||
910 | printf(" Type: Hold switch\n"); | ||
911 | printf(" Switch State: %s\n", $info->[0]?"On":"Off"); | ||
912 | } elsif ($type == 0x0D) { | ||
913 | $info = [unpack("xC", $data)]; | ||
914 | printf(" Type: Sound check\n"); | ||
915 | printf(" Sound check: %s\n", $info->[0]?"On":"Off"); | ||
916 | } elsif ($type == 0x0E) { | ||
917 | $info = [unpack("xC", $data)]; | ||
918 | printf(" Type: Audiobook speed\n"); | ||
919 | printf(" Speed: %s\n", $info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved"); | ||
920 | } elsif ($type == 0x0F) { | ||
921 | $info = unpack("xN", $data); | ||
922 | printf(" Type: Track position\n"); | ||
923 | printf(" Position: %d s\n", $info); | ||
924 | } elsif ($type == 0x10) { | ||
925 | $info = [unpack("xCCC", $data)]; | ||
926 | printf(" Type: Mute/UI/Absolute volume\n"); | ||
927 | printf(" Mute State: %s\n", $info->[0]?"On":"Off"); | ||
928 | printf(" UI Volume level: %d\n", $info->[1]); | ||
929 | printf(" Absolute Volume: %d\n", $info->[2]); | ||
930 | } else { | ||
931 | printf(" Reserved\n"); | ||
932 | } | ||
933 | |||
934 | return 1; | ||
935 | } | ||
936 | |||
937 | |||
938 | sub _h_03_000e { | ||
939 | my $self = shift; | ||
940 | my $data = shift; | ||
941 | my $state = shift; | ||
942 | my $type; | ||
943 | my $info; | ||
944 | |||
945 | $type = unpack("C", $data); | ||
946 | |||
947 | printf("SetiPodStateInfo (0x03, 0x0E) D->I\n"); | ||
948 | |||
949 | if ($type == 0x00) { | ||
950 | $info = unpack("xN", $data); | ||
951 | printf(" Type: Track position\n"); | ||
952 | printf(" Position: %d ms\n", $info); | ||
953 | } elsif ($type == 0x01) { | ||
954 | $info = unpack("xN", $data); | ||
955 | printf(" Type: Track index\n"); | ||
956 | printf(" Index: %d\n", $info); | ||
957 | } elsif ($type == 0x02) { | ||
958 | $info = unpack("xn", $data); | ||
959 | printf(" Type: Chapter index\n"); | ||
960 | printf(" Index: %d\n", $info); | ||
961 | } elsif ($type == 0x03) { | ||
962 | $info = unpack("xC", $data); | ||
963 | printf(" Type: Play status\n"); | ||
964 | printf(" Status: %s (%d)\n", ( | ||
965 | "Stopped", | ||
966 | "Playing", | ||
967 | "Paused", | ||
968 | "FF", | ||
969 | "REW", | ||
970 | "End FF/REW")[$info], $info); | ||
971 | } elsif ($type == 0x04) { | ||
972 | $info = [unpack("xCCC", $data)]; | ||
973 | printf(" Type: Mute/Volume\n"); | ||
974 | printf(" Mute State: %s\n", $info->[0]?"On":"Off"); | ||
975 | printf(" Volume level: %d\n", $info->[1]); | ||
976 | printf(" Restore on exit: %s\n", $info->[2]?"Yes":"No"); | ||
977 | } elsif ($type == 0x06) { | ||
978 | $info = [unpack("xNC", $data)]; | ||
979 | printf(" Type: Equalizer\n"); | ||
980 | printf(" Index: %d\n", $info->[0]); | ||
981 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
982 | } elsif ($type == 0x07) { | ||
983 | $info = [unpack("xCC", $data)]; | ||
984 | printf(" Type: Shuffle\n"); | ||
985 | printf(" Shuffle State: %s\n", $info->[0]?"On":"Off"); | ||
986 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
987 | } elsif ($type == 0x08) { | ||
988 | $info = [unpack("xCC", $data)]; | ||
989 | printf(" Type: Repeat\n"); | ||
990 | printf(" Repeat State: %s\n", $info->[0]?"On":"Off"); | ||
991 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
992 | } elsif ($type == 0x09) { | ||
993 | $info = [unpack("xnCCCC", $data)]; | ||
994 | printf(" Type: Date\n"); | ||
995 | printf(" Date: %02d.%02d.%04d %02d:%02d\n", $info->[2], $info->[1], $info->[0], $info->[3], $info->[4]); | ||
996 | } elsif ($type == 0x0A) { | ||
997 | $info = [unpack("xCCCC", $data)]; | ||
998 | printf(" Type: Alarm\n"); | ||
999 | printf(" Alarm State: %s\n", $info->[0]?"On":"Off"); | ||
1000 | printf(" Time: %02d:%02d\n", $info->[1], $info->[2]); | ||
1001 | printf(" Restore on exit: %s\n", $info->[3]?"Yes":"No"); | ||
1002 | } elsif ($type == 0x0B) { | ||
1003 | $info = [unpack("xCC", $data)]; | ||
1004 | printf(" Type: Backlight\n"); | ||
1005 | printf(" Backlight State: %s\n", $info->[0]?"On":"Off"); | ||
1006 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
1007 | } elsif ($type == 0x0D) { | ||
1008 | $info = [unpack("xCC", $data)]; | ||
1009 | printf(" Type: Sound check\n"); | ||
1010 | printf(" Sound check: %s\n", $info->[0]?"On":"Off"); | ||
1011 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
1012 | } elsif ($type == 0x0E) { | ||
1013 | $info = [unpack("xCC", $data)]; | ||
1014 | printf(" Type: Audiobook speed\n"); | ||
1015 | printf(" Speed: %s\n", $info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved"); | ||
1016 | printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No"); | ||
1017 | } elsif ($type == 0x0F) { | ||
1018 | $info = unpack("xN", $data); | ||
1019 | printf(" Type: Track position\n"); | ||
1020 | printf(" Position: %d s\n", $info); | ||
1021 | } elsif ($type == 0x10) { | ||
1022 | $info = [unpack("xCCCC", $data)]; | ||
1023 | printf(" Type: Mute/UI/Absolute volume\n"); | ||
1024 | printf(" Mute State: %s\n", $info->[0]?"On":"Off"); | ||
1025 | printf(" UI Volume level: %d\n", $info->[1]); | ||
1026 | printf(" Absolute Volume: %d\n", $info->[2]); | ||
1027 | printf(" Restore on exit: %s\n", $info->[3]?"Yes":"No"); | ||
1028 | } else { | ||
1029 | printf(" Reserved\n"); | ||
1030 | } | ||
1031 | |||
1032 | return 1; | ||
1033 | } | ||
1034 | |||
1035 | sub _h_03_000f { | ||
1036 | my $self = shift; | ||
1037 | my $data = shift; | ||
1038 | my $state = shift; | ||
1039 | |||
1040 | print("GetPlayStatus (0x03, 0x0F) D->I\n"); | ||
1041 | |||
1042 | return 1; | ||
1043 | } | ||
1044 | |||
1045 | sub _h_03_0010 { | ||
1046 | my $self = shift; | ||
1047 | my $data = shift; | ||
1048 | my $state = shift; | ||
1049 | my ($status, $idx, $len, $pos); | ||
1050 | |||
1051 | ($status, $idx, $len, $pos) = unpack("CNNN", $data); | ||
1052 | |||
1053 | printf("RetPlayStatus (0x03, 0x10) I->D\n"); | ||
1054 | printf(" Status: %s (%d)\n", ( | ||
1055 | "Stopped", | ||
1056 | "Playing", | ||
1057 | "Paused")[$status], $status); | ||
1058 | if ($status != 0x00) { | ||
1059 | printf(" Track index: %d\n", $idx); | ||
1060 | printf(" Track length: %d\n", $len); | ||
1061 | printf(" Track position: %d\n", $pos); | ||
1062 | } | ||
1063 | |||
1064 | return 1; | ||
1065 | } | ||
1066 | |||
1067 | sub _h_03_0012 { | ||
1068 | my $self = shift; | ||
1069 | my $data = shift; | ||
1070 | my $state = shift; | ||
1071 | my ($info, $tidx, $cidx); | ||
1072 | |||
1073 | ($info, $tidx, $cidx) = unpack("CNn", $data); | ||
1074 | |||
1075 | printf("GetIndexedPlayingTrackInfo (0x03, 0x12) D->I\n"); | ||
1076 | printf(" Requested info: %s (%d)\n", ( | ||
1077 | "Track caps/info", | ||
1078 | "Chapter time/name", | ||
1079 | "Artist name", | ||
1080 | "Album name", | ||
1081 | "Genre name", | ||
1082 | "Track title", | ||
1083 | "Composer name", | ||
1084 | "Lyrics", | ||
1085 | "Artwork count")[$info], $info); | ||
1086 | printf(" Track index: %d\n", $tidx); | ||
1087 | printf(" Chapter index: %d\n", $cidx); | ||
1088 | |||
1089 | return 1; | ||
1090 | } | ||
1091 | |||
1092 | sub _h_03_0013 { | ||
1093 | my $self = shift; | ||
1094 | my $data = shift; | ||
1095 | my $state = shift; | ||
1096 | my $info; | ||
1097 | |||
1098 | $info = unpack("C", $data); | ||
1099 | printf("RetIndexedPlayingTrackInfo (0x03, 0x13) I->D\n"); | ||
1100 | printf(" Returned info: %s (%d)\n", ( | ||
1101 | "Track caps/info", | ||
1102 | "Chapter time/name", | ||
1103 | "Artist name", | ||
1104 | "Album name", | ||
1105 | "Genre name", | ||
1106 | "Track title", | ||
1107 | "Composer name", | ||
1108 | "Lyrics", | ||
1109 | "Artwork count")[$info], $info); | ||
1110 | if ($info == 0x00) { | ||
1111 | my ($caps, $len, $chap) = unpack("xNNn", $data); | ||
1112 | printf(" Track is audiobook\n") if ($caps & 0x01); | ||
1113 | printf(" Track has chapters\n") if ($caps & 0x02); | ||
1114 | printf(" Track has artwork\n") if ($caps & 0x04); | ||
1115 | printf(" Track contains video\n") if ($caps & 0x80); | ||
1116 | printf(" Track queued as video\n") if ($caps & 0x100); | ||
1117 | |||
1118 | printf(" Track length: %d ms\n", $len); | ||
1119 | printf(" Track chapters: %d\n", $chap); | ||
1120 | } elsif ($info == 0x01) { | ||
1121 | my ($len, $name) = unpack("xNZ*"); | ||
1122 | printf(" Chapter time: %d ms\n", $len); | ||
1123 | printf(" Chapter name: %s\n", $name); | ||
1124 | } elsif ($info >= 0x02 && $info <= 0x06) { | ||
1125 | my $name = unpack("xZ*", $data); | ||
1126 | printf(" Name/Title: %s\n", $name) | ||
1127 | } elsif ($info == 0x07) { | ||
1128 | my ($info, $index, $data) = unpack("xCnZ*"); | ||
1129 | printf(" Part of multiple packets\n") if ($info & 0x01); | ||
1130 | printf(" Is last packet\n") if ($info & 0x02); | ||
1131 | printf(" Packet index: %d\n", $index); | ||
1132 | printf(" Data: %s\n", $data); | ||
1133 | } elsif ($info == 0x08) { | ||
1134 | |||
1135 | } | ||
1136 | |||
1137 | return 1; | ||
1138 | } | ||
1139 | |||
1140 | sub _h_04_0001 { | ||
1141 | my $self = shift; | ||
1142 | my $data = shift; | ||
1143 | my $state = shift; | ||
1144 | my $res; | ||
1145 | my $cmd; | ||
1146 | |||
1147 | ($res, $cmd) = unpack("Cn", $data); | ||
1148 | |||
1149 | printf("ACK (0x04, 0x0001) I->D\n"); | ||
1150 | printf(" Acknowledged command: 0x%02x\n", $cmd); | ||
1151 | printf(" Status: %s (%d)\n", | ||
1152 | ("Success", | ||
1153 | "ERROR: Unknown Database Category", | ||
1154 | "ERROR: Command Failed", | ||
1155 | "ERROR: Out Of Resource", | ||
1156 | "ERROR: Bad Parameter", | ||
1157 | "ERROR: Unknown ID", | ||
1158 | "Reserved", | ||
1159 | "ERROR: Not Authenticated")[$res], $res); | ||
1160 | |||
1161 | return 1; | ||
1162 | } | ||
1163 | |||
1164 | sub _h_04_000c { | ||
1165 | my $self = shift; | ||
1166 | my $data = shift; | ||
1167 | my $state = shift; | ||
1168 | my ($info, $track, $chapter); | ||
1169 | |||
1170 | ($info, $track, $chapter) = unpack("CNn", $data); | ||
1171 | |||
1172 | printf("GetIndexedPlayingTrackInfo (0x04, 0x000C) D->I\n"); | ||
1173 | printf(" Track: %d\n", $track); | ||
1174 | printf(" Chapter: %d\n", $chapter); | ||
1175 | printf(" Info requested: %s (%d)\n", ( | ||
1176 | "Capabilities and information", | ||
1177 | "Podcast name", | ||
1178 | "Track release date", | ||
1179 | "Track description", | ||
1180 | "Track song lyrics", | ||
1181 | "Track genre", | ||
1182 | "Track Composer", | ||
1183 | "Tracn Artwork count")[$info], $info); | ||
1184 | |||
1185 | return 1; | ||
1186 | } | ||
1187 | |||
1188 | sub _h_04_000d { | ||
1189 | my $self = shift; | ||
1190 | my $data = shift; | ||
1191 | my $state = shift; | ||
1192 | my $info; | ||
1193 | |||
1194 | $info = unpack("C", $data); | ||
1195 | |||
1196 | printf("ReturnIndexedPlayingTrackInfo (0x04, 0x000D) I->D\n"); | ||
1197 | if ($info == 0x00) { | ||
1198 | my ($capability, $length, $chapter); | ||
1199 | |||
1200 | ($capability, $length, $chapter) = unpack("xNNn", $data); | ||
1201 | printf(" Capabilities:\n"); | ||
1202 | printf(" Is audiobook\n") if ($capability & 0x00000001); | ||
1203 | printf(" Has chapters\n") if ($capability & 0x00000002); | ||
1204 | printf(" Has album artwork\n") if ($capability & 0x00000004); | ||
1205 | printf(" Has song lyrics\n") if ($capability & 0x00000008); | ||
1206 | printf(" Is a podcast episode\n") if ($capability & 0x00000010); | ||
1207 | printf(" Has release date\n") if ($capability & 0x00000020); | ||
1208 | printf(" Has description\n") if ($capability & 0x00000040); | ||
1209 | printf(" Contains video\n") if ($capability & 0x00000080); | ||
1210 | printf(" Queued to play as video\n") if ($capability & 0x00000100); | ||
1211 | |||
1212 | printf(" Length: %d ms\n", $length); | ||
1213 | printf(" Chapters: %d\n", $chapter); | ||
1214 | } else { | ||
1215 | printf(" WARNING: Unknown info\n"); | ||
1216 | return 1; | ||
1217 | } | ||
1218 | |||
1219 | return 1; | ||
1220 | } | ||
1221 | |||
1222 | sub _h_04_0012 { | ||
1223 | my $self = shift; | ||
1224 | my $data = shift; | ||
1225 | my $state = shift; | ||
1226 | |||
1227 | printf("RequestProtocolVersion (0x04, 0x0012) D->I\n"); | ||
1228 | |||
1229 | return 1; | ||
1230 | } | ||
1231 | |||
1232 | sub _h_04_0013 { | ||
1233 | my $self = shift; | ||
1234 | my $data = shift; | ||
1235 | my $state = shift; | ||
1236 | my ($maj, $min); | ||
1237 | |||
1238 | ($maj, $min) = unpack("CC", $data); | ||
1239 | |||
1240 | printf("ReturnProtocolVersion (0x04, 0x0013) I->D\n"); | ||
1241 | printf(" Lingo 0x04 version: %d.%02d\n", $maj, $min); | ||
1242 | |||
1243 | return 1; | ||
1244 | } | ||
1245 | |||
1246 | sub _h_04_0016 { | ||
1247 | my $self = shift; | ||
1248 | my $data = shift; | ||
1249 | my $state = shift; | ||
1250 | |||
1251 | printf("ResetDBSelection (0x04, 0x0016) D->I\n"); | ||
1252 | |||
1253 | return 1; | ||
1254 | } | ||
1255 | |||
1256 | sub _h_04_0018 { | ||
1257 | my $self = shift; | ||
1258 | my $data = shift; | ||
1259 | my $state = shift; | ||
1260 | my $category; | ||
1261 | |||
1262 | $category = unpack("C", $data); | ||
1263 | |||
1264 | printf("GetNumberCategorizedDBRecords (0x04, 0x0018) D->I\n"); | ||
1265 | printf(" Category: %s (%d)\n", ( | ||
1266 | "Reserved", | ||
1267 | "Playlist", | ||
1268 | "Artist", | ||
1269 | "Album", | ||
1270 | "Genre", | ||
1271 | "Track", | ||
1272 | "Composer", | ||
1273 | "Audiobook", | ||
1274 | "Podcast", | ||
1275 | "Nested Playlist")[$category], $category); | ||
1276 | |||
1277 | return 1; | ||
1278 | } | ||
1279 | |||
1280 | sub _h_04_0019 { | ||
1281 | my $self = shift; | ||
1282 | my $data = shift; | ||
1283 | my $state = shift; | ||
1284 | my $count; | ||
1285 | |||
1286 | $count = unpack("N", $data); | ||
1287 | |||
1288 | printf("ReturnNumberCategorizedDBRecords (0x04, 0x0019) I->D\n"); | ||
1289 | printf(" Count: %d\n", $count); | ||
1290 | |||
1291 | return 1; | ||
1292 | } | ||
1293 | |||
1294 | sub _h_04_001c { | ||
1295 | my $self = shift; | ||
1296 | my $data = shift; | ||
1297 | my $state = shift; | ||
1298 | |||
1299 | printf("GetPlayStatus (0x04, 0x001C) D->I\n"); | ||
1300 | |||
1301 | return 1; | ||
1302 | } | ||
1303 | |||
1304 | sub _h_04_001d { | ||
1305 | my $self = shift; | ||
1306 | my $data = shift; | ||
1307 | my $state = shift; | ||
1308 | my ($len, $pos, $s); | ||
1309 | |||
1310 | ($len, $pos, $s) = unpack("NNC", $data); | ||
1311 | |||
1312 | printf("ReturnPlayStatus (0x04, 0x001D) I->D\n"); | ||
1313 | printf(" Song length: %d ms\n", $len); | ||
1314 | printf(" Song position: %d ms\n", $pos); | ||
1315 | printf(" Player state: "); | ||
1316 | if ($s == 0x00) { | ||
1317 | printf("Stopped\n"); | ||
1318 | } elsif ($s == 0x01) { | ||
1319 | printf("Playing\n"); | ||
1320 | } elsif ($s == 0x02) { | ||
1321 | printf("Paused\n"); | ||
1322 | } elsif ($s == 0xFF) { | ||
1323 | printf("Error\n"); | ||
1324 | } else { | ||
1325 | printf("Reserved\n"); | ||
1326 | } | ||
1327 | |||
1328 | return 1; | ||
1329 | } | ||
1330 | |||
1331 | sub _h_04_001e { | ||
1332 | my $self = shift; | ||
1333 | my $data = shift; | ||
1334 | my $state = shift; | ||
1335 | |||
1336 | printf("GetCurrentPlayingTrackIndex (0x04, 0x001E) D->I\n"); | ||
1337 | |||
1338 | return 1; | ||
1339 | } | ||
1340 | |||
1341 | sub _h_04_001f { | ||
1342 | my $self = shift; | ||
1343 | my $data = shift; | ||
1344 | my $state = shift; | ||
1345 | my $num; | ||
1346 | |||
1347 | $num = unpack("N", $data); | ||
1348 | |||
1349 | printf("ReturnCurrentPlayingTrackIndex (0x04, 0x001F) I->D\n"); | ||
1350 | printf(" Index: %d\n", $num); | ||
1351 | |||
1352 | return 1; | ||
1353 | } | ||
1354 | |||
1355 | sub _h_04_0020 { | ||
1356 | my $self = shift; | ||
1357 | my $data = shift; | ||
1358 | my $state = shift; | ||
1359 | my $track; | ||
1360 | |||
1361 | $track = unpack("N", $data); | ||
1362 | |||
1363 | printf("GetIndexedPlayingTrackTitle (0x04, 0x0020) D->I\n"); | ||
1364 | printf(" Track: %d\n", $track); | ||
1365 | |||
1366 | return 1; | ||
1367 | } | ||
1368 | |||
1369 | sub _h_04_0021 { | ||
1370 | my $self = shift; | ||
1371 | my $data = shift; | ||
1372 | my $state = shift; | ||
1373 | my $title; | ||
1374 | |||
1375 | $title = unpack("Z*", $data); | ||
1376 | |||
1377 | printf("ReturnIndexedPlayingTrackTitle (0x04, 0x0021) I->D\n"); | ||
1378 | printf(" Title: %s\n", $title); | ||
1379 | |||
1380 | return 1; | ||
1381 | } | ||
1382 | |||
1383 | sub _h_04_0022 { | ||
1384 | my $self = shift; | ||
1385 | my $data = shift; | ||
1386 | my $state = shift; | ||
1387 | my $track; | ||
1388 | |||
1389 | $track = unpack("N", $data); | ||
1390 | |||
1391 | printf("GetIndexedPlayingTrackArtistName (0x04, 0x0022) D->I\n"); | ||
1392 | printf(" Track: %d\n", $track); | ||
1393 | |||
1394 | return 1; | ||
1395 | } | ||
1396 | |||
1397 | sub _h_04_0023 { | ||
1398 | my $self = shift; | ||
1399 | my $data = shift; | ||
1400 | my $state = shift; | ||
1401 | my $artist; | ||
1402 | |||
1403 | $artist = unpack("Z*", $data); | ||
1404 | |||
1405 | printf("ReturnIndexedPlayingTrackArtistName (0x04, 0x0023) I->D\n"); | ||
1406 | printf(" Artist: %s\n", $artist); | ||
1407 | |||
1408 | return 1; | ||
1409 | } | ||
1410 | |||
1411 | sub _h_04_0024 { | ||
1412 | my $self = shift; | ||
1413 | my $data = shift; | ||
1414 | my $state = shift; | ||
1415 | my $track; | ||
1416 | |||
1417 | $track = unpack("N", $data); | ||
1418 | |||
1419 | printf("GetIndexedPlayingTrackAlbumName (0x04, 0x0024) D->I\n"); | ||
1420 | printf(" Track: %d\n", $track); | ||
1421 | |||
1422 | return 1; | ||
1423 | } | ||
1424 | |||
1425 | sub _h_04_0025 { | ||
1426 | my $self = shift; | ||
1427 | my $data = shift; | ||
1428 | my $state = shift; | ||
1429 | my $title; | ||
1430 | |||
1431 | $title = unpack("Z*", $data); | ||
1432 | |||
1433 | printf("ReturnIndexedPlayingTrackAlbumName (0x04, 0x0025) I->D\n"); | ||
1434 | printf(" Album: %s\n", $title); | ||
1435 | |||
1436 | return 1; | ||
1437 | } | ||
1438 | |||
1439 | sub _h_04_0026 { | ||
1440 | my $self = shift; | ||
1441 | my $data = shift; | ||
1442 | my $state = shift; | ||
1443 | my $notification; | ||
1444 | |||
1445 | if (length($data) == 1) { | ||
1446 | $notification = unpack("C", $data); | ||
1447 | } elsif (length($data) == 4) { | ||
1448 | $notification = unpack("N", $data); | ||
1449 | } | ||
1450 | |||
1451 | printf("SetPlayStatusChangeNotification (0x04, 0x0026) D->I\n"); | ||
1452 | |||
1453 | if (length($data) == 1) { | ||
1454 | printf(" Events for: %s (%d)\n", ( | ||
1455 | "Disable all", | ||
1456 | "Basic play state, track index, track time position, FFW/REW seek stop, and chapter index changes")[$notification], $notification); | ||
1457 | } elsif (length($data) == 4) { | ||
1458 | printf(" Events for:\n"); | ||
1459 | printf(" Basic play state changes\n") if ($notification & 0x00000001); | ||
1460 | printf(" Extended play state changes\n") if ($notification & 0x00000002); | ||
1461 | printf(" Track index\n") if ($notification & 0x00000004); | ||
1462 | printf(" Track time offset (ms)\n") if ($notification & 0x00000008); | ||
1463 | printf(" Track time offset (s)\n") if ($notification & 0x00000010); | ||
1464 | printf(" Chapter index\n") if ($notification & 0x00000020); | ||
1465 | printf(" Chapter time offset (ms)\n") if ($notification & 0x00000040); | ||
1466 | printf(" Chapter time offset (s)\n") if ($notification & 0x00000080); | ||
1467 | printf(" Track unique identifier\n") if ($notification & 0x00000100); | ||
1468 | printf(" Track media tyoe\n") if ($notification & 0x00000200); | ||
1469 | printf(" Track lyrics\n") if ($notification & 0x00000400); | ||
1470 | } else { | ||
1471 | printf(" WARNING: Unknown length for state\n"); | ||
1472 | return 0; | ||
1473 | } | ||
1474 | |||
1475 | return 1; | ||
1476 | } | ||
1477 | |||
1478 | sub _h_04_0027 { | ||
1479 | my $self = shift; | ||
1480 | my $data = shift; | ||
1481 | my $state = shift; | ||
1482 | my $info; | ||
1483 | |||
1484 | $info = unpack("C", $data); | ||
1485 | |||
1486 | printf("PlayStatusChangeNotification (0x04, 0x0029) I->D\n"); | ||
1487 | printf(" Status:\n"); | ||
1488 | if ($info == 0x00) { | ||
1489 | printf(" Playback stopped\n"); | ||
1490 | } elsif ($info == 0x01) { | ||
1491 | my $index = unpack("xN", $data); | ||
1492 | |||
1493 | printf(" Track index: %d\n", $index); | ||
1494 | } elsif ($info == 0x02) { | ||
1495 | printf(" Playback FF seek stop\n"); | ||
1496 | } elsif ($info == 0x03) { | ||
1497 | printf(" Playback REW seek stop\n"); | ||
1498 | } elsif ($info == 0x04) { | ||
1499 | my $offset = unpack("xN", $data); | ||
1500 | |||
1501 | printf(" Track time offset: %d ms\n", $offset); | ||
1502 | } elsif ($info == 0x05) { | ||
1503 | my $index = unpack("xN", $data); | ||
1504 | |||
1505 | printf(" Chapter index: %d\n", $index); | ||
1506 | } elsif ($info == 0x06) { | ||
1507 | my $status = unpack("xC", $data); | ||
1508 | |||
1509 | printf(" Playback status extended: %s (%d)\n", ( | ||
1510 | "Reserved", | ||
1511 | "Reserved", | ||
1512 | "Stopped", | ||
1513 | "Reserved", | ||
1514 | "Reserved", | ||
1515 | "FF seek started", | ||
1516 | "REW seek started", | ||
1517 | "FF/REW seek stopped", | ||
1518 | "Reserved", | ||
1519 | "Reserved", | ||
1520 | "Playing", | ||
1521 | "Paused")[$status], $status); | ||
1522 | } elsif ($info == 0x07) { | ||
1523 | my $offset = unpack("xN", $data); | ||
1524 | |||
1525 | printf(" Track time offset: %d s\n", $offset); | ||
1526 | } elsif ($info == 0x08) { | ||
1527 | my $offset = unpack("xN", $data); | ||
1528 | |||
1529 | printf(" Chapter time offset %d ms\n", $offset); | ||
1530 | } elsif ($info == 0x09) { | ||
1531 | my $offset = unpack("xN", $data); | ||
1532 | |||
1533 | printf(" Chapter time offset %d s\n", $offset); | ||
1534 | } elsif ($info == 0x0A) { | ||
1535 | my ($uidhi, $uidlo) = unpack("xNN", $data); | ||
1536 | |||
1537 | printf(" Track UID: %08x%08x\n", $uidhi, $uidlo); | ||
1538 | } elsif ($info == 0x0B) { | ||
1539 | my $mode = unpack("xC", $data); | ||
1540 | |||
1541 | printf(" Track mode: %s (%d)\n", ( | ||
1542 | "Audio track", | ||
1543 | "Video track")[$mode], $mode); | ||
1544 | } elsif ($info == 0x0C) { | ||
1545 | printf(" Track lyrics ready\n"); | ||
1546 | } else { | ||
1547 | printf(" Reserved\n"); | ||
1548 | } | ||
1549 | |||
1550 | return 1; | ||
1551 | } | ||
1552 | |||
1553 | sub _h_04_0029 { | ||
1554 | my $self = shift; | ||
1555 | my $data = shift; | ||
1556 | my $state = shift; | ||
1557 | my $control; | ||
1558 | |||
1559 | $control = unpack("C", $data); | ||
1560 | |||
1561 | printf("PlayControl (0x04, 0x0029) D->I\n"); | ||
1562 | printf(" Command: %s (%d)\n", ( | ||
1563 | "Reserved", | ||
1564 | "Toggle Play/Pause", | ||
1565 | "Stop", | ||
1566 | "Next track", | ||
1567 | "Previous track", | ||
1568 | "Start FF", | ||
1569 | "Start Rev", | ||
1570 | "Stop FF/Rev", | ||
1571 | "Next", | ||
1572 | "Previous", | ||
1573 | "Play", | ||
1574 | "Pause", | ||
1575 | "Next chapter", | ||
1576 | "Previous chapter")[$control], $control); | ||
1577 | |||
1578 | return 1; | ||
1579 | } | ||
1580 | |||
1581 | sub _h_04_002c { | ||
1582 | my $self = shift; | ||
1583 | my $data = shift; | ||
1584 | my $state = shift; | ||
1585 | |||
1586 | printf("GetShuffle (0x04, 0x002C) D->I\n"); | ||
1587 | |||
1588 | return 1; | ||
1589 | } | ||
1590 | |||
1591 | sub _h_04_002d { | ||
1592 | my $self = shift; | ||
1593 | my $data = shift; | ||
1594 | my $state = shift; | ||
1595 | my $mode; | ||
1596 | |||
1597 | $mode = unpack("C", $data); | ||
1598 | |||
1599 | printf("ReturnShuffle (0x04, 0x002D) I->D\n"); | ||
1600 | printf(" Mode: %s (%d)\n", ( | ||
1601 | "Off", | ||
1602 | "Tracks", | ||
1603 | "Albums")[$mode], $mode); | ||
1604 | |||
1605 | return 1; | ||
1606 | } | ||
1607 | |||
1608 | sub _h_04_002f { | ||
1609 | my $self = shift; | ||
1610 | my $data = shift; | ||
1611 | my $state = shift; | ||
1612 | |||
1613 | printf("GetRepeat (0x04, 0x002F) D->I\n"); | ||
1614 | |||
1615 | return 1; | ||
1616 | } | ||
1617 | |||
1618 | sub _h_04_0030 { | ||
1619 | my $self = shift; | ||
1620 | my $data = shift; | ||
1621 | my $state = shift; | ||
1622 | my $mode; | ||
1623 | |||
1624 | $mode = unpack("C", $data); | ||
1625 | |||
1626 | printf("ReturnRepeat (0x04, 0x0030) I->D\n"); | ||
1627 | printf(" Mode: %s (%d)\n", ( | ||
1628 | "Off", | ||
1629 | "One Track", | ||
1630 | "All Tracks")[$mode], $mode); | ||
1631 | |||
1632 | return 1; | ||
1633 | } | ||
1634 | |||
1635 | sub _h_04_0032 { | ||
1636 | my $self = shift; | ||
1637 | my $data = shift; | ||
1638 | my $state = shift; | ||
1639 | my ($index, $format, $width, $height, $stride, $imagedata); | ||
1640 | |||
1641 | $state->{-index} = -1 unless(exists($state->{-index})); | ||
1642 | $state->{-image} = '' unless(exists($state->{-image})); | ||
1643 | |||
1644 | ($index, $format, $width, $height, $stride, $imagedata) = unpack("nCnnNa*", $data); | ||
1645 | |||
1646 | printf("SetDisplayImage (0x04, 0x0032) D->I\n"); | ||
1647 | if ($index == 0) { | ||
1648 | printf(" Width: %d\n", $width); | ||
1649 | printf(" Height: %d\n", $height); | ||
1650 | printf(" Stride: %d\n", $stride); | ||
1651 | printf(" Format: %s (%d)\n", ( | ||
1652 | "Reserved", | ||
1653 | "Monochrome, 2 bps", | ||
1654 | "RGB 565, little endian", | ||
1655 | "RGB 565, big endian")[$format], $format); | ||
1656 | |||
1657 | $state->{-imagelength} = $height * $stride; | ||
1658 | } else { | ||
1659 | ($index, $imagedata) = unpack("na*", $data); | ||
1660 | } | ||
1661 | |||
1662 | if ($index-1 != $state->{-index}) { | ||
1663 | printf(" WARNING! Out of order segment\n"); | ||
1664 | return 0; | ||
1665 | } | ||
1666 | |||
1667 | $state->{-index} = $index; | ||
1668 | $state->{-image} .= $imagedata; | ||
1669 | |||
1670 | if (length($state->{-image}) >= $state->{-imagelength}) { | ||
1671 | printf(" Image data: %s\n", Device::iPod->_hexstring($state->{-image})); | ||
1672 | } | ||
1673 | |||
1674 | return 1; | ||
1675 | } | ||
1676 | |||
1677 | sub _h_04_0033 { | ||
1678 | my $self = shift; | ||
1679 | my $data = shift; | ||
1680 | my $state = shift; | ||
1681 | |||
1682 | printf("GetMonoDisplayImageLimits (0x04, 0x0033) D->I\n"); | ||
1683 | |||
1684 | return 1; | ||
1685 | } | ||
1686 | |||
1687 | sub _h_04_0034 { | ||
1688 | my $self = shift; | ||
1689 | my $data = shift; | ||
1690 | my $state = shift; | ||
1691 | my ($width, $height, $format); | ||
1692 | |||
1693 | ($width, $height, $format) = unpack("nnC", $data); | ||
1694 | |||
1695 | printf("ReturnMonoDisplayImageLimits (0x04, 0x0034) I->D\n"); | ||
1696 | printf(" Width: %d\n", $width); | ||
1697 | printf(" Height: %d\n", $height); | ||
1698 | printf(" Format: %s (%d)\n", ( | ||
1699 | "Reserved", | ||
1700 | "Monochrome, 2 bps", | ||
1701 | "RGB 565, little endian", | ||
1702 | "RGB 565, big endian")[$format], $format); | ||
1703 | |||
1704 | return 1; | ||
1705 | } | ||
1706 | |||
1707 | sub _h_04_0035 { | ||
1708 | my $self = shift; | ||
1709 | my $data = shift; | ||
1710 | my $state = shift; | ||
1711 | |||
1712 | printf("GetNumPlayingTracks (0x04, 0x0035) D->I\n"); | ||
1713 | |||
1714 | return 1; | ||
1715 | } | ||
1716 | |||
1717 | sub _h_04_0036 { | ||
1718 | my $self = shift; | ||
1719 | my $data = shift; | ||
1720 | my $state = shift; | ||
1721 | my $num; | ||
1722 | |||
1723 | $num = unpack("N", $data); | ||
1724 | |||
1725 | printf("ReturnNumPlayingTracks (0x04, 0x0036) I->D\n"); | ||
1726 | printf(" Number: %d\n", $num); | ||
1727 | |||
1728 | return 1; | ||
1729 | } | ||
1730 | |||
1731 | sub _h_04_0037 { | ||
1732 | my $self = shift; | ||
1733 | my $data = shift; | ||
1734 | my $state = shift; | ||
1735 | my $num; | ||
1736 | |||
1737 | $num = unpack("N", $data); | ||
1738 | |||
1739 | printf("SetCurrentPlayingTrack (0x04, 0x0037) D->I\n"); | ||
1740 | printf(" Track: %d\n", $num); | ||
1741 | |||
1742 | return 1; | ||
1743 | } | ||
1744 | |||
1745 | sub _h_07_0005 { | ||
1746 | my $self = shift; | ||
1747 | my $data = shift; | ||
1748 | my $state = shift; | ||
1749 | my $control; | ||
1750 | |||
1751 | $control = unpack("C", $data); | ||
1752 | |||
1753 | printf("SetTunerCtrl (0x07, 0x05) I->D\n"); | ||
1754 | printf(" Options:\n"); | ||
1755 | printf(" Power %s\n", ($control & 0x01)?"on":"off"); | ||
1756 | printf(" Status change notifications %s\n", ($control & 0x02)?"on":"off"); | ||
1757 | printf(" Raw mode %s\n", ($control & 0x04)?"on":"off"); | ||
1758 | } | ||
1759 | |||
1760 | sub _h_07_0020 { | ||
1761 | my $self = shift; | ||
1762 | my $data = shift; | ||
1763 | my $state = shift; | ||
1764 | my $options; | ||
1765 | |||
1766 | $options = unpack("N", $data); | ||
1767 | |||
1768 | printf("SetRDSNotifyMask (0x07, 0x20) I->D\n"); | ||
1769 | printf(" Options:\n"); | ||
1770 | printf(" Radiotext\n") if ($options & 0x00000010); | ||
1771 | printf(" Program Service Name\n") if ($options & 0x40000000); | ||
1772 | printf(" Reserved\n") if ($options & 0xBFFFFFEF); | ||
1773 | |||
1774 | return 1; | ||
1775 | } | ||
1776 | |||
1777 | sub _h_07_0024 { | ||
1778 | my $self = shift; | ||
1779 | my $data = shift; | ||
1780 | my $state = shift; | ||
1781 | |||
1782 | printf("Reserved command (0x07, 0x24) I->D\n"); | ||
1783 | |||
1784 | return 1; | ||
1785 | } | ||
1786 | |||
1787 | |||
1788 | package main; | ||
1789 | |||
1790 | use Device::iPod; | ||
1791 | use Getopt::Long; | ||
1792 | use strict; | ||
1793 | |||
1794 | my $decoder; | ||
1795 | my $device; | ||
1796 | my $unpacker; | ||
1797 | my $line; | ||
1798 | |||
1799 | sub unpack_hexstring { | ||
1800 | my $line = shift; | ||
1801 | my $m; | ||
1802 | my @m; | ||
1803 | |||
1804 | $line =~ s/(..)/chr(hex($1))/ge; | ||
1805 | $device->{-inbuf} = $line; | ||
1806 | |||
1807 | $m = $device->_message(); | ||
1808 | next unless defined($m); | ||
1809 | @m = $device->_unframe_cmd($m); | ||
1810 | unless(@m) { | ||
1811 | printf("Line %d: Error decoding frame: %s\n", $., $device->error()); | ||
1812 | return (); | ||
1813 | } | ||
1814 | |||
1815 | return @m; | ||
1816 | } | ||
1817 | |||
1818 | sub unpack_iaplog { | ||
1819 | my $line = shift; | ||
1820 | my @m; | ||
1821 | |||
1822 | unless ($line =~ /^(?:\[\d+\] )?[RT]::? /) { | ||
1823 | printf("Skipped: %s\n", $line); | ||
1824 | return (); | ||
1825 | } | ||
1826 | |||
1827 | $line =~ s/^(?:\[\d+\] )?[RT]::? //; | ||
1828 | $line =~ s/\\x(..)/chr(hex($1))/ge; | ||
1829 | $line =~ s/\\\\/\\/g; | ||
1830 | |||
1831 | @m = unpack("CCa*", $line); | ||
1832 | if ($m[0] == 0x04) { | ||
1833 | @m = unpack("Cna*", $line); | ||
1834 | } | ||
1835 | |||
1836 | return @m; | ||
1837 | } | ||
1838 | |||
1839 | |||
1840 | $decoder = iap::decode->new(); | ||
1841 | $device = Device::iPod->new(); | ||
1842 | $unpacker = \&unpack_iaplog; | ||
1843 | |||
1844 | GetOptions("hexstring" => sub {$unpacker = \&unpack_hexstring}); | ||
1845 | |||
1846 | while ($line = <>) { | ||
1847 | my @m; | ||
1848 | |||
1849 | chomp($line); | ||
1850 | |||
1851 | @m = $unpacker->($line); | ||
1852 | next unless (@m); | ||
1853 | |||
1854 | printf("Line %d: ", $.); | ||
1855 | $decoder->display(@m); | ||
1856 | } | ||
diff --git a/tools/iap/ipod-001-general.t b/tools/iap/ipod-001-general.t new file mode 100644 index 0000000000..f2b5451dbc --- /dev/null +++ b/tools/iap/ipod-001-general.t | |||
@@ -0,0 +1,133 @@ | |||
1 | use Test::More qw( no_plan ); | ||
2 | use strict; | ||
3 | |||
4 | BEGIN { use_ok('Device::iPod'); } | ||
5 | require_ok('Device::iPod'); | ||
6 | |||
7 | my $ipod = Device::iPod->new(); | ||
8 | my $m; | ||
9 | my ($l, $c, $p); | ||
10 | |||
11 | isa_ok($ipod, 'Device::iPod'); | ||
12 | |||
13 | $ipod->{-debug} = 1; | ||
14 | $ipod->open("/dev/ttyUSB0"); | ||
15 | |||
16 | $m = $ipod->sendraw("\xFF" x 16); # Wake up and sync | ||
17 | ok($m == 1, "Wakeup sent"); | ||
18 | |||
19 | # Empty the buffer | ||
20 | $ipod->emptyrecv(); | ||
21 | |||
22 | # Send a command with wrong checksum | ||
23 | # We expect no response (Timeout) | ||
24 | $m = $ipod->sendraw("\xff\x55\x02\x00\x03\x00"); | ||
25 | ok($m == 1, "Broken checksum sent"); | ||
26 | ($l, $c, $p) = $ipod->recvmsg(); | ||
27 | subtest "Timeout" => sub { | ||
28 | ok(!defined($l), "No response received"); | ||
29 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
30 | }; | ||
31 | |||
32 | # Empty the buffer | ||
33 | $ipod->emptyrecv(); | ||
34 | |||
35 | # Send a too short command | ||
36 | # We expect an ACK Bad Parameter as response | ||
37 | $m = $ipod->sendmsg(0x00, 0x13, ""); | ||
38 | ok($m == 1, "Short command sent"); | ||
39 | ($l, $c, $p) = $ipod->recvmsg(); | ||
40 | subtest "ACK Bad Parameter" => sub { | ||
41 | ok(defined($l), "Response received"); | ||
42 | is($l, 0x00, "Response lingo"); | ||
43 | is($c, 0x02, "Response command"); | ||
44 | is($p, "\x04\x13", "Response payload"); | ||
45 | }; | ||
46 | |||
47 | # Empty the buffer | ||
48 | $ipod->emptyrecv(); | ||
49 | |||
50 | # Send an undefined lingo | ||
51 | # We expect a timeout | ||
52 | $m = $ipod->sendmsg(0x1F, 0x00); | ||
53 | ok($m == 1, "Undefined lingo sent"); | ||
54 | ($l, $c, $p) = $ipod->recvmsg(); | ||
55 | subtest "Timeout" => sub { | ||
56 | ok(!defined($l), "No response received"); | ||
57 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
58 | }; | ||
59 | |||
60 | # Empty the buffer | ||
61 | $ipod->emptyrecv(); | ||
62 | |||
63 | # IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00) | ||
64 | # We expect an ACK Command Failed message as response | ||
65 | $m = $ipod->sendmsg(0x00, 0x13, "\x80\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
66 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00) sent"); | ||
67 | ($l, $c, $p) = $ipod->recvmsg(); | ||
68 | subtest "ACK Command Failed" => sub { | ||
69 | ok(defined($l), "Response received"); | ||
70 | is($l, 0x00, "Response lingo"); | ||
71 | is($c, 0x02, "Response command"); | ||
72 | is($p, "\x02\x13", "Response payload"); | ||
73 | }; | ||
74 | |||
75 | # Empty the buffer | ||
76 | $ipod->emptyrecv(); | ||
77 | |||
78 | # Identify(lingo=0xFF) | ||
79 | # We expect no response (timeout) | ||
80 | $m = $ipod->sendmsg(0x00, 0x01, "\xFF"); | ||
81 | ok($m == 1, "Identify(lingo=0xFF) sent"); | ||
82 | ($l, $c, $p) = $ipod->recvmsg(); | ||
83 | subtest "Timeout" => sub { | ||
84 | ok(!defined($l), "No response received"); | ||
85 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
86 | }; | ||
87 | |||
88 | # Empty the buffer | ||
89 | $ipod->emptyrecv(); | ||
90 | |||
91 | # IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00) | ||
92 | # We expect an ACK Command Failed message as response | ||
93 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
94 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00) sent"); | ||
95 | ($l, $c, $p) = $ipod->recvmsg(); | ||
96 | subtest "ACK Command Failed" => sub { | ||
97 | ok(defined($l), "Response received"); | ||
98 | is($l, 0x00, "Response lingo"); | ||
99 | is($c, 0x02, "Response command"); | ||
100 | is($p, "\x02\x13", "Response payload"); | ||
101 | }; | ||
102 | |||
103 | # Empty the buffer | ||
104 | $ipod->emptyrecv(); | ||
105 | # IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00) | ||
106 | # We expect an ACK Command Failed message as response | ||
107 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
108 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00) sent"); | ||
109 | ($l, $c, $p) = $ipod->recvmsg(); | ||
110 | subtest "ACK Command Failed" => sub { | ||
111 | ok(defined($l), "Response received"); | ||
112 | is($l, 0x00, "Response lingo"); | ||
113 | is($c, 0x02, "Response command"); | ||
114 | is($p, "\x02\x13", "Response payload"); | ||
115 | }; | ||
116 | |||
117 | # Empty the buffer | ||
118 | $ipod->emptyrecv(); | ||
119 | |||
120 | # RequestLingoProtocolVersion(lingo=0xFF) | ||
121 | # We expect an ACK Bad Parameter message as response | ||
122 | $m = $ipod->sendmsg(0x00, 0x0F, "\xFF"); | ||
123 | ok($m == 1, "RequestLingoProtocolVersion(lingo=0xFF) sent"); | ||
124 | ($l, $c, $p) = $ipod->recvmsg(); | ||
125 | subtest "ACK Bad Parameter" => sub { | ||
126 | ok(defined($l), "Response received"); | ||
127 | is($l, 0x00, "Response lingo"); | ||
128 | is($c, 0x02, "Response command"); | ||
129 | is($p, "\x04\x0F", "Response payload"); | ||
130 | }; | ||
131 | |||
132 | # Empty the buffer | ||
133 | $ipod->emptyrecv(); | ||
diff --git a/tools/iap/ipod-002-lingo0.t b/tools/iap/ipod-002-lingo0.t new file mode 100644 index 0000000000..c3bb676553 --- /dev/null +++ b/tools/iap/ipod-002-lingo0.t | |||
@@ -0,0 +1,277 @@ | |||
1 | use Test::More qw( no_plan ); | ||
2 | use strict; | ||
3 | |||
4 | BEGIN { use_ok('Device::iPod'); } | ||
5 | require_ok('Device::iPod'); | ||
6 | |||
7 | my $ipod = Device::iPod->new(); | ||
8 | my $m; | ||
9 | my ($l, $c, $p); | ||
10 | |||
11 | isa_ok($ipod, 'Device::iPod'); | ||
12 | |||
13 | $ipod->{-debug} = 1; | ||
14 | $ipod->open("/dev/ttyUSB0"); | ||
15 | |||
16 | $m = $ipod->sendraw("\xFF" x 16); # Wake up and sync | ||
17 | ok($m == 1, "Wakeup sent"); | ||
18 | |||
19 | # Empty the buffer | ||
20 | $ipod->emptyrecv(); | ||
21 | |||
22 | # IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) | ||
23 | # We expect an ACK OK message as response | ||
24 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
25 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent"); | ||
26 | ($l, $c, $p) = $ipod->recvmsg(); | ||
27 | subtest "ACK OK" => sub { | ||
28 | ok(defined($l), "Response received"); | ||
29 | is($l, 0x00, "Response lingo"); | ||
30 | is($c, 0x02, "Response command"); | ||
31 | is($p, "\x00\x13", "Response payload"); | ||
32 | }; | ||
33 | |||
34 | # Empty the buffer | ||
35 | $ipod->emptyrecv(); | ||
36 | |||
37 | # RequestRemoteUIMode | ||
38 | # We expect an ACK Bad Parameter as response, as we have not | ||
39 | # negotiated lingo 0x04 | ||
40 | $m = $ipod->sendmsg(0x00, 0x03); | ||
41 | ok($m == 1, "RequestRemoteUIMode sent"); | ||
42 | ($l, $c, $p) = $ipod->recvmsg(); | ||
43 | subtest "ACK Bad Parameter" => sub { | ||
44 | ok(defined($l), "Response received"); | ||
45 | is($l, 0x00, "Response lingo"); | ||
46 | is($c, 0x02, "Response command"); | ||
47 | is($p, "\x04\x03", "Response payload"); | ||
48 | }; | ||
49 | |||
50 | # Empty the buffer | ||
51 | $ipod->emptyrecv(); | ||
52 | |||
53 | # EnterRemoteUIMode | ||
54 | # We expect an ACK Bad Parameter as response, as we have not | ||
55 | # negotiated lingo 0x04 | ||
56 | $m = $ipod->sendmsg(0x00, 0x05); | ||
57 | ok($m == 1, "EnterRemoteUIMode sent"); | ||
58 | ($l, $c, $p) = $ipod->recvmsg(); | ||
59 | subtest "ACK Bad Parameter" => sub { | ||
60 | ok(defined($l), "Response received"); | ||
61 | is($l, 0x00, "Response lingo"); | ||
62 | is($c, 0x02, "Response command"); | ||
63 | is($p, "\x04\x05", "Response payload"); | ||
64 | }; | ||
65 | |||
66 | # Empty the buffer | ||
67 | $ipod->emptyrecv(); | ||
68 | |||
69 | # ExitRemoteUIMode | ||
70 | # We expect an ACK Bad Parameter as response, as we have not | ||
71 | # negotiated lingo 0x04 | ||
72 | $m = $ipod->sendmsg(0x00, 0x06); | ||
73 | ok($m == 1, "ExitRemoteUIMode sent"); | ||
74 | ($l, $c, $p) = $ipod->recvmsg(); | ||
75 | subtest "ACK Bad Parameter" => sub { | ||
76 | ok(defined($l), "Response received"); | ||
77 | is($l, 0x00, "Response lingo"); | ||
78 | is($c, 0x02, "Response command"); | ||
79 | is($p, "\x04\x06", "Response payload"); | ||
80 | }; | ||
81 | |||
82 | # Empty the buffer | ||
83 | $ipod->emptyrecv(); | ||
84 | |||
85 | # RequestiPodName | ||
86 | # We expect a ReturniPodName packet | ||
87 | $m = $ipod->sendmsg(0x00, 0x07); | ||
88 | ok($m == 1, "RequestiPodName sent"); | ||
89 | ($l, $c, $p) = $ipod->recvmsg(); | ||
90 | subtest "ReturniPodName" => sub { | ||
91 | ok(defined($l), "Response received"); | ||
92 | is($l, 0x00, "Response lingo"); | ||
93 | is($c, 0x08, "Response command"); | ||
94 | like($p, "/^[^\\x00]*\\x00\$/", "Response payload"); | ||
95 | }; | ||
96 | |||
97 | # Empty the buffer | ||
98 | $ipod->emptyrecv(); | ||
99 | |||
100 | # RequestiPodSoftwareVersion | ||
101 | # We expect a ReturniPodSoftwareVersion packet | ||
102 | $m = $ipod->sendmsg(0x00, 0x09); | ||
103 | ok($m == 1, "RequestiPodSoftwareVersion sent"); | ||
104 | ($l, $c, $p) = $ipod->recvmsg(); | ||
105 | subtest "ReturniPodSoftwareVersion" => sub { | ||
106 | ok(defined($l), "Response received"); | ||
107 | is($l, 0x00, "Response lingo"); | ||
108 | is($c, 0x0A, "Response command"); | ||
109 | like($p, "/^...\$/", "Response payload"); | ||
110 | }; | ||
111 | |||
112 | # Empty the buffer | ||
113 | $ipod->emptyrecv(); | ||
114 | |||
115 | # RequestiPodSerialNumber | ||
116 | # We expect a ReturniPodSerialNumber packet | ||
117 | $m = $ipod->sendmsg(0x00, 0x0B); | ||
118 | ok($m == 1, "RequestiPodSerialNumber sent"); | ||
119 | ($l, $c, $p) = $ipod->recvmsg(); | ||
120 | subtest "ReturniPodSerialNumber" => sub { | ||
121 | ok(defined($l), "Response received"); | ||
122 | is($l, 0x00, "Response lingo"); | ||
123 | is($c, 0x0C, "Response command"); | ||
124 | like($p, "/^[^\\x00]*\\x00\$/", "Response payload"); | ||
125 | }; | ||
126 | |||
127 | # Empty the buffer | ||
128 | $ipod->emptyrecv(); | ||
129 | |||
130 | # RequestiPodModelNum | ||
131 | # We expect a ReturniPodModelNum packet | ||
132 | $m = $ipod->sendmsg(0x00, 0x0D); | ||
133 | ok($m == 1, "RequestiPodModelNum sent"); | ||
134 | ($l, $c, $p) = $ipod->recvmsg(); | ||
135 | subtest "ReturniPodModelNum" => sub { | ||
136 | ok(defined($l), "Response received"); | ||
137 | is($l, 0x00, "Response lingo"); | ||
138 | is($c, 0x0E, "Response command"); | ||
139 | like($p, "/^....[^\\x00]*\\x00\$/", "Response payload"); | ||
140 | }; | ||
141 | |||
142 | # Empty the buffer | ||
143 | $ipod->emptyrecv(); | ||
144 | |||
145 | # RequestLingoProtocolVersion(lingo=0x00) | ||
146 | # We expect a ReturnLingoProtocolVersion packet | ||
147 | $m = $ipod->sendmsg(0x00, 0x0F, "\x00"); | ||
148 | ok($m == 1, "RequestLingoProtocolVersion(lingo=0x00) sent"); | ||
149 | ($l, $c, $p) = $ipod->recvmsg(); | ||
150 | subtest "ReturnLingoProtocolVersion" => sub { | ||
151 | ok(defined($l), "Response received"); | ||
152 | is($l, 0x00, "Response lingo"); | ||
153 | is($c, 0x10, "Response command"); | ||
154 | like($p, "/^\\x00..\$/", "Response payload"); | ||
155 | }; | ||
156 | |||
157 | # Empty the buffer | ||
158 | $ipod->emptyrecv(); | ||
159 | |||
160 | # IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00) | ||
161 | # We expect an ACK OK message as response | ||
162 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
163 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00) sent"); | ||
164 | ($l, $c, $p) = $ipod->recvmsg(); | ||
165 | subtest "ACK OK" => sub { | ||
166 | ok(defined($l), "Response received"); | ||
167 | is($l, 0x00, "Response lingo"); | ||
168 | is($c, 0x02, "Response command"); | ||
169 | is($p, "\x00\x13", "Response payload"); | ||
170 | }; | ||
171 | |||
172 | # Empty the buffer | ||
173 | $ipod->emptyrecv(); | ||
174 | |||
175 | # RequestRemoteUIMode | ||
176 | # We expect an ReturnRemoteUIMode packet specifying standard mode | ||
177 | $m = $ipod->sendmsg(0x00, 0x03); | ||
178 | ok($m == 1, "RequestRemoteUIMode sent"); | ||
179 | ($l, $c, $p) = $ipod->recvmsg(); | ||
180 | subtest "ReturnRemoteUIMode" => sub { | ||
181 | ok(defined($l), "Response received"); | ||
182 | is($l, 0x00, "Response lingo"); | ||
183 | is($c, 0x04, "Response command"); | ||
184 | is($p, "\x00", "Response payload"); | ||
185 | }; | ||
186 | |||
187 | # Empty the buffer | ||
188 | $ipod->emptyrecv(); | ||
189 | |||
190 | # EnterRemoteUIMode | ||
191 | # We expect an ACK Pending packet, followed by an ACK OK packet | ||
192 | $m = $ipod->sendmsg(0x00, 0x05); | ||
193 | ok($m == 1, "EnterRemoteUIMode sent"); | ||
194 | ($l, $c, $p) = $ipod->recvmsg(); | ||
195 | subtest "ACK Pending" => sub { | ||
196 | ok(defined($l), "Response received"); | ||
197 | is($l, 0x00, "Response lingo"); | ||
198 | is($c, 0x02, "Response command"); | ||
199 | like($p, "/^\\x06\\x05/", "Response payload"); | ||
200 | }; | ||
201 | ($l, $c, $p) = $ipod->recvmsg(); | ||
202 | subtest "ACK OK" => sub { | ||
203 | ok(defined($l), "Response received"); | ||
204 | is($l, 0x00, "Response lingo"); | ||
205 | is($c, 0x02, "Response command"); | ||
206 | is($p, "\x00\x05", "Response payload"); | ||
207 | }; | ||
208 | |||
209 | # Empty the buffer | ||
210 | $ipod->emptyrecv(); | ||
211 | |||
212 | # RequestRemoteUIMode | ||
213 | # We expect an ReturnRemoteUIMode packet specifying extended mode | ||
214 | $m = $ipod->sendmsg(0x00, 0x03); | ||
215 | ok($m == 1, "RequestRemoteUIMode sent"); | ||
216 | ($l, $c, $p) = $ipod->recvmsg(); | ||
217 | subtest "ReturnRemoteUIMode" => sub { | ||
218 | ok(defined($l), "Response received"); | ||
219 | is($l, 0x00, "Response lingo"); | ||
220 | is($c, 0x04, "Response command"); | ||
221 | isnt($p, "\x00", "Response payload"); | ||
222 | }; | ||
223 | |||
224 | # Empty the buffer | ||
225 | $ipod->emptyrecv(); | ||
226 | |||
227 | # ExitRemoteUIMode | ||
228 | # We expect an ACK Pending packet, followed by an ACK OK packet | ||
229 | $m = $ipod->sendmsg(0x00, 0x06); | ||
230 | ok($m == 1, "ExitRemoteUIMode sent"); | ||
231 | ($l, $c, $p) = $ipod->recvmsg(); | ||
232 | subtest "ACK Pending" => sub { | ||
233 | ok(defined($l), "Response received"); | ||
234 | is($l, 0x00, "Response lingo"); | ||
235 | is($c, 0x02, "Response command"); | ||
236 | like($p, "/^\\x06\\x06/", "Response payload"); | ||
237 | }; | ||
238 | ($l, $c, $p) = $ipod->recvmsg(); | ||
239 | subtest "ACK OK" => sub { | ||
240 | ok(defined($l), "Response received"); | ||
241 | is($l, 0x00, "Response lingo"); | ||
242 | is($c, 0x02, "Response command"); | ||
243 | is($p, "\x00\x06", "Response payload"); | ||
244 | }; | ||
245 | |||
246 | # Empty the buffer | ||
247 | $ipod->emptyrecv(); | ||
248 | |||
249 | # RequestRemoteUIMode | ||
250 | # We expect an ReturnRemoteUIMode packet specifying standard mode | ||
251 | $m = $ipod->sendmsg(0x00, 0x03); | ||
252 | ok($m == 1, "RequestRemoteUIMode sent"); | ||
253 | ($l, $c, $p) = $ipod->recvmsg(); | ||
254 | subtest "ReturnRemoteUIMode" => sub { | ||
255 | ok(defined($l), "Response received"); | ||
256 | is($l, 0x00, "Response lingo"); | ||
257 | is($c, 0x04, "Response command"); | ||
258 | is($p, "\x00", "Response payload"); | ||
259 | }; | ||
260 | |||
261 | # Empty the buffer | ||
262 | $ipod->emptyrecv(); | ||
263 | |||
264 | # Send an undefined command | ||
265 | # We expect an ACK Bad Parameter as response | ||
266 | $m = $ipod->sendmsg(0x00, 0xFF); | ||
267 | ok($m == 1, "Undefined command sent"); | ||
268 | ($l, $c, $p) = $ipod->recvmsg(); | ||
269 | subtest "ACK Bad Parameter" => sub { | ||
270 | ok(defined($l), "Response received"); | ||
271 | is($l, 0x00, "Response lingo"); | ||
272 | is($c, 0x02, "Response command"); | ||
273 | is($p, "\x04\xFF", "Response payload"); | ||
274 | }; | ||
275 | |||
276 | # Empty the buffer | ||
277 | $ipod->emptyrecv(); | ||
diff --git a/tools/iap/ipod-003-lingo2.t b/tools/iap/ipod-003-lingo2.t new file mode 100644 index 0000000000..ee0bd6972e --- /dev/null +++ b/tools/iap/ipod-003-lingo2.t | |||
@@ -0,0 +1,220 @@ | |||
1 | use Test::More qw( no_plan ); | ||
2 | use strict; | ||
3 | |||
4 | BEGIN { use_ok('Device::iPod'); } | ||
5 | require_ok('Device::iPod'); | ||
6 | |||
7 | my $ipod = Device::iPod->new(); | ||
8 | my $m; | ||
9 | my ($l, $c, $p); | ||
10 | |||
11 | isa_ok($ipod, 'Device::iPod'); | ||
12 | |||
13 | $ipod->{-debug} = 1; | ||
14 | $ipod->open("/dev/ttyUSB0"); | ||
15 | |||
16 | $m = $ipod->sendraw("\xFF" x 16); # Wake up and sync | ||
17 | ok($m == 1, "Wakeup sent"); | ||
18 | |||
19 | # Empty the buffer | ||
20 | $ipod->emptyrecv(); | ||
21 | |||
22 | # IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) | ||
23 | # We expect an ACK OK message as response | ||
24 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
25 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent"); | ||
26 | ($l, $c, $p) = $ipod->recvmsg(); | ||
27 | subtest "ACK OK" => sub { | ||
28 | ok(defined($l), "Response received"); | ||
29 | is($l, 0x00, "Response lingo"); | ||
30 | is($c, 0x02, "Response command"); | ||
31 | is($p, "\x00\x13", "Response payload"); | ||
32 | }; | ||
33 | |||
34 | # Empty the buffer | ||
35 | $ipod->emptyrecv(); | ||
36 | |||
37 | # ContextButtonStatus(0x00) | ||
38 | # We expect an ACK Bad Parameter message as response | ||
39 | $m = $ipod->sendmsg(0x02, 0x00, "\x00"); | ||
40 | ok($m == 1, "ContextButtonStatus(0x00)"); | ||
41 | ($l, $c, $p) = $ipod->recvmsg(); | ||
42 | subtest "ACK Bad Parameter" => sub { | ||
43 | ok(defined($l), "Response received"); | ||
44 | is($l, 0x02, "Response lingo"); | ||
45 | is($c, 0x01, "Response command"); | ||
46 | is($p, "\x04\x00", "Response payload"); | ||
47 | }; | ||
48 | |||
49 | # Empty the buffer | ||
50 | $ipod->emptyrecv(); | ||
51 | |||
52 | # Identify(lingo=0x00) | ||
53 | # We expect no response (timeout) | ||
54 | $m = $ipod->sendmsg(0x00, 0x01, "\x00"); | ||
55 | ok($m == 1, "Identify(lingo=0x00) sent"); | ||
56 | ($l, $c, $p) = $ipod->recvmsg(); | ||
57 | subtest "Timeout" => sub { | ||
58 | ok(!defined($l), "No response received"); | ||
59 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
60 | }; | ||
61 | |||
62 | # Empty the buffer | ||
63 | $ipod->emptyrecv(); | ||
64 | |||
65 | # ContextButtonStatus(0x00) | ||
66 | # We expect a timeout as response | ||
67 | $m = $ipod->sendmsg(0x02, 0x00, "\x00"); | ||
68 | ok($m == 1, "ContextButtonStatus(0x00)"); | ||
69 | ($l, $c, $p) = $ipod->recvmsg(); | ||
70 | subtest "Timeout" => sub { | ||
71 | ok(!defined($l), "Response received"); | ||
72 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
73 | }; | ||
74 | |||
75 | # Empty the buffer | ||
76 | $ipod->emptyrecv(); | ||
77 | |||
78 | # Identify(lingo=0x02) | ||
79 | # We expect no response (timeout) | ||
80 | $m = $ipod->sendmsg(0x00, 0x01, "\x02"); | ||
81 | ok($m == 1, "Identify(lingo=0x02) sent"); | ||
82 | ($l, $c, $p) = $ipod->recvmsg(); | ||
83 | subtest "Timeout" => sub { | ||
84 | ok(!defined($l), "No response received"); | ||
85 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
86 | }; | ||
87 | |||
88 | # Empty the buffer | ||
89 | $ipod->emptyrecv(); | ||
90 | |||
91 | # ContextButtonStatus(0x00) | ||
92 | # We expect a timeout as response | ||
93 | $m = $ipod->sendmsg(0x02, 0x00, "\x00"); | ||
94 | ok($m == 1, "ContextButtonStatus(0x00)"); | ||
95 | ($l, $c, $p) = $ipod->recvmsg(); | ||
96 | subtest "Timeout" => sub { | ||
97 | ok(!defined($l), "Response received"); | ||
98 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
99 | }; | ||
100 | |||
101 | # Empty the buffer | ||
102 | $ipod->emptyrecv(); | ||
103 | |||
104 | # IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00) | ||
105 | # We expect an ACK OK message as response | ||
106 | $m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00"); | ||
107 | ok($m == 1, "IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00) sent"); | ||
108 | ($l, $c, $p) = $ipod->recvmsg(); | ||
109 | subtest "ACK OK" => sub { | ||
110 | ok(defined($l), "Response received"); | ||
111 | is($l, 0x00, "Response lingo"); | ||
112 | is($c, 0x02, "Response command"); | ||
113 | is($p, "\x00\x13", "Response payload"); | ||
114 | }; | ||
115 | |||
116 | # Empty the buffer | ||
117 | $ipod->emptyrecv(); | ||
118 | |||
119 | # RequestLingoProtocolVersion(lingo=0x02) | ||
120 | # We expect a ReturnLingoProtocolVersion packet | ||
121 | $m = $ipod->sendmsg(0x00, 0x0F, "\x02"); | ||
122 | ok($m == 1, "RequestLingoProtocolVersion(lingo=0x02) sent"); | ||
123 | ($l, $c, $p) = $ipod->recvmsg(); | ||
124 | subtest "ReturnLingoProtocolVersion" => sub { | ||
125 | ok(defined($l), "Response received"); | ||
126 | is($l, 0x00, "Response lingo"); | ||
127 | is($c, 0x10, "Response command"); | ||
128 | like($p, "/^\\x02..\$/", "Response payload"); | ||
129 | }; | ||
130 | |||
131 | # Empty the buffer | ||
132 | $ipod->emptyrecv(); | ||
133 | |||
134 | # Send an undefined command | ||
135 | # We expect an ACK Bad Parameter as response | ||
136 | $m = $ipod->sendmsg(0x02, 0xFF); | ||
137 | ok($m == 1, "Undefined command sent"); | ||
138 | ($l, $c, $p) = $ipod->recvmsg(); | ||
139 | subtest "ACK Bad Parameter" => sub { | ||
140 | ok(defined($l), "Response received"); | ||
141 | is($l, 0x02, "Response lingo"); | ||
142 | is($c, 0x01, "Response command"); | ||
143 | is($p, "\x04\xFF", "Response payload"); | ||
144 | }; | ||
145 | |||
146 | # Empty the buffer | ||
147 | $ipod->emptyrecv(); | ||
148 | |||
149 | # ContextButtonStatus(0x00) | ||
150 | # We expect a timeout as response | ||
151 | $m = $ipod->sendmsg(0x02, 0x00, "\x00"); | ||
152 | ok($m == 1, "ContextButtonStatus(0x00)"); | ||
153 | ($l, $c, $p) = $ipod->recvmsg(); | ||
154 | subtest "Timeout" => sub { | ||
155 | ok(!defined($l), "Response received"); | ||
156 | like($ipod->error(), '/Timeout/', "Timeout reading response"); | ||
157 | }; | ||
158 | |||
159 | # Empty the buffer | ||
160 | $ipod->emptyrecv(); | ||
161 | |||
162 | # Send a too short command | ||
163 | # We expect an ACK Bad Parameter as response | ||
164 | $m = $ipod->sendmsg(0x02, 0x00, ""); | ||
165 | ok($m == 1, "Short command sent"); | ||
166 | ($l, $c, $p) = $ipod->recvmsg(); | ||
167 | subtest "ACK Bad Parameter" => sub { | ||
168 | ok(defined($l), "Response received"); | ||
169 | is($l, 0x02, "Response lingo"); | ||
170 | is($c, 0x01, "Response command"); | ||
171 | is($p, "\x04\x00", "Response payload"); | ||
172 | }; | ||
173 | |||
174 | # Empty the buffer | ||
175 | $ipod->emptyrecv(); | ||
176 | |||
177 | # ImageButtonStatus(0x00) | ||
178 | # We expect an ACK Not Authenticated as response | ||
179 | $m = $ipod->sendmsg(0x02, 0x02, "\x00"); | ||
180 | ok($m == 1, "ImageButtonStatus(0x00)"); | ||
181 | ($l, $c, $p) = $ipod->recvmsg(); | ||
182 | subtest "ACK Not Authenticated" => sub { | ||
183 | ok(defined($l), "Response received"); | ||
184 | is($l, 0x02, "Response lingo"); | ||
185 | is($c, 0x01, "Response command"); | ||
186 | is($p, "\x07\x02", "Response payload"); | ||
187 | }; | ||
188 | |||
189 | # Empty the buffer | ||
190 | $ipod->emptyrecv(); | ||
191 | |||
192 | # VideoButtonStatus(0x00) | ||
193 | # We expect an ACK Not Authenticated as response | ||
194 | $m = $ipod->sendmsg(0x02, 0x03, "\x00"); | ||
195 | ok($m == 1, "VideoButtonStatus(0x00)"); | ||
196 | ($l, $c, $p) = $ipod->recvmsg(); | ||
197 | subtest "ACK Not Authenticated" => sub { | ||
198 | ok(defined($l), "Response received"); | ||
199 | is($l, 0x02, "Response lingo"); | ||
200 | is($c, 0x01, "Response command"); | ||
201 | is($p, "\x07\x03", "Response payload"); | ||
202 | }; | ||
203 | |||
204 | # Empty the buffer | ||
205 | $ipod->emptyrecv(); | ||
206 | |||
207 | # AudioButtonStatus(0x00) | ||
208 | # We expect an ACK Not Authenticated as response | ||
209 | $m = $ipod->sendmsg(0x02, 0x04, "\x00"); | ||
210 | ok($m == 1, "AudioButtonStatus(0x00)"); | ||
211 | ($l, $c, $p) = $ipod->recvmsg(); | ||
212 | subtest "ACK Not Authenticated" => sub { | ||
213 | ok(defined($l), "Response received"); | ||
214 | is($l, 0x02, "Response lingo"); | ||
215 | is($c, 0x01, "Response command"); | ||
216 | is($p, "\x07\x04", "Response payload"); | ||
217 | }; | ||
218 | |||
219 | # Empty the buffer | ||
220 | $ipod->emptyrecv(); | ||