From 539cfa511bf5c2c505d797a177893b14ec341fbe Mon Sep 17 00:00:00 2001 From: Kyle Johannes Date: Fri, 3 Feb 2023 18:27:08 +0000 Subject: [PATCH] LIN: Network support --- CMakeLists.txt | 3 + communication/decoder.cpp | 14 ++ communication/encoder.cpp | 17 +- communication/message/linmessage.cpp | 22 ++ communication/packet/linpacket.cpp | 153 ++++++++++++++ examples/CMakeLists.txt | 6 +- examples/cpp/lin/CMakeLists.txt | 27 +++ examples/cpp/lin/src/LINExample.cpp | 151 +++++++++++++ .../icsneo/communication/message/linmessage.h | 65 ++++++ .../icsneo/communication/message/message.h | 3 + .../icsneo/communication/packet/linpacket.h | 66 ++++++ include/icsneo/icsneocpp.h | 1 + test/linencoderdecodertest.cpp | 199 ++++++++++++++++++ 13 files changed, 723 insertions(+), 4 deletions(-) create mode 100644 communication/message/linmessage.cpp create mode 100644 communication/packet/linpacket.cpp create mode 100644 examples/cpp/lin/CMakeLists.txt create mode 100644 examples/cpp/lin/src/LINExample.cpp create mode 100644 include/icsneo/communication/message/linmessage.h create mode 100644 include/icsneo/communication/packet/linpacket.h create mode 100644 test/linencoderdecodertest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7e0364..7f2b018 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,7 @@ set(SRC_FILES communication/message/callback/streamoutput/a2bwavoutput.cpp communication/message/neomessage.cpp communication/message/ethphymessage.cpp + communication/message/linmessage.cpp communication/packet/flexraypacket.cpp communication/packet/canpacket.cpp communication/packet/a2bpacket.cpp @@ -184,6 +185,7 @@ set(SRC_FILES communication/packet/logicaldiskinfopacket.cpp communication/packet/wivicommandpacket.cpp communication/packet/i2cpacket.cpp + communication/packet/linpacket.cpp communication/packet/scriptstatuspacket.cpp communication/decoder.cpp communication/encoder.cpp @@ -394,6 +396,7 @@ if(LIBICSNEO_BUILD_TESTS) test/eventmanagertest.cpp test/ethernetpacketizertest.cpp test/i2cencoderdecodertest.cpp + test/linencoderdecodertest.cpp test/a2bencoderdecodertest.cpp ) diff --git a/communication/decoder.cpp b/communication/decoder.cpp index 2086c49..c265f02 100644 --- a/communication/decoder.cpp +++ b/communication/decoder.cpp @@ -11,6 +11,7 @@ #include "icsneo/communication/message/a2bmessage.h" #include "icsneo/communication/message/flexray/control/flexraycontrolmessage.h" #include "icsneo/communication/message/i2cmessage.h" +#include "icsneo/communication/message/linmessage.h" #include "icsneo/communication/command.h" #include "icsneo/device/device.h" #include "icsneo/communication/packet/canpacket.h" @@ -24,6 +25,7 @@ #include "icsneo/communication/packet/wivicommandpacket.h" #include "icsneo/communication/packet/i2cpacket.h" #include "icsneo/communication/packet/scriptstatuspacket.h" +#include "icsneo/communication/packet/linpacket.h" #include using namespace icsneo; @@ -362,6 +364,18 @@ bool Decoder::decode(std::shared_ptr& result, const std::shared_ptrnetwork; return true; } + case Network::Type::LIN: { + result = HardwareLINPacket::DecodeToMessage(packet->data); + + if(!result) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; // A nullptr was returned, the packet was not long enough to decode + } + + LINMessage& msg = *static_cast(result.get()); + msg.network = packet->network; + return true; + } } // For the moment other types of messages will automatically be decoded as raw messages diff --git a/communication/encoder.cpp b/communication/encoder.cpp index 5ff9d59..598cc85 100644 --- a/communication/encoder.cpp +++ b/communication/encoder.cpp @@ -9,7 +9,7 @@ #include "icsneo/communication/packet/i2cpacket.h" #include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/packet/a2bpacket.h" - +#include "icsneo/communication/packet/linpacket.h" using namespace icsneo; @@ -96,6 +96,18 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector& result, } break; } // End of Network::Type::I2C + case Network::Type::LIN: { + auto linmsg = std::dynamic_pointer_cast(message); + if(!linmsg) { + report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); + return false; + } + buffer = &result; + if(!HardwareLINPacket::EncodeFromMessage(*linmsg, result, report)) { + return false; + } + break; + } // End of Network::Type::LIN default: report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error); return false; @@ -182,7 +194,8 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector& result, // Size for the host-to-device long format is the size of the entire packet + 1 // So +1 for AA header, +1 for short format header, +2 for long format size, and +2 for long format NetID // Then an extra +1, due to a firmware idiosyncrasy - uint16_t size = uint16_t(buffer->size()) + 1 + 1 + 2 + 2 + 1; + uint16_t size = static_cast(buffer->size()) + 1 + 1 + 2 + 2 + 1; + buffer->insert(buffer->begin(), { (uint8_t)Network::NetID::RED, // 0x0C for long message (uint8_t)size, // Size, little endian 16-bit diff --git a/communication/message/linmessage.cpp b/communication/message/linmessage.cpp new file mode 100644 index 0000000..4dc68c8 --- /dev/null +++ b/communication/message/linmessage.cpp @@ -0,0 +1,22 @@ +#include "icsneo/communication/message/linmessage.h" +#include + +namespace icsneo { + +void LINMessage::calcChecksum(LINMessage& message) { + uint16_t sum = 0; + auto limitFunc = [](uint16_t x, uint16_t y) -> uint16_t { + if ((x + y) > 0xFFu) + return ((x + y) - 0xFFu); + else + return (x + y); + }; + + message.checksum = static_cast(std::accumulate(message.data.begin(), message.data.end(), sum, limitFunc)); + if(message.isEnhancedChecksum) + message.checksum = static_cast(limitFunc(message.checksum, message.protectedID)); + + message.checksum ^= 0xFFu; +} + +} //namespace icsneo \ No newline at end of file diff --git a/communication/packet/linpacket.cpp b/communication/packet/linpacket.cpp new file mode 100644 index 0000000..026eb46 --- /dev/null +++ b/communication/packet/linpacket.cpp @@ -0,0 +1,153 @@ +#include "icsneo/communication/packet/linpacket.h" +#include "icsneo/communication/message/linmessage.h" +#include "icsneo/communication/packetizer.h" + +namespace icsneo { + +std::shared_ptr HardwareLINPacket::DecodeToMessage(const std::vector& bytestream) { + auto msg = std::make_shared(); + const HardwareLINPacket* packet = reinterpret_cast(bytestream.data()); + size_t numDataBytes = packet->CoreMiniBitsLIN.len; + size_t numHeaderBytes = sizeof(HardwareLINPacket::CoreMiniBitsLIN); + + if( (sizeof(HardwareLINPacket) != bytestream.size()) || + ((numDataBytes + numHeaderBytes) > bytestream.size()) ) + return nullptr; + + if(numDataBytes) + --numDataBytes; //If data is present, there will be a checksum included + + msg->network = Network::GetNetIDFromCoreMiniNetwork(static_cast(packet->networkID)); + msg->protectedID = packet->CoreMiniBitsLIN.ID; + msg->ID = (packet->CoreMiniBitsLIN.ID & 0x3Fu); + msg->isEnhancedChecksum = static_cast(packet->CoreMiniBitsLIN.TxChkSumEnhanced); + + /* Minimum one responder byte and one checksum byte. */ + if(2u > packet->CoreMiniBitsLIN.len) + msg->type = LINMessage::Type::LIN_ERROR; + + auto dataStart = bytestream.begin() + numHeaderBytes; + std::copy(dataStart, (dataStart+numDataBytes), std::back_inserter(msg->data)); + + /* If OK, validate the checksum*/ + auto isChecksumInvalid = [&]() -> bool { + /* messages with no data have no checksum (e.g. header only) */ + if(!msg->data.size()) + return true; + + uint8_t checkSum = (8 > numDataBytes) ? *(dataStart + numDataBytes) : packet->CoreMiniBitsLIN.LINByte9; + LINMessage::calcChecksum(*msg); + if(checkSum != msg->checksum) { + msg->isEnhancedChecksum = true; + LINMessage::calcChecksum(*msg); + if(checkSum != msg->checksum) { + msg->isEnhancedChecksum = false; + msg->checksum = checkSum; + return true; + } + } + return false; + }; + + /* if any of the status bits are set, then this is + either a failed reception or a bus status update. */ + msg->errFlags = + { + static_cast(packet->CoreMiniBitsLIN.ErrRxOnlyBreak), + static_cast(packet->CoreMiniBitsLIN.ErrRxOnlyBreakSync), + static_cast(packet->CoreMiniBitsLIN.ErrTxRxMismatch), + static_cast(packet->CoreMiniBitsLIN.ErrRxBreakNotZero), + static_cast(packet->CoreMiniBitsLIN.ErrRxBreakTooShort), + static_cast(packet->CoreMiniBitsLIN.ErrRxSyncNot55), + static_cast(packet->CoreMiniBitsLIN.ErrRxDataGreater8), + static_cast(packet->CoreMiniBitsLIN.SyncFerr), + static_cast(packet->CoreMiniBitsLIN.MidFerr), + static_cast(packet->CoreMiniBitsLIN.ResponderByteFerr), + isChecksumInvalid(), /* ErrChecksumMatch */ + }; + + msg->statusFlags = + { + static_cast(packet->CoreMiniBitsLIN.TxChkSumEnhanced), + static_cast(packet->CoreMiniBitsLIN.TXCommander), + static_cast(packet->CoreMiniBitsLIN.TXResponder), + static_cast(packet->CoreMiniBitsLIN.TxAborted), + static_cast(packet->CoreMiniBitsLIN.UpdateResponderOnce), + static_cast(packet->CoreMiniBitsLIN.HasUpdatedResponderOnce), + static_cast(packet->CoreMiniBitsLIN.BusRecovered), + static_cast(packet->CoreMiniBitsLIN.BreakOnly) + }; + if(msg->statusFlags.TxCommander || msg->statusFlags.TxResponder) + msg->type = LINMessage::Type::LIN_COMMANDER_MSG; + else if(msg->statusFlags.BreakOnly) + msg->type = LINMessage::Type::LIN_BREAK_ONLY; + if( msg->errFlags.ErrRxBreakOnly || msg->errFlags.ErrRxBreakSyncOnly || + msg->errFlags.ErrTxRxMismatch || msg->errFlags.ErrRxBreakNotZero || + msg->errFlags.ErrRxBreakTooShort || msg->errFlags.ErrRxSyncNot55 || + msg->errFlags.ErrRxDataLenOver8 || msg->errFlags.ErrFrameSync || + msg->errFlags.ErrFrameMessageID || msg->errFlags.ErrChecksumMatch || + msg->errFlags.ErrFrameResponderData ) + { msg->type = LINMessage::Type::LIN_ERROR; } + + msg->timestamp = packet->timestamp; + return msg; +} + +bool HardwareLINPacket::EncodeFromMessage(LINMessage& message, std::vector& bytestream, const device_eventhandler_t& report) +{ + uint8_t size = ((std::min(8ul, message.data.size()) + 3ul) & 0xFu); + if(size > 3) { ++size; } // add a checksum byte if there's data + switch(message.type) { + case LINMessage::Type::LIN_HEADER_ONLY: + case LINMessage::Type::LIN_COMMANDER_MSG: + { + size |= 0x80u; + break; + } + case LINMessage::Type::LIN_BREAK_ONLY: + { + size |= 0x20u; + break; + } + case LINMessage::Type::NOT_SET: + { + report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); + return false; + } + default: + break; + } + + message.protectedID = message.ID; + auto bit = [&](uint8_t pos)->uint8_t { return ((message.protectedID >> pos) & 0x1u); }; + message.protectedID |= (~(bit(1) ^ bit(3) ^ bit(4) ^ bit(5)) << 7); + message.protectedID |= ((bit(0) ^ bit(1) ^ bit(2) ^ bit(4)) << 6); + + bytestream.insert(bytestream.end(), + { + static_cast(0x00u), + static_cast(size), + static_cast((message.description >> 8) & 0xFF), + static_cast(message.description & 0xFF), + static_cast(message.protectedID) + }); + + switch(message.type) { + case(LINMessage::Type::LIN_COMMANDER_MSG): + case(LINMessage::Type::LIN_UPDATE_RESPONDER): + { + std::copy(message.data.begin(), message.data.end(), std::back_inserter(bytestream)); + LINMessage::calcChecksum(message); + bytestream.push_back(message.checksum); + break; + } + default: + break; + } + if(bytestream.size() % 2) + bytestream.push_back(0x41); //padding + + return true; +} + +} //namespace icsneo \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f65d96e..3ed9045 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,8 +2,7 @@ option(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE "Build the command-line interactive 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) - - +option(LIBICSNEO_BUILD_CPP_LIN_EXAMPLE "Build the LIN example." ON) # Disabled until we properly build these in-tree # option(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE "Build the command-line interactive C# example." OFF) # option(LIBICSNEO_BUILD_JAVA_INTERACTIVE_EXAMPLE "Build the command-line interactive Java example." OFF) @@ -24,6 +23,9 @@ if(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE) add_subdirectory(cpp/a2b) endif() +if(LIBICSNEO_BUILD_CPP_LIN_EXAMPLE) + add_subdirectory(cpp/lin) +endif() # if(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE) # add_subdirectory(csharp) # endif() diff --git a/examples/cpp/lin/CMakeLists.txt b/examples/cpp/lin/CMakeLists.txt new file mode 100644 index 0000000..f72e1a1 --- /dev/null +++ b/examples/cpp/lin/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.2) +project(libicsneocpp-lin VERSION 0.1.0) + +set(CMAKE_CXX_STANDARD_REQUIRED 11) + +include(GNUInstallDirs) + +# Add an include directory like so if desired +#include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Enable Warnings +if(MSVC) + # Force to always compile with W4 + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() +else() #if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-switch -Wno-unknown-pragmas") +endif() + +# Add libicsneo, usually a git submodule within your project works well +#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/libicsneo ${CMAKE_CURRENT_BINARY_DIR}/third-party/libicsneo) + +add_executable(libicsneocpp-lin src/LINExample.cpp) +target_link_libraries(libicsneocpp-lin icsneocpp) \ No newline at end of file diff --git a/examples/cpp/lin/src/LINExample.cpp b/examples/cpp/lin/src/LINExample.cpp new file mode 100644 index 0000000..4d007ca --- /dev/null +++ b/examples/cpp/lin/src/LINExample.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" +#include "icsneo/communication/message/linmessage.h" + +/* Note: This example requires LIN 1 and LIN 2 channels to be connected on the device */ + +char getCharInput(std::vector allowed) { + bool found = false; + std::string input; + + while(!found) { + std::cin >> input; + + if(input.length() == 1) { + for(char compare : allowed) { + if(compare == input.c_str()[0]) { + found = true; + break; + } + } + } + + if(!found) { + std::cout << "Input did not match expected options. Please try again." << std::endl; + std::cout << "" << std::endl; + } + } + + return input.c_str()[0]; +} + +int main() { + // Print version + std::cout << "Running libicsneo " << icsneo::GetVersion() << std::endl; + std::cout << "\nFinding devices... " << std::flush; + auto devices = icsneo::FindAllDevices(); // This is type std::vector> + // You now hold the shared_ptrs for these devices, you are considered to "own" these devices from a memory perspective + std::cout << "OK, " << devices.size() << " device" << (devices.size() == 1 ? "" : "s") << " found" << std::endl; + + // List off the devices + for(auto& device : devices) + std::cout << '\t' << device->describe() << " @ Handle " << device->getNeoDevice().handle << std::endl; + std::cout << std::endl; + + for(auto& device : devices) { + std::cout << "Connecting to " << device->describe() << "... "; + bool ret = device->open(); + if(!ret) { // Failed to open + std::cout << "FAIL" << std::endl; + std::cout << icsneo::GetLastError() << std::endl << std::endl; + continue; + } + std::cout << "OK" << std::endl; + + // The concept of going "online" tells the connected device to start listening, i.e. ACKing traffic and giving it to us + std::cout << "\tGoing online... "; + ret = device->goOnline(); + if(!ret) { + std::cout << "FAIL" << std::endl; + device->close(); + continue; + } + std::cout << "OK" << std::endl; + + // A real application would just check the result of icsneo_goOnline() rather than calling this + // This function is intended to be called later on if needed + std::cout << "\tChecking online status... "; + ret = device->isOnline(); + if(!ret) { + std::cout << "FAIL\n" << std::endl; + device->close(); + continue; + } + std::cout << "OK" << std::endl; + + auto handler = device->addMessageCallback(std::make_shared([&](std::shared_ptr message) { + if(icsneo::Message::Type::Frame == message->type) { + auto frame = std::static_pointer_cast(message); + if(icsneo::Network::Type::LIN == frame->network.getType()) { + auto msg = std::static_pointer_cast(message); + std::cout << msg->network << " RX frame | ID: 0x" << std::hex << static_cast(msg->ID) << " | "; + std::cout << "Protected ID: 0x" << static_cast(msg->protectedID) << "\n" << "Data: "; + for(uint8_t& each : msg->data) { + std::cout << "0x" << static_cast(each) << " "; + } + std::cout << "\nChecksum type: " << (msg->isEnhancedChecksum ? "Enhanced" : "Classic"); + std::cout << "\nChecksum: 0x" << static_cast(msg->checksum) << "\n"; + std::cout << "Is checksum valid: " << ((!msg->errFlags.ErrChecksumMatch) ? "yes" : "no") << "\n\n"; + } + } + })); + + // We can transmit messages + std::cout << "\tTransmitting a LIN responder data frame... "; + auto lin_r = std::make_shared(); + lin_r->network = icsneo::Network::NetID::LIN2; + lin_r->ID = 0x11; + lin_r->type = icsneo::LINMessage::Type::LIN_UPDATE_RESPONDER; + lin_r->data = {0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22, 0x33, 0x44}; + ret = device->transmit(lin_r); // This will return false if the device does not support LIN + std::cout << (ret ? "OK" : "FAIL") << std::endl; + + std::cout << "\tTransmitting a LIN commander frame... "; + auto lin_c = std::make_shared(); + lin_c->network = icsneo::Network::NetID::LIN; + lin_c->ID = 0x11; + lin_c->type = icsneo::LINMessage::Type::LIN_HEADER_ONLY; + ret = device->transmit(lin_c); + std::cout << (ret ? "OK" : "FAIL") << std::endl << std::endl; + + std::cout << "\tTransmitting a LIN commander frame with responder data... "; + auto lin_d = std::make_shared(); + lin_d->network = icsneo::Network::NetID::LIN; + lin_d->ID = 0x22; + lin_d->isEnhancedChecksum = true; + lin_d->type = icsneo::LINMessage::Type::LIN_COMMANDER_MSG; + lin_d->data = {0x11, 0x22, 0x33, 0x44, 0xaa, 0xbb, 0xcc, 0xdd}; + ret = device->transmit(lin_d); + std::cout << (ret ? "OK" : "FAIL") << std::endl << std::endl; + std::cout << "\n\n"; + + // Go offline, stop sending and receiving traffic + auto shutdown = [&](){ + device->removeMessageCallback(handler); + std::cout << "\tGoing offline... "; + ret = device->goOffline(); + std::cout << (ret ? "OK" : "FAIL") << std::endl; + std::cout << "\tDisconnecting... "; + ret = device->close(); + std::cout << (ret ? "OK\n" : "FAIL\n") << std::endl; + }; + + while(true) { + char input = getCharInput(std::vector {'X', 'x'}); + switch(input) { + case 'X': + case 'x': + shutdown(); + printf("Exiting program\n"); + return 0; + default: + break; + } + } + } + return 0; +} \ No newline at end of file diff --git a/include/icsneo/communication/message/linmessage.h b/include/icsneo/communication/message/linmessage.h new file mode 100644 index 0000000..3832955 --- /dev/null +++ b/include/icsneo/communication/message/linmessage.h @@ -0,0 +1,65 @@ +#ifndef __LINMESSAGE_H_ +#define __LINMESSAGE_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/message/message.h" +#include "icsneo/communication/network.h" +#include + +namespace icsneo { + +struct LINErrorFlags { + bool ErrRxBreakOnly = false; + bool ErrRxBreakSyncOnly = false; + bool ErrTxRxMismatch = false; + bool ErrRxBreakNotZero = false; + bool ErrRxBreakTooShort = false; + bool ErrRxSyncNot55 = false; + bool ErrRxDataLenOver8 = false; + bool ErrFrameSync = false; + bool ErrFrameMessageID = false; + bool ErrFrameResponderData = false; + bool ErrChecksumMatch = false; +}; + +struct LINStatusFlags { + bool TxChecksumEnhanced = false; + bool TxCommander = false; + bool TxResponder = false; + bool TxAborted = false; + bool UpdateResponderOnce = false; + bool HasUpdatedResponderOnce = false; + bool BusRecovered = false; + bool BreakOnly = false; +}; + +class LINMessage : public Frame { +public: + static void calcChecksum(LINMessage& message); + + enum class Type { + NOT_SET, + LIN_COMMANDER_MSG, + LIN_HEADER_ONLY, + LIN_BREAK_ONLY, + LIN_SYNC_ONLY, + LIN_UPDATE_RESPONDER, + LIN_ERROR + }; + + uint8_t ID = 0; + uint8_t protectedID = 0; + uint8_t checksum = 0; + LINMessage::Type type = Type::NOT_SET; + bool isEnhancedChecksum = false; + bool isLINStd2x = true; + LINErrorFlags errFlags; + LINStatusFlags statusFlags; +}; + +} + +#endif // __cplusplus + +#endif \ No newline at end of file diff --git a/include/icsneo/communication/message/message.h b/include/icsneo/communication/message/message.h index 592025c..8bb16a8 100644 --- a/include/icsneo/communication/message/message.h +++ b/include/icsneo/communication/message/message.h @@ -18,6 +18,9 @@ public: CANErrorCount = 0x100, + LINHeaderOnly = 0x200, + LINBreak = 0x201, + // Past 0x8000 are all for internal use only Invalid = 0x8000, RawMessage = 0x8001, diff --git a/include/icsneo/communication/packet/linpacket.h b/include/icsneo/communication/packet/linpacket.h new file mode 100644 index 0000000..243dcd0 --- /dev/null +++ b/include/icsneo/communication/packet/linpacket.h @@ -0,0 +1,66 @@ +#ifndef __LINPACKET_H__ +#define __LINPACKET_H__ + +#ifdef __cplusplus + +#include "icsneo/communication/message/linmessage.h" +#include "icsneo/api/eventmanager.h" +#include "icsneo/communication/packetizer.h" +#include "icsneo/communication/network.h" +#include +#include + +namespace icsneo { + +#pragma pack(push, 2) + +struct HardwareLINPacket { + static std::shared_ptr DecodeToMessage(const std::vector& bytestream); + static bool EncodeFromMessage(LINMessage& message, std::vector& bytestream, const device_eventhandler_t& report); + struct { + //CxLIN3 + uint16_t ErrRxOnlyBreak : 1; + uint16_t ErrRxOnlyBreakSync : 1; + uint16_t ID : 11; + uint16_t NETWORKINDEX : 3;//DO NOT CLOBBER THIS + + // CxLIN + uint8_t LINByte9; + uint8_t ErrTxRxMismatch : 1; + uint8_t TxChkSumEnhanced : 1; + uint8_t TXCommander : 1; + uint8_t TXResponder : 1; + uint8_t ErrRxBreakNotZero : 1; + uint8_t ErrRxBreakTooShort : 1; + uint8_t ErrRxSyncNot55 : 1; + uint8_t ErrRxDataGreater8 : 1; + + // CxLIN2 + uint16_t len : 4; + uint16_t ExtendedNetworkIndexBit2 : 1;//DO NOT CLOBBER THIS + uint16_t UpdateResponderOnce : 1; + uint16_t HasUpdatedResponderOnce : 1; + uint16_t ExtendedNetworkIndexBit : 1;//DO NOT CLOBBER THIS + uint16_t BusRecovered : 1; + uint16_t SyncFerr : 1; //Framing error in sync byte + uint16_t MidFerr : 1; // Framing error in message id + uint16_t ResponderByteFerr : 1; //Framing error in one of our responder bytes. + uint16_t TxAborted : 1;//!< This transmit was aborted. + uint16_t BreakOnly : 1; + uint16_t : 2; + } CoreMiniBitsLIN; + + uint8_t data[8]; + + uint16_t stats; //CxTRB0STAT + uint64_t timestamp; //Large timestamp + //CoreMiniMsgExtendedHdr + uint16_t networkID; + uint16_t length; +}; + +#pragma pack(pop) + +} //namespace libicsneo +#endif //_cplusplus +#endif \ No newline at end of file diff --git a/include/icsneo/icsneocpp.h b/include/icsneo/icsneocpp.h index d62305d..584260c 100644 --- a/include/icsneo/icsneocpp.h +++ b/include/icsneo/icsneocpp.h @@ -18,6 +18,7 @@ #include "icsneo/communication/message/ethphymessage.h" #include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/message/a2bmessage.h" +#include "icsneo/communication/message/linmessage.h" #include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h" diff --git a/test/linencoderdecodertest.cpp b/test/linencoderdecodertest.cpp new file mode 100644 index 0000000..5c44830 --- /dev/null +++ b/test/linencoderdecodertest.cpp @@ -0,0 +1,199 @@ +#include "icsneo/icsneocpp.h" +#include "icsneo/communication/encoder.h" +#include "icsneo/communication/packet/linpacket.h" +#include "icsneo/communication/message/linmessage.h" +#include "icsneo/communication/packetizer.h" +#include "icsneo/api/eventmanager.h" +#include "gtest/gtest.h" +#include +#include + +using namespace icsneo; + +class LINEncoderDecoderTest : public ::testing::Test { +protected: + void SetUp() override { + report = [](APIEvent::Type, APIEvent::Severity) { + // Unless caught by the test, the packetizer should not throw errors + EXPECT_TRUE(false); + }; + packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + } + device_eventhandler_t report; + std::optional packetEncoder; + std::optional packetizer; + std::optional packetDecoder; + //Responder load data before response LIN 2 + // ID 0x22 pID 0xE2 length 8 + std::vector testRespData = + {0xaa, 0x0c, + 0x15, 0x00, + 0x30, 0x00, + 0x00, 0x0c, + 0x00, 0x00, + 0xe2, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99}; + + //Controller header LIN 1 + // ID 0x22 pID 0xE2 length 8 + std::vector testControllerHeaderOnly = + {0xaa, 0x0c, + 0x0d, 0x00, + 0x10, 0x00, + 0x00, 0x83, + 0x00, 0x00, + 0xE2, 0x41}; + + std::vector recvBytes = + {0xaa, 0x0c, 0x22, 0x00, + 0x10, 0x00, 0x88, 0x03, + 0x00, 0x08, 0x04, 0x00, + 0xaa, 0xbb, 0xcc, 0xcc, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb3, 0x34, + 0xa8, 0x10, 0x29, 0x13, + 0x48, 0x00, 0x02, 0x00, + 0x00, 0x00, + 0xaa, 0x0c, 0x22, 0x00, + 0x30, 0x00, 0x88, 0x03, + 0x00, 0x04, 0x04, 0x00, + 0xaa, 0xbb, 0xcc, 0xcc, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb4, 0x34, + 0xa8, 0x10, 0x29, 0x13, + 0x48, 0x00, 0x03, 0x00, + 0x00, 0x00}; + +std::vector testControllerWithData = + {0xaa, 0x0c, + 0x11, 0x00, + 0x10, 0x00, + 0x00, 0x87, + 0x00, 0x00, + 0x11, 0xaa, + 0xbb, 0xcc, + 0xcc, 0x41}; +}; + +TEST_F(LINEncoderDecoderTest, ProtectedIDCalcTest) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN; + message->ID = 0x22; + message->type = icsneo::LINMessage::Type::LIN_UPDATE_RESPONDER; + message->isEnhancedChecksum = false; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(message->protectedID, 0xE2); +} + +TEST_F(LINEncoderDecoderTest, ChecksumCalcTestClassic) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN2; + message->ID = 0x22; + message->type = icsneo::LINMessage::Type::LIN_UPDATE_RESPONDER; + message->isEnhancedChecksum = false; + message->data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(message->checksum, 0x99); +} + +TEST_F(LINEncoderDecoderTest, ChecksumCalcTestEnhanced) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN2; + message->ID = 0x22; + message->type = icsneo::LINMessage::Type::LIN_UPDATE_RESPONDER; + message->isEnhancedChecksum = true; + message->data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(message->checksum, 0xB6); +} + +TEST_F(LINEncoderDecoderTest, PacketEncoderResponderLoadTest) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN2; + message->ID = 0x22; + message->type = icsneo::LINMessage::Type::LIN_UPDATE_RESPONDER; + message->isEnhancedChecksum = false; + message->data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testRespData); +} + +TEST_F(LINEncoderDecoderTest, PacketEncoderControllerHeaderTest) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN; + message->ID = 0x22; + message->type = icsneo::LINMessage::Type::LIN_HEADER_ONLY; + message->isEnhancedChecksum = false; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testControllerHeaderOnly); +} + +TEST_F(LINEncoderDecoderTest, PacketEncoderControllerWithDataTest) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::LIN; + message->ID = 0x11; + message->type = icsneo::LINMessage::Type::LIN_COMMANDER_MSG; + message->isEnhancedChecksum = false; + message->data = {0xaa, 0xbb, 0xcc}; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testControllerWithData); +} + +TEST_F(LINEncoderDecoderTest, PacketDecoderTest) { + std::shared_ptr decodeMsg; + auto msg1 = std::make_shared(); + auto msg2 = std::make_shared(); + + msg1->network = icsneo::Network::NetID::LIN2; + msg1->ID = 0x22; + msg1->type = icsneo::LINMessage::Type::LIN_COMMANDER_MSG; + msg1->isEnhancedChecksum = false; + msg1->data = {0xaa, 0xbb, 0xcc}; + msg1->checksum = 0xcc; + + msg2->network = icsneo::Network::NetID::LIN; + msg2->ID = 0x22; + msg2->type = icsneo::LINMessage::Type::LIN_COMMANDER_MSG; + msg2->isEnhancedChecksum = false; + msg2->data = {0xaa, 0xbb, 0xcc}; + msg2->checksum = 0xcc; + + EXPECT_TRUE(packetizer->input(recvBytes)); + auto packets = packetizer->output(); + if(packets.size() != 2) { EXPECT_TRUE(false); } + //LIN2 frame from device + EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back())); + auto testMessage = std::dynamic_pointer_cast(decodeMsg); + EXPECT_EQ(msg1->network, testMessage->network); + EXPECT_EQ(msg1->ID, testMessage->ID); + EXPECT_EQ(msg1->type, testMessage->type); + EXPECT_EQ(msg1->isEnhancedChecksum, testMessage->isEnhancedChecksum); + EXPECT_EQ(msg1->data, testMessage->data); + EXPECT_EQ(msg1->checksum, testMessage->checksum); + packets.pop_back(); + + //LIN1 frame from device + EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back())); + auto testMessage2 = std::dynamic_pointer_cast(decodeMsg); + EXPECT_EQ(msg2->network, testMessage2->network); + EXPECT_EQ(msg2->ID, testMessage2->ID); + EXPECT_EQ(msg2->type, testMessage2->type); + EXPECT_EQ(msg2->isEnhancedChecksum, testMessage2->isEnhancedChecksum); + EXPECT_EQ(msg2->data, testMessage2->data); + EXPECT_EQ(msg2->checksum, testMessage2->checksum); +} \ No newline at end of file