esp-test-utils 0.3.4__tar.gz → 0.4.0__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.3.4 → esp_test_utils-0.4.0}/.pre-commit-config.yaml +0 -1
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/CHANGELOG.md +12 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/PKG-INFO +1 -2
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/PKG-INFO +1 -2
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/SOURCES.txt +5 -3
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/entry_points.txt +2 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/requires.txt +0 -1
- esp_test_utils-0.4.0/esptest/devices/serial_tools.py +82 -0
- esp_test_utils-0.4.0/esptest/scripts/fetch_repo.py +90 -0
- esp_test_utils-0.4.0/esptest/scripts/relay.py +116 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/copy_bin.py +74 -19
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/download_bin.py +63 -55
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/utility/parse_bin_path.py +83 -21
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/pyproject.toml +2 -4
- esp_test_utils-0.4.0/tests/scripts/test_fetch_repo.py +166 -0
- esp_test_utils-0.4.0/tests/scripts/test_relay_script.py +87 -0
- esp_test_utils-0.4.0/tests/tools/test_copy_bin.py +96 -0
- esp_test_utils-0.4.0/tests/tools/test_download_bin.py +194 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/utility/test_parse_bin_path.py +77 -2
- esp_test_utils-0.3.4/esptest/db/runners.py +0 -202
- esp_test_utils-0.3.4/esptest/devices/serial_tools.py +0 -39
- esp_test_utils-0.3.4/tests/__init__.py +0 -0
- esp_test_utils-0.3.4/tests/db/test_db_runners.py +0 -66
- esp_test_utils-0.3.4/tests/tools/test_download_bin.py +0 -81
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.github/.gitkeep +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.github/workflows/pypi-publish.yml +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.gitignore +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.gitlab-ci.yml +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/CONTRIBUTING.md +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/LICENSE +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/README.md +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/Makefile +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/conf.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/index.rst +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/make.bat +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/dependency_links.txt +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/top_level.txt +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/__main__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/create_dut.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/dut_base.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_dut.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_mixin.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/mac_mixin.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/wrapper.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/base_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/data_monitor_mixin.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/serial_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/shell_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/all.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/compat_typing.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/data_monitor.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/decorators.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/encoding.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/generator.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/parser.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/shell.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/timestamp.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/env_config.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/global_config.py +0 -0
- {esp_test_utils-0.3.4/esptest/db → esp_test_utils-0.4.0/esptest/devices}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/attenuator.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/esp_serial.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/serial_dut.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/switch.py +0 -0
- {esp_test_utils-0.3.4/esptest/devices → esp_test_utils-0.4.0/esptest/env}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/env/base_env.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/env/wifi_env.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/esp_console/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/esp_console/wifi_cmd.py +0 -0
- {esp_test_utils-0.3.4/esptest/env → esp_test_utils-0.4.0/esptest/interface}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/interface/dut.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/interface/port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_results.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_test.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_test.test.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/line_chart.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/logger/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/logger/logger.py +0 -0
- {esp_test_utils-0.3.4/esptest/interface → esp_test_utils-0.4.0/esptest/network}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/mac.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/netif.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/nic.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/downbin.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/list_ports.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/monitor.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/set_att.py +0 -0
- {esp_test_utils-0.3.4/esptest/network → esp_test_utils-0.4.0/esptest/testcase}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/testcase/result.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/http_download.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/pip_check.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/uart_monitor.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/utility/gen_esp32part.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/jap_test.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/restart_test.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/use_data_monitor.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/setup.cfg +0 -0
- {esp_test_utils-0.3.4/esptest/testcase → esp_test_utils-0.4.0/tests}/__init__.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_Dut.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_base_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_shell_port.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_data_monitor.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_decorators.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_network.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/conftest.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/devices/test_switch.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/conftest.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/test_WifiCmd.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_chart.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_iperf_results.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_iperf_util.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_EnvConfig.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_common.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_global_config.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_import.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_parse_expand_list.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/testcase/test_result.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/tools/test_download_file.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/tools/test_pip_check.py +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/utility/_files/test-bin.zip +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig +0 -0
- {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tools/ci/check_dev_version.py +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v0.4.0 (2026-05-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
- feat: add fetch repo script
|
|
5
|
+
- feat: add script to control relay power on
|
|
6
|
+
- feat: add nvs dump args and reuse partition lookup
|
|
7
|
+
- fix: remove database / sqlalchemy from esptest
|
|
8
|
+
- feat: support download partition
|
|
9
|
+
- feat: add check serial port in use
|
|
10
|
+
- feat: add copy_bin cli options and option behavior tests
|
|
11
|
+
- fix: correct copy_bin zip output and add regression test
|
|
12
|
+
|
|
1
13
|
## v0.3.4 (2026-04-27)
|
|
2
14
|
|
|
3
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -232,7 +232,6 @@ Requires-Dist: pyudev
|
|
|
232
232
|
Requires-Dist: esptool
|
|
233
233
|
Requires-Dist: packaging
|
|
234
234
|
Requires-Dist: rich
|
|
235
|
-
Requires-Dist: sqlalchemy
|
|
236
235
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
237
236
|
Provides-Extra: idfci
|
|
238
237
|
Requires-Dist: pyecharts; extra == "idfci"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -232,7 +232,6 @@ Requires-Dist: pyudev
|
|
|
232
232
|
Requires-Dist: esptool
|
|
233
233
|
Requires-Dist: packaging
|
|
234
234
|
Requires-Dist: rich
|
|
235
|
-
Requires-Dist: sqlalchemy
|
|
236
235
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
237
236
|
Provides-Extra: idfci
|
|
238
237
|
Requires-Dist: pyecharts; extra == "idfci"
|
|
@@ -47,8 +47,6 @@ esptest/common/timestamp.py
|
|
|
47
47
|
esptest/config/__init__.py
|
|
48
48
|
esptest/config/env_config.py
|
|
49
49
|
esptest/config/global_config.py
|
|
50
|
-
esptest/db/__init__.py
|
|
51
|
-
esptest/db/runners.py
|
|
52
50
|
esptest/devices/__init__.py
|
|
53
51
|
esptest/devices/attenuator.py
|
|
54
52
|
esptest/devices/esp_serial.py
|
|
@@ -75,8 +73,10 @@ esptest/network/mac.py
|
|
|
75
73
|
esptest/network/netif.py
|
|
76
74
|
esptest/network/nic.py
|
|
77
75
|
esptest/scripts/downbin.py
|
|
76
|
+
esptest/scripts/fetch_repo.py
|
|
78
77
|
esptest/scripts/list_ports.py
|
|
79
78
|
esptest/scripts/monitor.py
|
|
79
|
+
esptest/scripts/relay.py
|
|
80
80
|
esptest/scripts/set_att.py
|
|
81
81
|
esptest/testcase/__init__.py
|
|
82
82
|
esptest/testcase/result.py
|
|
@@ -103,7 +103,6 @@ tests/adapter/test_shell_port.py
|
|
|
103
103
|
tests/basic/test_data_monitor.py
|
|
104
104
|
tests/basic/test_decorators.py
|
|
105
105
|
tests/basic/test_network.py
|
|
106
|
-
tests/db/test_db_runners.py
|
|
107
106
|
tests/devices/test_switch.py
|
|
108
107
|
tests/esp_console/conftest.py
|
|
109
108
|
tests/esp_console/test_WifiCmd.py
|
|
@@ -116,7 +115,10 @@ tests/iperf_utility/_files/dut_iperf_rx1.log
|
|
|
116
115
|
tests/iperf_utility/_files/dut_iperf_rx2.log
|
|
117
116
|
tests/iperf_utility/_files/pc_iperf_rx.log
|
|
118
117
|
tests/iperf_utility/_files/pc_iperf_rx2.log
|
|
118
|
+
tests/scripts/test_fetch_repo.py
|
|
119
|
+
tests/scripts/test_relay_script.py
|
|
119
120
|
tests/testcase/test_result.py
|
|
121
|
+
tests/tools/test_copy_bin.py
|
|
120
122
|
tests/tools/test_download_bin.py
|
|
121
123
|
tests/tools/test_download_file.py
|
|
122
124
|
tests/tools/test_pip_check.py
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
esp-copybin = esptest.tools.copy_bin:main
|
|
3
3
|
esp-downbin = esptest.scripts.downbin:main
|
|
4
|
+
esp-fetch-repo = esptest.scripts.fetch_repo:main
|
|
4
5
|
esp-listports = esptest.scripts.list_ports:main
|
|
5
6
|
esp-monitor = esptest.scripts.monitor:main
|
|
6
7
|
esp-pipcheck = esptest.tools.pip_check:main
|
|
8
|
+
esp-relay = esptest.scripts.relay:main
|
|
7
9
|
esp-setatt = esptest.scripts.set_att:main
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import serial.tools.list_ports
|
|
7
|
+
from serial.tools.list_ports_common import ListPortInfo
|
|
8
|
+
|
|
9
|
+
from ..logger import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger('devices')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@lru_cache()
|
|
15
|
+
def get_all_serial_ports(user: str = 'default', include_links: bool = False) -> List[ListPortInfo]:
|
|
16
|
+
"""list_ports could spend a very long time if there are many ports"""
|
|
17
|
+
# pylint: disable=unused-argument
|
|
18
|
+
# disable unused argument user was only used by lru_cache
|
|
19
|
+
# remove /dev/ttyS0 {location: None, pid: None, hwid: PNP0501, subsystem:pnp, serial_number: None}
|
|
20
|
+
return [p for p in serial.tools.list_ports.comports(include_links=include_links) if p.device and p.location]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def compute_serial_port(port: str, strict: bool = False) -> str:
|
|
24
|
+
"""Get the real serial port device from device, port name or usb location
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
port (str): device, port name or usb location
|
|
28
|
+
strict (bool, optional): raise Exception if not found locally.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: port device. return the given input port
|
|
32
|
+
"""
|
|
33
|
+
ports = get_all_serial_ports(include_links=True)
|
|
34
|
+
for p in ports:
|
|
35
|
+
if port in [p.name, p.device, p.location]:
|
|
36
|
+
assert isinstance(p.device, str)
|
|
37
|
+
return p.device
|
|
38
|
+
if strict:
|
|
39
|
+
raise serial.SerialException(f'Can not compute {port}')
|
|
40
|
+
logger.warning(f'Can not compute port {port}, is it exist?')
|
|
41
|
+
return port
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_serial_port_in_use(port: str) -> bool:
|
|
45
|
+
"""Return whether the serial device is likely open in another process.
|
|
46
|
+
|
|
47
|
+
Resolves ``port`` with :func:`compute_serial_port`, then prefers ``fuser(1)``
|
|
48
|
+
(``fuser -s`` on the device path: exit 0 means some process is using the
|
|
49
|
+
file). If ``fuser`` is unavailable, fails, or returns an unexpected exit
|
|
50
|
+
code, falls back to ``lsof(8)`` (exit 0 means holders were found).
|
|
51
|
+
|
|
52
|
+
If neither tool is available or both fail, raise OSError (occupancy unknown; callers may still open the port).
|
|
53
|
+
"""
|
|
54
|
+
device = compute_serial_port(port)
|
|
55
|
+
|
|
56
|
+
check_commands = {
|
|
57
|
+
'fuser': {
|
|
58
|
+
'cmd': ['fuser', '-s', device],
|
|
59
|
+
'return_code': 0,
|
|
60
|
+
},
|
|
61
|
+
'lsof': {
|
|
62
|
+
'cmd': ['lsof', device],
|
|
63
|
+
'return_code': 0,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for cmd, cmd_info in check_commands.items():
|
|
68
|
+
if shutil.which(cmd):
|
|
69
|
+
try:
|
|
70
|
+
_cmd: List[str] = cmd_info['cmd'] # type: ignore
|
|
71
|
+
proc = subprocess.run(
|
|
72
|
+
_cmd,
|
|
73
|
+
check=False,
|
|
74
|
+
stdout=subprocess.DEVNULL,
|
|
75
|
+
stderr=subprocess.DEVNULL,
|
|
76
|
+
)
|
|
77
|
+
except OSError as e:
|
|
78
|
+
raise OSError(f'Failed to run {cmd} on {device}') from e
|
|
79
|
+
if proc.returncode == cmd_info['return_code']:
|
|
80
|
+
return True
|
|
81
|
+
return False
|
|
82
|
+
raise OSError(f'Failed to check if serial port {device} is in use')
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_cmd(cmd: List[str], **kwargs: Any) -> None:
|
|
12
|
+
logging.debug(f'Running command: {" ".join(cmd)}')
|
|
13
|
+
subprocess.run(cmd, check=True, **kwargs)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_git_repo(repo_path: str) -> None:
|
|
17
|
+
if not os.path.isdir(repo_path):
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
git_dir = os.path.join(repo_path, '.git')
|
|
21
|
+
if not os.path.isdir(git_dir):
|
|
22
|
+
# path is not a git repository, remove it
|
|
23
|
+
shutil.rmtree(repo_path)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
run_cmd(['git', '-C', repo_path, 'rev-parse', '--git-dir'])
|
|
28
|
+
except subprocess.CalledProcessError:
|
|
29
|
+
logging.error(f'Git repo {repo_path} corrupted, removing...')
|
|
30
|
+
shutil.rmtree(repo_path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fetch_repo(url: str, path: str, ref: str, depth: Optional[int] = None) -> None:
|
|
34
|
+
check_git_repo(path)
|
|
35
|
+
|
|
36
|
+
# ref not set, clone default branch
|
|
37
|
+
if not ref:
|
|
38
|
+
if os.path.isdir(path):
|
|
39
|
+
# path is a git repository, but no ref provided
|
|
40
|
+
logging.warning(f'Path {path} already exists, but no ref provided, skipping fetch...')
|
|
41
|
+
return
|
|
42
|
+
# clone repo from remote url with default branch
|
|
43
|
+
clone_args = ['git', 'clone', url, path]
|
|
44
|
+
if depth:
|
|
45
|
+
clone_args.extend(['--depth', str(depth)])
|
|
46
|
+
run_cmd(clone_args)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# fetch specific ref
|
|
50
|
+
if not os.path.isdir(path):
|
|
51
|
+
os.makedirs(path, exist_ok=True)
|
|
52
|
+
init_args = ['git', '-C', path, 'init']
|
|
53
|
+
run_cmd(init_args)
|
|
54
|
+
|
|
55
|
+
# path is a git repository, configure remote origin url and fetch
|
|
56
|
+
# ref may be a branch, tag or commit hash.
|
|
57
|
+
# always checkout to new branch with FETCH_HEAD
|
|
58
|
+
run_cmd(['git', '-C', path, 'config', 'remote.origin.url', url])
|
|
59
|
+
# refspec = f'refs/heads/{ref}:refs/remotes/origin/{ref}'
|
|
60
|
+
fetch_args = ['git', '-C', path, 'fetch', 'origin', ref]
|
|
61
|
+
if depth:
|
|
62
|
+
fetch_args.extend(['--depth', str(depth)])
|
|
63
|
+
run_cmd(fetch_args)
|
|
64
|
+
run_cmd(['git', '-C', path, 'checkout', '-f', '-B', ref, 'FETCH_HEAD'])
|
|
65
|
+
run_cmd(['git', '-C', path, 'reset', '--hard'])
|
|
66
|
+
run_cmd(['git', '-C', path, 'clean', '-ffdx'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|
70
|
+
parser = argparse.ArgumentParser(description='Fetch git repository to local path')
|
|
71
|
+
parser.add_argument('--url', required=True, help='Git repository URL')
|
|
72
|
+
parser.add_argument('--path', required=True, help='Local repository path')
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
'--ref',
|
|
75
|
+
help='Branch/ref to clone/checkout (e.g. origin/master or chip/foo)',
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument('--depth', type=int, default=0, help='Shallow fetch/clone depth, 0 means full history')
|
|
78
|
+
return parser.parse_args(argv)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main(argv: Optional[List[str]] = None) -> None:
|
|
82
|
+
args = parse_args(argv)
|
|
83
|
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s')
|
|
84
|
+
depth = args.depth if args.depth > 0 else 0
|
|
85
|
+
logging.info(f'Fetching git repository {args.url} to {args.path} with ref {args.ref}, depth={depth}')
|
|
86
|
+
fetch_repo(args.url, args.path, args.ref, depth=depth)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == '__main__':
|
|
90
|
+
main()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import serial
|
|
10
|
+
|
|
11
|
+
from esptest.devices.serial_tools import compute_serial_port, get_all_serial_ports
|
|
12
|
+
|
|
13
|
+
# Channel 1: open / close (checksum = low 8 bits of sum of preceding bytes)
|
|
14
|
+
CHANNEL1_OPEN_HEX = 'A0 01 01 A2'
|
|
15
|
+
CHANNEL1_CLOSE_HEX = 'A0 01 00 A1'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# QinHeng CH340 serial converter (USB ID 1a86:7523)
|
|
19
|
+
VID_CH340 = 0x1A86
|
|
20
|
+
PID_CH340 = 0x7523
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_relay_device(port: str = '') -> Optional[str]:
|
|
24
|
+
if port:
|
|
25
|
+
return compute_serial_port(port, strict=True)
|
|
26
|
+
# find the first CH340 device
|
|
27
|
+
for p in get_all_serial_ports():
|
|
28
|
+
if p.vid == VID_CH340 and p.pid == PID_CH340:
|
|
29
|
+
return str(p.device)
|
|
30
|
+
raise ValueError('No CH340 (1a86:7523) found; connect the USB serial adapter and retry.')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_battery_level_pct() -> Optional[int]:
|
|
34
|
+
"""Run ``adb shell dumpsys battery`` and return SoC 0–100, or None on failure."""
|
|
35
|
+
try:
|
|
36
|
+
r = subprocess.run(
|
|
37
|
+
['adb', 'shell', 'dumpsys', 'battery'],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
timeout=15,
|
|
41
|
+
check=False,
|
|
42
|
+
)
|
|
43
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
44
|
+
return None
|
|
45
|
+
if r.returncode != 0:
|
|
46
|
+
return None
|
|
47
|
+
m = re.search(r'level:\s*(\d+)', r.stdout, re.I)
|
|
48
|
+
if not m:
|
|
49
|
+
return None
|
|
50
|
+
return int(m.group(1))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RelayControl:
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
port: str,
|
|
57
|
+
open_cmd: str = CHANNEL1_OPEN_HEX,
|
|
58
|
+
close_cmd: str = CHANNEL1_CLOSE_HEX,
|
|
59
|
+
):
|
|
60
|
+
self.port = get_relay_device(port)
|
|
61
|
+
assert self.port, f'Failed to get relay device: {port}'
|
|
62
|
+
self.open_cmd = bytes.fromhex(open_cmd)
|
|
63
|
+
self.close_cmd = bytes.fromhex(close_cmd)
|
|
64
|
+
|
|
65
|
+
def open(self) -> None:
|
|
66
|
+
logging.info(f'opening relay on {self.port}: sending {self.open_cmd.hex()}')
|
|
67
|
+
with serial.Serial(self.port, baudrate=9600, timeout=0.01) as ser:
|
|
68
|
+
ser.write(self.open_cmd)
|
|
69
|
+
time.sleep(0.1)
|
|
70
|
+
|
|
71
|
+
def close(self) -> None:
|
|
72
|
+
logging.info(f'closing relay on {self.port}: sending {self.close_cmd.hex()}')
|
|
73
|
+
with serial.Serial(self.port, baudrate=9600, timeout=0.01) as ser:
|
|
74
|
+
ser.write(self.close_cmd)
|
|
75
|
+
time.sleep(0.1)
|
|
76
|
+
|
|
77
|
+
def check_phone(self) -> None:
|
|
78
|
+
logging.info(f'checking phone on {self.port}')
|
|
79
|
+
self.open()
|
|
80
|
+
time.sleep(2)
|
|
81
|
+
level = get_battery_level_pct()
|
|
82
|
+
if level is None:
|
|
83
|
+
logging.error('adb battery: could not read level (no device or no level: in dumpsys)')
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
if level >= 80:
|
|
86
|
+
logging.info(f'battery level {level} is greater than or equal to 80, closing relay')
|
|
87
|
+
self.close()
|
|
88
|
+
else:
|
|
89
|
+
logging.info(f'battery level {level} is less than 80, keeping relay open')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main() -> None:
|
|
93
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
|
|
94
|
+
args = argparse.ArgumentParser(description='Relay control')
|
|
95
|
+
args.add_argument('action', choices=['open', 'close', 'check-phone'], help='Action to perform')
|
|
96
|
+
args.add_argument('--port', type=str, help='Serial port')
|
|
97
|
+
args.add_argument('--open-cmd', type=str, default=CHANNEL1_OPEN_HEX, help='open device command')
|
|
98
|
+
args.add_argument('--close-cmd', type=str, default=CHANNEL1_CLOSE_HEX, help='close device command')
|
|
99
|
+
|
|
100
|
+
args = args.parse_args()
|
|
101
|
+
|
|
102
|
+
relay_control = RelayControl(args.port, args.open_cmd, args.close_cmd)
|
|
103
|
+
if args.action == 'open':
|
|
104
|
+
relay_control.open()
|
|
105
|
+
elif args.action == 'close':
|
|
106
|
+
relay_control.close()
|
|
107
|
+
elif args.action == 'check-phone':
|
|
108
|
+
relay_control.check_phone()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == '__main__':
|
|
112
|
+
try:
|
|
113
|
+
main()
|
|
114
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
115
|
+
logging.exception(f'{type(e)}: {e}')
|
|
116
|
+
sys.exit(1)
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess
|
|
6
|
+
import tempfile
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import List, Optional
|
|
8
9
|
|
|
@@ -24,6 +25,7 @@ class BuildFilesPatterns:
|
|
|
24
25
|
'partition_table/*.bin',
|
|
25
26
|
'partition_table/*.csv',
|
|
26
27
|
'flasher_args.json',
|
|
28
|
+
'flash_args',
|
|
27
29
|
'flash_project_args',
|
|
28
30
|
'config/sdkconfig.json',
|
|
29
31
|
'sdkconfig',
|
|
@@ -35,12 +37,17 @@ class BuildFilesPatterns:
|
|
|
35
37
|
'bootloader/*.elf',
|
|
36
38
|
'*.map',
|
|
37
39
|
'*.elf',
|
|
40
|
+
'dependencies.lock',
|
|
41
|
+
'build_log.txt',
|
|
42
|
+
'size.json',
|
|
38
43
|
]
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
def copy_bin_to_new_path(
|
|
42
47
|
from_dir: str,
|
|
43
|
-
|
|
48
|
+
to_path: str,
|
|
49
|
+
*,
|
|
50
|
+
zip_output: bool = False,
|
|
44
51
|
force: bool = True,
|
|
45
52
|
copy_elf: bool = True,
|
|
46
53
|
extra_files: Optional[List[str]] = None,
|
|
@@ -49,41 +56,50 @@ def copy_bin_to_new_path(
|
|
|
49
56
|
|
|
50
57
|
Args:
|
|
51
58
|
from_dir (str): the app build directory. eg: ./build/
|
|
52
|
-
|
|
59
|
+
to_path (str): Destination directory or zip file to save the bin files.
|
|
60
|
+
zip_output (bool, optional): Zip the destination directory. Defaults to False.
|
|
53
61
|
force (bool, optional): Delete the destination dir if it is already exists. Defaults to True.
|
|
54
62
|
copy_elf (bool, optional): Copy elf and map files as well. Defaults to True.
|
|
55
63
|
extra_files (Optional[List[str]], optional): . Defaults to None.
|
|
56
64
|
"""
|
|
57
65
|
from_path = Path(from_dir).resolve().absolute()
|
|
58
|
-
|
|
59
|
-
logger.debug(f'Copying bin files from {from_path} to {to_path}')
|
|
66
|
+
to_path_obj = Path(to_path).resolve().absolute()
|
|
60
67
|
assert from_path.is_dir()
|
|
61
|
-
if force and
|
|
62
|
-
logger.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
if force and to_path_obj.exists():
|
|
69
|
+
logger.info(f'Removing existing destination directory or file {to_path_obj}')
|
|
70
|
+
if to_path_obj.is_dir():
|
|
71
|
+
shutil.rmtree(str(to_path_obj))
|
|
72
|
+
else:
|
|
73
|
+
to_path_obj.unlink()
|
|
74
|
+
if zip_output:
|
|
75
|
+
assert to_path_obj.suffix == '.zip'
|
|
76
|
+
to_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
to_dir = Path(tempfile.mkdtemp())
|
|
78
|
+
else:
|
|
79
|
+
to_dir = to_path_obj
|
|
80
|
+
to_dir.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
logger.debug(f'Copying bin files from {from_path} to {to_path_obj}')
|
|
82
|
+
|
|
83
|
+
all_patterns = BuildFilesPatterns.BIN_FILES[:]
|
|
68
84
|
if copy_elf:
|
|
69
85
|
all_patterns.extend(BuildFilesPatterns.MAP_AND_ELF_FILES)
|
|
70
86
|
if extra_files:
|
|
71
87
|
all_patterns.extend(extra_files)
|
|
72
88
|
|
|
73
|
-
for pattern in
|
|
89
|
+
for pattern in all_patterns:
|
|
74
90
|
for _file in from_path.glob(pattern):
|
|
75
91
|
assert _file.is_file()
|
|
76
92
|
relative_path = _file.relative_to(from_path)
|
|
77
|
-
to_file =
|
|
93
|
+
to_file = to_dir / relative_path
|
|
78
94
|
if not to_file.parent.is_dir():
|
|
79
95
|
to_file.parent.mkdir(parents=True)
|
|
80
96
|
logging.debug(f'Copying file {relative_path}')
|
|
81
|
-
shutil.copy(_file,
|
|
97
|
+
shutil.copy(_file, to_dir / relative_path)
|
|
82
98
|
|
|
83
99
|
# parse 'partition-table.bin'
|
|
84
100
|
if IDF_PATH:
|
|
85
|
-
part_csv = Path(
|
|
86
|
-
part_bin = Path(
|
|
101
|
+
part_csv = Path(to_dir) / 'partition_table' / 'partition-table.csv'
|
|
102
|
+
part_bin = Path(to_dir) / 'partition_table' / 'partition-table.bin'
|
|
87
103
|
parttool = Path(IDF_PATH) / 'components' / 'partition_table' / 'gen_esp32part.py'
|
|
88
104
|
if not part_csv.is_file() and part_bin.is_file():
|
|
89
105
|
assert parttool.is_file(), 'Can not find gen_esp32part.py'
|
|
@@ -92,16 +108,55 @@ def copy_bin_to_new_path(
|
|
|
92
108
|
subprocess.check_call(_cmd, shell=False)
|
|
93
109
|
except subprocess.SubprocessError as e:
|
|
94
110
|
logger.error(f'Failed to gen partition-table.csv: {str(e)}')
|
|
111
|
+
# zip the destination directory
|
|
112
|
+
if zip_output:
|
|
113
|
+
shutil.make_archive(str(to_path_obj.with_suffix('')), 'zip', root_dir=str(to_dir))
|
|
114
|
+
shutil.rmtree(str(to_dir))
|
|
95
115
|
|
|
96
116
|
|
|
97
117
|
def main() -> None:
|
|
98
118
|
logging.basicConfig(level=logging.DEBUG)
|
|
99
119
|
parser = argparse.ArgumentParser(description='Copy bin files')
|
|
100
120
|
parser.add_argument('from_dir', type=str, help='source directory of build bin files')
|
|
101
|
-
parser.add_argument('
|
|
121
|
+
parser.add_argument('to_path', type=str, help='destination directory or zip file')
|
|
122
|
+
parser.add_argument(
|
|
123
|
+
'--zip',
|
|
124
|
+
action='store_true',
|
|
125
|
+
help='zip destination, and to_path should end with .zip',
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
'--no-force',
|
|
129
|
+
dest='force',
|
|
130
|
+
action='store_false',
|
|
131
|
+
help='do not remove existing destination directory or file',
|
|
132
|
+
)
|
|
133
|
+
parser.set_defaults(force=True)
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
'--no-copy-elf',
|
|
136
|
+
dest='copy_elf',
|
|
137
|
+
action='store_false',
|
|
138
|
+
help='do not copy elf/map files',
|
|
139
|
+
)
|
|
140
|
+
parser.set_defaults(copy_elf=True)
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
'--extra-files',
|
|
143
|
+
metavar='PATTERN',
|
|
144
|
+
action='append',
|
|
145
|
+
nargs='+',
|
|
146
|
+
default=[],
|
|
147
|
+
help='extra glob patterns to copy, can be used multiple times',
|
|
148
|
+
)
|
|
102
149
|
args = parser.parse_args()
|
|
103
|
-
|
|
104
|
-
|
|
150
|
+
extra_files = [pattern for item in args.extra_files for pattern in item] or None
|
|
151
|
+
|
|
152
|
+
copy_bin_to_new_path(
|
|
153
|
+
args.from_dir,
|
|
154
|
+
args.to_path,
|
|
155
|
+
zip_output=args.zip,
|
|
156
|
+
force=args.force,
|
|
157
|
+
copy_elf=args.copy_elf,
|
|
158
|
+
extra_files=extra_files,
|
|
159
|
+
)
|
|
105
160
|
|
|
106
161
|
|
|
107
162
|
if __name__ == '__main__':
|