rockbox/apps/iap.c

875 lines
28 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* 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 <memory.h>
#include <string.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"
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;
unsigned char serbuf[RX_BUFLEN];
static int serbuf_i = 0;
static unsigned char response[TX_BUFLEN];
static int responselen;
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);
}
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;
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]);
}
}
int iap_getc(unsigned char x)
{
static unsigned char last_x = 0;
static bool newpkt = true;
static unsigned char chksum = 0;
/* Restart if the sync word is seen */
if(x == 0x55 && last_x == 0xff/* && newpkt*/)
{
serbuf[0] = 0;
serbuf_i = 0;
chksum = 0;
newpkt = false;
}
else
{
if(serbuf_i >= RX_BUFLEN)
serbuf_i = 0;
serbuf[serbuf_i++] = x;
chksum += x;
}
last_x = x;
/* Broadcast to queue if we have a complete message */
if(serbuf_i && (serbuf_i == serbuf[0]+2))
{
serbuf_i = 0;
newpkt = true;
if(chksum == 0)
queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0);
}
return newpkt;
}
/* called by playback when the next track starts */
void iap_track_changed(void *ignored)
{
(void)ignored;
iap_changedctr = 1;
}
void iap_periodic(void)
{
if(!iap_setupflag) return;
if(!iap_pollspeed) return;
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;
}
data[4] = time_elapsed >> 24;
data[5] = time_elapsed >> 16;
data[6] = time_elapsed >> 8;
data[7] = time_elapsed;
iap_send_pkt(data, sizeof(data));
}
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));
}
void iap_handlepkt(void)
{
if(!iap_setupflag) return;
if(serbuf[0] == 0) 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 Mode 0 */
if (serbuf[1] == 0x00)
{
switch (serbuf[2])
{
case 0x24:
{
/* ipod video send this */
unsigned char data[] = {0x00, 0x25, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,0x01};
iap_send_pkt(data, sizeof(data));
break;
}
case 0x18:
{
/* ciphered authentication command */
/* Isn't used since we don't send the 0x00 0x17 command */
break;
}
case 0x15:
{
unsigned char data0[] = {0x00, 0x16, 0x00};
iap_send_pkt(data0, sizeof(data0));
unsigned char data1[] = {0x00, 0x27, 0x00};
iap_send_pkt(data1, sizeof(data1));
/* authentication ack, mandatory to enable some hardware */
unsigned char data2[] = {0x00, 0x19, 0x00};
iap_send_pkt(data2, sizeof(data2));
if (radio_present == 1)
{
/* get tuner capacities */
unsigned char data3[] = {0x07, 0x01};
iap_send_pkt(data3, sizeof(data3));
}
iap_set_remote_volume();
break;
}
case 0x13:
{
unsigned char data[] = {0x00, 0x02, 0x00, 0x13};
iap_send_pkt(data, sizeof(data));
if (serbuf[6] == 0x35)
/* FM transmitter sends this: */
/* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/
{
unsigned char data2[] = {0x00, 0x27, 0x00};
iap_send_pkt(data2, sizeof(data2));
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 (serbuf[6] |= 0x80)
radio_present = 1;
unsigned char data4[] = {0x00, 0x14};
iap_send_pkt(data4, sizeof(data4));
}
break;
}
/* Init */
case 0x0F:
{
unsigned char data[] = {0x00, 0x10, 0x00, 0x01, 0x05};
data[2] = serbuf[3];
iap_send_pkt(data, sizeof(data));
break;
}
/* get model info */
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) */
unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10,
'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00};
iap_send_pkt(data, sizeof(data));
break;
}
/* Ipod FM remote sends this: FF 55 02 00 09 F5 */
case 0x09:
{
/* ipod5G firmware version */
unsigned char data[] = {0x00, 0x0A, 0x01, 0x02, 0x01 };
iap_send_pkt(data, sizeof(data));
break;
}
/* FM transmitter sends this: */
/* FF 55 02 00 05 F9 (mode switch: AiR mode) */
case 0x05:
{
unsigned char data[] = {0x00, 0x02, 0x06,
0x05, 0x00, 0x00, 0x0B, 0xB8, 0x28};
iap_send_pkt(data, sizeof(data));
unsigned char data2[] = {0x00, 0x02, 0x00, 0x05};
iap_send_pkt(data2, sizeof(data2));
break;
}
case 0x01:
{
/* FM transmitter sends this: */
/* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */
if(serbuf[3] == 0x05)
{
sleep(HZ/3);
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(serbuf[3] == 0x02)
{
/* useful only for apple firmware */
}
break;
}
/* default response is with cmd ok packet */
default:
{
unsigned char data[] = {0x00, 0x02, 0x00, 0x00};
data[3] = serbuf[2]; /* respond with cmd */
iap_send_pkt(data, sizeof(data));
break;
}
}
}
/* Handle Mode 2 */
else if (serbuf[1] == 0x02)
{
if(serbuf[2] != 0) return;
iap_remotebtn = BUTTON_NONE;
iap_remotetick = false;
if(serbuf[0] >= 3 && serbuf[3] != 0)
{
if(serbuf[3] & 1)
iap_remotebtn |= BUTTON_RC_PLAY;
if(serbuf[3] & 2)
iap_remotebtn |= BUTTON_RC_VOL_UP;
if(serbuf[3] & 4)
iap_remotebtn |= BUTTON_RC_VOL_DOWN;
if(serbuf[3] & 8)
iap_remotebtn |= BUTTON_RC_RIGHT;
if(serbuf[3] & 16)
iap_remotebtn |= BUTTON_RC_LEFT;
}
else if(serbuf[0] >= 4 && serbuf[4] != 0)
{
if(serbuf[4] & 1) /* play */
{
if (audio_status() != AUDIO_STATUS_PLAY)
{
iap_remotebtn |= BUTTON_RC_PLAY;
iap_repeatbtn = 2;
iap_remotetick = false;
iap_changedctr = 1;
}
}
if(serbuf[4] & 2) /* pause */
{
if (audio_status() == AUDIO_STATUS_PLAY)
{
iap_remotebtn |= BUTTON_RC_PLAY;
iap_repeatbtn = 2;
iap_remotetick = false;
iap_changedctr = 1;
}
}
if((serbuf[4] & 128) && !iap_btnshuffle) /* shuffle */
{
iap_btnshuffle = true;
if(!global_settings.playlist_shuffle)
{
global_settings.playlist_shuffle = 1;
settings_save();
settings_apply(false);
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();
settings_apply(false);
if (audio_status() & AUDIO_STATUS_PLAY)
playlist_sort(NULL, true);
}
}
else
iap_btnshuffle = false;
}
else if(serbuf[0] >= 5 && serbuf[5] != 0)
{
if((serbuf[5] & 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();
settings_apply(false);
if (audio_status() & AUDIO_STATUS_PLAY)
audio_flush_and_reload_tracks();
}
else
iap_btnrepeat = false;
if(serbuf[5] & 16) /* ffwd */
{
iap_remotebtn |= BUTTON_RC_RIGHT;
}
if(serbuf[5] & 32) /* frwd */
{
iap_remotebtn |= BUTTON_RC_LEFT;
}
}
}
/* Handle Mode 3 */
else if (serbuf[1] == 0x03)
{
switch(serbuf[2])
{
/* some kind of status packet? */
case 0x01:
{
unsigned char data[] = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00};
iap_send_pkt(data, sizeof(data));
break;
}
case 0x08:
{
/* ACK */
unsigned char data[] = {0x03, 0x00, 0x00, 0x08};
iap_send_pkt(data, sizeof(data));
break;
}
case 0x0C:
{
/* request ipod volume */
if (serbuf[3] == 0x04)
{
iap_set_remote_volume();
}
break;
}
/* get volume from accessory */
case 0x0E:
if (serbuf[3] == 0x04)
global_settings.volume = (-58)+((int)serbuf[5]+1)/4;
sound_set_volume(global_settings.volume);
break;
}
}
/* Handle Mode 4 */
else if (serbuf[1] == 0x04)
{
switch (((unsigned long)serbuf[2] << 8) | serbuf[3])
{
/* Get data updated??? flag */
case 0x0009:
{
unsigned char data[] = {0x04, 0x00, 0x0A, 0x00};
data[3] = iap_updateflag ? 0 : 1;
iap_send_pkt(data, sizeof(data));
break;
}
/* Set data updated??? flag */
case 0x000B:
{
iap_updateflag = serbuf[4] ? 0 : 1;
/* respond with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x0B};
iap_send_pkt(data, sizeof(data));
break;
}
/* Get iPod size? */
case 0x0012:
{
unsigned char data[] = {0x04, 0x00, 0x13, 0x01, 0x0B};
iap_send_pkt(data, sizeof(data));
break;
}
/* Get count of given types */
case 0x0018:
{
unsigned char data[] = {0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00};
unsigned long num = 0;
switch(serbuf[4]) /* type number */
{
case 0x01: /* total number of playlists */
num = 1;
break;
case 0x05: /* total number of songs */
num = 1;
}
data[3] = num >> 24;
data[4] = num >> 16;
data[5] = num >> 8;
data[6] = num;
iap_send_pkt(data, sizeof(data));
break;
}
/* Get time and status */
case 0x001C:
{
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();
data[3] = time_total >> 24;
data[4] = time_total >> 16;
data[5] = time_total >> 8;
data[6] = time_total;
data[7] = time_elapsed >> 24;
data[8] = time_elapsed >> 16;
data[9] = time_elapsed >> 8;
data[10] = 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;
}
/* Get current pos in playlist */
case 0x001E:
{
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();
data[3] = playlist_pos >> 24;
data[4] = playlist_pos >> 16;
data[5] = playlist_pos >> 8;
data[6] = playlist_pos;
iap_send_pkt(data, sizeof(data));
break;
}
/* Get title of a song number */
case 0x0020:
/* Get artist of a song number */
case 0x0022:
/* Get album of a song number */
case 0x0024:
{
unsigned char data[70] = {0x04, 0x00, 0xFF};
struct mp3entry id3;
int fd;
size_t len;
long tracknum = (signed long)serbuf[4] << 24 |
(signed long)serbuf[5] << 16 |
(signed long)serbuf[6] << 8 | serbuf[7];
data[2] = serbuf[3] + 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(serbuf[3])
{
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;
}
/* Set polling mode */
case 0x0026:
{
iap_pollspeed = serbuf[4] ? 1 : 0;
/*responsed with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x26};
iap_send_pkt(data, sizeof(data));
break;
}
/* AiR playback control */
case 0x0029:
{
/* respond with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x29};
iap_send_pkt(data, sizeof(data));
switch(serbuf[4])
{
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;
}
break;
}
/* Get shuffle mode */
case 0x002C:
{
unsigned char data[] = {0x04, 0x00, 0x2D, 0x00};
data[3] = global_settings.playlist_shuffle ? 1 : 0;
iap_send_pkt(data, sizeof(data));
break;
}
/* Set shuffle mode */
case 0x002E:
{
if(serbuf[4] && !global_settings.playlist_shuffle)
{
global_settings.playlist_shuffle = 1;
settings_save();
settings_apply(false);
if (audio_status() & AUDIO_STATUS_PLAY)
playlist_randomise(NULL, current_tick, true);
}
else if(!serbuf[4] && global_settings.playlist_shuffle)
{
global_settings.playlist_shuffle = 0;
settings_save();
settings_apply(false);
if (audio_status() & AUDIO_STATUS_PLAY)
playlist_sort(NULL, true);
}
/* respond with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x2E};
iap_send_pkt(data, sizeof(data));
break;
}
/* Get repeat mode */
case 0x002F:
{
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;
}
/* Set repeat mode */
case 0x0031:
{
int oldmode = global_settings.repeat_mode;
if (serbuf[4] == 0)
global_settings.repeat_mode = REPEAT_OFF;
else if (serbuf[4] == 1)
global_settings.repeat_mode = REPEAT_ONE;
else if (serbuf[4] == 2)
global_settings.repeat_mode = REPEAT_ALL;
if (oldmode != global_settings.repeat_mode)
{
settings_save();
settings_apply(false);
if (audio_status() & AUDIO_STATUS_PLAY)
audio_flush_and_reload_tracks();
}
/* respond with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x31};
iap_send_pkt(data, sizeof(data));
break;
}
/* Get Max Screen Size for Picture Upload??? */
case 0x0033:
{
unsigned char data[] = {0x04, 0x00, 0x34, 0x01, 0x36, 0x00, 0xA8, 0x01};
iap_send_pkt(data, sizeof(data));
break;
}
/* Get number songs in current playlist */
case 0x0035:
{
unsigned char data[] = {0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00};
unsigned long playlist_amt = playlist_amount();
data[3] = playlist_amt >> 24;
data[4] = playlist_amt >> 16;
data[5] = playlist_amt >> 8;
data[6] = playlist_amt;
iap_send_pkt(data, sizeof(data));
break;
}
/* Jump to track number in current playlist */
case 0x0037:
{
int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
long tracknum = (signed long)serbuf[4] << 24 |
(signed long)serbuf[5] << 16 |
(signed long)serbuf[6] << 8 | serbuf[7];
audio_pause();
audio_skip(tracknum - playlist_next(0));
if (!paused)
audio_resume();
/* respond with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x00};
data[4] = serbuf[2];
data[5] = serbuf[3];
iap_send_pkt(data, sizeof(data));
break;
}
default:
{
/* default response is with cmd ok packet */
unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x00};
data[4] = serbuf[2];
data[5] = serbuf[3];
iap_send_pkt(data, sizeof(data));
break;
}
}
}
/* Handle Mode 7 */
else if (serbuf[1] == 0x07)
{
switch(serbuf[2])
{
/* tuner capabilities */
case 0x02:
{
/* do nothing */
unsigned char data[] = {0x00, 0x27, 0x00};
iap_send_pkt(data, sizeof(data));
break;
}
/* actual tuner frequency */
case 0x0A:
/* fall through */
/* tuner frequency from scan */
case 0x13:
{
rmt_tuner_freq();
break;
}
/* RDS station name 0x21 1E 00 + ASCII text*/
case 0x21:
{
rmt_tuner_rds_data();
break;
}
}
}
serbuf[0] = 0;
}
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;
}