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