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