esp-test-utils 0.1.0__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.
Files changed (42) hide show
  1. esp_test_utils/__init__.py +1 -0
  2. esp_test_utils/__main__.py +20 -0
  3. esp_test_utils/adapter/__init__.py +0 -0
  4. esp_test_utils/adapter/base_port.py +452 -0
  5. esp_test_utils/adapter/dut/__init__.py +2 -0
  6. esp_test_utils/adapter/dut/dut_base.py +118 -0
  7. esp_test_utils/adapter/dut/serial_dut.py +107 -0
  8. esp_test_utils/adapter/dut/wrapper.py +63 -0
  9. esp_test_utils/basic/__init__.py +3 -0
  10. esp_test_utils/basic/decorators.py +101 -0
  11. esp_test_utils/basic/encoding.py +38 -0
  12. esp_test_utils/basic/network.py +193 -0
  13. esp_test_utils/basic/timestamp.py +17 -0
  14. esp_test_utils/basic/utils.py +5 -0
  15. esp_test_utils/config/__init__.py +1 -0
  16. esp_test_utils/config/default_config.py +2 -0
  17. esp_test_utils/config/env_config.py +130 -0
  18. esp_test_utils/devices/__init__.py +2 -0
  19. esp_test_utils/devices/attenuator.py +287 -0
  20. esp_test_utils/devices/serial_dut.py +28 -0
  21. esp_test_utils/devices/serial_tools.py +39 -0
  22. esp_test_utils/env/__init__.py +0 -0
  23. esp_test_utils/env/base_env.py +44 -0
  24. esp_test_utils/env/wifi_env.py +8 -0
  25. esp_test_utils/esp_console/__init__.py +1 -0
  26. esp_test_utils/esp_console/wifi_cmd.py +251 -0
  27. esp_test_utils/iperf_utility/__init__.py +10 -0
  28. esp_test_utils/iperf_utility/iperf_results.py +192 -0
  29. esp_test_utils/iperf_utility/iperf_test.py +137 -0
  30. esp_test_utils/iperf_utility/iperf_test.test.py +19 -0
  31. esp_test_utils/iperf_utility/line_chart.py +91 -0
  32. esp_test_utils/logger/__init__.py +1 -0
  33. esp_test_utils/logger/logger.py +10 -0
  34. esp_test_utils/network/__init__.py +0 -0
  35. esp_test_utils/tools/copy_bin.py +91 -0
  36. esp_test_utils/tools/set_att.py +25 -0
  37. esp_test_utils-0.1.0.dist-info/LICENSE +201 -0
  38. esp_test_utils-0.1.0.dist-info/METADATA +273 -0
  39. esp_test_utils-0.1.0.dist-info/RECORD +42 -0
  40. esp_test_utils-0.1.0.dist-info/WHEEL +5 -0
  41. esp_test_utils-0.1.0.dist-info/entry_points.txt +2 -0
  42. esp_test_utils-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ from .adapter.dut import dut_wrapper # noqa: F401
@@ -0,0 +1,20 @@
1
+ import sys
2
+
3
+
4
+ def main() -> None:
5
+ """main function, show python version and module version"""
6
+ print(f'Current python version: {sys.version}')
7
+ assert sys.version_info.major == 3, 'Only support python3'
8
+ if sys.version_info.minor < 8:
9
+ import pkg_resources
10
+
11
+ package_ver = pkg_resources.get_distribution('esp_test_utils').version
12
+ else:
13
+ from importlib.metadata import version
14
+
15
+ package_ver = version('esp_test_utils')
16
+ print(f'Installed esp_test_utils version: {package_ver}')
17
+
18
+
19
+ if __name__ == '__main__':
20
+ main()
File without changes
@@ -0,0 +1,452 @@
1
+ import abc
2
+ import functools
3
+ import logging
4
+ import os
5
+ import queue
6
+ import re
7
+ import threading
8
+ import time
9
+ from typing import AnyStr
10
+ from typing import Callable
11
+ from typing import Generic
12
+ from typing import Optional
13
+ from typing import overload
14
+ from typing import Tuple
15
+ from typing import Type
16
+ from typing import TypeVar
17
+ from typing import Union
18
+
19
+ import pexpect.spawnbase
20
+
21
+ from ..basic import generate_timestamp
22
+ from ..basic import to_bytes
23
+ from ..basic import to_str
24
+ from ..logger import get_logger
25
+
26
+ try:
27
+ from typing import Self
28
+ except ImportError:
29
+ # ignore type hints: Self
30
+ pass
31
+
32
+ LOGGER = get_logger('SerialDut')
33
+ NEVER_MATCHED_MAGIC_STRING = 'o6K,Q.(w+~yr~N9R'
34
+
35
+
36
+ class ExpectTimeout(TimeoutError):
37
+ """raise same ExpectTimeout rather than different Exception from different framework"""
38
+
39
+
40
+ class RawPort(metaclass=abc.ABCMeta):
41
+ """Define a minimum Dut class, the dut objects should at least support these methods
42
+
43
+ the dut should at least support these attributes:
44
+ - attribute name with type str
45
+ - method: write_bytes() with parameters: data[bytes]
46
+ - method: read_bytes() with parameters: timeout[float]
47
+ """
48
+
49
+ @classmethod
50
+ def __subclasshook__(cls, subclass: object) -> bool:
51
+ if not hasattr(subclass, 'read_bytes') or not callable(subclass.read_bytes):
52
+ return False
53
+ if not hasattr(subclass, 'write_bytes') or not callable(subclass.write_bytes):
54
+ return False
55
+ return True
56
+
57
+ def write_bytes(self, data: bytes) -> None:
58
+ """write bytes"""
59
+ raise NotImplementedError('Port class should implement this method')
60
+
61
+ def read_bytes(self, timeout: float = 0) -> bytes:
62
+ """blocking read bytes"""
63
+ raise NotImplementedError('Port class should implement this method')
64
+
65
+
66
+ T = TypeVar('T', bound=RawPort)
67
+
68
+
69
+ class PortSpawn(pexpect.spawnbase.SpawnBase, Generic[T]):
70
+ """Create a new class for pexpect with port read()/write() method.
71
+
72
+ There's some reason that we can not use pyserial with pexpect.fdpexpect directly:
73
+ - pyserial do not support fileno in windows.
74
+ - Pexpect only read from serial during expect() method.
75
+ - Can not read more than 4K data at once, the data may be lost if it is not read in time:
76
+ - https://stackoverflow.com/questions/2415074/serial-port-not-able-to-write-big-chunk-of-data
77
+
78
+ """
79
+
80
+ DEFAULT_READ_INTERVAL = 0.005
81
+
82
+ def __init__(
83
+ self,
84
+ port: T,
85
+ name: str = '',
86
+ log_file: Optional[str] = None,
87
+ timeout: float = 30,
88
+ logger: logging.Logger = LOGGER,
89
+ ) -> None:
90
+ """PortSpawn for pexpect
91
+
92
+ Args:
93
+ port (RawPort): port instance with read() method.
94
+ log_file (str, optional): log file path for saving serial output logs. Defaults to None.
95
+ timeout (int, optional): pexpect default timeout. Defaults to 30.
96
+ logger (logging.Logger): Specific port logger for logging.
97
+ """
98
+ super().__init__(timeout=timeout)
99
+ assert isinstance(port, RawPort)
100
+ self.name = name
101
+ self._port = port
102
+ if not self.name and hasattr(self.port, 'name'):
103
+ assert isinstance(self.port.name, str)
104
+ self.name = self.port.name
105
+ self.logger = logger
106
+ # Save serial logs to file
107
+ self.log_file = log_file
108
+
109
+ self._data_cache = b''
110
+ self._line_cache = b''
111
+ self._last_write_log_time = time.time()
112
+ # Create a new thread to read data from serial port
113
+ self._read_queue: queue.Queue = queue.Queue()
114
+ self._read_thread_stop_event = threading.Event()
115
+ self._read_thread = threading.Thread(target=self._read_incoming, name=f'Spawn_{self.name}')
116
+ self._read_thread.daemon = True
117
+ self._read_thread.start()
118
+ self.receive_callback: Optional[Callable[[str, AnyStr], None]] = None
119
+
120
+ @property
121
+ def port(self) -> T:
122
+ return self._port
123
+
124
+ @property
125
+ def read_timeout(self) -> float:
126
+ if hasattr(self.port, 'read_timeout'):
127
+ _timeout = self.port.read_timeout
128
+ assert isinstance(_timeout, float)
129
+ assert _timeout > 0
130
+ return _timeout
131
+ return self.DEFAULT_READ_INTERVAL
132
+
133
+ @property
134
+ def data_cache(self) -> str:
135
+ return self._data_cache.decode('utf-8', errors='replace')
136
+
137
+ def _write_port_log(self, data: bytes) -> None:
138
+ """Write serial outputs to log file"""
139
+ data_to_write = b''
140
+ if data:
141
+ self._line_cache += data
142
+ if self._line_cache.endswith(b'\n'):
143
+ data_to_write = self._line_cache
144
+ self._line_cache = b''
145
+ elif b'\n' in self._line_cache:
146
+ _index = self._line_cache.rfind(b'\n') + 1
147
+ data_to_write = self._line_cache[:_index]
148
+ self._line_cache = self._line_cache[_index:]
149
+ if not data_to_write and self._line_cache and time.time() - self._last_write_log_time > self.read_timeout * 5:
150
+ # No new data for a long time, flush line cache
151
+ # Default timeout is serial.timeout * 5, depends on read timeout of serial instance
152
+ # Minimum serial timeout is 1ms, 5 ms should be enough for most lines.
153
+ data_to_write = self._line_cache
154
+ self._line_cache = b''
155
+
156
+ if data_to_write:
157
+ self._last_write_log_time = time.time()
158
+ if self.log_file:
159
+ with open(self.log_file, 'ab+') as f:
160
+ _time_info = f'\n[{generate_timestamp()}]\n'.encode()
161
+ f.write(_time_info)
162
+ f.write(data_to_write)
163
+ else:
164
+ self.logger.debug(f'[{self.name}]: {to_str(data_to_write)}')
165
+
166
+ def _read_incoming(self) -> None:
167
+ """Running in a thread to read serial output and save to data cache."""
168
+ self.logger.debug(f'Start serial {self.name} read thread.')
169
+ assert isinstance(self.read_timeout, float)
170
+ assert self.read_timeout > 0
171
+ while True:
172
+ if self._read_thread_stop_event.is_set():
173
+ # Stop the thread when spawn stop.
174
+ self.logger.debug(f'Stop port {self.name} read thread.')
175
+ return
176
+ new_data = b''
177
+ try:
178
+ # some port instances do not support changing read timeout, therefore use default timeout of the
179
+ new_data = self.port.read_bytes(timeout=self.read_timeout)
180
+ except Exception as e: # pylint: disable=W0718
181
+ self._log(to_bytes(f'PortRead {type(e)}: {str(e)}'), 'read')
182
+ self._write_port_log(to_bytes(f'SerialException: {str(e)}'))
183
+ self.logger.exception(f'{self.name} reading thread stopped {type(e)}: {str(e)}')
184
+ return
185
+ if new_data:
186
+ self._read_queue.put(new_data)
187
+ if self.receive_callback and callable(self.receive_callback):
188
+ # https://stackoverflow.com/questions/69732212/pylint-self-xxx-is-not-callable
189
+ self.receive_callback(self.name, new_data) # pylint: disable=E1102
190
+ # the last line may be cached, to make the file more readable after adding timestamp
191
+ # always check need write to file or not whether there's new data
192
+ self._write_port_log(new_data)
193
+
194
+ def write(self, data: AnyStr) -> None:
195
+ self.port.write_bytes(to_bytes(data))
196
+
197
+ def read_nonblocking(self, size: int = 1, timeout: Optional[Union[int, float]] = None) -> bytes:
198
+ """This method was used during expect(), reads data from serial output data cache.
199
+
200
+ If the data cache is not empty, it will return immediately. Otherwise, waiting for new data.
201
+
202
+ Args:
203
+ size (int, optional): maximum size of returning data. Defaults to 1.
204
+ timeout (Union[int, float] | None, optional): maximum block time waiting for new data.
205
+
206
+ Returns:
207
+ bytes: new serial output data.
208
+ """
209
+ if timeout is None:
210
+ timeout = self.timeout
211
+ assert timeout is not None
212
+ t0 = time.time()
213
+ # Read out all cache from queue first
214
+ while True:
215
+ try:
216
+ _new_data = self._read_queue.get(timeout=0)
217
+ self._data_cache += _new_data
218
+ except queue.Empty:
219
+ break
220
+ self.logger.debug(self._data_cache)
221
+ # Waiting for more data until timeout if there's no data cache.
222
+ # Any new data should be returned immediately.
223
+ time_left = t0 + timeout - time.time()
224
+ while not self._data_cache and time_left > 0:
225
+ try:
226
+ _new_data = self._read_queue.get(timeout=time_left)
227
+ self._data_cache += _new_data
228
+ except queue.Empty:
229
+ break
230
+ time_left = t0 + timeout - time.time()
231
+ # Returned data should not more than given size.
232
+ if self._data_cache:
233
+ ret_data = self._data_cache[:size]
234
+ self._data_cache = self._data_cache[size:]
235
+ else:
236
+ ret_data = b''
237
+ # _log here to be same with pexpect SpawnBase
238
+ self._log(ret_data, 'read') # type: ignore
239
+ return ret_data
240
+
241
+ def stop(self) -> None:
242
+ """Stop and clean up"""
243
+ self.logger.debug(f'Stopping SerialSpawn {self.name}')
244
+ self._read_thread_stop_event.set()
245
+ self._read_thread.join()
246
+ self._read_queue.empty()
247
+ self.receive_callback = None
248
+ self._data_cache = b''
249
+ self._line_cache = b''
250
+
251
+
252
+ class BasePort(Generic[T]):
253
+ """A class to simply port methods for all devices / shell / sockets to similar usage
254
+
255
+ - Create receive thread and pexpect spawn process for data read/expect
256
+ - Redefine
257
+
258
+ """
259
+
260
+ EXPECT_TIMEOUT_EXCEPTIONS: Tuple[Type[Exception], ...] = (
261
+ TimeoutError,
262
+ pexpect.exceptions.ExceptionPexpect,
263
+ )
264
+ INIT_START_PEXPECT_PROC: bool = True
265
+ DISABLE_PEXPECT_PROC: bool = False
266
+ PEXPECT_DEFAULT_TIMEOUT: float = 30
267
+
268
+ def __init__(
269
+ self,
270
+ port: T,
271
+ name: str = '',
272
+ log_file: str = '',
273
+ logger: Optional[logging.Logger] = None,
274
+ ) -> None:
275
+ if port:
276
+ assert isinstance(port, RawPort)
277
+ self._port = port
278
+ self._name = name
279
+ self._log_file = log_file
280
+ self.expect_timeout_exceptions = self.EXPECT_TIMEOUT_EXCEPTIONS
281
+ self.logger = logger or LOGGER
282
+ self.timeout = self.PEXPECT_DEFAULT_TIMEOUT
283
+
284
+ self._pexpect_proc: Optional[PortSpawn] = None
285
+ if self.INIT_START_PEXPECT_PROC:
286
+ self.start_pexpect_proc()
287
+
288
+ @property
289
+ def port(self) -> T:
290
+ return self._port # type: ignore
291
+
292
+ @property
293
+ def name(self) -> str:
294
+ return self._name
295
+
296
+ @name.setter
297
+ def name(self, value: str) -> None:
298
+ self._name = value
299
+ if self.spawn:
300
+ self.spawn.name = value
301
+
302
+ def _init_log_file(self) -> None:
303
+ if self.log_file:
304
+ os.makedirs(os.path.dirname(self.log_file), exist_ok=True)
305
+ with open(self.log_file, 'ab+') as f:
306
+ f.write(f'--------- Saving {self.name}:{self.port} logs to this file --------\n'.encode())
307
+ else:
308
+ self.logger.debug(f'do not save {self.name}:{self.port} logs to file')
309
+
310
+ @property
311
+ def log_file(self) -> str:
312
+ """Get Current dut log file."""
313
+ if not self._log_file:
314
+ return ''
315
+ return os.path.abspath(self._log_file)
316
+
317
+ @log_file.setter
318
+ def log_file(self, new_log_file: str) -> None:
319
+ """Set Current dut log file."""
320
+ if new_log_file == self._log_file:
321
+ return
322
+ if self._pexpect_proc:
323
+ self._pexpect_proc.serial_log_file = new_log_file
324
+ self._log_file = new_log_file
325
+
326
+ @property
327
+ def spawn(self) -> Optional[PortSpawn]:
328
+ """Allow the use of pexpect spawn enhancements, if pexpect process is available"""
329
+ return self._pexpect_proc
330
+
331
+ def start_pexpect_proc(self) -> None:
332
+ if self.DISABLE_PEXPECT_PROC:
333
+ return
334
+ if self._pexpect_proc:
335
+ return
336
+ self._init_log_file()
337
+ self._pexpect_proc = PortSpawn(self.port, self.name, self.log_file, self.PEXPECT_DEFAULT_TIMEOUT, self.logger)
338
+
339
+ @staticmethod
340
+ def _handle_expect_timeout(func: Callable) -> Callable:
341
+ """Raise same type exception ExpectTimeout for ports from different frameworks"""
342
+
343
+ @functools.wraps(func)
344
+ def wrap(self, *args, **kwargs): # type: ignore
345
+ try:
346
+ result = func(self, *args, **kwargs)
347
+ except self.expect_timeout_exceptions as e:
348
+ raise ExpectTimeout(str(e)) from e
349
+ return result
350
+
351
+ return wrap
352
+
353
+ def write(self, data: AnyStr) -> None:
354
+ if self._pexpect_proc:
355
+ return self._pexpect_proc.write(data)
356
+ raise NotImplementedError()
357
+
358
+ def write_line(self, data: AnyStr, end: str = '\n') -> None:
359
+ return self.write(to_bytes(data, end))
360
+
361
+ @_handle_expect_timeout
362
+ def expect_exact(self, pattern: Union[str, bytes], timeout: float) -> None:
363
+ """this is similar to expect(), but only uses plain string/bytes matching"""
364
+ if self.spawn:
365
+ pexpect_pattern = to_bytes(pattern)
366
+ self.spawn.expect_exact(pexpect_pattern, timeout=timeout)
367
+ raise NotImplementedError()
368
+
369
+ @overload
370
+ def expect(self, pattern: str, timeout: float = 30) -> None: ...
371
+ @overload
372
+ def expect(self, pattern: bytes, timeout: float = 30) -> None: ...
373
+ @overload
374
+ def expect(self, pattern: re.Pattern[str], timeout: float = 30) -> re.Match[str]: ...
375
+ @overload
376
+ def expect(self, pattern: re.Pattern[bytes], timeout: float = 30) -> re.Match[bytes]: ...
377
+
378
+ @_handle_expect_timeout
379
+ def expect(self, pattern, timeout=PEXPECT_DEFAULT_TIMEOUT): # type: ignore
380
+ """This seeks through the stream until a pattern is matched.
381
+
382
+ This expect() method is different with the one in pexpect.
383
+ This method only accepts pattern type str/bytes or re.Pattern. Does not accept list, EOF or TIMEOUT.
384
+ If the pattern type is str or bytes, this method is similar to expect_exact(), but returning None.
385
+ If the pattern type is re.Pattern, this method will return a re.Match object if the pattern is matched.
386
+ Can read all output data by pattern=re.compile('.+', re.DOTALL)
387
+
388
+ Args:
389
+ pattern (Union[str, bytes, re.Pattern]): pattern to match
390
+ timeout (int, optional): seconds of waiting for new data if match failed. Defaults to 30s.
391
+
392
+ Returns:
393
+ Optional[re.Match]: match result if the input pattern is re.Pattern
394
+ """
395
+ if self._pexpect_proc:
396
+ if isinstance(pattern, (bytes, str)):
397
+ self._pexpect_proc.expect_exact(pattern, timeout=timeout)
398
+ return None
399
+
400
+ assert isinstance(pattern, re.Pattern)
401
+ if isinstance(pattern.pattern, str):
402
+ # re-compile regex pattern using bytes, with same flags
403
+ re_flags = pattern.flags & (re.DOTALL | re.MULTILINE | re.IGNORECASE)
404
+ pexpect_pattern = re.compile(to_bytes(pattern.pattern), re_flags)
405
+ else:
406
+ pexpect_pattern = pattern
407
+ self._pexpect_proc.expect(pexpect_pattern, timeout=timeout)
408
+ match = self._pexpect_proc.match
409
+ if isinstance(pattern.pattern, str) and isinstance(match, re.Match):
410
+ # convert the match result into string
411
+ match = pattern.match(to_str(match.group(0)))
412
+ return match # type: ignore
413
+ raise NotImplementedError()
414
+
415
+ @property
416
+ def data_cache(self) -> str:
417
+ return self.read_all_data(flush=False)
418
+
419
+ def flush_data(self) -> str:
420
+ return self.read_all_data(flush=True)
421
+
422
+ def read_all_data(self, flush: bool = True) -> str:
423
+ return to_str(self.read_all_bytes(flush))
424
+
425
+ def read_all_bytes(self, flush: bool = False) -> bytes:
426
+ """Read out all data from dut, return immediately.
427
+
428
+ Returns:
429
+ bytes: all data read from dut
430
+ """
431
+ buffer = b''
432
+ if flush:
433
+ match = self.expect(re.compile(b'.*', re.DOTALL), timeout=0)
434
+ assert match
435
+ buffer = match.group(0)
436
+ else:
437
+ # flush spawn buffer
438
+ assert self._pexpect_proc
439
+ self._pexpect_proc.expect_exact(pexpect.TIMEOUT, timeout=0)
440
+ buffer = to_bytes(self._pexpect_proc.buffer)
441
+ assert isinstance(buffer, bytes)
442
+ return buffer
443
+
444
+ def close(self) -> None:
445
+ if self._pexpect_proc:
446
+ self._pexpect_proc.stop()
447
+
448
+ def __enter__(self) -> 'Self':
449
+ return self
450
+
451
+ def __exit__(self, exc_type, exc_value, trace) -> None: # type: ignore
452
+ self.close()
@@ -0,0 +1,2 @@
1
+ from .dut_base import DutPort # noqa: F401
2
+ from .wrapper import dut_wrapper # noqa: F401
@@ -0,0 +1,118 @@
1
+ from typing import Any
2
+ from typing import AnyStr
3
+ from typing import Dict
4
+ from typing import Union
5
+
6
+ from ...basic.utils import mac_offset
7
+ from ..base_port import BasePort
8
+
9
+
10
+ class DutMacMixin:
11
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
12
+ super().__init__(*args, **kwargs)
13
+ self._device_mac: str = '00:00:00:00:00:00'
14
+ self._sta_mac: str = ''
15
+ self._ap_mac: str = ''
16
+ self._bt_mac: str = ''
17
+ self._eth_mac: str = ''
18
+
19
+ @property
20
+ def mac(self) -> str:
21
+ return self._device_mac
22
+
23
+ @property
24
+ def device_mac(self) -> str:
25
+ return self._device_mac
26
+
27
+ @device_mac.setter
28
+ def device_mac(self, value: str) -> None:
29
+ self._device_mac = value
30
+
31
+ @property
32
+ def sta_mac(self) -> str:
33
+ return self._sta_mac or self._device_mac
34
+
35
+ @sta_mac.setter
36
+ def sta_mac(self, value: str) -> None:
37
+ self._sta_mac = value
38
+
39
+ @property
40
+ def ap_mac(self) -> str:
41
+ return self._ap_mac or mac_offset(self._device_mac, 1)
42
+
43
+ @ap_mac.setter
44
+ def ap_mac(self, value: str) -> None:
45
+ self._ap_mac = value
46
+
47
+ @property
48
+ def bt_mac(self) -> str:
49
+ return self._bt_mac or mac_offset(self._device_mac, 2)
50
+
51
+ @bt_mac.setter
52
+ def bt_mac(self, value: str) -> None:
53
+ self._bt_mac = value
54
+
55
+ @property
56
+ def eth_mac(self) -> str:
57
+ # do not generate eth mac from device mac
58
+ return self._eth_mac # or mac_offset(self._device_mac, 3)
59
+
60
+ @eth_mac.setter
61
+ def eth_mac(self, value: str) -> None:
62
+ self._eth_mac = value
63
+
64
+
65
+ class DutPort(DutMacMixin, BasePort):
66
+ """Add dut related methods to Port"""
67
+
68
+ def __init__(self, dut: Any, name: str, log_file: str = '') -> None:
69
+ super().__init__(dut, name, log_file)
70
+
71
+ def write_line(self, data: AnyStr, end: str = '\r\n') -> None:
72
+ """Use \\r\\n as default ending"""
73
+ return super().write_line(data, end)
74
+
75
+ def stop_receive_thread(self) -> None:
76
+ raise NotImplementedError()
77
+
78
+ def start_receive_thread(self) -> None:
79
+ self.start_pexpect_proc()
80
+
81
+ # Attributes needed by bin path
82
+ @property
83
+ def bin_path(self) -> str:
84
+ raise NotImplementedError()
85
+
86
+ @property
87
+ def sdkconfig(self) -> Dict[str, Any]:
88
+ raise NotImplementedError()
89
+
90
+ @property
91
+ def target(self) -> str:
92
+ raise NotImplementedError()
93
+
94
+ @property
95
+ def partition_table(self) -> Dict[str, Any]:
96
+ raise NotImplementedError()
97
+
98
+ # Serial Specific
99
+ def reconfigure(self) -> bool:
100
+ raise NotImplementedError()
101
+
102
+ def hard_reset(self) -> None:
103
+ raise NotImplementedError()
104
+
105
+ # EspTool Specific
106
+ def flash(self, bin_path: str = '') -> None:
107
+ raise NotImplementedError()
108
+
109
+ def flash_partition(self, part: Union[int, str], bin_path: str = '') -> None:
110
+ raise NotImplementedError()
111
+
112
+ def flash_nvs(self, bin_path: str = '') -> None:
113
+ raise NotImplementedError()
114
+
115
+ def dump_flash(self, part: Union[int, str], bin_path: str, size: int = 0) -> None:
116
+ raise NotImplementedError()
117
+
118
+ # More extra methods may be implemented