Skip to content

Commit f454c8b

Browse files
author
Evgeni Raikhel
committed
Add composite-option metadata API, and HKR MinZ Control struct/casts
- A full-surface walkthrough sample exercising every composite-option entry point (enumeration, get, read-modify-write set, get_range, metadata queries) against real hardware. - rs2_minz_control: the second composite-option consumer's struct, byte-aligned to the real wire layout, plus typed cast helpers and notes on future MinZ versioning - groundwork for registering it as an actual device control in the next commit.
1 parent e5d55fb commit f454c8b

10 files changed

Lines changed: 650 additions & 6 deletions

File tree

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ add_subdirectory(eth-config)
5959
add_subdirectory(embedded-filters)
6060
add_subdirectory(object-detection)
6161
add_subdirectory(hkr-temporal-filter-dpp-mock)
62+
add_subdirectory(composite-option-walkthrough)
6263

6364
unset_security_flags_for_executable()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# License: Apache 2.0. See LICENSE file in root directory.
2+
# Copyright(c) 2026 RealSense, Inc. All Rights Reserved.
3+
cmake_minimum_required(VERSION 3.10)
4+
5+
project(RealsenseExamplesCompositeOptionWalkthrough)
6+
7+
# PROTOTYPE / DEMO reference sample: a "how do I use the composite-option API" walkthrough,
8+
# exercising the FULL surface (enumeration, set, get, get_range, metadata queries) against a fake
9+
# in-memory librealsense::composite_option_interface implementation standing in for real HKR/D555
10+
# hardware (librealsense::composite_xu_option, src/ds/composite-xu-option.h), which is not present
11+
# on this dev machine. See rs-hkr-temporal-filter-dpp-mock for the companion round-trip/atomicity
12+
# proof; this sample is meant to read as documentation for other engineers. Drives everything
13+
# through the REAL PUBLIC API (rs2::options::get_supported_composite_options()/
14+
# set_composite_option()/get_composite_option()/get_composite_option_range()/
15+
# supports_composite_option()/is_composite_option_read_only()/get_composite_option_description()),
16+
# so it links against realsense2 like any other example.
17+
add_executable(rs-composite-option-walkthrough
18+
rs-composite-option-walkthrough.cpp
19+
)
20+
set_property(TARGET rs-composite-option-walkthrough PROPERTY CXX_STANDARD 14)
21+
target_link_libraries(rs-composite-option-walkthrough PRIVATE ${LRS_TARGET})
22+
set_target_properties (rs-composite-option-walkthrough PROPERTIES FOLDER Examples)
23+
install(TARGETS rs-composite-option-walkthrough RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
// License: Apache 2.0. See LICENSE file in root directory.
2+
// Copyright(c) 2026 RealSense, Inc. All Rights Reserved.
3+
4+
// PROTOTYPE / DEMO reference sample - "how do I use the composite-option API" walkthrough, NOT
5+
// a hardware test (see rs-hkr-temporal-filter-dpp-mock for the round-trip/atomicity proof this
6+
// sample complements). No physical HKR/D555 device is required, present, or touched - this uses
7+
// the same fake-transport approach as that example (a minimal in-memory
8+
// librealsense::composite_option_interface implementation standing in for
9+
// librealsense::composite_xu_option), wrapped in the same rs2_options C-struct a real
10+
// rs2::sensor/rs2::embedded_filter wraps, so every call below goes through the REAL public API
11+
// exactly as application code would.
12+
//
13+
// Composite options are a completely separate identity/registry space from ordinary rs2_option
14+
// scalar options (see include/librealsense2/h/rs_composite_option.h) - there is no per-id
15+
// dispatch inside the SDK for what a composite option's payload *means*; the SDK only moves
16+
// opaque bytes atomically. It is the APPLICATION's job to know, for whichever
17+
// rs2_composite_option_id it cares about, what struct that id's payload casts to (documented per
18+
// id - e.g. RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP casts to rs2_temporal_filter_dpp_config /
19+
// rs2_temporal_filter_dpp_range, see rs_hkr_temporal_filter_dpp.h). This sample walks through
20+
// exactly that application-side cast, step by step:
21+
//
22+
// 1) Enumeration - rs2::options::get_supported_composite_options()
23+
// 2) Set - rs2::options::set_composite_option(id, &cfg, sizeof(cfg))
24+
// 3) Get - rs2::options::get_composite_option(id), cast bytes -> rs2_temporal_filter_dpp_config
25+
// 4) Get range - rs2::options::get_composite_option_range(id), cast bytes -> rs2_temporal_filter_dpp_range
26+
// 5) Query info - supports_composite_option()/is_composite_option_read_only()/get_composite_option_description()
27+
28+
#include <librealsense2/rs.hpp>
29+
30+
#include <src/composite-option-interface.h>
31+
#include <src/core/options-interface.h>
32+
#include <src/proc/synthetic-stream.h>
33+
34+
#include <cstdint>
35+
#include <cstring>
36+
#include <iostream>
37+
#include <stdexcept>
38+
39+
using namespace librealsense;
40+
41+
namespace {
42+
43+
// Minimal fake composite option standing in for librealsense::composite_xu_option (which would
44+
// talk to real HKR/D555 hardware over UVC XU - see src/ds/composite-xu-option.h). Implements
45+
// ONLY composite_option_interface - no relationship to librealsense::option whatsoever, matching
46+
// the real class exactly.
47+
class fake_temporal_filter_dpp_option : public composite_option_interface
48+
{
49+
public:
50+
bool is_enabled() const override { return true; }
51+
bool is_read_only() const override { return false; }
52+
const char * get_description() const override
53+
{
54+
return "HKR Temporal Filter DPP (prototype) - a 4-field composite control exchanged "
55+
"atomically; see rs_hkr_temporal_filter_dpp.h for the wire layout.";
56+
}
57+
58+
// The "wire": one call per logical get/set, whole payload atomically - exactly the contract
59+
// librealsense::composite_xu_option::get_raw()/set_raw() implement via one get_xu()/set_xu()
60+
// call each against the real device.
61+
std::vector< uint8_t > get_raw() const override { return _storage; }
62+
63+
void set_raw( const void * data, size_t size ) override
64+
{
65+
auto p = reinterpret_cast< const uint8_t * >( data );
66+
_storage.assign( p, p + size );
67+
}
68+
69+
// Fixed, documented {min,max,step,def} bounds for this prototype control (see
70+
// rs2_temporal_filter_dpp_range in rs_hkr_temporal_filter_dpp.h) - a real
71+
// composite_xu_option would instead issue one get_xu_range() call to the device.
72+
std::vector< uint8_t > get_raw_range() const override
73+
{
74+
rs2_temporal_filter_dpp_range range{};
75+
range.version = 1;
76+
range.min = { 0, 0.f, 1, 0 };
77+
range.max = { 1, 1.f, 100, 8 };
78+
range.step = { 1, 0.01f, 1, 1 };
79+
range.def = { 0, 0.4f, 20, 3 };
80+
81+
std::vector< uint8_t > bytes( sizeof( range ) );
82+
std::memcpy( bytes.data(), &range, sizeof( range ) );
83+
return bytes;
84+
}
85+
86+
private:
87+
std::vector< uint8_t > _storage = []()
88+
{
89+
// Seed with the documented defaults so get_composite_option() has something sensible to
90+
// return even before this walkthrough's own "Set" step runs.
91+
rs2_temporal_filter_dpp_config def{ 0, 0.4f, 20, 3 };
92+
std::vector< uint8_t > bytes( sizeof( def ) );
93+
std::memcpy( bytes.data(), &def, sizeof( def ) );
94+
return bytes;
95+
}();
96+
};
97+
98+
// Minimal fake options container - implements librealsense::options_interface directly (no
99+
// scalar rs2_option registered at all), exposing exactly one composite option:
100+
// RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP. Mirrors rs-hkr-temporal-filter-dpp-mock's
101+
// fake_options_container.
102+
class fake_options_container : public options_interface
103+
{
104+
public:
105+
explicit fake_options_container( std::shared_ptr< fake_temporal_filter_dpp_option > opt )
106+
: _opt( std::move( opt ) )
107+
{
108+
}
109+
110+
option & get_option( rs2_option ) override { throw std::runtime_error( "fake_options_container: no scalar options" ); }
111+
const option & get_option( rs2_option ) const override { throw std::runtime_error( "fake_options_container: no scalar options" ); }
112+
bool supports_option( rs2_option ) const override { return false; }
113+
std::vector< rs2_option > get_supported_options() const override { return {}; }
114+
std::string const & get_option_name( rs2_option ) const override { return _name; }
115+
116+
composite_option_interface & get_composite_option( rs2_composite_option_id id ) override
117+
{
118+
return const_cast< composite_option_interface & >(
119+
const_cast< const fake_options_container * >( this )->get_composite_option( id ) );
120+
}
121+
const composite_option_interface & get_composite_option( rs2_composite_option_id id ) const override
122+
{
123+
if( id != RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP )
124+
throw std::runtime_error( "fake_options_container: unsupported composite option id" );
125+
return *_opt;
126+
}
127+
bool supports_composite_option( rs2_composite_option_id id ) const override
128+
{
129+
return id == RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP;
130+
}
131+
std::vector< rs2_composite_option_id > get_supported_composite_options() const override
132+
{
133+
return { RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP };
134+
}
135+
std::string const & get_composite_option_name( rs2_composite_option_id ) const override { return _name; }
136+
137+
rsutils::subscription register_options_changed_callback( options_watcher::callback && ) override
138+
{
139+
return rsutils::subscription();
140+
}
141+
142+
void create_snapshot( std::shared_ptr< options_interface > & snapshot ) const override { snapshot.reset(); }
143+
void enable_recording( std::function< void( const options_interface & ) > ) override {}
144+
145+
private:
146+
std::shared_ptr< fake_temporal_filter_dpp_option > _opt;
147+
std::string _name = "HKR Temporal Filter DPP";
148+
};
149+
150+
// Lets this standalone sample call the protected rs2::options(rs2_options*) constructor - the
151+
// same one rs2::sensor/rs2::embedded_filter use internally.
152+
class fake_options_handle : public rs2::options
153+
{
154+
public:
155+
explicit fake_options_handle( rs2_options * o )
156+
: options( o )
157+
{
158+
}
159+
};
160+
161+
} // namespace
162+
163+
164+
int main()
165+
try
166+
{
167+
auto fake_opt = std::make_shared< fake_temporal_filter_dpp_option >();
168+
fake_options_container container( fake_opt );
169+
rs2_options wrapper( &container );
170+
fake_options_handle sensor( &wrapper );
171+
172+
std::cout << "=== Composite-option API walkthrough (HKR Temporal Filter DPP prototype) ===\n\n";
173+
174+
// -----------------------------------------------------------------------------------------
175+
// 1) Enumeration - composite options are a SEPARATE list from get_supported_options()
176+
// (scalar rs2_option ids); this sensor legitimately has zero of those in this sample.
177+
// -----------------------------------------------------------------------------------------
178+
std::cout << "[1] Enumeration: get_supported_composite_options()\n";
179+
auto supported = sensor.get_supported_composite_options();
180+
for( auto id : supported )
181+
std::cout << " - composite option id " << (int)id
182+
<< ( id == RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP ? " (RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP)" : "" )
183+
<< '\n';
184+
if( supported.empty() )
185+
throw std::runtime_error( "expected at least one supported composite option" );
186+
std::cout << '\n';
187+
188+
auto const id = RS2_COMPOSITE_OPTION_HKR_TEMPORAL_FILTER_DPP;
189+
190+
// -----------------------------------------------------------------------------------------
191+
// 2) Set - build the application's own copy of the documented wire-layout struct and send
192+
// it as one atomic transaction. The SDK never inspects these bytes - it is pure payload.
193+
// -----------------------------------------------------------------------------------------
194+
std::cout << "[2] Set: set_composite_option(id, &cfg, sizeof(cfg))\n";
195+
rs2_temporal_filter_dpp_config cfg_to_send{};
196+
cfg_to_send.enabled = 1;
197+
cfg_to_send.smooth_alpha = 0.55f;
198+
cfg_to_send.smooth_delta = 35;
199+
cfg_to_send.persistency_index = 5;
200+
sensor.set_composite_option( id, &cfg_to_send, sizeof( cfg_to_send ) );
201+
std::cout << " sent: enabled=" << cfg_to_send.enabled << " smooth_alpha=" << cfg_to_send.smooth_alpha
202+
<< " smooth_delta=" << cfg_to_send.smooth_delta
203+
<< " persistency_index=" << cfg_to_send.persistency_index << "\n\n";
204+
205+
// -----------------------------------------------------------------------------------------
206+
// 3) Get / query value - get_composite_option() returns opaque bytes; the SDK ships no
207+
// per-id dispatch, so the APPLICATION casts them to the struct it knows this id uses.
208+
// -----------------------------------------------------------------------------------------
209+
std::cout << "[3] Get: get_composite_option(id), cast to rs2_temporal_filter_dpp_config\n";
210+
std::vector< uint8_t > raw_value = sensor.get_composite_option( id );
211+
if( raw_value.size() != sizeof( rs2_temporal_filter_dpp_config ) )
212+
throw std::runtime_error( "unexpected payload size from get_composite_option" );
213+
rs2_temporal_filter_dpp_config cfg{};
214+
std::memcpy( &cfg, raw_value.data(), sizeof( cfg ) ); // <-- the application-side cast
215+
std::cout << " received: enabled=" << cfg.enabled << " smooth_alpha=" << cfg.smooth_alpha
216+
<< " smooth_delta=" << cfg.smooth_delta << " persistency_index=" << cfg.persistency_index
217+
<< '\n';
218+
if( cfg.enabled != cfg_to_send.enabled || cfg.smooth_alpha != cfg_to_send.smooth_alpha
219+
|| cfg.smooth_delta != cfg_to_send.smooth_delta || cfg.persistency_index != cfg_to_send.persistency_index )
220+
throw std::runtime_error( "round-trip mismatch between set and get" );
221+
std::cout << " (matches what was sent in step 2)\n\n";
222+
223+
// -----------------------------------------------------------------------------------------
224+
// 4) Get range - same "opaque bytes + application-side cast" story, this time to the
225+
// per-id range struct (one instance of the config struct per bound: min/max/step/def).
226+
// -----------------------------------------------------------------------------------------
227+
std::cout << "[4] Get range: get_composite_option_range(id), cast to rs2_temporal_filter_dpp_range\n";
228+
std::vector< uint8_t > raw_range = sensor.get_composite_option_range( id );
229+
if( raw_range.size() != sizeof( rs2_temporal_filter_dpp_range ) )
230+
throw std::runtime_error( "unexpected payload size from get_composite_option_range" );
231+
rs2_temporal_filter_dpp_range range{};
232+
std::memcpy( &range, raw_range.data(), sizeof( range ) ); // <-- the application-side cast
233+
std::cout << " version=" << range.version << '\n';
234+
std::cout << " enabled: min=" << range.min.enabled << " max=" << range.max.enabled
235+
<< " step=" << range.step.enabled << " def=" << range.def.enabled << '\n';
236+
std::cout << " smooth_alpha: min=" << range.min.smooth_alpha << " max=" << range.max.smooth_alpha
237+
<< " step=" << range.step.smooth_alpha << " def=" << range.def.smooth_alpha << '\n';
238+
std::cout << " smooth_delta: min=" << range.min.smooth_delta << " max=" << range.max.smooth_delta
239+
<< " step=" << range.step.smooth_delta << " def=" << range.def.smooth_delta << '\n';
240+
std::cout << " persistency_index: min=" << range.min.persistency_index << " max=" << range.max.persistency_index
241+
<< " step=" << range.step.persistency_index << " def=" << range.def.persistency_index << '\n';
242+
std::cout << '\n';
243+
244+
// -----------------------------------------------------------------------------------------
245+
// 5) Query info - metadata about the composite option itself (not its payload value).
246+
// -----------------------------------------------------------------------------------------
247+
std::cout << "[5] Query info: supports_composite_option() / is_composite_option_read_only() / get_composite_option_description()\n";
248+
std::cout << " supports_composite_option: " << ( sensor.supports_composite_option( id ) ? "true" : "false" ) << '\n';
249+
std::cout << " is_composite_option_read_only: " << ( sensor.is_composite_option_read_only( id ) ? "true" : "false" ) << '\n';
250+
std::cout << " description: \"" << sensor.get_composite_option_description( id ) << "\"\n\n";
251+
252+
std::cout << "PASS: composite-option API walkthrough completed - enumeration, set, get (with "
253+
"application-side cast), get_range (with application-side cast), and metadata "
254+
"queries all exercised through the real public API."
255+
<< std::endl;
256+
return 0;
257+
}
258+
catch( const rs2::error & e )
259+
{
260+
std::cerr << "FAIL: librealsense error: " << e.what() << std::endl;
261+
return 1;
262+
}
263+
catch( const std::exception & e )
264+
{
265+
std::cerr << "FAIL: unexpected exception: " << e.what() << std::endl;
266+
return 1;
267+
}

include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ target_sources( ${PROJECT_NAME}
2121
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/h/rs_eth_config.h"
2222
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/h/rs_composite_option.h"
2323
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/h/rs_hkr_temporal_filter_dpp.h"
24+
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/h/rs_hkr_minz_control.h"
2425

2526
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/hpp/rs_types.hpp"
2627
"${CMAKE_CURRENT_LIST_DIR}/librealsense2/hpp/rs_context.hpp"

0 commit comments

Comments
 (0)