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