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