xref: /btstack/tool/btstack_parser.py (revision c9921182ab4b1f83e3e5c671446dca5ffdf45b90)
1#!/usr/bin/env python3
2# BlueKitchen GmbH (c) 2014
3
4import re
5import os
6import sys
7
8# paths
9bluetooth_h_path = 'src/bluetooth.h'
10btstack_defines_h_path = 'src/btstack_defines.h'
11daemon_cmds_c_path = 'platform/daemon/src/daemon_cmds.c'
12daemon_cmds_h_path = 'platform/daemon/src/daemon_cmds.h'
13hci_cmds_c_path = 'src/hci_cmd.c'
14hci_cmds_h_path = 'src/hci_cmd.h'
15hci_h_path = 'src/hci.h'
16
17btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
18print ("BTstack root %s" % btstack_root)
19
20def set_btstack_root(path):
21    global btstack_root
22    btstack_root = path
23
24def assert_dir(path):
25    if not os.access(path, os.R_OK):
26        os.makedirs(path)
27
28def cap(x):
29    if x.lower() == 'btstack':
30        return 'BTstack'
31    acronyms = ['ATT', 'GAP', 'GATT', 'HCI', 'L2CAP', 'LE', 'RFCOMM', 'SM', 'SDP', 'UUID16', 'UUID128', 'HSP', 'HFP', 'ANCS']
32    if x.upper() in acronyms:
33        return x.upper()
34    return x.capitalize()
35
36def camel_case(name):
37    return ''.join(map(cap, name.split('_')))
38
39def camel_case_var(name):
40    if name in ['uuid128', 'uuid16']:
41        return name
42    camel = camel_case(name)
43    return camel[0].lower() + camel[1:]
44
45def read_defines(infile):
46    defines = dict()
47    with open (infile, 'rt') as fin:
48        for line in fin:
49            parts = re.match('#define\s+(\w+)\s+(\w*)',line)
50            if parts and len(parts.groups()) == 2:
51                (key, value) = parts.groups()
52                defines[key] = value
53    return defines
54
55def parse_defines():
56    global btstack_root
57    defines = dict()
58    defines.update(read_defines(btstack_root + '/' + hci_cmds_h_path))
59    defines.update(read_defines(btstack_root + '/' + hci_h_path))
60    defines.update(read_defines(btstack_root + '/' + bluetooth_h_path))
61    defines.update(read_defines(btstack_root + '/' + btstack_defines_h_path))
62    return defines
63
64def my_parse_events(path):
65    events = []
66    subevents = []
67    params = []
68    event_types = set()
69    format = None
70    with open (path, 'rt') as fin:
71        for line in fin:
72            parts = re.match('.*@format\s*(\w*)\s*', line)
73            if parts and len(parts.groups()) == 1:
74                format = parts.groups()[0]
75            parts = re.match('.*@param\s*(\w*)\s*', line)
76            if parts and len(parts.groups()) == 1:
77                param = parts.groups()[0]
78                params.append(param)
79            parts = re.match('\s*#define\s+(\w+)\s+(\w*)',line)
80            if parts and len(parts.groups()) == 2:
81                (key, value) = parts.groups()
82                if format != None:
83                    # renaming needed by Java Binding (... subevents are just enumerated with others due to event factory)
84                    if "_subevent_" in key.lower():
85                        subevents.append((value, key, format, params))
86                    else:
87                        events.append((value, key, format, params))
88                    event_types.add(key)
89                    params = []
90                    format = None
91    return (events, subevents, event_types)
92
93def parse_events():
94    global btstack_root
95
96    # parse bluetooth.h to get used events
97    (bluetooth_events, bluetooth_subevents, bluetooth_event_types) = my_parse_events(btstack_root + '/' + bluetooth_h_path)
98
99    # parse btstack_defines to get events
100    (btstack_events, btstack_subevents, btstack_event_types) = my_parse_events(btstack_root + '/' + btstack_defines_h_path)
101
102    # concat lists
103    (events, subvents, event_types) = (bluetooth_events + btstack_events, bluetooth_subevents + btstack_subevents, bluetooth_event_types | btstack_event_types)
104
105    return (events, subvents, event_types)
106def my_parse_opcodes(infile, convert_to_camel_case):
107    opcodes = {}
108    with open (infile, 'rt') as fin:
109        for line in fin:
110            definition = re.match('\s*(DAEMON_OPCODE_\w+)\s*=\s*DAEMON_OPCODE\s*\(\s*(\w+)\s*\).*', line)
111            if definition:
112                (opcode, daemon_ocf) = definition.groups()
113                # opcodes.append((opcode, 'OGF_BTSTACK', daemon_ocf))
114                opcodes[opcode] = ('OGF_BTSTACK', daemon_ocf)
115            definition = re.match('\s*(HCI_OPCODE_\w+)\s*=\s*HCI_OPCODE\s*\(\s*(\w+)\s*,\s*(\w+)\s*\).*', line)
116            if definition:
117                (opcode, ogf, ocf) = definition.groups()
118                # opcodes.append((opcode, ogf, ocf))
119                opcodes[opcode] = (ogf, ocf)
120    return opcodes
121
122def parse_opcodes(camel_case=True):
123    global btstack_root
124    opcodes = {}
125    opcodes.update(my_parse_opcodes(btstack_root + '/' + hci_cmds_h_path, camel_case))
126    opcodes.update(my_parse_opcodes(btstack_root + '/' + daemon_cmds_h_path, camel_case))
127    return opcodes
128
129def my_parse_commands(infile, opcodes, convert_to_camel_case):
130    commands = []
131    with open (infile, 'rt') as fin:
132
133        params = []
134        for line in fin:
135
136            parts = re.match('.*@param\s*(\w*)\s*', line)
137            if parts and len(parts.groups()) == 1:
138                param = parts.groups()[0]
139                if convert_to_camel_case:
140                    param = camel_case_var(param)
141                else:
142                    param = param.lower()
143                params.append(param)
144                continue
145
146            declaration = re.match('const\s+hci_cmd_t\s+(\w+)[\s=]+', line)
147            if declaration:
148                command_name = declaration.groups()[0]
149                # drop _cmd suffix for daemon commands
150                if command_name.endswith('_cmd'):
151                    command_name = command_name[:-len('_cmd')]
152                if convert_to_camel_case:
153                    command_name = camel_case(command_name)
154                else:
155                    command_name = command_name.lower()
156                continue
157
158            # HCI_OPCODE or DAEMON_OPCODE definition
159            definition = re.match('\s*(HCI_OPCODE_\w+|DAEMON_OPCODE_\w+)\s*,\s\\"(\w*)\\".*', line)
160            if definition:
161                (opcode, format) = definition.groups()
162                if len(params) != len(format):
163                    params = []
164                    arg_counter = 1
165                    for f in format:
166                        arg_name = 'arg%u' % arg_counter
167                        params.append(arg_name)
168                        arg_counter += 1
169                (ogf, ocf) = opcodes[opcode]
170                commands.append((command_name, ogf, ocf, format, params))
171                params = []
172                continue
173
174    return commands
175
176def parse_commands(camel_case=True):
177    global btstack_root
178    opcodes = parse_opcodes(camel_case)
179
180    commands = []
181    commands += my_parse_commands(btstack_root + '/' + hci_cmds_c_path, opcodes, camel_case)
182    commands += my_parse_commands(btstack_root + '/' + daemon_cmds_c_path, opcodes, camel_case)
183    return commands
184
185def parse_daemon_commands(camel_case=True):
186    global btstack_root
187    opcodes = parse_opcodes(camel_case)
188    return my_parse_commands(btstack_root + '/' + daemon_cmds_c_path, opcodes, camel_case)
189
190def print_opcode_enum(commands):
191    print("typedef enum {")
192    for command in commands:
193        (name, opcode, format, params) = command
194        print("    DAEMON_OPCODE_%s = HCI_OPCODE (%s, %s)," % (name.upper(), ogf, ocf))
195    print("} daemon_opcode_t;")
196
197
198