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