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