|
| 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 | +} |
0 commit comments