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