diff -Nru asterisk-1.2.14.org/channels/chan_bluetooth.c asterisk-1.2.14/channels/chan_bluetooth.c
--- asterisk-1.2.14.org/channels/chan_bluetooth.c	1970-01-01 01:00:00.000000000 +0100
+++ asterisk-1.2.14/channels/chan_bluetooth.c	2006-12-27 09:04:03.000000000 +0100
@@ -0,0 +1,3599 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Asterisk Bluetooth Channel
+ *
+ * Author: Theo Zourzouvillys <theo@adaptive-it.co.uk>
+ *
+ * Adaptive Linux Solutions <http://www.adaptive-it.co.uk>
+ *
+ * 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 <theo@adaptive-it.co.uk> 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:
+ *
+ *  <theo@adaptive-it.co.uk>
+ *
+ * ------------------------------------------------------------------
+ */
+
+/* ---------------------------------- */
+
+#include <stdio.h>
+#include <string.h>
+#include <asterisk/lock.h>
+#include <asterisk/utils.h>
+#include <asterisk/channel.h>
+#include <asterisk/config.h>
+#include <asterisk/logger.h>
+#include <asterisk/module.h>
+#include <asterisk/pbx.h>
+#include <asterisk/sched.h>
+#include <asterisk/options.h>
+#include <asterisk/cli.h>
+#include <asterisk/callerid.h>
+#include <asterisk/version.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <ctype.h>
+#include <endian.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+/* --- 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: 6526 $"
+
+/* ---------------------------------- */
+
+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 <asterisk/channel_pvt.h>
+#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_config_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=<HS/AG>\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 <device> sendcmd <cmd>\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 -Nru asterisk-1.2.14.org/channels/Makefile asterisk-1.2.14/channels/Makefile
--- asterisk-1.2.14.org/channels/Makefile	2006-08-17 23:57:19.000000000 +0200
+++ asterisk-1.2.14/channels/Makefile	2006-12-27 09:03:53.000000000 +0100
@@ -249,6 +249,13 @@
 #chan_modem.so : chan_modem.o
 #	$(CC) -rdynamic -shared -Xlinker -x -o $@ $<
 
+#
+# Asterisk Bluetooth Support
+# http://www.crazygreek.co.uk/content/chan_bluetooth
+#
+chan_bluetooth.so: chan_bluetooth.o
+	$(CC) $(SOLINK) -o $@ $< $(EXTRA_LDFLAGS) -lbluetooth
+
 install: all
 	for x in $(CHANNEL_LIBS); do $(INSTALL) -m 755 $$x $(DESTDIR)$(MODULES_DIR) ; done
 	if ! [ -f chan_iax.so ]; then rm -f $(DESTDIR)$(MODULES_DIR)/chan_iax.so ; fi
diff -Nru asterisk-1.2.14.org/configs/bluetooth.conf asterisk-1.2.14/configs/bluetooth.conf
--- asterisk-1.2.14.org/configs/bluetooth.conf	1970-01-01 01:00:00.000000000 +0100
+++ asterisk-1.2.14/configs/bluetooth.conf	2006-12-27 09:03:53.000000000 +0100
@@ -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
