11import json
2+ import logging
23import os
3- from typing import ClassVar
44
55from pytest_embedded .app import App
66
@@ -13,60 +13,95 @@ class ArduinoApp(App):
1313 sketch (str): Sketch name.
1414 fqbn (str): Fully Qualified Board Name.
1515 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.
16+ flash_settings (dict[str, str]): Flash settings for the target.
17+ binary_file (str): Merged binary file path.
18+ elf_file (str): ELF file path.
1719 """
1820
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-
4721 def __init__ (
4822 self ,
4923 ** kwargs ,
5024 ):
5125 super ().__init__ (** kwargs )
5226
53- self .sketch = os .path .basename (self .app_path )
27+ # Extract information from binary files in the build directory
28+ self .binary_path = self ._get_build_path ()
29+ self .sketch = self ._get_sketch_name (self .binary_path )
5430 self .fqbn = self ._get_fqbn (self .binary_path )
5531 self .target = self .fqbn .split (':' )[2 ]
56- self .flash_files = self ._get_bin_files (self .binary_path , self .sketch , self .target )
32+ self .flash_settings = self ._get_flash_settings ()
33+ self .binary_file = os .path .realpath (os .path .join (self .binary_path , self .sketch + '.ino.merged.bin' ))
5734 self .elf_file = os .path .realpath (os .path .join (self .binary_path , self .sketch + '.ino.elf' ))
5835
59- def _get_fqbn (self , build_path ) -> str :
36+ logging .debug (f'Build path: { self .binary_path } ' )
37+ logging .debug (f'Sketch name: { self .sketch } ' )
38+ logging .debug (f'FQBN: { self .fqbn } ' )
39+ logging .debug (f'Target: { self .target } ' )
40+ logging .debug (f'Flash settings: { self .flash_settings } ' )
41+ logging .debug (f'Binary file: { self .binary_file } ' )
42+ logging .debug (f'ELF file: { self .elf_file } ' )
43+
44+ def _get_build_path (self ) -> str :
45+ """Infer build path from binary path or app path."""
46+
47+ # Prioritize binary path over app path
48+ build_path = self .binary_path or self .app_path
49+
50+ if not build_path :
51+ raise ValueError ('No binary path or app path provided.' )
52+
53+ if os .path .isdir (build_path ):
54+ # If build path is a directory, we need to check if it contains a .ino.bin or .ino.merged.bin file
55+ # If not we need to recursively check the subdirectories
56+ for root , dirs , files in os .walk (build_path ):
57+ for filename in files :
58+ if filename .endswith ('.ino.bin' ) or filename .endswith ('.ino.merged.bin' ):
59+ return root
60+ raise ValueError (f'Could not find a valid binary file in { build_path } or its subdirectories.' )
61+ elif os .path .isfile (build_path ) and (build_path .endswith ('.ino.merged.bin' ) or build_path .endswith ('.ino.bin' )):
62+ # If build path is a recognized binary file, use the directory of the file
63+ return os .path .dirname (build_path )
64+ else :
65+ raise ValueError (f'Path { build_path } is not a directory or valid binary file.' )
66+
67+ def _get_sketch_name (self , build_path : str ) -> str :
68+ """Extract sketch name from binary files in the build directory."""
69+ if not build_path or not os .path .isdir (build_path ):
70+ logging .warning ('No build path found. Using default sketch name "sketch".' )
71+ return 'sketch'
72+
73+ # Look for .ino.bin or .ino.merged.bin files
74+ for filename in os .listdir (build_path ):
75+ if filename .endswith ('.ino.bin' ) or filename .endswith ('.ino.merged.bin' ):
76+ # Extract sketch name (everything before .ino.bin or .ino.merged.bin)
77+ if filename .endswith ('.ino.merged.bin' ):
78+ return filename [: - len ('.ino.merged.bin' )]
79+ else :
80+ return filename [: - len ('.ino.bin' )]
81+
82+ # If no .ino.bin or .ino.merged.bin files found, raise an error
83+ raise ValueError (f'No .ino.bin or .ino.merged.bin file found in { build_path } ' )
84+
85+ def _get_fqbn (self , build_path : str ) -> str :
86+ """Get FQBN from build.options.json file."""
6087 options_file = os .path .realpath (os .path .join (build_path , 'build.options.json' ))
6188 with open (options_file ) as f :
6289 options = json .load (f )
6390 fqbn = options ['fqbn' ]
6491 return fqbn
6592
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 )]
93+ def _get_flash_settings (self ) -> dict [str , str ]:
94+ """Get flash settings from flash_args file."""
95+ flash_args_file = os .path .realpath (os .path .join (self .binary_path , 'flash_args' ))
96+ with open (flash_args_file ) as f :
97+ flash_args = f .readline ().split (' ' )
98+
99+ flash_settings = {}
100+ for i , arg in enumerate (flash_args ):
101+ if arg .startswith ('--' ):
102+ flash_settings [arg [2 :].strip ()] = flash_args [i + 1 ].strip ()
103+
104+ if flash_settings == {}:
105+ raise ValueError (f'Flash settings not found in { flash_args_file } ' )
106+
107+ return flash_settings
0 commit comments