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