diff --git a/tool/convert_gatt_service.py b/tool/convert_gatt_service.py new file mode 100755 index 000000000..05ede0b5f --- /dev/null +++ b/tool/convert_gatt_service.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# +# Convert offical Bluetooth GATT Service definitions into BTstack's .gatt format +# Copyright 2016 BlueKitchen GmbH +# + +from lxml import html +import codecs +import datetime +import os +import requests +import sys +import xml.etree.ElementTree as ET + + +def indent(elem, level=0): + i = "\n" + level*" " + j = "\n" + (level-1)*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for subelem in elem: + indent(subelem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = j + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = j + return elem + +def list_services(): + url = 'https://www.bluetooth.com/specifications/gatt/services' + print("Fetching list of services from %s" % url) + print('') + + page = requests.get(url) + tree = html.fromstring(page.content) + # get all elements in + rows = tree.xpath('//table[@id="gattTable"]/tr') + print("%-55s| %-30s| %s" % ('Service ID', 'Summary', 'UUID')) + print('-'*55 + '+-' + '-' * 30 + '+-' + '-'*10) + maxlen_type = 0 + maxlen_name = 0 + services = [] + for row in rows: + children = row.getchildren() + summary = children[0].text_content() + id = children[1].text_content() + uuid = children[2].text_content() + if (len(id)): + services.append((id, summary, uuid)) + # sort + services.sort(key=lambda tup: tup[1]) + for service in services: + if (len(id) > maxlen_type): + maxlen_type = len(id) + if (len(summary) > maxlen_name): + maxlen_name = len(summary) + print("%-55s| %-30s| %s" % service) + +def parse_properties(element): + properties = element.find('Properties') + property_list_human = [] + for property in properties: + property_name = property.tag + property_requirement = property.text + if (property_requirement != "Excluded"): + property_list_human.append(property_name) + return (property_list_human) + + print("Characteristic %s - properties %s" % (name, property_list_human)) + fout.write('CHARACTERISTIC, %s, %s,\n' % (type, ' | '.join(property_list))) + +def handle_todo(fout, name): + print('-- Descriptor %-40s - TODO: Please set values' % name) + fout.write('// TODO: %s: please set values\n' % name) + + +def convert_service(fout, specification_type): + url = 'https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=%s.xml' % specification_type + + print('Fetching %s from %s' %(specification_type, url)) + + try: + page = requests.get(url) + tree = ET.fromstring(page.content) + service_attributes = tree.attrib + except ET.ParseError: + print('Failed to download file') + sys.exit(10) + + print('') + + # indent(tree) + # ET.dump(tree) + # return + + service_name = service_attributes['name'] + service_uuid = service_attributes['uuid'] + + print("Service %s" % service_name) + fout.write('// Specification Type %s\n' % specification_type) + fout.write('// %s\n' % url) + fout.write('\n') + fout.write('// %s %s\n' % (service_name, service_uuid)) + fout.write('PRIMARY_SERVICE, %s\n' % specification_type) + + characteristics = tree.find('Characteristics') + for characteristic in characteristics: + type = characteristic.attrib['type'] + name = characteristic.attrib['name'] + property_list_human = parse_properties(characteristic) + properties = ' | '.join( ['DYNAMIC'] + property_list_human).upper() + print("- Characteristic %s - properties %s" % (name, property_list_human)) + fout.write('CHARACTERISTIC, %s, %s,\n' % (type, properties)) + + descriptors = characteristic.find('Descriptors') + for descriptor in descriptors: + type = descriptor.attrib['type'] + name = descriptor.attrib['name'] + property_list_human = parse_properties(descriptor) + properties = ' | '.join(property_list_human).upper() + + if (type == 'org.bluetooth.descriptor.gatt.client_characteristic_configuration'): + print('-- Descriptor %-40s - SKIPPED: automatically generated currently by compile_gatt.py' % name) + continue + + if (type == 'org.bluetooth.descriptor.gatt.characteristic_presentation_format'): + handle_todo(fout, 'Characteristic Presentation Format') + fout.write('CHARACTERISTIC_FORMAT, %s, _format_, _exponent_, _unit_, _name_space_, _description_\n' % properties) + continue + + if (type == 'org.bluetooth.descriptor.gatt.characteristic_user_description'): + handle_todo(fout, 'Characteristic User Description') + fout.write('CHARACTERISTIC_USER_DESCRIPTION, %s, _user_description_\n' % properties) + continue + + if (type == 'org.bluetooth.descriptor.gatt.server_characteristic_configuration'): + handle_todo(fout, 'Server Characteristic Configuration') + fout.write('SERVER_CHARACTERISTIC_CONFIGURATION, %s,\n' % properties) + continue + + if (type == 'org.bluetooth.descriptor.gatt.characteristic_aggregate_format'): + handle_todo(fout, 'Characteristic Aggregate Format') + fout.write('CHARACTERISTIC_AGGREGATE_FORMAT, %s, _list_of_handles_\n' % properties) + continue + + if (type == 'org.bluetooth.descriptor.valid_range'): + print('-- Descriptor %-40s - WARNING: VALID_RANGE not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.external_report_reference'): + print('-- Descriptor %-40s - WARNING: EXTERNAL_REPORT_REFERENCE not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.report_reference'): + print('-- Descriptor %-40s - WARNING: REPORT_REFERENCE not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.number_of_digitals'): + print('-- Descriptor %-40s - WARNING: NUMBER_OF_DIGITALS not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.value_trigger_setting'): + print('-- Descriptor %-40s - WARNING: VALUE_TRIGGER_SETTING not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.es_configuration'): + print('-- Descriptor %-40s - WARNING: ENVIRONMENTAL_SENSING_CONFIGURATION not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.es_measurement'): + print('-- Descriptor %-40s - WARNING: ENVIRONMENTAL_SENSING_MEASUREMENT not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.es_trigger_setting'): + print('-- Descriptor %-40s - WARNING: ENVIRONMENTAL_SENSING_TRIGGER_SETTING not supported yet' % name) + continue + + if (type == 'org.bluetooth.descriptor.gatt.characteristic_extended_properties'): + print('-- Descriptor %-40s - WARNING: not supported yet' % name) + continue + + print('-- Descriptor %-40s -- WARNING: not supported yet' % name) + + +if (len(sys.argv) < 2): + list_services() + print('\n') + # print(maxlen_type, maxlen_name) + print('To convert service into .gatt file, call %s with the requested service id' % sys.argv[0]) +else: + specification_type = sys.argv[1] + filename = '%s.gatt' % specification_type + with open(filename, 'wt') as fout: + convert_service(fout, specification_type) + print('') + print('Service successfully converted into %s' % filename) + print('Please check for TODOs in the .gatt file') \ No newline at end of file