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