summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRalf Ertzinger <rockbox@camperquake.de>2013-06-22 10:08:23 +0100
committerFrank Gevaerts <frank@gevaerts.be>2013-11-10 18:41:24 +0100
commitb170c73f922e3457b923b4e7fcbec794a8885c77 (patch)
tree89fbdbd8c25af5101a29a1ede3b896332a4e205c
parent500b137308a6ee5c2aba873734a8956d70472f56 (diff)
downloadrockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.gz
rockbox-b170c73f922e3457b923b4e7fcbec794a8885c77.tar.bz2
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/SOURCES6
-rw-r--r--apps/iap.c1110
-rw-r--r--apps/iap/iap-core.c1392
-rw-r--r--apps/iap/iap-core.h250
-rw-r--r--apps/iap/iap-lingo.h23
-rw-r--r--apps/iap/iap-lingo0.c1035
-rw-r--r--apps/iap/iap-lingo2.c278
-rw-r--r--apps/iap/iap-lingo3.c1508
-rw-r--r--apps/iap/iap-lingo4.c3153
-rw-r--r--apps/misc.c8
-rw-r--r--firmware/export/iap.h4
-rw-r--r--firmware/export/kernel.h2
-rw-r--r--firmware/target/arm/pp/debug-pp.c9
-rw-r--r--tools/iap/Device/iPod.pm386
-rw-r--r--tools/iap/Makefile7
-rw-r--r--tools/iap/README23
-rw-r--r--tools/iap/device-ipod.t74
-rw-r--r--tools/iap/iap-verbose.pl1856
-rw-r--r--tools/iap/ipod-001-general.t133
-rw-r--r--tools/iap/ipod-002-lingo0.t277
-rw-r--r--tools/iap/ipod-003-lingo2.t220
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
filetree.c
scrobbler.c
#ifdef IPOD_ACCESSORY_PROTOCOL
-iap.c
+iap/iap-core.c
+iap/iap-lingo0.c
+iap/iap-lingo2.c
+iap/iap-lingo3.c
+iap/iap-lingo4.c
#endif
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 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2002 by Alan Korr & Nick Robinson
- *
- * All files in this archive are subject to the GNU General Public License.
- * See the file COPYING in the source tree root for full license agreement.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-
-#include "panic.h"
-#include "iap.h"
-#include "button.h"
-#include "config.h"
-#include "cpu.h"
-#include "system.h"
-#include "kernel.h"
-#include "serial.h"
-#include "appevents.h"
-
-#include "playlist.h"
-#include "playback.h"
-#include "audio.h"
-#include "settings.h"
-#include "metadata.h"
-#include "wps.h"
-#include "sound.h"
-#include "action.h"
-#include "powermgmt.h"
-
-#include "tuner.h"
-#include "ipod_remote_tuner.h"
-
-#include "filetree.h"
-#include "dir.h"
-
-static volatile int iap_pollspeed = 0;
-static volatile bool iap_remotetick = true;
-static bool iap_setupflag = false, iap_updateflag = false;
-static int iap_changedctr = 0;
-
-static unsigned long iap_remotebtn = 0;
-static int iap_repeatbtn = 0;
-static bool iap_btnrepeat = false, iap_btnshuffle = false;
-
-static unsigned char serbuf[RX_BUFLEN];
-
-static unsigned char response[TX_BUFLEN];
-
-static char cur_dbrecord[5] = {0};
-
-/* states of the iap de-framing state machine */
-enum fsm_state {
- ST_SYNC, /* wait for 0xFF sync byte */
- ST_SOF, /* wait for 0x55 start-of-frame byte */
- ST_LEN, /* receive length byte (small packet) */
- ST_LENH, /* receive length high byte (large packet) */
- ST_LENL, /* receive length low byte (large packet) */
- ST_DATA, /* receive data */
- ST_CHECK /* verify checksum */
-};
-
-static struct state_t {
- enum fsm_state state; /* current fsm state */
- unsigned int len; /* payload data length */
- unsigned char *payload; /* payload data pointer */
- unsigned int check; /* running checksum over [len,payload,check] */
- unsigned int count; /* playload bytes counter */
-} frame_state = {
- .state = ST_SYNC
-};
-
-static void put_u32(unsigned char *buf, uint32_t data)
-{
- buf[0] = (data >> 24) & 0xFF;
- buf[1] = (data >> 16) & 0xFF;
- buf[2] = (data >> 8) & 0xFF;
- buf[3] = (data >> 0) & 0xFF;
-}
-
-static uint32_t get_u32(const unsigned char *buf)
-{
- return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
-}
-
-static void iap_task(void)
-{
- static int count = 0;
-
- count += iap_pollspeed;
- if (count < (500/10)) return;
-
- /* exec every 500ms if pollspeed == 1 */
- count = 0;
- queue_post(&button_queue, SYS_IAP_PERIODIC, 0);
-}
-
-/* called by playback when the next track starts */
-static void iap_track_changed(void *ignored)
-{
- (void)ignored;
- iap_changedctr = 1;
-}
-
-void iap_setup(int ratenum)
-{
- iap_bitrate_set(ratenum);
- iap_pollspeed = 0;
- iap_remotetick = true;
- iap_updateflag = false;
- iap_changedctr = 0;
- iap_setupflag = true;
- iap_remotebtn = BUTTON_NONE;
- tick_add_task(iap_task);
- add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed);
-}
-
-void iap_bitrate_set(int ratenum)
-{
- switch(ratenum)
- {
- case 0:
- serial_bitrate(0);
- break;
- case 1:
- serial_bitrate(9600);
- break;
- case 2:
- serial_bitrate(19200);
- break;
- case 3:
- serial_bitrate(38400);
- break;
- case 4:
- serial_bitrate(57600);
- break;
- }
-}
-
-/* Message format:
- 0xff
- 0x55
- length
- mode
- command (2 bytes)
- parameters (0-n bytes)
- checksum (length+mode+parameters+checksum == 0)
-*/
-
-void iap_send_pkt(const unsigned char * data, int len)
-{
- int i, chksum, responselen;
-
- if(len > TX_BUFLEN-4) len = TX_BUFLEN-4;
- responselen = len + 4;
-
- response[0] = 0xFF;
- response[1] = 0x55;
-
- chksum = response[2] = len;
- for(i = 0; i < len; i ++)
- {
- chksum += data[i];
- response[i+3] = data[i];
- }
-
- response[i+3] = 0x100 - (chksum & 0xFF);
-
- for(i = 0; i < responselen; i ++)
- {
- while (!tx_rdy()) ;
- tx_writec(response[i]);
- }
-}
-
-bool iap_getc(unsigned char x)
-{
- struct state_t *s = &frame_state;
-
- /* run state machine to detect and extract a valid frame */
- switch (s->state) {
- case ST_SYNC:
- if (x == 0xFF) {
- s->state = ST_SOF;
- }
- break;
- case ST_SOF:
- if (x == 0x55) {
- /* received a valid sync/SOF pair */
- s->state = ST_LEN;
- } else {
- s->state = ST_SYNC;
- return iap_getc(x);
- }
- break;
- case ST_LEN:
- s->check = x;
- s->count = 0;
- s->payload = serbuf;
- if (x == 0) {
- /* large packet */
- s->state = ST_LENH;
- } else {
- /* small packet */
- s->len = x;
- s->state = ST_DATA;
- }
- break;
- case ST_LENH:
- s->check += x;
- s->len = x << 8;
- s->state = ST_LENL;
- break;
- case ST_LENL:
- s->check += x;
- s->len += x;
- if ((s->len == 0) || (s->len > RX_BUFLEN)) {
- /* invalid length */
- s->state = ST_SYNC;
- return iap_getc(x);
- } else {
- s->state = ST_DATA;
- }
- break;
- case ST_DATA:
- s->check += x;
- s->payload[s->count++] = x;
- if (s->count == s->len) {
- s->state = ST_CHECK;
- }
- break;
- case ST_CHECK:
- s->check += x;
- if ((s->check & 0xFF) == 0) {
- /* done, received a valid frame */
- queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0);
- }
- s->state = ST_SYNC;
- break;
- default:
- panicf("Unhandled iap state %d", (int) s->state);
- break;
- }
-
- /* return true while still hunting for the sync and start-of-frame byte */
- return (s->state == ST_SYNC) || (s->state == ST_SOF);
-}
-
-void iap_periodic(void)
-{
- if(!iap_setupflag) return;
- if(!iap_pollspeed) return;
-
- /* PlayStatusChangeNotification */
- unsigned char data[] = {0x04, 0x00, 0x27, 0x04, 0x00, 0x00, 0x00, 0x00};
- unsigned long time_elapsed = audio_current_track()->elapsed;
-
- time_elapsed += wps_get_ff_rewind_count();
-
- data[3] = 0x04; /* playing */
-
- /* If info has changed, don't flag it right away */
- if(iap_changedctr && iap_changedctr++ >= iap_pollspeed * 2)
- {
- /* track info has changed */
- iap_changedctr = 0;
- data[3] = 0x01; /* 0x02 has same effect? */
- iap_updateflag = true;
- }
-
- put_u32(&data[4], time_elapsed);
- iap_send_pkt(data, sizeof(data));
-}
-
-static void iap_set_remote_volume(void)
-{
- unsigned char data[] = {0x03, 0x0D, 0x04, 0x00, 0x00};
- data[4] = (char)((global_settings.volume+58) * 4);
- iap_send_pkt(data, sizeof(data));
-}
-
-static void cmd_ok_mode0(unsigned char cmd)
-{
- unsigned char data[] = {0x00, 0x02, 0x00, 0x00};
- data[3] = cmd; /* respond with cmd */
- iap_send_pkt(data, sizeof(data));
-}
-
-static void iap_handlepkt_mode0(unsigned int len, const unsigned char *buf)
-{
- (void)len; /* len currently unused */
-
- unsigned int cmd = buf[1];
- switch (cmd) {
- /* Identify */
- case 0x01:
- {
- /* FM transmitter sends this: */
- /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */
- if(buf[2] == 0x05)
- {
- sleep(HZ/3);
- /* RF Transmitter: Begin transmission */
- unsigned char data[] = {0x05, 0x02};
- iap_send_pkt(data, sizeof(data));
- }
- /* FM remote sends this: */
- /* FF 55 03 00 01 02 FA (1st thing sent) */
- else if (buf[2] == 0x02)
- {
- /* useful only for apple firmware */
- }
- break;
- }
-
- /* EnterRemoteUIMode, FM transmitter sends FF 55 02 00 05 F9 */
- case 0x05:
- {
- /* ACK Pending (3000 ms) */
- unsigned char data[] = {0x00, 0x02, 0x06,
- 0x05, 0x00, 0x00, 0x0B, 0xB8};
- iap_send_pkt(data, sizeof(data));
- cmd_ok_mode0(cmd);
- break;
- }
-
- /* ExitRemoteUIMode */
- case 0x06:
- {
- audio_stop();
- cmd_ok_mode0(cmd);
- break;
- }
-
- /* RequestiPodSoftwareVersion, Ipod FM remote sends FF 55 02 00 09 F5 */
- case 0x09:
- {
- /* ReturniPodSoftwareVersion, ipod5G firmware version */
- unsigned char data[] = {0x00, 0x0A, 0x01, 0x02, 0x01};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* RequestiPodModelNum */
- case 0x0D:
- {
- /* ipod is supposed to work only with 5G and nano 2G */
- /*{0x00, 0x0E, 0x00, 0x0B, 0x00, 0x05, 0x50, 0x41, 0x31, 0x34,
- 0x37, 0x4C, 0x4C, 0x00}; PA147LL (IPOD 5G 60 GO) */
- /* ReturniPodModelNum */
- unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10,
- 'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* RequestLingoProtocolVersion */
- case 0x0F:
- {
- /* ReturnLingoProtocolVersion */
- unsigned char data[] = {0x00, 0x10, 0x00, 0x01, 0x05};
- data[2] = buf[2];
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* IdentifyDeviceLingoes */
- case 0x13:
- {
- cmd_ok_mode0(cmd);
-
- uint32_t lingoes = get_u32(&buf[2]);
-
- if (lingoes == 0x35)
- /* FM transmitter sends this: */
- /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/
- {
- /* GetAccessoryInfo */
- unsigned char data2[] = {0x00, 0x27, 0x00};
- iap_send_pkt(data2, sizeof(data2));
- /* RF Transmitter: Begin transmission */
- unsigned char data3[] = {0x05, 0x02};
- iap_send_pkt(data3, sizeof(data3));
- }
- else
- {
- /* ipod fm remote sends this: */
- /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */
- if (lingoes & (1 << 7)) /* bit 7 = RF tuner lingo */
- radio_present = 1;
- /* GetDevAuthenticationInfo */
- unsigned char data4[] = {0x00, 0x14};
- iap_send_pkt(data4, sizeof(data4));
- }
- break;
- }
-
- /* RetDevAuthenticationInfo */
- case 0x15:
- {
- /* AckDevAuthenticationInfo */
- unsigned char data0[] = {0x00, 0x16, 0x00};
- iap_send_pkt(data0, sizeof(data0));
- /* GetAccessoryInfo */
- unsigned char data1[] = {0x00, 0x27, 0x00};
- iap_send_pkt(data1, sizeof(data1));
- /* AckDevAuthenticationStatus, mandatory to enable some hardware */
- unsigned char data2[] = {0x00, 0x19, 0x00};
- iap_send_pkt(data2, sizeof(data2));
- if (radio_present == 1)
- {
- /* GetTunerCaps */
- unsigned char data3[] = {0x07, 0x01};
- iap_send_pkt(data3, sizeof(data3));
- }
- iap_set_remote_volume();
- break;
- }
-
- /* RetDevAuthenticationSignature */
- case 0x18:
- {
- /* Isn't used since we don't send the 0x00 0x17 command */
- break;
- }
-
- /* GetIpodOptions */
- case 0x24:
- {
- /* RetIpodOptions (ipod video send this) */
- unsigned char data[] = {0x00, 0x25, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* default response is with cmd ok packet */
- default:
- {
- cmd_ok_mode0(cmd);
- break;
- }
- }
-}
-
-static void iap_handlepkt_mode2(unsigned int len, const unsigned char *buf)
-{
- if(buf[1] != 0) return;
- iap_remotebtn = BUTTON_NONE;
- iap_remotetick = false;
-
- if(len >= 3 && buf[2] != 0)
- {
- if(buf[2] & 1)
- iap_remotebtn |= BUTTON_RC_PLAY;
- if(buf[2] & 2)
- iap_remotebtn |= BUTTON_RC_VOL_UP;
- if(buf[2] & 4)
- iap_remotebtn |= BUTTON_RC_VOL_DOWN;
- if(buf[2] & 8)
- iap_remotebtn |= BUTTON_RC_RIGHT;
- if(buf[2] & 16)
- iap_remotebtn |= BUTTON_RC_LEFT;
- }
- else if(len >= 4 && buf[3] != 0)
- {
- if(buf[3] & 1) /* play */
- {
- if (audio_status() != AUDIO_STATUS_PLAY)
- {
- iap_remotebtn |= BUTTON_RC_PLAY;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- iap_changedctr = 1;
- }
- }
- if(buf[3] & 2) /* pause */
- {
- if (audio_status() == AUDIO_STATUS_PLAY)
- {
- iap_remotebtn |= BUTTON_RC_PLAY;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- iap_changedctr = 1;
- }
- }
- if((buf[3] & 128) && !iap_btnshuffle) /* shuffle */
- {
- iap_btnshuffle = true;
- if(!global_settings.playlist_shuffle)
- {
- global_settings.playlist_shuffle = 1;
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- playlist_randomise(NULL, current_tick, true);
- }
- else if(global_settings.playlist_shuffle)
- {
- global_settings.playlist_shuffle = 0;
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- playlist_sort(NULL, true);
- }
- }
- else
- iap_btnshuffle = false;
- }
- else if(len >= 5 && buf[4] != 0)
- {
- if((buf[4] & 1) && !iap_btnrepeat) /* repeat */
- {
- int oldmode = global_settings.repeat_mode;
- iap_btnrepeat = true;
-
- if (oldmode == REPEAT_ONE)
- global_settings.repeat_mode = REPEAT_OFF;
- else if (oldmode == REPEAT_ALL)
- global_settings.repeat_mode = REPEAT_ONE;
- else if (oldmode == REPEAT_OFF)
- global_settings.repeat_mode = REPEAT_ALL;
-
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- audio_flush_and_reload_tracks();
- }
- else
- iap_btnrepeat = false;
-
- if(buf[4] & 16) /* ffwd */
- {
- iap_remotebtn |= BUTTON_RC_RIGHT;
- }
- if(buf[4] & 32) /* frwd */
- {
- iap_remotebtn |= BUTTON_RC_LEFT;
- }
- }
-}
-
-static void iap_handlepkt_mode3(unsigned int len, const unsigned char *buf)
-{
- (void)len; /* len currently unused */
-
- unsigned int cmd = buf[1];
- switch (cmd)
- {
- /* GetCurrentEQProfileIndex */
- case 0x01:
- {
- /* RetCurrentEQProfileIndex */
- unsigned char data[] = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SetRemoteEventNotification */
- case 0x08:
- {
- /* ACK */
- unsigned char data[] = {0x03, 0x00, 0x00, 0x08};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* GetiPodStateInfo */
- case 0x0C:
- {
- /* request ipod volume */
- if (buf[2] == 0x04)
- {
- iap_set_remote_volume();
- }
- break;
- }
-
- /* SetiPodStateInfo */
- case 0x0E:
- {
- if (buf[2] == 0x04)
- global_settings.volume = (-58)+((int)buf[4]+1)/4;
- sound_set_volume(global_settings.volume); /* indent BUG? */
- break;
- }
- }
-}
-
-static void cmd_ok_mode4(unsigned int cmd)
-{
- unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x00};
- data[4] = (cmd >> 8) & 0xFF;
- data[5] = (cmd >> 0) & 0xFF;
- iap_send_pkt(data, sizeof(data));
-}
-
-static void get_playlist_name(unsigned char *dest,
- unsigned long item_offset,
- size_t max_length)
-{
- if (item_offset == 0) return;
- DIR* dp;
- struct dirent* playlist_file = NULL;
-
- dp = opendir(global_settings.playlist_catalog_dir);
-
- char *extension;
- unsigned long nbr = 0;
- while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL))
- {
- /*Increment only if there is a playlist extension*/
- if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){
- if ((strcmp(extension, ".m3u") == 0 ||
- strcmp(extension, ".m3u8") == 0))
- nbr++;
- }
- }
- if (playlist_file != NULL) {
- strlcpy(dest, playlist_file->d_name, max_length);
- }
- closedir(dp);
-}
-
-static void iap_handlepkt_mode4(unsigned int len, const unsigned char *buf)
-{
- (void)len; /* len currently unused */
-
- unsigned int cmd = (buf[1] << 8) | buf[2];
- switch (cmd)
- {
- /* GetAudioBookSpeed */
- case 0x0009:
- {
- /* ReturnAudioBookSpeed */
- unsigned char data[] = {0x04, 0x00, 0x0A, 0x00};
- data[3] = iap_updateflag ? 0 : 1;
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SetAudioBookSpeed */
- case 0x000B:
- {
- iap_updateflag = buf[3] ? 0 : 1;
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* RequestProtocolVersion */
- case 0x0012:
- {
- /* ReturnProtocolVersion */
- unsigned char data[] = {0x04, 0x00, 0x13, 0x01, 0x0B};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SelectDBRecord */
- case 0x0017:
- {
- memcpy(cur_dbrecord, buf + 3, 5);
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* GetNumberCategorizedDBRecords */
- case 0x0018:
- {
- /* ReturnNumberCategorizedDBRecords */
- unsigned char data[] = {0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00};
- unsigned long num = 0;
-
- DIR* dp;
- unsigned long nbr_total_playlists = 0;
- struct dirent* playlist_file = NULL;
- char *extension;
-
- switch(buf[3]) /* type number */
- {
- case 0x01: /* total number of playlists */
- dp = opendir(global_settings.playlist_catalog_dir);
- while ((playlist_file = readdir(dp)) != NULL)
- {
- /*Increment only if there is a playlist extension*/
- if ((extension=strrchr(playlist_file->d_name, '.'))
- != NULL) {
- if ((strcmp(extension, ".m3u") == 0 ||
- strcmp(extension, ".m3u8") == 0))
- nbr_total_playlists++;
- }
- }
- closedir(dp);
- /*Add 1 for the main playlist*/
- num = nbr_total_playlists + 1;
- break;
- case 0x05: /* total number of songs */
- num = 1;
- break;
- }
- put_u32(&data[3], num);
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* RetrieveCategorizedDatabaseRecords */
- case 0x001A:
- {
- /* ReturnCategorizedDatabaseRecord */
- unsigned char data[7 + MAX_PATH] =
- {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00,
- 'R', 'O', 'C', 'K', 'B', 'O', 'X', '\0'};
-
- unsigned long item_offset = get_u32(&buf[4]);
-
- get_playlist_name(data + 7, item_offset, MAX_PATH);
- /*Remove file extension*/
- char *dot=NULL;
- dot = (strrchr(data+7, '.'));
- if (dot != NULL)
- *dot = '\0';
- iap_send_pkt(data, 7 + strlen(data+7) + 1);
- break;
- }
-
- /* GetPlayStatus */
- case 0x001C:
- {
- /* ReturnPlayStatus */
- unsigned char data[] = {0x04, 0x00, 0x1D, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
- struct mp3entry *id3 = audio_current_track();
- unsigned long time_total = id3->length;
- unsigned long time_elapsed = id3->elapsed;
- int status = audio_status();
- put_u32(&data[3], time_total);
- put_u32(&data[7], time_elapsed);
- if (status == AUDIO_STATUS_PLAY)
- data[11] = 0x01; /* play */
- else if (status & AUDIO_STATUS_PAUSE)
- data[11] = 0x02; /* pause */
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* GetCurrentPlayingTrackIndex */
- case 0x001E:
- {
- /* ReturnCurrentPlayingTrackIndex */
- unsigned char data[] = {0x04, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00};
- long playlist_pos = playlist_next(0);
- playlist_pos -= playlist_get_first_index(NULL);
- if(playlist_pos < 0)
- playlist_pos += playlist_amount();
- put_u32(&data[3], playlist_pos);
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* GetIndexedPlayingTrackTitle */
- case 0x0020:
- /* GetIndexedPlayingTrackArtistName */
- case 0x0022:
- /* GetIndexedPlayingTrackAlbumName */
- case 0x0024:
- {
- unsigned char data[70] = {0x04, 0x00, 0xFF};
- struct mp3entry id3;
- int fd;
- size_t len;
- long tracknum = get_u32(&buf[3]);
-
- data[2] = cmd + 1;
- memcpy(&id3, audio_current_track(), sizeof(id3));
- tracknum += playlist_get_first_index(NULL);
- if(tracknum >= playlist_amount())
- tracknum -= playlist_amount();
-
- /* If the tracknumber is not the current one,
- read id3 from disk */
- if(playlist_next(0) != tracknum)
- {
- struct playlist_track_info info;
- playlist_get_track_info(NULL, tracknum, &info);
- fd = open(info.filename, O_RDONLY);
- memset(&id3, 0, sizeof(struct mp3entry));
- get_metadata(&id3, fd, info.filename);
- close(fd);
- }
-
- /* Return the requested track data */
- switch(cmd)
- {
- case 0x20:
- len = strlcpy((char *)&data[3], id3.title, 64);
- iap_send_pkt(data, 4+len);
- break;
- case 0x22:
- len = strlcpy((char *)&data[3], id3.artist, 64);
- iap_send_pkt(data, 4+len);
- break;
- case 0x24:
- len = strlcpy((char *)&data[3], id3.album, 64);
- iap_send_pkt(data, 4+len);
- break;
- }
- break;
- }
-
- /* SetPlayStatusChangeNotification */
- case 0x0026:
- {
- iap_pollspeed = buf[3] ? 1 : 0;
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* PlayCurrentSelection */
- case 0x0028:
- {
- switch (cur_dbrecord[0])
- {
- case 0x01:
- {/*Playlist*/
- unsigned long item_offset = get_u32(&cur_dbrecord[1]);
-
- unsigned char selected_playlist
- [sizeof(global_settings.playlist_catalog_dir)
- + 1
- + MAX_PATH] = {0};
-
- strcpy(selected_playlist,
- global_settings.playlist_catalog_dir);
- int len = strlen(selected_playlist);
- selected_playlist[len] = '/';
- get_playlist_name (selected_playlist + len + 1,
- item_offset,
- MAX_PATH);
- ft_play_playlist(selected_playlist,
- global_settings.playlist_catalog_dir,
- strrchr(selected_playlist, '/') + 1);
- break;
- }
- }
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* PlayControl */
- case 0x0029:
- {
- switch(buf[3])
- {
- case 0x01: /* play/pause */
- iap_remotebtn = BUTTON_RC_PLAY;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- iap_changedctr = 1;
- break;
- case 0x02: /* stop */
- iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- iap_changedctr = 1;
- break;
- case 0x03: /* skip++ */
- iap_remotebtn = BUTTON_RC_RIGHT;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- break;
- case 0x04: /* skip-- */
- iap_remotebtn = BUTTON_RC_LEFT;
- iap_repeatbtn = 2;
- iap_remotetick = false;
- break;
- case 0x05: /* ffwd */
- iap_remotebtn = BUTTON_RC_RIGHT;
- iap_remotetick = false;
- if(iap_pollspeed) iap_pollspeed = 5;
- break;
- case 0x06: /* frwd */
- iap_remotebtn = BUTTON_RC_LEFT;
- iap_remotetick = false;
- if(iap_pollspeed) iap_pollspeed = 5;
- break;
- case 0x07: /* end ffwd/frwd */
- iap_remotebtn = BUTTON_NONE;
- iap_remotetick = false;
- if(iap_pollspeed) iap_pollspeed = 1;
- break;
- }
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* GetShuffle */
- case 0x002C:
- {
- /* ReturnShuffle */
- unsigned char data[] = {0x04, 0x00, 0x2D, 0x00};
- data[3] = global_settings.playlist_shuffle ? 1 : 0;
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SetShuffle */
- case 0x002E:
- {
- if(buf[3] && !global_settings.playlist_shuffle)
- {
- global_settings.playlist_shuffle = 1;
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- playlist_randomise(NULL, current_tick, true);
- }
- else if(!buf[3] && global_settings.playlist_shuffle)
- {
- global_settings.playlist_shuffle = 0;
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- playlist_sort(NULL, true);
- }
-
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* GetRepeat */
- case 0x002F:
- {
- /* ReturnRepeat */
- unsigned char data[] = {0x04, 0x00, 0x30, 0x00};
- if(global_settings.repeat_mode == REPEAT_OFF)
- data[3] = 0;
- else if(global_settings.repeat_mode == REPEAT_ONE)
- data[3] = 1;
- else
- data[3] = 2;
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SetRepeat */
- case 0x0031:
- {
- int oldmode = global_settings.repeat_mode;
- if (buf[3] == 0)
- global_settings.repeat_mode = REPEAT_OFF;
- else if (buf[3] == 1)
- global_settings.repeat_mode = REPEAT_ONE;
- else if (buf[3] == 2)
- global_settings.repeat_mode = REPEAT_ALL;
-
- if (oldmode != global_settings.repeat_mode)
- {
- settings_save();
- if (audio_status() & AUDIO_STATUS_PLAY)
- audio_flush_and_reload_tracks();
- }
-
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- /* GetMonoDisplayImageLimits */
- case 0x0033:
- {
- /* ReturnMonoDisplayImageLimits */
- unsigned char data[] = {0x04, 0x00, 0x34,
- LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
- LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
- 0x01};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* GetNumPlayingTracks */
- case 0x0035:
- {
- /* ReturnNumPlayingTracks */
- unsigned char data[] = {0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00};
- unsigned long playlist_amt = playlist_amount();
- put_u32(&data[3], playlist_amt);
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* SetCurrentPlayingTrack */
- case 0x0037:
- {
- int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
- long tracknum = get_u32(&buf[3]);
-
- audio_pause();
- audio_skip(tracknum - playlist_next(0));
- if (!paused)
- audio_resume();
-
- /* respond with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
-
- default:
- {
- /* default response is with cmd ok packet */
- cmd_ok_mode4(cmd);
- break;
- }
- }
-}
-
-static void iap_handlepkt_mode7(unsigned int len, const unsigned char *buf)
-{
- unsigned int cmd = buf[1];
- switch (cmd)
- {
- /* RetTunerCaps */
- case 0x02:
- {
- /* do nothing */
-
- /* GetAccessoryInfo */
- unsigned char data[] = {0x00, 0x27, 0x00};
- iap_send_pkt(data, sizeof(data));
- break;
- }
-
- /* RetTunerFreq */
- case 0x0A:
- /* fall through */
- /* TunerSeekDone */
- case 0x13:
- {
- rmt_tuner_freq(len, buf);
- break;
- }
-
- /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/
- case 0x21:
- {
- rmt_tuner_rds_data(len, buf);
- break;
- }
- }
-}
-
-void iap_handlepkt(void)
-{
- struct state_t *s = &frame_state;
-
- if(!iap_setupflag) return;
-
- /* if we are waiting for a remote button to go out,
- delay the handling of the new packet */
- if(!iap_remotetick)
- {
- queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0);
- return;
- }
-
- /* handle command by mode */
- unsigned char mode = s->payload[0];
- switch (mode) {
- case 0: iap_handlepkt_mode0(s->len, s->payload); break;
- case 2: iap_handlepkt_mode2(s->len, s->payload); break;
- case 3: iap_handlepkt_mode3(s->len, s->payload); break;
- case 4: iap_handlepkt_mode4(s->len, s->payload); break;
- case 7: iap_handlepkt_mode7(s->len, s->payload); break;
- }
-}
-
-int remote_control_rx(void)
-{
- int btn = iap_remotebtn;
- if(iap_repeatbtn)
- {
- iap_repeatbtn--;
- if(!iap_repeatbtn)
- {
- iap_remotebtn = BUTTON_NONE;
- iap_remotetick = true;
- }
- }
- else
- iap_remotetick = true;
-
- return btn;
-}
-
-const unsigned char *iap_get_serbuf(void)
-{
- return serbuf;
-}
-
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "panic.h"
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "button.h"
+#include "config.h"
+#include "cpu.h"
+#include "system.h"
+#include "kernel.h"
+#include "thread.h"
+#include "serial.h"
+#include "appevents.h"
+#include "core_alloc.h"
+
+#include "playlist.h"
+#include "playback.h"
+#include "audio.h"
+#include "settings.h"
+#include "metadata.h"
+#include "sound.h"
+#include "action.h"
+#include "powermgmt.h"
+
+#include "tuner.h"
+#include "ipod_remote_tuner.h"
+
+
+/* MS_TO_TICKS converts a milisecond time period into the
+ * corresponding amount of ticks. If the time period cannot
+ * be accurately measured in ticks it will round up.
+ */
+#if (HZ>1000)
+#error "HZ is >1000, please fix MS_TO_TICKS"
+#endif
+#define MS_PER_HZ (1000/HZ)
+#define MS_TO_TICKS(x) (((x)+MS_PER_HZ-1)/MS_PER_HZ)
+/* IAP specifies a timeout of 25ms for traffic from a device to the iPod.
+ * Depending on HZ this cannot be accurately measured. Find out the next
+ * best thing.
+ */
+#define IAP_PKT_TIMEOUT (MS_TO_TICKS(25))
+
+/* Events in the iap_queue */
+#define IAP_EV_TICK (1) /* The regular task timeout */
+#define IAP_EV_MSG_RCVD (2) /* A complete message has been received from the device */
+#define IAP_EV_MALLOC (3) /* Allocate memory for the RX/TX buffers */
+
+static bool iap_started = false;
+static bool iap_setupflag = false, iap_running = false;
+/* This is set to true if a SYS_POWEROFF message is received,
+ * signalling impending power off
+ */
+static bool iap_shutdown = false;
+static struct timeout iap_task_tmo;
+
+unsigned long iap_remotebtn = 0;
+/* Used to make sure a button press is delivered to the processing
+ * backend. While this is !0, no new incoming messasges are processed.
+ * Counted down by remote_control_rx()
+ */
+int iap_repeatbtn = 0;
+/* Used to time out button down events in case we miss the button up event
+ * from the device somehow.
+ * If a device sends a button down event it's required to repeat that event
+ * every 30 to 100ms as long as the button is pressed, and send an explicit
+ * button up event if the button is released.
+ * In case the button up event is lost any down events will time out after
+ * ~200ms.
+ * iap_periodic() will count down this variable and reset all buttons if
+ * it reaches 0
+ */
+unsigned int iap_timeoutbtn = 0;
+bool iap_btnrepeat = false, iap_btnshuffle = false;
+
+static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)];
+static struct event_queue iap_queue;
+
+/* These are pointer used to manage a dynamically allocated buffer which
+ * will hold both the RX and TX side of things.
+ *
+ * iap_buffer_handle is the handle returned from core_alloc()
+ * iap_buffers points to the start of the complete buffer
+ *
+ * The buffer is partitioned as follows:
+ * - TX_BUFLEN+6 bytes for the TX buffer
+ * The 6 extra bytes are for the sync byte, the SOP byte, the length indicators
+ * (3 bytes) and the checksum byte.
+ * iap_txstart points to the beginning of the TX buffer
+ * iap_txpayload points to the beginning of the payload portion of the TX buffer
+ * iap_txnext points to the position where the next byte will be placed
+ *
+ * - RX_BUFLEN+2 bytes for the RX buffer
+ * The RX buffer can hold multiple packets at once, up to it's
+ * maximum capacity. Every packet consists of a two byte length
+ * indicator followed by the actual payload. The length indicator
+ * is two bytes for every length, even for packets with a length <256
+ * bytes.
+ *
+ * Once a packet has been processed from the RX buffer the rest
+ * of the buffer (and the pointers below) are shifted to the front
+ * so that the next packet again starts at the beginning of the
+ * buffer. This happens with interrupts disabled, to prevent
+ * writing into the buffer during the move.
+ *
+ * iap_rxstart points to the beginning of the RX buffer
+ * iap_rxpayload starts to the beginning of the currently recieved
+ * packet
+ * iap_rxnext points to the position where the next incoming byte
+ * will be placed
+ * iap_rxlen is not a pointer, but an indicator of the free
+ * space left in the RX buffer.
+ *
+ * The RX buffer is placed behind the TX buffer so that an eventual TX
+ * buffer overflow has some place to spill into where it will not cause
+ * immediate damage. See the comments for IAP_TX_* and iap_send_tx()
+ */
+#define IAP_MALLOC_SIZE (TX_BUFLEN+6+RX_BUFLEN+2)
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_buffer_handle;
+#endif
+static unsigned char* iap_buffers;
+static unsigned char* iap_rxstart;
+static unsigned char* iap_rxpayload;
+static unsigned char* iap_rxnext;
+static uint32_t iap_rxlen;
+static unsigned char* iap_txstart;
+unsigned char* iap_txpayload;
+unsigned char* iap_txnext;
+
+/* The versions of the various Lingoes we support. A major version
+ * of 0 means unsupported
+ */
+unsigned char lingo_versions[32][2] = {
+ {1, 9}, /* General lingo, 0x00 */
+ {0, 0}, /* Microphone lingo, 0x01, unsupported */
+ {1, 2}, /* Simple remote lingo, 0x02 */
+ {1, 5}, /* Display remote lingo, 0x03 */
+ {1, 12}, /* Extended Interface lingo, 0x04 */
+ {1, 1}, /* RF/BT Transmitter lingo, 0x05 */
+ {} /* All others are unsupported */
+};
+
+/* states of the iap de-framing state machine */
+enum fsm_state {
+ ST_SYNC, /* wait for 0xFF sync byte */
+ ST_SOF, /* wait for 0x55 start-of-frame byte */
+ ST_LEN, /* receive length byte (small packet) */
+ ST_LENH, /* receive length high byte (large packet) */
+ ST_LENL, /* receive length low byte (large packet) */
+ ST_DATA, /* receive data */
+ ST_CHECK /* verify checksum */
+};
+
+static struct state_t {
+ enum fsm_state state; /* current fsm state */
+ unsigned int len; /* payload data length */
+ unsigned int check; /* running checksum over [len,payload,check] */
+ unsigned int count; /* playload bytes counter */
+} frame_state = {
+ .state = ST_SYNC
+};
+
+enum interface_state interface_state = IST_STANDARD;
+
+struct device_t device;
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new);
+
+static struct buflib_callbacks iap_buflib_callbacks = {
+ iap_move_callback,
+ NULL
+};
+#endif
+
+static void iap_malloc(void);
+
+void put_u16(unsigned char *buf, const uint16_t data)
+{
+ buf[0] = (data >> 8) & 0xFF;
+ buf[1] = (data >> 0) & 0xFF;
+}
+
+void put_u32(unsigned char *buf, const uint32_t data)
+{
+ buf[0] = (data >> 24) & 0xFF;
+ buf[1] = (data >> 16) & 0xFF;
+ buf[2] = (data >> 8) & 0xFF;
+ buf[3] = (data >> 0) & 0xFF;
+}
+
+uint32_t get_u32(const unsigned char *buf)
+{
+ return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
+uint16_t get_u16(const unsigned char *buf)
+{
+ return (buf[0] << 8) | buf[1];
+}
+
+#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
+/* Convert a buffer into a printable string, perl style
+ * buf contains the data to be converted, len is the length
+ * of the buffer.
+ *
+ * This will convert at most 1024 bytes from buf
+ */
+static char* hexstring(const unsigned char *buf, unsigned int len) {
+ static char hexbuf[4097];
+ unsigned int l;
+ const unsigned char* p;
+ unsigned char* out;
+ unsigned char h[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ if (len > 1024) {
+ l = 1024;
+ } else {
+ l = len;
+ }
+ p = buf;
+ out = hexbuf;
+ do {
+ *out++ = h[(*p)>>4];
+ *out++ = h[*p & 0x0F];
+ } while(--l && p++);
+
+ *out = 0x00;
+
+ return hexbuf;
+}
+#endif
+
+
+void iap_tx_strlcpy(const unsigned char *str)
+{
+ ptrdiff_t txfree;
+ int r;
+
+ txfree = TX_BUFLEN - (iap_txnext - iap_txstart);
+ r = strlcpy(iap_txnext, str, txfree);
+
+ if (r < txfree)
+ {
+ /* No truncation occured
+ * Account for the terminating \0
+ */
+ iap_txnext += (r+1);
+ } else {
+ /* Truncation occured, the TX buffer is now full. */
+ iap_txnext = iap_txstart + TX_BUFLEN;
+ }
+}
+
+void iap_reset_auth(struct auth_t* auth)
+{
+ auth->state = AUST_NONE;
+ auth->max_section = 0;
+ auth->next_section = 0;
+}
+
+void iap_reset_device(struct device_t* device)
+{
+ iap_reset_auth(&(device->auth));
+ device->lingoes = 0;
+ device->notifications = 0;
+ device->changed_notifications = 0;
+ device->do_notify = false;
+ device->do_power_notify = false;
+ device->accinfo = ACCST_NONE;
+ device->capabilities = 0;
+ device->capabilities_queried = 0;
+}
+
+static int iap_task(struct timeout *tmo)
+{
+ (void) tmo;
+
+ queue_post(&iap_queue, IAP_EV_TICK, 0);
+ return MS_TO_TICKS(100);
+}
+
+/* This thread is waiting for events posted to iap_queue and calls
+ * the appropriate subroutines in response
+ */
+static void iap_thread(void)
+{
+ struct queue_event ev;
+ while(1) {
+ queue_wait(&iap_queue, &ev);
+ switch (ev.id)
+ {
+ /* Handle the regular 100ms tick used for driving the
+ * authentication state machine and notifications
+ */
+ case IAP_EV_TICK:
+ {
+ iap_periodic();
+ break;
+ }
+
+ /* Handle a newly received message from the device */
+ case IAP_EV_MSG_RCVD:
+ {
+ iap_handlepkt();
+ break;
+ }
+
+ /* Handle memory allocation. This is used only once, during
+ * startup
+ */
+ case IAP_EV_MALLOC:
+ {
+ iap_malloc();
+ break;
+ }
+
+ /* Handle poweroff message */
+ case SYS_POWEROFF:
+ {
+ iap_shutdown = true;
+ break;
+ }
+ }
+ }
+}
+
+/* called by playback when the next track starts */
+static void iap_track_changed(void *ignored)
+{
+ (void)ignored;
+ if ((interface_state == IST_EXTENDED) && device.do_notify) {
+ long playlist_pos = playlist_next(0);
+ playlist_pos -= playlist_get_first_index(NULL);
+ if(playlist_pos < 0)
+ playlist_pos += playlist_amount();
+
+ IAP_TX_INIT4(0x04, 0x0027);
+ IAP_TX_PUT(0x01);
+ IAP_TX_PUT_U32(playlist_pos);
+
+ iap_send_tx();
+ return;
+ }
+}
+
+/* Do general setup of the needed infrastructure.
+ *
+ * Please note that a lot of additional work is done by iap_start()
+ */
+void iap_setup(const int ratenum)
+{
+ iap_bitrate_set(ratenum);
+ iap_remotebtn = BUTTON_NONE;
+ iap_setupflag = true;
+ iap_started = false;
+ iap_running = false;
+}
+
+/* Actually bring up the message queue, message handler thread and
+ * notification timer
+ *
+ * NOTE: This is running in interrupt context
+ */
+static void iap_start(void)
+{
+ unsigned int tid;
+
+ if (iap_started)
+ return;
+
+ iap_reset_device(&device);
+ queue_init(&iap_queue, true);
+ tid = create_thread(iap_thread, thread_stack, sizeof(thread_stack),
+ 0, "iap"
+ IF_PRIO(, PRIORITY_SYSTEM)
+ IF_COP(, CPU));
+ if (!tid)
+ panicf("Could not create iap thread");
+ timeout_register(&iap_task_tmo, iap_task, MS_TO_TICKS(100), (intptr_t)NULL);
+ add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed);
+
+ /* Since we cannot allocate memory while in interrupt context
+ * post a message to our own queue to get that done
+ */
+ queue_post(&iap_queue, IAP_EV_MALLOC, 0);
+ iap_started = true;
+}
+
+static void iap_malloc(void)
+{
+#ifndef IAP_MALLOC_DYNAMIC
+ static unsigned char serbuf[IAP_MALLOC_SIZE];
+#endif
+
+ if (iap_running)
+ return;
+
+#ifdef IAP_MALLOC_DYNAMIC
+ iap_buffer_handle = core_alloc_ex("iap", IAP_MALLOC_SIZE, &iap_buflib_callbacks);
+ if (iap_buffer_handle < 0)
+ panicf("Could not allocate buffer memory");
+ iap_buffers = core_get_data(iap_buffer_handle);
+#else
+ iap_buffers = serbuf;
+#endif
+ iap_txstart = iap_buffers;
+ iap_txpayload = iap_txstart+5;
+ iap_txnext = iap_txpayload;
+ iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+ iap_rxpayload = iap_rxstart;
+ iap_rxnext = iap_rxpayload;
+ iap_rxlen = RX_BUFLEN+2;
+ iap_running = true;
+}
+
+void iap_bitrate_set(const int ratenum)
+{
+ switch(ratenum)
+ {
+ case 0:
+ serial_bitrate(0);
+ break;
+ case 1:
+ serial_bitrate(9600);
+ break;
+ case 2:
+ serial_bitrate(19200);
+ break;
+ case 3:
+ serial_bitrate(38400);
+ break;
+ case 4:
+ serial_bitrate(57600);
+ break;
+ }
+}
+
+/* Message format:
+ 0xff
+ 0x55
+ length
+ mode
+ command (2 bytes)
+ parameters (0-n bytes)
+ checksum (length+mode+parameters+checksum == 0)
+*/
+
+/* Send the current content of the TX buffer.
+ * This will check for TX buffer overflow and panic, but it might
+ * be too late by then (although one would have to overflow the complete
+ * RX buffer as well)
+ */
+void iap_send_tx(void)
+{
+ int i, chksum;
+ ptrdiff_t txlen;
+ unsigned char* txstart;
+
+ txlen = iap_txnext - iap_txpayload;
+
+ if (txlen <= 0)
+ return;
+
+ if (txlen > TX_BUFLEN)
+ panicf("IAP: TX buffer overflow");
+
+ if (txlen < 256)
+ {
+ /* Short packet */
+ txstart = iap_txstart+2;
+ *(txstart+2) = txlen;
+ chksum = txlen;
+ } else {
+ /* Long packet */
+ txstart = iap_txstart;
+ *(txstart+2) = 0x00;
+ *(txstart+3) = (txlen >> 8) & 0xFF;
+ *(txstart+4) = (txlen) & 0xFF;
+ chksum = *(txstart+3) + *(txstart+4);
+ }
+ *(txstart) = 0xFF;
+ *(txstart+1) = 0x55;
+
+ for (i=0; i<txlen; i++)
+ {
+ chksum += iap_txpayload[i];
+ }
+ *(iap_txnext) = 0x100 - (chksum & 0xFF);
+
+#ifdef LOGF_ENABLE
+ logf("T: %s", hexstring(txstart+3, (iap_txnext - txstart)-3));
+#endif
+ for (i=0; i <= (iap_txnext - txstart); i++)
+ {
+ while(!tx_rdy()) ;
+ tx_writec(txstart[i]);
+ }
+}
+
+/* This is just a compatibility wrapper around the new TX buffer
+ * infrastructure
+ */
+void iap_send_pkt(const unsigned char * data, const int len)
+{
+ if (!iap_running)
+ return;
+
+ iap_txnext = iap_txpayload;
+ IAP_TX_PUT_DATA(data, len);
+ iap_send_tx();
+}
+
+bool iap_getc(const unsigned char x)
+{
+ struct state_t *s = &frame_state;
+ static long pkt_timeout;
+
+ if (!iap_setupflag)
+ return false;
+
+ /* Check the time since the last packet arrived. */
+ if ((s->state != ST_SYNC) && TIME_AFTER(current_tick, pkt_timeout)) {
+ /* Packet timeouts only make sense while not waiting for the
+ * sync byte */
+ s->state = ST_SYNC;
+ return iap_getc(x);
+ }
+
+
+ /* run state machine to detect and extract a valid frame */
+ switch (s->state) {
+ case ST_SYNC:
+ if (x == 0xFF) {
+ /* The IAP infrastructure is started by the first received sync
+ * byte. It takes a while to spin up, so do not advance the state
+ * machine until it has started.
+ */
+ if (!iap_running)
+ {
+ iap_start();
+ break;
+ }
+ iap_rxnext = iap_rxpayload;
+ s->state = ST_SOF;
+ }
+ break;
+ case ST_SOF:
+ if (x == 0x55) {
+ /* received a valid sync/SOF pair */
+ s->state = ST_LEN;
+ } else {
+ s->state = ST_SYNC;
+ return iap_getc(x);
+ }
+ break;
+ case ST_LEN:
+ s->check = x;
+ s->count = 0;
+ if (x == 0) {
+ /* large packet */
+ s->state = ST_LENH;
+ } else {
+ /* small packet */
+ if (x > (iap_rxlen-2))
+ {
+ /* Packet too long for buffer */
+ s->state = ST_SYNC;
+ break;
+ }
+ s->len = x;
+ s->state = ST_DATA;
+ put_u16(iap_rxnext, s->len);
+ iap_rxnext += 2;
+ }
+ break;
+ case ST_LENH:
+ s->check += x;
+ s->len = x << 8;
+ s->state = ST_LENL;
+ break;
+ case ST_LENL:
+ s->check += x;
+ s->len += x;
+ if ((s->len == 0) || (s->len > (iap_rxlen-2))) {
+ /* invalid length */
+ s->state = ST_SYNC;
+ break;
+ } else {
+ s->state = ST_DATA;
+ put_u16(iap_rxnext, s->len);
+ iap_rxnext += 2;
+ }
+ break;
+ case ST_DATA:
+ s->check += x;
+ *(iap_rxnext++) = x;
+ s->count += 1;
+ if (s->count == s->len) {
+ s->state = ST_CHECK;
+ }
+ break;
+ case ST_CHECK:
+ s->check += x;
+ if ((s->check & 0xFF) == 0) {
+ /* done, received a valid frame */
+ iap_rxpayload = iap_rxnext;
+ queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+ } else {
+ /* Invalid frame */
+ }
+ s->state = ST_SYNC;
+ break;
+ default:
+#ifdef LOGF_ENABLE
+ logf("Unhandled iap state %d", (int) s->state);
+#else
+ panicf("Unhandled iap state %d", (int) s->state);
+#endif
+ break;
+ }
+
+ pkt_timeout = current_tick + IAP_PKT_TIMEOUT;
+
+ /* return true while still hunting for the sync and start-of-frame byte */
+ return (s->state == ST_SYNC) || (s->state == ST_SOF);
+}
+
+void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3)
+{
+ int tracknum;
+ int fd;
+ struct playlist_track_info info;
+
+ tracknum = track;
+
+ tracknum += playlist_get_first_index(NULL);
+ if(tracknum >= playlist_amount())
+ tracknum -= playlist_amount();
+
+ /* If the tracknumber is not the current one,
+ read id3 from disk */
+ if(playlist_next(0) != tracknum)
+ {
+ playlist_get_track_info(NULL, tracknum, &info);
+ fd = open(info.filename, O_RDONLY);
+ memset(id3, 0, sizeof(*id3));
+ get_metadata(id3, fd, info.filename);
+ close(fd);
+ } else {
+ memcpy(id3, audio_current_track(), sizeof(*id3));
+ }
+}
+
+uint32_t iap_get_trackpos(void)
+{
+ struct mp3entry *id3 = audio_current_track();
+
+ return id3->elapsed;
+}
+
+uint32_t iap_get_trackindex(void)
+{
+ struct playlist_info* playlist = playlist_get_current();
+
+ return (playlist->index - playlist->first_index);
+}
+
+void iap_periodic(void)
+{
+ static int count;
+
+ if(!iap_setupflag) return;
+
+ /* Handle pending authentication tasks */
+ switch (device.auth.state)
+ {
+ case AUST_INIT:
+ {
+ /* Send out GetDevAuthenticationInfo */
+ IAP_TX_INIT(0x00, 0x14);
+
+ iap_send_tx();
+ device.auth.state = AUST_CERTREQ;
+ break;
+ }
+
+ case AUST_CERTDONE:
+ {
+ /* Send out GetDevAuthenticationSignature, with
+ * 20 bytes of challenge and a retry counter of 1.
+ * Since we do not really care about the content of the
+ * challenge we just use the first 20 bytes of whatever
+ * is in the RX buffer right now.
+ */
+ IAP_TX_INIT(0x00, 0x17);
+ IAP_TX_PUT_DATA(iap_rxstart, 20);
+ IAP_TX_PUT(0x01);
+
+ iap_send_tx();
+ device.auth.state = AUST_CHASENT;
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ /* Time out button down events */
+ if (iap_timeoutbtn)
+ iap_timeoutbtn -= 1;
+
+ if (!iap_timeoutbtn)
+ {
+ iap_remotebtn = BUTTON_NONE;
+ iap_repeatbtn = 0;
+ iap_btnshuffle = false;
+ iap_btnrepeat = false;
+ }
+
+ /* Handle power down messages. */
+ if (iap_shutdown && device.do_power_notify)
+ {
+ /* NotifyiPodStateChange */
+ IAP_TX_INIT(0x00, 0x23);
+ IAP_TX_PUT(0x01);
+
+ iap_send_tx();
+
+ /* No further actions, we're going down */
+ iap_reset_device(&device);
+ return;
+ }
+
+ /* Handle GetAccessoryInfo messages */
+ if (device.accinfo == ACCST_INIT)
+ {
+ /* GetAccessoryInfo */
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ device.accinfo = ACCST_SENT;
+ }
+
+ /* Do not send requests for device information while
+ * an authentication is still running, this seems to
+ * confuse some devices
+ */
+ if (!DEVICE_AUTH_RUNNING && (device.accinfo == ACCST_DATA))
+ {
+ int first_set;
+
+ /* Find the first bit set in the capabilities field,
+ * ignoring those we already asked for
+ */
+ first_set = find_first_set_bit(device.capabilities & (~device.capabilities_queried));
+
+ if (first_set != 32)
+ {
+ /* Add bit to queried cababilities */
+ device.capabilities_queried |= BIT_N(first_set);
+
+ switch (first_set)
+ {
+ /* Name */
+ case 0x01:
+ /* Firmware version */
+ case 0x04:
+ /* Hardware version */
+ case 0x05:
+ /* Manufacturer */
+ case 0x06:
+ /* Model number */
+ case 0x07:
+ /* Serial number */
+ case 0x08:
+ /* Maximum payload size */
+ case 0x09:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(first_set);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* Minimum supported iPod firmware version */
+ case 0x02:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(2);
+ IAP_TX_PUT_U32(IAP_IPOD_MODEL);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* Minimum supported lingo version. Queries Lingo 0 */
+ case 0x03:
+ {
+ IAP_TX_INIT(0x00, 0x27);
+ IAP_TX_PUT(3);
+ IAP_TX_PUT(0);
+
+ iap_send_tx();
+ break;
+ }
+ }
+
+ device.accinfo = ACCST_SENT;
+ }
+ }
+
+ if (!device.do_notify) return;
+ if (device.notifications == 0) return;
+
+ /* Volume change notifications are sent every 100ms */
+ if (device.notifications & (BIT_N(4) | BIT_N(16))) {
+ /* Currently we do not track volume changes, so this is
+ * never sent.
+ *
+ * TODO: Fix volume tracking
+ */
+ }
+
+ /* All other events are sent every 500ms */
+ count += 1;
+ if (count < 5) return;
+
+ count = 0;
+
+ /* RemoteEventNotification */
+
+ /* Mode 04 PlayStatusChangeNotification */
+ /* Are we in Extended Mode */
+ if (interface_state == IST_EXTENDED) {
+ /* Return Track Position */
+ struct mp3entry *id3 = audio_current_track();
+ unsigned long time_elapsed = id3->elapsed;
+ IAP_TX_INIT4(0x04, 0x0027);
+ IAP_TX_PUT(0x04);
+ IAP_TX_PUT_U32(time_elapsed);
+
+ iap_send_tx();
+ }
+
+ /* Track position (ms) or Track position (s) */
+ if (device.notifications & (BIT_N(0) | BIT_N(15)))
+ {
+ uint32_t t;
+ uint16_t ts;
+ bool changed;
+
+ t = iap_get_trackpos();
+ ts = (t / 1000) & 0xFFFF;
+
+ if ((device.notifications & BIT_N(0)) && (device.trackpos_ms != t))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT_U32(t);
+ device.changed_notifications |= BIT_N(0);
+ changed = true;
+
+ iap_send_tx();
+ }
+
+ if ((device.notifications & BIT_N(15)) && (device.trackpos_s != ts)) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x0F);
+ IAP_TX_PUT_U16(ts);
+ device.changed_notifications |= BIT_N(15);
+ changed = true;
+
+ iap_send_tx();
+ }
+
+ if (changed)
+ {
+ device.trackpos_ms = t;
+ device.trackpos_s = ts;
+ }
+ }
+
+ /* Track index */
+ if (device.notifications & BIT_N(1))
+ {
+ uint32_t index;
+
+ index = iap_get_trackindex();
+
+ if (device.track_index != index) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x01);
+ IAP_TX_PUT_U32(index);
+ device.changed_notifications |= BIT_N(1);
+
+ iap_send_tx();
+
+ device.track_index = index;
+ }
+ }
+
+ /* Chapter index */
+ if (device.notifications & BIT_N(2))
+ {
+ uint32_t index;
+
+ index = iap_get_trackindex();
+
+ if (device.track_index != index)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x02);
+ IAP_TX_PUT_U32(index);
+ IAP_TX_PUT_U16(0);
+ IAP_TX_PUT_U16(0xFFFF);
+ device.changed_notifications |= BIT_N(2);
+
+ iap_send_tx();
+
+ device.track_index = index;
+ }
+ }
+
+ /* Play status */
+ if (device.notifications & BIT_N(3))
+ {
+ unsigned char play_status;
+
+ play_status = audio_status();
+
+ if (device.play_status != play_status)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x03);
+ if (play_status & AUDIO_STATUS_PLAY) {
+ /* Playing or paused */
+ if (play_status & AUDIO_STATUS_PAUSE) {
+ /* Paused */
+ IAP_TX_PUT(0x02);
+ } else {
+ /* Playing */
+ IAP_TX_PUT(0x01);
+ }
+ } else {
+ IAP_TX_PUT(0x00);
+ }
+ device.changed_notifications |= BIT_N(3);
+
+ iap_send_tx();
+
+ device.play_status = play_status;
+ }
+ }
+
+ /* Power/Battery */
+ if (device.notifications & BIT_N(5))
+ {
+ unsigned char power_state;
+ unsigned char battery_l;
+
+ power_state = charger_input_state;
+ battery_l = battery_level();
+
+ if ((device.power_state != power_state) || (device.battery_level != battery_l))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x05);
+
+ iap_fill_power_state();
+ device.changed_notifications |= BIT_N(5);
+
+ iap_send_tx();
+
+ device.power_state = power_state;
+ device.battery_level = battery_l;
+ }
+ }
+
+ /* Equalizer state
+ * This is not handled yet.
+ *
+ * TODO: Fix equalizer handling
+ */
+
+ /* Shuffle */
+ if (device.notifications & BIT_N(7))
+ {
+ unsigned char shuffle;
+
+ shuffle = global_settings.playlist_shuffle;
+
+ if (device.shuffle != shuffle)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x07);
+ IAP_TX_PUT(shuffle?0x01:0x00);
+ device.changed_notifications |= BIT_N(7);
+
+ iap_send_tx();
+
+ device.shuffle = shuffle;
+ }
+ }
+
+ /* Repeat */
+ if (device.notifications & BIT_N(8))
+ {
+ unsigned char repeat;
+
+ repeat = global_settings.repeat_mode;
+
+ if (device.repeat != repeat)
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x08);
+ switch (repeat)
+ {
+ case REPEAT_OFF:
+ {
+ IAP_TX_PUT(0x00);
+ break;
+ }
+
+ case REPEAT_ONE:
+ {
+ IAP_TX_PUT(0x01);
+ break;
+ }
+
+ case REPEAT_ALL:
+ {
+ IAP_TX_PUT(0x02);
+ break;
+ }
+ }
+ device.changed_notifications |= BIT_N(8);
+
+ iap_send_tx();
+
+ device.repeat = repeat;
+ }
+ }
+
+ /* Date/Time */
+ if (device.notifications & BIT_N(9))
+ {
+ struct tm* tm;
+
+ tm = get_time();
+
+ if (memcmp(tm, &(device.datetime), sizeof(struct tm)))
+ {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x09);
+ IAP_TX_PUT_U16(tm->tm_year);
+
+ /* Month */
+ IAP_TX_PUT(tm->tm_mon+1);
+
+ /* Day */
+ IAP_TX_PUT(tm->tm_mday);
+
+ /* Hour */
+ IAP_TX_PUT(tm->tm_hour);
+
+ /* Minute */
+ IAP_TX_PUT(tm->tm_min);
+
+ device.changed_notifications |= BIT_N(9);
+
+ iap_send_tx();
+
+ memcpy(&(device.datetime), tm, sizeof(struct tm));
+ }
+ }
+
+ /* Alarm
+ * This is not supported yet.
+ *
+ * TODO: Fix alarm handling
+ */
+
+ /* Backlight
+ * This is not supported yet.
+ *
+ * TODO: Fix backlight handling
+ */
+
+ /* Hold switch */
+ if (device.notifications & BIT_N(0x0C))
+ {
+ unsigned char hold;
+
+ hold = button_hold();
+ if (device.hold != hold) {
+ IAP_TX_INIT(0x03, 0x09);
+ IAP_TX_PUT(0x0C);
+ IAP_TX_PUT(hold?0x01:0x00);
+
+ device.changed_notifications |= BIT_N(0x0C);
+
+ iap_send_tx();
+
+ device.hold = hold;
+ }
+ }
+
+ /* Sound check
+ * This is not supported yet.
+ *
+ * TODO: Fix sound check handling
+ */
+
+ /* Audiobook check
+ * This is not supported yet.
+ *
+ * TODO: Fix audiobook handling
+ */
+}
+
+/* Change the current interface state.
+ * On a change from IST_EXTENDED to IST_STANDARD, or from IST_STANDARD
+ * to IST_EXTENDED, pause playback, if playing
+ */
+void iap_interface_state_change(const enum interface_state new)
+{
+ if (((interface_state == IST_EXTENDED) && (new == IST_STANDARD)) ||
+ ((interface_state == IST_STANDARD) && (new == IST_EXTENDED))) {
+ if (audio_status() == AUDIO_STATUS_PLAY)
+ {
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ }
+ }
+
+ interface_state = new;
+}
+
+static void iap_handlepkt_mode5(const unsigned int len, const unsigned char *buf)
+{
+ (void) len;
+ unsigned int cmd = buf[1];
+ switch (cmd)
+ {
+ /* Sent from iPod Begin Transmission */
+ case 0x02:
+ {
+ /* RF Transmitter: Begin High Power transmission */
+ unsigned char data0[] = {0x05, 0x02};
+ iap_send_pkt(data0, sizeof(data0));
+ break;
+ }
+
+ /* Sent from iPod End High Power Transmission */
+ case 0x03:
+ {
+ /* RF Transmitter: End High Power transmission */
+ unsigned char data1[] = {0x05, 0x03};
+ iap_send_pkt(data1, sizeof(data1));
+ break;
+ }
+ /* Return Version Number ?*/
+ case 0x04:
+ {
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+#if 0
+static void iap_handlepkt_mode7(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = buf[1];
+ switch (cmd)
+ {
+ /* RetTunerCaps */
+ case 0x02:
+ {
+ /* do nothing */
+
+ /* GetAccessoryInfo */
+ unsigned char data[] = {0x00, 0x27, 0x00};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+
+ /* RetTunerFreq */
+ case 0x0A:
+ /* fall through */
+ /* TunerSeekDone */
+ case 0x13:
+ {
+ rmt_tuner_freq(len, buf);
+ break;
+ }
+
+ /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/
+ case 0x21:
+ {
+ rmt_tuner_rds_data(len, buf);
+ break;
+ }
+ }
+}
+#endif
+
+void iap_handlepkt(void)
+{
+ int level;
+ int length;
+
+ if(!iap_setupflag) return;
+
+ /* if we are waiting for a remote button to go out,
+ delay the handling of the new packet */
+ if(iap_repeatbtn)
+ {
+ queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+ sleep(1);
+ return;
+ }
+
+ /* handle command by mode */
+ length = get_u16(iap_rxstart);
+#ifdef LOGF_ENABLE
+ logf("R: %s", hexstring(iap_rxstart+2, (length)));
+#endif
+
+ unsigned char mode = *(iap_rxstart+2);
+ switch (mode) {
+ case 0: iap_handlepkt_mode0(length, iap_rxstart+2); break;
+ case 2: iap_handlepkt_mode2(length, iap_rxstart+2); break;
+ case 3: iap_handlepkt_mode3(length, iap_rxstart+2); break;
+ case 4: iap_handlepkt_mode4(length, iap_rxstart+2); break;
+ case 5: iap_handlepkt_mode5(length, iap_rxstart+2); break;
+ /* case 7: iap_handlepkt_mode7(length, iap_rxstart+2); break; */
+ }
+
+ /* Remove the handled packet from the RX buffer
+ * This needs to be done with interrupts disabled, to make
+ * sure the buffer and the pointers into it are handled
+ * cleanly
+ */
+ level = disable_irq_save();
+ memmove(iap_rxstart, iap_rxstart+(length+2), (RX_BUFLEN+2)-(length+2));
+ iap_rxnext -= (length+2);
+ iap_rxpayload -= (length+2);
+ iap_rxlen += (length+2);
+ restore_irq(level);
+
+ /* poke the poweroff timer */
+ reset_poweroff_timer();
+}
+
+int remote_control_rx(void)
+{
+ int btn = iap_remotebtn;
+ if(iap_repeatbtn)
+ iap_repeatbtn--;
+
+ return btn;
+}
+
+const unsigned char *iap_get_serbuf(void)
+{
+ return iap_rxstart;
+}
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new)
+{
+ (void) handle;
+ (void) current;
+
+ iap_txstart = new;
+ iap_txpayload = iap_txstart+5;
+ iap_txnext = iap_txpayload;
+ iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+
+ return BUFLIB_CB_OK;
+}
+#endif
+
+/* Change the shuffle state */
+void iap_shuffle_state(const bool state)
+{
+ /* Set shuffle to enabled */
+ if(state && !global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 1;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_randomise(NULL, current_tick, true);
+ }
+ /* Set shuffle to disabled */
+ else if(!state && global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 0;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_sort(NULL, true);
+ }
+}
+
+/* Change the repeat state */
+void iap_repeat_state(const unsigned char state)
+{
+ if (state != global_settings.repeat_mode)
+ {
+ global_settings.repeat_mode = state;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ audio_flush_and_reload_tracks();
+ }
+}
+
+void iap_repeat_next(void)
+{
+ switch (global_settings.repeat_mode)
+ {
+ case REPEAT_OFF:
+ {
+ iap_repeat_state(REPEAT_ALL);
+ break;
+ }
+ case REPEAT_ALL:
+ {
+ iap_repeat_state(REPEAT_ONE);
+ break;
+ }
+ case REPEAT_ONE:
+ {
+ iap_repeat_state(REPEAT_OFF);
+ break;
+ }
+ }
+}
+
+/* This function puts the current power/battery state
+ * into the TX buffer. The buffer is assumed to be initialized
+ */
+void iap_fill_power_state(void)
+{
+ unsigned char power_state;
+ unsigned char battery_l;
+
+ power_state = charger_input_state;
+ battery_l = battery_level();
+
+ if (power_state == NO_CHARGER) {
+ if (battery_l < 30) {
+ IAP_TX_PUT(0x00);
+ } else {
+ IAP_TX_PUT(0x01);
+ }
+ IAP_TX_PUT((char)((battery_l * 255)/100));
+ } else {
+ IAP_TX_PUT(0x04);
+ IAP_TX_PUT(0x00);
+ }
+}
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _IAP_CORE_H
+#define _IAP_CORE_H
+
+#include <stdint.h>
+#include <string.h>
+#include "timefuncs.h"
+#include "metadata.h"
+#include "playlist.h"
+#include "iap.h"
+
+#define LOGF_ENABLE
+/* #undef LOGF_ENABLE */
+#ifdef LOGF_ENABLE
+ #include "logf.h"
+#endif
+
+/* The Model ID of the iPod we emulate. Currently a 160GB classic */
+#define IAP_IPOD_MODEL (0x00130200U)
+
+/* The firmware version we emulate. Currently 2.0.3 */
+#define IAP_IPOD_FIRMWARE_MAJOR (2)
+#define IAP_IPOD_FIRMWARE_MINOR (0)
+#define IAP_IPOD_FIRMWARE_REV (3)
+
+/* Status code for IAP ack messages */
+#define IAP_ACK_OK (0x00) /* Success */
+#define IAP_ACK_UNKNOWN_DB (0x01) /* Unknown Database Category */
+#define IAP_ACK_CMD_FAILED (0x02) /* Command failed */
+#define IAP_ACK_NO_RESOURCE (0x03) /* Out of resources */
+#define IAP_ACK_BAD_PARAM (0x04) /* Bad parameter */
+#define IAP_ACK_UNKNOWN_ID (0x05) /* Unknown ID */
+#define IAP_ACK_PENDING (0x06) /* Command pending */
+#define IAP_ACK_NO_AUTHEN (0x07) /* Not authenticated */
+#define IAP_ACK_BAD_AUTHEN (0x08) /* Bad authentication version */
+/* 0x09 reserved */
+#define IAP_ACK_CERT_INVAL (0x0A) /* Certificate invalid */
+#define IAP_ACK_CERT_PERM (0x0B) /* Certificate permissions invalid */
+/* 0x0C-0x10 reserved */
+#define IAP_ACK_RES_INVAL (0x11) /* Invalid accessory resistor value */
+
+/* Add a button to the remote button bitfield. Also set iap_repeatbtn=1
+ * to ensure a button press is at least delivered once.
+ */
+#define REMOTE_BUTTON(x) do { \
+ iap_remotebtn |= (x); \
+ iap_timeoutbtn = 3; \
+ iap_repeatbtn = 2; \
+ } while(0)
+
+/* States of the extended command support */
+enum interface_state {
+ IST_STANDARD, /* General state, support lingo 0x00 commands */
+ IST_EXTENDED, /* Extended Interface lingo (0x04) negotiated */
+};
+
+/* States of the authentication state machine */
+enum authen_state {
+ AUST_NONE, /* Initial state, no message sent */
+ AUST_INIT, /* Remote side has requested authentication */
+ AUST_CERTREQ, /* Remote certificate requested */
+ AUST_CERTBEG, /* Certificate is being received */
+ AUST_CERTDONE, /* Certificate received */
+ AUST_CHASENT, /* Challenge sent */
+ AUST_CHADONE, /* Challenge response received */
+ AUST_AUTH, /* Authentication complete */
+};
+
+/* State of authentication */
+struct auth_t {
+ enum authen_state state; /* Current state of authentication */
+ unsigned char max_section; /* The maximum number of certificate sections */
+ unsigned char next_section; /* The next expected section number */
+};
+
+/* State of GetAccessoryInfo */
+enum accinfo_state {
+ ACCST_NONE, /* Initial state, no message sent */
+ ACCST_INIT, /* Send out initial GetAccessoryInfo */
+ ACCST_SENT, /* Wait for initial RetAccessoryInfo */
+ ACCST_DATA, /* Query device information, according to capabilities */
+};
+
+/* A struct describing an attached device and it's current
+ * state
+ */
+struct device_t {
+ struct auth_t auth; /* Authentication state */
+ enum accinfo_state accinfo; /* Accessory information state */
+ uint32_t lingoes; /* Negotiated lingoes */
+ uint32_t notifications; /* Requested notifications. These are just the
+ * notifications explicitly requested by the
+ * device
+ */
+ uint32_t changed_notifications; /* Tracks notifications that changed since the last
+ * call to SetRemoteEventNotification or GetRemoteEventStatus
+ */
+ bool do_notify; /* Notifications enabled */
+ bool do_power_notify; /* Whether to send power change notifications.
+ * These are sent automatically to all devices
+ * that used IdentifyDeviceLingoes to identify
+ * themselves, independent of other notifications
+ */
+
+ uint32_t trackpos_ms; /* These fields are to save the current state */
+ uint32_t track_index; /* of various fields so we can send a notification */
+ uint32_t chapter_index; /* if they change */
+ unsigned char play_status;
+ bool mute;
+ unsigned char volume;
+ unsigned char power_state;
+ unsigned char battery_level;
+ uint32_t equalizer_index;
+ unsigned char shuffle;
+ unsigned char repeat;
+ struct tm datetime;
+ unsigned char alarm_state;
+ unsigned char alarm_hour;
+ unsigned char alarm_minute;
+ unsigned char backlight;
+ bool hold;
+ unsigned char soundcheck;
+ unsigned char audiobook;
+ uint16_t trackpos_s;
+ uint32_t capabilities; /* Capabilities of the device, as returned by type 0
+ * of GetAccessoryInfo
+ */
+ uint32_t capabilities_queried; /* Capabilities already queried */
+};
+
+extern struct device_t device;
+#define DEVICE_AUTHENTICATED (device.auth.state == AUST_AUTH)
+#define DEVICE_AUTH_RUNNING ((device.auth.state != AUST_NONE) && (device.auth.state != AUST_AUTH))
+#define DEVICE_LINGO_SUPPORTED(x) (device.lingoes & BIT_N((x)&0x1f))
+
+extern unsigned long iap_remotebtn;
+extern unsigned int iap_timeoutbtn;
+extern int iap_repeatbtn;
+
+extern unsigned char* iap_txpayload;
+extern unsigned char* iap_txnext;
+
+/* These are a number of helper macros to manage the dynamic TX buffer content
+ * These macros DO NOT CHECK for buffer overflow. iap_send_tx() will, but
+ * it might be too late at that point. See the current size of TX_BUFLEN
+ */
+
+/* Initialize the TX buffer with a lingo and command ID. This will reset the
+ * data pointer, effectively invalidating unsent information in the TX buffer.
+ * There are two versions of this, one for 1 byte command IDs (all Lingoes except
+ * 0x04) and one for two byte command IDs (Lingo 0x04)
+ */
+#define IAP_TX_INIT(lingo, command) do { \
+ iap_txnext = iap_txpayload; \
+ IAP_TX_PUT((lingo)); \
+ IAP_TX_PUT((command)); \
+ } while (0)
+
+#define IAP_TX_INIT4(lingo, command) do { \
+ iap_txnext = iap_txpayload; \
+ IAP_TX_PUT((lingo)); \
+ IAP_TX_PUT_U16((command)); \
+ } while (0)
+
+/* Put an unsigned char into the TX buffer */
+#define IAP_TX_PUT(data) *(iap_txnext++) = (data)
+
+/* Put a 16bit unsigned quantity into the TX buffer */
+#define IAP_TX_PUT_U16(data) do { \
+ put_u16(iap_txnext, (data)); \
+ iap_txnext += 2; \
+ } while (0)
+
+/* Put a 32bit unsigned quantity into the TX buffer */
+#define IAP_TX_PUT_U32(data) do { \
+ put_u32(iap_txnext, (data)); \
+ iap_txnext += 4; \
+ } while (0)
+
+/* Put an arbitrary amount of data (identified by a char pointer and
+ * a length) into the TX buffer
+ */
+#define IAP_TX_PUT_DATA(data, len) do { \
+ memcpy(iap_txnext, (unsigned char *)(data), (len)); \
+ iap_txnext += (len); \
+ } while(0)
+
+/* Put a NULL terminated string into the TX buffer, including the
+ * NULL byte
+ */
+#define IAP_TX_PUT_STRING(str) IAP_TX_PUT_DATA((str), strlen((str))+1)
+
+/* Put a NULL terminated string into the TX buffer, taking care not to
+ * overflow the buffer. If the string does not fit into the TX buffer
+ * it will be truncated, but always NULL terminated.
+ *
+ * This function is expensive compared to the other IAP_TX_PUT_*
+ * functions
+ */
+#define IAP_TX_PUT_STRLCPY(str) iap_tx_strlcpy(str)
+
+extern unsigned char lingo_versions[32][2];
+#define LINGO_SUPPORTED(x) (LINGO_MAJOR((x)&0x1f) > 0)
+#define LINGO_MAJOR(x) (lingo_versions[(x)&0x1f][0])
+#define LINGO_MINOR(x) (lingo_versions[(x)&0x1f][1])
+
+void put_u16(unsigned char *buf, const uint16_t data);
+void put_u32(unsigned char *buf, const uint32_t data);
+uint32_t get_u32(const unsigned char *buf);
+uint16_t get_u16(const unsigned char *buf);
+void iap_tx_strlcpy(const unsigned char *str);
+
+void iap_reset_auth(struct auth_t* auth);
+void iap_reset_device(struct device_t* device);
+
+void iap_shuffle_state(bool state);
+void iap_repeat_state(unsigned char state);
+void iap_repeat_next(void);
+void iap_fill_power_state(void);
+
+void iap_send_tx(void);
+
+extern enum interface_state interface_state;
+void iap_interface_state_change(const enum interface_state new);
+
+extern bool iap_btnrepeat;
+extern bool iap_btnshuffle;
+
+uint32_t iap_get_trackpos(void);
+uint32_t iap_get_trackindex(void);
+void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3);
+
+#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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf);
+void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf);
+void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf);
+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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "kernel.h"
+#include "system.h"
+
+/*
+ * This macro is meant to be used inside an IAP mode message handler.
+ * It is passed the expected minimum length of the message buffer.
+ * If the buffer does not have the required lenght an ACK
+ * packet with a Bad Parameter error is generated.
+ */
+#define CHECKLEN(x) do { \
+ if (len < (x)) { \
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
+ return; \
+ }} while(0)
+
+/* Check for authenticated state, and return an ACK Not
+ * Authenticated on failure.
+ */
+#define CHECKAUTH do { \
+ if (!DEVICE_AUTHENTICATED) { \
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
+ return; \
+ }} while(0)
+
+static void cmd_ack(const unsigned char cmd, const unsigned char status)
+{
+ IAP_TX_INIT(0x00, 0x02);
+ IAP_TX_PUT(status);
+ IAP_TX_PUT(cmd);
+ iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+static void cmd_pending(const unsigned char cmd, const uint32_t msdelay)
+{
+ IAP_TX_INIT(0x00, 0x02);
+ IAP_TX_PUT(0x06);
+ IAP_TX_PUT(cmd);
+ IAP_TX_PUT_U32(msdelay);
+ iap_send_tx();
+}
+
+void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = buf[1];
+
+ /* We expect at least two bytes in the buffer, one for the
+ * lingo, one for the command
+ */
+ CHECKLEN(2);
+
+ switch (cmd) {
+ /* RequestIdentify (0x00)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* Identify (0x01)
+ * This command is deprecated.
+ *
+ * It is used by a device to inform the iPod of the devices
+ * presence and of the lingo the device supports.
+ *
+ * Also, it is used to negotiate power for RF transmitters
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x01
+ * 0x02: Lingo supported by the device
+ *
+ * Some RF transmitters use an extended version of this
+ * command:
+ *
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x01
+ * 0x02: Lingo supported by the device, always 0x05 (RF Transmitter)
+ * 0x03: Reserved, always 0x00
+ * 0x04: Number of valid bits in the following fields
+ * 0x05-N: Datafields holding the number of bits specified in 0x04
+ *
+ * Returns: (none)
+ *
+ * TODO:
+ * BeginHighPower/EndHighPower should be send in the periodic handler,
+ * depending on the current play status
+ */
+ case 0x01:
+ {
+ unsigned char lingo = buf[2];
+
+ /* This is sufficient even for Lingo 0x05, as we are
+ * not actually reading from the extended bits for now
+ */
+ CHECKLEN(3);
+
+ /* Issuing this command exits any extended interface states
+ * and resets authentication
+ */
+ iap_interface_state_change(IST_STANDARD);
+ iap_reset_device(&device);
+
+ switch (lingo) {
+ case 0x04:
+ {
+ /* A single lingo device negotiating the
+ * extended interface lingo. This causes an interface
+ * state change.
+ */
+ iap_interface_state_change(IST_EXTENDED);
+ break;
+ }
+
+ case 0x05:
+ {
+ /* FM transmitter sends this: */
+ /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */
+ sleep(HZ/3);
+ /* RF Transmitter: Begin transmission */
+ IAP_TX_INIT(0x05, 0x02);
+
+ iap_send_tx();
+ break;
+ }
+ }
+
+ if (lingo < 32) {
+ /* All devices that Identify get access to Lingoes 0x00 and 0x02 */
+ device.lingoes = BIT_N(0x00) | BIT_N(0x02);
+
+ device.lingoes |= BIT_N(lingo);
+
+ /* Devices that Identify with Lingo 0x04 also gain access
+ * to Lingo 0x03
+ */
+ if (lingo == 0x04)
+ device.lingoes |= BIT_N(0x03);
+ } else {
+ device.lingoes = 0;
+ }
+ break;
+ }
+
+ /* ACK (0x02)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RequestRemoteUIMode (0x03)
+ *
+ * Request the current Extended Interface Mode state
+ * This command may be used only if the accessory requests Lingo 0x04
+ * during its identification process.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x03
+ *
+ * Returns on success:
+ * ReturnRemoteUIMode
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x04
+ * 0x02: Current Extended Interface Mode (zero: false, non-zero: true)
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x03:
+ {
+ if (!DEVICE_LINGO_SUPPORTED(0x04)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ IAP_TX_INIT(0x00, 0x04);
+ if (interface_state == IST_EXTENDED)
+ IAP_TX_PUT(0x01);
+ else
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* ReturnRemoteUIMode (0x04)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* EnterRemoteUIMode (0x05)
+ *
+ * Request Extended Interface Mode
+ * This command may be used only if the accessory requests Lingo 0x04
+ * during its identification process.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x05
+ *
+ * Returns on success:
+ * IAP_ACK_PENDING
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x05:
+ {
+ if (!DEVICE_LINGO_SUPPORTED(0x04)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ cmd_pending(cmd, 1000);
+ iap_interface_state_change(IST_EXTENDED);
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* ExitRemoteUIMode (0x06)
+ *
+ * Leave Extended Interface Mode
+ * This command may be used only if the accessory requests Lingo 0x04
+ * during its identification process.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x06
+ *
+ * Returns on success:
+ * IAP_ACK_PENDING
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x06:
+ {
+ if (!DEVICE_LINGO_SUPPORTED(0x04)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ cmd_pending(cmd, 1000);
+ iap_interface_state_change(IST_STANDARD);
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* RequestiPodName (0x07)
+ *
+ * Retrieves the name of the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x07
+ *
+ * Returns:
+ * ReturniPodName
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x08
+ * 0x02-0xNN: iPod name as NULL-terminated UTF8 string
+ */
+ case 0x07:
+ {
+ IAP_TX_INIT(0x00, 0x08);
+ IAP_TX_PUT_STRING("ROCKBOX");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* ReturniPodName (0x08)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RequestiPodSoftwareVersion (0x09)
+ *
+ * Returns the major, minor and revision numbers of the iPod
+ * software version. This not any Lingo protocol version.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x09
+ *
+ * Returns:
+ * ReturniPodSoftwareVersion
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0A
+ * 0x02: iPod major software version
+ * 0x03: iPod minor software version
+ * 0x04: iPod revision software version
+ */
+ case 0x09:
+ {
+ IAP_TX_INIT(0x00, 0x0A);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR);
+ IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* ReturniPodSoftwareVersion (0x0A)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RequestiPodSerialNum (0x0B)
+ *
+ * Returns the iPod serial number
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0B
+ *
+ * Returns:
+ * ReturniPodSerialNumber
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0C
+ * 0x02-0xNN: Serial number as NULL-terminated UTF8 string
+ */
+ case 0x0B:
+ {
+ IAP_TX_INIT(0x00, 0x0C);
+ IAP_TX_PUT_STRING("0123456789");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* ReturniPodSerialNum (0x0C)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RequestiPodModelNum (0x0D)
+ *
+ * Returns the model number as a 32bit unsigned integer and
+ * as a string.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0D
+ *
+ * Returns:
+ * ReturniPodModelNum
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0E
+ * 0x02-0x05: Model number as 32bit integer
+ * 0x06-0xNN: Model number as NULL-terminated UTF8 string
+ */
+ case 0x0D:
+ {
+ IAP_TX_INIT(0x00, 0x0E);
+ IAP_TX_PUT_U32(IAP_IPOD_MODEL);
+ IAP_TX_PUT_STRING("ROCKBOX");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* ReturniPodSerialNum (0x0E)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RequestLingoProtocolVersion (0x0F)
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x0F
+ * 0x02: Lingo for which to request version information
+ *
+ * Returns on success:
+ * ReturnLingoProtocolVersion
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x10
+ * 0x02: Lingo for which version information is returned
+ * 0x03: Major protocol version for the given lingo
+ * 0x04: Minor protocol version for the given lingo
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x0F:
+ {
+ unsigned char lingo = buf[2];
+
+ CHECKLEN(3);
+
+ /* Supported lingos and versions are read from the lingo_versions
+ * array
+ */
+ if (LINGO_SUPPORTED(lingo)) {
+ IAP_TX_INIT(0x00, 0x10);
+ IAP_TX_PUT(lingo);
+ IAP_TX_PUT(LINGO_MAJOR(lingo));
+ IAP_TX_PUT(LINGO_MINOR(lingo));
+
+ iap_send_tx();
+ } else {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ }
+ break;
+ }
+
+ /* ReturnLingoProtocolVersion (0x10)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* IdentifyDeviceLingoes (0x13);
+ *
+ * Used by a device to inform the iPod of the devices
+ * presence and of the lingoes the device supports.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x13
+ * 0x02-0x05: Device lingoes spoken
+ * 0x06-0x09: Device options
+ * 0x0A-0x0D: Device ID. Only important for authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_CMD_FAILED
+ */
+ case 0x13:
+ {
+ uint32_t lingoes = get_u32(&buf[2]);
+ uint32_t options = get_u32(&buf[6]);
+ uint32_t deviceid = get_u32(&buf[0x0A]);
+ bool seen_unsupported = false;
+ unsigned char i;
+
+ CHECKLEN(14);
+
+ /* Issuing this command exits any extended interface states */
+ iap_interface_state_change(IST_STANDARD);
+
+ /* Loop through the lingoes advertised by the device.
+ * If it tries to use a lingo we do not support, return
+ * a Command Failed ACK.
+ */
+ for(i=0; i<32; i++) {
+ if (lingoes & BIT_N(i)) {
+ /* Bit set by device */
+ if (!LINGO_SUPPORTED(i)) {
+ seen_unsupported = true;
+ }
+ }
+ }
+
+ /* Bit 0 _must_ be set by the device */
+ if (!(lingoes & 1)) {
+ seen_unsupported = true;
+ }
+
+ /* Specifying a deviceid without requesting authentication is
+ * an error
+ */
+ if (deviceid && !(options & 0x03))
+ seen_unsupported = true;
+
+ /* Specifying authentication without a deviceid is an error */
+ if (!deviceid && (options & 0x03))
+ seen_unsupported = true;
+
+ device.lingoes = 0;
+ if (seen_unsupported) {
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+ iap_reset_device(&device);
+ device.lingoes = lingoes;
+
+ /* Devices using IdentifyDeviceLingoes get power off notifications */
+ device.do_power_notify = true;
+
+ /* If a new authentication is requested, start the auth
+ * process.
+ * The periodic handler will take care of sending out the
+ * GetDevAuthenticationInfo packet
+ *
+ * If no authentication is requested, schedule the start of
+ * GetAccessoryInfo
+ */
+ if (deviceid && (options & 0x03) && !DEVICE_AUTH_RUNNING) {
+ device.auth.state = AUST_INIT;
+ } else {
+ device.accinfo = ACCST_INIT;
+ }
+
+ cmd_ok(cmd);
+
+ /* Bit 5: RF Transmitter lingo */
+ if (lingoes & (1 << 5))
+ {
+ /* FM transmitter sends this: */
+ /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/
+
+ /* GetAccessoryInfo */
+ unsigned char data2[] = {0x00, 0x27, 0x00};
+ iap_send_pkt(data2, sizeof(data2));
+ /* RF Transmitter: Begin transmission */
+ unsigned char data3[] = {0x05, 0x02};
+ iap_send_pkt(data3, sizeof(data3));
+ }
+
+
+#if 0
+ /* Bit 7: RF Tuner lingo */
+ if (lingoes & (1 << 7))
+ {
+ /* ipod fm remote sends this: */
+ /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */
+ radio_present = 1;
+ /* GetDevAuthenticationInfo */
+ unsigned char data4[] = {0x00, 0x14};
+ iap_send_pkt(data4, sizeof(data4));
+ }
+#endif
+ break;
+ }
+
+ /* GetDevAuthenticationInfo (0x14)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RetDevAuthenticationInfo (0x15)
+ *
+ * Send certificate information from the device to the iPod.
+ * The certificate may come in multiple parts and has
+ * to be reassembled.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x15
+ * 0x02: Authentication major version
+ * 0x03: Authentication minor version
+ * 0x04: Certificate current section index
+ * 0x05: Certificate maximum section index
+ * 0x06-0xNN: Certificate data
+ *
+ * Returns on success:
+ * IAP_ACK_OK for intermediate sections
+ * AckDevAuthenticationInfo for the last section
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAMETER
+ * AckDevAuthenticationInfo for version mismatches
+ *
+ */
+ case 0x15:
+ {
+ /* There are two formats of this packet. One with only
+ * the version information bytes (for Auth version 1.0)
+ * and the long form shown above
+ */
+ CHECKLEN(4);
+
+ if (!DEVICE_AUTH_RUNNING) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* We only support version 2.0 */
+ if ((buf[2] != 2) || (buf[3] != 0)) {
+ /* Version mismatches are signalled by AckDevAuthenticationInfo
+ * with the status set to Authentication Information unsupported
+ */
+ iap_reset_auth(&(device.auth));
+
+ IAP_TX_INIT(0x00, 0x16);
+ IAP_TX_PUT(0x08);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* There must be at least one byte of certificate data
+ * in the packet
+ */
+ CHECKLEN(7);
+
+ switch (device.auth.state)
+ {
+ /* This is the first packet. Note the maximum section number
+ * so we can check it later.
+ */
+ case AUST_CERTREQ:
+ {
+ device.auth.max_section = buf[5];
+ device.auth.state = AUST_CERTBEG;
+
+ /* Intentional fall-through */
+ }
+ /* All following packets */
+ case AUST_CERTBEG:
+ {
+ /* Check if this is the expected section */
+ if (buf[4] != device.auth.next_section) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* Is this the last section? */
+ if (device.auth.next_section == device.auth.max_section) {
+ /* If we could really do authentication we'd have to
+ * check the certificate here. Since we can't, just acknowledge
+ * the packet with an "everything OK" AckDevAuthenticationInfo
+ *
+ * Also, start GetAccessoryInfo process
+ */
+ IAP_TX_INIT(0x00, 0x16);
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ device.auth.state = AUST_CERTDONE;
+ device.accinfo = ACCST_INIT;
+ } else {
+ device.auth.next_section++;
+ cmd_ok(cmd);
+ }
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+
+ break;
+ }
+
+ /* AckDevAuthenticationInfo (0x16)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetDevAuthenticationSignature (0x17)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RetDevAuthenticationSignature (0x18)
+ *
+ * Return a calculated signature based on the device certificate
+ * and the challenge sent with GetDevAuthenticationSignature
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x17
+ * 0x02-0xNN: Certificate data
+ *
+ * Returns on success:
+ * AckDevAuthenticationStatus
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x19
+ * 0x02: Status (0x00: OK)
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ *
+ * TODO:
+ * There is a timeout of 75 seconds between GetDevAuthenticationSignature
+ * and RetDevAuthenticationSignature for Auth 2.0. This is currently not
+ * checked.
+ */
+ case 0x18:
+ {
+ if (device.auth.state != AUST_CHASENT) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* Here we could check the signature. Since we can't, just
+ * acknowledge and go to authenticated status
+ */
+ IAP_TX_INIT(0x00, 0x19);
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ device.auth.state = AUST_AUTH;
+ break;
+ }
+
+ /* AckDevAuthenticationStatus (0x19)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetiPodAuthenticationInfo (0x1A)
+ *
+ * Obtain authentication information from the iPod.
+ * This cannot be implemented without posessing an Apple signed
+ * certificate and the corresponding private key.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x1A
+ *
+ * This command requires authentication
+ *
+ * Returns:
+ * IAP_ACK_CMD_FAILED
+ */
+ case 0x1A:
+ {
+ CHECKAUTH;
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* RetiPodAuthenticationInfo (0x1B)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* AckiPodAuthenticationInfo (0x1C)
+ *
+ * Confirm authentication information from the iPod.
+ * This cannot be implemented without posessing an Apple signed
+ * certificate and the corresponding private key.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x1C
+ * 0x02: Authentication state (0x00: OK)
+ *
+ * This command requires authentication
+ *
+ * Returns: (none)
+ */
+ case 0x1C:
+ {
+ CHECKAUTH;
+
+ break;
+ }
+
+ /* GetiPodAuthenticationSignature (0x1D)
+ *
+ * Send challenge information to the iPod.
+ * This cannot be implemented without posessing an Apple signed
+ * certificate and the corresponding private key.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x1D
+ * 0x02-0x15: Challenge
+ *
+ * This command requires authentication
+ *
+ * Returns:
+ * IAP_ACK_CMD_FAILED
+ */
+ case 0x1D:
+ {
+ CHECKAUTH;
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* RetiPodAuthenticationSignature (0x1E)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* AckiPodAuthenticationStatus (0x1F)
+ *
+ * Confirm chellenge information from the iPod.
+ * This cannot be implemented without posessing an Apple signed
+ * certificate and the corresponding private key.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x1C
+ * 0x02: Challenge state (0x00: OK)
+ *
+ * This command requires authentication
+ *
+ * Returns: (none)
+ */
+ case 0x1F:
+ {
+ CHECKAUTH;
+
+ break;
+ }
+
+ /* NotifyiPodStateChange (0x23)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetIpodOptions (0x24)
+ *
+ * Request supported features of the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x24
+ *
+ * Retuns:
+ * RetiPodOptions
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x25
+ * 0x02-0x09: Options as a bitfield
+ */
+ case 0x24:
+ {
+ /* There are only two features that can be communicated via this
+ * function, video support and the ability to control line-out usage.
+ * Rockbox supports neither
+ */
+ IAP_TX_INIT(0x00, 0x25);
+ IAP_TX_PUT_U32(0x00);
+ IAP_TX_PUT_U32(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetiPodOptions (0x25)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetAccessoryInfo (0x27)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* RetAccessoryInfo (0x28)
+ *
+ * Send information about the device
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x28
+ * 0x02: Accessory info type
+ * 0x03-0xNN: Accessory information (depends on 0x02)
+ *
+ * Returns: (none)
+ *
+ * TODO: Actually do something with the information received here.
+ * Some devices actually expect us to request the data they
+ * offer, so completely ignoring this does not work, either.
+ */
+ case 0x28:
+ {
+ CHECKLEN(3);
+
+ switch (buf[0x02])
+ {
+ /* Info capabilities */
+ case 0x00:
+ {
+ CHECKLEN(7);
+
+ device.capabilities = get_u32(&buf[0x03]);
+ /* Type 0x00 was already queried, that's where this information comes from */
+ device.capabilities_queried = 0x01;
+ device.capabilities &= ~0x01;
+ break;
+ }
+
+ /* For now, ignore all other information */
+ default:
+ {
+ break;
+ }
+ }
+
+ /* If there are any unqueried capabilities left, do so */
+ if (device.capabilities)
+ device.accinfo = ACCST_DATA;
+
+ break;
+ }
+
+ /* GetiPodPreferences (0x29)
+ *
+ * Retrieve information about the current state of the
+ * iPod.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x29
+ * 0x02: Information class requested
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * RetiPodPreferences
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x2A
+ * 0x02: Information class provided
+ * 0x03: Information
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x29:
+ {
+ CHECKLEN(3);
+ CHECKAUTH;
+
+ IAP_TX_INIT(0x00, 0x2A);
+ /* The only information really supported is 0x03, Line-out usage.
+ * All others are video related
+ */
+ if (buf[2] == 0x03) {
+ IAP_TX_PUT(0x03);
+ IAP_TX_PUT(0x01); /* Line-out enabled */
+
+ iap_send_tx();
+ } else {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ }
+
+ break;
+ }
+
+ /* RetiPodPreference (0x2A)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetiPodPreferences (0x2B)
+ *
+ * Set preferences on the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: General Lingo, always 0x00
+ * 0x01: Command, always 0x29
+ * 0x02: Prefecence class requested
+ * 0x03: Preference setting
+ * 0x04: Restore on exit
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ * IAP_ACK_CMD_FAILED
+ */
+ case 0x2B:
+ {
+ CHECKLEN(5);
+ CHECKAUTH;
+
+ /* The only information really supported is 0x03, Line-out usage.
+ * All others are video related
+ */
+ if (buf[2] == 0x03) {
+ /* If line-out disabled is requested, reply with IAP_ACK_CMD_FAILED,
+ * otherwise with IAP_ACK_CMD_OK
+ */
+ if (buf[3] == 0x00) {
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ } else {
+ cmd_ok(cmd);
+ }
+ } else {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ }
+
+ break;
+ }
+
+ /* The default response is IAP_ACK_BAD_PARAM */
+ default:
+ {
+#ifdef LOGF_ENABLE
+ logf("iap: Unsupported Mode00 Command");
+#else
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+#endif
+ break;
+ }
+ }
+}
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/* Lingo 0x02, Simple Remote Lingo
+ *
+ * TODO:
+ * - Fix cmd 0x00 handling, there has to be a more elegant way of doing
+ * this
+ */
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "system.h"
+#include "button.h"
+#include "audio.h"
+#include "settings.h"
+
+/*
+ * This macro is meant to be used inside an IAP mode message handler.
+ * It is passed the expected minimum length of the message buffer.
+ * If the buffer does not have the required lenght an ACK
+ * packet with a Bad Parameter error is generated.
+ */
+#define CHECKLEN(x) do { \
+ if (len < (x)) { \
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
+ return; \
+ }} while(0)
+
+static void cmd_ack(const unsigned char cmd, const unsigned char status)
+{
+ IAP_TX_INIT(0x02, 0x01);
+ IAP_TX_PUT(status);
+ IAP_TX_PUT(cmd);
+
+ iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = buf[1];
+
+ /* We expect at least three bytes in the buffer, one for the
+ * lingo, one for the command, and one for the first button
+ * state bits.
+ */
+ CHECKLEN(3);
+
+ /* Lingo 0x02 must have been negotiated */
+ if (!DEVICE_LINGO_SUPPORTED(0x02)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ return;
+ }
+
+ switch (cmd)
+ {
+ /* ContextButtonStatus (0x00)
+ *
+ * Transmit button events from the device to the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+ * 0x01: Command, always 0x00
+ * 0x02: Button states 0:7
+ * 0x03: Button states 8:15 (optional)
+ * 0x04: Button states 16:23 (optional)
+ * 0x05: Button states 24:31 (optional)
+ *
+ * Returns: (none)
+ */
+ case 0x00:
+ {
+ iap_remotebtn = BUTTON_NONE;
+ iap_timeoutbtn = 0;
+
+ if(buf[2] != 0)
+ {
+ if(buf[2] & 1)
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ if(buf[2] & 2)
+ REMOTE_BUTTON(BUTTON_RC_VOL_UP);
+ if(buf[2] & 4)
+ REMOTE_BUTTON(BUTTON_RC_VOL_DOWN);
+ if(buf[2] & 8)
+ REMOTE_BUTTON(BUTTON_RC_RIGHT);
+ if(buf[2] & 16)
+ REMOTE_BUTTON(BUTTON_RC_LEFT);
+ }
+ else if(len >= 4 && buf[3] != 0)
+ {
+ if(buf[3] & 1) /* play */
+ {
+ if (audio_status() != AUDIO_STATUS_PLAY)
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ }
+ if(buf[3] & 2) /* pause */
+ {
+ if (audio_status() == AUDIO_STATUS_PLAY)
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ }
+ if(buf[3] & 128) /* Shuffle */
+ {
+ if (!iap_btnshuffle)
+ {
+ iap_shuffle_state(!global_settings.playlist_shuffle);
+ iap_btnshuffle = true;
+ }
+ }
+ }
+ else if(len >= 5 && buf[4] != 0)
+ {
+ if(buf[4] & 1) /* repeat */
+ {
+ if (!iap_btnrepeat)
+ {
+ iap_repeat_next();
+ iap_btnrepeat = true;
+ }
+ }
+
+ /* Power off
+ * Not quite sure how to react to this, but stopping playback
+ * is a good start.
+ */
+ if (buf[4] & 0x04)
+ {
+ if (audio_status() == AUDIO_STATUS_PLAY)
+ REMOTE_BUTTON(BUTTON_RC_PLAY);
+ }
+
+ if(buf[4] & 16) /* ffwd */
+ REMOTE_BUTTON(BUTTON_RC_RIGHT);
+ if(buf[4] & 32) /* frwd */
+ REMOTE_BUTTON(BUTTON_RC_LEFT);
+ }
+
+ break;
+ }
+ /* ACK (0x01)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* ImageButtonStatus (0x02)
+ *
+ * Transmit image button events from the device to the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+ * 0x01: Command, always 0x02
+ * 0x02: Button states 0:7
+ * 0x03: Button states 8:15 (optional)
+ * 0x04: Button states 16:23 (optional)
+ * 0x05: Button states 24:31 (optional)
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_*
+ */
+ case 0x02:
+ {
+ if (!DEVICE_AUTHENTICATED) {
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+ break;
+ }
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* VideoButtonStatus (0x03)
+ *
+ * Transmit video button events from the device to the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+ * 0x01: Command, always 0x03
+ * 0x02: Button states 0:7
+ * 0x03: Button states 8:15 (optional)
+ * 0x04: Button states 16:23 (optional)
+ * 0x05: Button states 24:31 (optional)
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_*
+ */
+ case 0x03:
+ {
+ if (!DEVICE_AUTHENTICATED) {
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+ break;
+ }
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* AudioButtonStatus (0x04)
+ *
+ * Transmit audio button events from the device to the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+ * 0x01: Command, always 0x04
+ * 0x02: Button states 0:7
+ * 0x03: Button states 8:15 (optional)
+ * 0x04: Button states 16:23 (optional)
+ * 0x05: Button states 24:31 (optional)
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_*
+ */
+ case 0x04:
+ {
+ unsigned char repeatbuf[6];
+
+ if (!DEVICE_AUTHENTICATED) {
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+ break;
+ }
+
+ /* This is basically the same command as ContextButtonStatus (0x00),
+ * with the difference that it requires authentication and that
+ * it returns an ACK packet to the device.
+ * So just route it through the handler again, with 0x00 as the
+ * command
+ */
+ memcpy(repeatbuf, buf, 6);
+ repeatbuf[1] = 0x00;
+ iap_handlepkt_mode2((len<6)?len:6, repeatbuf);
+
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* The default response is IAP_ACK_BAD_PARAM */
+ default:
+ {
+#ifdef LOGF_ENABLE
+ logf("iap: Unsupported Mode02 Command");
+#else
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+#endif
+ break;
+ }
+ }
+}
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/* Lingo 0x03: Display Remote Lingo
+ *
+ * A bit of a hodgepogde of odds and ends.
+ *
+ * Used to control the equalizer in version 1.00 of the Lingo, but later
+ * grew functions to control album art transfer and check the player
+ * status.
+ *
+ * TODO:
+ * - Actually support multiple equalizer profiles, currently only the
+ * profile 0 (equalizer disabled) is supported
+ */
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "system.h"
+#include "audio.h"
+#include "powermgmt.h"
+#include "settings.h"
+#include "metadata.h"
+#include "playback.h"
+
+/*
+ * This macro is meant to be used inside an IAP mode message handler.
+ * It is passed the expected minimum length of the message buffer.
+ * If the buffer does not have the required lenght an ACK
+ * packet with a Bad Parameter error is generated.
+ */
+#define CHECKLEN(x) do { \
+ if (len < (x)) { \
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
+ return; \
+ }} while(0)
+
+/* Check for authenticated state, and return an ACK Not
+ * Authenticated on failure.
+ */
+#define CHECKAUTH do { \
+ if (!DEVICE_AUTHENTICATED) { \
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
+ return; \
+ }} while(0)
+
+static void cmd_ack(const unsigned char cmd, const unsigned char status)
+{
+ IAP_TX_INIT(0x03, 0x00);
+ IAP_TX_PUT(status);
+ IAP_TX_PUT(cmd);
+
+ iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = buf[1];
+
+ /* We expect at least two bytes in the buffer, one for the
+ * state bits.
+ */
+ CHECKLEN(2);
+
+ /* Lingo 0x03 must have been negotiated */
+ if (!DEVICE_LINGO_SUPPORTED(0x03)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ return;
+ }
+
+ switch (cmd)
+ {
+ /* ACK (0x00)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetCurrentEQProfileIndex (0x01)
+ *
+ * Return the index of the current equalizer profile.
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x01
+ *
+ * Returns:
+ * RetCurrentEQProfileIndex
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x02
+ * 0x02-0x05: Index as an unsigned 32bit integer
+ */
+ case 0x01:
+ {
+ IAP_TX_INIT(0x03, 0x02);
+ IAP_TX_PUT_U32(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetCurrentEQProfileIndex (0x02)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetCurrentEQProfileIndex (0x03)
+ *
+ * Set the active equalizer profile
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x03
+ * 0x02-0x05: Profile index to activate
+ * 0x06: Whether to restore the previous profile on detach
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_CMD_FAILED
+ *
+ * TODO: Figure out return code for invalid index
+ */
+ case 0x03:
+ {
+ uint32_t index;
+
+ CHECKLEN(7);
+
+ index = get_u32(&buf[2]);
+
+ if (index > 0) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* Currently, we just ignore the command and acknowledge it */
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* GetNumEQProfiles (0x04)
+ *
+ * Get the number of available equalizer profiles
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x04
+ *
+ * Returns:
+ * RetNumEQProfiles
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x05
+ * 0x02-0x05: Number as an unsigned 32bit integer
+ */
+ case 0x04:
+ {
+ IAP_TX_INIT(0x03, 0x05);
+ /* Return one profile (0, the disabled profile) */
+ IAP_TX_PUT_U32(0x01);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetNumEQProfiles (0x05)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetIndexedEQProfileName (0x06)
+ *
+ * Return the name of the indexed equalizer profile
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x06
+ * 0x02-0x05: Profile index to get the name of
+ *
+ * Returns on success:
+ * RetIndexedEQProfileName
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x06
+ * 0x02-0xNN: Name as an UTF-8 null terminated string
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ *
+ * TODO: Figure out return code for out of range index
+ */
+ case 0x06:
+ {
+ uint32_t index;
+
+ CHECKLEN(6);
+
+ index = get_u32(&buf[2]);
+
+ if (index > 0) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ IAP_TX_INIT(0x03, 0x07);
+ IAP_TX_PUT_STRING("Default");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetIndexedQUProfileName (0x07)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetRemoteEventNotification (0x08)
+ *
+ * Set events the device would like to be notified about
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x08
+ * 0x02-0x05: Event bitmask
+ *
+ * Returns:
+ * IAP_ACK_OK
+ */
+ case 0x08:
+ {
+ struct tm* tm;
+
+ CHECKLEN(6);
+ CHECKAUTH;
+
+ /* Save the current state of the various attributes we track */
+ device.trackpos_ms = iap_get_trackpos();
+ device.track_index = iap_get_trackindex();
+ device.chapter_index = 0;
+ device.play_status = audio_status();
+ /* TODO: Fix this */
+ device.mute = false;
+ device.volume = 0x80;
+ device.power_state = charger_input_state;
+ device.battery_level = battery_level();
+ /* TODO: Fix this */
+ device.equalizer_index = 0;
+ device.shuffle = global_settings.playlist_shuffle;
+ device.repeat = global_settings.repeat_mode;
+ tm = get_time();
+ memcpy(&(device.datetime), tm, sizeof(struct tm));
+ device.alarm_state = 0;
+ device.alarm_hour = 0;
+ device.alarm_minute = 0;
+ /* TODO: Fix this */
+ device.backlight = 0;
+ device.hold = button_hold();
+ device.soundcheck = 0;
+ device.audiobook = 0;
+ device.trackpos_s = (device.trackpos_ms/1000) & 0xFFFF;
+
+ /* Get the notification bits */
+ device.do_notify = false;
+ device.changed_notifications = 0;
+ device.notifications = get_u32(&buf[0x02]);
+ if (device.notifications)
+ device.do_notify = true;
+
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* RemoteEventNotification (0x09)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetRemoteEventStatus (0x0A)
+ *
+ * Request the events changed since the last call to GetREmoteEventStatus
+ * or SetRemoteEventNotification
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0A
+ *
+ * This command requires authentication
+ *
+ * Returns:
+ * RetRemoteEventNotification
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0B
+ * 0x02-0x05: Event status bits
+ */
+ case 0x0A:
+ {
+ CHECKAUTH;
+ IAP_TX_INIT(0x03, 0x0B);
+ IAP_TX_PUT_U32(device.changed_notifications);
+
+ iap_send_tx();
+
+ device.changed_notifications = 0;
+ break;
+ }
+
+ /* RetRemoteEventStatus (0x0B)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetiPodStateInfo (0x0C)
+ *
+ * Request state information from the iPod
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0C
+ * 0x02: Type information
+ *
+ * This command requires authentication
+ *
+ * Returns:
+ * RetiPodStateInfo
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0D
+ * 0x02: Type information
+ * 0x03-0xNN: State information
+ */
+ case 0x0C:
+ {
+ struct mp3entry* id3;
+ struct playlist_info* playlist;
+ int play_status;
+ struct tm* tm;
+
+ CHECKLEN(3);
+ CHECKAUTH;
+
+ IAP_TX_INIT(0x03, 0x0D);
+ IAP_TX_PUT(buf[0x02]);
+
+ switch (buf[0x02])
+ {
+ /* 0x00: Track position
+ * Data length: 4
+ */
+ case 0x00:
+ {
+ id3 = audio_current_track();
+ IAP_TX_PUT_U32(id3->elapsed);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x01: Track index
+ * Data length: 4
+ */
+ case 0x01:
+ {
+ playlist = playlist_get_current();
+ IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x02: Chapter information
+ * Data length: 8
+ */
+ case 0x02:
+ {
+ playlist = playlist_get_current();
+ IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+ /* Indicate that track does not have chapters */
+ IAP_TX_PUT_U16(0x0000);
+ IAP_TX_PUT_U16(0xFFFF);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x03: Play status
+ * Data length: 1
+ */
+ case 0x03:
+ {
+ /* TODO: Handle FF/REW
+ */
+ play_status = audio_status();
+ if (play_status & AUDIO_STATUS_PLAY) {
+ if (play_status & AUDIO_STATUS_PAUSE) {
+ IAP_TX_PUT(0x02);
+ } else {
+ IAP_TX_PUT(0x01);
+ }
+ } else {
+ IAP_TX_PUT(0x00);
+ }
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x04: Mute/UI/Volume
+ * Data length: 2
+ */
+ case 0x04:
+ {
+ /* Figuring out what the current volume is
+ * seems to be tricky.
+ * TODO: Fix.
+ */
+
+ /* Mute status */
+ IAP_TX_PUT(0x00);
+ /* Volume */
+ IAP_TX_PUT(0x80);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x05: Power/Battery
+ * Data length: 2
+ */
+ case 0x05:
+ {
+ iap_fill_power_state();
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x06: Equalizer state
+ * Data length: 4
+ */
+ case 0x06:
+ {
+ /* Currently only one equalizer setting supported, 0 */
+ IAP_TX_PUT_U32(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x07: Shuffle
+ * Data length: 1
+ */
+ case 0x07:
+ {
+ IAP_TX_PUT(global_settings.playlist_shuffle?0x01:0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x08: Repeat
+ * Data length: 1
+ */
+ case 0x08:
+ {
+ switch (global_settings.repeat_mode)
+ {
+ case REPEAT_OFF:
+ {
+ IAP_TX_PUT(0x00);
+ break;
+ }
+
+ case REPEAT_ONE:
+ {
+ IAP_TX_PUT(0x01);
+ break;
+ }
+
+ case REPEAT_ALL:
+ {
+ IAP_TX_PUT(0x02);
+ break;
+ }
+ }
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x09: Data/Time
+ * Data length: 6
+ */
+ case 0x09:
+ {
+ tm = get_time();
+
+ /* Year */
+ IAP_TX_PUT_U16(tm->tm_year);
+
+ /* Month */
+ IAP_TX_PUT(tm->tm_mon+1);
+
+ /* Day */
+ IAP_TX_PUT(tm->tm_mday);
+
+ /* Hour */
+ IAP_TX_PUT(tm->tm_hour);
+
+ /* Minute */
+ IAP_TX_PUT(tm->tm_min);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0A: Alarm
+ * Data length: 3
+ */
+ case 0x0A:
+ {
+ /* Alarm not supported, always off */
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0B: Backlight
+ * Data length: 1
+ */
+ case 0x0B:
+ {
+ /* TOOD: Find out how to do this */
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0C: Hold switch
+ * Data length: 1
+ */
+ case 0x0C:
+ {
+ IAP_TX_PUT(button_hold()?0x01:0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0D: Sound check
+ * Data length: 1
+ */
+ case 0x0D:
+ {
+ /* TODO: Find out what the hell this is. Default to off */
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0E: Audiobook
+ * Data length: 1
+ */
+ case 0x0E:
+ {
+ /* Default to normal */
+ IAP_TX_PUT(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x0F: Track position in seconds
+ * Data length: 2
+ */
+ case 0x0F:
+ {
+ unsigned int pos;
+
+ id3 = audio_current_track();
+ pos = id3->elapsed/1000;
+
+ IAP_TX_PUT_U16(pos);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x10: Mute/UI/Absolute volume
+ * Data length: 3
+ */
+ case 0x10:
+ {
+ /* TODO: See volume above */
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT(0x80);
+ IAP_TX_PUT(0x80);
+
+ iap_send_tx();
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+ break;
+ }
+
+ /* RetiPodStateInfo (0x0D)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetiPodStateInfo (0x0E)
+ *
+ * Set status information to new values
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0E
+ * 0x02: Type of information to change
+ * 0x03-0xNN: New information
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_CMD_FAILED
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x0E:
+ {
+ CHECKLEN(3);
+ CHECKAUTH;
+ switch (buf[0x02])
+ {
+ /* Track position (ms)
+ * Data length: 4
+ */
+ case 0x00:
+ {
+ uint32_t pos;
+
+ CHECKLEN(7);
+ pos = get_u32(&buf[0x03]);
+ audio_ff_rewind(pos);
+
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* Track index
+ * Data length: 4
+ */
+ case 0x01:
+ {
+ uint32_t index;
+
+ CHECKLEN(7);
+ index = get_u32(&buf[0x03]);
+ audio_skip(index-iap_get_trackindex());
+
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* Chapter index
+ * Data length: 2
+ */
+ case 0x02:
+ {
+ /* This is not supported */
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Play status
+ * Data length: 1
+ */
+ case 0x03:
+ {
+ CHECKLEN(4);
+ switch(buf[0x03])
+ {
+ case 0x00:
+ {
+ audio_stop();
+ cmd_ok(cmd);
+ break;
+ }
+
+ case 0x01:
+ {
+ audio_resume();
+ cmd_ok(cmd);
+ break;
+ }
+
+ case 0x02:
+ {
+ audio_pause();
+ cmd_ok(cmd);
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+ }
+ break;
+ }
+
+ /* Volume/Mute
+ * Data length: 2
+ * TODO: Fix this
+ */
+ case 0x04:
+ {
+ CHECKLEN(5);
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Equalizer
+ * Data length: 5
+ */
+ case 0x06:
+ {
+ uint32_t index;
+
+ CHECKLEN(8);
+ index = get_u32(&buf[0x03]);
+ if (index == 0) {
+ cmd_ok(cmd);
+ } else {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ }
+ break;
+ }
+
+ /* Shuffle
+ * Data length: 2
+ */
+ case 0x07:
+ {
+ CHECKLEN(5);
+
+ switch(buf[0x03])
+ {
+ case 0x00:
+ {
+ iap_shuffle_state(false);
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x01:
+ case 0x02:
+ {
+ iap_shuffle_state(true);
+ cmd_ok(cmd);
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+ break;
+ }
+
+ /* Repeat
+ * Data length: 2
+ */
+ case 0x08:
+ {
+ CHECKLEN(5);
+
+ switch(buf[0x03])
+ {
+ case 0x00:
+ {
+ iap_repeat_state(REPEAT_OFF);
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x01:
+ {
+ iap_repeat_state(REPEAT_ONE);
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x02:
+ {
+ iap_repeat_state(REPEAT_ALL);
+ cmd_ok(cmd);
+ break;
+ }
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+ break;
+ }
+
+ /* Date/Time
+ * Data length: 6
+ */
+ case 0x09:
+ {
+ CHECKLEN(9);
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Alarm
+ * Data length: 4
+ */
+ case 0x0A:
+ {
+ CHECKLEN(7);
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Backlight
+ * Data length: 2
+ */
+ case 0x0B:
+ {
+ CHECKLEN(5);
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Sound check
+ * Data length: 2
+ */
+ case 0x0D:
+ {
+ CHECKLEN(5);
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Audio book speed
+ * Data length: 1
+ */
+ case 0x0E:
+ {
+ CHECKLEN(4);
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* Track position (s)
+ * Data length: 2
+ */
+ case 0x0F:
+ {
+ uint16_t pos;
+
+ CHECKLEN(5);
+ pos = get_u16(&buf[0x03]);
+ audio_ff_rewind(1000L * pos);
+
+ cmd_ok(cmd);
+ break;
+ }
+
+ /* Volume/Mute/Absolute
+ * Data length: 4
+ * TODO: Fix this
+ */
+ case 0x10:
+ {
+ CHECKLEN(7);
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+
+ break;
+ }
+
+ /* GetPlayStatus (0x0F)
+ *
+ * Request the current play status information
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x0F
+ *
+ * This command requires authentication
+ *
+ * Returns:
+ * RetPlayStatus
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x10
+ * 0x02: Play state
+ * 0x03-0x06: Current track index
+ * 0x07-0x0A: Current track length (ms)
+ * 0x0B-0x0E: Current track position (ms)
+ */
+ case 0x0F:
+ {
+ int play_status;
+ struct mp3entry* id3;
+ struct playlist_info* playlist;
+
+ CHECKAUTH;
+
+ IAP_TX_INIT(0x03, 0x10);
+
+ play_status = audio_status();
+
+ if (play_status & AUDIO_STATUS_PLAY) {
+ /* Playing or paused */
+ if (play_status & AUDIO_STATUS_PAUSE) {
+ /* Paused */
+ IAP_TX_PUT(0x02);
+ } else {
+ /* Playing */
+ IAP_TX_PUT(0x01);
+ }
+ playlist = playlist_get_current();
+ IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+ id3 = audio_current_track();
+ IAP_TX_PUT_U32(id3->length);
+ IAP_TX_PUT_U32(id3->elapsed);
+ } else {
+ /* Stopped, all values are 0x00 */
+ IAP_TX_PUT(0x00);
+ IAP_TX_PUT_U32(0x00);
+ IAP_TX_PUT_U32(0x00);
+ IAP_TX_PUT_U32(0x00);
+ }
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetPlayStatus (0x10)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetCurrentPlayingTrack (0x11)
+ *
+ * Set the current playing track
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x11
+ * 0x02-0x05: Index of track to play
+ *
+ * This command requires authentication
+ *
+ * Returns on success:
+ * IAP_ACK_OK
+ *
+ * Returns on failure:
+ * IAP_ACK_BAD_PARAM
+ */
+ case 0x11:
+ {
+ uint32_t index;
+ uint32_t trackcount;
+
+ CHECKAUTH;
+ CHECKLEN(6);
+
+ index = get_u32(&buf[0x02]);
+ trackcount = playlist_amount();
+
+ if (index >= trackcount)
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ audio_skip(index-iap_get_trackindex());
+ cmd_ok(cmd);
+
+ break;
+ }
+
+ /* GetIndexedPlayingTrackInfo (0x12)
+ *
+ * Request information about a given track
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x12
+ * 0x02: Type of information to retrieve
+ * 0x03-0x06: Track index
+ * 0x07-0x08: Chapter index
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetIndexedPlayingTrackInfo
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x13
+ * 0x02: Type of information returned
+ * 0x03-0xNN: Information
+ */
+ case 0x12:
+ {
+ /* NOTE:
+ *
+ * Retrieving the track information from a track which is not
+ * the currently playing track can take a seriously long time,
+ * in the order of several seconds.
+ *
+ * This most certainly violates the IAP spec, but there's no way
+ * around this for now.
+ */
+ uint32_t track_index;
+ struct playlist_track_info track;
+ struct mp3entry id3;
+
+ CHECKLEN(0x09);
+ CHECKAUTH;
+
+ track_index = get_u32(&buf[0x03]);
+ if (-1 == playlist_get_track_info(NULL, track_index, &track)) {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ IAP_TX_INIT(0x03, 0x13);
+ IAP_TX_PUT(buf[2]);
+ switch (buf[2])
+ {
+ /* 0x00: Track caps/info
+ * Information length: 10 bytes
+ */
+ case 0x00:
+ {
+ iap_get_trackinfo(track_index, &id3);
+ /* Track capabilities. None of these are supported, yet */
+ IAP_TX_PUT_U32(0x00);
+
+ /* Track length in ms */
+ IAP_TX_PUT_U32(id3.length);
+
+ /* Chapter count, stays at 0 */
+ IAP_TX_PUT_U16(0x00);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x01: Chapter time/name
+ * Information length: 4+variable
+ */
+ case 0x01:
+ {
+ /* Chapter length, set at 0 (no chapters) */
+ IAP_TX_PUT_U32(0x00);
+
+ /* Chapter name, empty */
+ IAP_TX_PUT_STRING("");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x02, Artist name
+ * Information length: variable
+ */
+ case 0x02:
+ {
+ /* Artist name */
+ iap_get_trackinfo(track_index, &id3);
+ IAP_TX_PUT_STRLCPY(id3.artist);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x03, Album name
+ * Information length: variable
+ */
+ case 0x03:
+ {
+ /* Album name */
+ iap_get_trackinfo(track_index, &id3);
+ IAP_TX_PUT_STRLCPY(id3.album);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x04, Genre name
+ * Information length: variable
+ */
+ case 0x04:
+ {
+ /* Genre name */
+ iap_get_trackinfo(track_index, &id3);
+ IAP_TX_PUT_STRLCPY(id3.genre_string);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x05, Track title
+ * Information length: variable
+ */
+ case 0x05:
+ {
+ /* Track title */
+ iap_get_trackinfo(track_index, &id3);
+ IAP_TX_PUT_STRLCPY(id3.title);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x06, Composer name
+ * Information length: variable
+ */
+ case 0x06:
+ {
+ /* Track Composer */
+ iap_get_trackinfo(track_index, &id3);
+ IAP_TX_PUT_STRLCPY(id3.composer);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x07, Lyrics
+ * Information length: variable
+ */
+ case 0x07:
+ {
+ /* Packet information bits. All 0 (single packet) */
+ IAP_TX_PUT(0x00);
+
+ /* Packet index */
+ IAP_TX_PUT_U16(0x00);
+
+ /* Lyrics */
+ IAP_TX_PUT_STRING("");
+
+ iap_send_tx();
+ break;
+ }
+
+ /* 0x08, Artwork count
+ * Information length: variable
+ */
+ case 0x08:
+ {
+ /* No artwork, return packet containing just the type byte */
+ iap_send_tx();
+ break;
+ }
+
+ default:
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+
+ break;
+ }
+
+ /* RetIndexedPlayingTrackInfo (0x13)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetNumPlayingTracks (0x14)
+ *
+ * Request the number of tracks in the current playlist
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x14
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetNumPlayingTracks
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x15
+ * 0x02-0xNN: Number of tracks
+ */
+ case 0x14:
+ {
+ CHECKAUTH;
+
+ IAP_TX_INIT(0x03, 0x15);
+ IAP_TX_PUT_U32(playlist_amount());
+
+ iap_send_tx();
+ }
+
+ /* RetNumPlayingTracks (0x15)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetArtworkFormats (0x16)
+ *
+ * Request a list of supported artwork formats
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x16
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetArtworkFormats
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x17
+ * 0x02-0xNN: list of 7 byte format descriptors
+ */
+ case 0x16:
+ {
+ CHECKAUTH;
+
+ /* We return the empty list, meaning no artwork
+ * TODO: Fix to return actual artwork formats
+ */
+ IAP_TX_INIT(0x03, 0x17);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetArtworkFormats (0x17)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetTrackArtworkData (0x18)
+ *
+ * Request artwork for the given track
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x18
+ * 0x02-0x05: Track index
+ * 0x06-0x07: Format ID
+ * 0x08-0x0B: Track offset in ms
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetTrackArtworkData
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x19
+ * 0x02-0x03: Descriptor index
+ * 0x04: Pixel format code
+ * 0x05-0x06: Image width in pixels
+ * 0x07-0x08: Image height in pixels
+ * 0x09-0x0A: Inset rectangle, top left x
+ * 0x0B-0x0C: Inset rectangle, top left y
+ * 0x0D-0x0E: Inset rectangle, bottom right x
+ * 0x0F-0x10: Inset rectangle, bottom right y
+ * 0x11-0x14: Row size in bytes
+ * 0x15-0xNN: Image data
+ *
+ * If the image data does not fit in a single packet, subsequent
+ * packets omit bytes 0x04-0x14.
+ */
+ case 0x18:
+ {
+ CHECKAUTH;
+ CHECKLEN(0x0C);
+
+ /* No artwork support currently */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* RetTrackArtworkFormat (0x19)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetPowerBatteryState (0x1A)
+ *
+ * Request the current power state
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1A
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetPowerBatteryState
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1B
+ * 0x02: Power state
+ * 0x03: Battery state
+ */
+ case 0x1A:
+ {
+ IAP_TX_INIT(0x03, 0x1B);
+
+ iap_fill_power_state();
+ iap_send_tx();
+ break;
+ }
+
+ /* RetPowerBatteryState (0x1B)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* GetSoundCheckState (0x1C)
+ *
+ * Request the current sound check state
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1C
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetSoundCheckState
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1D
+ * 0x02: Sound check state
+ */
+ case 0x1C:
+ {
+ CHECKAUTH;
+
+ IAP_TX_INIT(0x03, 0x1D);
+ IAP_TX_PUT(0x00); /* Always off */
+
+ iap_send_tx();
+ break;
+ }
+
+ /* RetSoundCheckState (0x1D)
+ *
+ * Sent from the iPod to the device
+ */
+
+ /* SetSoundCheckState (0x1E)
+ *
+ * Set the sound check state
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1E
+ * 0x02: Sound check state
+ * 0x03: Restore on exit
+ *
+ * This command requires authentication.
+ *
+ * Returns on success
+ * IAP_ACK_OK
+ *
+ * Returns on failure
+ * IAP_ACK_CMD_FAILED
+ */
+ case 0x1E:
+ {
+ CHECKAUTH;
+ CHECKLEN(4);
+
+ /* Sound check is not supported right now
+ * TODO: Fix
+ */
+
+ cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+ break;
+ }
+
+ /* GetTrackArtworkTimes (0x1F)
+ *
+ * Request a list of timestamps at which artwork exists in a track
+ *
+ * Packet format (offset in buf[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x1F
+ * 0x02-0x05: Track index
+ * 0x06-0x07: Format ID
+ * 0x08-0x09: Artwork Index
+ * 0x0A-0x0B: Artwork count
+ *
+ * This command requires authentication.
+ *
+ * Returns:
+ * RetTrackArtworkTimes
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+ * 0x01: Command, always 0x20
+ * 0x02-0x05: Offset in ms
+ *
+ * Bytes 0x02-0x05 can be repeated multiple times
+ */
+ case 0x1F:
+ {
+ uint32_t index;
+ uint32_t trackcount;
+
+ CHECKAUTH;
+ CHECKLEN(0x0C);
+
+ /* Artwork is currently unsuported, just check for a valid
+ * track index
+ */
+ index = get_u32(&buf[0x02]);
+ trackcount = playlist_amount();
+
+ if (index >= trackcount)
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+
+ /* Send an empty list */
+ IAP_TX_INIT(0x03, 0x20);
+
+ iap_send_tx();
+ break;
+ }
+
+ /* The default response is IAP_ACK_BAD_PARAM */
+ default:
+ {
+#ifdef LOGF_ENABLE
+ logf("iap: Unsupported Mode03 Command");
+#else
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+#endif
+ break;
+ }
+ }
+}
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Alan Korr & Nick Robinson
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ******************************************************************************/
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "dir.h"
+#include "settings.h"
+#include "filetree.h"
+#include "wps.h"
+#include "playback.h"
+
+/*
+ * This macro is meant to be used inside an IAP mode message handler.
+ * It is passed the expected minimum length of the message buffer.
+ * If the buffer does not have the required lenght an ACK
+ * packet with a Bad Parameter error is generated.
+ */
+#define CHECKLEN(x) do { \
+ if (len < (x)) { \
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
+ return; \
+ }} while(0)
+
+/* Check for authenticated state, and return an ACK Not
+ * Authenticated on failure.
+ */
+#define CHECKAUTH do { \
+ if (!DEVICE_AUTHENTICATED) { \
+ cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
+ return; \
+ }} while(0)
+
+/* Used to remember the last Type and Record requested */
+static char cur_dbrecord[5] = {0};
+
+/* Used to remember the total number of filtered database records */
+static unsigned long dbrecordcount = 0;
+
+/* Used to remember the LAST playlist selected */
+static unsigned long last_selected_playlist = 0;
+
+static void cmd_ack(const unsigned int cmd, const unsigned char status)
+{
+ IAP_TX_INIT4(0x04, 0x0001);
+ IAP_TX_PUT(status);
+ IAP_TX_PUT_U16(cmd);
+ iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+static void get_playlist_name(unsigned char *dest,
+ unsigned long item_offset,
+ size_t max_length)
+{
+ if (item_offset == 0) return;
+ DIR* dp;
+ struct dirent* playlist_file = NULL;
+
+ dp = opendir(global_settings.playlist_catalog_dir);
+
+ char *extension;
+ unsigned long nbr = 0;
+ while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL))
+ {
+ /*Increment only if there is a playlist extension*/
+ if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){
+ if ((strcmp(extension, ".m3u") == 0 ||
+ strcmp(extension, ".m3u8") == 0))
+ nbr++;
+ }
+ }
+ if (playlist_file != NULL) {
+ strlcpy(dest, playlist_file->d_name, max_length);
+ }
+ closedir(dp);
+}
+
+static void seek_to_playlist(unsigned long index)
+{
+ unsigned char selected_playlist
+ [sizeof(global_settings.playlist_catalog_dir)
+ + 1
+ + MAX_PATH] = {0};
+
+ strcpy(selected_playlist,
+ global_settings.playlist_catalog_dir);
+ int len = strlen(selected_playlist);
+ selected_playlist[len] = '/';
+ get_playlist_name (selected_playlist + len + 1,
+ index,
+ MAX_PATH);
+ ft_play_playlist(selected_playlist,
+ global_settings.playlist_catalog_dir,
+ strrchr(selected_playlist, '/') + 1);
+
+}
+
+static unsigned long nbr_total_playlists(void)
+{
+ DIR* dp;
+ unsigned long nbr_total_playlists = 0;
+ struct dirent* playlist_file = NULL;
+ char *extension;
+ dp = opendir(global_settings.playlist_catalog_dir);
+ while ((playlist_file = readdir(dp)) != NULL)
+ {
+ /*Increment only if there is a playlist extension*/
+ if ((extension=strrchr(playlist_file->d_name, '.')) != NULL)
+ {
+ if ((strcmp(extension, ".m3u") == 0 ||
+ strcmp(extension, ".m3u8") == 0))
+ {
+ nbr_total_playlists++;
+ }
+ }
+ }
+ closedir(dp);
+ return nbr_total_playlists;
+}
+
+void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf)
+{
+ unsigned int cmd = (buf[1] << 8) | buf[2];
+ /* Lingo 0x04 commands are at least 3 bytes in length */
+ CHECKLEN(3);
+
+ /* Lingo 0x04 must have been negotiated */
+ if (!DEVICE_LINGO_SUPPORTED(0x04)) {
+#ifdef LOGF_ENABLE
+ logf("iap: Mode04 Not Negotiated");
+#endif
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ return;
+ }
+
+ /* All these commands require extended interface mode */
+ if (interface_state != IST_EXTENDED) {
+#ifdef LOGF_ENABLE
+ logf("iap: Not in Mode04 Extended Mode");
+#endif
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ return;
+ }
+ switch (cmd)
+ {
+ case 0x0001: /* CmdAck. See above cmd_ack() */
+ /*
+ * The following is the description for the Apple Firmware
+ * The iPod sends this telegram to acknowledge the receipt of a
+ * command and return the command status. The command ID field
+ * indicates the device command for which the response is being
+ * sent. The command status indicates the results of the command
+ * (success or failure).
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x06 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x01 Command ID (bits 7:0)
+ * 6 0xNN Command result status. Possible values are:
+ *  0x00 = Success (OK)
+ * 0x01 = ERROR: Unknown database category
+ *  0x02 = ERROR: Command failed
+ * 0x03 = ERROR: Out of resources
+ * 0x04 = ERROR: Bad parameter
+ * 0x05 = ERROR: Unknown ID
+ * 0x06 = Reserved
+ * 0x07 = Accessory not authenticated
+ *  0x08 - 0xFF = Reserved
+ * 7 0xNN The ID of the command being acknowledged (bits 15:8).
+ * 8 0xNN The ID of the command being acknowledged (bits 7:0).
+ * 9 0xNN Telegram payload checksum byte
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0002: /* GetCurrentPlayingTrackChapterInfo */
+ /* The following is the description for the Apple Firmware
+ * Requests the chapter information of the currently playing track.
+ * In response, the iPod sends a
+ * Command 0x0003: ReturnCurrentPlayingTrackChapterInfo
+ * telegram to the device.
+ * Note: The returned track index is valid only when there is a
+ * currently playing or paused track.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x02 Command ID (bits 7:0)
+ * 6 0xF7 Telegram payload checksum byte
+ *
+ * We Return that the track does not have chapter information by
+ * returning chapter index -1 (0xFFFFFFFF) and chapter count 0
+ * (0x00000000)
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x03,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0003: /* ReturnCurrentPlayingTrackChapterInfo. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the chapter information of the currently playing track.
+ * The iPod sends this telegramin response to the
+ * Command 0x0002: GetCurrentPlayingTrackChapterInfo
+ * telegram from the device. The track chapter information includes
+ * the currently playingtrack's chapter index,as well as the
+ * total number of chapters in the track. The track chapter and the
+ * total number of chapters are 32-bit signed integers. The chapter
+ * index of the firstchapter is always 0x00000000. If the track does
+ * not have chapter information, a chapter index of -1(0xFFFFFFFF)
+ * and a chapter count of 0 (0x00000000) are returned.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0B Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x03 Command ID (bits 7:0)
+ * 6 0xNN Current chapter index (bits 31:24)
+ * 7 0xNN Current chapter index (bits 23:16)
+ * 8 0xNN Current chapter index (bits 15:8)
+ * 9 0xNN Current chapter index (bits 7:0)
+ * 10 0xNN Chapter count (bits 31:24)
+ * 11 0xNN Chapter count (bits 23:16)
+ * 12 0xNN Chapter count (bits 15:8)
+ * 13 0xNN Chapter count (bits 7:0)
+ * 14 0xNN Telegram payload checksum byte
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0004: /* SetCurrentPlayingTrackChapter */
+ /* The following is the description for the Apple Firmware
+ *
+ * Sets the currently playing track chapter.You can send the Command
+ * 0x0002: GetCurrentPlayingTrackChapterInfo telegram to get the
+ * chapter count and the index of the currently playing chapter in
+ * the current track. In response to the command
+ * SetCurrentPlayingTrackChapter, the iPod sends an ACK telegram
+ * with the command status.
+ *
+ * Note: This command should be used only when the iPod is in a
+ * playing or paused state. The command fails if the iPod is stopped
+ * or if the track does not contain chapter information.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x04 Command ID (bits 7:0)
+ * 6 0xNN Chapter index (bits 31:24)
+ * 7 0xNN Chapter index (bits 23:16)
+ * 8 0xNN Chapter index (bits 15:8)
+ * 9 0xNN Chapter index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ * We don't do anything with this as we don't support chapters,
+ * so just return BAD_PARAM
+ */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0005: /* GetCurrentPlayingTrackChapterPlayStatus */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the chapter playtime status of the currently playing
+ * track. The status includes the chapter length and the time
+ * elapsed within that chapter. In response to a valid telegram, the
+ * iPod sends a Command 0x0006:
+ * ReturnCurrentPlayingTrackChapterPlayStatus telegram to the
+ * device.
+ *
+ * Note: If the telegram length or chapter index is invalid for
+ * instance, if the track does not contain chapter information the
+ * iPod responds with an ACK telegram including the specific error
+ * status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x05 Command ID (bits 7:0)
+ * 6 0xNN Currently playingchapter index (bits31:24)
+ * 7 0xNN Currently playingchapter index (bits23:16)
+ * 8 0xNN Currently playing chapter index (bits 15:8)
+ * 9 0xNN Currently playing chapter index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ * The returned data includes 4 bytes for Chapter Length followed
+ * by 4 bytes of elapsed time If there is no currently playing
+ * chapter, length and elapsed are 0
+ * We don't do anything with this as we don't support chapters,
+ * so just return BAD_PARAM
+ */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0006: /* ReturnCurrentPlayingTrackChapterPlayStatus. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the play status of the currently playing track chapter.
+ * The iPod sends this telegram in response to the Command 0x0005:
+ * GetCurrentPlayingTrackChapterPlayStatus telegram from the device.
+ * The returned information includes the chapter length and elapsed
+ * time, in milliseconds. If there is no currently playing chapter,
+ * the chapter length and elapsed time are zero.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0B Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x06 Command ID (bits 7:0)
+ * 6 0xNN Chapter length in milliseconds (bits 31:24)
+ * 7 0xNN Chapter length in milliseconds (bits 23:16)
+ * 8 0xNN Chapter length in milliseconds (bits 15:8)
+ * 9 0xNN Chapter length in milliseconds (bits 7:0)
+ * 10 0xNN Elapsed time in chapter, in milliseconds (bits 31:24)
+ * 11 0xNN Elapsed time in chapter, in milliseconds (bits 23:16)
+ * 12 0xNN Elapsed time in chapter, in milliseconds (bits 15:8)
+ * 13 0xNN Elapsed time in chapter, in milliseconds (bits 7:0)
+ * 14 0xNN Telegram payload checksum byte
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0007: /* GetCurrentPlayingTrackChapterName */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests a chapter name in the currently playing track. In
+ * response to a valid telegram, the iPod sends a Command 0x0008:
+ * ReturnCurrentPlayingTrackChapterName telegram to the device.
+ *
+ * Note: If the received telegram length or track index is invalid
+ * for instance, if the track does not have chapter information or
+ * is not a part of the Audiobook category, the iPod responds with
+ * an ACK telegram including the specific error status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x07 Command ID (bits 7:0)
+ * 6 0xNN Chapter index (bits 31:24)
+ * 7 0xNN Chapter index (bits 23:16)
+ * 8 0xNN Chapter index (bits 15:8)
+ * 9 0xNN Chapter index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ * We don't do anything with this as we don't support chapters,
+ * so just return BAD_PARAM
+ */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0008: /* ReturnCurrentPlayingTrackChapterName. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns a chapter name in the currently playing track. The iPod
+ * sends this telegram in response to a valid Command 0x0007:
+ * GetCurrentPlayingTrackChapterName telegram from the
+ * device. The chapter name is encoded as a null-terminated UTF-8
+ * character array.
+ *
+ * Note: The chapter name string is not limited to 252 characters;
+ * it may be sent in small or large telegram format depending on
+ * the string length. The small telegram format is shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x08 Command ID (bits 7:0)
+ * 6-N 0xNN Chapter name as UTF-8 character array
+ *(last byte) 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0009: /* GetAudioBookSpeed */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the current iPod audiobook speed state. The iPod
+ * responds with the “Command 0x000A: ReturnAudiobookSpeed”
+ * telegram indicating the current audiobook speed.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x09 Command ID (bits 7:0)
+ * 6 0xF0 Telegram payload checksum byte
+ *
+ * ReturnAudioBookSpeed
+ * 0x00 = Normal, 0xFF = Slow, 0x01 = Fast
+ * We always respond with Normal speed
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x0A, 0x00};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x000A: /* ReturnAudioBookSpeed. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the current audiobook speed setting. The iPod sends
+ * this telegram in response to the Command 0x0009:
+ * GetAudiobookSpeed command from the device.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0A Command ID (bits 7:0)
+ * 6 0xNN Audiobook speed status code.
+ * 0xFF Slow (-1)
+ * 0x00 Normal
+ * 0x01 Fast (+1)
+ * 7 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x000B: /* SetAudioBookSpeed */
+ /* The following is the description for the Apple Firmware
+ * Sets the speed of audiobook playback. The iPod audiobook speed
+ * states are listed above. This telegram has two modes: one to
+ * set the speed of the currently playing audiobook and a second
+ * to set the audiobook speed for all audiobooks. Byte number 7
+ * is an optional byte; devices should not send this byte if they
+ * want to set the speed only of the currently playing audiobook.
+ * If devices want to set the global audiobook speed setting then
+ * they must use byte number 7. This is the Restore on Exit byte;
+ * a nonzero value restores the original audiobook speed setting
+ * when the accessory is detached. If this byte is zero, the
+ * audiobook speed setting set by the accessory is saved and
+ * persists after the accessory is detached from the iPod. See below
+ * for the telegram format used when including the Restore on Exit
+ * byte.
+ * In response to this command, the iPod sends an ACK telegram with
+ * the command status.
+ *
+ * Note: Accessory developers are encouraged to always use the
+ * Restore on Exit byte with a nonzero value, to restore any of the
+ * user's iPod settings that were modified by the accessory.
+ *
+ * Byte Value Meaning (Cuurent audiobook only
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0B Command ID (bits 7:0)
+ * 6 0xNN New audiobook speed code.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ *
+ * SetAudiobookSpeed telegram to set the global audiobook speed
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x05 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0B Command ID (bits 7:0)
+ * 6 0xNN Global audiobook speed code.
+ * 7 0xNN Restore on Exit byte.
+ * If 1, the original setting is restored on detach;
+ * if 0, the newsetting persists after accessory detach.
+ * 8 0xNN Telegram payload checksum byte
+ *
+ *
+ * We don't do anything with this as we don't support chapters,
+ * so just return BAD_PARAM
+ */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x000C: /* GetIndexedPlayingTrackInfo */
+ /* The following is the description for the Apple Firmware
+ *
+ * Gets track information for the track at the specified index.
+ * The track info type field specifies the type of information to be
+ * returned, such as song lyrics, podcast name, episode date, and
+ * episode description. In response, the iPod sends the Command
+ * 0x000D: ReturnIndexedPlayingTrackInfo command with the requested
+ * track information. If the information type is invalid or does not
+ * apply to the selected track, the iPod returns an ACK with an
+ * error status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0A Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0C Command ID (bits 7:0)
+ * 6 0xNN Track info type. See Below
+ * 7 0xNN Track index (bits 31:24)
+ * 8 0xNN Track index (bits 23:16)
+ * 9 0xNN Track index (bits 15:8)
+ * 10 0xNN Track index (bits 7:0)
+ * 11 0xNN Chapter index (bits 15:8)
+ * 12 0xNN Chapter index (bits 7:0)
+ * (last byte)0xNN Telegram payload checksum byte
+ *
+ * Track Info Types: Return Data
+ * 0x00 Track Capabilities. 10byte data
+ * 0x01 Podcast Name UTF-8 String
+ * 0x02 Track Release Date 7Byte Data All 0 if invalid
+ * 0x03 Track Description UTF-8 String
+ * 0x04 Track Song Lyrics UTF-8 String
+ * 0x05 Track Genre UTF-8 String
+ * 0x06 Track Composer UTF-8 String
+ * 0x07 Track Artwork Count 2byte formatID and 2byte count of images
+ * 0x08-0xff Reserved
+ *
+ * Track capabilities
+ * 0x00-0x03
+ * Bit0 is Audiobook
+ * Bit1 has chapters
+ * Bit2 has album art
+ * Bit3 has lyrics
+ * Bit4 is podcast episode
+ * Bit5 has Release Date
+ * Bit6 has Decription
+ * Bit7 Contains Video
+ * Bit8 Play as Video
+ * Bit9+ Reserved
+ * 0x04-0x07 Total Track Length in ms
+ * 0x08-0x09 Chapter Count
+ *
+ * Track Release Date Encoding
+ * 0x00 Seconds 0-59
+ * 0x01 Minutes 0-59
+ * 0x02 Hours 0-23
+ * 0x03 Day Of Month 1-31
+ * 0x04 Month 1-12
+ * 0x05 Year Bits 15:8 2006 = 2006AD
+ * 0x06 Year Bits 7:0
+ * 0x07 Weekday 0-6 where 0=Sunday 6=Saturday
+ *
+ *
+ */
+ {
+ if (len < (10))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ struct mp3entry *id3 = audio_current_track();
+
+ switch(buf[3])
+ {
+ case 0x01: /* Podcast Not Supported */
+ case 0x04: /* Lyrics Not Supported */
+ case 0x03: /* Description */
+ case 0x05: /* Genre */
+ case 0x06: /* Composer */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ default:
+ {
+ IAP_TX_INIT4(0x04, 0x000D);
+ IAP_TX_PUT(buf[3]);
+ switch(buf[3])
+ {
+ case 0x00:
+ {
+ /* Track Capabilities 10Bytes Data */
+ IAP_TX_PUT_U32(0); /* Track Capabilities. */
+ /* Currently None Supported*/
+ IAP_TX_PUT_U32(id3->length); /* Track Length */
+ IAP_TX_PUT_U16(0x00); /* Chapter Count */
+ break;
+ }
+ case 0x02:
+ {
+ /* Track Release Date 7 Bytes Data
+ * Currently only returns a fixed value,
+ * Sunday 1st Feb 2011 3Hr 4Min 5Secs
+ */
+ IAP_TX_PUT(5); /* Seconds 0-59 */
+ IAP_TX_PUT(4); /* Minutes 0-59 */
+ IAP_TX_PUT(3); /* Hours 0-23 */
+ IAP_TX_PUT(1); /* Day Of Month 1-31 */
+ IAP_TX_PUT(2); /* Month 1-12 */
+ IAP_TX_PUT_U16(2011); /* Year */
+ IAP_TX_PUT(0); /* Day 0=Sunday */
+ break;
+ }
+ case 0x07:
+ {
+ /* Track Artwork Count */
+ /* Currently not supported */
+ IAP_TX_PUT_U16(0x00); /* Format ID */
+ IAP_TX_PUT_U16(0x00); /* Image Count */
+ break;
+ }
+ }
+ iap_send_tx();
+ break;
+ }
+ }
+ break;
+ }
+ case 0x000D: /* ReturnIndexedPlayingTrackInfo. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the requested track information type and data. The iPod
+ * sends this command in response to the Command 0x000C:
+ * GetIndexedPlayingTrackInfo command.
+ * Data returned as strings are encoded as null-terminated UTF-8
+ * character arrays.
+ * If the track information string does not exist, a null UTF-8
+ * string is returned. If the track has no release date, then the
+ * returned release date has all bytes zeros. Track song lyrics and
+ * the track description are sent in a large or small telegram
+ * format with an incrementing packet index field, spanning
+ * multiple packets if needed.
+ *
+ * Below is the packet format for the
+ * ReturnIndexedPlayingTrackInfo telegram sent in response to a
+ * request for information types 0x00 to 0x02.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0D Command ID (bits 7:0)
+ * 6 0xNN Track info type. See
+ * 7-N 0xNN Track information. The data format is specific
+ * to the track info type.
+ * NN 0xNN Telegram payload checksum byte
+ *
+ * Below is the large packet format for the
+ * ReturnIndexedPlayingTrackInfo telegram sent in response to a
+ * request for information types 0x03 to 0x04. The small telegram
+ * format is not shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte
+ * 1 0x55 Start of telegram
+ * 2 0x00 Telegram payload marker (large format)
+ * 3 0xNN Large telegram payload length (bits 15:8)
+ * 4 0xNN Large telegram payload length (bits 7:0)
+ * 5 0x04 Lingo ID (Extended Interface lingo)
+ * 6 0x00 Command ID (bits 15:8)
+ * 7 0x0D Command ID (bits 7:0)
+ * 8 0xNN Track info type.
+ * 9 0xNN Packet information bits. If set,
+ * these bits have the following meanings:
+ * Bit 0: Indicates that there are multiple packets.
+ * Bit 1: This is the last packet. Applicable only if
+ * bit 0 is set.
+ * Bit 31:2 Reserved
+ * 10 0xNN Packet Index (bits 15:8)
+ * 11 0xNN Packet Index (bits 7:0)
+ * 12-N 0xNN Track information as a UTF-8 string.
+ * NN 0xNN Telegram payload checksum byte
+ *
+ * Track info types and return data
+ * Info Type Code Data Format
+ * 0x00 Track Capabilities and Information 10-byte data.
+ * 0x01 PodcastName UTF-8 string
+ * 0x02 Track Release Date 7-byte data.
+ * 0x03 Track Description UTF-8 string
+ * 0x04 Track Song Lyrics UTF-8 string
+ * 0x05 TrackGenre UTF-8 string
+ * 0x06 Track Composer UTF-8 string
+ * 0x07 Track Artwork Count Artwork count data. The
+ * artwork count is a sequence of 4-byte records; each record
+ * consists of a 2-byte format ID value followed by a 2-byte
+ * count of images in that format for this track. For more
+ * information about formatID and chapter index values, see
+ * commands 0x000E-0x0011 and 0x002A-0x002B.
+ * 0x08-0xFF Reserved
+ *
+ * Track Capabilities and Information encoding
+ * Byte Code
+ * 0x00-0x03 Track Capability bits. If set, these bits have the
+ * following meanings:
+ * Bit 0: Track is audiobook
+ * Bit 1: Track has chapters
+ * Bit 2: Track has album artwork
+ * Bit 3: Track has song lyrics
+ * Bit 4: Track is a podcast episode
+ * Bit 5: Track has release date
+ * Bit 6: Track has description
+ * Bit 7: Track contains video (a video podcast, music
+ * video, movie, or TV show)
+ * Bit 8: Track is currently queued to play as a video
+ * Bit 31:9: Reserved
+ * 0x04 Total track length, in milliseconds (bits 31:24)
+ * 0x05 Total track length, in milliseconds (bits 23:16)
+ * 0x06 Total track length, in milliseconds (bits 15:8)
+ * 0x07 Total track length, in milliseconds (bits 7:0)
+ * 0x08 Chapter count (bits 15:8)
+ * 0x09 Chapter count (bits 7:0)
+ *
+ * Track Release Date encoding
+ * Byte Code
+ * 0x00 Seconds (0-59)
+ * 0x01 Minutes (0-59)
+ * 0x02 Hours (0-23)
+ * 0x03 Day of themonth(1-31)
+ * 0x04 Month (1-12)
+ * 0x05 Year (bits 15:8). For example, 2006 signifies the year 2006
+ * 0x06 Year (bits 7:0)
+ * 0x07 Weekday (0-6, where 0 = Sunday and 6 = Saturday)
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x000E: /* GetArtworkFormats */
+ /* The following is the description for the Apple Firmware
+ *
+ * The device sends this command to obtain the list of supported
+ * artwork formats on the iPod. No parameters are sent.
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0E Command ID (bits 7:0)
+ * 6 0xEB Checksum/
+ *
+ * Returned Artwork Formats are a 7byte record.
+ * formatID:2
+ * pixelFormat:1
+ * width:2
+ * height:2
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x0F,
+ 0x04, 0x04, /* FormatID */
+ 0x02, /* PixelFormat*/
+ 0x00, 0x64, /* 100 pixels */
+ 0x00, 0x64, /* 100 pixels */
+ 0x04, 0x05, /* FormatID */
+ 0x02, /* PixelFormat*/
+ 0x00, 0xC8, /* 200 pixels */
+ 0x00, 0xC8 /* 200 pixels */
+ };
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x000F: /* RetArtworkFormats. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * The iPod sends this command to the device, giving it the list
+ * of supported artwork formats. Each format is described in a
+ * 7-byte record (formatID:2, pixelFormat:1, width:2, height:2).
+ * The formatID is used when sending GetTrackArtworkTimes.
+ * The device may return zero records.
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x0F Command ID (bits 7:0)
+ * NN 0xNN formatID (15:8) iPod-assigned value for this format
+ * NN 0xNN formatID (7:0)
+ * NN 0xNN pixelFormat. Same as from SetDisplayImage
+ * NN 0xNN imageWidth(15:8).Number of pixels widefor eachimage.
+ * NN 0xNN imageWidth (7:0)
+ * NN 0xNN imageHeight (15:8). Number of pixels high for each
+ * NN 0xNN imageHeight (7:0). image
+ * Previous 7 bytes may be repeated NN times
+ * NN 0xNN Checksum
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0010: /* GetTrackArtworkData */
+ /* The following is the description for the Apple Firmware
+ * The device sends this command to the iPod to request data for a
+ * given trackIndex, formatID, and artworkIndex. The time offset
+ * from track start is the value returned by GetTrackArtworkTimes
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0D Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x10 Command ID (bits 7:0)
+ * 6 0xNN trackIndex (31:24).
+ * 7 0xNN trackIndex(23:16)
+ * 8 0xNN trackIndex (15:8)
+ * 9 0xNN trackIndex (7:0)
+ * 10 0xNN formatID (15:8)
+ * 11 0xNN formatID (7:0)
+ * 12 0xNN time offset from track start, in ms (31:24)
+ * 13 0xNN time offset from track start, in ms (23:16)
+ * 14 0xNN time offset from track start, in ms (15:8)
+ * 15 0xNN time offset from track start, in ms (7:0)
+ * 16 0xNN Checksum
+ *
+ * Returned data is
+ * DescriptorTelegramIndex: 2
+ * pixelformatcode: 1
+ * ImageWidthPixels: 2
+ * ImageHeightPixels: 2
+ * InsetRectangleTopLeftX: 2
+ * InsetRectangleTopLeftY: 2
+ * InsetRectangleBotRightX: 2
+ * InsetRectangleBotRightY: 2
+ * RowSizeInBytes: 4
+ * ImagePixelData:VariableLength
+ * Subsequent packets omit bytes 8 - 24
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x11,
+ 0x00, 0x00, /* DescriptorIndex */
+ 0x00, /* PixelFormat */
+ 0x00, 0x00, /* ImageWidthPixels*/
+ 0x00, 0x00, /* ImageHeightPixels*/
+ 0x00, 0x00, /* InsetRectangleTopLeftX*/
+ 0x00, 0x00, /* InsetRectangleTopLeftY*/
+ 0x00, 0x00, /* InsetRectangleBotRightX*/
+ 0x00, 0x00, /* InsetRectangleBotRoghtY*/
+ 0x00, 0x00, 0x00, 0x00,/* RowSize*/
+ 0x00 /* ImagePixelData Var Length*/
+ };
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0011: /* RetTrackArtworkData. See Abobe */
+ /* The following is the description for the Apple Firmware
+ *
+ * The iPod sends the requested artwork to the accessory. Multiple
+ * RetTrackArtworkData commands may be necessary to transfer all
+ * the data because it will be too much to fit into a single packet.
+ * This command uses nearly the same format as the SetDisplayImage
+ * command (command 0x0032). The only difference is the addition
+ * of 2 coordinates; they define an inset rectangle that describes
+ * any padding that may have been added to the image. The
+ * coordinates consist of two x,y pairs. Each x or y value is 2
+ * bytes, so the total size of the coordinate set is 8 bytes.
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x11 Command ID (bits 7:0)
+ * 6 0x00 Descriptor telegram index (15:8).
+ * These fields uniquely identify each packet in the
+ * RetTrackArtworkData transaction. The first telegram
+ * is the descriptor telegram, which always starts with
+ * an index of 0x0000.
+ * 7 0x00 Descriptor telegram index (7:0)
+ * 8 0xNN Display pixel format code.
+ * 9 0xNN Imagewidth in pixels (15:8)
+ * 10 0xNN Image width in pixels (7:0)
+ * 11 0xNN Image height in pixels (15:8)
+ * 12 0xNN Image height in pixels (7:0)
+ * 13 0xNN Inset rectangle, top-left point, x value (15:8)
+ * 14 0xNN Inset rectangle, top-left point, x value (7:0)
+ * 15 0xNN Inset rectangle, top-left point, y value (15:8)
+ * 16 0xNN Inset rectangle, top-left point, y value (7:0)
+ * 17 0xNN Inset rectangle,bottom-rightpoint,xvalue(15:8)
+ * 18 0xNN Inset rectangle,bottom-rightpoint, x value(7:0)
+ * 19 0xNN Inset rectangle,bottom-rightpoint,y value(15:8)
+ * 20 0xNN Inset rectangle, bottom-right point, y value(7:0)
+ * 21 0xNN Rowsize in bytes (31:24)
+ * 22 0xNN Rowsize in bytes (23:16)
+ * 23 0xNN Row size in bytes (15:8)
+ * 24 0xNN Row size in bytes (7:0)
+ * 25-NN 0xNN Image pixel data (variable length)
+ * NN 0xNN Checksum
+ *
+ * In subsequent packets in the sequence, bytes 8 through 24 are
+ * omitted.
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0012: /* RequestProtocolVersion */
+ /* The following is the description for the Apple Firmware
+ *
+ * This command is deprecated.
+ *
+ * Requests the version of the running protocol from the iPod.
+ * The iPod responds with a Command 0x0013:ReturnProtocolVersion
+ * command.
+ *
+ * Note: This command requests the Extended Interface protocol
+ * version, not the iPod software version.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x12 Command ID (bits 7:0)
+ * 6 0xE7 Telegram payload checksum byte
+ *
+ */
+ {
+ IAP_TX_INIT4(0x04, 0x0013);
+ IAP_TX_PUT(LINGO_MAJOR(0x04));
+ IAP_TX_PUT(LINGO_MINOR(0x04));
+ iap_send_tx();
+ break;
+ }
+ case 0x0013: /* ReturnProtocolVersion. See Above */
+ /* The following is the description for the Apple Firmware
+ * This command is deprecated.
+ * Sent from the iPod to the device
+ * Returns the iPod Extended Interface protocol version number.
+ * The iPod sends this command in response to the Command 0x0012:
+ * RequestProtocolVersion command from the device. The major
+ * version number specifies the protocol version digits to the left
+ * of the decimal point; the minor version number specifies the
+ * digits to the right of the decimal point. For example, a major
+ * version number of 0x01 and a minor version number of 0x08
+ * represents an Extended Interface protocol version of 1.08. This
+ * protocol information is also available through the General lingo
+ * (lingo 0x00) command RequestLingoProtocolVersion when passing
+ * lingo 0x04 as the lingo parameter.
+ *
+ * Note: This command returns the Extended Interface protocol
+ * version, not the iPod software version.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x05 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x13 Command ID (bits 7:0)
+ * 6 0xNN Protocol major version number
+ * 7 0xNN Protocol minor version number
+ * 8 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0014: /* RequestiPodName */
+ /* The following is the description for the Apple Firmware
+ * This command is deprecated.
+ * Retrieves the name of the iPod
+ *
+ * Returns the name of the user's iPod or 'iPod' if the iPod name
+ * is undefined. This allows the iPod name to be shown in the
+ * human-machineinterface(HMI) of the interfacingbody. The iPod
+ * responds with the Command 0x0015: ReturniPodName command
+ * containing the iPod name text string.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x14 Command ID (bits 7:0)
+ * 6 0xE5 Telegram payload checksum byte
+ *
+ * We return ROCKBOX, should this be definable?
+ * Should it be Volume Name?
+ */
+ {
+ IAP_TX_INIT4(0x04, 0x0015);
+ IAP_TX_PUT_STRING("ROCKBOX");
+ iap_send_tx();
+ break;
+ }
+ case 0x0015: /* ReturniPodName. See Above */
+ /* The following is the description for the Apple Firmware
+ * This command is deprecated.
+ * Sent from the iPod to the device
+ *
+ * The iPod sends this command in response to the Command 0x0014:
+ * RequestiPodName telegram from the device. The iPod name is
+ * encoded as a null-terminated UTF-8 character array. The iPod
+ * name string is not limited to 252 characters; it may be sent
+ * in small or large telegram format. The small telegram format
+ * is shown.
+ *
+ * Note: Starting with version 1.07 of the Extended Interface lingo,
+ * the ReturniPodName command on Windows-formatted iPods returns the
+ * iTunes name of the iPod instead of the Windows volume name.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x15 Command ID (bits 7:0)
+ * 6-N 0xNN iPod name as UTF-8 character array
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0016: /* ResetDBSelection */
+ /* The following is the description for the Apple Firmware
+ *
+ * Resets the current database selection to an empty state and
+ * invalidates the category entry count that is, sets the count
+ * to 0, for all categories except the playlist category. This is
+ * analogous to pressing the Menu button repeatedly to get to the
+ * topmost iPod HMI menu. Any previously selected database items
+ * are deselected. The command has no effect on the playback engine
+ * In response, the iPod sends an ACK telegram with the command
+ * status. Once the accessory has reset the database selection,
+ * it must initialize the category count before it can select
+ * database records. Please refer to Command 0x0018:
+ * GetNumberCategorizedDBRecords and Command 0x0017:
+ * SelectDBRecord for details.
+ *
+ * Note: Starting with protocol version 1.07, the ResetDBSelection
+ * command clears the sort order.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x16 Command ID (bits 7:0)
+ * 6 0xE3 Telegram payload checksum byte
+ *
+ *
+ * Reset the DB Record Type
+ * Hierarchy is as follows
+ * All = 0
+ * Playlist = 1
+ * Artist = 2
+ * Album = 3
+ * Genre = 4
+ * Tracks = 5
+ * Composers = 6
+ * Audiobooks = 7
+ * Podcasts = 8
+ *
+ */
+ {
+ cur_dbrecord[0] = 0;
+ put_u32(&cur_dbrecord[1],0);
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0017: /* SelectDBRecord */
+ /* The following is the description for the Apple Firmware
+ * Selects one or more records in the Database Engine, based on a
+ * category relative index. For example, selecting category two
+ * (artist) and record index one results in a list of selected
+ * tracks (or database records) from the second artist in the
+ * artist list.
+ * Selections are additive and limited by the category hierarchy;
+ * Subsequent selections are made based on the subset of records
+ * resulting from the previous selections and not from the entire
+ * database.
+ * Note that the selection of a single record automatically passes
+ * it to the Playback Engine and starts its playback. Record indices
+ * consist of a 32-bit signed integer. To select database records
+ * with a specific sort order, use
+ * Command 0x0038: SelectSortDBRecord
+ * SelectDBRecord should be called only once a category count has
+ * been initialized through a call to Command 0x0018:
+ * GetNumberCategorizedDBRecords. Without a valid category count,
+ * the SelectDBRecord call cannot select a database record and will
+ * fail with a command failed ACK. Accessories that make use of
+ * Command 0x0016: ResetDBSelection must always initialize the
+ * category count before selecting a new database record using
+ * SelectDBRecord.
+ * Accessories should pay close attention to the ACK returned by the
+ * SelectDBRecord command. Ignoring errors may cause unexpected
+ * behavior.
+ * To undo a database selection, send the SelectDBRecord telegram
+ * with the current category selected in theDatabase Engine and a
+ * record index of -1 (0xFFFFFFFF). This has the same effect as
+ * pressing the iPod Menu button once and moves the database
+ * selection up to the next highest menu level. For example, if a
+ * device selected artist number three and then album number one,
+ * it could use the SelectDBRecord(Album, -1) telegram to return
+ * to the database selection of artist number three. If multiple
+ * database selections have been made, devices can use any of the
+ * previously used categories to return to the next highest database
+ * selection. If the category used in one of these SelectDBRecord
+ * telegrams has not been used in a previous database selection
+ * then the command is treated as a no-op.
+ * Sending a SelectDBRecord telegram with the Track category and a
+ * record index of -1 is invalid, because the previous database
+ * selection made with the Track category and a valid index passes
+ * the database selection to the Playback Engine.
+ * Sending a SelectDBRecord(Track, -1) telegram returns a parameter
+ * error. The iPod also returns a bad parameter error ACK when
+ * devices send the SelectDBRecord telegram with an invalid category
+ * type, or with the Track category and an index greater than the
+ * total number of tracks available on the iPod.
+ *
+ * Note: Selecting a podcast always selects from the main podcast
+ * library regardless of the current category context of the iPod.
+ *
+ * To immediately go to the topmost iPod menu level and reset all
+ * database selections, send the ResetDBSelection telegram to the
+ * iPod.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x08 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x17 Command ID (bits 7:0)
+ * 6 0xNN Database category type. See
+ * 7 0xNN Database record index (bits 31:24)
+ * 8 0xNN Database record index (bits 23:16)
+ * 9 0xNN Database record index (bits 15:8)
+ * 10 0xNN Database record index (bits 7:0)
+ * 11 0xNN Telegram payload checksum byte
+ *
+ * The valid database categories are listed below
+ *
+ * Category Code Protocol version
+ * Reserved 0x00 N/A
+ * Playlist 0x01 1.00
+ * Artist 0x02 1.00
+ * Album 0x03 1.00
+ * Genre 0x04 1.00
+ * Track 0x05 1.00
+ * Composer 0x06 1.00
+ * Audiobook0x07 1.06
+ * Podcast 0x08 1.08
+ * Reserved 0x09+ N/A
+ *
+ * cur_dbrecord[0] is the record type
+ * cur_dbrecord[1-4] is the u32 of the record number requested
+ * which might be a playlist or a track number depending on
+ * the value of cur_dbrecord[0]
+ */
+ {
+ memcpy(cur_dbrecord, buf + 3, 5);
+
+ int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+ uint32_t index;
+ uint32_t trackcount;
+ index = get_u32(&cur_dbrecord[1]);
+ trackcount = playlist_amount();
+ if ((cur_dbrecord[0] == 5) && (index > trackcount))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ if ((cur_dbrecord[0] == 1) && (index > (nbr_total_playlists() + 1)))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ audio_pause();
+ switch (cur_dbrecord[0])
+ {
+ case 0x01: /* Playlist*/
+ {
+ if (index != 0x00) /* 0x00 is the On-The-Go Playlist and
+ we do nothing with it */
+ {
+ last_selected_playlist = index;
+ audio_skip(-iap_get_trackindex());
+ seek_to_playlist(last_selected_playlist);
+ }
+ break;
+ }
+ case 0x02: /* Artist */
+ case 0x03: /* Album */
+ case 0x04: /* Genre */
+ case 0x05: /* Track */
+ case 0x06: /* Composer */
+ {
+ audio_skip(index - playlist_next(0));
+ break;
+ }
+ default:
+ {
+ /* We don't do anything with the other selections.
+ * YET.
+ */
+ break;
+ }
+ }
+ if (!paused)
+ audio_resume();
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0018: /* GetNumberCategorizedDBRecords */
+ /* The following is the description for the Apple Firmware
+ *
+ * Retrieves the number of records in a particular database
+ * category.
+ * For example, a device can get the number of artists or albums
+ * present in the database. The category types are described above.
+ * The iPod responds with a Command 0x0019:
+ * ReturnNumberCategorizedDBRecords telegram indicating the number
+ * of records present for this category.
+ * GetNumberCategorizedDBRecords must be called to initialize the
+ * category count before selecting a database record using Command
+ * 0x0017: SelectDBRecord or Command 0x0038: SelectSortDBRecord
+ * commands. A category’s record count can change based on the prior
+ * categories selected and the database hierarchy. The accessory
+ * is expected to call GetNumberCategorizedDBRecords in order to
+ * get the valid range of category entries before selecting a
+ * record in that category.
+ *
+ * Note: The record count returned by this command depends on the
+ * database state before this command is sent. If the database has
+ * been reset using Command 0x0016: ResetDBSelection this command
+ * returns the total number of records for a given category.
+ * However, if this command is sent after one or more categories
+ * are selected, the record count is the subset of records that are
+ * members of all the categories selected prior to this command.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x18 Command ID (bits 7:0)
+ * 6 0xNN Database category type. See above
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * This is the actual number of available records for that category
+ * some head units (Alpine CDE-103BT) use this command before
+ * requesting records and then hang if not enough records are
+ * returned.
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00};
+ switch(buf[3]) /* type number */
+ {
+ case 0x01: /* total number of playlists */
+ dbrecordcount = nbr_total_playlists() + 1;
+ break;
+ case 0x05: /* total number of Tracks */
+ case 0x02: /* total number of Artists */
+ case 0x03: /* total number of Albums */
+ /* We don't sort on the above but some Head Units
+ * require at least one to exist so we just return
+ * the number of tracks in the playlist. */
+ dbrecordcount = playlist_amount();
+ break;
+ case 0x04: /* total number of Genres */
+ case 0x06: /* total number of Composers */
+ case 0x07: /* total number of AudioBooks */
+ case 0x08: /* total number of Podcasts */
+ /* We don't support the above so just return that
+ there are none available. */
+ dbrecordcount = 0;
+ break;
+ }
+ put_u32(&data[3], dbrecordcount);
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0019: /* ReturnNumberCategorizedDBRecords. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the number of database records matching the specified
+ * database category. The iPod sends this telegram in response to
+ * the Command 0x0018: GetNumberCategorizedDBRecords telegram from
+ * the device. Individual records can then be extracted by sending
+ * Command 0x001A: RetrieveCategorizedDatabaseRecords to the iPod.
+ * If no matching database records are found, a record count of
+ * zero is returned. Category types are described above.
+ * After selecting the podcast category, the number of artist,
+ * album, composer, genre, and audiobook records is always zero.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x19 Command ID (bits 7:0)
+ * 6 0xNN Database record count (bits 31:24)
+ * 7 0xNN Database record count (bits 23:16)
+ * 8 0xNN Database record count (bits 15:8)
+ * 9 0xNN Database record count (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x001A: /* RetrieveCategorizedDatabaseRecords */
+ /* The following is the description for the Apple Firmware
+ *
+ * Retrieves one or more database records from the iPod,
+ * typically based on the results from the Command 0x0018:
+ * GetNumberCategorizedDBRecords query. The database
+ * category types are described above. This telegram
+ * specifies the starting record index and the number of
+ * records to retrieve (the record count). This allows a device
+ * to retrieve an individual record or the entire set of records
+ * for a category. The record start index and record count consist
+ * of 32-bit signed integers. To retrieve all records from a given
+ * starting record index, set the record count to -1 (0xFFFFFFFF).
+ * The iPod responds to this telegram with a separate Command
+ * 0x001B: ReturnCategorizedDatabaseRecord telegram FOR EACH record
+ * matching the specified criteria (category and record index
+ * range).
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0C Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1A Command ID (bits 7:0)
+ * 6 0xNN Database category type. See above
+ * 7 0xNN Database record start index (bits 31:24)
+ * 8 0xNN Database record start index (bits 23:16)
+ * 9 0xNN Database record start index (bits 15:8)
+ * 10 0xNN Database record start index (bits 7:0)
+ * 11 0xNN Database record read count (bits 31:24)
+ * 12 0xNN Database record read count (bits 23:16)
+ * 13 0xNN Database record read count (bits 15:8)
+ * 14 0xNN Database record read count (bits 7:0)
+ * 15 0xNN Telegram payload checksum byte
+
+ * The returned data
+ * contains information for a single database record. The iPod sends
+ * one or more of these telegrams in response to the Command 0x001A:
+ * RetrieveCategorizedDatabaseRecords telegram from the device. The
+ * category record index is included to allow the device to
+ * determine which record has been sent. The record data is sent as
+ * a null-terminated UTF-8 encoded data array.
+ */
+ {
+ unsigned char data[7 + MAX_PATH] =
+ {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00,
+ 'O','n','-','T','h','e','-','G','o','\0'};
+ struct playlist_track_info track;
+ struct mp3entry id3;
+
+ unsigned long start_index = get_u32(&buf[4]);
+ unsigned long read_count = get_u32(&buf[8]);
+ unsigned long counter = 0;
+ unsigned int number_of_playlists = nbr_total_playlists();
+ uint32_t trackcount;
+ trackcount = playlist_amount();
+ size_t len;
+
+ if ((buf[3] == 0x05) && ((start_index + read_count ) > trackcount))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ if ((buf[3] == 0x01) && ((start_index + read_count) > (number_of_playlists + 1)))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ for (counter=0;counter<read_count;counter++)
+ {
+ switch(buf[3]) /* type number */
+ {
+ case 0x01: /* Playlists */
+ get_playlist_name(data +7,start_index+counter, MAX_PATH);
+ /*Remove file extension*/
+ char *dot=NULL;
+ dot = (strrchr(data+7, '.'));
+ if (dot != NULL)
+ *dot = '\0';
+ break;
+ case 0x05: /* Tracks */
+ case 0x02: /* Artists */
+ case 0x03: /* Albums */
+ case 0x04: /* Genre */
+ case 0x06: /* Composer */
+ playlist_get_track_info(NULL, start_index + counter,
+ &track);
+ iap_get_trackinfo(start_index + counter, &id3);
+ switch(buf[3])
+ {
+ case 0x05:
+ len = strlcpy((char *)&data[7], id3.title,64);
+ break;
+ case 0x02:
+ len = strlcpy((char *)&data[7], id3.artist,64);
+ break;
+ case 0x03:
+ len = strlcpy((char *)&data[7], id3.album,64);
+ break;
+ case 0x04:
+ case 0x06:
+ len = strlcpy((char *)&data[7], "Not Supported",14);
+ break;
+ }
+ break;
+ }
+ put_u32(&data[3], start_index+counter);
+ iap_send_pkt(data, 7 + strlen(data+7) + 1);
+ yield();
+ }
+ break;
+ }
+ case 0x001B: /* ReturnCategorizedDatabaseRecord. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Contains information for a single database record. The iPod sends
+ * ONE OR MORE of these telegrams in response to the Command 0x001A:
+ * RetrieveCategorizedDatabaseRecords telegram from the device. The
+ * category record index is included to allow the device to
+ * determine which record has been sent. The record data is sent
+ * as a null-terminated UTF-8 encoded data array.
+ *
+ * Note: The database record string is not limited to 252 characters
+ * it may be sent in small or large telegram format, depending on
+ * the record size. The small telegram format is shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1B Command ID (bits 7:0)
+ * 6 0xNN Database record category index (bits 31:24)
+ * 7 0xNN Database record category index (bits 23:16)
+ * 8 0xNN Database record category index (bits 15:8)
+ * 9 0xNN Database record category index (bits 7:0)
+ * 10-N 0xNN Database record as a UTF-8 character array.
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x001C: /* GetPlayStatus */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the current iPod playback status, allowing the
+ * device to display feedback to the user. In response, the
+ * iPod sends a Command 0x001D: ReturnPlayStatus telegram
+ * with the current playback status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1C Command ID (bits 7:0)
+ * 6 0xDD Telegram payload checksum byte
+ *
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x1D,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00};
+ struct mp3entry *id3 = audio_current_track();
+ unsigned long time_total = id3->length;
+ unsigned long time_elapsed = id3->elapsed;
+ int status = audio_status();
+ put_u32(&data[3], time_total);
+ put_u32(&data[7], time_elapsed);
+ if (status == AUDIO_STATUS_PLAY)
+ data[11] = 0x01; /* play */
+ else if (status & AUDIO_STATUS_PAUSE)
+ data[11] = 0x02; /* pause */
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x001D: /* ReturnPlayStatus. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the current iPod playback status. The iPod sends this
+ * telegram in response to the Command 0x001C: GetPlayStatus
+ * telegram from the device. The information returned includes the
+ * current track length, track position, and player state.
+ *
+ * Note: The track length and track position fields are valid only
+ * if the player state is Playing or Paused. For other player
+ * states, these fields should be ignored.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0C Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1D Command ID (bits 7:0)
+ * 6 0xNN Track length in milliseconds (bits 31:24)
+ * 7 0xNN Track length in milliseconds (bits 23:16)
+ * 8 0xNN Track length in milliseconds (bits 15:8)
+ * 9 0xNN Track length in milliseconds (bits 7:0)
+ * 10 0xNN Track position in milliseconds (bits 31:24)
+ * 11 0xNN Track position in milliseconds (bits 23:16)
+ * 12 0xNN Track position in milliseconds (bits 15:8)
+ * 13 0xNN Track position in milliseconds (bits 7:0)
+ * 14 0xNN Player state. Possible values are:
+ * 0x00 = Stopped
+ * 0x01 = Playing
+ * 0x02 = Paused
+ * 0x03 - 0xFE = Reserved
+ * 0xFF = Error
+ * 15 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x001E: /* GetCurrentPlayingTrackIndex */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the playback engine index of the currently playing
+ * track. In response, the iPod sends a Command 0x001F:
+ * ReturnCurrentPlayingTrackIndex telegram to the device.
+ *
+ * Note: The track index returned is valid only if there is
+ * currently a track playing or paused.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1E Command ID (bits 7:0)
+ * 6 0xDB Telegram payload checksum byte
+ *
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x1F,
+ 0xFF, 0xFF, 0xFF, 0xFF};
+ long playlist_pos = playlist_next(0);
+ int status = audio_status();
+ playlist_pos -= playlist_get_first_index(NULL);
+ if(playlist_pos < 0)
+ playlist_pos += playlist_amount();
+ if ((status == AUDIO_STATUS_PLAY) || (status & AUDIO_STATUS_PAUSE))
+ put_u32(&data[3], playlist_pos);
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x001F: /* ReturnCurrentPlayingTrackIndex. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the playback engine index of the current playing track in
+ * response to the Command 0x001E: GetCurrentPlayingTrackIndex
+ * telegram from the device. The track index is a 32-bit signed
+ * integer.
+ * If there is no track currently playing or paused, an index of -1
+ * (0xFFFFFFFF) is returned.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x1F Command ID (bits 7:0)
+ * 6 0xNN Playback track index (bits 31:24)
+ * 7 0xNN Playback track index (bits 23:16)
+ * 8 0xNN Playback track index (bits 15:8)
+ * 9 0xNN Playback track index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0020: /* GetIndexedPlayingTrackTitle. See 0x0024 below */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the title name of the indexed playing track from the
+ * iPod. In response to a valid telegram, the iPod sends a
+ * Command 0x0021: ReturnIndexedPlayingTrackTitle telegram to the
+ * device.
+ *
+ * Note: If the telegram length or playing track index is invalid,
+ * the iPod responds with an ACK telegram including the specific
+ * error status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x20 Command ID (bits 7:0)
+ * 6 0xNN Playback track index (bits 31:24)
+ * 7 0xNN Playback track index (bits 23:16)
+ * 8 0xNN Playback track index (bits 15:8)
+ * 9 0xNN Playback track index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ case 0x0021: /* ReturnIndexedPlayingTrackTitle. See 0x0024 Below */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the title of the indexed playing track in response to
+ * a valid Command 0x0020 GetIndexedPlayingTrackTitle telegram from
+ * the device. The track title is encoded as a null-terminated UTF-8
+ * character array.
+ *
+ * Note: The track title string is not limited to 252 characters;
+ * it may be sent in small or large telegram format, depending on
+ * the string length. The small telegram format is shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x21 Command ID (bits 7:0)
+ * 6-N 0xNN Track title as a UTF-8 character array
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ case 0x0022: /* GetIndexedPlayingTrackArtistName. See 0x0024 Below */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the name of the artist of the indexed playing track
+ * In response to a valid telegram, the iPod sends a
+ * Command 0x0023: ReturnIndexedPlayingTrackArtistName telegram to
+ * the device.
+ *
+ * Note: If the telegram length or playing track index is invalid,
+ * the iPod responds with an ACK telegram including the specific
+ * error status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x22 Command ID (bits 7:0)
+ * 6 0xNN Playback track index (bits 31:24)
+ * 7 0xNN Playback track index (bits 23:16)
+ * 8 0xNN Playback track index (bits 15:8)
+ * 9 0xNN Playback track index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ case 0x0023: /* ReturnIndexedPlayingTrackArtistName. See 0x0024 Below */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the artist name of the indexed playing track in response
+ * to a valid Command 0x0022 GetIndexedPlayingTrackArtistName
+ * telegram from the device. The track artist name is encoded as a
+ * null-terminated UTF-8 character array.
+ *
+ * Note: The artist name string is not limited to 252 characters;
+ * it may be sent in small or large telegram format, depending on
+ * the string length. The small telegram format is shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x23 Command ID (bits 7:0)
+ * 6-N 0xNN Track Artist as a UTF-8 character array
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ case 0x0024: /* GetIndexedPlayingTrackAlbumName AND
+ * GetIndexedPlayingTrackTitle AND
+ * AND GetIndexedPlayingTrackArtistName. */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the album name of the indexed playing track
+ * In response to a valid telegram, the iPod sends a
+ * Command 0x0025: ReturnIndexedPlayingTrackAlbumName telegram to
+ * the device.
+ *
+ * Note: If the telegram length or playing track index is invalid,
+ * the iPod responds with an ACK telegram including the specific
+ * error status.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x24 Command ID (bits 7:0)
+ * 6 0xNN Playback track index (bits 31:24)
+ * 7 0xNN Playback track index (bits 23:16)
+ * 8 0xNN Playback track index (bits 15:8)
+ * 9 0xNN Playback track index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ unsigned char data[70] = {0x04, 0x00, 0xFF};
+ struct mp3entry id3;
+ int fd;
+ size_t len;
+ long tracknum = get_u32(&buf[3]);
+
+ data[2] = cmd + 1;
+ memcpy(&id3, audio_current_track(), sizeof(id3));
+ tracknum += playlist_get_first_index(NULL);
+ if(tracknum >= playlist_amount())
+ tracknum -= playlist_amount();
+ /* If the tracknumber is not the current one,
+ read id3 from disk */
+ if(playlist_next(0) != tracknum)
+ {
+ struct playlist_track_info info;
+ playlist_get_track_info(NULL, tracknum, &info);
+ fd = open(info.filename, O_RDONLY);
+ memset(&id3, 0, sizeof(struct mp3entry));
+ get_metadata(&id3, fd, info.filename);
+ close(fd);
+ }
+ /* Return the requested track data */
+ switch(cmd)
+ {
+ case 0x20:
+ len = strlcpy((char *)&data[3], id3.title, 64);
+ iap_send_pkt(data, 4+len);
+ break;
+ case 0x22:
+ len = strlcpy((char *)&data[3], id3.artist, 64);
+ iap_send_pkt(data, 4+len);
+ break;
+ case 0x24:
+ len = strlcpy((char *)&data[3], id3.album, 64);
+ iap_send_pkt(data, 4+len);
+ break;
+ }
+ break;
+ }
+ case 0x0025: /* ReturnIndexedPlayingTrackAlbumName. See 0x0024 Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the album name of the indexed playing track in response
+ * to a valid Command 0x0024 GetIndexedPlayingTrackAlbumName
+ * telegram from the device. The track artist name is encoded as a
+ * null-terminated UTF-8 character array.
+ *
+ * Note: The album name string is not limited to 252 characters;
+ * it may be sent in small or large telegram format, depending on
+ * the string length. The small telegram format is shown.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x25 Command ID (bits 7:0)
+ * 6-N 0xNN Track Album as a UTF-8 character array
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0026: /* SetPlayStatusChangeNotification */
+ /* The following is the description for the Apple Firmware
+ * Sets the state of play status change notifications from the iPod
+ * to the device. Notification of play status changes can be
+ * globally enabled or disabled. If notifications are enabled, the
+ * iPod sends a Command 0x0027: PlayStatusChangeNotification
+ * telegram to the device each time the play status changes, until
+ * the device sends this telegram again with the disable
+ * notification option. In response, the iPod sends an ACK telegram
+ * indicating the status of the SetPlayStatusChangeNotification
+ * command.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x26 Command ID (bits 7:0)
+ * 6 0xNN The state of play status change notifications.
+ * Possible values are:
+ * 0x00 = Disable all change notification
+ * 0x01 = Enable all change notification
+ * 0x02 - 0xFF = Reserved
+ * 7 0xNN Telegram payload checksum byte
+ */
+ {
+ device.do_notify = buf[3] ? true : false;
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0027: /* PlayStatusChangeNotification */
+ /* This response is handled by iap_track_changed() and iap_periodic()
+ * within iap-core.c
+ * The following is the description for the Apple Firmware
+ *
+ * The iPod sends this telegram to the device when the iPod play status
+ * changes, if the device has previously enabled notifications using
+ * Command 0x0026: SetPlayStatusChangeNotification . This telegram
+ * contains details about the new play status. Notification telegrams
+ * for changes in track position occur approximately every 500
+ * milliseconds while the iPod is playing. Notification telegrams are
+ * sent from the iPod until the device sends the
+ * SetPlayStatusChangeNotification telegram with the option to disable
+ * all notifications.
+ * Some notifications include additional data about the new play status.
+ *
+ * PlayStatusChangeNotification telegram: notifications 0x00, 0x02, 0x03
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x27 Command ID (bits 7:0)
+ * 6 0xNN New play status. See Below.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * Play Status Change Code Extended Status Data (if
+ * any)
+ * Playback stopped 0x00 None
+ * Playback track changed 0x01 New track record index
+ * (32 bits)
+ * Playback forward seek stop 0x02 None
+ * Playback backward seek stop 0x03 None
+ * Playback track position 0x04 New track position in
+ * milliseconds (32 bits)
+ * Playback chapter changed 0x05 New chapter index (32
+ * bits)
+ * Reserved 0x06 - 0xFF N/A
+ *
+ * Playback track changed (code 0x01)
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x08 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x27 Command ID (bits 7:0)
+ * 6 0x01 New status: Playback track changed
+ * 7 0xNN New playback track index (bits 31:24)
+ * 8 0xNN New playback track index (bits 23:16)
+ * 9 0xNN New playback track index (bits 15:8)
+ * 10 0xNN New playback track index (bits 7:0)
+ * 11 0xNN Telegram payload checksum byte
+ *
+ * Playback track position changed (code 0x04)
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x08 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x27 Command ID (bits 7:0)
+ * 6 0x04 New status: Playback track position changed
+ * 7 0xNN New track position in milliseconds (bits 31:24)
+ * 8 0xNN New track position in milliseconds (bits 23:16)
+ * 9 0xNN New track position in milliseconds (bits 15:8)
+ * 10 0xNN New track position in milliseconds (bits 7:0)
+ * 11 0xNN Telegram payload checksum byte
+ *
+ * Playback chapter changed (code 0x05)
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x08 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x27 Command ID (bits 7:0)
+ * 6 0x05 New status: Playback chapter changed
+ * 7 0xNN New track chapter index (bits 31:24)
+ * 8 0xNN New track chapter index (bits 23:16)
+ * 9 0xNN New track chapter index (bits 15:8)
+ * 10 0xNN New track chapter index (bits 7:0)
+ * 11 0xNN Telegram payload checksum byte
+ *
+ */
+ case 0x0028: /* PlayCurrentSelection */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests playback of the currently selected track or list of
+ * tracks. The currently selected tracks are placed in the Now
+ * Playing playlist, where they are optionally shuffled (if the
+ * shuffle feature is enabled). Finally, the specified track record
+ * index is passed to the player to play. Note that if the track
+ * index is -1(0xFFFFFFFF), the first track after the shuffle is
+ * complete is played first. If a track index of n is sent, the nth
+ * track of the selected tracks is played first, regardless of
+ * where it is located in the Now Playing playlist after the shuffle
+ * is performed (assuming that shuffle is on). In response, the iPod
+ * sends an ACK telegram indicating the status of the command.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x28 Command ID (bits 7:0)
+ * 6 0xNN Selection track record index (bits 31:24)
+ * 7 0xNN Selection track record index (bits 23:16)
+ * 8 0xNN Selection track record index (bits 15:8)
+ * 9 0xNN Selection track record index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+ uint32_t index;
+ uint32_t trackcount;
+ index = get_u32(&buf[3]);
+ trackcount = playlist_amount();
+ if (index >= trackcount)
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ audio_pause();
+ if(global_settings.playlist_shuffle)
+ {
+ playlist_randomise(NULL, current_tick, true);
+ }
+ else
+ {
+ playlist_sort(NULL, true);
+ }
+ audio_skip(index - playlist_next(0));
+ if (!paused)
+ audio_resume();
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0029: /* PlayControl */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Sets the new play state of the iPod. The play control command
+ * codes are shown below. In response, the iPod sends an ACK
+ * telegram indicating the status of the command.
+ *
+ * Note: If a remote device requests the Next or Previous Chapter
+ * for a track that does not contain chapters, the iPod returns a
+ * command failed ACK.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x29 Command ID (bits 7:0)
+ * 6 0xNN Play control command code.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * Play Control Command Code Protocol version
+ * Reserved 0x00 N/A
+ * Toggle Play/Pause 0x01 1.00
+ * Stop 0x02 1.00
+ * Next Track 0x03 1.00
+ * Previous Track 0x04 1.00
+ * StartFF 0x05 1.00
+ * StartRew 0x06 1.00
+ * EndFFRew 0x07 1.00
+ * NextChapter 0x08 1.06
+ * Previous Chapter 0x09 1.06
+ * Reserved 0x0A - 0xFF
+ *
+ */
+ switch(buf[3])
+ {
+ case 0x01: /* play/pause */
+ iap_remotebtn = BUTTON_RC_PLAY;
+ iap_repeatbtn = 2;
+ break;
+ case 0x02: /* stop */
+ iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT;
+ iap_repeatbtn = 2;
+ break;
+ case 0x03: /* skip++ */
+ iap_remotebtn = BUTTON_RC_RIGHT;
+ iap_repeatbtn = 2;
+ break;
+ case 0x04: /* skip-- */
+ iap_remotebtn = BUTTON_RC_LEFT;
+ iap_repeatbtn = 2;
+ break;
+ case 0x05: /* ffwd */
+ iap_remotebtn = BUTTON_RC_RIGHT;
+ break;
+ case 0x06: /* frwd */
+ iap_remotebtn = BUTTON_RC_LEFT;
+ break;
+ case 0x07: /* end ffwd/frwd */
+ iap_remotebtn = BUTTON_NONE;
+ break;
+ }
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x002A: /* GetTrackArtworkTimes */
+ /* The following is the description for the Apple Firmware
+ *
+ * The device sends this command to the iPod to request the list of
+ * artwork time locations for a track. A 4-byte track Index
+ * specifies which track from the Playback Engine is to be selected.
+ * A 2-byte formatID indicates which type of artwork is desired.
+ * The 2-byte artworkIndex specifies at which index to begin
+ * searching for artwork. A value of 0 indicates that the iPod
+ * should start with the first available artwork.
+ * The 2-byte artworkCount specifies the maximum number of times
+ * (artwork locations) to be returned. A value of -1 (0xFFFF)
+ * indicates that there is no preferred limit. Note that podcasts
+ * may have a large number of associated images.
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x0D Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2A Command ID (bits 7:0)
+ * 6 0xNN trackIndex(31:24)
+ * 7 0xNN trackIndex(23:16)
+ * 8 0xNN trackIndex (15:8)
+ * 9 0xNN trackIndex (7:0)
+ * 10 0xNN formatID (15:8)
+ * 11 0xNN formatID (7:0)
+ * 12 0xNN artworkIndex (15:8)
+ * 13 0xNN artworkIndex (7:0)
+ * 14 0xNN artworkCount (15:8)
+ * 15 0xNN artworkCount (7:0)
+ * 16 0xNN Checksum
+ *
+ */
+ {
+ unsigned char data[] = {0x04, 0x00, 0x2B,
+ 0x00, 0x00, 0x00, 0x00};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x002B: /* ReturnTrackArtworkTimes. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * The iPod sends this command to the device to return the list of
+ * artwork times for a given track. The iPod returns zero or more
+ * 4-byte times, one for each piece of artwork associated with the
+ * track and format specified by GetTrackArtworkTimes.
+ * The number of records returned will be no greater than the number
+ * specified in the GetTrackArtworkTimes command. It may however, be
+ * less than requested. This can happen if there are fewer pieces of
+ * artwork available than were requested, or if the iPod is unable
+ * to place the full number in a single packet. Check the number of
+ * records returned against the results of
+ * RetIndexedPlayingTrackInfo with infoType 7 to ensure that all
+ * artwork has been received.
+ *
+ * Byte Value Comment
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Length of packet
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2B Command ID (bits 7:0)
+ * 6 0xNN time offset from track start in ms (31:24)
+ * 7 0xNN time offset from track start in ms (23:16)
+ * 8 0xNN time offset from track start in ms (15:8)
+ * 9 0xNN time offset from track start in ms (7:0)
+ * Preceding 4 bytes may be repeated NN times
+ * NN 0xNN Checksum
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x002C: /* GetShuffle */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the current state of the iPod shuffle setting. The iPod
+ * responds with the Command0x002D: ReturnShuffle telegram.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2C Command ID (bits 7:0)
+ * 6 0xCD Telegram payload checksum byte
+ *
+ */
+ unsigned char data[] = {0x04, 0x00, 0x2D,
+ 0x00};
+ data[3] = global_settings.playlist_shuffle ? 1 : 0;
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x002D: /* ReturnShuffle. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the current state of the shuffle setting. The iPod sends
+ * this telegram in response to the Command 0x002C: GetShuffle
+ * telegram from the device.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2D Command ID (bits 7:0)
+ * 6 0xNN Shuffle mode. See Below.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * Possible values of the shufflemode.
+ * Value Meaning
+ * 0x00 Shuffle off
+ * 0x01 Shuffle tracks
+ * 0x02 Shuffle albums
+ * 0x03 – 0xFF Reserved
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x002E: /* SetShuffle */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Sets the iPod shuffle mode. The iPod shuffle modes are listed
+ * below. In response, the iPod sends an ACK telegram with the
+ * command status.
+ * This telegram has an optional byte, byte 0x07, called the
+ * RestoreonExit byte. This byte can be used to restore the
+ * original shuffle setting in use when the accessory was attached
+ * to the iPod. A non zero value restores the original shuffle
+ * setting of the iPod when the accessory is detached. If this byte
+ * is zero, the shuffle setting set by the accessory overwrites the
+ * original setting and persists after the accessory is detached
+ * from the iPod.
+ * Accessory engineers should note that the shuffle mode affects
+ * items only in the playback engine. The shuffle setting does not
+ * affect the order of tracks in the database engine, so calling
+ * Command 0x001A: RetrieveCategorizedDatabaseRecords on a database
+ * selection with the shuffle mode set returns a list of unshuffled
+ * tracks. To get the shuffled playlist, an accessory must query the
+ * playback engine by calling Command 0x0020
+ * GetIndexedPlayingTrackTitle.
+ * Shuffling tracks does not affect the track index, just the track
+ * at that index. If an unshuffled track at playback index 1 is
+ * shuffled, a new track is placed into index 1. The playback
+ * indexes themselves are not shuffled.
+ * When shuffle mode is enabled, tracks that are marked 'skip when
+ * shuffling' are filtered from the database selection. This affects
+ * all audiobooks and all tracks that the user has marked in iTunes.
+ * It also affects all podcasts unless their default 'skip when
+ * shuffling' markings have been deliberately removed. To apply the
+ * filter to the playback engine, the accessory should send
+ * Command 0x0017: SelectDBRecord or
+ * Command 0x0028: PlayCurrentSelection after enabling shuffle mode.
+ *
+ * Note: Accessory developers are encouraged to always use the
+ * Restore on Exit byte with a nonzero value to restore any settings
+ * modified by the accessory upon detach.
+ *
+ * SetShuffle telegram with Restore on Exit byte
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x05 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2E Command ID (bits 7:0)
+ * 6 0xNN New shuffle mode. See above .
+ * 7 0xNN Restore on Exit byte. If 1, the orig setting is
+ * restored on detach; if 0, the newsetting persists
+ * after accessory detach.
+ * 8 0xNN Telegram payload checksum byte
+ *
+ * SetShuffle setting persistent after the accessory detach.
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2E Command ID (bits 7:0)
+ * 6 0xNN New shuffle mode. See above.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ */
+ if(buf[3] && !global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 1;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_randomise(NULL, current_tick, true);
+ }
+ else if(!buf[3] && global_settings.playlist_shuffle)
+ {
+ global_settings.playlist_shuffle = 0;
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ playlist_sort(NULL, true);
+ }
+
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x002F: /* GetRepeat */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the track repeat state of the iPod. In response, the
+ * iPod sends a Command 0x0030: ReturnRepeat telegram
+ * to the device.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x2F Command ID (bits 7:0)
+ * 6 0xCA Telegram payload checksum byte
+ *
+ */
+ unsigned char data[] = {0x04, 0x00, 0x30, 0x00};
+ if(global_settings.repeat_mode == REPEAT_OFF)
+ data[3] = 0;
+ else if(global_settings.repeat_mode == REPEAT_ONE)
+ data[3] = 1;
+ else
+ data[3] = 2;
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0030: /* ReturnRepeat. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the current iPod track repeat state to the device.
+ * The iPod sends this telegram in response to the Command
+ * 0x002F:GetRepeat command.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x30 Command ID (bits 7:0)
+ * 6 0xNN Repeat state. See Below.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * Repeat state values
+ * Value Meaning
+ * 0x00 Repeat off
+ * 0x01 Repeat one track
+ * 0x02 Repeat all tracks
+ * 0x03 - 0xFF Reserved
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0031: /* SetRepeat */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Sets the repeat state of the iPod. The iPod track repeat modes
+ * are listed above. In response, the iPod sends an ACK telegram
+ * with the command status.
+ *
+ * This telegram has an optional byte, byte 0x07, called the
+ * RestoreonExitbyte. This byte can be used to restore the original
+ * repeat setting in use when the accessory was attached to the
+ * iPod. A nonzero value restores the original repeat setting of
+ * the iPod when the accessory is detached. If this byte is zero,
+ * the repeat setting set by the accessory overwrites the original
+ * setting and persists after the accessory is detached from the
+ * iPod.
+ *
+ * Note: Accessory developers are encouraged to always use the
+ * Restore on Exit byte with a nonzero value to restore any
+ * settings modified by the accessory upon detach.
+ *
+ * SetRepeat telegram with Restore on Exit byte
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x05 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x31 Command ID (bits 7:0)
+ * 6 0xNN New repeat state. See above.
+ * 7 0xNN Restore on Exit byte. If 1, the original setting is
+ * restored on detach; if 0, the newsetting persists
+ * after accessory detach.
+ * 8 0xNN Telegram payload checksum byte
+ *
+ * SetRepeat setting persistent after the accessory detach.
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x31 Command ID (bits 7:0)
+ * 6 0xNN New repeat state. See above.
+ * 7 0xNN Telegram payload checksum byte
+ *
+ */
+ int oldmode = global_settings.repeat_mode;
+ if (buf[3] == 0)
+ global_settings.repeat_mode = REPEAT_OFF;
+ else if (buf[3] == 1)
+ global_settings.repeat_mode = REPEAT_ONE;
+ else if (buf[3] == 2)
+ global_settings.repeat_mode = REPEAT_ALL;
+
+ if (oldmode != global_settings.repeat_mode)
+ {
+ settings_save();
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ audio_flush_and_reload_tracks();
+ }
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0032: /* SetDisplayImage */
+ {
+ /* The following is the description for the Apple Firmware
+ * This sets a bitmap image
+ * that is displayed on the iPod display when connected to the
+ * device. It replaces the default checkmark bitmap image that is
+ * displayed when iPod is connected to an external device
+ *
+ * Sets a bitmap image that is shown on the iPod display when it is
+ * connected to the device. The intent is to allow third party
+ * branding when the iPod is communicating with an external device.
+ * An image downloaded using this mechanism replaces the default
+ * checkmark bitmap image that is displayed when iPod is connected
+ * to an external device. The new bitmap is retained in RAM for as
+ * long as the iPod remains powered. After a system reset or deep
+ * sleep state, the new bitmap is lost and the checkmark image
+ * restored as the default when the iPod next enters Extended
+ * Interface mode after power-up.
+ * Before setting a monochrome display image, the device can send
+ * the Command 0x0033: GetMonoDisplayImageLimits telegram to obtain
+ * the current iPod display width, height and pixel format.The
+ * monochrome display information returned in the Command 0x0034:
+ * ReturnMonoDisplayImageLimits telegram can be useful to the
+ * device in deciding which type of display image format is suitable
+ * for downloading to the iPod.
+ * On iPods withcolor displays, devices can send the Command 0x0039:
+ * GetColorDisplayImageLimits telegram to obtain the iPod color
+ * display width,height,and pixel formats. The color display
+ * information is returned in the Command 0x003A:
+ * ReturnColorDisplayImageLimits” telegram.
+ * To set a display image, the device must successfully send
+ * SetDisplayImage descriptor and data telegrams to the iPod. The
+ * SetDisplayImage descriptor telegram (telegram index 0x0000) must
+ * be sent first, as it gives the iPod a description of the image
+ * to be downloaded. This telegram is shown in below. The image
+ * descriptor telegram includes image pixel format, image width and
+ * height, and display row size (stride) in bytes. Optionally, the
+ * descriptor telegram may also contain the beginning data of the
+ * display image, as long as it remains within the maximum length
+ * limits of the telegram.
+ * Following the descriptor telegram, the SetDisplayImage data
+ * telegrams (telegram index 0x0001 - 0xNNNN) should be sent using
+ * sequential telegram indices until the entire image has been sent
+ * to the iPod.
+ *
+ * Note: The SetDisplayImage telegram payload length is limited to
+ * 500 bytes. This telegram length limit and the size and format of
+ * the display image generally determine the minimum number of
+ * telegrams that are required to set a display image.
+ *
+ * Note: Starting with the second generation iPod nano with version
+ * 1.1.2 firmware, the use of the SetDisplayImage command is
+ * limited to once every 15 seconds over USB transport. The iPod
+ * classic and iPod 3G nano apply this restriction to both USB and
+ * UART transports. Calls made to SetDisplayImage more frequently
+ * than every 15 seconds will return a successful ACK command, but
+ * the bitmap will not be displayed on the iPod’s screen. Hence use
+ * of the SetDisplayImage command should be limited to drawing one
+ * bitmap image per accessory connect. The iPod touch will accept
+ * the SetDisplayImage command but will not draw it on the iPod’s
+ * screen.
+ *
+ * Below shows the format of a descriptor telegram. This example
+ * assumes the display image descriptor data exceeds the small
+ * telegram payload capacity; a large telegram format is shown.
+ *
+ * SetDisplayImage descriptor telegram (telegram index = 0x0000)
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x00 Telegram payload marker (large format)
+ * 3 0xNN Large telegram payload length (bits 15:8)
+ * 4 0xNN Large telegram payload length (bits 7:0)
+ * 5 0x04 Lingo ID: Extended Interface lingo
+ * 6 0x00 Command ID (bits 15:8)
+ * 7 0x32 Command ID (bits 7:0)
+ * 8 0x00 Descriptor telegram index (bits 15:8). These fields
+ * uniquely identify each packet in the SetDisplayImage
+ * transaction. The first telegram is the Descriptor
+ * telegram and always starts with an index of 0x0000.
+ * 9 0x00 Descriptor telegram index (bits 7:0)
+ * 10 0xNN Display pixel format code. See Below.
+ * 11 0xNN Imagewidth in pixels (bits 15:8). The number of
+ * pixels, from left to right, per row.
+ * 12 0xNN Image width in pixels (bits 7:0)
+ * 13 0xNN Image height in pixels (bits 15:8). The number of
+ * rows, from top to bottom, in the image.
+ * 14 0xNN Image height in pixels (bits 7:0)
+ * 15 0xNN Row size (stride) in bytes (bits 31:24). The number of
+ * bytes representing one row of pixels. Each row is
+ * zero-padded to end on a 32-bit boundary. The
+ * cumulative size, in bytes, of the image data,
+ * transferred across all telegrams in this transaction
+ * is effectively (Row Size * Image Height).
+ * 16 0xNN Row size (stride) in bytes (bits 23:16)
+ * 17 0xNN Row size (stride) in bytes (bits 15:8)
+ * 18 0xNN Row size (stride) in bytes (bits 7:0)
+ * 19–N 0xNN Display image pixel data
+ * NN 0xNN Telegram payload checksum byte
+ *
+ * SetDisplayImage data telegram (telegram index = 0x0001 - 0xNNNN)
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x00 Telegram payload marker (large format)
+ * 3 0xNN Large telegram payload length (bits 15:8)
+ * 4 0xNN Large telegram payload length (bits 7:0)
+ * 5 0x04 Lingo ID: Extended Interface lingo
+ * 6 0x00 Command ID (bits 15:8)
+ * 7 0x32 Command ID (bits 7:0)
+ * 8 0xNN Descriptor telegram index (bits 15:8). These fields
+ * uniquely identify each packet in the SetDisplayImage
+ * transaction. The first telegram is the descriptor
+ * telegram, shown in Table 6-68 (page 97). The
+ * remaining n-1 telegrams are simply data telegrams,
+ * where n is determined by the size of the image.
+ * 9 0xNN Descriptor telegram index (bits 7:0)
+ * 10–N 0xNN Display image pixel data
+ * NN 0xNN Telegram payload checksum byte
+ *
+ * Note: A known issue causes SetDisplayImage data telegram
+ * lengths less than 11 bytes to return a bad parameter error (0x04)
+ * ACK on 3G iPods.
+ *
+ * The iPod display is oriented as a rectangular grid of pixels. In
+ * the horizontal direction (x-coordinate), the pixel columns are
+ * numbered, left to right, from 0 to Cmax. In the vertical
+ * direction (y-coordinate), the pixel rows are numbered, top to
+ * bottom, from 0 to Rmax. Therefore, an (x,y) coordinate of (0,0)
+ * represents the upper-leftmost pixel on the display and
+ * (Cmax,Rmax) represents the lower-rightmost pixel on the display.
+ * A portion of the iPod display pixel layout is shown below, where
+ * x is the column number, y is the row number, and (Cmax,Rmax)
+ * represents the maximum row and column numbers.
+ *
+ * Pixel layout
+ * x
+ * y 0,0 1,0 2,0 3,0 4,0 5,0 6,0 7,0 - Cmax,0
+ * 0,1 1,1 2,1 3,1 4,1 5,1 6,1 7,1 - Cmax,1
+ * 0,2 1,2 2,2 3,2 4,2 5,2 6,2 7,2 - Cmax,2
+ * 0,3 1,3 2,3 3,3 4,3 5,3 6,3 7,3 - Cmax,3
+ * 0,4 1,4 2,4 3,4 4,4 5,4 6,4 7,4 - Cmax,4
+ * 0,5 1,5 2,5 3,5 4,5 5,5 6,5 7,5 - Cmax,5
+ * 0,6 1,6 2,6 3,6 4,6 5,6 6,6 7,6 - Cmax,6
+ * 0,7 1,7 2,7 3,7 4,7 5,7 6,7 7,7 - Cmax,7
+ * " " " " " " " " " "
+ * 0, 1, 2, 3, 4, 5, 6, 7, - Cmax,
+ * RmaxRmaxRmaxRmaxRmaxRmaxRmaxRmax Rmax
+ *
+ * Display pixel format codes
+ * Display pixel format Code Protocol version
+ * Reserved 0x00 N/A
+ * Monochrome, 2 bits per pixel 0x01 1.01
+ * RGB 565 color, little-endian, 16 bpp 0x02 1.09
+ * RGB 565 color, big-endian, 16 bpp 0x03 1.09
+ * Reserved 0x04-0xFF N/A
+ *
+ * iPods with color screens support all three image formats. All
+ * other iPods support only display pixel format 0x01 (monochrome,
+ * 2 bpp).
+ *
+ * Display Pixel Format 0x01
+ * Display pixel format 0x01 (monochrome, 2 bits per pixel) is the
+ * pixel format supported by all iPods. Each pixel consists of 2
+ * bits that control the pixel intensity. The pixel intensities and
+ * associated binary codes are listed below.
+ *
+ * 2 bpp monochrome pixel intensities
+ * Pixel Intensity Binary Code
+ * Pixel off (not visible) 00b
+ * Pixel on 25% (light grey) 01b
+ * Pixel on 50% (dark grey) 10b
+ * Pixel on 100% (black) 11b
+ *
+ * Each byte of image data contains four packed pixels. The pixel
+ * ordering within bytes and the byte ordering within 32 bits is
+ * shown below.
+ *
+ * Image Data Byte 0x0000
+ * Pixel0 Pixel1 Pixel2 Pixel3
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0x0001
+ * Pixel4 Pixel5 Pixel6 Pixel7
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0x0002
+ * Pixel8 Pixel9 Pixel10 Pixel11
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0x0003
+ * Pixel12 Pixel13 Pixel14 Pixel15
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0xNNNN
+ * PixelN PixelN+1 PixelN+2 PixelN+3
+ * 7 6 5 4 3 2 1 0
+ *
+ * Display Pixel Formats 0x02 and 0x03
+ * Display pixel format 0x02 (RGB 565, little-endian) and display
+ * pixel format 0x03 (RGB 565, big-endian) are available for use
+ * in all iPods with color screens. Each pixel consists of 16 bits
+ * that control the pixel intensity for the colors red, green, and
+ * blue.
+ * It takes two bytes to represent a single pixel. Red is
+ * represented by 5 bits, green is represented by 6 bits, and blue
+ * by the final 5 bits. A 32-bit sequence represents 2 pixels. The
+ * pixel ordering within bytes and the byte ordering within 32 bits
+ * for display format 0x02 (RGB 565, little-endian) is shown below.
+ *
+ * Image Data Byte 0x0000
+ * Pixel 0, lower 3 bits of green Pixel 0, all 5 bits of blue
+ * 7 6 5 4 3 2 1 0
+ * Image Data Byte 0x0001
+ * Pixel 0, all 5 bits of red Pixel 0,upper 3 bits of green
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0x0002
+ * Pixel 1, lower 3 bits of green Pixel 1, all 5 bits of blue
+ * 7 6 5 4 3 2 1 0
+ *
+ * Image Data Byte 0x0003
+ * Pixel 1, all 5 bits of red Pixel 1, upper 3 bits of green
+ * 7 6 5 4 3 2 1 0
+ *
+ * The format for display pixel format 0x03 (RGB 565, big-endian, 16
+ * bpp) is almost identical, with the exception that bytes 0 and 1
+ * are swapped and bytes 2 and 3 are swapped.
+ *
+ */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0033: /* GetMonoDisplayImageLimits */
+
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the limiting characteristics of the monochrome image
+ * that can be sent to the iPod for display while it is connected
+ * to the device. It can be used to determine the display pixel
+ * format and maximum width and height of a monochrome image to be
+ * set using the Command 0x0032: SetDisplayImage telegram. In
+ * response, the iPod sends a Command 0x0034:
+ * ReturnMonoDisplayImageLimits telegram to the device with the
+ * requested display information. The GetMonoDisplayImageLimits
+ * command is supported by iPods with either monochrome or color
+ * displays. To obtain color display image limits, use Command
+ * 0x0039: GetColorDisplayImageLimits.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x33 Command ID (bits 7:0)
+ * 6 0xC6 Telegram payload checksum byte
+ *
+ */
+ unsigned char data[] = {0x04, 0x00, 0x34,
+ LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
+ LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
+ 0x01};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0034: /* ReturnMonoDisplayImageLimits. See Above*/
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the limiting characteristics of the monochrome image that
+ * can be sent to the iPod for display while it is connected to the
+ * device. The iPod sends this telegram in response to the Command
+ * 0x0033: GetMonoDisplayImageLimits telegram. Monochrome display
+ * characteristics include maximum image width and height and the
+ * display pixel format.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x34 Command ID (bits 7:0)
+ * 6 0xNN Maximum image width in pixels (bits 15:8)
+ * 7 0xNN Maximum image width in pixels (bits 7:0)
+ * 8 0xNN Maximum image height in pixels (bits 15:8)
+ * 9 0xNN Maximumimage height in pixels (bits 7:0)
+ * 10 0xNN Display pixel format (see Table 6-70 (page 99)).
+ * 11 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0035: /* GetNumPlayingTracks */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the number of tracks in the list of tracks queued to
+ * play on the iPod. In response, the iPod sends a Command 0x0036:
+ * ReturnNumPlayingTracks telegram with the count of tracks queued
+ * to play.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x35 Command ID (bits 7:0)
+ * 6 0xC4 Telegram payload checksum byte
+ *
+ */
+ unsigned char data[] = {0x04, 0x00, 0x36,
+ 0x00, 0x00, 0x00, 0x00};
+ unsigned long playlist_amt = playlist_amount();
+ put_u32(&data[3], playlist_amt);
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x0036: /* ReturnNumPlayingTracks. See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the number of tracks in the actual list of tracks queued
+ * to play, including the currently playing track (if any). The
+ * iPod sends this telegram in response to the Command 0x0035:
+ * GetNumPlayingTracks telegram.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x36 Command ID (bits 7:0)
+ * 6 0xNN Number of tracks playing(bits 31:24)
+ * 7 0xNN Number of tracks playing(bits 23:16)
+ * 8 0xNN Number of tracks playing (bits 15:8)
+ * 9 0xNN Number of tracks playing (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x0037: /* SetCurrentPlayingTrack */
+ /* The following is the description for the Apple Firmware
+ *
+ * Sets the index of the track to play in the Now Playing playlist
+ * on the iPod. The index that is specified here is obtained by
+ * sending the Command 0x0035: GetNumPlayingTracks and Command
+ * 0x001E: GetCurrentPlayingTrackIndex telegrams to obtain the
+ * number of playing tracks and the current playing track index,
+ * respectively. In response, the iPod sends an ACK telegram
+ * indicating the status of the command.
+ *
+ * Note: The behavior of this command has changed. Before the
+ * 2G nano, if this command was sent with the current playing track
+ * index the iPod would pause playback momentarily and then resume.
+ * Starting with the 2G nano, the iPod restarts playback of the
+ * current track from the beginning. Older iPods will not be
+ * updated to the new behavior.
+ *
+ * Note: This command is usable only when the iPod is in a playing
+ * or paused state. If the iPod is stopped, this command fails.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x07 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x37 Command ID (bits 7:0)
+ * 6 0xNN New current playing track index (bits 31:24)
+ * 7 0xNN New current playing track index (bits 23:16)
+ * 8 0xNN New current playing track index (bits 15:8)
+ * 9 0xNN New current playing track index (bits 7:0)
+ * 10 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+ long tracknum = get_u32(&buf[3]);
+
+ audio_pause();
+ audio_skip(tracknum - playlist_next(0));
+ if (!paused)
+ audio_resume();
+
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0038: /* SelectSortDBRecord */
+ /* The following is the description for the Apple Firmware
+ *
+ * Selects one or more records in the iPod database, based on a
+ * category-relative index. For example, selecting category 2
+ * (Artist), record index 1, and sort order 3 (Album) results in a
+ * list of selected tracks (records) from the second artist in the
+ * artist list, sorted by album name. Selections are additive and
+ * limited by the category hierarchy. Subsequent selections are
+ * made based on the subset of records resulting from previous
+ * selections and not from the entire database. The database
+ * category types are shown above. The sort order options and codes
+ * are shown below.
+ *
+ * SelectSortDBRecord telegram
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x09 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x38 Command ID (bits 7:0)
+ * 6 0xNN Database category type.
+ * 7 0xNN Category record index (bits 31:24)
+ * 8 0xNN Category record index (bits 23:16)
+ * 9 0xNN Category record index (bits 15:8)
+ * 10 0xNN Category record index (bits 7:0)
+ * 11 0xNN Database sort type.
+ * 12 0xNN Telegram payload checksum byte
+ *
+ * Database sort order options
+ * Sort Order Code Protocol version
+ * Sort by genre 0x00 1.00
+ * Sort by artist 0x01 1.00
+ * Sort by composer 0x02 1.00
+ * Sort by album 0x03 1.00
+ * Sort by name 0x04 1.00
+ * Sort by playlist 0x05 1.00
+ * Sort by release date 0x06 1.08
+ * Reserved 0x07 - 0xFE N/A
+ * Use default sort type 0xFF 1.00
+ *
+ * The default order of song and audiobook tracks on the iPod is
+ * alphabetical by artist, then alphabetical by that artist's
+ * albums, then ordered according to the order of the tracks on the
+ * album.
+ * For podcasts, the default order of episode tracks is reverse
+ * chronological. That is, the newest ones are first,then
+ * alphabetical by podcast name.
+ * The SelectSortDBRecord command can be used to sort all the song
+ * and audiobook tracks on the iPod alphabetically as follows:
+ * 1. Command 0x0016: ResetDBSelection
+ * 2. Command 0x0018: GetNumberCategorizedDBRecords for the Playlist
+ * category.
+ * 3. SelectSortDBRecord based on the Playlist category, using a
+ * record index of 0 to select the All Tracks
+ * playlist and the sort by name (0x04) sort
+ * order.
+ * 4. GetNumberCategorizedDBRecords for the Track category.
+ * 5. Command 0x001A :RetrieveCategorizedDatabaseRecords based on
+ * the Track category, using a start index of 0
+ * and an end index of the number of records
+ * returned by the call to
+ * GetNumberCategorizedDBRecords in step 4.
+ *
+ * The sort order of artist names ignores certain articles such
+ * that the artist “The Doors” is sorted under the letter ‘D’ and
+ * not ‘T’; this matches the behavior of iTunes. The sort order is
+ * different depending on the language setting used in the iPod.
+ * The list of ignored articles may change in the future without
+ * notice.
+ * The SelectDBRecord command may also be used to select a database
+ * record with the default sort order.
+ *
+ * Note: The sort order field is ignored for the Audiobook category.
+ * Audiobooks are automatically sorted by track title. The sort
+ * order for podcast tracks defaults to release date, with the
+ * newest track coming first.
+ *
+ * Selects one or more records in the iPod database, based on a
+ * category-relative index. This appears to be hardcoded hierarchy
+ * decided by Apple that the external devices follow.
+ * This is as follows for all except podcasts,
+ *
+ * All (highest level),
+ * Playlist,
+ * Genre or Media Kind,
+ * Artist or Composer,
+ * Album,
+ * Track or Audiobook (lowest)
+ *
+ * for Podcasts, the order is
+ *
+ * All (highest),
+ * Podcast,
+ * Episode
+ * Track (lowest)
+ *
+ * Categories are
+ *
+ * 0x00 Reserved
+ * 0x01 Playlist
+ * 0x02 Artist
+ * 0x03 Album
+ * 0x04 Genre
+ * 0x05 Track
+ * 0x06 Composer
+ * 0x07 Audiobook
+ * 0x08 Podcast
+ * 0x09 - 0xff Reserved
+ *
+ * Sort Order optiona and codes are
+ *
+ * 0x00 Sort by Genre
+ * 0x01 Sort by Artist
+ * 0x02 Sort by Composer
+ * 0x03 Sort by Album
+ * 0x04 Sort by Name (Song Title)
+ * 0x05 Sort by Playlist
+ * 0x06 Sort by Release Date
+ * 0x07 - 0xfe Reserved
+ * 0xff Use default Sort Type
+ *
+ * Packet format (offset in data[]: Description)
+ * 0x00: Lingo ID: Extended Interface Protocol Lingo, always 0x04
+ * 0x01-0x02: Command, always 0x0038
+ * 0x03: Database Category Type
+ * 0x04-0x07: Category Record Index
+ * 0x08 Database Sort Type
+ *
+ * On Rockbox, if the recordtype is playlist, we load the selected
+ * playlist and start playing from the first track.
+ * If the recordtype is track, we play that track from the current
+ * playlist.
+ * On anything else we just play the current track from the current
+ * playlist.
+ * cur_dbrecord[0] is the recordtype
+ * cur_dbrecord[1-4] is the u32 of the record number requested
+ * which might be a playlist or a track number depending on
+ * the value of cur_dbrecord[0]
+ */
+ {
+ memcpy(cur_dbrecord, buf + 3, 5);
+
+ int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+ unsigned int number_of_playlists = nbr_total_playlists();
+ uint32_t index;
+ uint32_t trackcount;
+ index = get_u32(&cur_dbrecord[1]);
+ trackcount = playlist_amount();
+ if ((cur_dbrecord[0] == 0x05) && (index > trackcount))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ if ((cur_dbrecord[0] == 0x01) && (index > (number_of_playlists + 1)))
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ switch (cur_dbrecord[0])
+ {
+ case 0x01: /* Playlist*/
+ {
+ if (index != 0x00) /* 0x00 is the On-The-Go Playlist and
+ we do nothing with it */
+ {
+ audio_pause();
+ audio_skip(-iap_get_trackindex());
+ playlist_sort(NULL, true);
+ last_selected_playlist = index;
+ seek_to_playlist(last_selected_playlist);
+ }
+ break;
+ }
+ case 0x02: /* Artist Do Nothing */
+ case 0x03: /* Album Do Nothing */
+ case 0x04: /* Genre Do Nothing */
+ case 0x06: /* Composer Do Nothing */
+ break;
+ case 0x05: /* Track*/
+ {
+ audio_pause();
+ audio_skip(-iap_get_trackindex());
+ playlist_sort(NULL, true);
+ audio_skip(index - playlist_next(0));
+ break;
+ }
+ }
+ if (!paused)
+ audio_resume();
+ /* respond with cmd ok packet */
+ cmd_ok(cmd);
+ break;
+ }
+ case 0x0039: /* GetColorDisplayImageLimits */
+ /* The following is the description for the Apple Firmware
+ *
+ * Requests the limiting characteristics of the color image that
+ * can be sent to the iPod for display while it is connected to
+ * the device. It can be used to determine the display pixel format
+ * and maximum width and height of a color image to be set using
+ * the Command 0x0032: SetDisplayImage telegram. In response, the
+ * iPod sends a Command 0x003A: ReturnColorDisplayImageLimits
+ * telegram to the device with the requested display information.
+ * This command is supported only by iPods with color displays.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x03 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x39 Command ID (bits 7:0)
+ * 6 0xC0 Telegram payload checksum byte
+ *
+ */
+ {
+ /* Set the same as the ReturnMonoDisplayImageLimits */
+ unsigned char data[] = {0x04, 0x00, 0x3A,
+ LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
+ LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
+ 0x01};
+ iap_send_pkt(data, sizeof(data));
+ break;
+ }
+ case 0x003A: /* ReturnColorDisplayImageLimits See Above */
+ /* The following is the description for the Apple Firmware
+ *
+ * Returns the limiting characteristics of the color image that can
+ * be sent to the iPod for display while it is connected to the
+ * device. The iPod sends this telegram in response to the Command
+ * 0x0039: GetColorDisplayImageLimits telegram. Display
+ * characteristics include maximum image width and height and the
+ * display pixel format.
+ *
+ * Note: If the iPod supports multiple display image formats, a five
+ * byte block of additional image width, height, and pixel format
+ * information is appended to the payload for each supported display
+ * format. The list of supported color display image formats
+ * returned by the iPod may change in future software versions.
+ * Devices must be able to parse a variable length list of supported
+ * color display formats to search for compatible formats.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0xNN Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x3A Command ID (bits 7:0)
+ * 6 0xNN Maximum image width in pixels (bits 15:8)
+ * 7 0xNN Maximum image width in pixels (bits 7:0)
+ * 8 0xNN Maximum image height in pixels (bits 15:8)
+ * 9 0xNN Maximum image height in pixels (bits 7:0)
+ * 10 0xNN Display pixel format (see Table 6-70 (page 99)).
+ * 11-N 0xNN Optional display image width, height, and pixel format
+ * for the second to nth supported display formats,
+ * if present.
+ * NN 0xNN Telegram payload checksum byte
+ *
+ */
+ {
+ /* We should NEVER receive this command so ERROR if we do */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ case 0x003B: /* ResetDBSelectionHierarchy */
+ {
+ /* The following is the description for the Apple Firmware
+ *
+ * This command carries a single byte in its payload (byte 6). A
+ * hierarchy selection value of 0x01 means that the accessory wants
+ * to navigate the music hierarchy; a hierarchy selection value of
+ * 0x02 means that the accessory wants to navigate the video
+ * hierarchy.
+ *
+ * Byte Value Meaning
+ * 0 0xFF Sync byte (required only for UART serial)
+ * 1 0x55 Start of telegram
+ * 2 0x04 Telegram payload length
+ * 3 0x04 Lingo ID: Extended Interface lingo
+ * 4 0x00 Command ID (bits 15:8)
+ * 5 0x3B Command ID (bits 7:0)
+ * 6 0x01 or 0x02 Hierarchy selection
+ * 7 0xNN Telegram payload checksum byte
+ *
+ * Note: The iPod will return an error if a device attempts to
+ * enable an unsupported hierarchy, such as a video hierarchy on an
+ * iPod model that does not support video.
+ */
+ dbrecordcount = 0;
+ cur_dbrecord[0] = 0;
+ put_u32(&cur_dbrecord[1],0);
+ switch (buf[3])
+ {
+ case 0x01: /* Music */
+ {
+ cmd_ok(cmd);
+ break;
+ }
+ default: /* Anything else */
+ {
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+ }
+ default:
+ {
+#ifdef LOGF_ENABLE
+ logf("iap: Unsupported Mode04 Command");
+#endif
+ /* The default response is IAP_ACK_BAD_PARAM */
+ cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+ break;
+ }
+ }
+}
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
unplug_change(false);
return SYS_PHONE_UNPLUGGED;
#endif
-#ifdef IPOD_ACCESSORY_PROTOCOL
- case SYS_IAP_PERIODIC:
- iap_periodic();
- return SYS_IAP_PERIODIC;
- case SYS_IAP_HANDLEPKT:
- iap_handlepkt();
- return SYS_IAP_HANDLEPKT;
-#endif
#if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO)
/* stop playback if we receive a call */
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 @@
#include <stdbool.h>
-#define RX_BUFLEN 512
+/* This is just the payload size, without sync, length and checksum */
+#define RX_BUFLEN (64*1024)
+/* This is the entire frame length, sync, length, payload and checksum */
#define TX_BUFLEN 128
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 @@
#define SYS_REMOTE_PLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 4)
#define SYS_REMOTE_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 5)
#define SYS_CAR_ADAPTER_RESUME MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 0)
-#define SYS_IAP_PERIODIC MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 1)
-#define SYS_IAP_HANDLEPKT MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 2)
#define SYS_CALL_INCOMING MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 3)
#define SYS_CALL_HUNG_UP MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 4)
#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)
#if defined(IPOD_ACCESSORY_PROTOCOL)
const unsigned char *serbuf = iap_get_serbuf();
- lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x",
- serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5],
- serbuf[6], serbuf[7]);
+ if (serbuf)
+ {
+ lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x",
+ serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5],
+ serbuf[6], serbuf[7]);
+ }
#endif
#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 @@
+package Device::iPod;
+
+use Device::SerialPort;
+use POSIX qw(isgraph);
+use strict;
+
+sub new {
+ my $class = shift;
+ my $port = shift;
+ my $self = {};
+ my $s;
+
+ $self->{-serial} = undef;
+ $self->{-inbuf} = '';
+ $self->{-error} = undef;
+ $self->{-baudrate} = 57600;
+ $self->{-debug} = 0;
+
+ return bless($self, $class);
+}
+
+sub open {
+ my $self = shift;
+ my $port = shift;
+
+ $self->{-serial} = new Device::SerialPort($port);
+ unless(defined($self->{-serial})) {
+ $self->{-error} = $!;
+ return undef;
+ }
+
+ $self->{-serial}->parity('none');
+ $self->{-serial}->databits(8);
+ $self->{-serial}->stopbits(1);
+ $self->{-serial}->handshake('none');
+ return $self->baudrate($self->{-baudrate});
+}
+
+sub baudrate {
+ my $self = shift;
+ my $baudrate = shift;
+
+ if ($baudrate < 1) {
+ $self->{-error} = "Invalid baudrate";
+ return undef;
+ }
+
+ $self->{-baudrate} = $baudrate;
+ if (defined($self->{-serial})) {
+ $self->{-serial}->baudrate($baudrate);
+ }
+
+ return 1;
+}
+
+sub sendmsg {
+ my $self = shift;
+ my $lingo = shift;
+ my $command = shift;
+ my $data = shift || '';
+
+ return $self->_nosetup() unless(defined($self->{-serial}));
+
+ if (($lingo < 0) || ($lingo > 255)) {
+ $self->{-error} = 'Invalid lingo';
+ return undef;
+ }
+
+ if ($command < 0) {
+ $self->{-error} = 'Invalid command';
+ return undef;
+ }
+
+ if ($lingo == 4) {
+ if ($command > 0xffff) {
+ $self->{-error} = 'Invalid command';
+ return undef;
+ }
+ return $self->_send($self->_frame_cmd(pack("Cn", $lingo, $command) . $data));
+ } else {
+ if ($command > 0xff) {
+ $self->{-error} = 'Invalid command';
+ return undef;
+ }
+ return $self->_send($self->_frame_cmd(pack("CC", $lingo, $command) . $data));
+ }
+}
+
+sub sendraw {
+ my $self = shift;
+ my $data = shift;
+
+ return $self->_nosetup() unless(defined($self->{-serial}));
+
+ return $self->_send($data);
+}
+
+sub recvmsg {
+ my $self = shift;
+ my $m;
+ my @m;
+
+ return $self->_nosetup() unless(defined($self->{-serial}));
+
+ $m = $self->_fillbuf();
+ unless(defined($m)) {
+ # Error was set by lower levels
+ return wantarray?():undef;
+ }
+
+ printf("Fetched %s\n", $self->_hexstring($m)) if $self->{-debug};
+
+ @m = $self->_unframe_cmd($m);
+
+ unless(@m) {
+ return undef;
+ }
+
+ if (wantarray()) {
+ return @m;
+ } else {
+ return {-lingo => $m[0], -cmd => $m[1], -payload => $m[2]};
+ }
+}
+
+sub emptyrecv {
+ my $self = shift;
+ my $m;
+
+ while ($m = $self->_fillbuf()) {
+ printf("Discarded %s\n", $self->_hexstring($m)) if (defined($m) && $self->{-debug});
+ }
+}
+
+sub error {
+ my $self = shift;
+
+ return $self->{-error};
+}
+
+sub _nosetup {
+ my $self = shift;
+
+ $self->{-error} = 'Serial port not setup';
+ return undef;
+}
+
+sub _frame_cmd {
+ my $self = shift;
+ my $data = shift;
+ my $l = length($data);
+ my $csum;
+
+ if ($l > 0xffff) {
+ $self->{-error} = 'Command too long';
+ return undef;
+ }
+
+ if ($l > 255) {
+ $data = pack("Cn", 0, length($data)) . $data;
+ } else {
+ $data = pack("C", length($data)) . $data;
+ }
+
+ foreach (unpack("C" x length($data), $data)) {
+ $csum += $_;
+ }
+ $csum &= 0xFF;
+ $csum = 0x100 - $csum;
+
+ return "\xFF\x55" . $data . pack("C", $csum);
+}
+
+sub _unframe_cmd {
+ my $self = shift;
+ my $data = shift;
+ my $payload = '';
+ my ($count, $length, $csum);
+ my $state = 0;
+ my $c;
+ my ($lingo, $cmd);
+
+ return () unless(defined($data));
+
+ foreach $c (unpack("C" x length($data), $data)) {
+ if ($state == 0) {
+ # Wait for sync
+ next unless($c == 255);
+ $state = 1;
+ } elsif ($state == 1) {
+ # Wait for sop
+ next unless($c == 85);
+ $state = 2;
+ } elsif ($state == 2) {
+ # Length (short frame)
+ $csum = $c;
+ if ($c == 0) {
+ # Large frame
+ $state = 3;
+ } else {
+ $state = 5;
+ }
+ $length = $c;
+ $count = 0;
+ next;
+ } elsif ($state == 3) {
+ # Large frame, hi
+ $csum += $c;
+ $length = ($c << 8);
+ $state = 4;
+ next;
+ } elsif ($state == 4) {
+ # Large frame, lo
+ $csum += $c;
+ $length |= $c;
+ if ($length == 0) {
+ $self->{-error} = 'Length is 0';
+ return ();
+ }
+ $state = 5;
+ next;
+ } elsif ($state == 5) {
+ # Data bytes
+ $csum += $c;
+ $payload .= chr($c);
+ $count += 1;
+ if ($count == $length) {
+ $state = 6;
+ }
+ } elsif ($state == 6) {
+ # Checksum byte
+ $csum += $c;
+ if (($csum & 0xFF) != 0) {
+ $self->{-error} = 'Invalid checksum';
+ return ();
+ }
+ $state = 7;
+ last;
+ } else {
+ $self->{-error} = 'Invalid state';
+ return ();
+ }
+ }
+
+ # If we get here, we either have data or not. Check.
+ if ($state != 7) {
+ $self->{-error} = 'Could not unframe data';
+ return ();
+ }
+
+ $lingo = unpack("C", $payload);
+ if ($lingo == 4) {
+ return unpack("Cna*", $payload);
+ } else {
+ return unpack("CCa*", $payload);
+ }
+}
+
+sub _send {
+ my $self = shift;
+ my $data = shift;
+ my $l = length($data);
+ my $c;
+
+ printf("Sending %s\n", $self->_hexstring($data)) if $self->{-debug};
+
+ $c = $self->{-serial}->write($data);
+ unless(defined($c)) {
+ $self->{-error} = 'write failed';
+ return undef;
+ }
+
+ if ($c != $l) {
+ $self->{-error} = 'incomplete write';
+ return undef;
+ }
+
+ return 1;
+}
+
+sub _fillbuf {
+ my $self = shift;
+ my $timeout = shift || 2;
+ my $to;
+
+ # Read from the port until we have a complete message in the buffer,
+ # or until we haven't read any new data for $timeout seconds, whatever
+ # comes first.
+
+ $to = $timeout;
+
+ while(!$self->_message_in_buffer() && $to > 0) {
+ my ($c, $s) = $self->{-serial}->read(255);
+ if ($c == 0) {
+ # No data read
+ select(undef, undef, undef, 0.1);
+ $to -= 0.1;
+ } else {
+ $self->{-inbuf} .= $s;
+ $to = $timeout;
+ }
+ }
+ if ($self->_message_in_buffer()) {
+ # There is a complete message in the buffer
+ return $self->_message();
+ } else {
+ # Timeout occured
+ $self->{-error} = 'Timeout reading from port';
+ return undef;
+ }
+}
+
+sub _message_in_buffer {
+ my $self = shift;
+ my $sp = 0;
+ my $i;
+
+ $i = index($self->{-inbuf}, "\xFF\x55", $sp);
+ while ($i != -1) {
+ my $header;
+ my $len;
+ my $large = 0;
+
+
+ $header = substr($self->{-inbuf}, $i, 3);
+ if (length($header) != 3) {
+ # Runt frame
+ return ();
+ }
+ $len = unpack("x2C", $header);
+ if ($len == 0) {
+ # Possible large frame
+ $header = substr($self->{-inbuf}, $i, 5);
+ if (length($header) != 5) {
+ # Runt frame
+ return ();
+ }
+ $large = 1;
+ $len = unpack("x3n", $header);
+ }
+
+ # Add framing, checksum and length
+ $len = $len+3+($large?3:1);
+
+ if (length($self->{-inbuf}) < ($i+$len)) {
+ # Buffer too short to hold rest of frame. Try again.
+ $sp = $i+1;
+ $i = index($self->{-inbuf}, "\xFF\x55", $sp);
+ } else {
+ return ($i, $len);
+ }
+ }
+
+ # No complete message found
+ return ();
+}
+
+
+sub _message {
+ my $self = shift;
+ my $start;
+ my $len;
+ my $m;
+
+ # Return the first complete message in the buffer, removing the message
+ # and everything before it from the buffer.
+ ($start, $len) = $self->_message_in_buffer();
+ unless(defined($start)) {
+ $self->{-error} = 'No complete message in buffer';
+ return undef;
+ }
+ $m = substr($self->{-inbuf}, $start, $len);
+ $self->{-inbuf} = substr($self->{-inbuf}, $start+$len);
+
+ return $m;
+}
+
+sub _hexstring {
+ my $self = shift;
+ my $s = shift;
+
+ return join("", map { (($_ == 0x20) || isgraph(chr($_)))?chr($_):sprintf("\\x%02x", $_) }
+ unpack("C" x length($s), $s));
+}
+
+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 @@
+default: test
+
+test:
+ perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV' ipod*.t
+
+moduletest:
+ 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 @@
+These are perl test scripts for validating the IAP implementation.
+Also included is a perl class for talking to an iPod via the serial
+port. You will probably need Linux to use this.
+
+Run "make moduletest" to test the perl module itself. This will not
+require any serial connection, or even an iPod, for that matter.
+
+Run "make test" to run the iPod communication tests themselves.
+
+In order to test make sure
+
+- the iPod is connected to a serial port
+- the test scripts assume that this port is /dev/ttyUSB0. Change
+ as neccessary
+
+Sometimes, tests will time out instead of giving the desired result.
+As long as the timeouts are not reproducable this is usually not a
+problem. The serial port is known to be unreliable, and devices will
+retransmit. This happens even with the OF.
+
+The tests were designed against an iPod Touch 2G as a reference device.
+Some older iPods fail some of the test, even with the OF, because of
+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 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+# Frame a short command
+$m = $ipod->_frame_cmd("\x00\x02\x00\x06");
+ok(defined($m) && ($m eq "\xFF\x55\x04\x00\x02\x00\x06\xF4"), "Framed command valid");
+
+# Frame a long command
+$m = $ipod->_frame_cmd("\x00" x 1024);
+ok(defined($m) && ($m eq "\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . "\xFC"), "Long framed command valid");
+
+# Frame an overly long command
+$m = $ipod->_frame_cmd("\x00" x 65537);
+ok(!defined($m) && ($ipod->error() =~ 'Command too long'), "Overly long command failed");
+
+# Unframe a short command
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x04\x00\x02\x00\x06\xF4");
+ok(defined($l) && ($l == 0x00) && ($c == 0x02) && ($p eq "\x00\x06"), "Unframed short command");
+
+# Unframe a long command
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . "\xFC");
+ok(defined($l) && ($l == 0x00) && ($c == 0x00) && ($p eq "\x00" x 1022), "Unframed long command");
+
+# Frame without sync byte
+($l, $c, $p) = $ipod->_unframe_cmd("\x00");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without sync byte failed");
+
+# Frame without SOP byte
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without SOP byte failed");
+
+# Frame with length 0
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Length is 0/), "Frame with length 0 failed");
+
+# Too short frame
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Too short frame failed");
+
+# Invalid checksum
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Invalid checksum/), "Invalid checksum failed");
+
+# Find a message in a string
+$ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4";
+($c, $l) = $ipod->_message_in_buffer();
+ok(defined($l) && ($c == 6) && ($l == 8), "Found message in buffer");
+
+# Return message from a string
+$ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4\x00";
+$m = $ipod->_message();
+ok(defined($m) && ($ipod->{-inbuf} eq "\x00"), "Retrieved message from buffer");
+
+# Return two messages from buffer
+$ipod->{-inbuf} = "\xffU\x04\x00\x02\x00\x13\xe7\xffU\x02\x00\x14\xea";
+$m = $ipod->_message();
+subtest "First message" => sub {
+ ok(defined($m), "Message received");
+ is($m, "\xffU\x04\x00\x02\x00\x13\xe7");
+};
+$m = $ipod->_message();
+subtest "Second message" => sub {
+ ok(defined($m), "Message received");
+ is($m, "\xffU\x02\x00\x14\xea");
+};
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 @@
+#!/usr/bin/perl -w
+
+package iap::decode;
+
+use Device::iPod;
+use Data::Dumper;
+use strict;
+
+sub new {
+ my $class = shift;
+ my $self = {-state => {}};
+
+ return bless($self, $class);
+}
+
+sub display {
+ my $self = shift;
+ my $lingo = shift;
+ my $command = shift;
+ my $data = shift;
+ my $name;
+ my $handler;
+ my $r;
+
+
+ $name = sprintf("_h_%02x_%04x", $lingo, $command);
+ $handler = $self->can($name);
+ if ($handler) {
+ unless(exists($self->{-state}->{$name})) {
+ $self->{-state}->{$name} = {};
+ }
+ $r = $handler->($self, $data, $self->{-state}->{$name});
+ } else {
+ $r = $self->generic($lingo, $command, $data);
+ }
+
+ printf("\n");
+ return $r;
+}
+
+sub generic {
+ my $self = shift;
+ my $lingo = shift;
+ my $command = shift;
+ my $data = shift;
+
+ printf("Unknown command\n");
+ printf(" Lingo: 0x%02x\n", $lingo);
+ printf(" Command 0x%04x\n", $command);
+ printf(" Data: %s\n", Device::iPod->_hexstring($data));
+
+ exit(1);
+
+ return 1;
+}
+
+sub _h_00_0001 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $lingo = shift;
+
+ $lingo = unpack("C", $data);
+
+ printf("Identify (0x00, 0x01) D->I\n");
+ printf(" Lingo: 0x%02x\n", $lingo);
+
+ return 1;
+}
+
+sub _h_00_0002 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+ my $cmd;
+ my $delay;
+
+ ($res, $cmd, $delay) = unpack("CCN", $data);
+
+ printf("ACK (0x00, 0x02) I->D\n");
+ printf(" Acknowledged command: 0x%02x\n", $cmd);
+ printf(" Status: %s (%d)\n",
+ ("Success",
+ "ERROR: Unknown Database Category",
+ "ERROR: Command Failed",
+ "ERROR: Out Of Resource",
+ "ERROR: Bad Parameter",
+ "ERROR: Unknown ID",
+ "Command Pending",
+ "ERROR: Not Authenticated",
+ "ERROR: Bad Authentication Version",
+ "ERROR: Accessory Power Mode Request Failed",
+ "ERROR: Certificate Invalid",
+ "ERROR: Certificate permissions invalid",
+ "ERROR: File is in use",
+ "ERROR: Invalid file handle",
+ "ERROR: Directory not empty",
+ "ERROR: Operation timed out",
+ "ERROR: Command unavailable in this iPod mode",
+ "ERROR: Invalid accessory resistor ID",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "ERROR: Maximum number of accessory connections already reached")[$res], $res);
+ if ($res == 6) {
+ $delay = unpack("xxN", $data);
+ printf(" Delay: %d ms\n", $delay);
+ }
+
+ return 1;
+}
+
+sub _h_00_0005 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("EnterRemoteUIMode (0x00, 0x05) D->I\n");
+
+ return 1;
+}
+
+sub _h_00_000d {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("RequestiPodModelNum (0x00, 0x0D) D->I\n");
+
+ return 1;
+}
+
+sub _h_00_000e {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($modelnum, $name);
+
+ ($modelnum, $name) = unpack("NZ*", $data);
+
+ printf("ReturniPodModelNum (0x00, 0x0E) I->D\n");
+ printf(" Model number: %08x\n", $modelnum);
+ printf(" Model name: %s\n", $name);
+
+ return 1;
+}
+
+sub _h_00_000f {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $lingo;
+
+ $lingo = unpack("C", $data);
+
+ printf("RequestLingoProtocolVersion (0x00, 0x0F) D->I\n");
+ printf(" Lingo: 0x%02x\n", $lingo);
+
+ return 1;
+}
+
+sub _h_00_0010 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($lingo, $maj, $min);
+
+ ($lingo, $maj, $min) = unpack("CCC", $data);
+
+ printf("ReturnLingoProtocolVersion (0x00, 0x10) I->D\n");
+ printf(" Lingo: 0x%02x\n", $lingo);
+ printf(" Version: %d.%02d\n", $maj, $min);
+
+ return 1;
+}
+
+
+sub _h_00_0013 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my @lingolist;
+ my ($lingoes, $options, $devid);
+
+ ($lingoes, $options, $devid) = unpack("N3", $data);
+
+ foreach (0..31) {
+ push(@lingolist, $_) if ($lingoes & (1 << $_));
+ }
+
+ printf("IdentifyDeviceLingoes (0x00, 0x13) D->I\n");
+ printf(" Supported lingoes: %s\n", join(", ", @lingolist));
+ printf(" Options:\n");
+ printf(" Authentication: %s\n", ("None", "Defer (1.0)", "Immediate (2.0)", "Reserved")[$options & 0x03]);
+ printf(" Power: %s\n", ("Low", "Intermittent high", "Reserved", "Constant high")[($options & 0x0C) >> 2]);
+ printf(" Device ID: 0x%08x\n", $devid);
+
+ delete($self->{-state}->{'_h_00_0015'});
+
+ return 1;
+}
+
+sub _h_00_0014 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetDevAuthenticationInfo (0x00, 0x14) I->D\n");
+
+ return 1;
+}
+
+sub _h_00_0015 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($amaj, $amin, $curidx, $maxidx, $certdata);
+
+ $state->{-curidx} = -1 unless(exists($state->{-curidx}));
+ $state->{-maxidx} = -1 unless(exists($state->{-maxidx}));
+ $state->{-cert} = '' unless(exists($state->{-cert}));
+
+ ($amaj, $amin, $curidx, $maxidx, $certdata) = unpack("CCCCa*", $data);
+
+ printf("RetDevAuthenticationInfo (0x00, 0x15) D->I\n");
+ printf(" Authentication version: %d.%d\n", $amaj, $amin);
+ printf(" Segment: %d of %d\n", $curidx, $maxidx);
+
+ if ($curidx-1 != $state->{-curidx}) {
+ printf(" WARNING! Out of order segment\n");
+ return 0;
+ }
+
+ if (($maxidx != $state->{-maxidx}) && ($state->{-maxidx} != -1)) {
+ printf(" WARNING! maxidx changed midstream\n");
+ return 0;
+ }
+
+ if ($curidx > $maxidx) {
+ printf(" WARNING! Too many segments\n");
+ return 0;
+ }
+
+ $state->{-curidx} = $curidx;
+ $state->{-maxidx} = $maxidx;
+ $state->{-cert} .= $certdata;
+
+ if ($curidx == $maxidx) {
+ printf(" Certificate: %s\n", Device::iPod->_hexstring($state->{-cert}));
+ }
+
+ return 1;
+}
+
+sub _h_00_0016 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+
+ $res = unpack("C", $data);
+
+ printf("AckDevAuthenticationInfo (0x00, 0x16) I->D\n");
+ printf(" Result: ");
+ if ($res == 0x00) {
+ printf("Authentication information supported\n");
+ } elsif ($res == 0x08) {
+ printf("Authentication information unpported\n");
+ } elsif ($res == 0x0A) {
+ printf("Certificate invalid\n");
+ } elsif ($res == 0x0B) {
+ printf("Certificate permissions are invalid\n");
+ } else {
+ printf("Unknown result 0x%02x\n", $res);
+ }
+
+ return 1;
+}
+
+sub _h_00_0017 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($challenge, $retry);
+
+ printf("GetDevAuthenticationSignature (0x00, 0x17) I->D\n");
+
+ if (length($data) == 17) {
+ ($challenge, $retry) = unpack("a16C", $data);
+ } elsif (length($data) == 21) {
+ ($challenge, $retry) = unpack("a20C", $data);
+ } else {
+ printf(" WARNING! Unsupported data length: %d\n", length($data));
+ return 0;
+ }
+
+ printf(" Challenge: %s (%d bytes)\n", Device::iPod->_hexstring($challenge), length($challenge));
+ printf(" Retry counter: %d\n", $retry);
+
+ return 1;
+}
+
+sub _h_00_0018 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $reply;
+
+ printf("RetDevAuthenticationSignature (0x00, 0x18) D->I\n");
+ printf(" Data: %s\n", Device::iPod->_hexstring($data));
+
+ return 1;
+}
+
+sub _h_00_0019 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+
+ $res = unpack("C", $data);
+
+ printf("AckiPodAuthenticationInfo (0x00, 0x19) I->D\n");
+ printf(" Status: %s (%d)\n", (
+ "OK")[$res], $res);
+
+ return 1;
+}
+
+sub _h_00_0024 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetiPodOptions (0x00, 0x24) D->I\n");
+
+ return 1;
+}
+
+sub _h_00_0025 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($ophi, $oplo);
+
+ ($ophi, $oplo) = unpack("NN", $data);
+
+ printf("RetiPodOptions (0x00, 0x25) I->D\n");
+ printf(" Options:\n");
+ printf(" iPod supports SetiPodPreferences\n") if ($oplo & 0x02);
+ printf(" iPod supports video\n") if ($oplo & 0x01);
+
+ return 1;
+}
+
+
+sub _h_00_0027 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $acctype;
+ my $accparam;
+
+ $acctype = unpack("C", $data);
+
+ printf("GetAccessoryInfo (0x00, 0x27) I->D\n");
+ printf(" Accessory Info Type: %s (%d)\n", (
+ "Info capabilities",
+ "Name",
+ "Minimum supported iPod firmware version",
+ "Minimum supported lingo version",
+ "Firmware version",
+ "Hardware version",
+ "Manufacturer",
+ "Model Number",
+ "Serial Number",
+ "Maximum payload size")[$acctype], $acctype);
+ if ($acctype == 0x02) {
+ my ($modelid, $maj, $min, $rev);
+
+ ($modelid, $maj, $min, $rev) = unpack("xNCCC", $data);
+ printf(" Model ID: 0x%04x\n", $modelid);
+ printf(" iPod Firmware: %d.%d.%d\n", $maj, $min, $rev);
+ } elsif ($acctype == 0x03) {
+ my $lingo;
+
+ $lingo = unpack("xC", $data);
+ printf(" Lingo: 0x%02x\n", $lingo);
+ }
+
+ return 1;
+}
+
+sub _h_00_0028 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $acctype;
+ my $accparam;
+
+ $acctype = unpack("C", $data);
+
+ printf("RetAccessoryInfo (0x00, 0x28) D->I\n");
+ printf(" Accessory Info Type: %s (%d)\n", (
+ "Info capabilities",
+ "Name",
+ "Minimum supported iPod firmware version",
+ "Minimum supported lingo version",
+ "Firmware version",
+ "Hardware version",
+ "Manufacturer",
+ "Model Number",
+ "Serial Number",
+ "Maximum payload size")[$acctype], $acctype);
+
+ if ($acctype == 0x00) {
+ $accparam = unpack("xN", $data);
+ printf(" Accessory Info capabilities\n") if ($accparam & 0x01);
+ printf(" Accessory name\n") if ($accparam & 0x02);
+ printf(" Accessory minimum supported iPod firmware\n") if ($accparam & 0x04);
+ printf(" Accessory minimum supported lingo version\n") if ($accparam & 0x08);
+ printf(" Accessory firmware version\n") if ($accparam & 0x10);
+ printf(" Accessory hardware version\n") if ($accparam & 0x20);
+ printf(" Accessory manufacturer\n") if ($accparam & 0x40);
+ printf(" Accessory model number\n") if ($accparam & 0x80);
+ printf(" Accessory serial number\n") if ($accparam & 0x100);
+ printf(" Accessory incoming max packet size\n") if ($accparam & 0x200);
+ }
+
+ if ($acctype ~~ [0x01, 0x06, 0x07, 0x08]) {
+ $accparam = unpack("xZ*", $data);
+ printf(" Data: %s\n", $accparam);
+ }
+
+ if ($acctype == 0x02) {
+ $accparam = [ unpack("xNCCC", $data) ];
+ printf(" Model ID: %08x\n", $accparam->[0]);
+ printf(" Firmware version: %d.%02d.%02d\n", $accparam->[1], $accparam->[2], $accparam->[3]);
+ }
+
+ if ($acctype == 0x03) {
+ $accparam = [ unpack("xCCC", $data) ];
+ printf(" Lingo: %02x\n", $accparam->[0]);
+ printf(" Version: %d.%02d\n", $accparam->[1], $accparam->[2]);
+ }
+
+ if ($acctype ~~ [0x04, 0x05]) {
+ $accparam = [ unpack("xCCC", $data) ];
+ printf(" Version: %d.%02d.%02d\n", @{$accparam});
+ }
+
+ return 1;
+}
+
+sub _h_00_0029 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $class;
+
+ $class = unpack("C", $data);
+
+ printf("GetiPodPreferences (0x00, 0x29) D->I\n");
+ printf(" Class: %s (%d)\n", (
+ "Video out setting",
+ "Screen configuration",
+ "Video signal format",
+ "Line Out usage",
+ "(Reserved)",
+ "(Reserved)",
+ "(Reserved)",
+ "(Reserved)",
+ "Video out connection",
+ "Closed captioning",
+ "Video aspect ratio",
+ "(Reserved)",
+ "Subtitles",
+ "Video alternate audio channel")[$class], $class);
+
+ return 1;
+}
+
+sub _h_00_002b {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($class, $setting);
+
+ ($class, $setting) = unpack("CC", $data);
+
+ printf("SetiPodPreferences (0x00, 0x2B) D->I\n");
+ printf(" Class: %s (%d)\n", (
+ "Video out setting",
+ "Screen configuration",
+ "Video signal format",
+ "Line out usage",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Video-out connection",
+ "Closed captioning",
+ "Video monitor aspect ratio",
+ "Reserved",
+ "Subtitles",
+ "Video alternate audio channel")[$class], $class);
+ printf(" Setting: %s (%d)\n", (
+ ["Off",
+ "On",
+ "Ask user"],
+ ["Fill screen",
+ "Fit to screen edge"],
+ ["NTSC",
+ "PAL"],
+ ["Not used",
+ "Used"],
+ [],
+ [],
+ [],
+ [],
+ ["None",
+ "Composite",
+ "S-video",
+ "Component"],
+ ["Off",
+ "On"],
+ ["4:3",
+ "16:9"],
+ [],
+ ["Off",
+ "On"],
+ ["Off",
+ "On"])[$class][$setting], $setting);
+
+ return 1;
+}
+
+sub _h_00_0038 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $transid;
+
+ $transid = unpack("n", $data);
+
+ printf("StartIDPS (0x00, 0x38) D->I\n");
+ printf(" TransID: %d\n", $transid);
+
+ return 1;
+}
+
+sub _h_00_003b {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($transid, $status);
+
+ ($transid, $status) = unpack("nC", $data);
+
+ printf("EndIDPS (0x00, 0x3B) D->I\n");
+ printf(" TransID: %d\n", $transid);
+ printf(" Action: %s (%d)\n", (
+ "Finished",
+ "Reset")[$status], $status);
+
+ return 1;
+}
+
+sub _h_00_004b {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $lingo;
+
+ $lingo = unpack("C", $data);
+
+ printf("GetiPodOptionsForLingo (0x00, 0x4B) D->I\n");
+ printf(" Lingo: 0x%02x\n", $lingo);
+
+ return 1;
+}
+
+sub _h_02_0000 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my @keys;
+
+ @keys = unpack("CCCC", $data);
+
+ printf("ContextButtonStatus (0x02, 0x00) D->I\n");
+ printf(" Buttons:\n");
+ printf(" Play/Pause\n") if ($keys[0] & 0x01);
+ printf(" Volume Up\n") if ($keys[0] & 0x02);
+ printf(" Volume Down\n") if ($keys[0] & 0x04);
+ printf(" Next Track\n") if ($keys[0] & 0x08);
+ printf(" Previous Track\n") if ($keys[0] & 0x10);
+ printf(" Next Album\n") if ($keys[0] & 0x20);
+ printf(" Previous Album\n") if ($keys[0] & 0x40);
+ printf(" Stop\n") if ($keys[0] & 0x80);
+
+ if (exists($keys[1])) {
+ printf(" Play/Resume\n") if ($keys[1] & 0x01);
+ printf(" Pause\n") if ($keys[1] & 0x02);
+ printf(" Mute toggle\n") if ($keys[1] & 0x04);
+ printf(" Next Chapter\n") if ($keys[1] & 0x08);
+ printf(" Previous Chapter\n") if ($keys[1] & 0x10);
+ printf(" Next Playlist\n") if ($keys[1] & 0x20);
+ printf(" Previous Playlist\n") if ($keys[1] & 0x40);
+ printf(" Shuffle Setting Advance\n") if ($keys[1] & 0x80);
+ }
+
+ if (exists($keys[2])) {
+ printf(" Repeat Setting Advance\n") if ($keys[2] & 0x01);
+ printf(" Power On\n") if ($keys[2] & 0x02);
+ printf(" Power Off\n") if ($keys[2] & 0x04);
+ printf(" Backlight for 30 seconds\n") if ($keys[2] & 0x08);
+ printf(" Begin FF\n") if ($keys[2] & 0x10);
+ printf(" Begin REW\n") if ($keys[2] & 0x20);
+ printf(" Menu\n") if ($keys[2] & 0x40);
+ printf(" Select\n") if ($keys[2] & 0x80);
+ }
+
+ if (exists($keys[3])) {
+ printf(" Up Arrow\n") if ($keys[3] & 0x01);
+ printf(" Down Arrow\n") if ($keys[3] & 0x02);
+ printf(" Backlight off\n") if ($keys[3] & 0x04);
+ }
+
+ return 1;
+}
+
+sub _h_02_0001 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+ my $cmd;
+
+ ($res, $cmd) = unpack("CC", $data);
+
+ printf("ACK (0x02, 0x01) I->D\n");
+ printf(" Acknowledged command: 0x%02x\n", $cmd);
+ printf(" Status: %s (%d)\n",
+ ("Success",
+ "ERROR: Unknown Database Category",
+ "ERROR: Command Failed",
+ "ERROR: Out Of Resource",
+ "ERROR: Bad Parameter",
+ "ERROR: Unknown ID",
+ "Command Pending",
+ "ERROR: Not Authenticated",
+ "ERROR: Bad Authentication Version",
+ "ERROR: Accessory Power Mode Request Failed",
+ "ERROR: Certificate Invalid",
+ "ERROR: Certificate permissions invalid",
+ "ERROR: File is in use",
+ "ERROR: Invalid file handle",
+ "ERROR: Directory not empty",
+ "ERROR: Operation timed out",
+ "ERROR: Command unavailable in this iPod mode",
+ "ERROR: Invalid accessory resistor ID",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "ERROR: Maximum number of accessory connections already reached")[$res], $res);
+
+ return 1;
+}
+
+sub _h_03_0000 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+ my $cmd;
+
+ ($res, $cmd) = unpack("CC", $data);
+
+ printf("ACK (0x03, 0x00) I->D\n");
+ printf(" Acknowledged command: 0x%02x\n", $cmd);
+ printf(" Status: %s (%d)\n",
+ ("Success",
+ "ERROR: Unknown Database Category",
+ "ERROR: Command Failed",
+ "ERROR: Out Of Resource",
+ "ERROR: Bad Parameter",
+ "ERROR: Unknown ID",
+ "Command Pending",
+ "ERROR: Not Authenticated",
+ "ERROR: Bad Authentication Version",
+ "ERROR: Accessory Power Mode Request Failed",
+ "ERROR: Certificate Invalid",
+ "ERROR: Certificate permissions invalid",
+ "ERROR: File is in use",
+ "ERROR: Invalid file handle",
+ "ERROR: Directory not empty",
+ "ERROR: Operation timed out",
+ "ERROR: Command unavailable in this iPod mode",
+ "ERROR: Invalid accessory resistor ID",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "ERROR: Maximum number of accessory connections already reached")[$res], $res);
+
+ return 1;
+}
+
+sub _h_03_0008 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $events;;
+
+ $events = unpack("N", $data);
+
+ printf("SetRemoteEventsNotification (0x03, 0x08) D->I\n");
+ printf(" Events:\n");
+ printf(" Track position in ms\n") if ($events & 0x00000001);
+ printf(" Track playback index\n") if ($events & 0x00000002);
+ printf(" Chapter index\n") if ($events & 0x00000004);
+ printf(" Play status\n") if ($events & 0x00000008);
+ printf(" Mute/UI volume\n") if ($events & 0x00000010);
+ printf(" Power/Battery\n") if ($events & 0x00000020);
+ printf(" Equalizer setting\n") if ($events & 0x00000040);
+ printf(" Shuffle setting\n") if ($events & 0x00000080);
+ printf(" Repeat setting\n") if ($events & 0x00000100);
+ printf(" Date and time setting\n") if ($events & 0x00000200);
+ printf(" Alarm setting\n") if ($events & 0x00000400);
+ printf(" Backlight state\n") if ($events & 0x00000800);
+ printf(" Hold switch state\n") if ($events & 0x00001000);
+ printf(" Sound check state\n") if ($events & 0x00002000);
+ printf(" Audiobook speed\n") if ($events & 0x00004000);
+ printf(" Track position in s\n") if ($events & 0x00008000);
+ printf(" Mute/UI/Absolute volume\n") if ($events & 0x00010000);
+
+ return 1;
+}
+
+sub _h_03_0009 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $event;
+ my $eventdata;
+
+ $event = unpack("C", $data);
+
+ printf("RemoteEventNotification (0x03, 0x09) I->D\n");
+ printf(" Event: %s (%d)\n", (
+ "Track position in ms",
+ "Track playback index",
+ "Chapter index",
+ "Play status",
+ "Mute/UI volume",
+ "Power/Battery",
+ "Equalizer setting",
+ "Shuffle setting",
+ "Repeat setting",
+ "Date and time setting",
+ "Alarm setting",
+ "Backlight state",
+ "Hold switch state",
+ "Sound check state",
+ "Audiobook speed",
+ "Track position in s",
+ "Mute/UI/Absolute volume")[$event], $event);
+
+ if ($event == 0x00) {
+ $eventdata = unpack("xN", $data);
+ printf(" Position: %d ms\n", $eventdata);
+ } elsif ($event == 0x01) {
+ $eventdata = unpack("xN", $data);
+ printf(" Track: %d\n", $eventdata);
+ } elsif ($event == 0x02) {
+ $eventdata = [ unpack("xNnn", $data) ];
+ printf(" Track: %d\n", $eventdata->[0]);
+ printf(" Chapter count: %d\n", $eventdata->[1]);
+ printf(" Chapter index: %d\n", $eventdata->[2]);
+ } elsif ($event == 0x03) {
+ $eventdata = unpack("xC", $data);
+ printf(" Status: %s (%d)\n", (
+ "Stopped",
+ "Playing",
+ "Paused",
+ "FF",
+ "REW",
+ "End FF/REW")[$eventdata], $eventdata);
+ } elsif ($event == 0x04) {
+ $eventdata = [ unpack("xCC") ];
+ printf(" Mute: %s\n", $eventdata->[0]?"Off":"On");
+ printf(" Volume: %d\n", $eventdata->[1]);
+ } elsif ($event == 0x0F) {
+ $eventdata = unpack("xn", $data);
+ printf(" Position: %d s\n", $eventdata);
+ }
+
+ return 1;
+}
+
+sub _h_03_000c {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $info;
+
+ $info = unpack("C", $data);
+
+ printf("GetiPodStateInfo (0x03, 0x0C) D->I\n");
+ printf(" Info to get: %s (%d)\n", (
+ "Track time in ms",
+ "Track playback index",
+ "Chapter information",
+ "Play status",
+ "Mute and volume information",
+ "Power and battery status",
+ "Equalizer setting",
+ "Shuffle setting",
+ "Repeat setting",
+ "Date and time",
+ "Alarm state and time",
+ "Backlight state",
+ "Hold switch state",
+ "Audiobook speed",
+ "Track time in seconds",
+ "Mute/UI/Absolute volume")[$info], $info);
+
+ return 1;
+}
+
+sub _h_03_000d {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $type;
+ my $info;
+
+ $type = unpack("C", $data);
+
+ printf("RetiPodStateInfo (0x03, 0x0E) D->I\n");
+
+ if ($type == 0x00) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track position\n");
+ printf(" Position: %d ms\n", $info);
+ } elsif ($type == 0x01) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track index\n");
+ printf(" Index: %d\n", $info);
+ } elsif ($type == 0x02) {
+ $info = [ unpack("xNnn", $data) ];
+ printf(" Type: Chapter Information\n");
+ printf(" Playing Track: %d\n", $info->[0]);
+ printf(" Chapter count: %d\n", $info->[1]);
+ printf(" Chapter index: %d\n", $info->[2]);
+ } elsif ($type == 0x03) {
+ $info = unpack("xC", $data);
+ printf(" Type: Play status\n");
+ printf(" Status: %s (%d)\n", (
+ "Stopped",
+ "Playing",
+ "Paused",
+ "FF",
+ "REW",
+ "End FF/REW")[$info], $info);
+ } elsif ($type == 0x04) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Mute/Volume\n");
+ printf(" Mute State: %s\n", $info->[0]?"On":"Off");
+ printf(" Volume level: %d\n", $info->[1]);
+ } elsif ($type == 0x05) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Battery Information\n");
+ printf(" Power state: %s (%d)\n", (
+ "Internal, low power (<30%)",
+ "Internal",
+ "External battery pack, no charging",
+ "External power, no charging",
+ "External power, charging",
+ "External power, charged")[$info->[0]], $info->[0]);
+ printf(" Battery level: %d%%\n", $info->[1]*100/255);
+ } elsif ($type == 0x06) {
+ $info = [unpack("xN", $data)];
+ printf(" Type: Equalizer\n");
+ printf(" Index: %d\n", $info->[0]);
+ } elsif ($type == 0x07) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Shuffle\n");
+ printf(" Shuffle State: %s\n", $info->[0]?"On":"Off");
+ } elsif ($type == 0x08) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Repeat\n");
+ printf(" Repeat State: %s\n", $info->[0]?"On":"Off");
+ } elsif ($type == 0x09) {
+ $info = [unpack("xnCCCC", $data)];
+ printf(" Type: Date\n");
+ printf(" Date: %02d.%02d.%04d %02d:%02d\n", $info->[2], $info->[1], $info->[0], $info->[3], $info->[4]);
+ } elsif ($type == 0x0A) {
+ $info = [unpack("xCCC", $data)];
+ printf(" Type: Alarm\n");
+ printf(" Alarm State: %s\n", $info->[0]?"On":"Off");
+ printf(" Time: %02d:%02d\n", $info->[1], $info->[2]);
+ } elsif ($type == 0x0B) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Backlight\n");
+ printf(" Backlight State: %s\n", $info->[0]?"On":"Off");
+ } elsif ($type == 0x0C) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Hold switch\n");
+ printf(" Switch State: %s\n", $info->[0]?"On":"Off");
+ } elsif ($type == 0x0D) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Sound check\n");
+ printf(" Sound check: %s\n", $info->[0]?"On":"Off");
+ } elsif ($type == 0x0E) {
+ $info = [unpack("xC", $data)];
+ printf(" Type: Audiobook speed\n");
+ printf(" Speed: %s\n", $info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved");
+ } elsif ($type == 0x0F) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track position\n");
+ printf(" Position: %d s\n", $info);
+ } elsif ($type == 0x10) {
+ $info = [unpack("xCCC", $data)];
+ printf(" Type: Mute/UI/Absolute volume\n");
+ printf(" Mute State: %s\n", $info->[0]?"On":"Off");
+ printf(" UI Volume level: %d\n", $info->[1]);
+ printf(" Absolute Volume: %d\n", $info->[2]);
+ } else {
+ printf(" Reserved\n");
+ }
+
+ return 1;
+}
+
+
+sub _h_03_000e {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $type;
+ my $info;
+
+ $type = unpack("C", $data);
+
+ printf("SetiPodStateInfo (0x03, 0x0E) D->I\n");
+
+ if ($type == 0x00) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track position\n");
+ printf(" Position: %d ms\n", $info);
+ } elsif ($type == 0x01) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track index\n");
+ printf(" Index: %d\n", $info);
+ } elsif ($type == 0x02) {
+ $info = unpack("xn", $data);
+ printf(" Type: Chapter index\n");
+ printf(" Index: %d\n", $info);
+ } elsif ($type == 0x03) {
+ $info = unpack("xC", $data);
+ printf(" Type: Play status\n");
+ printf(" Status: %s (%d)\n", (
+ "Stopped",
+ "Playing",
+ "Paused",
+ "FF",
+ "REW",
+ "End FF/REW")[$info], $info);
+ } elsif ($type == 0x04) {
+ $info = [unpack("xCCC", $data)];
+ printf(" Type: Mute/Volume\n");
+ printf(" Mute State: %s\n", $info->[0]?"On":"Off");
+ printf(" Volume level: %d\n", $info->[1]);
+ printf(" Restore on exit: %s\n", $info->[2]?"Yes":"No");
+ } elsif ($type == 0x06) {
+ $info = [unpack("xNC", $data)];
+ printf(" Type: Equalizer\n");
+ printf(" Index: %d\n", $info->[0]);
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x07) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Shuffle\n");
+ printf(" Shuffle State: %s\n", $info->[0]?"On":"Off");
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x08) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Repeat\n");
+ printf(" Repeat State: %s\n", $info->[0]?"On":"Off");
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x09) {
+ $info = [unpack("xnCCCC", $data)];
+ printf(" Type: Date\n");
+ printf(" Date: %02d.%02d.%04d %02d:%02d\n", $info->[2], $info->[1], $info->[0], $info->[3], $info->[4]);
+ } elsif ($type == 0x0A) {
+ $info = [unpack("xCCCC", $data)];
+ printf(" Type: Alarm\n");
+ printf(" Alarm State: %s\n", $info->[0]?"On":"Off");
+ printf(" Time: %02d:%02d\n", $info->[1], $info->[2]);
+ printf(" Restore on exit: %s\n", $info->[3]?"Yes":"No");
+ } elsif ($type == 0x0B) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Backlight\n");
+ printf(" Backlight State: %s\n", $info->[0]?"On":"Off");
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x0D) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Sound check\n");
+ printf(" Sound check: %s\n", $info->[0]?"On":"Off");
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x0E) {
+ $info = [unpack("xCC", $data)];
+ printf(" Type: Audiobook speed\n");
+ printf(" Speed: %s\n", $info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved");
+ printf(" Restore on exit: %s\n", $info->[1]?"Yes":"No");
+ } elsif ($type == 0x0F) {
+ $info = unpack("xN", $data);
+ printf(" Type: Track position\n");
+ printf(" Position: %d s\n", $info);
+ } elsif ($type == 0x10) {
+ $info = [unpack("xCCCC", $data)];
+ printf(" Type: Mute/UI/Absolute volume\n");
+ printf(" Mute State: %s\n", $info->[0]?"On":"Off");
+ printf(" UI Volume level: %d\n", $info->[1]);
+ printf(" Absolute Volume: %d\n", $info->[2]);
+ printf(" Restore on exit: %s\n", $info->[3]?"Yes":"No");
+ } else {
+ printf(" Reserved\n");
+ }
+
+ return 1;
+}
+
+sub _h_03_000f {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ print("GetPlayStatus (0x03, 0x0F) D->I\n");
+
+ return 1;
+}
+
+sub _h_03_0010 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($status, $idx, $len, $pos);
+
+ ($status, $idx, $len, $pos) = unpack("CNNN", $data);
+
+ printf("RetPlayStatus (0x03, 0x10) I->D\n");
+ printf(" Status: %s (%d)\n", (
+ "Stopped",
+ "Playing",
+ "Paused")[$status], $status);
+ if ($status != 0x00) {
+ printf(" Track index: %d\n", $idx);
+ printf(" Track length: %d\n", $len);
+ printf(" Track position: %d\n", $pos);
+ }
+
+ return 1;
+}
+
+sub _h_03_0012 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($info, $tidx, $cidx);
+
+ ($info, $tidx, $cidx) = unpack("CNn", $data);
+
+ printf("GetIndexedPlayingTrackInfo (0x03, 0x12) D->I\n");
+ printf(" Requested info: %s (%d)\n", (
+ "Track caps/info",
+ "Chapter time/name",
+ "Artist name",
+ "Album name",
+ "Genre name",
+ "Track title",
+ "Composer name",
+ "Lyrics",
+ "Artwork count")[$info], $info);
+ printf(" Track index: %d\n", $tidx);
+ printf(" Chapter index: %d\n", $cidx);
+
+ return 1;
+}
+
+sub _h_03_0013 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $info;
+
+ $info = unpack("C", $data);
+ printf("RetIndexedPlayingTrackInfo (0x03, 0x13) I->D\n");
+ printf(" Returned info: %s (%d)\n", (
+ "Track caps/info",
+ "Chapter time/name",
+ "Artist name",
+ "Album name",
+ "Genre name",
+ "Track title",
+ "Composer name",
+ "Lyrics",
+ "Artwork count")[$info], $info);
+ if ($info == 0x00) {
+ my ($caps, $len, $chap) = unpack("xNNn", $data);
+ printf(" Track is audiobook\n") if ($caps & 0x01);
+ printf(" Track has chapters\n") if ($caps & 0x02);
+ printf(" Track has artwork\n") if ($caps & 0x04);
+ printf(" Track contains video\n") if ($caps & 0x80);
+ printf(" Track queued as video\n") if ($caps & 0x100);
+
+ printf(" Track length: %d ms\n", $len);
+ printf(" Track chapters: %d\n", $chap);
+ } elsif ($info == 0x01) {
+ my ($len, $name) = unpack("xNZ*");
+ printf(" Chapter time: %d ms\n", $len);
+ printf(" Chapter name: %s\n", $name);
+ } elsif ($info >= 0x02 && $info <= 0x06) {
+ my $name = unpack("xZ*", $data);
+ printf(" Name/Title: %s\n", $name)
+ } elsif ($info == 0x07) {
+ my ($info, $index, $data) = unpack("xCnZ*");
+ printf(" Part of multiple packets\n") if ($info & 0x01);
+ printf(" Is last packet\n") if ($info & 0x02);
+ printf(" Packet index: %d\n", $index);
+ printf(" Data: %s\n", $data);
+ } elsif ($info == 0x08) {
+
+ }
+
+ return 1;
+}
+
+sub _h_04_0001 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $res;
+ my $cmd;
+
+ ($res, $cmd) = unpack("Cn", $data);
+
+ printf("ACK (0x04, 0x0001) I->D\n");
+ printf(" Acknowledged command: 0x%02x\n", $cmd);
+ printf(" Status: %s (%d)\n",
+ ("Success",
+ "ERROR: Unknown Database Category",
+ "ERROR: Command Failed",
+ "ERROR: Out Of Resource",
+ "ERROR: Bad Parameter",
+ "ERROR: Unknown ID",
+ "Reserved",
+ "ERROR: Not Authenticated")[$res], $res);
+
+ return 1;
+}
+
+sub _h_04_000c {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($info, $track, $chapter);
+
+ ($info, $track, $chapter) = unpack("CNn", $data);
+
+ printf("GetIndexedPlayingTrackInfo (0x04, 0x000C) D->I\n");
+ printf(" Track: %d\n", $track);
+ printf(" Chapter: %d\n", $chapter);
+ printf(" Info requested: %s (%d)\n", (
+ "Capabilities and information",
+ "Podcast name",
+ "Track release date",
+ "Track description",
+ "Track song lyrics",
+ "Track genre",
+ "Track Composer",
+ "Tracn Artwork count")[$info], $info);
+
+ return 1;
+}
+
+sub _h_04_000d {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $info;
+
+ $info = unpack("C", $data);
+
+ printf("ReturnIndexedPlayingTrackInfo (0x04, 0x000D) I->D\n");
+ if ($info == 0x00) {
+ my ($capability, $length, $chapter);
+
+ ($capability, $length, $chapter) = unpack("xNNn", $data);
+ printf(" Capabilities:\n");
+ printf(" Is audiobook\n") if ($capability & 0x00000001);
+ printf(" Has chapters\n") if ($capability & 0x00000002);
+ printf(" Has album artwork\n") if ($capability & 0x00000004);
+ printf(" Has song lyrics\n") if ($capability & 0x00000008);
+ printf(" Is a podcast episode\n") if ($capability & 0x00000010);
+ printf(" Has release date\n") if ($capability & 0x00000020);
+ printf(" Has description\n") if ($capability & 0x00000040);
+ printf(" Contains video\n") if ($capability & 0x00000080);
+ printf(" Queued to play as video\n") if ($capability & 0x00000100);
+
+ printf(" Length: %d ms\n", $length);
+ printf(" Chapters: %d\n", $chapter);
+ } else {
+ printf(" WARNING: Unknown info\n");
+ return 1;
+ }
+
+ return 1;
+}
+
+sub _h_04_0012 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("RequestProtocolVersion (0x04, 0x0012) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_0013 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($maj, $min);
+
+ ($maj, $min) = unpack("CC", $data);
+
+ printf("ReturnProtocolVersion (0x04, 0x0013) I->D\n");
+ printf(" Lingo 0x04 version: %d.%02d\n", $maj, $min);
+
+ return 1;
+}
+
+sub _h_04_0016 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("ResetDBSelection (0x04, 0x0016) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_0018 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $category;
+
+ $category = unpack("C", $data);
+
+ printf("GetNumberCategorizedDBRecords (0x04, 0x0018) D->I\n");
+ printf(" Category: %s (%d)\n", (
+ "Reserved",
+ "Playlist",
+ "Artist",
+ "Album",
+ "Genre",
+ "Track",
+ "Composer",
+ "Audiobook",
+ "Podcast",
+ "Nested Playlist")[$category], $category);
+
+ return 1;
+}
+
+sub _h_04_0019 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $count;
+
+ $count = unpack("N", $data);
+
+ printf("ReturnNumberCategorizedDBRecords (0x04, 0x0019) I->D\n");
+ printf(" Count: %d\n", $count);
+
+ return 1;
+}
+
+sub _h_04_001c {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetPlayStatus (0x04, 0x001C) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_001d {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($len, $pos, $s);
+
+ ($len, $pos, $s) = unpack("NNC", $data);
+
+ printf("ReturnPlayStatus (0x04, 0x001D) I->D\n");
+ printf(" Song length: %d ms\n", $len);
+ printf(" Song position: %d ms\n", $pos);
+ printf(" Player state: ");
+ if ($s == 0x00) {
+ printf("Stopped\n");
+ } elsif ($s == 0x01) {
+ printf("Playing\n");
+ } elsif ($s == 0x02) {
+ printf("Paused\n");
+ } elsif ($s == 0xFF) {
+ printf("Error\n");
+ } else {
+ printf("Reserved\n");
+ }
+
+ return 1;
+}
+
+sub _h_04_001e {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetCurrentPlayingTrackIndex (0x04, 0x001E) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_001f {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $num;
+
+ $num = unpack("N", $data);
+
+ printf("ReturnCurrentPlayingTrackIndex (0x04, 0x001F) I->D\n");
+ printf(" Index: %d\n", $num);
+
+ return 1;
+}
+
+sub _h_04_0020 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $track;
+
+ $track = unpack("N", $data);
+
+ printf("GetIndexedPlayingTrackTitle (0x04, 0x0020) D->I\n");
+ printf(" Track: %d\n", $track);
+
+ return 1;
+}
+
+sub _h_04_0021 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $title;
+
+ $title = unpack("Z*", $data);
+
+ printf("ReturnIndexedPlayingTrackTitle (0x04, 0x0021) I->D\n");
+ printf(" Title: %s\n", $title);
+
+ return 1;
+}
+
+sub _h_04_0022 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $track;
+
+ $track = unpack("N", $data);
+
+ printf("GetIndexedPlayingTrackArtistName (0x04, 0x0022) D->I\n");
+ printf(" Track: %d\n", $track);
+
+ return 1;
+}
+
+sub _h_04_0023 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $artist;
+
+ $artist = unpack("Z*", $data);
+
+ printf("ReturnIndexedPlayingTrackArtistName (0x04, 0x0023) I->D\n");
+ printf(" Artist: %s\n", $artist);
+
+ return 1;
+}
+
+sub _h_04_0024 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $track;
+
+ $track = unpack("N", $data);
+
+ printf("GetIndexedPlayingTrackAlbumName (0x04, 0x0024) D->I\n");
+ printf(" Track: %d\n", $track);
+
+ return 1;
+}
+
+sub _h_04_0025 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $title;
+
+ $title = unpack("Z*", $data);
+
+ printf("ReturnIndexedPlayingTrackAlbumName (0x04, 0x0025) I->D\n");
+ printf(" Album: %s\n", $title);
+
+ return 1;
+}
+
+sub _h_04_0026 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $notification;
+
+ if (length($data) == 1) {
+ $notification = unpack("C", $data);
+ } elsif (length($data) == 4) {
+ $notification = unpack("N", $data);
+ }
+
+ printf("SetPlayStatusChangeNotification (0x04, 0x0026) D->I\n");
+
+ if (length($data) == 1) {
+ printf(" Events for: %s (%d)\n", (
+ "Disable all",
+ "Basic play state, track index, track time position, FFW/REW seek stop, and chapter index changes")[$notification], $notification);
+ } elsif (length($data) == 4) {
+ printf(" Events for:\n");
+ printf(" Basic play state changes\n") if ($notification & 0x00000001);
+ printf(" Extended play state changes\n") if ($notification & 0x00000002);
+ printf(" Track index\n") if ($notification & 0x00000004);
+ printf(" Track time offset (ms)\n") if ($notification & 0x00000008);
+ printf(" Track time offset (s)\n") if ($notification & 0x00000010);
+ printf(" Chapter index\n") if ($notification & 0x00000020);
+ printf(" Chapter time offset (ms)\n") if ($notification & 0x00000040);
+ printf(" Chapter time offset (s)\n") if ($notification & 0x00000080);
+ printf(" Track unique identifier\n") if ($notification & 0x00000100);
+ printf(" Track media tyoe\n") if ($notification & 0x00000200);
+ printf(" Track lyrics\n") if ($notification & 0x00000400);
+ } else {
+ printf(" WARNING: Unknown length for state\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+sub _h_04_0027 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $info;
+
+ $info = unpack("C", $data);
+
+ printf("PlayStatusChangeNotification (0x04, 0x0029) I->D\n");
+ printf(" Status:\n");
+ if ($info == 0x00) {
+ printf(" Playback stopped\n");
+ } elsif ($info == 0x01) {
+ my $index = unpack("xN", $data);
+
+ printf(" Track index: %d\n", $index);
+ } elsif ($info == 0x02) {
+ printf(" Playback FF seek stop\n");
+ } elsif ($info == 0x03) {
+ printf(" Playback REW seek stop\n");
+ } elsif ($info == 0x04) {
+ my $offset = unpack("xN", $data);
+
+ printf(" Track time offset: %d ms\n", $offset);
+ } elsif ($info == 0x05) {
+ my $index = unpack("xN", $data);
+
+ printf(" Chapter index: %d\n", $index);
+ } elsif ($info == 0x06) {
+ my $status = unpack("xC", $data);
+
+ printf(" Playback status extended: %s (%d)\n", (
+ "Reserved",
+ "Reserved",
+ "Stopped",
+ "Reserved",
+ "Reserved",
+ "FF seek started",
+ "REW seek started",
+ "FF/REW seek stopped",
+ "Reserved",
+ "Reserved",
+ "Playing",
+ "Paused")[$status], $status);
+ } elsif ($info == 0x07) {
+ my $offset = unpack("xN", $data);
+
+ printf(" Track time offset: %d s\n", $offset);
+ } elsif ($info == 0x08) {
+ my $offset = unpack("xN", $data);
+
+ printf(" Chapter time offset %d ms\n", $offset);
+ } elsif ($info == 0x09) {
+ my $offset = unpack("xN", $data);
+
+ printf(" Chapter time offset %d s\n", $offset);
+ } elsif ($info == 0x0A) {
+ my ($uidhi, $uidlo) = unpack("xNN", $data);
+
+ printf(" Track UID: %08x%08x\n", $uidhi, $uidlo);
+ } elsif ($info == 0x0B) {
+ my $mode = unpack("xC", $data);
+
+ printf(" Track mode: %s (%d)\n", (
+ "Audio track",
+ "Video track")[$mode], $mode);
+ } elsif ($info == 0x0C) {
+ printf(" Track lyrics ready\n");
+ } else {
+ printf(" Reserved\n");
+ }
+
+ return 1;
+}
+
+sub _h_04_0029 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $control;
+
+ $control = unpack("C", $data);
+
+ printf("PlayControl (0x04, 0x0029) D->I\n");
+ printf(" Command: %s (%d)\n", (
+ "Reserved",
+ "Toggle Play/Pause",
+ "Stop",
+ "Next track",
+ "Previous track",
+ "Start FF",
+ "Start Rev",
+ "Stop FF/Rev",
+ "Next",
+ "Previous",
+ "Play",
+ "Pause",
+ "Next chapter",
+ "Previous chapter")[$control], $control);
+
+ return 1;
+}
+
+sub _h_04_002c {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetShuffle (0x04, 0x002C) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_002d {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $mode;
+
+ $mode = unpack("C", $data);
+
+ printf("ReturnShuffle (0x04, 0x002D) I->D\n");
+ printf(" Mode: %s (%d)\n", (
+ "Off",
+ "Tracks",
+ "Albums")[$mode], $mode);
+
+ return 1;
+}
+
+sub _h_04_002f {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetRepeat (0x04, 0x002F) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_0030 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $mode;
+
+ $mode = unpack("C", $data);
+
+ printf("ReturnRepeat (0x04, 0x0030) I->D\n");
+ printf(" Mode: %s (%d)\n", (
+ "Off",
+ "One Track",
+ "All Tracks")[$mode], $mode);
+
+ return 1;
+}
+
+sub _h_04_0032 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($index, $format, $width, $height, $stride, $imagedata);
+
+ $state->{-index} = -1 unless(exists($state->{-index}));
+ $state->{-image} = '' unless(exists($state->{-image}));
+
+ ($index, $format, $width, $height, $stride, $imagedata) = unpack("nCnnNa*", $data);
+
+ printf("SetDisplayImage (0x04, 0x0032) D->I\n");
+ if ($index == 0) {
+ printf(" Width: %d\n", $width);
+ printf(" Height: %d\n", $height);
+ printf(" Stride: %d\n", $stride);
+ printf(" Format: %s (%d)\n", (
+ "Reserved",
+ "Monochrome, 2 bps",
+ "RGB 565, little endian",
+ "RGB 565, big endian")[$format], $format);
+
+ $state->{-imagelength} = $height * $stride;
+ } else {
+ ($index, $imagedata) = unpack("na*", $data);
+ }
+
+ if ($index-1 != $state->{-index}) {
+ printf(" WARNING! Out of order segment\n");
+ return 0;
+ }
+
+ $state->{-index} = $index;
+ $state->{-image} .= $imagedata;
+
+ if (length($state->{-image}) >= $state->{-imagelength}) {
+ printf(" Image data: %s\n", Device::iPod->_hexstring($state->{-image}));
+ }
+
+ return 1;
+}
+
+sub _h_04_0033 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetMonoDisplayImageLimits (0x04, 0x0033) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_0034 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my ($width, $height, $format);
+
+ ($width, $height, $format) = unpack("nnC", $data);
+
+ printf("ReturnMonoDisplayImageLimits (0x04, 0x0034) I->D\n");
+ printf(" Width: %d\n", $width);
+ printf(" Height: %d\n", $height);
+ printf(" Format: %s (%d)\n", (
+ "Reserved",
+ "Monochrome, 2 bps",
+ "RGB 565, little endian",
+ "RGB 565, big endian")[$format], $format);
+
+ return 1;
+}
+
+sub _h_04_0035 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("GetNumPlayingTracks (0x04, 0x0035) D->I\n");
+
+ return 1;
+}
+
+sub _h_04_0036 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $num;
+
+ $num = unpack("N", $data);
+
+ printf("ReturnNumPlayingTracks (0x04, 0x0036) I->D\n");
+ printf(" Number: %d\n", $num);
+
+ return 1;
+}
+
+sub _h_04_0037 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $num;
+
+ $num = unpack("N", $data);
+
+ printf("SetCurrentPlayingTrack (0x04, 0x0037) D->I\n");
+ printf(" Track: %d\n", $num);
+
+ return 1;
+}
+
+sub _h_07_0005 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $control;
+
+ $control = unpack("C", $data);
+
+ printf("SetTunerCtrl (0x07, 0x05) I->D\n");
+ printf(" Options:\n");
+ printf(" Power %s\n", ($control & 0x01)?"on":"off");
+ printf(" Status change notifications %s\n", ($control & 0x02)?"on":"off");
+ printf(" Raw mode %s\n", ($control & 0x04)?"on":"off");
+}
+
+sub _h_07_0020 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+ my $options;
+
+ $options = unpack("N", $data);
+
+ printf("SetRDSNotifyMask (0x07, 0x20) I->D\n");
+ printf(" Options:\n");
+ printf(" Radiotext\n") if ($options & 0x00000010);
+ printf(" Program Service Name\n") if ($options & 0x40000000);
+ printf(" Reserved\n") if ($options & 0xBFFFFFEF);
+
+ return 1;
+}
+
+sub _h_07_0024 {
+ my $self = shift;
+ my $data = shift;
+ my $state = shift;
+
+ printf("Reserved command (0x07, 0x24) I->D\n");
+
+ return 1;
+}
+
+
+package main;
+
+use Device::iPod;
+use Getopt::Long;
+use strict;
+
+my $decoder;
+my $device;
+my $unpacker;
+my $line;
+
+sub unpack_hexstring {
+ my $line = shift;
+ my $m;
+ my @m;
+
+ $line =~ s/(..)/chr(hex($1))/ge;
+ $device->{-inbuf} = $line;
+
+ $m = $device->_message();
+ next unless defined($m);
+ @m = $device->_unframe_cmd($m);
+ unless(@m) {
+ printf("Line %d: Error decoding frame: %s\n", $., $device->error());
+ return ();
+ }
+
+ return @m;
+}
+
+sub unpack_iaplog {
+ my $line = shift;
+ my @m;
+
+ unless ($line =~ /^(?:\[\d+\] )?[RT]::? /) {
+ printf("Skipped: %s\n", $line);
+ return ();
+ }
+
+ $line =~ s/^(?:\[\d+\] )?[RT]::? //;
+ $line =~ s/\\x(..)/chr(hex($1))/ge;
+ $line =~ s/\\\\/\\/g;
+
+ @m = unpack("CCa*", $line);
+ if ($m[0] == 0x04) {
+ @m = unpack("Cna*", $line);
+ }
+
+ return @m;
+}
+
+
+$decoder = iap::decode->new();
+$device = Device::iPod->new();
+$unpacker = \&unpack_iaplog;
+
+GetOptions("hexstring" => sub {$unpacker = \&unpack_hexstring});
+
+while ($line = <>) {
+ my @m;
+
+ chomp($line);
+
+ @m = $unpacker->($line);
+ next unless (@m);
+
+ printf("Line %d: ", $.);
+ $decoder->display(@m);
+}
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 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a command with wrong checksum
+# We expect no response (Timeout)
+$m = $ipod->sendraw("\xff\x55\x02\x00\x03\x00");
+ok($m == 1, "Broken checksum sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "No response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a too short command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x00, 0x13, "");
+ok($m == 1, "Short command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined lingo
+# We expect a timeout
+$m = $ipod->sendmsg(0x1F, 0x00);
+ok($m == 1, "Undefined lingo sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "No response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x80\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0xFF)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\xFF");
+ok($m == 1, "Identify(lingo=0xFF) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "No response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+# IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0xFF)
+# We expect an ACK Bad Parameter message as response
+$m = $ipod->sendmsg(0x00, 0x0F, "\xFF");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0xFF) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\x0F", "Response payload");
+};
+
+# Empty the buffer
+$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 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\x03", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# EnterRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x05);
+ok($m == 1, "EnterRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\x05", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ExitRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x06);
+ok($m == 1, "ExitRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\x06", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodName
+# We expect a ReturniPodName packet
+$m = $ipod->sendmsg(0x00, 0x07);
+ok($m == 1, "RequestiPodName sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodName" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x08, "Response command");
+ like($p, "/^[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodSoftwareVersion
+# We expect a ReturniPodSoftwareVersion packet
+$m = $ipod->sendmsg(0x00, 0x09);
+ok($m == 1, "RequestiPodSoftwareVersion sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodSoftwareVersion" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x0A, "Response command");
+ like($p, "/^...\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodSerialNumber
+# We expect a ReturniPodSerialNumber packet
+$m = $ipod->sendmsg(0x00, 0x0B);
+ok($m == 1, "RequestiPodSerialNumber sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodSerialNumber" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x0C, "Response command");
+ like($p, "/^[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodModelNum
+# We expect a ReturniPodModelNum packet
+$m = $ipod->sendmsg(0x00, 0x0D);
+ok($m == 1, "RequestiPodModelNum sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodModelNum" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x0E, "Response command");
+ like($p, "/^....[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0x00)
+# We expect a ReturnLingoProtocolVersion packet
+$m = $ipod->sendmsg(0x00, 0x0F, "\x00");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnLingoProtocolVersion" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x10, "Response command");
+ like($p, "/^\\x00..\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying standard mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x04, "Response command");
+ is($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# EnterRemoteUIMode
+# We expect an ACK Pending packet, followed by an ACK OK packet
+$m = $ipod->sendmsg(0x00, 0x05);
+ok($m == 1, "EnterRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Pending" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ like($p, "/^\\x06\\x05/", "Response payload");
+};
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x05", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying extended mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x04, "Response command");
+ isnt($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ExitRemoteUIMode
+# We expect an ACK Pending packet, followed by an ACK OK packet
+$m = $ipod->sendmsg(0x00, 0x06);
+ok($m == 1, "ExitRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Pending" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ like($p, "/^\\x06\\x06/", "Response payload");
+};
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x06", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying standard mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x04, "Response command");
+ is($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x00, 0xFF);
+ok($m == 1, "Undefined command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x04\xFF", "Response payload");
+};
+
+# Empty the buffer
+$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 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect an ACK Bad Parameter message as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x04\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0x00)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\x00");
+ok($m == 1, "Identify(lingo=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "No response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "Response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0x02)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\x02");
+ok($m == 1, "Identify(lingo=0x02) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "No response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "Response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x02, "Response command");
+ is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0x02)
+# We expect a ReturnLingoProtocolVersion packet
+$m = $ipod->sendmsg(0x00, 0x0F, "\x02");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0x02) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnLingoProtocolVersion" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x00, "Response lingo");
+ is($c, 0x10, "Response command");
+ like($p, "/^\\x02..\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x02, 0xFF);
+ok($m == 1, "Undefined command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x04\xFF", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+ ok(!defined($l), "Response received");
+ like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a too short command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x02, 0x00, "");
+ok($m == 1, "Short command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x04\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ImageButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x02, "\x00");
+ok($m == 1, "ImageButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x07\x02", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# VideoButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x03, "\x00");
+ok($m == 1, "VideoButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x07\x03", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# AudioButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x04, "\x00");
+ok($m == 1, "AudioButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+ ok(defined($l), "Response received");
+ is($l, 0x02, "Response lingo");
+ is($c, 0x01, "Response command");
+ is($p, "\x07\x04", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();