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