xref: /btstack/tool/compile_gatt.py (revision d7ec1d24bc1e3be5bf8c482d95765d3ecaaa7bda)
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
50security_permsission = ['ANYBODY','ENCRYPTED', 'AUTHENTICATED', 'AUTHORIZED']
51
52property_flags = {
53    # GATT Characteristic Properties
54    'BROADCAST' :                   0x01,
55    'READ' :                        0x02,
56    'WRITE_WITHOUT_RESPONSE' :      0x04,
57    'WRITE' :                       0x08,
58    'NOTIFY':                       0x10,
59    'INDICATE' :                    0x20,
60    'AUTHENTICATED_SIGNED_WRITE' :  0x40,
61    'EXTENDED_PROPERTIES' :         0x80,
62    # custom BTstack extension
63    'DYNAMIC':                      0x100,
64    'LONG_UUID':                    0x200,
65
66    # read permissions
67    'READ_PERMISSION_BIT_0':        0x400,
68    'READ_PERMISSION_BIT_1':        0x800,
69
70    #
71    'ENCRYPTION_KEY_SIZE_7':       0x6000,
72    'ENCRYPTION_KEY_SIZE_8':       0x7000,
73    'ENCRYPTION_KEY_SIZE_9':       0x8000,
74    'ENCRYPTION_KEY_SIZE_10':      0x9000,
75    'ENCRYPTION_KEY_SIZE_11':      0xa000,
76    'ENCRYPTION_KEY_SIZE_12':      0xb000,
77    'ENCRYPTION_KEY_SIZE_13':      0xc000,
78    'ENCRYPTION_KEY_SIZE_14':      0xd000,
79    'ENCRYPTION_KEY_SIZE_15':      0xe000,
80    'ENCRYPTION_KEY_SIZE_16':      0xf000,
81    'ENCRYPTION_KEY_SIZE_MASK':    0xf000,
82
83    # only used by gatt compiler >= 0xffff
84    # Extended Properties
85    'RELIABLE_WRITE':              0x0010000,
86    'AUTHENTICATION_REQUIRED':     0x0020000,
87    'AUTHORIZATION_REQUIRED':      0x0040000,
88    'READ_ANYBODY':                0x0080000,
89    'READ_ENCRYPTED':              0x0100000,
90    'READ_AUTHENTICATED':          0x0200000,
91    'READ_AUTHORIZED':             0x0400000,
92    'WRITE_ANYBODY':               0x0800000,
93    'WRITE_ENCRYPTED':             0x1000000,
94    'WRITE_AUTHENTICATED':         0x2000000,
95    'WRITE_AUTHORIZED':            0x4000000,
96
97    # Broadcast, Notify, Indicate, Extended Properties are only used to describe a GATT Characteristic, but are free to use with att_db
98
99    # write permissions
100    'WRITE_PERMISSION_BIT_0':      0x01,
101    'WRITE_PERMISSION_BIT_1':      0x10,
102    # 0x20
103    # 0x80
104}
105
106btstack_root = ''
107services = dict()
108characteristic_indices = dict()
109presentation_formats = dict()
110current_service_uuid_string = ""
111current_service_start_handle = 0
112current_characteristic_uuid_string = ""
113defines_for_characteristics = []
114defines_for_services = []
115
116handle = 1
117total_size = 0
118
119def read_defines(infile):
120    defines = dict()
121    with open (infile, 'rt') as fin:
122        for line in fin:
123            parts = re.match('#define\s+(\w+)\s+(\w+)',line)
124            if parts and len(parts.groups()) == 2:
125                (key, value) = parts.groups()
126                defines[key] = int(value, 16)
127    return defines
128
129def keyForUUID(uuid):
130    keyUUID = ""
131    for i in uuid:
132        keyUUID += "%02x" % i
133    return keyUUID
134
135def c_string_for_uuid(uuid):
136    return uuid.replace('-', '_')
137
138def twoByteLEFor(value):
139    return [ (value & 0xff), (value >> 8)]
140
141def is_128bit_uuid(text):
142    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):
143        return True
144    return False
145
146def parseUUID128(uuid):
147    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)
148    uuid_bytes = []
149    for i in range(8, 0, -1):
150        uuid_bytes = uuid_bytes + twoByteLEFor(int(parts.group(i),16))
151    return uuid_bytes
152
153def parseUUID(uuid):
154    if uuid in assigned_uuids:
155        return twoByteLEFor(assigned_uuids[uuid])
156    uuid_upper = uuid.upper().replace('.','_')
157    if uuid_upper in bluetooth_gatt:
158        return twoByteLEFor(bluetooth_gatt[uuid_upper])
159    if is_128bit_uuid(uuid):
160        return parseUUID128(uuid)
161    uuidInt = int(uuid, 16)
162    return twoByteLEFor(uuidInt)
163
164def parseProperties(properties):
165    value = 0
166    parts = properties.split("|")
167    for property in parts:
168        property = property.strip()
169        if property in property_flags:
170            value |= property_flags[property]
171        else:
172            print("WARNING: property %s undefined" % (property))
173
174    return value;
175
176def gatt_characteristic_properties(properties):
177    return properties & 0xff
178
179def att_flags(properties):
180    # drop Broadcast (0x01), Notify (0x10), Indicate (0x20) - not used for flags
181    properties &= 0xffffffce
182
183    # rw permissions distinct
184    distinct_permissions_used = properties & (
185        property_flags['READ_AUTHORIZED'] |
186        property_flags['READ_AUTHENTICATED'] |
187        property_flags['READ_ENCRYPTED'] |
188        property_flags['READ_ANYBODY'] |
189        property_flags['WRITE_AUTHORIZED'] |
190        property_flags['WRITE_AUTHENTICATED'] |
191        property_flags['WRITE_ENCRYPTED'] |
192        property_flags['WRITE_ANYBODY']
193    ) != 0
194
195    # post process properties
196    encryption_key_size_specified = (properties & property_flags['ENCRYPTION_KEY_SIZE_MASK']) != 0
197
198    # if distinct permissions not used and encyrption key size specified -> set READ/WRITE Encrypted
199    if encryption_key_size_specified and not distinct_permissions_used:
200        properties |= property_flags['READ_ENCRYPTED'] | property_flags['WRITE_ENCRYPTED']
201
202    # if distinct permissions not used and authentication is requires -> set READ/WRITE Authenticated
203    if properties & property_flags['AUTHENTICATION_REQUIRED'] and not distinct_permissions_used:
204        properties |= property_flags['READ_AUTHENTICATED'] | property_flags['WRITE_AUTHENTICATED']
205
206    # if distinct permissions not used and authorized is requires -> set READ/WRITE Authorized
207    if properties & property_flags['AUTHORIZATION_REQUIRED'] and not distinct_permissions_used:
208        properties |= property_flags['READ_AUTHORIZED'] | property_flags['WRITE_AUTHORIZED']
209
210    # determine read/write security requirements
211    read_security_level  = 0
212    write_security_level = 0
213    if properties & property_flags['READ_AUTHORIZED']:
214        read_security_level = 3
215    elif properties & property_flags['READ_AUTHENTICATED']:
216        read_security_level = 2
217    elif properties & property_flags['READ_ENCRYPTED']:
218        read_security_level = 1
219    if properties & property_flags['WRITE_AUTHORIZED']:
220        write_security_level = 3
221    elif properties & property_flags['WRITE_AUTHENTICATED']:
222        write_security_level = 2
223    elif properties & property_flags['WRITE_ENCRYPTED']:
224        write_security_level = 1
225
226    # map security requirements to flags
227    if read_security_level & 2:
228        properties |= property_flags['READ_PERMISSION_BIT_1']
229    if read_security_level & 1:
230        properties |= property_flags['READ_PERMISSION_BIT_0']
231    if write_security_level & 2:
232        properties |= property_flags['WRITE_PERMISSION_BIT_1']
233    if write_security_level & 1:
234        properties |= property_flags['WRITE_PERMISSION_BIT_0']
235
236    return properties
237
238def write_permissions_and_key_size_flags_from_properties(properties):
239    return att_flags(properties) & (property_flags['ENCRYPTION_KEY_SIZE_MASK'] | property_flags['WRITE_PERMISSION_BIT_0'] | property_flags['WRITE_PERMISSION_BIT_1'])
240
241def write_8(fout, value):
242    fout.write( "0x%02x, " % (value & 0xff))
243
244def write_16(fout, value):
245    fout.write('0x%02x, 0x%02x, ' % (value & 0xff, (value >> 8) & 0xff))
246
247def write_uuid(uuid):
248    for byte in uuid:
249        fout.write( "0x%02x, " % byte)
250
251def write_string(fout, text):
252    for l in text.lstrip('"').rstrip('"'):
253        write_8(fout, ord(l))
254
255def write_sequence(fout, text):
256    parts = text.split()
257    for part in parts:
258        fout.write("0x%s, " % (part.strip()))
259
260def write_indent(fout):
261    fout.write("    ")
262
263def read_permissions_from_flags(flags):
264    permissions = 0
265    if flags & property_flags['READ_PERMISSION_BIT_0']:
266        permissions |= 1
267    if flags & property_flags['READ_PERMISSION_BIT_1']:
268        permissions |= 2
269    return permissions
270
271def write_permissions_from_flags(flags):
272    permissions = 0
273    if flags & property_flags['WRITE_PERMISSION_BIT_0']:
274        permissions |= 1
275    if flags & property_flags['WRITE_PERMISSION_BIT_1']:
276        permissions |= 2
277    return permissions
278
279def encryption_key_size_from_flags(flags):
280    encryption_key_size = (flags & 0xf000) >> 12
281    if encryption_key_size > 0:
282        encryption_key_size += 1
283    return encryption_key_size
284
285def is_string(text):
286    for item in text.split(" "):
287        if not all(c in string.hexdigits for c in item):
288            return True
289    return False
290
291def add_client_characteristic_configuration(properties):
292    return properties & (property_flags['NOTIFY'] | property_flags['INDICATE'])
293
294def serviceDefinitionComplete(fout):
295    global services
296    if current_service_uuid_string:
297        fout.write("\n")
298        # print("append service %s = [%d, %d]" % (current_characteristic_uuid_string, current_service_start_handle, handle-1))
299        defines_for_services.append('#define ATT_SERVICE_%s_START_HANDLE 0x%04x' % (current_service_uuid_string, current_service_start_handle))
300        defines_for_services.append('#define ATT_SERVICE_%s_END_HANDLE 0x%04x' % (current_service_uuid_string, handle-1))
301        services[current_service_uuid_string] = [current_service_start_handle, handle-1]
302
303def dump_flags(fout, flags):
304    global security_permsission
305    encryption_key_size = encryption_key_size_from_flags(flags)
306    read_permissions    = security_permsission[read_permissions_from_flags(flags)]
307    write_permissions   = security_permsission[write_permissions_from_flags(flags)]
308    write_indent(fout)
309    fout.write('// ')
310    first = 1
311    if flags & property_flags['READ']:
312        fout.write('READ_%s' % read_permissions)
313        first = 0
314    if flags & (property_flags['WRITE'] | property_flags['WRITE_WITHOUT_RESPONSE']):
315        if not first:
316            fout.write(', ')
317        first = 0
318        fout.write('WRITE_%s' % write_permissions)
319    if encryption_key_size > 0:
320        if not first:
321            fout.write(', ')
322        first = 0
323        fout.write('ENCRYPTION_KEY_SIZE=%u' % encryption_key_size)
324    fout.write('\n')
325
326def parseService(fout, parts, service_type):
327    global handle
328    global total_size
329    global current_service_uuid_string
330    global current_service_start_handle
331
332    serviceDefinitionComplete(fout)
333
334    read_only_anybody_flags = property_flags['READ'];
335
336    write_indent(fout)
337    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
338
339    uuid = parseUUID(parts[1])
340    uuid_size = len(uuid)
341
342    size = 2 + 2 + 2 + uuid_size + 2
343
344    if service_type == 0x2802:
345        size += 4
346
347    write_indent(fout)
348    write_16(fout, size)
349    write_16(fout, read_only_anybody_flags)
350    write_16(fout, handle)
351    write_16(fout, service_type)
352    write_uuid(uuid)
353    fout.write("\n")
354
355    current_service_uuid_string = c_string_for_uuid(parts[1])
356    current_service_start_handle = handle
357    handle = handle + 1
358    total_size = total_size + size
359
360def parsePrimaryService(fout, parts):
361    parseService(fout, parts, 0x2800)
362
363def parseSecondaryService(fout, parts):
364    parseService(fout, parts, 0x2801)
365
366def parseIncludeService(fout, parts):
367    global handle
368    global total_size
369
370    read_only_anybody_flags = property_flags['READ'];
371
372    write_indent(fout)
373    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts)))
374
375    uuid = parseUUID(parts[1])
376    uuid_size = len(uuid)
377    if uuid_size > 2:
378        uuid_size = 0
379    # print("Include Service ", c_string_for_uuid(uuid))
380
381    size = 2 + 2 + 2 + 2 + 4 + uuid_size
382
383    keyUUID = c_string_for_uuid(parts[1])
384
385    write_indent(fout)
386    write_16(fout, size)
387    write_16(fout, read_only_anybody_flags)
388    write_16(fout, handle)
389    write_16(fout, 0x2802)
390    write_16(fout, services[keyUUID][0])
391    write_16(fout, services[keyUUID][1])
392    if uuid_size > 0:
393        write_uuid(uuid)
394    fout.write("\n")
395
396    handle = handle + 1
397    total_size = total_size + size
398
399
400def parseCharacteristic(fout, parts):
401    global handle
402    global total_size
403    global current_characteristic_uuid_string
404    global characteristic_indices
405
406    read_only_anybody_flags = property_flags['READ'];
407
408    # enumerate characteristics with same UUID, using optional name tag if available
409    current_characteristic_uuid_string = c_string_for_uuid(parts[1]);
410    index = 1
411    if current_characteristic_uuid_string in characteristic_indices:
412        index = characteristic_indices[current_characteristic_uuid_string] + 1
413    characteristic_indices[current_characteristic_uuid_string] = index
414    if len(parts) > 4:
415        current_characteristic_uuid_string += '_' + parts[4].upper().replace(' ','_')
416    else:
417        current_characteristic_uuid_string += ('_%02x' % index)
418
419    uuid       = parseUUID(parts[1])
420    uuid_size  = len(uuid)
421    properties = parseProperties(parts[2])
422    value = ', '.join([str(x) for x in parts[3:]])
423
424    # reliable writes is defined in an extended properties
425    if (properties & property_flags['RELIABLE_WRITE']):
426        properties = properties | property_flags['EXTENDED_PROPERTIES']
427
428    write_indent(fout)
429    fout.write('// 0x%04x %s\n' % (handle, '-'.join(parts[0:3])))
430
431
432    characteristic_properties = gatt_characteristic_properties(properties)
433    size = 2 + 2 + 2 + 2 + (1+2+uuid_size)
434    write_indent(fout)
435    write_16(fout, size)
436    write_16(fout, read_only_anybody_flags)
437    write_16(fout, handle)
438    write_16(fout, 0x2803)
439    write_8(fout, characteristic_properties)
440    write_16(fout, handle+1)
441    write_uuid(uuid)
442    fout.write("\n")
443    handle = handle + 1
444    total_size = total_size + size
445
446    size = 2 + 2 + 2 + uuid_size
447    if is_string(value):
448        size = size + len(value)
449    else:
450        size = size + len(value.split())
451
452    value_flags = att_flags(properties)
453
454    # add UUID128 flag for value handle
455    if uuid_size == 16:
456        value_flags = value_flags | property_flags['LONG_UUID'];
457
458    write_indent(fout)
459    fout.write('// 0x%04x VALUE-%s-'"'%s'"'\n' % (handle, '-'.join(parts[1:3]),value))
460
461    dump_flags(fout, value_flags)
462
463    write_indent(fout)
464    write_16(fout, size)
465    write_16(fout, value_flags)
466    write_16(fout, handle)
467    write_uuid(uuid)
468    if is_string(value):
469        write_string(fout, value)
470    else:
471        write_sequence(fout,value)
472
473    fout.write("\n")
474    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_VALUE_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
475    handle = handle + 1
476
477    if add_client_characteristic_configuration(properties):
478        # use write permissions and encryption key size from attribute value and set READ_ANYBODY | READ | WRITE | DYNAMIC
479        flags  = write_permissions_and_key_size_flags_from_properties(properties)
480        flags |= property_flags['READ']
481        flags |= property_flags['WRITE']
482        flags |= property_flags['DYNAMIC']
483        size = 2 + 2 + 2 + 2 + 2
484
485        write_indent(fout)
486        fout.write('// 0x%04x CLIENT_CHARACTERISTIC_CONFIGURATION\n' % (handle))
487
488        dump_flags(fout, flags)
489
490        write_indent(fout)
491        write_16(fout, size)
492        write_16(fout, flags)
493        write_16(fout, handle)
494        write_16(fout, 0x2902)
495        write_16(fout, 0)
496        fout.write("\n")
497        defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
498        handle = handle + 1
499
500    if properties & property_flags['RELIABLE_WRITE']:
501        size = 2 + 2 + 2 + 2 + 2
502        write_indent(fout)
503        fout.write('// 0x%04x CHARACTERISTIC_EXTENDED_PROPERTIES\n' % (handle))
504        write_indent(fout)
505        write_16(fout, size)
506        write_16(fout, read_only_anybody_flags)
507        write_16(fout, handle)
508        write_16(fout, 0x2900)
509        write_16(fout, 1)   # Reliable Write
510        fout.write("\n")
511        handle = handle + 1
512
513def parseCharacteristicUserDescription(fout, parts):
514    global handle
515    global total_size
516    global current_characteristic_uuid_string
517
518    properties = parseProperties(parts[1])
519    value      = parts[2]
520
521    size = 2 + 2 + 2 + 2
522    if is_string(value):
523        size = size + len(value) - 2
524    else:
525        size = size + len(value.split())
526
527    # use write, write permissions and encryption key size from attribute value and set READ_ANYBODY
528    flags  = write_permissions_and_key_size_flags_from_properties(properties)
529    flags |= properties & property_flags['WRITE']
530    flags |= property_flags['READ']
531
532    write_indent(fout)
533    fout.write('// 0x%04x CHARACTERISTIC_USER_DESCRIPTION-%s\n' % (handle, '-'.join(parts[1:])))
534
535    dump_flags(fout, flags)
536
537    write_indent(fout)
538    write_16(fout, size)
539    write_16(fout, flags)
540    write_16(fout, handle)
541    write_16(fout, 0x2901)
542    if is_string(value):
543        write_string(fout, value)
544    else:
545        write_sequence(fout,value)
546    fout.write("\n")
547    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
548    handle = handle + 1
549
550def parseServerCharacteristicConfiguration(fout, parts):
551    global handle
552    global total_size
553    global current_characteristic_uuid_string
554
555    properties = parseProperties(parts[1])
556    size = 2 + 2 + 2 + 2
557
558    # use write permissions and encryption key size from attribute value and set READ, WRITE, DYNAMIC, READ_ANYBODY
559    flags  = write_permissions_and_key_size_flags_from_properties(properties)
560    flags |= property_flags['READ']
561    flags |= property_flags['WRITE']
562    flags |= property_flags['DYNAMIC']
563
564    write_indent(fout)
565    fout.write('// 0x%04x SERVER_CHARACTERISTIC_CONFIGURATION-%s\n' % (handle, '-'.join(parts[1:])))
566
567    dump_flags(fout, flags)
568
569    write_indent(fout)
570    write_16(fout, size)
571    write_16(fout, flags)
572    write_16(fout, handle)
573    write_16(fout, 0x2903)
574    fout.write("\n")
575    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
576    handle = handle + 1
577
578def parseCharacteristicFormat(fout, parts):
579    global handle
580    global total_size
581
582    read_only_anybody_flags = property_flags['READ'];
583
584    identifier = parts[1]
585    presentation_formats[identifier] = handle
586    # print("format '%s' with handle %d\n" % (identifier, handle))
587
588    format     = parts[2]
589    exponent   = parts[3]
590    unit       = parseUUID(parts[4])
591    name_space = parts[5]
592    description = parseUUID(parts[6])
593
594    size = 2 + 2 + 2 + 2 + 7
595
596    write_indent(fout)
597    fout.write('// 0x%04x CHARACTERISTIC_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
598    write_indent(fout)
599    write_16(fout, size)
600    write_16(fout, read_only_anybody_flags)
601    write_16(fout, handle)
602    write_16(fout, 0x2904)
603    write_sequence(fout, format)
604    write_sequence(fout, exponent)
605    write_uuid(unit)
606    write_sequence(fout, name_space)
607    write_uuid(description)
608    fout.write("\n")
609    handle = handle + 1
610
611
612def parseCharacteristicAggregateFormat(fout, parts):
613    global handle
614    global total_size
615
616    read_only_anybody_flags = property_flags['READ'];
617    size = 2 + 2 + 2 + 2 + (len(parts)-1) * 2
618
619    write_indent(fout)
620    fout.write('// 0x%04x CHARACTERISTIC_AGGREGATE_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
621    write_indent(fout)
622    write_16(fout, size)
623    write_16(fout, read_only_anybody_flags)
624    write_16(fout, handle)
625    write_16(fout, 0x2905)
626    for identifier in parts[1:]:
627        format_handle = presentation_formats[identifier]
628        if format == 0:
629            print("ERROR: identifier '%s' in CHARACTERISTIC_AGGREGATE_FORMAT undefined" % identifier)
630            sys.exit(1)
631        write_16(fout, format_handle)
632    fout.write("\n")
633    handle = handle + 1
634
635def parseReportReference(fout, parts):
636    global handle
637    global total_size
638
639    read_only_anybody_flags = property_flags['READ'];
640    size = 2 + 2 + 2 + 2 + 1 + 1
641
642    report_id = parts[2]
643    report_type = parts[3]
644
645    write_indent(fout)
646    fout.write('// 0x%04x REPORT_REFERENCE-%s\n' % (handle, '-'.join(parts[1:])))
647    write_indent(fout)
648    write_16(fout, size)
649    write_16(fout, read_only_anybody_flags)
650    write_16(fout, handle)
651    write_16(fout, 0x2908)
652    write_sequence(fout, report_id)
653    write_sequence(fout, report_type)
654    fout.write("\n")
655    handle = handle + 1
656
657
658def parseNumberOfDigitals(fout, parts):
659    global handle
660    global total_size
661
662    read_only_anybody_flags = property_flags['READ'];
663    size = 2 + 2 + 2 + 2 + 1
664
665    no_of_digitals = parts[1]
666
667    write_indent(fout)
668    fout.write('// 0x%04x NUMBER_OF_DIGITALS-%s\n' % (handle, '-'.join(parts[1:])))
669    write_indent(fout)
670    write_16(fout, size)
671    write_16(fout, read_only_anybody_flags)
672    write_16(fout, handle)
673    write_16(fout, 0x2909)
674    write_sequence(fout, no_of_digitals)
675    fout.write("\n")
676    handle = handle + 1
677
678def parseLines(fname_in, fin, fout):
679    global handle
680    global total_size
681
682    line_count = 0;
683    for line in fin:
684        line = line.strip("\n\r ")
685        line_count += 1
686
687        if line.startswith("//"):
688            fout.write("    //" + line.lstrip('/') + '\n')
689            continue
690
691        if line.startswith("#import"):
692            imported_file = ''
693            parts = re.match('#import\s+<(.*)>\w*',line)
694            if parts and len(parts.groups()) == 1:
695                imported_file = btstack_root+'/src/ble/gatt-service/' + parts.groups()[0]
696            parts = re.match('#import\s+"(.*)"\w*',line)
697            if parts and len(parts.groups()) == 1:
698                imported_file = os.path.abspath(os.path.dirname(fname_in) + '/'+parts.groups()[0])
699            if len(imported_file) == 0:
700                print('ERROR: #import in file %s - line %u neither <name.gatt> nor "name.gatt" form', (fname_in, line_count))
701                continue
702
703            print("Importing %s" % imported_file)
704            try:
705                imported_fin = codecs.open (imported_file, encoding='utf-8')
706                fout.write('    // ' + line + ' -- BEGIN\n')
707                parseLines(imported_file, imported_fin, fout)
708                fout.write('    // ' + line + ' -- END\n')
709            except IOError as e:
710                print('ERROR: Import failed. Please check path.')
711
712            continue
713
714        if line.startswith("#TODO"):
715            print ("WARNING: #TODO in file %s - line %u not handled, skipping declaration:" % (fname_in, line_count))
716            print ("'%s'" % line)
717            fout.write("// " + line + '\n')
718            continue
719
720        if len(line) == 0:
721            continue
722
723        f = io.StringIO(line)
724        parts_list = csv.reader(f, delimiter=',', quotechar='"')
725
726        for parts in parts_list:
727            for index, object in enumerate(parts):
728                parts[index] = object.strip().lstrip('"').rstrip('"')
729
730            if parts[0] == 'PRIMARY_SERVICE':
731                parsePrimaryService(fout, parts)
732                continue
733
734            if parts[0] == 'SECONDARY_SERVICE':
735                parseSecondaryService(fout, parts)
736                continue
737
738            if parts[0] == 'INCLUDE_SERVICE':
739                parseIncludeService(fout, parts)
740                continue
741
742            # 2803
743            if parts[0] == 'CHARACTERISTIC':
744                parseCharacteristic(fout, parts)
745                continue
746
747            # 2900 Characteristic Extended Properties
748
749            # 2901
750            if parts[0] == 'CHARACTERISTIC_USER_DESCRIPTION':
751                parseCharacteristicUserDescription(fout, parts)
752                continue
753
754
755            # 2902 Client Characteristic Configuration - automatically included in Characteristic if
756            # notification / indication is supported
757            if parts[0] == 'CLIENT_CHARACTERISTIC_CONFIGURATION':
758                continue
759
760            # 2903
761            if parts[0] == 'SERVER_CHARACTERISTIC_CONFIGURATION':
762                parseServerCharacteristicConfiguration(fout, parts)
763                continue
764
765            # 2904
766            if parts[0] == 'CHARACTERISTIC_FORMAT':
767                parseCharacteristicFormat(fout, parts)
768                continue
769
770            # 2905
771            if parts[0] == 'CHARACTERISTIC_AGGREGATE_FORMAT':
772                parseCharacteristicAggregateFormat(fout, parts)
773                continue
774
775            # 2906
776            if parts[0] == 'VALID_RANGE':
777                print("WARNING: %s not implemented yet\n" % (parts[0]))
778                continue
779
780            # 2907
781            if parts[0] == 'EXTERNAL_REPORT_REFERENCE':
782                print("WARNING: %s not implemented yet\n" % (parts[0]))
783                continue
784
785            # 2908
786            if parts[0] == 'REPORT_REFERENCE':
787                parseReportReference(fout, parts)
788                continue
789
790            # 2909
791            if parts[0] == 'NUMBER_OF_DIGITALS':
792                parseNumberOfDigitals(fout, parts)
793                continue
794
795            # 290A
796            if parts[0] == 'VALUE_TRIGGER_SETTING':
797                print("WARNING: %s not implemented yet\n" % (parts[0]))
798                continue
799
800            # 290B
801            if parts[0] == 'ENVIRONMENTAL_SENSING_CONFIGURATION':
802                print("WARNING: %s not implemented yet\n" % (parts[0]))
803                continue
804
805            # 290C
806            if parts[0] == 'ENVIRONMENTAL_SENSING_MEASUREMENT':
807                print("WARNING: %s not implemented yet\n" % (parts[0]))
808                continue
809
810            # 290D
811            if parts[0] == 'ENVIRONMENTAL_SENSING_TRIGGER_SETTING':
812                print("WARNING: %s not implemented yet\n" % (parts[0]))
813                continue
814
815            # 2906
816            if parts[0] == 'VALID_RANGE':
817                print("WARNING: %s not implemented yet\n" % (parts[0]))
818                continue
819
820            print("WARNING: unknown token: %s\n" % (parts[0]))
821
822def parse(fname_in, fin, fname_out, fout):
823    global handle
824    global total_size
825
826    fout.write(header.format(fname_out, fname_in))
827    fout.write('{\n')
828
829    parseLines(fname_in, fin, fout)
830
831    serviceDefinitionComplete(fout)
832    write_indent(fout)
833    fout.write("// END\n");
834    write_indent(fout)
835    write_16(fout,0)
836    fout.write("\n")
837    total_size = total_size + 2
838
839    fout.write("}; // total size %u bytes \n" % total_size);
840
841def listHandles(fout):
842    fout.write('\n\n')
843    fout.write('//\n')
844    fout.write('// list service handle ranges\n')
845    fout.write('//\n')
846    for define in defines_for_services:
847        fout.write(define)
848        fout.write('\n')
849    fout.write('\n')
850    fout.write('//\n')
851    fout.write('// list mapping between characteristics and handles\n')
852    fout.write('//\n')
853    for define in defines_for_characteristics:
854        fout.write(define)
855        fout.write('\n')
856
857if (len(sys.argv) < 3):
858    print(usage)
859    sys.exit(1)
860try:
861    # read defines from bluetooth_gatt.h
862    btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
863    gen_path = btstack_root + '/src/bluetooth_gatt.h'
864    bluetooth_gatt = read_defines(gen_path)
865
866    filename = sys.argv[2]
867    fin  = codecs.open (sys.argv[1], encoding='utf-8')
868    fout = open (filename, 'w')
869    parse(sys.argv[1], fin, filename, fout)
870    listHandles(fout)
871    fout.close()
872    print('Created %s' % filename)
873
874except IOError as e:
875
876    print(usage)
877    sys.exit(1)
878
879print('Compilation successful!\n')
880