esp-test-utils 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/.gitignore +3 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/.pre-commit-config.yaml +7 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/CHANGELOG.md +9 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/PKG-INFO +3 -1
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/PKG-INFO +3 -1
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/SOURCES.txt +9 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/requires.txt +2 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/dut_base.py +10 -5
- esp_test_utils-0.2.1/esptest/db/runners.py +199 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/scripts/downbin.py +20 -8
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/tools/download_bin.py +53 -4
- esp_test_utils-0.2.1/esptest/tools/http_download.py +61 -0
- esp_test_utils-0.2.1/esptest/utility/gen_esp32part.py +808 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/utility/parse_bin_path.py +125 -44
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/pyproject.toml +7 -0
- esp_test_utils-0.2.1/tests/conftest.py +0 -0
- esp_test_utils-0.2.1/tests/db/test_db_runners.py +66 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/test_Dut.py +1 -1
- esp_test_utils-0.2.1/tests/tools/test_download_file.py +44 -0
- esp_test_utils-0.2.1/tests/utility/_files/test-bin.zip +0 -0
- esp_test_utils-0.2.1/tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig +80 -0
- esp_test_utils-0.2.1/tests/utility/test_parse_bin_path.py +149 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/.github/.gitkeep +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/.github/workflows/pypi-publish.yml +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/.gitlab-ci.yml +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/CONTRIBUTING.md +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/LICENSE +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/README.md +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/docs/Makefile +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/docs/conf.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/docs/index.rst +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/docs/make.bat +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/dependency_links.txt +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/entry_points.txt +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esp_test_utils.egg-info/top_level.txt +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/__main__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/create_dut.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/esp_dut.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/esp_mixin.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/esp_port.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/mac_mixin.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/dut/wrapper.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/port/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/port/base_port.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/adapter/port/serial_port.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/all.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/compat_typing.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/data_monitor.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/decorators.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/encoding.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/generator.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/shell.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/common/timestamp.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/config/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/config/default_config.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/config/env_config.py +0 -0
- {esp_test_utils-0.2.0/esptest/devices → esp_test_utils-0.2.1/esptest/db}/__init__.py +0 -0
- {esp_test_utils-0.2.0/esptest/env → esp_test_utils-0.2.1/esptest/devices}/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/devices/attenuator.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/devices/esp_serial.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/devices/serial_dut.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/devices/serial_tools.py +0 -0
- {esp_test_utils-0.2.0/esptest/interface → esp_test_utils-0.2.1/esptest/env}/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/env/base_env.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/env/wifi_env.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/esp_console/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/esp_console/wifi_cmd.py +0 -0
- {esp_test_utils-0.2.0/esptest/network → esp_test_utils-0.2.1/esptest/interface}/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/interface/dut.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/interface/port.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/iperf_utility/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/iperf_utility/iperf_results.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/iperf_utility/iperf_test.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/iperf_utility/iperf_test.test.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/iperf_utility/line_chart.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/logger/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/logger/logger.py +0 -0
- {esp_test_utils-0.2.0/tests → esp_test_utils-0.2.1/esptest/network}/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/network/mac.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/network/netif.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/network/nic.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/scripts/list_ports.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/scripts/monitor.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/scripts/set_att.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/tools/copy_bin.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/esptest/tools/pip_check.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/example/jap_test.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/example/restart_test.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/setup.cfg +0 -0
- /esp_test_utils-0.2.0/tests/conftest.py → /esp_test_utils-0.2.1/tests/__init__.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/basic/test_decorators.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/basic/test_network.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/esp_console/conftest.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/esp_console/test_WifiCmd.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/test_chart.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/test_iperf_results.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/iperf_utility/test_iperf_util.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/test_EnvConfig.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/test_common.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/test_import.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tests/tools/test_pip_check.py +0 -0
- {esp_test_utils-0.2.0 → esp_test_utils-0.2.1}/tools/ci/check_dev_version.py +0 -0
|
@@ -35,6 +35,7 @@ repos:
|
|
|
35
35
|
- 'types-psutil'
|
|
36
36
|
- 'types-PyYAML'
|
|
37
37
|
- 'typing_extensions'
|
|
38
|
+
- 'sqlalchemy'
|
|
38
39
|
|
|
39
40
|
- repo: https://github.com/espressif/conventional-precommit-linter
|
|
40
41
|
rev: v1.10.0
|
|
@@ -48,3 +49,9 @@ repos:
|
|
|
48
49
|
- id: codespell
|
|
49
50
|
args: ["--write-changes"]
|
|
50
51
|
additional_dependencies: [tomli]
|
|
52
|
+
|
|
53
|
+
exclude: |
|
|
54
|
+
(
|
|
55
|
+
^docs/.*
|
|
56
|
+
| gen_esp32part\.py$
|
|
57
|
+
)
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## v0.2.1 (2025-09-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
- feat(utility): support to flash bin to encrypted device
|
|
5
|
+
- feat: add runners database
|
|
6
|
+
- feat: add default gen part tool
|
|
7
|
+
- feat(utility): support to check flash enc from bin path
|
|
8
|
+
- fix: get baud from bin path sdkconfig file
|
|
9
|
+
|
|
1
10
|
## v0.2.0 (2025-07-16)
|
|
2
11
|
|
|
3
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -228,7 +228,9 @@ Requires-Dist: pyserial
|
|
|
228
228
|
Requires-Dist: PyYAML
|
|
229
229
|
Requires-Dist: pexpect
|
|
230
230
|
Requires-Dist: pyusb
|
|
231
|
+
Requires-Dist: esptool
|
|
231
232
|
Requires-Dist: packaging
|
|
233
|
+
Requires-Dist: sqlalchemy
|
|
232
234
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
233
235
|
Provides-Extra: idfci
|
|
234
236
|
Requires-Dist: pyecharts; extra == "idfci"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -228,7 +228,9 @@ Requires-Dist: pyserial
|
|
|
228
228
|
Requires-Dist: PyYAML
|
|
229
229
|
Requires-Dist: pexpect
|
|
230
230
|
Requires-Dist: pyusb
|
|
231
|
+
Requires-Dist: esptool
|
|
231
232
|
Requires-Dist: packaging
|
|
233
|
+
Requires-Dist: sqlalchemy
|
|
232
234
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
233
235
|
Provides-Extra: idfci
|
|
234
236
|
Requires-Dist: pyecharts; extra == "idfci"
|
|
@@ -44,6 +44,8 @@ esptest/common/timestamp.py
|
|
|
44
44
|
esptest/config/__init__.py
|
|
45
45
|
esptest/config/default_config.py
|
|
46
46
|
esptest/config/env_config.py
|
|
47
|
+
esptest/db/__init__.py
|
|
48
|
+
esptest/db/runners.py
|
|
47
49
|
esptest/devices/__init__.py
|
|
48
50
|
esptest/devices/attenuator.py
|
|
49
51
|
esptest/devices/esp_serial.py
|
|
@@ -74,7 +76,9 @@ esptest/scripts/monitor.py
|
|
|
74
76
|
esptest/scripts/set_att.py
|
|
75
77
|
esptest/tools/copy_bin.py
|
|
76
78
|
esptest/tools/download_bin.py
|
|
79
|
+
esptest/tools/http_download.py
|
|
77
80
|
esptest/tools/pip_check.py
|
|
81
|
+
esptest/utility/gen_esp32part.py
|
|
78
82
|
esptest/utility/parse_bin_path.py
|
|
79
83
|
example/jap_test.py
|
|
80
84
|
example/restart_test.py
|
|
@@ -86,6 +90,7 @@ tests/test_common.py
|
|
|
86
90
|
tests/test_import.py
|
|
87
91
|
tests/basic/test_decorators.py
|
|
88
92
|
tests/basic/test_network.py
|
|
93
|
+
tests/db/test_db_runners.py
|
|
89
94
|
tests/esp_console/conftest.py
|
|
90
95
|
tests/esp_console/test_WifiCmd.py
|
|
91
96
|
tests/esp_console/_files/wifi_cmd_connected_1.log
|
|
@@ -97,5 +102,9 @@ tests/iperf_utility/_files/dut_iperf_rx1.log
|
|
|
97
102
|
tests/iperf_utility/_files/dut_iperf_rx2.log
|
|
98
103
|
tests/iperf_utility/_files/pc_iperf_rx.log
|
|
99
104
|
tests/iperf_utility/_files/pc_iperf_rx2.log
|
|
105
|
+
tests/tools/test_download_file.py
|
|
100
106
|
tests/tools/test_pip_check.py
|
|
107
|
+
tests/utility/test_parse_bin_path.py
|
|
108
|
+
tests/utility/_files/test-bin.zip
|
|
109
|
+
tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig
|
|
101
110
|
tools/ci/check_dev_version.py
|
|
@@ -27,7 +27,8 @@ DEFAULT_SERIAL_CONFIGS = {'timeout': 0.005}
|
|
|
27
27
|
class DutConfig:
|
|
28
28
|
name: str = '' # default = dut name / port name
|
|
29
29
|
device: str = '' # log serial device, eg: '/dev/ttyUSB0', 'COM3', etc.
|
|
30
|
-
baudrate: int = 0 # 0: get from bin path or 115200
|
|
30
|
+
baudrate: int = 0 # console baudrate, 0: get from bin path or 115200
|
|
31
|
+
baudrate_from_bin_path: bool = True # always get baudrate from bin path if bin_path is set
|
|
31
32
|
serial_configs: t.Optional[t.Dict[str, t.Any]] = None # serial configs, eg: {'bytesize': 8, 'timeout': 0.1}
|
|
32
33
|
# capabilities
|
|
33
34
|
support_esptool: bool = False # esp port or serial port
|
|
@@ -71,10 +72,14 @@ class DutConfig:
|
|
|
71
72
|
# bin_path and get variables from bin path
|
|
72
73
|
if self.bin_path:
|
|
73
74
|
self.bin_path = Path(self.bin_path).expanduser().resolve()
|
|
74
|
-
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
parsed_bin = ParseBinPath(self.bin_path)
|
|
76
|
+
self.esptool_stub = parsed_bin.stub
|
|
77
|
+
self.esptool_chip = parsed_bin.chip
|
|
78
|
+
if self.baudrate_from_bin_path:
|
|
79
|
+
# baudrate from bin path is much reliable than the specified one for uart0
|
|
80
|
+
self.baudrate = get_baud_from_bin_path(self.bin_path) or self.baudrate or 115200
|
|
81
|
+
if not self.baudrate:
|
|
82
|
+
self.baudrate = 115200 # set default baudrate to 115200
|
|
78
83
|
# download device
|
|
79
84
|
if not self.download_device:
|
|
80
85
|
self.download_device = self.device
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import csv
|
|
3
|
+
from typing import Any, Dict, Generator, List, Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import JSON, Engine, ForeignKey, Integer, String
|
|
6
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base(DeclarativeBase):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class User(Base):
|
|
14
|
+
__tablename__ = 'user'
|
|
15
|
+
|
|
16
|
+
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True)
|
|
17
|
+
name: Mapped[str] = mapped_column(String(50), unique=True)
|
|
18
|
+
email: Mapped[Optional[str]] = mapped_column(String(50))
|
|
19
|
+
runners: Mapped[List['Runner']] = relationship(back_populates='user')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Runner(Base):
|
|
23
|
+
__tablename__ = 'runner'
|
|
24
|
+
|
|
25
|
+
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True)
|
|
26
|
+
mac: Mapped[str] = mapped_column(String(50), unique=True)
|
|
27
|
+
ip: Mapped[str] = mapped_column(String(100), unique=True)
|
|
28
|
+
vlan: Mapped[Optional[int]] = mapped_column(Integer)
|
|
29
|
+
switch_info: Mapped[Optional[str]] = mapped_column(String) # ip-port
|
|
30
|
+
name: Mapped[str] = mapped_column(String(50))
|
|
31
|
+
owner: Mapped[Optional[str]] = mapped_column(ForeignKey('user.name'), nullable=True)
|
|
32
|
+
tags: Mapped[Optional[List[str]]] = mapped_column(JSON) # only PostgreSQL supports ARRAY(String)
|
|
33
|
+
raw_data: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON)
|
|
34
|
+
description: Mapped[Optional[str]] = mapped_column(String)
|
|
35
|
+
# Link to user table
|
|
36
|
+
user: Mapped[User] = relationship(back_populates='runners')
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
tags_str = ','.join(self.tags) if self.tags else ''
|
|
40
|
+
return (
|
|
41
|
+
f'Runner('
|
|
42
|
+
f'id={self.id},'
|
|
43
|
+
f'mac={self.mac},'
|
|
44
|
+
f'ip={self.ip},'
|
|
45
|
+
f'vlan={self.vlan},'
|
|
46
|
+
f'switch_info={self.switch_info},'
|
|
47
|
+
f'name={self.name},'
|
|
48
|
+
f'owner={self.owner},'
|
|
49
|
+
f'tags="{tags_str}",'
|
|
50
|
+
f'raw_data={self.raw_data},'
|
|
51
|
+
f'description="{self.description}"'
|
|
52
|
+
')'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RunnerDB:
|
|
57
|
+
def __init__(self, engine: Engine) -> None:
|
|
58
|
+
self.engine = engine
|
|
59
|
+
self._session: Optional[Session] = None
|
|
60
|
+
self.__init_db()
|
|
61
|
+
|
|
62
|
+
def __init_db(self) -> None:
|
|
63
|
+
Base.metadata.create_all(self.engine)
|
|
64
|
+
|
|
65
|
+
@contextlib.contextmanager
|
|
66
|
+
def open_session(self, *args: Any, **kwargs: Any) -> Generator['DBSession', None, None]:
|
|
67
|
+
session = DBSession(self, *args, **kwargs)
|
|
68
|
+
yield session
|
|
69
|
+
try:
|
|
70
|
+
session.commit()
|
|
71
|
+
except:
|
|
72
|
+
session.rollback()
|
|
73
|
+
raise
|
|
74
|
+
finally:
|
|
75
|
+
session.close()
|
|
76
|
+
|
|
77
|
+
def to_csv(self, csv_file: str) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Export the runners to a CSV file.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
csv_file: The path to the CSV file.
|
|
83
|
+
session: The session to use. If None, use the default session.
|
|
84
|
+
"""
|
|
85
|
+
with self.open_session() as session:
|
|
86
|
+
runners = session.all_runners()
|
|
87
|
+
fields = [c.name for c in Runner.__table__.columns]
|
|
88
|
+
with open(csv_file, 'w', encoding='utf-8') as f:
|
|
89
|
+
writer = csv.writer(f)
|
|
90
|
+
writer.writerow(fields)
|
|
91
|
+
for runner in runners:
|
|
92
|
+
writer.writerow([getattr(runner, f) for f in fields])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class DBSession(Session):
|
|
96
|
+
def __init__(self, runner_db: RunnerDB, *args: Any, **kwargs: Any) -> None:
|
|
97
|
+
self.runner_db = runner_db
|
|
98
|
+
super().__init__(self.runner_db.engine, *args, **kwargs)
|
|
99
|
+
|
|
100
|
+
def all_runners(self) -> List[Runner]:
|
|
101
|
+
return self.query(Runner).outerjoin(Runner.user).all()
|
|
102
|
+
|
|
103
|
+
def all_users(self) -> List[User]:
|
|
104
|
+
return self.query(User).outerjoin(User.runners).all()
|
|
105
|
+
|
|
106
|
+
def get_runners_by_mac(self, mac: str) -> List[Runner]:
|
|
107
|
+
return self.query(Runner).filter(Runner.mac == mac).all()
|
|
108
|
+
|
|
109
|
+
def get_runners_by_ip(self, ip: str) -> List[Runner]:
|
|
110
|
+
return self.query(Runner).filter(Runner.ip == ip).all()
|
|
111
|
+
|
|
112
|
+
def get_users_by_name(self, name: str) -> List[User]:
|
|
113
|
+
return self.query(User).outerjoin(Runner.user).filter(User.name == name).all()
|
|
114
|
+
|
|
115
|
+
def get_users_by_email(self, email: str) -> List[User]:
|
|
116
|
+
return self.query(User).outerjoin(Runner.user).filter(User.email == email).all()
|
|
117
|
+
|
|
118
|
+
def remove_runner(self, runner: Runner) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Remove a runner.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
runner: The runner to remove.
|
|
124
|
+
session: The session to use. If None, use the default session.
|
|
125
|
+
"""
|
|
126
|
+
# session.merge(runner)
|
|
127
|
+
self.delete(runner)
|
|
128
|
+
self.flush()
|
|
129
|
+
|
|
130
|
+
def add_or_update_runner(self, runner: Runner) -> int:
|
|
131
|
+
"""
|
|
132
|
+
Add or update a runner.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
runner: The runner to add or update.
|
|
136
|
+
session: The session to use. If None, use the default session.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
if runner.id is not None:
|
|
140
|
+
self.merge(runner)
|
|
141
|
+
else:
|
|
142
|
+
if self.get_runners_by_mac(runner.mac) or self.get_runners_by_ip(runner.ip):
|
|
143
|
+
raise ValueError(f'Runner with mac {runner.mac} or ip {runner.ip} already exists')
|
|
144
|
+
self.add(runner)
|
|
145
|
+
self.flush()
|
|
146
|
+
return runner.id
|
|
147
|
+
|
|
148
|
+
def add_or_update_user(self, user: User) -> int:
|
|
149
|
+
"""
|
|
150
|
+
Add or update an user.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
user: The user to add or update.
|
|
154
|
+
session: The session to use. If None, use the default session.
|
|
155
|
+
"""
|
|
156
|
+
if user.id is not None:
|
|
157
|
+
self.merge(user)
|
|
158
|
+
else:
|
|
159
|
+
if self.get_users_by_name(user.name):
|
|
160
|
+
raise ValueError(f'User with name {user.name} already exists')
|
|
161
|
+
self.add(user)
|
|
162
|
+
self.flush()
|
|
163
|
+
return user.id
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == '__main__':
|
|
167
|
+
from sqlalchemy import create_engine
|
|
168
|
+
|
|
169
|
+
engine1 = create_engine('sqlite:///test.db')
|
|
170
|
+
# engine = create_engine("mysql+pymysql://user:pass@localhost/testdb")
|
|
171
|
+
db1 = RunnerDB(engine1)
|
|
172
|
+
import logging
|
|
173
|
+
|
|
174
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
175
|
+
|
|
176
|
+
with db1.open_session() as session1:
|
|
177
|
+
for i in range(5):
|
|
178
|
+
runner1 = Runner(
|
|
179
|
+
# id=i+1,
|
|
180
|
+
mac=f'11:22:33:44:55:6{i}',
|
|
181
|
+
ip=f'192.168.1.{i}',
|
|
182
|
+
owner=f'test{i // 2}',
|
|
183
|
+
name=f'runner00{i}',
|
|
184
|
+
description=f'test runner 00{i} update 2',
|
|
185
|
+
tags=['esp32', 'generic', 'eco4'],
|
|
186
|
+
raw_data={'envs': {'esp32': [f'wifi_iperf_{i}']}, 'os': 'linux'},
|
|
187
|
+
)
|
|
188
|
+
session1.add_or_update_runner(runner1)
|
|
189
|
+
new_user = User(
|
|
190
|
+
# id=1,
|
|
191
|
+
name='test0'
|
|
192
|
+
)
|
|
193
|
+
session1.add_or_update_user(new_user)
|
|
194
|
+
session1.commit()
|
|
195
|
+
users = session1.query(User).outerjoin(User.runners).all()
|
|
196
|
+
print(users)
|
|
197
|
+
all_runners = session1.all_runners()
|
|
198
|
+
print(','.join([str(r) for r in all_runners]))
|
|
199
|
+
db1.to_csv('runners.csv')
|
|
@@ -2,26 +2,34 @@ import argparse
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import sys
|
|
5
6
|
|
|
6
7
|
from esptest.devices.serial_tools import get_all_serial_ports
|
|
7
|
-
from esptest.tools.download_bin import download_bin_to_ports
|
|
8
|
+
from esptest.tools.download_bin import bin_path_to_dir, download_bin_to_ports
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def main() -> None:
|
|
11
|
-
|
|
12
|
+
usage_string = '%(prog)s [bin_path] [options]'
|
|
13
|
+
parser = argparse.ArgumentParser(description='Download bin', usage=usage_string)
|
|
12
14
|
parser.add_argument('bin_path', type=str, nargs='?', help='esp bin path, default ./build')
|
|
13
15
|
parser.add_argument('-p', '--ports', type=str, nargs='*', help='download port list')
|
|
14
16
|
parser.add_argument(
|
|
15
17
|
'--range', type=str, help='port list from range (linux), eg: "0-10" equals to "-p ttyUSB0 ttyUSB1 ... ttyUSB10"'
|
|
16
18
|
)
|
|
17
|
-
parser.add_argument(
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'--all', action='store_true', help='download to all serial ports, ignored if "-p/--ports" is specified.'
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument('--no-erase-nvs', dest='erase_nvs', action='store_false', help='skip erase nvs')
|
|
23
|
+
parser.add_argument('--max-workers', type=int, default=0, help='max download threads')
|
|
20
24
|
args = parser.parse_args()
|
|
21
25
|
|
|
22
26
|
bin_path = args.bin_path or './build'
|
|
23
27
|
if not os.path.isdir(bin_path):
|
|
24
|
-
|
|
28
|
+
try:
|
|
29
|
+
bin_path = bin_path_to_dir(bin_path)
|
|
30
|
+
except Exception as e: # pylint: disable=broad-except
|
|
31
|
+
logging.exception(f'Invalid bin path {bin_path} : {str(e)}')
|
|
32
|
+
sys.exit(1)
|
|
25
33
|
|
|
26
34
|
ports = []
|
|
27
35
|
if args.ports:
|
|
@@ -37,9 +45,13 @@ def main() -> None:
|
|
|
37
45
|
ports = [os.getenv('ESPPORT') or '/dev/ttyUSB0']
|
|
38
46
|
assert isinstance(ports, list)
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
try:
|
|
49
|
+
download_bin_to_ports(bin_path, ports, args.erase_nvs, args.max_workers)
|
|
50
|
+
except RuntimeError as e:
|
|
51
|
+
logging.error(str(e))
|
|
52
|
+
sys.exit(1)
|
|
42
53
|
|
|
43
54
|
|
|
44
55
|
if __name__ == '__main__':
|
|
56
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
|
|
45
57
|
main()
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import concurrent
|
|
3
3
|
import concurrent.futures
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
4
6
|
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import zipfile
|
|
5
9
|
from asyncio.events import AbstractEventLoop
|
|
6
10
|
from functools import lru_cache, partial
|
|
7
11
|
|
|
8
12
|
import esptest.common.compat_typing as t
|
|
9
13
|
from esptest.devices.serial_tools import compute_serial_port
|
|
10
14
|
from esptest.logger import get_logger
|
|
15
|
+
from esptest.tools.http_download import download_file
|
|
11
16
|
from esptest.utility.parse_bin_path import ParseBinPath
|
|
12
17
|
|
|
13
18
|
logger = get_logger('download_bin')
|
|
@@ -18,6 +23,32 @@ def _get_bin_parser(bin_path: str, parttool: str) -> ParseBinPath:
|
|
|
18
23
|
return ParseBinPath(bin_path, parttool)
|
|
19
24
|
|
|
20
25
|
|
|
26
|
+
@lru_cache()
|
|
27
|
+
def _tmp_dir() -> str:
|
|
28
|
+
return tempfile.mkdtemp()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@lru_cache()
|
|
32
|
+
def bin_path_to_dir(bin_path: str) -> str:
|
|
33
|
+
bin_hash = hash(bin_path)
|
|
34
|
+
bin_hash_name = os.path.basename(bin_path)
|
|
35
|
+
if bin_path.startswith('http'):
|
|
36
|
+
assert bin_path.endswith('.zip') # for now only support zip from url
|
|
37
|
+
new_bin_path = os.path.join(_tmp_dir(), f'{bin_hash}', bin_hash_name)
|
|
38
|
+
os.makedirs(os.path.dirname(new_bin_path), exist_ok=True)
|
|
39
|
+
download_file(bin_path, new_bin_path)
|
|
40
|
+
bin_path = new_bin_path
|
|
41
|
+
if bin_path.endswith('.zip'):
|
|
42
|
+
new_bin_path = os.path.join(_tmp_dir(), f'{bin_hash}', bin_hash_name.removesuffix('.zip'))
|
|
43
|
+
os.makedirs(new_bin_path, exist_ok=True)
|
|
44
|
+
with zipfile.ZipFile(bin_path, 'r') as zip_ref:
|
|
45
|
+
zip_ref.extractall(new_bin_path)
|
|
46
|
+
bin_path = new_bin_path
|
|
47
|
+
if 'partition_table' not in os.listdir(bin_path):
|
|
48
|
+
logger.warning('Can not find partition_table from bin_path, maybe invalid!')
|
|
49
|
+
return bin_path
|
|
50
|
+
|
|
51
|
+
|
|
21
52
|
def _filter_esptool_log(log: str) -> str:
|
|
22
53
|
lines = log.splitlines(keepends=True)
|
|
23
54
|
new_log = ''
|
|
@@ -36,6 +67,7 @@ def _filter_esptool_log(log: str) -> str:
|
|
|
36
67
|
class DownBinTool:
|
|
37
68
|
# RETRY_CNT = 2
|
|
38
69
|
DEFAULT_BAUD_LIST = [921600, 460800]
|
|
70
|
+
FLASH_CRYPT_CNT_PATTERN = re.compile(r'(?:FLASH_CRYPT_CNT|SPI_BOOT_CRYPT_CNT).*\(0b([01]+)')
|
|
39
71
|
|
|
40
72
|
def __init__(
|
|
41
73
|
self,
|
|
@@ -54,11 +86,29 @@ class DownBinTool:
|
|
|
54
86
|
else:
|
|
55
87
|
self.baud_list = baud
|
|
56
88
|
self.esptool = esptool or 'python -m esptool'
|
|
89
|
+
self.espefuse = self.esptool.replace('esptool', 'espefuse')
|
|
57
90
|
self.erase_nvs = erase_nvs
|
|
58
91
|
self.bin_parser = _get_bin_parser(bin_path, parttool)
|
|
59
92
|
self.force_no_stub = force_no_stub
|
|
60
93
|
|
|
94
|
+
def check_flash_encrypted(self, efuse_summary: str) -> bool:
|
|
95
|
+
match = self.FLASH_CRYPT_CNT_PATTERN.search(efuse_summary)
|
|
96
|
+
if match:
|
|
97
|
+
return match.group(1).count('1') % 2 == 1
|
|
98
|
+
return False
|
|
99
|
+
|
|
61
100
|
def download(self) -> None:
|
|
101
|
+
efuse_cmd = self.espefuse.split()
|
|
102
|
+
try:
|
|
103
|
+
summary = subprocess.check_output(
|
|
104
|
+
efuse_cmd + ['--port', self.port, 'summary'], stderr=subprocess.STDOUT, text=True
|
|
105
|
+
)
|
|
106
|
+
except subprocess.CalledProcessError as err:
|
|
107
|
+
logger.error(err.output)
|
|
108
|
+
raise RuntimeError(f'Failed to get efuse information from {self.port}') from err
|
|
109
|
+
|
|
110
|
+
enc_indicator = ' [encrypted]' if self.check_flash_encrypted(summary) else ''
|
|
111
|
+
|
|
62
112
|
download_log = ''
|
|
63
113
|
for baud in self.baud_list:
|
|
64
114
|
args = self.esptool.split()
|
|
@@ -66,19 +116,18 @@ class DownBinTool:
|
|
|
66
116
|
args += ['--no-stub']
|
|
67
117
|
args += ['-p', self.port]
|
|
68
118
|
args += ['-b', f'{baud}']
|
|
69
|
-
args += self.bin_parser.flash_bin_args(erase_nvs=self.erase_nvs)
|
|
119
|
+
args += self.bin_parser.flash_bin_args(erase_nvs=self.erase_nvs, encrypted=bool(enc_indicator))
|
|
70
120
|
|
|
71
|
-
logger.
|
|
121
|
+
logger.info(f'Downloading {self.port}@{baud}{enc_indicator}: {self.bin_path}')
|
|
72
122
|
# get return code rather than check
|
|
73
123
|
ret = subprocess.run(args, capture_output=True, text=True, check=False)
|
|
74
124
|
if ret.returncode == 0:
|
|
75
125
|
return # succeed
|
|
76
126
|
# failed
|
|
77
|
-
download_log
|
|
127
|
+
download_log += f'esptool cmd failed ({ret.returncode}): ' + ' '.join(args)
|
|
78
128
|
download_log += f'\nDownload failed: [{self.port}@{baud}]\n'
|
|
79
129
|
esptool_msg = ret.stdout + ret.stderr
|
|
80
130
|
download_log += f'esptool output: {_filter_esptool_log(esptool_msg)}'
|
|
81
|
-
logger.debug(download_log)
|
|
82
131
|
logger.error(download_log)
|
|
83
132
|
raise RuntimeError(f'Failed to download Bin to {self.port}')
|
|
84
133
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import urllib.request
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _progress(downloaded: int, total_size: int) -> None:
|
|
9
|
+
if total_size > 0:
|
|
10
|
+
percent = min(downloaded / total_size * 100, 100)
|
|
11
|
+
bar_len = 50
|
|
12
|
+
filled_len = int(bar_len * downloaded // total_size)
|
|
13
|
+
progress_bar = '█' * filled_len + '-' * (bar_len - filled_len)
|
|
14
|
+
sys.stdout.write(f'\r[{progress_bar}] {percent:6.1f}%')
|
|
15
|
+
sys.stdout.flush()
|
|
16
|
+
else:
|
|
17
|
+
# show downloaded size if no total_size
|
|
18
|
+
sys.stdout.write(f'\rDownloaded {downloaded} bytes')
|
|
19
|
+
sys.stdout.flush()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def download_file(url: str, local_filename: str, timeout: Optional[float] = None, progress: bool = True) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Download a file from a URL.
|
|
25
|
+
|
|
26
|
+
The optional *timeout* parameter specifies a timeout in seconds for
|
|
27
|
+
blocking operations like the connection attempt (if not specified, the
|
|
28
|
+
global default timeout setting will be used). This only works for HTTP,
|
|
29
|
+
HTTPS and FTP connections.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
url: The URL of the file to download.
|
|
33
|
+
local_filename: The local filename to save the downloaded file.
|
|
34
|
+
progress: Whether to show the download progress.
|
|
35
|
+
"""
|
|
36
|
+
if os.path.exists(local_filename):
|
|
37
|
+
os.remove(local_filename)
|
|
38
|
+
try:
|
|
39
|
+
logging.info(f'Downloading {url} -> {local_filename}')
|
|
40
|
+
with urllib.request.urlopen(url, timeout=timeout) as response, open(local_filename, 'wb') as out_file:
|
|
41
|
+
total_length = int(response.getheader('Content-Length') or '0')
|
|
42
|
+
downloaded = 0
|
|
43
|
+
block_size = 8192
|
|
44
|
+
while True:
|
|
45
|
+
chunk = response.read(block_size)
|
|
46
|
+
if not chunk:
|
|
47
|
+
break
|
|
48
|
+
out_file.write(chunk)
|
|
49
|
+
downloaded += len(chunk)
|
|
50
|
+
if progress:
|
|
51
|
+
_progress(downloaded, total_length)
|
|
52
|
+
if progress:
|
|
53
|
+
sys.stdout.write('\n')
|
|
54
|
+
sys.stdout.flush()
|
|
55
|
+
if total_length and total_length != downloaded:
|
|
56
|
+
logging.error(f'Download {url} failed! maybe url timeout.')
|
|
57
|
+
raise OSError(f'Download {url} failed: total_length {total_length} != downloaded {downloaded}')
|
|
58
|
+
logging.info('Download complete!')
|
|
59
|
+
except OSError as e:
|
|
60
|
+
logging.error(f'Download {url} failed: {str(e)}')
|
|
61
|
+
raise e
|