btstack/tool/compile_gatt.py

911 lines
29 KiB
Python
Executable File

#!/usr/bin/env python
#
# BLE GATT configuration generator for use with BTstack
# Copyright 2018 BlueKitchen GmbH
#
# Format of input file:
# PRIMARY_SERVICE, SERVICE_UUID
# CHARACTERISTIC, ATTRIBUTE_TYPE_UUID, [READ | WRITE | DYNAMIC], VALUE
import codecs
import csv
import io
import os
import re
import string
import sys
import argparse
header = '''
// {0} generated from {1} for BTstack
// att db format version 1
// binary attribute representation:
// - size in bytes (16), flags(16), handle (16), uuid (16/128), value(...)
#include <stdint.h>
const uint8_t profile_data[] =
'''
print('''
BLE configuration generator for use with BTstack
Copyright 2018 BlueKitchen GmbH
''')
assigned_uuids = {
'GAP_SERVICE' : 0x1800,
'GATT_SERVICE' : 0x1801,
'GAP_DEVICE_NAME' : 0x2a00,
'GAP_APPEARANCE' : 0x2a01,
'GAP_PERIPHERAL_PRIVACY_FLAG' : 0x2A02,
'GAP_RECONNECTION_ADDRESS' : 0x2A03,
'GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS' : 0x2A04,
'GATT_SERVICE_CHANGED' : 0x2a05,
}
security_permsission = ['ANYBODY','ENCRYPTED', 'AUTHENTICATED', 'AUTHORIZED']
property_flags = {
# GATT Characteristic Properties
'BROADCAST' : 0x01,
'READ' : 0x02,
'WRITE_WITHOUT_RESPONSE' : 0x04,
'WRITE' : 0x08,
'NOTIFY': 0x10,
'INDICATE' : 0x20,
'AUTHENTICATED_SIGNED_WRITE' : 0x40,
'EXTENDED_PROPERTIES' : 0x80,
# custom BTstack extension
'DYNAMIC': 0x100,
'LONG_UUID': 0x200,
# read permissions
'READ_PERMISSION_BIT_0': 0x400,
'READ_PERMISSION_BIT_1': 0x800,
#
'ENCRYPTION_KEY_SIZE_7': 0x6000,
'ENCRYPTION_KEY_SIZE_8': 0x7000,
'ENCRYPTION_KEY_SIZE_9': 0x8000,
'ENCRYPTION_KEY_SIZE_10': 0x9000,
'ENCRYPTION_KEY_SIZE_11': 0xa000,
'ENCRYPTION_KEY_SIZE_12': 0xb000,
'ENCRYPTION_KEY_SIZE_13': 0xc000,
'ENCRYPTION_KEY_SIZE_14': 0xd000,
'ENCRYPTION_KEY_SIZE_15': 0xe000,
'ENCRYPTION_KEY_SIZE_16': 0xf000,
'ENCRYPTION_KEY_SIZE_MASK': 0xf000,
# only used by gatt compiler >= 0xffff
# Extended Properties
'RELIABLE_WRITE': 0x0010000,
'AUTHENTICATION_REQUIRED': 0x0020000,
'AUTHORIZATION_REQUIRED': 0x0040000,
'READ_ANYBODY': 0x0080000,
'READ_ENCRYPTED': 0x0100000,
'READ_AUTHENTICATED': 0x0200000,
'READ_AUTHORIZED': 0x0400000,
'WRITE_ANYBODY': 0x0800000,
'WRITE_ENCRYPTED': 0x1000000,
'WRITE_AUTHENTICATED': 0x2000000,
'WRITE_AUTHORIZED': 0x4000000,
# Broadcast, Notify, Indicate, Extended Properties are only used to describe a GATT Characteristic, but are free to use with att_db
# write permissions
'WRITE_PERMISSION_BIT_0': 0x01,
'WRITE_PERMISSION_BIT_1': 0x10,
# 0x20
# 0x80
}
services = dict()
characteristic_indices = dict()
presentation_formats = dict()
current_service_uuid_string = ""
current_service_start_handle = 0
current_characteristic_uuid_string = ""
defines_for_characteristics = []
defines_for_services = []
handle = 1
total_size = 0
def read_defines(infile):
defines = dict()
with open (infile, 'rt') as fin:
for line in fin:
parts = re.match('#define\s+(\w+)\s+(\w+)',line)
if parts and len(parts.groups()) == 2:
(key, value) = parts.groups()
defines[key] = int(value, 16)
return defines
def keyForUUID(uuid):
keyUUID = ""
for i in uuid:
keyUUID += "%02x" % i
return keyUUID
def c_string_for_uuid(uuid):
return uuid.replace('-', '_')
def twoByteLEFor(value):
return [ (value & 0xff), (value >> 8)]
def is_128bit_uuid(text):
if re.match("[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}", text):
return True
return False
def parseUUID128(uuid):
parts = re.match("([0-9A-Fa-f]{4})([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})([0-9A-Fa-f]{4})([0-9A-Fa-f]{4})", uuid)
uuid_bytes = []
for i in range(8, 0, -1):
uuid_bytes = uuid_bytes + twoByteLEFor(int(parts.group(i),16))
return uuid_bytes
def parseUUID(uuid):
if uuid in assigned_uuids:
return twoByteLEFor(assigned_uuids[uuid])
uuid_upper = uuid.upper().replace('.','_')
if uuid_upper in bluetooth_gatt:
return twoByteLEFor(bluetooth_gatt[uuid_upper])
if is_128bit_uuid(uuid):
return parseUUID128(uuid)
uuidInt = int(uuid, 16)
return twoByteLEFor(uuidInt)
def parseProperties(properties):
value = 0
parts = properties.split("|")
for property in parts:
property = property.strip()
if property in property_flags:
value |= property_flags[property]
else:
print("WARNING: property %s undefined" % (property))
return value;
def gatt_characteristic_properties(properties):
return properties & 0xff
def att_flags(properties):
# drop Broadcast (0x01), Notify (0x10), Indicate (0x20) - not used for flags
properties &= 0xffffffce
# rw permissions distinct
distinct_permissions_used = properties & (
property_flags['READ_AUTHORIZED'] |
property_flags['READ_AUTHENTICATED'] |
property_flags['READ_ENCRYPTED'] |
property_flags['READ_ANYBODY'] |
property_flags['WRITE_AUTHORIZED'] |
property_flags['WRITE_AUTHENTICATED'] |
property_flags['WRITE_ENCRYPTED'] |
property_flags['WRITE_ANYBODY']
) != 0
# post process properties
encryption_key_size_specified = (properties & property_flags['ENCRYPTION_KEY_SIZE_MASK']) != 0
# if distinct permissions not used and encyrption key size specified -> set READ/WRITE Encrypted
if encryption_key_size_specified and not distinct_permissions_used:
properties |= property_flags['READ_ENCRYPTED'] | property_flags['WRITE_ENCRYPTED']
# if distinct permissions not used and authentication is requires -> set READ/WRITE Authenticated
if properties & property_flags['AUTHENTICATION_REQUIRED'] and not distinct_permissions_used:
properties |= property_flags['READ_AUTHENTICATED'] | property_flags['WRITE_AUTHENTICATED']
# if distinct permissions not used and authorized is requires -> set READ/WRITE Authorized
if properties & property_flags['AUTHORIZATION_REQUIRED'] and not distinct_permissions_used:
properties |= property_flags['READ_AUTHORIZED'] | property_flags['WRITE_AUTHORIZED']
# determine read/write security requirements
read_security_level = 0
write_security_level = 0
if properties & property_flags['READ_AUTHORIZED']:
read_security_level = 3
elif properties & property_flags['READ_AUTHENTICATED']:
read_security_level = 2
elif properties & property_flags['READ_ENCRYPTED']:
read_security_level = 1
if properties & property_flags['WRITE_AUTHORIZED']:
write_security_level = 3
elif properties & property_flags['WRITE_AUTHENTICATED']:
write_security_level = 2
elif properties & property_flags['WRITE_ENCRYPTED']:
write_security_level = 1
# map security requirements to flags
if read_security_level & 2:
properties |= property_flags['READ_PERMISSION_BIT_1']
if read_security_level & 1:
properties |= property_flags['READ_PERMISSION_BIT_0']
if write_security_level & 2:
properties |= property_flags['WRITE_PERMISSION_BIT_1']
if write_security_level & 1:
properties |= property_flags['WRITE_PERMISSION_BIT_0']
return properties
def write_permissions_and_key_size_flags_from_properties(properties):
return att_flags(properties) & (property_flags['ENCRYPTION_KEY_SIZE_MASK'] | property_flags['WRITE_PERMISSION_BIT_0'] | property_flags['WRITE_PERMISSION_BIT_1'])
def write_8(fout, value):
fout.write( "0x%02x, " % (value & 0xff))
def write_16(fout, value):
fout.write('0x%02x, 0x%02x, ' % (value & 0xff, (value >> 8) & 0xff))
def write_uuid(uuid):
for byte in uuid:
fout.write( "0x%02x, " % byte)
def write_string(fout, text):
for l in text.lstrip('"').rstrip('"'):
write_8(fout, ord(l))
def write_sequence(fout, text):
parts = text.split()
for part in parts:
fout.write("0x%s, " % (part.strip()))
def write_indent(fout):
fout.write(" ")
def read_permissions_from_flags(flags):
permissions = 0
if flags & property_flags['READ_PERMISSION_BIT_0']:
permissions |= 1
if flags & property_flags['READ_PERMISSION_BIT_1']:
permissions |= 2
return permissions
def write_permissions_from_flags(flags):
permissions = 0
if flags & property_flags['WRITE_PERMISSION_BIT_0']:
permissions |= 1
if flags & property_flags['WRITE_PERMISSION_BIT_1']:
permissions |= 2
return permissions
def encryption_key_size_from_flags(flags):
encryption_key_size = (flags & 0xf000) >> 12
if encryption_key_size > 0:
encryption_key_size += 1
return encryption_key_size
def is_string(text):
for item in text.split(" "):
if not all(c in string.hexdigits for c in item):
return True
return False
def add_client_characteristic_configuration(properties):
return properties & (property_flags['NOTIFY'] | property_flags['INDICATE'])
def serviceDefinitionComplete(fout):
global services
if current_service_uuid_string:
fout.write("\n")
# print("append service %s = [%d, %d]" % (current_characteristic_uuid_string, current_service_start_handle, handle-1))
defines_for_services.append('#define ATT_SERVICE_%s_START_HANDLE 0x%04x' % (current_service_uuid_string, current_service_start_handle))
defines_for_services.append('#define ATT_SERVICE_%s_END_HANDLE 0x%04x' % (current_service_uuid_string, handle-1))
services[current_service_uuid_string] = [current_service_start_handle, handle-1]
def dump_flags(fout, flags):
global security_permsission
encryption_key_size = encryption_key_size_from_flags(flags)
read_permissions = security_permsission[read_permissions_from_flags(flags)]
write_permissions = security_permsission[write_permissions_from_flags(flags)]
write_indent(fout)
fout.write('// ')
first = 1
if flags & property_flags['READ']:
fout.write('READ_%s' % read_permissions)
first = 0
if flags & (property_flags['WRITE'] | property_flags['WRITE_WITHOUT_RESPONSE']):
if not first:
fout.write(', ')
first = 0
fout.write('WRITE_%s' % write_permissions)
if encryption_key_size > 0:
if not first:
fout.write(', ')
first = 0
fout.write('ENCRYPTION_KEY_SIZE=%u' % encryption_key_size)
fout.write('\n')
def parseService(fout, parts, service_type):
global handle
global total_size
global current_service_uuid_string
global current_service_start_handle
serviceDefinitionComplete(fout)
read_only_anybody_flags = property_flags['READ'];
write_indent(fout)
fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
uuid = parseUUID(parts[1])
uuid_size = len(uuid)
size = 2 + 2 + 2 + uuid_size + 2
if service_type == 0x2802:
size += 4
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, service_type)
write_uuid(uuid)
fout.write("\n")
current_service_uuid_string = c_string_for_uuid(parts[1])
current_service_start_handle = handle
handle = handle + 1
total_size = total_size + size
def parsePrimaryService(fout, parts):
parseService(fout, parts, 0x2800)
def parseSecondaryService(fout, parts):
parseService(fout, parts, 0x2801)
def parseIncludeService(fout, parts):
global handle
global total_size
read_only_anybody_flags = property_flags['READ'];
write_indent(fout)
fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
uuid = parseUUID(parts[1])
uuid_size = len(uuid)
if uuid_size > 2:
uuid_size = 0
# print("Include Service ", c_string_for_uuid(uuid))
size = 2 + 2 + 2 + 2 + 4 + uuid_size
keyUUID = c_string_for_uuid(parts[1])
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2802)
write_16(fout, services[keyUUID][0])
write_16(fout, services[keyUUID][1])
if uuid_size > 0:
write_uuid(uuid)
fout.write("\n")
handle = handle + 1
total_size = total_size + size
def parseCharacteristic(fout, parts):
global handle
global total_size
global current_characteristic_uuid_string
global characteristic_indices
read_only_anybody_flags = property_flags['READ'];
# enumerate characteristics with same UUID, using optional name tag if available
current_characteristic_uuid_string = c_string_for_uuid(parts[1]);
index = 1
if current_characteristic_uuid_string in characteristic_indices:
index = characteristic_indices[current_characteristic_uuid_string] + 1
characteristic_indices[current_characteristic_uuid_string] = index
if len(parts) > 4:
current_characteristic_uuid_string += '_' + parts[4].upper().replace(' ','_')
else:
current_characteristic_uuid_string += ('_%02x' % index)
uuid = parseUUID(parts[1])
uuid_size = len(uuid)
properties = parseProperties(parts[2])
value = ', '.join([str(x) for x in parts[3:]])
# reliable writes is defined in an extended properties
if (properties & property_flags['RELIABLE_WRITE']):
properties = properties | property_flags['EXTENDED_PROPERTIES']
write_indent(fout)
fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts[0:3])))
characteristic_properties = gatt_characteristic_properties(properties)
size = 2 + 2 + 2 + 2 + (1+2+uuid_size)
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2803)
write_8(fout, characteristic_properties)
write_16(fout, handle+1)
write_uuid(uuid)
fout.write("\n")
handle = handle + 1
total_size = total_size + size
size = 2 + 2 + 2 + uuid_size
if is_string(value):
size = size + len(value)
else:
size = size + len(value.split())
value_flags = att_flags(properties)
# add UUID128 flag for value handle
if uuid_size == 16:
value_flags = value_flags | property_flags['LONG_UUID'];
write_indent(fout)
fout.write('// 0x%04x VALUE-%s-'"'%s'"'\n' % (handle, '-'.join(parts[1:3]),value))
dump_flags(fout, value_flags)
write_indent(fout)
write_16(fout, size)
write_16(fout, value_flags)
write_16(fout, handle)
write_uuid(uuid)
if is_string(value):
write_string(fout, value)
else:
write_sequence(fout,value)
fout.write("\n")
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_VALUE_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
handle = handle + 1
if add_client_characteristic_configuration(properties):
# use write permissions and encryption key size from attribute value and set READ_ANYBODY | READ | WRITE | DYNAMIC
flags = write_permissions_and_key_size_flags_from_properties(properties)
flags |= property_flags['READ']
flags |= property_flags['WRITE']
flags |= property_flags['WRITE_WITHOUT_RESPONSE']
flags |= property_flags['DYNAMIC']
size = 2 + 2 + 2 + 2 + 2
write_indent(fout)
fout.write('// 0x%04x CLIENT_CHARACTERISTIC_CONFIGURATION\n' % (handle))
dump_flags(fout, flags)
write_indent(fout)
write_16(fout, size)
write_16(fout, flags)
write_16(fout, handle)
write_16(fout, 0x2902)
write_16(fout, 0)
fout.write("\n")
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
handle = handle + 1
if properties & property_flags['RELIABLE_WRITE']:
size = 2 + 2 + 2 + 2 + 2
write_indent(fout)
fout.write('// 0x%04x CHARACTERISTIC_EXTENDED_PROPERTIES\n' % (handle))
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2900)
write_16(fout, 1) # Reliable Write
fout.write("\n")
handle = handle + 1
def parseCharacteristicUserDescription(fout, parts):
global handle
global total_size
global current_characteristic_uuid_string
properties = parseProperties(parts[1])
value = parts[2]
size = 2 + 2 + 2 + 2
if is_string(value):
size = size + len(value)
else:
size = size + len(value.split())
# use write, write permissions and encryption key size from attribute value and set READ_ANYBODY
flags = write_permissions_and_key_size_flags_from_properties(properties)
flags |= properties & property_flags['WRITE']
flags |= property_flags['READ']
write_indent(fout)
fout.write('// 0x%04x CHARACTERISTIC_USER_DESCRIPTION-%s\n' % (handle, '-'.join(parts[1:])))
dump_flags(fout, flags)
write_indent(fout)
write_16(fout, size)
write_16(fout, flags)
write_16(fout, handle)
write_16(fout, 0x2901)
if is_string(value):
write_string(fout, value)
else:
write_sequence(fout,value)
fout.write("\n")
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
handle = handle + 1
def parseServerCharacteristicConfiguration(fout, parts):
global handle
global total_size
global current_characteristic_uuid_string
properties = parseProperties(parts[1])
size = 2 + 2 + 2 + 2
# use write permissions and encryption key size from attribute value and set READ, WRITE, DYNAMIC, READ_ANYBODY
flags = write_permissions_and_key_size_flags_from_properties(properties)
flags |= property_flags['READ']
flags |= property_flags['WRITE']
flags |= property_flags['DYNAMIC']
write_indent(fout)
fout.write('// 0x%04x SERVER_CHARACTERISTIC_CONFIGURATION-%s\n' % (handle, '-'.join(parts[1:])))
dump_flags(fout, flags)
write_indent(fout)
write_16(fout, size)
write_16(fout, flags)
write_16(fout, handle)
write_16(fout, 0x2903)
fout.write("\n")
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
handle = handle + 1
def parseCharacteristicFormat(fout, parts):
global handle
global total_size
read_only_anybody_flags = property_flags['READ'];
identifier = parts[1]
presentation_formats[identifier] = handle
# print("format '%s' with handle %d\n" % (identifier, handle))
format = parts[2]
exponent = parts[3]
unit = parseUUID(parts[4])
name_space = parts[5]
description = parseUUID(parts[6])
size = 2 + 2 + 2 + 2 + 7
write_indent(fout)
fout.write('// 0x%04x CHARACTERISTIC_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2904)
write_sequence(fout, format)
write_sequence(fout, exponent)
write_uuid(unit)
write_sequence(fout, name_space)
write_uuid(description)
fout.write("\n")
handle = handle + 1
def parseCharacteristicAggregateFormat(fout, parts):
global handle
global total_size
read_only_anybody_flags = property_flags['READ'];
size = 2 + 2 + 2 + 2 + (len(parts)-1) * 2
write_indent(fout)
fout.write('// 0x%04x CHARACTERISTIC_AGGREGATE_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2905)
for identifier in parts[1:]:
format_handle = presentation_formats[identifier]
if format == 0:
print("ERROR: identifier '%s' in CHARACTERISTIC_AGGREGATE_FORMAT undefined" % identifier)
sys.exit(1)
write_16(fout, format_handle)
fout.write("\n")
handle = handle + 1
def parseReportReference(fout, parts):
global handle
global total_size
read_only_anybody_flags = property_flags['READ'];
size = 2 + 2 + 2 + 2 + 1 + 1
report_id = parts[2]
report_type = parts[3]
write_indent(fout)
fout.write('// 0x%04x REPORT_REFERENCE-%s\n' % (handle, '-'.join(parts[1:])))
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2908)
write_sequence(fout, report_id)
write_sequence(fout, report_type)
fout.write("\n")
handle = handle + 1
def parseNumberOfDigitals(fout, parts):
global handle
global total_size
read_only_anybody_flags = property_flags['READ'];
size = 2 + 2 + 2 + 2 + 1
no_of_digitals = parts[1]
write_indent(fout)
fout.write('// 0x%04x NUMBER_OF_DIGITALS-%s\n' % (handle, '-'.join(parts[1:])))
write_indent(fout)
write_16(fout, size)
write_16(fout, read_only_anybody_flags)
write_16(fout, handle)
write_16(fout, 0x2909)
write_sequence(fout, no_of_digitals)
fout.write("\n")
handle = handle + 1
def parseLines(fname_in, fin, fout):
global handle
global total_size
line_count = 0;
for line in fin:
line = line.strip("\n\r ")
line_count += 1
if line.startswith("//"):
fout.write(" //" + line.lstrip('/') + '\n')
continue
if line.startswith("#import"):
imported_file = ''
parts = re.match('#import\s+<(.*)>\w*',line)
if parts and len(parts.groups()) == 1:
imported_file = parts.groups()[0]
parts = re.match('#import\s+"(.*)"\w*',line)
if parts and len(parts.groups()) == 1:
imported_file = parts.groups()[0]
if len(imported_file) == 0:
print('ERROR: #import in file %s - line %u neither <name.gatt> nor "name.gatt" form', (fname_in, line_count))
continue
imported_file = getFile( imported_file )
print("Importing %s" % imported_file)
try:
imported_fin = codecs.open (imported_file, encoding='utf-8')
fout.write(' // ' + line + ' -- BEGIN\n')
parseLines(imported_file, imported_fin, fout)
fout.write(' // ' + line + ' -- END\n')
except IOError as e:
print('ERROR: Import failed. Please check path.')
continue
if line.startswith("#TODO"):
print ("WARNING: #TODO in file %s - line %u not handled, skipping declaration:" % (fname_in, line_count))
print ("'%s'" % line)
fout.write("// " + line + '\n')
continue
if len(line) == 0:
continue
f = io.StringIO(line)
parts_list = csv.reader(f, delimiter=',', quotechar='"')
for parts in parts_list:
for index, object in enumerate(parts):
parts[index] = object.strip().lstrip('"').rstrip('"')
if parts[0] == 'PRIMARY_SERVICE':
parsePrimaryService(fout, parts)
continue
if parts[0] == 'SECONDARY_SERVICE':
parseSecondaryService(fout, parts)
continue
if parts[0] == 'INCLUDE_SERVICE':
parseIncludeService(fout, parts)
continue
# 2803
if parts[0] == 'CHARACTERISTIC':
parseCharacteristic(fout, parts)
continue
# 2900 Characteristic Extended Properties
# 2901
if parts[0] == 'CHARACTERISTIC_USER_DESCRIPTION':
parseCharacteristicUserDescription(fout, parts)
continue
# 2902 Client Characteristic Configuration - automatically included in Characteristic if
# notification / indication is supported
if parts[0] == 'CLIENT_CHARACTERISTIC_CONFIGURATION':
continue
# 2903
if parts[0] == 'SERVER_CHARACTERISTIC_CONFIGURATION':
parseServerCharacteristicConfiguration(fout, parts)
continue
# 2904
if parts[0] == 'CHARACTERISTIC_FORMAT':
parseCharacteristicFormat(fout, parts)
continue
# 2905
if parts[0] == 'CHARACTERISTIC_AGGREGATE_FORMAT':
parseCharacteristicAggregateFormat(fout, parts)
continue
# 2906
if parts[0] == 'VALID_RANGE':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 2907
if parts[0] == 'EXTERNAL_REPORT_REFERENCE':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 2908
if parts[0] == 'REPORT_REFERENCE':
parseReportReference(fout, parts)
continue
# 2909
if parts[0] == 'NUMBER_OF_DIGITALS':
parseNumberOfDigitals(fout, parts)
continue
# 290A
if parts[0] == 'VALUE_TRIGGER_SETTING':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 290B
if parts[0] == 'ENVIRONMENTAL_SENSING_CONFIGURATION':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 290C
if parts[0] == 'ENVIRONMENTAL_SENSING_MEASUREMENT':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 290D
if parts[0] == 'ENVIRONMENTAL_SENSING_TRIGGER_SETTING':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
# 2906
if parts[0] == 'VALID_RANGE':
print("WARNING: %s not implemented yet\n" % (parts[0]))
continue
print("WARNING: unknown token: %s\n" % (parts[0]))
def parse(fname_in, fin, fname_out, fout):
global handle
global total_size
fout.write(header.format(fname_out, fname_in))
fout.write('{\n')
write_indent(fout)
fout.write('// ATT DB Version\n')
write_indent(fout)
fout.write('1,\n')
fout.write("\n")
parseLines(fname_in, fin, fout)
serviceDefinitionComplete(fout)
write_indent(fout)
fout.write("// END\n");
write_indent(fout)
write_16(fout,0)
fout.write("\n")
total_size = total_size + 2
fout.write("}; // total size %u bytes \n" % total_size);
def listHandles(fout):
fout.write('\n\n')
fout.write('//\n')
fout.write('// list service handle ranges\n')
fout.write('//\n')
for define in defines_for_services:
fout.write(define)
fout.write('\n')
fout.write('\n')
fout.write('//\n')
fout.write('// list mapping between characteristics and handles\n')
fout.write('//\n')
for define in defines_for_characteristics:
fout.write(define)
fout.write('\n')
def getFile( fileName ):
inc = args.I
for d in inc:
fullFile = d[0] + fileName
print("test %s" % fullFile)
if os.path.isfile( fullFile ) == True:
return fullFile
print ("'{0}' not found".format( fileName ))
print ("Include paths: %s" % ", ".join(inc))
exit(-1)
btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
default_includes = [ btstack_root + '/src/', btstack_root + '/src/ble/gatt-service/']
parser = argparse.ArgumentParser(description='BLE GATT configuration generator for use with BTstack')
parser.add_argument('-I', action='append', nargs=1, metavar='includes',
help='include search path for .gatt service files and bluetooth_gatt.h (default: %s)' % ", ".join(default_includes))
parser.add_argument('gattfile', metavar='gattfile', type=str,
help='gatt file to be compiled')
parser.add_argument('hfile', metavar='hfile', type=str,
help='header file to be generated')
args = parser.parse_args()
# append default include paths
if args.I == None:
args.I = []
for d in default_includes:
args.I.append([d])
try:
# read defines from bluetooth_gatt.h
gen_path = getFile( 'bluetooth_gatt.h' )
bluetooth_gatt = read_defines(gen_path)
filename = args.hfile
fin = codecs.open (args.gattfile, encoding='utf-8')
fout = open (filename, 'w')
parse(args.gattfile, fin, filename, fout)
listHandles(fout)
fout.close()
print('Created %s' % filename)
except IOError as e:
print(usage)
sys.exit(1)
print('Compilation successful!\n')