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