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