From 3a44ab0365c152d4ae55b7bcce3b6803ac192987 Mon Sep 17 00:00:00 2001 From: Jeffrey C. Ollie Date: Thu, 16 Oct 2008 16:12:01 -0500 Subject: [PATCH 03/15] Add chan_mobile from asterisk-addons. --- build_tools/menuselect-deps.in | 1 + channels/Makefile | 1 + channels/chan_mobile.c | 2148 ++++++++++++++++++++++++++++++++++++++++ configs/mobile.conf.sample | 60 ++ configure.ac | 4 + doc/chan_mobile.txt | 240 +++++ makeopts.in | 3 + 7 files changed, 2457 insertions(+), 0 deletions(-) create mode 100644 channels/chan_mobile.c create mode 100644 configs/mobile.conf.sample create mode 100644 doc/chan_mobile.txt diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index f63f591..6d94984 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -1,5 +1,6 @@ ASOUND=@PBX_ALSA@ CRYPTO=@PBX_CRYPTO@ +BLUETOOTH=@PBX_BLUETOOTH@ CURL=@PBX_CURL@ DAHDI=@PBX_DAHDI@ FREETDS=@PBX_FREETDS@ diff --git a/channels/Makefile b/channels/Makefile index 4ceec69..239c18a 100644 --- a/channels/Makefile +++ b/channels/Makefile @@ -107,3 +107,4 @@ chan_usbradio.o: ./xpmr/xpmr.c ./xpmr/xpmr.h ./xpmr/xpmr_coef.h chan_usbradio.so: LIBS+=-lusb -lasound +chan_mobile.so: LIBS+=$(BLUETOOTH_LIB) diff --git a/channels/chan_mobile.c b/channels/chan_mobile.c new file mode 100644 index 0000000..42659b2 --- /dev/null +++ b/channels/chan_mobile.c @@ -0,0 +1,2148 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * 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 + * + * \ingroup channel_drivers + */ + +/*** MODULEINFO + bluetooth + ***/ + +#include + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MBL_CONFIG "mobile.conf" + +#define DEVICE_FRAME_SIZE 48 +#define DEVICE_FRAME_FORMAT AST_FORMAT_SLINEAR +#define CHANNEL_FRAME_SIZE 320 + +static int prefformat = DEVICE_FRAME_FORMAT; + +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; + +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_INIT7, + 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 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? */ + int sco_socket; + AST_LIST_ENTRY(adapter_pvt) entry; +}; + +static AST_RWLIST_HEAD_STATIC(adapters, adapter_pvt); + +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 */ + 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 */ + char connected; /* is it connected? */ + 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]; + char io_save_buf[DEVICE_FRAME_SIZE]; + int io_save_len; + int io_pipe[2]; + int sco_socket; /* sco socket descriptor */ + pthread_t sco_listener_thread; /* inbound sco listener for this device */ + enum mbl_state state; /* monitor thread current state */ + pthread_t monitor_thread; /* monitor thread handle */ + char dial_number[AST_MAX_EXTENSION]; /* number for the monitor thread to dial */ + int dial_timeout; + char ciev_call_0[5]; /* dynamically built reponse strings */ + char ciev_call_1[5]; + char ciev_callsetup_0[5]; + char ciev_callsetup_1[5]; + char ciev_callsetup_2[5]; + char ciev_callsetup_3[5]; + unsigned int no_callsetup:1; + unsigned int has_sms:1; + unsigned int sent_answer:1; + unsigned int do_alignment_detection:1; + unsigned int alignment_detection_triggered:1; + unsigned int do_hangup:1; + short alignment_samples[4]; + int alignment_count; + char sms_txt[160]; + struct ast_dsp *dsp; + struct ast_frame *dsp_fr; + int dtmf_skip; + int skip_frames; + char hangup_count; + AST_LIST_ENTRY(mbl_pvt) entry; +}; + +static AST_RWLIST_HEAD_STATIC(devices, mbl_pvt); + +/* 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 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"), +}; + +/* 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); +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 void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen); + +static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel); +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(bdaddr_t src, bdaddr_t dst); +static int sco_write(int s, char *buf, int len); +static int sco_read(int s, char *buf, int len); + +static void *do_sco_listen(void *data); +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 +}; + +/* 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 %-5.5s %-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) { + ba2str(&pvt->addr, bdaddr); + snprintf(group, 5, "%d", pvt->group); + ast_cli(a->fd, FORMAT1, pvt->id, bdaddr, group, pvt->adapter->id, pvt->connected ? "Yes" : "No", + (pvt->state == MBL_STATE_IDLE) ? "Free" : (pvt->state < MBL_STATE_IDLE) ? "Init" : "Busy", + (pvt->has_sms) ? "Yes" : "No"); + } + 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 = 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 \n" + " Send to the rfcomm port on the device\n" + " with the specified .\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 || !pvt->connected) { + ast_cli(a->fd, "Device %s not found.\n", a->argv[2]); + return CLI_SUCCESS; + } + + snprintf(buf, sizeof(buf), "%s\r", a->argv[3]); + rfcomm_write(pvt, buf); + + return CLI_SUCCESS; +} + +/* + + Dialplan applications implementation + +*/ + +static int mbl_status_exec(struct ast_channel *ast, void *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) { + if (pvt->connected) + stat = 2; + if (pvt->owner) + stat = 3; + } + + 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, void *data) +{ + + struct mbl_pvt *pvt; + char *parse; + + 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); + return -1; + } + + if (!pvt->connected) { + ast_log(LOG_ERROR,"Bluetooth device %s wasn't connected -- SMS will not be sent.\n", args.device); + return -1; + } + + if (!pvt->has_sms) { + ast_log(LOG_ERROR,"Bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n", args.device); + return -1; + } + + if (pvt->state != MBL_STATE_IDLE) { + ast_log(LOG_ERROR,"Bluetooth device %s isn't IDLE -- SMS will not be sent.\n", args.device); + return -1; + } + + ast_copy_string(pvt->dial_number, args.dest, sizeof(pvt->dial_number)); + ast_copy_string(pvt->sms_txt, args.message, sizeof(pvt->sms_txt)); + pvt->state = MBL_STATE_OUTSMS; + + return 0; + +} + +/* + + Channel Driver callbacks + +*/ + +static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num) +{ + + struct ast_channel *chn; + + if (pipe(pvt->io_pipe) == -1) { + ast_log(LOG_ERROR, "Failed to create io_pipe.\n"); + return NULL; + } + + if (pvt->sco_socket != -1) + close(pvt->sco_socket); + pvt->sco_socket = -1; + pvt->io_save_len = 0; + pvt->sent_answer = 0; + pvt->skip_frames = 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; + pvt->do_hangup = 1; + chn = ast_channel_alloc(1, state, cid_num, pvt->id, 0, 0, pvt->context, 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->tech_pvt = pvt; + ast_channel_set_fd(chn, 0, pvt->io_pipe[0]); + if (state == AST_STATE_RING) + chn->rings = 1; + ast_string_field_set(chn, language, "en"); + pvt->owner = chn; + return chn; + } + + return NULL; + +} + +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, group; + + 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_RWLIST_RDLOCK(&devices); + AST_RWLIST_TRAVERSE(&devices, pvt, entry) { + if (((dest_dev[0] == 'g') || (dest_dev[0] == 'G')) && ((dest_dev[1] >= '0') && (dest_dev[1] <= '9'))) { + group = atoi(dest_dev+1); + if (pvt->group == group) + 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, "Cant determine destination number.\n"); + *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION; + return NULL; + } + + 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; + } + + ast_debug(1, "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; + + ast_debug(1, "Hanging up device %s.\n", pvt->id); + + ast_channel_set_fd(ast, 0, -1); + close(pvt->io_pipe[0]); + close(pvt->io_pipe[1]); + + 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) { + if (pvt->do_hangup) { + 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; + + ast_debug(1, "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 '#': + snprintf(buf, sizeof(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; + +} + +static struct ast_frame *mbl_read(struct ast_channel *ast) +{ + + struct mbl_pvt *pvt = ast->tech_pvt; + struct ast_frame *f; + int r; + + ast_debug(2, "*** mbl_read()\n"); + + if (!pvt->owner) { + return &ast_null_frame; + } + memset(&pvt->fr, 0x00, sizeof(struct ast_frame)); + pvt->fr.frametype = AST_FRAME_VOICE; + pvt->fr.subclass = DEVICE_FRAME_FORMAT; + pvt->fr.datalen = CHANNEL_FRAME_SIZE; + pvt->fr.samples = CHANNEL_FRAME_SIZE / 2; + 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.ptr = pvt->io_buf + AST_FRIENDLY_OFFSET; + + if ((r = read(pvt->io_pipe[0], pvt->fr.data.ptr, CHANNEL_FRAME_SIZE)) != CHANNEL_FRAME_SIZE) { + if (r == -1) { + ast_log(LOG_ERROR, "read error %d\n", errno); + return &ast_null_frame; + } else { + pvt->fr.datalen = r; + pvt->fr.samples = r / 2; + } + } + + 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; + } + + return &pvt->fr; + +} + +static int mbl_write(struct ast_channel *ast, struct ast_frame *frame) +{ + + struct mbl_pvt *pvt = ast->tech_pvt; + int i, r, io_need, num_frames; + char *pfr, buf[DEVICE_FRAME_SIZE]; + + ast_debug(2, "*** mbl_write\n"); + + if (frame->frametype != AST_FRAME_VOICE) { + return 0; + } + + io_need = 0; + if (pvt->io_save_len > 0) { + io_need = DEVICE_FRAME_SIZE - pvt->io_save_len; + memcpy(pvt->io_save_buf + pvt->io_save_len, frame->data.ptr, io_need); + sco_write(pvt->sco_socket, pvt->io_save_buf, DEVICE_FRAME_SIZE); + if ((r = sco_read(pvt->sco_socket, buf, DEVICE_FRAME_SIZE))) { + if (pvt->do_alignment_detection) + do_alignment_detection(pvt, buf, r); + if (ast->_state == AST_STATE_UP) /* Dont queue the audio in the pipe if the call is not up yet. just toss it. */ + sco_write(pvt->io_pipe[1], buf, r); + } + } + + num_frames = (frame->datalen - io_need) / DEVICE_FRAME_SIZE; + pfr = frame->data.ptr + io_need; + + for (i=0; isco_socket, pfr, DEVICE_FRAME_SIZE); + if ((r = sco_read(pvt->sco_socket, buf, DEVICE_FRAME_SIZE))) { + if (pvt->do_alignment_detection) + do_alignment_detection(pvt, buf, r); + if (ast->_state == AST_STATE_UP) + sco_write(pvt->io_pipe[1], buf, r); + } + pfr += DEVICE_FRAME_SIZE; + } + + pvt->io_save_len = (frame->datalen - io_need) - (num_frames * DEVICE_FRAME_SIZE); + if (pvt->io_save_len > 0) { + memcpy(pvt->io_save_buf, pfr, pvt->io_save_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, "")); + + ast_debug(1, "Checking device state for device %s\n", device); + + AST_RWLIST_RDLOCK(&devices); + AST_RWLIST_TRAVERSE(&devices, pvt, entry) { + if (!strcmp(pvt->id, device)) + break; + } + AST_RWLIST_UNLOCK(&devices); + + if (pvt) { + if (pvt->connected) { + if (pvt->owner) + res = AST_DEVICE_INUSE; + else + res = AST_DEVICE_NOT_INUSE; + } + } + + return res; + +} + +/* + + Callback helpers + +*/ + +/* + + do_alignment_detection() + + This routine attempts to detect where we get misaligned sco audio data from the bluetooth adaptor. + + Its enabled by alignmentdetect=yes under the adapter entry in mobile.conf + + Some adapters suffer a problem where occasionally they will byte shift the audio stream one byte to the right. + The result is static or white noise on the inbound (from the adapter) leg of the call. + This is characterised by a sudden jump in magnitude of the value of the 16 bit samples. + + Here we look at the first 4 48 byte frames. We average the absolute values of each sample in the frame, + then average the sum of the averages of frames 1, 2, and 3. + Frame zero is usually zero. + If the end result > 100, and it usually is if we have the problem, set a flag and compensate by shifting the bytes + for each subsequent frame during the call. + + If the result is <= 100 then clear the flag so we dont come back in here... + + This seems to work OK.... + +*/ + +static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen) +{ + + int i; + short a, *s; + char *p; + + if (pvt->alignment_detection_triggered) { + for (i=buflen, p=buf+buflen-1; i>0; i--, p--) + *p = *(p-1); + *(p+1) = 0; + return; + } + + if (pvt->alignment_count < 4) { + s = (short *)buf; + for (i=0, a=0; ialignment_samples[pvt->alignment_count++] = a; + return; + } + + ast_debug(1, "Alignment Detection result is [%-d %-d %-d %-d]\n", pvt->alignment_samples[0], pvt->alignment_samples[1], pvt->alignment_samples[2], pvt->alignment_samples[3]); + + a = abs(pvt->alignment_samples[1]) + abs(pvt->alignment_samples[2]) + abs(pvt->alignment_samples[3]); + a /= 3; + if (a > 100) { + pvt->alignment_detection_triggered = 1; + ast_debug(1, "Alignment Detection Triggered.\n"); + } else + pvt->do_alignment_detection = 0; + +} + +/* + + rfcomm helpers + +*/ + +static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel) { + + struct sockaddr_rc addr; + int s; + + if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { + ast_debug(1, "socket() failed (%d).\n", errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, &src); + addr.rc_channel = (uint8_t) 1; + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ast_debug(1, "bind() failed (%d).\n", errno); + close(s); + 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) { + ast_debug(1, "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; + + ast_debug(1, "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_debug(1, "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; + +} + +/* + + sco helpers + +*/ + +static int sco_connect(bdaddr_t src, bdaddr_t dst) +{ + + struct sockaddr_sco addr; + int s; + + if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { + ast_debug(1, "socket() failed (%d).\n", errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &src); + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ast_debug(1, "bind() failed (%d).\n", errno); + close(s); + 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) { + ast_debug(1, "sco connect() failed (%d).\n", errno); + close(s); + return -1; + } + + return s; + +} + +static int sco_write(int s, char *buf, int len) +{ + + int r; + + if (s == -1) { + ast_debug(2, "sco_write() not ready\n"); + return 0; + } + + ast_debug(2, "sco_write()\n"); + + r = write(s, buf, len); + if (r == -1) { + ast_debug(2, "sco write error %d\n", errno); + return 0; + } + + return 1; + +} + +static int sco_read(int s, char *buf, int len) +{ + + int r; + + if (s == -1) { + ast_debug(2, "sco_read() not ready\n"); + return 0; + } + + ast_debug(2, "sco_read()\n"); + + r = read(s, buf, len); + if (r == -1) { + ast_debug(2, "sco_read() error %d\n", errno); + return 0; + } + + return r; + +} + +/* + + sdp helpers + +*/ + +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) { + ast_debug(1, "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 + ast_debug(1, "No responses returned for device %s.\n", addr); + } else + ast_debug(1, "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; + +} + +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); + + if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY))) + ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n"); + else + 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; + +} + +/* + + Thread routines + +*/ + +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[160]; + + 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')) { + ast_debug(1, "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; iciev_call_0, sizeof(pvt->ciev_call_0), "%d,0", callp); + snprintf(pvt->ciev_call_1, sizeof(pvt->ciev_call_1), "%d,1", callp); + snprintf(pvt->ciev_callsetup_0, sizeof(pvt->ciev_callsetup_0), "%d,0", callsetupp); + snprintf(pvt->ciev_callsetup_1, sizeof(pvt->ciev_callsetup_1), "%d,1", callsetupp); + snprintf(pvt->ciev_callsetup_2, sizeof(pvt->ciev_callsetup_2), "%d,2", callsetupp); + snprintf(pvt->ciev_callsetup_3, sizeof(pvt->ciev_callsetup_3), "%d,3", callsetupp); + if (callsetupp == 0) /* This phone has no call setup indication!! ... */ + pvt->no_callsetup = 1; + ast_debug(1, "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+VGS=15\r"); + pvt->state++; + } + break; + case MBL_STATE_INIT5: + if (strstr(buf, "OK")) { + rfcomm_write(pvt, "AT+CMGF=1\r"); + pvt->state++; + } + break; + case MBL_STATE_INIT6: + 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_INIT7: + 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: + ast_debug(1, "Device %s [%s]\n", 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 */ + pvt->do_hangup = 0; + 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; + } + } + 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: + 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)) { + pvt->do_hangup = 0; + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP); + } + } + break; + case MBL_STATE_HANGUP: + if (strstr(buf, "OK") || strstr(buf, pvt->ciev_call_0)) { + close(pvt->sco_socket); + pvt->sco_socket = -1; + 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 { + ast_copy_string(sms_txt, buf, sizeof(sms_txt)); + } + 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) { + snprintf(buf, sizeof(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; + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id); + pvt->state = MBL_STATE_IDLE; + } else if (pvt->state == MBL_STATE_DIAL) { + snprintf(buf, sizeof(buf), "ATD%s;\r", pvt->dial_number); + if (!rfcomm_write(pvt, buf)) { + ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state); + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + pvt->state = MBL_STATE_IDLE; + } else { + pvt->state = MBL_STATE_DIAL1; + } + } else if (pvt->state == MBL_STATE_DIAL1) { + ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state); + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP); + pvt->state = MBL_STATE_IDLE; + } else if (pvt->state == MBL_STATE_RING) { /* No CLIP?, bump it */ + pvt->state = MBL_STATE_RING2; + } else if (pvt->state == MBL_STATE_HANGUP) { + if (pvt->do_hangup) { + if (pvt->hangup_count == 6) { + ast_debug(1, "Device %s failed to hangup after 6 tries, disconnecting.\n", pvt->id); + monitor = 0; + } + rfcomm_write(pvt, "AT+CHUP\r"); + pvt->hangup_count++; + } else + pvt->state = MBL_STATE_IDLE; + } else if (pvt->state == MBL_STATE_OUTSMS) { + snprintf(buf, sizeof(buf), "AT+CMGS=\"%s\"\r", pvt->dial_number); + rfcomm_write(pvt, buf); + pvt->state = MBL_STATE_OUTSMS1; + } else if (pvt->state == MBL_STATE_OUTSMS1) { + if (pvt->rfcomm_buf[0] == '>') { + snprintf(buf, sizeof(buf), "%s%c", pvt->sms_txt, 0x1a); + rfcomm_write(pvt, buf); + pvt->state = MBL_STATE_OUTSMS2; + } else { + ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id); + pvt->state = MBL_STATE_IDLE; + } + } else if (pvt->state == MBL_STATE_OUTSMS2) { + ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id); + pvt->state = MBL_STATE_IDLE; + } + } else if (s == -1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has disconnected, reason (%d).\n", pvt->id, errno); + monitor = 0; + } + + } + + if (pvt->rfcomm_socket > -1) + close(pvt->rfcomm_socket); + if (pvt->sco_socket > -1) + close(pvt->sco_socket); + pvt->sco_socket = -1; + pvt->connected = 0; + pvt->monitor_thread = AST_PTHREADT_NULL; + + pthread_cancel(pvt->sco_listener_thread); + pthread_join(pvt->sco_listener_thread, NULL); + pvt->sco_listener_thread = AST_PTHREADT_NULL; + + close(pvt->adapter->sco_socket); + + manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id); + + pvt->adapter->inuse = 0; + + return NULL; + +} + +static void *do_monitor_headset(void *data) +{ + + struct mbl_pvt *pvt = (struct mbl_pvt *)data; + char monitor = 1; + char buf[256]; + int s, t; + + pvt->state = MBL_STATE_PREIDLE; + + while (monitor) { + + if (pvt->state == MBL_STATE_RING2) + t = 2; + else + t = 1; + s = rfcomm_read(pvt, buf, 0, t); + + if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) { + ast_debug(1, "rfcomm_read() (%s) [%s]\n", pvt->id, buf); + switch (pvt->state) { + case MBL_STATE_RING2: + if (strstr(buf, "AT+CKPD=")) { + ast_queue_control(pvt->owner, AST_CONTROL_ANSWER); + pvt->state = MBL_STATE_INCOMING; + rfcomm_write(pvt, "\r\n+VGS=13\r\n"); + rfcomm_write(pvt, "\r\n+VGM=13\r\n"); + } + break; + case MBL_STATE_INCOMING: + if (strstr(buf, "AT+CKPD=")) { + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP); + } + break; + default: + break; + } + if (strstr(buf, "AT+VGS=")) { + rfcomm_write(pvt, "\r\nOK\r\n"); + } else if (strstr(buf, "AT+VGM=")) { + rfcomm_write(pvt, "\r\nOK\r\n"); + } + } else if (s == 0) { /* Timeouts */ + if (pvt->state == MBL_STATE_PREIDLE) { + pvt->connected = 1; + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id); + pvt->state = MBL_STATE_IDLE; + } else if (pvt->state == MBL_STATE_RING) { + pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr); + if (pvt->sco_socket > -1) { + ast_setstate(pvt->owner, AST_STATE_RINGING); + ast_queue_control(pvt->owner, AST_CONTROL_RINGING); + pvt->state = MBL_STATE_RING2; + } else { + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP); + } + } else if (pvt->state == MBL_STATE_RING2) { + rfcomm_write(pvt, "\r\nRING\r\n"); + } + } else if (s == -1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has disconnected, reason (%d).\n", pvt->id, errno); + monitor = 0; + } + + } + + if (pvt->rfcomm_socket > -1) + close(pvt->rfcomm_socket); + if (pvt->sco_socket > -1) + close(pvt->sco_socket); + pvt->sco_socket = -1; + pvt->connected = 0; + pvt->monitor_thread = AST_PTHREADT_NULL; + + manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id); + + pvt->adapter->inuse = 0; + + return NULL; + +} + +static int start_monitor(struct mbl_pvt *pvt) +{ + + if (pvt->type == MBL_TYPE_PHONE) { + if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) { + pvt->monitor_thread = AST_PTHREADT_NULL; + return 0; + } + /* we are a phone, so spin the sco listener on the adapter as well */ + if (ast_pthread_create_background(&pvt->sco_listener_thread, NULL, do_sco_listen, pvt->adapter) < 0) { + ast_log(LOG_ERROR, "Unable to create sco listener thread for device %s.\n", pvt->id); + } + + } else { + if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_headset, pvt) < 0) { + pvt->monitor_thread = AST_PTHREADT_NULL; + return 0; + } + } + + return 1; + +} + +static void *do_discovery(void *data) +{ + + struct adapter_pvt *adapter; + struct mbl_pvt *pvt; + + for (;;) { + AST_RWLIST_RDLOCK(&adapters); + AST_RWLIST_TRAVERSE(&adapters, adapter, entry) { + if (!adapter->inuse) { + AST_RWLIST_RDLOCK(&devices); + AST_RWLIST_TRAVERSE(&devices, pvt, entry) { + if (!adapter->inuse && !pvt->connected && !strcmp(adapter->id, pvt->adapter->id)) { + if ((pvt->rfcomm_socket = rfcomm_connect(adapter->addr, pvt->addr, pvt->rfcomm_port)) > -1) { + pvt->state = 0; + if (start_monitor(pvt)) { + pvt->connected = 1; + adapter->inuse = 1; + manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Connect\r\nDevice: %s\r\n", pvt->id); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has connected.\n", pvt->id); + } + } + } + } + AST_RWLIST_UNLOCK(&devices); + } + } + AST_RWLIST_UNLOCK(&adapters); + /* Go to sleep */ + sleep(discovery_interval); + } + + return NULL; +} + +static void *do_sco_listen(void *data) +{ + + int ns; + struct sockaddr_sco addr; + char saddr[18]; + struct sco_options so; + socklen_t len; + int opt = 1; + socklen_t addrlen; + struct mbl_pvt *pvt; + struct adapter_pvt *adapter = (struct adapter_pvt *) data; + + if ((adapter->sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { + ast_log(LOG_ERROR, "Unable to create sco listener socket.\n"); + return NULL; + } + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &adapter->addr); + if (bind(adapter->sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno); + close(adapter->sco_socket); + return NULL; + } + if (setsockopt(adapter->sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { + ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n"); + close(adapter->sco_socket); + return NULL; + } + if (listen(adapter->sco_socket, 5) < 0) { + ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n"); + close(adapter->sco_socket); + return NULL; + } + while (1) { + ast_debug(1, "About to accept() socket.\n"); + addrlen = sizeof(struct sockaddr_sco); + if ((ns = accept(adapter->sco_socket, (struct sockaddr *)&addr, &addrlen)) > -1) { + ast_debug(1, "accept()ed socket.\n"); + len = sizeof(so); + getsockopt(ns, SOL_SCO, SCO_OPTIONS, &so, &len); + + ba2str(&addr.sco_bdaddr, saddr); + ast_debug(1, "Incoming Audio Connection from device %s MTU is %d\n", saddr, so.mtu); + + pvt = NULL; + AST_RWLIST_RDLOCK(&devices); + AST_RWLIST_TRAVERSE(&devices, pvt, entry) { + if (!bacmp(&pvt->addr, &addr.sco_bdaddr)) + break; + } + AST_RWLIST_UNLOCK(&devices); + if (pvt) { + if (pvt->sco_socket != -1) + close(pvt->sco_socket); + pvt->sco_socket = ns; + } else + ast_debug(1, "Could not find device for incoming Audio Connection.\n"); + } else { + ast_log(LOG_ERROR, "accept() failed %d\n", errno); + } + } + + return NULL; + +} + +/* + + Module + +*/ + +static int mbl_load_config(void) +{ + + struct ast_config *cfg = NULL; + char *cat = NULL; + struct ast_variable *var; + const char *id, *address, *useadapter, *port, *context, *type, *skip, *group, *master, *nocallsetup, *aligndetect; + struct mbl_pvt *pvt; + struct adapter_pvt *adapter; + uint16_t vs; + struct hci_dev_req dr; + char nadapters = 0; + struct ast_flags config_flags = { 0 }; + + cfg = ast_config_load(MBL_CONFIG, config_flags); + if (!cfg) + return 0; + + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { + if (!strcasecmp(var->name, "interval")) + discovery_interval = atoi(var->value); + } + + /* load adapters first */ + cat = ast_category_browse(cfg, NULL); + while (cat) { + if (!strcasecmp(cat, "adapter")) { + id = ast_variable_retrieve(cfg, cat, "id"); + address = ast_variable_retrieve(cfg, cat, "address"); + master = ast_variable_retrieve(cfg, cat, "forcemaster"); + aligndetect = ast_variable_retrieve(cfg, cat, "alignmentdetection"); + ast_debug(1, "Loading adapter %s %s.\n", id, address); + if (!ast_strlen_zero(id) && !ast_strlen_zero(address)) { + if ((adapter = ast_calloc(1, sizeof(*adapter)))) { + ast_copy_string(adapter->id, id, sizeof(adapter->id)); + str2ba(address, &adapter->addr); + if (!ast_strlen_zero(aligndetect)) { + if (*aligndetect == 'Y' || *aligndetect == 'y') + adapter->alignment_detection = 1; + } + adapter->dev_id = hci_devid(address); + adapter->hci_socket = hci_open_dev(adapter->dev_id); + if (adapter->dev_id < 0 || adapter->hci_socket < 0) { + ast_log(LOG_ERROR, "Unable to open adapter %s. It won't be enabled.\n", adapter->id); + ast_free(adapter); + } else { + if ((master) && (*master)) { + dr.dev_id = adapter->dev_id; + if (hci_strtolm("master", &dr.dev_opt)) { + if (ioctl(adapter->hci_socket, HCISETLINKMODE, (unsigned long) &dr) < 0) { + ast_log(LOG_WARNING, "Unable to set adapter %s link mode to MASTER.\n", adapter->id); + } + } + } + hci_read_voice_setting(adapter->hci_socket, &vs, 1000); + vs = htobs(vs); + if (vs != 0x0060) { + ast_log(LOG_ERROR, "Incorrect voice setting for adapter %s, it must be 0x0060 - see 'man hciconfig' for details.\n", adapter->id); + hci_close_dev(adapter->hci_socket); + ast_free(adapter); + } else { + AST_RWLIST_WRLOCK(&adapters); + AST_RWLIST_INSERT_HEAD(&adapters, adapter, entry); + AST_RWLIST_UNLOCK(&adapters); + nadapters++; + } + } + } + } else + ast_log(LOG_ERROR, "id/address missing for adapter %s. It won't be enabled.\n", cat); + } + cat = ast_category_browse(cfg, cat); + } + + if (!nadapters) { + ast_log(LOG_WARNING, "***********************************************************************\n"); + ast_log(LOG_WARNING, "No Adapters defined. Please review mobile.conf. See sample for details.\n"); + ast_log(LOG_WARNING, "***********************************************************************\n"); + } + + /* now load devices */ + cat = ast_category_browse(cfg, NULL); + while (cat) { + if (strcasecmp(cat, "general") && strcasecmp(cat, "adapter")) { + ast_debug(1, "Loading device %s.\n", cat); + address = ast_variable_retrieve(cfg, cat, "address"); + useadapter = ast_variable_retrieve(cfg, cat, "adapter"); + port = ast_variable_retrieve(cfg, cat, "port"); + context = ast_variable_retrieve(cfg, cat, "context"); + type = ast_variable_retrieve(cfg, cat, "type"); + skip = ast_variable_retrieve(cfg, cat, "dtmfskip"); + group = ast_variable_retrieve(cfg, cat, "group"); + nocallsetup = ast_variable_retrieve(cfg, cat, "nocallsetup"); + if (!ast_strlen_zero(address) && !ast_strlen_zero(port) && !ast_strlen_zero(useadapter)) { + /* find the adapter */ + AST_RWLIST_RDLOCK(&adapters); + AST_RWLIST_TRAVERSE(&adapters, adapter, entry) { + if (!strcmp(adapter->id, useadapter)) + break; + } + AST_RWLIST_UNLOCK(&adapters); + if (!adapter) { + ast_log(LOG_ERROR, "Device %s configured to use unknown adapter %s. It won't be enabled.\n", cat, useadapter); + break; + } + if ((pvt = ast_calloc(1, sizeof(*pvt)))) { + if (type && !strcmp(type, "headset")) + pvt->type = MBL_TYPE_HEADSET; + else + pvt->type = MBL_TYPE_PHONE; + ast_copy_string(pvt->id, cat, sizeof(pvt->id)); + str2ba(address, &pvt->addr); + ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context)); + if (group) + pvt->group = atoi(group); /* group 0 if invalid */ + pvt->state = MBL_STATE_INIT; + pvt->rfcomm_socket = -1; + pvt->rfcomm_port = atoi(port); + pvt->sco_socket = -1; + pvt->monitor_thread = AST_PTHREADT_NULL; + pvt->sco_listener_thread = AST_PTHREADT_NULL; + if (!ast_strlen_zero(nocallsetup)) { + if ((*nocallsetup == 'y') || (*nocallsetup == 'Y')) { + pvt->no_callsetup = 1; + ast_debug(1, "Setting nocallsetup mode for device %s.\n", pvt->id); + } + } + pvt->dsp = ast_dsp_new(); + if (skip) { + if ((pvt->dtmf_skip = atoi(skip)) == 0) + pvt->dtmf_skip = 200; + } else + pvt->dtmf_skip = 200; + ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT); + ast_dsp_set_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); + pvt->adapter = adapter; + AST_RWLIST_WRLOCK(&devices); + AST_RWLIST_INSERT_HEAD(&devices, pvt, entry); + AST_RWLIST_UNLOCK(&devices); + } + } else { + ast_log(LOG_ERROR, "Device %s has no address/port/adapter configured. It won't be enabled.\n", cat); + } + } + cat = ast_category_browse(cfg, cat); + } + + ast_config_destroy(cfg); + + return 1; + +} + +static int unload_module(void) +{ + + struct mbl_pvt *pvt; + struct adapter_pvt *adapter; + + /* First, take us out of the channel loop */ + ast_channel_unregister(&mbl_tech); + + /* Kill the discovery thread */ + if (discovery_thread != AST_PTHREADT_NULL) { + pthread_cancel(discovery_thread); + pthread_join(discovery_thread, NULL); + } + + /* Destroy the device list */ + AST_RWLIST_WRLOCK(&devices); + while ((pvt = AST_RWLIST_REMOVE_HEAD(&devices, entry))) { + if (pvt->monitor_thread != AST_PTHREADT_NULL) { + pthread_cancel(pvt->monitor_thread); + pthread_join(pvt->monitor_thread, NULL); + } + if (pvt->sco_listener_thread != AST_PTHREADT_NULL) { + pthread_cancel(pvt->sco_listener_thread); + pthread_join(pvt->sco_listener_thread, NULL); + } + if (pvt->sco_socket > -1) { + close(pvt->sco_socket); + } + if (pvt->adapter->sco_socket > -1) { + close(pvt->adapter->sco_socket); + } + if (pvt->rfcomm_socket > -1) { + close(pvt->rfcomm_socket); + } + ast_dsp_free(pvt->dsp); + ast_free(pvt); + } + AST_RWLIST_UNLOCK(&devices); + + /* Destroy the adapter list */ + AST_RWLIST_WRLOCK(&adapters); + while ((adapter = AST_RWLIST_REMOVE_HEAD(&adapters, entry))) { + hci_close_dev(adapter->hci_socket); + ast_free(adapter); + } + AST_RWLIST_UNLOCK(&adapters); + + if (sdp_session) + sdp_close(sdp_session); + + /* Unregister the CLI & APP */ + ast_cli_unregister_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0])); + ast_unregister_application(app_mblstatus); + ast_unregister_application(app_mblsendsms); + + return 0; + +} + +static int load_module(void) +{ + + int dev_id, s; + + /* Check if we have Bluetooth, no point loading otherwise... */ + dev_id = hci_get_route(NULL); + s = hci_open_dev(dev_id); + if (dev_id < 0 || s < 0) { + ast_log(LOG_ERROR, "No Bluetooth device found. Not loading module.\n"); + return AST_MODULE_LOAD_DECLINE; + } + + hci_close_dev(s); + + if (!mbl_load_config()) { + ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", MBL_CONFIG); + return AST_MODULE_LOAD_DECLINE; + } + + sdp_session = sdp_register(); + + /* Spin the discovery thread */ + if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to create discovery thread.\n"); + return AST_MODULE_LOAD_DECLINE; + } + + ast_cli_register_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0])); + ast_register_application(app_mblstatus, mbl_status_exec, mblstatus_synopsis, mblstatus_desc); + ast_register_application(app_mblsendsms, mbl_sendsms_exec, mblsendsms_synopsis, mblsendsms_desc); + + /* Make sure we can register our channel type */ + if (ast_channel_register(&mbl_tech)) { + ast_log(LOG_ERROR, "Unable to register channel class %s\n", "Mobile"); + return AST_MODULE_LOAD_FAILURE; + } + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bluetooth Mobile Device Channel Driver", + .load = load_module, + .unload = unload_module, +); diff --git a/configs/mobile.conf.sample b/configs/mobile.conf.sample new file mode 100644 index 0000000..7ef26d8 --- /dev/null +++ b/configs/mobile.conf.sample @@ -0,0 +1,60 @@ +; +; mobile.conf +; configuration file for chan_mobile +; + +[general] +interval=30 ; Number of seconds between trying to connect to devices. + +; The following is a list of adapters we use. +; id must be unique and address is the bdaddr of the adapter from hciconfig. +; Each adapter may only have one device (headset or phone) connected at a time. +; Add an [adapter] entry for each adapter you have. + +[adapter] +id=blue +address=00:09:DD:60:01:A3 +;forcemaster=yes ; attempt to force adapter into master mode. default is no. +;alignmentdetection=yes ; enable this if you sometimes get 'white noise' on asterisk side of the call + ; its a bug in the bluetooth adapter firmware, enabling this will compensate for it. + ; default is no. + +[adapter] +id=dlink +address=00:80:C8:35:52:78 + +; The following is a list of the devices we deal with. +; Every device listed below will be available for calls in and out of Asterisk. +; Each device needs an adapter=xxxx entry which determines which bluetooth adapter is used. +; Use the CLI command 'mobile search' to discover devices. +; Use the CLI command 'mobile show devices' to see device status. +; +; To place a call out through a mobile phone use Dial(Mobile/[device]/NNN.....) or Dial(Mobile/gn/NNN......) in your dialplan. +; To call a headset use Dial(Mobile/[device]). + +[LGTU550] +address=00:E0:91:7F:46:44 ; the address of the phone +port=4 ; the rfcomm port number (from mobile search) +context=incoming-mobile ; dialplan context for incoming calls +adapter=dlink ; adapter to use +group=1 ; this phone is in channel group 1 +;nocallsetup=yes ; set this only if your phone reports that it supports call progress notification, but does not do it. Motorola L6 for example. + +[6310i] +address=00:60:57:32:7E:B1 +port=13 +context=incoming-mobile +adapter=dlink +group=1 ; this phone is in channel group 1 also. + +[headset] +address=00:0B:9E:11:AE:C6 +port=1 +type=headset ; This is a headset, not a Phone ! +adapter=blue + +[headset1] +address=00:0B:9E:11:74:A5 +port=1 +type=headset +adapter=dlink diff --git a/configure.ac b/configure.ac index e0d0466..31b512f 100644 --- a/configure.ac +++ b/configure.ac @@ -211,6 +211,8 @@ AST_EXT_LIB_SETUP([ALSA], [Advanced Linux Sound Architecture], [asound]) # BKTR is used for backtrace support on platforms that do not # have it natively. AST_EXT_LIB_SETUP([BKTR], [Stack Backtrace support], [execinfo]) +AST_EXT_LIB_SETUP([BLUETOOTH], [Bluetooth Support], [bluetooth]) +AST_EXT_LIB_SETUP([CURL], [cURL], [curl]) AST_EXT_LIB_SETUP([CAP], [POSIX 1.e capabilities], [cap]) AST_EXT_LIB_SETUP([CURL], [cURL], [curl]) AST_EXT_LIB_SETUP([CURSES], [curses], [curses]) @@ -588,6 +590,8 @@ AC_CHECK_SIZEOF(int) AST_EXT_LIB_CHECK([ALSA], [asound], [snd_spcm_init], [alsa/asoundlib.h], [-lm -ldl]) +AST_EXT_LIB_CHECK([BLUETOOTH], [bluetooth], [ba2str], [bluetooth/bluetooth.h]) + AST_EXT_LIB_CHECK([CURSES], [curses], [initscr], [curses.h]) if test "x${host_os}" = "xlinux-gnu" ; then diff --git a/doc/chan_mobile.txt b/doc/chan_mobile.txt new file mode 100644 index 0000000..32fcfd4 --- /dev/null +++ b/doc/chan_mobile.txt @@ -0,0 +1,240 @@ +chan_mobile + +Asterisk Channel Driver to allow Bluetooth Cell/Mobile Phones to be used as FXO devices, and Headsets as FXS devices. + + +Features :- + +Multiple Bluetooth Adapters supported. +Multiple phones can be connected. +Multiple headsets can be connected. +Asterisk automatically connects to each configured mobile phone / headset when it comes in range. +CLI command to discover bluetooth devices. +Inbound calls on the mobile network to the mobile phones are handled by Asterisk, just like inbound calls on a Zap channel. +CLI passed through on inbound calls. +Dial outbound on a mobile phone using Dial(Mobile/device/nnnnnnn) in the dialplan. +Dial a headset using Dial(Mobile/device) in the dialplan. +Application MobileStatus can be used in the dialplan to see if a mobile phone / headset is connected. +Supports devicestate for dialplan hinting. +Supports Inbound and Outbound SMS. +Supports 'channel' groups for implementing 'GSM Gateways' + + +Requirements :- + +In order to use chan_mobile, you must have a working bluetooth subsystem on your Asterisk box. +This means one or more working bluetooth adapters, and the BlueZ packages. + +Any bluetooth adapter supported by the Linux kernel will do, including usb bluetooth dongles. + +The BlueZ package you need is bluez-utils. If you are using a GUI then you might want to install bluez-pin also. +You also need libbluetooth, and libbluetooth-dev if you are compiling Asterisk from source. + +You need to get bluetooth working with your phone before attempting to use chan_mobile. +This means 'pairing' your phone or headset with your Asterisk box. I dont describe how to do this here as the process +differs from distro to distro. You only need to pair once per adapter. + +See www.bluez.org for details about setting up Bluetooth under Linux. + + +Concepts :- + +chan_mobile deals with both bluetooth adapters and bluetooth devices. This means you need to tell chan_mobile about the +bluetooth adapters installed in your server as well as the devices (phones / headsets) you wish to use. + +chan_mobile currently only allows one device (phone or headset) to be connected to an adapter at a time. This means you need +one adapter for each device you wish to use simultaneously. Much effort has gone into trying to make multiple devices per adapter +work, but in short it doesnt. + +Periodically chan_mobile looks at each configured adapter, and if it is not in use (i.e. no device connected) will initiate a +search for devices configured to use this adapater that may be in range. If it finds one it will connect the device and it +will be available for Asterisk to use. When the device goes out of range, chan_mobile will disconnect the device and the adapter +will become available for other devices. + + +Configuring chan_mobile :- + +The configuration file for chan_mobile is /etc/asterisk/mobile.conf. It is a normal Asterisk config file consisting of sections and key=value pairs. + +See configs/mobile.conf.sample for an example and an explanation of the configuration. + + +Using chan_mobile :- + +chan_mobile.so must be loaded either by loading it using the Asterisk CLI, or by adding it to /etc/asterisk/modules.conf + +Search for your bluetooth devices using the CLI command 'mobile search'. Be patient with this command as +it will take 8 - 10 seconds to do the discovery. This requires a free adapter. + +Headsets will generally have to be put into 'pairing' mode before they will show up here. + +This will return something like the following :- + +*CLI> mobile search +Address Name Usable Type Port +00:12:56:90:6E:00 LG TU500 Yes Phone 4 +00:80:C8:35:52:78 Toaster No Headset 0 +00:0B:9E:11:74:A5 Hello II Plus Yes Headset 1 +00:0F:86:0E:AE:42 Daves Blackberry Yes Phone 7 + +This is a list of all bluetooth devices seen and whether or not they are usable with chan_mobile. +The Address field contains the 'bd address' of the device. This is like an ethernet mac address. +The Name field is whatever is configured into the device as its name. +The Usable field tells you whether or not the device supports the Bluetooth Handsfree Profile or Headset profile. +The Type field tells you whether the device is usable as a Phone line (FXO) or a headset (FXS) +The Port field is the number to put in the configuration file. + +Choose which device(s) you want to use and edit /etc/asterisk/mobile.conf. There is a sample included +with the Asterisk-addons source under configs/mobile.conf.sample. + +Be sure to configure the right bd address and port number from the search. If you want inbound +calls on a device to go to a specific context, add a context= line, otherwise the default will +be used. The 'id' of the device [bitinbrackets] can be anything you like, just make it unique. + +If you are configuring a Headset be sure to include the type=headset line, if left out it defaults +to phone. + +The CLI command 'mobile show devices' can be used at any time to show the status of configured devices, +and whether or not the device is capable of sending / receiving SMS via bluetooth. + +*CLI> mobile show devices +ID Address Group Adapter Connected State SMS +headset 00:0B:9E:11:AE:C6 0 blue No Init No +LGTU550 00:E0:91:7F:46:44 1 dlink No Init No +*CLI> + +As each phone is connected you will see a message on the Asterisk console :- + + Loaded chan_mobile.so => (Bluetooth Mobile Device Channel Driver) + -- Bluetooth Device blackberry has connected. + -- Bluetooth Device dave has connected. + +To make outbound calls, add something to you Dialplan like the following :- (modify to suit) + +; Calls via LGTU5500 +exten => _9X.,1,Dial(Mobile/LGTU550/${EXTEN:1},45) +exten => _9X.,n,Hangup + +To use channel groups, add an entry to each phones definition in mobile.conf like group=n +where n is a number. + +Then if you do something like Dial(Mobile/g1/123456) Asterisk will dial 123456 on the first +connected free phone in group 1. + +Phones which do not have a specific 'group=n' will be in group 0. + + +To dial out on a headset, you need to use some other mechanism, because the headset is not likely +to have all the needed buttons on it. res_clioriginate is good for this :- + +*CLI> originate Mobile/headset extension NNNNN@context + +This will call your headset, once you answer, Asterisk will call NNNNN at context context + +Dialplan hints :- + +chan_mobile supports 'device status' so you can do somthing like + +exten => 1234,hint,SIP/30&Mobile/dave&Mobile/blackberry + + +MobileStatus Application :- + +chan_mobile also registers an application named MobileStatus. You can use this in your Dialplan +to determine the 'state' of a device. + +For example, suppose you wanted to call dave's extension, but only if he was in the office. You could +test to see if his mobile phone was attached to Asterisk, if it is dial his extension, otherwise dial his +mobile phone. + +exten => 40,1,MobileStatus(dave,DAVECELL) +exten => 40,2,GotoIf($["${DAVECELL}" = "1"]?3:5) +exten => 40,3,Dial(ZAP/g1/0427466412,45,tT) +exten => 40,4,Hangup +exten => 40,5,Dial(SIP/40,45,tT) +exten => 40,6,Hangup + +MobileStatus sets the value of the given variable to :- + +1 = Disconnected. i.e. Device not in range of Asterisk, or turned off etc etc +2 = Connected and Not on a call. i.e. Free +3 = Connected and on a call. i.e. Busy + + +SMS Sending / Receiving + +If Asterisk has detected your mobile phone is capable of SMS via bluetooth, you will be able to send and +receive SMS. + +Incoming SMS's cause Asterisk to create an inbound call to the context you defined in mobile.conf or the default +context if you did not define one. The call will start at extension 'sms'. Two channel variables will be available, +SMSSRC = the number of the originator of the SMS and SMSTXT which is the text of the SMS. +This is not a voice call, so grab the values of the variables and hang the call up. + +So, to handle incoming SMS's, do something like the following in your dialplan + +[incoming-mobile] +exten => sms,1,Verbose(Incoming SMS from ${SMSSRC} ${SMSTXT}) +exten => sms,n,Hangup() + +The above will just print the message on the console. + +If you use res_jabber, you could do something like this :- + +[incoming-mobile] +exten => sms,1,JabberSend(transport,user@jabber.somewhere.com,SMS from ${SMSRC} ${SMSTXT}) +exten => sms,2,Hangup() + +To send an SMS, use the application MobileSendSMS like the following :- + +exten => 99,1,MobileSendSMS(dave,0427123456,Hello World) + +This will send 'Hello World' via device 'dave' to '0427123456' + + +DTMF Debouncing :- + +DTMF detection varies from phone to phone. There is a configuration variable that allows you to tune +this to your needs. e.g. in mobile.conf + +[LGTU550] +address=00:12:56:90:6E:00 +port=4 +context=incoming-mobile +dtmfskip=50 + +change dtmfskip to suit your phone. The default is 200. The larger the number, the more chance of missed DTMF. +The smaller the number the more chance of multiple digits being detected. + + +Debugging :- + +Different phone manufacturers have different interpretations of the Bluetooth Handsfree Profile Spec. +This means that not all phones work the same way, particularly in the connection setup / initialisation +sequence. I've tried to make chan_mobile as general as possible, but it may need modification to +support some phone i've never tested. + +Some phones, most notably Sony Ericsson 'T' series, dont quite conform to the Bluetooth HFP spec. +chan_mobile will detect these and adapt accordingly. The T-610 and T-630 have been tested and +work fine. + +If your phone doesnt behave has expected, turn on Asterisk debugging with 'core set debug 1'. + +This will log a bunch of debug messages indicating what the phone is doing, importantly the rfcomm +conversation between Asterisk and the phone. This can be used to sort out what your phone is doing +and make chan_mobile support it. + +Be aware also, that just about all mobile phones behave differently. For example my LG TU500 wont dial unless +the phone is a the 'idle' screen. i.e. if the phone is showing a 'menu' on the display, when you dial via +Asterisk, the call will not work. chan_mobile handles this, but there may be other phones that do +other things too... + +Important: Watch what your mobile phone is doing the first few times. Asterisk wont make random calls but +if chan_mobile fails to hangup for some reason and you get a huge bill from your telco, dont blame me ;) + + +Feedback, Support, Please can you make Mobile Phone X work... etc :- + +as always, bugs should be reported at http://bugs.digium.com + +email me at david.bowerman at gmail.com or dseeb_ on #asterisk & #asterisk-dev irc. diff --git a/makeopts.in b/makeopts.in index 6a24183..a5b2a5f 100644 --- a/makeopts.in +++ b/makeopts.in @@ -72,6 +72,9 @@ AST_FORTIFY_SOURCE=@AST_FORTIFY_SOURCE@ ASOUND_INCLUDE=@ALSA_INCLUDE@ ASOUND_LIB=@ALSA_LIB@ +BLUETOOTH_LIB=@BLUETOOTH_LIB@ +BLUETOOTH_INCLUDE=@BLUETOOTH_INCLUDE@ + CURL_INCLUDE=@CURL_INCLUDE@ CURL_LIB=@CURL_LIB@ -- 1.6.1