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