xref: /btstack/tool/compile_gatt.py (revision 7dc86dfd3569d69491d87d64749fd45afb46c67a)
1#!/usr/bin/env python
2#
3# BLE GATT configuration generator for use with BTstack, v0.1
4# Copyright 2011 Matthias Ringwald
5#
6# Format of input file:
7# PRIMARY_SERVICE, SERVICE_UUID
8# CHARACTERISTIC, ATTRIBUTE_TYPE_UUID, [READ | WRITE | DYNAMIC], VALUE
9
10import codecs
11import csv
12import io
13import os
14import re
15import string
16import sys
17
18header = '''
19// {0} generated from {1} for BTstack
20
21// binary representation
22// attribute size in bytes (16), flags(16), handle (16), uuid (16/128), value(...)
23
24#include <stdint.h>
25
26const uint8_t profile_data[] =
27'''
28
29usage = '''
30Usage: ./compile_gatt.py profile.gatt profile.h
31'''
32
33
34print('''
35BLE configuration generator for use with BTstack, v0.1
36Copyright 2011 Matthias Ringwald
37''')
38
39assigned_uuids = {
40    'GAP_SERVICE'          : 0x1800,
41    'GATT_SERVICE'         : 0x1801,
42    'GAP_DEVICE_NAME'      : 0x2a00,
43    'GAP_APPEARANCE'       : 0x2a01,
44    'GAP_PERIPHERAL_PRIVACY_FLAG' : 0x2A02,
45    'GAP_RECONNECTION_ADDRESS'    : 0x2A03,
46    'GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS' : 0x2A04,
47    'GATT_SERVICE_CHANGED' : 0x2a05,
48}
49
50property_flags = {
51    # GATT Characteristic Properties
52    'BROADCAST' :                   0x01,
53    'READ' :                        0x02,
54    'WRITE_WITHOUT_RESPONSE' :      0x04,
55    'WRITE' :                       0x08,
56    'NOTIFY':                       0x10,
57    'INDICATE' :                    0x20,
58    'AUTHENTICATED_SIGNED_WRITE' :  0x40,
59    'EXTENDED_PROPERTIES' :         0x80,
60    # custom BTstack extension
61    'DYNAMIC':                      0x100,
62    'LONG_UUID':                    0x200,
63    'AUTHENTICATION_REQUIRED':      0x400,
64    'AUTHORIZATION_REQUIRED':       0x800,
65    'ENCRYPTION_KEY_SIZE_7':       0x6000,
66    'ENCRYPTION_KEY_SIZE_8':       0x7000,
67    'ENCRYPTION_KEY_SIZE_9':       0x8000,
68    'ENCRYPTION_KEY_SIZE_10':      0x9000,
69    'ENCRYPTION_KEY_SIZE_11':      0xa000,
70    'ENCRYPTION_KEY_SIZE_12':      0xb000,
71    'ENCRYPTION_KEY_SIZE_13':      0xc000,
72    'ENCRYPTION_KEY_SIZE_14':      0xd000,
73    'ENCRYPTION_KEY_SIZE_15':      0xe000,
74    'ENCRYPTION_KEY_SIZE_16':      0xf000,
75
76    # only used by gatt compiler >= 0xffff
77    # Extended Properties
78    'RELIABLE_WRITE':              0x10000,
79
80    # Broadcast, Notify, Indicate, Extended Properties are only used to describe a GATT Characteristic, but are free to use with att_db
81    'READ_WITHOUT_AUTHENTICATION': 0x0001,
82    'PERSISTENT_WRITE_CCC':        0x0010,
83    # 0x10
84    # 0x20
85    # 0x80
86}
87
88btstack_root = ''
89services = dict()
90characteristic_indices = dict()
91presentation_formats = dict()
92current_service_uuid_string = ""
93current_service_start_handle = 0
94current_characteristic_uuid_string = ""
95defines_for_characteristics = []
96defines_for_services = []
97
98handle = 1
99total_size = 0
100
101def read_defines(infile):
102    defines = dict()
103    with open (infile, 'rt') as fin:
104        for line in fin:
105            parts = re.match('#define\s+(\w+)\s+(\w+)',line)
106            if parts and len(parts.groups()) == 2:
107                (key, value) = parts.groups()
108                defines[key] = int(value, 16)
109    return defines
110
111def keyForUUID(uuid):
112    keyUUID = ""
113    for i in uuid:
114        keyUUID += "%02x" % i
115    return keyUUID
116
117def c_string_for_uuid(uuid):
118    return uuid.replace('-', '_')
119
120def twoByteLEFor(value):
121    return [ (value & 0xff), (value >> 8)]
122
123def is_128bit_uuid(text):
124    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):
125        return True
126    return False
127
128def parseUUID128(uuid):
129    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)
130    uuid_bytes = []
131    for i in range(8, 0, -1):
132        uuid_bytes = uuid_bytes + twoByteLEFor(int(parts.group(i),16))
133    return uuid_bytes
134
135def parseUUID(uuid):
136    if uuid in assigned_uuids:
137        return twoByteLEFor(assigned_uuids[uuid])
138    uuid_upper = uuid.upper().replace('.','_')
139    if uuid_upper in bluetooth_gatt:
140        return twoByteLEFor(bluetooth_gatt[uuid_upper])
141    if is_128bit_uuid(uuid):
142        return parseUUID128(uuid)
143    uuidInt = int(uuid, 16)
144    return twoByteLEFor(uuidInt)
145
146def parseProperties(properties):
147    value = 0
148    parts = properties.split("|")
149    for property in parts:
150        property = property.strip()
151        if property in property_flags:
152            value |= property_flags[property]
153        else:
154            print("WARNING: property %s undefined" % (property))
155    return value
156
157def write_8(fout, value):
158    fout.write( "0x%02x, " % (value & 0xff))
159
160def write_16(fout, value):
161    fout.write('0x%02x, 0x%02x, ' % (value & 0xff, (value >> 8) & 0xff))
162
163def write_uuid(uuid):
164    for byte in uuid:
165        fout.write( "0x%02x, " % byte)
166
167def write_string(fout, text):
168    for l in text.lstrip('"').rstrip('"'):
169        write_8(fout, ord(l))
170
171def write_sequence(fout, text):
172    parts = text.split()
173    for part in parts:
174        fout.write("0x%s, " % (part.strip()))
175
176def write_indent(fout):
177    fout.write("    ")
178
179def is_string(text):
180    for item in text.split(" "):
181        if not all(c in string.hexdigits for c in item):
182            return True
183    return False
184
185def add_client_characteristic_configuration(properties):
186    return properties & (property_flags['NOTIFY'] | property_flags['INDICATE'])
187
188def serviceDefinitionComplete(fout):
189    global services
190    if current_service_uuid_string:
191        fout.write("\n")
192        # print("append service %s = [%d, %d]" % (current_characteristic_uuid_string, current_service_start_handle, handle-1))
193        defines_for_services.append('#define ATT_SERVICE_%s_START_HANDLE 0x%04x' % (current_service_uuid_string, current_service_start_handle))
194        defines_for_services.append('#define ATT_SERVICE_%s_END_HANDLE 0x%04x' % (current_service_uuid_string, handle-1))
195        services[current_service_uuid_string] = [current_service_start_handle, handle-1]
196
197def parseService(fout, parts, service_type):
198    global handle
199    global total_size
200    global current_service_uuid_string
201    global current_service_start_handle
202
203    serviceDefinitionComplete(fout)
204
205    property = property_flags['READ'];
206
207    write_indent(fout)
208    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
209
210    uuid = parseUUID(parts[1])
211    uuid_size = len(uuid)
212
213    size = 2 + 2 + 2 + uuid_size + 2
214
215    if service_type == 0x2802:
216        size += 4
217
218    write_indent(fout)
219    write_16(fout, size)
220    write_16(fout, property)
221    write_16(fout, handle)
222    write_16(fout, service_type)
223    write_uuid(uuid)
224    fout.write("\n")
225
226    current_service_uuid_string = c_string_for_uuid(parts[1])
227    current_service_start_handle = handle
228    handle = handle + 1
229    total_size = total_size + size
230
231def parsePrimaryService(fout, parts):
232    parseService(fout, parts, 0x2800)
233
234def parseSecondaryService(fout, parts):
235    parseService(fout, parts, 0x2801)
236
237def parseIncludeService(fout, parts):
238    global handle
239    global total_size
240
241    property = property_flags['READ'];
242
243    write_indent(fout)
244    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
245
246    uuid = parseUUID(parts[1])
247    uuid_size = len(uuid)
248    if uuid_size > 2:
249        uuid_size = 0
250    # print("Include Service ", c_string_for_uuid(uuid))
251
252    size = 2 + 2 + 2 + 2 + 4 + uuid_size
253
254    keyUUID = c_string_for_uuid(parts[1])
255
256    write_indent(fout)
257    write_16(fout, size)
258    write_16(fout, property)
259    write_16(fout, handle)
260    write_16(fout, 0x2802)
261    write_16(fout, services[keyUUID][0])
262    write_16(fout, services[keyUUID][1])
263    if uuid_size > 0:
264        write_uuid(uuid)
265    fout.write("\n")
266
267    handle = handle + 1
268    total_size = total_size + size
269
270
271def parseCharacteristic(fout, parts):
272    global handle
273    global total_size
274    global current_characteristic_uuid_string
275    global characteristic_indices
276
277    property_read = property_flags['READ'];
278
279    # enumerate characteristics with same UUID, using optional name tag if available
280    current_characteristic_uuid_string = c_string_for_uuid(parts[1]);
281    index = 1
282    if current_characteristic_uuid_string in characteristic_indices:
283        index = characteristic_indices[current_characteristic_uuid_string] + 1
284    characteristic_indices[current_characteristic_uuid_string] = index
285    if len(parts) > 4:
286        current_characteristic_uuid_string += '_' + parts[4].upper().replace(' ','_')
287    else:
288        current_characteristic_uuid_string += ('_%02x' % index)
289
290    uuid       = parseUUID(parts[1])
291    uuid_size  = len(uuid)
292    properties = parseProperties(parts[2])
293    value = ', '.join([str(x) for x in parts[3:]])
294
295    # reliable writes is defined in an extended properties
296    if (properties & property_flags['RELIABLE_WRITE']):
297        properties = properties | property_flags['EXTENDED_PROPERTIES']
298
299    write_indent(fout)
300    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts[0:3])))
301
302    size = 2 + 2 + 2 + 2 + (1+2+uuid_size)
303    write_indent(fout)
304    write_16(fout, size)
305    write_16(fout, property_read)
306    write_16(fout, handle)
307    write_16(fout, 0x2803)
308    write_8(fout, properties)
309    write_16(fout, handle+1)
310    write_uuid(uuid)
311    fout.write("\n")
312    handle = handle + 1
313    total_size = total_size + size
314
315    size = 2 + 2 + 2 + uuid_size
316    if is_string(value):
317        size = size + len(value)
318    else:
319        size = size + len(value.split())
320
321    # drop Broadcast (0x01), Notify (0x10), Indicate (0x20)- not used for flags
322    #
323    value_properties = properties & 0x1ffce
324
325    # add UUID128 flag for value handle
326    if uuid_size == 16:
327        value_properties = value_properties | property_flags['LONG_UUID'];
328
329    write_indent(fout)
330    fout.write('// 0x%04x VALUE-%s-'"'%s'"'\n' % (handle, '-'.join(parts[1:3]),value))
331    write_indent(fout)
332    write_16(fout, size)
333    write_16(fout, value_properties)
334    write_16(fout, handle)
335    write_uuid(uuid)
336    if is_string(value):
337        write_string(fout, value)
338    else:
339        write_sequence(fout,value)
340
341    fout.write("\n")
342    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_VALUE_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
343    handle = handle + 1
344
345    if add_client_characteristic_configuration(properties):
346        # replace GATT Characterstic Properties with READ|WRITE|READ_WITHOUT_AUTHENTICATION|DYNAMIC
347        ccc_properties = (properties & 0x1fc00) |           \
348            property_flags['READ_WITHOUT_AUTHENTICATION'] | \
349            property_flags['READ'] |                        \
350            property_flags['WRITE'] |                       \
351            property_flags['DYNAMIC'] |                     \
352            property_flags['PERSISTENT_WRITE_CCC'];
353        size = 2 + 2 + 2 + 2 + 2
354        write_indent(fout)
355        fout.write('// 0x%04x CLIENT_CHARACTERISTIC_CONFIGURATION\n' % (handle))
356        write_indent(fout)
357        write_16(fout, size)
358        write_16(fout, ccc_properties)
359        write_16(fout, handle)
360        write_16(fout, 0x2902)
361        write_16(fout, 0)
362        fout.write("\n")
363        defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
364        handle = handle + 1
365
366    if properties & property_flags['RELIABLE_WRITE']:
367        size = 2 + 2 + 2 + 2 + 2
368        write_indent(fout)
369        fout.write('// 0x%04x CHARACTERISTIC_EXTENDED_PROPERTIES\n' % (handle))
370        write_indent(fout)
371        write_16(fout, size)
372        write_16(fout, property_flags['READ'])
373        write_16(fout, handle)
374        write_16(fout, 0x2900)
375        write_16(fout, 1)   # Reliable Write
376        fout.write("\n")
377        handle = handle + 1
378
379def parseCharacteristicUserDescription(fout, parts):
380    global handle
381    global total_size
382    global current_characteristic_uuid_string
383
384    properties = parseProperties(parts[1])
385    value      = parts[2]
386
387    size = 2 + 2 + 2 + 2
388    if is_string(value):
389        size = size + len(value) - 2
390    else:
391        size = size + len(value.split())
392
393    write_indent(fout)
394    fout.write('// 0x%04x CHARACTERISTIC_USER_DESCRIPTION-%s\n' % (handle, '-'.join(parts[1:])))
395    write_indent(fout)
396    write_16(fout, size)
397    write_16(fout, properties)
398    write_16(fout, handle)
399    write_16(fout, 0x2901)
400    if is_string(value):
401        write_string(fout, value)
402    else:
403        write_sequence(fout,value)
404    fout.write("\n")
405    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
406    handle = handle + 1
407
408def parseServerCharacteristicConfiguration(fout, parts):
409    global handle
410    global total_size
411    global current_characteristic_uuid_string
412
413    properties = parseProperties(parts[1])
414    properties = properties | property_flags['DYNAMIC']
415    size = 2 + 2 + 2 + 2
416
417    write_indent(fout)
418    fout.write('// 0x%04x SERVER_CHARACTERISTIC_CONFIGURATION-%s\n' % (handle, '-'.join(parts[1:])))
419    write_indent(fout)
420    write_16(fout, size)
421    write_16(fout, properties)
422    write_16(fout, handle)
423    write_16(fout, 0x2903)
424    fout.write("\n")
425    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
426    handle = handle + 1
427
428def parseCharacteristicFormat(fout, parts):
429    global handle
430    global total_size
431
432    property_read = property_flags['READ'];
433
434    identifier = parts[1]
435    presentation_formats[identifier] = handle
436    # print("format '%s' with handle %d\n" % (identifier, handle))
437
438    format     = parts[2]
439    exponent   = parts[3]
440    unit       = parseUUID(parts[4])
441    name_space = parts[5]
442    description = parseUUID(parts[6])
443
444    size = 2 + 2 + 2 + 2 + 7
445
446    write_indent(fout)
447    fout.write('// 0x%04x CHARACTERISTIC_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
448    write_indent(fout)
449    write_16(fout, size)
450    write_16(fout, property_read)
451    write_16(fout, handle)
452    write_16(fout, 0x2904)
453    write_sequence(fout, format)
454    write_sequence(fout, exponent)
455    write_uuid(unit)
456    write_sequence(fout, name_space)
457    write_uuid(description)
458    fout.write("\n")
459    handle = handle + 1
460
461
462def parseCharacteristicAggregateFormat(fout, parts):
463    global handle
464    global total_size
465
466    property_read = property_flags['READ'];
467    size = 2 + 2 + 2 + 2 + (len(parts)-1) * 2
468
469    write_indent(fout)
470    fout.write('// 0x%04x CHARACTERISTIC_AGGREGATE_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
471    write_indent(fout)
472    write_16(fout, size)
473    write_16(fout, property_read)
474    write_16(fout, handle)
475    write_16(fout, 0x2905)
476    for identifier in parts[1:]:
477        format_handle = presentation_formats[identifier]
478        if format == 0:
479            print("ERROR: identifier '%s' in CHARACTERISTIC_AGGREGATE_FORMAT undefined" % identifier)
480            sys.exit(1)
481        write_16(fout, format_handle)
482    fout.write("\n")
483    handle = handle + 1
484
485def parseReportReference(fout, parts):
486    global handle
487    global total_size
488
489    property_read = property_flags['READ'];
490    size = 2 + 2 + 2 + 2 + 1 + 1
491
492    properties = parseProperties(parts[1])
493
494    report_id = parts[2]
495    report_type = parts[3]
496
497    write_indent(fout)
498    fout.write('// 0x%04x REPORT_REFERENCE-%s\n' % (handle, '-'.join(parts[1:])))
499    write_indent(fout)
500    write_16(fout, size)
501    write_16(fout, property_read)
502    write_16(fout, handle)
503    write_16(fout, 0x2908)
504    write_sequence(fout, report_id)
505    write_sequence(fout, report_type)
506    fout.write("\n")
507    handle = handle + 1
508
509
510def parseNumberOfDigitals(fout, parts):
511    global handle
512    global total_size
513
514    property_read = property_flags['READ'];
515    size = 2 + 2 + 2 + 2 + 1
516
517    no_of_digitals = parts[1]
518
519    write_indent(fout)
520    fout.write('// 0x%04x NUMBER_OF_DIGITALS-%s\n' % (handle, '-'.join(parts[1:])))
521    write_indent(fout)
522    write_16(fout, size)
523    write_16(fout, property_read)
524    write_16(fout, handle)
525    write_16(fout, 0x2909)
526    write_sequence(fout, no_of_digitals)
527    fout.write("\n")
528    handle = handle + 1
529
530def parseLines(fname_in, fin, fout):
531    global handle
532    global total_size
533
534    line_count = 0;
535    for line in fin:
536        line = line.strip("\n\r ")
537        line_count += 1
538
539        if line.startswith("//"):
540            fout.write("    //" + line.lstrip('/') + '\n')
541            continue
542
543        if line.startswith("#import"):
544            imported_file = ''
545            parts = re.match('#import\s+<(.*)>\w*',line)
546            if parts and len(parts.groups()) == 1:
547                imported_file = btstack_root+'/src/ble/gatt-service/' + parts.groups()[0]
548            parts = re.match('#import\s+"(.*)"\w*',line)
549            if parts and len(parts.groups()) == 1:
550                imported_file = os.path.abspath(os.path.dirname(fname_in) + '/'+parts.groups()[0])
551            if len(imported_file) == 0:
552                print('ERROR: #import in file %s - line %u neither <name.gatt> nor "name.gatt" form', (fname_in, line_count))
553                continue
554
555            print("Importing %s" % imported_file)
556            try:
557                imported_fin = codecs.open (imported_file, encoding='utf-8')
558                fout.write('    // ' + line + ' -- BEGIN\n')
559                parseLines(imported_file, imported_fin, fout)
560                fout.write('    // ' + line + ' -- END\n')
561            except IOError as e:
562                print('ERROR: Import failed. Please check path.')
563
564            continue
565
566        if line.startswith("#TODO"):
567            print ("WARNING: #TODO in file %s - line %u not handled, skipping declaration:" % (fname_in, line_count))
568            print ("'%s'" % line)
569            fout.write("// " + line + '\n')
570            continue
571
572        if len(line) == 0:
573            continue
574
575        f = io.StringIO(line)
576        parts_list = csv.reader(f, delimiter=',', quotechar='"')
577
578        for parts in parts_list:
579            for index, object in enumerate(parts):
580                parts[index] = object.strip().lstrip('"').rstrip('"')
581
582            if parts[0] == 'PRIMARY_SERVICE':
583                parsePrimaryService(fout, parts)
584                continue
585
586            if parts[0] == 'SECONDARY_SERVICE':
587                parseSecondaryService(fout, parts)
588                continue
589
590            if parts[0] == 'INCLUDE_SERVICE':
591                parseIncludeService(fout, parts)
592                continue
593
594            # 2803
595            if parts[0] == 'CHARACTERISTIC':
596                parseCharacteristic(fout, parts)
597                continue
598
599            # 2900 Characteristic Extended Properties
600
601            # 2901
602            if parts[0] == 'CHARACTERISTIC_USER_DESCRIPTION':
603                parseCharacteristicUserDescription(fout, parts)
604                continue
605
606
607            # 2902 Client Characteristic Configuration - automatically included in Characteristic if
608            # notification / indication is supported
609            if parts[0] == 'CLIENT_CHARACTERISTIC_CONFIGURATION':
610                continue
611
612            # 2903
613            if parts[0] == 'SERVER_CHARACTERISTIC_CONFIGURATION':
614                parseServerCharacteristicConfiguration(fout, parts)
615                continue
616
617            # 2904
618            if parts[0] == 'CHARACTERISTIC_FORMAT':
619                parseCharacteristicFormat(fout, parts)
620                continue
621
622            # 2905
623            if parts[0] == 'CHARACTERISTIC_AGGREGATE_FORMAT':
624                parseCharacteristicAggregateFormat(fout, parts)
625                continue
626
627            # 2906
628            if parts[0] == 'VALID_RANGE':
629                print("WARNING: %s not implemented yet\n" % (parts[0]))
630                continue
631
632            # 2907
633            if parts[0] == 'EXTERNAL_REPORT_REFERENCE':
634                print("WARNING: %s not implemented yet\n" % (parts[0]))
635                continue
636
637            # 2908
638            if parts[0] == 'REPORT_REFERENCE':
639                parseReportReference(fout, parts)
640                continue
641
642            # 2909
643            if parts[0] == 'NUMBER_OF_DIGITALS':
644                parseNumberOfDigitals(fout, parts)
645                continue
646
647            # 290A
648            if parts[0] == 'VALUE_TRIGGER_SETTING':
649                print("WARNING: %s not implemented yet\n" % (parts[0]))
650                continue
651
652            # 290B
653            if parts[0] == 'ENVIRONMENTAL_SENSING_CONFIGURATION':
654                print("WARNING: %s not implemented yet\n" % (parts[0]))
655                continue
656
657            # 290C
658            if parts[0] == 'ENVIRONMENTAL_SENSING_MEASUREMENT':
659                print("WARNING: %s not implemented yet\n" % (parts[0]))
660                continue
661
662            # 290D
663            if parts[0] == 'ENVIRONMENTAL_SENSING_TRIGGER_SETTING':
664                print("WARNING: %s not implemented yet\n" % (parts[0]))
665                continue
666
667            # 2906
668            if parts[0] == 'VALID_RANGE':
669                print("WARNING: %s not implemented yet\n" % (parts[0]))
670                continue
671
672            print("WARNING: unknown token: %s\n" % (parts[0]))
673
674def parse(fname_in, fin, fname_out, fout):
675    global handle
676    global total_size
677
678    fout.write(header.format(fname_out, fname_in))
679    fout.write('{\n')
680
681    parseLines(fname_in, fin, fout)
682
683    serviceDefinitionComplete(fout)
684    write_indent(fout)
685    fout.write("// END\n");
686    write_indent(fout)
687    write_16(fout,0)
688    fout.write("\n")
689    total_size = total_size + 2
690
691    fout.write("}; // total size %u bytes \n" % total_size);
692
693def listHandles(fout):
694    fout.write('\n\n')
695    fout.write('//\n')
696    fout.write('// list service handle ranges\n')
697    fout.write('//\n')
698    for define in defines_for_services:
699        fout.write(define)
700        fout.write('\n')
701    fout.write('\n')
702    fout.write('//\n')
703    fout.write('// list mapping between characteristics and handles\n')
704    fout.write('//\n')
705    for define in defines_for_characteristics:
706        fout.write(define)
707        fout.write('\n')
708
709if (len(sys.argv) < 3):
710    print(usage)
711    sys.exit(1)
712try:
713    # read defines from bluetooth_gatt.h
714    btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
715    gen_path = btstack_root + '/src/bluetooth_gatt.h'
716    bluetooth_gatt = read_defines(gen_path)
717
718    filename = sys.argv[2]
719    fin  = codecs.open (sys.argv[1], encoding='utf-8')
720    fout = open (filename, 'w')
721    parse(sys.argv[1], fin, filename, fout)
722    listHandles(fout)
723    fout.close()
724    print('Created %s' % filename)
725
726except IOError as e:
727    print(usage)
728    sys.exit(1)
729
730print('Compilation successful!\n')
731