diff --git a/api/icsneoc2/icsneoc2.cpp b/api/icsneoc2/icsneoc2.cpp index 2f15a9c..d73b8ca 100644 --- a/api/icsneoc2/icsneoc2.cpp +++ b/api/icsneoc2/icsneoc2.cpp @@ -43,7 +43,7 @@ bool safe_str_copy(char* dest, size_t* dest_size, std::string_view src) { } icsneoc2_error_t icsneoc2_error_code_get(icsneoc2_error_t error_code, char* value, size_t* value_length) { - static const char* error_strings[icsneoc2_error_maxsize] = { + static const char* error_strings[] = { "Success", // icsneoc2_error_success "Invalid function parameters", // icsneoc2_error_invalid_parameters "Open failed", // icsneoc2_error_open_failed @@ -66,6 +66,8 @@ icsneoc2_error_t icsneoc2_error_code_get(icsneoc2_error_t error_code, char* valu "Script clear failed", // icsneoc2_error_script_clear_failed "Script upload failed", // icsneoc2_error_script_upload_failed "Script load prepare failed", // icsneoc2_error_script_load_prepare_failed + "Close failed", // icsneoc2_error_close_failed + "Reconnect failed", // icsneoc2_error_reconnect_failed }; static_assert(std::size(error_strings) == icsneoc2_error_maxsize, "error_strings is out of sync with _icsneoc2_error_t enum - update both together"); @@ -194,37 +196,27 @@ icsneoc2_error_t icsneoc2_device_is_open(const icsneoc2_device_t* device, bool* return icsneoc2_error_success; } -icsneoc2_error_t icsneoc2_device_is_disconnected(const icsneoc2_device_t* device, bool* is_disconnected) { - auto res = icsneoc2_device_is_valid(device); - if(res != icsneoc2_error_success) { - return res; - } - if(!is_disconnected) { +icsneoc2_error_t icsneoc2_device_create(const icsneoc2_device_info_t* device_info, icsneoc2_device_t** device) { + if(!device_info || !device_info->device || !device) { return icsneoc2_error_invalid_parameters; } - auto dev = device->device; - *is_disconnected = dev->isDisconnected(); - + auto* new_device = new (std::nothrow) icsneoc2_device_t; + if(!new_device) { + return icsneoc2_error_out_of_memory; + } + new_device->device = device_info->device; + *device = new_device; return icsneoc2_error_success; } -static icsneoc2_error_t open_device_with_options(std::shared_ptr dev, icsneoc2_open_options_t options, icsneoc2_device_t** device) { +static icsneoc2_error_t open_device_with_options(std::shared_ptr dev, icsneoc2_open_options_t options) { if(!dev) { return icsneoc2_error_invalid_device; } - // Nothing to do if already open - if(dev->isOpen()) { - *device = new (std::nothrow) icsneoc2_device_t; - if(!*device) { - return icsneoc2_error_out_of_memory; - } - (*device)->device = dev; - return icsneoc2_error_success; - } if(!dev->enableMessagePolling(std::make_optional())) { return icsneoc2_error_enable_message_polling_failed; } - if(!dev->open()) { + if(!dev->isOpen() && !dev->open()) { return icsneoc2_error_open_failed; } if((options & ICSNEOC2_OPEN_OPTIONS_SYNC_RTC) && !dev->setRTC(std::chrono::system_clock::now())) { @@ -235,23 +227,15 @@ static icsneoc2_error_t open_device_with_options(std::shared_ptr dev, ic dev->close(); return icsneoc2_error_go_online_failed; } - *device = new (std::nothrow) icsneoc2_device_t; - if(!*device) { - dev->close(); - return icsneoc2_error_out_of_memory; - } - (*device)->device = dev; return icsneoc2_error_success; } -icsneoc2_error_t icsneoc2_device_open(const icsneoc2_device_info_t* device_info, icsneoc2_open_options_t options, icsneoc2_device_t** device) { - if(!device_info || !device) { - return icsneoc2_error_invalid_parameters; +icsneoc2_error_t icsneoc2_device_open(const icsneoc2_device_t* device, icsneoc2_open_options_t options) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; } - if(!device_info->device) { - return icsneoc2_error_invalid_device; - } - return open_device_with_options(device_info->device, options, device); + return open_device_with_options(device->device, options); } icsneoc2_error_t icsneoc2_device_open_serial(const char* serial, icsneoc2_open_options_t options, icsneoc2_device_t** device) { @@ -266,7 +250,15 @@ icsneoc2_error_t icsneoc2_device_open_serial(const char* serial, icsneoc2_open_o std::string_view target(serial); for(auto* cur = devs; cur; cur = cur->next) { if(cur->device && cur->device->getSerial() == target) { - res = open_device_with_options(cur->device, options, device); + if (res = icsneoc2_device_create(cur, device); res != icsneoc2_error_success) { + icsneoc2_enumeration_free(devs); + return res; + } + res = open_device_with_options((*device)->device, options); + if (res != icsneoc2_error_success) { + icsneoc2_device_free(*device); + *device = nullptr; + } icsneoc2_enumeration_free(devs); return res; } @@ -286,7 +278,15 @@ icsneoc2_error_t icsneoc2_device_open_first(icsneoc2_devicetype_t device_type, i } for(auto* cur = devs; cur; cur = cur->next) { if(cur->device && !cur->device->isOpen()) { - res = open_device_with_options(cur->device, options, device); + if (res = icsneoc2_device_create(cur, device); res != icsneoc2_error_success) { + icsneoc2_enumeration_free(devs); + return res; + } + res = open_device_with_options((*device)->device, options); + if (res != icsneoc2_error_success) { + icsneoc2_device_free(*device); + *device = nullptr; + } icsneoc2_enumeration_free(devs); return res; } @@ -295,6 +295,34 @@ icsneoc2_error_t icsneoc2_device_open_first(icsneoc2_devicetype_t device_type, i return icsneoc2_error_invalid_device; } +icsneoc2_error_t icsneoc2_device_reconnect(icsneoc2_device_t* device, icsneoc2_open_options_t options, uint32_t timeout_ms) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + // If the device is currently open, close it first before trying to reconnect + if (device->device->isOpen()) { + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + return res; + } + } + + const auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); + while(std::chrono::steady_clock::now() < timeout) { + icsneoc2_device_t* new_device = nullptr; + res = icsneoc2_device_open_serial(device->device->getSerial().c_str(), options, &new_device); + if(res == icsneoc2_error_success) { + device->device = new_device->device; + delete new_device; + return icsneoc2_error_success; + } + // Avoid busy looping + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return icsneoc2_error_reconnect_failed; +} + icsneoc2_error_t icsneoc2_device_close(icsneoc2_device_t* device) { auto res = icsneoc2_device_is_valid(device); if(res != icsneoc2_error_success) { @@ -304,7 +332,21 @@ icsneoc2_error_t icsneoc2_device_close(icsneoc2_device_t* device) { if(!dev->isOpen()) { return icsneoc2_error_success; } - dev->close(); + + return dev->close() ? icsneoc2_error_success : icsneoc2_error_close_failed; +} + +icsneoc2_error_t icsneoc2_device_free(icsneoc2_device_t* device) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if (device->device->isOpen()) { + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + return res; + } + } delete device; return icsneoc2_error_success; } diff --git a/device/device.cpp b/device/device.cpp index ec08fe2..f133b6c 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -395,7 +395,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { if(heartbeatCV.wait_for(recvLk, std::chrono::milliseconds(3500), [&](){ return receivedMessage; })) { receivedMessage = false; } else { - if(!stopHeartbeatThread && !isDisconnected()) { + if(!stopHeartbeatThread) { close(); report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 20fde5e..bd5a733 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,6 +4,7 @@ option(LIBICSNEO_BUILD_C_LEGACY_EXAMPLE "Build the command-line simple C example option(LIBICSNEO_BUILD_C2_SIMPLE_EXAMPLE "Build the simple C2 example." ON) option(LIBICSNEO_BUILD_C2_READ_MESSAGES_EXAMPLE "Build the C2 read messages example." ON) option(LIBICSNEO_BUILD_C2_DISKFORMAT_EXAMPLE "Build the C2 disk format example." ON) +option(LIBICSNEO_BUILD_C2_RECONNECT_EXAMPLE "Build the C2 reconnect example." ON) option(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE "Build the simple C++ example." ON) option(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON) option(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE "Build the A2B example." ON) @@ -47,6 +48,10 @@ if(LIBICSNEO_BUILD_C2_DISKFORMAT_EXAMPLE) add_subdirectory(c2/diskformat) endif() +if(LIBICSNEO_BUILD_C2_RECONNECT_EXAMPLE) + add_subdirectory(c2/reconnect) +endif() + if(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE) add_subdirectory(cpp/simple) endif() diff --git a/examples/c2/diskformat/src/main.c b/examples/c2/diskformat/src/main.c index 4c33d27..9358c64 100644 --- a/examples/c2/diskformat/src/main.c +++ b/examples/c2/diskformat/src/main.c @@ -48,6 +48,7 @@ int main() { size_t description_length = sizeof(description); res = icsneoc2_device_description_get(device, description, &description_length); if(res != icsneoc2_error_success) { + icsneoc2_device_free(device); return print_error_code("\tFailed to get device description", res); } printf("\tOpened device: %s\n", description); @@ -58,11 +59,13 @@ int main() { if(res != icsneoc2_error_success) { print_error_code("\tFailed to check disk formatting support", res); icsneoc2_device_close(device); + icsneoc2_device_free(device); return -1; } if(!supported) { printf("\terror: %s does not support disk formatting\n", description); icsneoc2_device_close(device); + icsneoc2_device_free(device); return -1; } @@ -79,6 +82,7 @@ int main() { printf("FAIL\n"); print_error_code("\tFailed to get disk details", res); icsneoc2_device_close(device); + icsneoc2_device_free(device); return -1; } printf("OK\n"); @@ -122,6 +126,7 @@ int main() { printf("\n\terror: no disks are present in the device\n"); icsneoc2_disk_details_free(details); icsneoc2_device_close(device); + icsneoc2_device_free(device); return -1; } @@ -133,6 +138,7 @@ int main() { printf("\tAborted.\n"); icsneoc2_disk_details_free(details); icsneoc2_device_close(device); + icsneoc2_device_free(device); return 0; } @@ -145,6 +151,7 @@ int main() { print_error_code("\tFormat failed", res); icsneoc2_disk_details_free(details); icsneoc2_device_close(device); + icsneoc2_device_free(device); return -1; } printf("\tFormat complete!\n"); @@ -171,5 +178,6 @@ int main() { printf("\tClosing device: %s...\n", description); icsneoc2_device_close(device); + icsneoc2_device_free(device); return 0; } diff --git a/examples/c2/read_messages/src/main.c b/examples/c2/read_messages/src/main.c index a0eeb91..cad6b6f 100644 --- a/examples/c2/read_messages/src/main.c +++ b/examples/c2/read_messages/src/main.c @@ -83,6 +83,7 @@ int main() { size_t description_length = 255; res = icsneoc2_device_description_get(open_device, description, &description_length); if(res != icsneoc2_error_success) { + icsneoc2_device_free(open_device); return print_error_code("\tFailed to get device description", res); }; printf("\tOpened device: %s\n", description); @@ -96,6 +97,7 @@ int main() { res = icsneoc2_device_message_get(open_device, &messages[i], 0); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); return print_error_code("\tFailed to get messages from device", res); }; if(messages[i] == NULL) { @@ -106,6 +108,7 @@ int main() { } if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); return print_error_code("\tFailed to get messages from device", res); } time_t end_time = time(NULL); @@ -114,6 +117,7 @@ int main() { res = process_message(messages, message_count); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); return print_error_code("\tFailed to process messages", res); } // Finally, close the device. @@ -121,8 +125,10 @@ int main() { res = icsneoc2_device_close(open_device); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); return print_error_code("\tFailed to close device", res); }; + icsneoc2_device_free(open_device); printf("\n"); return 0; diff --git a/examples/c2/reconnect/CMakeLists.txt b/examples/c2/reconnect/CMakeLists.txt new file mode 100644 index 0000000..e55b8e4 --- /dev/null +++ b/examples/c2/reconnect/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-reconnect-example src/main.c) +target_link_libraries(libicsneoc2-reconnect-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-reconnect-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/reconnect/src/main.c b/examples/c2/reconnect/src/main.c new file mode 100644 index 0000000..ff3756a --- /dev/null +++ b/examples/c2/reconnect/src/main.c @@ -0,0 +1,132 @@ +#include + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +void sleep_ms(uint32_t ms) { +#ifdef _WIN32 + Sleep(ms); +#else + usleep(ms * 1000); +#endif +} + +int print_error_code(const char* message, icsneoc2_error_t error) { + char error_str[64]; + 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) { + printf("%s: Failed to get string for error code %d with error code %d\n", message, error, res); + return res; + } + printf("%s: \"%s\" (%u)\n", message, error_str, error); + return (int)error; +} + +void print_events(void) { + icsneoc2_event_t* events[256] = {0}; + size_t events_count = 256; + for(size_t i = 0; i < events_count; ++i) { + icsneoc2_error_t res = icsneoc2_event_get(&events[i], NULL); + if(res != icsneoc2_error_success) { + (void)print_error_code("\tFailed to get events", res); + return; + } + if(events[i] == NULL) { + events_count = i; + break; + } + } + for(size_t i = 0; i < events_count; i++) { + char description[255] = {0}; + size_t description_length = 255; + icsneoc2_error_t res = icsneoc2_event_description_get(events[i], description, &description_length); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to get event description", res); + continue; + } + printf("\tEvent %zu: %s\n", i, description); + } + for(size_t i = 0; i < events_count; i++) { + icsneoc2_event_free(events[i]); + } + if(events_count > 0) { + printf("\tReceived %zu events\n", events_count); + } +} + +int main(void) { + /* Open the first available device */ + printf("Opening first available device...\n"); + icsneoc2_device_t* device = NULL; + icsneoc2_error_t res = icsneoc2_device_open_first(0, icsneoc2_open_options_default, &device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to open first device", res); + } + + /* Get a description of the opened device */ + char description[255] = {0}; + size_t description_length = 255; + res = icsneoc2_device_description_get(device, description, &description_length); + if(res != icsneoc2_error_success) { + icsneoc2_device_free(device); + return print_error_code("Failed to get device description", res); + } + printf("Opened device: %s\n", description); + + /* Wait for the device to disconnect */ + printf("Waiting for device to disconnect (unplug device from PC)...\n"); + for(;;) { + bool is_open = true; + res = icsneoc2_device_is_open(device, &is_open); + if(res != icsneoc2_error_success) { + print_events(); + icsneoc2_device_free(device); + return print_error_code("Failed to check open status", res); + } + if(!is_open) { + printf("Device disconnected!\n"); + break; + } + sleep_ms(500); + } + + /* Attempt to reconnect */ + uint32_t timeout_ms = 20000; // 20 second timeout + printf("Attempting to reconnect (%u second timeout)...\n", timeout_ms / 1000); + res = icsneoc2_device_reconnect(device, icsneoc2_open_options_default, timeout_ms); + if(res != icsneoc2_error_success) { + print_events(); + icsneoc2_device_free(device); + return print_error_code("Failed to reconnect", res); + } + + printf("Reconnected successfully!\n"); + + /* Verify by getting the description again */ + description_length = 255; + res = icsneoc2_device_description_get(device, description, &description_length); + if(res == icsneoc2_error_success) { + printf("Device: %s\n", description); + } + + /* Print any events */ + print_events(); + + /* Close the device */ + printf("Closing device...\n"); + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + icsneoc2_device_free(device); + return print_error_code("Failed to close device", res); + } + icsneoc2_device_free(device); + + printf("Done.\n"); + return 0; +} diff --git a/examples/c2/simple/src/main.c b/examples/c2/simple/src/main.c index aea3b2d..0445f3d 100644 --- a/examples/c2/simple/src/main.c +++ b/examples/c2/simple/src/main.c @@ -116,6 +116,7 @@ int main() { size_t description_length = 255; res = icsneoc2_device_info_description_get(cur, description, &description_length); if(res != icsneoc2_error_success) { + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get device description", res); }; printf("%.*s\n", (int)description_length, description); @@ -126,8 +127,15 @@ int main() { printf("\tDevice open options: 0x%x\n", options); printf("\tOpening device: %s...\n", description); icsneoc2_device_t* open_device = NULL; - res = icsneoc2_device_open(cur, options, &open_device); + res = icsneoc2_device_create(cur, &open_device); if(res != icsneoc2_error_success) { + icsneoc2_enumeration_free(found_devices); + return print_error_code("\tFailed to create device", res); + } + res = icsneoc2_device_open(open_device, options); + if(res != icsneoc2_error_success) { + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to open device", res); }; @@ -137,6 +145,8 @@ int main() { res = icsneoc2_device_timestamp_resolution_get(open_device, ×tamp_resolution); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get timestamp resolution", res); } printf("%uns\n", timestamp_resolution); @@ -146,6 +156,8 @@ int main() { res = icsneoc2_settings_baudrate_get(open_device, icsneoc2_netid_dwcan_01, &baudrate); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get baudrate", res); }; printf("%" PRIu64 "mbit/s\n", baudrate); @@ -155,6 +167,8 @@ int main() { res = icsneoc2_settings_canfd_baudrate_get(open_device, icsneoc2_netid_dwcan_01, &fd_baudrate); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get FD baudrate", res); }; printf("%" PRIu64 "mbit/s\n", fd_baudrate); @@ -165,6 +179,8 @@ int main() { res = icsneoc2_settings_baudrate_set(open_device, icsneoc2_netid_dwcan_01, baudrate); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to set baudrate", res); }; printf("Ok\n"); @@ -173,6 +189,8 @@ int main() { res = icsneoc2_settings_canfd_baudrate_set(open_device, icsneoc2_netid_dwcan_01, fd_baudrate); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to set FD baudrate", res); }; printf("Ok\n"); @@ -181,6 +199,8 @@ int main() { res = get_and_print_rtc(open_device); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get RTC", res); } // Set RTC @@ -189,6 +209,8 @@ int main() { res = icsneoc2_device_rtc_set(open_device, current_time); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to set RTC", res); } printf("Ok\n"); @@ -197,6 +219,8 @@ int main() { res = get_and_print_rtc(open_device); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get RTC", res); } // Go online, start acking traffic @@ -204,6 +228,8 @@ int main() { res = icsneoc2_device_go_online(open_device, true); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to go online", res); } // Redundant check to show how to check if the device is online, if the previous @@ -212,6 +238,8 @@ int main() { res = icsneoc2_device_is_online(open_device, &is_online); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to check if online", res); } printf("%s\n", is_online ? "Online" : "Offline"); @@ -219,6 +247,8 @@ int main() { res = transmit_can_messages(open_device); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to transmit CAN messages", res); } // Wait for the bus to collect some messages, requires an active bus to get messages @@ -231,7 +261,12 @@ int main() { for(size_t i = 0; i < message_count; ++i) { res = icsneoc2_device_message_get(open_device, &messages[i], 0); if(res != icsneoc2_error_success) { + for(size_t j = 0; j < i; ++j) { + icsneoc2_message_free(messages[j]); + } print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to get messages from device", res); }; if(messages[i] == NULL) { @@ -243,7 +278,12 @@ int main() { // Process the messages res = process_messages(messages, message_count); if(res != icsneoc2_error_success) { + for(size_t i = 0; i < message_count; ++i) { + icsneoc2_message_free(messages[i]); + } print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to process messages", res); } for(size_t i = 0; i < message_count; ++i) { @@ -254,11 +294,13 @@ int main() { res = icsneoc2_device_close(open_device); if(res != icsneoc2_error_success) { print_events(description); + icsneoc2_device_free(open_device); + icsneoc2_enumeration_free(found_devices); return print_error_code("\tFailed to close device", res); }; // Print device events print_events(description); - + icsneoc2_device_free(open_device); } icsneoc2_enumeration_free(found_devices); printf("\n"); @@ -285,6 +327,9 @@ void print_events(const char* device_description) { // no device filter, get all events icsneoc2_error_t res = icsneoc2_event_get(&events[i], NULL); if(res != icsneoc2_error_success) { + for(size_t j = 0; j < i; ++j) { + icsneoc2_event_free(events[j]); + } (void)print_error_code("\tFailed to get device events", res); return; } @@ -397,6 +442,7 @@ int transmit_can_messages(icsneoc2_device_t* device) { res += icsneoc2_message_can_props_set(message, &arb_id, &flags); res += icsneoc2_message_data_set(message, (uint8_t*)&counter, sizeof(counter)); if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); return print_error_code("\tFailed to modify message", res); } res = icsneoc2_device_message_transmit(device, message); diff --git a/include/icsneo/icsneoc2.h b/include/icsneo/icsneoc2.h index 20faeb8..61476e6 100644 --- a/include/icsneo/icsneoc2.h +++ b/include/icsneo/icsneoc2.h @@ -42,6 +42,8 @@ typedef enum _icsneoc2_error_t { icsneoc2_error_script_clear_failed, // Failed to clear script icsneoc2_error_script_upload_failed, // Failed to upload coremini script icsneoc2_error_script_load_prepare_failed, // Failed to prepare script load + icsneoc2_error_close_failed, // Failed to close device + icsneoc2_error_reconnect_failed, // Failed to reconnect to device // NOTE: Any new values added here should be updated in icsneoc2_error_code_get icsneoc2_error_maxsize } _icsneoc2_error_t; @@ -166,29 +168,31 @@ icsneoc2_error_t icsneoc2_device_is_valid(const icsneoc2_device_t* device); icsneoc2_error_t icsneoc2_device_is_open(const icsneoc2_device_t* device, bool* is_open); /** - * Check if a device is disconnected. - * - * @param[in] device The device to check. - * @param[out] is_disconnected true if the device is disconnected, false otherwise - * - * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + * Create a device handle from an enumeration node without opening it. Need to call icsneoc2_device_free() to free the handle when finished. + * The device can then be opened with icsneoc2_device_open(). + * + * @param[in] device_info The device info node to create from. + * @param[out] device Pointer to receive the created device handle. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_device otherwise. + * + * @see icsneoc2_device_open icsneoc2_device_free */ -icsneoc2_error_t icsneoc2_device_is_disconnected(const icsneoc2_device_t* device, bool* is_disconnected); +icsneoc2_error_t icsneoc2_device_create(const icsneoc2_device_info_t* device_info, icsneoc2_device_t** device); /** * Open a device from an enumeration node. * * After a successful call, icsneoc2_device_close() must be called to close the device. * - * @param[in] device_info The device info node to open. + * @param[in] device Pointer to the device to open. * @param[in] options Open options (e.g. icsneoc2_open_options_default). - * @param[out] device Pointer to receive the opened device handle. * * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_open_failed otherwise. * - * @see icsneoc2_device_close + * @see icsneoc2_device_close icsneoc2_device_free */ -icsneoc2_error_t icsneoc2_device_open(const icsneoc2_device_info_t* device_info, icsneoc2_open_options_t options, icsneoc2_device_t** device); +icsneoc2_error_t icsneoc2_device_open(const icsneoc2_device_t* device, icsneoc2_open_options_t options); /** * Convenience: enumerate, find by serial, open, and free enumeration. @@ -199,13 +203,14 @@ icsneoc2_error_t icsneoc2_device_open(const icsneoc2_device_info_t* device_info, * * @return icsneoc2_error_t icsneoc2_error_success if successful. * - * @see icsneoc2_device_close + * @see icsneoc2_device_close icsneoc2_device_free */ icsneoc2_error_t icsneoc2_device_open_serial(const char* serial, icsneoc2_open_options_t options, icsneoc2_device_t** device); /** * Convenience: enumerate, find first available device (optionally filtered by type), open, and free enumeration. * Pass 0 for device_type to match any device. + * * * @param[in] device_type The device type to match, or 0 for any. * @param[in] options Open options (e.g. icsneoc2_open_options_default). @@ -213,24 +218,46 @@ icsneoc2_error_t icsneoc2_device_open_serial(const char* serial, icsneoc2_open_o * * @return icsneoc2_error_t icsneoc2_error_success if successful. * - * @see icsneoc2_device_close + * @see icsneoc2_device_close icsneoc2_device_free */ icsneoc2_error_t icsneoc2_device_open_first(icsneoc2_devicetype_t device_type, icsneoc2_open_options_t options, icsneoc2_device_t** device); +/** + * Reconnect to a device. This is useful if the device was disconnected and reconnected, or if the connection was lost for some reason. + * + * @param[in] device The device to reconnect. + * @param[in] options Open options (e.g. icsneoc2_open_options_default). + * @param[in] timeout_ms The timeout in milliseconds to keep trying to reconnect before giving up. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_reconnect_failed if the timeout was reached without reconnecting, or icsneoc2_device_is_valid() errors otherwise. + */ +icsneoc2_error_t icsneoc2_device_reconnect(icsneoc2_device_t* device, icsneoc2_open_options_t options, uint32_t timeout_ms); + /** * Close a connection to a previously opened device. * * After a successful call to icsneoc2_device_open(), this function must be called to close the device. - * An already closed device will still succeed. All messages and events related to the device will be freed. + * An already closed device will still succeed. The device handle must be freed with icsneoc2_device_free() when finished. * * @param[in,out] device Pointer to the device to close. * * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_device_is_valid() errors otherwise. * - * @see icsneoc2_device_open icsneoc2_device_is_valid + * @see icsneoc2_device_open icsneoc2_device_is_valid icsneoc2_device_free */ icsneoc2_error_t icsneoc2_device_close(icsneoc2_device_t* device); +/** + * Free a device handle created by icsneoc2_device_create(). Device should be closed before freeing. + * + * @param[in] device The device handle to free. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_device_is_valid() errors otherwise. + * + * @see icsneoc2_device_create icsneoc2_device_close + */ +icsneoc2_error_t icsneoc2_device_free(icsneoc2_device_t* device); + /** * Get the description of a device * diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index 49d6591..abc9295 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -143,7 +143,10 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_info_description_get(NULL, placeholderStr, &placeholderSizeT)); // Test open/close with NULL parameters - ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_open(NULL, 0, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_create(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_free(NULL)); + + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_open(NULL, 0)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_open_serial(NULL, 0, NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_open_first(0, 0, NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_close(NULL)); @@ -154,7 +157,6 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_is_online_supported(NULL, &placeholderBool)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_is_valid(NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_is_open(NULL, &placeholderBool)); - ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_is_disconnected(NULL, &placeholderBool)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_rtc_get(NULL, (int64_t *)&placeholderUnsignedInteger64)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_rtc_set(NULL, 0));