diff -ruN asterisk-1.0.9-old/channels/Makefile asterisk-1.0.9-new/channels/Makefile --- asterisk-1.0.9-old/channels/Makefile 2005-08-22 20:42:22.000000000 +0200 +++ asterisk-1.0.9-new/channels/Makefile 2005-08-22 21:12:14.000000000 +0200 @@ -202,6 +202,13 @@ chan_h323.so: chan_h323.o h323/libchanh323.a $(CC) $(SOLINK) -o $@ $< h323/libchanh323.a $(CHANH323LIB) -L$(PWLIBDIR)/lib $(PTLIB) -L$(OPENH323DIR)/lib $(H323LIB) -L/usr/lib -lcrypto -lssl -lexpat +# +# Asterisk Bluetooth Support +# http://www.crazygreek.co.uk/content/chan_bluetooth +# +chan_bluetooth.so: chan_bluetooth.o + $(CC) $(SOLINK) -o $@ $< $(EXTRA_LDFLAGS) -lbluetooth + #chan_modem.so : chan_modem.o # $(CC) -rdynamic -shared -Xlinker -x -o $@ $< diff -ruN asterisk-1.0.9-old/channels/chan_bluetooth.c asterisk-1.0.9-new/channels/chan_bluetooth.c --- asterisk-1.0.9-old/channels/chan_bluetooth.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.9-new/channels/chan_bluetooth.c 2005-09-06 22:51:30.000000000 +0200 @@ -0,0 +1,3598 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Asterisk Bluetooth Channel + * + * Author: Theo Zourzouvillys + * + * Adaptive Linux Solutions + * + * Copyright (C) 2004 Adaptive Linux Solutions + * + * This program is free software, distributed under the terms of + * the GNU General Public License + * + * ******************* NOTE NOTE NOTE NOTE NOTE ********************* + * + * This code is not at all tested, and only been developed with a + * HBH-200 headset and a Nokia 6310i right now. + * + * Expect it to crash, dial random numbers, and steal all your money. + * + * PLEASE try any headsets and phones, and let me know the results, + * working or not, along with all debug output! + * + * ------------------------------------------------------------------ + * + * Asterisk Bluetooth Support + * + * Well, here we go - Attempt to provide Handsfree profile support in + * both AG and HF modes, AG (AudioGateway) mode support for using + * headsets, and HF (Handsfree) mode for utilising mobile/cell phones + * + * It would be nice to also provide Headset support at some time in + * the future, however, a working Handsfree profile is nice for now, + * and as far as I can see, almost all new HS devices also support HF + * + * ------------------------------------------------------------------ + * INSTRUCTIONS + * + * You need to have bluez's bluetooth stack, along with user space + * tools (>=v2.10), and running hcid and sdsp. + * + * See bluetooth.conf for configuration details. + * + * - Ensure bluetooth subsystem is up and running. 'hciconfig' + * should show interface as UP. + * + * - If you're trying to use a headset/HS, start up asterisk, and try + * to pair it as you normally would. + * + * - If you're trying to use a Phone/AG, just make sure bluetooth is + * enabled on your phone, and start up asterisk. + * + * - 'bluetooth show peers' will show all bluetooth devices states. + * + * - Send a call out by using Dial(BLT/DevName/0123456). Call a HS + * with Dial(BLT/DevName) + * + * ------------------------------------------------------------------ + * BUGS + * + * - What should happen when an AG is paired with asterisk and + * someone uses the AG dalling a number manually? My test phone + * seems to try to open an SCO link. Perhaps an extension to + * route the call to, or maybe drop the RFCOM link all together? + * + * ------------------------------------------------------------------ + * COMPATIBILITY + * + * PLEASE email with the results of ANY + * device not listed in here (working or not), or if the device is + * listed and it doesn't work! Please also email full debug output + * for any device not working correctly or generating errors in log. + * + * HandsFree Profile: + * + * HS (HeadSet): + * - Ericsson HBH-200 + * + * AG (AudioGateway): + * - Nokia 6310i + * + * ------------------------------------------------------------------ + * + * Questions, bugs, or (preferably) patches to: + * + * + * + * ------------------------------------------------------------------ + */ + +/* ---------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* --- Data types and definitions --- */ + +#ifndef HANDSFREE_AUDIO_GW_SVCLASS_ID +# define HANDSFREE_AUDIO_GW_SVCLASS_ID 0x111f +#endif +#define BLUETOOTH_FORMAT AST_FORMAT_SLINEAR +#define BLT_CHAN_NAME "BLT" +#define BLT_CONFIG_FILE "bluetooth.conf" +#define BLT_RDBUFF_MAX 1024 +#define BLT_DEFAULT_HCI_DEV 0 +#define BLT_SVN_REVISION "$Rev$" + +/* ---------------------------------- */ + +typedef enum { + BLT_ROLE_NONE = 0, // Unknown Device + BLT_ROLE_HS = 1, // Device is a Headset + BLT_ROLE_AG = 2, // Device is an Audio Gateway + BLT_ROLE_GUI = 3 // Device is used as an GUI +} blt_role_t; + +/* State when we're in HS mode */ + +typedef enum { + BLT_STATE_WANT_R = 0, + BLT_STATE_WANT_N = 1, + BLT_STATE_WANT_CMD = 2, + BLT_STATE_WANT_N2 = 3, +} blt_state_t; + +typedef enum { + BLT_STATUS_DOWN, + BLT_STATUS_CONNECTING, + BLT_STATUS_NEGOTIATING, + BLT_STATUS_READY, + BLT_STATUS_RINGING, + BLT_STATUS_IN_CALL, +} blt_status_t; + +/* ---------------------------------- */ + +/* Default config settings */ + +#define BLT_DEFAULT_CHANNEL_AG 5 +#define BLT_DEFAULT_CHANNEL_HS 6 +#define BLT_DEFAULT_CHANNEL_GUI 1 +#define BLT_DEFAULT_ROLE BLT_ROLE_HS +#define BLT_OBUF_LEN (48 * 25) + +#define BUFLEN (4800) + +/* ---------------------------------- */ + +typedef struct blt_dev blt_dev_t; + +void ag_cgmi_response(blt_dev_t * dev, char * cmd); +void ag_unknown_response(blt_dev_t * dev, char * cmd); +void ag_cgmi_valid_response(blt_dev_t * dev, char * cmd); +void ag_clip_response(blt_dev_t * dev, char * cmd); +void ag_cmer_response(blt_dev_t * dev, char * cmd); +void ag_cind_status_response(blt_dev_t * dev, char * cmd); +void ag_cind_response(blt_dev_t * dev, char * cmd); +void ag_brsf_response(blt_dev_t * dev, char * cmd); +void remove_sdp_records(void); + +void gui_easm_response(blt_dev_t * dev, char * cmd); + +int sock_err(int fd); +int parse_clip(const char * str, char *number, int number_len, char * name, int name_len, int *type); +int set_buffer(char * ring, char * data, int circular_len, int * pos, int data_len); +int get_buffer(char * dst, char * ring, int ring_size, int * head, int to_copy); +void gui_eaid_response(blt_dev_t * dev, char * cmd); + + + +struct blt_ring { + unsigned char buf[BUFLEN]; +}; +// XXX:T: Tidy this lot up. +struct blt_dev { + + blt_status_t status; /* Device Status */ + + struct ast_channel * owner; /* Channel we belong to, possibly NULL */ + blt_dev_t * dev; /* The bluetooth device channel is for */ + struct ast_frame fr; /* Recieved frame */ + + /* SCO Handler */ + int sco_pipe[2]; /* SCO alert pipe */ + int sco; /* SCO fd */ + int sco_handle; /* SCO Handle */ + int sco_mtu; /* SCO MTU */ + int sco_running; /* 1 when sCO thread should be running */ + pthread_t sco_thread; /* SCO thread */ + ast_mutex_t sco_lock; /* SCO lock */ + int sco_pos_in; /* Reader in position (drain)*/ + int sco_pos_inrcv; /* Reader in position (fill) */ + int wakeread; /* blt_read() needs to be woken */ + int sco_pos_out; /* Reader out position */ + int sco_sending; /* Sending SCO packets */ + char buf[1200]; /* Incoming data buffer */ + int bufpos; + char sco_buf_out[BUFLEN]; /* 24 chunks of 48 */ + char sco_buf_in[BUFLEN]; /* 24 chunks of 48 */ + + char dnid[1024]; /* Outgoi gncall dialed number */ + unsigned char * obuf[BLT_OBUF_LEN]; /* Outgoing data buffer */ + int obuf_len; /* Output Buffer Position */ + int obuf_wpos; /* Buffer Reader */ + + // device + int autoconnect; /* 1 for autoconnect */ + int outgoing_id; /* Outgoing connection scheduler id */ + char * name; /* Devices friendly name */ + blt_role_t role; /* Device role (HS or AG) */ + bdaddr_t bdaddr; /* remote address */ + int channel; /* remote channel */ + int rd; /* RFCOMM fd */ + int tmp_rd; /* RFCOMM fd */ + int call_cnt; /* Number of attempted calls */ + ast_mutex_t lock; /* RFCOMM socket lock */ + char rd_buff[BLT_RDBUFF_MAX]; /* RFCOMM input buffer */ + int rd_buff_pos; /* RFCOMM input buffer position */ + int ready; /* 1 When ready */ + char *context; + + /* AG mode */ + char last_ok_cmd[BLT_RDBUFF_MAX]; /* Runtime[AG]: Last AT command that was OK */ + int cind; /* Runtime[AG]: Recieved +CIND */ + int call_pos, service_pos, callsetup_pos; /* Runtime[AG]: Positions in CIND/CMER */ + int call, service, callsetup; /* Runtime[AG]: Values */ + char cid_num[AST_MAX_EXTENSION]; + char cid_name[AST_MAX_EXTENSION]; + + /* HS mode */ + blt_state_t state; /* Runtime: Device state (AG mode only) */ + int ring_timer; /* Runtime:Ring Timer */ + char last_err_cmd[BLT_RDBUFF_MAX]; /* Runtime: Last AT command that was OK */ + void (*cb)(blt_dev_t * dev, char * str); /* Runtime: Callback when in HS mode */ + + int brsf; /* Runtime: Bluetooth Retrieve Supported Features */ + int bvra; /* Runtime: Bluetooth Voice Recognised Activation */ + int gain_speaker; /* Runtime: Gain Of Speaker */ + int clip; /* Runtime: Supports CLID */ + int colp; /* Runtime: Connected Line ID */ + int elip; /* Runtime: (Ericsson) Supports CLID */ + int eolp; /* Runtime: (Ericsson) Connected Line ID */ + int ringing; /* Runtime: Device is ringing */ + + blt_dev_t * next; /* Next in linked list */ + +}; + +typedef struct blt_atcb { + + /* The command */ + char * str; + + /* DTE callbacks: */ + int (*set)(blt_dev_t * dev, const char * arg, int len); + int (*read)(blt_dev_t * dev); + int (*execute)(blt_dev_t * dev, const char * data); + int (*test)(blt_dev_t * dev); + + /* DCE callbacks: */ + int (*unsolicited)(blt_dev_t * dev, const char * value); + +} blt_atcb_t; + +/* ---------------------------------- */ + +static void rd_close(blt_dev_t * dev, int reconnect, int err); +static int send_atcmd(blt_dev_t * device, const char * fmt, ...); +static int sco_connect(blt_dev_t * dev); +static int sco_start(blt_dev_t * dev, int fd); + +/* ---------------------------------- */ + +/* RFCOMM channel we listen on*/ +static int rfcomm_channel_ag = BLT_DEFAULT_CHANNEL_AG; +static int rfcomm_channel_hs = BLT_DEFAULT_CHANNEL_HS; +static int rfcomm_channel_gui = BLT_DEFAULT_CHANNEL_GUI; + +static char* gui_default_sip_number = ""; +static char* gui_default_sip_address = ""; + +/* Address of local bluetooth interface */ +static int hcidev_id; +static bdaddr_t local_bdaddr; + +/* All the current sockets */ +AST_MUTEX_DEFINE_STATIC(iface_lock); +static blt_dev_t * iface_head; +static int ifcount = 0; + +static int sdp_record_hs = -1; +static int sdp_record_ag = -1; +static int sdp_record_gui = -1; + +/* RFCOMM listen socket */ +static int rfcomm_sock_ag = -1; +static int rfcomm_sock_hs = -1; +static int rfcomm_sock_gui = -1; + +static int sco_socket = -1; + +static int monitor_pid = -1; + +/* The socket monitoring thread */ +static pthread_t monitor_thread = AST_PTHREADT_NULL; +AST_MUTEX_DEFINE_STATIC(monitor_lock); + +/* Count how many times this module is currently in use */ +static int usecnt = 0; +AST_MUTEX_DEFINE_STATIC(usecnt_lock); + +static struct sched_context * sched = NULL; + +/* ---------------------------------- */ + +#if ASTERISK_VERSION_NUM <= 010107 +#include +#define tech_pvt pvt->pvt +#else /* CVS. FIXME: Version number */ +static struct ast_channel *blt_request(const char *type, int format, void *data, int *cause); +static int blt_hangup(struct ast_channel *c); +static int blt_answer(struct ast_channel *c); +static struct ast_frame *blt_read(struct ast_channel *chan); +static int blt_call(struct ast_channel *c, char *dest, int timeout); +static int blt_write(struct ast_channel *chan, struct ast_frame *f); +static int blt_indicate(struct ast_channel *chan, int cond); + +static const struct ast_channel_tech blt_tech = { + .type = BLT_CHAN_NAME, + .description = "Bluetooth Channel Driver", + .capabilities = BLUETOOTH_FORMAT, + .requester = blt_request, + .hangup = blt_hangup, + .answer = blt_answer, + .read = blt_read, + .call = blt_call, + .write = blt_write, + .indicate = blt_indicate, +}; +#endif +/* ---------------------------------- */ + +static const char * +role2str(blt_role_t role) +{ + switch (role) { + case BLT_ROLE_HS: + return "HS"; + case BLT_ROLE_AG: + return "AG"; + case BLT_ROLE_GUI: + return "GUI"; + case BLT_ROLE_NONE: + default: + return "??"; + } +} + +static const char * +status2str(blt_status_t status) +{ + switch (status) { + case BLT_STATUS_DOWN: + return "Down"; + case BLT_STATUS_CONNECTING: + return "Connecting"; + case BLT_STATUS_NEGOTIATING: + return "Negotiating"; + case BLT_STATUS_READY: + return "Ready"; + case BLT_STATUS_RINGING: + return "Ringing"; + case BLT_STATUS_IN_CALL: + return "InCall"; + }; + return "Unknown"; +} + +int sock_err(int fd) +{ + int ret; + int len = sizeof(ret); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len); + return ret; +} + +/* ---------------------------------- */ +int parse_clip(const char * str, char *number, int number_len, char * name, int name_len, int *type) +{ + const char *c = str; + const char *start; + int length; + char typestr[256]; + + memset(number, 0, number_len); + memset(name, 0, name_len); + *type = 0; + + number[0] = '\0'; + name[0] = '\0'; + while(*c && *c != '"') + c++; + c++; + start = c; + while(*c && *c != '"') + c++; + length = c - start < number_len ? c - start : number_len; + strncpy(number, start, length); + number[length] = '\0'; + c++; + while(*c && *c != ',') + c++; + c++; + start = c; + while(*c && *c != ',') + c++; + length = c - start < number_len ? c - start : number_len; + strncpy(typestr, start, length); + typestr[length] = '\0'; + *type = atoi(typestr); + c++; + while(*c && *c != ',') + c++; + c++; + while(*c && *c != ',') + c++; + c++; + while(*c && *c != '"') + c++; + c++; + start = c; + while(*c && *c != '"') + c++; + length = c - start < number_len ? c - start : number_len; + strncpy(name, start, length); + name[length] = '\0'; + + return(1); +} + + +static const char * +parse_cind(const char * str, char * name, int name_len) +{ + int c = 0; + + memset(name, 0, name_len); + + while (*str) { + if (*str == '(') { + if (++c == 1 && *(str+1) == '"') { + const char * start = str + 2; + int len = 0; + str += 2; + while (*str && *str != '"') { + len++; + str++; + } + if (len == 0) + return NULL; + strncpy(name, start, (len > name_len) ? name_len : len); + } + } else if (*str == ')') + c--; + else if (c == 0 && *str == ',') + return str + 1; + str++; + } + return NULL; +} + +static void +set_cind(blt_dev_t * dev, int indicator, int val) +{ + + ast_log(LOG_DEBUG, "CIND %d set to %d\n", indicator, val); + + if (indicator == dev->callsetup_pos) { + + // call progress + + dev->callsetup = val; + + switch (val) { + case 3: + // Outgoing ringing + if ((dev->owner && dev->role == BLT_ROLE_AG) || + (dev->owner && dev->role == BLT_ROLE_GUI)) + ast_queue_control(dev->owner, AST_CONTROL_RINGING); + break; + case 2: + break; + case 1: + break; + case 0: + if ((dev->owner && dev->role == BLT_ROLE_AG && dev->call == 0) || + (dev->owner && dev->role == BLT_ROLE_AG && dev->call == 0)) + ast_queue_control(dev->owner, AST_CONTROL_CONGESTION); + break; + } + + } else if (indicator == dev->service_pos) { + + // Signal + + if (val == 0) + ast_log(LOG_NOTICE, "Audio Gateway %s lost signal\n", dev->name); + else if (dev->service == 0 && val > 0) + ast_log(LOG_NOTICE, "Audio Gateway %s got signal\n", dev->name); + + dev->service = val; + + } else if (indicator == dev->call_pos) { + + // Call + + dev->call = val; + + if (dev->owner) { + if (val == 1) { + sco_start(dev, -1); + ast_queue_control(dev->owner, AST_CONTROL_ANSWER); + } else if (val == 0) + ast_queue_control(dev->owner, AST_CONTROL_HANGUP); + } + + } + + +} + +/* ---------------------------------- */ + +int +set_buffer(char * ring, char * data, int circular_len, int * pos, int data_len) +{ + int start_pos = *(pos); + int done = 0; + int copy; + + while (data_len) { + // Set can_do to the most we can do in this copy. + + copy = MIN(circular_len - start_pos, data_len); + memcpy(ring + start_pos, data + done, copy); + done += copy; + start_pos += copy; + data_len -= copy; + + if (start_pos == circular_len) { + start_pos = 0; + } + } + *(pos) = start_pos; + return 0; +} + +int +get_buffer(char * dst, char * ring, int ring_size, int * head, int to_copy) +{ + int copy; + + // |1|2|3|4|5|6|7|8|9| + // |-----| + + while (to_copy) { + + // Set can_do to the most we can do in this copy. + copy = MIN(ring_size - *head, to_copy); + + // ast_log(LOG_DEBUG, "Getting: %d bytes, From pos %d\n", copy, *head); +#if __BYTE_ORDER == __LITTLE_ENDIAN + memcpy(dst, ring + *head, copy); +#else + // memcpy(dst, ring + *head, copy); + ast_swapcopy_samples(dst, ring+*head, copy/2); +#endif + memset(ring+*head, 0, copy); + dst += copy; + *head += copy; + to_copy -= copy; + + if (*head == ring_size ) { + *head = 0; + } + + } + + return 0; +} + +/* Handle SCO audio sync. + * + * If we are the MASTER, then we control the timing, + * in 48 byte chunks. If we're the SLAVE, we send + * as and when we recieve a packet. + * + * Because of packet/timing nessecity, we + * start up a thread when we're passing audio, so + * that things are timed exactly right. + * + * sco_thread() is the function that handles it. + * + */ + +static void * +sco_thread(void * data) +{ + blt_dev_t * dev = (blt_dev_t*)data; + int res; + struct pollfd pfd[2]; + int in_pos = 0; + int out_pos = 0; + char c = 1; + int sending; + char buf[1024]; + int len; + + // Avoid deadlock in odd circumstances + + ast_log(LOG_WARNING, "SCO thread started on fd %d, pid %d\n", dev->sco, getpid()); + + if (fcntl(dev->sco_pipe[1], F_SETFL, O_RDWR|O_NONBLOCK)) { + ast_log(LOG_WARNING, "fcntl failed on sco_pipe\n"); + } + + // dev->status = BLT_STATUS_IN_CALL; + // ast_queue_control(dev->owner, AST_CONTROL_ANSWER); + // Set buffer to silence, just incase. + + ast_mutex_lock(&(dev->sco_lock)); + + memset(dev->sco_buf_in, 0, BUFLEN); + memset(dev->sco_buf_out, 0, BUFLEN); + + dev->sco_pos_in = 0; + dev->sco_pos_out = 0; + dev->sco_pos_inrcv = 0; + dev->wakeread = 1; + + ast_mutex_unlock(&(dev->sco_lock)); + + while (1) { + + ast_mutex_lock(&(dev->sco_lock)); + + if (dev->sco_running != 1) { + ast_log(LOG_DEBUG, "SCO stopped.\n"); + break; + } + + pfd[0].fd = dev->sco; + pfd[0].events = POLLIN; + + pfd[1].fd = dev->sco_pipe[1]; + pfd[1].events = POLLIN; + + ast_mutex_unlock(&(dev->sco_lock)); + + res = poll(pfd, 2, 50); + + if (res == -1 && errno != EINTR) { + ast_log(LOG_DEBUG, "SCO poll() error\n"); + break; + } + + if (res == 0) + continue; + + + if (pfd[0].revents & POLLIN) { + + len = read(dev->sco, buf, 48); + + if (len) { + ast_mutex_lock(&(dev->lock)); + + if (dev->owner && dev->owner->_state == AST_STATE_UP) { + ast_mutex_lock(&(dev->sco_lock)); + set_buffer(dev->sco_buf_in, buf, BUFLEN, &in_pos, len); + dev->sco_pos_inrcv = in_pos; + + get_buffer(buf, dev->sco_buf_out, BUFLEN, &out_pos, len); + if (write(dev->sco, buf, len) != len) + ast_log(LOG_WARNING, "Wrote <48 to sco\n"); + + if (dev->wakeread) { + /* blt_read has caught up. Kick it */ + dev->wakeread = 0; + if(write(dev->sco_pipe[1], &c, 1) != 1) + ast_log(LOG_WARNING, "write to kick sco_pipe failed\n"); + } + ast_mutex_unlock(&(dev->sco_lock)); + } + ast_mutex_unlock(&(dev->lock)); + } + + } else if (pfd[0].revents) { + + int e = sock_err(pfd[0].fd); + ast_log(LOG_ERROR, "SCO connection error: %s (errno %d)\n", strerror(e), e); + break; + + } else if (pfd[1].revents & POLLIN) { + + int len; + + len = read(pfd[1].fd, &c, 1); + sending = (sending) ? 0 : 1; + + ast_mutex_unlock(&(dev->sco_lock)); + + } else if (pfd[1].revents) { + + int e = sock_err(pfd[1].fd); + ast_log(LOG_ERROR, "SCO pipe connection event %d on pipe[1]=%d: %s (errno %d)\n", pfd[1].revents, pfd[1].fd, strerror(e), e); + break; + + } else { + ast_log(LOG_NOTICE, "Unhandled poll output\n"); + ast_mutex_unlock(&(dev->sco_lock)); + } + + } + + ast_mutex_lock(&(dev->lock)); + close(dev->sco); + dev->sco = -1; + dev->sco_running = -1; + + memset(dev->sco_buf_in, 0, BUFLEN); + memset(dev->sco_buf_out, 0, BUFLEN); + + dev->sco_pos_in = 0; + dev->sco_pos_out = 0; + dev->sco_pos_inrcv = 0; + + ast_mutex_unlock(&(dev->sco_lock)); + if (dev->owner) + ast_queue_control(dev->owner, AST_CONTROL_HANGUP); + ast_mutex_unlock(&(dev->lock)); + ast_log(LOG_DEBUG, "SCO thread stopped\n"); + return NULL; +} + +/* Start SCO thread. Must be called with dev->lock */ + +static int +sco_start(blt_dev_t * dev, int fd) +{ + + if (dev->sco_pipe[1] <= 0) { + ast_log(LOG_ERROR, "SCO pipe[1] == %d\n", dev->sco_pipe[1]); + return -1; + } + + ast_mutex_lock(&(dev->sco_lock)); + + if (dev->sco_running != -1) { + ast_log(LOG_ERROR, "Tried to start SCO thread while already running\n"); + ast_mutex_unlock(&(dev->sco_lock)); + return -1; + } + + if (dev->sco == -1) { + if (fd > 0) { + dev->sco = fd; + } else if (sco_connect(dev) != 0) { + ast_log(LOG_ERROR, "SCO fd invalid\n"); + ast_mutex_unlock(&(dev->sco_lock)); + return -1; + } + } + + dev->sco_running = 1; + + if (ast_pthread_create(&(dev->sco_thread), NULL, sco_thread, dev) < 0) { + ast_log(LOG_ERROR, "Unable to start SCO thread.\n"); + dev->sco_running = -1; + ast_mutex_unlock(&(dev->sco_lock)); + return -1; + } + + ast_mutex_unlock(&(dev->sco_lock)); + + return 0; +} + +/* Stop SCO thread. Must be called with dev->lock */ + +static int +sco_stop(blt_dev_t * dev) +{ + ast_mutex_lock(&(dev->sco_lock)); + if (dev->sco_running == 1) + dev->sco_running = 0; + else + dev->sco_running = -1; + dev->sco_sending = 0; + ast_mutex_unlock(&(dev->sco_lock)); + return 0; +} + +/* ---------------------------------- */ + +/* Answer the call. Call with lock held on device */ + +static int +answer(blt_dev_t * dev) +{ + + if ( (!dev->owner) || (dev->ready != 1) || (dev->status != BLT_STATUS_READY && dev->status != BLT_STATUS_RINGING)) { + ast_log(LOG_ERROR, "Attempt to answer() in invalid state (owner=%p, ready=%d, status=%s)\n", + dev->owner, dev->ready, status2str(dev->status)); + return -1; + } + + // dev->sd = sco_connect(&local_bdaddr, &(dev->bdaddr), NULL, NULL, 0); + // dev->status = BLT_STATUS_IN_CALL; + // dev->owner->fds[0] = dev->sd; + // if we are answering (hitting button): + ast_queue_control(dev->owner, AST_CONTROL_ANSWER); + // if asterisk signals us to answer: + // ast_setstate(ast, AST_STATE_UP); + + /* Start SCO link */ + sco_start(dev, -1); + return 0; +} + +/* ---------------------------------- */ + +static int +blt_write(struct ast_channel * ast, struct ast_frame * frame) +{ + blt_dev_t * dev = ast->tech_pvt; + + /* Write a frame of (presumably voice) data */ + + if (frame->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype); + return 0; + } + + if (!(frame->subclass & BLUETOOTH_FORMAT)) { + static int fish = 5; + if (fish) { + ast_log(LOG_WARNING, "Cannot handle frames in format %d\n", frame->subclass); + fish--; + } + return 0; + } + + if (ast->_state != AST_STATE_UP) { + return 0; + } + + ast_mutex_lock(&(dev->sco_lock)); + set_buffer(dev->sco_buf_out, frame->data, BUFLEN, &(dev->sco_pos_out), MIN(frame->datalen, BUFLEN)); + ast_mutex_unlock(&(dev->sco_lock)); + + return 0; + +} + +static struct ast_frame * +blt_read(struct ast_channel * ast) +{ + blt_dev_t * dev = ast->tech_pvt; + char c = 1; + int len; + static int fish = 0; + /* Some nice norms */ + + dev->fr.datalen = 0; + dev->fr.samples = 0; + dev->fr.data = NULL; + dev->fr.src = BLT_CHAN_NAME; + dev->fr.offset = 0; + dev->fr.mallocd = AST_MALLOCD_DATA; + dev->fr.delivery.tv_sec = 0; + dev->fr.delivery.tv_usec = 0; + read(dev->sco_pipe[0], &c, 1); + ast_mutex_lock(&(dev->sco_lock)); + dev->sco_sending = 1; + + if (dev->sco_pos_inrcv < dev->sco_pos_in) { + /* Buffer wrapped. Read only till the end */ + len = BUFLEN - dev->sco_pos_in + dev->sco_pos_inrcv; + } else { + len = dev->sco_pos_inrcv - dev->sco_pos_in; + } + dev->fr.data = malloc(AST_FRIENDLY_OFFSET+len) + AST_FRIENDLY_OFFSET; + + get_buffer(dev->fr.data, dev->sco_buf_in, BUFLEN, &(dev->sco_pos_in), len); + dev->wakeread = 1; + ast_mutex_unlock(&(dev->sco_lock)); + if (fish) { + unsigned char *x = dev->fr.data; + ast_log(LOG_WARNING, "blt_read %d: %02x %02x %02x %02x %02x %02x\n", + dev->fr.datalen, x[0], x[1], x[2], x[3], x[4], x[5]); + fish--; + } + + dev->fr.samples = len / 2; + dev->fr.datalen = len; + dev->fr.frametype = AST_FRAME_VOICE; + dev->fr.subclass = BLUETOOTH_FORMAT; + dev->fr.offset = AST_FRIENDLY_OFFSET; + return &dev->fr; +} + +/* Escape Any '"' in str. Return malloc()ed string */ +static char * +escape_str(char * str) +{ + char * ptr = str; + char * pret; + char * ret; + int len = 0; + + while (*ptr) { + if (*ptr == '"') + len++; + len++; + ptr++; + } + + ret = malloc(len + 1); + pret = memset(ret, 0, len + 1); + + ptr = str; + + while (*ptr) { + if (*ptr == '"') + *pret++ = '\\'; + *pret++ = *ptr++; + } + + return ret; +} + +static int +ring_hs(blt_dev_t * dev) +{ +#if (ASTERISK_VERSION_NUM < 010100) + char tmp[AST_MAX_EXTENSION]; + char *name, *num; +#endif + + ast_mutex_lock(&(dev->lock)); + + if (dev->owner == NULL) { + ast_mutex_unlock(&(dev->lock)); + return 0; + } + + dev->ringing = 1; + dev->status = BLT_STATUS_RINGING; + + send_atcmd(dev, "RING"); + + dev->owner->rings++; + + // XXX:T: '"' needs to be escaped in ELIP. + +#if (ASTERISK_VERSION_NUM < 010100) + + if (dev->owner->callerid) { + + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, dev->owner->callerid, sizeof(tmp)-1); + + if (!ast_callerid_parse(tmp, &name, &num)) { + + if (dev->clip && num) + send_atcmd(dev, "+CLIP: \"%s\",129", num); + + if (dev->elip && name) { + char * esc = escape_str(name); + send_atcmd(dev, "*ELIP: \"%s\"", esc); + free(esc); + } + } + } + + +#else + + if (dev->clip && dev->owner->cid.cid_num) + send_atcmd(dev, "+CLIP: \"%s\",129", dev->owner->cid.cid_num); + + if (dev->elip && dev->owner->cid.cid_name) { + char * esc = escape_str(dev->owner->cid.cid_name); + send_atcmd(dev, "*ELIP: \"%s\"", esc); + free(esc); + } + +#endif + + ast_mutex_unlock(&(dev->lock)); + + return 1; +} + +/* + * If the HS is already connected, then just send RING, otherwise, things get a + * little more sticky. We first have to find the channel for HS using SDP, + * then initiate the connection. Once we've done that, we can start the call. + */ + +static int +blt_call(struct ast_channel * ast, char * dest, int timeout) +{ + blt_dev_t * dev = ast->tech_pvt; + + if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) { + ast_log(LOG_WARNING, "blt_call called on %s, neither down nor reserved\n", ast->name); + return -1; + } + + ast_log(LOG_DEBUG, "Calling %s on %s [t: %d]\n", dest, ast->name, timeout); + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); + return -1; + } + +// ast_mutex_lock(&(dev->lock)); + + if (dev->ready == 0) { + ast_log(LOG_WARNING, "Tried to call a device not ready/connected.\n"); + ast_setstate(ast, AST_CONTROL_CONGESTION); +// ast_mutex_unlock(&(dev->lock)); + ast_mutex_unlock(&iface_lock); + return 0; + } + + if (dev->role == BLT_ROLE_HS) { + + send_atcmd(dev, "+CIEV: 3,1"); + + dev->ring_timer = ast_sched_add(sched, 5000, AST_SCHED_CB(ring_hs), dev); + + ring_hs(dev); + + ast_setstate(ast, AST_STATE_RINGING); + ast_queue_control(ast, AST_CONTROL_RINGING); + + } else if (dev->role == BLT_ROLE_AG) { + + send_atcmd(dev, "ATD%s;", dev->dnid); +// it does not seem like we should start the audio until the call is connected +// sco_start(dev, -1); + } else if (dev->role == BLT_ROLE_GUI) { + + send_atcmd(dev, "ATD%s;", dev->dnid); + + } else { + + ast_setstate(ast, AST_CONTROL_CONGESTION); + ast_log(LOG_ERROR, "Unknown device role\n"); + + } + +// ast_mutex_unlock(&(dev->lock)); + ast_mutex_unlock(&iface_lock); + + return 0; +} + +static int +blt_hangup(struct ast_channel * ast) +{ + blt_dev_t * dev = ast->tech_pvt; + + ast_log(LOG_DEBUG, "blt_hangup(%s)\n", ast->name); + + if (!ast->tech_pvt) { + ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); + return 0; + } + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get iface_lock\n"); + return 0; + } + + ast_mutex_lock(&(dev->lock)); + + sco_stop(dev); + dev->sco_sending = 0; + + if (dev->role == BLT_ROLE_HS) { + + if (dev->ringing == 0) { + // Actual call in progress + send_atcmd(dev, "+CIEV: 2,0"); + } else { + + // Just ringing still + + if (dev->role == BLT_ROLE_HS) + send_atcmd(dev, "+CIEV: 3,0"); + + if (dev->ring_timer >= 0) + ast_sched_del(sched, dev->ring_timer); + + dev->ring_timer = -1; + dev->ringing = 0; + + } + + } else if (dev->role == BLT_ROLE_AG) { + + // Cancel call. + send_atcmd(dev, "ATH"); + send_atcmd(dev, "AT+CHUP"); + + } + + if (dev->status == BLT_STATUS_IN_CALL || dev->status == BLT_STATUS_RINGING) + dev->status = BLT_STATUS_READY; + + ast->tech_pvt = NULL; + dev->owner = NULL; + ast_mutex_unlock(&(dev->lock)); + ast_setstate(ast, AST_STATE_DOWN); + ast_mutex_unlock(&(iface_lock)); + + return 0; +} + +static int +blt_indicate(struct ast_channel * c, int condition) +{ + ast_log(LOG_DEBUG, "blt_indicate (%d)\n", condition); + + switch(condition) { + case AST_CONTROL_RINGING: + return -1; + default: + ast_log(LOG_WARNING, "Don't know how to condition %d\n", condition); + break; + } + return -1; +} + +static int +blt_answer(struct ast_channel * ast) +{ + blt_dev_t * dev = ast->tech_pvt; + + ast_mutex_lock(&dev->lock); + + // if (dev->ring_timer >= 0) + // ast_sched_del(sched, dev->ring_timer); + // dev->ring_timer = -1; + + ast_log(LOG_DEBUG, "Answering interface\n"); + + if (ast->_state != AST_STATE_UP) { + send_atcmd(dev, "+CIEV: 2,1"); + send_atcmd(dev, "+CIEV: 3,0"); + sco_start(dev, -1); + ast_setstate(ast, AST_STATE_UP); + } + + ast_mutex_unlock(&dev->lock); + + return 0; +} + +static struct ast_channel * +blt_new(blt_dev_t * dev, int state, const char * context, const char * number) +{ + struct ast_channel * ast; + char c = 0; + + if ((ast = ast_channel_alloc(1)) == NULL) { + ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); + return NULL; + } + + snprintf(ast->name, sizeof(ast->name), "BLT/%s", dev->name); + + // ast->fds[0] = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + + ast->nativeformats = BLUETOOTH_FORMAT; + //ast->rawreadformat = BLUETOOTH_FORMAT; + //ast->rawwriteformat = BLUETOOTH_FORMAT; + ast->writeformat = BLUETOOTH_FORMAT; + ast->readformat = BLUETOOTH_FORMAT; + + ast_setstate(ast, state); + + ast->type = BLT_CHAN_NAME; + + ast->tech_pvt = dev; +#if ASTERISK_VERSION_NUM > 010107 + ast->tech = &blt_tech; +#else + ast->pvt->call = blt_call; + ast->pvt->indicate = blt_indicate; + ast->pvt->hangup = blt_hangup; + ast->pvt->read = blt_read; + ast->pvt->write = blt_write; + ast->pvt->answer = blt_answer; +#endif + strncpy(ast->context, context, sizeof(ast->context)-1); + strncpy(ast->exten, number, sizeof(ast->exten) - 1); + if(0 == strcmp(number, "s")) + { + //ast_set_callerid(ast, dev->cid_num, dev->cid_name, dev->cid_num); + } + + ast->language[0] = '\0'; + + ast->fds[0] = dev->sco_pipe[0]; + write(dev->sco_pipe[1], &c, 1); + + dev->owner = ast; + + ast_mutex_lock(&usecnt_lock); + usecnt++; + ast_mutex_unlock(&usecnt_lock); + + ast_update_use_count(); + + if (state != AST_STATE_DOWN) { + if (ast_pbx_start(ast)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast->name); + ast_hangup(ast); + } + } + + return ast; +} + +static struct ast_channel * +#if (ASTERISK_VERSION_NUM < 010100) +blt_request(char * type, int format, void * local_data) +#elif (ASTERISK_VERSION_NUM <= 010107) +blt_request(const char * type, int format, void * local_data) +#else +blt_request(const char * type, int format, void * local_data, int *cause) +#endif +{ + char * data = (char*)local_data; + int oldformat; + blt_dev_t * dev = NULL; + struct ast_channel * ast = NULL; + char * number = data, * dname; + + dname = strsep(&number, "/"); + + oldformat = format; + + format &= BLUETOOTH_FORMAT; + + if (!format) { + ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat); + return NULL; + } + + ast_log(LOG_DEBUG, "Dialing '%s' via '%s'\n", number, dname); + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Unable to lock iface_list\n"); + return NULL; + } + + dev = iface_head; + + while (dev) { + if (strcmp(dev->name, dname) == 0) { + ast_mutex_lock(&(dev->lock)); + if (!dev->ready) { + ast_log(LOG_ERROR, "Device %s is not connected\n", dev->name); + ast_mutex_unlock(&(dev->lock)); + ast_mutex_unlock(&iface_lock); + return NULL; + } + break; + } + dev = dev->next; + } + + ast_mutex_unlock(&iface_lock); + + if (!dev) { + ast_log(LOG_WARNING, "Failed to find device named '%s'\n", dname); + return NULL; + } + + if (number && dev->role != BLT_ROLE_AG) { + ast_log(LOG_WARNING, "Tried to send a call out on non AG\n"); + ast_mutex_unlock(&(dev->lock)); + return NULL; + } + + if (dev->role == BLT_ROLE_AG) + strncpy(dev->dnid, number, sizeof(dev->dnid) - 1); + + ast = blt_new(dev, AST_STATE_DOWN, dev->context, "s"); + + ast_mutex_unlock(&(dev->lock)); + + return ast; +} + +/* ---------------------------------- */ + + +/* ---- AT COMMAND SOCKET STUFF ---- */ + +static int +send_atcmd(blt_dev_t * dev, const char * fmt, ...) +{ + char buf[1024]; + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, 1023, fmt, ap); + va_end(ap); + + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < %s\n", role2str(dev->role), 10, dev->name, buf); + + write(dev->rd, "\r\n", 2); + len = write(dev->rd, buf, len); + write(dev->rd, "\r\n", 2); + return (len) ? 0 : -1; +} + + +static int +send_atcmd_ok(blt_dev_t * dev, const char * cmd) +{ + int len; + strncpy(dev->last_ok_cmd, cmd, BLT_RDBUFF_MAX - 1); + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < OK\n", role2str(dev->role), 10, dev->name); + len = write(dev->rd, "\r\nOK\r\n", 6); + return (len) ? 0 : -1; +} + +static int +send_atcmd_error(blt_dev_t * dev) +{ + int len; + + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < ERROR\n", role2str(dev->role), 10, dev->name); + +// write(dev->rd, "\r\n", 2); +// len = write(dev->rd, dev->last_ok_cmd, 5); + write(dev->rd, "\r\n", 2); + len = write(dev->rd, "ERROR", 5); + write(dev->rd, "\r\n", 2); + + return (len) ? 0 : -1; +} + + +/* ---------------------------------- */ + +/* -- Handle negotiation when we're an AG -- */ + +/* Bluetooth Support */ + +static int +atcmd_brsf_set(blt_dev_t * dev, const char * arg, int len) +{ + ast_log(LOG_DEBUG, "Device Supports: %s\n", arg); + dev->brsf = atoi(arg); + send_atcmd(dev, "+BRSF: %d", 23); + return 0; +} + +/* Bluetooth Voice Recognition */ + +static int +atcmd_bvra_set(blt_dev_t * dev, const char * arg, int len) +{ + ast_log(LOG_WARNING, "+BVRA Not Yet Supported\n"); + return -1; +#if 0 + // XXX:T: Fix voice recognition somehow! + int action = atoi(arg); + ast_log(LOG_DEBUG, "Voice Recognition: %s\n", (a) ? "ACTIVATED" : "DEACTIVATED"); + if ((action == 0) & (dev->bvra == 1)) { + /* Disable it */ + dev->bvra = 0; + // XXX:T: Shutdown any active bvra channel + ast_log(LOG_DEBUG, "Voice Recognition: DISABLED\n"); + } else if ((action == 1) && (dev->bvra == 0)) { + /* Enable it */ + dev->bvra = 1; + // XXX:T: Schedule connection to voice recognition extension/application + ast_log(LOG_DEBUG, "Voice Recognition: ENABLED\n"); + } else { + ast_log(LOG_ERROR, "+BVRA out of sync (we think %d, but HS wants %d)\n", dev->bvra, action); + return -1; + } + return 0; +#endif +} + +/* Clock */ + +static int +atcmd_cclk_read(blt_dev_t * dev) +{ + struct tm t, *tp; + const time_t ti = time(0); + tp = localtime_r(&ti, &t); + send_atcmd(dev, "+CCLK: \"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"", + (tp->tm_year % 100), (tp->tm_mon + 1), (tp->tm_mday), + tp->tm_hour, tp->tm_min, tp->tm_sec, ((tp->tm_gmtoff / 60) / 15)); + return 0; +} + +/* CHUP - Hangup Call */ + +static int +atcmd_chup_execute(blt_dev_t * dev, const char * data) +{ + if (!dev->owner) { + ast_log(LOG_ERROR, "Request to hangup call when none in progress\n"); + return -1; + } + ast_log(LOG_DEBUG, "Hangup Call\n"); + ast_queue_control(dev->owner, AST_CONTROL_HANGUP); + return 0; +} + +/* CIND - Call Indicator */ + +static int +atcmd_cind_read(blt_dev_t * dev) +{ + send_atcmd(dev, "+CIND: 1,0,0"); + return 0; +} + +static int +atcmd_cind_test(blt_dev_t * dev) +{ + send_atcmd(dev, "+CIND: (\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-4))"); + return 0; +} + +/* Set Language */ + +static int +atcmd_clan_read(blt_dev_t * dev) +{ + send_atcmd(dev, "+CLAN: \"en\""); + return 0; +} + +/* Caller Id Presentation */ + +static int +atcmd_clip_set(blt_dev_t * dev, const char * arg, int len) +{ + dev->clip = atoi(arg); + return 0; +} + +/* Connected Line Identification Presentation */ + +static int +atcmd_colp_set(blt_dev_t * dev, const char * arg, int len) +{ + dev->colp = atoi(arg); + return 0; +} + +/* CMER - Mobile Equipment Event Reporting */ + +static int +atcmd_cmer_set(blt_dev_t * dev, const char * arg, int len) +{ + dev->ready = 1; + dev->status = BLT_STATUS_READY; + return 0; +} + +/* PhoneBook Types: + * + * - FD - SIM Fixed Dialing Phone Book + * - ME - ME Phone book + * - SM - SIM Phone Book + * - DC - ME dialled-calls list + * - RC - ME recieved-calls lisr + * - MC - ME missed-calls list + * - MV - ME Voice Activated Dialing List + * - HP - Hierachial Phone Book + * - BC - Own Business Card (PIN2 required) + * + */ + +/* Read Phone Book Entry */ + +static int +atcmd_cpbr_set(blt_dev_t * dev, const char * arg, int len) +{ + // XXX:T: Fix the phone book! + // * Maybe add res_phonebook or something? */ + send_atcmd(dev, "+CPBR: %d,\"%s\",128,\"%s\"", atoi(arg), arg, arg); + return 0; +} + +/* Select Phone Book */ + +static int +atcmd_cpbs_set(blt_dev_t * dev, const char * arg, int len) +{ + // XXX:T: I guess we'll just accept any? + return 0; +} + +static int +atcmd_cscs_set(blt_dev_t * dev, const char * arg, int len) +{ + // XXX:T: Language + return 0; +} + +static int +atcmd_eips_set(blt_dev_t * dev, const char * arg, int len) +{ + ast_log(LOG_DEBUG, "Identify Presentation Set: %s=%s\n", + (*(arg) == 49) ? "ELIP" : "EOLP", + (*(arg+2) == 49) ? "ON" : "OFF"); + + if (*(arg) == 49) + dev->eolp = (*(arg+2) == 49) ? 1 : 0; + else + dev->elip = (*(arg+2) == 49) ? 1 : 0; + + return 0; +} + +/* VGS - Speaker Volume Gain */ + +static int +atcmd_vgs_set(blt_dev_t * dev, const char * arg, int len) +{ + dev->gain_speaker = atoi(arg); + return 0; +} + +void +gui_eaid_response(blt_dev_t * dev, char * cmd) +{ + ast_log(LOG_NOTICE, "Submenu displayed.\n"); +} + +static int +atcmd_eami_execute(blt_dev_t * dev, const char * data) +{ + char * number = NULL; + + number = strndup(data, strlen(data)); + int menuitem = atoi(number); + + ast_log(LOG_NOTICE, "Menu Item '%d'.\n", menuitem); + + dev->cb = gui_eaid_response; + + if (menuitem == 1) { + char command[1024] = ""; + const char* c1 = "AT*EAID=8,1,\"Make a SIP call\",\"Number\",\""; + const char* c2 = "\""; + + (void)strncat(command, c1, sizeof(command) - strlen(command) - 1); + (void)strncat(command, gui_default_sip_number, sizeof(command) - strlen(command) - 1); + (void)strncat(command, c2, sizeof(command) - strlen(command) - 1); + + //strcat(command, "AT*EAID=8,1,\"Make a SIP call\",\"Number\",\""); + //strcat(command, gui_default_sip_number); + //strcat(command, "\""); + send_atcmd(dev, command); + } else if (menuitem == 2) { + char command[1024] = ""; + const char* c1 = "AT*EAID=11,1,\"Make a SIP call\",\"SIP Address\",100,\""; + const char* c2 = "\""; + + (void)strncat(command, c1, sizeof(command) - strlen(command) - 1); + (void)strncat(command, gui_default_sip_address, sizeof(command) - strlen(command) - 1); + (void)strncat(command, c2, sizeof(command) - strlen(command) - 1); + + //strcat(command, "AT*EAID=11,1,\"Make a SIP call\",\"SIP Address\",100,\""); + //strcat(command, gui_default_sip_address); + //strcat(command, "\""); + send_atcmd(dev, command); + } else if (menuitem == 0) { + dev->cb = gui_easm_response; +// send_atcmd(dev,"AT*EASM=\"SIP Menu\",1,1,3,\"Call Number\",\"Call Address\",\"More Options\",1"); + send_atcmd(dev,"AT*EASM=\"SIP Menu\",1,1,2,\"Call Number\",\"Call Address\",1"); + } else { + ast_log(LOG_ERROR, "Menu item not implementented.\n"); + } + return 0; +} + +static int +atcmd_eaii_execute(blt_dev_t * dev, const char * data) +{ + int pos = 1, len = 0; + char type[128]; + char val[128]; + const char * start = data; + struct sockaddr_in addr; + + while (*data) { + if (*data == ',') { + memset(type, 0, 128); + strncpy(type, start, len); + + ast_log(LOG_NOTICE, "Number(8)/Address(11): '%s'.\n", type); + + pos++; + len = 0; + data++; + start = data; + continue; + } + len++; + data++; + } + + memset(val, 0, 128); + strncpy(val, start, len); + + char del[]= "\""; + char* address; + address = strtok(val, del); + int type_int = atoi(type); + + if (strcmp(address, " 0") == 0) { + ast_log(LOG_NOTICE, "Spurious EAII:\n"); + ast_log(LOG_NOTICE, data); + return 0; + } + + if (type_int == 8) { + (void)strncat(address, "@sipgate.de", sizeof(address) - strlen(address) - 1); + } + + ast_log(LOG_NOTICE, "SIP number/address: '%i','%s'.\n", type_int, address); + + if (type_int == 8 || type_int == 11) { + + char messagebox[1024] = ""; + const char* mb1 = "AT*EAID=1,1,\"Setting up SIP call to "; + const char* mb2 = "\",30"; + + (void)strncat(messagebox, mb1, sizeof(messagebox) - strlen(messagebox) - 1); + (void)strncat(messagebox, address, sizeof(messagebox) - strlen(messagebox) - 1); + (void)strncat(messagebox, mb2, sizeof(messagebox) - strlen(messagebox) - 1); + + //strcat(messagebox, "AT*EAID=1,1,\"Setting up SIP call to "); + //strcat(messagebox, address); + //strcat(messagebox, "\",30"); + send_atcmd(dev, messagebox); + + send_atcmd(dev, "AT*ESKS=2"); + send_atcmd(dev, "AT*EKSP"); + send_atcmd(dev, "AT*ESKS=0"); + + //Create manager connection to create call + int s = socket(AF_INET,SOCK_STREAM,0); + if (s < 0) { + ast_log(LOG_ERROR, "Manager connection failed."); + + dev->cb = ag_cgmi_response; + send_atcmd(dev, "AT*EAID=1,1,\"Call failed\""); + return -1; + } + addr.sin_family = AF_INET; + addr.sin_port = htons(5038); + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + memset(&(addr.sin_zero), '\0', 8); + + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ast_log(LOG_ERROR, "Manager connection failed. (2)"); + dev->cb = ag_cgmi_response; + send_atcmd(dev, "AT*EAID=1,1,\"Call failed\""); + return -1; + } + char* command = "Action: login\r\nUsername: markus\r\nSecret: supAEr\r\n\r\n"; + if (write(s,command,strlen(command)) < 0) { + ast_log(LOG_ERROR, "Manager connection failed. (3)"); + dev->cb = ag_cgmi_response; + send_atcmd(dev, "AT*EAID=1,1,\"Call failed\""); + return -1; + } + + char command3[1024] = ""; + const char* action = "Action: Originate\r\nChannel: SIP/"; + const char* action2 = "\r\nExten: 1235\r\nPriority: 1\r\nContext: sipgate.de\r\n\r\nAction: logoff\r\n\r\n"; + + (void)strncat(command3, action, sizeof(command3) - strlen(command3) - 1); + (void)strncat(command3, address, sizeof(command3) - strlen(command3) - 1); + (void)strncat(command3, action2, sizeof(command3) - strlen(command3) - 1); + + //strcat(command3, "Action: Originate\r\nChannel: SIP/"); + //strcat(command3, address); + //strcat(command3, "\r\nExten: 1235\r\nPriority: 1\r\nContext: sipgate.de\r\n\r\n"); + ast_log(LOG_NOTICE, command3); + + if (write(s,command3,strlen(command3)) < 0) { + ast_log(LOG_ERROR, "Manager connection failed. (5)"); + return -1; + } + } + //dev->cb = ag_cgmi_response; + return 0; +} + +/* Dial */ +static int +atcmd_dial_execute(blt_dev_t * dev, const char * data) +{ + char * number = NULL; + + /* Make sure there is a ';' at the end of the line */ + if (*(data + (strlen(data) - 1)) != ';') { + ast_log(LOG_WARNING, "Can't dial non-voice right now: %s\n", data); + return -1; + } + + number = strndup(data, strlen(data) - 1); + ast_log(LOG_NOTICE, "Dial: [%s]\n", number); + + send_atcmd(dev, "+CIEV: 2,1"); + send_atcmd(dev, "+CIEV: 3,0"); + + sco_start(dev, -1); + + if (blt_new(dev, AST_STATE_UP, dev->context, number) == NULL) { + sco_stop(dev); + } + + free(number); + + return 0; +} + +static int atcmd_bldn_execute(blt_dev_t * dev, const char *data) +{ + return atcmd_dial_execute(dev, "bldn;"); +} + +/* Answer */ + +static int +atcmd_answer_execute(blt_dev_t * dev, const char * data) +{ + + if (!dev->ringing || !dev->owner) { + ast_log(LOG_WARNING, "Can't answer non existant call\n"); + return -1; + } + + dev->ringing = 0; + + if (dev->ring_timer >= 0) + ast_sched_del(sched, dev->ring_timer); + + dev->ring_timer = -1; + + send_atcmd(dev, "+CIEV: 2,1"); + send_atcmd(dev, "+CIEV: 3,0"); + + return answer(dev); +} + +static int +ag_unsol_ciev(blt_dev_t * dev, const char * data) +{ + const char * orig = data; + int indicator; + int status; + + while (*(data) && *(data) == ' ') + data++; + + if (*(data) == 0) { + ast_log(LOG_WARNING, "Invalid value[1] for '+CIEV:%s'\n", orig); + return -1; + } + + indicator = *(data++) - 48; + + if (*(data++) != ',') { + ast_log(LOG_WARNING, "Invalid value[2] for '+CIEV:%s'\n", orig); + return -1; + } + + if (*(data) == 0) { + ast_log(LOG_WARNING, "Invalid value[3] for '+CIEV:%s'\n", orig); + return -1; + } + + status = *(data) - 48; + + set_cind(dev, indicator, status); + + return 0; +} + +static int +ag_unsol_cind(blt_dev_t * dev, const char * data) +{ + + while (*(data) && *(data) == ' ') + data++; + + + if (dev->cind == 0) + { + int pos = 1; + char name[1024]; + + while ((data = parse_cind(data, name, 1023)) != NULL) { + ast_log(LOG_DEBUG, "CIND: %d=%s\n", pos, name); + if (strcmp(name, "call") == 0) + dev->call_pos = pos; + else if (strcmp(name, "service") == 0) + dev->service_pos = pos; + else if (strcmp(name, "call_setup") == 0 || strcmp(name, "callsetup") == 0) + dev->callsetup_pos = pos; + pos++; + } + + ast_log(LOG_DEBUG, "CIND: %d=%s\n", pos, name); + + } else { + + int pos = 1, len = 0; + char val[128]; + const char * start = data; + + while (*data) { + if (*data == ',') { + memset(val, 0, 128); + strncpy(val, start, len); + set_cind(dev, pos, atoi(val)); + pos++; + len = 0; + data++; + start = data; + continue; + } + len++; + data++; + } + + memset(val, 0, 128); + strncpy(val, start, len); + ast_log(LOG_DEBUG, "CIND IND %d set to %d [%s]\n", pos, atoi(val), val); + + + } + + return 0; +} + +/* + * handle an incoming call + */ +static int +ag_unsol_clip(blt_dev_t * dev, const char * data) +{ + const char * orig = data; + char name[256]; + char number[64]; + int type; + + while (*(data) && *(data) == ' ') + data++; + + if (*(data) == 0) { + ast_log(LOG_WARNING, "Invalid value[1] for '+CLIP:%s'\n", orig); + return -1; + } + + parse_clip(data, number, sizeof(number)-1, name, sizeof(name)-1, &type); + ast_log(LOG_NOTICE, "Parsed '+CLIP: %s' number='%s' type='%d' name='%s'\n", data, number, type, name); + + blt_new(dev, AST_STATE_RING, dev->context, "s"); + + return 0; +} + + + +static blt_atcb_t +atcmd_list[] = +{ + { "A", NULL, NULL, atcmd_answer_execute, NULL, NULL }, + { "D", NULL, NULL, atcmd_dial_execute, NULL, NULL }, + { "+BRSF", atcmd_brsf_set, NULL, NULL, NULL, NULL }, + { "+BVRA", atcmd_bvra_set, NULL, NULL, NULL, NULL }, + { "+CCLK", NULL, atcmd_cclk_read, NULL, NULL, NULL }, + { "+CHUP", NULL, NULL, atcmd_chup_execute, NULL, NULL }, + { "+CIEV", NULL, NULL, NULL, NULL, ag_unsol_ciev }, + { "+CIND", NULL, atcmd_cind_read, NULL, atcmd_cind_test, ag_unsol_cind }, + { "*EAMI", NULL, NULL, atcmd_eami_execute, NULL, NULL}, + { "*EAII", NULL, NULL, atcmd_eaii_execute, NULL, NULL}, + + { "+CLAN", NULL, atcmd_clan_read, NULL, NULL, NULL }, + { "+CLIP", atcmd_clip_set, NULL, NULL, NULL, ag_unsol_clip }, + { "+COLP", atcmd_colp_set, NULL, NULL, NULL, NULL }, + { "+CMER", atcmd_cmer_set, NULL, NULL, NULL, NULL }, + { "+CPBR", atcmd_cpbr_set, NULL, NULL, NULL, NULL }, + { "+CPBS", atcmd_cpbs_set, NULL, NULL, NULL, NULL }, + { "+CSCS", atcmd_cscs_set, NULL, NULL, NULL, NULL }, + { "*EIPS", atcmd_eips_set, NULL, NULL, NULL, NULL }, + { "+VGS", atcmd_vgs_set, NULL, NULL, NULL, NULL }, + { "+BLDN", NULL, NULL, atcmd_bldn_execute, NULL, NULL }, +}; + +#define ATCMD_LIST_LEN (sizeof(atcmd_list) / sizeof(blt_atcb_t)) + +/* ---------------------------------- */ + +/* -- Handle negotiation when we're a HS -- */ + +void +ag_unknown_response(blt_dev_t * dev, char * cmd) +{ + ast_log(LOG_DEBUG, "Got UNKN response: %s\n", cmd); + + // DELAYED + // NO CARRIER + +} + +void +gui_easm_response(blt_dev_t * dev, char * cmd) +{ + ast_log(LOG_NOTICE, "Menu displayed.\n"); +} + +void +ag_cgmi_response(blt_dev_t * dev, char * cmd) +{ + // CGMM - Phone Model + // CGMR - Phone Revision + // CGSN - IMEI + // AT* + // VTS - send tone + // CREG + // CBC - BATTERY + // CSQ - SIGANL + // CSMS - SMS STUFFS + // CMGL + // CMGR + // CMGS + // CSCA - sms CENTER NUMBER + // CNMI - SMS INDICATION + // ast_log(LOG_DEBUG, "Manufacturer: %s\n", cmd); + + if (dev->role == BLT_ROLE_GUI) { + ast_log(LOG_NOTICE, "Displaying Menu.\n"); + dev->cb = gui_easm_response; +// send_atcmd(dev,"AT*EASM=\"SIP Menu\",1,1,3,\"Call Number\",\"Call Address\",\"More Options\",1"); + send_atcmd(dev,"AT*EASM=\"SIP Menu\",1,1,2,\"Call Number\",\"Call Address\",1"); + } else { + dev->cb = ag_unknown_response; + } +} + +void +ag_cgmi_valid_response(blt_dev_t * dev, char * cmd) +{ + // send_atcmd(dev, "AT+WS46?"); + // send_atcmd(dev, "AT+CRC=1"); + // send_atcmd(dev, "AT+CNUM"); + + if (strcmp(cmd, "OK") == 0) { + send_atcmd(dev, "AT+CGMI"); + dev->cb = ag_cgmi_response; + } else { + dev->cb = ag_unknown_response; + } +} + +void +ag_clip_response(blt_dev_t * dev, char * cmd) +{ + send_atcmd(dev, "AT+CGMI=?"); + dev->cb = ag_cgmi_valid_response; +} + +void +ag_cmer_response(blt_dev_t * dev, char * cmd) +{ + dev->cb = ag_clip_response; + dev->ready = 1; + dev->status = BLT_STATUS_READY; + send_atcmd(dev, "AT+CLIP=1"); +} + +void +ag_cind_status_response(blt_dev_t * dev, char * cmd) +{ + // XXX:T: Handle response. + dev->cb = ag_cmer_response; + send_atcmd(dev, "AT+CMER=3,0,0,1"); + // Initiliase SCO link! +} + +void +ag_cind_response(blt_dev_t * dev, char * cmd) +{ + dev->cb = ag_cind_status_response; + dev->cind = 1; + send_atcmd(dev, "AT+CIND?"); +} + +void +ag_brsf_response(blt_dev_t * dev, char * cmd) +{ + dev->cb = ag_cind_response; + ast_log(LOG_DEBUG, "Bluetooth features: %s\n", cmd); + dev->cind = 0; + send_atcmd(dev, "AT+CIND=?"); +} + +/* ---------------------------------- */ + +static int +sdp_register(sdp_session_t * session) +{ + // XXX:T: Fix this horrible function so it makes some sense and is extensible! + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_list_t *aproto, *proto[2]; + sdp_record_t record; + uint8_t u8 = rfcomm_channel_ag; + uint8_t u8_hs = rfcomm_channel_hs; + sdp_data_t *channel; + int ret = 0; + + memset((void *)&record, 0, sizeof(sdp_record_t)); + record.handle = 0xffffffff; + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(&record, root); + + // Register as an AG + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AUDIO_GW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(&record, svclass_id); + sdp_uuid16_create(&profile.uuid, 0x111f); + profile.version = 0x0100; + pfseq = sdp_list_append(0, &profile); + + sdp_set_profile_descs(&record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &u8); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(&record, aproto); + + sdp_set_info_attr(&record, "Voice Gateway", 0, 0); + + if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { + ast_log(LOG_ERROR, "Service Record registration failed\n"); + ret = -1; + goto end; + } + + sdp_record_ag = record.handle; + sdp_record_gui = record.handle; + + ast_log(LOG_NOTICE, "HeadsetAudioGateway service registered\n"); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(aproto, 0); + + // ------------- + + memset((void *)&record, 0, sizeof(sdp_record_t)); + record.handle = 0xffffffff; + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(&record, root); + + // Register as an HS + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AUDIO_GW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(&record, svclass_id); + sdp_uuid16_create(&profile.uuid, 0x111e); + profile.version = 0x0100; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(&record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &u8_hs); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(&record, aproto); + sdp_set_info_attr(&record, "Voice Gateway", 0, 0); + + if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { + ast_log(LOG_ERROR, "Service Record registration failed\n"); + ret = -1; + goto end; + } + + sdp_record_hs = record.handle; + + ast_log(LOG_NOTICE, "HeadsetAudioGateway service registered\n"); + +end: + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(aproto, 0); + + return ret; +} + +static int +rfcomm_listen(bdaddr_t * bdaddr, int channel) +{ + + int sock = -1; + struct sockaddr_rc loc_addr; + int on = 1; + + if ((sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { + ast_log(LOG_ERROR, "Can't create socket: %s (errno: %d)\n", strerror(errno), errno); + return -1; + } + + loc_addr.rc_family = AF_BLUETOOTH; + + /* Local Interface Address */ + bacpy(&loc_addr.rc_bdaddr, bdaddr); + + /* Channel */ + loc_addr.rc_channel = channel; + + if (bind(sock, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) { + ast_log(LOG_ERROR, "Can't bind socket: %s (errno: %d)\n", strerror(errno), errno); + close(sock); + return -1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + ast_log(LOG_ERROR, "Set socket SO_REUSEADDR option on failed: errno %d, %s", errno, strerror(errno)); + close(sock); + return -1; + } + + if (fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK) != 0) + ast_log(LOG_ERROR, "Can't set RFCOMM socket to NBIO\n"); + + if (listen(sock, 10) < 0) { + ast_log(LOG_ERROR,"Can not listen on the socket. %s(%d)\n", strerror(errno), errno); + close(sock); + return -1; + } + + ast_log(LOG_NOTICE, "Listening for RFCOMM channel %d connections on FD %d\n", channel, sock); + + return sock; +} + + +static int +sco_listen(bdaddr_t * bdaddr) +{ + int sock = -1; + int on = 1; + struct sockaddr_sco loc_addr; + + memset(&loc_addr, 0, sizeof(loc_addr)); + + if ((sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { + ast_log(LOG_ERROR, "Can't create SCO socket: %s (errno: %d)\n", strerror(errno), errno); + return -1; + } + + loc_addr.sco_family = AF_BLUETOOTH; + bacpy(&loc_addr.sco_bdaddr, BDADDR_ANY); + + if (bind(sock, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) { + ast_log(LOG_ERROR, "Can't bind SCO socket: %s (errno: %d)\n", strerror(errno), errno); + close(sock); + return -1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + ast_log(LOG_ERROR, "Set SCO socket SO_REUSEADDR option on failed: errno %d, %s", errno, strerror(errno)); + close(sock); + return -1; + } + + if (fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK) != 0) + ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); + + if (listen(sock, 10) < 0) { + ast_log(LOG_ERROR,"Can not listen on SCO socket: %s(%d)\n", strerror(errno), errno); + close(sock); + return -1; + } + + ast_log(LOG_NOTICE, "Listening for SCO connections on FD %d\n", sock); + + return sock; +} + +static int +rfcomm_connect(bdaddr_t * src, bdaddr_t * dst, int channel, int nbio) +{ + struct sockaddr_rc addr; + int s; + + if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = 0; + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + if (nbio) { + if (fcntl(s, F_SETFL, O_RDWR|O_NONBLOCK) != 0) + ast_log(LOG_ERROR, "Can't set RFCOMM socket to NBIO\n"); + } + + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 && (nbio != 1 || (errno != EAGAIN))) { + close(s); + return -1; + } + + return s; +} + +/* Must be called with dev->lock held */ + +static int +sco_connect(blt_dev_t * dev) +{ + struct sockaddr_sco addr; + // struct sco_conninfo conn; + // struct sco_options opts; + // int size; + // bdaddr_t * src = &local_bdaddr; + + int s; + bdaddr_t * dst = &(dev->bdaddr); + + if (dev->sco != -1) { + ast_log(LOG_ERROR, "SCO fd already open.\n"); + return -1; + } + + if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { + ast_log(LOG_ERROR, "Can't create SCO socket(): %s\n", strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, BDADDR_ANY); + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ast_log(LOG_ERROR, "Can't bind() SCO socket: %s\n", strerror(errno)); + close(s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, dst); + + if (fcntl(s, F_SETFL, O_RDWR|O_NONBLOCK) != 0) + ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); + + if ((connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) && (errno != EAGAIN)) { + ast_log(LOG_ERROR, "Can't connect() SCO socket: %s (errno %d)\n", strerror(errno), errno); + close(s); + return -1; + } + + //size = sizeof(conn); + + +/* XXX:T: HERE, fix getting SCO conninfo. + + if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) { + ast_log(LOG_ERROR, "Can't getsockopt SCO_CONNINFO on SCO socket: %s\n", strerror(errno)); + close(s); + return -1; + } + + size = sizeof(opts); + + if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) { + ast_log(LOG_ERROR, "Can't getsockopt SCO_OPTIONS on SCO socket: %s\n", strerror(errno)); + close(s); + return -1; + } + + dev->sco_handle = conn.hci_handle; + dev->sco_mtu = opts.mtu; + +*/ + + ast_log(LOG_DEBUG, "SCO: %d\n", s); + + dev->sco = s; + + return 0; +} + + +/* ---------------------------------- */ + +/* Non blocking (async) outgoing bluetooth connection */ + +static int +try_connect(blt_dev_t * dev) +{ + int fd; + ast_mutex_lock(&(dev->lock)); + + if (dev->status != BLT_STATUS_CONNECTING && dev->status != BLT_STATUS_DOWN) { + ast_mutex_unlock(&(dev->lock)); + return 0; + } + + if (dev->rd != -1) { + + int ret; + struct pollfd pfd; + + if (dev->status != BLT_STATUS_CONNECTING) { + ast_mutex_unlock(&(dev->lock)); + dev->outgoing_id = -1; + return 0; + } + + // ret = connect(dev->rd, (struct sockaddr *)&(dev->addr), sizeof(struct sockaddr_rc)); // + + pfd.fd = dev->rd; + pfd.events = POLLIN | POLLOUT; + + ret = poll(&pfd, 1, 0); + + if (ret == -1) { + close(dev->rd); + dev->rd = -1; + dev->status = BLT_STATUS_DOWN; + dev->outgoing_id = ast_sched_add(sched, 10000, AST_SCHED_CB(try_connect), dev); + ast_mutex_unlock(&(dev->lock)); + return 0; + } + + if (ret > 0) { + + int len = sizeof(ret); + getsockopt(dev->rd, SOL_SOCKET, SO_ERROR, &ret, &len); + + if (ret == 0) { + + ast_log(LOG_NOTICE, "Initialised bluetooth link to device %s\n", dev->name); + +#if 0 + { + struct hci_conn_info_req * cr; + int dd; + char name[248]; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + dd = hci_open_dev(hcidev_id); + cr->type = ACL_LINK; + bacpy(&cr->bdaddr, &(dev->bdaddr)); + + if (ioctl(dd, HCIGETCONNINFO, (unsigned long)cr) < 0) { + ast_log(LOG_ERROR, "Failed to get connection info: %s\n", strerror(errno)); + } else { + ast_log(LOG_DEBUG, "HCI Handle: %d\n", cr->conn_info->handle); + } + + if (hci_read_remote_name(dd, &(dev->bdaddr), sizeof(name), name, 25000) == 0) + ast_log(LOG_DEBUG, "Remote Name: %s\n", name); + free(cr); + } +#endif + + dev->status = BLT_STATUS_NEGOTIATING; + + /* If this device is an AG/GUI, we initiate the negotiation. */ + + if (dev->role == BLT_ROLE_AG || + dev->role == BLT_ROLE_GUI) { + dev->cb = ag_brsf_response; + send_atcmd(dev, "AT+BRSF=23"); + } + + dev->outgoing_id = -1; + ast_mutex_unlock(&(dev->lock)); + return 0; + + } else { + + if (ret != EHOSTDOWN) + ast_log(LOG_NOTICE, "Connect to device %s failed: %s (errno %d)\n", dev->name, strerror(ret), ret); + + close(dev->rd); + dev->rd = -1; + dev->status = BLT_STATUS_DOWN; + dev->outgoing_id = ast_sched_add(sched, (ret == EHOSTDOWN) ? 10000 : 2500, AST_SCHED_CB(try_connect), dev); + ast_mutex_unlock(&(dev->lock)); + return 0; + + } + + } + + dev->outgoing_id = ast_sched_add(sched, 100, AST_SCHED_CB(try_connect), dev); + ast_mutex_unlock(&(dev->lock)); + return 0; + } + + ast_log(LOG_NOTICE, "RFCOMM connect start.\n"); + fd = rfcomm_connect(&local_bdaddr, &(dev->bdaddr), dev->channel, 1); + ast_log(LOG_NOTICE, "RFCOMM connect done.\n"); + + if (fd == -1) { + ast_log(LOG_WARNING, "NBIO connect() to %s returned %d: %s\n", dev->name, errno, strerror(errno)); + dev->outgoing_id = ast_sched_add(sched, 5000, AST_SCHED_CB(try_connect), dev); + ast_mutex_unlock(&(dev->lock)); + return 0; + } + + dev->rd = fd; + dev->status = BLT_STATUS_CONNECTING; + dev->outgoing_id = ast_sched_add(sched, 100, AST_SCHED_CB(try_connect), dev); + ast_mutex_unlock(&(dev->lock)); + return 0; +} + + +/* Called whenever a new command is received while we're the AG */ + + +static int +process_rfcomm_cmd(blt_dev_t * dev, char * cmd) +{ + int i; + char * fullcmd = cmd; + + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, cmd); + + /* Read the 'AT' from the start of the string */ + if (strncmp(cmd, "AT", 2)) { + ast_log(LOG_WARNING, "Unknown command without 'AT': %s\n", cmd); + send_atcmd_error(dev); + return 0; + } + + cmd += 2; + + // Don't forget 'AT' on its own is OK. + + if (strlen(cmd) == 0) { + send_atcmd_ok(dev, fullcmd); + return 0; + } + + for (i = 0 ; i < ATCMD_LIST_LEN ; i++) { + if (strncmp(atcmd_list[i].str, cmd, strlen(atcmd_list[i].str)) == 0) { + char * pos = (cmd + strlen(atcmd_list[i].str)); + if ((strncmp(pos, "=?", 2) == 0) && (strlen(pos) == 2)) { + /* TEST command */ + if (atcmd_list[i].test) { + if (atcmd_list[i].test(dev) == 0) + send_atcmd_ok(dev, fullcmd); + else + send_atcmd_error(dev); + } else { + send_atcmd_ok(dev, fullcmd); + } + } else if ((strncmp(pos, "?", 1) == 0) && (strlen(pos) == 1)) { + /* READ command */ + if (atcmd_list[i].read) { + if (atcmd_list[i].read(dev) == 0) + send_atcmd_ok(dev, fullcmd); + else + send_atcmd_error(dev); + } else { + ast_log(LOG_WARNING, "AT Command: '%s' missing READ function\n", fullcmd); + send_atcmd_error(dev); + } + } else if (strncmp(pos, "=", 1) == 0) { + /* SET command */ + if (atcmd_list[i].set) { + if (atcmd_list[i].set(dev, (pos + 1), (*(pos + 1)) ? strlen(pos + 1) : 0) == 0) + send_atcmd_ok(dev, fullcmd); + else + send_atcmd_error(dev); + } else { + ast_log(LOG_WARNING, "AT Command: '%s' missing SET function\n", fullcmd); + send_atcmd_error(dev); + } + } else { + /* EXECUTE command */ + if (atcmd_list[i].execute) { + if (atcmd_list[i].execute(dev, cmd + strlen(atcmd_list[i].str)) == 0) + send_atcmd_ok(dev, fullcmd); + else + send_atcmd_error(dev); + } else { + ast_log(LOG_WARNING, "AT Command: '%s' missing EXECUTE function\n", fullcmd); + send_atcmd_error(dev); + } + } + return 0; + } + } + + ast_log(LOG_NOTICE, "Unknown AT Command: '%s' (%s)\n", fullcmd, cmd); + send_atcmd_error(dev); + + return 0; +} + +/* Called when a socket is incoming */ + +static void +handle_incoming(int fd, blt_role_t role) +{ + blt_dev_t * dev; + struct sockaddr_rc addr; + int len = sizeof(addr); + + // Got a new incoming socket. + ast_log(LOG_DEBUG, "Incoming RFCOMM socket\n"); + + ast_mutex_lock(&iface_lock); + + fd = accept(fd, (struct sockaddr*)&addr, &len); + + dev = iface_head; + while (dev) { + if (bacmp(&(dev->bdaddr), &addr.rc_bdaddr) == 0) { + ast_log(LOG_DEBUG, "Connect from %s\n", dev->name); + ast_mutex_lock(&(dev->lock)); + /* Kill any outstanding connect attempt. */ + if (dev->outgoing_id > -1) { + ast_sched_del(sched, dev->outgoing_id); + dev->outgoing_id = -1; + } + + rd_close(dev, 0, 0); + + dev->status = BLT_STATUS_NEGOTIATING; + dev->rd = fd; + + if (dev->role == BLT_ROLE_AG || + dev->role == BLT_ROLE_GUI) { + dev->cb = ag_brsf_response; + send_atcmd(dev, "AT+BRSF=23"); + } + ast_mutex_unlock(&(dev->lock)); + break; + } + dev = dev->next; + } + + if (dev == NULL) { + ast_log(LOG_WARNING, "Connect from unknown device\n"); + close(fd); + } + ast_mutex_unlock(&iface_lock); + + return; +} + +static void +handle_incoming_sco(int master) +{ + + blt_dev_t * dev; + struct sockaddr_sco addr; + struct sco_conninfo conn; + struct sco_options opts; + int len = sizeof(addr); + int fd; + + ast_log(LOG_DEBUG, "Incoming SCO socket\n"); + + fd = accept(master, (struct sockaddr*)&addr, &len); + + if (fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK) != 0) { + ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); + close(fd); + return; + } + + len = sizeof(conn); + + if (getsockopt(fd, SOL_SCO, SCO_CONNINFO, &conn, &len) < 0) { + ast_log(LOG_ERROR, "Can't getsockopt SCO_CONNINFO on SCO socket: %s\n", strerror(errno)); + close(fd); + return; + } + + len = sizeof(opts); + + if (getsockopt(fd, SOL_SCO, SCO_OPTIONS, &opts, &len) < 0) { + ast_log(LOG_ERROR, "Can't getsockopt SCO_OPTIONS on SCO socket: %s\n", strerror(errno)); + close(fd); + return; + } + + ast_mutex_lock(&iface_lock); + dev = iface_head; + while (dev) { + if (bacmp(&(dev->bdaddr), &addr.sco_bdaddr) == 0) { + ast_log(LOG_DEBUG, "SCO Connect from %s\n", dev->name); + ast_mutex_lock(&(dev->lock)); + if (dev->sco_running != -1) { + ast_log(LOG_ERROR, "Incoming SCO socket, but SCO thread already running.\n"); + } else { + sco_start(dev, fd); + } + ast_mutex_unlock(&(dev->lock)); + break; + } + dev = dev->next; + } + + ast_mutex_unlock(&iface_lock); + + if (dev == NULL) { + ast_log(LOG_WARNING, "SCO Connect from unknown device\n"); + close(fd); + } else { + // XXX:T: We need to handle the fact we might have an outgoing connection attempt in progress. + ast_log(LOG_DEBUG, "SCO: %d, HCIHandle=%d, MUT=%d\n", fd, conn.hci_handle, opts.mtu); + } + + + + return; +} + +/* Called when there is data waiting on a socket */ + +static int +handle_rd_data(blt_dev_t * dev) +{ + char c; + int ret; + + while ((ret = read(dev->rd, &c, 1)) == 1) { + + // log_buf[i++] = c; + + if (dev->role == BLT_ROLE_HS) { + + if (c == '\r') { + ret = process_rfcomm_cmd(dev, dev->rd_buff); + dev->rd_buff_pos = 0; + memset(dev->rd_buff, 0, BLT_RDBUFF_MAX); + return ret; + } + + if (dev->rd_buff_pos >= BLT_RDBUFF_MAX) + return 0; + + dev->rd_buff[dev->rd_buff_pos++] = c; + + } else if (dev->role == BLT_ROLE_AG || + dev->role == BLT_ROLE_GUI) { + + //ast_log(LOG_ERROR, "%s: %c\n", dev->name, c); + + switch (dev->state) { + case BLT_STATE_WANT_R: + if (c == '\r' || c == 10) { + dev->state = BLT_STATE_WANT_N; + } else if (c == '+') { + dev->state = BLT_STATE_WANT_CMD; + dev->rd_buff[dev->rd_buff_pos++] = '+'; + } else { + ast_log(LOG_ERROR, "Device %s: Expected '\\r', got %d. state=BLT_STATE_WANT_R\n", dev->name, c); + return -1; + } + break; + + case BLT_STATE_WANT_N: + if (c == '\n' || c == 13) + dev->state = BLT_STATE_WANT_CMD; + else { + ast_log(LOG_ERROR, "Device %s: Expected '\\n', got %d. state=BLT_STATE_WANT_N\n", dev->name, c); + return -1; + } + break; + + case BLT_STATE_WANT_CMD: + if (c == '\r' || c == 10) + dev->state = BLT_STATE_WANT_N2; + else { + if (dev->rd_buff_pos >= BLT_RDBUFF_MAX) { + ast_log(LOG_ERROR, "Device %s: Buffer exceeded\n", dev->name); + return -1; + } + dev->rd_buff[dev->rd_buff_pos++] = c; + } + break; + + case BLT_STATE_WANT_N2: + if (c == '\n' || c == 13) { + + dev->state = BLT_STATE_WANT_R; + + if (dev->rd_buff[0] == '+') { + int i; + // find unsolicited + for (i = 0 ; i < ATCMD_LIST_LEN ; i++) { + if (strncmp(atcmd_list[i].str, dev->rd_buff, strlen(atcmd_list[i].str)) == 0) { + if (atcmd_list[i].unsolicited) + atcmd_list[i].unsolicited(dev, dev->rd_buff + strlen(atcmd_list[i].str) + 1); + else + ast_log(LOG_WARNING, "Device %s: Unhandled Unsolicited: %s\n", dev->name, dev->rd_buff); + break; + } + } + + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); + + if (i == ATCMD_LIST_LEN) + ast_log(LOG_NOTICE, "Device %s: Got unsolicited message: %s\n", dev->name, dev->rd_buff); + + } else if (dev->rd_buff[0] == '*') { + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s]* %*s > %s\n", role2str(dev->role), 9, dev->name, dev->rd_buff); + + int i; + // find execute + for (i = 0 ; i < ATCMD_LIST_LEN ; i++) { + if (strncmp(atcmd_list[i].str, dev->rd_buff, strlen(atcmd_list[i].str)) == 0) { + if (atcmd_list[i].execute) + atcmd_list[i].execute(dev, dev->rd_buff + strlen(atcmd_list[i].str) + 1); + else + ast_log(LOG_ERROR, "Device %s: Unhandled Execute: %s\n", dev->name, dev->rd_buff); + break; + } + } + + + } else { + + if ( + strcmp(dev->rd_buff, "OK") != 0 && + strcmp(dev->rd_buff, "CONNECT") != 0 && + strcmp(dev->rd_buff, "RING") != 0 && + strcmp(dev->rd_buff, "NO CARRIER") != 0 && + strcmp(dev->rd_buff, "ERROR") != 0 && + strcmp(dev->rd_buff, "NO DIALTONE") != 0 && + strcmp(dev->rd_buff, "BUSY") != 0 && + strcmp(dev->rd_buff, "NO ANSWER") != 0 && + strcmp(dev->rd_buff, "DELAYED") != 0 + ){ + // It must be a multiline error + strncpy(dev->last_err_cmd, dev->rd_buff, 1023); + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); + } else if (dev->cb) { + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); + dev->cb(dev, dev->rd_buff); + } else { + ast_log(LOG_ERROR, "Device %s: Data on socket in HS mode, but no callback\n", dev->name); + } + + } + + dev->rd_buff_pos = 0; + memset(dev->rd_buff, 0, BLT_RDBUFF_MAX); + } else { + + ast_log(LOG_ERROR, "Device %s: Expected '\\n' got %d. state = BLT_STATE_WANT_N2:\n", dev->name, c); + return -1; + + } + + break; + + default: + ast_log(LOG_ERROR, "Device %s: Unknown device state %d\n", dev->name, dev->state); + return -1; + + } + + } + + } + + return 0; +} + +/* Close the devices RFCOMM socket, and SCO if it exists. Must hold dev->lock */ + +static void +rd_close(blt_dev_t * dev, int reconnect, int e) +{ + dev->ready = 0; + + if (dev->rd) + close(dev->rd); + + dev->rd = -1; + + dev->status = BLT_STATUS_DOWN; + + sco_stop(dev); + + if (dev->owner) { + ast_setstate(dev->owner, AST_STATE_DOWN); + ast_queue_control(dev->owner, AST_CONTROL_HANGUP); + } + + /* Schedule a reconnect */ + if (reconnect && dev->autoconnect) { + dev->outgoing_id = ast_sched_add(sched, 5000, AST_SCHED_CB(try_connect), dev); + + if (monitor_thread == pthread_self()) { + // Because we're not the monitor thread, we needd to inturrupt poll(). + pthread_kill(monitor_thread, SIGURG); + } + + if (e) + ast_log(LOG_NOTICE, "Device %s disconnected, scheduled reconnect in 5 seconds: %s (errno %d)\n", dev->name, strerror(e), e); + } else if (e) { + ast_log(LOG_NOTICE, "Device %s disconnected: %s (errno %d)\n", dev->name, strerror(e), e); + } + + return; +} + +/* + * Remember that we can only add to the scheduler from + * the do_monitor thread, as it calculates time to next one from + * this loop. + */ + +static void * +do_monitor(void * data) +{ +#define SRV_SOCK_CNT 4 + + int res = 0; + blt_dev_t * dev; + struct pollfd * pfds = malloc(sizeof(struct pollfd) * (ifcount + SRV_SOCK_CNT)); + + /* -- We start off by trying to connect all of our devices (non blocking) -- */ + + monitor_pid = getpid(); + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); + return NULL; + } + + dev = iface_head; + while (dev) { + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, dev->sco_pipe) != 0) { + ast_log(LOG_ERROR, "Failed to create socket pair: %s (errno %d)\n", strerror(errno), errno); + ast_mutex_unlock(&iface_lock); + return NULL; + } + + if (dev->autoconnect && dev->status == BLT_STATUS_DOWN) + dev->outgoing_id = ast_sched_add(sched, 1500, AST_SCHED_CB(try_connect), dev); + dev = dev->next; + } + ast_mutex_unlock(&iface_lock); + + /* -- Now, Scan all sockets, and service scheduler -- */ + + pfds[0].fd = rfcomm_sock_ag; + pfds[0].events = POLLIN; + + pfds[1].fd = rfcomm_sock_hs; + pfds[1].events = POLLIN; + + pfds[2].fd = rfcomm_sock_gui; + pfds[2].events = POLLIN; + + pfds[3].fd = sco_socket; + pfds[3].events = POLLIN; + + while (1) { + int cnt = SRV_SOCK_CNT; + int i; + + /* -- Build pfds -- */ + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); + return NULL; + } + dev = iface_head; + while (dev) { + ast_mutex_lock(&(dev->lock)); + if (dev->rd > 0 && ((dev->status != BLT_STATUS_DOWN) && (dev->status != BLT_STATUS_CONNECTING))) { + pfds[cnt].fd = dev->rd; + pfds[cnt].events = POLLIN; + cnt++; + } + ast_mutex_unlock(&(dev->lock)); + dev = dev->next; + } + ast_mutex_unlock(&iface_lock); + + /* -- End Build pfds -- */ + + res = ast_sched_wait(sched); + res = poll(pfds, cnt, MAX(100, MIN(100, res))); + + if (res == 0) + ast_sched_runq(sched); + + if (pfds[0].revents) { + handle_incoming(rfcomm_sock_ag, BLT_ROLE_AG); + res--; + } + + if (pfds[1].revents) { + handle_incoming(rfcomm_sock_hs, BLT_ROLE_HS); + res--; + } + + if (pfds[2].revents) { + handle_incoming(rfcomm_sock_gui, BLT_ROLE_GUI); + res--; + } + + if (pfds[3].revents) { + handle_incoming_sco(sco_socket); + res--; + } + + if (res == 0) + continue; + + for (i = SRV_SOCK_CNT ; i < cnt ; i++) { + + /* Optimise a little bit */ + if (res == 0) + break; + else if (pfds[i].revents == 0) + continue; + + /* -- Find the socket that has activity -- */ + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); + return NULL; + } + + dev = iface_head; + + while (dev) { + if (pfds[i].fd == dev->rd) { + ast_mutex_lock(&(dev->lock)); + if (pfds[i].revents & POLLIN) { + if (handle_rd_data(dev) == -1) { + rd_close(dev, 0, 0); + } + } else { + rd_close(dev, 1, sock_err(dev->rd)); + } + ast_mutex_unlock(&(dev->lock)); + res--; + break; + } + dev = dev->next; + } + + if (dev == NULL) { + ast_log(LOG_ERROR, "Unhandled fd from poll()\n"); + close(pfds[i].fd); + } + + ast_mutex_unlock(&iface_lock); + + /* -- End find socket with activity -- */ + + } + + } + + return NULL; +} + +static int +restart_monitor(void) +{ + + if (monitor_thread == AST_PTHREADT_STOP) + return 0; + + if (ast_mutex_lock(&monitor_lock)) { + ast_log(LOG_WARNING, "Unable to lock monitor\n"); + return -1; + } + + if (monitor_thread == pthread_self()) { + ast_mutex_unlock(&monitor_lock); + ast_log(LOG_WARNING, "Cannot kill myself\n"); + return -1; + } + + if (monitor_thread != AST_PTHREADT_NULL) { + + /* Just signal it to be sure it wakes up */ + pthread_cancel(monitor_thread); + pthread_kill(monitor_thread, SIGURG); + ast_log(LOG_DEBUG, "Waiting for monitor thread to join...\n"); + pthread_join(monitor_thread, NULL); + ast_log(LOG_DEBUG, "joined\n"); + + } else { + + /* Start a new monitor */ + if (ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL) < 0) { + ast_mutex_unlock(&monitor_lock); + ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); + return -1; + } + + } + + ast_mutex_unlock(&monitor_lock); + return 0; +} + +static int +blt_parse_config(void) +{ + struct ast_config * cfg; + struct ast_variable * v; + char * cat; + + cfg = ast_load(BLT_CONFIG_FILE); + + if (!cfg) { + ast_log(LOG_NOTICE, "Unable to load Bluetooth config: %s. Bluetooth disabled\n", BLT_CONFIG_FILE); + return -1; + } + + v = ast_variable_browse(cfg, "general"); + + while (v) { + if (!strcasecmp(v->name, "rfchannel_ag")) { + rfcomm_channel_ag = atoi(v->value); + } else if (!strcasecmp(v->name, "rfchannel_hs")) { + rfcomm_channel_hs = atoi(v->value); + } else if (!strcasecmp(v->name, "rfchannel_gui")) { + rfcomm_channel_gui = atoi(v->value); + } else if (!strcasecmp(v->name, "interface")) { + hcidev_id = atoi(v->value); + } else if (!strcasecmp(v->name, "gui_default_sip_number")) { + gui_default_sip_number = v->value; + } else if (!strcasecmp(v->name, "gui_default_sip_address")) { + gui_default_sip_address = v->value; + } else { + ast_log(LOG_WARNING, "Unknown config key '%s' in section [general]\n", v->name); + } + v = v->next; + } + cat = ast_category_browse(cfg, NULL); + + while(cat) { + + char * str; + + if (strcasecmp(cat, "general")) { + blt_dev_t * device = malloc(sizeof(blt_dev_t)); + memset(device, 0, sizeof(blt_dev_t)); + device->sco_running = -1; + device->sco = -1; + device->rd = -1; + device->outgoing_id = -1; + device->status = BLT_STATUS_DOWN; + str2ba(cat, &(device->bdaddr)); + device->name = ast_variable_retrieve(cfg, cat, "name"); + + str = ast_variable_retrieve(cfg, cat, "type"); + + if (str == NULL) { + ast_log(LOG_ERROR, "Device [%s] has no role. Specify type=\n", cat); + return -1; + } else if (strcasecmp(str, "HS") == 0) { + device->role = BLT_ROLE_HS; + } else if (strcasecmp(str, "AG") == 0) { + device->role = BLT_ROLE_AG; + } else if (strcasecmp(str, "GUI") == 0) { + device->role = BLT_ROLE_GUI; + } else { + ast_log(LOG_ERROR, "Device [%s] has invalid role '%s'\n", cat, str); + return -1; + } + + /* XXX:T: Find channel to use using SDP. + * However, this needs to be non blocking, and I can't see + * anything in sdp_lib.h that will allow non blocking calls. + */ + + device->channel = 1; + + if ((str = ast_variable_retrieve(cfg, cat, "channel")) != NULL) + device->channel = atoi(str); + + if ((str = ast_variable_retrieve(cfg, cat, "autoconnect")) != NULL) + device->autoconnect = (strcasecmp(str, "yes") == 0 || strcmp(str, "1") == 0) ? 1 : 0; + + if ((str = ast_variable_retrieve(cfg, cat, "context")) != NULL) + device->context = str; + else + device->context = "bluetooth"; + + device->next = iface_head; + iface_head = device; + ifcount++; + } + + cat = ast_category_browse(cfg, cat); + } + return 0; +} + + +static int +blt_show_peers(int fd, int argc, char *argv[]) +{ + blt_dev_t * dev; + + if (ast_mutex_lock(&iface_lock)) { + ast_log(LOG_ERROR, "Failed to get Iface lock\n"); + ast_cli(fd, "Failed to get iface lock\n"); + return RESULT_FAILURE; + } + + dev = iface_head; + + ast_cli(fd, "BDAddr Name Role Status A/C SCOCon/Fd/Th Sig\n"); + ast_cli(fd, "----------------- ---------- ---- ----------- --- ------------ ---\n"); + + while (dev) { + char b1[18]; + ba2str(&(dev->bdaddr), b1); + ast_cli(fd, "%s %-10s %-4s %-11s %-3s %2d/%02d/%-6ld %s\n", + b1, dev->name, +// (dev->role == BLT_ROLE_HS) ? "HS" : "AG", + (dev->role == BLT_ROLE_HS) ? "HS" : (dev->role == BLT_ROLE_AG) ? "AG" : "GUI", + status2str(dev->status), + (dev->autoconnect) ? "Yes" : "No", + dev->sco_running, + dev->sco, + dev->sco_thread, + (dev->role == BLT_ROLE_AG) ? (dev->service) ? "Yes" : "No" : "N/A" + ); + dev = dev->next; + } + + ast_mutex_unlock(&iface_lock); + return RESULT_SUCCESS; +} + +static int +blt_show_information(int fd, int argc, char *argv[]) +{ + char b1[18]; + ba2str(&local_bdaddr, b1); + ast_cli(fd, "-------------------------------------------\n"); + ast_cli(fd, " Version : %s\n", BLT_SVN_REVISION); + ast_cli(fd, " Monitor PID : %d\n", monitor_pid); + ast_cli(fd, " RFCOMM AG : Channel %d, FD %d\n", rfcomm_channel_ag, rfcomm_sock_ag); + ast_cli(fd, " RFCOMM HS : Channel %d, FD %d\n", rfcomm_channel_hs, rfcomm_sock_hs); + ast_cli(fd, " RFCOMM GUI : Channel %d, FD %d\n", rfcomm_channel_gui, rfcomm_sock_gui); + ast_cli(fd, " Device : hci%d, MAC Address %s\n", hcidev_id, b1); + ast_cli(fd, "-------------------------------------------\n"); + return RESULT_SUCCESS; +} + +static int +blt_ag_sendcmd(int fd, int argc, char *argv[]) +{ + blt_dev_t * dev; + + if (argc != 4) + return RESULT_SHOWUSAGE; + + ast_mutex_lock(&iface_lock); + dev = iface_head; + while (dev) { + if (!strcasecmp(argv[2], dev->name)) + break; + dev = dev->next; + } + ast_mutex_unlock(&iface_lock); + + if (!dev) { + ast_cli(fd, "Device '%s' does not exist\n", argv[2]); + return RESULT_FAILURE; + } + + if ((dev->role != BLT_ROLE_AG) && (dev->role != BLT_ROLE_GUI)) { + ast_cli(fd, "Device '%s' is not an AG or GUI\n", argv[2]); + return RESULT_FAILURE; + } + + if (dev->status == BLT_STATUS_DOWN || dev->status == BLT_STATUS_NEGOTIATING) { + ast_cli(fd, "Device '%s' is not connected\n", argv[2]); + return RESULT_FAILURE; + } + + if (*(argv[3] + strlen(argv[3]) - 1) == '.') + *(argv[3] + strlen(argv[3]) - 1) = '?'; + + ast_cli(fd, "Sending AT command to %s: %s\n", dev->name, argv[3]); + + ast_mutex_lock(&(dev->lock)); + send_atcmd(dev, argv[3]); + ast_mutex_unlock(&(dev->lock)); + + return RESULT_SUCCESS; +} + +static char * +complete_device(char * line, char * word, int pos, int state, int rpos, blt_role_t role) +{ + blt_dev_t * dev; + int which = 0; + char *ret; + + if (pos != rpos) + return NULL; + + ast_mutex_lock(&iface_lock); + + dev = iface_head; + + while (dev) { + + if ((dev->role == role) && (!strncasecmp(word, dev->name, strlen(word)))) { + if (++which > state) + break; + } + + dev = dev->next; + } + + if (dev) + ret = strdup(dev->name); + else + ret = NULL; + + ast_mutex_unlock(&iface_lock); + + return ret; +} + +static char * +complete_device_2_ag_gui(char * line, char * word, int pos, int state) +{ + return complete_device(line, word, pos, state, 2, BLT_ROLE_AG); +} + +static char show_peers_usage[] = +"Usage: bluetooth show peers\n" +" List all bluetooth peers and their status\n"; + +static struct ast_cli_entry +cli_show_peers = + { { "bluetooth", "show", "peers", NULL }, blt_show_peers, "List Bluetooth Peers", show_peers_usage }; + + +static char ag_sendcmd[] = +"Usage: bluetooth sendcmd \n" +" Sends a AT cmd over the RFCOMM link, and print result (AG only)\n"; + +static struct ast_cli_entry +cli_ag_sendcmd = + { { "bluetooth", "sendcmd", NULL }, blt_ag_sendcmd, "Send AG/GUI an AT command", ag_sendcmd, complete_device_2_ag_gui }; + +static char show_information[] = +"Usage: bluetooth show information\n" +" Lists information about the bluetooth subsystem\n"; + +static struct ast_cli_entry +cli_show_information = + { { "bluetooth", "show", "information", NULL }, blt_show_information, "List Bluetooth Info", show_information }; + +void +remove_sdp_records(void) +{ + + sdp_session_t * sdp; + sdp_list_t * attr; + sdp_record_t * rec; + int res = -1; + uint32_t range = 0x0000ffff; + + if (sdp_record_ag == -1 || sdp_record_gui == -1 || sdp_record_hs == -1) + return; + + ast_log(LOG_DEBUG, "Removing SDP records\n"); + + sdp = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); + + if (!sdp) + return; + + attr = sdp_list_append(0, &range); + rec = sdp_service_attr_req(sdp, sdp_record_ag, SDP_ATTR_REQ_RANGE, attr); + sdp_list_free(attr, 0); + + if (rec) + if (sdp_record_unregister(sdp, rec) == 0) + res = 0; + + rec = sdp_service_attr_req(sdp, sdp_record_gui, SDP_ATTR_REQ_RANGE, attr); + sdp_list_free(attr, 0); + + if (rec) + if (sdp_record_unregister(sdp, rec) == 0) + res = 0; + + attr = sdp_list_append(0, &range); + rec = sdp_service_attr_req(sdp, sdp_record_hs, SDP_ATTR_REQ_RANGE, attr); + sdp_list_free(attr, 0); + + if (rec) + if (sdp_record_unregister(sdp, rec) == 0) + res = 0; + + sdp_close(sdp); + + if (res == 0) + ast_log(LOG_NOTICE, "Removed SDP records\n"); + else + ast_log(LOG_ERROR, "Failed to remove SDP records\n"); + +} + +static int +__unload_module(void) +{ + +#if ASTERISK_VERSION_NUM <= 010107 + ast_channel_unregister(BLT_CHAN_NAME); +#else + ast_channel_unregister(&blt_tech); +#endif + + if (monitor_thread != AST_PTHREADT_NULL) { + + if (ast_mutex_lock(&monitor_lock)) { + + if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { + pthread_cancel(monitor_thread); + pthread_kill(monitor_thread, SIGURG); + fprintf(stderr, "Waiting for monitor thread to join...\n"); + pthread_join(monitor_thread, NULL); + fprintf(stderr, "joined\n"); + } + monitor_thread = AST_PTHREADT_STOP; + ast_mutex_unlock(&monitor_lock); + + } else { + + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + + } + + } + + ast_unregister_atexit(remove_sdp_records); + remove_sdp_records(); + return 0; +} + +int +load_module() +{ + sdp_session_t * sess; + int dd; + uint16_t vs; + + hcidev_id = BLT_DEFAULT_HCI_DEV; + + if (blt_parse_config() != 0) { + ast_log(LOG_ERROR, "Bluetooth configuration error. Bluetooth Disabled\n"); + return unload_module(); + } + + dd = hci_open_dev(hcidev_id); + if (dd == -1) { + ast_log(LOG_ERROR, "Unable to open interface hci%d: %s.\n", hcidev_id, strerror(errno)); + return -1; + } + + hci_read_voice_setting(dd, &vs, 1000); + vs = htobs(vs); + close(dd); + + if (vs != 0x0060) { + ast_log(LOG_ERROR, "Bluetooth voice setting must be 0x0060, not 0x%04x\n", vs); + unload_module(); + return 0; + } + + if ((sched = sched_context_create()) == NULL) { + ast_log(LOG_WARNING, "Unable to create schedule context\n"); + return -1; + } + + memset(&local_bdaddr, 0, sizeof(local_bdaddr)); + + hci_devba(hcidev_id, &local_bdaddr); + + /* --- Add SDP record --- */ + + sess = sdp_connect(&local_bdaddr, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); + + if ((rfcomm_sock_ag = rfcomm_listen(&local_bdaddr, rfcomm_channel_ag)) < 0) { + return -1; + } + + if ((rfcomm_sock_hs = rfcomm_listen(&local_bdaddr, rfcomm_channel_hs)) < 0) + return -1; + + if ((rfcomm_sock_gui = rfcomm_listen(&local_bdaddr, rfcomm_channel_gui)) < 0) + return -1; + + if ((sco_socket = sco_listen(&local_bdaddr)) < 0) + return -1; + + if (!sess) { + ast_log(LOG_ERROR, "Failed to connect to SDP server: %s\n", strerror(errno)); + return -1; + } + + if (sdp_register(sess) != 0) { + ast_log(LOG_ERROR, "Failed to register HeadsetAudioGateway in SDP\n"); + return -1; + } + + sdp_close(sess); + + if (restart_monitor() != 0) + return -1; + +#if ASTERISK_VERSION_NUM <= 010107 + if (ast_channel_register(BLT_CHAN_NAME, "Bluetooth Driver", BLUETOOTH_FORMAT, blt_request)) { +#else + if (ast_channel_register(&blt_tech)) { +#endif + ast_log(LOG_ERROR, "Unable to register channel class BTL\n"); + __unload_module(); + return -1; + } + + ast_cli_register(&cli_show_information); + ast_cli_register(&cli_show_peers); + ast_cli_register(&cli_ag_sendcmd); + + ast_register_atexit(remove_sdp_records); + + ast_log(LOG_NOTICE, "Loaded Bluetooth support, %s\n", BLT_SVN_REVISION + 1); + + return 0; +} + +int +unload_module(void) +{ + ast_cli_unregister(&cli_ag_sendcmd); + ast_cli_unregister(&cli_show_peers); + ast_cli_unregister(&cli_show_information); + return __unload_module(); +} + +int +usecount() +{ + int res; + ast_mutex_lock(&usecnt_lock); + res = usecnt; + ast_mutex_unlock(&usecnt_lock); + return res; +} + +char *description() +{ + return "Bluetooth Channel Driver"; +} + +char * +key() +{ + return ASTERISK_GPL_KEY; +} + + diff -ruN asterisk-1.0.9-old/configs/bluetooth.conf asterisk-1.0.9-new/configs/bluetooth.conf --- asterisk-1.0.9-old/configs/bluetooth.conf 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.9-new/configs/bluetooth.conf 2005-09-06 22:51:38.000000000 +0200 @@ -0,0 +1,46 @@ +[general] +; Channel we listen on as a HS (Headset) +rfchannel_hs = 2 +; Channel we listen on as an AG (AudioGateway) +rfchannel_ag = 3 +; Channel we listen on as GUI +rfchannel_gui = 4 +; hci interface to use (number - e.g '0') +interface = 0 + +; RFCOMM channel to connect to. For a HandsSet: +; sdptool search --bdaddr xx:xx:xx:xx:xx:xx 0x111E +; or,for an AudioGateway (Phone): +; sdptool search --bdaddr xx:xx:xx:xx:xx:xx 0x111F +; +; Find the 'channel' value under RFCOMM. +; +;channel = 6 +; Automatically connect? +;autoconnect = yes + +;example for a SonyEricsson mobile as a GUI device +[00:0F:DE:6E:77:6B] +name = T610 +type = GUI +channel = 6 +;channel = 1 +autoconnect = yes + +;[00:0E:6D:1A:3D:86] +;name = Nokia +;type = AG +;channel = 13 +;autoconnect = yes + +[00:0E:A1:01:49:AE] +name = AutoBlue +type = HS +channel = 2 +autoconnect = yes + +;[00:0A:D9:EB:FD:D8] +;name = P900 +;type = AG +;channel = 8 +;autoconnect = no