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