xref: /btstack/tool/compile_gatt.py (revision 630ffdd469bbec3276322f46b93e6cfdfcb21c27)
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['WRITE_WITHOUT_RESPONSE']
479        flags |= property_flags['DYNAMIC']
480        size = 2 + 2 + 2 + 2 + 2
481
482        write_indent(fout)
483        fout.write('// 0x%04x CLIENT_CHARACTERISTIC_CONFIGURATION\n' % (handle))
484
485        dump_flags(fout, flags)
486
487        write_indent(fout)
488        write_16(fout, size)
489        write_16(fout, flags)
490        write_16(fout, handle)
491        write_16(fout, 0x2902)
492        write_16(fout, 0)
493        fout.write("\n")
494        defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
495        handle = handle + 1
496
497    if properties & property_flags['RELIABLE_WRITE']:
498        size = 2 + 2 + 2 + 2 + 2
499        write_indent(fout)
500        fout.write('// 0x%04x CHARACTERISTIC_EXTENDED_PROPERTIES\n' % (handle))
501        write_indent(fout)
502        write_16(fout, size)
503        write_16(fout, read_only_anybody_flags)
504        write_16(fout, handle)
505        write_16(fout, 0x2900)
506        write_16(fout, 1)   # Reliable Write
507        fout.write("\n")
508        handle = handle + 1
509
510def parseCharacteristicUserDescription(fout, parts):
511    global handle
512    global total_size
513    global current_characteristic_uuid_string
514
515    properties = parseProperties(parts[1])
516    value      = parts[2]
517
518    size = 2 + 2 + 2 + 2
519    if is_string(value):
520        size = size + len(value)
521    else:
522        size = size + len(value.split())
523
524    # use write, write permissions and encryption key size from attribute value and set READ_ANYBODY
525    flags  = write_permissions_and_key_size_flags_from_properties(properties)
526    flags |= properties & property_flags['WRITE']
527    flags |= property_flags['READ']
528
529    write_indent(fout)
530    fout.write('// 0x%04x CHARACTERISTIC_USER_DESCRIPTION-%s\n' % (handle, '-'.join(parts[1:])))
531
532    dump_flags(fout, flags)
533
534    write_indent(fout)
535    write_16(fout, size)
536    write_16(fout, flags)
537    write_16(fout, handle)
538    write_16(fout, 0x2901)
539    if is_string(value):
540        write_string(fout, value)
541    else:
542        write_sequence(fout,value)
543    fout.write("\n")
544    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
545    handle = handle + 1
546
547def parseServerCharacteristicConfiguration(fout, parts):
548    global handle
549    global total_size
550    global current_characteristic_uuid_string
551
552    properties = parseProperties(parts[1])
553    size = 2 + 2 + 2 + 2
554
555    # use write permissions and encryption key size from attribute value and set READ, WRITE, DYNAMIC, READ_ANYBODY
556    flags  = write_permissions_and_key_size_flags_from_properties(properties)
557    flags |= property_flags['READ']
558    flags |= property_flags['WRITE']
559    flags |= property_flags['DYNAMIC']
560
561    write_indent(fout)
562    fout.write('// 0x%04x SERVER_CHARACTERISTIC_CONFIGURATION-%s\n' % (handle, '-'.join(parts[1:])))
563
564    dump_flags(fout, flags)
565
566    write_indent(fout)
567    write_16(fout, size)
568    write_16(fout, flags)
569    write_16(fout, handle)
570    write_16(fout, 0x2903)
571    fout.write("\n")
572    defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
573    handle = handle + 1
574
575def parseCharacteristicFormat(fout, parts):
576    global handle
577    global total_size
578
579    read_only_anybody_flags = property_flags['READ'];
580
581    identifier = parts[1]
582    presentation_formats[identifier] = handle
583    # print("format '%s' with handle %d\n" % (identifier, handle))
584
585    format     = parts[2]
586    exponent   = parts[3]
587    unit       = parseUUID(parts[4])
588    name_space = parts[5]
589    description = parseUUID(parts[6])
590
591    size = 2 + 2 + 2 + 2 + 7
592
593    write_indent(fout)
594    fout.write('// 0x%04x CHARACTERISTIC_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
595    write_indent(fout)
596    write_16(fout, size)
597    write_16(fout, read_only_anybody_flags)
598    write_16(fout, handle)
599    write_16(fout, 0x2904)
600    write_sequence(fout, format)
601    write_sequence(fout, exponent)
602    write_uuid(unit)
603    write_sequence(fout, name_space)
604    write_uuid(description)
605    fout.write("\n")
606    handle = handle + 1
607
608
609def parseCharacteristicAggregateFormat(fout, parts):
610    global handle
611    global total_size
612
613    read_only_anybody_flags = property_flags['READ'];
614    size = 2 + 2 + 2 + 2 + (len(parts)-1) * 2
615
616    write_indent(fout)
617    fout.write('// 0x%04x CHARACTERISTIC_AGGREGATE_FORMAT-%s\n' % (handle, '-'.join(parts[1:])))
618    write_indent(fout)
619    write_16(fout, size)
620    write_16(fout, read_only_anybody_flags)
621    write_16(fout, handle)
622    write_16(fout, 0x2905)
623    for identifier in parts[1:]:
624        format_handle = presentation_formats[identifier]
625        if format == 0:
626            print("ERROR: identifier '%s' in CHARACTERISTIC_AGGREGATE_FORMAT undefined" % identifier)
627            sys.exit(1)
628        write_16(fout, format_handle)
629    fout.write("\n")
630    handle = handle + 1
631
632def parseReportReference(fout, parts):
633    global handle
634    global total_size
635
636    read_only_anybody_flags = property_flags['READ'];
637    size = 2 + 2 + 2 + 2 + 1 + 1
638
639    report_id = parts[2]
640    report_type = parts[3]
641
642    write_indent(fout)
643    fout.write('// 0x%04x REPORT_REFERENCE-%s\n' % (handle, '-'.join(parts[1:])))
644    write_indent(fout)
645    write_16(fout, size)
646    write_16(fout, read_only_anybody_flags)
647    write_16(fout, handle)
648    write_16(fout, 0x2908)
649    write_sequence(fout, report_id)
650    write_sequence(fout, report_type)
651    fout.write("\n")
652    handle = handle + 1
653
654
655def parseNumberOfDigitals(fout, parts):
656    global handle
657    global total_size
658
659    read_only_anybody_flags = property_flags['READ'];
660    size = 2 + 2 + 2 + 2 + 1
661
662    no_of_digitals = parts[1]
663
664    write_indent(fout)
665    fout.write('// 0x%04x NUMBER_OF_DIGITALS-%s\n' % (handle, '-'.join(parts[1:])))
666    write_indent(fout)
667    write_16(fout, size)
668    write_16(fout, read_only_anybody_flags)
669    write_16(fout, handle)
670    write_16(fout, 0x2909)
671    write_sequence(fout, no_of_digitals)
672    fout.write("\n")
673    handle = handle + 1
674
675def parseLines(fname_in, fin, fout):
676    global handle
677    global total_size
678
679    line_count = 0;
680    for line in fin:
681        line = line.strip("\n\r ")
682        line_count += 1
683
684        if line.startswith("//"):
685            fout.write("    //" + line.lstrip('/') + '\n')
686            continue
687
688        if line.startswith("#import"):
689            imported_file = ''
690            parts = re.match('#import\s+<(.*)>\w*',line)
691            if parts and len(parts.groups()) == 1:
692                imported_file = parts.groups()[0]
693            parts = re.match('#import\s+"(.*)"\w*',line)
694            if parts and len(parts.groups()) == 1:
695                imported_file = parts.groups()[0]
696            if len(imported_file) == 0:
697                print('ERROR: #import in file %s - line %u neither <name.gatt> nor "name.gatt" form', (fname_in, line_count))
698                continue
699
700            imported_file = getFile( imported_file )
701            print("Importing %s" % imported_file)
702            try:
703                imported_fin = codecs.open (imported_file, encoding='utf-8')
704                fout.write('    // ' + line + ' -- BEGIN\n')
705                parseLines(imported_file, imported_fin, fout)
706                fout.write('    // ' + line + ' -- END\n')
707            except IOError as e:
708                print('ERROR: Import failed. Please check path.')
709
710            continue
711
712        if line.startswith("#TODO"):
713            print ("WARNING: #TODO in file %s - line %u not handled, skipping declaration:" % (fname_in, line_count))
714            print ("'%s'" % line)
715            fout.write("// " + line + '\n')
716            continue
717
718        if len(line) == 0:
719            continue
720
721        f = io.StringIO(line)
722        parts_list = csv.reader(f, delimiter=',', quotechar='"')
723
724        for parts in parts_list:
725            for index, object in enumerate(parts):
726                parts[index] = object.strip().lstrip('"').rstrip('"')
727
728            if parts[0] == 'PRIMARY_SERVICE':
729                parsePrimaryService(fout, parts)
730                continue
731
732            if parts[0] == 'SECONDARY_SERVICE':
733                parseSecondaryService(fout, parts)
734                continue
735
736            if parts[0] == 'INCLUDE_SERVICE':
737                parseIncludeService(fout, parts)
738                continue
739
740            # 2803
741            if parts[0] == 'CHARACTERISTIC':
742                parseCharacteristic(fout, parts)
743                continue
744
745            # 2900 Characteristic Extended Properties
746
747            # 2901
748            if parts[0] == 'CHARACTERISTIC_USER_DESCRIPTION':
749                parseCharacteristicUserDescription(fout, parts)
750                continue
751
752
753            # 2902 Client Characteristic Configuration - automatically included in Characteristic if
754            # notification / indication is supported
755            if parts[0] == 'CLIENT_CHARACTERISTIC_CONFIGURATION':
756                continue
757
758            # 2903
759            if parts[0] == 'SERVER_CHARACTERISTIC_CONFIGURATION':
760                parseServerCharacteristicConfiguration(fout, parts)
761                continue
762
763            # 2904
764            if parts[0] == 'CHARACTERISTIC_FORMAT':
765                parseCharacteristicFormat(fout, parts)
766                continue
767
768            # 2905
769            if parts[0] == 'CHARACTERISTIC_AGGREGATE_FORMAT':
770                parseCharacteristicAggregateFormat(fout, parts)
771                continue
772
773            # 2906
774            if parts[0] == 'VALID_RANGE':
775                print("WARNING: %s not implemented yet\n" % (parts[0]))
776                continue
777
778            # 2907
779            if parts[0] == 'EXTERNAL_REPORT_REFERENCE':
780                print("WARNING: %s not implemented yet\n" % (parts[0]))
781                continue
782
783            # 2908
784            if parts[0] == 'REPORT_REFERENCE':
785                parseReportReference(fout, parts)
786                continue
787
788            # 2909
789            if parts[0] == 'NUMBER_OF_DIGITALS':
790                parseNumberOfDigitals(fout, parts)
791                continue
792
793            # 290A
794            if parts[0] == 'VALUE_TRIGGER_SETTING':
795                print("WARNING: %s not implemented yet\n" % (parts[0]))
796                continue
797
798            # 290B
799            if parts[0] == 'ENVIRONMENTAL_SENSING_CONFIGURATION':
800                print("WARNING: %s not implemented yet\n" % (parts[0]))
801                continue
802
803            # 290C
804            if parts[0] == 'ENVIRONMENTAL_SENSING_MEASUREMENT':
805                print("WARNING: %s not implemented yet\n" % (parts[0]))
806                continue
807
808            # 290D
809            if parts[0] == 'ENVIRONMENTAL_SENSING_TRIGGER_SETTING':
810                print("WARNING: %s not implemented yet\n" % (parts[0]))
811                continue
812
813            # 2906
814            if parts[0] == 'VALID_RANGE':
815                print("WARNING: %s not implemented yet\n" % (parts[0]))
816                continue
817
818            print("WARNING: unknown token: %s\n" % (parts[0]))
819
820def parse(fname_in, fin, fname_out, fout):
821    global handle
822    global total_size
823
824    fout.write(header.format(fname_out, fname_in))
825    fout.write('{\n')
826    write_indent(fout)
827    fout.write('// ATT DB Version\n')
828    write_indent(fout)
829    fout.write('1,\n')
830    fout.write("\n")
831
832    parseLines(fname_in, fin, fout)
833
834    serviceDefinitionComplete(fout)
835    write_indent(fout)
836    fout.write("// END\n");
837    write_indent(fout)
838    write_16(fout,0)
839    fout.write("\n")
840    total_size = total_size + 2
841
842    fout.write("}; // total size %u bytes \n" % total_size);
843
844def listHandles(fout):
845    fout.write('\n\n')
846    fout.write('//\n')
847    fout.write('// list service handle ranges\n')
848    fout.write('//\n')
849    for define in defines_for_services:
850        fout.write(define)
851        fout.write('\n')
852    fout.write('\n')
853    fout.write('//\n')
854    fout.write('// list mapping between characteristics and handles\n')
855    fout.write('//\n')
856    for define in defines_for_characteristics:
857        fout.write(define)
858        fout.write('\n')
859
860def getFile( fileName ):
861    inc = args.I
862    for d in inc:
863        fullFile = d[0] + fileName
864        print("test %s" % fullFile)
865        if os.path.isfile( fullFile ) == True:
866            return fullFile
867    print ("'{0}' not found".format( fileName ))
868    print ("Include paths: %s" % ", ".join(inc))
869    exit(-1)
870
871
872btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
873default_includes = [ btstack_root + '/src/', btstack_root + '/src/ble/gatt-service/']
874
875parser = argparse.ArgumentParser(description='BLE GATT configuration generator for use with BTstack')
876
877parser.add_argument('-I', action='append', nargs=1, metavar='includes',
878        help='include search path for .gatt service files and bluetooth_gatt.h (default: %s)' % ", ".join(default_includes))
879parser.add_argument('gattfile', metavar='gattfile', type=str,
880        help='gatt file to be compiled')
881parser.add_argument('hfile', metavar='hfile', type=str,
882        help='header file to be generated')
883
884args = parser.parse_args()
885
886# append default include paths
887if args.I == None:
888    args.I = []
889for d in default_includes:
890    args.I.append([d])
891
892try:
893    # read defines from bluetooth_gatt.h
894    gen_path = getFile( 'bluetooth_gatt.h' )
895    bluetooth_gatt = read_defines(gen_path)
896
897    filename = args.hfile
898    fin  = codecs.open (args.gattfile, encoding='utf-8')
899    fout = open (filename, 'w')
900    parse(args.gattfile, fin, filename, fout)
901    listHandles(fout)
902    fout.close()
903    print('Created %s' % filename)
904
905except IOError as e:
906
907    print(usage)
908    sys.exit(1)
909
910print('Compilation successful!\n')
911