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# Imports
17# -----------------------------------------------------------------------------
18import asyncio
19import sys
20import os
21import logging
22
23from bumble.device import Device
24from bumble.transport import open_transport_or_link
25from bumble.sdp import (
26    DataElement,
27    ServiceAttribute,
28    SDP_PUBLIC_BROWSE_ROOT,
29    SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
30    SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
31    SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
32    SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
33    SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
34)
35from bumble.core import (
36    BT_AUDIO_SINK_SERVICE,
37    BT_L2CAP_PROTOCOL_ID,
38    BT_AVDTP_PROTOCOL_ID,
39    BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
40)
41
42# -----------------------------------------------------------------------------
43SDP_SERVICE_RECORDS = {
44    0x00010001: [
45        ServiceAttribute(
46            SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
47            DataElement.unsigned_integer_32(0x00010001),
48        ),
49        ServiceAttribute(
50            SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
51            DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
52        ),
53        ServiceAttribute(
54            SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
55            DataElement.sequence([DataElement.uuid(BT_AUDIO_SINK_SERVICE)]),
56        ),
57        ServiceAttribute(
58            SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
59            DataElement.sequence(
60                [
61                    DataElement.sequence(
62                        [
63                            DataElement.uuid(BT_L2CAP_PROTOCOL_ID),
64                            DataElement.unsigned_integer_16(25),
65                        ]
66                    ),
67                    DataElement.sequence(
68                        [
69                            DataElement.uuid(BT_AVDTP_PROTOCOL_ID),
70                            DataElement.unsigned_integer_16(256),
71                        ]
72                    ),
73                ]
74            ),
75        ),
76        ServiceAttribute(
77            SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
78            DataElement.sequence(
79                [
80                    DataElement.sequence(
81                        [
82                            DataElement.uuid(BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE),
83                            DataElement.unsigned_integer_16(256),
84                        ]
85                    )
86                ]
87            ),
88        ),
89    ]
90}
91
92
93# -----------------------------------------------------------------------------
94async def main() -> None:
95    if len(sys.argv) < 3:
96        print('Usage: run_classic_discoverable.py <device-config> <transport-spec>')
97        print('example: run_classic_discoverable.py classic1.json usb:04b4:f901')
98        return
99
100    print('<<< connecting to HCI...')
101    async with await open_transport_or_link(sys.argv[2]) as hci_transport:
102        print('<<< connected')
103
104        # Create a device
105        device = Device.from_config_file_with_hci(
106            sys.argv[1], hci_transport.source, hci_transport.sink
107        )
108        device.classic_enabled = True
109        device.sdp_service_records = SDP_SERVICE_RECORDS
110        await device.power_on()
111
112        # Start being discoverable and connectable
113        await device.set_discoverable(True)
114        await device.set_connectable(True)
115
116        await hci_transport.source.wait_for_termination()
117
118
119# -----------------------------------------------------------------------------
120logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
121asyncio.run(main())
122