-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathapp.py
More file actions
287 lines (240 loc) · 9.09 KB
/
app.py
File metadata and controls
287 lines (240 loc) · 9.09 KB
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
import json
import logging
import os
import shlex
import subprocess
import sys
from typing import Any, ClassVar, NamedTuple
from pytest_embedded.app import App
class FlashFile(NamedTuple):
offset: int
file_path: str
encrypted: bool = False
class IdfApp(App):
"""
Idf App class
Attributes:
elf_file (str): elf file path
flash_args (dict[str, Any]): dict of flasher_args.json
flash_files (list[FlashFile]): list of (offset, file path, encrypted) of files need to be flashed in
flash_settings (dict[str, Any]): dict of flash settings
"""
XTENSA_TARGETS: ClassVar[list[str]] = ['esp32', 'esp32s2', 'esp32s3']
RISCV32_TARGETS: ClassVar[list[str]] = [
'esp32c3',
'esp32c2',
'esp32c6',
'esp32c5',
'esp32p4',
'esp32h2',
'esp32c61',
]
FLASH_ARGS_FILENAME = 'flash_args'
FLASH_PROJECT_ARGS_FILENAME = 'flash_project_args'
FLASH_ARGS_JSON_FILENAME = 'flasher_args.json'
def __init__(
self,
*args,
part_tool: str | None = None,
**kwargs,
):
super().__init__(*args, **kwargs)
# Optional info
self._sdkconfig = None
self._target = None
# the partition table is used for nvs
self._parttool = part_tool
self._partition_table = None
if not self.binary_path:
logging.debug('Binary path not specified, skipping parsing app...')
return
# Required if binary path exists
self.elf_file = self._get_elf_file()
# loadable elf file skip the rest of these
# TODO to be improved in #186
# 5.1 changed from APP_BUILD_TYPE_ELF_RAM to APP_BUILD_TYPE_RAM
# keep backward compatibility
if self.sdkconfig.get('APP_BUILD_TYPE_ELF_RAM') or self.sdkconfig.get('APP_BUILD_TYPE_RAM'):
self.is_loadable_elf = True
else:
self.is_loadable_elf = False
self.bin_file = self._get_bin_file()
self.flash_args, self.flash_files, self.flash_settings = {}, [], {}
if not self.is_loadable_elf:
self.flash_args, self.flash_files, self.flash_settings = self._parse_flash_args_json()
@property
def parttool_path(self) -> str:
"""
Returns:
Partition tool path
"""
parttool_filepath = self._parttool or os.path.join(
os.getenv('IDF_PATH', ''),
'components',
'partition_table',
'gen_esp32part.py',
)
if os.path.isfile(parttool_filepath):
return os.path.realpath(parttool_filepath)
raise ValueError('Partition Tool not found. (Default: $IDF_PATH/components/partition_table/gen_esp32part.py)')
@property
def sdkconfig(self) -> dict[str, Any]:
"""
Returns:
dict contains all k-v pairs from the sdkconfig file
"""
if self._sdkconfig is not None:
return self._sdkconfig
sdkconfig_json_path = os.path.join(self.binary_path or '', 'config', 'sdkconfig.json')
if not os.path.isfile(sdkconfig_json_path):
logging.warning(f"{sdkconfig_json_path} doesn't exist. Skipping...")
self._sdkconfig = {}
else:
self._sdkconfig = json.load(open(sdkconfig_json_path))
return self._sdkconfig
@property
def target(self) -> str:
"""
Returns:
target chip type
"""
if self.sdkconfig:
return self.sdkconfig.get('IDF_TARGET', 'esp32')
else:
return self.flash_args.get('extra_esptool_args', {}).get('chip', 'esp32')
@property
def is_riscv32(self):
if self.sdkconfig.get('IDF_TARGET_ARCH_RISCV'):
return True
if self.target in self.RISCV32_TARGETS:
return True
return False
@property
def is_xtensa(self):
if self.sdkconfig.get('IDF_TARGET_ARCH_XTENSA'):
return True
if self.target in self.XTENSA_TARGETS:
return True
return False
@property
def partition_table(self) -> dict[str, Any]:
"""
Returns:
partition table dict generated by the partition tool
"""
if self._partition_table is not None:
return self._partition_table
partition_file = os.path.join(
self.binary_path,
self.flash_args.get('partition_table', self.flash_args.get('partition-table', {})).get('file', ''),
)
process = subprocess.Popen(
[sys.executable, self.parttool_path, partition_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, _ = process.communicate()
raw_data = stdout.decode() if isinstance(stdout, bytes) else stdout
partition_table = {}
for line in raw_data.splitlines():
if line[0] != '#':
try:
_name, _type, _subtype, _offset, _size, _flags = line.split(',')
if _size[-1] == 'K':
_size = int(_size[:-1]) * 1024
elif _size[-1] == 'M':
_size = int(_size[:-1]) * 1024 * 1024
else:
_size = int(_size)
_offset = int(_offset, 0)
except ValueError:
continue
partition_table[_name] = {
'type': _type,
'subtype': _subtype,
'offset': _offset,
'size': _size,
'flags': _flags,
}
self._partition_table = partition_table
return self._partition_table
def _get_elf_file(self) -> str | None:
for fn in os.listdir(self.binary_path):
if os.path.splitext(fn)[-1] == '.elf':
return os.path.realpath(os.path.join(self.binary_path, fn))
return None
def _get_bin_file(self) -> str | None:
for fn in os.listdir(self.binary_path):
if os.path.splitext(fn)[-1] == '.bin':
return os.path.realpath(os.path.join(self.binary_path, fn))
return None
@property
def write_flash_args(self):
"""
Returns:
list of flash args
"""
flash_args_filepath = None
for fn in os.listdir(self.binary_path):
if fn in [self.FLASH_PROJECT_ARGS_FILENAME, self.FLASH_ARGS_FILENAME]:
flash_args_filepath = os.path.realpath(os.path.join(self.binary_path, fn))
break
if flash_args_filepath:
with open(flash_args_filepath) as fr:
return shlex.split(fr.read().strip())
# generate it from flasher_args.json
if 'write_flash_args' in self.flash_args and 'flash_files' in self.flash_args:
return self.flash_args['write_flash_args'] + [
item for pair in self.flash_args['flash_files'].items() for item in pair
]
else:
raise ValueError(
f'write_flash_args and flash_files fields are not found in {self.FLASH_ARGS_JSON_FILENAME}'
)
def _parse_flash_args_json(
self,
) -> tuple[dict[str, Any], list[FlashFile], dict[str, str]]:
flash_args_json_filepath = None
for fn in os.listdir(self.binary_path):
if fn == self.FLASH_ARGS_JSON_FILENAME:
flash_args_json_filepath = os.path.realpath(os.path.join(self.binary_path, fn))
break
if not flash_args_json_filepath:
raise ValueError(f'{self.FLASH_ARGS_JSON_FILENAME} not found')
with open(flash_args_json_filepath) as fr:
flash_args = json.load(fr)
def _is_encrypted(_flash_args: dict[str, Any], _offset: int, _file_path: str):
for entry in _flash_args.values():
try:
if (entry['offset'], entry['file']) == (_offset, _file_path):
return entry['encrypted'] == 'true'
except (TypeError, KeyError):
continue
return False
flash_files = []
for offset, file_path in flash_args['flash_files'].items():
flash_files.append(
FlashFile(
int(offset, 0),
os.path.join(self.binary_path, file_path),
_is_encrypted(flash_args, offset, file_path),
)
)
flash_files.sort()
flash_settings = flash_args['flash_settings']
flash_settings['encrypt'] = any([file.encrypted for file in flash_files])
return flash_args, flash_files, flash_settings
def get_sha256(self, filepath: str) -> str | None:
"""
Get the sha256 of the file
Args:
filepath: path to the file
Returns:
sha256 value appended to app
"""
from esptool.bin_image import LoadFirmwareImage
from esptool.util import hexify
image = LoadFirmwareImage(self.target, filepath)
if image.append_digest:
return hexify(image.stored_digest).lower()
return None