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