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