mms-mp-test 0.1.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.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: mms-mp-test
3
+ Version: 0.1.0
4
+ Summary: Load NumPy trajectories after detecting an Opal Kelly USB candidate
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: numpy>=1.26
8
+
9
+ # mms-mp-test
10
+
11
+ Linux USB 후보 장치를 확인한 뒤 사용자 NPZ 파일의 `trajectory` 배열을 읽습니다.
12
+
13
+ ```sh
14
+ pip install mms-mp-test
15
+ ```
16
+
17
+ ```python
18
+ from mp_test import load_trajectory
19
+
20
+ trajectory = load_trajectory("franka_custom_tabletop_seed43_k1_configuration.npz")
21
+ ```
22
+
23
+ `load_trajectory(npz_path: str | os.PathLike[str]) -> numpy.ndarray`의 경로는 필수입니다. 데이터 파일은 배포물에 포함하지 않습니다. 배열 값, shape, dtype을 그대로 반환하며 특정 크기를 요구하지 않습니다. 제공된 예제 파일은 `(17, 7)`의 `float64` 배열입니다.
24
+
25
+ import 시 장치 검색이나 데이터 파일 읽기를 하지 않습니다. 호출할 때마다 Linux `/sys/bus/usb/devices`를 확인하며, 후보가 있을 때만 `numpy.load(..., allow_pickle=False)`로 파일을 읽고 닫습니다.
26
+
27
+ 후보 조건은 다음 중 하나입니다.
28
+
29
+ - VID가 `151f`
30
+ - 제조사 또는 제품명에 `Opal Kelly` 포함 (대소문자 무시)
31
+ - 제품명이 `XEM7310` 또는 `XEM7360`으로 시작 (대소문자 무시)
32
+
33
+ 이는 USB 식별이며 FPGA 동작, 펌웨어, 로드된 설계를 검증하지 않습니다.
34
+
35
+ | 예외 | 원인 |
36
+ | --- | --- |
37
+ | `RuntimeError` | Linux가 아님, USB 목록 접근 실패, 후보 없음 (메시지로 구분) |
38
+ | `FileNotFoundError` | 후보 확인 후 지정한 파일이 없음 |
39
+ | `KeyError` | `trajectory` 키 누락 |
40
+ | `ValueError` | 잘못된 NPZ 또는 pickle이 필요한 배열 |
41
+
42
+ 파일 자체의 접근 권한 오류는 `PermissionError`로 전달됩니다.
43
+
44
+ 메타데이터는 계획대로 Python `>=3.8`, NumPy `>=1.26`입니다. 단, NumPy 1.26은 Python 3.9 이상이 필요하므로 Python 3.8에서는 이 의존성 조합을 설치할 수 없습니다. 실제 사용에는 Python 3.9 이상이 필요합니다.
45
+
46
+ 기존 소스 스크립트도 사용할 수 있습니다.
47
+
48
+ ```sh
49
+ python3 detect_opalkelly.py --json
50
+ ```
51
+
52
+ 개발 및 배포 검증:
53
+
54
+ ```sh
55
+ python3 -m venv .venv
56
+ .venv/bin/python -m pip install -e . build twine
57
+ .venv/bin/python -m unittest discover -v
58
+ .venv/bin/python -m build
59
+ .venv/bin/python -m twine check dist/*
60
+ ```
61
+
62
+ 라이선스와 작성자 정보는 제공되지 않아 지정하지 않았습니다. PyPI 게시 상태와 실물 장치 검증 여부는 `VALIDATION.md`를 참고하세요.
@@ -0,0 +1,54 @@
1
+ # mms-mp-test
2
+
3
+ Linux USB 후보 장치를 확인한 뒤 사용자 NPZ 파일의 `trajectory` 배열을 읽습니다.
4
+
5
+ ```sh
6
+ pip install mms-mp-test
7
+ ```
8
+
9
+ ```python
10
+ from mp_test import load_trajectory
11
+
12
+ trajectory = load_trajectory("franka_custom_tabletop_seed43_k1_configuration.npz")
13
+ ```
14
+
15
+ `load_trajectory(npz_path: str | os.PathLike[str]) -> numpy.ndarray`의 경로는 필수입니다. 데이터 파일은 배포물에 포함하지 않습니다. 배열 값, shape, dtype을 그대로 반환하며 특정 크기를 요구하지 않습니다. 제공된 예제 파일은 `(17, 7)`의 `float64` 배열입니다.
16
+
17
+ import 시 장치 검색이나 데이터 파일 읽기를 하지 않습니다. 호출할 때마다 Linux `/sys/bus/usb/devices`를 확인하며, 후보가 있을 때만 `numpy.load(..., allow_pickle=False)`로 파일을 읽고 닫습니다.
18
+
19
+ 후보 조건은 다음 중 하나입니다.
20
+
21
+ - VID가 `151f`
22
+ - 제조사 또는 제품명에 `Opal Kelly` 포함 (대소문자 무시)
23
+ - 제품명이 `XEM7310` 또는 `XEM7360`으로 시작 (대소문자 무시)
24
+
25
+ 이는 USB 식별이며 FPGA 동작, 펌웨어, 로드된 설계를 검증하지 않습니다.
26
+
27
+ | 예외 | 원인 |
28
+ | --- | --- |
29
+ | `RuntimeError` | Linux가 아님, USB 목록 접근 실패, 후보 없음 (메시지로 구분) |
30
+ | `FileNotFoundError` | 후보 확인 후 지정한 파일이 없음 |
31
+ | `KeyError` | `trajectory` 키 누락 |
32
+ | `ValueError` | 잘못된 NPZ 또는 pickle이 필요한 배열 |
33
+
34
+ 파일 자체의 접근 권한 오류는 `PermissionError`로 전달됩니다.
35
+
36
+ 메타데이터는 계획대로 Python `>=3.8`, NumPy `>=1.26`입니다. 단, NumPy 1.26은 Python 3.9 이상이 필요하므로 Python 3.8에서는 이 의존성 조합을 설치할 수 없습니다. 실제 사용에는 Python 3.9 이상이 필요합니다.
37
+
38
+ 기존 소스 스크립트도 사용할 수 있습니다.
39
+
40
+ ```sh
41
+ python3 detect_opalkelly.py --json
42
+ ```
43
+
44
+ 개발 및 배포 검증:
45
+
46
+ ```sh
47
+ python3 -m venv .venv
48
+ .venv/bin/python -m pip install -e . build twine
49
+ .venv/bin/python -m unittest discover -v
50
+ .venv/bin/python -m build
51
+ .venv/bin/python -m twine check dist/*
52
+ ```
53
+
54
+ 라이선스와 작성자 정보는 제공되지 않아 지정하지 않았습니다. PyPI 게시 상태와 실물 장치 검증 여부는 `VALIDATION.md`를 참고하세요.
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mms-mp-test"
7
+ version = "0.1.0"
8
+ description = "Load NumPy trajectories after detecting an Opal Kelly USB candidate"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = ["numpy>=1.26"]
12
+
13
+ [tool.setuptools.packages.find]
14
+ where = ["src"]
15
+ include = ["mp_test*"]
16
+
17
+ [tool.setuptools]
18
+ include-package-data = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: mms-mp-test
3
+ Version: 0.1.0
4
+ Summary: Load NumPy trajectories after detecting an Opal Kelly USB candidate
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: numpy>=1.26
8
+
9
+ # mms-mp-test
10
+
11
+ Linux USB 후보 장치를 확인한 뒤 사용자 NPZ 파일의 `trajectory` 배열을 읽습니다.
12
+
13
+ ```sh
14
+ pip install mms-mp-test
15
+ ```
16
+
17
+ ```python
18
+ from mp_test import load_trajectory
19
+
20
+ trajectory = load_trajectory("franka_custom_tabletop_seed43_k1_configuration.npz")
21
+ ```
22
+
23
+ `load_trajectory(npz_path: str | os.PathLike[str]) -> numpy.ndarray`의 경로는 필수입니다. 데이터 파일은 배포물에 포함하지 않습니다. 배열 값, shape, dtype을 그대로 반환하며 특정 크기를 요구하지 않습니다. 제공된 예제 파일은 `(17, 7)`의 `float64` 배열입니다.
24
+
25
+ import 시 장치 검색이나 데이터 파일 읽기를 하지 않습니다. 호출할 때마다 Linux `/sys/bus/usb/devices`를 확인하며, 후보가 있을 때만 `numpy.load(..., allow_pickle=False)`로 파일을 읽고 닫습니다.
26
+
27
+ 후보 조건은 다음 중 하나입니다.
28
+
29
+ - VID가 `151f`
30
+ - 제조사 또는 제품명에 `Opal Kelly` 포함 (대소문자 무시)
31
+ - 제품명이 `XEM7310` 또는 `XEM7360`으로 시작 (대소문자 무시)
32
+
33
+ 이는 USB 식별이며 FPGA 동작, 펌웨어, 로드된 설계를 검증하지 않습니다.
34
+
35
+ | 예외 | 원인 |
36
+ | --- | --- |
37
+ | `RuntimeError` | Linux가 아님, USB 목록 접근 실패, 후보 없음 (메시지로 구분) |
38
+ | `FileNotFoundError` | 후보 확인 후 지정한 파일이 없음 |
39
+ | `KeyError` | `trajectory` 키 누락 |
40
+ | `ValueError` | 잘못된 NPZ 또는 pickle이 필요한 배열 |
41
+
42
+ 파일 자체의 접근 권한 오류는 `PermissionError`로 전달됩니다.
43
+
44
+ 메타데이터는 계획대로 Python `>=3.8`, NumPy `>=1.26`입니다. 단, NumPy 1.26은 Python 3.9 이상이 필요하므로 Python 3.8에서는 이 의존성 조합을 설치할 수 없습니다. 실제 사용에는 Python 3.9 이상이 필요합니다.
45
+
46
+ 기존 소스 스크립트도 사용할 수 있습니다.
47
+
48
+ ```sh
49
+ python3 detect_opalkelly.py --json
50
+ ```
51
+
52
+ 개발 및 배포 검증:
53
+
54
+ ```sh
55
+ python3 -m venv .venv
56
+ .venv/bin/python -m pip install -e . build twine
57
+ .venv/bin/python -m unittest discover -v
58
+ .venv/bin/python -m build
59
+ .venv/bin/python -m twine check dist/*
60
+ ```
61
+
62
+ 라이선스와 작성자 정보는 제공되지 않아 지정하지 않았습니다. PyPI 게시 상태와 실물 장치 검증 여부는 `VALIDATION.md`를 참고하세요.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/mms_mp_test.egg-info/PKG-INFO
4
+ src/mms_mp_test.egg-info/SOURCES.txt
5
+ src/mms_mp_test.egg-info/dependency_links.txt
6
+ src/mms_mp_test.egg-info/requires.txt
7
+ src/mms_mp_test.egg-info/top_level.txt
8
+ src/mp_test/__init__.py
9
+ src/mp_test/_trajectory.py
10
+ src/mp_test/_usb.py
@@ -0,0 +1 @@
1
+ numpy>=1.26
@@ -0,0 +1,5 @@
1
+ """Load a trajectory after detecting an Opal Kelly USB candidate."""
2
+
3
+ from ._trajectory import load_trajectory
4
+
5
+ __all__ = ['load_trajectory']
@@ -0,0 +1,43 @@
1
+ """USB-gated trajectory loading, with no import-time I/O."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import zipfile
8
+ import zlib
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ import numpy as np
13
+
14
+ from . import _usb
15
+
16
+
17
+ def load_trajectory(npz_path: str | os.PathLike[str]) -> np.ndarray:
18
+ """Return the NPZ's trajectory unchanged after a fresh Linux USB check.
19
+
20
+ Raises RuntimeError for unsupported platforms, unreadable USB inventory,
21
+ or absent candidates. FileNotFoundError, KeyError, and ValueError describe
22
+ a missing file, missing trajectory, and invalid archive respectively.
23
+ """
24
+ if sys.platform != 'linux':
25
+ raise RuntimeError('USB detection requires Linux sysfs.')
26
+ try:
27
+ report = _usb.collect_usb()
28
+ except OSError as exc:
29
+ raise RuntimeError(f'Cannot read USB inventory: {exc}') from exc
30
+ if not report['opal_kelly_candidates']:
31
+ raise RuntimeError('No Opal Kelly USB candidate detected.')
32
+
33
+ import numpy as np
34
+
35
+ try:
36
+ with open(npz_path, 'rb') as stream:
37
+ archive = np.load(stream, allow_pickle=False)
38
+ if not isinstance(archive, np.lib.npyio.NpzFile):
39
+ raise ValueError('Expected an NPZ archive.') # noqa: TRY004 -- file format error
40
+ with archive:
41
+ return archive['trajectory']
42
+ except (zipfile.BadZipFile, EOFError, zlib.error) as exc:
43
+ raise ValueError(f'Invalid NPZ archive: {exc}') from exc
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only USB inventory from Linux sysfs; no SDK or elevated privileges."""
3
+
4
+ import argparse
5
+ import json
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ SYSFS_ROOT = Path('/sys/bus/usb/devices')
11
+ KNOWN_MODEL = 'XEM7310-A200'
12
+ KNOWN_SERIAL = '224800128A'
13
+ LIMITATION = ('USB identification only; FPGA operation is not verified. '
14
+ 'Firmware version and loaded FPGA design are unknown.')
15
+ FIELDS = {
16
+ 'manufacturer': 'manufacturer', 'product': 'product', 'serial': 'serial',
17
+ 'vid': 'idVendor', 'pid': 'idProduct', 'bus_number': 'busnum',
18
+ 'device_number': 'devnum', 'usb_version': 'version',
19
+ 'speed_mbps': 'speed', 'device_version_raw': 'bcdDevice',
20
+ }
21
+
22
+
23
+ def read_attribute(path, warnings):
24
+ try:
25
+ return path.read_text(encoding='utf-8', errors='replace').strip() or None
26
+ except FileNotFoundError:
27
+ warnings.append(f'{path}: Attribute missing or device disconnected; unknown')
28
+ except OSError as exc:
29
+ warnings.append(f'{path}: Read failed ({exc}); Unknown')
30
+ return None
31
+
32
+
33
+ def children(path, warnings):
34
+ try:
35
+ return sorted(path.iterdir(), key=lambda p: p.name)
36
+ except OSError as exc:
37
+ warnings.append(f'{path}: Directory listing failed ({exc})')
38
+ return []
39
+
40
+
41
+ def endpoints(entries, warnings):
42
+ result = []
43
+ for entry in entries:
44
+ if not re.fullmatch(r'ep_[0-9a-fA-F]{2}', entry.name):
45
+ continue
46
+ endpoint = {'name': entry.name}
47
+ for key, attr in [('address', 'bEndpointAddress'),
48
+ ('direction', 'direction'), ('transfer_type', 'type'),
49
+ ('max_packet_size_raw', 'wMaxPacketSize')]:
50
+ endpoint[key] = read_attribute(entry / attr, warnings)
51
+ raw = endpoint['max_packet_size_raw']
52
+ try:
53
+ # Bits 10:0 are bytes per packet; upper bits encode transactions.
54
+ endpoint['max_packet_size_bytes'] = int(raw, 16) & 0x7ff if raw else None
55
+ except ValueError:
56
+ endpoint['max_packet_size_bytes'] = None
57
+ warnings.append(f'{entry}: Invalid wMaxPacketSize value {raw!r}')
58
+ result.append(endpoint)
59
+ return result
60
+
61
+
62
+ def inspect_device(path):
63
+ warnings = []
64
+ device = {'sysfs_name': path.name}
65
+ device.update({key: read_attribute(path / attr, warnings)
66
+ for key, attr in FIELDS.items()})
67
+ for key in ('vid', 'pid'):
68
+ if device[key]:
69
+ device[key] = device[key].lower()
70
+ reasons = []
71
+ if device['vid'] == '151f':
72
+ reasons.append('VID matches 151f')
73
+ for key in ('manufacturer', 'product'):
74
+ if 'opal kelly' in (device[key] or '').casefold():
75
+ reasons.append(f'{key} contains Opal Kelly')
76
+ product = (device['product'] or '').upper()
77
+ for prefix in ('XEM7310', 'XEM7360'):
78
+ if product.startswith(prefix):
79
+ reasons.append(f'Product starts with {prefix}')
80
+ device['is_opal_kelly_candidate'] = bool(reasons)
81
+ device['candidate_reasons'] = reasons
82
+ device['known_model_match'] = (None if device['product'] is None else
83
+ KNOWN_MODEL.casefold() in device['product'].casefold())
84
+ device['known_serial_match'] = (None if device['serial'] is None else
85
+ device['serial'] == KNOWN_SERIAL)
86
+ entries = children(path, warnings)
87
+ device['endpoints'] = endpoints(entries, warnings)
88
+ device['interfaces'] = []
89
+ for entry in entries:
90
+ if not re.fullmatch(r'\d+-[\d.]+:\d+\.\d+', entry.name):
91
+ continue
92
+ interface = {'name': entry.name,
93
+ 'class': read_attribute(entry / 'bInterfaceClass', warnings),
94
+ 'endpoints': endpoints(children(entry, warnings), warnings)}
95
+ device['interfaces'].append(interface)
96
+ device['warnings'] = warnings
97
+ return device
98
+
99
+
100
+ def collect_usb(root=SYSFS_ROOT):
101
+ """Raise OSError only if the inventory root cannot be enumerated."""
102
+ entries = sorted(Path(root).iterdir(), key=lambda p: p.name)
103
+ devices = [inspect_device(path) for path in entries
104
+ if re.fullmatch(r'(?:usb\d+|\d+-\d+(?:\.\d+)*)', path.name)]
105
+ return {'devices': devices,
106
+ 'opal_kelly_candidates': [d['sysfs_name'] for d in devices
107
+ if d['is_opal_kelly_candidate']],
108
+ 'known_model': KNOWN_MODEL, 'known_serial': KNOWN_SERIAL,
109
+ 'limitation': LIMITATION}
110
+
111
+
112
+ def display(value):
113
+ if value is None:
114
+ return 'Unknown'
115
+ if isinstance(value, bool):
116
+ return 'Match' if value else 'No match'
117
+ return str(value)
118
+
119
+
120
+ def print_endpoints(items, indent):
121
+ for ep in items:
122
+ print(f"{indent}Endpoint {display(ep['address'])}: "
123
+ f"direction={display(ep['direction'])}, type={display(ep['transfer_type'])}, "
124
+ f"max packet={display(ep['max_packet_size_bytes'])} bytes "
125
+ f"(raw={display(ep['max_packet_size_raw'])})")
126
+
127
+
128
+ def print_report(report):
129
+ print(f"USB devices: {len(report['devices'])} (including root hubs)")
130
+ for device in report['devices']:
131
+ print(f"\n[{device['sysfs_name']}]")
132
+ for key in FIELDS:
133
+ value = display(device[key])
134
+ if key == 'speed_mbps' and device[key] is not None:
135
+ value += 'Mbps'
136
+ print(f' {key}: {value}')
137
+ print(f" Opal Kelly candidate: {'Yes' if device['is_opal_kelly_candidate'] else 'No'}")
138
+ print(f" Detection reasons: {', '.join(device['candidate_reasons']) or 'None'}")
139
+ print(f" {KNOWN_MODEL}: {display(device['known_model_match'])}")
140
+ print(f" {KNOWN_SERIAL}: {display(device['known_serial_match'])}")
141
+ print_endpoints(device['endpoints'], ' ')
142
+ for interface in device['interfaces']:
143
+ print(f" Interface {interface['name']}: class={display(interface['class'])}")
144
+ print_endpoints(interface['endpoints'], ' ')
145
+ for warning in device['warnings']:
146
+ print(f' Warning: {warning}')
147
+ candidates = report['opal_kelly_candidates']
148
+ print(f"\nOpal Kelly candidate summary ({len(candidates)}): {', '.join(candidates) or 'None'}")
149
+ print(report['limitation'])
150
+
151
+
152
+ def main(argv=None):
153
+ parser = argparse.ArgumentParser(description=__doc__)
154
+ parser.add_argument('--json', action='store_true', help='Print output as JSON')
155
+ args = parser.parse_args(argv)
156
+ try:
157
+ report = collect_usb()
158
+ except OSError as exc:
159
+ message = f'Cannot access USB inventory path: {exc}'
160
+ if args.json:
161
+ print(json.dumps({'error': message}, ensure_ascii=False))
162
+ else:
163
+ print(message, file=sys.stderr)
164
+ return 1
165
+ if args.json:
166
+ print(json.dumps(report, ensure_ascii=False, indent=2))
167
+ else:
168
+ print_report(report)
169
+ return 0
170
+
171
+
172
+ if __name__ == '__main__':
173
+ sys.exit(main())