|  | #!/usr/bin/python | 
|  |  | 
|  | from __future__ import absolute_import, print_function, unicode_literals | 
|  |  | 
|  | from optparse import OptionParser, make_option | 
|  | import os | 
|  | from socket import SOCK_SEQPACKET, socket | 
|  | import sys | 
|  | import dbus | 
|  | import dbus.service | 
|  | import dbus.mainloop.glib | 
|  | import glib | 
|  | try: | 
|  | from gi.repository import GObject | 
|  | except ImportError: | 
|  | import gobject as GObject | 
|  |  | 
|  | mainloop = None | 
|  | audio_supported = True | 
|  |  | 
|  | try: | 
|  | from socket import AF_BLUETOOTH, BTPROTO_SCO | 
|  | except: | 
|  | print("WARNING: python compiled without Bluetooth support" | 
|  | " - audio will not be available") | 
|  | audio_supported = False | 
|  |  | 
|  | BUF_SIZE = 1024 | 
|  |  | 
|  | BDADDR_ANY = '00:00:00:00:00:00' | 
|  |  | 
|  | HF_NREC			= 0x0001 | 
|  | HF_3WAY			= 0x0002 | 
|  | HF_CLI			= 0x0004 | 
|  | HF_VOICE_RECOGNITION	= 0x0008 | 
|  | HF_REMOTE_VOL		= 0x0010 | 
|  | HF_ENHANCED_STATUS	= 0x0020 | 
|  | HF_ENHANCED_CONTROL	= 0x0040 | 
|  | HF_CODEC_NEGOTIATION	= 0x0080 | 
|  |  | 
|  | AG_3WAY			= 0x0001 | 
|  | AG_NREC			= 0x0002 | 
|  | AG_VOICE_RECOGNITION	= 0x0004 | 
|  | AG_INBAND_RING		= 0x0008 | 
|  | AG_VOICE_TAG		= 0x0010 | 
|  | AG_REJECT_CALL		= 0x0020 | 
|  | AG_ENHANCED_STATUS	= 0x0040 | 
|  | AG_ENHANCED_CONTROL	= 0x0080 | 
|  | AG_EXTENDED_RESULT	= 0x0100 | 
|  | AG_CODEC_NEGOTIATION	= 0x0200 | 
|  |  | 
|  | HF_FEATURES = (HF_3WAY | HF_CLI | HF_VOICE_RECOGNITION | | 
|  | HF_REMOTE_VOL | HF_ENHANCED_STATUS | | 
|  | HF_ENHANCED_CONTROL | HF_CODEC_NEGOTIATION) | 
|  |  | 
|  | AVAIL_CODECS = "1,2" | 
|  |  | 
|  | class HfpConnection: | 
|  | slc_complete = False | 
|  | fd = None | 
|  | io_id = 0 | 
|  | version = 0 | 
|  | features = 0 | 
|  | pending = None | 
|  |  | 
|  | def disconnect(self): | 
|  | if (self.fd >= 0): | 
|  | os.close(self.fd) | 
|  | self.fd = -1 | 
|  | glib.source_remove(self.io_id) | 
|  | self.io_id = 0 | 
|  |  | 
|  | def slc_completed(self): | 
|  | print("SLC establisment complete") | 
|  | self.slc_complete = True | 
|  |  | 
|  | def slc_next_cmd(self, cmd): | 
|  | if not cmd: | 
|  | self.send_cmd("AT+BRSF=%u" % (HF_FEATURES)) | 
|  | elif (cmd.startswith("AT+BRSF")): | 
|  | if (self.features & AG_CODEC_NEGOTIATION and | 
|  | HF_FEATURES & HF_CODEC_NEGOTIATION): | 
|  | self.send_cmd("AT+BAC=%s" % (AVAIL_CODECS)) | 
|  | else: | 
|  | self.send_cmd("AT+CIND=?") | 
|  | elif (cmd.startswith("AT+BAC")): | 
|  | self.send_cmd("AT+CIND=?") | 
|  | elif (cmd.startswith("AT+CIND=?")): | 
|  | self.send_cmd("AT+CIND?") | 
|  | elif (cmd.startswith("AT+CIND?")): | 
|  | self.send_cmd("AT+CMER=3,0,0,1") | 
|  | elif (cmd.startswith("AT+CMER=")): | 
|  | if (HF_FEATURES & HF_3WAY and self.features & AG_3WAY): | 
|  | self.send_cmd("AT+CHLD=?") | 
|  | else: | 
|  | self.slc_completed() | 
|  | elif (cmd.startswith("AT+CHLD=?")): | 
|  | self.slc_completed() | 
|  | else: | 
|  | print("Unknown SLC command completed: %s" % (cmd)) | 
|  |  | 
|  | def io_cb(self, fd, cond): | 
|  | buf = os.read(fd, BUF_SIZE) | 
|  | buf = buf.strip() | 
|  |  | 
|  | print("Received: %s" % (buf)) | 
|  |  | 
|  | if (buf == "OK" or buf == "ERROR"): | 
|  | cmd = self.pending | 
|  | self.pending = None | 
|  |  | 
|  | if (not self.slc_complete): | 
|  | self.slc_next_cmd(cmd) | 
|  |  | 
|  | return True | 
|  |  | 
|  | parts = buf.split(':') | 
|  |  | 
|  | if (parts[0] == "+BRSF"): | 
|  | self.features = int(parts[1]) | 
|  |  | 
|  | return True | 
|  |  | 
|  | def send_cmd(self, cmd): | 
|  | if (self.pending): | 
|  | print("ERROR: Another command is pending") | 
|  | return | 
|  |  | 
|  | print("Sending: %s" % (cmd)) | 
|  |  | 
|  | os.write(self.fd, cmd + "\r\n") | 
|  | self.pending = cmd | 
|  |  | 
|  | def __init__(self, fd, version, features): | 
|  | self.fd = fd | 
|  | self.version = version | 
|  | self.features = features | 
|  |  | 
|  | print("Version 0x%04x Features 0x%04x" % (version, features)) | 
|  |  | 
|  | self.io_id = glib.io_add_watch(fd, glib.IO_IN, self.io_cb) | 
|  |  | 
|  | self.slc_next_cmd(None) | 
|  |  | 
|  | class HfpProfile(dbus.service.Object): | 
|  | sco_socket = None | 
|  | io_id = 0 | 
|  | conns = {} | 
|  |  | 
|  | def sco_cb(self, sock, cond): | 
|  | (sco, peer) = sock.accept() | 
|  | print("New SCO connection from %s" % (peer)) | 
|  |  | 
|  | def init_sco(self, sock): | 
|  | self.sco_socket = sock | 
|  | self.io_id = glib.io_add_watch(sock, glib.IO_IN, self.sco_cb) | 
|  |  | 
|  | def __init__(self, bus, path, sco): | 
|  | dbus.service.Object.__init__(self, bus, path) | 
|  |  | 
|  | if sco: | 
|  | self.init_sco(sco) | 
|  |  | 
|  | @dbus.service.method("org.bluez.Profile1", | 
|  | in_signature="", out_signature="") | 
|  | def Release(self): | 
|  | print("Release") | 
|  | mainloop.quit() | 
|  |  | 
|  | @dbus.service.method("org.bluez.Profile1", | 
|  | in_signature="", out_signature="") | 
|  | def Cancel(self): | 
|  | print("Cancel") | 
|  |  | 
|  | @dbus.service.method("org.bluez.Profile1", | 
|  | in_signature="o", out_signature="") | 
|  | def RequestDisconnection(self, path): | 
|  | conn = self.conns.pop(path) | 
|  | conn.disconnect() | 
|  |  | 
|  | @dbus.service.method("org.bluez.Profile1", | 
|  | in_signature="oha{sv}", out_signature="") | 
|  | def NewConnection(self, path, fd, properties): | 
|  | fd = fd.take() | 
|  | version = 0x0105 | 
|  | features = 0 | 
|  | print("NewConnection(%s, %d)" % (path, fd)) | 
|  | for key in properties.keys(): | 
|  | if key == "Version": | 
|  | version = properties[key] | 
|  | elif key == "Features": | 
|  | features = properties[key] | 
|  |  | 
|  | conn = HfpConnection(fd, version, features) | 
|  |  | 
|  | self.conns[path] = conn | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) | 
|  |  | 
|  | bus = dbus.SystemBus() | 
|  |  | 
|  | manager = dbus.Interface(bus.get_object("org.bluez", | 
|  | "/org/bluez"), "org.bluez.ProfileManager1") | 
|  |  | 
|  | option_list = [ | 
|  | make_option("-p", "--path", action="store", | 
|  | type="string", dest="path", | 
|  | default="/bluez/test/hfp"), | 
|  | make_option("-n", "--name", action="store", | 
|  | type="string", dest="name", | 
|  | default=None), | 
|  | make_option("-C", "--channel", action="store", | 
|  | type="int", dest="channel", | 
|  | default=None), | 
|  | ] | 
|  |  | 
|  | parser = OptionParser(option_list=option_list) | 
|  |  | 
|  | (options, args) = parser.parse_args() | 
|  |  | 
|  | mainloop = GObject.MainLoop() | 
|  |  | 
|  | opts = { | 
|  | "Version" : dbus.UInt16(0x0106), | 
|  | "Features" : dbus.UInt16(HF_FEATURES), | 
|  | } | 
|  |  | 
|  | if (options.name): | 
|  | opts["Name"] = options.name | 
|  |  | 
|  | if (options.channel is not None): | 
|  | opts["Channel"] = dbus.UInt16(options.channel) | 
|  |  | 
|  | if audio_supported: | 
|  | sco = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO) | 
|  | sco.bind(BDADDR_ANY) | 
|  | sco.listen(1) | 
|  | else: | 
|  | sco = None | 
|  |  | 
|  | profile = HfpProfile(bus, options.path, sco) | 
|  |  | 
|  | manager.RegisterProfile(options.path, "hfp-hf", opts) | 
|  |  | 
|  | print("Profile registered - waiting for connections") | 
|  |  | 
|  | mainloop.run() |