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

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.8

- Correct bounded poll shutdown, logical health generations and final optional-query errors in the explicit Python policy.
- Add Python-only routed receive capacity configuration, discovery propagation, overflow and installed-consumer regressions. Native defaults and transport v0.3.4 minimum remain unchanged.
- Rebuild SDK and consumers. No new hardware acceptance is claimed; see [release notes](RELEASE_NOTES.md).

## 0.3.7

- Add publication-based channel sequences, identity sample metadata, filtered retained-cache waits, firmware-optional query degradation and unified angle feedback policies to the explicit Python runtime policy.
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.8

- 修正显式 Python 策略下的有界停止轮询、健康逻辑代次和可选查询降级后的最终错误记录。
- 增加 Python 策略接收路由容量配置、发现传递、溢出及安装消费者回归,保留 Native 默认行为及 transport v0.3.4 最低依赖。
- SDK 和消费者需要重新编译,不新增实机验收结论,详见[发行说明](RELEASE_NOTES.md)。

## 0.3.7

- 为显式 Python 运行策略补齐按发布推进的通道序号、身份采样元数据、过滤反馈的缓存等待、固件可选查询降级和统一角度反馈策略。
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.7
VERSION 0.3.8
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.7 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.8 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.7 requires transport 0.3.4 or newer in the 0.3 minor line,
Hand SDK 0.3.8 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
11 changes: 11 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Release notes

## 0.3.8

- Bound Python-policy `stop_polling()` by `shutdown_timeout`. Remove unclaimed poll demand, release poll observers and wait for already-claimed polling transactions to finish. Keep coalesced active readers and other devices running. A stop timeout leaves the device open and its polling intent disabled; recovery does not restore the stopped plan.
- Project Python-policy health and channel-age generations onto the facade's successful open/recovery generation, matching retained snapshots across close, reopen and recovery. Physical Session generations remain separate. Native health projection is unchanged.
- Record an optional query's final `UnsupportedFirmwareError` in health without counting its underlying failure twice. Count post-decode unsupported results once when native metadata reads succeeded. Associate the update with that native failure's revision so a later error cannot be overwritten. Successful initialization clears prior errors only when no newer native error followed its last successful transaction.
- Add optional `route_queue_capacity` to device and discovery runtime configuration for the Python policy. The unset Python value selects 256 routed receive frames per device; a positive override reaches the existing Runtime inbox and survives recovery. Native defaults remain 512, and Native configurations reject the new Python-only override. Invalid capacity is rejected before transport creation. No receive layer or physical send queue is added.

Existing C++ method signatures and Native defaults remain available. Public configuration 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 uses offline FakeTransport lifecycle, coalesced-reader, shared-Session isolation, error-ordering and inbox-overflow regressions, source/prebuilt/installed consumers, sanitizers, complete static analysis and the twelve-target release matrix. These checks do not add physical-device acceptance. 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.

## 0.3.7

- Extend the explicit `RuntimeBehavior::kPython` policy with publication-based channel sequences and coherent retained snapshots. Cache readers do not advance acquisition counters. Identity reads carry independent sample metadata; closed Python-policy caches retain identity evidence with invalid metadata.
Expand Down
10 changes: 10 additions & 0 deletions docs/en/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,13 @@ The following additions apply to the explicit Python runtime policy; native defa
- `PollingChannel::angle_feedback` selects `kQueryOnly`, `kCommandFeedbackOnly` or `kAdaptive`; the Python-policy default is adaptive. Command-only mode requires a declared verified current-state response and never repeats motion targets. Query-only mode does not use command feedback to postpone queries. Adaptive mode postpones only redundant angle queries after verified feedback. Feedback, completed-read, timeout and missed-deadline metrics remain distinct. Reapplying an equivalent plan preserves deadlines and metrics. All physical frames still pass through the existing Session.

Python bindings must map retained snapshot and filtered wait APIs explicitly. GIL, callback, destructor/interpreter-exit and allocation-failure proof, wall-clock conversion and shared hand/arm channel integration are not provided by this native patch. Hardware acceptance remains separate. O20 and automatic reconnect are not included.

## Fourth compatibility batch (0.3.8)

Only the explicit Python policy changes. `stop_polling()` removes future/unclaimed poll demand and waits up to `shutdown_timeout` for a polling transaction already claimed by the read coordinator. Stopping a poll does not cancel a coalesced active reader. A timed-out poll observer does not mean its transaction has finished. A stop timeout keeps the device open and leaves the desired plan disabled, including across recovery. Device-local draining never owns the shared Session sender. Native poll stop behavior and existing signatures remain unchanged.

Public Python-policy health generations, including channel ages and recent-error context, use the same successful facade generation as retained snapshots. Closing and recovery preparation preserve that generation until successful initialization advances it. Nested Session/transport generations retain their physical meaning. Optional-query degradation replaces only the error revision produced by that query and does not double-count failures or hide later errors. Successful initialization clears prior errors after baseline completion without erasing a newer native error.

`HandDeviceConfig::route_queue_capacity` and `HandRuntimeConfig::route_queue_capacity` are optional Python-only settings. Unset selects 256 frames per existing device route inbox; a positive override is propagated through discovery, direct creation and recovery. This differs from `read_waiter_capacity` (active caller capacity) and the Session's transmit capacity. Native construction retains its 512-frame default and rejects the new override. Conflicting capacity for an existing endpoint is rejected without rebinding it. Existing C++ signatures remain available; rebuild SDK and consumers after the appended configuration fields.

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.
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.7 requires transport 0.3.4 or newer in
are excluded. Versions remain independent. Hand 0.3.8 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.7 requires transport 0.3.4 or newer in the 0.3 minor line. Source,
Hand 0.3.8 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
10 changes: 10 additions & 0 deletions docs/zh/guides/python-behavior-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,13 @@ runtime.recovery_quiet = 200ms;
- 统一角度轮询提供 query-only、command-feedback-only 和 adaptive 三种策略,Python 策略默认 adaptive。只有已声明为实际状态的命令反馈才能满足采样需求;command-only 不补发运动目标,query-only 不因命令反馈推迟查询。相同计划不重置期限或指标,物理帧继续交给既有 Session 发送。

绑定层仍需完成 API、异常、不可变对象及墙钟转换映射,并证明内存分配失败、GIL、回调、析构和解释器退出的行为。本批不接入 hand/arm,不包含 O20、自动重连或新的实机验收。实际后端、适配器/驱动、固件、触觉布局、时间戳及多设备长时间运行需独立验收。

## 第四批兼容补全(0.3.8)

本批仅修改显式 Python 策略。`stop_polling()` 删除未来及未领取的轮询需求,并在 `shutdown_timeout` 内等待读取协调器已领取的轮询事务结束;不取消合并的主动读取。轮询观察者超时不等于底层事务结束。停止等待超时后设备仍打开,期望轮询计划保持停用,恢复时也不重新启用该计划。等待仅属于本设备,不占用共享 Session 发送器。Native 停止轮询行为和现有签名不变。

Python 策略公开健康状态、通道采样年龄和最近错误上下文的代次,与缓存使用同一成功打开/恢复代次;关闭及恢复准备期间保留上一成功代次,成功初始化后再推进。嵌套 Session/transport 代次仍表示物理连接。可选查询降级只替换该查询产生的错误记录,不重复计数,也不覆盖后续错误;成功初始化在基线完成后清理旧错误,不抹去更新的原生错误。

新增 `HandDeviceConfig::route_queue_capacity` 和 `HandRuntimeConfig::route_queue_capacity`,仅允许 Python 策略使用。未指定时,现有每设备路由接收队列容量为 256 帧;正值覆盖通过直接构造、发现和恢复传递。它不是 `read_waiter_capacity` 主动等待者容量,也不是 Session 发送队列容量。Native 构造仍使用默认 512 帧,并拒绝新的容量覆盖。同一端点已有不同容量绑定时拒绝复用,不修改其连接。新增结构体字段要求重新编译 SDK 和消费者。

本批为离线准备,不增加 O20、Python 绑定、hand/arm 接入或实机验收。触觉阶段之间的角度调度、扩展及原始身份字段、O30 逐字段身份查询和可选自动重连仍为独立工作。
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.7 的源码、预构建及安装消费者均要求 transport 0.3.x 且不低于 0.3.4。
两个项目版本独立。hand 0.3.8 的源码、预构建及安装消费者均要求 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.7 要求 transport 0.3.x 且不低于 0.3.4。源码、预构建及安装消费者测试均拒绝更低补丁版本和其他 minor 版本。
hand 0.3.8 要求 transport 0.3.x 且不低于 0.3.4。源码、预构建及安装消费者测试均拒绝更低补丁版本和其他 minor 版本。
源码开发继续使用 README 与 CI 固定的提交,并在测试报告中记录实际版本和构建配置。

### 从源码构建
Expand Down
6 changes: 6 additions & 0 deletions include/linkerbot/hand/api/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ namespace linkerbot::hand {
std::optional<std::chrono::milliseconds> response_timeout;
std::chrono::milliseconds recovery_timeout{5000};
std::chrono::milliseconds recovery_quiet{200};
// Python policy only: nullopt selects 256 routed receive frames per device.
// Native constructors retain their existing 512-frame default.
std::optional<size_t> route_queue_capacity;
};

// Fully resolved input consumed by HandDevice. Direct construction fills
Expand Down Expand Up @@ -114,5 +117,8 @@ namespace linkerbot::hand {
std::optional<std::chrono::milliseconds> response_timeout;
std::chrono::milliseconds recovery_timeout{5000};
std::chrono::milliseconds recovery_quiet{200};
// Python policy only: nullopt selects 256 routed receive frames per device.
// Native constructors retain their existing 512-frame default.
std::optional<size_t> route_queue_capacity;
};
} // namespace linkerbot::hand
3 changes: 3 additions & 0 deletions include/linkerbot/hand/api/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ namespace linkerbot::hand {

void start_polling();
void start_polling(const PollingPlan &plan);
// Python policy: remove future poll demand and boundedly drain claimed polls.
// A timeout keeps the device open and the desired polling plan disabled.
// Coalesced active readers continue; Native stop behavior is unchanged.
void stop_polling();

// Ends the active generation, recovers the backend, verifies that the
Expand Down
1 change: 1 addition & 0 deletions src/hand/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ namespace linkerbot::hand {
config.empty_polling_plan_disables = runtime.empty_polling_plan_disables;
config.runtime_behavior = runtime.runtime_behavior;
config.read_waiter_capacity = runtime.read_waiter_capacity;
config.route_queue_capacity = runtime.route_queue_capacity;
config.response_timeout = runtime.response_timeout;
config.recovery_timeout = runtime.recovery_timeout;
config.recovery_quiet = runtime.recovery_quiet;
Expand Down
7 changes: 6 additions & 1 deletion src/hand/o30/sensor.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "../unified/detail/runtime_access.hpp"
#include "linkerbot/hand/o30/sensor_manager.hpp"
#include <linkerbot/hand/errors.hpp>

Expand Down Expand Up @@ -216,7 +217,11 @@ namespace linkerbot::hand::o30 {
throw UnsupportedOperationError("unified O30 tactile fields require HOP 0.0.4");
}
SensorData result;
const auto deadline = std::chrono::steady_clock::now() + timeout_ms;
// Python response windows pass an unbounded composite budget. Saturate
// its conversion instead of adding the rounded duration to a later now.
const auto deadline = unified_detail::RuntimeAccess::uses_response_windows(*client_->runtime())
? runtime::deadline_after(timeout_ms)
: std::chrono::steady_clock::now() + timeout_ms;
const auto remaining = [&]() {
const auto value =
std::chrono::duration_cast<std::chrono::milliseconds>(deadline - std::chrono::steady_clock::now());
Expand Down
Loading