1#!/usr/bin/env python 2# BlueKitchen GmbH (c) 2018 3 4import glob 5import re 6import sys 7import os 8 9import btstack_parser as parser 10 11print(''' 12Python binding generator for BTstack Server 13Copyright 2018, BlueKitchen GmbH 14''') 15 16# com.bluekitchen.btstack.BTstack.java templates 17command_builder_header = \ 18'''#!/usr/bin/env python3 19 20import struct 21 22# Command Builder 23 24class CommandBuilder(object): 25 def __init__(self): 26 pass 27 28 def pack24(value): 29 return struct.pack("B", value & 0xff) + struct.pack("<H", value >> 8) 30 31 def name248(str): 32 arg = str.encode('utf-8') 33 return arg[:248] + bytes(248-len(arg)) 34 35 def send_command(command): 36 return FALSE 37 38''' 39command_builder_command = ''' 40 def {name}({args}): 41 cmd_args = bytes() 42{args_builder} 43 cmd = struct.pack("<HB", opcode({ogf}, {ocf}), len(cmd_args)) + cmd_args 44 return self.send_hci_command(cmd) 45''' 46 47# com.bluekitchen.btstack.EventFactory template 48java_event_factory_template = \ 49'''package {0}; 50 51import {0}.event.*; 52 53public class EventFactory {{ 54 55 /** @brief event codes */ 56 57{1} 58 public static Event eventForPacket(Packet packet){{ 59 int eventType = Util.readByte(packet.getBuffer(), 0); 60 switch (eventType){{ 61{2} 62 case 0x3e: // LE_META_EVENT 63 int subEventType = Util.readByte(packet.getBuffer(), 2); 64 switch (subEventType){{ 65{3} 66 default: 67 return new Event(packet); 68 }} 69 70 default: 71 return new Event(packet); 72 }} 73 }} 74}} 75''' 76java_event_factory_event = ''' 77 case {0}: 78 return new {1}(packet); 79''' 80java_event_factory_subevent = ''' 81 case {0}: 82 return new {1}(packet); 83''' 84 85# com.bluekitchen.btstack.events.* template 86java_event_template = \ 87'''package {0}.event; 88 89import {0}.*; 90 91public class {1} extends Event {{ 92 93 public {1}(Packet packet) {{ 94 super(packet); 95 }} 96 {2} 97 {3} 98}} 99''' 100 101java_event_getter = \ 102''' 103 /** 104 * @return {1} as {0} 105 */ 106 public {0} get{1}(){{ 107 {2} 108 }} 109''' 110 111java_event_getter_data = \ 112'''int len = get{0}(); 113 byte[] result = new byte[len]; 114 System.arraycopy(data, {1}, result, 0, len); 115 return result;''' 116 117java_event_getter_data_fixed = \ 118'''int len = {0}; 119 byte[] result = new byte[len]; 120 System.arraycopy(data, {1}, result, 0, len); 121 return result;''' 122 123java_event_getter_remaining_data = \ 124'''int len = getPayloadLen() - {0}; 125 byte[] result = new byte[len]; 126 System.arraycopy(data, {0}, result, 0, len); 127 return result;''' 128 129java_event_to_string = \ 130''' 131 public String toString(){{ 132 StringBuffer t = new StringBuffer(); 133 t.append("{0} < type = "); 134 t.append(String.format("0x%02x, ", getEventType())); 135 t.append(getEventType()); 136{1} t.append(" >"); 137 return t.toString(); 138 }} 139''' 140 141 142# global variables/defines 143package ='com.bluekitchen.btstack' 144gen_path = 'gen/' + package.replace('.', '/') 145 146defines = dict() 147defines_used = set() 148 149def java_type_for_btstack_type(type): 150 param_types = { '1' : 'int', '2' : 'int', '3' : 'int', '4' : 'long', 'H' : 'int', 'B' : 'BD_ADDR', 151 'D' : 'byte []', 'E' : 'byte [] ', 'N' : 'String' , 'P' : 'byte []', 'A' : 'byte []', 152 'R' : 'byte []', 'S' : 'byte []', 'Q' : 'byte []', 153 'J' : 'int', 'L' : 'int', 'V' : 'byte []', 'U' : 'BT_UUID', 154 'X' : 'GATTService', 'Y' : 'GATTCharacteristic', 'Z' : 'GATTCharacteristicDescriptor', 155 'T' : 'String'} 156 return param_types[type] 157 158def size_for_type(type): 159 param_sizes = { '1' : 1, '2' : 2, '3' : 3, '4' : 4, 'H' : 2, 'B' : 6, 'D' : 8, 'E' : 240, 'N' : 248, 'P' : 16, 160 'A' : 31, 'S' : -1, 'V': -1, 'J' : 1, 'L' : 2, 'Q' : 32, 'U' : 16, 'X' : 20, 'Y' : 24, 'Z' : 18, 'T':-1} 161 return param_sizes[type] 162 163def create_command_python(fout, name, ogf, ocf, format, params): 164 global command_builder_command 165 166 ind = ' ' 167 param_store = { 168 '1' : 'cmd_args += struct.pack("B", %s)', 169 'J' : 'cmd_args += struct.pack("B", %s)', 170 '2' : 'cmd_args += struct.pack("<H", %s)', 171 'H' : 'cmd_args += struct.pack("<H", %s)', 172 'L' : 'cmd_args += struct.pack("<H", %s)', 173 '3' : 'cmd_args += self.pack24(%s)', 174 '4' : 'cmd_args += struct.pack("<H", %s)', 175 'N' : 'cmd_args += self.name248(%s)', 176 'B' : 'cmd_args += %s.get_bytes()', 177 'U' : 'cmd_args += %s.get_bytes()', 178 'X' : 'cmd_args += %s.get_bytes()', 179 'Y' : 'cmd_args += %s.get_bytes()', 180 'Z' : 'cmd_args += %s.get_bytes()', 181 'S' : 'cmd_args += %s', 182 # TODO: support serialization for these 183 'D' : '# D / TODO Util.storeBytes(command, offset, %s, 8)', 184 'E' : '# E / TODO Util.storeBytes(command, offset, %s, 240)', 185 'P' : '# P / TODO Util.storeBytes(command, offset, %s, 16)', 186 'Q' : '# Q / TODO Util.storeBytes(command, offset, %s, 32)', 187 'A' : '# A / TODO Util.storeBytes(command, offset, %s, 31)', 188 } 189 # method arguments 190 arg_counter = 1 191 args = [] 192 for param_type, arg_name in zip(format, params): 193 arg_size = size_for_type(param_type) 194 arg = (param_type, arg_size, arg_name) 195 args.append(arg) 196 arg_counter += 1 197 print(args) 198 199 # method argument declaration 200 args2 = [] 201 for arg in args: 202 args2.append(arg[2]) 203 args_string = ', '.join(args2) 204 print(args_string) 205 206 # command size (opcode, len) 207 size_fixed = 3 208 size_var = '' 209 for arg in args: 210 size = arg[1] 211 if size > 0: 212 size_fixed += size 213 else: 214 size_var += ' + %s.length' % arg[2] 215 size_string = '%u%s' % (size_fixed, size_var) 216 print(size_string) 217 218 store_params = '' 219 220 length_name = '' 221 for (param_type, arg_size, arg_name) in args: 222 if param_type in ['L', 'J']: 223 length_name = arg_name 224 if param_type == 'V': 225 store_params += ind + 'Util.storeBytes(command, offset, %s, %s);' % (arg_name, length_name) + '\n'; 226 length_name = '' 227 else: 228 store_params += ind + (param_store[param_type] % arg_name) + '\n'; 229 size = arg_size 230 231 fout.write( command_builder_command.format(name=name, args=args_string, ogf=ogf, ocf=ocf, args_builder=store_params)) 232 233def mark_define_as_used(term): 234 if term.startswith('0'): 235 return 236 defines_used.add(term) 237 238def java_define_string(key): 239 global defines 240 if key in defines: 241 return ' public static final int %s = %s;\n' % (key, defines[key]) 242 else: 243 return ' // defines[%s] not set\n' % key 244 245def java_defines_string(keys): 246 return '\n'.join( map(java_define_string, sorted(keys))) 247 248def create_command_builder(commands): 249 global gen_path 250 parser.assert_dir(gen_path) 251 252 outfile = '%s/command_builder.py' % gen_path 253 254 with open(outfile, 'wt') as fout: 255 256 fout.write(command_builder_header) 257 258 for command in commands: 259 (command_name, ogf, ocf, format, params) = command 260 create_command_python(fout, command_name, ogf, ocf, format, params); 261 mark_define_as_used(ogf) 262 mark_define_as_used(ocf) 263 264 # fout.write('\n # defines used\n\n') 265 # for key in sorted(defines_used): 266 # fout.write(java_define_string(key)) 267 268def create_event(event_name, format, args): 269 global gen_path 270 global package 271 global java_event_template 272 273 param_read = { 274 '1' : 'return Util.readByte(data, %u);', 275 'J' : 'return Util.readByte(data, %u);', 276 '2' : 'return Util.readBt16(data, %u);', 277 'H' : 'return Util.readBt16(data, %u);', 278 'L' : 'return Util.readBt16(data, %u);', 279 '3' : 'return Util.readBt24(data, %u);', 280 '4' : 'return Util.readBt32(data, %u);', 281 'B' : 'return Util.readBdAddr(data, %u);', 282 'X' : 'return Util.readGattService(data, %u);', 283 'Y' : 'return Util.readGattCharacteristic(data, %u);', 284 'Z' : 'return Util.readGattCharacteristicDescriptor(data, %u);', 285 'T' : 'int offset = %u; \n return Util.getText(data, offset, getPayloadLen()-offset);', 286 'N' : 'return Util.getText(data, %u, 248);', 287 'D' : 'Util.storeBytes(data, %u, 8);', 288 'Q' : 'Util.storeBytes(data, %u, 32);', 289 # 'E' : 'Util.storeBytes(data, %u, 240);', 290 # 'P' : 'Util.storeBytes(data, %u, 16);', 291 # 'A' : 'Util.storeBytes(data, %u, 31);', 292 # 'S' : 'Util.storeBytes(data, %u);' 293 } 294 295 gen_event_path = '%s/event' % gen_path 296 outfile = '%s/%s.java' % (gen_event_path, event_name) 297 with open(outfile, 'wt') as fout: 298 offset = 2 299 getters = '' 300 length_name = '' 301 for f, arg in zip(format, args): 302 # just remember name 303 if f in ['L','J']: 304 length_name = parser.camel_case(arg) 305 if f == 'R': 306 # remaining data 307 access = java_event_getter_remaining_data.format(offset) 308 size = 0 309 elif f == 'V': 310 access = java_event_getter_data.format(length_name, offset) 311 size = 0 312 elif f in ['D', 'Q']: 313 size = size_for_type(f) 314 access = java_event_getter_data_fixed.format(size, offset) 315 else: 316 access = param_read[f] % offset 317 size = size_for_type(f) 318 getters += java_event_getter.format(java_type_for_btstack_type(f), parser.camel_case(arg), access) 319 offset += size 320 to_string_args = '' 321 for arg in args: 322 to_string_args += ' t.append(", %s = ");\n' % arg 323 to_string_args += ' t.append(get%s());\n' % parser.camel_case(arg) 324 to_string_method = java_event_to_string.format(event_name, to_string_args) 325 fout.write(java_event_template.format(package, event_name, getters, to_string_method)) 326 327def event_supported(event_name): 328 parts = event_name.split('_') 329 return parts[0] in ['ATT', 'BTSTACK', 'DAEMON', 'L2CAP', 'RFCOMM', 'SDP', 'GATT', 'GAP', 'HCI', 'SM', 'BNEP'] 330 331def class_name_for_event(event_name): 332 return parser.camel_case(event_name.replace('SUBEVENT','EVENT')) 333 334def create_events(events): 335 global gen_path 336 gen_path_events = gen_path + '/event' 337 parser.assert_dir(gen_path_events) 338 339 for event_type, event_name, format, args in events: 340 if not event_supported(event_name): 341 continue 342 class_name = class_name_for_event(event_name) 343 create_event(class_name, format, args) 344 345 346def create_event_factory(events, subevents, defines): 347 global gen_path 348 global package 349 global java_event_factory_event 350 global java_event_factory_template 351 352 outfile = '%s/EventFactory.java' % gen_path 353 354 cases = '' 355 for event_type, event_name, format, args in events: 356 event_name = parser.camel_case(event_name) 357 cases += java_event_factory_event.format(event_type, event_name) 358 subcases = '' 359 for event_type, event_name, format, args in subevents: 360 if not event_supported(event_name): 361 continue 362 class_name = class_name_for_event(event_name) 363 print class_name 364 subcases += java_event_factory_subevent.format(event_type, class_name) 365 366 with open(outfile, 'wt') as fout: 367 defines_text = java_defines_string(defines) 368 fout.write(java_event_factory_template.format(package, defines_text, cases, subcases)) 369 370# find root 371btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..') 372gen_path = btstack_root + '/platform/daemon/binding/python/btstack/' 373 374 375# read defines from hci_cmds.h and hci.h 376defines = parser.parse_defines() 377 378# parse commands 379commands = parser.parse_commands() 380 381# parse bluetooth.h to get used events 382# (events, le_events, event_types) = parser.parse_events() 383 384# create events, le meta events, event factory, and 385# create_events(events) 386# create_events(le_events) 387# create_event_factory(events, le_events, event_types) 388create_command_builder(commands) 389 390# done 391print('Done!') 392