Skip to content

Commit

Permalink
Add support for NuttX RTOS, with optional support for Espressif devices.
Browse files Browse the repository at this point in the history
Squashed commits:
Main pytest_embedded modifications to support Nuttx
Binary test files for nuttx
Created Nuttx App
Added serial module of NuttX
Added DUT for Nuttx
Fixes on nuttx serial
Added nuttx example
General fixes + docs
Dut now has return code method
Fixed return code issues. Added write_and_return
Return code now waits for nsh ready
Added short delay after write to obtain nsh prompt
Checking filename at the stem instead of full path
Skipping 'merged' files
Target is passed to App in dut_factory
Debug app path
Documentation update
General fixes
Documentation update
Improved nsh prompt parse
  • Loading branch information
fdcavalcanti committed Nov 12, 2024
1 parent 63131fc commit b4c564b
Show file tree
Hide file tree
Showing 19 changed files with 532 additions and 3 deletions.
18 changes: 18 additions & 0 deletions docs/apis/pytest-embedded-nuttx.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#######################
pytest-embedded-nuttx
#######################

.. automodule:: pytest_embedded_nuttx.app
:members:
:undoc-members:
:show-inheritance:

.. automodule:: pytest_embedded_nuttx.dut
:members:
:undoc-members:
:show-inheritance:

.. automodule:: pytest_embedded_nuttx.serial
:members:
:undoc-members:
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/concepts/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ The arrow points from the dependent service to the service it depends on. For ex
graph LR
pytest-embedded-serial
pytest-embedded-nuttx --> pytest-embedded-serial
pytest-embedded-nuttx -->|optional, support test on espressif chips| pytest-embedded-serial-esp
pytest-embedded-serial-esp --> pytest-embedded-serial
pytest-embedded-jtag --> pytest-embedded-serial
Expand Down Expand Up @@ -53,3 +56,6 @@ Activate a service would enable a set of fixtures or add some extra functionalit

```{include} ../../pytest-embedded-wokwi/README.md
```

```{include} ../../pytest-embedded-nuttx/README.md
```
18 changes: 18 additions & 0 deletions examples/nuttx-esp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# NuttX with Espressif Device Example

The example shows run pytest-embedded using NuttX and Espressif devices.
Simple uses such as serial port data exchange can be done with pytest-embedded only.
Using pytest-embedded-nuttx allows for the of `esptool` along Pytest.

## Prerequisites

1. Install following packages
- `pytest_embedded`
- `pytest_embedded_nuttx`
- `esptool`

## Test Steps

```shell
$ pytest
```
8 changes: 8 additions & 0 deletions examples/nuttx-esp/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[pytest]
addopts = --embedded-services nuttx,esp -s --port=/dev/ttyUSB0

# log related
log_cli = True
log_cli_level = INFO
log_cli_format = %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
15 changes: 15 additions & 0 deletions examples/nuttx-esp/test_nuttx_esp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test example for Espressif devices"""

import pytest


@pytest.fixture(autouse=True)
def reset_to_nsh(dut, serial):
"""Resets the device and waits until NSH prompt is ready."""
serial.hard_reset()
dut.expect('nsh>', timeout=5)


def test_nuttx_esp_ps(dut):
dut.write('ps')
dut.expect('Idle_Task', timeout=2)
1 change: 1 addition & 0 deletions foreach.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ DEFAULT_PACKAGES=" \
pytest-embedded-qemu \
pytest-embedded-arduino \
pytest-embedded-wokwi \
pytest-embedded-nuttx \
"

action=${1:-"install"}
Expand Down
21 changes: 21 additions & 0 deletions pytest-embedded-nuttx/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
17 changes: 17 additions & 0 deletions pytest-embedded-nuttx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### pytest-embedded-nuttx

Pytest embedded service for NuttX project. alongside Espressif devices.

Using the 'nuttx' service alongside 'serial' allows writing and reading from
the serial port, taking the NuttShell into consideration when running programs
and even getting return codes.

While using pytest-embedded-nuttx allows you to communicate with serial
devices and run simple tests, enabling the 'esp' service adds extra capabilities for
Espressif devices, such as flashing and device rebooting.

Extra Functionalities:

- `app`: Explore the NuttX binary directory and identify firmware and bootloader files.
- `serial`: Parse the binary information and flash the board. Requires 'esp' service.
- `dut`: Send commands to device through serial port. Requires 'serial' service or 'esp' for Espressif devices.
46 changes: 46 additions & 0 deletions pytest-embedded-nuttx/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "pytest-embedded-nuttx"
authors = [
{name = "Filipe Cavalcanti", email = "[email protected]"},
{name = "Fu Hanxi", email = "[email protected]"},
]
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Pytest",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python",
"Topic :: Software Development :: Testing",
]
dynamic = ["version", "description"]
requires-python = ">=3.7"

dependencies = [
"pytest-embedded-serial~=1.11.8",
]

[project.optional-dependencies]
esp = [
"pytest-embedded-serial-esp~=1.11.8",
]

[project.urls]
homepage = "https://github.com/espressif/pytest-embedded"
repository = "https://github.com/espressif/pytest-embedded"
documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
24 changes: 24 additions & 0 deletions pytest-embedded-nuttx/pytest_embedded_nuttx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Make pytest-embedded plugin work with NuttX."""

import importlib

from pytest_embedded.utils import lazy_load

from .app import NuttxApp
from .dut import NuttxDut, NuttxEspDut

__getattr__ = lazy_load(
importlib.import_module(__name__),
{
'NuttxDut': NuttxDut,
'NuttxEspDut': NuttxEspDut, # requires 'esp' service
},
{
'NuttxApp': '.app', # requires 'esp' service
'NuttxSerial': '.serial', # requires 'esp' service
},
)

__all__ = ['NuttxApp', 'NuttxSerial', 'NuttxEspDut', 'NuttxDut']

__version__ = '1.11.8'
100 changes: 100 additions & 0 deletions pytest-embedded-nuttx/pytest_embedded_nuttx/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import logging
from pathlib import Path

from esptool.cmds import FLASH_MODES, LoadFirmwareImage
from pytest_embedded.app import App


class NuttxApp(App):
"""
NuttX App class for Espressif devices.
Evaluates binary files (firmware and bootloader) and extract information
required for flashing.
Attributes:
file_extension (str): app binary file extension.
"""

def __init__(
self,
file_extension='.bin',
**kwargs,
):
super().__init__(**kwargs)

self.flash_size = None
self.flash_freq = None
self.flash_mode = None
self.file_extension = file_extension
files = self._get_bin_files()
self.app_file, self.bootloader_file, self.merge_file = files
self._get_binary_target_info()

def _get_bin_files(self) -> list:
"""
Get path to binary files available in the app_path.
If either the application image or bootloader is not found,
None is returned.
Returns:
list: path to application binary file and bootloader file.
"""
search_path = Path(self.app_path)
search_pattern = '*' + self.file_extension
bin_files = list(search_path.rglob(search_pattern))
app_file, bootloader_file, merge_file = None, None, None

logging.info('Searching %s', str(search_path))
if not bin_files:
logging.warning('No binary files found with pattern: %s', search_pattern)

for file_path in bin_files:
file_name = str(file_path.stem)
if 'nuttx' in file_name:
if 'merged' in file_name:
merge_file = file_path
else:
app_file = file_path
if 'mcuboot-' in file_name:
bootloader_file = file_path

if not app_file:
logging.error('App file not found: %s', app_file)
print(bin_files)

return app_file, bootloader_file, merge_file

def _get_binary_target_info(self):
"""Binary target should be in the format nuttx.merged.bin, where
the 'merged.bin' extension can be modified by the file_extension
argument.
Important note regarding MCUBoot:
If enabled, the magic number will be on the MCUBoot binary. In this
case, image_info should run on the mcuboot binary, not the NuttX one.
"""

def get_key_from_value(dictionary, val):
"""Get key from value in dictionary"""
for key, value in dictionary.items():
if value == val:
return key
return None

binary_path = self.app_file
if self.bootloader_file:
binary_path = self.bootloader_file

# Load app image and retrieve flash information
image = LoadFirmwareImage(self.target, binary_path.as_posix())

# Flash Size
flash_s_bits = image.flash_size_freq & 0xF0
self.flash_size = get_key_from_value(image.ROM_LOADER.FLASH_SIZES, flash_s_bits)

# Flash Frequency
flash_fr_bits = image.flash_size_freq & 0x0F # low four bits
self.flash_freq = get_key_from_value(image.ROM_LOADER.FLASH_FREQUENCY, flash_fr_bits)

# Flash Mode
self.flash_mode = get_key_from_value(FLASH_MODES, image.flash_mode)
Loading

0 comments on commit b4c564b

Please sign in to comment.