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.
Files changed (135) hide show
  1. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.pre-commit-config.yaml +0 -1
  2. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/CHANGELOG.md +12 -0
  3. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/PKG-INFO +1 -2
  4. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/PKG-INFO +1 -2
  5. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/SOURCES.txt +5 -3
  6. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/entry_points.txt +2 -0
  7. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/requires.txt +0 -1
  8. esp_test_utils-0.4.0/esptest/devices/serial_tools.py +82 -0
  9. esp_test_utils-0.4.0/esptest/scripts/fetch_repo.py +90 -0
  10. esp_test_utils-0.4.0/esptest/scripts/relay.py +116 -0
  11. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/copy_bin.py +74 -19
  12. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/download_bin.py +63 -55
  13. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/utility/parse_bin_path.py +83 -21
  14. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/pyproject.toml +2 -4
  15. esp_test_utils-0.4.0/tests/scripts/test_fetch_repo.py +166 -0
  16. esp_test_utils-0.4.0/tests/scripts/test_relay_script.py +87 -0
  17. esp_test_utils-0.4.0/tests/tools/test_copy_bin.py +96 -0
  18. esp_test_utils-0.4.0/tests/tools/test_download_bin.py +194 -0
  19. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/utility/test_parse_bin_path.py +77 -2
  20. esp_test_utils-0.3.4/esptest/db/runners.py +0 -202
  21. esp_test_utils-0.3.4/esptest/devices/serial_tools.py +0 -39
  22. esp_test_utils-0.3.4/tests/__init__.py +0 -0
  23. esp_test_utils-0.3.4/tests/db/test_db_runners.py +0 -66
  24. esp_test_utils-0.3.4/tests/tools/test_download_bin.py +0 -81
  25. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.github/.gitkeep +0 -0
  26. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.github/workflows/pypi-publish.yml +0 -0
  27. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.gitignore +0 -0
  28. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/.gitlab-ci.yml +0 -0
  29. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/CONTRIBUTING.md +0 -0
  30. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/LICENSE +0 -0
  31. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/README.md +0 -0
  32. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/Makefile +0 -0
  33. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/conf.py +0 -0
  34. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/index.rst +0 -0
  35. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/docs/make.bat +0 -0
  36. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/dependency_links.txt +0 -0
  37. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esp_test_utils.egg-info/top_level.txt +0 -0
  38. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/__init__.py +0 -0
  39. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/__main__.py +0 -0
  40. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/__init__.py +0 -0
  41. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/__init__.py +0 -0
  42. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/create_dut.py +0 -0
  43. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/dut_base.py +0 -0
  44. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_dut.py +0 -0
  45. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_mixin.py +0 -0
  46. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/esp_port.py +0 -0
  47. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/mac_mixin.py +0 -0
  48. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/dut/wrapper.py +0 -0
  49. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/__init__.py +0 -0
  50. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/base_port.py +0 -0
  51. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/data_monitor_mixin.py +0 -0
  52. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/serial_port.py +0 -0
  53. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/adapter/port/shell_port.py +0 -0
  54. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/all.py +0 -0
  55. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/__init__.py +0 -0
  56. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/compat_typing.py +0 -0
  57. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/data_monitor.py +0 -0
  58. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/decorators.py +0 -0
  59. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/encoding.py +0 -0
  60. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/generator.py +0 -0
  61. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/parser.py +0 -0
  62. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/shell.py +0 -0
  63. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/common/timestamp.py +0 -0
  64. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/__init__.py +0 -0
  65. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/env_config.py +0 -0
  66. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/config/global_config.py +0 -0
  67. {esp_test_utils-0.3.4/esptest/db → esp_test_utils-0.4.0/esptest/devices}/__init__.py +0 -0
  68. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/attenuator.py +0 -0
  69. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/esp_serial.py +0 -0
  70. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/serial_dut.py +0 -0
  71. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/devices/switch.py +0 -0
  72. {esp_test_utils-0.3.4/esptest/devices → esp_test_utils-0.4.0/esptest/env}/__init__.py +0 -0
  73. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/env/base_env.py +0 -0
  74. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/env/wifi_env.py +0 -0
  75. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/esp_console/__init__.py +0 -0
  76. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/esp_console/wifi_cmd.py +0 -0
  77. {esp_test_utils-0.3.4/esptest/env → esp_test_utils-0.4.0/esptest/interface}/__init__.py +0 -0
  78. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/interface/dut.py +0 -0
  79. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/interface/port.py +0 -0
  80. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/__init__.py +0 -0
  81. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_results.py +0 -0
  82. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_test.py +0 -0
  83. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/iperf_test.test.py +0 -0
  84. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/iperf_utility/line_chart.py +0 -0
  85. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/logger/__init__.py +0 -0
  86. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/logger/logger.py +0 -0
  87. {esp_test_utils-0.3.4/esptest/interface → esp_test_utils-0.4.0/esptest/network}/__init__.py +0 -0
  88. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/mac.py +0 -0
  89. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/netif.py +0 -0
  90. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/network/nic.py +0 -0
  91. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/downbin.py +0 -0
  92. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/list_ports.py +0 -0
  93. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/monitor.py +0 -0
  94. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/scripts/set_att.py +0 -0
  95. {esp_test_utils-0.3.4/esptest/network → esp_test_utils-0.4.0/esptest/testcase}/__init__.py +0 -0
  96. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/testcase/result.py +0 -0
  97. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/http_download.py +0 -0
  98. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/pip_check.py +0 -0
  99. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/tools/uart_monitor.py +0 -0
  100. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/esptest/utility/gen_esp32part.py +0 -0
  101. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/jap_test.py +0 -0
  102. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/restart_test.py +0 -0
  103. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/example/use_data_monitor.py +0 -0
  104. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/setup.cfg +0 -0
  105. {esp_test_utils-0.3.4/esptest/testcase → esp_test_utils-0.4.0/tests}/__init__.py +0 -0
  106. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_Dut.py +0 -0
  107. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_base_port.py +0 -0
  108. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/adapter/test_shell_port.py +0 -0
  109. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_data_monitor.py +0 -0
  110. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_decorators.py +0 -0
  111. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/basic/test_network.py +0 -0
  112. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/conftest.py +0 -0
  113. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/devices/test_switch.py +0 -0
  114. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
  115. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
  116. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/conftest.py +0 -0
  117. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/esp_console/test_WifiCmd.py +0 -0
  118. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
  119. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
  120. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
  121. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
  122. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_chart.py +0 -0
  123. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_iperf_results.py +0 -0
  124. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/iperf_utility/test_iperf_util.py +0 -0
  125. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_EnvConfig.py +0 -0
  126. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_common.py +0 -0
  127. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_global_config.py +0 -0
  128. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_import.py +0 -0
  129. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/test_parse_expand_list.py +0 -0
  130. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/testcase/test_result.py +0 -0
  131. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/tools/test_download_file.py +0 -0
  132. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/tools/test_pip_check.py +0 -0
  133. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tests/utility/_files/test-bin.zip +0 -0
  134. {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
  135. {esp_test_utils-0.3.4 → esp_test_utils-0.4.0}/tools/ci/check_dev_version.py +0 -0
@@ -35,7 +35,6 @@ repos:
35
35
  - 'types-psutil'
36
36
  - 'types-PyYAML'
37
37
  - 'typing_extensions'
38
- - 'sqlalchemy'
39
38
 
40
39
  - repo: https://github.com/espressif/conventional-precommit-linter
41
40
  rev: v1.10.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.4
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.4
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
@@ -7,7 +7,6 @@ pyudev
7
7
  esptool
8
8
  packaging
9
9
  rich
10
- sqlalchemy
11
10
 
12
11
  [:python_version < "3.11"]
13
12
  typing_extensions
@@ -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
- to_dir: str,
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
- to_dir (str): Destination directory to save the bin files.
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
- to_path = Path(to_dir).resolve().absolute()
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 to_path.is_dir():
62
- logger.debug(f'Removing existing destination directory {to_path}')
63
- shutil.rmtree(str(to_path))
64
- assert not to_path.exists()
65
- to_path.mkdir(parents=True)
66
-
67
- all_patterns = BuildFilesPatterns.BIN_FILES
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 BuildFilesPatterns.BIN_FILES:
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 = to_path / relative_path
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, to_path / relative_path)
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(to_path) / 'partition_table' / 'partition-table.csv'
86
- part_bin = Path(to_path) / 'partition_table' / 'partition-table.bin'
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('to_dir', type=str, help='destination directory')
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
- copy_bin_to_new_path(args.from_dir, args.to_dir)
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__':