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