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.9'
default: '0.3.10'

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

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

## 0.3.10

- Correct Python-policy missing-command-feedback health without changing physical dispatch outcomes or Native defaults.
- Include the polling, logical latency and force-field health updates from the unpublished v0.3.9 tag.
- Keep transport v0.3.4 minimum, rebuild requirements and separate hardware acceptance.

## 0.3.9

Tagged but not published; superseded for publication by 0.3.10.

- Align explicit Python-policy polling counters and logical transaction latency with the Python reference, including nearest-even percentiles and a bounded device-lifetime window.
- Add force-field health projection and shared-query interval resolution; preserve Native defaults and the transport v0.3.4 minimum.
- Rebuild SDK and consumers. No new hardware acceptance is claimed; see [release notes](RELEASE_NOTES.md).
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

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

## 0.3.10

- 修正 Python 策略缺少命令反馈时的健康统计,保持实际发送结果及 Native 默认行为。
- 包含未公开发行的 v0.3.9 标签中的轮询、逻辑延迟和力字段健康投影修正。
- 保持 transport v0.3.4 最低依赖,SDK 和消费者需要重新编译,实机验收单独进行。

## 0.3.9

已创建标签,但未公开发行;公开发行由 0.3.10 接替。

- 统一显式 Python 策略的轮询计数和逻辑事务延迟,使用与 Python 一致的百分位取整及设备生命周期内的有界窗口。
- 补齐力字段健康投影和共享查询间隔合并,保留 Native 默认行为及 transport v0.3.4 最低依赖。
- SDK 和消费者需要重新编译,不新增实机验收结论,详见[发行说明](RELEASE_NOTES.md)。
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.9
VERSION 0.3.10
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.9 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.10 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.9 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.10 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
15 changes: 15 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Release notes

## 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.
- Project Python-policy polling counters independently of native transaction totals. Inactive plans report zero polling counters; active reads, verified angle command feedback and disabled optional queries retain their distinct accounting.
- Measure complete logical transactions from their first transport admission through successful completion, including transport waiting and multi-stage execution. Partial stage success does not add a completed logical transaction or clear a prior logical error. Keep the latest 2048 successful durations across close, reopen and recovery, with Python nearest-even percentile selection. Native latency accounting remains unchanged.
- Add Python-policy force-field health rows with requested, shared-query and achieved rates, query identity and observed support. Optional `PollingChannel::force_fields` entries select named fields at millisecond intervals. Shared queries use the fastest requested interval; field rows must not be summed as physical traffic. L20Lite scalar categories remain independent. Native configurations reject the new selector.

Existing C++ method signatures and Native defaults remain available. Public structure additions require rebuilding the SDK and downstream consumers with matching headers and libraries; binary compatibility with earlier headers is not claimed. The minimum and paired transport remain v0.3.4 (`0e190032d65ed0b315337b45b3779cf4f650ed68`).

Validation covers offline lifecycle, polling, command feedback, multi-stage failure, field-query resolution, bounded latency windows, source/prebuilt/installed consumers, sanitizers, complete static analysis and the twelve-target release matrix. No new physical-device acceptance is claimed. Deployed adapters/drivers, firmware, physical timestamps, mixed CAN/FD topology and multi-device duration tests require separate hardware validation. O20, Python bindings and hand/arm integration are outside this patch. Tactile-stage angle scheduling, extended identity parity and optional automatic reconnect remain separate work.

v0.3.9 was tagged but not published. Post-merge verification found that missing command feedback could add a successful latency sample; this patch includes that correction. The v0.3.9 tag remains unchanged.

## 0.3.9

Tagged but not published; superseded for publication by 0.3.10.

- Project Python-policy polling counters independently of native transaction totals. Inactive plans report zero polling counters; active reads, verified angle command feedback and disabled optional queries retain their distinct accounting.
- Measure complete logical transactions from their first transport admission through successful completion, including transport waiting and multi-stage execution. Partial stage success does not add a completed logical transaction or clear a prior logical error. Keep the latest 2048 successful durations across close, reopen and recovery, with Python nearest-even percentile selection. Native latency accounting remains unchanged.
- Add Python-policy force-field health rows with requested, shared-query and achieved rates, query identity and observed support. Optional `PollingChannel::force_fields` entries select named fields at millisecond intervals. Shared queries use the fastest requested interval; field rows must not be summed as physical traffic. L20Lite scalar categories remain independent. Native configurations reject the new selector.
Expand Down
4 changes: 3 additions & 1 deletion docs/en/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Public Python-policy health generations, including channel ages and recent-error
This batch is offline preparation. It does not add O20, Python bindings, hand/arm integration or hardware acceptance. Tactile-stage angle scheduling, extended/raw identity fields, O30 field-by-field identity queries and optional automatic reconnect remain separate work.


## Fifth compatibility batch (0.3.9)
## Fifth compatibility batch (0.3.10)

Only `RuntimeBehavior::kPython` uses the following health rules. Native counters, latency calculations and defaults are retained.

Expand All @@ -161,3 +161,5 @@ Global transaction latency records one successful logical operation, including a
`PollingChannel::force_fields` is an optional field-level selector on `kForce`. For example, `{{"fn", 20ms}, {"fns", 50ms}}` requests 50 Hz and 20 Hz respectively; if both share one wire query, that query runs at 50 Hz. An empty selector preserves the existing channel form and resolves Python's default force fields `fn` and `fns`. Intervals are positive representable whole milliseconds. Invalid or duplicate field names and unsupported explicit fields are rejected before replacing the active plan. Native policy rejects a nonempty field selector. Public methods remain available, but consumers must rebuild for the appended public structure fields.

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.
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.9 requires transport 0.3.4 or newer in
are excluded. Versions remain independent. Hand 0.3.10 requires transport 0.3.4 or newer in
the 0.3 minor line in source, prebuilt and installed-consumer builds.


Expand Down
2 changes: 1 addition & 1 deletion docs/en/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A parent-provided `linkerbot::comm` target is reused and version-checked.
The legacy empty `LINKERBOT_COMM_SOURCE_DIR=` still selects package discovery through
`CMAKE_PREFIX_PATH` in AUTO mode. Configure logs identify the selected dependency.
Use separate build directories when switching dependencies to avoid stale CMake caches.
Hand 0.3.9 requires transport 0.3.4 or newer in the 0.3 minor line. Source,
Hand 0.3.10 requires transport 0.3.4 or newer in the 0.3 minor line. Source,
prebuilt and installed-consumer checks reject older patches and other minor lines.
For paired development, keep the source commit pinned in README and CI; record the
actual revision and configuration in test reports.
Expand Down
4 changes: 3 additions & 1 deletion docs/zh/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Python 策略公开健康状态、通道采样年龄和最近错误上下文的
本批为离线准备,不增加 O20、Python 绑定、hand/arm 接入或实机验收。触觉阶段之间的角度调度、扩展及原始身份字段、O30 逐字段身份查询和可选自动重连仍为独立工作。


## 第五批兼容修正(0.3.9)
## 第五批兼容修正(0.3.10)

以下规则仅用于显式 `RuntimeBehavior::kPython`,Native 计数、延迟算法和默认行为保持不变。

Expand All @@ -132,3 +132,5 @@ Python 策略公开健康状态、通道采样年龄和最近错误上下文的
`PollingChannel::force_fields` 是 `kForce` 上的可选字段选择器。例如 `{{"fn", 20ms}, {"fns", 50ms}}` 分别请求 50 Hz 和 20 Hz;若共用查询,实际查询为 50 Hz。空选择器保留原有通道用法,解析为 Python 默认字段 `fn`、`fns`。间隔为可表示的正整数毫秒。非法名称、重复字段和显式不支持的字段在替换计划前拒绝。Native 策略拒绝非空字段选择器。公开方法保持兼容;公开结构追加字段后,SDK 和消费者需要重新编译。

离线回归覆盖计数边界、逻辑事务成功与失败、共享 Session 的设备隔离、力字段投影,以及 6144 次窗口更新的独立 Python 百分位对照。这些验证不代表硬件时序或吞吐验收。本批不包括 O20、Python 绑定、hand/arm 接入、触觉阶段角度调度以及剩余身份字段和重连差异。

缺少已声明的角度命令反馈时,保留实际发送的 `Dispatched` 状态,同时记录一次逻辑响应超时;不增加成功事务延迟,也不推进最近成功事务时间。连续缺失反馈增加连续错误数,后续完整逻辑成功清零该计数。是否启用轮询不影响这一规则。Native 可选反馈行为保持不变。
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.9 的源码、预构建及安装消费者均要求 transport 0.3.x 且不低于 0.3.4。
两个项目版本独立。hand 0.3.10 的源码、预构建及安装消费者均要求 transport 0.3.x 且不低于 0.3.4。


配套源码固定提交和预构建下载统一使用 v0.3.4 发行版。transport 公共结构有新增字段,
Expand Down
2 changes: 1 addition & 1 deletion docs/zh/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
父项目已经提供 `linkerbot::comm` 时,复用父项目的目标,并检查版本。
显式设置旧参数 `LINKERBOT_COMM_SOURCE_DIR=` 仍可在 AUTO 下通过 `CMAKE_PREFIX_PATH`
使用安装包。配置日志会显示实际选择的依赖;切换依赖时使用独立构建目录,避免旧 CMake 缓存。
hand 0.3.9 要求 transport 0.3.x 且不低于 0.3.4。源码、预构建及安装消费者测试均拒绝更低补丁版本和其他 minor 版本。
hand 0.3.10 要求 transport 0.3.x 且不低于 0.3.4。源码、预构建及安装消费者测试均拒绝更低补丁版本和其他 minor 版本。
源码开发继续使用 README 与 CI 固定的提交,并在测试报告中记录实际版本和构建配置。

### 从源码构建
Expand Down
14 changes: 14 additions & 0 deletions src/hand/runtime/device_runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,20 @@ namespace linkerbot::hand::runtime {
::linkerbot::hand::detail::checked_value(
state->plan.optional_response_window,
"missing runtime state: state->plan.optional_response_window")) {
if (state->timing && canonical_channel(state->plan.domain) == "angles") {
// Only angle feedback is a declared Python command response.
// Other native optional observations do not gate logical success.
state->timing->failed.store(true, std::memory_order_release);
(void)record_error_locked(
std::make_exception_ptr(ResponseTimeoutError(
"command feedback window expired", state->context,
state->plan.optional_response_window.value_or(std::chrono::microseconds{0}),
state->plan.late_guard)),
state->context, ErrorCode::kTimeout);
if (state->error_capture) {
state->error_capture->store(error_revision, std::memory_order_release);
}
}
if (publication_handler && state->scoped_response_policy) {
publication_handler(state->plan.domain, Sample{});
}
Expand Down
42 changes: 42 additions & 0 deletions tests/hand/device/test_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4242,3 +4242,45 @@ TEST_CASE("Python O30 force fields project one completed shared polling query",
device.stop_polling();
device.close();
}

TEST_CASE("Python L20 non-angle writes do not require native optional observations", "[comm][python-phase5]") {
using namespace std::chrono_literals;
for (const auto semantic : {CommandSemantic::kSpeeds, CommandSemantic::kCurrentLimits}) {
CAPTURE(semantic);
auto config = config_for(Model::kL20);
config.runtime_behavior = RuntimeBehavior::kPython;
config.identity_timeout = 300ms;
config.response_timeout = 20ms;
FakeTransport *transport = nullptr;
std::shared_ptr<o30_comm::HopEndpoint> hop;
HandDevice device(config, [&] {
auto next = std::make_unique<FakeTransport>();
transport = next.get();
install_fake_device(Model::kL20, transport, hop);
return next;
});
device.open();
const auto before = device.get_health().transaction_latency.count;
transport->on_request(0x28, [](const Message &request) {
return request.dlc > 1 ? std::vector<Message>{} : classic_angle_reply(Model::kL20, request);
});
const std::vector<double> values(device.get_capabilities().joints.size(), 10);
auto command =
semantic == CommandSemantic::kSpeeds ? device.set_speeds(values) : device.set_current_limits(values);
completed(command);
// A read with the same wire key drains the optional observation before
// succeeding. Its success must not conceal a spurious recent timeout.
if (semantic == CommandSemantic::kSpeeds) {
REQUIRE(device.get_speeds(1s).meta.valid);
} else {
REQUIRE(device.get_currents(1s).meta.valid);
}
const auto health = device.get_health();
REQUIRE(health.transaction_latency.count == before + 2);
REQUIRE(health.consecutive_errors == 0);
REQUIRE_FALSE(health.recent_error);
REQUIRE(command.stage() == CommandStage::kDispatched);
REQUIRE_FALSE(command.error());
device.close();
}
}
Loading