mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-01-01 00:28:18 +00:00
452 lines
14 KiB
Python
Executable File
452 lines
14 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# BlueKtichen GmbH (c) 2014
|
|
|
|
import glob
|
|
import re
|
|
import sys
|
|
import os
|
|
|
|
print '''
|
|
Java binding generator for BTstack
|
|
Copyright 2014, BlueKitchen GmbH
|
|
'''
|
|
|
|
# com.bluekitchen.btstack.BTstack.java templates
|
|
java_btstack_header = \
|
|
'''/**
|
|
* BTstack Client Library
|
|
*/
|
|
|
|
package %s;
|
|
|
|
public class BTstack extends BTstackClient {
|
|
|
|
'''
|
|
java_btstack_command = '''
|
|
public boolean %s(%s){
|
|
// %s
|
|
int command_len = %s;
|
|
byte[] command = new byte[command_len];
|
|
Util.storeBt16(command, 0, Util.opcode(%s, %s));
|
|
int offset = 2;
|
|
Util.storeByte(command, offset, command_len - 3);
|
|
offset++;
|
|
%s
|
|
Packet packet = new Packet(Packet.HCI_COMMAND_PACKET, 0, command, command.length);
|
|
return sendPacket(packet);
|
|
}
|
|
'''
|
|
java_btstack_footer = '''
|
|
}
|
|
'''
|
|
|
|
# com.bluekitchen.btstack.EventFactory template
|
|
java_event_factory_template = \
|
|
'''package {0};
|
|
|
|
import {0}.event.*;
|
|
|
|
public class EventFactory {{
|
|
|
|
/** @brief event codes */
|
|
|
|
{1}
|
|
public static Event eventForPacket(Packet packet){{
|
|
int eventType = Util.readByte(packet.getBuffer(), 0);
|
|
switch (eventType){{
|
|
{2}
|
|
case 0x3e: // LE_META_EVENT
|
|
int subEventType = Util.readByte(packet.getBuffer(), 2);
|
|
switch (subEventType){{
|
|
{3}
|
|
default:
|
|
return new Event(packet);
|
|
}}
|
|
|
|
default:
|
|
return new Event(packet);
|
|
}}
|
|
}}
|
|
}}
|
|
'''
|
|
java_event_factory_event = '''
|
|
case {0}:
|
|
return new {1}(packet);
|
|
'''
|
|
java_event_factory_subevent = '''
|
|
case {0}:
|
|
return new {1}(packet);
|
|
'''
|
|
|
|
# com.bluekitchen.btstack.events.* template
|
|
java_event_template = \
|
|
'''package {0}.event;
|
|
|
|
import {0}.*;
|
|
|
|
public class {1} extends Event {{
|
|
|
|
public {1}(Packet packet) {{
|
|
super(packet);
|
|
}}
|
|
{2}
|
|
{3}
|
|
}}
|
|
'''
|
|
|
|
java_event_getter = \
|
|
'''
|
|
/**
|
|
* @return {1} as {0}
|
|
*/
|
|
public {0} get{1}(){{
|
|
{2}
|
|
}}
|
|
'''
|
|
|
|
java_event_getter_data = \
|
|
'''int len = get{0}();
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(data, {1}, result, 0, len);
|
|
return result;'''
|
|
|
|
java_event_getter_remaining_data = \
|
|
'''int len = getPayloadLen() - {0};
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(data, {0}, result, 0, len);
|
|
return result;'''
|
|
|
|
java_event_to_string = \
|
|
'''
|
|
public String toString(){{
|
|
StringBuffer t = new StringBuffer();
|
|
t.append("{0} < type = ");
|
|
t.append(String.format("0x%02x, ", getEventType()));
|
|
t.append(getEventType());
|
|
{1} t.append(" >");
|
|
return t.toString();
|
|
}}
|
|
'''
|
|
|
|
|
|
# global variables/defines
|
|
package='com.bluekitchen.btstack'
|
|
gen_path = 'gen/' + package.replace('.', '/')
|
|
hci_cmds_h_path = '../include/btstack/hci_cmds.h'
|
|
hci_cmds_c_path = '../src/hci_cmds.c'
|
|
hci_h_path = '../src/hci.h'
|
|
|
|
defines = dict()
|
|
defines_used = set()
|
|
|
|
def assert_dir(path):
|
|
if not os.access(path, os.R_OK):
|
|
os.makedirs(path)
|
|
|
|
def cap(x):
|
|
if x.lower() == 'btstack':
|
|
return 'BTstack'
|
|
acronyms = ['GAP', 'GATT', 'HCI', 'L2CAP', 'LE', 'RFCOMM', 'SM', 'SDP', 'UUID16', 'UUID128']
|
|
if x.upper() in acronyms:
|
|
return x.upper()
|
|
return x.capitalize()
|
|
|
|
def camel_case(name):
|
|
return ''.join(map(cap, name.split('_')))
|
|
|
|
def camel_case_var(name):
|
|
if name in ['uuid128', 'uuid16']:
|
|
return name
|
|
camel = camel_case(name)
|
|
return camel[0].lower() + camel[1:]
|
|
|
|
def read_defines(infile):
|
|
global defines
|
|
with open (infile, 'rb') as fin:
|
|
|
|
for line in fin:
|
|
parts = re.match('#define\s+(\w+)\s+(\w*)',line)
|
|
if parts and len(parts.groups()) == 2:
|
|
(key, value) = parts.groups()
|
|
defines[key] = value
|
|
|
|
def java_type_for_btstack_type(type):
|
|
param_types = { '1' : 'int', '2' : 'int', '3' : 'int', '4' : 'long', 'H' : 'int', 'B' : 'BD_ADDR',
|
|
'D' : 'byte []', 'E' : 'byte [] ', 'N' : 'String' , 'P' : 'byte []', 'A' : 'byte []',
|
|
'R' : 'byte []', 'S' : 'byte []',
|
|
'J' : 'int', 'L' : 'int', 'V' : 'byte []', 'U' : 'BT_UUID',
|
|
'X' : 'GATTService', 'Y' : 'GATTCharacteristic', 'Z' : 'GATTCharacteristicDescriptor',
|
|
'T' : 'String'}
|
|
return param_types[type]
|
|
|
|
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, 'U' : 16, 'X' : 20, 'Y' : 24, 'Z' : 18, 'T':-1}
|
|
return param_sizes[type]
|
|
|
|
def create_command_java(fout, name, ogf, ocf, format, params):
|
|
global java_btstack_command
|
|
|
|
ind = ' '
|
|
param_store = {
|
|
'1' : 'Util.storeByte(command, offset, %s);',
|
|
'J' : 'Util.storeByte(command, offset, %s);',
|
|
'2' : 'Util.storeBt16(command, offset, %s);',
|
|
'H' : 'Util.storeBt16(command, offset, %s);',
|
|
'L' : 'Util.storeBt16(command, offset, %s);',
|
|
'3' : 'Util.storeBt24(command, offset, %s);',
|
|
'4' : 'Util.storeBt32(command, offset, %s);',
|
|
'D' : 'Util.storeBytes(command, offset, %s, 8);',
|
|
'E' : 'Util.storeBytes(command, offset, %s, 240);',
|
|
'P' : 'Util.storeBytes(command, offset, %s, 16);',
|
|
'A' : 'Util.storeBytes(command, offset, %s, 31);',
|
|
'S' : 'Util.storeBytes(command, offset, %s);',
|
|
'B' : 'Util.storeBytes(command, offset, %s.getBytes());',
|
|
'U' : 'Util.storeBytes(command, offset, %s.getBytes());',
|
|
'X' : 'Util.storeBytes(command, offset, %s.getBytes());',
|
|
'Y' : 'Util.storeBytes(command, offset, %s.getBytes());',
|
|
'Z' : 'Util.storeBytes(command, offset, %s.getBytes());',
|
|
'N' : 'Util.storeString(command, offset, %s, 248);',
|
|
}
|
|
# method arguments
|
|
arg_counter = 1
|
|
args = []
|
|
for param_type, arg_name in zip(format, params):
|
|
arg_type = java_type_for_btstack_type(param_type)
|
|
arg_size = size_for_type(param_type)
|
|
arg = (param_type, arg_type, arg_size, arg_name)
|
|
args.append(arg)
|
|
arg_counter += 1
|
|
|
|
# method argument declaration
|
|
args2 = []
|
|
for arg in args:
|
|
args2.append('%s %s' % (arg[1], arg[3]))
|
|
args_string = ', '.join(args2)
|
|
|
|
# command size (opcode, len)
|
|
size_fixed = 3
|
|
size_var = ''
|
|
for arg in args:
|
|
size = arg[2]
|
|
if size > 0:
|
|
size_fixed += size
|
|
else:
|
|
size_var += ' + %s.length' % arg[3]
|
|
size_string = '%u%s' % (size_fixed, size_var)
|
|
|
|
store_params = ''
|
|
|
|
length_name = ''
|
|
for (param_type, arg_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';
|
|
store_params += ind + 'offset += %s;\n' % length_name;
|
|
length_name = ''
|
|
else:
|
|
store_params += ind + (param_store[param_type] % arg_name) + '\n';
|
|
size = arg_size
|
|
if size > 0:
|
|
store_params += ind + 'offset += %u;\n' % arg_size;
|
|
else:
|
|
store_params += ind + 'offset += %s.length;\n' % arg_name
|
|
|
|
fout.write( java_btstack_command % (name, args_string, format, size_string, ogf, ocf, store_params))
|
|
|
|
def mark_define_as_used(term):
|
|
if term.startswith('0'):
|
|
return
|
|
defines_used.add(term)
|
|
|
|
def java_define_string(key):
|
|
global defines
|
|
return ' public static final int %s = %s;\n' % (key, defines[key])
|
|
|
|
def java_defines_string(keys):
|
|
return '\n'.join( map(java_define_string, sorted(keys)))
|
|
|
|
def parse_commands(infile):
|
|
|
|
global gen_path
|
|
assert_dir(gen_path)
|
|
|
|
outfile = '%s/BTstack.java' % gen_path
|
|
|
|
with open(outfile, 'w') as fout:
|
|
|
|
fout.write(java_btstack_header % package)
|
|
|
|
with open (infile, 'rb') as fin:
|
|
|
|
params = []
|
|
for line in fin:
|
|
|
|
parts = re.match('.*@param\s*(\w*)\s*', line)
|
|
if parts and len(parts.groups()) == 1:
|
|
param = parts.groups()[0]
|
|
params.append(camel_case_var(param))
|
|
continue
|
|
|
|
declaration = re.match('const\s+hci_cmd_t\s+(\w+)[\s=]+', line)
|
|
if declaration:
|
|
command_name = camel_case(declaration.groups()[0])
|
|
if command_name.endswith('Cmd'):
|
|
command_name = command_name[:-len('Cmd')]
|
|
continue
|
|
|
|
definition = re.match('\s*OPCODE\\(\s*(\w+)\s*,\s+(\w+)\s*\\)\s*,\s\\"(\w*)\\".*', line)
|
|
if definition:
|
|
(ogf, ocf, format) = definition.groups()
|
|
if len(params) != len(format):
|
|
params = []
|
|
arg_counter = 1
|
|
for f in format:
|
|
arg_name = 'arg%u' % arg_counter
|
|
params.append(arg_name)
|
|
arg_counter += 1
|
|
create_command_java(fout, command_name, ogf, ocf, format, params);
|
|
mark_define_as_used(ogf)
|
|
mark_define_as_used(ocf)
|
|
params = []
|
|
continue
|
|
|
|
fout.write('\n /** defines used */\n\n')
|
|
for key in sorted(defines_used):
|
|
fout.write(java_define_string(key))
|
|
|
|
fout.write(java_btstack_footer)
|
|
|
|
def create_event(event_name, format, args):
|
|
global gen_path
|
|
global package
|
|
global java_event_template
|
|
|
|
param_read = {
|
|
'1' : 'return Util.readByte(data, %u);',
|
|
'J' : 'return Util.readByte(data, %u);',
|
|
'2' : 'return Util.readBt16(data, %u);',
|
|
'3' : 'return Util.readBt24(data, %u);',
|
|
'4' : 'return Util.readBt32(data, %u);',
|
|
'H' : 'return Util.readBt16(data, %u);',
|
|
'L' : 'return Util.readByte(data, %u);',
|
|
'B' : 'return Util.readBdAddr(data, %u);',
|
|
'X' : 'return Util.readGattService(data, %u);',
|
|
'Y' : 'return Util.readGattCharacteristic(data, %u);',
|
|
'Z' : 'return Util.readGattCharacteristicDescriptor(data, %u);',
|
|
'T' : 'int offset = %u; \n return Util.getText(data, offset, getPayloadLen()-offset);',
|
|
'N' : 'return Util.getText(data, %u, 248);',
|
|
# 'D' : 'Util.storeBytes(data, %u, 8);',
|
|
# 'E' : 'Util.storeBytes(data, %u, 240);',
|
|
# 'P' : 'Util.storeBytes(data, %u, 16);',
|
|
# 'A' : 'Util.storeBytes(data, %u, 31);',
|
|
# 'S' : 'Util.storeBytes(data, %u);'
|
|
}
|
|
|
|
gen_event_path = '%s/event' % gen_path
|
|
outfile = '%s/%s.java' % (gen_event_path, event_name)
|
|
with open(outfile, 'w') as fout:
|
|
offset = 2
|
|
getters = ''
|
|
length_name = ''
|
|
for f, arg in zip(format, args):
|
|
# just remember name
|
|
if f in ['L','J']:
|
|
length_name = camel_case(arg)
|
|
if f == 'R':
|
|
# remaining data
|
|
access = java_event_getter_remaining_data.format(offset)
|
|
size = 0
|
|
elif f == 'V':
|
|
access = java_event_getter_data.format(length_name, offset)
|
|
size = 0
|
|
else:
|
|
access = param_read[f] % offset
|
|
size = size_for_type(f)
|
|
getters += java_event_getter.format(java_type_for_btstack_type(f), camel_case(arg), access)
|
|
offset += size
|
|
to_string_args = ''
|
|
for arg in args:
|
|
to_string_args += ' t.append(", %s = ");\n' % arg
|
|
to_string_args += ' t.append(get%s());\n' % camel_case(arg)
|
|
to_string_method = java_event_to_string.format(event_name, to_string_args)
|
|
fout.write(java_event_template.format(package, event_name, getters, to_string_method))
|
|
|
|
def create_events(events):
|
|
global gen_path
|
|
gen_path_events = gen_path + '/event'
|
|
assert_dir(gen_path_events)
|
|
|
|
for event_type, event_name, format, args in events:
|
|
event_name = camel_case(event_name)
|
|
create_event(event_name, format, args)
|
|
|
|
def create_event_factory(events, le_events, defines):
|
|
global gen_path
|
|
global package
|
|
global java_event_factory_event
|
|
global java_event_factory_template
|
|
|
|
outfile = '%s/EventFactory.java' % gen_path
|
|
|
|
cases = ''
|
|
for event_type, event_name, format, args in events:
|
|
event_name = camel_case(event_name)
|
|
cases += java_event_factory_event.format(event_type, event_name)
|
|
subcases = ''
|
|
for event_type, event_name, format, args in le_events:
|
|
event_name = camel_case(event_name)
|
|
subcases += java_event_factory_subevent.format(event_type, event_name)
|
|
|
|
with open(outfile, 'w') as fout:
|
|
defines_text = java_defines_string(defines)
|
|
fout.write(java_event_factory_template.format(package, defines_text, cases, subcases))
|
|
|
|
def parse_events(path):
|
|
global gen_path
|
|
events = []
|
|
le_events = []
|
|
params = []
|
|
event_types = set()
|
|
format = None
|
|
with open (path, 'rb') as fin:
|
|
for line in fin:
|
|
parts = re.match('.*@format\s*(\w*)\s*', line)
|
|
if parts and len(parts.groups()) == 1:
|
|
format = parts.groups()[0]
|
|
parts = re.match('.*@param\s*(\w*)\s*', line)
|
|
if parts and len(parts.groups()) == 1:
|
|
param = parts.groups()[0]
|
|
params.append(param)
|
|
parts = re.match('\s*#define\s+(\w+)\s+(\w*)',line)
|
|
if parts and len(parts.groups()) == 2:
|
|
(key, value) = parts.groups()
|
|
if format != None:
|
|
if key.lower().startswith('hci_subevent_'):
|
|
le_events.append((value, key.lower().replace('hci_subevent_', 'hci_event_'), format, params))
|
|
else:
|
|
events.append((value, key, format, params))
|
|
event_types.add(key)
|
|
params = []
|
|
format = None
|
|
return (events, le_events, event_types)
|
|
|
|
# # read defines from hci_cmds.h and hci.h
|
|
read_defines(hci_cmds_h_path)
|
|
read_defines(hci_h_path)
|
|
|
|
# # parse commands and generate BTstack.java
|
|
parse_commands(hci_cmds_c_path)
|
|
|
|
# parse hci.h to get used events
|
|
(events, le_events, event_types) = parse_events(hci_cmds_h_path)
|
|
|
|
# create events, le meta events, and event factory
|
|
create_events(events)
|
|
create_events(le_events)
|
|
create_event_factory(events, le_events, event_types)
|
|
|
|
# done
|
|
print 'Done!'
|