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