diff --git a/api/icsneoc2/icsneoc2messages.cpp b/api/icsneoc2/icsneoc2messages.cpp index 4d1124b..99b98d8 100644 --- a/api/icsneoc2/icsneoc2messages.cpp +++ b/api/icsneoc2/icsneoc2messages.cpp @@ -216,3 +216,133 @@ icsneoc2_error_t icsneoc2_message_is_can(icsneoc2_message_t* message, bool* is_c return icsneoc2_error_success; } + +icsneoc2_error_t icsneoc2_message_is_lin(icsneoc2_message_t* message, bool* is_lin) { + if(!message || !is_lin) { + return icsneoc2_error_invalid_parameters; + } + *is_lin = std::dynamic_pointer_cast(message->message) != nullptr; + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_create(icsneoc2_message_t** message, uint8_t id) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + try { + *message = new icsneoc2_message_t; + (*message)->message = std::make_shared(id); + } catch(const std::bad_alloc&) { + return icsneoc2_error_out_of_memory; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_props_get(const icsneoc2_message_t* message, + uint8_t* id, uint8_t* protected_id, uint8_t* checksum, + icsneoc2_lin_msg_type_t* msg_type, bool* is_enhanced_checksum) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + if(id) { + *id = lin_msg->ID; + } + if(protected_id) { + *protected_id = lin_msg->protectedID; + } + if(checksum) { + *checksum = lin_msg->checksum; + } + if(msg_type) { + *msg_type = static_cast(lin_msg->linMsgType); + } + if(is_enhanced_checksum) { + *is_enhanced_checksum = lin_msg->isEnhancedChecksum; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_props_set(icsneoc2_message_t* message, + const uint8_t* id, const uint8_t* checksum, + const icsneoc2_lin_msg_type_t* msg_type, const bool* is_enhanced_checksum) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + if(id) { + lin_msg->ID = *id & 0x3Fu; + lin_msg->protectedID = lin_msg->calcProtectedID(lin_msg->ID); + } + if(checksum) { + lin_msg->checksum = *checksum; + } + if(msg_type) { + lin_msg->linMsgType = static_cast(*msg_type); + } + if(is_enhanced_checksum) { + lin_msg->isEnhancedChecksum = *is_enhanced_checksum; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_err_flags_get(const icsneoc2_message_t* message, icsneoc2_lin_err_flags_t* err_flags) { + if(!message || !err_flags) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + *err_flags = 0; + if(lin_msg->errFlags.ErrRxBreakOnly) *err_flags |= ICSNEOC2_LIN_ERR_RX_BREAK_ONLY; + if(lin_msg->errFlags.ErrRxBreakSyncOnly) *err_flags |= ICSNEOC2_LIN_ERR_RX_BREAK_SYNC_ONLY; + if(lin_msg->errFlags.ErrTxRxMismatch) *err_flags |= ICSNEOC2_LIN_ERR_TX_RX_MISMATCH; + if(lin_msg->errFlags.ErrRxBreakNotZero) *err_flags |= ICSNEOC2_LIN_ERR_RX_BREAK_NOT_ZERO; + if(lin_msg->errFlags.ErrRxBreakTooShort) *err_flags |= ICSNEOC2_LIN_ERR_RX_BREAK_TOO_SHORT; + if(lin_msg->errFlags.ErrRxSyncNot55) *err_flags |= ICSNEOC2_LIN_ERR_RX_SYNC_NOT_55; + if(lin_msg->errFlags.ErrRxDataLenOver8) *err_flags |= ICSNEOC2_LIN_ERR_RX_DATA_LEN_OVER_8; + if(lin_msg->errFlags.ErrFrameSync) *err_flags |= ICSNEOC2_LIN_ERR_FRAME_SYNC; + if(lin_msg->errFlags.ErrFrameMessageID) *err_flags |= ICSNEOC2_LIN_ERR_FRAME_MESSAGE_ID; + if(lin_msg->errFlags.ErrFrameResponderData) *err_flags |= ICSNEOC2_LIN_ERR_FRAME_RESPONDER_DATA; + if(lin_msg->errFlags.ErrChecksumMatch) *err_flags |= ICSNEOC2_LIN_ERR_CHECKSUM_MATCH; + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_status_flags_get(const icsneoc2_message_t* message, icsneoc2_lin_status_flags_t* status_flags) { + if(!message || !status_flags) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + *status_flags = 0; + if(lin_msg->statusFlags.TxChecksumEnhanced) *status_flags |= ICSNEOC2_LIN_STATUS_TX_CHECKSUM_ENHANCED; + if(lin_msg->statusFlags.TxCommander) *status_flags |= ICSNEOC2_LIN_STATUS_TX_COMMANDER; + if(lin_msg->statusFlags.TxResponder) *status_flags |= ICSNEOC2_LIN_STATUS_TX_RESPONDER; + if(lin_msg->statusFlags.TxAborted) *status_flags |= ICSNEOC2_LIN_STATUS_TX_ABORTED; + if(lin_msg->statusFlags.UpdateResponderOnce) *status_flags |= ICSNEOC2_LIN_STATUS_UPDATE_RESPONDER_ONCE; + if(lin_msg->statusFlags.HasUpdatedResponderOnce) *status_flags |= ICSNEOC2_LIN_STATUS_HAS_UPDATED_RESPONDER_ONCE; + if(lin_msg->statusFlags.BusRecovered) *status_flags |= ICSNEOC2_LIN_STATUS_BUS_RECOVERED; + if(lin_msg->statusFlags.BreakOnly) *status_flags |= ICSNEOC2_LIN_STATUS_BREAK_ONLY; + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_calc_checksum(icsneoc2_message_t* message) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + LINMessage::calcChecksum(*lin_msg); + return icsneoc2_error_success; +} diff --git a/communication/message/transmitmessage.cpp b/communication/message/transmitmessage.cpp index faae6d1..d37ada7 100644 --- a/communication/message/transmitmessage.cpp +++ b/communication/message/transmitmessage.cpp @@ -102,10 +102,71 @@ static std::vector EncodeFromMessageCAN(std::shared_ptr frame, c return encoded; } -static std::vector EncodeFromMessageLIN(std::shared_ptr /* frame */, const device_eventhandler_t& report) { - // TODO - report(APIEvent::Type::UnsupportedTXNetwork, APIEvent::Severity::Error); - return {}; +static std::vector EncodeFromMessageLIN(std::shared_ptr frame, const device_eventhandler_t& report) { + auto linmsg = std::dynamic_pointer_cast(frame); + if(!linmsg) { + report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); + return {}; + } + if(linmsg->linMsgType == LINMessage::Type::NOT_SET) { + report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); + return {}; + } + + size_t dataLen = std::min(8, linmsg->data.size()); + std::vector encoded; + encoded.resize(sizeof(TransmitMessage)); + + TransmitMessage* const msg = (TransmitMessage*)encoded.data(); + HardwareLINPacket* const linpacket = (HardwareLINPacket*)(msg->commonHeader); + memset(linpacket, 0, sizeof(HardwareLINPacket)); + + // Protected ID and ID + linmsg->protectedID = linmsg->calcProtectedID(linmsg->ID); + linpacket->CoreMiniBitsLIN.ID = linmsg->protectedID & 0x3F; + + // LIN message type flags + switch(linmsg->linMsgType) { + case LINMessage::Type::LIN_COMMANDER_MSG: + linpacket->CoreMiniBitsLIN.TXCommander = 1; + break; + case LINMessage::Type::LIN_HEADER_ONLY: + linpacket->CoreMiniBitsLIN.TXCommander = 1; + break; + case LINMessage::Type::LIN_UPDATE_RESPONDER: + linpacket->CoreMiniBitsLIN.TXResponder = 1; + break; + case LINMessage::Type::LIN_BREAK_ONLY: + linpacket->CoreMiniBitsLIN.BreakOnly = 1; + break; + default: + break; + } + + // Enhanced checksum + linpacket->CoreMiniBitsLIN.TxChkSumEnhanced = linmsg->isEnhancedChecksum ? 1 : 0; + + // Data and checksum + bool hasData = (linmsg->linMsgType == LINMessage::Type::LIN_COMMANDER_MSG || + linmsg->linMsgType == LINMessage::Type::LIN_UPDATE_RESPONDER) && dataLen > 0; + + if(hasData) { + // len includes data bytes + 1 checksum byte + linpacket->CoreMiniBitsLIN.len = static_cast(dataLen + 1); + std::copy(linmsg->data.begin(), linmsg->data.begin() + dataLen, linpacket->data); + // Checksum goes after data: in data[dataLen] if < 8, otherwise in LINByte9 + if(dataLen < 8) + linpacket->data[dataLen] = linmsg->checksum; + else + linpacket->CoreMiniBitsLIN.LINByte9 = linmsg->checksum; + } else { + linpacket->CoreMiniBitsLIN.len = 0; + } + + // Description/stats and network + linpacket->stats = linmsg->description; + + return encoded; } std::vector TransmitMessage::EncodeFromMessage(std::shared_ptr frame, uint32_t client_id, const device_eventhandler_t& report) { @@ -127,6 +188,8 @@ std::vector TransmitMessage::EncodeFromMessage(std::shared_ptr f return result; } // common fields + if(result.empty()) + return result; TransmitMessage* const msg = (TransmitMessage*)result.data(); msg->options.clientId = client_id; msg->options.networkId = static_cast(frame->network.getNetID()); diff --git a/docs/icsneoc2/examples.rst b/docs/icsneoc2/examples.rst index 377548d..336e991 100644 --- a/docs/icsneoc2/examples.rst +++ b/docs/icsneoc2/examples.rst @@ -33,3 +33,19 @@ Device Info .. literalinclude:: ../../examples/c2/device_info/src/main.c :language: c + +LIN +=== + +:download:`Download example <../../examples/c2/lin/src/main.c>` + +.. literalinclude:: ../../examples/c2/lin/src/main.c + :language: c + +LIN Transmit +============ + +:download:`Download example <../../examples/c2/lin_transmit/src/main.c>` + +.. literalinclude:: ../../examples/c2/lin_transmit/src/main.c + :language: c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b819cb9..c4190ca 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,6 +6,8 @@ option(LIBICSNEO_BUILD_C2_READ_MESSAGES_EXAMPLE "Build the C2 read messages exam 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_C2_DEVICE_INFO_EXAMPLE "Build the C2 device info example." ON) +option(LIBICSNEO_BUILD_C2_LIN_EXAMPLE "Build the C2 LIN example." ON) +option(LIBICSNEO_BUILD_C2_LIN_TRANSMIT_EXAMPLE "Build the C2 LIN transmit 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) @@ -57,6 +59,14 @@ if(LIBICSNEO_BUILD_C2_DEVICE_INFO_EXAMPLE) add_subdirectory(c2/device_info) endif() +if(LIBICSNEO_BUILD_C2_LIN_EXAMPLE) + add_subdirectory(c2/lin) +endif() + +if(LIBICSNEO_BUILD_C2_LIN_TRANSMIT_EXAMPLE) + add_subdirectory(c2/lin_transmit) +endif() + if(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE) add_subdirectory(cpp/simple) endif() diff --git a/examples/c2/lin/CMakeLists.txt b/examples/c2/lin/CMakeLists.txt new file mode 100644 index 0000000..7910d14 --- /dev/null +++ b/examples/c2/lin/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-lin-example src/main.c) +target_link_libraries(libicsneoc2-lin-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-lin-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/lin/src/main.c b/examples/c2/lin/src/main.c new file mode 100644 index 0000000..94f30a6 --- /dev/null +++ b/examples/c2/lin/src/main.c @@ -0,0 +1,311 @@ +/* Note: This example requires LIN 1 and LIN 2 channels to be connected on the device */ + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include + +void sleep_ms(uint32_t ms) { +#ifdef _WIN32 + Sleep(ms); +#else + sleep(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(const char* device_description) { + icsneoc2_event_t* events[1024] = {0}; + size_t events_count = 1024; + for(size_t i = 0; i < events_count; ++i) { + 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; + } + if(events[i] == NULL) { + events_count = i; + break; + } + } + for(size_t i = 0; i < events_count; i++) { + char event_description[255] = {0}; + size_t event_description_length = 255; + icsneoc2_error_t res = icsneoc2_event_description_get(events[i], event_description, &event_description_length); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to get event description", res); + continue; + } + printf("\t%s: Event %zu: %s\n", device_description, i, event_description); + } + for(size_t i = 0; i < events_count; i++) { + icsneoc2_event_free(events[i]); + } + printf("\t%s: Received %zu events\n", device_description, events_count); +} + +void process_lin_messages(icsneoc2_message_t** messages, size_t count) { + for(size_t i = 0; i < count; i++) { + bool is_lin = false; + icsneoc2_error_t res = icsneoc2_message_is_lin(messages[i], &is_lin); + if(res != icsneoc2_error_success || !is_lin) + continue; + + uint8_t id = 0; + uint8_t protected_id = 0; + uint8_t checksum = 0; + icsneoc2_lin_msg_type_t msg_type = 0; + bool is_enhanced_checksum = false; + res = icsneoc2_message_lin_props_get(messages[i], &id, &protected_id, &checksum, &msg_type, &is_enhanced_checksum); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to get LIN properties", res); + continue; + } + + icsneoc2_netid_t netid = 0; + icsneoc2_message_netid_get(messages[i], &netid); + char netid_name[128] = {0}; + size_t netid_name_length = 128; + icsneoc2_netid_name_get(netid, netid_name, &netid_name_length); + + uint8_t data[64] = {0}; + size_t data_length = 64; + icsneoc2_message_data_get(messages[i], data, &data_length); + + icsneoc2_lin_err_flags_t err_flags = 0; + icsneoc2_message_lin_err_flags_get(messages[i], &err_flags); + + printf("\t%s RX | ID: 0x%02x | Protected ID: 0x%02x\n", netid_name, id, protected_id); + printf("\tData: ["); + for(size_t x = 0; x < data_length; x++) { + printf(" 0x%02x", data[x]); + } + printf(" ]\n"); + printf("\tChecksum type: %s\n", is_enhanced_checksum ? "Enhanced" : "Classic"); + printf("\tChecksum: 0x%02x\n", checksum); + printf("\tChecksum valid: %s\n\n", (!(err_flags & ICSNEOC2_LIN_ERR_CHECKSUM_MATCH)) ? "yes" : "no"); + } +} + +int main() { + /* 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("\tFailed to open first device", res); + } + + 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_close(device); + return print_error_code("\tFailed to get device description", res); + } + printf("\tOpened device: %s\n\n", description); + + /* Configure LIN settings */ + int64_t baud = 19200; + + printf("Enable LIN 01 commander resistor... "); + res = icsneoc2_settings_commander_resistor_set(device, icsneoc2_netid_lin_01, true); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Disable LIN 02 commander resistor... "); + res = icsneoc2_settings_commander_resistor_set(device, icsneoc2_netid_lin_02, false); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Setting LIN 01 baudrate to %" PRId64 " bit/s... ", baud); + res = icsneoc2_settings_baudrate_set(device, icsneoc2_netid_lin_01, baud); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Setting LIN 02 baudrate to %" PRId64 " bit/s... ", baud); + res = icsneoc2_settings_baudrate_set(device, icsneoc2_netid_lin_02, baud); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Setting LIN 01 mode to NORMAL... "); + res = icsneoc2_settings_lin_mode_set(device, icsneoc2_netid_lin_01, icsneoc2_lin_mode_normal); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Setting LIN 02 mode to NORMAL... "); + res = icsneoc2_settings_lin_mode_set(device, icsneoc2_netid_lin_02, icsneoc2_lin_mode_normal); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + printf("Applying settings... "); + res = icsneoc2_settings_apply(device); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to apply settings", res); + } + printf("OK\n"); + + printf("Getting LIN 01 baudrate... "); + int64_t read_baud = 0; + res = icsneoc2_settings_baudrate_get(device, icsneoc2_netid_lin_01, &read_baud); + if(res == icsneoc2_error_success) + printf("OK, %" PRId64 " bit/s\n", read_baud); + else + printf("FAIL\n"); + + printf("Getting LIN 02 baudrate... "); + res = icsneoc2_settings_baudrate_get(device, icsneoc2_netid_lin_02, &read_baud); + if(res == icsneoc2_error_success) + printf("OK, %" PRId64 " bit/s\n\n", read_baud); + else + printf("FAIL\n\n"); + + /* Transmit a LIN responder data update on LIN 02 */ + printf("Transmitting a LIN 02 responder data frame... "); + { + icsneoc2_message_t* msg = NULL; + res = icsneoc2_message_lin_create(&msg, 0x11); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to create LIN message", res); + } + icsneoc2_lin_msg_type_t msg_type = icsneoc2_lin_msg_type_update_responder; + res = icsneoc2_message_lin_props_set(msg, NULL, NULL, &msg_type, NULL); + uint8_t data[] = {0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22, 0x33, 0x44}; + res += icsneoc2_message_data_set(msg, data, sizeof(data)); + res += icsneoc2_message_netid_set(msg, icsneoc2_netid_lin_02); + res += icsneoc2_message_lin_calc_checksum(msg); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(msg); + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to set LIN message properties", res); + } + res = icsneoc2_device_message_transmit(device, msg); + icsneoc2_message_free(msg); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to transmit LIN responder message", res); + } + printf("OK\n"); + } + + /* Transmit a LIN commander header on LIN 01 */ + printf("Transmitting a LIN 01 commander header... "); + { + icsneoc2_message_t* msg = NULL; + res = icsneoc2_message_lin_create(&msg, 0x11); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to create LIN message", res); + } + icsneoc2_lin_msg_type_t msg_type = icsneoc2_lin_msg_type_header_only; + res = icsneoc2_message_lin_props_set(msg, NULL, NULL, &msg_type, NULL); + res += icsneoc2_message_netid_set(msg, icsneoc2_netid_lin_01); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(msg); + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to set LIN message properties", res); + } + res = icsneoc2_device_message_transmit(device, msg); + icsneoc2_message_free(msg); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to transmit LIN header", res); + } + printf("OK\n\n"); + } + + sleep_ms(100); + + /* Transmit a LIN commander message with data on LIN 01 */ + printf("Transmitting a LIN 01 commander frame with data... "); + { + icsneoc2_message_t* msg = NULL; + res = icsneoc2_message_lin_create(&msg, 0x22); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to create LIN message", res); + } + icsneoc2_lin_msg_type_t msg_type = icsneoc2_lin_msg_type_commander_msg; + bool enhanced = true; + res = icsneoc2_message_lin_props_set(msg, NULL, NULL, &msg_type, &enhanced); + uint8_t data[] = {0x11, 0x22, 0x33, 0x44, 0xaa, 0xbb, 0xcc, 0xdd}; + res += icsneoc2_message_data_set(msg, data, sizeof(data)); + res += icsneoc2_message_netid_set(msg, icsneoc2_netid_lin_01); + res += icsneoc2_message_lin_calc_checksum(msg); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(msg); + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to set LIN message properties", res); + } + res = icsneoc2_device_message_transmit(device, msg); + icsneoc2_message_free(msg); + if(res != icsneoc2_error_success) { + print_events(description); + icsneoc2_device_close(device); + return print_error_code("\tFailed to transmit LIN commander message", res); + } + printf("OK\n\n"); + } + + sleep_ms(100); + + /* Read back any received messages and display LIN frames */ + icsneoc2_message_t* messages[2048] = {0}; + size_t message_count = 2048; + printf("Getting messages...\n"); + for(size_t i = 0; i < message_count; ++i) { + res = icsneoc2_device_message_get(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_close(device); + return print_error_code("\tFailed to get messages", res); + } + if(messages[i] == NULL) { + message_count = i; + break; + } + } + printf("\tReceived %zu messages\n", message_count); + process_lin_messages(messages, message_count); + for(size_t i = 0; i < message_count; ++i) { + icsneoc2_message_free(messages[i]); + } + + /* Cleanup */ + print_events(description); + printf("Closing device... "); + res = icsneoc2_device_close(device); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + + return 0; +} diff --git a/examples/c2/lin_transmit/CMakeLists.txt b/examples/c2/lin_transmit/CMakeLists.txt new file mode 100644 index 0000000..474bc4f --- /dev/null +++ b/examples/c2/lin_transmit/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-lin-transmit-example src/main.c) +target_link_libraries(libicsneoc2-lin-transmit-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-lin-transmit-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/lin_transmit/src/main.c b/examples/c2/lin_transmit/src/main.c new file mode 100644 index 0000000..791d8f3 --- /dev/null +++ b/examples/c2/lin_transmit/src/main.c @@ -0,0 +1,276 @@ +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include +#include +#include + +void sleep_ms(uint32_t ms) { +#ifdef _WIN32 + Sleep(ms); +#else + sleep(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; +} + +int read_int(const char* prompt, int min_val, int max_val) { + int value; + while(1) { + printf("%s", prompt); + if(scanf("%d", &value) != 1) { + /* Clear invalid input */ + int c; + while((c = getchar()) != '\n' && c != EOF) {} + printf("Invalid input, try again.\n"); + continue; + } + if(value < min_val || value > max_val) { + printf("Please enter a value between %d and %d.\n", min_val, max_val); + continue; + } + return value; + } +} + +int read_hex(const char* prompt, int min_val, int max_val) { + int value; + while(1) { + printf("%s", prompt); + if(scanf("%x", &value) != 1) { + /* Clear invalid input */ + int c; + while((c = getchar()) != '\n' && c != EOF) {} + printf("Invalid input, try again.\n"); + continue; + } + if(value < min_val || value > max_val) { + printf("Please enter a value between 0x%X and 0x%X.\n", min_val, max_val); + continue; + } + return value; + } +} + +int main() { + icsneoc2_error_t res; + + /* ===== Device Selection ===== */ + printf("Searching for devices...\n"); + icsneoc2_device_info_t* found_devices = NULL; + res = icsneoc2_device_enumerate(0, &found_devices); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to enumerate devices", res); + } + if(!found_devices) { + printf("No devices found.\n"); + return 1; + } + + /* Count and display devices */ + int device_count = 0; + for(icsneoc2_device_info_t* cur = found_devices; cur; cur = icsneoc2_device_info_next(cur)) { + char desc[128] = {0}; + size_t desc_len = sizeof(desc); + icsneoc2_device_info_description_get(cur, desc, &desc_len); + printf(" [%d] %s\n", device_count + 1, desc); + device_count++; + } + + int device_choice = read_int("Select device: ", 1, device_count); + + /* Find the selected device_info node */ + icsneoc2_device_info_t* selected_info = found_devices; + for(int i = 1; i < device_choice; i++) { + selected_info = icsneoc2_device_info_next(selected_info); + } + + /* Open the selected device */ + icsneoc2_device_t* device = NULL; + res = icsneoc2_device_create(selected_info, &device); + if(res != icsneoc2_error_success) { + icsneoc2_enumeration_free(found_devices); + return print_error_code("Failed to create device from device info", res); + } + res = icsneoc2_device_open(device, icsneoc2_open_options_default); + icsneoc2_enumeration_free(found_devices); + if(res != icsneoc2_error_success) { + icsneoc2_device_free(device); + return print_error_code("Failed to open device", res); + } + + char description[128] = {0}; + size_t description_length = sizeof(description); + icsneoc2_device_description_get(device, description, &description_length); + printf("Opened device: %s\n\n", description); + + /* ===== LIN Network Selection ===== */ + /* Get all supported TX networks */ + size_t tx_net_count = 0; + res = icsneoc2_device_supported_tx_networks_get(device, NULL, &tx_net_count); + if(res != icsneoc2_error_success || tx_net_count == 0) { + printf("No supported TX networks.\n"); + icsneoc2_device_close(device); + return 1; + } + + icsneoc2_netid_t* tx_networks = (icsneoc2_netid_t*)malloc(tx_net_count * sizeof(icsneoc2_netid_t)); + if(!tx_networks) { + printf("Out of memory.\n"); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return 1; + } + res = icsneoc2_device_supported_tx_networks_get(device, tx_networks, &tx_net_count); + if(res != icsneoc2_error_success) { + free(tx_networks); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return print_error_code("Failed to get TX networks", res); + } + + /* Filter for LIN networks */ + icsneoc2_netid_t lin_networks[64] = {0}; + char lin_names[64][128] = {{0}}; + size_t lin_count = 0; + for(size_t i = 0; i < tx_net_count && lin_count < 64; i++) { + icsneoc2_message_t* tmp = NULL; + res = icsneoc2_message_lin_create(&tmp, 0); + if(res != icsneoc2_error_success) continue; + res = icsneoc2_message_netid_set(tmp, tx_networks[i]); + if(res != icsneoc2_error_success) { icsneoc2_message_free(tmp); continue; } + icsneoc2_network_type_t ntype = 0; + res = icsneoc2_message_network_type_get(tmp, &ntype); + icsneoc2_message_free(tmp); + if(res != icsneoc2_error_success) continue; + if(ntype == icsneoc2_network_type_lin) { + lin_networks[lin_count] = tx_networks[i]; + size_t name_len = 128; + icsneoc2_netid_name_get(tx_networks[i], lin_names[lin_count], &name_len); + lin_count++; + } + } + free(tx_networks); + + if(lin_count == 0) { + printf("No LIN networks available on this device.\n"); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return 1; + } + + printf("Available LIN networks:\n"); + for(size_t i = 0; i < lin_count; i++) { + printf(" [%zu] %s\n", i + 1, lin_names[i]); + } + + int lin_choice = read_int("Select LIN network: ", 1, (int)lin_count); + icsneoc2_netid_t selected_netid = lin_networks[lin_choice - 1]; + printf("Selected: %s\n\n", lin_names[lin_choice - 1]); + + /* ===== Commander / Responder Selection ===== */ + printf("Message type:\n"); + printf(" [1] Commander frame\n"); + printf(" [2] Responder frame (update responder + header only)\n"); + int type_choice = read_int("Select message type: ", 1, 2); + bool is_commander = (type_choice == 1); + printf("Selected: %s\n\n", is_commander ? "Commander" : "Responder"); + + uint8_t id_choice = (uint8_t)read_hex("Select LIN ID (0x00-0x3F): ", 0, 0x3F); + printf("Selected: 0x%02X\n\n", id_choice); + + /* ===== Configure LIN ===== */ + printf("Configuring %s... ", lin_names[lin_choice - 1]); + res = icsneoc2_settings_commander_resistor_set(device, selected_netid, is_commander); + res += icsneoc2_settings_baudrate_set(device, selected_netid, 19200); + res += icsneoc2_settings_lin_mode_set(device, selected_netid, icsneoc2_lin_mode_normal); + res += icsneoc2_settings_apply(device); + if(res != icsneoc2_error_success) { + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return print_error_code("Failed to configure LIN", res); + } + printf("OK\n\n"); + + /* ===== Transmit Loop ===== */ + printf("Transmitting on %s every second for 10 seconds...\n", lin_names[lin_choice - 1]); + uint8_t counter = 0; + for(int i = 0; i < 10; i++) { + icsneoc2_message_t* msg = NULL; + res = icsneoc2_message_lin_create(&msg, id_choice); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to create LIN message", res); + break; + } + + uint8_t data[] = {counter, counter + 1, counter + 2, counter + 3, 0xAA, 0xBB, 0xCC, 0xDD}; + bool enhanced = true; + + if(is_commander) { + /* Commander: send header-only frame to poll the bus */ + icsneoc2_lin_msg_type_t msg_type = icsneoc2_lin_msg_type_header_only; + res = icsneoc2_message_lin_props_set(msg, NULL, NULL, &msg_type, &enhanced); + res += icsneoc2_message_netid_set(msg, selected_netid); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(msg); + print_error_code("\tFailed to set commander properties", res); + break; + } + res = icsneoc2_device_message_transmit(device, msg); + icsneoc2_message_free(msg); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to transmit header", res); + break; + } + } else { + /* Responder: update the responder table with new data */ + icsneoc2_lin_msg_type_t msg_type = icsneoc2_lin_msg_type_update_responder; + res = icsneoc2_message_lin_props_set(msg, NULL, NULL, &msg_type, &enhanced); + res += icsneoc2_message_data_set(msg, data, sizeof(data)); + res += icsneoc2_message_lin_calc_checksum(msg); + res += icsneoc2_message_netid_set(msg, selected_netid); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(msg); + print_error_code("\tFailed to update responder", res); + break; + } + res = icsneoc2_device_message_transmit(device, msg); + icsneoc2_message_free(msg); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to transmit responder update", res); + break; + } + } + printf("[%2d/10] Transmitted %s msg ID=0x%02X, counter=%u\n", + i + 1, is_commander ? "commander" : "responder", id_choice, counter); + counter += 4; + sleep_ms(1000); + } + + /* Cleanup */ + printf("\nClosing device... "); + res = icsneoc2_device_close(device); + printf("%s\n", res == icsneoc2_error_success ? "OK" : "FAIL"); + icsneoc2_device_free(device); + + return 0; +} diff --git a/include/icsneo/communication/message/linmessage.h b/include/icsneo/communication/message/linmessage.h index 683fc1e..06111a5 100644 --- a/include/icsneo/communication/message/linmessage.h +++ b/include/icsneo/communication/message/linmessage.h @@ -1,6 +1,8 @@ #ifndef __LINMESSAGE_H_ #define __LINMESSAGE_H_ +#include "icsneo/icsneoc2messages.h" + #ifdef __cplusplus #include "icsneo/communication/message/message.h" @@ -36,14 +38,14 @@ struct LINStatusFlags { class LINMessage : public Frame { public: - enum class Type : uint8_t { - NOT_SET = 0, - LIN_COMMANDER_MSG, - LIN_HEADER_ONLY, - LIN_BREAK_ONLY, - LIN_SYNC_ONLY, - LIN_UPDATE_RESPONDER, - LIN_ERROR + enum class Type : icsneoc2_lin_msg_type_t { + NOT_SET = icsneoc2_lin_msg_type_not_set, + LIN_COMMANDER_MSG = icsneoc2_lin_msg_type_commander_msg, + LIN_HEADER_ONLY = icsneoc2_lin_msg_type_header_only, + LIN_BREAK_ONLY = icsneoc2_lin_msg_type_break_only, + LIN_SYNC_ONLY = icsneoc2_lin_msg_type_sync_only, + LIN_UPDATE_RESPONDER = icsneoc2_lin_msg_type_update_responder, + LIN_ERROR = icsneoc2_lin_msg_type_error }; static void calcChecksum(LINMessage& message); diff --git a/include/icsneo/icsneoc2messages.h b/include/icsneo/icsneoc2messages.h index f19f6ad..5e9b745 100644 --- a/include/icsneo/icsneoc2messages.h +++ b/include/icsneo/icsneoc2messages.h @@ -171,6 +171,139 @@ icsneoc2_error_t icsneoc2_message_is_frame(icsneoc2_message_t* message, bool* is */ icsneoc2_error_t icsneoc2_message_is_can(icsneoc2_message_t* message, bool* is_can); +/** + * Check if a message is a LIN message + * + * @param[in] message The message to check. + * @param[out] is_lin Pointer to a bool to copy the LIN status of the message into. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_message_is_lin(icsneoc2_message_t* message, bool* is_lin); + +// ---- LIN Message Types ---- + +typedef enum _icsneoc2_lin_msg_type_t { + icsneoc2_lin_msg_type_not_set = 0, + icsneoc2_lin_msg_type_commander_msg = 1, + icsneoc2_lin_msg_type_header_only = 2, + icsneoc2_lin_msg_type_break_only = 3, + icsneoc2_lin_msg_type_sync_only = 4, + icsneoc2_lin_msg_type_update_responder = 5, + icsneoc2_lin_msg_type_error = 6, +} _icsneoc2_lin_msg_type_t; + +typedef uint8_t icsneoc2_lin_msg_type_t; + +// LIN error flags bitmask +#define ICSNEOC2_LIN_ERR_RX_BREAK_ONLY 0x0001 +#define ICSNEOC2_LIN_ERR_RX_BREAK_SYNC_ONLY 0x0002 +#define ICSNEOC2_LIN_ERR_TX_RX_MISMATCH 0x0004 +#define ICSNEOC2_LIN_ERR_RX_BREAK_NOT_ZERO 0x0008 +#define ICSNEOC2_LIN_ERR_RX_BREAK_TOO_SHORT 0x0010 +#define ICSNEOC2_LIN_ERR_RX_SYNC_NOT_55 0x0020 +#define ICSNEOC2_LIN_ERR_RX_DATA_LEN_OVER_8 0x0040 +#define ICSNEOC2_LIN_ERR_FRAME_SYNC 0x0080 +#define ICSNEOC2_LIN_ERR_FRAME_MESSAGE_ID 0x0100 +#define ICSNEOC2_LIN_ERR_FRAME_RESPONDER_DATA 0x0200 +#define ICSNEOC2_LIN_ERR_CHECKSUM_MATCH 0x0400 + +typedef uint32_t icsneoc2_lin_err_flags_t; + +// LIN status flags bitmask +#define ICSNEOC2_LIN_STATUS_TX_CHECKSUM_ENHANCED 0x01 +#define ICSNEOC2_LIN_STATUS_TX_COMMANDER 0x02 +#define ICSNEOC2_LIN_STATUS_TX_RESPONDER 0x04 +#define ICSNEOC2_LIN_STATUS_TX_ABORTED 0x08 +#define ICSNEOC2_LIN_STATUS_UPDATE_RESPONDER_ONCE 0x10 +#define ICSNEOC2_LIN_STATUS_HAS_UPDATED_RESPONDER_ONCE 0x20 +#define ICSNEOC2_LIN_STATUS_BUS_RECOVERED 0x40 +#define ICSNEOC2_LIN_STATUS_BREAK_ONLY 0x80 + +typedef uint32_t icsneoc2_lin_status_flags_t; + +/** + * Create a LIN message. + * + * @param[out] message Pointer to receive the new LIN message handle. + * @param[in] id The LIN frame ID (0-63). Bits above 0x3F are masked off. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful. + * + * @see icsneoc2_message_free + */ +icsneoc2_error_t icsneoc2_message_lin_create(icsneoc2_message_t** message, uint8_t id); + +/** + * Get the LIN-specific properties of a message. + * + * Any output pointer may be NULL to skip that field. + * + * @param[in] message The message to query (must be a LIN message). + * @param[out] id Pointer to receive the LIN frame ID. + * @param[out] protected_id Pointer to receive the protected ID (ID with parity bits). + * @param[out] checksum Pointer to receive the checksum byte. + * @param[out] msg_type Pointer to receive the LIN message type. + * @param[out] is_enhanced_checksum Pointer to receive whether enhanced checksum is used. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + */ +icsneoc2_error_t icsneoc2_message_lin_props_get(const icsneoc2_message_t* message, + uint8_t* id, uint8_t* protected_id, uint8_t* checksum, + icsneoc2_lin_msg_type_t* msg_type, bool* is_enhanced_checksum); + +/** + * Set the LIN-specific properties of a message. + * + * Any input pointer may be NULL to skip that field. Setting the ID also recalculates the protected ID. + * + * @param[in] message The message to modify (must be a LIN message). + * @param[in] id Pointer to the LIN frame ID to set (0-63). NULL to skip. + * @param[in] checksum Pointer to the checksum byte to set. NULL to skip. + * @param[in] msg_type Pointer to the LIN message type to set. NULL to skip. + * @param[in] is_enhanced_checksum Pointer to set enhanced checksum mode. NULL to skip. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + */ +icsneoc2_error_t icsneoc2_message_lin_props_set(icsneoc2_message_t* message, + const uint8_t* id, const uint8_t* checksum, + const icsneoc2_lin_msg_type_t* msg_type, const bool* is_enhanced_checksum); + +/** + * Get the LIN error flags of a message. + * + * @param[in] message The message to query (must be a LIN message). + * @param[out] err_flags Pointer to receive the error flags bitmask. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + * + * @see ICSNEOC2_LIN_ERR_* + */ +icsneoc2_error_t icsneoc2_message_lin_err_flags_get(const icsneoc2_message_t* message, icsneoc2_lin_err_flags_t* err_flags); + +/** + * Get the LIN status flags of a message. + * + * @param[in] message The message to query (must be a LIN message). + * @param[out] status_flags Pointer to receive the status flags bitmask. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + * + * @see ICSNEOC2_LIN_STATUS_* + */ +icsneoc2_error_t icsneoc2_message_lin_status_flags_get(const icsneoc2_message_t* message, icsneoc2_lin_status_flags_t* status_flags); + +/** + * Calculate and set the checksum on a LIN message. + * + * Uses enhanced or classic checksum based on the isEnhancedChecksum property. + * + * @param[in] message The LIN message to calculate the checksum for. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + */ +icsneoc2_error_t icsneoc2_message_lin_calc_checksum(icsneoc2_message_t* message); + /** * Get the network type of a message * diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index f51b583..3c74d1d 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -131,6 +132,15 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_is_transmit(NULL, NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_is_valid(NULL, NULL)); + // LIN message NULL parameter checks + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_is_lin(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_create(NULL, 0)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_props_get(NULL, NULL, NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_props_set(NULL, NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_err_flags_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_status_flags_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_calc_checksum(NULL)); + // Test utility functions with NULL parameters ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_version_get(NULL, NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_serial_num_to_string(0, NULL, NULL)); @@ -688,4 +698,228 @@ TEST(icsneoc2, test_icsneoc2_script_error_codes) ASSERT_GT(len, 0u); } +TEST(icsneoc2, test_lin_message_create_and_props) +{ + // Create a LIN message with ID 0x15 + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x15)); + ASSERT_NE(msg, nullptr); + + // Verify it reports as LIN + bool is_lin = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_lin(msg, &is_lin)); + ASSERT_TRUE(is_lin); + + // Verify it does NOT report as CAN + bool is_can = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_can(msg, &is_can)); + ASSERT_FALSE(is_can); + + // CAN props should fail on a LIN message + uint64_t arb_id = 0; + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_can_props_get(msg, &arb_id, NULL)); + + // Read back default props + uint8_t id = 0xFF, protected_id = 0, checksum = 0xFF; + icsneoc2_lin_msg_type_t msg_type = 0xFF; + bool enhanced = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, &id, &protected_id, &checksum, &msg_type, &enhanced)); + ASSERT_EQ(id, 0x15); + ASSERT_NE(protected_id, 0); // Should have parity bits + ASSERT_EQ(checksum, 0); + ASSERT_EQ(msg_type, icsneoc2_lin_msg_type_not_set); + ASSERT_FALSE(enhanced); + + // ID should be masked to 6 bits + icsneoc2_message_t* msg_masked = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg_masked, 0xFF)); + uint8_t masked_id = 0xFF; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg_masked, &masked_id, NULL, NULL, NULL, NULL)); + ASSERT_EQ(masked_id, 0x3F); // 0xFF & 0x3F + icsneoc2_message_free(msg_masked); + + icsneoc2_message_free(msg); +} + +TEST(icsneoc2, test_lin_message_props_set) +{ + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x00)); + + // Set individual properties using NULL to skip others + uint8_t new_id = 0x2A; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_set(msg, &new_id, NULL, NULL, NULL)); + + uint8_t read_id = 0, read_pid = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, &read_id, &read_pid, NULL, NULL, NULL)); + ASSERT_EQ(read_id, 0x2A); + ASSERT_NE(read_pid, 0x2A); // Protected ID should differ (has parity bits) + + // Set checksum + uint8_t new_checksum = 0xAB; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_set(msg, NULL, &new_checksum, NULL, NULL)); + uint8_t read_checksum = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, NULL, NULL, &read_checksum, NULL, NULL)); + ASSERT_EQ(read_checksum, 0xAB); + + // Set msg type + icsneoc2_lin_msg_type_t new_type = icsneoc2_lin_msg_type_commander_msg; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_set(msg, NULL, NULL, &new_type, NULL)); + icsneoc2_lin_msg_type_t read_type = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, NULL, NULL, NULL, &read_type, NULL)); + ASSERT_EQ(read_type, icsneoc2_lin_msg_type_commander_msg); + + // Set enhanced checksum + bool new_enhanced = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_set(msg, NULL, NULL, NULL, &new_enhanced)); + bool read_enhanced = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, NULL, NULL, NULL, NULL, &read_enhanced)); + ASSERT_TRUE(read_enhanced); + + // LIN props set on a CAN message should fail + icsneoc2_message_t* can_msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_can_create(&can_msg)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_props_set(can_msg, &new_id, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_props_get(can_msg, &read_id, NULL, NULL, NULL, NULL)); + icsneoc2_message_free(can_msg); + + icsneoc2_message_free(msg); +} + +TEST(icsneoc2, test_lin_message_data_and_netid) +{ + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x10)); + + // Set data + uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_data_set(msg, data, sizeof(data))); + + // Read data back + uint8_t read_data[8] = {0}; + size_t read_len = sizeof(read_data); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_data_get(msg, read_data, &read_len)); + ASSERT_EQ(read_len, sizeof(data)); + ASSERT_EQ(memcmp(data, read_data, sizeof(data)), 0); + + // Set and verify netid + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_netid_set(msg, icsneoc2_netid_lin_01)); + icsneoc2_netid_t netid = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_netid_get(msg, &netid)); + ASSERT_EQ(netid, icsneoc2_netid_lin_01); + + // Verify is_frame and is_raw + bool is_frame = false, is_raw = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_frame(msg, &is_frame)); + ASSERT_TRUE(is_frame); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_raw(msg, &is_raw)); + ASSERT_TRUE(is_raw); + + icsneoc2_message_free(msg); +} + +TEST(icsneoc2, test_lin_message_flags) +{ + // Create a LIN message and verify default flags are clear + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x01)); + + icsneoc2_lin_err_flags_t err_flags = 0xFFFFFFFF; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_err_flags_get(msg, &err_flags)); + ASSERT_EQ(err_flags, 0u); + + icsneoc2_lin_status_flags_t status_flags = 0xFFFFFFFF; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_status_flags_get(msg, &status_flags)); + ASSERT_EQ(status_flags, 0u); + + // Error/status flags on CAN message should fail + icsneoc2_message_t* can_msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_can_create(&can_msg)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_err_flags_get(can_msg, &err_flags)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_status_flags_get(can_msg, &status_flags)); + icsneoc2_message_free(can_msg); + + icsneoc2_message_free(msg); +} + +TEST(icsneoc2, test_lin_message_calc_checksum) +{ + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x01)); + + // Set some data and calculate checksum (classic) + uint8_t data[] = {0x01, 0x02, 0x03}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_data_set(msg, data, sizeof(data))); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_calc_checksum(msg)); + + uint8_t checksum = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, NULL, NULL, &checksum, NULL, NULL)); + ASSERT_NE(checksum, 0); // Checksum should be non-zero for this data + + // Now set enhanced checksum and recalculate — should give a different value + uint8_t classic_checksum = checksum; + bool enhanced = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_set(msg, NULL, NULL, NULL, &enhanced)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_calc_checksum(msg)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_props_get(msg, NULL, NULL, &checksum, NULL, NULL)); + ASSERT_NE(checksum, classic_checksum); // Enhanced and classic should differ + + // calc_checksum on a CAN message should fail + icsneoc2_message_t* can_msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_can_create(&can_msg)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_calc_checksum(can_msg)); + icsneoc2_message_free(can_msg); + + icsneoc2_message_free(msg); +} + +TEST(icsneoc2, test_lin_msg_type_enum_values) +{ + ASSERT_EQ(icsneoc2_lin_msg_type_not_set, 0); + ASSERT_EQ(icsneoc2_lin_msg_type_commander_msg, 1); + ASSERT_EQ(icsneoc2_lin_msg_type_header_only, 2); + ASSERT_EQ(icsneoc2_lin_msg_type_break_only, 3); + ASSERT_EQ(icsneoc2_lin_msg_type_sync_only, 4); + ASSERT_EQ(icsneoc2_lin_msg_type_update_responder, 5); + ASSERT_EQ(icsneoc2_lin_msg_type_error, 6); + ASSERT_EQ(sizeof(icsneoc2_lin_msg_type_t), sizeof(uint8_t)); +} + +TEST(icsneoc2, test_lin_msg_type_cpp_enum_sync) +{ + using T = icsneo::LINMessage::Type; + ASSERT_EQ(static_cast(T::NOT_SET), icsneoc2_lin_msg_type_not_set); + ASSERT_EQ(static_cast(T::LIN_COMMANDER_MSG), icsneoc2_lin_msg_type_commander_msg); + ASSERT_EQ(static_cast(T::LIN_HEADER_ONLY), icsneoc2_lin_msg_type_header_only); + ASSERT_EQ(static_cast(T::LIN_BREAK_ONLY), icsneoc2_lin_msg_type_break_only); + ASSERT_EQ(static_cast(T::LIN_SYNC_ONLY), icsneoc2_lin_msg_type_sync_only); + ASSERT_EQ(static_cast(T::LIN_UPDATE_RESPONDER), icsneoc2_lin_msg_type_update_responder); + ASSERT_EQ(static_cast(T::LIN_ERROR), icsneoc2_lin_msg_type_error); +} + +TEST(icsneoc2, test_lin_flag_bitmask_values) +{ + // Error flags should be distinct bits + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_BREAK_ONLY, 0x0001); + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_BREAK_SYNC_ONLY, 0x0002); + ASSERT_EQ(ICSNEOC2_LIN_ERR_TX_RX_MISMATCH, 0x0004); + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_BREAK_NOT_ZERO, 0x0008); + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_BREAK_TOO_SHORT, 0x0010); + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_SYNC_NOT_55, 0x0020); + ASSERT_EQ(ICSNEOC2_LIN_ERR_RX_DATA_LEN_OVER_8, 0x0040); + ASSERT_EQ(ICSNEOC2_LIN_ERR_FRAME_SYNC, 0x0080); + ASSERT_EQ(ICSNEOC2_LIN_ERR_FRAME_MESSAGE_ID, 0x0100); + ASSERT_EQ(ICSNEOC2_LIN_ERR_FRAME_RESPONDER_DATA, 0x0200); + ASSERT_EQ(ICSNEOC2_LIN_ERR_CHECKSUM_MATCH, 0x0400); + + // Status flags should be distinct bits + ASSERT_EQ(ICSNEOC2_LIN_STATUS_TX_CHECKSUM_ENHANCED, 0x01); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_TX_COMMANDER, 0x02); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_TX_RESPONDER, 0x04); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_TX_ABORTED, 0x08); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_UPDATE_RESPONDER_ONCE, 0x10); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_HAS_UPDATED_RESPONDER_ONCE, 0x20); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_BUS_RECOVERED, 0x40); + ASSERT_EQ(ICSNEOC2_LIN_STATUS_BREAK_ONLY, 0x80); +}