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