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