| #!/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() |