C2: Add LIN message support

master
David Rebbe 2026-04-15 19:50:33 +00:00 committed by Kyle Schwarz
parent 6dc005fcea
commit f5f6d0828b
11 changed files with 1199 additions and 12 deletions

View File

@ -216,3 +216,133 @@ icsneoc2_error_t icsneoc2_message_is_can(icsneoc2_message_t* message, bool* is_c
return icsneoc2_error_success; 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<LINMessage>(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<LINMessage>(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<LINMessage>(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<icsneoc2_lin_msg_type_t>(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<LINMessage>(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<LINMessage::Type>(*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<LINMessage>(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<LINMessage>(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<LINMessage>(message->message);
if(!lin_msg) {
return icsneoc2_error_invalid_type;
}
LINMessage::calcChecksum(*lin_msg);
return icsneoc2_error_success;
}

View File

@ -102,10 +102,71 @@ static std::vector<uint8_t> EncodeFromMessageCAN(std::shared_ptr<Frame> frame, c
return encoded; return encoded;
} }
static std::vector<uint8_t> EncodeFromMessageLIN(std::shared_ptr<Frame> /* frame */, const device_eventhandler_t& report) { static std::vector<uint8_t> EncodeFromMessageLIN(std::shared_ptr<Frame> frame, const device_eventhandler_t& report) {
// TODO auto linmsg = std::dynamic_pointer_cast<LINMessage>(frame);
report(APIEvent::Type::UnsupportedTXNetwork, APIEvent::Severity::Error); if(!linmsg) {
return {}; 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<size_t>(8, linmsg->data.size());
std::vector<uint8_t> 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<uint16_t>(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<uint8_t> TransmitMessage::EncodeFromMessage(std::shared_ptr<Frame> frame, uint32_t client_id, const device_eventhandler_t& report) { std::vector<uint8_t> TransmitMessage::EncodeFromMessage(std::shared_ptr<Frame> frame, uint32_t client_id, const device_eventhandler_t& report) {
@ -127,6 +188,8 @@ std::vector<uint8_t> TransmitMessage::EncodeFromMessage(std::shared_ptr<Frame> f
return result; return result;
} }
// common fields // common fields
if(result.empty())
return result;
TransmitMessage* const msg = (TransmitMessage*)result.data(); TransmitMessage* const msg = (TransmitMessage*)result.data();
msg->options.clientId = client_id; msg->options.clientId = client_id;
msg->options.networkId = static_cast<uint32_t>(frame->network.getNetID()); msg->options.networkId = static_cast<uint32_t>(frame->network.getNetID());

View File

@ -33,3 +33,19 @@ Device Info
.. literalinclude:: ../../examples/c2/device_info/src/main.c .. literalinclude:: ../../examples/c2/device_info/src/main.c
:language: 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

View File

@ -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_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_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_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_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_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON)
option(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE "Build the A2B 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) add_subdirectory(c2/device_info)
endif() 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) if(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE)
add_subdirectory(cpp/simple) add_subdirectory(cpp/simple)
endif() endif()

View File

@ -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()

View File

@ -0,0 +1,311 @@
/* Note: This example requires LIN 1 and LIN 2 channels to be connected on the device */
#include <icsneo/icsneoc2.h>
#include <icsneo/icsneoc2messages.h>
#include <icsneo/icsneoc2settings.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <stdio.h>
#include <inttypes.h>
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;
}

View File

@ -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()

View File

@ -0,0 +1,276 @@
#include <icsneo/icsneoc2.h>
#include <icsneo/icsneoc2messages.h>
#include <icsneo/icsneoc2settings.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
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;
}

View File

@ -1,6 +1,8 @@
#ifndef __LINMESSAGE_H_ #ifndef __LINMESSAGE_H_
#define __LINMESSAGE_H_ #define __LINMESSAGE_H_
#include "icsneo/icsneoc2messages.h"
#ifdef __cplusplus #ifdef __cplusplus
#include "icsneo/communication/message/message.h" #include "icsneo/communication/message/message.h"
@ -36,14 +38,14 @@ struct LINStatusFlags {
class LINMessage : public Frame { class LINMessage : public Frame {
public: public:
enum class Type : uint8_t { enum class Type : icsneoc2_lin_msg_type_t {
NOT_SET = 0, NOT_SET = icsneoc2_lin_msg_type_not_set,
LIN_COMMANDER_MSG, LIN_COMMANDER_MSG = icsneoc2_lin_msg_type_commander_msg,
LIN_HEADER_ONLY, LIN_HEADER_ONLY = icsneoc2_lin_msg_type_header_only,
LIN_BREAK_ONLY, LIN_BREAK_ONLY = icsneoc2_lin_msg_type_break_only,
LIN_SYNC_ONLY, LIN_SYNC_ONLY = icsneoc2_lin_msg_type_sync_only,
LIN_UPDATE_RESPONDER, LIN_UPDATE_RESPONDER = icsneoc2_lin_msg_type_update_responder,
LIN_ERROR LIN_ERROR = icsneoc2_lin_msg_type_error
}; };
static void calcChecksum(LINMessage& message); static void calcChecksum(LINMessage& message);

View File

@ -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); 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 * Get the network type of a message
* *

View File

@ -3,6 +3,7 @@
#include <icsneo/icsneoc2settings.h> #include <icsneo/icsneoc2settings.h>
#include <icsneo/icsneoc2messages.h> #include <icsneo/icsneoc2messages.h>
#include <icsneo/device/devicetype.h> #include <icsneo/device/devicetype.h>
#include <icsneo/communication/message/linmessage.h>
#include <vector> #include <vector>
@ -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_transmit(NULL, NULL));
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_is_valid(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 // Test utility functions with NULL parameters
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_version_get(NULL, NULL)); 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)); 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); 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<uint8_t>(T::NOT_SET), icsneoc2_lin_msg_type_not_set);
ASSERT_EQ(static_cast<uint8_t>(T::LIN_COMMANDER_MSG), icsneoc2_lin_msg_type_commander_msg);
ASSERT_EQ(static_cast<uint8_t>(T::LIN_HEADER_ONLY), icsneoc2_lin_msg_type_header_only);
ASSERT_EQ(static_cast<uint8_t>(T::LIN_BREAK_ONLY), icsneoc2_lin_msg_type_break_only);
ASSERT_EQ(static_cast<uint8_t>(T::LIN_SYNC_ONLY), icsneoc2_lin_msg_type_sync_only);
ASSERT_EQ(static_cast<uint8_t>(T::LIN_UPDATE_RESPONDER), icsneoc2_lin_msg_type_update_responder);
ASSERT_EQ(static_cast<uint8_t>(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);
}