From 299766403f91c281da368d598fabd7e8196550da Mon Sep 17 00:00:00 2001 From: Thomas Stoddard Date: Fri, 19 Jun 2026 02:23:13 +0000 Subject: [PATCH] Settings: Add GPTP --- api/icsneoc2/icsneoc2.cpp | 38 ++ api/icsneoc2/icsneoc2settings.cpp | 110 ++++++ .../icsneopy/device/idevicesettings.cpp | 19 + device/idevicesettings.cpp | 77 +++- examples/CMakeLists.txt | 10 + examples/c2/gptp/CMakeLists.txt | 6 + examples/c2/gptp/src/main.c | 330 ++++++++++++++++++ examples/cpp/gptp/CMakeLists.txt | 2 + examples/cpp/gptp/src/GPTPSettingsExample.cpp | 179 ++++++++++ examples/python/gptp/gptp_settings.py | 151 ++++++++ include/icsneo/device/idevicesettings.h | 24 ++ .../tree/neoviconnect/neoviconnectsettings.h | 9 + .../tree/neovifire3/neovifire3settings.h | 9 + .../neovifire3flexraysettings.h | 9 + .../neovifire3t1slinsettings.h | 9 + .../device/tree/neovired2/neovired2settings.h | 9 + .../device/tree/rada2b/rada2bsettings.h | 9 + .../device/tree/radcomet2/radcomet2settings.h | 9 + .../device/tree/radcomet3/radcomet3settings.h | 9 + .../device/tree/radgalaxy/radgalaxysettings.h | 9 + .../tree/radgalaxy2/radgalaxy2settings.h | 9 + .../tree/radgigastar/radgigastarsettings.h | 9 + .../tree/radgigastar2/radgigastar2settings.h | 8 + .../device/tree/radmoon2/radmoon2settings.h | 9 + .../tree/radmoont1s/radmoont1ssettings.h | 9 + .../device/tree/radstar2/radstar2settings.h | 9 + include/icsneo/icsneoc2.h | 21 ++ include/icsneo/icsneoc2settings.h | 80 +++++ include/icsneo/icsneoc2types.h | 35 ++ test/unit/icsneoc2.cpp | 41 +++ 30 files changed, 1256 insertions(+), 1 deletion(-) create mode 100644 examples/c2/gptp/CMakeLists.txt create mode 100644 examples/c2/gptp/src/main.c create mode 100644 examples/cpp/gptp/CMakeLists.txt create mode 100644 examples/cpp/gptp/src/GPTPSettingsExample.cpp create mode 100644 examples/python/gptp/gptp_settings.py diff --git a/api/icsneoc2/icsneoc2.cpp b/api/icsneoc2/icsneoc2.cpp index d82aebc0..d42e4a7e 100644 --- a/api/icsneoc2/icsneoc2.cpp +++ b/api/icsneoc2/icsneoc2.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -787,6 +788,43 @@ icsneoc2_error_t icsneoc2_device_tc10_status_get(const icsneoc2_device_t* device return icsneoc2_error_success; } +icsneoc2_error_t icsneoc2_device_supports_gptp(const icsneoc2_device_t* device, bool* supported) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!supported) { + return icsneoc2_error_invalid_parameters; + } + *supported = device->device->supportsGPTP(); + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_device_gptp_status_get(const icsneoc2_device_t* device, uint32_t timeout_ms, icsneoc2_gptp_status_t* status) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!status) { + return icsneoc2_error_invalid_parameters; + } + auto cpp_status = device->device->getGPTPStatus(std::chrono::milliseconds(timeout_ms)); + if(!cpp_status.has_value()) { + return icsneoc2_error_get_settings_failure; + } + status->current_time_seconds = cpp_status->currentTime.seconds; + status->current_time_nanoseconds = cpp_status->currentTime.nanoseconds; + status->ms_offset_ns = cpp_status->msOffsetNs; + status->is_sync = cpp_status->isSync; + status->link_status = cpp_status->linkStatus; + status->link_delay_ns = cpp_status->linkDelayNS; + status->selected_role = cpp_status->selectedRole; + status->as_capable = cpp_status->asCapable; + status->is_syntonized = cpp_status->isSyntonized; + status->short_format = cpp_status->shortFormat; + return icsneoc2_error_success; +} + icsneoc2_error_t icsneoc2_device_digital_io_get(const icsneoc2_device_t* device, icsneoc2_io_type_t type, uint32_t number, bool* value) { auto res = icsneoc2_device_is_valid(device); if(res != icsneoc2_error_success) { diff --git a/api/icsneoc2/icsneoc2settings.cpp b/api/icsneoc2/icsneoc2settings.cpp index 6e59a882..76376c67 100644 --- a/api/icsneoc2/icsneoc2settings.cpp +++ b/api/icsneoc2/icsneoc2settings.cpp @@ -892,3 +892,113 @@ icsneoc2_error_t icsneoc2_settings_readonly_get(icsneoc2_device_t* device, bool* *value = device->device->settings->readonly; return icsneoc2_error_success; } + +icsneoc2_error_t icsneoc2_settings_gptp_profile_get(icsneoc2_device_t* device, icsneoc2_gptp_profile_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + if(auto result = device->device->settings->getGPTPProfile(); result.has_value()) { + *value = static_cast(result.value()); + return icsneoc2_error_success; + } + return icsneoc2_error_get_settings_failure; +} + +icsneoc2_error_t icsneoc2_settings_gptp_profile_set(icsneoc2_device_t* device, icsneoc2_gptp_profile_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(value >= icsneoc2_gptp_profile_maxsize) { + return icsneoc2_error_invalid_parameters; + } + if(!device->device->settings->setGPTPProfile(static_cast(value))) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_gptp_role_get(icsneoc2_device_t* device, icsneoc2_gptp_role_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + if(auto result = device->device->settings->getGPTPRole(); result.has_value()) { + *value = static_cast(result.value()); + return icsneoc2_error_success; + } + return icsneoc2_error_get_settings_failure; +} + +icsneoc2_error_t icsneoc2_settings_gptp_role_set(icsneoc2_device_t* device, icsneoc2_gptp_role_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(value >= icsneoc2_gptp_role_maxsize) { + return icsneoc2_error_invalid_parameters; + } + if(!device->device->settings->setGPTPRole(static_cast(value))) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_gptp_enabled_port_get(icsneoc2_device_t* device, uint8_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + if(auto result = device->device->settings->getGPTPEnabledPort(); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } + return icsneoc2_error_get_settings_failure; +} + +icsneoc2_error_t icsneoc2_settings_gptp_enabled_port_set(icsneoc2_device_t* device, uint8_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!device->device->settings->setGPTPEnabledPort(value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_gptp_clock_syntonization_enabled_get(icsneoc2_device_t* device, bool* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + if(auto result = device->device->settings->isGPTPClockSyntonizationEnabled(); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } + return icsneoc2_error_get_settings_failure; +} + +icsneoc2_error_t icsneoc2_settings_gptp_clock_syntonization_enabled_set(icsneoc2_device_t* device, bool value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!device->device->settings->setGPTPClockSyntonizationEnabled(value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} diff --git a/bindings/python/icsneopy/device/idevicesettings.cpp b/bindings/python/icsneopy/device/idevicesettings.cpp index 91dcf8fe..db273c78 100644 --- a/bindings/python/icsneopy/device/idevicesettings.cpp +++ b/bindings/python/icsneopy/device/idevicesettings.cpp @@ -40,6 +40,16 @@ void init_idevicesettings(pybind11::module_& m) { .value("Normal", LINMode::NORMAL_MODE) .value("Fast", LINMode::FAST_MODE); + pybind11::enum_(settings, "GPTPProfile") + .value("Standard", RADGPTPProfile::RAD_GPTP_PROFILE_STANDARD) + .value("Automotive", RADGPTPProfile::RAD_GPTP_PROFILE_AUTOMOTIVE); + + pybind11::enum_(settings, "GPTPRole") + .value("Disabled", RADGPTPRole::RAD_GPTP_ROLE_DISABLED) + .value("Passive", RADGPTPRole::RAD_GPTP_ROLE_PASSIVE) + .value("Master", RADGPTPRole::RAD_GPTP_ROLE_MASTER) + .value("Slave", RADGPTPRole::RAD_GPTP_ROLE_SLAVE); + pybind11::enum_(settings, "MiscIOAnalogVoltage") .value("V0", MiscIOAnalogVoltage::V0) .value("V1", MiscIOAnalogVoltage::V1) @@ -123,6 +133,15 @@ void init_idevicesettings(pybind11::module_& m) { // Performance blast .def("is_perf_test_enabled", &IDeviceSettings::isPerfTestEnabled, pybind11::call_guard()) .def("set_perf_test_enable", &IDeviceSettings::setPerfTestEnable, pybind11::call_guard()) + // gPTP methods + .def("get_gptp_profile", &IDeviceSettings::getGPTPProfile, pybind11::call_guard()) + .def("set_gptp_profile", &IDeviceSettings::setGPTPProfile, pybind11::call_guard()) + .def("get_gptp_role", &IDeviceSettings::getGPTPRole, pybind11::call_guard()) + .def("set_gptp_role", &IDeviceSettings::setGPTPRole, pybind11::call_guard()) + .def("get_gptp_enabled_port", &IDeviceSettings::getGPTPEnabledPort, pybind11::call_guard()) + .def("set_gptp_enabled_port", &IDeviceSettings::setGPTPEnabledPort, pybind11::call_guard()) + .def("is_gptp_clock_syntonization_enabled", &IDeviceSettings::isGPTPClockSyntonizationEnabled, pybind11::call_guard()) + .def("set_gptp_clock_syntonization_enabled", &IDeviceSettings::setGPTPClockSyntonizationEnabled, pybind11::call_guard()) // Status properties .def_readonly("disabled", &IDeviceSettings::disabled) diff --git a/device/idevicesettings.cpp b/device/idevicesettings.cpp index d80aff8d..06963468 100644 --- a/device/idevicesettings.cpp +++ b/device/idevicesettings.cpp @@ -959,4 +959,79 @@ bool IDeviceSettings::setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage vol (void)voltage; report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error); return false; -} \ No newline at end of file +} +std::optional IDeviceSettings::getGPTPProfile() const { + const auto* gptp = getGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return std::nullopt; + } + return static_cast(gptp->profile); +} + +bool IDeviceSettings::setGPTPProfile(RADGPTPProfile profile) { + auto* gptp = getMutableGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return false; + } + gptp->profile = static_cast(profile); + return true; +} + +std::optional IDeviceSettings::getGPTPRole() const { + const auto* gptp = getGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return std::nullopt; + } + return static_cast(gptp->gptpPortRole); +} + +bool IDeviceSettings::setGPTPRole(RADGPTPRole role) { + auto* gptp = getMutableGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return false; + } + gptp->gptpPortRole = static_cast(role); + return true; +} + +std::optional IDeviceSettings::getGPTPEnabledPort() const { + const auto* gptp = getGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return std::nullopt; + } + return gptp->gptpEnabledPort; +} + +bool IDeviceSettings::setGPTPEnabledPort(uint8_t port) { + auto* gptp = getMutableGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return false; + } + gptp->gptpEnabledPort = port; + return true; +} + +std::optional IDeviceSettings::isGPTPClockSyntonizationEnabled() const { + const auto* gptp = getGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return std::nullopt; + } + return gptp->enableClockSyntonization != 0; +} + +bool IDeviceSettings::setGPTPClockSyntonizationEnabled(bool enable) { + auto* gptp = getMutableGPTPSettings(); + if(!gptp) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning); + return false; + } + gptp->enableClockSyntonization = enable ? 1 : 0; + return true; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8ecc74b5..8967bbfd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,7 @@ option(LIBICSNEO_BUILD_C2_ETHERNET_TRANSMIT_EXAMPLE "Build the C2 ethernet trans option(LIBICSNEO_BUILD_C2_ETHERNET_RECEIVE_EXAMPLE "Build the C2 ethernet receive example." ON) option(LIBICSNEO_BUILD_C2_T1S_LOOPBACK_EXAMPLE "Build the C2 RAD-Comet3 T1S loopback example." ON) option(LIBICSNEO_BUILD_C2_TC10_EXAMPLE "Build the C2 TC10 example." ON) +option(LIBICSNEO_BUILD_C2_GPTP_EXAMPLE "Build the C2 gPTP settings example." ON) option(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE "Build the simple C++ example." ON) option(LIBICSNEO_BUILD_CPP_DEVICE_INFO_EXAMPLE "Build the C++ device info example." ON) option(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON) @@ -31,6 +32,7 @@ option(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE "Build the NetworkMutex example." ON) option(LIBICSNEO_BUILD_CPP_ANALOG_OUT_EXAMPLE "Build the analog output example." ON) option(LIBICSNEO_BUILD_CPP_DISKFORMAT_EXAMPLE "Build the disk format example." ON) option(LIBICSNEO_BUILD_CPP_T1S_EXAMPLE "Build the T1S example." ON) +option(LIBICSNEO_BUILD_CPP_GPTP_EXAMPLE "Build the C++ gPTP settings example." ON) add_compile_options(${LIBICSNEO_COMPILER_WARNINGS}) @@ -98,6 +100,10 @@ if(LIBICSNEO_BUILD_C2_TC10_EXAMPLE) add_subdirectory(c2/tc10) endif() +if(LIBICSNEO_BUILD_C2_GPTP_EXAMPLE) + add_subdirectory(c2/gptp) +endif() + if(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE) add_subdirectory(cpp/simple) endif() @@ -165,3 +171,7 @@ endif() if(LIBICSNEO_BUILD_CPP_T1S_EXAMPLE) add_subdirectory(cpp/t1s) endif() + +if(LIBICSNEO_BUILD_CPP_GPTP_EXAMPLE) + add_subdirectory(cpp/gptp) +endif() diff --git a/examples/c2/gptp/CMakeLists.txt b/examples/c2/gptp/CMakeLists.txt new file mode 100644 index 00000000..6cfecc23 --- /dev/null +++ b/examples/c2/gptp/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-gptp-example src/main.c) +target_link_libraries(libicsneoc2-gptp-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-gptp-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/gptp/src/main.c b/examples/c2/gptp/src/main.c new file mode 100644 index 00000000..b69f38f2 --- /dev/null +++ b/examples/c2/gptp/src/main.c @@ -0,0 +1,330 @@ +/* + * gPTP settings configurator example. + * + * Lists connected devices and their gPTP support, then lets the user + * configure the gPTP profile, role, port, and clock syntonization on any + * device that supports it. If --serial is omitted, the first available + * gPTP-capable device is used. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct { + const char* serial; + bool list; +} args_t; + +/* Maps an icsneoc2_netid_t to its gPTP port index (0 = not a gPTP port). */ +static uint8_t netid_to_gptp_port(icsneoc2_netid_t netid) { + switch(netid) { + case icsneoc2_netid_ae_01: return 1; + case icsneoc2_netid_ae_02: return 2; + case icsneoc2_netid_ae_03: return 3; + case icsneoc2_netid_ae_04: return 4; + case icsneoc2_netid_ae_05: return 5; + case icsneoc2_netid_ae_06: return 6; + case icsneoc2_netid_ae_07: return 7; + case icsneoc2_netid_ae_08: return 8; + case icsneoc2_netid_ae_09: return 9; + case icsneoc2_netid_ae_10: return 10; + case icsneoc2_netid_ae_11: return 11; + case icsneoc2_netid_ae_12: return 12; + case icsneoc2_netid_ethernet_01: return 13; + case icsneoc2_netid_ethernet_02: return 14; + case icsneoc2_netid_ethernet_03: return 15; + case icsneoc2_netid_ae_13: return 16; + case icsneoc2_netid_ae_14: return 17; + case icsneoc2_netid_ae_15: return 18; + case icsneoc2_netid_ae_16: return 19; + default: return 0; + } +} + +static const char* gptp_profile_str(icsneoc2_gptp_profile_t p) { + switch(p) { + case icsneoc2_gptp_profile_standard: return "Standard"; + case icsneoc2_gptp_profile_automotive: return "Automotive"; + default: return "Unknown"; + } +} + +static const char* gptp_role_str(icsneoc2_gptp_role_t r) { + switch(r) { + case icsneoc2_gptp_role_disabled: return "Disabled"; + case icsneoc2_gptp_role_passive: return "Passive"; + case icsneoc2_gptp_role_master: return "Master"; + case icsneoc2_gptp_role_slave: return "Slave"; + default: return "Unknown"; + } +} + +static int print_error_code(const char* message, icsneoc2_error_t error) { + char error_str[64] = {0}; + size_t error_str_len = sizeof(error_str); + icsneoc2_error_t res = icsneoc2_error_code_get(error, error_str, &error_str_len); + if(res != icsneoc2_error_success) { + fprintf(stderr, "%s: failed to get string for error code %u\n", message, error); + return (int)res; + } + fprintf(stderr, "%s: \"%s\" (%u)\n", message, error_str, error); + return (int)error; +} + +static void print_usage(const char* prog) { + printf("Usage:\n"); + printf(" %s --list\n", prog); + printf(" %s [--serial SERIAL]\n", prog); + printf("\n"); + printf(" --list List connected devices and their gPTP support.\n"); + printf(" --serial SERIAL Serial number of the device to configure.\n"); + printf(" -h, --help Show this message.\n"); +} + +static int parse_args(int argc, char** argv, args_t* out) { + memset(out, 0, sizeof(*out)); + for(int i = 1; i < argc; ++i) { + const char* a = argv[i]; + if(strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) { + print_usage(argv[0]); + exit(0); + } else if(strcmp(a, "--list") == 0) { + out->list = true; + } else if(strcmp(a, "--serial") == 0) { + if(i + 1 >= argc) { + fprintf(stderr, "error: --serial requires a value\n"); + return 1; + } + out->serial = argv[++i]; + } else { + fprintf(stderr, "error: unknown argument '%s'\n", a); + print_usage(argv[0]); + return 1; + } + } + return 0; +} + +static icsneoc2_device_info_t* find_device(icsneoc2_device_info_t* list, const char* serial) { + for(icsneoc2_device_info_t* cur = list; cur != NULL; cur = icsneoc2_device_info_next(cur)) { + if(serial == NULL) + return cur; + char dev_serial[64] = {0}; + size_t dev_serial_len = sizeof(dev_serial); + if(icsneoc2_device_info_serial_get(cur, dev_serial, &dev_serial_len) != icsneoc2_error_success) + continue; + if(strcmp(dev_serial, serial) == 0) + return cur; + } + return NULL; +} + +/* + * Print a list of available Ethernet/AE networks with their gPTP port indices. + * Returns the number of ports printed. + */ +static size_t print_gptp_ports(icsneoc2_device_t* device) { + icsneoc2_netid_t nets[128] = {0}; + size_t count = sizeof(nets) / sizeof(nets[0]); + if(icsneoc2_device_supported_tx_networks_get(device, nets, &count) != icsneoc2_error_success) + return 0; + + size_t printed = 0; + for(size_t i = 0; i < count; ++i) { + uint8_t port = netid_to_gptp_port(nets[i]); + if(port == 0) + continue; + char name[64] = {0}; + size_t name_len = sizeof(name); + icsneoc2_netid_name_get(nets[i], name, &name_len); + printf(" [%u] %s\n", (unsigned)port, name); + ++printed; + } + return printed; +} + +static int list_devices(icsneoc2_device_info_t* list) { + size_t index = 0; + for(icsneoc2_device_info_t* cur = list; cur != NULL; cur = icsneoc2_device_info_next(cur)) { + char serial[64] = {0}; + size_t serial_len = sizeof(serial); + char description[256] = {0}; + size_t description_len = sizeof(description); + icsneoc2_device_info_serial_get(cur, serial, &serial_len); + icsneoc2_device_info_description_get(cur, description, &description_len); + printf("[%zu] %s (%s)\n", index++, description, serial); + + icsneoc2_device_t* device = NULL; + if(icsneoc2_device_create(cur, &device) != icsneoc2_error_success) + continue; + + icsneoc2_open_options_t opts = icsneoc2_open_options_default; + opts &= ~ICSNEOC2_OPEN_OPTIONS_SYNC_RTC; + opts &= ~ICSNEOC2_OPEN_OPTIONS_GO_ONLINE; + if(icsneoc2_device_open(device, opts) != icsneoc2_error_success) { + icsneoc2_device_free(device); + continue; + } + + bool supported = false; + icsneoc2_device_supports_gptp(device, &supported); + printf(" gPTP supported: %s\n", supported ? "yes" : "no"); + if(supported) { + printf(" gPTP ports:\n"); + print_gptp_ports(device); + } + + icsneoc2_device_close(device); + icsneoc2_device_free(device); + } + return 0; +} + +static void print_current_settings(icsneoc2_device_t* device) { + icsneoc2_gptp_profile_t profile = icsneoc2_gptp_profile_standard; + icsneoc2_gptp_role_t role = icsneoc2_gptp_role_disabled; + uint8_t port = 0; + bool clock_syntonization = false; + + printf("\nCurrent gPTP settings:\n"); + if(icsneoc2_settings_gptp_profile_get(device, &profile) == icsneoc2_error_success) + printf(" Profile: %s\n", gptp_profile_str(profile)); + if(icsneoc2_settings_gptp_role_get(device, &role) == icsneoc2_error_success) + printf(" Role: %s\n", gptp_role_str(role)); + if(icsneoc2_settings_gptp_enabled_port_get(device, &port) == icsneoc2_error_success) + printf(" Enabled port: %u%s\n", (unsigned)port, port == 0 ? " (disabled)" : ""); + if(icsneoc2_settings_gptp_clock_syntonization_enabled_get(device, &clock_syntonization) == icsneoc2_error_success) + printf(" Clock syntonization: %s\n", clock_syntonization ? "enabled" : "disabled"); +} + +static long prompt_long(const char* prompt, long default_val, long min, long max) { + char buf[32] = {0}; + printf("%s [%ld]: ", prompt, default_val); + fflush(stdout); + if(fgets(buf, sizeof(buf), stdin) == NULL || buf[0] == '\n') + return default_val; + char* end = NULL; + long val = strtol(buf, &end, 10); + if(end == buf || val < min || val > max) { + printf("Invalid input, using %ld.\n", default_val); + return default_val; + } + return val; +} + +static int configure_device(icsneoc2_device_t* device) { + icsneoc2_error_t res; + + res = icsneoc2_settings_refresh(device); + if(res != icsneoc2_error_success) + return print_error_code("Failed to refresh settings", res); + + print_current_settings(device); + + printf("\nAvailable gPTP ports (0 = disabled):\n"); + printf(" [0] Disabled\n"); + print_gptp_ports(device); + + printf("\nConfigure gPTP:\n"); + + long profile = prompt_long(" Profile (0=Standard, 1=Automotive)", 1, 0, 1); + res = icsneoc2_settings_gptp_profile_set(device, (icsneoc2_gptp_profile_t)profile); + if(res != icsneoc2_error_success) + return print_error_code("Failed to set profile", res); + + long role = prompt_long(" Role (0=Disabled, 1=Passive, 2=Master, 3=Slave)", 3, 0, 3); + res = icsneoc2_settings_gptp_role_set(device, (icsneoc2_gptp_role_t)role); + if(res != icsneoc2_error_success) + return print_error_code("Failed to set role", res); + + long port = prompt_long(" Enabled port index", 0, 0, 19); + res = icsneoc2_settings_gptp_enabled_port_set(device, (uint8_t)port); + if(res != icsneoc2_error_success) + return print_error_code("Failed to set enabled port", res); + + long syntonization = prompt_long(" Clock syntonization (0=disabled, 1=enabled)", 0, 0, 1); + res = icsneoc2_settings_gptp_clock_syntonization_enabled_set(device, syntonization != 0); + if(res != icsneoc2_error_success) + return print_error_code("Failed to set clock syntonization", res); + + printf("\nNote: icsneoc2_settings_apply() persists settings on the device.\n"); + res = icsneoc2_settings_apply(device); + if(res != icsneoc2_error_success) + return print_error_code("Failed to apply settings", res); + + printf("\nUpdated gPTP settings:\n"); + print_current_settings(device); + return 0; +} + +int main(int argc, char** argv) { + args_t args; + if(parse_args(argc, argv, &args) != 0) + return 1; + + icsneoc2_device_info_t* found_devices = NULL; + icsneoc2_error_t res = icsneoc2_device_enumerate(0, &found_devices); + if(res != icsneoc2_error_success) + return print_error_code("Failed to enumerate devices", res); + if(found_devices == NULL) { + fprintf(stderr, "error: no devices found\n"); + return 1; + } + + if(args.list) { + int rc = list_devices(found_devices); + icsneoc2_enumeration_free(found_devices); + return rc; + } + + icsneoc2_device_info_t* info = find_device(found_devices, args.serial); + if(info == NULL) { + fprintf(stderr, "error: unable to find device %s\n", args.serial ? args.serial : "(any)"); + icsneoc2_enumeration_free(found_devices); + return 1; + } + + char description[256] = {0}; + size_t description_len = sizeof(description); + icsneoc2_device_info_description_get(info, description, &description_len); + + icsneoc2_device_t* device = NULL; + res = icsneoc2_device_create(info, &device); + if(res != icsneoc2_error_success) { + icsneoc2_enumeration_free(found_devices); + return print_error_code("Failed to create device", res); + } + + printf("Opening %s\n", description); + res = icsneoc2_device_open(device, icsneoc2_open_options_default); + if(res != icsneoc2_error_success) { + icsneoc2_device_free(device); + icsneoc2_enumeration_free(found_devices); + return print_error_code("Failed to open device", res); + } + + bool supported = false; + icsneoc2_device_supports_gptp(device, &supported); + if(!supported) { + fprintf(stderr, "error: device does not support gPTP (%s)\n", description); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + icsneoc2_enumeration_free(found_devices); + return 1; + } + + int rc = configure_device(device); + + printf("Closing %s\n", description); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + icsneoc2_enumeration_free(found_devices); + return rc; +} diff --git a/examples/cpp/gptp/CMakeLists.txt b/examples/cpp/gptp/CMakeLists.txt new file mode 100644 index 00000000..9722156d --- /dev/null +++ b/examples/cpp/gptp/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(libicsneocpp-gptp-settings src/GPTPSettingsExample.cpp) +target_link_libraries(libicsneocpp-gptp-settings icsneocpp) diff --git a/examples/cpp/gptp/src/GPTPSettingsExample.cpp b/examples/cpp/gptp/src/GPTPSettingsExample.cpp new file mode 100644 index 00000000..38aaa225 --- /dev/null +++ b/examples/cpp/gptp/src/GPTPSettingsExample.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" + +/* Maps a Network::NetID to its gPTP port index (0 = not a gPTP port). */ +static uint8_t netidToGPTPPort(icsneo::Network::NetID netid) { + using N = icsneo::Network::NetID; + switch(netid) { + case N::AE_01: return 1; + case N::AE_02: return 2; + case N::AE_03: return 3; + case N::AE_04: return 4; + case N::AE_05: return 5; + case N::AE_06: return 6; + case N::AE_07: return 7; + case N::AE_08: return 8; + case N::AE_09: return 9; + case N::AE_10: return 10; + case N::AE_11: return 11; + case N::AE_12: return 12; + case N::ETHERNET_01: return 13; + case N::ETHERNET_02: return 14; + case N::ETHERNET_03: return 15; + case N::AE_13: return 16; + case N::AE_14: return 17; + case N::AE_15: return 18; + case N::AE_16: return 19; + default: return 0; + } +} + +static std::string gptpProfileStr(RADGPTPProfile p) { + switch(p) { + case RAD_GPTP_PROFILE_STANDARD: return "Standard"; + case RAD_GPTP_PROFILE_AUTOMOTIVE: return "Automotive"; + default: return "Unknown"; + } +} + +static std::string gptpRoleStr(RADGPTPRole r) { + switch(r) { + case RAD_GPTP_ROLE_DISABLED: return "Disabled"; + case RAD_GPTP_ROLE_PASSIVE: return "Passive"; + case RAD_GPTP_ROLE_MASTER: return "Master"; + case RAD_GPTP_ROLE_SLAVE: return "Slave"; + default: return "Unknown"; + } +} + +template +static std::string optStr(const std::optional& opt) { + if(!opt.has_value()) return "N/A"; + if constexpr(std::is_same_v) + return opt.value() ? "enabled" : "disabled"; + else + return std::to_string(static_cast(opt.value())); +} + +static long promptLong(const std::string& prompt, long defaultVal, long min, long max) { + std::cout << prompt << " [" << defaultVal << "]: " << std::flush; + std::string input; + std::getline(std::cin, input); + if(input.empty()) + return defaultVal; + try { + long val = std::stol(input); + if(val >= min && val <= max) + return val; + } catch(...) {} + std::cout << "Invalid input, using " << defaultVal << ".\n"; + return defaultVal; +} + +static void printGPTPPorts(const std::shared_ptr& device) { + for(const auto& net : device->getSupportedTXNetworks()) { + uint8_t port = netidToGPTPPort(net.getNetID()); + if(port == 0) continue; + std::cout << " [" << (int)port << "] " << net << "\n"; + } +} + +static void printCurrentSettings(const std::shared_ptr& device) { + std::cout << "\nCurrent gPTP settings:\n"; + auto profile = device->settings->getGPTPProfile(); + auto role = device->settings->getGPTPRole(); + auto port = device->settings->getGPTPEnabledPort(); + auto synton = device->settings->isGPTPClockSyntonizationEnabled(); + + if(profile.has_value()) + std::cout << " Profile: " << gptpProfileStr(profile.value()) << "\n"; + if(role.has_value()) + std::cout << " Role: " << gptpRoleStr(role.value()) << "\n"; + if(port.has_value()) + std::cout << " Enabled port: " << (int)port.value() << (port.value() == 0 ? " (disabled)" : "") << "\n"; + if(synton.has_value()) + std::cout << " Clock syntonization: " << optStr(synton) << "\n"; +} + +int main() { + std::cout << "Finding devices... " << std::flush; + auto devices = icsneo::FindAllDevices(); + std::cout << devices.size() << " found\n"; + + if(devices.empty()) { + auto err = icsneo::GetLastError(); + if(err.getType() != icsneo::APIEvent::Type::NoErrorFound) + std::cout << err << "\n"; + return 1; + } + + std::vector> gptp_devices; + for(auto& d : devices) { + if(d->supportsGPTP()) + gptp_devices.push_back(d); + } + + if(gptp_devices.empty()) { + std::cout << "No gPTP-capable devices found.\n"; + return 1; + } + + std::cout << "\nAvailable gPTP-capable devices:\n"; + for(size_t i = 0; i < gptp_devices.size(); ++i) + std::cout << " [" << (i + 1) << "] " << gptp_devices[i]->describe() << "\n"; + + long choice = promptLong("Select device", 1, 1, (long)gptp_devices.size()); + auto device = gptp_devices[choice - 1]; + + std::cout << "\nOpening " << device->describe() << "... " << std::flush; + if(!device->open()) { + std::cout << "failed\n" << icsneo::GetLastError() << "\n"; + return 1; + } + std::cout << "OK\n"; + + if(!device->settings->refresh()) { + std::cout << "Failed to refresh settings\n"; + device->close(); + return 1; + } + + printCurrentSettings(device); + + std::cout << "\nAvailable gPTP ports (0 = disabled):\n"; + std::cout << " [0] Disabled\n"; + printGPTPPorts(device); + + std::cout << "\nConfigure gPTP:\n"; + + long profile = promptLong(" Profile (0=Standard, 1=Automotive)", 1, 0, 1); + device->settings->setGPTPProfile(static_cast(profile)); + + long role = promptLong(" Role (0=Disabled, 1=Passive, 2=Master, 3=Slave)", 3, 0, 3); + device->settings->setGPTPRole(static_cast(role)); + + long port = promptLong(" Enabled port index", 0, 0, 19); + device->settings->setGPTPEnabledPort(static_cast(port)); + + long syntonization = promptLong(" Clock syntonization (0=disabled, 1=enabled)", 0, 0, 1); + device->settings->setGPTPClockSyntonizationEnabled(syntonization != 0); + + long permanent = promptLong("\nSave to EEPROM? (0=temporary, 1=permanent)", 0, 0, 1); + if(!device->settings->apply(permanent == 0)) { + std::cout << "Failed to apply settings\n" << icsneo::GetLastError() << "\n"; + device->close(); + return 1; + } + + printCurrentSettings(device); + + std::cout << "\nClosing " << device->describe() << "\n"; + device->close(); + return 0; +} diff --git a/examples/python/gptp/gptp_settings.py b/examples/python/gptp/gptp_settings.py new file mode 100644 index 00000000..de0278cb --- /dev/null +++ b/examples/python/gptp/gptp_settings.py @@ -0,0 +1,151 @@ +import icsneopy + +GPTP_PORT_MAP = { + icsneopy.Network.NetID.AE_01: (1, "AE 01"), + icsneopy.Network.NetID.AE_02: (2, "AE 02"), + icsneopy.Network.NetID.AE_03: (3, "AE 03"), + icsneopy.Network.NetID.AE_04: (4, "AE 04"), + icsneopy.Network.NetID.AE_05: (5, "AE 05"), + icsneopy.Network.NetID.AE_06: (6, "AE 06"), + icsneopy.Network.NetID.AE_07: (7, "AE 07"), + icsneopy.Network.NetID.AE_08: (8, "AE 08"), + icsneopy.Network.NetID.AE_09: (9, "AE 09"), + icsneopy.Network.NetID.AE_10: (10, "AE 10"), + icsneopy.Network.NetID.AE_11: (11, "AE 11"), + icsneopy.Network.NetID.AE_12: (12, "AE 12"), + icsneopy.Network.NetID.ETHERNET_01: (13, "Ethernet 01"), + icsneopy.Network.NetID.ETHERNET_02: (14, "Ethernet 02"), + icsneopy.Network.NetID.ETHERNET_03: (15, "Ethernet 03"), + icsneopy.Network.NetID.AE_13: (16, "AE 13"), + icsneopy.Network.NetID.AE_14: (17, "AE 14"), + icsneopy.Network.NetID.AE_15: (18, "AE 15"), + icsneopy.Network.NetID.AE_16: (19, "AE 16"), +} + +PROFILE_NAMES = { + icsneopy.Settings.GTPPProfile.Standard: "Standard", + icsneopy.Settings.GTPPProfile.Automotive: "Automotive", +} + +ROLE_NAMES = { + icsneopy.Settings.GTPPRole.Disabled: "Disabled", + icsneopy.Settings.GTPPRole.Passive: "Passive", + icsneopy.Settings.GTPPRole.Master: "Master", + icsneopy.Settings.GTPPRole.Slave: "Slave", +} + + +def prompt_int(prompt, default, lo, hi): + try: + raw = input(f"{prompt} [{default}]: ").strip() + if not raw: + return default + val = int(raw) + if lo <= val <= hi: + return val + except (ValueError, EOFError): + pass + print(f"Invalid input, using {default}.") + return default + + +def gptp_ports(device): + ports = [] + for net in device.get_supported_tx_networks(): + entry = GPTP_PORT_MAP.get(net.get_net_id()) + if entry: + ports.append(entry) + return sorted(ports) + + +def print_settings(device): + s = device.settings + profile = s.get_gptp_profile() + role = s.get_gptp_role() + port = s.get_gptp_enabled_port() + synton = s.is_gptp_clock_syntonization_enabled() + + print("\nCurrent gPTP settings:") + if profile is not None: + print(f" Profile: {PROFILE_NAMES.get(profile, profile)}") + if role is not None: + print(f" Role: {ROLE_NAMES.get(role, role)}") + if port is not None: + suffix = " (disabled)" if port == 0 else "" + print(f" Enabled port: {port}{suffix}") + if synton is not None: + print(f" Clock syntonization: {'enabled' if synton else 'disabled'}") + + +def configure(device): + if not device.settings.refresh(): + print("error: failed to refresh settings") + return False + + print_settings(device) + + ports = gptp_ports(device) + print("\nAvailable gPTP ports (0 = disabled):") + print(" [0] Disabled") + for idx, name in ports: + print(f" [{idx}] {name}") + + print("\nConfigure gPTP:") + profile = prompt_int(" Profile (0=Standard, 1=Automotive)", 1, 0, 1) + role = prompt_int(" Role (0=Disabled, 1=Passive, 2=Master, 3=Slave)", 3, 0, 3) + port = prompt_int(" Enabled port index", 0, 0, 19) + synton = prompt_int(" Clock syntonization (0=disabled, 1=enabled)", 0, 0, 1) + permanent = prompt_int("\nSave to EEPROM? (0=temporary, 1=permanent)", 0, 0, 1) + + s = device.settings + profile_enum = list(PROFILE_NAMES.keys())[profile] + role_enum = list(ROLE_NAMES.keys())[role] + + s.set_gptp_profile(profile_enum) + s.set_gptp_role(role_enum) + s.set_gptp_enabled_port(port) + s.set_gptp_clock_syntonization_enabled(bool(synton)) + + if not s.apply(not permanent): + print("error: failed to apply settings") + return False + + print_settings(device) + return True + + +def main(): + devices = icsneopy.find_all_devices() + if not devices: + print("error: no devices found") + return 1 + + gptp_devices = [d for d in devices if d.settings.get_gptp_profile() is not None] + if not gptp_devices: + print("error: no gPTP-capable devices found") + return 1 + + print("Available gPTP-capable devices:") + for i, d in enumerate(gptp_devices, 1): + print(f" [{i}] {d}") + + choice = prompt_int("Select device", 1, 1, len(gptp_devices)) + device = gptp_devices[choice - 1] + + print(f"\nOpening {device}... ", end="", flush=True) + if not device.open(): + print("failed") + return 1 + print("OK") + + try: + ok = configure(device) + finally: + print(f"\nClosing {device}") + device.close() + + return 0 if ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/include/icsneo/device/idevicesettings.h b/include/icsneo/device/idevicesettings.h index 49a85d1c..20ff84fc 100644 --- a/include/icsneo/device/idevicesettings.h +++ b/include/icsneo/device/idevicesettings.h @@ -613,6 +613,18 @@ enum : uint8_t RESISTOR_OFF }; +enum RADGPTPProfile : icsneoc2_gptp_profile_t { + RAD_GPTP_PROFILE_STANDARD = icsneoc2_gptp_profile_standard, + RAD_GPTP_PROFILE_AUTOMOTIVE = icsneoc2_gptp_profile_automotive, +}; + +enum RADGPTPRole : icsneoc2_gptp_role_t { + RAD_GPTP_ROLE_DISABLED = icsneoc2_gptp_role_disabled, + RAD_GPTP_ROLE_PASSIVE = icsneoc2_gptp_role_passive, + RAD_GPTP_ROLE_MASTER = icsneoc2_gptp_role_master, + RAD_GPTP_ROLE_SLAVE = icsneoc2_gptp_role_slave, +}; + /* Mode in LIN_SETTINGS */ enum LINMode { @@ -1278,6 +1290,18 @@ public: virtual bool setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled); virtual bool setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage); + // gPTP methods + std::optional getGPTPProfile() const; + bool setGPTPProfile(RADGPTPProfile profile); + std::optional getGPTPRole() const; + bool setGPTPRole(RADGPTPRole role); + std::optional getGPTPEnabledPort() const; + bool setGPTPEnabledPort(uint8_t port); + std::optional isGPTPClockSyntonizationEnabled() const; + bool setGPTPClockSyntonizationEnabled(bool enable); + virtual const RAD_GPTP_SETTINGS* getGPTPSettings() const { return nullptr; } + virtual RAD_GPTP_SETTINGS* getMutableGPTPSettings() { return nullptr; } + const void* getRawStructurePointer() const { return settingsInDeviceRAM.data(); } void* getMutableRawStructurePointer() { return settings.data(); } template const T* getStructurePointer() const { return reinterpret_cast(getRawStructurePointer()); } diff --git a/include/icsneo/device/tree/neoviconnect/neoviconnectsettings.h b/include/icsneo/device/tree/neoviconnect/neoviconnectsettings.h index 542ae681..3c491f78 100644 --- a/include/icsneo/device/tree/neoviconnect/neoviconnectsettings.h +++ b/include/icsneo/device/tree/neoviconnect/neoviconnectsettings.h @@ -171,6 +171,15 @@ public: cfg->perf_en = !!enable; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/neovifire3/neovifire3settings.h b/include/icsneo/device/tree/neovifire3/neovifire3settings.h index 8036f99b..442434c6 100644 --- a/include/icsneo/device/tree/neovifire3/neovifire3settings.h +++ b/include/icsneo/device/tree/neovifire3/neovifire3settings.h @@ -422,6 +422,15 @@ protected: return nullptr; return &cfg->termination_enables; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/neovifire3flexray/neovifire3flexraysettings.h b/include/icsneo/device/tree/neovifire3flexray/neovifire3flexraysettings.h index afd3da0a..62e698d8 100644 --- a/include/icsneo/device/tree/neovifire3flexray/neovifire3flexraysettings.h +++ b/include/icsneo/device/tree/neovifire3flexray/neovifire3flexraysettings.h @@ -261,6 +261,15 @@ protected: return nullptr; return &cfg->termination_enables; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/neovifire3t1slin/neovifire3t1slinsettings.h b/include/icsneo/device/tree/neovifire3t1slin/neovifire3t1slinsettings.h index 18de5616..08595d7d 100644 --- a/include/icsneo/device/tree/neovifire3t1slin/neovifire3t1slinsettings.h +++ b/include/icsneo/device/tree/neovifire3t1slin/neovifire3t1slinsettings.h @@ -572,6 +572,15 @@ protected: return nullptr; return &cfg->termination_enables; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/neovired2/neovired2settings.h b/include/icsneo/device/tree/neovired2/neovired2settings.h index 112cc5a3..d21c9d8e 100644 --- a/include/icsneo/device/tree/neovired2/neovired2settings.h +++ b/include/icsneo/device/tree/neovired2/neovired2settings.h @@ -320,6 +320,15 @@ protected: return nullptr; return &cfg->termination_enables; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/rada2b/rada2bsettings.h b/include/icsneo/device/tree/rada2b/rada2bsettings.h index da3786f4..993f99dd 100644 --- a/include/icsneo/device/tree/rada2b/rada2bsettings.h +++ b/include/icsneo/device/tree/rada2b/rada2bsettings.h @@ -259,6 +259,15 @@ public: cfg->perf_en = !!enable; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radcomet2/radcomet2settings.h b/include/icsneo/device/tree/radcomet2/radcomet2settings.h index ed449d16..edd4635a 100644 --- a/include/icsneo/device/tree/radcomet2/radcomet2settings.h +++ b/include/icsneo/device/tree/radcomet2/radcomet2settings.h @@ -290,6 +290,15 @@ private: return nullptr; } } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radcomet3/radcomet3settings.h b/include/icsneo/device/tree/radcomet3/radcomet3settings.h index 5e869944..1497bb85 100644 --- a/include/icsneo/device/tree/radcomet3/radcomet3settings.h +++ b/include/icsneo/device/tree/radcomet3/radcomet3settings.h @@ -728,6 +728,15 @@ private: return nullptr; } } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h b/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h index 9470b5ed..af6038da 100644 --- a/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h +++ b/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h @@ -290,6 +290,15 @@ public: cfg->perf_en = !!enable; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radgalaxy2/radgalaxy2settings.h b/include/icsneo/device/tree/radgalaxy2/radgalaxy2settings.h index 27b33442..82577c67 100644 --- a/include/icsneo/device/tree/radgalaxy2/radgalaxy2settings.h +++ b/include/icsneo/device/tree/radgalaxy2/radgalaxy2settings.h @@ -277,6 +277,15 @@ public: cfg->perf_en = !!enable; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radgigastar/radgigastarsettings.h b/include/icsneo/device/tree/radgigastar/radgigastarsettings.h index eaba199c..5ee2ab0a 100644 --- a/include/icsneo/device/tree/radgigastar/radgigastarsettings.h +++ b/include/icsneo/device/tree/radgigastar/radgigastarsettings.h @@ -203,6 +203,15 @@ protected: return nullptr; return &cfg->termination_enables; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; typedef struct { diff --git a/include/icsneo/device/tree/radgigastar2/radgigastar2settings.h b/include/icsneo/device/tree/radgigastar2/radgigastar2settings.h index f5bfc9c5..2b7fafd9 100644 --- a/include/icsneo/device/tree/radgigastar2/radgigastar2settings.h +++ b/include/icsneo/device/tree/radgigastar2/radgigastar2settings.h @@ -645,6 +645,14 @@ namespace icsneo return nullptr; return &cfg->termination_enables; } + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; typedef struct diff --git a/include/icsneo/device/tree/radmoon2/radmoon2settings.h b/include/icsneo/device/tree/radmoon2/radmoon2settings.h index 8b6162fc..6e60b3b2 100644 --- a/include/icsneo/device/tree/radmoon2/radmoon2settings.h +++ b/include/icsneo/device/tree/radmoon2/radmoon2settings.h @@ -43,6 +43,15 @@ static_assert(sizeof(radmoon2_settings_t) == 170, "RADMoon2 settings size mismat class RADMoon2Settings : public IDeviceSettings { public: RADMoon2Settings(std::shared_ptr com) : IDeviceSettings(com, sizeof(radmoon2_settings_t)) {} + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radmoont1s/radmoont1ssettings.h b/include/icsneo/device/tree/radmoont1s/radmoont1ssettings.h index 4ecb2cbc..71aff434 100644 --- a/include/icsneo/device/tree/radmoont1s/radmoont1ssettings.h +++ b/include/icsneo/device/tree/radmoont1s/radmoont1ssettings.h @@ -380,6 +380,15 @@ private: cfg->t1sExt.multi_id[index] = id; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/device/tree/radstar2/radstar2settings.h b/include/icsneo/device/tree/radstar2/radstar2settings.h index 7d566b97..1ecacd6b 100644 --- a/include/icsneo/device/tree/radstar2/radstar2settings.h +++ b/include/icsneo/device/tree/radstar2/radstar2settings.h @@ -134,6 +134,15 @@ public: cfg->perf_en = !!enable; return true; } + + const RAD_GPTP_SETTINGS* getGPTPSettings() const override { + auto cfg = getStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } + RAD_GPTP_SETTINGS* getMutableGPTPSettings() override { + auto cfg = getMutableStructurePointer(); + return cfg ? &cfg->gPTP : nullptr; + } }; } diff --git a/include/icsneo/icsneoc2.h b/include/icsneo/icsneoc2.h index 8209eeaf..3db32292 100644 --- a/include/icsneo/icsneoc2.h +++ b/include/icsneo/icsneoc2.h @@ -581,6 +581,27 @@ icsneoc2_error_t icsneoc2_device_tc10_sleep_request(const icsneoc2_device_t* dev * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. */ icsneoc2_error_t icsneoc2_device_tc10_status_get(const icsneoc2_device_t* device, icsneoc2_netid_t netid, icsneoc2_tc10_sleep_status_t* sleep_status, icsneoc2_tc10_wake_status_t* wake_status); + +/** + * Check if the device supports gPTP. + * + * @param[in] device The device to check against. + * @param[out] supported Pointer to a bool to copy the value into. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_device_supports_gptp(const icsneoc2_device_t* device, bool* supported); + +/** + * Get the current gPTP status from the device. + * + * @param[in] device The device to query. + * @param[in] timeout_ms Timeout in milliseconds to wait for the device response. + * @param[out] status Pointer to an icsneoc2_gptp_status_t to copy the status into. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_device_gptp_status_get(const icsneoc2_device_t* device, uint32_t timeout_ms, icsneoc2_gptp_status_t* status); /** * Get the current state of a digital I/O pin. * diff --git a/include/icsneo/icsneoc2settings.h b/include/icsneo/icsneoc2settings.h index 0d606f9f..616efc91 100644 --- a/include/icsneo/icsneoc2settings.h +++ b/include/icsneo/icsneoc2settings.h @@ -676,6 +676,86 @@ icsneoc2_error_t icsneoc2_settings_disabled_get(icsneoc2_device_t* device, bool* */ icsneoc2_error_t icsneoc2_settings_readonly_get(icsneoc2_device_t* device, bool* value); +/** + * Get the gPTP profile. + * + * @param[in] device The device to check. + * @param[out] value Pointer to store the gPTP profile. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_profile_get(icsneoc2_device_t* device, icsneoc2_gptp_profile_t* value); + +/** + * Set the gPTP profile. + * + * @param[in] device The device to configure. + * @param[in] value The gPTP profile to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_set_settings_failure otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_profile_set(icsneoc2_device_t* device, icsneoc2_gptp_profile_t value); + +/** + * Get the gPTP port role. + * + * @param[in] device The device to check. + * @param[out] value Pointer to store the gPTP port role. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_role_get(icsneoc2_device_t* device, icsneoc2_gptp_role_t* value); + +/** + * Set the gPTP port role. + * + * @param[in] device The device to configure. + * @param[in] value The gPTP port role to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_set_settings_failure otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_role_set(icsneoc2_device_t* device, icsneoc2_gptp_role_t value); + +/** + * Get the gPTP enabled port index. A value of 0 indicates gPTP is disabled. + * + * @param[in] device The device to check. + * @param[out] value Pointer to store the enabled port index. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_enabled_port_get(icsneoc2_device_t* device, uint8_t* value); + +/** + * Set the gPTP enabled port index. Set to 0 to disable gPTP. + * + * @param[in] device The device to configure. + * @param[in] value The port index to enable gPTP on, or 0 to disable. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_set_settings_failure otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_enabled_port_set(icsneoc2_device_t* device, uint8_t value); + +/** + * Check if gPTP clock syntonization is enabled. + * + * @param[in] device The device to check. + * @param[out] value Pointer to store the clock syntonization enable state. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_clock_syntonization_enabled_get(icsneoc2_device_t* device, bool* value); + +/** + * Enable or disable gPTP clock syntonization. + * + * @param[in] device The device to configure. + * @param[in] value The clock syntonization enable state to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_set_settings_failure otherwise. + */ +icsneoc2_error_t icsneoc2_settings_gptp_clock_syntonization_enabled_set(icsneoc2_device_t* device, bool value); + #ifdef __cplusplus } #endif diff --git a/include/icsneo/icsneoc2types.h b/include/icsneo/icsneoc2types.h index 67e96a6e..d7848ddf 100644 --- a/include/icsneo/icsneoc2types.h +++ b/include/icsneo/icsneoc2types.h @@ -352,6 +352,41 @@ typedef enum _icsneoc2_misc_io_analog_voltage_t { typedef uint8_t icsneoc2_misc_io_analog_voltage_t; +typedef enum _icsneoc2_gptp_profile_t { + icsneoc2_gptp_profile_standard = 0, // IEEE 802.1AS standard profile + icsneoc2_gptp_profile_automotive = 1, // Automotive gPTP profile + + // Must be last entry. Don't use as a gPTP profile. + icsneoc2_gptp_profile_maxsize +} _icsneoc2_gptp_profile_t; + +typedef uint8_t icsneoc2_gptp_profile_t; + +typedef enum _icsneoc2_gptp_role_t { + icsneoc2_gptp_role_disabled = 0, // gPTP disabled on this port + icsneoc2_gptp_role_passive = 1, // Passive role + icsneoc2_gptp_role_master = 2, // Master role + icsneoc2_gptp_role_slave = 3, // Slave role + + // Must be last entry. Don't use as a gPTP role. + icsneoc2_gptp_role_maxsize +} _icsneoc2_gptp_role_t; + +typedef uint8_t icsneoc2_gptp_role_t; + +typedef struct icsneoc2_gptp_status_t { + uint64_t current_time_seconds; // Current PTP time (seconds portion) + uint32_t current_time_nanoseconds; // Current PTP time (nanoseconds portion) + int64_t ms_offset_ns; // Master-slave clock offset in nanoseconds + uint8_t is_sync; // Non-zero if the clock is synchronized + uint8_t link_status; // Non-zero if the link is up + int64_t link_delay_ns; // One-way propagation delay in nanoseconds + uint8_t selected_role; // Negotiated port role (see icsneoc2_gptp_role_t) + uint8_t as_capable; // Non-zero if the port is AS-capable (802.1AS) + uint8_t is_syntonized; // Non-zero if frequency is locked to grandmaster + uint8_t short_format; // Non-zero if firmware returned a partial response +} icsneoc2_gptp_status_t; + typedef struct icsneoc2_disk_details_t icsneoc2_disk_details_t; typedef enum _icsneoc2_disk_layout_t { diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index 534d4c15..74d7a556 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -299,6 +299,22 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_disabled_get(NULL, &placeholderBool)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_readonly_get(NULL, &placeholderBool)); + // gPTP settings NULL parameter checks + icsneoc2_gptp_profile_t placeholderGptpProfile = 0; + icsneoc2_gptp_role_t placeholderGptpRole = 0; + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_profile_get(NULL, &placeholderGptpProfile)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_profile_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_profile_set(NULL, placeholderGptpProfile)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_role_get(NULL, &placeholderGptpRole)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_role_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_role_set(NULL, placeholderGptpRole)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_enabled_port_get(NULL, &placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_enabled_port_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_enabled_port_set(NULL, 0)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_clock_syntonization_enabled_get(NULL, &placeholderBool)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_clock_syntonization_enabled_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_gptp_clock_syntonization_enabled_set(NULL, false)); + // Disk formatting functions ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_disk_count_get(NULL, &placeholderSizeT)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_supports_disk_formatting(NULL, &placeholderBool)); @@ -1763,4 +1779,29 @@ TEST(icsneoc2, test_icsneoc2_eth_invalid_type) ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(can_msg)); } +TEST(icsneoc2, test_gptp_enum_values) +{ + ASSERT_EQ(icsneoc2_gptp_profile_standard, 0); + ASSERT_EQ(icsneoc2_gptp_profile_automotive, 1); + ASSERT_EQ(sizeof(icsneoc2_gptp_profile_t), sizeof(uint8_t)); + + ASSERT_EQ(icsneoc2_gptp_role_disabled, 0); + ASSERT_EQ(icsneoc2_gptp_role_passive, 1); + ASSERT_EQ(icsneoc2_gptp_role_master, 2); + ASSERT_EQ(icsneoc2_gptp_role_slave, 3); + ASSERT_EQ(sizeof(icsneoc2_gptp_role_t), sizeof(uint8_t)); +} + +TEST(icsneoc2, test_gptp_enum_alignment) +{ + ASSERT_EQ(RADGPTPProfile::RAD_GPTP_PROFILE_STANDARD, icsneoc2_gptp_profile_standard); + ASSERT_EQ(RADGPTPProfile::RAD_GPTP_PROFILE_AUTOMOTIVE, icsneoc2_gptp_profile_automotive); + ASSERT_EQ(sizeof(RADGPTPProfile), sizeof(icsneoc2_gptp_profile_t)); + + ASSERT_EQ(RADGPTPRole::RAD_GPTP_ROLE_DISABLED, icsneoc2_gptp_role_disabled); + ASSERT_EQ(RADGPTPRole::RAD_GPTP_ROLE_PASSIVE, icsneoc2_gptp_role_passive); + ASSERT_EQ(RADGPTPRole::RAD_GPTP_ROLE_MASTER, icsneoc2_gptp_role_master); + ASSERT_EQ(RADGPTPRole::RAD_GPTP_ROLE_SLAVE, icsneoc2_gptp_role_slave); + ASSERT_EQ(sizeof(RADGPTPRole), sizeof(icsneoc2_gptp_role_t)); +}