xref: /btstack/tool/compile_gatt.py (revision eb6072adf6784b3d9056e8dc659130816e7ed950)
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    if uuid_size == 16:
321        properties = properties | property_flags['LONG_UUID'];
322
323    write_indent(fout)
324    fout.write('// 0x%04x VALUE-%s-'"'%s'"'\n' % (handle, '-'.join(parts[1:3]),value))
325    # drop Broadcast, Notify, Indicate - not used for flags
326    value_properties = properties & 0x1ffce
327    write_indent(fout)
328    write_16(fout, size)
329    write_16(fout, value_properties)
330    write_16(fout, handle)
331    write_uuid(uuid)
332    if is_string(value):
333        write_string(fout, value)
334    else:
335        write_sequence(fout,value)
336
337    fout.write("\n")
338    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_VALUE_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
339    handle = handle + 1
340
341    if add_client_characteristic_configuration(properties):
342        # replace GATT Characterstic Properties with READ|WRITE|READ_WITHOUT_AUTHENTICATION|DYNAMIC
343        ccc_properties = (properties & 0x1ff00) | property_flags['READ_WITHOUT_AUTHENTICATION'] | property_flags['READ'] | property_flags['WRITE'] | property_flags['DYNAMIC'];
344        size = 2 + 2 + 2 + 2 + 2
345        write_indent(fout)
346        fout.write('// 0x%04x CLIENT_CHARACTERISTIC_CONFIGURATION\n' % (handle))
347        write_indent(fout)
348        write_16(fout, size)
349        write_16(fout, ccc_properties)
350        write_16(fout, handle)
351        write_16(fout, 0x2902)
352        write_16(fout, 0)
353        fout.write("\n")
354        defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
355        handle = handle + 1
356
357    if properties & property_flags['RELIABLE_WRITE']:
358        size = 2 + 2 + 2 + 2 + 2
359        write_indent(fout)
360        fout.write('// 0x%04x CHARACTERISTIC_EXTENDED_PROPERTIES\n' % (handle))
361        write_indent(fout)
362        write_16(fout, size)
363        write_16(fout, property_flags['READ'])
364        write_16(fout, handle)
365        write_16(fout, 0x2900)
366        write_16(fout, 1)   # Reliable Write
367        fout.write("\n")
368        handle = handle + 1
369
370def parseCharacteristicUserDescription(fout, parts):
371    global handle
372    global total_size
373    global current_characteristic_uuid_string
374
375    properties = parseProperties(parts[1])
376    value      = parts[2]
377
378    size = 2 + 2 + 2 + 2
379    if is_string(value):
380        size = size + len(value) - 2
381    else:
382        size = size + len(value.split())
383
384    write_indent(fout)
385    fout.write('// 0x%04x CHARACTERISTIC_USER_DESCRIPTION-%s\n' % (handle, '-'.join(parts[1:])))
386    write_indent(fout)
387    write_16(fout, size)
388    write_16(fout, properties)
389    write_16(fout, handle)
390    write_16(fout, 0x2901)
391    if is_string(value):
392        write_string(fout, value)
393    else:
394        write_sequence(fout,value)
395    fout.write("\n")
396    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
397    handle = handle + 1
398
399def parseServerCharacteristicConfiguration(fout, parts):
400    global handle
401    global total_size
402    global current_characteristic_uuid_string
403
404    properties = parseProperties(parts[1])
405    properties = properties | property_flags['DYNAMIC']
406    size = 2 + 2 + 2 + 2
407
408    write_indent(fout)
409    fout.write('// 0x%04x SERVER_CHARACTERISTIC_CONFIGURATION-%s\n' % (handle, '-'.join(parts[1:])))
410    write_indent(fout)
411    write_16(fout, size)
412    write_16(fout, properties)
413    write_16(fout, handle)
414    write_16(fout, 0x2903)
415    fout.write("\n")
416    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
417    handle = handle + 1
418
419def parseCharacteristicFormat(fout, parts):
420    global handle
421    global total_size
422
423    property_read = property_flags['READ'];
424
425    identifier = parts[1]
426    presentation_formats[identifier] = handle
427    # print("format '%s' with handle %d\n" % (identifier, handle))
428
429    format     = parts[2]
430    exponent   = parts[3]
431    unit       = parseUUID(parts[4])
432    name_space = parts[5]
433    description = parseUUID(parts[6])
434
435    size = 2 + 2 + 2 + 2 + 7
436
437    write_indent(fout)
438    fout.write('// 0x%04x CHARACTERISTIC_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
439    write_indent(fout)
440    write_16(fout, size)
441    write_16(fout, property_read)
442    write_16(fout, handle)
443    write_16(fout, 0x2904)
444    write_sequence(fout, format)
445    write_sequence(fout, exponent)
446    write_uuid(unit)
447    write_sequence(fout, name_space)
448    write_uuid(description)
449    fout.write("\n")
450    handle = handle + 1
451
452
453def parseCharacteristicAggregateFormat(fout, parts):
454    global handle
455    global total_size
456
457    property_read = property_flags['READ'];
458    size = 2 + 2 + 2 + 2 + (len(parts)-1) * 2
459
460    write_indent(fout)
461    fout.write('// 0x%04x CHARACTERISTIC_AGGREGATE_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
462    write_indent(fout)
463    write_16(fout, size)
464    write_16(fout, property_read)
465    write_16(fout, handle)
466    write_16(fout, 0x2905)
467    for identifier in parts[1:]:
468        format_handle = presentation_formats[identifier]
469        if format == 0:
470            print("ERROR: identifier '%s' in CHARACTERISTIC_AGGREGATE_FORMAT undefined" % identifier)
471            sys.exit(1)
472        write_16(fout, format_handle)
473    fout.write("\n")
474    handle = handle + 1
475
476def parseReportReference(fout, parts):
477    global handle
478    global total_size
479
480    property_read = property_flags['READ'];
481    size = 2 + 2 + 2 + 2 + 1 + 1
482
483    properties = parseProperties(parts[1])
484
485    report_id = parts[2]
486    report_type = parts[3]
487
488    write_indent(fout)
489    fout.write('// 0x%04x REPORT_REFERENCE-%s\n' % (handle, '-'.join(parts[1:])))
490    write_indent(fout)
491    write_16(fout, size)
492    write_16(fout, property_read)
493    write_16(fout, handle)
494    write_16(fout, 0x2908)
495    write_sequence(fout, report_id)
496    write_sequence(fout, report_type)
497    fout.write("\n")
498    handle = handle + 1
499
500
501def parseNumberOfDigitals(fout, parts):
502    global handle
503    global total_size
504
505    property_read = property_flags['READ'];
506    size = 2 + 2 + 2 + 2 + 1
507
508    no_of_digitals = parts[1]
509
510    write_indent(fout)
511    fout.write('// 0x%04x NUMBER_OF_DIGITALS-%s\n' % (handle, '-'.join(parts[1:])))
512    write_indent(fout)
513    write_16(fout, size)
514    write_16(fout, property_read)
515    write_16(fout, handle)
516    write_16(fout, 0x2909)
517    write_sequence(fout, no_of_digitals)
518    fout.write("\n")
519    handle = handle + 1
520
521def parseLines(fname_in, fin, fout):
522    global handle
523    global total_size
524
525    line_count = 0;
526    for line in fin:
527        line = line.strip("\n\r ")
528        line_count += 1
529
530        if line.startswith("//"):
531            fout.write("    //" + line.lstrip('/') + '\n')
532            continue
533
534        if line.startswith("#import"):
535            imported_file = ''
536            parts = re.match('#import\s+<(.*)>\w*',line)
537            if parts and len(parts.groups()) == 1:
538                imported_file = btstack_root+'/src/ble/gatt-service/' + parts.groups()[0]
539            parts = re.match('#import\s+"(.*)"\w*',line)
540            if parts and len(parts.groups()) == 1:
541                imported_file = os.path.abspath(os.path.dirname(fname_in) + '/'+parts.groups()[0])
542            if len(imported_file) == 0:
543                print('ERROR: #import in file %s - line %u neither <name.gatt> nor "name.gatt" form', (fname_in, line_count))
544                continue
545
546            print("Importing %s" % imported_file)
547            try:
548                imported_fin = codecs.open (imported_file, encoding='utf-8')
549                fout.write('    // ' + line + ' -- BEGIN\n')
550                parseLines(imported_file, imported_fin, fout)
551                fout.write('    // ' + line + ' -- END\n')
552            except IOError as e:
553                print('ERROR: Import failed. Please check path.')
554
555            continue
556
557        if line.startswith("#TODO"):
558            print ("WARNING: #TODO in file %s - line %u not handled, skipping declaration:" % (fname_in, line_count))
559            print ("'%s'" % line)
560            fout.write("// " + line + '\n')
561            continue
562
563        if len(line) == 0:
564            continue
565
566        f = io.StringIO(line)
567        parts_list = csv.reader(f, delimiter=',', quotechar='"')
568
569        for parts in parts_list:
570            for index, object in enumerate(parts):
571                parts[index] = object.strip().lstrip('"').rstrip('"')
572
573            if parts[0] == 'PRIMARY_SERVICE':
574                parsePrimaryService(fout, parts)
575                continue
576
577            if parts[0] == 'SECONDARY_SERVICE':
578                parseSecondaryService(fout, parts)
579                continue
580
581            if parts[0] == 'INCLUDE_SERVICE':
582                parseIncludeService(fout, parts)
583                continue
584
585            # 2803
586            if parts[0] == 'CHARACTERISTIC':
587                parseCharacteristic(fout, parts)
588                continue
589
590            # 2900 Characteristic Extended Properties
591
592            # 2901
593            if parts[0] == 'CHARACTERISTIC_USER_DESCRIPTION':
594                parseCharacteristicUserDescription(fout, parts)
595                continue
596
597
598            # 2902 Client Characteristic Configuration - automatically included in Characteristic if
599            # notification / indication is supported
600            if parts[0] == 'CLIENT_CHARACTERISTIC_CONFIGURATION':
601                continue
602
603            # 2903
604            if parts[0] == 'SERVER_CHARACTERISTIC_CONFIGURATION':
605                parseServerCharacteristicConfiguration(fout, parts)
606                continue
607
608            # 2904
609            if parts[0] == 'CHARACTERISTIC_FORMAT':
610                parseCharacteristicFormat(fout, parts)
611                continue
612
613            # 2905
614            if parts[0] == 'CHARACTERISTIC_AGGREGATE_FORMAT':
615                parseCharacteristicAggregateFormat(fout, parts)
616                continue
617
618            # 2906
619            if parts[0] == 'VALID_RANGE':
620                print("WARNING: %s not implemented yet\n" % (parts[0]))
621                continue
622
623            # 2907
624            if parts[0] == 'EXTERNAL_REPORT_REFERENCE':
625                print("WARNING: %s not implemented yet\n" % (parts[0]))
626                continue
627
628            # 2908
629            if parts[0] == 'REPORT_REFERENCE':
630                parseReportReference(fout, parts)
631                continue
632
633            # 2909
634            if parts[0] == 'NUMBER_OF_DIGITALS':
635                parseNumberOfDigitals(fout, parts)
636                continue
637
638            # 290A
639            if parts[0] == 'VALUE_TRIGGER_SETTING':
640                print("WARNING: %s not implemented yet\n" % (parts[0]))
641                continue
642
643            # 290B
644            if parts[0] == 'ENVIRONMENTAL_SENSING_CONFIGURATION':
645                print("WARNING: %s not implemented yet\n" % (parts[0]))
646                continue
647
648            # 290C
649            if parts[0] == 'ENVIRONMENTAL_SENSING_MEASUREMENT':
650                print("WARNING: %s not implemented yet\n" % (parts[0]))
651                continue
652
653            # 290D
654            if parts[0] == 'ENVIRONMENTAL_SENSING_TRIGGER_SETTING':
655                print("WARNING: %s not implemented yet\n" % (parts[0]))
656                continue
657
658            # 2906
659            if parts[0] == 'VALID_RANGE':
660                print("WARNING: %s not implemented yet\n" % (parts[0]))
661                continue
662
663            print("WARNING: unknown token: %s\n" % (parts[0]))
664
665def parse(fname_in, fin, fname_out, fout):
666    global handle
667    global total_size
668
669    fout.write(header.format(fname_out, fname_in))
670    fout.write('{\n')
671
672    parseLines(fname_in, fin, fout)
673
674    serviceDefinitionComplete(fout)
675    write_indent(fout)
676    fout.write("// END\n");
677    write_indent(fout)
678    write_16(fout,0)
679    fout.write("\n")
680    total_size = total_size + 2
681
682    fout.write("}; // total size %u bytes \n" % total_size);
683
684def listHandles(fout):
685    fout.write('\n\n')
686    fout.write('//\n')
687    fout.write('// list service handle ranges\n')
688    fout.write('//\n')
689    for define in defines_for_services:
690        fout.write(define)
691        fout.write('\n')
692    fout.write('\n')
693    fout.write('//\n')
694    fout.write('// list mapping between characteristics and handles\n')
695    fout.write('//\n')
696    for define in defines_for_characteristics:
697        fout.write(define)
698        fout.write('\n')
699
700if (len(sys.argv) < 3):
701    print(usage)
702    sys.exit(1)
703try:
704    # read defines from bluetooth_gatt.h
705    btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
706    gen_path = btstack_root + '/src/bluetooth_gatt.h'
707    bluetooth_gatt = read_defines(gen_path)
708
709    filename = sys.argv[2]
710    fin  = codecs.open (sys.argv[1], encoding='utf-8')
711    fout = open (filename, 'w')
712    parse(sys.argv[1], fin, filename, fout)
713    listHandles(fout)
714    fout.close()
715    print('Created %s' % filename)
716
717except IOError as e:
718    print(usage)
719    sys.exit(1)
720
721print('Compilation successful!\n')
722