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