上传文件至「serial」
This commit is contained in:
+51
-20
@@ -1,8 +1,10 @@
|
||||
"""Single-threaded finger acquisition API, independent of UART and the CLI.
|
||||
|
||||
Sensor IDs are 1-based, assigned after each successful add(). Calls and runtime
|
||||
ownership belong to one thread; close the bank in a finally block. Values use the
|
||||
SDK's FNORMAL/FSHEARX/FSHEARY axes and calibration units, not assumed Newtons.
|
||||
Sensor IDs are 1-based, assigned once per successful add() and never reused
|
||||
within a process, so a host that caches IDs stays correct across removals.
|
||||
Calls and runtime ownership belong to one thread; close the bank in a finally
|
||||
block. Values use the SDK's FNORMAL/FSHEARX/FSHEARY axes and calibration
|
||||
units, not assumed Newtons.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -40,20 +42,31 @@ class ForceReading:
|
||||
image: object
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SensorInfo:
|
||||
sensor_id: int
|
||||
source: Source
|
||||
kind: str # "camera" or "file"
|
||||
config: str
|
||||
calibration: str | None
|
||||
|
||||
|
||||
class FingerSensorBank:
|
||||
"""Own up to four independently added runtimes; add() precedes read_all().
|
||||
|
||||
Processing/connection failures raise exceptions; they never become zero force.
|
||||
A shared config can be reused, or each source can have its own config.
|
||||
Processing/connection failures raise exceptions; they never become zero
|
||||
force. A shared config can be reused, or each source can have its own
|
||||
config.
|
||||
"""
|
||||
|
||||
def __init__(self, *, config="finger", verbose=False):
|
||||
self.default_config = config
|
||||
self.sources = {}
|
||||
self.configs = {}
|
||||
self.runtimes = {}
|
||||
self.calibration_paths = {}
|
||||
self.sources: dict[int, Source] = {}
|
||||
self.configs: dict[int, str] = {}
|
||||
self.runtimes: dict[int, object] = {}
|
||||
self.calibration_paths: dict[int, str | None] = {}
|
||||
self.verbose = verbose
|
||||
self._next_id = 1
|
||||
|
||||
@staticmethod
|
||||
def _source_identity(source):
|
||||
@@ -101,7 +114,8 @@ class FingerSensorBank:
|
||||
except Exception:
|
||||
logger.exception("New sensor cleanup failed")
|
||||
raise
|
||||
sensor_id = len(self.runtimes) + 1
|
||||
sensor_id = self._next_id
|
||||
self._next_id += 1
|
||||
self.sources[sensor_id] = source
|
||||
self.configs[sensor_id] = config
|
||||
self.runtimes[sensor_id] = runtime
|
||||
@@ -157,12 +171,35 @@ class FingerSensorBank:
|
||||
except BaseException:
|
||||
for sensor_id in added:
|
||||
try:
|
||||
self._close_one(sensor_id)
|
||||
self.remove_sensor(sensor_id)
|
||||
except Exception:
|
||||
logger.exception("Setup rollback failed for sensor %s", sensor_id)
|
||||
raise
|
||||
return tuple(added)
|
||||
|
||||
def remove_sensor(self, sensor_id: int) -> None:
|
||||
"""Close and forget one sensor. Its ID is never reused."""
|
||||
if sensor_id not in self.runtimes:
|
||||
raise ValueError(f"no sensor {sensor_id}")
|
||||
runtime = self.runtimes.pop(sensor_id)
|
||||
self.sources.pop(sensor_id, None)
|
||||
self.configs.pop(sensor_id, None)
|
||||
self.calibration_paths.pop(sensor_id, None)
|
||||
runtime.close()
|
||||
|
||||
def list_sensors(self) -> tuple[SensorInfo, ...]:
|
||||
"""Snapshot of currently registered sensors, in ID order."""
|
||||
return tuple(
|
||||
SensorInfo(
|
||||
sensor_id=sid,
|
||||
source=self.sources[sid],
|
||||
kind="file" if is_video_file(self.sources[sid]) else "camera",
|
||||
config=self.configs.get(sid, "-"),
|
||||
calibration=self.calibration_paths.get(sid),
|
||||
)
|
||||
for sid in sorted(self.runtimes)
|
||||
)
|
||||
|
||||
def read_all(self, *, loop_video=False) -> tuple[ForceReading, ...]:
|
||||
"""Read a complete round. EOF/failure raises; no partial round is returned.
|
||||
|
||||
@@ -171,7 +208,7 @@ class FingerSensorBank:
|
||||
if not self.runtimes:
|
||||
raise RuntimeError("no sensors added")
|
||||
readings = []
|
||||
for sensor_id in self.sources:
|
||||
for sensor_id in sorted(self.runtimes):
|
||||
try:
|
||||
reading = self._read(sensor_id, loop_video=loop_video)
|
||||
except Exception as exc:
|
||||
@@ -206,22 +243,16 @@ class FingerSensorBank:
|
||||
units = getattr(tactile, "force_units", ("unknown", "unknown", "unknown"))
|
||||
return ForceReading(sensor_id, *forces, tuple(units), image)
|
||||
|
||||
def _close_one(self, sensor_id):
|
||||
runtime = self.runtimes.pop(sensor_id)
|
||||
self.sources.pop(sensor_id)
|
||||
self.configs.pop(sensor_id)
|
||||
self.calibration_paths.pop(sensor_id)
|
||||
runtime.close()
|
||||
|
||||
def close(self) -> None:
|
||||
first_error = None
|
||||
for sensor_id in list(self.runtimes):
|
||||
try:
|
||||
self._close_one(sensor_id)
|
||||
self.remove_sensor(sensor_id)
|
||||
except Exception as exc:
|
||||
logger.exception("Sensor %s cleanup failed", sensor_id)
|
||||
first_error = first_error or exc
|
||||
self.sources.clear()
|
||||
self.configs.clear()
|
||||
self.calibration_paths.clear()
|
||||
if first_error is not None:
|
||||
raise first_error
|
||||
|
||||
Reference in New Issue
Block a user