// License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2024 Intel Corporation. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using rsutils::json; #define NAME pyrealdds #define SNAME "pyrealdds" namespace { py::list get_vector3( geometry_msgs::msg::Vector3 const & v ) { py::list obj( 3 ); obj[0] = py::float_( v.x() ); obj[1] = py::float_( v.y() ); obj[2] = py::float_( v.z() ); return std::move( obj ); } void set_vector3( geometry_msgs::msg::Vector3 & v, std::array< double, 3 > const & l ) { v.x( l[0] ); v.y( l[1] ); v.z( l[2] ); } std::string script_name() { // Returns the name of the python script that's currently running us return rsutils::os::base_name( py::module_::import( "__main__" ).attr( "__file__" ).cast< std::string >() ); } json load_rs_settings( json const & local_settings ) { // Load the realsense configuration file settings std::string const filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + "realsense-config.json"; auto config = rsutils::json_config::load_from_file( filename ); // Load "python"-specific settings json settings = rsutils::json_config::load_app_settings( config, "python", "context", "config-file" ); // Take the "dds" settings only settings = settings.nested( "dds" ); // Patch any script-specific settings // NOTE: this is also accessed by pyrealsense2, where a "context" hierarchy is still used auto script = script_name(); if( auto script_settings = config.nested( script, "context", "dds" ) ) settings.override( script_settings, "config-file/" + script + "/context" ); // We should always have DDS enabled if( settings.is_object() ) settings.erase( "enabled" ); // Patch the given local settings into the configuration settings.override( local_settings, "local settings" ); return settings; } } // namespace PYBIND11_MODULE(NAME, m) { m.doc() = R"pbdoc( RealSense DDS Server Python Bindings )pbdoc"; m.attr( "__version__" ) = "0.1"; // RS2_API_VERSION_STR; // Configure the same logger as librealsense, and default to only errors by default... rsutils::configure_elpp_logger(); // And set the DDS logger similarly eprosima::fastdds::dds::Log::ClearConsumers(); eprosima::fastdds::dds::Log::RegisterConsumer( realdds::log_consumer::create() ); eprosima::fastdds::dds::Log::SetVerbosity( eprosima::fastdds::dds::Log::Error ); m.def( "debug", &rsutils::configure_elpp_logger, py::arg( "enable" ), py::arg( "nested-string" ) = "", py::arg( "logger" ) = LIBREALSENSE_ELPP_ID ); using realdds::dds_guid; py::class_< dds_guid >( m, "guid" ) .def( py::init<>() ) .def_static( "from_string", []( std::string const & raw_guid ) { return realdds::guid_from_string( raw_guid ); } ) .def( "__bool__", []( dds_guid const & self ) { return self != realdds::unknown_guid; } ) .def( "__str__", []( dds_guid const & self ) { return rsutils::string::from( realdds::print_guid( (self) ) ).str(); } ) .def( "__repr__", []( dds_guid const & self ) { return rsutils::string::from( realdds::print_raw_guid( ( self ) ) ).str(); } ) // Following two (hash and ==) are needed if we want to be able to use guids as dictionary keys .def( "__hash__", []( dds_guid const & self ) { return std::hash< std::string >{}( rsutils::string::from( realdds::print_raw_guid( self ) ) ); } ) .def( py::self == py::self ); using realdds::dds_participant; using eprosima::fastrtps::types::ReturnCode_t; py::class_< dds_participant::listener, std::shared_ptr< dds_participant::listener > // handled with a shared_ptr > listener( m, "participant.listener" ); listener // no ctor: use participant.create_listener() .def( FN_FWD( dds_participant::listener, on_writer_added, ( dds_guid guid, char const * topic_name ), ( dds_guid guid, char const * topic_name ), callback( guid, topic_name ); ) ) .def( FN_FWD( dds_participant::listener, on_writer_removed, ( dds_guid guid, char const * topic_name ), ( dds_guid guid, char const * topic_name ), callback( guid, topic_name ); ) ) .def( FN_FWD( dds_participant::listener, on_reader_added, ( dds_guid guid, char const * topic_name ), ( dds_guid guid, char const * topic_name ), callback( guid, topic_name ); ) ) .def( FN_FWD( dds_participant::listener, on_reader_removed, ( dds_guid guid, char const * topic_name ), ( dds_guid guid, char const * topic_name ), callback( guid, topic_name ); ) ) .def( FN_FWD( dds_participant::listener, on_participant_added, ( dds_guid guid, char const * name ), ( dds_guid guid, char const * name ), callback( guid, name ); ) ) .def( FN_FWD( dds_participant::listener, on_participant_removed, ( dds_guid guid, char const * name ), ( dds_guid guid, char const * name ), callback( guid, name ); ) ) .def( FN_FWD( dds_participant::listener, on_type_discovery, ( std::string const & topic_name, std::string const & type_name ), ( char const * topic_name, eprosima::fastrtps::types::DynamicType_ptr dyn_type ), callback( topic_name, dyn_type->get_name() ); ) ); m.def( "load_rs_settings", &load_rs_settings, "local-settings"_a = json::object() ); m.def( "script_name", &script_name ); py::class_< dds_participant, std::shared_ptr< dds_participant > // handled with a shared_ptr > participant( m, "participant" ); participant.def( py::init<>() ) .def( "init", []( dds_participant & self, json const & local_settings, realdds::dds_domain_id domain_id ) { self.init( domain_id, script_name(), local_settings ); }, "local-settings"_a = json::object(), "domain-id"_a = -1 ) .def( "init", &dds_participant::init, "domain-id"_a, "participant-name"_a, "local-settings"_a = json::object() ) .def( "is_valid", &dds_participant::is_valid ) .def( "guid", &dds_participant::guid ) .def( "create_guid", &dds_participant::create_guid ) .def( "__bool__", &dds_participant::is_valid ) .def( "name", []( dds_participant const & self ) { eprosima::fastdds::dds::DomainParticipantQos qos; if( ReturnCode_t::RETCODE_OK == self.get()->get_qos( qos ) ) return std::string( qos.name() ); return std::string(); } ) .def( "name_from_guid", []( dds_guid const & guid ) { return dds_participant::name_from_guid( guid ); } ) .def( "names", []( dds_participant const & self ) { return self.get()->get_participant_names(); } ) .def( "settings", &dds_participant::settings ) .def( "__repr__", []( const dds_participant & self ) { std::ostringstream os; os << "<" SNAME ".participant"; if( ! self.is_valid() ) { os << " NULL"; } else { eprosima::fastdds::dds::DomainParticipantQos qos; if( ReturnCode_t::RETCODE_OK == self.get()->get_qos( qos ) ) os << " \"" << qos.name() << "\""; os << " " << realdds::print_guid( self.guid() ); } os << ">"; return os.str(); } ) .def( "create_listener", []( dds_participant & self ) { return self.create_listener(); } ); // The 'message' submodule will contain all the known message types in realdds. auto message = m.def_submodule( "message", "all the messages that " SNAME " can work with" ); // The 'topics' submodule will contain pointers to the topic names: auto topics = m.def_submodule( "topics", "all the topics that " SNAME " knows" ); topics.attr( "separator" ) = realdds::topics::SEPARATOR; topics.attr( "root" ) = realdds::topics::ROOT; topics.attr( "device_info" ) = realdds::topics::DEVICE_INFO_TOPIC_NAME; topics.attr( "device_notification" ) = realdds::topics::NOTIFICATION_TOPIC_NAME; topics.attr( "device_control" ) = realdds::topics::CONTROL_TOPIC_NAME; topics.attr( "device_metadata" ) = realdds::topics::METADATA_TOPIC_NAME; topics.attr( "device_dfu" ) = realdds::topics::DFU_TOPIC_NAME; using realdds::video_intrinsics; py::class_< video_intrinsics, std::shared_ptr< video_intrinsics > >( m, "video_intrinsics" ) .def( py::init<>() ) .def_readwrite( "width", &video_intrinsics::width ) .def_readwrite( "height", &video_intrinsics::height ) .def_readwrite( "principal_point_x", &video_intrinsics::principal_point_x ) .def_readwrite( "principal_point_y", &video_intrinsics::principal_point_y ) .def_readwrite( "focal_lenght_x", &video_intrinsics::focal_lenght_x ) .def_readwrite( "focal_lenght_y", &video_intrinsics::focal_lenght_y ) .def_readwrite( "distortion_model", &video_intrinsics::distortion_model ) .def_readwrite( "distortion_coeffs", &video_intrinsics::distortion_coeffs ); using realdds::motion_intrinsics; py::class_< motion_intrinsics, std::shared_ptr< motion_intrinsics > >( m, "motion_intrinsics" ) .def( py::init<>() ) .def_readwrite( "data", &motion_intrinsics::data ) .def_readwrite( "noise_variances", &motion_intrinsics::noise_variances ) .def_readwrite( "bias_variances", &motion_intrinsics::bias_variances ); using realdds::extrinsics; py::class_< extrinsics, std::shared_ptr< extrinsics > >( m, "extrinsics" ) .def( py::init<>() ) .def_readwrite( "rotation", &extrinsics::rotation ) .def_readwrite( "translation", &extrinsics::translation ); using realdds::dds_topic; py::class_< dds_topic, std::shared_ptr< dds_topic > >( m, "topic" ) .def( "get_participant", &dds_topic::get_participant ) .def( "name", []( dds_topic const & self ) { return self->get_name(); } ) .def( "__repr__", []( dds_topic const & self ) { std::ostringstream os; os << "<" SNAME ".topic \"" << self->get_name() << "\""; os << ">"; return os.str(); } ); using durability = eprosima::fastdds::dds::DurabilityQosPolicyKind; py::enum_< durability >( m, "durability" ) .value( "volatile", eprosima::fastdds::dds::DurabilityQosPolicyKind::VOLATILE_DURABILITY_QOS ) .value( "transient_local", eprosima::fastdds::dds::DurabilityQosPolicyKind::TRANSIENT_LOCAL_DURABILITY_QOS ) .value( "transient", eprosima::fastdds::dds::DurabilityQosPolicyKind::TRANSIENT_DURABILITY_QOS ); using reliability = eprosima::fastdds::dds::ReliabilityQosPolicyKind; py::enum_< reliability >( m, "reliability" ) .value( "reliable", eprosima::fastdds::dds::ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS ) .value( "best_effort", eprosima::fastdds::dds::ReliabilityQosPolicyKind::BEST_EFFORT_RELIABILITY_QOS ); using reader_qos = realdds::dds_topic_reader::qos; py::class_< reader_qos >( m, "reader_qos" ) // .def( "__repr__", []( reader_qos const & self ) { std::ostringstream os; os << "<" SNAME ".reader_qos"; os << ">"; return os.str(); } ); using realdds::dds_topic_reader; py::class_< dds_topic_reader, std::shared_ptr< dds_topic_reader > >( m, "topic_reader" ) .def( py::init< std::shared_ptr< dds_topic > const & >() ) .def( FN_FWD( dds_topic_reader, on_data_available, (dds_topic_reader &), (), callback( self ); ) ) .def( FN_FWD( dds_topic_reader, on_subscription_matched, (dds_topic_reader &, int), ( eprosima::fastdds::dds::SubscriptionMatchedStatus const & status ), callback( self, status.current_count_change ); ) ) .def( FN_FWD( dds_topic_reader, on_sample_lost, (dds_topic_reader &, int, int), (eprosima::fastdds::dds::SampleLostStatus const & status), callback( self, status.total_count, status.total_count_change ); ) ) .def( "topic", &dds_topic_reader::topic ) .def( "run", &dds_topic_reader::run ) .def( "qos", []() { return reader_qos(); } ) .def( "qos", []( reliability r, durability d ) { return reader_qos( r, d ); } ); using writer_qos = realdds::dds_topic_writer::qos; py::class_< writer_qos >( m, "writer_qos" ) // .def( "__repr__", []( writer_qos const & self ) { std::ostringstream os; os << "<" SNAME ".writer_qos"; os << ">"; return os.str(); } ); using realdds::dds_publisher; py::class_< dds_publisher, std::shared_ptr< dds_publisher > >( m, "publisher" ) .def( py::init< std::shared_ptr< dds_participant > const & >() ); using realdds::dds_subscriber; py::class_< dds_subscriber, std::shared_ptr< dds_subscriber > >( m, "subscriber" ) .def( py::init< std::shared_ptr< dds_participant > const & >() ); using realdds::dds_topic_writer; py::class_< dds_topic_writer, std::shared_ptr< dds_topic_writer > >( m, "topic_writer" ) .def( py::init< std::shared_ptr< dds_topic > const & >() ) .def( "guid", &dds_topic_writer::guid ) .def( FN_FWD( dds_topic_writer, on_publication_matched, (dds_topic_writer &, int), ( eprosima::fastdds::dds::PublicationMatchedStatus const & status ), callback( self, status.current_count_change ); ) ) .def( "topic", &dds_topic_writer::topic ) .def( "run", &dds_topic_writer::run ) .def( "has_readers", &dds_topic_writer::has_readers ) .def( "wait_for_readers", &dds_topic_writer::wait_for_readers ) .def( "wait_for_acks", &dds_topic_writer::wait_for_acks ) .def_static( "qos", []() { return writer_qos(); } ) .def_static( "qos", []( reliability r, durability d ) { return writer_qos( r, d ); } ); // The actual types are declared as functions and not classes: the py::init<> inheritance rules are pretty strict // and, coupled with shared_ptr usage, are very hard to get around. This is much simpler... using realdds::topics::device_info; using realdds::topics::flexible_msg; typedef std::shared_ptr< dds_topic > flexible_msg_create_topic( std::shared_ptr< dds_participant > const &, char const * ); py::class_< device_info >( message, "device_info" ) .def( py::init<>() ) .def_static( "create_topic", static_cast< flexible_msg_create_topic * >( &flexible_msg::create_topic ) ) .def_property( "name", &device_info::name, &device_info::set_name ) .def_property( "topic_root", &device_info::topic_root, &device_info::set_topic_root ) .def_property( "serial", &device_info::serial_number, &device_info::set_serial_number ) .def_static( "from_json", &device_info::from_json ) .def( "to_json", &device_info::to_json ) .def( "__repr__", []( device_info const & self ) { std::ostringstream os; os << "<" SNAME ".device_info"; os << " " << self.to_json(); os << ">"; return os.str(); } ); using realdds::topics::flexible_msg; py::enum_< flexible_msg::data_format >( message, "flexible_format" ) .value( "json", flexible_msg::data_format::JSON ) .value( "cbor", flexible_msg::data_format::CBOR ) .value( "custom", flexible_msg::data_format::CUSTOM ); using eprosima::fastrtps::rtps::SampleIdentity; py::class_< SampleIdentity >( message, "sample_identity" ) // .def( "__repr__", []( SampleIdentity const & self ) { std::ostringstream os; os << realdds::print_guid( self.writer_guid() ); os << '.'; os << self.sequence_number(); return os.str(); } ); using eprosima::fastdds::dds::SampleInfo; py::class_< SampleInfo >( message, "sample_info" ) // .def( py::init<>() ) .def( "identity", []( SampleInfo const & self ) { return self.sample_identity; } ) .def( "source_timestamp", []( SampleInfo const & self ) { return self.source_timestamp.to_ns(); } ) .def( "reception_timestamp", []( SampleInfo const & self ) { return self.reception_timestamp.to_ns(); } ); using realdds::dds_time; using realdds::dds_nsec; py::class_< dds_time >( m, "time" ) .def( py::init<>() ) .def( py::init< int32_t, uint32_t >() ) // sec, nsec .def( py::init< long double >() ) // inexact (1.001 -> 1.000999999) .def( py::init( []( dds_nsec ns ) { return realdds::time_from( ns ); } ) ) // exact .def_readwrite( "seconds", &dds_time::seconds ) .def_readwrite( "nanosec", &dds_time::nanosec ) .def( "to_ns", &dds_time::to_ns ) .def_static( "from_ns", []( dds_nsec ns ) { return realdds::time_from( ns ); } ) .def_static( "from_double", []( long double d ) { return realdds::dds_time( d ); } ) .def( "to_double", &realdds::time_to_double ) .def( "__repr__", &realdds::time_to_string ) .def( pybind11::self == pybind11::self ) .def( pybind11::self != pybind11::self ); // We need a timestamp function that returns timestamps in the same domain as the sample-info timestamps using realdds::timestr; m.def( "now", []() { return realdds::now(); } ); py::enum_< timestr::no_suffix_t >( m, "no_suffix_t" ); m.attr( "no_suffix" ) = timestr::no_suffix; py::enum_< timestr::rel_t >( m, "rel_t" ); m.attr( "rel" ) = timestr::rel; py::enum_< timestr::abs_t >( m, "abs_t" ); m.attr( "abs" ) = timestr::abs; m.def( "timestr", []( dds_nsec t ) { return timestr( t ).to_string(); } ); m.def( "timestr", []( dds_nsec t, timestr::no_suffix_t ) { return timestr( t, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_nsec dt, timestr::rel_t ) { return timestr( dt, timestr::rel ).to_string(); } ); m.def( "timestr", []( dds_nsec dt, timestr::rel_t, timestr::no_suffix_t ) { return timestr( dt, timestr::rel, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_nsec t1, dds_nsec t2 ) { return timestr( t1, t2 ).to_string(); } ); m.def( "timestr", []( dds_nsec t1, dds_nsec t2, timestr::no_suffix_t ) { return timestr( t1, t2, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_time t, dds_time start, timestr::no_suffix_t ) { return timestr( t, start, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_time t, dds_nsec start, timestr::no_suffix_t ) { return timestr( t, start, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_nsec t, dds_time start, timestr::no_suffix_t ) { return timestr( t, start, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_time t, timestr::no_suffix_t ) { return timestr( t, timestr::no_suffix ).to_string(); } ); m.def( "timestr", []( dds_time t, dds_time start ) { return timestr( t, start ).to_string(); } ); m.def( "timestr", []( dds_time t, dds_nsec start ) { return timestr( t, start ).to_string(); } ); m.def( "timestr", []( dds_nsec t, dds_time start ) { return timestr( t, start ).to_string(); } ); m.def( "timestr", []( dds_time t ) { return timestr( t ).to_string(); } ); py::class_< flexible_msg >( message, "flexible" ) .def( py::init<>() ) .def( py::init( []( py::dict const & dict, uint32_t version ) { return flexible_msg( py_to_json( dict ), version ); } ), "json"_a, "version"_a = 0 ) .def( py::init( []( std::string const & json_string ) { return flexible_msg( flexible_msg::data_format::JSON, json::parse( json_string ) ); } ) ) .def_static( "create_topic", static_cast< flexible_msg_create_topic * >( &flexible_msg::create_topic ) ) .def_readwrite( "data_format", &flexible_msg::_data_format ) .def_readwrite( "version", &flexible_msg::_version ) .def_readwrite( "data", &flexible_msg::_data ) .def( "invalidate", &flexible_msg::invalidate ) .def( "is_valid", &flexible_msg::is_valid ) .def( "__bool__", &flexible_msg::is_valid ) .def( "__repr__", []( flexible_msg const & self ) { std::ostringstream os; os << "<" SNAME ".flexible_msg "; switch( self._data_format ) { case flexible_msg::data_format::JSON: os << "JSON"; break; case flexible_msg::data_format::CBOR: os << "CBOR"; break; case flexible_msg::data_format::CUSTOM: os << "CUSTOM"; break; default: os << "UNKNOWN(" << (int)self._data_format << ")"; break; } os << ' '; if( ! self.is_valid() ) os << "INVALID"; else { os << self._data.size(); switch( self._data_format ) { case flexible_msg::data_format::JSON: os << ' '; os << std::string( (char const *)self._data.data(), self._data.size() ); break; default: break; } } os << ">"; return os.str(); } ) .def_static( "take_next", []( dds_topic_reader & reader, SampleInfo * sample ) { auto actual_type = reader.topic()->get()->get_type_name(); if( actual_type != flexible_msg::type().getName() ) throw std::runtime_error( "can't initialize raw::flexible from " + actual_type ); flexible_msg data; if( ! flexible_msg::take_next( reader, &data, sample ) ) assert( ! data.is_valid() ); return data; }, py::arg( "reader" ), py::arg( "sample" ) = nullptr, py::call_guard< py::gil_scoped_release >() ) .def( "json_data", &flexible_msg::json_data ) .def( "json_string", []( flexible_msg const & self ) { return std::string( (char const *)self._data.data(), self._data.size() ); } ) .def( "write_to", []( flexible_msg & self, dds_topic_writer & writer ) { std::move( self ).write_to( writer ); } ); using image_msg = realdds::topics::image_msg; py::class_< image_msg, std::shared_ptr< image_msg > >( message, "image" ) .def( py::init<>() ) .def_static( "create_topic", &image_msg::create_topic ) .def_readwrite( "data", &image_msg::raw_data ) .def_readwrite( "width", &image_msg::width ) .def_readwrite( "height", &image_msg::height ) .def_readwrite( "timestamp", &image_msg::timestamp ) .def( "__repr__", []( image_msg const & self ) { std::ostringstream os; os << "<" SNAME ".image_msg"; if( self.width > 0 && self.height > 0 ) { os << ' ' << self.width << 'x' << self.height; os << 'x' << (self.raw_data.size() / (self.width * self.height)); } os << " @ " << realdds::time_to_string( self.timestamp ); os << ">"; return os.str(); } ) .def_static( "take_next", []( dds_topic_reader & reader, SampleInfo * sample ) { auto actual_type = reader.topic()->get()->get_type_name(); if( actual_type != image_msg::type().getName() ) throw std::runtime_error( "can't initialize raw::image from " + actual_type ); image_msg data; if( ! image_msg::take_next( reader, &data, sample ) ) assert( ! data.is_valid() ); return data; }, py::arg( "reader" ), py::arg( "sample" ) = nullptr, py::call_guard< py::gil_scoped_release >() ) /*.def("write_to", &image_msg::write_to, py::call_guard< py::gil_scoped_release >())*/; using blob_msg = realdds::topics::blob_msg; py::class_< blob_msg, std::shared_ptr< blob_msg > >( message, "blob" ) .def( py::init<>() ) .def( py::init( []( py::bytes const & bytes ) { auto info = py::buffer( bytes ).request(); auto data = reinterpret_cast< uint8_t const * >( info.ptr ); size_t length = static_cast< size_t >( info.size ); return blob_msg( std::vector< uint8_t >( data, data + length ) ); } ) ) .def_static( "create_topic", static_cast< std::shared_ptr< dds_topic > ( * )( std::shared_ptr< dds_participant > const &, std::string const & ) >( &blob_msg::create_topic ) ) .def( "data", []( blob_msg const & self ) { return self.data(); } ) .def( "size", []( blob_msg const & self ) { return self.data().size(); } ) .def( "__repr__", []( blob_msg const & self ) { std::ostringstream os; os << "<" SNAME ".blob_msg"; os << ' ' << self.data().size(); os << ' ' << rsutils::number::calc_crc32( self.data().data(), self.data().size() ); os << ">"; return os.str(); } ) .def_static( "take_next", []( dds_topic_reader & reader, SampleInfo * sample ) { auto actual_type = reader.topic()->get()->get_type_name(); if( actual_type != blob_msg::type().getName() ) throw std::runtime_error( "can't initialize blob from " + actual_type ); blob_msg data; if( ! blob_msg::take_next( reader, &data, sample ) ) assert( ! data.is_valid() ); return data; }, py::arg( "reader" ), py::arg( "sample" ) = nullptr, py::call_guard< py::gil_scoped_release >() ) .def( "write_to", &blob_msg::write_to, py::call_guard< py::gil_scoped_release >() ); using imu_msg = realdds::topics::imu_msg; py::class_< imu_msg, std::shared_ptr< imu_msg > >( message, "imu" ) .def( py::init<>() ) .def_static( "create_topic", &imu_msg::create_topic ) .def_property( "gyro_data", []( imu_msg const & self ) { return get_vector3( self.gyro_data() ); }, []( imu_msg & self, std::array< double, 3 > const & xyz ) { set_vector3( self.gyro_data(), xyz ); } ) .def_property( "accel_data", []( imu_msg const & self ) { return get_vector3( self.accel_data() ); }, []( imu_msg & self, std::array< double, 3 > const & xyz ) { set_vector3( self.accel_data(), xyz ); } ) .def_property( "timestamp", []( imu_msg const & self ) { return self.timestamp(); }, []( imu_msg & self, dds_time time ) { self.timestamp( time ); } ) .def( "__repr__", []( imu_msg const & self ) { std::ostringstream os; os << "<" SNAME ".imu_msg " << self.to_string() << ">"; return os.str(); } ) .def_static( "take_next", []( dds_topic_reader & reader, SampleInfo * sample ) { auto actual_type = reader.topic()->get()->get_type_name(); if( actual_type != imu_msg::type().getName() ) throw std::runtime_error( "can't initialize raw::imu from " + actual_type ); imu_msg data; if( ! imu_msg::take_next( reader, &data, sample ) ) assert( ! data.is_valid() ); return data; }, py::arg( "reader" ), py::arg( "sample" ) = nullptr, py::call_guard< py::gil_scoped_release >() ) .def( "write_to", &imu_msg::write_to, py::call_guard< py::gil_scoped_release >() ); using realdds::dds_device_broadcaster; py::class_< dds_device_broadcaster, std::shared_ptr< dds_device_broadcaster > >( m, "device_broadcaster" ) .def( py::init<>( []( std::shared_ptr< dds_publisher > const & publisher, device_info const & device_info ) { return std::make_shared< dds_device_broadcaster >( publisher, device_info, nullptr ); } ) ); using realdds::dds_option; py::class_< dds_option, std::shared_ptr< dds_option > >( m, "option" ) .def_static( "from_json", &dds_option::from_json ) .def( "get_name", &dds_option::get_name ) .def( "get_default_value", &dds_option::get_default_value ) .def( "get_description", &dds_option::get_description ) .def( "value_type", &dds_option::value_type ) .def( "is_read_only", &dds_option::is_read_only ) .def( "is_optional", &dds_option::is_optional ) .def( "stream", &dds_option::stream ) .def( "get_value", &dds_option::get_value ) .def( "set_value", &dds_option::set_value ) .def( "get_minimum_value", &dds_option::get_minimum_value ) .def( "get_maximum_value", &dds_option::get_maximum_value ) .def( "get_stepping", &dds_option::get_stepping ) .def( "to_json", []( dds_option const & self ) { return self.to_json(); } ) .def( "__repr__", []( dds_option const & self ) { std::ostringstream os; os << '<' << self.get_name(); os << " " << self.get_value(); os << " |"; if( self.is_optional() ) os << " optional"; if( self.is_read_only() ) os << " r/o"; if( self.get_default_value().exists() ) os << " default=" << self.get_default_value(); if( ! self.get_minimum_value().is_null() ) os << " min=" << self.get_minimum_value(); if( ! self.get_maximum_value().is_null() ) os << " max=" << self.get_maximum_value(); if( ! self.get_stepping().is_null() ) os << " step=" << self.get_stepping(); os << ">"; return os.str(); } ); using realdds::dds_video_encoding; py::class_< dds_video_encoding > video_encoding( m, "video_encoding" ); video_encoding // .def( py::init<>() ) .def( py::init< std::string const & >() ) .def( "is_valid", &dds_video_encoding::is_valid ) .def( "to_rs2", &dds_video_encoding::to_rs2 ) .def( "from_rs2", &dds_video_encoding::from_rs2 ) .def( "__bool__", &dds_video_encoding::is_valid ) .def( "__repr__", []( dds_video_encoding const & self ) { return self.to_string(); } ); video_encoding.attr( "z16" ) = dds_video_encoding( "16UC1" ); video_encoding.attr( "y8" ) = dds_video_encoding( "mono8" ); video_encoding.attr( "y16" ) = dds_video_encoding( "Y16" ); // todo should be mono16 video_encoding.attr( "byr2" ) = dds_video_encoding( "BYR2" ); video_encoding.attr( "yuyv" ) = dds_video_encoding( "yuv422_yuy2" ); video_encoding.attr( "uyvy" ) = dds_video_encoding( "uyvy" ); video_encoding.attr( "rgb" ) = dds_video_encoding( "rgb8" ); using realdds::dds_stream_profile; py::class_< dds_stream_profile, std::shared_ptr< dds_stream_profile > > stream_profile_base( m, "stream_profile" ); stream_profile_base .def( "frequency", &dds_stream_profile::frequency ) .def( "stream", &dds_stream_profile::stream ) .def( "to_string", &dds_stream_profile::to_string ) .def( "details_to_string", &dds_stream_profile::details_to_string ) .def( "to_json", []( dds_stream_profile const & self ) { return self.to_json().dump(); } ) .def( "__repr__", []( dds_stream_profile const & self ) { std::ostringstream os; std::string self_as_string = self.to_string(); //