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