[asterisk-addons-commits] qwell: trunk r384 - in /trunk: ./ build_tools/ configs/ doc/

asterisk-addons-commits at lists.digium.com asterisk-addons-commits at lists.digium.com
Thu May 17 08:49:04 MST 2007


Author: qwell
Date: Thu May 17 10:49:03 2007
New Revision: 384

URL: http://svn.digium.com/view/asterisk-addons?view=rev&rev=384
Log:
Add a new channel driver for mobile devices.

Currently only supports bluetooth, but may allow more technologies (such as USB) in the future.

Currently only supports libbluetooth/bluez which is Linux only.
I believe that there is BSD stack in the works, but it may not be compat with libbluetooth.


A *HUGE* thanks goes to David Bowerman for doing nearly all of the work on this.
I personally am incredibly impressed with his work, and am very thrilled to be adding this.


As always, please report bugs on http://bugs.digium.com

Please see documentation in doc/chan_mobile.txt.

Added:
    trunk/chan_mobile.c   (with props)
    trunk/configs/mobile.conf.sample   (with props)
    trunk/doc/chan_mobile.txt   (with props)
Modified:
    trunk/Makefile
    trunk/build_tools/menuselect-deps.in
    trunk/configure
    trunk/configure.ac
    trunk/makeopts.in
    trunk/menuselect-tree

Modified: trunk/Makefile
URL: http://svn.digium.com/view/asterisk-addons/trunk/Makefile?view=diff&rev=384&r1=383&r2=384
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Thu May 17 10:49:03 2007
@@ -49,7 +49,7 @@
 endif
 MODULES_DIR=$(ASTLIBDIR)/modules
 
-MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql
+MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql chan_mobile
 
 SELECTED_MODS:=$(patsubst %,%.so,$(filter-out $(MENUSELECT_ADDONS),$(MODS)))
 
@@ -143,6 +143,9 @@
 app_addon_sql_mysql.so: app_addon_sql_mysql.o
 	$(CC) $(SOLINK) -o $@ $< $(MYSQLCLIENT_LIB)
 
+chan_mobile.so: chan_mobile.o
+	$(CC) $(SOLINK) -o $@ $< $(BLUETOOTH_LIB)
+
 chan_ooh323.so:
 	@if [ ! -f asterisk-ooh323c/Makefile ] ; then \
 		cd asterisk-ooh323c && ./configure ; \

Modified: trunk/build_tools/menuselect-deps.in
URL: http://svn.digium.com/view/asterisk-addons/trunk/build_tools/menuselect-deps.in?view=diff&rev=384&r1=383&r2=384
==============================================================================
--- trunk/build_tools/menuselect-deps.in (original)
+++ trunk/build_tools/menuselect-deps.in Thu May 17 10:49:03 2007
@@ -1,2 +1,3 @@
+BLUETOOTH=@PBX_BLUETOOTH@
 MYSQLCLIENT=@PBX_MYSQLCLIENT@
 ASTERISK=@PBX_ASTERISK@

Added: trunk/chan_mobile.c
URL: http://svn.digium.com/view/asterisk-addons/trunk/chan_mobile.c?view=auto&rev=384
==============================================================================
--- trunk/chan_mobile.c (added)
+++ trunk/chan_mobile.c Thu May 17 10:49:03 2007
@@ -1,0 +1,1848 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster at 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 at gmail.com>
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+	<depend>bluetooth</depend>
+ ***/
+
+#include <asterisk.h>
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.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 <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 <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>
+
+#define AST_MODULE "chan_mobile"
+
+#define MBL_CONFIG "mobile.conf"
+
+static int prefformat = AST_FORMAT_SLINEAR;
+
+static int discovery_interval = 60;	/* The device discovery interval, default 60 seconds. */
+static int sco_socket;			/* This is global so it can be closed on module unload outside of the listener thread */
+static sdp_session_t *sdp_session;
+
+enum mbl_type {
+	MBL_TYPE_PHONE,
+	MBL_TYPE_HEADSET
+};
+
+enum mbl_state {
+	MBL_STATE_INIT = 0,
+	MBL_STATE_INIT1,
+	MBL_STATE_INIT2,
+	MBL_STATE_INIT3,
+	MBL_STATE_INIT4,
+	MBL_STATE_INIT5,
+	MBL_STATE_INIT6,
+	MBL_STATE_PREIDLE,
+	MBL_STATE_IDLE,
+	MBL_STATE_DIAL,
+	MBL_STATE_DIAL1,
+	MBL_STATE_OUTGOING,
+	MBL_STATE_RING,
+	MBL_STATE_RING2,
+	MBL_STATE_RING3,
+	MBL_STATE_INCOMING,
+	MBL_STATE_HANGUP,
+	MBL_STATE_INSMS,
+	MBL_STATE_OUTSMS,
+	MBL_STATE_OUTSMS1,
+	MBL_STATE_OUTSMS2
+};
+
+struct mbl_pvt {
+	struct ast_channel *owner;		/* Channel we belong to, possibly NULL */
+	struct ast_frame fr;			/* "null" frame */
+	enum mbl_type type;			/* Phone or Headset */
+	char id[31];				/* The id from mobile.conf */
+	char bdaddr[18];			/* the bdaddr of the device */
+	char context[AST_MAX_CONTEXT];		/* the context for incoming calls */
+	char connected;				/* is it connected? */
+	int rfcomm_port;			/* rfcomm port number */
+	int rfcomm_socket;			/* rfcomm socket descriptor */
+	char rfcomm_buf[256];
+	int sco_socket;				/* sco socket descriptor */
+	enum mbl_state state;			/* monitor thread current state */
+	pthread_t monitor_thread;		/* monitor thread handle */
+	char sco_in_buf[48 + AST_FRIENDLY_OFFSET];
+	char sco_out_buf[352];
+	char *sco_out_ptr;
+	int sco_out_len;
+	char dial_number[AST_MAX_EXTENSION];	/* number for the monitor thread to dial */
+	int dial_timeout;
+	char ciev_call_0[4];			/* dynamically build reponse strings */
+	char ciev_call_1[4];
+	char ciev_callsetup_0[4];
+	char ciev_callsetup_1[4];
+	char ciev_callsetup_2[4];
+	char ciev_callsetup_3[4];
+	char no_callsetup;
+	char has_sms;
+	char sms_txt[161];
+	struct ast_dsp *dsp;
+	struct ast_frame *dsp_fr;
+	int dtmf_skip;
+	int skip_frames;
+	char sent_answer;
+	char hangup_count;
+	AST_LIST_ENTRY(mbl_pvt) entry;
+};
+
+static AST_LIST_HEAD_STATIC(devices, mbl_pvt);
+
+/* The discovery thread */
+static pthread_t discovery_thread = AST_PTHREADT_NULL;
+/* The sco listener thread */
+static pthread_t sco_listener_thread = AST_PTHREADT_NULL;
+
+/* CLI stuff */
+static const char show_usage[] =
+"Usage: mobile show devices\n" 
+"       Shows the state of Bluetooth Cell / Mobile devices.\n";
+
+static const char search_usage[] =
+"Usage: mobile search\n" 
+"       Searches for Bluetooth Cell / Mobile devices in range.\n";
+
+static const char rfcomm_usage[] =
+"Usage: mobile rfcomm command\n" 
+"       Send command to the rfcomm port.\n";
+
+static int do_show_devices(int, int, char **);
+static int do_search_devices(int, int, char **);
+static int do_send_rfcomm(int, int, char **);
+
+static struct ast_cli_entry mbl_cli[] = {
+	{{"mobile", "show", "devices", NULL}, do_show_devices, "Show Bluetooth Cell / Mobile devices", show_usage},
+	{{"mobile", "search", NULL}, do_search_devices, "Search for Bluetooth Cell / Mobile devices", search_usage},
+	{{"mobile", "rfcomm", NULL}, do_send_rfcomm, "Send commands to the rfcomm port for debugging", rfcomm_usage}
+};
+
+/* 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_request(const char *type, int format, void *data, int *cause);
+static int mbl_call(struct ast_channel *ast, char *dest, int timeout);
+static int mbl_hangup(struct ast_channel *ast);
+static int mbl_answer(struct ast_channel *ast);
+static int mbl_digit_begin(struct ast_channel *ast, char digit);
+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(void *data);
+static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num);
+
+static int rfcomm_write(struct mbl_pvt *pvt, char *buf);
+static int rfcomm_read(struct mbl_pvt *pvt, char *buf, char flush, int timeout);
+static int sco_connect(char *bdaddr);
+static int sdp_search(char *addr, int profile);
+
+static const struct ast_channel_tech mbl_tech = {
+	.type = "Mobile",
+	.description = "Bluetooth Mobile Device Channel Driver",
+	.capabilities = AST_FORMAT_SLINEAR,
+	.requester = mbl_request,
+	.call = mbl_call,
+	.hangup = mbl_hangup,
+	.answer = mbl_answer,
+	.send_digit_begin = mbl_digit_begin,
+	.send_digit_end = mbl_digit_end,
+	.read = mbl_read,
+	.write = mbl_write,
+	.fixup = mbl_fixup,
+	.devicestate = mbl_devicestate
+};
+
+static int do_show_devices(int fd, int argc, char **argv)
+{
+
+	struct mbl_pvt *pvt;
+
+	#define FORMAT "%-15.15s %-17.17s %-9.9s %-5.5s %-3.3s\n"
+
+	ast_cli(fd, FORMAT, "ID", "Address", "Connected", "State", "SMS");
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		ast_cli(fd, FORMAT, pvt->id, pvt->bdaddr, pvt->connected?"Yes":"No", (pvt->state == MBL_STATE_IDLE)?"Free":(pvt->state < MBL_STATE_IDLE)?"Init":"Busy", (pvt->has_sms)?"Yes":"No");
+	}	
+
+	return RESULT_SUCCESS;
+
+}
+
+static int do_search_devices(int fd, int argc, char **argv)
+{
+
+	int hci_socket;
+	inquiry_info *ii = NULL;
+	int max_rsp, num_rsp;
+	int dev_id, len, flags;
+	int i, phport, hsport;
+	char addr[19] = {0};
+	char name[31] = {0};
+
+	#define FORMAT2 "%-17.17s %-30.30s %-6.6s %-7.7s %-4.4s\n"
+	#define FORMAT3 "%-17.17s %-30.30s %-6.6s %-7.7s %d\n"
+
+	dev_id = hci_get_route(NULL);
+	hci_socket = hci_open_dev(dev_id);
+	len  = 8;
+	max_rsp = 255;
+	flags = IREQ_CACHE_FLUSH;
+
+	ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
+	num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
+	if (num_rsp > 0) {
+		ast_cli(fd, FORMAT2, "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(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(fd, FORMAT3, addr, name, (phport > 0 || hsport > 0)?"Yes":"No", (phport > 0)?"Phone":"Headset", (phport > 0)?phport:hsport);
+		}
+	} else
+		ast_cli(fd, "No Bluetooth Cell / Mobile devices found.\n");
+
+	free(ii);
+
+	hci_close_dev(hci_socket);
+
+	return RESULT_SUCCESS;
+
+}
+
+static int do_send_rfcomm(int fd, int argc, char **argv)
+{
+
+	struct mbl_pvt *pvt;
+	char buf[128];
+
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		if (!strcmp(pvt->id, argv[2]))
+			break;
+	}
+
+	if (!pvt || !pvt->connected) {
+		sprintf(buf, "Device %s not found.\n", argv[2]);
+		ast_cli(fd, buf);
+		return RESULT_SUCCESS;
+	}
+
+	sprintf(buf, "%s\r", argv[3]);
+	rfcomm_write(pvt, buf);
+
+	return RESULT_SUCCESS;
+
+}
+
+static int mbl_status_exec(struct ast_channel *ast, void *data)
+{
+
+	struct mbl_pvt *pvt;
+	char *args = NULL, *device = NULL, *variable = NULL;
+	int stat;
+	char status[2];
+
+	if (!data)
+		return -1;
+
+	args = ast_strdupa((char *)data);
+	device = strsep(&args, "|");
+	if (device && (device[0] != 0x00)) {
+		variable = args;
+	} else
+		return -1;
+
+	stat = 1;
+
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		if (!strcmp(pvt->id, device))
+			break;
+	}
+
+	if (pvt) {
+		if (pvt->connected)
+			stat = 2;
+		if (pvt->owner)
+			stat = 3;
+	}
+
+	sprintf(status, "%d", stat);
+	pbx_builtin_setvar_helper(ast, variable, status);
+
+	return 0;
+
+}
+
+static int mbl_sendsms_exec(struct ast_channel *ast, void *data)
+{
+
+	struct mbl_pvt *pvt;
+	char *args = NULL, *device = NULL, *dest = NULL, *message = NULL;
+
+	if (!data)
+		return -1;
+
+	args = ast_strdupa((char *)data);
+	device = strsep(&args, "|");
+	if (device && (device[0] != 0x00)) {
+		dest = strsep(&args, "|");
+		if (dest && (dest[0] != 0x00)) {
+			message = args;
+			if (!message || (message[0] == 0x00))
+				return -1;
+		} else
+			return -1;
+	} else
+		return -1;
+
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		if (!strcmp(pvt->id, device))
+			break;
+	}
+
+	if (!pvt)
+		return -1;
+	if (!pvt->connected)
+		return -1;
+	if (!pvt->has_sms)
+		return -1;
+	if (pvt->state != MBL_STATE_IDLE)
+		return -1;
+
+	strcpy(pvt->dial_number, dest);
+	memset(pvt->sms_txt, 0x0, sizeof(pvt->sms_txt));
+	strncpy(pvt->sms_txt, message, 160);
+	pvt->state = MBL_STATE_OUTSMS;
+
+	return 0;
+
+}
+
+static struct ast_channel *mbl_request(const char *type, int format, void *data, int *cause)
+{
+
+	struct ast_channel *chn = NULL;
+	struct mbl_pvt *pvt;
+	char *dest_dev = NULL;
+	char *dest_num = NULL;
+	int oldformat;
+
+	if (!data) {
+		ast_log(LOG_WARNING, "Channel requested with no data\n");
+		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+		return NULL;
+	}
+
+	oldformat = format;
+	format &= (AST_FORMAT_SLINEAR);
+	if (!format) {
+		ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+		*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
+		return NULL;
+	}
+
+	dest_dev = ast_strdupa((char *)data);
+
+	dest_num = strchr(dest_dev, '/');
+	if (dest_num)
+		*dest_num++ = 0x00;
+
+	/* Find requested device and make sure its connected. */
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		if (!strcmp(pvt->id, dest_dev)) {
+			break;
+		}
+	}
+	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, "Cant determine destination number.\n");
+		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+		return NULL;
+	}
+
+	pvt->sco_out_ptr = pvt->sco_out_buf;
+	pvt->sco_out_len = 0;
+
+	chn = mbl_new(AST_STATE_DOWN, pvt, NULL);
+	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, char *dest, int timeout)
+{
+
+	struct mbl_pvt *pvt;
+	char *dest_dev = NULL;
+	char *dest_num = NULL;
+
+	dest_dev = ast_strdupa((char *)dest);
+
+	pvt = ast->tech_pvt;
+
+	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->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+		ast_log(LOG_WARNING, "mbl_call called on %s, neither down nor reserved\n", ast->name);
+		return -1;
+	}
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "Calling %s on %s\n", dest, ast->name);
+
+	if (pvt->type == MBL_TYPE_PHONE) {
+		ast_copy_string(pvt->dial_number, dest_num, sizeof(pvt->dial_number));
+		pvt->state = MBL_STATE_DIAL;
+		pvt->dial_timeout = (timeout == 0) ? 30 : timeout;
+	} else {
+		pvt->state = MBL_STATE_RING;
+	}
+
+
+	return 0;
+
+}
+
+static int mbl_hangup(struct ast_channel *ast)
+{
+
+	struct mbl_pvt *pvt;
+
+	if (!ast->tech_pvt) {
+		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+		return 0;
+	}
+	pvt = ast->tech_pvt;
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "Hanging up device %s.\n", pvt->id);
+
+	ast_channel_lock(ast);
+	ast->fds[0] = -1;
+	ast_channel_unlock(ast);
+
+	if (pvt->type == MBL_TYPE_HEADSET && pvt->sco_socket != -1) {
+		close(pvt->sco_socket);
+		pvt->sco_socket = -1;
+	}
+
+	if ((pvt->state == MBL_STATE_INCOMING || pvt->state == MBL_STATE_OUTGOING || pvt->state == MBL_STATE_DIAL1 || pvt->state == MBL_STATE_RING3) && pvt->type == MBL_TYPE_PHONE) {
+		rfcomm_write(pvt, "AT+CHUP\r");
+		pvt->state = MBL_STATE_HANGUP;
+		pvt->hangup_count = 0;
+	} else
+		pvt->state = MBL_STATE_IDLE;
+
+	pvt->owner = NULL;
+	ast->tech_pvt = NULL;
+	ast_setstate(ast, AST_STATE_DOWN);
+
+	return 0;
+
+}
+
+static int mbl_answer(struct ast_channel *ast)
+{
+
+	struct mbl_pvt *pvt;
+
+	pvt = ast->tech_pvt;
+
+	rfcomm_write(pvt, "ATA\r");
+
+	ast_setstate(ast, AST_STATE_UP);
+
+	pvt->sent_answer = 1;
+
+	return 0;
+
+}
+
+static int mbl_digit_begin(struct ast_channel *chan, char digit)
+{
+
+	return 0;
+
+}
+
+static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+
+	struct mbl_pvt *pvt;
+	char buf[11];
+	
+	pvt = ast->tech_pvt;
+
+	if (pvt->type == MBL_TYPE_HEADSET)
+		return 0;
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "Dialed %c\n", digit);
+
+	switch(digit) {
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	case '*':
+	case '#':
+		sprintf(buf, "AT+VTS=%c\r", digit);
+		rfcomm_write(pvt, buf);
+		break;
+	default:
+		ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit);
+		return -1;
+	}
+
+	return 0;
+
+}
+
+/*
+
+	The SCO protocol basically delivers audio in 48 byte 'frames' in slin format.
+	Here we just package these into an ast_frame and return them.
+	The SCO connection from the device to Asterisk happens asynchronously, so it is feasible
+	that Asterisk will call mbl_read() before the device has connected. In that case we just return
+	a null frame.
+
+*/
+
+static struct ast_frame *mbl_read(struct ast_channel *ast)
+{
+
+	struct mbl_pvt *pvt = ast->tech_pvt;
+	int r;
+	struct ast_frame *f;
+
+	if (!pvt->owner)
+		return &ast_null_frame;
+
+	if (pvt->state == MBL_STATE_HANGUP)
+		return &ast_null_frame;
+
+	if (pvt->sco_socket == -1)
+		return &ast_null_frame;
+
+	pvt->fr.frametype = AST_FRAME_VOICE;
+	pvt->fr.subclass = AST_FORMAT_SLINEAR;
+	pvt->fr.data = pvt->sco_in_buf + AST_FRIENDLY_OFFSET;
+
+	if ((r = read(pvt->sco_socket, pvt->fr.data, 48)) == 48) {
+		if (pvt->skip_frames == 0) {
+			f = ast_dsp_process(0, pvt->dsp, &pvt->fr);
+			if (f && (f->frametype == AST_FRAME_DTMF_END)) {
+				pvt->fr.frametype = AST_FRAME_DTMF_END;
+				pvt->fr.subclass = f->subclass;
+				pvt->skip_frames = pvt->dtmf_skip;
+				if (option_debug)
+					ast_log(LOG_DEBUG, "DTMF digit %c detected.\n", pvt->fr.subclass);
+			}
+			return &pvt->fr;
+		} else
+			pvt->skip_frames--;
+	} else if (r == -1) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "mbl_read() read error %d.\n", errno);
+		close(pvt->sco_socket);
+		pvt->sco_socket = -1;
+		ast_channel_lock(ast);
+		ast->fds[0] = -1;
+		ast_channel_unlock(ast);
+	} else {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "mbl_read() read short frame. (%d)\n", r);
+	}
+
+	return &ast_null_frame;
+
+}
+
+/*
+
+	We need to deliver 48 byte 'frames' of slin format audio to the device.
+	mbl_write() handles this by buffering short frames until the next time we are called.
+
+*/
+
+static int mbl_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+
+	struct mbl_pvt *pvt = ast->tech_pvt;
+	int num_frames, i, r;
+	char *pfr;
+
+	if (frame->frametype != AST_FRAME_VOICE)
+		return 0;
+
+	if (pvt->sco_socket == -1)
+		return 0;
+
+	if (pvt->state == MBL_STATE_HANGUP)
+		return 0;
+
+	if (frame->datalen > sizeof(pvt->sco_out_buf) - pvt->sco_out_len) {
+		frame->datalen = sizeof(pvt->sco_out_buf) - pvt->sco_out_len;
+		if (option_debug) {
+			ast_log(LOG_DEBUG, "Overrun on sco_out_buf detected.\n");
+		}
+	}
+
+	memmove(pvt->sco_out_ptr, frame->data, frame->datalen);
+	pvt->sco_out_len += frame->datalen;
+	num_frames = pvt->sco_out_len / 48;
+
+	pfr = pvt->sco_out_buf;
+	for (i=0; i<num_frames; i++) {
+		if ((r = write(pvt->sco_socket, pfr, 48)) == -1) {
+			close(pvt->sco_socket);
+			pvt->sco_socket = -1;
+		}
+		pfr += 48;
+	}
+
+	pvt->sco_out_len = pvt->sco_out_len - (num_frames * 48);
+	memmove(pvt->sco_out_buf, pfr, pvt->sco_out_len);
+	pvt->sco_out_ptr = pvt->sco_out_buf + pvt->sco_out_len; 
+
+	return 0;
+
+}
+
+static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+
+	struct mbl_pvt *pvt = oldchan->tech_pvt;
+
+	if (pvt && pvt->owner == oldchan)
+		pvt->owner = newchan;
+
+	return 0;
+
+}
+
+static int mbl_devicestate(void *data)
+{
+
+	char *device;
+	int res = AST_DEVICE_INVALID;
+	struct mbl_pvt *pvt;
+
+	device = ast_strdupa(S_OR(data, ""));
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "Checking device state for device %s\n", device);
+
+	AST_LIST_TRAVERSE(&devices, pvt, entry) {
+		if (!strcmp(pvt->id, device))
+			break;
+	}
+
+	if (pvt) {
+		if (pvt->connected) {
+			if (pvt->owner)
+				res = AST_DEVICE_INUSE;
+			else
+				res = AST_DEVICE_NOT_INUSE;
+		}
+	}
+
+	return res;
+
+}
+
+static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num)
+{
+
+	struct ast_channel *chn;
+
+	chn = ast_channel_alloc(1, state, 0, 0, 0, 0, 0, 0, "Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
+	if (chn) {
+		chn->tech = &mbl_tech;
+		chn->nativeformats = prefformat;
+		chn->rawreadformat = prefformat;
+		chn->rawwriteformat = prefformat;
+		chn->writeformat = prefformat;
+		chn->readformat = prefformat;
+		chn->readq.first = NULL;
+		pvt->fr.frametype = AST_FRAME_VOICE;
+		pvt->fr.subclass = AST_FORMAT_SLINEAR;
+		pvt->fr.datalen = 48;
+		pvt->fr.samples = 24;
+		pvt->fr.src = "Mobile";
+		pvt->fr.offset = AST_FRIENDLY_OFFSET;
+		pvt->fr.mallocd = 0;
+		pvt->fr.delivery.tv_sec = 0;
+		pvt->fr.delivery.tv_usec = 0;
+		pvt->fr.data = pvt->sco_in_buf + AST_FRIENDLY_OFFSET;
+		chn->tech_pvt = pvt;
+		if (state == AST_STATE_RING)
+			chn->rings = 1;
+		ast_copy_string(chn->context, pvt->context, sizeof(chn->context));
+		ast_copy_string(chn->exten, "s", sizeof(chn->exten));
+		ast_string_field_set(chn, language, "en");
+		if (cid_num)
+			chn->cid.cid_num = ast_strdup(cid_num);
+		chn->cid.cid_name = ast_strdup(pvt->id);
+		pvt->owner = chn;
+
+	}
+
+	return chn;
+
+}
+
+static int rfcomm_connect(char *bdaddr, int remote_channel) {
+
+	bdaddr_t dst;
+	struct sockaddr_rc addr;
+	int s;
+
+	str2ba(bdaddr, &dst);
+
+	if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "socket() failed (%d).\n", errno);
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.rc_family = AF_BLUETOOTH;
+	bacpy(&addr.rc_bdaddr, &dst);
+	addr.rc_channel = remote_channel;
+	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "connect() failed (%d).\n", errno);
+		close(s);
+		return -1;
+	}
+
+	return s;
+
+}
+
+static int rfcomm_write(struct mbl_pvt *pvt, char *buf)
+{
+
+	char *p;
+	ssize_t num_write;
+	int len;
+
+	if (option_debug)
+		ast_log(LOG_DEBUG, "rfcomm_write() (%s) [%s]\n", pvt->id, buf);
+	len = strlen(buf);
+	p = buf;
+	while (len > 0) {
+		if ((num_write = write(pvt->rfcomm_socket, p, len)) == -1) {
+			ast_log(LOG_DEBUG, "rfcomm_write() error [%d]\n", errno);
+			return 0;
+		}
+		len -= num_write;
+		p += num_write;
+	}
+
+	return 1;
+
+}
+
+/*
+
+	Here we need to return complete '\r' terminated single responses to the devices monitor thread, or
+	a timeout if nothing is available.
+	The rfcomm connection to the device is asynchronous, so there is no guarantee that responses will
+	be returned in a single read() call. We handle this by buffering the input and returning one response
+	per call, or a timeout if nothing is available.
+
+*/
+
+static int rfcomm_read(struct mbl_pvt *pvt, char *buf, char flush, int timeout)
+{
+
+	int sel, rlen, slen;
+	fd_set rfds;
+	struct timeval tv;
+	char *p;
+
+	if (!flush) {
+		if ((p = strchr(pvt->rfcomm_buf, '\r'))) {
+			*p++ = 0x00;
+			if (*p == '\n')
+				p++;
+			memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
+			*(buf + strlen(pvt->rfcomm_buf)) = 0x00;
+			memmove(pvt->rfcomm_buf, p, strlen(p));
+			*(pvt->rfcomm_buf+strlen(p)) = 0x00;
+			return 1;
+		}
+	} else {
+		pvt->rfcomm_buf[0] = 0x00;
+	}
+
+	FD_ZERO(&rfds);
+	FD_SET(pvt->rfcomm_socket, &rfds);
+
+	tv.tv_sec = timeout;
+	tv.tv_usec = 0;
+
+	if ((sel = select(pvt->rfcomm_socket + 1, &rfds, NULL, NULL, &tv)) > 0) {
+		if (FD_ISSET(pvt->rfcomm_socket, &rfds)) {
+			slen = strlen(pvt->rfcomm_buf);
+			rlen = read(pvt->rfcomm_socket, pvt->rfcomm_buf + slen, sizeof(pvt->rfcomm_buf) - slen - 1);
+			if (rlen > 0) {
+				pvt->rfcomm_buf[slen+rlen] = 0x00;
+				if ((p = strchr(pvt->rfcomm_buf, '\r'))) {
+					*p++ = 0x00;
+					if (*p == '\n')
+						p++;
+					memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
+					*(buf + strlen(pvt->rfcomm_buf)) = 0x00;
+					memmove(pvt->rfcomm_buf, p, strlen(p));
+					*(pvt->rfcomm_buf+strlen(p)) = 0x00;
+					return 1;
+				}
+			} else
+				return rlen;
+		}
+	} else if (sel == 0) { /* timeout */
+		return 0;
+	}
+
+	return 1;
+
+}
+
+static int sco_connect(char *bdaddr)
+{
+
+	bdaddr_t dst;
+	struct sockaddr_sco addr;
+	int s;
+
+	str2ba(bdaddr, &dst);
+
+	if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "socket() failed (%d).\n", errno);
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sco_family = AF_BLUETOOTH;
+	bacpy(&addr.sco_bdaddr, &dst);
+
+	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "sco connect() failed (%d).\n", errno);
+		close(s);
+		return -1;
+	}
+
+	return s;
+
+}
+
+/*
+
+	sdp_search() performs a service discovery on the given device to determine whether
+	or not it supports the Handsfree Profile.
+
+*/
+
+static int sdp_search(char *addr, int profile)
+{
+
+	sdp_session_t *session = 0;
+	bdaddr_t bdaddr;
+	uuid_t svc_uuid;
+	uint32_t range = 0x0000ffff;
+	sdp_list_t *response_list, *search_list, *attrid_list;
+	int status, port;
+	sdp_list_t *proto_list;
+	sdp_record_t *sdprec;
+
+	str2ba(addr, &bdaddr);
+	port = 0;
+	session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
+	if (!session) {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "sdp_connect() failed on device %s.\n", addr);
+		return 0;
+	}
+
+	sdp_uuid32_create(&svc_uuid, profile);
+	search_list = sdp_list_append(0, &svc_uuid);
+	attrid_list = sdp_list_append(0, &range);
+	response_list = 0x00;
+	status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
+	if (status == 0) {
+		if (response_list) {
+			sdprec = (sdp_record_t *) response_list->data;
+			proto_list = 0x00;
+			if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
+				port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
+				sdp_list_free(proto_list, 0);
+			}
+			sdp_record_free(sdprec);
+			sdp_list_free(response_list, 0);
+		} else {
+			if (option_debug)
+				ast_log(LOG_DEBUG, "No responses returned for device %s.\n", addr);
+		}
+	} else {
+		if (option_debug)
+			ast_log(LOG_DEBUG, "sdp_service_search_attr_req() failed on device %s.\n", addr);
+	}
+
+	sdp_list_free(search_list, 0);
+	sdp_list_free(attrid_list, 0);
+	sdp_close(session);
+
+	return port;
+
+}
+
+/*
+
+	sdp_register()
+
+	Register GENERIC_AUDIO & HEADSET with the SDP daemon on the Asterisk Box.
+	This assists connections to phones/pda's with dud bluetooth stacks like the IMate Jasjam
+	and some Nokia's
+
+*/
+
+static sdp_session_t *sdp_register(void)
+{
+
+	uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
+	uint8_t rfcomm_channel = 1;
+	const char *service_name = "Asterisk PABX";
+	const char *service_dsc = "Asterisk PABX";
+	const char *service_prov = "Asterisk";
+
+	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
+	sdp_list_t  *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
+	sdp_data_t *channel = 0;
+
+	int err = 0;
+	sdp_session_t *session = 0;
+
+	sdp_record_t *record = sdp_record_alloc();
+
+	sdp_uuid128_create(&svc_uuid, &service_uuid_int);
+	sdp_set_service_id(record, svc_uuid);
+
+	sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
+	sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);
+
+	svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
+	svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
+	sdp_set_service_classes(record, svc_uuid_list);
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root_list = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups( record, root_list );
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	l2cap_list = sdp_list_append(0, &l2cap_uuid);
+	proto_list = sdp_list_append(0, l2cap_list);
+
+	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+	channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
+	rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
+	sdp_list_append(rfcomm_list, channel);
+	sdp_list_append(proto_list, rfcomm_list);
+
+	access_proto_list = sdp_list_append(0, proto_list);
+	sdp_set_access_protos(record, access_proto_list);
+
+	sdp_set_info_attr(record, service_name, service_prov, service_dsc);
+
+	session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
+	err = sdp_record_register(session, record, 0);
+
+	sdp_data_free(channel);
+	sdp_list_free(rfcomm_list, 0);
+	sdp_list_free(root_list, 0);
+	sdp_list_free(access_proto_list, 0);
+	sdp_list_free(svc_uuid_list, 0);
+
+	return session;
+
+}
+
+/*
+
+	Phone Monitor Thread
+
+	This thread is spun once a phone device is discovered and considered capable of being used, i.e. supports Handsfree Profile,
+	and its configured in mobile.conf.
+	The thread lives for the lifetime of the bluetooth connection, and handles the 'control' side of all calls.
+
+*/
+
+static void *do_monitor_phone(void *data)
+{
+
+	struct mbl_pvt *pvt = (struct mbl_pvt *)data;
+	struct ast_channel *chn;
+	char monitor = 1;
+	char buf[256];
+	char cid_num[AST_MAX_EXTENSION], *pcids, *pcide;
+	int s, t, i, smsi;
+	int group, group2;
+	int callp = 0, callsetupp;
+	char brsf, nsmode, *p, *p1;
+	char sms_src[13];
+	char sms_txt[161];
+
+	brsf = nsmode = 0;
+
+	if (!rfcomm_write(pvt, "AT+BRSF=4\r"))
+		monitor = 0;
+
+	while (monitor) {
+
+		if (pvt->state == MBL_STATE_DIAL1)
+			t = pvt->dial_timeout;
+		else if (pvt->state == MBL_STATE_HANGUP)
+			t = 2;
+		else if (pvt->state == MBL_STATE_OUTSMS1)
+			t = 2;
+		else if (pvt->state == MBL_STATE_OUTSMS2)
+			t = 10;
+		else
+			t = 1;
+
+		s = rfcomm_read(pvt, buf, 0, t);
+
+		if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
+			if (option_debug)
+				ast_log(LOG_DEBUG, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
+			switch (pvt->state) {
+			case MBL_STATE_INIT:
+				if (strstr(buf, "+BRSF:")) {
+					brsf = 1;
+				} else if (strstr(buf, "ERROR") && !nsmode) { /* Hmmm, Non-Standard Phone, just continue */
+					rfcomm_write(pvt, "AT+CIND=?\r");
+					pvt->state++;
+					nsmode = 1;
+				} else if (strstr(buf, "OK") && brsf) {
+					rfcomm_write(pvt, "AT+CIND=?\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT1:
+				if (strstr(buf, "+CIND:")) {
+					group = callp = callsetupp = 0;
+					group2 = 1;
+					for (i=0; i<strlen(buf); i++) {
+						if (buf[i] == '(')
+							group++;
+						if (buf[i] == ')') {
+							group--;
+							if (group == 0)
+								group2++;
+						}
+						if (strstr(buf+i, "\"call\""))
+							callp = group2;	
+						if (strstr(buf+i, "\"call_setup\""))
+							callsetupp = group2;
+						if (strstr(buf+i, "\"callsetup\""))
+							callsetupp = group2;
+					}
+					sprintf(pvt->ciev_call_0, "%d,0", callp);
+					sprintf(pvt->ciev_call_1, "%d,1", callp);
+					sprintf(pvt->ciev_callsetup_0, "%d,0", callsetupp);
+					sprintf(pvt->ciev_callsetup_1, "%d,1", callsetupp);
+					sprintf(pvt->ciev_callsetup_2, "%d,2", callsetupp);
+					sprintf(pvt->ciev_callsetup_3, "%d,3", callsetupp);
+					if (callsetupp == 0) /* This phone has no call setup indication!! ... */
+						pvt->no_callsetup = 1;
+					if (option_debug)
+						ast_log(LOG_DEBUG, "CIEV_CALL=%d CIEV_CALLSETUP=%d\n", callp, callsetupp);
+				}
+				if (strstr(buf, "OK")) {
+					rfcomm_write(pvt, "AT+CIND?\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT2:
+				if ((p = strstr(buf, "+CIND:"))) {
+					p += 5;
+					if (*(p+(callp*2)) == '1') {
+						ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
+						monitor = 0;
+					}
+				} else if (strstr(buf, "OK")) {
+					rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT3:
+				if (strstr(buf, "OK")) {
+					rfcomm_write(pvt, "AT+CLIP=1\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT4:
+				if (strstr(buf, "OK")) {
+					rfcomm_write(pvt, "AT+CMGF=1\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT5:
+				if (strstr(buf, "ERROR")) { /* No SMS Support ! */
+					pvt->state = MBL_STATE_PREIDLE;
+				} else if (strstr(buf, "OK")) {
+					rfcomm_write(pvt, "AT+CNMI=2,1,0,1,0\r");
+					pvt->state++;
+				}
+				break;
+			case MBL_STATE_INIT6:
+				if (strstr(buf, "OK")) { /* We have SMS Support */
+					pvt->has_sms = 1;
+					pvt->state = MBL_STATE_PREIDLE;
+				} else if (strstr(buf, "ERROR")) {
+					pvt->has_sms = 0;
+					pvt->state = MBL_STATE_PREIDLE;
+				}
+				break;
+			case MBL_STATE_PREIDLE: /* Nothing handled here, wait for timeout, then off we go... */
+				break;
+			case MBL_STATE_IDLE:
+				if (option_debug)
+					ast_log(LOG_DEBUG, "Device %s %s [%s]\n", pvt->bdaddr, pvt->id, buf);
+				if (strstr(buf, "RING")) {
+					pvt->state = MBL_STATE_RING;
+				} else if (strstr(buf, "+CIEV:")) {
+					if (strstr(buf, pvt->ciev_callsetup_3)) {	/* User has dialed out on handset */
+						monitor = 0;				/* We disconnect now, as he is    */
+					}						/* probably leaving BT range...   */
+				}
+				break;
+			case MBL_STATE_DIAL: /* Nothing handled here, we need to wait for a timeout */
+				break;
+			case MBL_STATE_DIAL1:
+				if (strstr(buf, "OK")) {
+					if (pvt->no_callsetup) {
+						ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
+					} else {
+						ast_setstate(pvt->owner, AST_STATE_RINGING);
+					}
+					pvt->state = MBL_STATE_OUTGOING;
+				}
+				break;
+			case MBL_STATE_OUTGOING:
+				if (strstr(buf, "+CIEV")) {
+					if (strstr(buf, pvt->ciev_call_0)) { 				/* call was hung up */
+						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
+					} else if (strstr(buf, pvt->ciev_callsetup_3)) { 		/* b-party ringing */
+						ast_queue_control(pvt->owner, AST_CONTROL_RINGING);
+					} else if (strstr(buf, pvt->ciev_call_1) && !pvt->no_callsetup) { /* b-party answer */
+						ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
+					}
+				}
+				break;
+			case MBL_STATE_RING:
+				cid_num[0] = 0x00;
+				if ((pcids = strstr(buf, "+CLIP:"))) {
+					if ((pcids = strchr(pcids, '"'))) {
+						if ((pcide = strchr(pcids+1, '"'))) {
+							strncpy(cid_num, pcids+1, pcide - pcids - 1);
+							cid_num[pcide - pcids - 1] = 0x00;
+						}
+					}
+					pvt->sco_out_ptr = pvt->sco_out_buf;
+					pvt->sco_out_len = 0;
+					pvt->sent_answer = 0;
+					chn = mbl_new(AST_STATE_RING, pvt, cid_num);
+					if (chn) {
+						if (ast_pbx_start(chn)) {
+							ast_log(LOG_ERROR, "Unable to start pbx on incoming call.\n");
+							ast_hangup(chn);
+						} else
+							pvt->state = MBL_STATE_RING3;
+					} else {
+						ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
+						rfcomm_write(pvt, "AT+CHUP\r");
+						pvt->state = MBL_STATE_IDLE;
+					}
+				}
+				break;
+			case MBL_STATE_RING2:
+				pvt->sco_out_ptr = pvt->sco_out_buf;
+				pvt->sco_out_len = 0;
+				pvt->sent_answer = 0;
+				chn = mbl_new(AST_STATE_RING, pvt, cid_num);
+				if (chn) {
+					if (ast_pbx_start(chn)) {
+						ast_log(LOG_ERROR, "Unable to start pbx on incoming call.\n");
+						ast_hangup(chn);
+					} else
+						pvt->state = MBL_STATE_RING3;
+				} else {
+					ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
+					rfcomm_write(pvt, "AT+CHUP\r");
+					pvt->state = MBL_STATE_IDLE;
+				}
+				break;
+			case MBL_STATE_RING3:
+				if (strstr(buf, "+CIEV")) {
+					if (strstr(buf, pvt->ciev_call_1)) {
+						if (pvt->sent_answer) {	/* We answered */
+							pvt->state = MBL_STATE_INCOMING;
+						} else {		/* User answered on handset!, disconnect */
+							pvt->state = MBL_STATE_IDLE;
+							if (pvt->sco_socket > -1)
+								close(pvt->sco_socket);
+							ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
+						}
+					}
+					if ((strstr(buf, pvt->ciev_callsetup_0) || strstr(buf, pvt->ciev_call_0))) { /* Caller disconnected */
+						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
+					}
+				} 
+				break;
+			case MBL_STATE_INCOMING:
+				if (strstr(buf, "+CIEV")) {
+					if (strstr(buf, pvt->ciev_call_0)) {
+						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
+					}
+				}
+				break;
+			case MBL_STATE_HANGUP:
+				if (strstr(buf, "OK") || strstr(buf, pvt->ciev_call_0)) {
+					pvt->state = MBL_STATE_IDLE;
+				}
+				break;
+			case MBL_STATE_INSMS:
+				if (strstr(buf, "+CMGR:")) {
+					memset(sms_src, 0x00, sizeof(sms_src));
+					if ((p = strchr(buf, ','))) {
+						if (*(++p) == '"')
+							p++;
+						if ((p1 = strchr(p, ','))) {
+							if (*(--p1) == '"')
+								p1--;
+							memset(sms_src, 0x00, sizeof(sms_src));
+							strncpy(sms_src, p, p1 - p + 1);
+						}
+					}
+				} else if (strstr(buf, "OK")) {
+					chn = mbl_new(AST_STATE_DOWN, pvt, NULL);
+					strcpy(chn->exten, "sms");
+					pbx_builtin_setvar_helper(chn, "SMSSRC", sms_src);
+					pbx_builtin_setvar_helper(chn, "SMSTXT", sms_txt);
+					if (ast_pbx_start(chn))
+						ast_log(LOG_ERROR, "Unable to start pbx on incoming sms.\n");
+					pvt->state = MBL_STATE_IDLE;
+				} else {
+					memset(sms_txt, 0x00, sizeof(sms_txt));
+					strncpy(sms_txt, buf, strlen(buf));
+				}
+				break;
+			case MBL_STATE_OUTSMS:
+				break;
+			case MBL_STATE_OUTSMS1:
+				break;
+			case MBL_STATE_OUTSMS2:
+				if (strstr(buf, "OK")) {
+					pvt->state = MBL_STATE_IDLE;
+				}
+				break;
+			}
+			/* Unsolicited responses */
+
+ 			if (strstr(buf, "+CMTI:")) {	/* SMS Incoming... */
+				if ((p = strchr(buf, ','))) {
+					p++;
+					smsi = atoi(p);
+					if (smsi > 0) {
+						sprintf(buf, "AT+CMGR=%d\r", smsi);
+						rfcomm_write(pvt, buf);
+						pvt->state = MBL_STATE_INSMS;
+					}
+				}
+			}
+
+		} else if (s == 0) { /* Timeouts */
+			if (pvt->state == MBL_STATE_INIT2) { /* Some devices dont respond to AT+CIND? properly. RIM Blackberry 4 example */
+				pvt->state++;
+				rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
+ 			} else if (pvt->state == MBL_STATE_INIT3) { /* Some devices dont respond to AT+CMER=3,0,0,1 properly. VK 2020 for example */
+                                 pvt->state++;
+                                 rfcomm_write(pvt, "AT+CLIP=1\r");
+			} else if (pvt->state == MBL_STATE_PREIDLE) {
+				pvt->connected = 1;

[... 1379 lines stripped ...]


More information about the asterisk-addons-commits mailing list