Skip to content

Commit 0c548e6

Browse files
committed
change(arduino): Rework arduino app
1 parent 8916539 commit 0c548e6

File tree

9 files changed

+94
-54
lines changed

9 files changed

+94
-54
lines changed

examples/arduino/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ adapt the build folder appropriately when run from a different location.
1818
On success, this will create a `build` directory under the `hello_world`
1919
example.
2020

21+
The Arduino service requires only the build directory to work properly.
22+
The app path is not required but can be used to derive the build directory. If not specified, it will be set to the current working directory.
23+
24+
The build directory is the directory that contains the binary and configuration files.
25+
It can be specified as an absolute path or a relative path to the app path.
26+
If nothing is specified, it will look for the `build` directory in the app path. If it still doesn't find it, it will assume the build directory is the app path.
27+
2128
### Run the tests
2229
2330
```shell
@@ -33,3 +40,9 @@ $ pytest examples/arduino -k test_hello_arduino
3340
3441
This will parse the `build` directory created earlier, flash the chip and
3542
expect the `Hello Arduino!` text to be printed.
43+
44+
You can run the tests specifiying the build directory used to build the example:
45+
46+
```shell
47+
$ pytest --build-dir build examples/arduino -k test_hello_arduino
48+
```
Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import logging
23
import os
34
from typing import ClassVar
45

@@ -13,60 +14,88 @@ class ArduinoApp(App):
1314
sketch (str): Sketch name.
1415
fqbn (str): Fully Qualified Board Name.
1516
target (str) : ESPxx chip.
16-
flash_files (List[Tuple[int, str, str]]): List of (offset, file path, encrypted) of files need to be flashed in.
17+
flash_settings (dict[str, str]): Flash settings for the target.
18+
bootloader_offset (int): Bootloader offset.
19+
binary_file (str): Merged binary file path.
20+
elf_file (str): ELF file path.
1721
"""
1822

19-
#: dict of flash settings
20-
flash_settings: ClassVar[dict[str, dict[str, str]]] = {
21-
'esp32': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
22-
'esp32c2': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '60m'},
23-
'esp32c3': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
24-
'esp32c5': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
25-
'esp32c6': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
26-
'esp32c61': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
27-
'esp32h2': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '48m'},
28-
'esp32p4': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
29-
'esp32s2': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
30-
'esp32s3': {'flash-mode': 'dio', 'flash-size': 'detect', 'flash-freq': '80m'},
31-
}
32-
33-
#: dict of binaries' offset.
34-
binary_offsets: ClassVar[dict[str, list[int]]] = {
35-
'esp32': [0x1000, 0x8000, 0x10000],
36-
'esp32c2': [0x0, 0x8000, 0x10000],
37-
'esp32c3': [0x0, 0x8000, 0x10000],
38-
'esp32c5': [0x2000, 0x8000, 0x10000],
39-
'esp32c6': [0x0, 0x8000, 0x10000],
40-
'esp32c61': [0x0, 0x8000, 0x10000],
41-
'esp32h2': [0x0, 0x8000, 0x10000],
42-
'esp32p4': [0x2000, 0x8000, 0x10000],
43-
'esp32s2': [0x1000, 0x8000, 0x10000],
44-
'esp32s3': [0x0, 0x8000, 0x10000],
45-
}
46-
4723
def __init__(
4824
self,
4925
**kwargs,
5026
):
5127
super().__init__(**kwargs)
5228

53-
self.sketch = os.path.basename(self.app_path)
29+
# If no valid binary path is found, assume the build directory is the app path
30+
if not self.binary_path and self.app_path:
31+
self.binary_path = self.app_path
32+
33+
# Extract sketch name from binary files in the build directory
34+
self.sketch = self._get_sketch_name(self.binary_path)
5435
self.fqbn = self._get_fqbn(self.binary_path)
5536
self.target = self.fqbn.split(':')[2]
56-
self.flash_files = self._get_bin_files(self.binary_path, self.sketch, self.target)
37+
self.flash_settings = self._get_flash_settings()
38+
self.bootloader_offset = self._get_bootloader_offset()
39+
self.binary_file = os.path.realpath(os.path.join(self.binary_path, self.sketch + '.ino.merged.bin'))
5740
self.elf_file = os.path.realpath(os.path.join(self.binary_path, self.sketch + '.ino.elf'))
5841

59-
def _get_fqbn(self, build_path) -> str:
42+
logging.debug(f'Sketch name: {self.sketch}')
43+
logging.debug(f'FQBN: {self.fqbn}')
44+
logging.debug(f'Target: {self.target}')
45+
logging.debug(f'Flash settings: {self.flash_settings}')
46+
logging.debug(f'Bootloader offset: {self.bootloader_offset}')
47+
logging.debug(f'Binary file: {self.binary_file}')
48+
logging.debug(f'ELF file: {self.elf_file}')
49+
50+
def _get_sketch_name(self, build_path: str) -> str:
51+
"""Extract sketch name from binary files in the build directory."""
52+
if not build_path or not os.path.isdir(build_path):
53+
logging.warning(f'No build path found. Using default sketch name "sketch".')
54+
return 'sketch'
55+
56+
# Look for .ino.bin or .ino.merged.bin files
57+
for filename in os.listdir(build_path):
58+
if filename.endswith('.ino.bin') or filename.endswith('.ino.merged.bin'):
59+
# Extract sketch name (everything before .ino.bin or .ino.merged.bin)
60+
if filename.endswith('.ino.merged.bin'):
61+
return filename[:-len('.ino.merged.bin')]
62+
else:
63+
return filename[:-len('.ino.bin')]
64+
65+
# If no .ino.bin or .ino.merged.bin files found, raise an error
66+
raise ValueError(f'No .ino.bin or .ino.merged.bin file found in {build_path}')
67+
68+
def _get_fqbn(self, build_path: str) -> str:
69+
"""Get FQBN from build.options.json file."""
6070
options_file = os.path.realpath(os.path.join(build_path, 'build.options.json'))
6171
with open(options_file) as f:
6272
options = json.load(f)
6373
fqbn = options['fqbn']
6474
return fqbn
6575

66-
def _get_bin_files(self, build_path, sketch, target) -> list[tuple[int, str, bool]]:
67-
bootloader = os.path.realpath(os.path.join(build_path, sketch + '.ino.bootloader.bin'))
68-
partitions = os.path.realpath(os.path.join(build_path, sketch + '.ino.partitions.bin'))
69-
app = os.path.realpath(os.path.join(build_path, sketch + '.ino.bin'))
70-
files = [bootloader, partitions, app]
71-
offsets = self.binary_offsets[target]
72-
return [(offsets[i], files[i], False) for i in range(3)]
76+
def _get_bootloader_offset(self) -> int:
77+
"""Get bootloader offset from flash_args file."""
78+
flash_args_file = os.path.realpath(os.path.join(self.binary_path, 'flash_args'))
79+
with open(flash_args_file) as f:
80+
bootloader_offset = int(f.readlines()[1].split(' ')[0].strip(), 16)
81+
82+
if bootloader_offset is None:
83+
raise ValueError(f'Bootloader offset not found in {flash_args_file}')
84+
85+
return bootloader_offset
86+
87+
def _get_flash_settings(self) -> dict[str, str]:
88+
"""Get flash settings from flash_args file."""
89+
flash_args_file = os.path.realpath(os.path.join(self.binary_path, 'flash_args'))
90+
with open(flash_args_file) as f:
91+
flash_args = f.readline().split(' ')
92+
93+
flash_settings = {}
94+
for i, arg in enumerate(flash_args):
95+
if arg.startswith('--'):
96+
flash_settings[arg[2:].strip()] = flash_args[i + 1].strip()
97+
98+
if flash_settings == {}:
99+
raise ValueError(f'Flash settings not found in {flash_args_file}')
100+
101+
return flash_settings

pytest-embedded-arduino/pytest_embedded_arduino/serial.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,9 @@ def flash(self) -> None:
3939
"""
4040
Flash the binary files to the board.
4141
"""
42-
flash_files = []
43-
for offset, path, encrypted in self.app.flash_files:
44-
if encrypted:
45-
continue
46-
flash_files.extend((str(offset), path))
4742

4843
flash_settings = []
49-
for k, v in self.app.flash_settings[self.app.target].items():
44+
for k, v in self.app.flash_settings.items():
5045
flash_settings.append(f'--{k}')
5146
flash_settings.append(v)
5247

@@ -55,7 +50,7 @@ def flash(self) -> None:
5550

5651
try:
5752
esptool.main(
58-
['--chip', self.app.target, 'write-flash', *flash_files, *flash_settings],
53+
['--chip', self.app.target, 'write-flash', hex(self.app.bootloader_offset), self.app.binary_file, *flash_settings],
5954
esp=self.esp,
6055
)
6156
except Exception:

pytest-embedded-arduino/tests/test_arduino.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def test_arduino_serial_flash(testdir):
77
import pytest
88
99
def test_arduino_app(app, dut):
10-
assert len(app.flash_files) == 3
10+
assert app.binary_file == os.path.join(testdir.tmpdir, 'hello_world_arduino/build/hello_world_arduino.ino.merged.bin')
1111
assert app.target == 'esp32'
1212
assert app.fqbn == 'espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app'
1313
dut.expect('Hello Arduino!')
@@ -19,10 +19,8 @@ def test_arduino_app(app, dut):
1919
'-s',
2020
'--embedded-services',
2121
'arduino,esp',
22-
'--app-path',
23-
os.path.join(testdir.tmpdir, 'hello_world_arduino'),
2422
'--build-dir',
25-
'build',
23+
os.path.join(testdir.tmpdir, 'hello_world_arduino', 'build'),
2624
)
2725

2826
result.assert_outcomes(passed=1)

pytest-embedded-wokwi/tests/test_wokwi.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_pexpect_by_wokwi(dut):
1818
dut.expect('Hello world!')
1919
dut.expect('Restarting')
2020
with pytest.raises(pexpect.TIMEOUT):
21-
dut.expect('foo bar not found', timeout=1)
21+
dut.expect('Hello world! or Restarting not found', timeout=1)
2222
""")
2323

2424
result = testdir.runpytest(
@@ -40,15 +40,15 @@ def test_pexpect_by_wokwi_esp32_arduino(testdir):
4040
def test_pexpect_by_wokwi(dut):
4141
dut.expect('Hello Arduino!')
4242
with pytest.raises(pexpect.TIMEOUT):
43-
dut.expect('foo bar not found', timeout=1)
43+
dut.expect('Hello Arduino! not found', timeout=1)
4444
""")
4545

4646
result = testdir.runpytest(
4747
'-s',
4848
'--embedded-services',
4949
'arduino,wokwi',
50-
'--app-path',
51-
os.path.join(testdir.tmpdir, 'hello_world_arduino'),
50+
'--build-dir',
51+
os.path.join(testdir.tmpdir, 'hello_world_arduino', 'build'),
5252
'--wokwi-diagram',
5353
os.path.join(testdir.tmpdir, 'hello_world_arduino/esp32.diagram.json'),
5454
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--flash-mode dio --flash-freq 80m --flash-size 4MB
2+
0x1000 hello_world_arduino.ino.bootloader.bin
3+
0x8000 hello_world_arduino.ino.partitions.bin
4+
0xe000 boot_app0.bin
5+
0x10000 hello_world_arduino.ino.bin
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)