xref: /btstack/tool/bluetooth_sdp.py (revision 5c54401929043df982e040eba652f5fd7763ce15)
1#!/usr/bin/env python3
2#
3# Scrape SDP UUIDs from Bluetooth SIG page
4# Copyright 2017 BlueKitchen GmbH
5#
6
7from lxml import html
8import datetime
9import requests
10import sys
11import os
12import codecs
13import re
14
15headers = {'user-agent': 'curl/7.63.0'}
16
17program_info = '''
18BTstack SDP UUID Scraper for BTstack
19Copyright 2017, BlueKitchen GmbH
20'''
21
22header = '''/**
23 * bluetooth_sdp.h generated from Bluetooth SIG website for BTstack by tool/bluetooth_sdp.py
24 * {page}
25 * {datetime}
26 */
27
28#ifndef BLUETOOTH_SDP_H
29#define BLUETOOTH_SDP_H
30
31'''
32
33trailer = '''
34#endif
35'''
36
37defines = []
38
39# Convert CamelCase to snake_case from http://stackoverflow.com/a/1176023
40def camel_to_underscore(name):
41    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
42    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).upper()
43
44def create_pretty_define(name):
45    name = name.lstrip()
46    to_delete = [ '(FTP v1.2 and later)', '(Deprecated)', '(FTP v1.2 and later)', '(GOEP v2.0 and later)',
47     '(BIP v1.1 and later)', '(MAP v1.2 and later)', '(OPP v1.2 and later)', '(Not used in PAN v1.0)', '(PBAP v1.2 and later)']
48    for item in to_delete:
49        name = name.replace(item, '')
50    name = name.rstrip()
51    name = name.replace(' - ', '_')
52    name = name.replace(' ', '_')
53    name = name.replace('/','')
54    name = name.replace('(','_')
55    name = name.replace(')','')
56    name = name.replace('-','_')
57    name = name.replace('.','_')
58    name = name.replace('PnP', 'PNP')
59    name = name.replace('IPv', 'IPV')
60    name = name.replace('ServiceDiscoveryServerServiceClassID', 'ServiceDiscoveryServer')
61    name = name.replace('BrowseGroupDescriptorServiceClassID', 'BrowseGroupDescriptor')
62    name = name.replace('&','and')
63    name = name.replace('__','_')
64    return camel_to_underscore(name).replace('__','_').replace('3_D','3D').replace('L2_CAP','L2CAP')
65
66def remove_newlines(remark):
67    return " ".join(remark.split())
68
69def process_rows(fout, rows, pattern):
70    for row in rows:
71        columns = row.getchildren()
72        name = columns[0].text_content().replace(u'\u2013','').replace(u'\u200b','')
73        value = columns[1].text_content().replace(u'\u200b','')
74        remark = ''
75        if (len(columns) > 2):
76            remark = columns[2].text_content().replace(u'\u200b','')
77        # skip tbody headers
78        if name in ["Protocol Name", "Service Class Name", "Attribute Name", "UUID Name",
79            "Reserved", 'Reserved for HID Attributes', 'Available for HID Language Strings']:
80            continue
81        # skip tbody footers
82        if value.startswith('(Max value '):
83            continue
84        name = create_pretty_define(name)
85        print(name)
86        if name.endswith("DISPLAY"):
87            for c in name:
88                print("ord %c = 0x%x" % (c, ord(c)))
89
90        # skip duplicate attributes
91        if name in defines:
92            continue
93        value = remove_newlines(value)
94        remark = remove_newlines(remark)
95        fout.write(pattern % (name, value, remark))
96        defines.append(name)
97
98def scrape_page(fout, url):
99    print("Parsing %s" % url)
100    fout.write(header.format(page=url.replace('https://',''),datetime=str(datetime.datetime.now())))
101
102    # get from web
103    r = requests.get(url, headers=headers)
104    content = r.text
105
106    # test: fetch from local file 'service-discovery.html'
107    # f = codecs.open("service-discovery.html", "r", "utf-8")
108    # content = f.read();
109
110    tree = html.fromstring(content)
111
112    # Protocol Identifiers
113    fout.write('/**\n')
114    fout.write(' * Protocol Identifiers\n')
115    fout.write(' */\n')
116    rows = tree.xpath("//table[2]/tbody/tr")
117    process_rows(fout, rows, '#define BLUETOOTH_PROTOCOL_%-55s %s // %s\n')
118    fout.write('\n')
119
120    # Service Classes
121    fout.write('/**\n')
122    fout.write(' * Service Classes\n')
123    fout.write(' */\n')
124    rows = tree.xpath("//table[3]/tr")
125    process_rows(fout, rows, '#define BLUETOOTH_SERVICE_CLASS_%-50s %s // %s\n')
126    fout.write('\n')
127
128    # Attributes
129    fout.write('/**\n')
130    fout.write(' * Attributes\n')
131    fout.write(' */\n')
132    table_names = [
133        # 'Base Universally Unique Identifier (UUID)',
134        'Browse Group Identifiers',
135        'Attribute Identifiers',
136        # 'Audio/Video Remote Control Profile (AVRCP)',
137        'Basic Imaging Profile (BIP)',
138        'Basic Printing Profile (BPP)',
139        'Bluetooth Core Specification: Universal Attributes',
140        'Bluetooth Core Specification: Service Discovery Service',
141        # 'Bluetooth Core Specification: Browse Group Descriptor Service',
142        # 'Cordless Telephony Profile [DEPRECATED]',
143        'Device Identification Profile',
144        # 'Fax Profile [DEPRECATED]',
145        'File Transfer Profile',
146        'Generic Object Exchange Profile',
147        # 'Global Navigation Satellite System Profile (GNSS)', -- note: SupportedFeatures, but different UUID
148        'Hands-Free Profile',
149        'Hardcopy Replacement Profile ',
150        'Headset Profile',
151        'Health Device Profile',
152        'Human Interface Device Profile',
153        # 'Interoperability Requirements for Bluetooth technology as a WAP Bearer [DEPRECATED]',
154        'Message Access Profile',
155        'Object Push Profile',
156        'Personal Area Networking Profile',
157        'Phone Book Access Profile',
158        'Synchronization Profile',
159        # 'Attribute ID Offsets for Strings',
160        # 'Protocol Parameters',
161        'Multi-Profile',
162        'Calendar Tasks and Notes',
163    ]
164    for table_name in table_names:
165        rows = tree.xpath("//table[preceding-sibling::h3 = '" + table_name +"']/tr")
166        process_rows(fout, rows, '#define BLUETOOTH_ATTRIBUTE_%-54s %s // %s\n')
167        # scrape_attributes(fout, tree, table_name)
168    # see above
169    fout.write('#define BLUETOOTH_ATTRIBUTE_GNSS_SUPPORTED_FEATURES                                0x0200\n');
170
171
172
173btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
174gen_path = btstack_root + '/src/bluetooth_sdp.h'
175
176print(program_info)
177
178with open(gen_path, 'wt') as fout:
179    scrape_page(fout, 'https://www.bluetooth.com/specifications/assigned-numbers/service-discovery')
180    fout.write(trailer)
181
182print('Scraping successful!\n')
183