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