/
generate_modules.py
187 lines (146 loc) · 5.88 KB
/
generate_modules.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""Generate Python modules for Bluetooth numbers."""
from __future__ import annotations
import json
import re
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
DATA_DIR = "data"
BLUETOOTH_NUMBERS_DIR = f"{DATA_DIR}/bluetooth-numbers-database/v1"
TEMPLATE_DIR = "templates"
CODE_DIR = "src/bluetooth_numbers"
UUID_TEMPLATE = "uuids.py.jinja"
CIC_TEMPLATE = "companies.py.jinja"
OUI_TEMPLATE = "ouis.py.jinja"
OUI_RE = re.compile(r"^([0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2})\s*\(hex\)\s+(.*)\s*$")
file_loader = FileSystemLoader(TEMPLATE_DIR)
env = Environment(loader=file_loader, autoescape=False) # noqa: S701
def generate_uuid16_dictionary(kind: str) -> dict[int, str]:
"""Generate 16-bit UUID dictionary for a module.
Args:
kind (str): Should be "sdo_service".
Returns:
dict[int, str]: A dict with 16-bit UUIDs as keys.
"""
uuid16_dict = {}
with (Path(DATA_DIR) / f"{kind}_uuids.json").open() as json_file:
json_data = json.loads(json_file.read())
for number in json_data:
name = number["name"]
uuid = number["uuid"]
uuid16_dict[uuid] = name
return uuid16_dict
def generate_uuid_dictionaries(kind: str) -> tuple[dict[int, str], dict[str, str]]:
"""Generate UUID dictionaries for a module.
Args:
kind (str): Should be "service", "characteristic", or "descriptor".
Returns:
tuple[dict[int, str], dict[str, str]]: A tuple of dicts. The first dict
has 16-bit UUIDs as keys, the second one 128-bit UUIDs.
"""
uuid16_dict = {}
uuid128_dict = {}
with (Path(BLUETOOTH_NUMBERS_DIR) / f"{kind}_uuids.json").open() as json_file:
json_data = json.loads(json_file.read())
for number in json_data:
name = number["name"]
uuid = number["uuid"]
if len(uuid) == 4: # noqa: PLR2004
uuid16_dict[uuid] = name
else:
uuid128_dict[uuid] = name
return uuid16_dict, uuid128_dict
def generate_uuid_module(
kind: str,
uuid16_dict: dict[int, str],
uuid128_dict: dict[str, str],
) -> None:
"""Generate Python module for UUIDs.
Args:
kind (str): Should be "service", "characteristic", or "descriptor".
uuid16_dict (dict[int, str]): Dict with 16-bit UUIDs as keys.
uuid128_dict (dict[str, str]): Dict with 128-bit UUIDs as keys.
"""
template = env.get_template(UUID_TEMPLATE)
with (Path(CODE_DIR) / f"_{kind}s.py").open("w") as python_file:
python_file.write(
template.render(uuids16=uuid16_dict, uuids128=uuid128_dict, uuid_dict=kind),
)
def generate_cic_dictionary() -> dict[str, str]:
"""Generate Company ID Code dictionary for a module.
Returns:
dict[str, str]: A dict with CIC keys and their name.
"""
cic_dict = {}
with (Path(BLUETOOTH_NUMBERS_DIR) / "company_ids.json").open() as json_file:
json_data = json.loads(json_file.read())
for number in json_data:
code = f"{number['code']:#06x}"
name = number["name"].replace('"', '\\"')
cic_dict[code] = name
return cic_dict
def generate_cic_module(cic_dict: dict[str, str]) -> None:
"""Generate Python module for Company ID Codes.
Args:
cic_dict (dict[str, str]): The dict with CICs to generate a Python
module for.
"""
template = env.get_template(CIC_TEMPLATE)
with (Path(CODE_DIR) / "_companies.py").open("w") as python_file:
python_file.write(template.render(cics=cic_dict))
def generate_oui_dictionary() -> dict[str, str]:
"""Generate OUI dictionary for a module.
Returns:
dict[str, str]: A dict with OUI prefixes and their name.
"""
oui_dict = {}
with (Path(DATA_DIR) / "oui.txt").open() as txt_file:
for line in txt_file:
extracted = OUI_RE.match(line)
if extracted:
oui_dict[extracted.group(1).replace("-", ":")] = extracted.group(2)
return oui_dict
def generate_oui_module(oui_dict: dict[str, str]) -> None:
"""Generate Python module for OUIs.
Args:
oui_dict (dict[str, str]): The dict with OUIs to generate a Python
module for.
"""
template = env.get_template(OUI_TEMPLATE)
with (Path(CODE_DIR) / "_ouis.py").open("w") as python_file:
python_file.write(template.render(ouis=oui_dict))
if __name__ == "__main__":
# Generate module for service UUIDs
service_uuid16, service_uuid128 = generate_uuid_dictionaries("service")
member_service_uuid16 = generate_uuid16_dictionary("member_service")
sdo_service_uuid16 = generate_uuid16_dictionary("sdo_service")
# Don't let the UUIDs from the Assigned Numbers document overwrite the ones from the
# Bluetooth Numbers Database because the latter has the names of the services, which
# give more information than the names of the companies in the Assigned Numbers
# document.
service_uuid16.update(
{
key: value
for key, value in member_service_uuid16.items()
if key not in service_uuid16
},
)
service_uuid16.update(sdo_service_uuid16)
generate_uuid_module("service", service_uuid16, service_uuid128)
# Generate module for characteristic UUIDs
characteristic_uuid16, characteristic_uuid128 = generate_uuid_dictionaries(
"characteristic",
)
generate_uuid_module(
"characteristic",
characteristic_uuid16,
characteristic_uuid128,
)
# Generate module for descriptor UUIDs
descriptor_uuid16, descriptor_uuid128 = generate_uuid_dictionaries("descriptor")
generate_uuid_module("descriptor", descriptor_uuid16, descriptor_uuid128)
# Generate module for Company ID Codes
cics = generate_cic_dictionary()
generate_cic_module(cics)
# Generate module for OUIs
ouis = generate_oui_dictionary()
generate_oui_module(ouis)