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.
- mms_mp_test-0.1.0/PKG-INFO +62 -0
- mms_mp_test-0.1.0/README.md +54 -0
- mms_mp_test-0.1.0/pyproject.toml +18 -0
- mms_mp_test-0.1.0/setup.cfg +4 -0
- mms_mp_test-0.1.0/src/mms_mp_test.egg-info/PKG-INFO +62 -0
- mms_mp_test-0.1.0/src/mms_mp_test.egg-info/SOURCES.txt +10 -0
- mms_mp_test-0.1.0/src/mms_mp_test.egg-info/dependency_links.txt +1 -0
- mms_mp_test-0.1.0/src/mms_mp_test.egg-info/requires.txt +1 -0
- mms_mp_test-0.1.0/src/mms_mp_test.egg-info/top_level.txt +1 -0
- mms_mp_test-0.1.0/src/mp_test/__init__.py +5 -0
- mms_mp_test-0.1.0/src/mp_test/_trajectory.py +43 -0
- mms_mp_test-0.1.0/src/mp_test/_usb.py +173 -0
|
@@ -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,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
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.26
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mp_test
|
|
@@ -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())
|