From 11cd58fff7a811ceffbfb00789461a9d7a6e0a91 Mon Sep 17 00:00:00 2001 From: Eran Date: Mon, 6 May 2024 07:56:48 +0300 Subject: [PATCH] add rs-dds-config --- tools/dds/CMakeLists.txt | 1 + tools/dds/dds-adapter/rs-dds-adapter.cpp | 4 +- tools/dds/dds-config/CMakeLists.txt | 27 ++ tools/dds/dds-config/eth-config-header.h | 18 ++ tools/dds/dds-config/eth-config-v3.h | 36 +++ tools/dds/dds-config/eth-config.cpp | 141 +++++++++ tools/dds/dds-config/eth-config.h | 72 +++++ tools/dds/dds-config/rs-dds-config.cpp | 387 +++++++++++++++++++++++ 8 files changed, 684 insertions(+), 2 deletions(-) create mode 100644 tools/dds/dds-config/CMakeLists.txt create mode 100644 tools/dds/dds-config/eth-config-header.h create mode 100644 tools/dds/dds-config/eth-config-v3.h create mode 100644 tools/dds/dds-config/eth-config.cpp create mode 100644 tools/dds/dds-config/eth-config.h create mode 100644 tools/dds/dds-config/rs-dds-config.cpp diff --git a/tools/dds/CMakeLists.txt b/tools/dds/CMakeLists.txt index 49597a59c4..2724dd767a 100644 --- a/tools/dds/CMakeLists.txt +++ b/tools/dds/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory(dds-sniffer) add_subdirectory(dds-adapter) +add_subdirectory(dds-config) diff --git a/tools/dds/dds-adapter/rs-dds-adapter.cpp b/tools/dds/dds-adapter/rs-dds-adapter.cpp index ff42f3e00d..527f337ee0 100644 --- a/tools/dds/dds-adapter/rs-dds-adapter.cpp +++ b/tools/dds/dds-adapter/rs-dds-adapter.cpp @@ -1,5 +1,5 @@ // License: Apache 2.0. See LICENSE file in root directory. -// Copyright(c) 2022 Intel Corporation. All Rights Reserved. +// Copyright(c) 2022-4 Intel Corporation. All Rights Reserved. #include #include @@ -113,7 +113,7 @@ int main( int argc, char * argv[] ) try { dds_domain_id domain = -1; // from settings; default to 0 - CmdLine cmd( "librealsense rs-dds-adapter tool, use CTRL + C to stop..", ' ' ); + CmdLine cmd( "librealsense rs-dds-adapter tool, use CTRL + C to stop..", ' ', RS2_API_FULL_VERSION_STR ); ValueArg< dds_domain_id > domain_arg( "d", "domain", "Select domain ID to publish on", diff --git a/tools/dds/dds-config/CMakeLists.txt b/tools/dds/dds-config/CMakeLists.txt new file mode 100644 index 0000000000..805d2e3ef9 --- /dev/null +++ b/tools/dds/dds-config/CMakeLists.txt @@ -0,0 +1,27 @@ +# License: Apache 2.0. See LICENSE file in root directory. +# Copyright(c) 2024 Intel Corporation. All Rights Reserved. +cmake_minimum_required( VERSION 3.1.0 ) +project( rs-dds-config ) + +add_executable( ${PROJECT_NAME} ) + +file( GLOB_RECURSE RS_DDS_CONFIG_SOURCE_FILES + LIST_DIRECTORIES false + RELATIVE ${PROJECT_SOURCE_DIR} + "${CMAKE_CURRENT_LIST_DIR}/*" + ) +target_sources( ${PROJECT_NAME} PRIVATE ${RS_DDS_CONFIG_SOURCE_FILES} ) + +target_link_libraries( ${PROJECT_NAME} PRIVATE realdds realsense2 tclap ) + +set_target_properties (${PROJECT_NAME} PROPERTIES + FOLDER Tools/dds + CXX_STANDARD 14 + ) + +install( TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + +using_easyloggingpp( ${PROJECT_NAME} SHARED ) + diff --git a/tools/dds/dds-config/eth-config-header.h b/tools/dds/dds-config/eth-config-header.h new file mode 100644 index 0000000000..fbf87f9f60 --- /dev/null +++ b/tools/dds/dds-config/eth-config-header.h @@ -0,0 +1,18 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#pragma once + +#include + + +#pragma pack( push, 1 ) + +// The header structure for eth config +struct eth_config_header +{ + uint16_t version; + uint16_t size; // without header + uint32_t crc; // without header +}; + +#pragma pack( pop ) diff --git a/tools/dds/dds-config/eth-config-v3.h b/tools/dds/dds-config/eth-config-v3.h new file mode 100644 index 0000000000..27fb480076 --- /dev/null +++ b/tools/dds/dds-config/eth-config-v3.h @@ -0,0 +1,36 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#pragma once + +#include "eth-config-header.h" + + +#pragma pack( push, 1 ) + +// The structure data for eth config info, version 3 +// See table definition in HWMC v0.53 spec +struct eth_config_v3 +{ + eth_config_header header; + uint32_t link_check_timeout; // The threshold to wait eth link(ms). + uint8_t config_ip[4]; // static IP + uint8_t config_netmask[4]; // static netmask + uint8_t config_gateway[4]; // static gateway + uint8_t actual_ip[4]; // actual IP when dhcp is ON, read-only + uint8_t actual_netmask[4]; // actual netmask when dhcp is ON, read-only + uint8_t actual_gateway[4]; // actual gateway when dhcp is ON, read-only + uint32_t link_speed; // Mbps read-only + uint32_t mtu; // read-only + uint8_t dhcp_on; // 0=off + uint8_t dhcp_timeout; // The threshold to wait valid ip when DHCP is on(s). + uint8_t domain_id; // dds domain id + uint8_t link_priority; // device link priority. 0-USB_ONLY, 1-ETH_ONLY, 2-ETH_FIRST, 3 USB_FIRST + uint8_t mac_address[6]; // read-only + uint8_t reserved[2]; // needed to make size divisible by 4 +}; + +#pragma pack( pop ) + + +static_assert( sizeof( eth_config_v3 ) % 4 == 0, "eth config v3 struct size must be divisible by 4" ); + diff --git a/tools/dds/dds-config/eth-config.cpp b/tools/dds/dds-config/eth-config.cpp new file mode 100644 index 0000000000..a3096972b0 --- /dev/null +++ b/tools/dds/dds-config/eth-config.cpp @@ -0,0 +1,141 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#include "eth-config.h" +#include "eth-config-v3.h" + +#include +#include +#include + + +std::ostream & operator<<( std::ostream & os, link_priority p ) +{ + if( static_cast< uint8_t >( p ) & static_cast< uint8_t >( link_priority::_dynamic_bit ) ) + { + os << "dynamic-"; + p = static_cast< link_priority >( static_cast< uint8_t >( p ) + & ~static_cast< uint8_t >( link_priority::_dynamic_bit ) ); + } + switch( p ) + { + case link_priority::usb_only: + os << "usb-only"; + break; + case link_priority::usb_first: + os << "usb-first"; + break; + case link_priority::eth_only: + os << "eth-only"; + break; + case link_priority::eth_first: + os << "eth-first"; + break; + default: + os << "UNKNOWN-" << (int) p; + break; + } + return os; +} + + +std::ostream & operator<<( std::ostream & os, ip_3 const & ip3 ) +{ + os << ip3.ip; + if( ip3.netmask.is_valid() ) + os << " & " << ip3.netmask; + if( ip3.gateway.is_valid() ) + os << " / " << ip3.gateway; + return os; +} + + +eth_config::eth_config( eth_config_v3 const & v3 ) + : header( v3.header ) + , mac_address( rsutils::string::from( rsutils::string::hexdump( v3.mac_address, sizeof( v3.mac_address ) ) + .format( "{01}:{01}:{01}:{01}:{01}:{01}" ) ) ) + , configured{ v3.config_ip, v3.config_netmask, v3.config_gateway } + , actual{ v3.actual_ip, v3.actual_netmask, v3.actual_gateway } + , dds{ v3.domain_id } + , link{ v3.mtu, v3.link_speed, v3.link_check_timeout, link_priority( v3.link_priority ) } + , dhcp{ v3.dhcp_on != 0, v3.dhcp_timeout } +{ +} + + +eth_config::eth_config( std::vector< uint8_t > const & hwm_response ) +{ + auto header = reinterpret_cast< eth_config_header const * >( hwm_response.data() ); + if( hwm_response.size() < sizeof( *header ) ) + throw std::runtime_error( rsutils::string::from() + << "HWM response size (" << hwm_response.size() << ") does not fit header (size " + << sizeof( *header ) << ")" ); + + if( hwm_response.size() != sizeof( *header ) + header->size ) + throw std::runtime_error( rsutils::string::from() + << "HWM response size (" << hwm_response.size() << ") does not fit header (" + << sizeof( *header ) << ") + header size (" << header->size << ")" ); + + auto const crc = rsutils::number::calc_crc32( hwm_response.data() + sizeof( *header ), header->size ); + if( header->crc != crc ) + throw std::runtime_error( rsutils::string::from() + << "Eth config table CRC (" << header->crc << ") does not match response " << crc ); + + switch( header->version ) + { + case 3: { + if( header->size != sizeof( eth_config_v3 ) - sizeof( *header ) ) + throw std::runtime_error( rsutils::string::from() + << "invalid Eth config table v3 size (" << header->size << "); expecting " + << sizeof( eth_config_v3 ) << "-" << sizeof( *header ) ); + auto config = reinterpret_cast< eth_config_v3 const * >( hwm_response.data() ); + *this = *config; + break; + } + + default: + throw std::runtime_error( rsutils::string::from() + << "unrecognized Eth config table version " << header->version ); + } +} + + +bool eth_config::operator==( eth_config const & other ) const noexcept +{ + // Only compare those items that are configurable + return configured.ip == other.configured.ip && configured.netmask == other.configured.netmask + && configured.gateway == other.configured.gateway && dds.domain_id == other.dds.domain_id + && dhcp.on == other.dhcp.on && link.priority == other.link.priority && link.timeout == other.link.timeout; +} + + +bool eth_config::operator!=( eth_config const & other ) const noexcept +{ + // Only compare those items that are configurable + return configured.ip != other.configured.ip || configured.netmask != other.configured.netmask + || configured.gateway != other.configured.gateway || dds.domain_id != other.dds.domain_id + || dhcp.on != other.dhcp.on || link.priority != other.link.priority || link.timeout != other.link.timeout; +} + + +std::vector< uint8_t > eth_config::build_command() const +{ + std::vector< uint8_t > data; + data.resize( sizeof( eth_config_v3 ) ); + eth_config_v3 & cfg = *reinterpret_cast< eth_config_v3 * >( data.data() ); + configured.ip.get_components( cfg.config_ip ); + configured.netmask.get_components( cfg.config_netmask ); + configured.gateway.get_components( cfg.config_gateway ); + cfg.dhcp_on = dhcp.on; + cfg.dhcp_timeout = dhcp.timeout; + cfg.domain_id = dds.domain_id; + cfg.link_check_timeout = link.timeout; + cfg.link_priority = (uint8_t)link.priority; + + cfg.mtu = 9000; // R/O, but must be set to this value + + cfg.header.version = 3; + cfg.header.size = sizeof( cfg ) - sizeof( cfg.header ); + cfg.header.crc = rsutils::number::calc_crc32( data.data() + sizeof( cfg.header ), cfg.header.size ); + return data; +} diff --git a/tools/dds/dds-config/eth-config.h b/tools/dds/dds-config/eth-config.h new file mode 100644 index 0000000000..c411da53fb --- /dev/null +++ b/tools/dds/dds-config/eth-config.h @@ -0,0 +1,72 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#pragma once + +#include "eth-config-header.h" + +#include + + +enum class link_priority : uint8_t +{ + usb_only = 0, + eth_only = 1, + eth_first = 2, + usb_first = 3, + + _dynamic_bit = 0x10, + dynamic_eth_first = eth_first | _dynamic_bit, + dynamic_usb_first = usb_first | _dynamic_bit +}; +std::ostream & operator<<( std::ostream & os, link_priority ); + + +struct ip_3 +{ + rsutils::type::ip_address ip; + rsutils::type::ip_address netmask; + rsutils::type::ip_address gateway; + + operator bool() const { return ip.is_valid(); } + + bool operator==( ip_3 const & other ) const { return ip == other.ip && netmask == other.netmask && gateway == other.gateway; } + bool operator!=( ip_3 const & other ) const { return ip != other.ip || netmask != other.netmask || gateway != other.gateway; } +}; +std::ostream & operator<<( std::ostream &, ip_3 const & ); + + +struct eth_config_v3; + + +struct eth_config +{ + eth_config_header header; + std::string mac_address; + ip_3 configured; + ip_3 actual; + struct + { + int domain_id; // dds domain id + } dds; + struct + { + unsigned mtu; // bytes per packet + unsigned speed; // Mbps read-only; 0 if link is off + unsigned timeout; // The threshold to wait eth link(ms) + link_priority priority; + } link; + struct + { + bool on; + int timeout; // The threshold to wait valid ip when DHCP is on(s) + } dhcp; + + eth_config() {} + explicit eth_config( std::vector< uint8_t > const & hwm_response_without_code ); + eth_config( eth_config_v3 const & ); + + bool operator==( eth_config const & ) const noexcept; + bool operator!=( eth_config const & ) const noexcept; + + std::vector< uint8_t > build_command() const; +}; diff --git a/tools/dds/dds-config/rs-dds-config.cpp b/tools/dds/dds-config/rs-dds-config.cpp new file mode 100644 index 0000000000..0de4955ec7 --- /dev/null +++ b/tools/dds/dds-config/rs-dds-config.cpp @@ -0,0 +1,387 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#include + +#include "eth-config.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace TCLAP; +using rsutils::json; +using rsutils::type::ip_address; + + +static json load_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 ) + RS2_CONFIG_FILENAME; + auto config = rsutils::json_config::load_from_file( filename ); + + // Take just the 'context' part + config = rsutils::json_config::load_settings( config, "context", "config-file" ); + + // Patch the given local settings into the configuration + config.override( local_settings, "local settings" ); + + return config; +} + + +uint32_t const GET_ETH_CONFIG = 0xBB; +uint32_t const SET_ETH_CONFIG = 0xBA; +char const * const HWM_FMT = "{4} {04}({4i},{4i},{4i},{4i}) {repeat:}{1}{:}"; + + +#define LOG_DEBUG( ... ) \ + do \ + { \ + std::ostringstream os__; \ + os__ << __VA_ARGS__; \ + rs2_log( RS2_LOG_SEVERITY_DEBUG, os__.str().c_str(), nullptr ); \ + } \ + while( false ) +bool g_quiet = false; +#define INFO( ... ) \ + do \ + { \ + if( ! g_quiet ) \ + std::cout << "-I- " << __VA_ARGS__ << std::endl; \ + } \ + while( false ) + + +struct indent +{ + int _spaces; + indent( int spaces ) : _spaces( spaces ) {} +}; +std::ostream & operator<<( std::ostream & os, indent const & ind ) +{ + for( int i = ind._spaces; i; --i ) + os << ' '; + return os; +} + + +template< class T > +struct _output_field +{ + char const * const _name; + T const & _current; + T const & _requested; +}; +template< class T > +std::ostream & operator<<( std::ostream & os, _output_field< T > const & f ) +{ + os << f._name << ": " << f._current; + if( f._requested != f._current ) + os << " --> " << f._requested; + return os; +} +template< class T > +_output_field< T > setting( const char * name, T const & current ) +{ + return { name, current, current }; +} +template< class T > +_output_field< T > setting( const char * name, T const & current, T const & requested ) +{ + return { name, current, requested }; +} + + +eth_config get_eth_config( rs2::debug_protocol hwm, bool golden ) +{ + if( ! hwm ) + throw std::runtime_error( "no debug_protocol available" ); + auto cmd = hwm.build_command( GET_ETH_CONFIG, golden ? 0 : 1 ); // 0=golden; 1=actual + LOG_DEBUG( "cmd: " << rsutils::string::hexdump( cmd.data(), cmd.size() ).format( HWM_FMT ) ); + auto data = hwm.send_and_receive_raw_data( cmd ); + int32_t const & code = *reinterpret_cast(data.data()); + if( data.size() < sizeof( code ) ) + throw std::runtime_error( rsutils::string::from() + << "bad response " << rsutils::string::hexdump( data.data(), data.size() ) ); + if( code != GET_ETH_CONFIG ) + throw std::runtime_error( rsutils::string::from() << "bad response " << code ); + LOG_DEBUG( "response: " << rsutils::string::hexdump( data.data(), data.size() ).format( "{4} {repeat:}{1}{:}" ) ); + data.erase( data.begin(), data.begin() + sizeof( code ) ); + + return eth_config( data ); +} + + +bool find_device( rs2::context const & ctx, + rs2::device & device, + eth_config & config, + ValueArg< std::string > & sn_arg, + bool const golden, + std::set< std::string > & devices_looked_at ) +{ + auto device_list = ctx.query_devices(); + auto const n_devices = device_list.size(); + for( uint32_t i = 0; i < n_devices; ++i ) + { + auto possible_device = device_list[i]; + try + { + std::string sn = possible_device.get_info( RS2_CAMERA_INFO_SERIAL_NUMBER ); + if( sn_arg.isSet() && sn != sn_arg.getValue() ) + continue; + if( ! devices_looked_at.insert( sn ).second ) + continue; // insert failed: device was already looked at + LOG_DEBUG( "trying " << possible_device.get_description() ); + config = get_eth_config( possible_device, golden ); + if( device ) + throw std::runtime_error( "More than one device is available; please use --serial-number" ); + device = possible_device; + } + catch( std::exception const & e ) + { + LOG_DEBUG( "failed! " << e.what() ); + } + } + return device; +} + + +int main( int argc, char * argv[] ) +try +{ + CmdLine cmd( "librealsense rs-dds-config tool", ' ', RS2_API_FULL_VERSION_STR ); + SwitchArg debug_arg( "", "debug", "Enable debug (-D-) output; by default only errors (-E-) are shown" ); + SwitchArg quiet_arg( "", "quiet", "Suppress regular informational (-I-) messages" ); + SwitchArg no_reset_arg( "", "no-reset", "Do not hardware reset after changes are made" ); + SwitchArg golden_arg( "", "golden", "Show R/O golden values vs. current; mutually exclusive with any changes" ); + SwitchArg factory_reset_arg( "", "factory-reset", "Reset settings back to the --golden values" ); + ValueArg< std::string > sn_arg( "", "serial-number", + "Device serial-number to use, if more than one device is available", + false, "", "S/N" ); + ValueArg< std::string > ip_arg( "", "ip", + "Device static IP address to use when DHCP is off", + false, "", "1.2.3.4" ); + ValueArg< std::string > mask_arg( "", "mask", + "Device static IP network mask to use when DHCP is off", + false, "", "1.2.3.4" ); + ValueArg< std::string > gateway_arg( "", "gateway", + "Device static IP network mask to use when DHCP is off", + false, "", "1.2.3.4" ); + ValueArg< std::string > dhcp_arg( "", "dhcp", + "DHCP dynamic IP discovery 'on' or 'off'", + false, "on", "on/off" ); + ValueArg< uint32_t > dhcp_timeout_arg( "", "dhcp-timout", + "Seconds before DHCP times out and falls back to a static IP", + false, 30, "seconds" ); + ValueArg< uint32_t > link_timeout_arg( "", "link-timout", + "Milliseconds before --eth-first link times out and falls back to USB", + false, 4000, "milliseconds" ); + ValueArg< uint8_t > domain_id_arg( "", "domain-id", + "DDS Domain ID to use (default is 0)", + false, 0, "0-232" ); + SwitchArg usb_first_arg( "", "usb-first", "Prioritize USB before Ethernet" ); + SwitchArg eth_first_arg( "", "eth-first", "Prioritize Ethernet and fall back to USB after link timeout" ); + SwitchArg dynamic_priority_arg( "", "dynamic-priority", "Dynamically prioritize the last-working connection method (the default)" ); + + // In reverse order to how they will be listed + cmd.add( no_reset_arg ); + cmd.add( domain_id_arg ); + cmd.add( gateway_arg ); + cmd.add( mask_arg ); + cmd.add( ip_arg ); + cmd.add( dhcp_timeout_arg ); + cmd.add( dhcp_arg ); + cmd.add( link_timeout_arg ); + cmd.add( dynamic_priority_arg ); + cmd.add( eth_first_arg ); + cmd.add( usb_first_arg ); + cmd.add( factory_reset_arg ); + cmd.add( golden_arg ); + cmd.add( sn_arg ); + cmd.add( quiet_arg ); + cmd.add( debug_arg ); + cmd.parse( argc, argv ); + + g_quiet = quiet_arg.isSet(); + bool const golden = golden_arg.isSet(); + + rs2::log_to_console( debug_arg.isSet() ? RS2_LOG_SEVERITY_DEBUG : RS2_LOG_SEVERITY_ERROR ); + + // Create a RealSense context and look for a device + json settings = load_settings( json::object() ); + rs2::context ctx( settings.dump() ); + + rs2::device device; + eth_config current; + std::set< std::string > devices_looked_at; + if( ! find_device( ctx, device, current, sn_arg, golden, devices_looked_at ) ) + { + auto dds_enabled = settings.nested( "dds", "enabled", &json::is_boolean ); + if( ! dds_enabled || dds_enabled.get< bool >() ) + { + LOG_DEBUG( "Waiting for ETH devices..." ); + int tries = 5; + while( tries-- > 0 ) + { + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + if( find_device( ctx, device, current, sn_arg, golden, devices_looked_at ) ) + break; + } + } + if( ! device ) + { + if( sn_arg.isSet() ) + throw std::runtime_error( "Device not found or does not support Eth" ); + + throw std::runtime_error( "No device found supporting Eth" ); + } + } + INFO( "Device: " << device.get_description() ); + + eth_config requested( current ); + if( golden || factory_reset_arg.isSet() ) + { + if( ip_arg.isSet() || mask_arg.isSet() || usb_first_arg.isSet() || eth_first_arg.isSet() + || dynamic_priority_arg.isSet() || link_timeout_arg.isSet() || dhcp_arg.isSet() || dhcp_timeout_arg.isSet() + || golden == factory_reset_arg.isSet() ) + { + throw std::runtime_error( "Cannot change any settings with --golden" ); + } + if( factory_reset_arg.isSet() ) + { + LOG_DEBUG( "getting golden:" ); + requested = get_eth_config( device, true ); // golden + } + else + { + // Current is the golden values; show them vs the actual values in requested + // No actual changes will be made + LOG_DEBUG( "getting current:" ); + requested = get_eth_config( device, false ); // not golden + } + } + else + { + if( ip_arg.isSet() ) + requested.configured.ip = ip_address( ip_arg.getValue(), rsutils::throw_if_not_valid ); + if( mask_arg.isSet() ) + requested.configured.netmask = ip_address( ip_arg.getValue(), rsutils::throw_if_not_valid ); + if( gateway_arg.isSet() ) + requested.configured.gateway = ip_address( ip_arg.getValue(), rsutils::throw_if_not_valid ); + if( usb_first_arg.isSet() + eth_first_arg.isSet() + dynamic_priority_arg.isSet() > 1 ) + throw std::invalid_argument( "--usb-first, --eth-first, and --dynamic-priority are mutually exclusive" ); + if( usb_first_arg.isSet() ) + requested.link.priority = link_priority::usb_first; + else if( eth_first_arg.isSet() ) + requested.link.priority = link_priority::eth_first; + else if( dynamic_priority_arg.isSet() ) + requested.link.priority // Enable eth-first if we have a link + = current.link.speed ? link_priority::dynamic_eth_first : link_priority::dynamic_usb_first; + if( link_timeout_arg.isSet() ) + requested.link.timeout = link_timeout_arg.getValue(); + if( dhcp_arg.isSet() ) + { + if( dhcp_arg.getValue() == "on" ) + requested.dhcp.on = true; + else if( dhcp_arg.getValue() == "off" ) + requested.dhcp.on = false; + else + throw std::invalid_argument( "--dhcp can be 'on' or 'off'; got " + dhcp_arg.getValue() ); + } + if( dhcp_timeout_arg.isSet() ) + requested.dhcp.timeout = dhcp_timeout_arg.getValue(); + if( domain_id_arg.isSet() ) + { + if( domain_id_arg.getValue() > 232 ) + throw std::invalid_argument( "--domain-id must be 0-232" ); + requested.dds.domain_id = domain_id_arg.getValue(); + } + } + + if( ! g_quiet ) + { + INFO( indent( 4 ) << setting( "MAC address", current.mac_address ) ); + INFO( indent( 4 ) << setting( "configured", current.configured, requested.configured ) ); + if( ! golden && current.actual && current.actual != current.configured ) + INFO( indent( 4 ) << setting( "actual ", current.actual ) ); + + { + INFO( indent( 4 ) << "DDS:" ); + INFO( indent( 8 ) << setting( "domain ID", current.dds.domain_id, requested.dds.domain_id ) ); + } + { + if( golden ) + { + INFO( indent( 4 ) << "link:" ); + } + else + { + if( current.link.speed ) + INFO( indent( 4 ) << setting( "link", current.link.speed ) << " Mbps" ); + else + INFO( indent( 4 ) << setting( "link", "OFF" ) ); + } + INFO( indent( 8 ) << setting( "MTU, bytes", current.link.mtu ) ); + INFO( indent( 8 ) << setting( "timeout, ms", current.link.timeout, requested.link.timeout ) ); + INFO( indent( 8 ) << setting( "priority", current.link.priority, requested.link.priority ) ); + } + { + std::string current_dhcp( current.dhcp.on ? "ON" : "OFF" ); + std::string requested_dhcp( requested.dhcp.on ? "ON" : "OFF" ); + INFO( indent( 4 ) << setting( "DHCP", current_dhcp, requested_dhcp ) ); + INFO( indent( 8 ) << setting( "timeout, sec", current.dhcp.timeout, requested.dhcp.timeout ) ); + } + } + + if( golden ) + { + LOG_DEBUG( "no changes requested with --golden" ); + } + else if( requested == current ) + { + LOG_DEBUG( "nothing to do" ); + } + else + { + rs2::debug_protocol hwm( device ); + auto cmd = hwm.build_command( SET_ETH_CONFIG, 0, 0, 0, 0, requested.build_command() ); + LOG_DEBUG( "cmd: " << rsutils::string::hexdump( cmd.data(), cmd.size() ).format( HWM_FMT ) ); + auto data = hwm.send_and_receive_raw_data( cmd ); + int32_t const & code = *reinterpret_cast< int32_t const * >( data.data() ); + if( data.size() != sizeof( code ) ) + throw std::runtime_error( rsutils::string::from() + << "Failed to change: bad response size " << data.size() << ' ' + << rsutils::string::hexdump( data.data(), data.size() ) ); + if( code != SET_ETH_CONFIG ) + throw std::runtime_error( rsutils::string::from() << "Failed to change: bad response " << code ); + INFO( "Successfully changed" ); + if( ! no_reset_arg.isSet() ) + { + INFO( "Resetting device..." ); + device.hardware_reset(); + } + } + + return EXIT_SUCCESS; +} +catch( const rs2::error & e ) +{ + std::cerr << "-F- RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() + << "):\n " << e.what() << std::endl; + return EXIT_FAILURE; +} +catch( const std::exception & e ) +{ + std::cerr << "-F- " << e.what() << std::endl; + return EXIT_FAILURE; +}