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