dexverse-uploader 0.1.4a1__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 (77) hide show
  1. dexverse_uploader-0.1.4a1/PKG-INFO +59 -0
  2. dexverse_uploader-0.1.4a1/README.md +17 -0
  3. dexverse_uploader-0.1.4a1/cloud_upload.sh +266 -0
  4. dexverse_uploader-0.1.4a1/package_runner.py +44 -0
  5. dexverse_uploader-0.1.4a1/pyproject.toml +105 -0
  6. dexverse_uploader-0.1.4a1/requirements/base.txt +3 -0
  7. dexverse_uploader-0.1.4a1/requirements/dev.txt +7 -0
  8. dexverse_uploader-0.1.4a1/requirements/hdf5.txt +2 -0
  9. dexverse_uploader-0.1.4a1/requirements/piper.txt +4 -0
  10. dexverse_uploader-0.1.4a1/requirements/tianji.txt +13 -0
  11. dexverse_uploader-0.1.4a1/requirements/umi.txt +8 -0
  12. dexverse_uploader-0.1.4a1/requirements/w1.txt +2 -0
  13. dexverse_uploader-0.1.4a1/src/dexverse_uploader/__init__.py +49 -0
  14. dexverse_uploader-0.1.4a1/src/dexverse_uploader/chunk_uploader.py +76 -0
  15. dexverse_uploader-0.1.4a1/src/dexverse_uploader/cloud_upload_config.py +109 -0
  16. dexverse_uploader-0.1.4a1/src/dexverse_uploader/collection_metadata.py +152 -0
  17. dexverse_uploader-0.1.4a1/src/dexverse_uploader/converter.py +954 -0
  18. dexverse_uploader-0.1.4a1/src/dexverse_uploader/converter_worker.py +274 -0
  19. dexverse_uploader-0.1.4a1/src/dexverse_uploader/device_main.py +170 -0
  20. dexverse_uploader-0.1.4a1/src/dexverse_uploader/file_scanner.py +666 -0
  21. dexverse_uploader-0.1.4a1/src/dexverse_uploader/hdf5_metadata.py +193 -0
  22. dexverse_uploader-0.1.4a1/src/dexverse_uploader/logger.py +41 -0
  23. dexverse_uploader-0.1.4a1/src/dexverse_uploader/mqtt_client.py +226 -0
  24. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/__init__.py +1 -0
  25. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/__init__.py +1 -0
  26. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/PROVENANCE.md +11 -0
  27. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/__init__.py +1 -0
  28. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/__init__.py +1 -0
  29. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/_stats.py +30 -0
  30. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/base.py +48 -0
  31. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/eef_consistency.py +74 -0
  32. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/modality.py +17 -0
  33. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/raw_activity.py +46 -0
  34. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/rot6d.py +123 -0
  35. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/spike.py +63 -0
  36. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/state_action.py +141 -0
  37. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/timestamp.py +99 -0
  38. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/validity.py +70 -0
  39. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/checks/video.py +194 -0
  40. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/io/__init__.py +1 -0
  41. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/io/loaders.py +356 -0
  42. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_algorithm/io/schema.py +129 -0
  43. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_freeze.py +47 -0
  44. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_metadata.py +36 -0
  45. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_profile.py +104 -0
  46. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_quality_checkers.py +268 -0
  47. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_quality_runtime.py +96 -0
  48. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/piper/piper_quality_worker.py +27 -0
  49. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/__init__.py +1 -0
  50. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/_vendor/__init__.py +1 -0
  51. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/_vendor/boundary_core.py +515 -0
  52. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_converter.py +294 -0
  53. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_format.py +63 -0
  54. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_mcap.py +221 -0
  55. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_metadata.py +97 -0
  56. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_output_validator.py +183 -0
  57. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_profile.py +114 -0
  58. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_quality.py +404 -0
  59. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_runtime.py +215 -0
  60. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/tj/tianji_worker_control.py +70 -0
  61. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/umi/__init__.py +1 -0
  62. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/umi/umi_alignment.py +148 -0
  63. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/umi/umi_converter.py +652 -0
  64. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/umi/umi_metadata.py +108 -0
  65. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/umi/umi_quality_checkers.py +427 -0
  66. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/w1/__init__.py +1 -0
  67. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/w1/feedback.py +53 -0
  68. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/w1/gst_h264_encoder.py +152 -0
  69. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/w1/hdf5_converter.py +999 -0
  70. dexverse_uploader-0.1.4a1/src/dexverse_uploader/protocols/w1/w1_quality_checker.py +251 -0
  71. dexverse_uploader-0.1.4a1/src/dexverse_uploader/quality_profiles.py +830 -0
  72. dexverse_uploader-0.1.4a1/src/dexverse_uploader/quality_report_store.py +99 -0
  73. dexverse_uploader-0.1.4a1/src/dexverse_uploader/quality_result.py +234 -0
  74. dexverse_uploader-0.1.4a1/src/dexverse_uploader/sample_hash.py +54 -0
  75. dexverse_uploader-0.1.4a1/src/dexverse_uploader/upload_control.py +343 -0
  76. dexverse_uploader-0.1.4a1/src/dexverse_uploader/upload_manager.py +1311 -0
  77. dexverse_uploader-0.1.4a1/src/dexverse_uploader/upload_state.py +126 -0
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.5
2
+ Name: dexverse-uploader
3
+ Version: 0.1.4a1
4
+ Summary: DexVerse edge-device data uploader
5
+ Requires-Python: <3.11,>=3.10
6
+ Requires-Dist: paho-mqtt<3,>=2.0
7
+ Requires-Dist: requests<3,>=2.25
8
+ Provides-Extra: hdf5
9
+ Requires-Dist: h5py<4,>=3; extra == 'hdf5'
10
+ Provides-Extra: piper
11
+ Requires-Dist: h5py<4,>=3.8; extra == 'piper'
12
+ Requires-Dist: numpy<3,>=1.24; extra == 'piper'
13
+ Requires-Dist: pillow<13,>=10; extra == 'piper'
14
+ Provides-Extra: tianji
15
+ Requires-Dist: av<19,>=8.1; extra == 'tianji'
16
+ Requires-Dist: h5ffmpeg==2.4.0; extra == 'tianji'
17
+ Requires-Dist: h5py<4,>=3.6; extra == 'tianji'
18
+ Requires-Dist: imgaug==0.4.0; extra == 'tianji'
19
+ Requires-Dist: lz4<5,>=4.3; extra == 'tianji'
20
+ Requires-Dist: mcap-ros2-support==0.5.7; extra == 'tianji'
21
+ Requires-Dist: mcap==1.4.0; extra == 'tianji'
22
+ Requires-Dist: numpy<2,>=1.24; extra == 'tianji'
23
+ Requires-Dist: pillow<12,>=9; extra == 'tianji'
24
+ Requires-Dist: pyyaml<7,>=6; extra == 'tianji'
25
+ Requires-Dist: zstandard<1,>=0.23; extra == 'tianji'
26
+ Provides-Extra: umi
27
+ Requires-Dist: h5ffmpeg==2.4.0; extra == 'umi'
28
+ Requires-Dist: h5py==3.14.0; extra == 'umi'
29
+ Requires-Dist: hdfdict==0.3.1; extra == 'umi'
30
+ Requires-Dist: numpy==1.26.4; extra == 'umi'
31
+ Requires-Dist: opencv-python-headless<5,>=4.8; extra == 'umi'
32
+ Requires-Dist: scipy<2,>=1.11; extra == 'umi'
33
+ Requires-Dist: tqdm<5,>=4.66; extra == 'umi'
34
+ Provides-Extra: w1
35
+ Requires-Dist: h5py<4,>=3; extra == 'w1'
36
+ Requires-Dist: numpy<3,>=1.24; extra == 'w1'
37
+ Requires-Dist: opencv-python-headless<5,>=4.8; extra == 'w1'
38
+ Requires-Dist: pillow<13,>=10; extra == 'w1'
39
+ Requires-Dist: pyyaml<7,>=5.1; extra == 'w1'
40
+ Requires-Dist: tqdm<5,>=4.66; extra == 'w1'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # DexVerse Uploader
44
+
45
+ The uploader is a standard `src`-layout Python package. Install the base
46
+ wheel for the MQTT/HTTP service and select a protocol extra when the target
47
+ runtime needs its Python-side conversion or quality-check dependencies:
48
+
49
+ ```text
50
+ pip install dexverse-uploader
51
+ pip install dexverse-uploader[w1]
52
+ pip install dexverse-uploader[umi]
53
+ ```
54
+
55
+ The profile requirements under `requirements/` remain the offline deployment
56
+ manifests. They are kept separate from package metadata because W1/GStreamer,
57
+ UMI/native codecs, and other protocol components can be supplied by the edge
58
+ image or a dedicated runtime. The service keeps the existing MQTT RPC
59
+ contracts and can be started with `python -m dexverse_uploader.device_main`.
@@ -0,0 +1,17 @@
1
+ # DexVerse Uploader
2
+
3
+ The uploader is a standard `src`-layout Python package. Install the base
4
+ wheel for the MQTT/HTTP service and select a protocol extra when the target
5
+ runtime needs its Python-side conversion or quality-check dependencies:
6
+
7
+ ```text
8
+ pip install dexverse-uploader
9
+ pip install dexverse-uploader[w1]
10
+ pip install dexverse-uploader[umi]
11
+ ```
12
+
13
+ The profile requirements under `requirements/` remain the offline deployment
14
+ manifests. They are kept separate from package metadata because W1/GStreamer,
15
+ UMI/native codecs, and other protocol components can be supplied by the edge
16
+ image or a dedicated runtime. The service keeps the existing MQTT RPC
17
+ contracts and can be started with `python -m dexverse_uploader.device_main`.
@@ -0,0 +1,266 @@
1
+ #!/bin/bash
2
+ # ----------------------------------------------------------------------------
3
+ # Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
4
+ #
5
+ # All rights reserved.
6
+ # ----------------------------------------------------------------------------
7
+ # cloud_upload.sh — 云端上传服务启动/重启/停止脚本
8
+ #
9
+ # 用法:
10
+ # bash dexverse/dexverse-uploader/cloud_upload.sh [start|restart|stop]
11
+ #
12
+ # 说明:
13
+ # 1) Python 包位于 src/dexverse_uploader/,外层目录名不参与导入。
14
+ # 2) base 主进程与 UMI 转换进程可使用不同 Python 环境。
15
+ # 3) 支持 start / restart / stop 三种模式。
16
+ # 4) 单实例锁,防止重复拉起。
17
+ # 5) 日志统一输出到模块内 log/ 目录,模块日志由 get_logger() 自动写入。
18
+ # ----------------------------------------------------------------------------
19
+
20
+ set -u
21
+
22
+ MODE="${1:-start}"
23
+ if [[ "${MODE}" != "start" && "${MODE}" != "restart" && "${MODE}" != "stop" ]]; then
24
+ echo "用法: bash $(basename "$0") [start|restart|stop]"
25
+ exit 1
26
+ fi
27
+
28
+ # 发布目录;外层目录名不参与模块导入。
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ RUNTIME_DIR="${CLOUD_UPLOAD_RUNTIME_DIR:-${SCRIPT_DIR}}"
31
+ CONFIG_PATH="${CLOUD_UPLOAD_CONFIG:-${RUNTIME_DIR}/config.json}"
32
+ PACKAGE_PARENT="${SCRIPT_DIR}/src"
33
+
34
+ # 日志目录
35
+ LOG_DIR="${CLOUD_UPLOAD_LOG_DIR:-${RUNTIME_DIR}/log}"
36
+ mkdir -p "${LOG_DIR}"
37
+
38
+ # base 主进程只需要扫描、MQTT、HTTP 和日志依赖。
39
+ PYTHON_BIN="${CLOUD_UPLOAD_BASE_PYTHON:-python3}"
40
+ BASE_DEPS_DIR="${CLOUD_UPLOAD_BASE_DEPS_DIR:-${RUNTIME_DIR}/.deps/base}"
41
+ W1_DEPS_DIR="${CLOUD_UPLOAD_W1_DEPS_DIR:-${RUNTIME_DIR}/.deps/w1}"
42
+ PIP_ZIPAPP="${CLOUD_UPLOAD_PIP_ZIPAPP:-${RUNTIME_DIR}/.deps/pip.pyz}"
43
+ BASE_REQUIREMENTS="${SCRIPT_DIR}/requirements/base.txt"
44
+ HDF5_REQUIREMENTS="${SCRIPT_DIR}/requirements/hdf5.txt"
45
+ W1_REQUIREMENTS="${SCRIPT_DIR}/requirements/w1.txt"
46
+ # UMI worker 使用独立环境;未配置时回退到 base Python,便于兼容现有部署。
47
+ UMI_PYTHON_BIN="${CLOUD_UPLOAD_UMI_PYTHON:-${PYTHON_BIN}}"
48
+ START_MODULE="device_main"
49
+ SERVICE_PATTERN="([p]ython[^ ]* -m [d]exverse_uploader.device_main|[p]ackage_runner.py device_main|python3 -m [d]exe_cloud_upload.device_main)"
50
+
51
+ # ── 单实例锁 ──────────────────────────────────────────────────────────
52
+ LAUNCHER_LOCK_FILE="${LOG_DIR}/dexverse_uploader.launcher.pid"
53
+ SERVICE_PID_FILE="${LOG_DIR}/dexverse_uploader.pid"
54
+
55
+ acquire_launcher_lock() {
56
+ local current_pid="$$"
57
+
58
+ if [[ -f "${LAUNCHER_LOCK_FILE}" ]]; then
59
+ local old_pid
60
+ old_pid="$(cat "${LAUNCHER_LOCK_FILE}" 2>/dev/null || true)"
61
+
62
+ if [[ -n "${old_pid}" && "${old_pid}" =~ ^[0-9]+$ ]] && kill -0 "${old_pid}" 2>/dev/null; then
63
+ if [[ "${MODE}" == "restart" || "${MODE}" == "stop" ]]; then
64
+ echo "[WARN] 检测到已有启动脚本实例 (pid=${old_pid}),先停止旧实例。"
65
+ kill -TERM "${old_pid}" 2>/dev/null || true
66
+ for _ in 1 2 3 4 5; do
67
+ if ! kill -0 "${old_pid}" 2>/dev/null; then
68
+ break
69
+ fi
70
+ sleep 1
71
+ done
72
+ if kill -0 "${old_pid}" 2>/dev/null; then
73
+ echo "[WARN] 旧实例未退出,执行强制停止: pid=${old_pid}"
74
+ kill -KILL "${old_pid}" 2>/dev/null || true
75
+ fi
76
+ else
77
+ echo "[ERROR] 已有启动脚本实例在运行 (pid=${old_pid})。"
78
+ echo "[ERROR] 请使用: bash $(basename "$0") restart"
79
+ exit 1
80
+ fi
81
+ fi
82
+ fi
83
+
84
+ echo "${current_pid}" > "${LAUNCHER_LOCK_FILE}"
85
+ trap 'rm -f "${LAUNCHER_LOCK_FILE}"' EXIT
86
+ }
87
+
88
+ # ── 环境检查 ──────────────────────────────────────────────────────────
89
+
90
+ check_env() {
91
+ if [[ ! -f "${PACKAGE_PARENT}/dexverse_uploader/device_main.py" ]]; then
92
+ echo "[ERROR] Python 包不存在: ${PACKAGE_PARENT}/dexverse_uploader"
93
+ exit 1
94
+ fi
95
+
96
+ if [[ ! -f "${CONFIG_PATH}" ]]; then
97
+ echo "[ERROR] 配置文件不存在: ${CONFIG_PATH}"
98
+ exit 1
99
+ fi
100
+
101
+ if [[ ! -f "${BASE_REQUIREMENTS}" ]]; then
102
+ echo "[ERROR] base 依赖清单不存在: ${BASE_REQUIREMENTS}"
103
+ exit 1
104
+ fi
105
+ if [[ ! -f "${HDF5_REQUIREMENTS}" ]]; then
106
+ echo "[ERROR] HDF5 依赖清单不存在: ${HDF5_REQUIREMENTS}"
107
+ exit 1
108
+ fi
109
+ if [[ ! -f "${W1_REQUIREMENTS}" ]]; then
110
+ echo "[ERROR] W1 依赖清单不存在: ${W1_REQUIREMENTS}"
111
+ exit 1
112
+ fi
113
+ }
114
+
115
+ ensure_base_dependencies() {
116
+ local import_check="import numpy, paho.mqtt.client, requests, h5py"
117
+ if PYTHONPATH="${BASE_DEPS_DIR}${PYTHONPATH:+:${PYTHONPATH}}" \
118
+ "${PYTHON_BIN}" -c "${import_check}" >/dev/null 2>&1; then
119
+ return 0
120
+ fi
121
+
122
+ echo "[INFO] 检测到 base 依赖缺失,安装到模块私有目录: ${BASE_DEPS_DIR}"
123
+ mkdir -p "${BASE_DEPS_DIR}" "$(dirname "${PIP_ZIPAPP}")"
124
+
125
+ if [[ ! -f "${PIP_ZIPAPP}" ]]; then
126
+ echo "[INFO] 下载 pip 启动器..."
127
+ "${PYTHON_BIN}" - "${PIP_ZIPAPP}" <<'PY'
128
+ import pathlib
129
+ import sys
130
+ import urllib.request
131
+
132
+ target = pathlib.Path(sys.argv[1])
133
+ urllib.request.urlretrieve("https://bootstrap.pypa.io/pip/pip.pyz", target)
134
+ PY
135
+ if [[ $? -ne 0 ]]; then
136
+ echo "[ERROR] pip 启动器下载失败,请检查网络或设置 CLOUD_UPLOAD_PIP_ZIPAPP。"
137
+ exit 1
138
+ fi
139
+ fi
140
+
141
+ "${PYTHON_BIN}" "${PIP_ZIPAPP}" install \
142
+ --disable-pip-version-check \
143
+ --target "${BASE_DEPS_DIR}" \
144
+ -r "${BASE_REQUIREMENTS}"
145
+ if [[ $? -ne 0 ]]; then
146
+ echo "[ERROR] base 依赖安装失败: ${BASE_REQUIREMENTS}"
147
+ exit 1
148
+ fi
149
+
150
+ "${PYTHON_BIN}" "${PIP_ZIPAPP}" install \
151
+ --disable-pip-version-check \
152
+ --no-deps \
153
+ --target "${BASE_DEPS_DIR}" \
154
+ -r "${HDF5_REQUIREMENTS}"
155
+ if [[ $? -ne 0 ]]; then
156
+ echo "[ERROR] HDF5 依赖安装失败: ${HDF5_REQUIREMENTS}"
157
+ exit 1
158
+ fi
159
+
160
+ if ! PYTHONPATH="${BASE_DEPS_DIR}${PYTHONPATH:+:${PYTHONPATH}}" \
161
+ "${PYTHON_BIN}" -c "${import_check}" >/dev/null 2>&1; then
162
+ echo "[ERROR] base 依赖安装后仍无法导入,请确认设备 Python 已提供兼容的 NumPy。"
163
+ exit 1
164
+ fi
165
+ }
166
+
167
+ # ── 停止已有服务 ──────────────────────────────────────────────────────
168
+
169
+ stop_existing_services() {
170
+ local pids
171
+ pids="$(pgrep -f "${SERVICE_PATTERN}" || true)"
172
+ if [[ -z "${pids}" ]]; then
173
+ rm -f "${SERVICE_PID_FILE}"
174
+ echo "[INFO] 未发现运行中的云端上传服务。"
175
+ return 0
176
+ fi
177
+
178
+ echo "[INFO] 准备停止云端上传服务 (pid=${pids//$'\n'/, })"
179
+ for pid in ${pids}; do
180
+ kill -TERM "${pid}" 2>/dev/null || true
181
+ done
182
+
183
+ # 等待优雅退出
184
+ for _ in 1 2 3 4 5; do
185
+ local any_alive=0
186
+ for pid in ${pids}; do
187
+ if kill -0 "${pid}" 2>/dev/null; then
188
+ any_alive=1
189
+ fi
190
+ done
191
+ [[ "${any_alive}" -eq 0 ]] && break
192
+ sleep 1
193
+ done
194
+
195
+ # 强制停止
196
+ for pid in ${pids}; do
197
+ if kill -0 "${pid}" 2>/dev/null; then
198
+ echo "[WARN] 进程 ${pid} 未在超时内退出,执行强制停止。"
199
+ kill -KILL "${pid}" 2>/dev/null || true
200
+ fi
201
+ done
202
+
203
+ rm -f "${SERVICE_PID_FILE}"
204
+ echo "[INFO] 云端上传服务已停止。"
205
+ }
206
+
207
+ # ── 主流程 ────────────────────────────────────────────────────────────
208
+
209
+ check_env
210
+
211
+ if [[ "${MODE}" == "stop" ]]; then
212
+ echo "[INFO] 模式: stop"
213
+ stop_existing_services
214
+ exit 0
215
+ fi
216
+
217
+ if [[ "${MODE}" == "restart" ]]; then
218
+ echo "[INFO] 模式: restart(先停后起)"
219
+ stop_existing_services
220
+ else
221
+ echo "[INFO] 模式: start"
222
+ existing_pids="$(pgrep -f "${SERVICE_PATTERN}" || true)"
223
+ if [[ -n "${existing_pids}" ]]; then
224
+ echo "[ERROR] 已有云端上传服务运行中 (pid=${existing_pids//$'\n'/, })。"
225
+ echo "[ERROR] 如需切换到当前模块,请使用: bash $(basename "$0") restart"
226
+ exit 1
227
+ fi
228
+ fi
229
+
230
+ acquire_launcher_lock
231
+ ensure_base_dependencies
232
+
233
+ cd "${RUNTIME_DIR}" || exit 1
234
+
235
+ echo "[INFO] 运行目录: ${RUNTIME_DIR}"
236
+ echo "[INFO] 启动云端上传服务..."
237
+
238
+ # 启动服务(后台运行,模块方式)
239
+ CONSOLE_LOG="${LOG_DIR}/service_console.log"
240
+ nohup env PYTHONPATH="${PACKAGE_PARENT}:${BASE_DEPS_DIR}${PYTHONPATH:+:${PYTHONPATH}}" \
241
+ CLOUD_UPLOAD_LOG_DIR="${LOG_DIR}" \
242
+ CLOUD_UPLOAD_RUNTIME_DIR="${RUNTIME_DIR}" \
243
+ CLOUD_UPLOAD_CONFIG="${CONFIG_PATH}" \
244
+ CLOUD_UPLOAD_W1_DEPS_DIR="${W1_DEPS_DIR}" \
245
+ CLOUD_UPLOAD_W1_REQUIREMENTS="${W1_REQUIREMENTS}" \
246
+ CLOUD_UPLOAD_PIP_ZIPAPP="${PIP_ZIPAPP}" \
247
+ CLOUD_UPLOAD_UMI_PYTHON="${UMI_PYTHON_BIN}" \
248
+ PYTHONUNBUFFERED=1 \
249
+ "${PYTHON_BIN}" -m "dexverse_uploader.${START_MODULE}" >> "${CONSOLE_LOG}" 2>&1 &
250
+
251
+ SERVICE_PID=$!
252
+ echo "${SERVICE_PID}" > "${SERVICE_PID_FILE}"
253
+ sleep 3
254
+ if ! kill -0 "${SERVICE_PID}" 2>/dev/null; then
255
+ rm -f "${SERVICE_PID_FILE}"
256
+ echo "[ERROR] 云端上传服务启动后立即退出。"
257
+ echo "[ERROR] 最近日志: ${CONSOLE_LOG}"
258
+ tail -n 20 "${CONSOLE_LOG}" 2>/dev/null || true
259
+ exit 1
260
+ fi
261
+ echo "[INFO] 云端上传服务已启动 (pid=${SERVICE_PID})"
262
+ echo "[INFO] 日志:"
263
+ echo " tail -f ${CONSOLE_LOG}"
264
+ echo " tail -f ${LOG_DIR}/mqtt_client.log"
265
+ echo " tail -f ${LOG_DIR}/file_scanner.log"
266
+ echo " tail -f ${LOG_DIR}/upload_manager.log"
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ """从任意名称的发布目录运行 dexverse_uploader 模块。"""
3
+
4
+ from __future__ import annotations
5
+
6
+ import importlib.util
7
+ import runpy
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ PACKAGE_NAME = "dexverse_uploader"
13
+ PACKAGE_PARENT = Path(__file__).resolve().parent / "src"
14
+ PACKAGE_ROOT = PACKAGE_PARENT / PACKAGE_NAME
15
+
16
+
17
+ def load_package() -> None:
18
+ """将源码布局中的固定包名注册给测试和源码运行器。"""
19
+ if PACKAGE_NAME in sys.modules:
20
+ return
21
+ spec = importlib.util.spec_from_file_location(
22
+ PACKAGE_NAME,
23
+ PACKAGE_ROOT / "__init__.py",
24
+ submodule_search_locations=[str(PACKAGE_ROOT)],
25
+ )
26
+ if spec is None or spec.loader is None:
27
+ raise RuntimeError(f"无法加载上传模块: {PACKAGE_ROOT}")
28
+ package = importlib.util.module_from_spec(spec)
29
+ sys.modules[PACKAGE_NAME] = package
30
+ spec.loader.exec_module(package)
31
+
32
+
33
+ def main() -> None:
34
+ """运行命令行指定的子模块(支持点分隔的协议路径)。"""
35
+ if len(sys.argv) < 2:
36
+ raise SystemExit(f"用法: {Path(sys.argv[0]).name} <module> [args...]")
37
+ module_name = sys.argv.pop(1)
38
+ load_package()
39
+ sys.argv[0] = str(PACKAGE_ROOT.joinpath(*module_name.split(".")).with_suffix(".py"))
40
+ runpy.run_module(f"{PACKAGE_NAME}.{module_name}", run_name="__main__")
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()
@@ -0,0 +1,105 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "dexverse-uploader"
7
+ version = "0.1.4a1"
8
+ description = "DexVerse edge-device data uploader"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10,<3.11"
11
+ dependencies = [
12
+ "paho-mqtt>=2.0,<3",
13
+ "requests>=2.25,<3",
14
+ ]
15
+
16
+ [project.scripts]
17
+ dexverse-uploader = "dexverse_uploader.device_main:main"
18
+ dexverse-uploader-converter = "dexverse_uploader.converter_worker:main"
19
+
20
+ [project.optional-dependencies]
21
+ # These profiles are intentionally separate: edge images may provide some of
22
+ # the native/system components themselves, while wheel-based environments can
23
+ # opt into the corresponding Python dependencies.
24
+ hdf5 = [
25
+ "h5py>=3,<4",
26
+ ]
27
+ w1 = [
28
+ "h5py>=3,<4",
29
+ "numpy>=1.24,<3",
30
+ "opencv-python-headless>=4.8,<5",
31
+ "Pillow>=10,<13",
32
+ "PyYAML>=5.1,<7",
33
+ "tqdm>=4.66,<5",
34
+ ]
35
+ piper = [
36
+ "numpy>=1.24,<3",
37
+ "h5py>=3.8,<4",
38
+ "Pillow>=10,<13",
39
+ ]
40
+ tianji = [
41
+ "mcap==1.4.0",
42
+ "mcap-ros2-support==0.5.7",
43
+ "lz4>=4.3,<5",
44
+ "zstandard>=0.23,<1",
45
+ "av>=8.1,<19",
46
+ "h5py>=3.6,<4",
47
+ "numpy>=1.24,<2",
48
+ "h5ffmpeg==2.4.0",
49
+ "imgaug==0.4.0",
50
+ "Pillow>=9,<12",
51
+ "PyYAML>=6,<7",
52
+ ]
53
+ umi = [
54
+ "h5ffmpeg==2.4.0",
55
+ "h5py==3.14.0",
56
+ "numpy==1.26.4",
57
+ "scipy>=1.11,<2",
58
+ "opencv-python-headless>=4.8,<5",
59
+ "hdfdict==0.3.1",
60
+ "tqdm>=4.66,<5",
61
+ ]
62
+
63
+ [dependency-groups]
64
+ dev = [
65
+ "mypy>=1.15,<2",
66
+ "pytest>=8.3,<10",
67
+ "ruff>=0.11,<1",
68
+ ]
69
+
70
+ [tool.hatch.build.targets.wheel]
71
+ packages = ["src/dexverse_uploader"]
72
+
73
+ [tool.hatch.build.targets.sdist]
74
+ include = [
75
+ "/pyproject.toml",
76
+ "/README.md",
77
+ "/src/dexverse_uploader",
78
+ "/requirements",
79
+ "/cloud_upload.sh",
80
+ "/package_runner.py",
81
+ ]
82
+
83
+ [tool.pytest.ini_options]
84
+ addopts = "-ra"
85
+ testpaths = ["tests"]
86
+ pythonpath = ["src"]
87
+
88
+ [tool.ruff]
89
+ target-version = "py310"
90
+ line-length = 120
91
+
92
+ [tool.ruff.lint]
93
+ select = ["E", "F"]
94
+ ignore = ["E501"]
95
+
96
+ [tool.mypy]
97
+ python_version = "3.10"
98
+ check_untyped_defs = true
99
+ disallow_any_generics = false
100
+ disallow_incomplete_defs = false
101
+ disallow_untyped_defs = false
102
+ warn_return_any = false
103
+ warn_unreachable = true
104
+ ignore_missing_imports = true
105
+ files = ["src"]
@@ -0,0 +1,3 @@
1
+ # dexverse_uploader 主进程:扫描、MQTT、HTTP 上传。
2
+ paho-mqtt>=2.0,<3
3
+ requests>=2.25,<3
@@ -0,0 +1,7 @@
1
+ # dexverse_uploader 开发与测试工具;不进入生产主进程环境。
2
+ -r base.txt
3
+ pytest>=8,<9
4
+ ruff>=0.6,<1
5
+ # 合成 HDF5 checker 测试所需;不引入 h5ffmpeg/转换 worker。
6
+ h5py>=3,<4
7
+ numpy>=1.24,<2
@@ -0,0 +1,2 @@
1
+ # HDF5 元信息写入;启动脚本使用 --no-deps 安装,复用设备已有的 NumPy。
2
+ h5py>=3,<4
@@ -0,0 +1,4 @@
1
+ # PIPER HDF5 质检私有依赖,不进入 base 主进程。
2
+ numpy>=1.24,<3
3
+ h5py>=3.8,<4
4
+ Pillow>=10,<13
@@ -0,0 +1,13 @@
1
+ # 天机 converter worker;首次转换时按需安装到模块私有目录。
2
+ mcap==1.4.0
3
+ mcap-ros2-support==0.5.7
4
+ lz4>=4.3,<5
5
+ zstandard>=0.23,<1
6
+ av>=8.1,<19
7
+ h5py>=3.6,<4
8
+ numpy>=1.24,<2
9
+ h5ffmpeg==2.4.0
10
+ # MR-compatible optional resize path (NumPy must remain <2 for imgaug).
11
+ imgaug==0.4.0
12
+ Pillow>=9,<12
13
+ PyYAML>=6,<7
@@ -0,0 +1,8 @@
1
+ # UMI converter worker;建议安装在容器或独立 venv 中。
2
+ h5ffmpeg==2.4.0
3
+ h5py==3.14.0
4
+ numpy==1.26.4
5
+ scipy>=1.11,<2
6
+ opencv-python-headless>=4.8,<5
7
+ hdfdict==0.3.1
8
+ tqdm>=4.66,<5
@@ -0,0 +1,2 @@
1
+ # W1 converter 按需安装;其余 NumPy/OpenCV/GStreamer 由 Jetson 系统环境提供。
2
+ h5py>=3,<4
@@ -0,0 +1,49 @@
1
+ # ----------------------------------------------------------------------------
2
+ # Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3
+ #
4
+ # All rights reserved.
5
+ # ----------------------------------------------------------------------------
6
+ """云端上传模块 — 将录制的遥操数据上传到云端服务器。
7
+
8
+ 控制面:MQTT RPC(get_data / upload)
9
+ 数据面:HTTP PUT 到 MINIO presigned-url
10
+ """
11
+
12
+ from dexverse_uploader.chunk_uploader import ChunkUploader
13
+ from dexverse_uploader.converter import (
14
+ Converter,
15
+ ConverterRegistry,
16
+ TianjiSubprocessConverter,
17
+ UmiSubprocessConverter,
18
+ W1Converter,
19
+ default_registry,
20
+ )
21
+ from dexverse_uploader.file_scanner import FileScanner
22
+ from dexverse_uploader.quality_profiles import (
23
+ QualityProfileError,
24
+ build_default_profile,
25
+ quality_templates,
26
+ validate_quality_profile,
27
+ )
28
+ from dexverse_uploader.quality_result import build_quality_result, compact_quality_result
29
+ from dexverse_uploader.quality_report_store import QualityReportStore
30
+ from dexverse_uploader.upload_manager import UploadManager
31
+
32
+ __all__ = [
33
+ "ChunkUploader",
34
+ "Converter",
35
+ "ConverterRegistry",
36
+ "FileScanner",
37
+ "QualityProfileError",
38
+ "QualityReportStore",
39
+ "TianjiSubprocessConverter",
40
+ "UmiSubprocessConverter",
41
+ "UploadManager",
42
+ "W1Converter",
43
+ "default_registry",
44
+ "build_default_profile",
45
+ "build_quality_result",
46
+ "compact_quality_result",
47
+ "quality_templates",
48
+ "validate_quality_profile",
49
+ ]
@@ -0,0 +1,76 @@
1
+ # ----------------------------------------------------------------------------
2
+ # Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3
+ #
4
+ # All rights reserved.
5
+ # ----------------------------------------------------------------------------
6
+ """chunk_uploader.py — 文件上传器:抽样摘要计算 + HTTP PUT 到 presigned-url。"""
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Callable, Dict
13
+
14
+ from dexverse_uploader.logger import get_logger
15
+ from dexverse_uploader.sample_hash import HASH_TYPE, compute_sample_hash_bytes, file_signature
16
+
17
+ logger = get_logger("chunk_uploader")
18
+
19
+
20
+ class ChunkUploader:
21
+ """文件上传器。
22
+
23
+ 云端接口模式:接收 presigned-url → HTTP PUT 整个文件 → 返回抽样摘要。
24
+ 不再做分片。
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ pass
29
+
30
+ def upload_file(
31
+ self,
32
+ file_path: Path,
33
+ presigned_url: str,
34
+ put_cb: Callable[[str, bytes], bool],
35
+ ) -> Dict:
36
+ """上传单个文件到 MINIO presigned-url。
37
+
38
+ Args:
39
+ file_path: 文件路径
40
+ presigned_url: MINIO presigned upload URL
41
+ put_cb: HTTP PUT 回调 (url, file_bytes) -> bool
42
+
43
+ Returns:
44
+ {"success": bool, "content_hash": str, "hash_type": str, "size": int, "error": str}
45
+ """
46
+ try:
47
+ with open(file_path, "rb") as f:
48
+ before = os.fstat(f.fileno())
49
+ data = f.read()
50
+ after = os.fstat(f.fileno())
51
+ if (file_signature(before) != file_signature(after)
52
+ or file_signature(after) != file_signature(Path(file_path).stat())
53
+ or len(data) != before.st_size):
54
+ raise OSError("文件在上传读取期间发生变化,请重试")
55
+ except Exception as e:
56
+ return {"success": False, "content_hash": "", "hash_type": "", "size": 0, "error": str(e)}
57
+
58
+ content_hash = compute_sample_hash_bytes(data)
59
+ file_size = len(data)
60
+
61
+ try:
62
+ ok = put_cb(presigned_url, data)
63
+ if ok:
64
+ return {"success": True, "content_hash": content_hash, "hash_type": HASH_TYPE, "size": file_size, "error": ""}
65
+ else:
66
+ return {"success": False, "content_hash": content_hash, "hash_type": HASH_TYPE, "size": file_size, "error": "PUT failed"}
67
+ except Exception as e:
68
+ error_type = type(e).__name__
69
+ logger.error("上传回调异常: %s", error_type)
70
+ return {
71
+ "success": False,
72
+ "content_hash": content_hash,
73
+ "hash_type": HASH_TYPE,
74
+ "size": file_size,
75
+ "error": f"PUT callback exception: {error_type}",
76
+ }