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