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 /apps/iap.c | |
parent | 500b137308a6ee5c2aba873734a8956d70472f56 (diff) | |
download | rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.gz rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.zip |
Updated IAP commands.
Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012.
They have been condensed into a single patch and some further additions
by Andy Potter.
Currently includes Authentication V2 support from iPod to Accessory,
RF/BlueTooth transmitter support, selecting a playlist and selecting a
track from the current playlist. Does not support uploading Album Art
or podcasts. Has been tested on the following iPods,
4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and
Video 5.5Gen.
Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e
Co-Authored by: Andy Potter <liveboxandy@gmail.com>
Reviewed-on: http://gerrit.rockbox.org/533
Reviewed-by: Frank Gevaerts <frank@gevaerts.be>
Diffstat (limited to 'apps/iap.c')
-rw-r--r-- | apps/iap.c | 1110 |
1 files changed, 0 insertions, 1110 deletions
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 | |||