compile_gatt.py 30 KB

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