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