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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.10.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any