Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ name: Release packages
expected_version:
description: 'Hand SDK version (transport may use a compatible newer patch)'
required: true
default: '0.3.10'
default: '0.3.11'

permissions:
contents: read
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

[English](CHANGELOG.md) | [简体中文](CHANGELOG.zh-CN.md)

## 0.3.11

- Release L20 dispatch-only write reply reservations under the explicit Python policy.
- Reject Python-policy response frames received before physical send claim; cover late replies, lifecycle transitions and shared Session isolation.
- Preserve Native behavior, public APIs and transport v0.3.4 minimum. Rebuild consumers; hardware validation remains separate. See [release notes](RELEASE_NOTES.md).

## 0.3.10

- Correct Python-policy missing-command-feedback health without changing physical dispatch outcomes or Native defaults.
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

[English](CHANGELOG.md) | [简体中文](CHANGELOG.zh-CN.md)

## 0.3.11

- 显式 Python 策略下,L20 仅发送写入不再保留可选回复及读取匹配资源。
- Python 策略拒绝读取实际开始发送前收到的回复,增加迟到回复、生命周期及共享 Session 隔离回归测试。
- 保持 Native 行为、公开 API 和 transport v0.3.4 最低依赖。消费者需重新编译,实机验证仍单独进行,详见 [发行说明](RELEASE_NOTES.md)。

## 0.3.10

- 修正 Python 策略缺少命令反馈时的健康统计,保持实际发送结果及 Native 默认行为。
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)

project(linkerbot-hand-cpp
VERSION 0.3.10
VERSION 0.3.11
DESCRIPTION "LinkerBot dexterous hand C++ SDK"
LANGUAGES CXX
)
Expand Down
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ This installs the SDK and communication library into the local `install/` direct
Catch2 and vendor development packages are not required. Run the remaining commands from
this repository's root directory.

Hand SDK 0.3.10 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.11 requires transport 0.3.4 or newer in the 0.3 minor line,
for source builds, prebuilt packages and installed consumers.

To reuse a prebuilt transport v0.3.4 SDK, configure with
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ This installs the SDK and communication library into the local `install/` direct
Catch2 and vendor development packages are not required. Run the remaining commands from
this repository's root directory.

Hand SDK 0.3.10 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.11 requires transport 0.3.4 or newer in the 0.3 minor line,
for source builds, prebuilt packages and installed consumers.

To reuse a prebuilt transport v0.3.4 SDK, configure with
Expand Down
10 changes: 10 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Release notes

## 0.3.11

- Under the explicit Python runtime policy, L20 speed, current-limit and fault-clear writes end at physical dispatch without retaining Native optional reply decoders or read-key reservations. Subsequent reads do not wait for an unused reply window.
- Python-policy response matching starts at physical send claim, using Session event order when available and timestamps as a compatibility fallback. Replies received before a queued read is claimed cannot complete that read. Idle L20 write replies are not published as telemetry. Classic L20 replies have no transaction identifier: a same-opcode late write reply received after a read is claimed remains indistinguishable from a read response, as in the Python reference.
- Keep Native optional feedback, existing public C++ APIs, frame contents and inter-frame delays unchanged. No additional sender or scheduling queue is introduced.

The minimum and paired transport remain v0.3.4 (`0e190032d65ed0b315337b45b3779cf4f650ed68`). Rebuild the SDK and downstream consumers to use the fix; no public structures or signatures are added in this patch.

Validation is offline and covers missing and late replies, repeated write/read sequences, close/reopen, explicit recovery, queued-read claim boundaries and device isolation on a shared Session. No new physical-device acceptance is claimed. Deployed firmware reply timing, adapters/drivers, physical timestamps and multi-device sustained operation require separate hardware validation. O20, Python bindings and hand/arm integration are outside this patch.

## 0.3.10

- Treat missing verified angle-command feedback as a logical timeout under the Python policy. Preserve the command's physical `Dispatched` outcome, exclude it from successful transaction latency and retain its error count until a complete logical success. Native optional-feedback accounting is unchanged.
Expand Down
6 changes: 6 additions & 0 deletions docs/en/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,9 @@ Global transaction latency records one successful logical operation, including a
Offline tests cover counter boundaries, logical success/failure, shared-device isolation, force projection and an independent Python percentile comparison over 6144 rolling-window updates. These tests do not establish hardware timing or throughput. O20, Python bindings, hand/arm integration, tactile-stage angle scheduling and remaining identity/reconnect differences remain outside this batch.

Missing verified angle-command feedback preserves physical `Dispatched` status while recording one logical response timeout. It does not add successful transaction latency or advance the last successful transaction timestamp. Repeated missing feedback increments consecutive errors; a subsequent complete logical success clears that counter. This applies with or without an active polling plan. Native optional-feedback behavior is unchanged.

## Dispatch-only L20 compatibility patch (0.3.11)

The explicit Python policy removes Native optional reply decoders, response windows and correlation-key reservations from L20 speed, current-limit and fault-clear writes. Physical dispatch, command receipts, frames and inter-frame gaps are retained. The Native policy and verified angle feedback are unchanged. Python-policy response matching uses Session event order after physical claim, with a timestamp fallback when order metadata is unavailable, rather than queue admission. This prevents a late write reply received while a later read is still queued from completing that read.

Offline regressions cover no replies, idle late/malformed replies, repeated write/read pairs, close/reopen, explicit recovery and independent devices on one Session. No second sender is added. L20 has no wire transaction identifier: a matching late write reply received after read claim cannot be distinguished reliably from the read response. This is also a limitation of the pinned Python implementation; the patch does not claim to remove that protocol ambiguity or establish hardware timing.
2 changes: 1 addition & 1 deletion docs/en/guides/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ availability of proprietary ARM64 drivers.

Hand SDK packages contain both hand and paired transport static libraries, headers, CMake packages,
documentation and read-only command-line examples. Test executables and proprietary drivers/libraries
are excluded. Versions remain independent. Hand 0.3.10 requires transport 0.3.4 or newer in
are excluded. Versions remain independent. Hand 0.3.11 requires transport 0.3.4 or newer in
the 0.3 minor line in source, prebuilt and installed-consumer builds.


Expand Down
6 changes: 6 additions & 0 deletions docs/zh/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,9 @@ Python 策略公开健康状态、通道采样年龄和最近错误上下文的
离线回归覆盖计数边界、逻辑事务成功与失败、共享 Session 的设备隔离、力字段投影,以及 6144 次窗口更新的独立 Python 百分位对照。这些验证不代表硬件时序或吞吐验收。本批不包括 O20、Python 绑定、hand/arm 接入、触觉阶段角度调度以及剩余身份字段和重连差异。

缺少已声明的角度命令反馈时,保留实际发送的 `Dispatched` 状态,同时记录一次逻辑响应超时;不增加成功事务延迟,也不推进最近成功事务时间。连续缺失反馈增加连续错误数,后续完整逻辑成功清零该计数。是否启用轮询不影响这一规则。Native 可选反馈行为保持不变。

## L20 仅发送写入兼容补丁(0.3.11)

显式 Python 策略移除 L20 速度、电流限制和清故障写入继承的 Native 可选回复解码器、回复窗口和读取匹配占用,保留实际发送、命令回执、帧内容和帧间隔。Native 策略及角度反馈保持原有行为。Python 策略优先使用 Session 事件顺序判断回复是否在实际发送认领之后,缺少顺序信息时按时间戳回退,避免后续读取尚在发送队列等待时收到的旧写入回复完成该读取。

离线回归覆盖无回复、空闲期迟到及畸形回复、连续写后读、关闭重开、显式恢复和共享 Session 的设备隔离,不增加第二套发送调度。L20 协议没有事务序号:读取实际开始发送后到达的同类旧回复无法可靠区分,固定版本 Python 实现也有这一限制。本补丁不声称消除了该协议歧义,也不代表完成实机时序验证。
2 changes: 1 addition & 1 deletion docs/zh/guides/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ libstdc++ ABI1 和 C++17。Windows 使用原生架构 runner 与 v143 14.44 工

产物是开发 SDK,Hand 包包含 hand 与配套 transport 静态库、头文件、CMake 包和文档,
保留只读命令行示例;不包含测试程序、厂商驱动或厂商库。
两个项目版本独立。hand 0.3.10 的源码、预构建及安装消费者均要求 transport 0.3.x 且不低于 0.3.4。
两个项目版本独立。hand 0.3.11 的源码、预构建及安装消费者均要求 transport 0.3.x 且不低于 0.3.4。


配套源码固定提交和预构建下载统一使用 v0.3.4 发行版。transport 公共结构有新增字段,
Expand Down
15 changes: 13 additions & 2 deletions src/hand/runtime/device_runtime.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "../detail/checked_optional.hpp"
#include "../detail/failure_kind.hpp"
#include "../unified/detail/python_response_boundary.hpp"
#include "../unified/detail/runtime_access.hpp"
#include <algorithm>
#include <cmath>
Expand Down Expand Up @@ -1112,8 +1113,18 @@ namespace linkerbot::hand::runtime {
bool matched = false;
const auto requests = pending;
for (const auto &state : requests) {
if (!state->plan.consume || input.received_at >= state->deadline ||
input.received_at < state->receipt->result().accepted_at) {
if (!state->plan.consume || input.received_at >= state->deadline) {
continue;
}
if (state->scoped_response_policy) {
const auto send = state->receipt->result();
// Python accepts response frames from physical claim, not
// queue admission. A queued read must not consume an older
// dispatch-only write echo while another batch owns TX.
if (!unified_detail::python_response_after_claim(input, send)) {
continue;
}
} else if (input.received_at < state->receipt->result().accepted_at) {
continue;
}
if (state->response || state->buffered_error) {
Expand Down
14 changes: 14 additions & 0 deletions src/hand/unified/detail/python_response_boundary.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <linkerbot/transport/session.hpp>

namespace linkerbot::hand::unified_detail {
inline bool python_response_after_claim(const transport::RoutedMessage &input, const transport::SendResult &send) {
// Session event order is authoritative when both sides provide it.
// Timestamp fallback matches the Python runner's legacy transport path.
if (input.received_order != 0 && send.claimed_order) {
return input.received_order > *send.claimed_order;
}
return send.claimed_at && input.received_at >= *send.claimed_at;
}
}
12 changes: 12 additions & 0 deletions src/hand/unified/detail/python_transactions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ namespace linkerbot::hand::unified_detail {
if (!plan.consume) {
return plan;
}
// Python classic multi-axis writes only collect angle feedback. Keep
// the physical batch/receipt, but do not reserve a read correlation key
// or publish native optional observations for dispatch-only controls.
if (config.model == Model::kL20 && plan.optional_response_window && !plan.command_family.empty() &&
plan.command_family != "angle") {
plan.consume = {};
plan.keys.clear();
plan.optional_response_window.reset();
plan.response_window.reset();
plan.supersede_optional_feedback = false;
return plan;
}
using namespace std::chrono_literals;
auto window = 100ms;
auto guard = 5ms;
Expand Down
Loading