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 201 # method argument declaration 202 args2 = [] 203 for arg in args: 204 args2.append(arg[2]) 205 args_string = ', '.join(args2) 206 207 # command size (opcode, len) 208 size_fixed = 3 209 size_var = '' 210 for arg in args: 211 size = arg[1] 212 if size > 0: 213 size_fixed += size 214 else: 215 size_var += ' + %s.length' % arg[2] 216 size_string = '%u%s' % (size_fixed, size_var) 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 python_define_string(key): 239 global defines 240 if key in defines: 241 return ' %s = %s\n' % (key, defines[key]) 242 else: 243 return ' # defines[%s] not set\n' % key 244 245def python_defines_string(keys): 246 return '\n'.join( map(python_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(python_define_string(key)) 267 268def create_event(fout, 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 offset = 2 296 getters = '' 297 length_name = '' 298 for f, arg in zip(format, args): 299 # just remember name 300 if f in ['L','J']: 301 length_name = parser.camel_case(arg) 302 if f == 'R': 303 # remaining data 304 access = java_event_getter_remaining_data.format(offset) 305 size = 0 306 elif f == 'V': 307 access = java_event_getter_data.format(length_name, offset) 308 size = 0 309 elif f in ['D', 'Q']: 310 size = size_for_type(f) 311 access = java_event_getter_data_fixed.format(size, offset) 312 else: 313 access = param_read[f] % offset 314 size = size_for_type(f) 315 getters += java_event_getter.format(java_type_for_btstack_type(f), parser.camel_case(arg), access) 316 offset += size 317 to_string_args = '' 318 for arg in args: 319 to_string_args += ' t.append(", %s = ");\n' % arg 320 to_string_args += ' t.append(get%s());\n' % parser.camel_case(arg) 321 to_string_method = java_event_to_string.format(event_name, to_string_args) 322 fout.write(java_event_template.format(package, event_name, getters, to_string_method)) 323 324def event_supported(event_name): 325 parts = event_name.split('_') 326 return parts[0] in ['ATT', 'BTSTACK', 'DAEMON', 'L2CAP', 'RFCOMM', 'SDP', 'GATT', 'GAP', 'HCI', 'SM', 'BNEP'] 327 328def class_name_for_event(event_name): 329 return parser.camel_case(event_name.replace('SUBEVENT','EVENT')) 330 331def create_events(fout, events): 332 global gen_path 333 gen_path_events = gen_path + '/event' 334 parser.assert_dir(gen_path_events) 335 336 for event_type, event_name, format, args in events: 337 if not event_supported(event_name): 338 continue 339 class_name = class_name_for_event(event_name) 340 create_event(fout, class_name, format, args) 341 342 343def create_event_factory(events, subevents, defines): 344 global gen_path 345 global package 346 global java_event_factory_event 347 global java_event_factory_template 348 349 outfile = '%s/event_factory.py' % gen_path 350 351 cases = '' 352 for event_type, event_name, format, args in events: 353 event_name = parser.camel_case(event_name) 354 cases += java_event_factory_event.format(event_type, event_name) 355 subcases = '' 356 for event_type, event_name, format, args in subevents: 357 if not event_supported(event_name): 358 continue 359 class_name = class_name_for_event(event_name) 360 subcases += java_event_factory_subevent.format(event_type, class_name) 361 362 with open(outfile, 'wt') as fout: 363 # event classes 364 create_events(fout, events) 365 create_events(fout, subevents) 366 # 367 defines_text = python_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(camel_case=False) 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 385create_event_factory(events, le_events, event_types) 386create_command_builder(commands) 387 388# done 389print('Done!') 390