Skip to content

Commit ac8e2cc

Browse files
committed
feat(esp): support flash with a different port
1 parent 81158ea commit ac8e2cc

File tree

7 files changed

+295
-39
lines changed

7 files changed

+295
-39
lines changed

docs/concepts/serial.rst

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
###################################################################
2+
Understand :class:`~pytest_embedded_serial.serial.Serial` Objects
3+
###################################################################
4+
5+
The ``Serial`` object is the main object that you will use for testing. This chapter will explain the basic concepts of the ``Serial`` object.
6+
7+
.. note::
8+
9+
This chapter is mainly for developers who want to understand the internal structure of the ``Serial`` object. If you are a user who just wants to use the ``Serial`` object, you can skip this chapter.
10+
11+
************************************************
12+
:class:`~pytest_embedded_serial.serial.Serial`
13+
************************************************
14+
15+
- :func:`__init__`
16+
17+
- decide port
18+
19+
Support auto-detecting port by port location
20+
21+
- :func:`_post_init`
22+
23+
occupy ports globally. Used for preventing other tests from using the same port while auto-detecting ports.
24+
25+
- :func:`_start`
26+
27+
doing nothing
28+
29+
- :func:`_finalize_init`
30+
31+
doing nothing
32+
33+
- :func:`start_redirect_thread`
34+
35+
Start the redirect thread. Read data from the serial port and write it to the log file, optionally echoing it to the console.
36+
37+
- :func:`close`:
38+
39+
- :func:`stop_redirect_thread`
40+
41+
Stop the redirect thread.
42+
43+
- close serial connection
44+
45+
- release the occupied port globally
46+
47+
***********************************************************************************************************************
48+
:class:`~pytest_embedded_serial_esp.serial.EspSerial` (Inherited from :class:`~pytest_embedded_serial.serial.Serial`)
49+
***********************************************************************************************************************
50+
51+
- :func:`__init__`
52+
53+
- :func:`_before_init_port` (newly added method before deciding port)
54+
55+
- parent class :func:`_post_init`
56+
57+
- decide port
58+
59+
Support auto-detecting port by device MAC, or device target. (Espressif-chips only)
60+
61+
- :func:`_post_init`
62+
63+
- Call :func:`set_port_target_cache`, speed up auto-detection next time
64+
65+
- erase flash if set :attr:`erase_all`, and not set :attr:`flash_port`
66+
67+
since if :attr:`flash_port` is set, the "erase" and "flash"" process will be done earlier already.
68+
69+
- parent class :func:`_post_init`
70+
71+
- :func:`_start`
72+
73+
Run :func:`esptool.hard_reset`
74+
75+
*******************************************************************************************************************************
76+
:class:`~pytest_embedded_arduino.serial.ArduinoSerial` (Inherited from :class:`~pytest_embedded_serial_esp.serial.EspSerial`)
77+
*******************************************************************************************************************************
78+
79+
- :func:`__init__`
80+
81+
- :func:`_start`
82+
83+
Auto-flash the app if not :attr:`skip_autoflash`
84+
85+
***********************************************************************************************************************
86+
:class:`~pytest_embedded_idf.serial.IdfSerial` (Inherited from :class:`~pytest_embedded_serial_esp.serial.EspSerial`)
87+
***********************************************************************************************************************
88+
89+
- :func:`__init__`
90+
91+
- :func:`_before_init_port`
92+
93+
If :attr:`flash_port` is set differently from the :attr:`port`, the target chip will always be flashed with the given port(without the port-app cache)
94+
95+
- Occupying the :attr:`flash_port` globally
96+
- erase flash if set :attr:`erase_all`
97+
- Flash the app if not set :attr:`skip_autoflash`
98+
- :func:`set_port_target_cache` for the flash port
99+
- :func:`set_port_app_cache` for the flash port
100+
101+
- :func:`_post_init`
102+
103+
- if set :attr:`flash_port`, do nothing
104+
- otherwise, check port-app cache, if the app has been flashed, skip the auto-flash process
105+
- Run parent :func:`_post_init`
106+
107+
- :func:`_start`
108+
109+
- if the target has been flashed while :func:`_before_init_port`, set the port-app cache with the :attr:`port` and :attr:`app` and do nothing
110+
- otherwise, run :func:`flash` automatically the app if not set :attr:`skip_autoflash`
111+
- Run parent :func:`_start`
112+
113+
- :func:`flash`
114+
115+
- flash the app
116+
- :func:`set_port_app_cache` for the flash port
117+
118+
- :func:`close`
119+
120+
- release the occupied flash port globally
121+
- Run parent :func:`close`

pytest-embedded-idf/pytest_embedded_idf/dut.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def setup_jtag(self):
264264

265265
def flash_via_jtag(self):
266266
if not self.openocd:
267-
logging.warning("no openocd instance created. can't flash via openocd `program_esp`")
267+
logging.debug('no openocd instance created. NOT flashing via openocd `program_esp`')
268268
return
269269

270270
if self.app.is_loadable_elf:

pytest-embedded-idf/pytest_embedded_idf/serial.py

+113-19
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import logging
44
import os
55
import tempfile
6+
import time
67
from typing import Optional, TextIO, Union
78

89
import esptool
10+
from pytest_embedded.log import MessageQueue
911
from pytest_embedded_serial_esp.serial import EspSerial
1012

1113
from .app import IdfApp
@@ -44,9 +46,56 @@ def __init__(
4446
**kwargs,
4547
)
4648

47-
def _post_init(self):
49+
def _before_init_port(self, q: MessageQueue):
50+
if not self.flash_port:
51+
return
52+
53+
self.occupied_ports[self.flash_port] = None
54+
logging.debug(f'occupied {self.flash_port}')
55+
4856
if self.erase_all:
49-
self.skip_autoflash = False
57+
self.skip_autoflash = False # do we really need it? may be a breaking change if removed
58+
59+
cmd = ['--port', self.flash_port, 'erase_flash']
60+
if self._force_flag():
61+
cmd.append('--force')
62+
63+
with contextlib.redirect_stdout(q):
64+
esptool.main(cmd)
65+
66+
if self._meta:
67+
self._meta.drop_port_app_cache(self.flash_port)
68+
69+
if self.skip_autoflash:
70+
return
71+
72+
if self.erase_nvs:
73+
_args, _kwargs = self._get_erase_nvs_cli_args(port=self.flash_port)
74+
with contextlib.redirect_stdout(q):
75+
esptool.main(_args, **_kwargs)
76+
77+
# this is required to wait for the reset happened after erase the nvs partition
78+
time.sleep(1)
79+
80+
_args, _kwargs = self._get_flash_cli_args(port=self.flash_port)
81+
with contextlib.redirect_stdout(q):
82+
esptool.main(_args, **_kwargs)
83+
84+
# after flash caches
85+
# here instead of ``_finalize_init`` to avoid occupying the port wrongly while multi-DUT test case
86+
self._flashed_with_different_port = True
87+
if self._meta:
88+
self._meta.set_port_target_cache(self.flash_port, self.app.target)
89+
self._meta.set_port_app_cache(self.flash_port, self.app)
90+
91+
# this is required to wait for the reset happened after flashing
92+
time.sleep(1)
93+
94+
def _post_init(self):
95+
if self._flashed_with_different_port:
96+
pass
97+
elif self.erase_all:
98+
self.skip_autoflash = False # do we really need it? may be a breaking change if removed
5099
elif self._meta and self._meta.hit_port_app_cache(self.port, self.app):
51100
if self.confirm_target_elf_sha256:
52101
if self.is_target_flashed_same_elf():
@@ -66,6 +115,13 @@ def _post_init(self):
66115
super()._post_init()
67116

68117
def _start(self):
118+
if self._flashed_with_different_port:
119+
if self._meta:
120+
self._meta.set_port_app_cache(self.port, self.app)
121+
122+
super()._start()
123+
return
124+
69125
if self.skip_autoflash:
70126
logging.info('Skipping auto flash...')
71127
super()._start()
@@ -75,6 +131,13 @@ def _start(self):
75131
else:
76132
self.flash()
77133

134+
def close(self):
135+
if self._flashed_with_different_port:
136+
self.occupied_ports.pop(self.flash_port, None)
137+
logging.debug(f'released {self.flash_port}')
138+
139+
super().close()
140+
78141
def load_ram(self) -> None:
79142
if not self.app.is_loadable_elf:
80143
raise ValueError('elf should be loadable elf')
@@ -131,11 +194,12 @@ def erase_flash(self, force: bool = False):
131194
else:
132195
super().erase_flash()
133196

134-
@EspSerial.use_esptool()
135-
def flash(self, app: Optional[IdfApp] = None) -> None:
136-
"""
137-
Flash the `app.flash_files` to the dut
138-
"""
197+
def _get_flash_cli_args(
198+
self,
199+
*,
200+
app: Optional[IdfApp] = None,
201+
port: Optional[str] = None,
202+
):
139203
if not app:
140204
app = self.app
141205

@@ -148,6 +212,8 @@ def flash(self, app: Optional[IdfApp] = None) -> None:
148212
return
149213

150214
_args = []
215+
_kwargs = {}
216+
151217
for k, v in app.flash_args['extra_esptool_args'].items():
152218
if isinstance(v, bool):
153219
if k == 'stub':
@@ -166,17 +232,6 @@ def flash(self, app: Optional[IdfApp] = None) -> None:
166232
_args.extend(['--baud', os.getenv('ESPBAUD', '921600')])
167233
_args.append('write_flash')
168234

169-
if self.erase_nvs:
170-
esptool.main(
171-
[
172-
'erase_region',
173-
str(app.partition_table['nvs']['offset']),
174-
str(app.partition_table['nvs']['size']),
175-
],
176-
esp=self.esp,
177-
)
178-
self.esp.connect()
179-
180235
encrypt_files = []
181236
flash_files = []
182237
for file in app.flash_files:
@@ -195,7 +250,46 @@ def flash(self, app: Optional[IdfApp] = None) -> None:
195250

196251
_args.extend([*app.flash_args['write_flash_args'], *self._force_flag(app)])
197252

198-
esptool.main(_args, esp=self.esp)
253+
if port:
254+
_args = ['--port', port, *_args]
255+
else:
256+
_kwargs.update({'esp': self.esp})
257+
258+
return _args, _kwargs
259+
260+
def _get_erase_nvs_cli_args(self, app: Optional[IdfApp] = None, port: Optional[str] = None):
261+
if not app:
262+
app = self.app
263+
264+
_args = [
265+
'erase_region',
266+
str(app.partition_table['nvs']['offset']),
267+
str(app.partition_table['nvs']['size']),
268+
]
269+
_kwargs = {}
270+
271+
if port:
272+
_args = ['--port', port, *_args]
273+
else:
274+
_kwargs.update({'esp': self.esp})
275+
276+
return _args, _kwargs
277+
278+
@EspSerial.use_esptool()
279+
def flash(self, app: Optional[IdfApp] = None) -> None:
280+
"""
281+
Flash the app to the target device, or the app provided.
282+
"""
283+
if not app:
284+
app = self.app
285+
286+
if self.erase_nvs:
287+
_args, _kwargs = self._get_erase_nvs_cli_args(app=app)
288+
esptool.main(_args, **_kwargs)
289+
self.esp.connect()
290+
291+
_args, _kwargs = self._get_flash_cli_args(app=app)
292+
esptool.main(_args, **_kwargs)
199293

200294
if self._meta:
201295
self._meta.set_port_app_cache(self.port, app)

0 commit comments

Comments
 (0)