feat: 新增消费 MeasurementFrame 的力侧会话 ForceStreamSession(T06a)
新会话直接消费 ImageMeasurement:参考版本或 session 改变时,在 resetting、collecting、ready 任一阶段都作废旧零点;请求的目标版本到达时采用并开始同版本基线采样,目标前的在途帧标为 zero_pending;重复、迟到、同时间戳的帧只给诊断;max_gap_s/max_window_s 超限给出原因;每帧输出带帧头和参考版本的结果行。不提供旧的外部复位接口;旧 ForceSession 原样保留给 App。标量与无标定场景与旧会话逐位一致。回归 374 项、lint 通过。
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# 新力侧会话(T06a)
|
||||
|
||||
本文件是单路 SensorStream 计划 T06a 的 SDK 合同:新的力侧会话直接消费 T05 的
|
||||
`ImageMeasurement`(T03 `MeasurementFrame` 头 + 同帧 raw/field),参考版本取帧上的
|
||||
`reference_generation`。旧 `ForceSession` 仍为 App 保留到 T09/T10 迁移,本会话不提供
|
||||
`process_measurement`、`request_zero_external_reset`、`tracker_reset_seq` 等旧外部复位接口。
|
||||
|
||||
## 接口
|
||||
|
||||
- `ForceStreamSession(sensor_family=..., calibration=None, settings=None)`:构造;T06a 支持
|
||||
无标定与 schema2 标量曲线,`settings` 为 `OutputSettings`(None 用旧默认)。
|
||||
- `request_zero(reference_id=..., declared_unloaded=True, expected_reference_generation=版本, max_gap_s=None, max_window_s=None)`:
|
||||
为指定参考版本确认无载归零;可选窗口上限。
|
||||
- `process(measurement) -> ForceStreamOutput`:消费一个 T05 `ImageMeasurement`,返回逐帧结果
|
||||
(`frame` 携带 T03 帧头与 `reference_generation`,以及 `raw/newton/status/quality/zero`)。
|
||||
|
||||
## 规则
|
||||
|
||||
- **版本/会话变化即废旧零点(K8.3)。** `reference_generation` 或 `session_id` 一旦变化,
|
||||
resetting、collecting、ready 任一阶段的旧零点立即失败(`zero.phase == "failed"`),
|
||||
必须对新的版本显式调用 `request_zero(expected_reference_generation=新版本)` 才能恢复有效 N。
|
||||
正常顺序(会话在处理 v0 → 复位得到目标 v1 → v1 到达)中,等于**请求目标**的新版本会被采用
|
||||
并开始/继续同版本窗口,不会作废这次请求;只有未请求的版本变化或版本超过目标才失败。
|
||||
旧的 `generation` 在采样开始后不再检查的缺陷(阶段 0 P3-2)由本会话修复。
|
||||
- **基线只取请求版本的帧(K8.3/K8.5)。** 归零请求绑定一个版本。目标到达前、仍属当前版本的
|
||||
在途帧记为 `zero_pending`(不是迟到,也绝不进入窗口);比当前版本更旧的帧才是 `late`;
|
||||
超过目标的版本使请求失败,而不是静默改绑。基线固定 30 帧无载输入;
|
||||
可选 `max_gap_s`/`max_window_s` 超限时失败并把原因写进 `zero.failure_reason`。
|
||||
- **重复、迟到、同时间戳(K4.2)。** 以 `sensor_id + session_id + source_frame_id`
|
||||
(无源编号时用 `frame_id`)去重,凡是送入引擎的帧都会登记身份。重复帧只产生诊断结果;
|
||||
旧版本和同时间戳/乱序帧只标记 `late`/`zero_pending`,不推进零点或滤波,也不作废旧零点。
|
||||
不同源帧时间戳相同不算重复。
|
||||
- **逐帧结果流(K6.1)。** 每个输入帧都返回一个冻结 `ForceStreamOutput`,携带 T03 帧头与
|
||||
`reference_generation`;`quality` 为 `ok` / `uncalibrated` / `zero_pending` / `duplicate` /
|
||||
`late` / `session_changed` / `version_changed` / `window_failed` / `invalid`。
|
||||
迟到、无效和带质量标记的结果不会被丢弃;拒绝帧仍保留 raw 特征。
|
||||
- **数值等价与滤波。** T06a 的零点/标定换算/读数滤波复用已发布算法;同一输入、无复位、
|
||||
同一标定时与旧 `ForceSession` 逐帧相等(测试覆盖标量曲线、无标定、median+lowpass 滤波器)。
|
||||
固定设计帧率与显式滤波切换属于 T07,不在本会话实现。
|
||||
- **线程与只读。** 一个实例只属于一个力侧线程;输入数组只读,输出只含标量/状态/不可变进度。
|
||||
T06b 在本合同上补充 field 模型、FN-only 全局增益模型与三类标定的完整等价测试。
|
||||
|
||||
## 示例
|
||||
|
||||
```python
|
||||
from orisys.force_stream_session import ForceStreamSession
|
||||
from orisys.image_pipeline import ImageMeasurement
|
||||
from orisys.stream_contract import FrameStamp, MeasurementFrame
|
||||
|
||||
session = ForceStreamSession(sensor_family="box")
|
||||
session.request_zero(reference_id="run-1", declared_unloaded=True,
|
||||
expected_reference_generation=0)
|
||||
frame = MeasurementFrame(
|
||||
stamp=FrameStamp(sensor_id="BOX-001", session_id="run-1", frame_id=1,
|
||||
source_frame_id=1, capture_time_ns=33_333_333,
|
||||
clock_id="host", capture_time_basis="capture_monotonic"),
|
||||
source_dt_s=None, reference_generation=0,
|
||||
)
|
||||
row = session.process(ImageMeasurement(frame=frame, force_raw=(0.0, 0.0, 0.0),
|
||||
torque=0.0, flow=None))
|
||||
assert row.frame.reference_generation == 0
|
||||
assert row.zero.phase in ("resetting", "collecting")
|
||||
```
|
||||
|
||||
## 后续
|
||||
|
||||
T06b:field(`hhd_normal_l1_field/1`)与 FN-only(schema3 `outputs=["fn"]`)标定、标定不匹配、
|
||||
field 加权基线与标量力域基线的分离,以及对照旧 ForceSession 的完整五场景数值等价。
|
||||
T07 才改变输出滤波的帧率与切换语义;T09/T10 迁移 App 后删除旧 `ForceSession`。
|
||||
另一项后续任务:删除旧 `ForceSession` 之前,先把零点、标定、读数引擎从旧类中抽出来,
|
||||
避免新会话依赖待删除的类型(T06b 或 T09 之前)。
|
||||
Reference in New Issue
Block a user