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