pytest-embedded-nuttx 1.11.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_embedded_nuttx/__init__.py +23 -0
- pytest_embedded_nuttx/app.py +100 -0
- pytest_embedded_nuttx/dut.py +106 -0
- pytest_embedded_nuttx/serial.py +75 -0
- pytest_embedded_nuttx-1.11.8.dist-info/LICENSE +21 -0
- pytest_embedded_nuttx-1.11.8.dist-info/METADATA +45 -0
- pytest_embedded_nuttx-1.11.8.dist-info/RECORD +8 -0
- pytest_embedded_nuttx-1.11.8.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Make pytest-embedded plugin work with NuttX."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
from pytest_embedded.utils import lazy_load
|
|
6
|
+
|
|
7
|
+
from .dut import NuttxDut, NuttxEspDut
|
|
8
|
+
|
|
9
|
+
__getattr__ = lazy_load(
|
|
10
|
+
importlib.import_module(__name__),
|
|
11
|
+
{
|
|
12
|
+
'NuttxDut': NuttxDut,
|
|
13
|
+
'NuttxEspDut': NuttxEspDut, # requires 'esp' service
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
'NuttxApp': '.app', # requires 'esp' service
|
|
17
|
+
'NuttxSerial': '.serial', # requires 'esp' service
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = ['NuttxApp', 'NuttxSerial', 'NuttxEspDut', 'NuttxDut']
|
|
22
|
+
|
|
23
|
+
__version__ = '1.11.8'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from esptool.cmds import FLASH_MODES, LoadFirmwareImage
|
|
5
|
+
from pytest_embedded.app import App
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NuttxApp(App):
|
|
9
|
+
"""
|
|
10
|
+
NuttX App class for Espressif devices.
|
|
11
|
+
Evaluates binary files (firmware and bootloader) and extract information
|
|
12
|
+
required for flashing.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
file_extension (str): app binary file extension.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
file_extension='.bin',
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
|
|
25
|
+
self.flash_size = None
|
|
26
|
+
self.flash_freq = None
|
|
27
|
+
self.flash_mode = None
|
|
28
|
+
self.file_extension = file_extension
|
|
29
|
+
files = self._get_bin_files()
|
|
30
|
+
self.app_file, self.bootloader_file, self.merge_file = files
|
|
31
|
+
self._get_binary_target_info()
|
|
32
|
+
|
|
33
|
+
def _get_bin_files(self) -> list:
|
|
34
|
+
"""
|
|
35
|
+
Get path to binary files available in the app_path.
|
|
36
|
+
If either the application image or bootloader is not found,
|
|
37
|
+
None is returned.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
list: path to application binary file and bootloader file.
|
|
41
|
+
"""
|
|
42
|
+
search_path = Path(self.app_path)
|
|
43
|
+
search_pattern = '*' + self.file_extension
|
|
44
|
+
bin_files = list(search_path.rglob(search_pattern))
|
|
45
|
+
app_file, bootloader_file, merge_file = None, None, None
|
|
46
|
+
|
|
47
|
+
logging.info('Searching %s', str(search_path))
|
|
48
|
+
if not bin_files:
|
|
49
|
+
logging.warning('No binary files found with pattern: %s', search_pattern)
|
|
50
|
+
|
|
51
|
+
for file_path in bin_files:
|
|
52
|
+
file_name = str(file_path.stem)
|
|
53
|
+
if 'nuttx' in file_name:
|
|
54
|
+
if 'merged' in file_name:
|
|
55
|
+
merge_file = file_path
|
|
56
|
+
else:
|
|
57
|
+
app_file = file_path
|
|
58
|
+
if 'mcuboot-' in file_name:
|
|
59
|
+
bootloader_file = file_path
|
|
60
|
+
|
|
61
|
+
if not app_file:
|
|
62
|
+
logging.error('App file not found: %s', app_file)
|
|
63
|
+
print(bin_files)
|
|
64
|
+
|
|
65
|
+
return app_file, bootloader_file, merge_file
|
|
66
|
+
|
|
67
|
+
def _get_binary_target_info(self):
|
|
68
|
+
"""Binary target should be in the format nuttx.merged.bin, where
|
|
69
|
+
the 'merged.bin' extension can be modified by the file_extension
|
|
70
|
+
argument.
|
|
71
|
+
|
|
72
|
+
Important note regarding MCUBoot:
|
|
73
|
+
If enabled, the magic number will be on the MCUBoot binary. In this
|
|
74
|
+
case, image_info should run on the mcuboot binary, not the NuttX one.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def get_key_from_value(dictionary, val):
|
|
78
|
+
"""Get key from value in dictionary"""
|
|
79
|
+
for key, value in dictionary.items():
|
|
80
|
+
if value == val:
|
|
81
|
+
return key
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
binary_path = self.app_file
|
|
85
|
+
if self.bootloader_file:
|
|
86
|
+
binary_path = self.bootloader_file
|
|
87
|
+
|
|
88
|
+
# Load app image and retrieve flash information
|
|
89
|
+
image = LoadFirmwareImage(self.target, binary_path.as_posix())
|
|
90
|
+
|
|
91
|
+
# Flash Size
|
|
92
|
+
flash_s_bits = image.flash_size_freq & 0xF0
|
|
93
|
+
self.flash_size = get_key_from_value(image.ROM_LOADER.FLASH_SIZES, flash_s_bits)
|
|
94
|
+
|
|
95
|
+
# Flash Frequency
|
|
96
|
+
flash_fr_bits = image.flash_size_freq & 0x0F # low four bits
|
|
97
|
+
self.flash_freq = get_key_from_value(image.ROM_LOADER.FLASH_FREQUENCY, flash_fr_bits)
|
|
98
|
+
|
|
99
|
+
# Flash Mode
|
|
100
|
+
self.flash_mode = get_key_from_value(FLASH_MODES, image.flash_mode)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from time import sleep
|
|
4
|
+
from typing import TYPE_CHECKING, AnyStr
|
|
5
|
+
|
|
6
|
+
import pexpect
|
|
7
|
+
from pytest_embedded_serial.dut import SerialDut
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .app import NuttxApp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NuttxDut(SerialDut):
|
|
14
|
+
"""
|
|
15
|
+
Generic DUT class for use with NuttX RTOS.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
PROMPT_NSH = 'nsh>'
|
|
19
|
+
PROMPT_TIMEOUT_S = 30
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
**kwargs,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(**kwargs)
|
|
26
|
+
|
|
27
|
+
def write(self, data: str) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Write to NuttShell and sleep for a few hundred milliseconds to
|
|
30
|
+
ensure there is time for Nuttshell prompt appear again.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data (str): data to be passed on to Nuttshell.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
None.
|
|
37
|
+
"""
|
|
38
|
+
super().write('')
|
|
39
|
+
self.expect(self.PROMPT_NSH, timeout=self.PROMPT_TIMEOUT_S)
|
|
40
|
+
super().write(data)
|
|
41
|
+
sleep(0.25)
|
|
42
|
+
|
|
43
|
+
def return_code(self, timeout: int = PROMPT_TIMEOUT_S) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Matches the 'echo $?' response and extracts the integer value
|
|
46
|
+
corresponding to the last program return code.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
int: return code.
|
|
50
|
+
"""
|
|
51
|
+
self.write('echo $?')
|
|
52
|
+
echo_match = self.expect(r'echo \$\?\r\n(\d+)', timeout=timeout)
|
|
53
|
+
ret_code = re.findall(r'\d+', echo_match.group().decode())
|
|
54
|
+
|
|
55
|
+
if not ret_code:
|
|
56
|
+
logging.error('Failed to retrieve return code')
|
|
57
|
+
|
|
58
|
+
return int(ret_code[0])
|
|
59
|
+
|
|
60
|
+
def write_and_return(self, data: str, timeout: int = 2) -> AnyStr:
|
|
61
|
+
"""
|
|
62
|
+
Writes to Nuttshell and returns all available serial data until
|
|
63
|
+
the timeout.
|
|
64
|
+
This is useful when parsing and reusing the data is required, and
|
|
65
|
+
pexect is not enough.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
data (str): data to be passed on to Nuttshell.
|
|
69
|
+
timeout (int): how long to wait for an answer in seconds.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
AnyStr
|
|
73
|
+
"""
|
|
74
|
+
self.write(data)
|
|
75
|
+
ans = self.expect(pexpect.TIMEOUT, timeout=timeout)
|
|
76
|
+
return ans.rstrip().decode()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class NuttxEspDut(NuttxDut):
|
|
80
|
+
"""
|
|
81
|
+
DUT class for serial ports connected to Espressif boards which are
|
|
82
|
+
flashed with NuttX RTOS.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
app: 'NuttxApp',
|
|
88
|
+
**kwargs,
|
|
89
|
+
) -> None:
|
|
90
|
+
self.target = app.target
|
|
91
|
+
|
|
92
|
+
super().__init__(app=app, **kwargs)
|
|
93
|
+
|
|
94
|
+
def reset_to_nsh(self, ready_prompt: str = NuttxDut.PROMPT_NSH) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Resets the board and waits until the Nuttshell prompt appears.
|
|
97
|
+
Defaults to 'nsh>'.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
ready_prompt (str): string on prompt that signals completion.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
None
|
|
104
|
+
"""
|
|
105
|
+
self.serial.hard_reset()
|
|
106
|
+
self.expect(ready_prompt, timeout=NuttxDut.PROMPT_TIMEOUT_S)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import ClassVar, Dict, Optional
|
|
2
|
+
|
|
3
|
+
import esptool
|
|
4
|
+
from pytest_embedded_serial_esp.serial import EspSerial
|
|
5
|
+
|
|
6
|
+
from .app import NuttxApp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NuttxSerial(EspSerial):
|
|
10
|
+
"""
|
|
11
|
+
NuttX serial DUT class.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Default offset for the primary MCUBoot slot across
|
|
15
|
+
# all Espressif devices on NuttX
|
|
16
|
+
MCUBOOT_PRIMARY_SLOT_OFFSET = 0x10000
|
|
17
|
+
FLASH_BAUDRATE = 921600
|
|
18
|
+
SERIAL_BAUDRATE = 115200
|
|
19
|
+
|
|
20
|
+
binary_offsets: ClassVar[Dict[str, int]] = {
|
|
21
|
+
'esp32': 0x1000,
|
|
22
|
+
'esp32s2': 0x1000,
|
|
23
|
+
'esp32c3': 0x0,
|
|
24
|
+
'esp32s3': 0x0,
|
|
25
|
+
'esp32c6': 0x0,
|
|
26
|
+
'esp32h2': 0x0,
|
|
27
|
+
'esp32p4': 0x2000,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
app: NuttxApp,
|
|
33
|
+
target: Optional[str] = None,
|
|
34
|
+
**kwargs,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.app = app
|
|
37
|
+
super().__init__(
|
|
38
|
+
target=target or self.app.target,
|
|
39
|
+
**kwargs,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@EspSerial.use_esptool()
|
|
43
|
+
def flash(self) -> None:
|
|
44
|
+
"""Flash the binary files to the board."""
|
|
45
|
+
|
|
46
|
+
flash_files = []
|
|
47
|
+
if self.app.bootloader_file:
|
|
48
|
+
flash_files.extend((str(self.binary_offsets[self.target]), self.app.bootloader_file.as_posix()))
|
|
49
|
+
flash_files.extend((str(self.MCUBOOT_PRIMARY_SLOT_OFFSET), self.app.app_file.as_posix()))
|
|
50
|
+
else:
|
|
51
|
+
flash_files.extend((str(self.binary_offsets[self.target]), self.app.app_file.as_posix()))
|
|
52
|
+
|
|
53
|
+
flash_settings = [
|
|
54
|
+
'--flash_mode',
|
|
55
|
+
self.app.flash_mode,
|
|
56
|
+
'--flash_size',
|
|
57
|
+
self.app.flash_size,
|
|
58
|
+
'--flash_freq',
|
|
59
|
+
self.app.flash_freq,
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
esptool.main(
|
|
63
|
+
[
|
|
64
|
+
'--chip',
|
|
65
|
+
self.app.target,
|
|
66
|
+
'--port',
|
|
67
|
+
self.port,
|
|
68
|
+
'--baud',
|
|
69
|
+
str(self.FLASH_BAUDRATE),
|
|
70
|
+
'write_flash',
|
|
71
|
+
*flash_files,
|
|
72
|
+
*flash_settings,
|
|
73
|
+
],
|
|
74
|
+
esp=self.esp,
|
|
75
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pytest-embedded-nuttx
|
|
3
|
+
Version: 1.11.8
|
|
4
|
+
Summary: Make pytest-embedded plugin work with NuttX.
|
|
5
|
+
Author-email: Filipe Cavalcanti <filipe.cavalcanti@espressif.com>, Fu Hanxi <fuhanxi@espressif.com>
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Framework :: Pytest
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Dist: pytest-embedded-serial~=1.11.8
|
|
25
|
+
Requires-Dist: pytest-embedded-serial-esp~=1.11.8 ; extra == "esp"
|
|
26
|
+
Project-URL: changelog, https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md
|
|
27
|
+
Project-URL: documentation, https://docs.espressif.com/projects/pytest-embedded/en/latest/
|
|
28
|
+
Project-URL: homepage, https://github.com/espressif/pytest-embedded
|
|
29
|
+
Project-URL: repository, https://github.com/espressif/pytest-embedded
|
|
30
|
+
Provides-Extra: esp
|
|
31
|
+
|
|
32
|
+
### pytest-embedded-nuttx
|
|
33
|
+
|
|
34
|
+
Pytest embedded service for the NuttX project, compatible with Espressif devices.
|
|
35
|
+
|
|
36
|
+
Using the 'nuttx' service alongside 'serial' enables reading from and writing to the serial port, taking NuttShell into account when running programs and retrieving return codes.
|
|
37
|
+
|
|
38
|
+
The `nuttx` service provides basic serial communication and testing. Adding the 'esp' service enables further capabilities for Espressif devices, including flashing and device rebooting.
|
|
39
|
+
|
|
40
|
+
Additional Features:
|
|
41
|
+
|
|
42
|
+
- `app`: Scans the NuttX binary directory to locate firmware and bootloader files.
|
|
43
|
+
- `serial`: Parses binary information and flashes the board. Requires the 'esp' service.
|
|
44
|
+
- `dut`: Sends commands to the device through the serial port. Requires the 'serial' service or 'esp' service for Espressif devices.
|
|
45
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pytest_embedded_nuttx/__init__.py,sha256=3Qk0lUQU0mlBMXDlRBfuOSZAVgEOFSrnS9txxXKCFBc,543
|
|
2
|
+
pytest_embedded_nuttx/app.py,sha256=Ss7kbBv2jYjT3AvO7pMJQA1N_Bh90G5jge5oS81HZSU,3364
|
|
3
|
+
pytest_embedded_nuttx/dut.py,sha256=3mI0fAUMBOntqbHBK_woWD4HSR8iPvrggQRmXAMPSmQ,2792
|
|
4
|
+
pytest_embedded_nuttx/serial.py,sha256=7n9d1HLaJgOE7CXrcjrTPa61whOjy1_VzHc6vR3INe0,1981
|
|
5
|
+
pytest_embedded_nuttx-1.11.8.dist-info/LICENSE,sha256=_Um-MzeKCR0JCDg5Ac_WFmZEfsPTd7PNlNi9JF1GBX8,1094
|
|
6
|
+
pytest_embedded_nuttx-1.11.8.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
7
|
+
pytest_embedded_nuttx-1.11.8.dist-info/METADATA,sha256=PiPcY5_6FHCUU2PJ-5VAU9jpRqwbkw21vNi7IF4GwjI,2328
|
|
8
|
+
pytest_embedded_nuttx-1.11.8.dist-info/RECORD,,
|