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