Skip to content
Snippets Groups Projects
chan_mobile.c 122 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 1999 - 2006, Digium, Inc.
     *
     * Mark Spencer <markster@digium.com>
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    /*! 
     * \file
     * \brief Bluetooth Mobile Device channel driver
     *
     * \author Dave Bowerman <david.bowerman@gmail.com>
     *
     * \ingroup channel_drivers
     */
    
    
    Andrew Latham's avatar
    Andrew Latham committed
    /*! \li \ref chan_mobile.c uses the configuration file \ref chan_mobile.conf
     * \addtogroup configuration_file Configuration Files
     */
    
    /*!
     * \page chan_mobile.conf chan_mobile.conf
     * \verbinclude chan_mobile.conf.sample
     */
    
    
    /*** MODULEINFO
    	<depend>bluetooth</depend>
    	<defaultenabled>no</defaultenabled>
    
    	<support_level>extended</support_level>
    
     ***/
    
    #include "asterisk.h"
    
    #include <pthread.h>
    #include <signal.h>
    
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/hci.h>
    #include <bluetooth/hci_lib.h>
    #include <bluetooth/sdp.h>
    #include <bluetooth/sdp_lib.h>
    #include <bluetooth/rfcomm.h>
    #include <bluetooth/sco.h>
    #include <bluetooth/l2cap.h>
    
    #include "asterisk/compat.h"
    #include "asterisk/lock.h"
    #include "asterisk/channel.h"
    #include "asterisk/config.h"
    #include "asterisk/logger.h"
    #include "asterisk/module.h"
    #include "asterisk/pbx.h"
    #include "asterisk/options.h"
    #include "asterisk/utils.h"
    #include "asterisk/linkedlists.h"
    #include "asterisk/cli.h"
    #include "asterisk/devicestate.h"
    #include "asterisk/causes.h"
    #include "asterisk/dsp.h"
    #include "asterisk/app.h"
    #include "asterisk/manager.h"
    #include "asterisk/io.h"
    
    #include "asterisk/smoother.h"
    #include "asterisk/format_cache.h"
    
    #define MBL_CONFIG "chan_mobile.conf"
    #define MBL_CONFIG_OLD "mobile.conf"
    
    #define DEVICE_FRAME_FORMAT ast_format_slin
    
    #define CHANNEL_FRAME_SIZE 320
    
    static int discovery_interval = 60;			/* The device discovery interval, default 60 seconds. */
    static pthread_t discovery_thread = AST_PTHREADT_NULL;	/* The discovery thread */
    static sdp_session_t *sdp_session;
    
    AST_MUTEX_DEFINE_STATIC(unload_mutex);
    static int unloading_flag = 0;
    static inline int check_unloading(void);
    static inline void set_unloading(void);
    
    enum mbl_type {
    	MBL_TYPE_PHONE,
    	MBL_TYPE_HEADSET
    };
    
    struct adapter_pvt {
    	int dev_id;					/* device id */
    	int hci_socket;					/* device descriptor */
    	char id[31];					/* the 'name' from mobile.conf */
    	bdaddr_t addr;					/* adddress of adapter */
    	unsigned int inuse:1;				/* are we in use ? */
    	unsigned int alignment_detection:1;		/* do alignment detection on this adpater? */
    	struct io_context *io;				/*!< io context for audio connections */
    	struct io_context *accept_io;			/*!< io context for sco listener */
    	int *sco_id;					/*!< the io context id of the sco listener socket */
    	int sco_socket;					/*!< sco listener socket */
    	pthread_t sco_listener_thread;			/*!< sco listener thread */
    	AST_LIST_ENTRY(adapter_pvt) entry;
    };
    
    static AST_RWLIST_HEAD_STATIC(adapters, adapter_pvt);
    
    struct msg_queue_entry;
    struct hfp_pvt;
    struct mbl_pvt {
    	struct ast_channel *owner;			/* Channel we belong to, possibly NULL */
    	struct ast_frame fr;				/* "null" frame */
    	ast_mutex_t lock;				/*!< pvt lock */
    	/*! queue for messages we are expecting */
    	AST_LIST_HEAD_NOLOCK(msg_queue, msg_queue_entry) msg_queue;
    	enum mbl_type type;				/* Phone or Headset */
    	char id[31];					/* The id from mobile.conf */
    	int group;					/* group number for group dialling */
    	bdaddr_t addr;					/* address of device */
    	struct adapter_pvt *adapter;			/* the adapter we use */
    	char context[AST_MAX_CONTEXT];			/* the context for incoming calls */
    	struct hfp_pvt *hfp;				/*!< hfp pvt */
    	int rfcomm_port;				/* rfcomm port number */
    	int rfcomm_socket;				/* rfcomm socket descriptor */
    	char rfcomm_buf[256];
    	char io_buf[CHANNEL_FRAME_SIZE + AST_FRIENDLY_OFFSET];
    	struct ast_smoother *smoother;			/* our smoother, for making 48 byte frames */
    	int sco_socket;					/* sco socket descriptor */
    	pthread_t monitor_thread;			/* monitor thread handle */
    	int timeout;					/*!< used to set the timeout for rfcomm data (may be used in the future) */
    	unsigned int no_callsetup:1;
    	unsigned int has_sms:1;
    	unsigned int do_alignment_detection:1;
    	unsigned int alignment_detection_triggered:1;
    	unsigned int blackberry:1;
    	short alignment_samples[4];
    	int alignment_count;
    	int ring_sched_id;
    	struct ast_dsp *dsp;
    
    	struct ast_sched_context *sched;
    
    	int hangupcause;
    
    
    	/* flags */
    	unsigned int outgoing:1;	/*!< outgoing call */
    	unsigned int incoming:1;	/*!< incoming call */
    	unsigned int outgoing_sms:1;	/*!< outgoing sms */
    	unsigned int incoming_sms:1;	/*!< outgoing sms */
    	unsigned int needcallerid:1;	/*!< we need callerid */
    	unsigned int needchup:1;	/*!< we need to send a chup */
    	unsigned int needring:1;	/*!< we need to send a RING */
    
    	unsigned int answered:1;	/*!< we sent/received an answer */
    
    	unsigned int connected:1;	/*!< do we have an rfcomm connection to a device */
    
    	AST_LIST_ENTRY(mbl_pvt) entry;
    };
    
    static AST_RWLIST_HEAD_STATIC(devices, mbl_pvt);
    
    static int handle_response_ok(struct mbl_pvt *pvt, char *buf);
    static int handle_response_error(struct mbl_pvt *pvt, char *buf);
    static int handle_response_ciev(struct mbl_pvt *pvt, char *buf);
    static int handle_response_clip(struct mbl_pvt *pvt, char *buf);
    static int handle_response_ring(struct mbl_pvt *pvt, char *buf);
    static int handle_response_cmti(struct mbl_pvt *pvt, char *buf);
    static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf);
    
    static int handle_response_cusd(struct mbl_pvt *pvt, char *buf);
    
    static int handle_response_busy(struct mbl_pvt *pvt);
    static int handle_response_no_dialtone(struct mbl_pvt *pvt, char *buf);
    static int handle_response_no_carrier(struct mbl_pvt *pvt, char *buf);
    
    static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf);
    
    /* CLI stuff */
    static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
    static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
    static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
    
    static char *handle_cli_mobile_cusd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
    
    
    static struct ast_cli_entry mbl_cli[] = {
    	AST_CLI_DEFINE(handle_cli_mobile_show_devices, "Show Bluetooth Cell / Mobile devices"),
    	AST_CLI_DEFINE(handle_cli_mobile_search,       "Search for Bluetooth Cell / Mobile devices"),
    	AST_CLI_DEFINE(handle_cli_mobile_rfcomm,       "Send commands to the rfcomm port for debugging"),
    
    	AST_CLI_DEFINE(handle_cli_mobile_cusd,         "Send CUSD commands to the mobile"),
    
    };
    
    /* App stuff */
    static char *app_mblstatus = "MobileStatus";
    static char *mblstatus_synopsis = "MobileStatus(Device,Variable)";
    static char *mblstatus_desc =
    "MobileStatus(Device,Variable)\n"
    "  Device - Id of mobile device from mobile.conf\n"
    "  Variable - Variable to store status in will be 1-3.\n"
    "             In order, Disconnected, Connected & Free, Connected & Busy.\n";
    
    static char *app_mblsendsms = "MobileSendSMS";
    static char *mblsendsms_synopsis = "MobileSendSMS(Device,Dest,Message)";
    static char *mblsendsms_desc =
    "MobileSendSms(Device,Dest,Message)\n"
    "  Device - Id of device from mobile.conf\n"
    "  Dest - destination\n"
    "  Message - text of the message\n";
    
    static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num,
    
    		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor);
    
    static struct ast_channel *mbl_request(const char *type, struct ast_format_cap *cap,
    
    		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
    
    static int mbl_call(struct ast_channel *ast, const char *dest, int timeout);
    
    static int mbl_hangup(struct ast_channel *ast);
    static int mbl_answer(struct ast_channel *ast);
    static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
    static struct ast_frame *mbl_read(struct ast_channel *ast);
    static int mbl_write(struct ast_channel *ast, struct ast_frame *frame);
    static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
    
    static int mbl_devicestate(const char *data);
    
    
    static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen);
    
    static int mbl_queue_control(struct mbl_pvt *pvt, enum ast_control_frame_type control);
    static int mbl_queue_hangup(struct mbl_pvt *pvt);
    static int mbl_ast_hangup(struct mbl_pvt *pvt);
    
    static int mbl_has_service(struct mbl_pvt *pvt);
    
    
    static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel);
    static int rfcomm_write(int rsock, char *buf);
    static int rfcomm_write_full(int rsock, char *buf, size_t count);
    static int rfcomm_wait(int rsock, int *ms);
    static ssize_t rfcomm_read(int rsock, char *buf, size_t count);
    
    static int sco_connect(bdaddr_t src, bdaddr_t dst);
    static int sco_write(int s, char *buf, int len);
    static int sco_accept(int *id, int fd, short events, void *data);
    static int sco_bind(struct adapter_pvt *adapter);
    
    static void *do_sco_listen(void *data);
    static int sdp_search(char *addr, int profile);
    
    static int headset_send_ring(const void *data);
    
    /*
     * bluetooth handsfree profile helpers
     */
    
    #define HFP_HF_ECNR	(1 << 0)
    #define HFP_HF_CW	(1 << 1)
    #define HFP_HF_CID	(1 << 2)
    #define HFP_HF_VOICE	(1 << 3)
    #define HFP_HF_VOLUME	(1 << 4)
    #define HFP_HF_STATUS	(1 << 5)
    #define HFP_HF_CONTROL	(1 << 6)
    
    #define HFP_AG_CW	(1 << 0)
    #define HFP_AG_ECNR	(1 << 1)
    #define HFP_AG_VOICE	(1 << 2)
    #define HFP_AG_RING	(1 << 3)
    #define HFP_AG_TAG	(1 << 4)
    #define HFP_AG_REJECT	(1 << 5)
    #define HFP_AG_STATUS	(1 << 6)
    #define HFP_AG_CONTROL	(1 << 7)
    #define HFP_AG_ERRORS	(1 << 8)
    
    #define HFP_CIND_UNKNOWN	-1
    #define HFP_CIND_NONE		0
    #define HFP_CIND_SERVICE	1
    #define HFP_CIND_CALL		2
    #define HFP_CIND_CALLSETUP	3
    #define HFP_CIND_CALLHELD	4
    #define HFP_CIND_SIGNAL		5
    #define HFP_CIND_ROAM		6
    #define HFP_CIND_BATTCHG	7
    
    /* call indicator values */
    #define HFP_CIND_CALL_NONE	0
    #define HFP_CIND_CALL_ACTIVE	1
    
    /* callsetup indicator values */
    #define HFP_CIND_CALLSETUP_NONE		0
    #define HFP_CIND_CALLSETUP_INCOMING	1
    #define HFP_CIND_CALLSETUP_OUTGOING	2
    #define HFP_CIND_CALLSETUP_ALERTING	3
    
    
    /* service indicator values */
    #define HFP_CIND_SERVICE_NONE		0
    #define HFP_CIND_SERVICE_AVAILABLE	1
    
    
    /*!
     * \brief This struct holds HFP features that we support.
     */
    struct hfp_hf {
    	int ecnr:1;	/*!< echo-cancel/noise reduction */
    	int cw:1;	/*!< call waiting and three way calling */
    	int cid:1;	/*!< cli presentation (callier id) */
    	int voice:1;	/*!< voice recognition activation */
    	int volume:1;	/*!< remote volume control */
    	int status:1;	/*!< enhanced call status */
    	int control:1;	/*!< enhanced call control*/
    };
    
    /*!
     * \brief This struct holds HFP features the AG supports.
     */
    struct hfp_ag {
    	int cw:1;	/*!< three way calling */
    	int ecnr:1;	/*!< echo-cancel/noise reduction */
    	int voice:1;	/*!< voice recognition */
    	int ring:1;	/*!< in band ring tone capability */
    	int tag:1;	/*!< attach a number to a voice tag */
    	int reject:1;	/*!< ability to reject a call */
    	int status:1;	/*!< enhanced call status */
    	int control:1;	/*!< enhanced call control*/
    	int errors:1;	/*!< extended error result codes*/
    };
    
    /*!
     * \brief This struct holds mappings for indications.
     */
    struct hfp_cind {
    	int service;	/*!< whether we have service or not */
    	int call;	/*!< call state */
    	int callsetup;	/*!< bluetooth call setup indications */
    	int callheld;	/*!< bluetooth call hold indications */
    	int signal;	/*!< signal strength */
    	int roam;	/*!< roaming indicator */
    	int battchg;	/*!< battery charge indicator */
    };
    
    
    /*!
     * \brief This struct holds state information about the current hfp connection.
     */
    struct hfp_pvt {
    	struct mbl_pvt *owner;		/*!< the mbl_pvt struct that owns this struct */
    	int initialized:1;		/*!< whether a service level connection exists or not */
    	int nocallsetup:1;		/*!< whether we detected a callsetup indicator */
    	struct hfp_ag brsf;		/*!< the supported feature set of the AG */
    	int cind_index[16];		/*!< the cind/ciev index to name mapping for this AG */
    	int cind_state[16];		/*!< the cind/ciev state for this AG */
    	struct hfp_cind cind_map;	/*!< the cind name to index mapping for this AG */
    	int rsock;			/*!< our rfcomm socket */
    	int rport;			/*!< our rfcomm port */
    
    	int sent_alerting;		/*!< have we sent alerting? */
    
    };
    
    
    /* Our supported features.
     * we only support caller id
     */
    static struct hfp_hf hfp_our_brsf = {
    	.ecnr = 0,
    	.cw = 0,
    	.cid = 1,
    	.voice = 0,
    	.volume = 0,
    	.status = 0,
    	.control = 0,
    };
    
    
    static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value);
    static char *hfp_parse_clip(struct hfp_pvt *hfp, char *buf);
    static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf);
    static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text);
    static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf);
    static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf);
    static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf);
    
    static char *hfp_parse_cusd(struct hfp_pvt *hfp, char *buf);
    
    
    static int hfp_brsf2int(struct hfp_hf *hf);
    static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag);
    
    static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf);
    static int hfp_send_cind(struct hfp_pvt *hfp);
    static int hfp_send_cind_test(struct hfp_pvt *hfp);
    static int hfp_send_cmer(struct hfp_pvt *hfp, int status);
    static int hfp_send_clip(struct hfp_pvt *hfp, int status);
    static int hfp_send_vgs(struct hfp_pvt *hfp, int value);
    
    #if 0
    static int hfp_send_vgm(struct hfp_pvt *hfp, int value);
    #endif
    static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit);
    static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode);
    static int hfp_send_cnmi(struct hfp_pvt *hfp);
    static int hfp_send_cmgr(struct hfp_pvt *hfp, int index);
    static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number);
    static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message);
    static int hfp_send_chup(struct hfp_pvt *hfp);
    static int hfp_send_atd(struct hfp_pvt *hfp, const char *number);
    static int hfp_send_ata(struct hfp_pvt *hfp);
    
    static int hfp_send_cusd(struct hfp_pvt *hfp, const char *code);
    
    
    /*
     * bluetooth headset profile helpers
     */
    static int hsp_send_ok(int rsock);
    static int hsp_send_error(int rsock);
    static int hsp_send_vgs(int rsock, int gain);
    static int hsp_send_vgm(int rsock, int gain);
    static int hsp_send_ring(int rsock);
    
    
    /*
     * Hayes AT command helpers
     */
    typedef enum {
    	/* errors */
    	AT_PARSE_ERROR = -2,
    	AT_READ_ERROR = -1,
    	AT_UNKNOWN = 0,
    	/* at responses */
    	AT_OK,
    	AT_ERROR,
    	AT_RING,
    	AT_BRSF,
    	AT_CIND,
    	AT_CIEV,
    	AT_CLIP,
    	AT_CMTI,
    	AT_CMGR,
    	AT_SMS_PROMPT,
    	AT_CMS_ERROR,
    	/* at commands */
    	AT_A,
    	AT_D,
    	AT_CHUP,
    	AT_CKPD,
    	AT_CMGS,
    	AT_VGM,
    	AT_VGS,
    	AT_VTS,
    	AT_CMGF,
    	AT_CNMI,
    	AT_CMER,
    	AT_CIND_TEST,
    
    	AT_BUSY,
    	AT_NO_DIALTONE,
    	AT_NO_CARRIER,
    	AT_ECAM,
    
    } at_message_t;
    
    static int at_match_prefix(char *buf, char *prefix);
    static at_message_t at_read_full(int rsock, char *buf, size_t count);
    static inline const char *at_msg2str(at_message_t msg);
    
    struct msg_queue_entry {
    	at_message_t expected;
    	at_message_t response_to;
    	void *data;
    
    	AST_LIST_ENTRY(msg_queue_entry) entry;
    };
    
    static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to);
    static int msg_queue_push_data(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to, void *data);
    static struct msg_queue_entry *msg_queue_pop(struct mbl_pvt *pvt);
    static void msg_queue_free_and_pop(struct mbl_pvt *pvt);
    static void msg_queue_flush(struct mbl_pvt *pvt);
    static struct msg_queue_entry *msg_queue_head(struct mbl_pvt *pvt);
    
    /*
     * channel stuff
     */
    
    
    static struct ast_channel_tech mbl_tech = {
    
    	.type = "Mobile",
    	.description = "Bluetooth Mobile Device Channel Driver",
    	.requester = mbl_request,
    	.call = mbl_call,
    	.hangup = mbl_hangup,
    	.answer = mbl_answer,
    	.send_digit_end = mbl_digit_end,
    	.read = mbl_read,
    	.write = mbl_write,
    	.fixup = mbl_fixup,
    	.devicestate = mbl_devicestate
    };
    
    /* CLI Commands implementation */
    
    static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct mbl_pvt *pvt;
    	char bdaddr[18];
    	char group[6];
    
    
    #define FORMAT1 "%-15.15s %-17.17s %-5.5s %-15.15s %-9.9s %-10.10s %-3.3s\n"
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "mobile show devices";
    		e->usage =
    			"Usage: mobile show devices\n"
    			"       Shows the state of Bluetooth Cell / Mobile devices.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 3)
    		return CLI_SHOWUSAGE;
    
    	ast_cli(a->fd, FORMAT1, "ID", "Address", "Group", "Adapter", "Connected", "State", "SMS");
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		ast_mutex_lock(&pvt->lock);
    		ba2str(&pvt->addr, bdaddr);
    		snprintf(group, sizeof(group), "%d", pvt->group);
    		ast_cli(a->fd, FORMAT1,
    				pvt->id,
    				bdaddr,
    				group,
    				pvt->adapter->id,
    				pvt->connected ? "Yes" : "No",
    
    				(!pvt->connected) ? "None" : (pvt->owner) ? "Busy" : (pvt->outgoing_sms || pvt->incoming_sms) ? "SMS" : (mbl_has_service(pvt)) ? "Free" : "No Service",
    
    				(pvt->has_sms) ? "Yes" : "No"
    		       );
    		ast_mutex_unlock(&pvt->lock);
    	}
    	AST_RWLIST_UNLOCK(&devices);
    
    #undef FORMAT1
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct adapter_pvt *adapter;
    	inquiry_info *ii = NULL;
    	int max_rsp, num_rsp;
    	int len, flags;
    	int i, phport, hsport;
    	char addr[19] = {0};
    	char name[31] = {0};
    
    #define FORMAT1 "%-17.17s %-30.30s %-6.6s %-7.7s %-4.4s\n"
    #define FORMAT2 "%-17.17s %-30.30s %-6.6s %-7.7s %d\n"
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "mobile search";
    		e->usage =
    			"Usage: mobile search\n"
    			"       Searches for Bluetooth Cell / Mobile devices in range.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 2)
    		return CLI_SHOWUSAGE;
    
    	/* find a free adapter */
    	AST_RWLIST_RDLOCK(&adapters);
    	AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
    		if (!adapter->inuse)
    			break;
    	}
    	AST_RWLIST_UNLOCK(&adapters);
    
    	if (!adapter) {
    		ast_cli(a->fd, "All Bluetooth adapters are in use at this time.\n");
    		return CLI_SUCCESS;
    	}
    
    	len  = 8;
    	max_rsp = 255;
    	flags = IREQ_CACHE_FLUSH;
    
    
    	ii = ast_alloca(max_rsp * sizeof(inquiry_info));
    
    	num_rsp = hci_inquiry(adapter->dev_id, len, max_rsp, NULL, &ii, flags);
    	if (num_rsp > 0) {
    		ast_cli(a->fd, FORMAT1, "Address", "Name", "Usable", "Type", "Port");
    		for (i = 0; i < num_rsp; i++) {
    			ba2str(&(ii + i)->bdaddr, addr);
    			name[0] = 0x00;
    			if (hci_read_remote_name(adapter->hci_socket, &(ii + i)->bdaddr, sizeof(name) - 1, name, 0) < 0)
    				strcpy(name, "[unknown]");
    			phport = sdp_search(addr, HANDSFREE_AGW_PROFILE_ID);
    			if (!phport)
    				hsport = sdp_search(addr, HEADSET_PROFILE_ID);
    			else
    				hsport = 0;
    			ast_cli(a->fd, FORMAT2, addr, name, (phport > 0 || hsport > 0) ? "Yes" : "No",
    				(phport > 0) ? "Phone" : "Headset", (phport > 0) ? phport : hsport);
    		}
    	} else
    		ast_cli(a->fd, "No Bluetooth Cell / Mobile devices found.\n");
    
    #undef FORMAT1
    #undef FORMAT2
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	char buf[128];
    	struct mbl_pvt *pvt = NULL;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "mobile rfcomm";
    		e->usage =
    			"Usage: mobile rfcomm <device ID> <command>\n"
    			"       Send <command> to the rfcomm port on the device\n"
    			"       with the specified <device ID>.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 4)
    		return CLI_SHOWUSAGE;
    
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		if (!strcmp(pvt->id, a->argv[2]))
    			break;
    	}
    	AST_RWLIST_UNLOCK(&devices);
    
    	if (!pvt) {
    		ast_cli(a->fd, "Device %s not found.\n", a->argv[2]);
    		goto e_return;
    	}
    
    	ast_mutex_lock(&pvt->lock);
    	if (!pvt->connected) {
    		ast_cli(a->fd, "Device %s not connected.\n", a->argv[2]);
    		goto e_unlock_pvt;
    	}
    
    	snprintf(buf, sizeof(buf), "%s\r", a->argv[3]);
    	rfcomm_write(pvt->rfcomm_socket, buf);
    	msg_queue_push(pvt, AT_OK, AT_UNKNOWN);
    
    e_unlock_pvt:
    	ast_mutex_unlock(&pvt->lock);
    e_return:
    	return CLI_SUCCESS;
    }
    
    
    static char *handle_cli_mobile_cusd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	char buf[128];
    	struct mbl_pvt *pvt = NULL;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "mobile cusd";
    		e->usage =
    			"Usage: mobile cusd <device ID> <command>\n"
    			"       Send cusd <command> to the rfcomm port on the device\n"
    			"       with the specified <device ID>.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 4)
    		return CLI_SHOWUSAGE;
    
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		if (!strcmp(pvt->id, a->argv[2]))
    			break;
    	}
    	AST_RWLIST_UNLOCK(&devices);
    
    	if (!pvt) {
    		ast_cli(a->fd, "Device %s not found.\n", a->argv[2]);
    		goto e_return;
    	}
    
    	ast_mutex_lock(&pvt->lock);
    	if (!pvt->connected) {
    		ast_cli(a->fd, "Device %s not connected.\n", a->argv[2]);
    		goto e_unlock_pvt;
    	}
    
    	snprintf(buf, sizeof(buf), "%s", a->argv[3]);
    	if (hfp_send_cusd(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CUSD)) {
    		ast_cli(a->fd, "[%s] error sending CUSD\n", pvt->id);
    		goto e_unlock_pvt;
    	}
    
    e_unlock_pvt:
    	ast_mutex_unlock(&pvt->lock);
    e_return:
    	return CLI_SUCCESS;
    }
    
    
    /*
    
    	Dialplan applications implementation
    
    */
    
    static int mbl_status_exec(struct ast_channel *ast, const char *data)
    {
    
    	struct mbl_pvt *pvt;
    	char *parse;
    	int stat;
    	char status[2];
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(device);
    		AST_APP_ARG(variable);
    	);
    
    	if (ast_strlen_zero(data))
    		return -1;
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.device) || ast_strlen_zero(args.variable))
    		return -1;
    
    	stat = 1;
    
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		if (!strcmp(pvt->id, args.device))
    			break;
    	}
    	AST_RWLIST_UNLOCK(&devices);
    
    	if (pvt) {
    		ast_mutex_lock(&pvt->lock);
    		if (pvt->connected)
    			stat = 2;
    		if (pvt->owner)
    			stat = 3;
    		ast_mutex_unlock(&pvt->lock);
    	}
    
    	snprintf(status, sizeof(status), "%d", stat);
    	pbx_builtin_setvar_helper(ast, args.variable, status);
    
    	return 0;
    
    }
    
    static int mbl_sendsms_exec(struct ast_channel *ast, const char *data)
    {
    
    	struct mbl_pvt *pvt;
    	char *parse, *message;
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(device);
    		AST_APP_ARG(dest);
    		AST_APP_ARG(message);
    	);
    
    	if (ast_strlen_zero(data))
    		return -1;
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.device)) {
    		ast_log(LOG_ERROR,"NULL device for message -- SMS will not be sent.\n");
    		return -1;
    	}
    
    	if (ast_strlen_zero(args.dest)) {
    		ast_log(LOG_ERROR,"NULL destination for message -- SMS will not be sent.\n");
    		return -1;
    	}
    
    	if (ast_strlen_zero(args.message)) {
    		ast_log(LOG_ERROR,"NULL Message to be sent -- SMS will not be sent.\n");
    		return -1;
    	}
    
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		if (!strcmp(pvt->id, args.device))
    			break;
    	}
    	AST_RWLIST_UNLOCK(&devices);
    
    	if (!pvt) {
    		ast_log(LOG_ERROR,"Bluetooth device %s wasn't found in the list -- SMS will not be sent.\n", args.device);
    		goto e_return;
    	}
    
    	ast_mutex_lock(&pvt->lock);
    	if (!pvt->connected) {
    		ast_log(LOG_ERROR,"Bluetooth device %s wasn't connected -- SMS will not be sent.\n", args.device);
    		goto e_unlock_pvt;
    	}
    
    	if (!pvt->has_sms) {
    		ast_log(LOG_ERROR,"Bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n", args.device);
    		goto e_unlock_pvt;
    	}
    
    	message = ast_strdup(args.message);
    
    	if (hfp_send_cmgs(pvt->hfp, args.dest)
    		|| msg_queue_push_data(pvt, AT_SMS_PROMPT, AT_CMGS, message)) {
    
    		ast_log(LOG_ERROR, "[%s] problem sending SMS message\n", pvt->id);
    		goto e_free_message;
    	}
    
    	ast_mutex_unlock(&pvt->lock);
    
    	return 0;
    
    e_free_message:
    	ast_free(message);
    e_unlock_pvt:
    	ast_mutex_unlock(&pvt->lock);
    e_return:
    	return -1;
    }
    
    /*
    
    	Channel Driver callbacks
    
    */
    
    static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num,
    
    		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
    
    {
    	struct ast_channel *chn;
    
    	pvt->answered = 0;
    	pvt->alignment_count = 0;
    	pvt->alignment_detection_triggered = 0;
    	if (pvt->adapter->alignment_detection)
    		pvt->do_alignment_detection = 1;
    	else
    		pvt->do_alignment_detection = 0;
    
    	ast_smoother_reset(pvt->smoother, DEVICE_FRAME_SIZE);
    	ast_dsp_digitreset(pvt->dsp);
    
    	chn = ast_channel_alloc(1, state, cid_num, pvt->id, 0, 0, pvt->context,
    
    			"Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
    	if (!chn) {
    		goto e_return;
    	}
    
    
    	ast_channel_tech_set(chn, &mbl_tech);
    
    	ast_channel_nativeformats_set(chn, mbl_tech.capabilities);
    	ast_channel_set_rawreadformat(chn, DEVICE_FRAME_FORMAT);
    	ast_channel_set_rawwriteformat(chn, DEVICE_FRAME_FORMAT);
    	ast_channel_set_writeformat(chn, DEVICE_FRAME_FORMAT);
    	ast_channel_set_readformat(chn, DEVICE_FRAME_FORMAT);
    
    	ast_channel_tech_pvt_set(chn, pvt);
    
    		ast_channel_rings_set(chn, 1);
    
    	ast_channel_language_set(chn, "en");
    
    	pvt->owner = chn;
    
    	if (pvt->sco_socket != -1) {
    		ast_channel_set_fd(chn, 0, pvt->sco_socket);
    	}
    
    	ast_channel_unlock(chn);
    
    
    static struct ast_channel *mbl_request(const char *type, struct ast_format_cap *cap,
    
    		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
    
    {
    
    	struct ast_channel *chn = NULL;
    	struct mbl_pvt *pvt;
    	char *dest_dev = NULL;
    	char *dest_num = NULL;
    
    
    	if (!data) {
    		ast_log(LOG_WARNING, "Channel requested with no data\n");
    		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
    		return NULL;
    	}
    
    
    	if (ast_format_cap_iscompatible_format(cap, DEVICE_FRAME_FORMAT) == AST_FORMAT_CMP_NOT_EQUAL) {
    
    		struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
    
    		ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &codec_buf));
    
    		*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
    		return NULL;
    	}
    
    
    
    	dest_num = strchr(dest_dev, '/');
    	if (dest_num)
    		*dest_num++ = 0x00;
    
    	if (((dest_dev[0] == 'g') || (dest_dev[0] == 'G')) && ((dest_dev[1] >= '0') && (dest_dev[1] <= '9'))) {
    		group = atoi(&dest_dev[1]);
    	}
    
    	/* Find requested device and make sure it's connected. */
    	AST_RWLIST_RDLOCK(&devices);
    	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
    		if (group > -1 && pvt->group == group && pvt->connected && !pvt->owner) {
    
    			break;
    		} else if (!strcmp(pvt->id, dest_dev)) {
    			break;
    		}
    	}
    	AST_RWLIST_UNLOCK(&devices);
    	if (!pvt || !pvt->connected || pvt->owner) {
    		ast_log(LOG_WARNING, "Request to call on device %s which is not connected / already in use.\n", dest_dev);
    		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
    		return NULL;
    	}
    
    	if ((pvt->type == MBL_TYPE_PHONE) && !dest_num) {
    		ast_log(LOG_WARNING, "Can't determine destination number.\n");
    		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
    		return NULL;
    	}
    
    	ast_mutex_lock(&pvt->lock);
    
    	chn = mbl_new(AST_STATE_DOWN, pvt, NULL, assignedids, requestor);
    
    	ast_mutex_unlock(&pvt->lock);
    	if (!chn) {
    		ast_log(LOG_WARNING, "Unable to allocate channel structure.\n");
    		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
    		return NULL;
    	}
    
    	return chn;
    
    }
    
    
    static int mbl_call(struct ast_channel *ast, const char *dest, int timeout)
    
    	pvt = ast_channel_tech_pvt(ast);
    
    
    	if (pvt->type == MBL_TYPE_PHONE) {
    		dest_num = strchr(dest_dev, '/');
    		if (!dest_num) {
    			ast_log(LOG_WARNING, "Cant determine destination number.\n");
    			return -1;
    		}
    		*dest_num++ = 0x00;
    	}
    
    
    	if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) {
    
    		ast_log(LOG_WARNING, "mbl_call called on %s, neither down nor reserved\n", ast_channel_name(ast));
    
    	ast_debug(1, "Calling %s on %s\n", dest, ast_channel_name(ast));
    
    
    	ast_mutex_lock(&pvt->lock);
    	if (pvt->type == MBL_TYPE_PHONE) {
    		if (hfp_send_atd(pvt->hfp, dest_num)) {
    			ast_mutex_unlock(&pvt->lock);
    			ast_log(LOG_ERROR, "error sending ATD command on %s\n", pvt->id);
    			return -1;
    		}
    
    		pvt->hangupcause = 0;
    
    		pvt->needchup = 1;
    		msg_queue_push(pvt, AT_OK, AT_D);
    	} else {
    		if (hsp_send_ring(pvt->rfcomm_socket)) {
    			ast_log(LOG_ERROR, "[%s] error ringing device\n", pvt->id);
    			ast_mutex_unlock(&pvt->lock);
    			return -1;
    		}