From a4e40718fd73edb8f7de6d77e5d0629d6c9d1840 Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:32:32 +0800 Subject: [PATCH 1/6] feat: expose raw wrench counts --- include/netft/types.hpp | 1 + src/detail/client_impl.cpp | 1 + test/test_client_stream.cpp | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/include/netft/types.hpp b/include/netft/types.hpp index c401b8e..feb6365 100644 --- a/include/netft/types.hpp +++ b/include/netft/types.hpp @@ -67,6 +67,7 @@ struct Config { struct Sample { std::uint32_t rdt_sequence{}, ft_sequence{}, status{}; + std::array raw_wrench{}; std::array force{}, torque{}; ForceUnit force_unit{ForceUnit::Unknown}; TorqueUnit torque_unit{TorqueUnit::Unknown}; diff --git a/src/detail/client_impl.cpp b/src/detail/client_impl.cpp index 8157d17..2726149 100644 --- a/src/detail/client_impl.cpp +++ b/src/detail/client_impl.cpp @@ -624,6 +624,7 @@ Client::Impl::handle_record(const detail::RawRecord &record, sample.rdt_sequence = record.rdt_sequence; sample.ft_sequence = record.ft_sequence; sample.status = record.status; + sample.raw_wrench = {record.fx, record.fy, record.fz, record.tx, record.ty, record.tz}; sample.force = {record.fx / calibration.counts_per_force_unit, record.fy / calibration.counts_per_force_unit, record.fz / calibration.counts_per_force_unit}; diff --git a/test/test_client_stream.cpp b/test/test_client_stream.cpp index c268880..e6f3058 100644 --- a/test/test_client_stream.cpp +++ b/test/test_client_stream.cpp @@ -97,6 +97,8 @@ TEST(ClientStream, ConvertsNativeUnitsAndPublishesLatestSample) { sensor.pause(); sensor.queue_record(1, 0, 100, {1'000'000, -2'000'000, 3'000'000, 1'000'000, -2'000'000, 3'000'000}); + const std::array expected_raw{1'000'000, -2'000'000, 3'000'000, + 1'000'000, -2'000'000, 3'000'000}; netft::Client client{config_for(sensor)}; std::mutex callback_mutex; std::optional callback_sample; @@ -115,6 +117,7 @@ TEST(ClientStream, ConvertsNativeUnitsAndPublishesLatestSample) { ASSERT_TRUE(sample); EXPECT_EQ(sample->rdt_sequence, 1U); EXPECT_EQ(sample->ft_sequence, 100U); + EXPECT_EQ(sample->raw_wrench, expected_raw); EXPECT_EQ(sample->force, (std::array{1.0, -2.0, 3.0})); EXPECT_EQ(sample->torque, (std::array{1.0, -2.0, 3.0})); EXPECT_EQ(sample->force_unit, netft::ForceUnit::Newton); @@ -124,6 +127,7 @@ TEST(ClientStream, ConvertsNativeUnitsAndPublishesLatestSample) { std::lock_guard lock(callback_mutex); ASSERT_TRUE(callback_sample); EXPECT_EQ(callback_sample->rdt_sequence, sample->rdt_sequence); + EXPECT_EQ(callback_sample->raw_wrench, expected_raw); EXPECT_EQ(callback_sample->force, sample->force); EXPECT_EQ(callback_sample->torque, sample->torque); } From 7b39f56b2623d44ccd4f8c7ae2044c63b9653a30 Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:36:15 +0800 Subject: [PATCH 2/6] release: prepare 0.2.0 --- CHANGELOG.md | 10 ++++++++++ CMakeLists.txt | 4 ++-- README.md | 9 +++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 356f122..20658ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project are documented in this file. +## 0.2.0 - 2026-07-24 + +### Added + +- Expose the exact signed RDT axis counts on every `Sample` through `raw_wrench`, ordered as force X/Y/Z followed by torque X/Y/Z. + +### Changed + +- Advance the shared-library ABI for the expanded public `Sample` layout. + ## 0.1.3 - 2026-07-23 ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 100b749..d66ffeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(netft VERSION 0.1.3 LANGUAGES CXX) +project(netft VERSION 0.2.0 LANGUAGES CXX) include(CTest) include(GNUInstallDirs) @@ -42,7 +42,7 @@ target_compile_definitions(netft PRIVATE NETFT_BUILDING_LIBRARY) set_target_properties(netft PROPERTIES VERSION "${PROJECT_VERSION}" - SOVERSION 0 + SOVERSION 1 CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES ) diff --git a/README.md b/README.md index 6489f53..c9e3602 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,8 @@ int main() { netft::Client client{config}; client.start([](const netft::Sample &sample) { - std::cout << sample.force[0] << ' ' << netft::to_string(sample.force_unit) << '\n'; + std::cout << sample.raw_wrench[0] << ' ' << sample.force[0] << ' ' + << netft::to_string(sample.force_unit) << '\n'; }); const bool received = client.wait_for_first_sample(std::chrono::seconds{2}); client.stop(); @@ -107,9 +108,9 @@ failure, and `130` for interruption by `SIGINT`. Without a manual override, the client requests `http://HOST:HTTP_PORT/netftapi2.xml` before each streaming session and reads the product name, counts per force unit, counts per torque unit, force unit, and torque unit. Raw RDT counts are divided by those sensor-selected -calibration scales. Every `Sample` carries the resulting `force_unit`, `torque_unit`, and -configuration revision, and the active configuration is also available through -`Client::health()`. +calibration scales. Every `Sample` carries the exact signed RDT counts in `raw_wrench`, the calibrated `force` +and `torque` values, the resulting `force_unit` and `torque_unit`, and the configuration +revision. The active configuration is also available through `Client::health()`. Sample values therefore use the units selected by the sensor; the library does not silently convert them to a preferred unit system. In particular, do not assume that an ATI sensor's From d823a6101aea1c470225c2619b4de3b2c2a78a3b Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:45:07 +0800 Subject: [PATCH 3/6] test: cover raw wrench axis ordering --- test/test_client_stream.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_client_stream.cpp b/test/test_client_stream.cpp index e6f3058..e7d7de7 100644 --- a/test/test_client_stream.cpp +++ b/test/test_client_stream.cpp @@ -96,9 +96,9 @@ TEST(ClientStream, ConvertsNativeUnitsAndPublishesLatestSample) { netft::test::FakeSensor sensor; sensor.pause(); sensor.queue_record(1, 0, 100, - {1'000'000, -2'000'000, 3'000'000, 1'000'000, -2'000'000, 3'000'000}); + {1'000'000, -2'000'000, 3'000'000, 4'000'000, -5'000'000, 6'000'000}); const std::array expected_raw{1'000'000, -2'000'000, 3'000'000, - 1'000'000, -2'000'000, 3'000'000}; + 4'000'000, -5'000'000, 6'000'000}; netft::Client client{config_for(sensor)}; std::mutex callback_mutex; std::optional callback_sample; @@ -119,7 +119,7 @@ TEST(ClientStream, ConvertsNativeUnitsAndPublishesLatestSample) { EXPECT_EQ(sample->ft_sequence, 100U); EXPECT_EQ(sample->raw_wrench, expected_raw); EXPECT_EQ(sample->force, (std::array{1.0, -2.0, 3.0})); - EXPECT_EQ(sample->torque, (std::array{1.0, -2.0, 3.0})); + EXPECT_EQ(sample->torque, (std::array{4.0, -5.0, 6.0})); EXPECT_EQ(sample->force_unit, netft::ForceUnit::Newton); EXPECT_EQ(sample->torque_unit, netft::TorqueUnit::NewtonMeter); EXPECT_EQ(sample->configuration_revision, 1U); From 95de6698eb918d56684e4277adc6ec10cc0103fe Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:48:28 +0800 Subject: [PATCH 4/6] fix: align 0.2.0 package compatibility --- .github/workflows/release.yml | 5 +++++ CMakeLists.txt | 2 +- pixi.toml | 2 +- test/consumer/CMakeLists.txt | 2 +- test/install_test.sh | 18 +++++++++++++++++- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50bfb77..ae3b331 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,11 @@ jobs: echo "tag version $version does not match CMake version $cmake_version" >&2 exit 1 fi + pixi_version="$(sed -nE 's/^version = "([0-9]+\.[0-9]+\.[0-9]+)"$/\1/p' pixi.toml)" + if [[ "$version" != "$pixi_version" ]]; then + echo "tag version $version does not match Pixi version $pixi_version" >&2 + exit 1 + fi escaped_version="${version//./\\.}" if ! grep -Eq "^## ${escaped_version} - [0-9]{4}-[0-9]{2}-[0-9]{2}$" CHANGELOG.md; then echo "CHANGELOG.md has no dated release heading for $version" >&2 diff --git a/CMakeLists.txt b/CMakeLists.txt index d66ffeb..fdc5e84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ configure_package_config_file( write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/netftConfigVersion.cmake" VERSION "${PROJECT_VERSION}" - COMPATIBILITY SameMajorVersion + COMPATIBILITY SameMinorVersion ) install( EXPORT netftTargets diff --git a/pixi.toml b/pixi.toml index f0a664f..1063601 100644 --- a/pixi.toml +++ b/pixi.toml @@ -1,6 +1,6 @@ [workspace] name = "netft-cpp" -version = "0.1.3" +version = "0.2.0" channels = ["conda-forge"] platforms = ["linux-64", "linux-aarch64"] diff --git a/test/consumer/CMakeLists.txt b/test/consumer/CMakeLists.txt index 8e83bc2..cadc57b 100644 --- a/test/consumer/CMakeLists.txt +++ b/test/consumer/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16) project(netft_consumer LANGUAGES CXX) -find_package(netft 0.1 CONFIG REQUIRED) +find_package(netft 0.2 CONFIG REQUIRED) if(DEFINED NETFT_EXPECTED_INCLUDE_DIR) get_target_property(netft_include_dirs diff --git a/test/install_test.sh b/test/install_test.sh index cbeaf2b..5972013 100755 --- a/test/install_test.sh +++ b/test/install_test.sh @@ -32,6 +32,8 @@ trap cleanup EXIT package_build="$test_root/package-build" prefix="$test_root/prefix" consumer_build="$test_root/consumer-build" +incompatible_consumer_source="$test_root/incompatible-consumer" +incompatible_consumer_build="$test_root/incompatible-consumer-build" install_include_dir="netft-headers" cmake_compiler_args=() @@ -62,6 +64,20 @@ test -f "$prefix/lib/cmake/netft/netftConfig.cmake" test -f "$prefix/lib/cmake/netft/netftConfigVersion.cmake" test -f "$prefix/lib/cmake/netft/netftTargets.cmake" +mkdir -p "$incompatible_consumer_source" +printf '%s\n' \ + 'cmake_minimum_required(VERSION 3.16)' \ + 'project(netft_incompatible_consumer LANGUAGES CXX)' \ + 'find_package(netft 0.1 CONFIG REQUIRED)' \ + >"$incompatible_consumer_source/CMakeLists.txt" +if cmake -S "$incompatible_consumer_source" \ + -B "$incompatible_consumer_build" -G Ninja \ + "${cmake_compiler_args[@]}" \ + -DCMAKE_PREFIX_PATH="$prefix"; then + echo "netft 0.2 package accepted incompatible 0.1 request" >&2 + exit 1 +fi + if find "$prefix" -name '*netft_cli_lib*' -print -quit | grep -q .; then echo "private netft_cli_lib was installed" >&2 exit 1 @@ -150,7 +166,7 @@ if [[ "$mode" == "shared" ]]; then LD_LIBRARY_PATH="$prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ ldd "$prefix/bin/netft" )" - grep -Fq "$prefix/lib/libnetft.so.0" <<<"$cli_dependencies" + grep -Fq "$prefix/lib/libnetft.so.1" <<<"$cli_dependencies" if grep -Fq 'netft_cli_lib' <<<"$cli_dependencies"; then echo "$cli_dependencies" >&2 exit 1 From 8af86fa17948287b1433c7e9d85e877f5020bd5a Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:52:40 +0800 Subject: [PATCH 5/6] test: harden package compatibility probe --- test/install_test.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/install_test.sh b/test/install_test.sh index 5972013..d2633a3 100755 --- a/test/install_test.sh +++ b/test/install_test.sh @@ -68,15 +68,15 @@ mkdir -p "$incompatible_consumer_source" printf '%s\n' \ 'cmake_minimum_required(VERSION 3.16)' \ 'project(netft_incompatible_consumer LANGUAGES CXX)' \ - 'find_package(netft 0.1 CONFIG REQUIRED)' \ + 'find_package(netft 0.1 CONFIG QUIET)' \ + 'if(netft_FOUND)' \ + ' message(FATAL_ERROR "netft 0.2 package accepted incompatible 0.1 request")' \ + 'endif()' \ >"$incompatible_consumer_source/CMakeLists.txt" -if cmake -S "$incompatible_consumer_source" \ - -B "$incompatible_consumer_build" -G Ninja \ - "${cmake_compiler_args[@]}" \ - -DCMAKE_PREFIX_PATH="$prefix"; then - echo "netft 0.2 package accepted incompatible 0.1 request" >&2 - exit 1 -fi +cmake -S "$incompatible_consumer_source" \ + -B "$incompatible_consumer_build" -G Ninja \ + "${cmake_compiler_args[@]}" \ + -DCMAKE_PREFIX_PATH="$prefix" if find "$prefix" -name '*netft_cli_lib*' -print -quit | grep -q .; then echo "private netft_cli_lib was installed" >&2 From 0c7c070b9c5c71daeaa0787836a94b251d7c5c82 Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:56:04 +0800 Subject: [PATCH 6/6] test: isolate package compatibility probe --- test/install_test.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/install_test.sh b/test/install_test.sh index d2633a3..e344fd0 100755 --- a/test/install_test.sh +++ b/test/install_test.sh @@ -68,7 +68,11 @@ mkdir -p "$incompatible_consumer_source" printf '%s\n' \ 'cmake_minimum_required(VERSION 3.16)' \ 'project(netft_incompatible_consumer LANGUAGES CXX)' \ - 'find_package(netft 0.1 CONFIG QUIET)' \ + 'if(NOT DEFINED NETFT_PROBE_PREFIX OR NETFT_PROBE_PREFIX STREQUAL "")' \ + ' message(FATAL_ERROR "NETFT_PROBE_PREFIX must be defined and nonempty")' \ + 'endif()' \ + 'find_package(netft 0.1 CONFIG QUIET' \ + ' PATHS "${NETFT_PROBE_PREFIX}" NO_DEFAULT_PATH)' \ 'if(netft_FOUND)' \ ' message(FATAL_ERROR "netft 0.2 package accepted incompatible 0.1 request")' \ 'endif()' \ @@ -76,7 +80,7 @@ printf '%s\n' \ cmake -S "$incompatible_consumer_source" \ -B "$incompatible_consumer_build" -G Ninja \ "${cmake_compiler_args[@]}" \ - -DCMAKE_PREFIX_PATH="$prefix" + -DNETFT_PROBE_PREFIX="$prefix" if find "$prefix" -name '*netft_cli_lib*' -print -quit | grep -q .; then echo "private netft_cli_lib was installed" >&2