tlabel 0.1.0a1__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.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: tlabel
3
+ Version: 0.1.0a1
4
+ Summary: TouchLabel AI - Tactile Data Annotation Toolkit
5
+ Author-email: Niuzu Tech <luoxi@touchlabelai.cn>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/liesliy/tlabel
8
+ Keywords: tactile,annotation,robotics,GelSight,PaXini,touch
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: numpy>=1.20
21
+ Provides-Extra: gelsight
22
+ Requires-Dist: opencv-python>=4.0; extra == "gelsight"
23
+ Provides-Extra: paxini
24
+ Requires-Dist: h5py>=3.0; extra == "paxini"
25
+ Provides-Extra: daimon
26
+ Requires-Dist: pyarrow>=10.0; extra == "daimon"
27
+ Provides-Extra: all
28
+ Requires-Dist: opencv-python>=4.0; extra == "all"
29
+ Requires-Dist: h5py>=3.0; extra == "all"
30
+ Requires-Dist: pyarrow>=10.0; extra == "all"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: jupyter; extra == "dev"
34
+
35
+ # TouchLabel AI — Tactile Data Annotation Toolkit
36
+
37
+ > 触觉数据标注工具包 · pip install 一行搞定
38
+
39
+ ## 安装
40
+
41
+ ```bash
42
+ # 基础安装
43
+ pip install tlabel
44
+
45
+ # 带GelSight/DIGIT支持
46
+ pip install tlabel[gelsight]
47
+
48
+ # 带帕西尼支持
49
+ pip install tlabel[paxini]
50
+
51
+ # 带戴盟支持
52
+ pip install tlabel[daimon]
53
+
54
+ # 全部传感器
55
+ pip install tlabel[all]
56
+ ```
57
+
58
+ ## 5分钟上手
59
+
60
+ ```python
61
+ import tlabel
62
+
63
+ # 加载数据 — 自动识别格式
64
+ data = tlabel.load("gelsight_force.pkl") # GelSight/DIGIT
65
+ data = tlabel.load("paxini_episode.h5") # 帕西尼
66
+ data = tlabel.load("daimon_data/") # 戴盟(目录或.parquet)
67
+
68
+ # 弹出彩色标注面板(Jupyter)
69
+ data.review()
70
+
71
+ # 英文界面
72
+ data.review(lang="en")
73
+
74
+ # 导出
75
+ data.export("output.json") # TLabel Format v2 JSON
76
+ data.export("output.csv") # CSV平面表
77
+ ```
78
+
79
+ ## 支持的传感器
80
+
81
+ | 传感器 | 格式 | 状态 |
82
+ |--------|------|------|
83
+ | GelSight Mini | .pkl | ✅ 第一期 |
84
+ | DIGIT | .pkl | ✅ 第一期 |
85
+ | PaXini PXCap | .h5/.hdf5 | ✅ 第一期 |
86
+ | Daimon DM-TacClaw | .parquet / 目录 | ✅ 支持 |
87
+
88
+ ## 交互面板功能
89
+
90
+ - 🎨 **彩色时间轴**:绿=接触、红=滑移、灰=无接触
91
+ - 🕸 **18维雷达图**:TLabel Format v2全部维度可视化
92
+ - ✏️ **批量修正**:选中帧区间,一键修改接触/滑移/力度
93
+ - 🔗 **联动规则**:接触=0时自动清除力度和滑移
94
+ - 🌐 **中英文切换**:右上角一键切换
95
+ - 📤 **导出**:JSON / CSV
96
+
97
+ ## TLabel Format v2 (18维)
98
+
99
+ ```
100
+ # v1 (11维)
101
+ contact · deformation_magnitude · force_magnitude · force_peak
102
+ force_direction · slip_entropy · slip_event · texture_energy
103
+ edge_density · contact_area · centroid_x
104
+
105
+ # v2新增 (7维)
106
+ normal_field_magnitude · normal_field_variance
107
+ shear_field_magnitude · shear_field_direction
108
+ delta_force_normal · delta_force_shear · friction_cone_ratio
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT © 牛宿科技
@@ -0,0 +1,79 @@
1
+ # TouchLabel AI — Tactile Data Annotation Toolkit
2
+
3
+ > 触觉数据标注工具包 · pip install 一行搞定
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ # 基础安装
9
+ pip install tlabel
10
+
11
+ # 带GelSight/DIGIT支持
12
+ pip install tlabel[gelsight]
13
+
14
+ # 带帕西尼支持
15
+ pip install tlabel[paxini]
16
+
17
+ # 带戴盟支持
18
+ pip install tlabel[daimon]
19
+
20
+ # 全部传感器
21
+ pip install tlabel[all]
22
+ ```
23
+
24
+ ## 5分钟上手
25
+
26
+ ```python
27
+ import tlabel
28
+
29
+ # 加载数据 — 自动识别格式
30
+ data = tlabel.load("gelsight_force.pkl") # GelSight/DIGIT
31
+ data = tlabel.load("paxini_episode.h5") # 帕西尼
32
+ data = tlabel.load("daimon_data/") # 戴盟(目录或.parquet)
33
+
34
+ # 弹出彩色标注面板(Jupyter)
35
+ data.review()
36
+
37
+ # 英文界面
38
+ data.review(lang="en")
39
+
40
+ # 导出
41
+ data.export("output.json") # TLabel Format v2 JSON
42
+ data.export("output.csv") # CSV平面表
43
+ ```
44
+
45
+ ## 支持的传感器
46
+
47
+ | 传感器 | 格式 | 状态 |
48
+ |--------|------|------|
49
+ | GelSight Mini | .pkl | ✅ 第一期 |
50
+ | DIGIT | .pkl | ✅ 第一期 |
51
+ | PaXini PXCap | .h5/.hdf5 | ✅ 第一期 |
52
+ | Daimon DM-TacClaw | .parquet / 目录 | ✅ 支持 |
53
+
54
+ ## 交互面板功能
55
+
56
+ - 🎨 **彩色时间轴**:绿=接触、红=滑移、灰=无接触
57
+ - 🕸 **18维雷达图**:TLabel Format v2全部维度可视化
58
+ - ✏️ **批量修正**:选中帧区间,一键修改接触/滑移/力度
59
+ - 🔗 **联动规则**:接触=0时自动清除力度和滑移
60
+ - 🌐 **中英文切换**:右上角一键切换
61
+ - 📤 **导出**:JSON / CSV
62
+
63
+ ## TLabel Format v2 (18维)
64
+
65
+ ```
66
+ # v1 (11维)
67
+ contact · deformation_magnitude · force_magnitude · force_peak
68
+ force_direction · slip_entropy · slip_event · texture_energy
69
+ edge_density · contact_area · centroid_x
70
+
71
+ # v2新增 (7维)
72
+ normal_field_magnitude · normal_field_variance
73
+ shear_field_magnitude · shear_field_direction
74
+ delta_force_normal · delta_force_shear · friction_cone_ratio
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT © 牛宿科技
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tlabel"
7
+ version = "0.1.0a1"
8
+ description = "TouchLabel AI - Tactile Data Annotation Toolkit"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Niuzu Tech", email = "luoxi@touchlabelai.cn"},
14
+ ]
15
+ keywords = ["tactile", "annotation", "robotics", "GelSight", "PaXini", "touch"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ ]
27
+
28
+ dependencies = [
29
+ "numpy>=1.20",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ gelsight = ["opencv-python>=4.0"]
34
+ paxini = ["h5py>=3.0"]
35
+ daimon = ["pyarrow>=10.0"]
36
+ all = ["opencv-python>=4.0", "h5py>=3.0", "pyarrow>=10.0"]
37
+ dev = ["pytest", "jupyter"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/liesliy/tlabel"
41
+
42
+ [tool.setuptools.packages.find]
43
+ include = ["tlabel*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,299 @@
1
+ """TouchLabel pip包单元测试"""
2
+ import json
3
+ import tempfile
4
+ import os
5
+ import pytest
6
+
7
+
8
+ class TestImport:
9
+ def test_import_tlabel(self):
10
+ import tlabel
11
+ assert hasattr(tlabel, "load")
12
+ assert hasattr(tlabel, "__version__")
13
+
14
+ def test_import_adapters(self):
15
+ from tlabel.adapters.gelsight import GelSightAdapter
16
+ from tlabel.adapters.paxini import PaxiniAdapter
17
+ from tlabel.adapters.daimon import DaimonAdapter
18
+
19
+ def test_import_types(self):
20
+ from tlabel.core.types import TLabelData, TLabelFrame
21
+
22
+
23
+ class TestRegistry:
24
+ def test_pkl_detection(self):
25
+ from tlabel.core.registry import auto_detect_format
26
+ with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as f:
27
+ f.write(b"test")
28
+ path = f.name
29
+ try:
30
+ assert auto_detect_format(path) == "gelsight"
31
+ finally:
32
+ os.unlink(path)
33
+
34
+ def test_h5_detection(self):
35
+ from tlabel.core.registry import auto_detect_format
36
+ with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f:
37
+ f.write(b"test")
38
+ path = f.name
39
+ try:
40
+ assert auto_detect_format(path) == "paxini"
41
+ finally:
42
+ os.unlink(path)
43
+
44
+ def test_hdf5_detection(self):
45
+ from tlabel.core.registry import auto_detect_format
46
+ with tempfile.NamedTemporaryFile(suffix=".hdf5", delete=False) as f:
47
+ f.write(b"test")
48
+ path = f.name
49
+ try:
50
+ assert auto_detect_format(path) == "paxini"
51
+ finally:
52
+ os.unlink(path)
53
+
54
+ def test_parquet_detection(self):
55
+ from tlabel.core.registry import auto_detect_format
56
+ with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as f:
57
+ f.write(b"test")
58
+ path = f.name
59
+ try:
60
+ assert auto_detect_format(path) == "daimon"
61
+ finally:
62
+ os.unlink(path)
63
+
64
+ def test_daimon_dir_detection(self):
65
+ from tlabel.core.registry import auto_detect_format
66
+ info = {"robot_type": "ugripper_right", "fps": 30, "total_episodes": 1}
67
+ with tempfile.TemporaryDirectory() as td:
68
+ meta_dir = os.path.join(td, "meta")
69
+ os.makedirs(meta_dir)
70
+ with open(os.path.join(meta_dir, "info.json"), "w") as f:
71
+ json.dump(info, f)
72
+ assert auto_detect_format(td) == "daimon"
73
+
74
+ def test_unknown_format(self):
75
+ from tlabel.core.registry import auto_detect_format
76
+ with tempfile.NamedTemporaryFile(suffix=".xyz", delete=False) as f:
77
+ f.write(b"test")
78
+ path = f.name
79
+ try:
80
+ assert auto_detect_format(path) is None
81
+ finally:
82
+ os.unlink(path)
83
+
84
+
85
+ class TestTLabelFrame:
86
+ def _make_frame(self, **overrides):
87
+ from tlabel.core.types import TLabelFrame
88
+ defaults = {
89
+ "frame_idx": 0,
90
+ "timestamp_s": 0.0,
91
+ "tlabel_v2": {
92
+ "contact": 1.0,
93
+ "force_magnitude": 0.8,
94
+ "force_peak": 0.6,
95
+ "slip_event": 0.3,
96
+ "contact_area": 0.5,
97
+ "deformation_magnitude": 0.4,
98
+ "force_direction": 0.2,
99
+ "slip_entropy": 0.1,
100
+ "texture_energy": 0.0,
101
+ "edge_density": 0.0,
102
+ "centroid_x": 0.5,
103
+ "normal_field_magnitude": 0.3,
104
+ "normal_field_variance": 0.2,
105
+ "shear_field_magnitude": 0.0,
106
+ "shear_field_direction": 0.0,
107
+ "delta_force_normal": 0.1,
108
+ "delta_force_shear": 0.05,
109
+ "friction_cone_ratio": 0.7,
110
+ },
111
+ "manipulation_phase": "stable_contact",
112
+ "confidence": 0.9,
113
+ }
114
+ defaults.update(overrides)
115
+ return TLabelFrame(**defaults)
116
+
117
+ def test_properties(self):
118
+ f = self._make_frame()
119
+ assert f.contact == 1.0
120
+ assert f.slip_event == 0.3
121
+ assert f.force_magnitude == 0.8
122
+
123
+ def test_patch_basic(self):
124
+ f = self._make_frame()
125
+ rec = f.patch("contact", 0.0, cascade=False)
126
+ assert f.tlabel_v2["contact"] == 0.0
127
+ assert rec["old_value"] == 1.0
128
+ assert rec["new_value"] == 0.0
129
+
130
+ def test_patch_cascade_contact_to_zero(self):
131
+ f = self._make_frame()
132
+ f.patch("contact", 0.0, cascade=True)
133
+ assert f.tlabel_v2["force_magnitude"] == 0.0
134
+ assert f.tlabel_v2["slip_event"] == 0.0
135
+ assert f.tlabel_v2["contact_area"] == 0.0
136
+ assert f.manipulation_phase == "idle"
137
+
138
+ def test_patch_no_cascade_when_not_zero(self):
139
+ f = self._make_frame()
140
+ f.patch("contact", 0.5, cascade=True)
141
+ # force should NOT be zeroed when contact != 0
142
+ assert f.tlabel_v2["force_magnitude"] == 0.8
143
+
144
+ def test_is_modified(self):
145
+ f = self._make_frame()
146
+ assert not f.is_modified
147
+ f.patch("contact", 0.0)
148
+ assert f.is_modified
149
+
150
+ def test_to_dict(self):
151
+ f = self._make_frame()
152
+ d = f.to_dict()
153
+ assert "frame_idx" in d
154
+ assert "tlabel_v2" in d
155
+ assert "manipulation_phase" in d
156
+
157
+
158
+ class TestTLabelData:
159
+ def _make_data(self, n_frames=10):
160
+ from tlabel.core.types import TLabelData, TLabelFrame
161
+ frames = []
162
+ for i in range(n_frames):
163
+ f = TLabelFrame(
164
+ frame_idx=i,
165
+ timestamp_s=i / 30.0,
166
+ tlabel_v2={"contact": 1.0 if i % 3 == 0 else 0.0,
167
+ "force_magnitude": 0.5 if i % 3 == 0 else 0.0,
168
+ "slip_event": 0.0, "force_peak": 0.0,
169
+ "deformation_magnitude": 0.0, "force_direction": 0.0,
170
+ "slip_entropy": 0.0, "texture_energy": 0.0,
171
+ "edge_density": 0.0, "contact_area": 0.0,
172
+ "centroid_x": 0.5, "normal_field_magnitude": 0.0,
173
+ "normal_field_variance": 0.0,
174
+ "shear_field_magnitude": 0.0,
175
+ "shear_field_direction": 0.0,
176
+ "delta_force_normal": 0.0, "delta_force_shear": 0.0,
177
+ "friction_cone_ratio": 0.0},
178
+ manipulation_phase="idle",
179
+ confidence=0.9,
180
+ )
181
+ frames.append(f)
182
+ return TLabelData(
183
+ frames=frames,
184
+ sensor_info={"type": "test"},
185
+ episode_info={"source": "test"},
186
+ capabilities={"contact": True},
187
+ )
188
+
189
+ def test_num_frames(self):
190
+ data = self._make_data(10)
191
+ assert data.num_frames == 10
192
+
193
+ def test_duration(self):
194
+ data = self._make_data(10)
195
+ assert data.duration_s > 0
196
+
197
+ def test_batch_patch(self):
198
+ data = self._make_data(10)
199
+ n = data.batch_patch(0, 9, "contact", 0.0)
200
+ assert n > 0
201
+
202
+ def test_to_dict(self):
203
+ data = self._make_data(5)
204
+ d = data.to_dict()
205
+ assert d["schema_version"] == "0.3.0"
206
+ assert "frames" in d
207
+ assert len(d["frames"]) == 5
208
+
209
+ def test_empty_data(self):
210
+ from tlabel.core.types import TLabelData
211
+ data = TLabelData(
212
+ frames=[], sensor_info={}, episode_info={}, capabilities={}
213
+ )
214
+ assert data.num_frames == 0
215
+ assert data.duration_s == 0.0
216
+ assert data.modified_count == 0
217
+
218
+ def test_len(self):
219
+ data = self._make_data(10)
220
+ assert len(data) == 10
221
+
222
+
223
+ class TestLoaderErrors:
224
+ def test_file_not_found(self):
225
+ import tlabel
226
+ with pytest.raises(FileNotFoundError):
227
+ tlabel.load("/nonexistent/path/data.pkl")
228
+
229
+ def test_unknown_format(self):
230
+ import tlabel
231
+ with tempfile.NamedTemporaryFile(suffix=".xyz", delete=False) as f:
232
+ f.write(b"test")
233
+ path = f.name
234
+ try:
235
+ with pytest.raises(ValueError, match="无法识别"):
236
+ tlabel.load(path)
237
+ finally:
238
+ os.unlink(path)
239
+
240
+
241
+ class TestExport:
242
+ def test_json_export(self):
243
+ from tlabel.core.types import TLabelData, TLabelFrame
244
+ from tlabel.export.writer import export_data
245
+ frames = [TLabelFrame(
246
+ frame_idx=0, timestamp_s=0.0,
247
+ tlabel_v2={"contact": 1.0, "force_magnitude": 0.5,
248
+ "slip_event": 0.0, "force_peak": 0.0,
249
+ "deformation_magnitude": 0.0, "force_direction": 0.0,
250
+ "slip_entropy": 0.0, "texture_energy": 0.0,
251
+ "edge_density": 0.0, "contact_area": 0.0,
252
+ "centroid_x": 0.5, "normal_field_magnitude": 0.0,
253
+ "normal_field_variance": 0.0,
254
+ "shear_field_magnitude": 0.0,
255
+ "shear_field_direction": 0.0,
256
+ "delta_force_normal": 0.0, "delta_force_shear": 0.0,
257
+ "friction_cone_ratio": 0.0},
258
+ manipulation_phase="idle", confidence=0.9,
259
+ )]
260
+ data = TLabelData(
261
+ frames=frames, sensor_info={"type": "test"},
262
+ episode_info={"source": "test"}, capabilities={"contact": True},
263
+ )
264
+ with tempfile.TemporaryDirectory() as td:
265
+ path = export_data(data, os.path.join(td, "out"), format="json")
266
+ assert os.path.exists(path)
267
+ with open(path) as f:
268
+ d = json.load(f)
269
+ assert d["frames"][0]["tlabel_v2"]["contact"] == 1.0
270
+
271
+ def test_csv_export(self):
272
+ from tlabel.core.types import TLabelData, TLabelFrame
273
+ from tlabel.export.writer import export_data
274
+ frames = [TLabelFrame(
275
+ frame_idx=0, timestamp_s=0.0,
276
+ tlabel_v2={"contact": 1.0, "force_magnitude": 0.5,
277
+ "slip_event": 0.0, "force_peak": 0.0,
278
+ "deformation_magnitude": 0.0, "force_direction": 0.0,
279
+ "slip_entropy": 0.0, "texture_energy": 0.0,
280
+ "edge_density": 0.0, "contact_area": 0.0,
281
+ "centroid_x": 0.5, "normal_field_magnitude": 0.0,
282
+ "normal_field_variance": 0.0,
283
+ "shear_field_magnitude": 0.0,
284
+ "shear_field_direction": 0.0,
285
+ "delta_force_normal": 0.0, "delta_force_shear": 0.0,
286
+ "friction_cone_ratio": 0.0},
287
+ manipulation_phase="idle", confidence=0.9,
288
+ )]
289
+ data = TLabelData(
290
+ frames=frames, sensor_info={"type": "test"},
291
+ episode_info={"source": "test"}, capabilities={"contact": True},
292
+ )
293
+ with tempfile.TemporaryDirectory() as td:
294
+ path = export_data(data, os.path.join(td, "out"), format="csv")
295
+ assert os.path.exists(path)
296
+ with open(path) as f:
297
+ lines = f.readlines()
298
+ assert len(lines) == 2 # header + 1 data row
299
+ assert "contact" in lines[0]
@@ -0,0 +1,8 @@
1
+ """TouchLabel AI - Tactile Data Annotation Toolkit"""
2
+
3
+ __version__ = "0.1.0a1"
4
+
5
+ from tlabel.core.loader import load
6
+ from tlabel.core.types import TLabelData
7
+
8
+ __all__ = ["load", "TLabelData"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0a1"
@@ -0,0 +1,5 @@
1
+ """适配器包"""
2
+
3
+ from tlabel.adapters.base import BaseAdapter
4
+
5
+ __all__ = ["BaseAdapter"]
@@ -0,0 +1,54 @@
1
+ """
2
+ 适配器抽象基类 — 所有传感器适配器的统一接口
3
+
4
+ 适配器的职责:在load阶段消化格式差异,输出统一的TLabelData。
5
+ review阶段全是TLabel格式,交互完全统一。
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Optional, Dict, Any
10
+
11
+ from tlabel.core.types import TLabelData, TLabelFrame
12
+
13
+
14
+ class BaseAdapter(ABC):
15
+ """传感器适配器基类"""
16
+
17
+ @property
18
+ @abstractmethod
19
+ def name(self) -> str:
20
+ """适配器名称"""
21
+ pass
22
+
23
+ @property
24
+ @abstractmethod
25
+ def supported_extensions(self) -> list:
26
+ """支持的文件扩展名"""
27
+ pass
28
+
29
+ @abstractmethod
30
+ def load(self, file_path: str,
31
+ trajectory_id: Optional[int] = None,
32
+ **kwargs) -> TLabelData:
33
+ """
34
+ 加载数据文件,转换为TLabelData
35
+
36
+ 参数:
37
+ file_path: 数据文件路径
38
+ trajectory_id: 轨迹ID(可选)
39
+ **kwargs: 适配器特有参数
40
+
41
+ 返回:
42
+ TLabelData — 统一标注容器
43
+ """
44
+ pass
45
+
46
+ @abstractmethod
47
+ def get_capabilities(self) -> Dict[str, bool]:
48
+ """返回该传感器的18维能力声明"""
49
+ pass
50
+
51
+ @abstractmethod
52
+ def get_sensor_info(self) -> Dict[str, Any]:
53
+ """返回传感器元信息"""
54
+ pass