bumble 0.0.198__py3-none-any.whl → 0.0.199__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bumble/smp.py CHANGED
@@ -764,7 +764,9 @@ class Session:
764
764
  self.peer_io_capability = SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY
765
765
 
766
766
  # OOB
767
- self.oob_data_flag = 0 if pairing_config.oob is None else 1
767
+ self.oob_data_flag = (
768
+ 1 if pairing_config.oob and pairing_config.oob.peer_data else 0
769
+ )
768
770
 
769
771
  # Set up addresses
770
772
  self_address = connection.self_resolvable_address or connection.self_address
@@ -1014,8 +1016,10 @@ class Session:
1014
1016
  self.send_command(response)
1015
1017
 
1016
1018
  def send_pairing_confirm_command(self) -> None:
1017
- self.r = crypto.r()
1018
- logger.debug(f'generated random: {self.r.hex()}')
1019
+
1020
+ if self.pairing_method != PairingMethod.OOB:
1021
+ self.r = crypto.r()
1022
+ logger.debug(f'generated random: {self.r.hex()}')
1019
1023
 
1020
1024
  if self.sc:
1021
1025
 
@@ -1735,7 +1739,6 @@ class Session:
1735
1739
  if self.pairing_method in (
1736
1740
  PairingMethod.JUST_WORKS,
1737
1741
  PairingMethod.NUMERIC_COMPARISON,
1738
- PairingMethod.OOB,
1739
1742
  ):
1740
1743
  ra = bytes(16)
1741
1744
  rb = ra
@@ -1743,6 +1746,22 @@ class Session:
1743
1746
  assert self.passkey
1744
1747
  ra = self.passkey.to_bytes(16, byteorder='little')
1745
1748
  rb = ra
1749
+ elif self.pairing_method == PairingMethod.OOB:
1750
+ if self.is_initiator:
1751
+ if self.peer_oob_data:
1752
+ rb = self.peer_oob_data.r
1753
+ ra = self.r
1754
+ else:
1755
+ rb = bytes(16)
1756
+ ra = self.r
1757
+ else:
1758
+ if self.peer_oob_data:
1759
+ ra = self.peer_oob_data.r
1760
+ rb = self.r
1761
+ else:
1762
+ ra = bytes(16)
1763
+ rb = self.r
1764
+
1746
1765
  else:
1747
1766
  return
1748
1767
 
bumble/transport/pyusb.py CHANGED
@@ -23,7 +23,7 @@ import time
23
23
  import usb.core
24
24
  import usb.util
25
25
 
26
- from typing import Optional
26
+ from typing import Optional, Set
27
27
  from usb.core import Device as UsbDevice
28
28
  from usb.core import USBError
29
29
  from usb.util import CTRL_TYPE_CLASS, CTRL_RECIPIENT_OTHER
@@ -46,6 +46,11 @@ RESET_DELAY = 3
46
46
  # -----------------------------------------------------------------------------
47
47
  logger = logging.getLogger(__name__)
48
48
 
49
+ # -----------------------------------------------------------------------------
50
+ # Global
51
+ # -----------------------------------------------------------------------------
52
+ devices_in_use: Set[int] = set()
53
+
49
54
 
50
55
  # -----------------------------------------------------------------------------
51
56
  async def open_pyusb_transport(spec: str) -> Transport:
@@ -216,6 +221,7 @@ async def open_pyusb_transport(spec: str) -> Transport:
216
221
  async def close(self):
217
222
  await self.source.stop()
218
223
  await self.sink.stop()
224
+ devices_in_use.remove(device.address)
219
225
  usb.util.release_interface(self.device, 0)
220
226
 
221
227
  usb_find = usb.core.find
@@ -233,7 +239,18 @@ async def open_pyusb_transport(spec: str) -> Transport:
233
239
  spec = spec[1:]
234
240
  if ':' in spec:
235
241
  vendor_id, product_id = spec.split(':')
236
- device = usb_find(idVendor=int(vendor_id, 16), idProduct=int(product_id, 16))
242
+ device = None
243
+ devices = usb_find(
244
+ find_all=True, idVendor=int(vendor_id, 16), idProduct=int(product_id, 16)
245
+ )
246
+ for d in devices:
247
+ if d.address in devices_in_use:
248
+ continue
249
+ device = d
250
+ devices_in_use.add(d.address)
251
+ break
252
+ if device is None:
253
+ raise ValueError('device already in use')
237
254
  elif '-' in spec:
238
255
 
239
256
  def device_path(device):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bumble
3
- Version: 0.0.198
3
+ Version: 0.0.199
4
4
  Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
5
5
  Home-page: https://github.com/google/bumble
6
6
  Author: Google
@@ -1,11 +1,11 @@
1
1
  bumble/__init__.py,sha256=Q8jkz6rgl95IMAeInQVt_2GLoJl3DcEP2cxtrQ-ho5c,110
2
- bumble/_version.py,sha256=wj4Uoyl4fbIliDzkMZfA-f07UNZEG-IYvFEisYUdjJ0,415
2
+ bumble/_version.py,sha256=-5ruRKhgD06H9Dn3PntBTwUGHMNmAuViLLPxyfe0nAg,415
3
3
  bumble/a2dp.py,sha256=VEeAOCfT1ZqpwnEgel6DJ32vxR8jYX3IAaBfCqPdWO8,22675
4
4
  bumble/at.py,sha256=Giu2VUSJKH-jIh10lOfumiqy-FyO99Ra6nJ7UiWQ0H8,3114
5
- bumble/att.py,sha256=TGzhhBKCQPA_P_eDDSNASJVfa3dCr-QzzrRB3GekrI0,32366
5
+ bumble/att.py,sha256=i-rs_xyTBUdGpyjoZCjb68f1IDiiS2kX9RylCeh6K0o,32757
6
6
  bumble/avc.py,sha256=Ho-WweU9lU15vlTwIbrGBa18M5vUKYm5q8aToaLSh70,16311
7
7
  bumble/avctp.py,sha256=yHAjJRjLGtR0Q-iWcLS7cJRz5Jr2YiRmZd6LZV4Xjt4,9935
8
- bumble/avdtp.py,sha256=6kx4FiFHuV6zmYYrHYnJO4ilDksJauyu9d6XBO4wPVg,77450
8
+ bumble/avdtp.py,sha256=K_Ujbruw0cNfSXeMU3OvgWedrLjZ8A3i6f_hy1rtpRo,77470
9
9
  bumble/avrcp.py,sha256=BhDjc8TxhOpPnuvGn5-dGB6JK1aqshg4x7tm_vI4LLU,69363
10
10
  bumble/bridge.py,sha256=T6es5oS1dy8QgkxQ8iOD-YcZ0SWOv8jaqC7TGxqodk4,3003
11
11
  bumble/codecs.py,sha256=yels_alICohfM5DuvR2wMpYlebt7OrZHCtRXWyMyYYI,15581
@@ -14,17 +14,17 @@ bumble/company_ids.py,sha256=B68e2QPsDeRYP9jjbGs4GGDwEkGxcXGTsON_CHA0uuI,118528
14
14
  bumble/controller.py,sha256=XkYTQb2J5MhH_dGfnFkrLXdChFD2s1wSvqXaHQFeo48,59688
15
15
  bumble/core.py,sha256=T43ZszjsU_89B0UJwQ9GCzvKU7oj1_RBzsdGr9jX9xY,72440
16
16
  bumble/crypto.py,sha256=L6z3dn9-dgKYRtOM6O3F6n6Ju4PwTM3LAFJtCg_ie78,9382
17
- bumble/decoder.py,sha256=N9nMvuVhuwpnfw7EDVuNe9uYY6B6c3RY2dh8RhRPC1U,9608
17
+ bumble/decoder.py,sha256=0-VNWZT-u7lvK3qBpAuYT0M6Rz_bMgMi4CjfUXX_6RM,9728
18
18
  bumble/device.py,sha256=4TcC6v_zwyTcZMAMZd7qb06oLVGtw84LtdlgIsilFbU,194632
19
19
  bumble/gap.py,sha256=dRU2_TWvqTDx80hxeSbXlWIeWvptWH4_XbItG5y948Q,2138
20
- bumble/gatt.py,sha256=FiueS6Uj4go3LB7PjhXlzLHBFiiiMJWiuHtsILEuVLo,38651
21
- bumble/gatt_client.py,sha256=C8HlxEYpe3FBOPS2RXZZ9_tX3KfqPnn0XZsmhakSXNc,43332
22
- bumble/gatt_server.py,sha256=uPYbn2-y0MLnyR8xxpOf18gPua_Q49pSlMR1zxEnU-Q,37118
23
- bumble/hci.py,sha256=v4JeftZq-j1XYCNUwgBH_3JnXOpJ_cJCYmj1HO6GKzE,275223
20
+ bumble/gatt.py,sha256=a07mQ3O0LFt5zgUzSUwa4Jak_FXOXSFX-njxnQNS_zY,39014
21
+ bumble/gatt_client.py,sha256=gIpgNQj5WNm9RirXXT4vuXQZ4WK82IFMOqnfqBtoYRU,42809
22
+ bumble/gatt_server.py,sha256=pafGMeAuGAAELnr_1pB_l3CcR5u4H4Y1-MRHjN53gdE,37449
23
+ bumble/hci.py,sha256=fx8BmNV29fiZWQPLLstihLWScb0_8zy5O-mX9IfngcI,285388
24
24
  bumble/helpers.py,sha256=m0w4UgFFNDEnXwHrDyfRlcBObdVed2fqXGL0lvR3c8s,12733
25
25
  bumble/hfp.py,sha256=OsBDREelxhLMi_UZO9Kxlqzbts08CcGxoiicUgrYlXg,75353
26
26
  bumble/hid.py,sha256=hJKm6qhNa0kQTGmp_VxNh3-ywgBDdJpPPFcvtFiRL0A,20335
27
- bumble/host.py,sha256=OIV1_Alfjn6-VbvnHzmnFuuXt6kVZ0zvIptaj3waRe4,48415
27
+ bumble/host.py,sha256=s7ZzGbExvewZ7qoMF3cdjyvFKae8NqiFH-xtHhd_a2M,48814
28
28
  bumble/keys.py,sha256=WbIQ7Ob81mW75qmEPQ2rBLfnqBMA-ts2yowWXP9UaCY,12654
29
29
  bumble/l2cap.py,sha256=Bu6oTD3DzqLOKiarynT1cfQefNgR0gCJoKxnwQJl2_o,81398
30
30
  bumble/link.py,sha256=MYKsIKpbbAkh_ldxPZKu_GhS90ImC0IQtCAqbY4Ddg4,24034
@@ -32,7 +32,7 @@ bumble/pairing.py,sha256=tgPUba6xNxMi-2plm3xfRlzHq-uPRNZEIGWaN0qNGCs,9853
32
32
  bumble/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  bumble/rfcomm.py,sha256=dh5t5vlDEfw3yHgQfzegMYPnShP8Zo-3ScABUvmXNLI,40751
34
34
  bumble/sdp.py,sha256=aajRQybcMWh_Kc4IuQahix6bXnHg61OXl1gSdrSysl4,45465
35
- bumble/smp.py,sha256=h8KSaBMSPeVSU_wwRPUnRu2wFSXW-J_ez8FQSOayH4s,77040
35
+ bumble/smp.py,sha256=FNLn-pt2n6CKNttQGJJDnfaeof2SVkxlapwySHWAVJY,77640
36
36
  bumble/snoop.py,sha256=1mzwmp9LToUXbPnFsLrt8S4UHs0kqzbu7LDydwbmkZI,5715
37
37
  bumble/utils.py,sha256=e0i-4d28-9zP3gYcd1rdNd669rkPnRs5oJCERUEDfxo,15099
38
38
  bumble/apps/README.md,sha256=XTwjRAY-EJWDXpl1V8K3Mw8B7kIqzUIUizRjVBVhoIE,1769
@@ -49,7 +49,7 @@ bumble/apps/gatt_dump.py,sha256=wwA-NhRnbgUkbj-Ukym7NDG2j2n_36t_tn93dLDVdIg,4487
49
49
  bumble/apps/gg_bridge.py,sha256=JdW5QT6xN9c2XDDJoHDRo5W3N_RdVkCtTmlcOsJhlx8,14693
50
50
  bumble/apps/hci_bridge.py,sha256=KISv352tKnsQsoxjkDiCQbMFmhnPWdnug5wSFAAXxEs,4033
51
51
  bumble/apps/l2cap_bridge.py,sha256=524VgEmgCP4g7T0UdgmsePmNVhDFRJECeaZ_uzKsbco,13062
52
- bumble/apps/pair.py,sha256=COU2D7YAIn4lo5iuM0ClObA1zZqQCdrXOcnsiCm0YlQ,17529
52
+ bumble/apps/pair.py,sha256=NtDxLfdnlOY_ZEtVNGVXWjv6_x_hndok_ydhV6zkFtI,18503
53
53
  bumble/apps/pandora_server.py,sha256=5qaoLCpcZE2KsGO21-7t6Vg4dBjBWbnyOQXwrLhxkuE,1397
54
54
  bumble/apps/rfcomm_bridge.py,sha256=PSszh4Qh1IsIw8ETs0fevOCAXEdVtqlgnV-ruzqGrZI,17215
55
55
  bumble/apps/scan.py,sha256=b6hIppiJqDfR7VFW2wl3-lkPdFvHLqYZKY8VjjNnhls,8366
@@ -71,17 +71,19 @@ bumble/apps/speaker/speaker.py,sha256=f7cOyVqD6HOiLPCgdcnwN7sakj2SScybVHVTBR6eFw
71
71
  bumble/drivers/__init__.py,sha256=lxqJTghh21rQFThRTurwLysZm385TUcn8ZdpHQSWD88,3196
72
72
  bumble/drivers/common.py,sha256=pS783hudolLZAzF8IUWp7g6TXyQsUCEzqCsd1VGeYfQ,1507
73
73
  bumble/drivers/intel.py,sha256=-YcJI4ZC_fhHHxWyE8b4eB8V7suOFH1n9uayckGE9Uw,3231
74
- bumble/drivers/rtk.py,sha256=xFPzTxSJYVTIkru2D3oQMtAcNCAXSChhMSIsE7ksO8I,21552
75
- bumble/pandora/__init__.py,sha256=5NBVmndeTulANawift0jPT9ISp562wyIHTZ-4uP34Mg,3283
74
+ bumble/drivers/rtk.py,sha256=nsfAqNzvxvAwbh6UYgxOCSNwgw7XouIBFA03FrLTs4M,22088
75
+ bumble/pandora/__init__.py,sha256=jaPtYCLfLeLUGj8-TmS3Gkv0l1_DBadYj8ZMAPLPAao,3463
76
76
  bumble/pandora/config.py,sha256=KD85n3oRbuvD65sRah2H0gpxEW4YbD7HbYbsxdcpDDA,2388
77
77
  bumble/pandora/device.py,sha256=LFqCWrgYkQWrFUSKArsAABXkge8sB2DhvaQoEsC4Jn0,5344
78
78
  bumble/pandora/host.py,sha256=LnEQ3DJAhmJVgHA7VyokvypQejsi2VcbauSFYn1a9Jk,39235
79
+ bumble/pandora/l2cap.py,sha256=OT-pPprVAQr7xwjYnNwDQujayG2zWUS5FPXVBGNCW3o,11725
79
80
  bumble/pandora/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
81
  bumble/pandora/security.py,sha256=YErueKNLsnmcRe6dKo724Ht-buOqmZl2Gauswcc8FW0,21946
81
82
  bumble/pandora/utils.py,sha256=Fq4glL0T5cJ2FODoDotmDNdYFOkTOR7DyyL8vkcxp20,3949
82
83
  bumble/profiles/__init__.py,sha256=yBGC8Ti5LvZuoh1F42XtfrBilb39T77_yuxESZeX2yI,581
84
+ bumble/profiles/aics.py,sha256=U0K03XqOKtqahXtq7U47WxftsAD7tkiDeUqxfaF4pwk,18540
83
85
  bumble/profiles/ascs.py,sha256=Y2_fd_5LOb9hon60LcQl3R-FoAIsOd_J37RZpveh-7c,25471
84
- bumble/profiles/asha_service.py,sha256=J4i5jkJciZWMtTWJ1zGJkEx65DlAEIADqjCRYf_CWNs,7220
86
+ bumble/profiles/asha.py,sha256=QXUp7ImuMD48L0mpuNxPcIkv8VvoENynCuF2PEVqzFM,10373
85
87
  bumble/profiles/bap.py,sha256=wLnrbXso203rgw0WNa91qIdMdjuIoGc9VqOHc9Ey_b4,19421
86
88
  bumble/profiles/bass.py,sha256=Wiqum0Wsr5PpVzTAPDcyKLTfJoKXJUYOzqB320aSiUs,14950
87
89
  bumble/profiles/battery_service.py,sha256=w-uF4jLoDozJOoykimb2RkrKjVyCke6ts2-h-F1PYyc,2292
@@ -89,6 +91,7 @@ bumble/profiles/cap.py,sha256=6gH7oOnUKjOggMPuB7rtbwj0AneoNmnWzQ_iR3io8e0,1945
89
91
  bumble/profiles/csip.py,sha256=qpI9W0_FWIpsuJrHhxfbKaa-TD21epxEa3EuCm8gh6c,10156
90
92
  bumble/profiles/device_information_service.py,sha256=RfqnXywcwcSTiFalxd1LVTTdeWLxHGsMvlvr9fI0GJI,6193
91
93
  bumble/profiles/gap.py,sha256=jUjfy6MPL7k6wgNn3ny3PVgSX6-SLmjUepFYHjGc3IU,3870
94
+ bumble/profiles/hap.py,sha256=9K6FR0FAjpRdDHoU5sgddndeVH2r1fQyWKRXM0dyoa0,25265
92
95
  bumble/profiles/heart_rate_service.py,sha256=_MG1ApmF1B7b4xRzELuahtRWzLm49i9O4m7I6ZFU4mw,8644
93
96
  bumble/profiles/le_audio.py,sha256=1x6OpIc0JMa32YVcfM8lq6jnXHlriHUcOkAs0EttJJk,3093
94
97
  bumble/profiles/mcp.py,sha256=vIN1r_if4LmOcGCgZuDYTYLMQzU6-1pKUFx1Z3iSAeY,17415
@@ -96,7 +99,7 @@ bumble/profiles/pacs.py,sha256=bhlU5M6E9e1YKf-qJnJL3GwTqOPuSvYDpY_UYq0Nnd8,8730
96
99
  bumble/profiles/pbp.py,sha256=51aoQcZMXzTzeatHd0zNVN7kYcv4atzr2OWpzxlSVP8,1630
97
100
  bumble/profiles/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
101
  bumble/profiles/tmap.py,sha256=hpz5VYEdEOty4D9ApCRDero9Tf3L9arSZWnLCNkZoHI,3005
99
- bumble/profiles/vcp.py,sha256=wkbTf2NRCbBtvpXplpNJq4dzXp6JGeaEHeeC1kHqW7s,7897
102
+ bumble/profiles/vcp.py,sha256=Lx1Odg7PQO9nXkpxRpK04MRuy975uIK8kSqcwrk4iSE,8035
100
103
  bumble/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
104
  bumble/tools/generate_company_id_list.py,sha256=ysbPb3zmxKFBiSQ_MBG2r15-sqR5P_GWT6i0YUTTXOM,1736
102
105
  bumble/tools/rtk_fw_download.py,sha256=KEPG-rDrdPGKBzZ78P4s3udLRYT3p7vesGhXvJTWTic,5453
@@ -109,7 +112,7 @@ bumble/transport/file.py,sha256=eVM2V6Nk2nDAFdE7Rt01ZI3JdTovsH9OEU1gKYPJjpE,2010
109
112
  bumble/transport/hci_socket.py,sha256=EdgWi3-O5yvYcH4R4BkPtG79pnUo7GQtXWawuUHDoDQ,6331
110
113
  bumble/transport/pty.py,sha256=grTl-yvjMWHflNwuME4ccVqDbk6NIEgQMgH6Y9lf1fU,2732
111
114
  bumble/transport/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
- bumble/transport/pyusb.py,sha256=znHdx0h_XNiDVa7bs2qlq9sG4vURlK8XloApZ73InDo,15532
115
+ bumble/transport/pyusb.py,sha256=IGcFJsYOlHaQRXcFCmGlwLZjg8D2BQq9IQrAs6TA2UA,16109
113
116
  bumble/transport/serial.py,sha256=loQxkeG7uE09enXWg2uGbxi6CeG70wn3kzPbEwULKw4,2446
114
117
  bumble/transport/tcp_client.py,sha256=deyUJYpj04QE00Mw_PTU5PHPA6mr1Nui3f5-QCy2zOw,1854
115
118
  bumble/transport/tcp_server.py,sha256=tvu7FuPeqiXfoj2HQU8wu4AiwKjDDDCKlKjgtqWc5hg,3779
@@ -152,9 +155,9 @@ bumble/vendor/android/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
152
155
  bumble/vendor/android/hci.py,sha256=GZrkhaWmcMt1JpnRhv0NoySGkf2H4lNUV2f_omRZW0I,10741
153
156
  bumble/vendor/zephyr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
154
157
  bumble/vendor/zephyr/hci.py,sha256=d83bC0TvT947eN4roFjLkQefWtHOoNsr4xib2ctSkvA,3195
155
- bumble-0.0.198.dist-info/LICENSE,sha256=FvaYh4NRWIGgS_OwoBs5gFgkCmAghZ-DYnIGBZPuw-s,12142
156
- bumble-0.0.198.dist-info/METADATA,sha256=fVbh2d9h95IUX4z5lP0FC4aXJ2WGsWX8Wb8gMKyjfLw,5672
157
- bumble-0.0.198.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
158
- bumble-0.0.198.dist-info/entry_points.txt,sha256=AOFf_gnWbZ7jk5fzspxXHCQUay1ik71pK3HYO7sZQsk,937
159
- bumble-0.0.198.dist-info/top_level.txt,sha256=tV6JJKaHPYMFiJYiBYFW24PCcfLxTJZdlu6BmH3Cb00,7
160
- bumble-0.0.198.dist-info/RECORD,,
158
+ bumble-0.0.199.dist-info/LICENSE,sha256=FvaYh4NRWIGgS_OwoBs5gFgkCmAghZ-DYnIGBZPuw-s,12142
159
+ bumble-0.0.199.dist-info/METADATA,sha256=Qfi-AevfEFjLoT0FfXuAjznE0U8MONF87tArGkqVIK0,5672
160
+ bumble-0.0.199.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
161
+ bumble-0.0.199.dist-info/entry_points.txt,sha256=AOFf_gnWbZ7jk5fzspxXHCQUay1ik71pK3HYO7sZQsk,937
162
+ bumble-0.0.199.dist-info/top_level.txt,sha256=tV6JJKaHPYMFiJYiBYFW24PCcfLxTJZdlu6BmH3Cb00,7
163
+ bumble-0.0.199.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,193 +0,0 @@
1
- # Copyright 2021-2022 Google LLC
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # https://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
-
16
- # -----------------------------------------------------------------------------
17
- # Imports
18
- # -----------------------------------------------------------------------------
19
- import struct
20
- import logging
21
- from typing import List, Optional
22
-
23
- from bumble import l2cap
24
- from ..core import AdvertisingData
25
- from ..device import Device, Connection
26
- from ..gatt import (
27
- GATT_ASHA_SERVICE,
28
- GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC,
29
- GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC,
30
- GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC,
31
- GATT_ASHA_VOLUME_CHARACTERISTIC,
32
- GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
33
- TemplateService,
34
- Characteristic,
35
- CharacteristicValue,
36
- )
37
- from ..utils import AsyncRunner
38
-
39
- # -----------------------------------------------------------------------------
40
- # Logging
41
- # -----------------------------------------------------------------------------
42
- logger = logging.getLogger(__name__)
43
-
44
-
45
- # -----------------------------------------------------------------------------
46
- class AshaService(TemplateService):
47
- UUID = GATT_ASHA_SERVICE
48
- OPCODE_START = 1
49
- OPCODE_STOP = 2
50
- OPCODE_STATUS = 3
51
- PROTOCOL_VERSION = 0x01
52
- RESERVED_FOR_FUTURE_USE = [00, 00]
53
- FEATURE_MAP = [0x01] # [LE CoC audio output streaming supported]
54
- SUPPORTED_CODEC_ID = [0x02, 0x01] # Codec IDs [G.722 at 16 kHz]
55
- RENDER_DELAY = [00, 00]
56
-
57
- def __init__(self, capability: int, hisyncid: List[int], device: Device, psm=0):
58
- self.hisyncid = hisyncid
59
- self.capability = capability # Device Capabilities [Left, Monaural]
60
- self.device = device
61
- self.audio_out_data = b''
62
- self.psm = psm # a non-zero psm is mainly for testing purpose
63
-
64
- # Handler for volume control
65
- def on_volume_write(connection, value):
66
- logger.info(f'--- VOLUME Write:{value[0]}')
67
- self.emit('volume', connection, value[0])
68
-
69
- # Handler for audio control commands
70
- def on_audio_control_point_write(connection: Optional[Connection], value):
71
- logger.info(f'--- AUDIO CONTROL POINT Write:{value.hex()}')
72
- opcode = value[0]
73
- if opcode == AshaService.OPCODE_START:
74
- # Start
75
- audio_type = ('Unknown', 'Ringtone', 'Phone Call', 'Media')[value[2]]
76
- logger.info(
77
- f'### START: codec={value[1]}, '
78
- f'audio_type={audio_type}, '
79
- f'volume={value[3]}, '
80
- f'otherstate={value[4]}'
81
- )
82
- self.emit(
83
- 'start',
84
- connection,
85
- {
86
- 'codec': value[1],
87
- 'audiotype': value[2],
88
- 'volume': value[3],
89
- 'otherstate': value[4],
90
- },
91
- )
92
- elif opcode == AshaService.OPCODE_STOP:
93
- logger.info('### STOP')
94
- self.emit('stop', connection)
95
- elif opcode == AshaService.OPCODE_STATUS:
96
- logger.info(f'### STATUS: connected={value[1]}')
97
-
98
- # OPCODE_STATUS does not need audio status point update
99
- if opcode != AshaService.OPCODE_STATUS:
100
- AsyncRunner.spawn(
101
- device.notify_subscribers(
102
- self.audio_status_characteristic, force=True
103
- )
104
- )
105
-
106
- self.read_only_properties_characteristic = Characteristic(
107
- GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC,
108
- Characteristic.Properties.READ,
109
- Characteristic.READABLE,
110
- bytes(
111
- [
112
- AshaService.PROTOCOL_VERSION, # Version
113
- self.capability,
114
- ]
115
- )
116
- + bytes(self.hisyncid)
117
- + bytes(AshaService.FEATURE_MAP)
118
- + bytes(AshaService.RENDER_DELAY)
119
- + bytes(AshaService.RESERVED_FOR_FUTURE_USE)
120
- + bytes(AshaService.SUPPORTED_CODEC_ID),
121
- )
122
-
123
- self.audio_control_point_characteristic = Characteristic(
124
- GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC,
125
- Characteristic.Properties.WRITE
126
- | Characteristic.Properties.WRITE_WITHOUT_RESPONSE,
127
- Characteristic.WRITEABLE,
128
- CharacteristicValue(write=on_audio_control_point_write),
129
- )
130
- self.audio_status_characteristic = Characteristic(
131
- GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC,
132
- Characteristic.Properties.READ | Characteristic.Properties.NOTIFY,
133
- Characteristic.READABLE,
134
- bytes([0]),
135
- )
136
- self.volume_characteristic = Characteristic(
137
- GATT_ASHA_VOLUME_CHARACTERISTIC,
138
- Characteristic.Properties.WRITE_WITHOUT_RESPONSE,
139
- Characteristic.WRITEABLE,
140
- CharacteristicValue(write=on_volume_write),
141
- )
142
-
143
- # Register an L2CAP CoC server
144
- def on_coc(channel):
145
- def on_data(data):
146
- logging.debug(f'<<< data received:{data}')
147
-
148
- self.emit('data', channel.connection, data)
149
- self.audio_out_data += data
150
-
151
- channel.sink = on_data
152
-
153
- # let the server find a free PSM
154
- self.psm = device.create_l2cap_server(
155
- spec=l2cap.LeCreditBasedChannelSpec(psm=self.psm, max_credits=8),
156
- handler=on_coc,
157
- ).psm
158
- self.le_psm_out_characteristic = Characteristic(
159
- GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
160
- Characteristic.Properties.READ,
161
- Characteristic.READABLE,
162
- struct.pack('<H', self.psm),
163
- )
164
-
165
- characteristics = [
166
- self.read_only_properties_characteristic,
167
- self.audio_control_point_characteristic,
168
- self.audio_status_characteristic,
169
- self.volume_characteristic,
170
- self.le_psm_out_characteristic,
171
- ]
172
-
173
- super().__init__(characteristics)
174
-
175
- def get_advertising_data(self):
176
- # Advertisement only uses 4 least significant bytes of the HiSyncId.
177
- return bytes(
178
- AdvertisingData(
179
- [
180
- (
181
- AdvertisingData.SERVICE_DATA_16_BIT_UUID,
182
- bytes(GATT_ASHA_SERVICE)
183
- + bytes(
184
- [
185
- AshaService.PROTOCOL_VERSION,
186
- self.capability,
187
- ]
188
- )
189
- + bytes(self.hisyncid[:4]),
190
- ),
191
- ]
192
- )
193
- )