add asterisk-addons-1.4.x (thanks to Hans Zandbelt)
[openwrt/svn-archive/archive.git] / net / asterisk-addons-1.4.x / patches / 011-chan_mobile.patch
1 diff -Nru asterisk-addons-1.4.2/build_tools/menuselect-deps.in asterisk-addons-svn/build_tools/menuselect-deps.in
2 --- asterisk-addons-1.4.2/build_tools/menuselect-deps.in 2007-05-14 18:22:44.000000000 +0200
3 +++ asterisk-addons-svn/build_tools/menuselect-deps.in 2007-06-04 19:10:59.000000000 +0200
4 @@ -1,2 +1,3 @@
5 +BLUETOOTH=@PBX_BLUETOOTH@
6 MYSQLCLIENT=@PBX_MYSQLCLIENT@
7 ASTERISK=@PBX_ASTERISK@
8 diff -Nru asterisk-addons-1.4.2/configs/mobile.conf.sample asterisk-addons-svn/configs/mobile.conf.sample
9 --- asterisk-addons-1.4.2/configs/mobile.conf.sample 1970-01-01 01:00:00.000000000 +0100
10 +++ asterisk-addons-svn/configs/mobile.conf.sample 2007-06-04 19:11:00.000000000 +0200
11 @@ -0,0 +1,30 @@
12 +;
13 +; mobile.conf
14 +;
15 +
16 +[general]
17 +interval=60 ; Number of seconds between trying to connect to devices.
18 +
19 +; The following is a list of the devices we deal with.
20 +; Every device listed below will be available for calls in and out of Asterisk.
21 +; Discovered devices not in this list are not available.
22 +; Use the CLI command 'mobile search' to discover devices.
23 +; Use the CLI command 'mobile show devices' to see device status.
24 +;
25 +; To place out through a cell phone use Dial(Mobile/[device]/NNN.....) in your dialplan.
26 +; To call a headset use Dial(Mobile/[device]).
27 +
28 +;[dave]
29 +;address=00:12:56:90:6E:00
30 +;port=4
31 +;context=incoming-mobile
32 +
33 +;[blackberry]
34 +;address=00:0F:86:0E:AE:42
35 +;port=2
36 +;context=incoming-mobile
37 +
38 +;[headset]
39 +;address=00:0B:9E:11:74:A5
40 +;port=1
41 +;type=headset
42 diff -Nru asterisk-addons-1.4.2/configure.ac asterisk-addons-svn/configure.ac
43 --- asterisk-addons-1.4.2/configure.ac 2007-05-14 18:22:44.000000000 +0200
44 +++ asterisk-addons-svn/configure.ac 2007-06-04 19:11:00.000000000 +0200
45 @@ -17,7 +17,7 @@
46 AC_CONFIG_SRCDIR([res_config_mysql.c])
47
48 AC_COPYRIGHT("Asterisk-addons")
49 -AC_REVISION($Revision: 382 $)
50 +AC_REVISION($Revision: 384 $)
51
52 case "${host}" in
53 *freebsd*)
54 @@ -159,13 +159,14 @@
55 # from here on down, library checking should be done in alphabetical order
56 # by the --with option name, to make things easier for the users :-)
57
58 +AST_EXT_LIB_SETUP([BLUETOOTH], [Bluetooth Support], [bluetooth])
59 AST_EXT_LIB_SETUP([CURSES], [curses], [curses])
60 AST_EXT_LIB_SETUP([NCURSES], [ncurses], [ncurses])
61 AST_EXT_LIB_SETUP([MYSQLCLIENT], [mysqlclient], [mysqlclient])
62 AST_EXT_LIB_SETUP([ASTERISK], [asterisk], [asterisk])
63
64 +AST_EXT_LIB_CHECK([BLUETOOTH], [bluetooth], [ba2str], [bluetooth/bluetooth.h])
65 AST_EXT_LIB_CHECK([CURSES], [curses], [initscr], [curses.h])
66 -
67 AST_EXT_LIB_CHECK([NCURSES], [ncurses], [initscr], [curses.h])
68
69 MYSQL_CONFIG=No
70 diff -Nru asterisk-addons-1.4.2/doc/chan_mobile.txt asterisk-addons-svn/doc/chan_mobile.txt
71 --- asterisk-addons-1.4.2/doc/chan_mobile.txt 1970-01-01 01:00:00.000000000 +0100
72 +++ asterisk-addons-svn/doc/chan_mobile.txt 2007-06-04 19:11:00.000000000 +0200
73 @@ -0,0 +1,262 @@
74 +chan_mobile
75 +
76 +Asterisk Channel Driver to allow Bluetooth Cell/Mobile Phones to be used as FXO devices, and Headsets as FXS devices.
77 +
78 +Features :-
79 +
80 +Multiple cell phones can be connected.
81 +Multiple headsets can be connected.
82 +Asterisk automatically connects to each configured cell phone / headset when it comes in range.
83 +CLI command to discover bluetooth devices.
84 +Inbound calls on the cell network to the cell phones are handled by Asterisk, just like inbound calls on a Zap channel.
85 +CLI passed through on inbound calls.
86 +Dial outbound on a cell phone using Dial(Mobile/device/nnnnnnn) in the dialplan.
87 +Dial a headset using Dial(Mobile/device) in the dialplan.
88 +Application MobileStatus can be used in the dialplan to see if a cell phone / headset is connected.
89 +Supports devicestate for dialplan hinting.
90 +Supports Inbound and Outbound SMS.
91 +
92 +Using chan_mobile :-
93 +
94 +In order to use chan_mobile, you must have a working bluetooth subsystem on your Asterisk box.
95 +This means a working bluetooth adapter, and the BlueZ packages.
96 +
97 +Any bluetooth adapter supported by the Linux kernel will do, including usb bluetooth dongles.
98 +
99 +The BlueZ package you need is bluez-utils. If you are using a GUI then you might want to install bluez-pin also.
100 +You also need libbluetooth, and libbluetooth-dev if you are compiling Asterisk from source.
101 +
102 +You need to get bluetooth working with your phone before attempting to use chan_mobile.
103 +This means 'pairing' your phone with your Asterisk box. I dont describe how to do this here as the process
104 +differs from distro to distro. You only need to pair once.
105 +
106 +However, the easist way to pair, is to use you cell phone to search for bluetooth devices, select your Asterisk box
107 +and enter the requested PIN.
108 +
109 +See www.bluez.org for other details about setting up Bluetooth under Linux.
110 +
111 +Assuming you have bluetooth working ok:-
112 +
113 +Load chan_mobile.so
114 +
115 +Search for your bluetooth devices using the CLI command 'mobile search'. Be patient with this command as
116 +it will take 8 - 10 seconds to do the discovery.
117 +
118 +Headsets will generally have to be put into 'pairing' mode before they will show up here.
119 +
120 +This will return something like the following :-
121 +
122 +*CLI> mobile search
123 +Address Name Usable Type Port
124 +00:12:56:90:6E:00 LG TU500 Yes Phone 4
125 +00:80:C8:35:52:78 Toaster No Headset 0
126 +00:0B:9E:11:74:A5 Hello II Plus Yes Headset 1
127 +00:0F:86:0E:AE:42 Daves Blackberry Yes Phone 7
128 +
129 +This is a list of all bluetooth devices seen and whether or not they are usable with chan_cellphone.
130 +The Address field contains the 'bd address' of the device. This is like an ethernet mac address.
131 +The Name field is whatever is configured into the device as its name.
132 +The Usable field tells you whether or not the device supports the Bluetooth Handsfree Profile or Headset profile.
133 +The Type field tells you whether the device is usable as a Phone line (FXO) or a headset (FXS)
134 +The Port field is the number to put in the configuration file.
135 +
136 +Choose which device(s) you want to use and edit /etc/asterisk/mobile.conf. There is a sample included
137 +with the Asterisk source under configs/mobile.conf.sample.
138 +
139 +Assuming we want to use the devices above, mobile.conf needs to look like this :-
140 +
141 +===================================================================================
142 +;
143 +; mobile.conf
144 +;
145 +
146 +[general]
147 +interval=60 ; Number of seconds between trying to connect to devices.
148 +
149 +; The following is a list of the devices we deal with.
150 +; Every device listed below will be available for calls in and out of Asterisk.
151 +; Discovered devices not in this list are not available.
152 +; Use the CLI command 'mobile search' to discover devices.
153 +; Use the CLI command 'mobile show devices' to see device status.
154 +;
155 +; To place a call use Dial(Mobile/[device]/NNN.....) in your dialplan.
156 +
157 +[dave]
158 +address=00:12:56:90:6E:00
159 +port=4
160 +context=incoming-mobile
161 +
162 +[headset]
163 +address=00:0B:9E:11:74:A5
164 +port=1
165 +type=headset
166 +===================================================================================
167 +
168 +Be sure to configure the right bd address and port number from the search. If you want inbound
169 +calls on a device to go to a specific context, add a context= line, otherwise the default will
170 +be used. The 'id' of the device [bitinbrackets] can be anything you like, just make the unique.
171 +
172 +If your are configuring a Headset be sure to include the type=headset line, if left out it defaults
173 +to phone.
174 +
175 +Having done this, unload chan_mobile and load it again.
176 +
177 +The CLI command 'mobile show devices' can be used at any time to show the status of configured devices,
178 +and whether or not the device is capable of sending / receiving SMS via bluetooth.
179 +
180 +*CLI> mobile show devices
181 +ID Address Connected State SMS
182 +blackberry 00:0F:86:0E:AE:42 Yes Free Yes
183 +dave 00:12:56:90:6E:00 Yes Free No
184 +headset 00:0B:9E:11:74:A5 Yes Free No
185 +*CLI>
186 +
187 +
188 +All being well Asterisk will now try and establish a connection to each configured device. If it cant
189 +it will retry after 'interval' seconds, infinately.
190 +
191 +This means that as your cell phone comes into range and goes out of range, Asterisk will automatically
192 +connect and disconnect from it. You dont need to worry about it.
193 +
194 +As each phone is connected you will see a message on the Asterisk console :-
195 +
196 + Loaded chan_mobile.so => (Bluetooth Mobile Device Channel Driver)
197 + -- Bluetooth Device blackberry has connected.
198 + -- Bluetooth Device dave has connected.
199 +
200 +If someone calls your cell phone now, Asterisk will handle the call and it will be sent into the
201 +context you specified, or the default context. Mostly likely this means some SIP phone somewhere will
202 +ring, pick it up and take the call.
203 +
204 +To make outbound calls, add something to you Dialplan like the following :- (modify to suit)
205 +
206 +; Calls via TU500
207 +exten => _9X.,1,Dial(Mobile/dave/${EXTEN:1},45)
208 +exten => _9X.,n,Hangup
209 +; Calls via Blackberry
210 +exten => _8X.,1,Dial(Mobile/blackberry/${EXTEN:1},45)
211 +exten => _8X.,n,Hangup
212 +
213 +Pick up a SIP phone and dial 9<number of pizza shop> and the call vill go via the device 'dave' in
214 +mobile.conf.
215 +
216 +To incoming calls to a headset do something like this :-
217 +
218 +[incoming-context]
219 +exten => s,1,Dial(Mobile/headset,30)
220 +exten => s,n,Hangup()
221 +
222 +To dial out on a headset, you need to use some other mechanism, because the headset is not likely
223 +to have all the needed buttons on it. res_clioriginate is good for this :-
224 +
225 +*CLI> originate Mobile/headset extension NNNNN@context
226 +
227 +This will call your headset, once you answer Asterisk will call NNNNN at context context
228 +
229 +Dialplan hints :-
230 +
231 +chan_mobile supports 'device status' so you can do somthing like
232 +
233 +exten => 1234,hint,SIP/30&Mobile/dave&Mobile/blackberry
234 +
235 +
236 +MobileStatus Application :-
237 +
238 +chan_mobile also registers an application named MobileStatus. You can use this in your Dialplan
239 +to determine the 'state' of a device.
240 +
241 +For example, suppose you wanted to call dave's extension, but only if he was in the office. You could
242 +test to see if his cell phone was attached to Asterisk, if it is dial his extension, otherwise dial his
243 +cell phone.
244 +
245 +exten => 40,1,MobileStatus(dave,DAVECELL)
246 +exten => 40,2,GotoIf($["${DAVECELL}" = "1"]?3:5)
247 +exten => 40,3,Dial(ZAP/g1/0427466412,45,tT)
248 +exten => 40,4,Hangup
249 +exten => 40,5,Dial(SIP/40,45,tT)
250 +exten => 40,6,Hangup
251 +
252 +MobileStatus sets the value of the given variable to :-
253 +
254 +1 = Disconnected. i.e. Device not in range of Asterisk, or turned off etc etc
255 +2 = Connected and Not on a call. i.e. Free
256 +3 = Connected and on a call. i.e. Busy
257 +
258 +
259 +SMS Sending / Receiving
260 +
261 +If Asterisk has detected your cell phone is capable of SMS via bluetooth, you will be able to send and
262 +receive SMS.
263 +
264 +Incoming SMS's cause Asterisk to create an inbound call to the context you defined in mobile.conf or the default
265 +context if you did not define one. The call will start at extension 'sms'. Two channel variables will be available,
266 +SMSSRC = the number of the originator of the SMS and SMSTXT which is the text of the SMS.
267 +This is not a voice call, so grab the values of the variables and hang the call up.
268 +
269 +So, to handle incoming SMS's, do something like the following in your dialplan
270 +
271 +[incoming-mobile]
272 +exten => sms,1,Verbose(Incoming SMS from ${SMSSRC} ${SMSTXT})
273 +exten => sms,n,Hangup()
274 +
275 +The above will just print the message on the console.
276 +
277 +If you use res_jabber, you could do something like this :-
278 +
279 +[incoming-mobile]
280 +exten => sms,1,JabberSend(transport,user@jabber.somewhere.com,SMS from ${SMSRC} ${SMSTXT})
281 +exten => sms,2,Hangup()
282 +
283 +To send an SMS, use the application MobileSendSMS like the following :-
284 +
285 +exten => 99,1,MobileSendSMS(dave,0427123456,Hello World)
286 +
287 +This will send 'Hello World' via device 'dave' to '0427123456'
288 +
289 +
290 +DTMF Debouncing :-
291 +
292 +DTMF detection varies from phone to phone. There is a configuration variable that allows you to tune
293 +this to your needs. e.g. in mobile.conf
294 +
295 +[dave]
296 +address=00:12:56:90:6E:00
297 +port=4
298 +context=incoming-mobile
299 +dtmfskip=50
300 +
301 +change dtmfskip to suit your phone. The default is 200. The larger the number, the more chance of missed DTMF.
302 +The smaller the number the more chance of multiple digits being detected.
303 +
304 +
305 +Debugging :-
306 +
307 +Different phone manufacturers have different interpretations of the Bluetooth Handsfree Profile Spec.
308 +This means that not all phones work the same way, particularly in the connection setup / initialisation
309 +sequence. I've tried to make chan_cellphone as general as possible, but it may need modification to
310 +support some phone i've never tested.
311 +
312 +The RIM Blackberry 7250 works extremely well. So does the LG TU500.
313 +
314 +Some phones, most notably Sony Ericsson 'T' series, dont quite conform to the Bluetooth HFP spec.
315 +chan_mobile will detect these and adapt accordingly. The T-610 and T-630 have been tested and
316 +work fine.
317 +
318 +If your phone doesnt behave has expected, turn on Asterisk debugging with 'core set debug 1'.
319 +
320 +This will log a bunch of debug messages indicating what the phone is doing, importantly the rfcomm
321 +conversation between Asterisk and the phone. This can be used to sort out what your phone is doing
322 +and make chan_mobile support it.
323 +
324 +Be aware also, that just about all cell phones behave differently. For example my LG TU500 wont dial unless
325 +the phone is a the 'idle' screen. i.e. if the phone is showing a 'menu' on the display, when you dial via
326 +Asterisk, the call will not work. chan_mobile handles this, but there may be other phones that do
327 +other things too...
328 +
329 +Important: Watch what your cell phone is doing the first few times. Asterisk wont make random calls but
330 +if chan_mobile fails to hangup for some reason and you get a huge bill from your telco, dont blame me.
331 +
332 +
333 +Feedback, Support, Please can you make Cell Phone X work... etc :-
334 +
335 +email me at david.bowerman at gmail.com or dseeb_ on #asterisk & #asterisk-dev irc.
336 diff -Nru asterisk-addons-1.4.2/Makefile asterisk-addons-svn/Makefile
337 --- asterisk-addons-1.4.2/Makefile 2007-06-06 00:05:09.000000000 +0200
338 +++ asterisk-addons-svn/Makefile 2007-07-28 15:12:17.000000000 +0200
339 @@ -49,7 +49,7 @@
340 endif
341 MODULES_DIR=$(ASTLIBDIR)/modules
342
343 -MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql
344 +MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql chan_mobile
345
346 SELECTED_MODS:=$(patsubst %,%.so,$(filter-out $(MENUSELECT_ADDONS),$(MODS)))
347
348 @@ -143,6 +143,9 @@
349 app_addon_sql_mysql.so: app_addon_sql_mysql.o
350 $(CC) $(SOLINK) -o $@ $< $(MYSQLCLIENT_LIB)
351
352 +chan_mobile.so: chan_mobile.o
353 + $(CC) $(SOLINK) -o $@ $< $(BLUETOOTH_LIB)
354 +
355 chan_ooh323.so:
356 @if [ ! -f asterisk-ooh323c/Makefile ] ; then \
357 cd asterisk-ooh323c && ./configure ; \
358 @@ -186,6 +189,8 @@
359 menuselect.makeopts menuselect.makedeps: menuselect/menuselect menuselect-tree
360 @menuselect/menuselect --check-deps $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts
361
362 +menuconfig: menuselect
363 +
364 menuselect: menuselect/menuselect menuselect-tree
365 -@menuselect/menuselect $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts && echo "menuselect changes saved!" || echo "menuselect changes NOT saved!"
366
367 diff -Nru asterisk-addons-1.4.2/makeopts.in asterisk-addons-svn/makeopts.in
368 --- asterisk-addons-1.4.2/makeopts.in 2007-05-14 18:22:44.000000000 +0200
369 +++ asterisk-addons-svn/makeopts.in 2007-06-04 19:11:00.000000000 +0200
370 @@ -33,6 +33,9 @@
371 sharedstatedir = @sharedstatedir@
372 sysconfdir = @sysconfdir@
373
374 +BLUETOOTH_LIB=@BLUETOOTH_LIB@
375 +BLUETOOTH_INCLUDE=@BLUETOOTH_INCLUDE@
376 +
377 CURSES_LIB=@CURSES_LIB@
378 CURSES_INCLUDE=@CURSES_INCLUDE@
379
380 diff -Nru asterisk-addons-1.4.2/menuselect-tree asterisk-addons-svn/menuselect-tree
381 --- asterisk-addons-1.4.2/menuselect-tree 2007-05-14 18:22:44.000000000 +0200
382 +++ asterisk-addons-svn/menuselect-tree 2007-06-04 19:11:00.000000000 +0200
383 @@ -13,6 +13,10 @@
384 <depend>mysqlclient</depend>
385 <depend>asterisk</depend>
386 </member>
387 + <member name="chan_mobile" remove_on_change="chan_mobile.so chan_mobile.o" displayname="Bluetooth Mobile Device channel driver">
388 + <depend>bluetooth</depend>
389 + <depend>asterisk</depend>
390 + </member>
391 <member name="chan_ooh323" displayname="Objective Systems H.323 Channel Driver">
392 <depend>asterisk</depend>
393 </member>
394 --- asterisk-addons-1.4.2/chan_mobile.c 1970-01-01 01:00:00.000000000 +0100
395 +++ asterisk-addons-svn/chan_mobile.c 2007-07-29 13:30:43.000000000 +0200
396 @@ -0,0 +1,1867 @@
397 +/*
398 + * Asterisk -- An open source telephony toolkit.
399 + *
400 + * Copyright (C) 1999 - 2006, Digium, Inc.
401 + *
402 + * Mark Spencer <markster@digium.com>
403 + *
404 + * See http://www.asterisk.org for more information about
405 + * the Asterisk project. Please do not directly contact
406 + * any of the maintainers of this project for assistance;
407 + * the project provides a web site, mailing lists and IRC
408 + * channels for your use.
409 + *
410 + * This program is free software, distributed under the terms of
411 + * the GNU General Public License Version 2. See the LICENSE file
412 + * at the top of the source tree.
413 + */
414 +
415 +/*! \file
416 + *
417 + * \brief Bluetooth Mobile Device channel driver
418 + *
419 + * \author Dave Bowerman <david.bowerman@gmail.com>
420 + *
421 + * \ingroup channel_drivers
422 + */
423 +
424 +/*** MODULEINFO
425 + <depend>bluetooth</depend>
426 + ***/
427 +
428 +#include <asterisk.h>
429 +
430 +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 416 $")
431 +
432 +#include <stdio.h>
433 +#include <string.h>
434 +#include <sys/socket.h>
435 +#include <sys/time.h>
436 +#include <errno.h>
437 +#include <unistd.h>
438 +#include <stdlib.h>
439 +#include <arpa/inet.h>
440 +#include <fcntl.h>
441 +#include <sys/ioctl.h>
442 +#include <signal.h>
443 +
444 +#include <bluetooth/bluetooth.h>
445 +#include <bluetooth/hci.h>
446 +#include <bluetooth/hci_lib.h>
447 +#include <bluetooth/sdp.h>
448 +#include <bluetooth/sdp_lib.h>
449 +#include <bluetooth/rfcomm.h>
450 +#include <bluetooth/sco.h>
451 +
452 +#include <asterisk/lock.h>
453 +#include <asterisk/channel.h>
454 +#include <asterisk/config.h>
455 +#include <asterisk/logger.h>
456 +#include <asterisk/module.h>
457 +#include <asterisk/pbx.h>
458 +#include <asterisk/options.h>
459 +#include <asterisk/utils.h>
460 +#include <asterisk/linkedlists.h>
461 +#include <asterisk/cli.h>
462 +#include <asterisk/devicestate.h>
463 +#include <asterisk/causes.h>
464 +#include <asterisk/dsp.h>
465 +
466 +#ifndef ast_debug
467 +#define ast_debug(level, ...) do { \
468 + if (option_debug >= (level)) { \
469 + ast_log(LOG_DEBUG, __VA_ARGS__); \
470 + } \
471 +} while (0)
472 +#endif
473 +
474 +#define AST_MODULE "chan_mobile"
475 +
476 +#define MBL_CONFIG "mobile.conf"
477 +
478 +static int prefformat = AST_FORMAT_SLINEAR;
479 +
480 +static int discovery_interval = 60; /* The device discovery interval, default 60 seconds. */
481 +static int sco_socket; /* This is global so it can be closed on module unload outside of the listener thread */
482 +static sdp_session_t *sdp_session;
483 +
484 +enum mbl_type {
485 + MBL_TYPE_PHONE,
486 + MBL_TYPE_HEADSET
487 +};
488 +
489 +enum mbl_state {
490 + MBL_STATE_INIT = 0,
491 + MBL_STATE_INIT1,
492 + MBL_STATE_INIT2,
493 + MBL_STATE_INIT3,
494 + MBL_STATE_INIT4,
495 + MBL_STATE_INIT5,
496 + MBL_STATE_INIT6,
497 + MBL_STATE_PREIDLE,
498 + MBL_STATE_IDLE,
499 + MBL_STATE_DIAL,
500 + MBL_STATE_DIAL1,
501 + MBL_STATE_OUTGOING,
502 + MBL_STATE_RING,
503 + MBL_STATE_RING2,
504 + MBL_STATE_RING3,
505 + MBL_STATE_INCOMING,
506 + MBL_STATE_HANGUP,
507 + MBL_STATE_INSMS,
508 + MBL_STATE_OUTSMS,
509 + MBL_STATE_OUTSMS1,
510 + MBL_STATE_OUTSMS2
511 +};
512 +
513 +struct mbl_pvt {
514 + struct ast_channel *owner; /* Channel we belong to, possibly NULL */
515 + struct ast_frame fr; /* "null" frame */
516 + enum mbl_type type; /* Phone or Headset */
517 + char id[31]; /* The id from mobile.conf */
518 + char bdaddr[18]; /* the bdaddr of the device */
519 + char context[AST_MAX_CONTEXT]; /* the context for incoming calls */
520 + char connected; /* is it connected? */
521 + int rfcomm_port; /* rfcomm port number */
522 + int rfcomm_socket; /* rfcomm socket descriptor */
523 + char rfcomm_buf[256];
524 + int sco_socket; /* sco socket descriptor */
525 + enum mbl_state state; /* monitor thread current state */
526 + pthread_t monitor_thread; /* monitor thread handle */
527 + char sco_in_buf[48 + AST_FRIENDLY_OFFSET];
528 + char sco_out_buf[352];
529 + char *sco_out_ptr;
530 + int sco_out_len;
531 + char dial_number[AST_MAX_EXTENSION]; /* number for the monitor thread to dial */
532 + int dial_timeout;
533 + char ciev_call_0[4]; /* dynamically build reponse strings */
534 + char ciev_call_1[4];
535 + char ciev_callsetup_0[4];
536 + char ciev_callsetup_1[4];
537 + char ciev_callsetup_2[4];
538 + char ciev_callsetup_3[4];
539 + char no_callsetup;
540 + char has_sms;
541 + char sms_txt[161];
542 + struct ast_dsp *dsp;
543 + struct ast_frame *dsp_fr;
544 + int dtmf_skip;
545 + int skip_frames;
546 + char sent_answer;
547 + char hangup_count;
548 + AST_LIST_ENTRY(mbl_pvt) entry;
549 +};
550 +
551 +static AST_LIST_HEAD_STATIC(devices, mbl_pvt);
552 +
553 +/* The discovery thread */
554 +static pthread_t discovery_thread = AST_PTHREADT_NULL;
555 +/* The sco listener thread */
556 +static pthread_t sco_listener_thread = AST_PTHREADT_NULL;
557 +
558 +/* CLI stuff */
559 +static const char show_usage[] =
560 +"Usage: mobile show devices\n"
561 +" Shows the state of Bluetooth Cell / Mobile devices.\n";
562 +
563 +static const char search_usage[] =
564 +"Usage: mobile search\n"
565 +" Searches for Bluetooth Cell / Mobile devices in range.\n";
566 +
567 +static const char rfcomm_usage[] =
568 +"Usage: mobile rfcomm command\n"
569 +" Send command to the rfcomm port.\n";
570 +
571 +static int do_show_devices(int, int, char **);
572 +static int do_search_devices(int, int, char **);
573 +static int do_send_rfcomm(int, int, char **);
574 +
575 +static struct ast_cli_entry mbl_cli[] = {
576 + {{"mobile", "show", "devices", NULL}, do_show_devices, "Show Bluetooth Cell / Mobile devices", show_usage},
577 + {{"mobile", "search", NULL}, do_search_devices, "Search for Bluetooth Cell / Mobile devices", search_usage},
578 + {{"mobile", "rfcomm", NULL}, do_send_rfcomm, "Send commands to the rfcomm port for debugging", rfcomm_usage}
579 +};
580 +
581 +/* App stuff */
582 +static char *app_mblstatus = "MobileStatus";
583 +static char *mblstatus_synopsis = "MobileStatus(Device,Variable)";
584 +static char *mblstatus_desc =
585 +"MobileStatus(Device,Variable)\n"
586 +" Device - Id of mobile device from mobile.conf\n"
587 +" Variable - Variable to store status in will be 1-3.\n"
588 +" In order, Disconnected, Connected & Free, Connected & Busy.\n";
589 +
590 +static char *app_mblsendsms = "MobileSendSMS";
591 +static char *mblsendsms_synopsis = "MobileSendSMS(Device,Dest,Message)";
592 +static char *mblsendsms_desc =
593 +"MobileSendSms(Device,Dest,Message)\n"
594 +" Device - Id of device from mobile.conf\n"
595 +" Dest - destination\n"
596 +" Message - text of the message\n";
597 +
598 +static struct ast_channel *mbl_request(const char *type, int format, void *data, int *cause);
599 +static int mbl_call(struct ast_channel *ast, char *dest, int timeout);
600 +static int mbl_hangup(struct ast_channel *ast);
601 +static int mbl_answer(struct ast_channel *ast);
602 +static int mbl_digit_begin(struct ast_channel *ast, char digit);
603 +static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
604 +static struct ast_frame *mbl_read(struct ast_channel *ast);
605 +static int mbl_write(struct ast_channel *ast, struct ast_frame *frame);
606 +static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
607 +static int mbl_devicestate(void *data);
608 +static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num);
609 +
610 +static int rfcomm_write(struct mbl_pvt *pvt, char *buf);
611 +static int rfcomm_read(struct mbl_pvt *pvt, char *buf, char flush, int timeout);
612 +static int sco_connect(char *bdaddr);
613 +static int sdp_search(char *addr, int profile);
614 +
615 +static const struct ast_channel_tech mbl_tech = {
616 + .type = "Mobile",
617 + .description = "Bluetooth Mobile Device Channel Driver",
618 + .capabilities = AST_FORMAT_SLINEAR,
619 + .requester = mbl_request,
620 + .call = mbl_call,
621 + .hangup = mbl_hangup,
622 + .answer = mbl_answer,
623 + .send_digit_begin = mbl_digit_begin,
624 + .send_digit_end = mbl_digit_end,
625 + .read = mbl_read,
626 + .write = mbl_write,
627 + .fixup = mbl_fixup,
628 + .devicestate = mbl_devicestate
629 +};
630 +
631 +static int do_show_devices(int fd, int argc, char **argv)
632 +{
633 +
634 + struct mbl_pvt *pvt;
635 +
636 + #define FORMAT "%-15.15s %-17.17s %-9.9s %-5.5s %-3.3s\n"
637 +
638 + ast_cli(fd, FORMAT, "ID", "Address", "Connected", "State", "SMS");
639 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
640 + 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");
641 + }
642 +
643 + return RESULT_SUCCESS;
644 +
645 +}
646 +
647 +static int do_search_devices(int fd, int argc, char **argv)
648 +{
649 +
650 + int hci_socket;
651 + inquiry_info *ii = NULL;
652 + int max_rsp, num_rsp;
653 + int dev_id, len, flags;
654 + int i, phport, hsport;
655 + char addr[19] = {0};
656 + char name[31] = {0};
657 +
658 + #define FORMAT2 "%-17.17s %-30.30s %-6.6s %-7.7s %-4.4s\n"
659 + #define FORMAT3 "%-17.17s %-30.30s %-6.6s %-7.7s %d\n"
660 +
661 + dev_id = hci_get_route(NULL);
662 + hci_socket = hci_open_dev(dev_id);
663 + len = 8;
664 + max_rsp = 255;
665 + flags = IREQ_CACHE_FLUSH;
666 +
667 + ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
668 + num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
669 + if (num_rsp > 0) {
670 + ast_cli(fd, FORMAT2, "Address", "Name", "Usable", "Type", "Port");
671 + for (i = 0; i < num_rsp; i++) {
672 + ba2str(&(ii+i)->bdaddr, addr);
673 + name[0] = 0x00;
674 + if (hci_read_remote_name(hci_socket, &(ii+i)->bdaddr, sizeof(name)-1, name, 0) < 0)
675 + strcpy(name, "[unknown]");
676 + phport = sdp_search(addr, HANDSFREE_AGW_PROFILE_ID);
677 + if (!phport)
678 + hsport = sdp_search(addr, HEADSET_PROFILE_ID);
679 + else
680 + hsport = 0;
681 + ast_cli(fd, FORMAT3, addr, name, (phport > 0 || hsport > 0)?"Yes":"No", (phport > 0)?"Phone":"Headset", (phport > 0)?phport:hsport);
682 + }
683 + } else
684 + ast_cli(fd, "No Bluetooth Cell / Mobile devices found.\n");
685 +
686 + free(ii);
687 +
688 + hci_close_dev(hci_socket);
689 +
690 + return RESULT_SUCCESS;
691 +
692 +}
693 +
694 +static int do_send_rfcomm(int fd, int argc, char **argv)
695 +{
696 +
697 + struct mbl_pvt *pvt;
698 + char buf[128];
699 +
700 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
701 + if (!strcmp(pvt->id, argv[2]))
702 + break;
703 + }
704 +
705 + if (!pvt || !pvt->connected) {
706 + sprintf(buf, "Device %s not found.\n", argv[2]);
707 + ast_cli(fd, buf);
708 + return RESULT_SUCCESS;
709 + }
710 +
711 + sprintf(buf, "%s\r", argv[3]);
712 + rfcomm_write(pvt, buf);
713 +
714 + return RESULT_SUCCESS;
715 +
716 +}
717 +
718 +static int mbl_status_exec(struct ast_channel *ast, void *data)
719 +{
720 +
721 + struct mbl_pvt *pvt;
722 + char *args = NULL, *device = NULL, *variable = NULL;
723 + int stat;
724 + char status[2];
725 +
726 + if (!data)
727 + return -1;
728 +
729 + args = ast_strdupa((char *)data);
730 + device = strsep(&args, "|");
731 + if (device && (device[0] != 0x00)) {
732 + variable = args;
733 + } else
734 + return -1;
735 +
736 + stat = 1;
737 +
738 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
739 + if (!strcmp(pvt->id, device))
740 + break;
741 + }
742 +
743 + if (pvt) {
744 + if (pvt->connected)
745 + stat = 2;
746 + if (pvt->owner)
747 + stat = 3;
748 + }
749 +
750 + sprintf(status, "%d", stat);
751 + pbx_builtin_setvar_helper(ast, variable, status);
752 +
753 + return 0;
754 +
755 +}
756 +
757 +static int mbl_sendsms_exec(struct ast_channel *ast, void *data)
758 +{
759 +
760 + struct mbl_pvt *pvt;
761 + char *args = NULL, *device = NULL, *dest = NULL, *message = NULL;
762 +
763 + if (!data)
764 + return -1;
765 +
766 + args = ast_strdupa((char *)data);
767 + device = strsep(&args, "|");
768 + if (device && (device[0] != 0x00)) {
769 + dest = strsep(&args, "|");
770 + if (dest && (dest[0] != 0x00)) {
771 + message = args;
772 + if (!message || (message[0] == 0x00)) {
773 + ast_log(LOG_ERROR,"NULL Message to be sent-- SMS will not be sent.\n");
774 + return -1;
775 + }
776 + } else {
777 + ast_log(LOG_ERROR,"NULL destination for message -- SMS will not be sent.\n");
778 + return -1;
779 + }
780 +
781 + } else {
782 + ast_log(LOG_ERROR,"NULL device for message -- SMS will not be sent.\n");
783 + return -1;
784 + }
785 +
786 +
787 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
788 + if (!strcmp(pvt->id, device))
789 + break;
790 + }
791 +
792 + if (!pvt) {
793 + ast_log(LOG_ERROR,"Bluetooth device %s wasn't found in the list -- SMS will not be sent.\n",device);
794 + return -1;
795 + }
796 +
797 + if (!pvt->connected) {
798 + ast_log(LOG_ERROR,"bluetooth device %s wasn't connected -- SMS will not be sent.\n",device);
799 + return -1;
800 + }
801 +
802 + if (!pvt->has_sms) {
803 + ast_log(LOG_ERROR,"bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n",device);
804 + return -1;
805 + }
806 +
807 + if (pvt->state != MBL_STATE_IDLE) {
808 + ast_log(LOG_ERROR,"bluetooth device %s isn't IDLE -- SMS will not be sent.\n",device);
809 + return -1;
810 + }
811 +
812 + strcpy(pvt->dial_number, dest);
813 + memset(pvt->sms_txt, 0x0, sizeof(pvt->sms_txt));
814 + strncpy(pvt->sms_txt, message, 160);
815 + pvt->state = MBL_STATE_OUTSMS;
816 +
817 + return 0;
818 +
819 +}
820 +
821 +static struct ast_channel *mbl_request(const char *type, int format, void *data, int *cause)
822 +{
823 +
824 + struct ast_channel *chn = NULL;
825 + struct mbl_pvt *pvt;
826 + char *dest_dev = NULL;
827 + char *dest_num = NULL;
828 + int oldformat;
829 +
830 + if (!data) {
831 + ast_log(LOG_WARNING, "Channel requested with no data\n");
832 + *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
833 + return NULL;
834 + }
835 +
836 + oldformat = format;
837 + format &= (AST_FORMAT_SLINEAR);
838 + if (!format) {
839 + ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n", oldformat);
840 + *cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
841 + return NULL;
842 + }
843 +
844 + dest_dev = ast_strdupa((char *)data);
845 +
846 + dest_num = strchr(dest_dev, '/');
847 + if (dest_num)
848 + *dest_num++ = 0x00;
849 +
850 + /* Find requested device and make sure its connected. */
851 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
852 + if (!strcmp(pvt->id, dest_dev)) {
853 + break;
854 + }
855 + }
856 + if (!pvt || !pvt->connected || pvt->owner) {
857 + ast_log(LOG_WARNING, "Request to call on device %s which is not connected / already in use.\n", dest_dev);
858 + *cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
859 + return NULL;
860 + }
861 +
862 + if ((pvt->type == MBL_TYPE_PHONE) && !dest_num) {
863 + ast_log(LOG_WARNING, "Cant determine destination number.\n");
864 + *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
865 + return NULL;
866 + }
867 +
868 + pvt->sco_out_ptr = pvt->sco_out_buf;
869 + pvt->sco_out_len = 0;
870 +
871 + chn = mbl_new(AST_STATE_DOWN, pvt, NULL);
872 + if (!chn) {
873 + ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
874 + *cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
875 + return NULL;
876 + }
877 +
878 + return chn;
879 +
880 +}
881 +
882 +static int mbl_call(struct ast_channel *ast, char *dest, int timeout)
883 +{
884 +
885 + struct mbl_pvt *pvt;
886 + char *dest_dev = NULL;
887 + char *dest_num = NULL;
888 +
889 + dest_dev = ast_strdupa((char *)dest);
890 +
891 + pvt = ast->tech_pvt;
892 +
893 + if (pvt->type == MBL_TYPE_PHONE) {
894 + dest_num = strchr(dest_dev, '/');
895 + if (!dest_num) {
896 + ast_log(LOG_WARNING, "Cant determine destination number.\n");
897 + return -1;
898 + }
899 + *dest_num++ = 0x00;
900 + }
901 +
902 + if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
903 + ast_log(LOG_WARNING, "mbl_call called on %s, neither down nor reserved\n", ast->name);
904 + return -1;
905 + }
906 +
907 + ast_debug(1, "Calling %s on %s\n", dest, ast->name);
908 +
909 + if (pvt->type == MBL_TYPE_PHONE) {
910 + ast_copy_string(pvt->dial_number, dest_num, sizeof(pvt->dial_number));
911 + pvt->state = MBL_STATE_DIAL;
912 + pvt->dial_timeout = (timeout == 0) ? 30 : timeout;
913 + } else {
914 + pvt->state = MBL_STATE_RING;
915 + }
916 +
917 +
918 + return 0;
919 +
920 +}
921 +
922 +static int mbl_hangup(struct ast_channel *ast)
923 +{
924 +
925 + struct mbl_pvt *pvt;
926 +
927 + if (!ast->tech_pvt) {
928 + ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
929 + return 0;
930 + }
931 + pvt = ast->tech_pvt;
932 +
933 + ast_debug(1, "Hanging up device %s.\n", pvt->id);
934 +
935 + ast_channel_lock(ast);
936 + ast->fds[0] = -1;
937 + ast_channel_unlock(ast);
938 +
939 + if (pvt->type == MBL_TYPE_HEADSET && pvt->sco_socket != -1) {
940 + close(pvt->sco_socket);
941 + pvt->sco_socket = -1;
942 + }
943 +
944 + 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) {
945 + rfcomm_write(pvt, "AT+CHUP\r");
946 + pvt->state = MBL_STATE_HANGUP;
947 + pvt->hangup_count = 0;
948 + } else
949 + pvt->state = MBL_STATE_IDLE;
950 +
951 + pvt->owner = NULL;
952 + ast->tech_pvt = NULL;
953 + ast_setstate(ast, AST_STATE_DOWN);
954 +
955 + return 0;
956 +
957 +}
958 +
959 +static int mbl_answer(struct ast_channel *ast)
960 +{
961 +
962 + struct mbl_pvt *pvt;
963 +
964 + pvt = ast->tech_pvt;
965 +
966 + rfcomm_write(pvt, "ATA\r");
967 +
968 + ast_setstate(ast, AST_STATE_UP);
969 +
970 + pvt->sent_answer = 1;
971 +
972 + return 0;
973 +
974 +}
975 +
976 +static int mbl_digit_begin(struct ast_channel *chan, char digit)
977 +{
978 +
979 + return 0;
980 +
981 +}
982 +
983 +static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
984 +{
985 +
986 + struct mbl_pvt *pvt;
987 + char buf[11];
988 +
989 + pvt = ast->tech_pvt;
990 +
991 + if (pvt->type == MBL_TYPE_HEADSET)
992 + return 0;
993 +
994 + ast_debug(1, "Dialed %c\n", digit);
995 +
996 + switch(digit) {
997 + case '0':
998 + case '1':
999 + case '2':
1000 + case '3':
1001 + case '4':
1002 + case '5':
1003 + case '6':
1004 + case '7':
1005 + case '8':
1006 + case '9':
1007 + case '*':
1008 + case '#':
1009 + sprintf(buf, "AT+VTS=%c\r", digit);
1010 + rfcomm_write(pvt, buf);
1011 + break;
1012 + default:
1013 + ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit);
1014 + return -1;
1015 + }
1016 +
1017 + return 0;
1018 +
1019 +}
1020 +
1021 +/*
1022 +
1023 + The SCO protocol basically delivers audio in 48 byte 'frames' in slin format.
1024 + Here we just package these into an ast_frame and return them.
1025 + The SCO connection from the device to Asterisk happens asynchronously, so it is feasible
1026 + that Asterisk will call mbl_read() before the device has connected. In that case we just return
1027 + a null frame.
1028 +
1029 +*/
1030 +
1031 +static struct ast_frame *mbl_read(struct ast_channel *ast)
1032 +{
1033 +
1034 + struct mbl_pvt *pvt = ast->tech_pvt;
1035 + int r;
1036 + struct ast_frame *f;
1037 +
1038 + if (!pvt->owner) {
1039 + return &ast_null_frame;
1040 + }
1041 +
1042 + if (pvt->state == MBL_STATE_HANGUP) {
1043 + return &ast_null_frame;
1044 + }
1045 +
1046 + if (pvt->sco_socket == -1) {
1047 + return &ast_null_frame;
1048 + }
1049 +
1050 + pvt->fr.frametype = AST_FRAME_VOICE;
1051 + pvt->fr.subclass = AST_FORMAT_SLINEAR;
1052 + pvt->fr.data = pvt->sco_in_buf + AST_FRIENDLY_OFFSET;
1053 +
1054 + if ((r = read(pvt->sco_socket, pvt->fr.data, 48)) == 48) {
1055 + if (pvt->skip_frames == 0) {
1056 + f = ast_dsp_process(0, pvt->dsp, &pvt->fr);
1057 + if (f && (f->frametype == AST_FRAME_DTMF_END)) {
1058 + pvt->fr.frametype = AST_FRAME_DTMF_END;
1059 + pvt->fr.subclass = f->subclass;
1060 + pvt->skip_frames = pvt->dtmf_skip;
1061 + }
1062 + return &pvt->fr;
1063 + } else {
1064 + pvt->skip_frames--;
1065 + }
1066 + } else if (r == -1) {
1067 + ast_debug(1, "mbl_read() read error %d.\n", errno);
1068 + close(pvt->sco_socket);
1069 + pvt->sco_socket = -1;
1070 + ast_channel_lock(ast);
1071 + ast->fds[0] = -1;
1072 + ast_channel_unlock(ast);
1073 + } else {
1074 + ast_debug(1, "mbl_read() read short frame. (%d)\n", r);
1075 + }
1076 +
1077 + return &ast_null_frame;
1078 +
1079 +}
1080 +
1081 +/*
1082 +
1083 + We need to deliver 48 byte 'frames' of slin format audio to the device.
1084 + mbl_write() handles this by buffering short frames until the next time we are called.
1085 +
1086 +*/
1087 +
1088 +static int mbl_write(struct ast_channel *ast, struct ast_frame *frame)
1089 +{
1090 +
1091 + struct mbl_pvt *pvt = ast->tech_pvt;
1092 + int num_frames, i, r;
1093 + char *pfr;
1094 +
1095 + if (frame->frametype != AST_FRAME_VOICE) {
1096 + return 0;
1097 + }
1098 + if (pvt->sco_socket == -1) {
1099 + return 0;
1100 + }
1101 +
1102 + if (pvt->state == MBL_STATE_HANGUP) {
1103 + return 0;
1104 + }
1105 +
1106 + if (frame->datalen > sizeof(pvt->sco_out_buf) - pvt->sco_out_len) {
1107 + frame->datalen = sizeof(pvt->sco_out_buf) - pvt->sco_out_len;
1108 + ast_debug(1, "Overrun on sco_out_buf detected.\n");
1109 + }
1110 +
1111 + memmove(pvt->sco_out_ptr, frame->data, frame->datalen);
1112 + pvt->sco_out_len += frame->datalen;
1113 + num_frames = pvt->sco_out_len / 48;
1114 +
1115 + pfr = pvt->sco_out_buf;
1116 + for (i=0; i<num_frames; i++) {
1117 + if ((r = write(pvt->sco_socket, pfr, 48)) == -1) {
1118 + close(pvt->sco_socket);
1119 + pvt->sco_socket = -1;
1120 + }
1121 + pfr += 48;
1122 + }
1123 +
1124 + pvt->sco_out_len = pvt->sco_out_len - (num_frames * 48);
1125 + memmove(pvt->sco_out_buf, pfr, pvt->sco_out_len);
1126 + pvt->sco_out_ptr = pvt->sco_out_buf + pvt->sco_out_len;
1127 +
1128 + return 0;
1129 +
1130 +}
1131 +
1132 +static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
1133 +{
1134 +
1135 + struct mbl_pvt *pvt = oldchan->tech_pvt;
1136 +
1137 + if (pvt && pvt->owner == oldchan)
1138 + pvt->owner = newchan;
1139 +
1140 + return 0;
1141 +
1142 +}
1143 +
1144 +static int mbl_devicestate(void *data)
1145 +{
1146 +
1147 + char *device;
1148 + int res = AST_DEVICE_INVALID;
1149 + struct mbl_pvt *pvt;
1150 +
1151 + device = ast_strdupa(S_OR(data, ""));
1152 +
1153 + ast_debug(1, "Checking device state for device %s\n", device);
1154 +
1155 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
1156 + if (!strcmp(pvt->id, device))
1157 + break;
1158 + }
1159 +
1160 + if (pvt) {
1161 + if (pvt->connected) {
1162 + if (pvt->owner)
1163 + res = AST_DEVICE_INUSE;
1164 + else
1165 + res = AST_DEVICE_NOT_INUSE;
1166 + }
1167 + }
1168 +
1169 + return res;
1170 +
1171 +}
1172 +
1173 +static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num)
1174 +{
1175 +
1176 + struct ast_channel *chn;
1177 +
1178 + chn = ast_channel_alloc(1, state, 0, 0, 0, 0, 0, 0, "Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
1179 + if (chn) {
1180 + chn->tech = &mbl_tech;
1181 + chn->nativeformats = prefformat;
1182 + chn->rawreadformat = prefformat;
1183 + chn->rawwriteformat = prefformat;
1184 + chn->writeformat = prefformat;
1185 + chn->readformat = prefformat;
1186 + chn->readq.first = NULL;
1187 + pvt->fr.frametype = AST_FRAME_VOICE;
1188 + pvt->fr.subclass = AST_FORMAT_SLINEAR;
1189 + pvt->fr.datalen = 48;
1190 + pvt->fr.samples = 24;
1191 + pvt->fr.src = "Mobile";
1192 + pvt->fr.offset = AST_FRIENDLY_OFFSET;
1193 + pvt->fr.mallocd = 0;
1194 + pvt->fr.delivery.tv_sec = 0;
1195 + pvt->fr.delivery.tv_usec = 0;
1196 + pvt->fr.data = pvt->sco_in_buf + AST_FRIENDLY_OFFSET;
1197 + chn->tech_pvt = pvt;
1198 + if (state == AST_STATE_RING)
1199 + chn->rings = 1;
1200 + ast_copy_string(chn->context, pvt->context, sizeof(chn->context));
1201 + ast_copy_string(chn->exten, "s", sizeof(chn->exten));
1202 + ast_string_field_set(chn, language, "en");
1203 + if (cid_num)
1204 + chn->cid.cid_num = ast_strdup(cid_num);
1205 + chn->cid.cid_name = ast_strdup(pvt->id);
1206 + pvt->owner = chn;
1207 +
1208 + }
1209 +
1210 + return chn;
1211 +
1212 +}
1213 +
1214 +static int rfcomm_connect(char *bdaddr, int remote_channel) {
1215 +
1216 + bdaddr_t dst;
1217 + struct sockaddr_rc addr;
1218 + int s;
1219 +
1220 + str2ba(bdaddr, &dst);
1221 +
1222 + if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
1223 + ast_debug(1, "socket() failed (%d).\n", errno);
1224 + return -1;
1225 + }
1226 +
1227 + memset(&addr, 0, sizeof(addr));
1228 + addr.rc_family = AF_BLUETOOTH;
1229 + bacpy(&addr.rc_bdaddr, &dst);
1230 + addr.rc_channel = remote_channel;
1231 + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1232 + ast_debug(1, "connect() failed (%d).\n", errno);
1233 + close(s);
1234 + return -1;
1235 + }
1236 +
1237 + return s;
1238 +
1239 +}
1240 +
1241 +static int rfcomm_write(struct mbl_pvt *pvt, char *buf)
1242 +{
1243 +
1244 + char *p;
1245 + ssize_t num_write;
1246 + int len;
1247 +
1248 + ast_debug(1, "rfcomm_write() (%s) [%s]\n", pvt->id, buf);
1249 + len = strlen(buf);
1250 + p = buf;
1251 + while (len > 0) {
1252 + if ((num_write = write(pvt->rfcomm_socket, p, len)) == -1) {
1253 + ast_debug(1, "rfcomm_write() error [%d]\n", errno);
1254 + return 0;
1255 + }
1256 + len -= num_write;
1257 + p += num_write;
1258 + }
1259 +
1260 + return 1;
1261 +
1262 +}
1263 +
1264 +/*
1265 +
1266 + Here we need to return complete '\r' terminated single responses to the devices monitor thread, or
1267 + a timeout if nothing is available.
1268 + The rfcomm connection to the device is asynchronous, so there is no guarantee that responses will
1269 + be returned in a single read() call. We handle this by buffering the input and returning one response
1270 + per call, or a timeout if nothing is available.
1271 +
1272 +*/
1273 +
1274 +static int rfcomm_read(struct mbl_pvt *pvt, char *buf, char flush, int timeout)
1275 +{
1276 +
1277 + int sel, rlen, slen;
1278 + fd_set rfds;
1279 + struct timeval tv;
1280 + char *p;
1281 +
1282 + if (!flush) {
1283 + if ((p = strchr(pvt->rfcomm_buf, '\r'))) {
1284 + *p++ = 0x00;
1285 + if (*p == '\n')
1286 + p++;
1287 + memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
1288 + *(buf + strlen(pvt->rfcomm_buf)) = 0x00;
1289 + memmove(pvt->rfcomm_buf, p, strlen(p));
1290 + *(pvt->rfcomm_buf+strlen(p)) = 0x00;
1291 + return 1;
1292 + }
1293 + } else {
1294 + pvt->rfcomm_buf[0] = 0x00;
1295 + }
1296 +
1297 + FD_ZERO(&rfds);
1298 + FD_SET(pvt->rfcomm_socket, &rfds);
1299 +
1300 + tv.tv_sec = timeout;
1301 + tv.tv_usec = 0;
1302 +
1303 + if ((sel = select(pvt->rfcomm_socket + 1, &rfds, NULL, NULL, &tv)) > 0) {
1304 + if (FD_ISSET(pvt->rfcomm_socket, &rfds)) {
1305 + slen = strlen(pvt->rfcomm_buf);
1306 + rlen = read(pvt->rfcomm_socket, pvt->rfcomm_buf + slen, sizeof(pvt->rfcomm_buf) - slen - 1);
1307 + if (rlen > 0) {
1308 + pvt->rfcomm_buf[slen+rlen] = 0x00;
1309 + if ((p = strchr(pvt->rfcomm_buf, '\r'))) {
1310 + *p++ = 0x00;
1311 + if (*p == '\n')
1312 + p++;
1313 + memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
1314 + *(buf + strlen(pvt->rfcomm_buf)) = 0x00;
1315 + memmove(pvt->rfcomm_buf, p, strlen(p));
1316 + *(pvt->rfcomm_buf+strlen(p)) = 0x00;
1317 + return 1;
1318 + }
1319 + } else
1320 + return rlen;
1321 + }
1322 + } else if (sel == 0) { /* timeout */
1323 + return 0;
1324 + }
1325 +
1326 + return 1;
1327 +
1328 +}
1329 +
1330 +static int sco_connect(char *bdaddr)
1331 +{
1332 +
1333 + bdaddr_t dst;
1334 + struct sockaddr_sco addr;
1335 + int s;
1336 +
1337 + str2ba(bdaddr, &dst);
1338 +
1339 + if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
1340 + ast_debug(1, "socket() failed (%d).\n", errno);
1341 + return -1;
1342 + }
1343 +
1344 + memset(&addr, 0, sizeof(addr));
1345 + addr.sco_family = AF_BLUETOOTH;
1346 + bacpy(&addr.sco_bdaddr, &dst);
1347 +
1348 + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1349 + ast_debug(1, "sco connect() failed (%d).\n", errno);
1350 + close(s);
1351 + return -1;
1352 + }
1353 +
1354 + return s;
1355 +
1356 +}
1357 +
1358 +/*
1359 +
1360 + sdp_search() performs a service discovery on the given device to determine whether
1361 + or not it supports the Handsfree Profile.
1362 +
1363 +*/
1364 +
1365 +static int sdp_search(char *addr, int profile)
1366 +{
1367 +
1368 + sdp_session_t *session = 0;
1369 + bdaddr_t bdaddr;
1370 + uuid_t svc_uuid;
1371 + uint32_t range = 0x0000ffff;
1372 + sdp_list_t *response_list, *search_list, *attrid_list;
1373 + int status, port;
1374 + sdp_list_t *proto_list;
1375 + sdp_record_t *sdprec;
1376 +
1377 + str2ba(addr, &bdaddr);
1378 + port = 0;
1379 + session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
1380 + if (!session) {
1381 + ast_debug(1, "sdp_connect() failed on device %s.\n", addr);
1382 + return 0;
1383 + }
1384 +
1385 + sdp_uuid32_create(&svc_uuid, profile);
1386 + search_list = sdp_list_append(0, &svc_uuid);
1387 + attrid_list = sdp_list_append(0, &range);
1388 + response_list = 0x00;
1389 + status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
1390 + if (status == 0) {
1391 + if (response_list) {
1392 + sdprec = (sdp_record_t *) response_list->data;
1393 + proto_list = 0x00;
1394 + if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
1395 + port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
1396 + sdp_list_free(proto_list, 0);
1397 + }
1398 + sdp_record_free(sdprec);
1399 + sdp_list_free(response_list, 0);
1400 + } else
1401 + ast_debug(1, "No responses returned for device %s.\n", addr);
1402 + } else
1403 + ast_debug(1, "sdp_service_search_attr_req() failed on device %s.\n", addr);
1404 +
1405 +
1406 + sdp_list_free(search_list, 0);
1407 + sdp_list_free(attrid_list, 0);
1408 + sdp_close(session);
1409 +
1410 + return port;
1411 +
1412 +}
1413 +
1414 +/*
1415 +
1416 + sdp_register()
1417 +
1418 + Register GENERIC_AUDIO & HEADSET with the SDP daemon on the Asterisk Box.
1419 + This assists connections to phones/pda's with dud bluetooth stacks like the IMate Jasjam
1420 + and some Nokia's
1421 +
1422 +*/
1423 +
1424 +static sdp_session_t *sdp_register(void)
1425 +{
1426 +
1427 + uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
1428 + uint8_t rfcomm_channel = 1;
1429 + const char *service_name = "Asterisk PABX";
1430 + const char *service_dsc = "Asterisk PABX";
1431 + const char *service_prov = "Asterisk";
1432 +
1433 + uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
1434 + sdp_list_t *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
1435 + sdp_data_t *channel = 0;
1436 +
1437 + int err = 0;
1438 + sdp_session_t *session = 0;
1439 +
1440 + sdp_record_t *record = sdp_record_alloc();
1441 +
1442 + sdp_uuid128_create(&svc_uuid, &service_uuid_int);
1443 + sdp_set_service_id(record, svc_uuid);
1444 +
1445 + sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
1446 + sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);
1447 +
1448 + svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
1449 + svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
1450 + sdp_set_service_classes(record, svc_uuid_list);
1451 +
1452 + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
1453 + root_list = sdp_list_append(0, &root_uuid);
1454 + sdp_set_browse_groups( record, root_list );
1455 +
1456 + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
1457 + l2cap_list = sdp_list_append(0, &l2cap_uuid);
1458 + proto_list = sdp_list_append(0, l2cap_list);
1459 +
1460 + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
1461 + channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
1462 + rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
1463 + sdp_list_append(rfcomm_list, channel);
1464 + sdp_list_append(proto_list, rfcomm_list);
1465 +
1466 + access_proto_list = sdp_list_append(0, proto_list);
1467 + sdp_set_access_protos(record, access_proto_list);
1468 +
1469 + sdp_set_info_attr(record, service_name, service_prov, service_dsc);
1470 +
1471 + if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY)))
1472 + ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n");
1473 + else
1474 + err = sdp_record_register(session, record, 0);
1475 +
1476 + sdp_data_free(channel);
1477 + sdp_list_free(rfcomm_list, 0);
1478 + sdp_list_free(root_list, 0);
1479 + sdp_list_free(access_proto_list, 0);
1480 + sdp_list_free(svc_uuid_list, 0);
1481 +
1482 + return session;
1483 +
1484 +}
1485 +
1486 +/*
1487 +
1488 + Phone Monitor Thread
1489 +
1490 + This thread is spun once a phone device is discovered and considered capable of being used, i.e. supports Handsfree Profile,
1491 + and its configured in mobile.conf.
1492 + The thread lives for the lifetime of the bluetooth connection, and handles the 'control' side of all calls.
1493 +
1494 +*/
1495 +
1496 +static void *do_monitor_phone(void *data)
1497 +{
1498 +
1499 + struct mbl_pvt *pvt = (struct mbl_pvt *)data;
1500 + struct ast_channel *chn;
1501 + char monitor = 1;
1502 + char buf[256];
1503 + char cid_num[AST_MAX_EXTENSION], *pcids, *pcide;
1504 + int s, t, i, smsi;
1505 + int group, group2;
1506 + int callp = 0, callsetupp;
1507 + char brsf, nsmode, *p, *p1;
1508 + char sms_src[13];
1509 + char sms_txt[161];
1510 +
1511 + brsf = nsmode = 0;
1512 +
1513 + if (!rfcomm_write(pvt, "AT+BRSF=4\r"))
1514 + monitor = 0;
1515 +
1516 + while (monitor) {
1517 +
1518 + if (pvt->state == MBL_STATE_DIAL1)
1519 + t = pvt->dial_timeout;
1520 + else if (pvt->state == MBL_STATE_HANGUP)
1521 + t = 2;
1522 + else if (pvt->state == MBL_STATE_OUTSMS1)
1523 + t = 2;
1524 + else if (pvt->state == MBL_STATE_OUTSMS2)
1525 + t = 10;
1526 + else
1527 + t = 1;
1528 +
1529 + s = rfcomm_read(pvt, buf, 0, t);
1530 +
1531 + if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
1532 + ast_debug(1, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
1533 + switch (pvt->state) {
1534 + case MBL_STATE_INIT:
1535 + if (strstr(buf, "+BRSF:")) {
1536 + brsf = 1;
1537 + } else if (strstr(buf, "ERROR") && !nsmode) { /* Hmmm, Non-Standard Phone, just continue */
1538 + rfcomm_write(pvt, "AT+CIND=?\r");
1539 + pvt->state++;
1540 + nsmode = 1;
1541 + } else if (strstr(buf, "OK") && brsf) {
1542 + rfcomm_write(pvt, "AT+CIND=?\r");
1543 + pvt->state++;
1544 + }
1545 + break;
1546 + case MBL_STATE_INIT1:
1547 + if (strstr(buf, "+CIND:")) {
1548 + group = callp = callsetupp = 0;
1549 + group2 = 1;
1550 + for (i=0; i<strlen(buf); i++) {
1551 + if (buf[i] == '(')
1552 + group++;
1553 + if (buf[i] == ')') {
1554 + group--;
1555 + if (group == 0)
1556 + group2++;
1557 + }
1558 + if (strstr(buf+i, "\"call\""))
1559 + callp = group2;
1560 + if (strstr(buf+i, "\"call_setup\""))
1561 + callsetupp = group2;
1562 + if (strstr(buf+i, "\"callsetup\""))
1563 + callsetupp = group2;
1564 + }
1565 + sprintf(pvt->ciev_call_0, "%d,0", callp);
1566 + sprintf(pvt->ciev_call_1, "%d,1", callp);
1567 + sprintf(pvt->ciev_callsetup_0, "%d,0", callsetupp);
1568 + sprintf(pvt->ciev_callsetup_1, "%d,1", callsetupp);
1569 + sprintf(pvt->ciev_callsetup_2, "%d,2", callsetupp);
1570 + sprintf(pvt->ciev_callsetup_3, "%d,3", callsetupp);
1571 + if (callsetupp == 0) /* This phone has no call setup indication!! ... */
1572 + pvt->no_callsetup = 1;
1573 + ast_debug(1, "CIEV_CALL=%d CIEV_CALLSETUP=%d\n", callp, callsetupp);
1574 + }
1575 + if (strstr(buf, "OK")) {
1576 + rfcomm_write(pvt, "AT+CIND?\r");
1577 + pvt->state++;
1578 + }
1579 + break;
1580 + case MBL_STATE_INIT2:
1581 + if ((p = strstr(buf, "+CIND:"))) {
1582 + p += 5;
1583 + if (*(p+(callp*2)) == '1') {
1584 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
1585 + monitor = 0;
1586 + }
1587 + } else if (strstr(buf, "OK")) {
1588 + rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
1589 + pvt->state++;
1590 + }
1591 + break;
1592 + case MBL_STATE_INIT3:
1593 + if (strstr(buf, "OK")) {
1594 + rfcomm_write(pvt, "AT+CLIP=1\r");
1595 + pvt->state++;
1596 + }
1597 + break;
1598 + case MBL_STATE_INIT4:
1599 + if (strstr(buf, "OK")) {
1600 + rfcomm_write(pvt, "AT+CMGF=1\r");
1601 + pvt->state++;
1602 + }
1603 + break;
1604 + case MBL_STATE_INIT5:
1605 + if (strstr(buf, "ERROR")) { /* No SMS Support ! */
1606 + pvt->state = MBL_STATE_PREIDLE;
1607 + } else if (strstr(buf, "OK")) {
1608 + rfcomm_write(pvt, "AT+CNMI=2,1,0,1,0\r");
1609 + pvt->state++;
1610 + }
1611 + break;
1612 + case MBL_STATE_INIT6:
1613 + if (strstr(buf, "OK")) { /* We have SMS Support */
1614 + pvt->has_sms = 1;
1615 + pvt->state = MBL_STATE_PREIDLE;
1616 + } else if (strstr(buf, "ERROR")) {
1617 + pvt->has_sms = 0;
1618 + ast_log(LOG_NOTICE,"Device %s has no bluetooth SMS capability.\n", pvt->id);
1619 + pvt->state = MBL_STATE_PREIDLE;
1620 + }
1621 + break;
1622 + case MBL_STATE_PREIDLE: /* Nothing handled here, wait for timeout, then off we go... */
1623 + break;
1624 + case MBL_STATE_IDLE:
1625 + ast_debug(1, "Device %s %s [%s]\n", pvt->bdaddr, pvt->id, buf);
1626 + if (strstr(buf, "RING")) {
1627 + pvt->state = MBL_STATE_RING;
1628 + } else if (strstr(buf, "+CIEV:")) {
1629 + if (strstr(buf, pvt->ciev_callsetup_3)) { /* User has dialed out on handset */
1630 + monitor = 0; /* We disconnect now, as he is */
1631 + } /* probably leaving BT range... */
1632 + }
1633 + break;
1634 + case MBL_STATE_DIAL: /* Nothing handled here, we need to wait for a timeout */
1635 + break;
1636 + case MBL_STATE_DIAL1:
1637 + if (strstr(buf, "OK")) {
1638 + if (pvt->no_callsetup) {
1639 + ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
1640 + } else {
1641 + ast_setstate(pvt->owner, AST_STATE_RINGING);
1642 + }
1643 + pvt->state = MBL_STATE_OUTGOING;
1644 + }
1645 + break;
1646 + case MBL_STATE_OUTGOING:
1647 + if (strstr(buf, "+CIEV")) {
1648 + if (strstr(buf, pvt->ciev_call_0)) { /* call was hung up */
1649 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1650 + } else if (strstr(buf, pvt->ciev_callsetup_3)) { /* b-party ringing */
1651 + ast_queue_control(pvt->owner, AST_CONTROL_RINGING);
1652 + } else if (strstr(buf, pvt->ciev_call_1) && !pvt->no_callsetup) { /* b-party answer */
1653 + ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
1654 + }
1655 + }
1656 + break;
1657 + case MBL_STATE_RING:
1658 + cid_num[0] = 0x00;
1659 + if ((pcids = strstr(buf, "+CLIP:"))) {
1660 + if ((pcids = strchr(pcids, '"'))) {
1661 + if ((pcide = strchr(pcids+1, '"'))) {
1662 + strncpy(cid_num, pcids+1, pcide - pcids - 1);
1663 + cid_num[pcide - pcids - 1] = 0x00;
1664 + }
1665 + }
1666 + pvt->sco_out_ptr = pvt->sco_out_buf;
1667 + pvt->sco_out_len = 0;
1668 + pvt->sent_answer = 0;
1669 + chn = mbl_new(AST_STATE_RING, pvt, cid_num);
1670 + if (chn) {
1671 + if (ast_pbx_start(chn)) {
1672 + ast_log(LOG_ERROR, "Unable to start pbx on incoming call.\n");
1673 + ast_hangup(chn);
1674 + } else
1675 + pvt->state = MBL_STATE_RING3;
1676 + } else {
1677 + ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
1678 + rfcomm_write(pvt, "AT+CHUP\r");
1679 + pvt->state = MBL_STATE_IDLE;
1680 + }
1681 + }
1682 + break;
1683 + case MBL_STATE_RING2:
1684 + pvt->sco_out_ptr = pvt->sco_out_buf;
1685 + pvt->sco_out_len = 0;
1686 + pvt->sent_answer = 0;
1687 + chn = mbl_new(AST_STATE_RING, pvt, cid_num);
1688 + if (chn) {
1689 + if (ast_pbx_start(chn)) {
1690 + ast_log(LOG_ERROR, "Unable to start pbx on incoming call.\n");
1691 + ast_hangup(chn);
1692 + } else
1693 + pvt->state = MBL_STATE_RING3;
1694 + } else {
1695 + ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
1696 + rfcomm_write(pvt, "AT+CHUP\r");
1697 + pvt->state = MBL_STATE_IDLE;
1698 + }
1699 + break;
1700 + case MBL_STATE_RING3:
1701 + if (strstr(buf, "+CIEV")) {
1702 + if (strstr(buf, pvt->ciev_call_1)) {
1703 + if (pvt->sent_answer) { /* We answered */
1704 + pvt->state = MBL_STATE_INCOMING;
1705 + } else { /* User answered on handset!, disconnect */
1706 + pvt->state = MBL_STATE_IDLE;
1707 + ast_log(LOG_NOTICE,"Closing the sco_socket in RING3 with CIEV\n");
1708 + if (pvt->sco_socket > -1)
1709 + close(pvt->sco_socket);
1710 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1711 + }
1712 + }
1713 + if ((strstr(buf, pvt->ciev_callsetup_0) || strstr(buf, pvt->ciev_call_0))) { /* Caller disconnected */
1714 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1715 + }
1716 + }
1717 + break;
1718 + case MBL_STATE_INCOMING:
1719 + if (strstr(buf, "+CIEV")) {
1720 + if (strstr(buf, pvt->ciev_call_0)) {
1721 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1722 + }
1723 + }
1724 + break;
1725 + case MBL_STATE_HANGUP:
1726 + if (strstr(buf, "OK") || strstr(buf, pvt->ciev_call_0)) {
1727 + pvt->state = MBL_STATE_IDLE;
1728 + }
1729 + break;
1730 + case MBL_STATE_INSMS:
1731 + if (strstr(buf, "+CMGR:")) {
1732 + memset(sms_src, 0x00, sizeof(sms_src));
1733 + if ((p = strchr(buf, ','))) {
1734 + if (*(++p) == '"')
1735 + p++;
1736 + if ((p1 = strchr(p, ','))) {
1737 + if (*(--p1) == '"')
1738 + p1--;
1739 + memset(sms_src, 0x00, sizeof(sms_src));
1740 + strncpy(sms_src, p, p1 - p + 1);
1741 + }
1742 + }
1743 + } else if (strstr(buf, "OK")) {
1744 + chn = mbl_new(AST_STATE_DOWN, pvt, NULL);
1745 + strcpy(chn->exten, "sms");
1746 + pbx_builtin_setvar_helper(chn, "SMSSRC", sms_src);
1747 + pbx_builtin_setvar_helper(chn, "SMSTXT", sms_txt);
1748 + if (ast_pbx_start(chn))
1749 + ast_log(LOG_ERROR, "Unable to start pbx on incoming sms.\n");
1750 + pvt->state = MBL_STATE_IDLE;
1751 + } else {
1752 + memset(sms_txt, 0x00, sizeof(sms_txt));
1753 + strncpy(sms_txt, buf, strlen(buf));
1754 + }
1755 + break;
1756 + case MBL_STATE_OUTSMS:
1757 + break;
1758 + case MBL_STATE_OUTSMS1:
1759 + break;
1760 + case MBL_STATE_OUTSMS2:
1761 + if (strstr(buf, "OK")) {
1762 + pvt->state = MBL_STATE_IDLE;
1763 + }
1764 + break;
1765 + }
1766 + /* Unsolicited responses */
1767 +
1768 + if (strstr(buf, "+CMTI:")) { /* SMS Incoming... */
1769 + if ((p = strchr(buf, ','))) {
1770 + p++;
1771 + smsi = atoi(p);
1772 + if (smsi > 0) {
1773 + sprintf(buf, "AT+CMGR=%d\r", smsi);
1774 + rfcomm_write(pvt, buf);
1775 + pvt->state = MBL_STATE_INSMS;
1776 + }
1777 + }
1778 + }
1779 +
1780 + } else if (s == 0) { /* Timeouts */
1781 + if (pvt->state == MBL_STATE_INIT2) { /* Some devices dont respond to AT+CIND? properly. RIM Blackberry 4 example */
1782 + pvt->state++;
1783 + rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
1784 + } else if (pvt->state == MBL_STATE_INIT3) { /* Some devices dont respond to AT+CMER=3,0,0,1 properly. VK 2020 for example */
1785 + pvt->state++;
1786 + rfcomm_write(pvt, "AT+CLIP=1\r");
1787 + } else if (pvt->state == MBL_STATE_PREIDLE) {
1788 + pvt->connected = 1;
1789 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id);
1790 + pvt->state = MBL_STATE_IDLE;
1791 + } else if (pvt->state == MBL_STATE_DIAL) {
1792 + sprintf(buf, "ATD%s;\r", pvt->dial_number);
1793 + if (!rfcomm_write(pvt, buf)) {
1794 + ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state);
1795 + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
1796 + pvt->state = MBL_STATE_IDLE;
1797 + } else {
1798 + pvt->state = MBL_STATE_DIAL1;
1799 + }
1800 + } else if (pvt->state == MBL_STATE_DIAL1) {
1801 + ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state);
1802 + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
1803 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1804 + pvt->state = MBL_STATE_IDLE;
1805 + } else if (pvt->state == MBL_STATE_RING) { /* No CLIP?, bump it */
1806 + pvt->state = MBL_STATE_RING2;
1807 + } else if (pvt->state == MBL_STATE_HANGUP) {
1808 + if (pvt->hangup_count == 6) {
1809 + ast_debug(1, "Device %s failed to hangup after 6 tries, disconnecting.\n", pvt->id);
1810 + monitor = 0;
1811 + }
1812 + rfcomm_write(pvt, "AT+CHUP\r");
1813 + pvt->hangup_count++;
1814 + } else if (pvt->state == MBL_STATE_OUTSMS) {
1815 + sprintf(buf, "AT+CMGS=\"%s\"\r", pvt->dial_number);
1816 + rfcomm_write(pvt, buf);
1817 + pvt->state = MBL_STATE_OUTSMS1;
1818 + } else if (pvt->state == MBL_STATE_OUTSMS1) {
1819 + if (pvt->rfcomm_buf[0] == '>') {
1820 + sprintf(buf, "%s%c", pvt->sms_txt, 0x1a);
1821 + rfcomm_write(pvt, buf);
1822 + pvt->state = MBL_STATE_OUTSMS2;
1823 + } else {
1824 + ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
1825 + pvt->state = MBL_STATE_IDLE;
1826 + }
1827 + } else if (pvt->state == MBL_STATE_OUTSMS2) {
1828 + ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
1829 + pvt->state = MBL_STATE_IDLE;
1830 + }
1831 + } else if (s == -1) {
1832 + if (option_verbose > 2)
1833 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has disconnected, reason (%d).\n", pvt->id, errno);
1834 + monitor = 0;
1835 + }
1836 +
1837 + }
1838 +
1839 + close(pvt->rfcomm_socket);
1840 + close(pvt->sco_socket);
1841 + pvt->sco_socket = -1;
1842 + pvt->connected = 0;
1843 + pvt->monitor_thread = AST_PTHREADT_NULL;
1844 +
1845 + return NULL;
1846 +
1847 +}
1848 +
1849 +/*
1850 +
1851 + Headset Monitor Thread
1852 +
1853 + This thread is spun once a headset device is discovered and considered capable of being used, i.e. supports Headset Profile,
1854 + and its configured in mobile.conf.
1855 + The thread lives for the lifetime of the bluetooth connection, and handles the 'control' side of all calls.
1856 +
1857 +*/
1858 +
1859 +static void *do_monitor_headset(void *data)
1860 +{
1861 +
1862 + struct mbl_pvt *pvt = (struct mbl_pvt *)data;
1863 + char monitor = 1;
1864 + char buf[256];
1865 + int s, t;
1866 +
1867 + pvt->state = MBL_STATE_PREIDLE;
1868 +
1869 + while (monitor) {
1870 +
1871 + if (pvt->state == MBL_STATE_RING2)
1872 + t = 2;
1873 + else
1874 + t = 1;
1875 + s = rfcomm_read(pvt, buf, 0, t);
1876 +
1877 + if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
1878 + ast_debug(1, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
1879 + switch (pvt->state) {
1880 + case MBL_STATE_RING2:
1881 + if (strstr(buf, "AT+CKPD=")) {
1882 + ast_channel_lock(pvt->owner);
1883 + pvt->owner->fds[0] = pvt->sco_socket;
1884 + ast_log(LOG_NOTICE,"pvt-sco_socket used for fds in headphone code\n");
1885 + ast_channel_unlock(pvt->owner);
1886 + ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
1887 + pvt->state = MBL_STATE_INCOMING;
1888 + rfcomm_write(pvt, "\r\n+VGS=13\r\n");
1889 + rfcomm_write(pvt, "\r\n+VGM=13\r\n");
1890 + }
1891 + break;
1892 + case MBL_STATE_INCOMING:
1893 + if (strstr(buf, "AT+CKPD=")) {
1894 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1895 + }
1896 + break;
1897 + default:
1898 + break;
1899 + }
1900 + if (strstr(buf, "AT+VGS=")) {
1901 + rfcomm_write(pvt, "\r\nOK\r\n");
1902 + } else if (strstr(buf, "AT+VGM=")) {
1903 + rfcomm_write(pvt, "\r\nOK\r\n");
1904 + }
1905 + } else if (s == 0) { /* Timeouts */
1906 + if (pvt->state == MBL_STATE_PREIDLE) {
1907 + pvt->connected = 1;
1908 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id);
1909 + pvt->state = MBL_STATE_IDLE;
1910 + } else if (pvt->state == MBL_STATE_RING) {
1911 + pvt->sco_socket = sco_connect(pvt->bdaddr);
1912 + if (pvt->sco_socket > -1) {
1913 + ast_log(LOG_NOTICE,"sco_connect returned -1 in state RING\n");
1914 + ast_setstate(pvt->owner, AST_STATE_RINGING);
1915 + ast_queue_control(pvt->owner, AST_CONTROL_RINGING);
1916 + pvt->state = MBL_STATE_RING2;
1917 + } else {
1918 + ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
1919 + }
1920 + } else if (pvt->state == MBL_STATE_RING2) {
1921 + rfcomm_write(pvt, "\r\nRING\r\n");
1922 + }
1923 + } else if (s == -1) {
1924 + if (option_verbose > 2)
1925 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has disconnected, reason (%d).\n", pvt->id, errno);
1926 + monitor = 0;
1927 + }
1928 +
1929 + }
1930 +
1931 + return NULL;
1932 +
1933 +}
1934 +
1935 +static int start_monitor(struct mbl_pvt *pvt)
1936 +{
1937 +
1938 + if (pvt->type == MBL_TYPE_PHONE) {
1939 + if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) {
1940 + pvt->monitor_thread = AST_PTHREADT_NULL;
1941 + return 0;
1942 + }
1943 + } else {
1944 + if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_headset, pvt) < 0) {
1945 + pvt->monitor_thread = AST_PTHREADT_NULL;
1946 + return 0;
1947 + }
1948 + }
1949 +
1950 + return 1;
1951 +
1952 +}
1953 +
1954 +/*
1955 +
1956 + Device Discovery Thread.
1957 +
1958 + This thread wakes every 'discovery_interval' seconds and trys to connect to
1959 + those configured devices which are not connected. This saves the user from having
1960 + to manually connect his/her cell phone to the asterisk box. Once a successful
1961 + connection is made, a monitor thread is spun on the device which lives for the
1962 + lifetime of the connection.
1963 +
1964 +*/
1965 +
1966 +static void *do_discovery(void *data)
1967 +{
1968 +
1969 + struct mbl_pvt *pvt = data;
1970 +
1971 + for (;;) {
1972 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
1973 + if (!pvt->connected) {
1974 + if ((pvt->rfcomm_socket = rfcomm_connect(pvt->bdaddr, pvt->rfcomm_port)) > -1) {
1975 + pvt->state = 0;
1976 + if (start_monitor(pvt)) {
1977 + pvt->connected = 1;
1978 + if (option_verbose > 2)
1979 + ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has connected.\n", pvt->id);
1980 + }
1981 + }
1982 + }
1983 + }
1984 + /* Go to sleep */
1985 + sleep(discovery_interval);
1986 + }
1987 +
1988 + return NULL;
1989 +}
1990 +
1991 +/*
1992 +
1993 + This thread listens for incoming sco connections.
1994 + Although the Bluetooth Handsfree Profile Specification says that either end may initiate the audio connection,
1995 + in practice some devices (LG TU500) get upset unless they initiate the connection.
1996 + We leave all sco initiation to the device.
1997 + On an inbound sco connection, we need to find the appropriate device, and set the channel fd accordingly.
1998 +
1999 +*/
2000 +
2001 +static void *do_sco_listen(void *data)
2002 +{
2003 +
2004 + int ns;
2005 + bdaddr_t local;
2006 + struct sockaddr_sco addr;
2007 + struct sco_options so;
2008 + socklen_t len;
2009 + int opt = 1;
2010 + char saddr[18];
2011 + socklen_t addrlen;
2012 + struct mbl_pvt *pvt;
2013 +
2014 + hci_devba(0, &local);
2015 +
2016 + if ((sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
2017 + ast_log(LOG_ERROR, "Unable to create sco listener socket.\n");
2018 + return NULL;
2019 + }
2020 + memset(&addr, 0, sizeof(addr));
2021 + addr.sco_family = AF_BLUETOOTH;
2022 + bacpy(&addr.sco_bdaddr, &local);
2023 + if (bind(sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
2024 + ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno);
2025 + close(sco_socket);
2026 + return NULL;
2027 + }
2028 + if (setsockopt(sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
2029 + ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n");
2030 + close(sco_socket);
2031 + return NULL;
2032 + }
2033 + if (listen(sco_socket, 5) < 0) {
2034 + ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n");
2035 + close(sco_socket);
2036 + return NULL;
2037 + }
2038 + while (1) {
2039 + addrlen = sizeof(struct sockaddr);
2040 + ast_log(LOG_NOTICE, "About to accept the sco_socket...\n");
2041 + if ((ns = accept(sco_socket, (struct sockaddr *)&addr, &addrlen)) > -1) {
2042 + ast_log(LOG_NOTICE, "sco_socket returns %d...\n",ns);
2043 + ba2str(&addr.sco_bdaddr, saddr);
2044 +
2045 + len = sizeof(so);
2046 + getsockopt(ns, SOL_SCO, SCO_OPTIONS, &so, &len);
2047 +
2048 + ast_debug(1, "Incoming Audio Connection from device %s MTU is %d\n", saddr, so.mtu);
2049 +
2050 + pvt = NULL;
2051 + AST_LIST_TRAVERSE(&devices, pvt, entry) {
2052 + if (!strcmp(pvt->bdaddr, saddr))
2053 + break;
2054 + }
2055 + if (pvt) {
2056 + ast_log(LOG_NOTICE,"about to close the pvt-sco_socket and set it ns\n");
2057 + if (pvt->sco_socket != -1)
2058 + close(pvt->sco_socket);
2059 + pvt->sco_socket = ns;
2060 + if (pvt->owner) {
2061 + ast_channel_lock(pvt->owner);
2062 + pvt->owner->fds[0] = ns;
2063 + ast_channel_unlock(pvt->owner);
2064 + }
2065 + } else
2066 + ast_debug(1, "Could not find device for incoming Audio Connection.\n");
2067 + }
2068 + else ast_log(LOG_NOTICE, "Accept got a -1....");
2069 + }
2070 +
2071 + return NULL;
2072 +
2073 +}
2074 +
2075 +static int mbl_load_config(void)
2076 +{
2077 +
2078 + struct ast_config *cfg = NULL;
2079 + char *cat = NULL;
2080 + struct ast_variable *var;
2081 + const char *address, *port, *context, *type, *skip;
2082 + struct mbl_pvt *pvt;
2083 +
2084 + cfg = ast_config_load(MBL_CONFIG);
2085 + if (!cfg)
2086 + return 0;
2087 +
2088 + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
2089 + if (!strcasecmp(var->name, "interval"))
2090 + discovery_interval = atoi(var->value);
2091 + }
2092 +
2093 + cat = ast_category_browse(cfg, NULL);
2094 + while (cat) {
2095 + if (strcasecmp(cat, "general")) {
2096 + ast_debug(1, "Loading device %s.\n", cat);
2097 + address = ast_variable_retrieve(cfg, cat, "address");
2098 + port = ast_variable_retrieve(cfg, cat, "port");
2099 + context = ast_variable_retrieve(cfg, cat, "context");
2100 + ast_log(LOG_NOTICE, "context for non-general category %s was %s\n", cat, context);
2101 + type = ast_variable_retrieve(cfg, cat, "type");
2102 + skip = ast_variable_retrieve(cfg, cat, "dtmfskip");
2103 + if (address && port) {
2104 + if ((pvt = ast_malloc(sizeof(struct mbl_pvt)))) {
2105 + if (type && !strcmp(type, "headset"))
2106 + pvt->type = MBL_TYPE_HEADSET;
2107 + else
2108 + pvt->type = MBL_TYPE_PHONE;
2109 + ast_copy_string(pvt->id, cat, sizeof(pvt->id));
2110 + ast_copy_string(pvt->bdaddr, address, sizeof(pvt->bdaddr));
2111 + ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context));
2112 + pvt->connected = 0;
2113 + pvt->state = MBL_STATE_INIT;
2114 + pvt->rfcomm_socket = -1;
2115 + pvt->rfcomm_port = atoi(port);
2116 + pvt->rfcomm_buf[0] = 0x00;
2117 + pvt->sco_socket = -1;
2118 + pvt->monitor_thread = AST_PTHREADT_NULL;
2119 + pvt->owner = NULL;
2120 + pvt->no_callsetup = 0;
2121 + pvt->has_sms = 0;
2122 + pvt->dsp = ast_dsp_new();
2123 + if (skip) {
2124 + if ((pvt->dtmf_skip = atoi(skip)) == 0)
2125 + pvt->dtmf_skip = 200;
2126 + } else
2127 + pvt->dtmf_skip = 200;
2128 + pvt->skip_frames = 0;
2129 + ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DTMF_DETECT);
2130 + ast_dsp_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
2131 + AST_LIST_INSERT_HEAD(&devices, pvt, entry);
2132 + }
2133 + } else {
2134 + ast_log(LOG_ERROR, "Device %s has no address/port configured. It wont be enabled.\n", cat);
2135 + }
2136 + }
2137 + cat = ast_category_browse(cfg, cat);
2138 + }
2139 +
2140 + ast_config_destroy(cfg);
2141 +
2142 + return 1;
2143 +
2144 +}
2145 +
2146 +static int reload_module(void)
2147 +{
2148 +
2149 + return 0;
2150 +
2151 +}
2152 +
2153 +static int unload_module(void)
2154 +{
2155 +
2156 + struct mbl_pvt *pvt;
2157 +
2158 + /* First, take us out of the channel loop */
2159 + ast_channel_unregister(&mbl_tech);
2160 +
2161 + /* Kill the discovery thread */
2162 + if (discovery_thread != AST_PTHREADT_NULL) {
2163 + pthread_cancel(discovery_thread);
2164 + pthread_join(discovery_thread, NULL);
2165 + }
2166 + /* Kill the sco listener thread */
2167 + if (sco_listener_thread != AST_PTHREADT_NULL) {
2168 + pthread_cancel(sco_listener_thread);
2169 + pthread_join(sco_listener_thread, NULL);
2170 + }
2171 + if ((close(sco_socket) == -1))
2172 + ast_log(LOG_ERROR, "Unable to close sco_socket %d.\n", errno);
2173 +
2174 + /* Unregister the CLI & APP */
2175 + ast_cli_unregister_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
2176 + ast_unregister_application(app_mblstatus);
2177 + ast_unregister_application(app_mblsendsms);
2178 +
2179 + /* Destroy the device list */
2180 + while ((pvt = AST_LIST_REMOVE_HEAD(&devices, entry))) {
2181 + if (pvt->monitor_thread != AST_PTHREADT_NULL) {
2182 + pthread_cancel(pvt->monitor_thread);
2183 + pthread_join(pvt->monitor_thread, NULL);
2184 + }
2185 + if (pvt->sco_socket > -1) {
2186 + close(pvt->sco_socket);
2187 + }
2188 + if (pvt->rfcomm_socket > -1) {
2189 + close(pvt->rfcomm_socket);
2190 + }
2191 + ast_dsp_free(pvt->dsp);
2192 + free(pvt);
2193 + }
2194 +
2195 + if (sdp_session)
2196 + sdp_close(sdp_session);
2197 +
2198 + return 0;
2199 +
2200 +}
2201 +
2202 +static int load_module(void)
2203 +{
2204 +
2205 + int dev_id, s;
2206 + uint16_t vs;
2207 +
2208 + /* Check if we have Bluetooth, no point loading otherwise... */
2209 + dev_id = hci_get_route(NULL);
2210 + s = hci_open_dev(dev_id);
2211 + if (dev_id < 0 || s < 0) {
2212 + ast_log(LOG_ERROR, "No Bluetooth device found. Not loading module.\n");
2213 + return AST_MODULE_LOAD_DECLINE;
2214 + }
2215 +
2216 + hci_read_voice_setting(s, &vs, 1000);
2217 + vs = htobs(vs);
2218 + if (vs != 0x0060) {
2219 + ast_log(LOG_ERROR, "Bluetooth voice setting must be 0x0060 - see hciconfig hci0 voice.\n");
2220 + hci_close_dev(s);
2221 + return AST_MODULE_LOAD_DECLINE;
2222 + }
2223 +
2224 + hci_close_dev(s);
2225 +
2226 + if (!mbl_load_config()) {
2227 + ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", MBL_CONFIG);
2228 + return AST_MODULE_LOAD_DECLINE;
2229 + }
2230 +
2231 + sdp_session = sdp_register();
2232 +
2233 + /* Spin the discovery thread */
2234 + if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) {
2235 + ast_log(LOG_ERROR, "Unable to create discovery thread.\n");
2236 + return AST_MODULE_LOAD_DECLINE;
2237 + }
2238 + /* Spin the sco listener thread */
2239 + if (ast_pthread_create_background(&sco_listener_thread, NULL, do_sco_listen, NULL) < 0) {
2240 + ast_log(LOG_ERROR, "Unable to create sco listener thread.\n");
2241 + pthread_cancel(discovery_thread);
2242 + pthread_join(discovery_thread, NULL);
2243 + return AST_MODULE_LOAD_DECLINE;
2244 + }
2245 +
2246 + ast_cli_register_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
2247 + ast_register_application(app_mblstatus, mbl_status_exec, mblstatus_synopsis, mblstatus_desc);
2248 + ast_register_application(app_mblsendsms, mbl_sendsms_exec, mblsendsms_synopsis, mblsendsms_desc);
2249 +
2250 + /* Make sure we can register our channel type */
2251 + if (ast_channel_register(&mbl_tech)) {
2252 + ast_log(LOG_ERROR, "Unable to register channel class %s\n", "Mobile");
2253 + return AST_MODULE_LOAD_FAILURE;
2254 + }
2255 +
2256 + return 0;
2257 +}
2258 +
2259 +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bluetooth Mobile Device Channel Driver",
2260 + .load = load_module,
2261 + .unload = unload_module,
2262 + .reload = reload_module,
2263 +);