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