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