blob: a8060439e4b79e2d306cff1cef4b3c5f1a03533c [file] [log] [blame]
Johan Hedberg2e82b4b2012-11-19 20:46:31 +02001#!/usr/bin/python
2
3from __future__ import absolute_import, print_function, unicode_literals
4
Petri Gynther7adf8d02014-01-17 17:45:07 -08005from optparse import OptionParser, make_option
Johan Hedberg2e82b4b2012-11-19 20:46:31 +02006import os
Petri Gynther7adf8d02014-01-17 17:45:07 -08007from socket import SOCK_SEQPACKET, socket
Johan Hedberg2e82b4b2012-11-19 20:46:31 +02008import sys
9import dbus
Johan Hedberg2e82b4b2012-11-19 20:46:31 +020010import dbus.service
11import dbus.mainloop.glib
Petri Gynther7adf8d02014-01-17 17:45:07 -080012import glib
13try:
14 from gi.repository import GObject
15except ImportError:
16 import gobject as GObject
Johan Hedberg2e82b4b2012-11-19 20:46:31 +020017
18mainloop = None
19audio_supported = True
20
21try:
22 from socket import AF_BLUETOOTH, BTPROTO_SCO
23except:
24 print("WARNING: python compiled without Bluetooth support"
25 " - audio will not be available")
26 audio_supported = False
27
28BUF_SIZE = 1024
29
30BDADDR_ANY = '00:00:00:00:00:00'
31
32HF_NREC = 0x0001
33HF_3WAY = 0x0002
34HF_CLI = 0x0004
35HF_VOICE_RECOGNITION = 0x0008
36HF_REMOTE_VOL = 0x0010
37HF_ENHANCED_STATUS = 0x0020
38HF_ENHANCED_CONTROL = 0x0040
39HF_CODEC_NEGOTIATION = 0x0080
40
41AG_3WAY = 0x0001
42AG_NREC = 0x0002
43AG_VOICE_RECOGNITION = 0x0004
44AG_INBAND_RING = 0x0008
45AG_VOICE_TAG = 0x0010
46AG_REJECT_CALL = 0x0020
47AG_ENHANCED_STATUS = 0x0040
48AG_ENHANCED_CONTROL = 0x0080
49AG_EXTENDED_RESULT = 0x0100
50AG_CODEC_NEGOTIATION = 0x0200
51
52HF_FEATURES = (HF_3WAY | HF_CLI | HF_VOICE_RECOGNITION |
53 HF_REMOTE_VOL | HF_ENHANCED_STATUS |
54 HF_ENHANCED_CONTROL | HF_CODEC_NEGOTIATION)
55
56AVAIL_CODECS = "1,2"
57
58class HfpConnection:
59 slc_complete = False
60 fd = None
61 io_id = 0
62 version = 0
63 features = 0
64 pending = None
65
Johan Hedberg24221372012-11-26 16:59:34 +020066 def disconnect(self):
67 if (self.fd >= 0):
68 os.close(self.fd)
69 self.fd = -1
70 glib.source_remove(self.io_id)
71 self.io_id = 0
72
Johan Hedberg2e82b4b2012-11-19 20:46:31 +020073 def slc_completed(self):
74 print("SLC establisment complete")
75 self.slc_complete = True
76
77 def slc_next_cmd(self, cmd):
78 if not cmd:
79 self.send_cmd("AT+BRSF=%u" % (HF_FEATURES))
80 elif (cmd.startswith("AT+BRSF")):
81 if (self.features & AG_CODEC_NEGOTIATION and
82 HF_FEATURES & HF_CODEC_NEGOTIATION):
83 self.send_cmd("AT+BAC=%s" % (AVAIL_CODECS))
84 else:
85 self.send_cmd("AT+CIND=?")
86 elif (cmd.startswith("AT+BAC")):
87 self.send_cmd("AT+CIND=?")
88 elif (cmd.startswith("AT+CIND=?")):
89 self.send_cmd("AT+CIND?")
90 elif (cmd.startswith("AT+CIND?")):
91 self.send_cmd("AT+CMER=3,0,0,1")
92 elif (cmd.startswith("AT+CMER=")):
93 if (HF_FEATURES & HF_3WAY and self.features & AG_3WAY):
94 self.send_cmd("AT+CHLD=?")
95 else:
96 self.slc_completed()
97 elif (cmd.startswith("AT+CHLD=?")):
98 self.slc_completed()
99 else:
100 print("Unknown SLC command completed: %s" % (cmd))
101
102 def io_cb(self, fd, cond):
103 buf = os.read(fd, BUF_SIZE)
104 buf = buf.strip()
105
106 print("Received: %s" % (buf))
107
108 if (buf == "OK" or buf == "ERROR"):
109 cmd = self.pending
110 self.pending = None
111
112 if (not self.slc_complete):
113 self.slc_next_cmd(cmd)
114
115 return True
116
117 parts = buf.split(':')
118
119 if (parts[0] == "+BRSF"):
120 self.features = int(parts[1])
121
122 return True
123
124 def send_cmd(self, cmd):
125 if (self.pending):
126 print("ERROR: Another command is pending")
127 return
128
129 print("Sending: %s" % (cmd))
130
131 os.write(self.fd, cmd + "\r\n")
132 self.pending = cmd
133
134 def __init__(self, fd, version, features):
135 self.fd = fd
136 self.version = version
137 self.features = features
138
139 print("Version 0x%04x Features 0x%04x" % (version, features))
140
141 self.io_id = glib.io_add_watch(fd, glib.IO_IN, self.io_cb)
142
143 self.slc_next_cmd(None)
144
145class HfpProfile(dbus.service.Object):
146 sco_socket = None
147 io_id = 0
148 conns = {}
149
150 def sco_cb(self, sock, cond):
151 (sco, peer) = sock.accept()
152 print("New SCO connection from %s" % (peer))
153
154 def init_sco(self, sock):
155 self.sco_socket = sock
156 self.io_id = glib.io_add_watch(sock, glib.IO_IN, self.sco_cb)
157
158 def __init__(self, bus, path, sco):
159 dbus.service.Object.__init__(self, bus, path)
160
161 if sco:
162 self.init_sco(sco)
163
164 @dbus.service.method("org.bluez.Profile1",
165 in_signature="", out_signature="")
166 def Release(self):
167 print("Release")
168 mainloop.quit()
169
170 @dbus.service.method("org.bluez.Profile1",
171 in_signature="", out_signature="")
172 def Cancel(self):
173 print("Cancel")
174
175 @dbus.service.method("org.bluez.Profile1",
Johan Hedberg24221372012-11-26 16:59:34 +0200176 in_signature="o", out_signature="")
177 def RequestDisconnection(self, path):
178 conn = self.conns.pop(path)
179 conn.disconnect()
180
181 @dbus.service.method("org.bluez.Profile1",
Johan Hedberg2e82b4b2012-11-19 20:46:31 +0200182 in_signature="oha{sv}", out_signature="")
183 def NewConnection(self, path, fd, properties):
184 fd = fd.take()
185 version = 0x0105
186 features = 0
187 print("NewConnection(%s, %d)" % (path, fd))
188 for key in properties.keys():
189 if key == "Version":
190 version = properties[key]
191 elif key == "Features":
192 features = properties[key]
193
194 conn = HfpConnection(fd, version, features)
195
196 self.conns[path] = conn
197
198if __name__ == '__main__':
199 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
200
201 bus = dbus.SystemBus()
202
203 manager = dbus.Interface(bus.get_object("org.bluez",
204 "/org/bluez"), "org.bluez.ProfileManager1")
205
206 option_list = [
207 make_option("-p", "--path", action="store",
208 type="string", dest="path",
209 default="/bluez/test/hfp"),
210 make_option("-n", "--name", action="store",
211 type="string", dest="name",
212 default=None),
213 make_option("-C", "--channel", action="store",
214 type="int", dest="channel",
215 default=None),
216 ]
217
218 parser = OptionParser(option_list=option_list)
219
220 (options, args) = parser.parse_args()
221
222 mainloop = GObject.MainLoop()
223
224 opts = {
Johan Hedberg2e82b4b2012-11-19 20:46:31 +0200225 "Version" : dbus.UInt16(0x0106),
226 "Features" : dbus.UInt16(HF_FEATURES),
227 }
228
229 if (options.name):
230 opts["Name"] = options.name
231
232 if (options.channel is not None):
233 opts["Channel"] = dbus.UInt16(options.channel)
234
235 if audio_supported:
236 sco = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)
237 sco.bind(BDADDR_ANY)
Johan Hedbergc6e0cd52013-09-28 22:49:24 +0300238 sco.listen(1)
Johan Hedberg2e82b4b2012-11-19 20:46:31 +0200239 else:
240 sco = None
241
242 profile = HfpProfile(bus, options.path, sco)
243
244 manager.RegisterProfile(options.path, "hfp-hf", opts)
245
246 print("Profile registered - waiting for connections")
247
248 mainloop.run()