#!/usr/bin/env python3 # BlueKitchen GmbH (c) 2018 import glob import re import sys import os import btstack_parser as parser print(''' Python binding generator for BTstack Server Copyright 2018, BlueKitchen GmbH ''') # com.bluekitchen.btstack.BTstack.java templates command_builder_header = \ '''#!/usr/bin/env python3 import struct def opcode(ogf, ocf): return ocf | (ogf << 10) def pack24(value): return struct.pack("B", value & 0xff) + struct.pack("<H", value >> 8) def name248(str): arg = str.encode('utf-8') return arg[:248] + bytes(248-len(arg)) # Command Builder class CommandBuilder(object): def __init__(self): pass def send_command(command): return FALSE ''' command_builder_command = ''' def {name}(self, {args}): cmd_args = bytes() {args_builder} cmd = struct.pack("<HB", opcode(self.{ogf}, self.{ocf}), len(cmd_args)) + cmd_args return self.send_hci_command(cmd) ''' # com.bluekitchen.btstack.EventFactory template event_factory_template = \ ''' # dictionary to map hci event types to event classes event_class_for_type = {{ {1}}} # dictionary to map hci le event types to event classes le_event_class_for_type = {{ {2}}} # list of all event types - not actually used {0} def event_for_payload(payload): event_type = payload[0] event_class = btstack.btstack_types.Event # LE Subevent if event_type == 0x3e: subevent_type = payload[2] event_class = le_event_class_for_type.get(subevent_type, event_class) else: event_class = event_class_for_type.get(event_type, event_class) return event_class(payload) ''' event_factory_event = \ ''' {0} : {1}, ''' event_factory_subevent = \ ''' {0} : {1}, ''' event_header = ''' import struct import btstack.btstack_types def hex_string(bytes): return " ".join([('%02x' % a) for a in bytes]) ''' event_template = \ ''' class {0}(btstack.btstack_types.Event): def __init__(self, payload): super().__init__(payload) {1} {2} ''' event_getter = \ ''' def get_{0}(self): {1} ''' event_getter_data = '''return self.payload[{offset}:{offset}+self.get_{length_name}()] ''' event_getter_data_fixed = \ '''return self.payload[{offset}:{offset}+{size}] ''' event_to_string = \ '''def __repr__(self): repr = '{0} < type=0x%02x' % self.get_event_type() {1} repr += " >" return repr ''' # global variables/defines package ='com.bluekitchen.btstack' gen_path = 'gen/' + package.replace('.', '/') defines = dict() defines_used = set() def size_for_type(type): param_sizes = { '1' : 1, '2' : 2, '3' : 3, '4' : 4, 'H' : 2, 'B' : 6, 'D' : 8, 'E' : 240, 'N' : 248, 'P' : 16, 'A' : 31, 'S' : -1, 'V': -1, 'J' : 1, 'L' : 2, 'Q' : 32, 'K' : 16, 'U' : 16, 'X' : 20, 'Y' : 24, 'Z' : 18, 'T':-1} return param_sizes[type] def create_command_python(fout, name, ogf, ocf, format, params): global command_builder_command ind = ' ' param_store = { '1' : 'cmd_args += struct.pack("B", %s)', 'J' : 'cmd_args += struct.pack("B", %s)', '2' : 'cmd_args += struct.pack("<H", %s)', 'H' : 'cmd_args += struct.pack("<H", %s)', 'L' : 'cmd_args += struct.pack("<H", %s)', '3' : 'cmd_args += pack24(%s)', '4' : 'cmd_args += struct.pack("<H", %s)', 'N' : 'cmd_args += name248(%s)', 'B' : 'cmd_args += %s.get_bytes()', 'U' : 'cmd_args += %s.get_bytes()', 'X' : 'cmd_args += %s.get_bytes()', 'Y' : 'cmd_args += %s.get_bytes()', 'Z' : 'cmd_args += %s.get_bytes()', 'S' : 'cmd_args += %s', # TODO: support serialization for these 'D' : '# D / TODO Util.storeBytes(command, offset, %s, 8)', 'E' : '# E / TODO Util.storeBytes(command, offset, %s, 240)', 'P' : '# P / TODO Util.storeBytes(command, offset, %s, 16)', 'Q' : '# Q / TODO Util.storeBytes(command, offset, %s, 32)', 'K' : '# Q / TODO Util.storeBytes(command, offset, %s, 32)', 'A' : '# A / TODO Util.storeBytes(command, offset, %s, 31)', } # method arguments arg_counter = 1 args = [] for param_type, arg_name in zip(format, params): arg_size = size_for_type(param_type) arg = (param_type, arg_size, arg_name) args.append(arg) arg_counter += 1 # method argument declaration args2 = [] for arg in args: args2.append(arg[2]) args_string = ', '.join(args2) # command size (opcode, len) size_fixed = 3 size_var = '' for arg in args: size = arg[1] if size > 0: size_fixed += size else: size_var += ' + %s.length' % arg[2] size_string = '%u%s' % (size_fixed, size_var) store_params = '' length_name = '' for (param_type, arg_size, arg_name) in args: if param_type in ['L', 'J']: length_name = arg_name if param_type == 'V': store_params += ind + 'Util.storeBytes(command, offset, %s, %s);' % (arg_name, length_name) + '\n'; length_name = '' else: store_params += ind + (param_store[param_type] % arg_name) + '\n'; size = arg_size fout.write( command_builder_command.format(name=name, args=args_string, ogf=ogf, ocf=ocf, args_builder=store_params)) def mark_define_as_used(term): if term.startswith('0'): return defines_used.add(term) def python_define_string(key): global defines if key in defines: return ' %s = %s\n' % (key, defines[key]) else: return ' # defines[%s] not set\n' % key def python_defines_string(keys): return '\n'.join( map(python_define_string, sorted(keys))) def create_command_builder(commands): global gen_path parser.assert_dir(gen_path) outfile = '%s/command_builder.py' % gen_path with open(outfile, 'wt') as fout: fout.write(command_builder_header) for command in commands: (command_name, ogf, ocf, format, params) = command create_command_python(fout, command_name, ogf, ocf, format, params); mark_define_as_used(ogf) mark_define_as_used(ocf) fout.write('\n # defines used\n\n') for key in sorted(defines_used): fout.write(python_define_string(key)) def create_event(fout, event_name, format, args): global gen_path global event_template param_read = { '1' : 'return self.payload[{offset}]', 'J' : 'return self.payload[{offset}]', '2' : 'return struct.unpack("<H", self.payload[{offset} : {offset}+2])', 'H' : 'return struct.unpack("<H", self.payload[{offset} : {offset}+2])', 'L' : 'return struct.unpack("<H", self.payload[{offset} : {offset}+2])', '3' : 'return btstack.btstack_types.unpack24(self.payload[{offset}:3])', '4' : 'return struct.unpack("<I", self.payload[{offset} : {offset}+4])', 'B' : 'data = bytearray(self.payload[{offset}:{offset}+6]); data.reverse(); return btstack.btstack_types.BD_ADDR(data)', 'X' : 'return btstack.btstack_types.GATTService(self.payload[{offset}:20])', 'Y' : 'return btstack.btstack_types.GATTCharacteristic(self.payload[{offset}:24])', 'Z' : 'return btstack.btstack_types.GATTCharacteristicDescriptor(self.payload[{offset}:18])', 'T' : 'return self.payload[{offset}:].decode("utf-8")', 'N' : 'return self.payload[{offset}:{offset}+248].decode("utf-8")', # 'D' : 'Util.storeBytes(self.payload, %u, 8);', # 'Q' : 'Util.storeBytes(self.payload, %u, 32);', # 'E' : 'Util.storeBytes(data, %u, 240);', # 'P' : 'Util.storeBytes(data, %u, 16);', # 'A' : 'Util.storeBytes(data, %u, 31);', # 'S' : 'Util.storeBytes(data, %u);' 'R' : 'return self.payload[{offset}:]', } offset = 2 getters = '' length_name = '' for f, arg in zip(format, args): # just remember name if f in ['L','J']: length_name = arg.lower() if f == 'R': # remaining data access = param_read[f].format(offset=offset) size = 0 elif f == 'V': access = event_getter_data.format(length_name=length_name, offset=offset) size = 0 elif f in ['D', 'Q', 'K']: size = size_for_type(f) access = event_getter_data_fixed.format(size=size, offset=offset) else: access = param_read[f].format(offset=offset) size = size_for_type(f) getters += event_getter.format(arg.lower(), access) offset += size to_string_args = '' for f, arg in zip(format, args): to_string_args += ' repr += ", %s = "\n' % arg if f in ['1','2','3','4','H']: to_string_args += ' repr += hex(self.get_%s())\n' % arg.lower() elif f in ['R', 'V', 'D', 'Q']: to_string_args += ' repr += hex_string(self.get_%s())\n' % arg.lower() else: to_string_args += ' repr += str(self.get_%s())\n' % arg.lower() to_string_method = event_to_string.format(event_name, to_string_args) fout.write('# %s - %s' % (event_name, format)) fout.write(event_template.format(event_name, getters, to_string_method)) def event_supported(event_name): parts = event_name.split('_') return parts[0] in ['ATT', 'BTSTACK', 'DAEMON', 'L2CAP', 'RFCOMM', 'SDP', 'GATT', 'GAP', 'HCI', 'SM', 'BNEP'] def class_name_for_event(event_name): return parser.camel_case(event_name.replace('SUBEVENT','EVENT')) def create_events(fout, events): global gen_path gen_path_events = gen_path + '/event' parser.assert_dir(gen_path_events) for event_type, event_name, format, args in events: if not event_supported(event_name): continue class_name = class_name_for_event(event_name) create_event(fout, class_name, format, args) def create_event_factory(events, subevents, defines): global gen_path global event_factory_event global event_factory_template outfile = '%s/event_factory.py' % gen_path cases = '' for event_type, event_name, format, args in events: event_name = parser.camel_case(event_name) cases += event_factory_event.format(event_type, event_name) subcases = '' for event_type, event_name, format, args in subevents: if not event_supported(event_name): continue class_name = class_name_for_event(event_name) subcases += event_factory_subevent.format(event_type, class_name) with open(outfile, 'wt') as fout: # header fout.write(event_header) # event classes create_events(fout, events) create_events(fout, subevents) # defines_text = '' # python_defines_string(,defines) fout.write(event_factory_template.format(defines_text, cases, subcases)) # find root btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..') gen_path = btstack_root + '/platform/daemon/binding/python/btstack/' # read defines from hci_cmds.h and hci.h defines = parser.parse_defines() # parse commands commands = parser.parse_daemon_commands(camel_case=False) # parse bluetooth.h to get used events (events, le_events, event_types) = parser.parse_events() # create events, le meta events, event factory, and create_event_factory(events, le_events, event_types) create_command_builder(commands) # done print('Done!')