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