diff --git a/api/icsneoc2/icsneoc2messages.cpp b/api/icsneoc2/icsneoc2messages.cpp index f8f60fc4..6e84cba2 100644 --- a/api/icsneoc2/icsneoc2messages.cpp +++ b/api/icsneoc2/icsneoc2messages.cpp @@ -431,6 +431,30 @@ icsneoc2_error_t icsneoc2_message_lin_props_set(icsneoc2_message_t* message, return icsneoc2_error_success; } +icsneoc2_error_t icsneoc2_message_lin_wakeup_width_get(const icsneoc2_message_t* message, uint16_t* width_us) { + if(!message || !width_us) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + *width_us = lin_msg->wakeupPulseWidthUs; + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_lin_wakeup_width_set(icsneoc2_message_t* message, uint16_t width_us) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto lin_msg = std::dynamic_pointer_cast(message->message); + if(!lin_msg) { + return icsneoc2_error_invalid_type; + } + lin_msg->wakeupPulseWidthUs = width_us; + 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; diff --git a/bindings/python/icsneopy/communication/message/linmessage.cpp b/bindings/python/icsneopy/communication/message/linmessage.cpp index 55036e8e..f7456c67 100644 --- a/bindings/python/icsneopy/communication/message/linmessage.cpp +++ b/bindings/python/icsneopy/communication/message/linmessage.cpp @@ -53,6 +53,7 @@ void init_linmessage(pybind11::module_& m) { .def_readwrite("checksum", &LINMessage::checksum) .def_readwrite("linMsgType", &LINMessage::linMsgType) .def_readwrite("isEnhancedChecksum", &LINMessage::isEnhancedChecksum) + .def_readwrite("wakeupPulseWidthUs", &LINMessage::wakeupPulseWidthUs) .def_readwrite("errFlags", &LINMessage::errFlags) .def_readwrite("statusFlags", &LINMessage::statusFlags); } diff --git a/communication/message/linmessage.cpp b/communication/message/linmessage.cpp index 835cd678..3048a0fe 100644 --- a/communication/message/linmessage.cpp +++ b/communication/message/linmessage.cpp @@ -27,4 +27,11 @@ uint8_t LINMessage::calcProtectedID(uint8_t& id) { return protID; } +int LINMessage::wakeupPulseWidthSteps(const LINMessage& message) { + const uint16_t widthUs = message.wakeupPulseWidthUs; + if(widthUs < 250 || widthUs > 5000) + return 0; + return (widthUs + 25) / 50; +} + } //namespace icsneo \ No newline at end of file diff --git a/communication/message/transmitmessage.cpp b/communication/message/transmitmessage.cpp index d638abe1..a8e80d5c 100644 --- a/communication/message/transmitmessage.cpp +++ b/communication/message/transmitmessage.cpp @@ -140,6 +140,10 @@ static std::vector EncodeFromMessageLIN(std::shared_ptr frame, c linpacket->CoreMiniBitsLIN.TXCommander = 1; linpacket->CoreMiniBitsLIN.BreakOnly = 1; break; + case LINMessage::Type::LIN_WAKEUP_REQUEST: + linpacket->CoreMiniBitsLIN.TXCommander = 1; + linpacket->CoreMiniBitsLIN.WakeupRequest = 1; + break; default: break; } @@ -165,6 +169,18 @@ static std::vector EncodeFromMessageLIN(std::shared_ptr frame, c linpacket->CoreMiniBitsLIN.len = 0; } + if(linmsg->linMsgType == LINMessage::Type::LIN_WAKEUP_REQUEST) { + const int steps = LINMessage::wakeupPulseWidthSteps(*linmsg); + if(!steps) { + report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); + return {}; + } + // data[0] is the pulse width in 50 us steps + // data[1] to data[7]: future bits reserved for more LIN wake features + linpacket->CoreMiniBitsLIN.len = 1; + linpacket->data[0] = static_cast(steps); + } + // Description/stats and network linpacket->stats = linmsg->description; diff --git a/communication/packet/linpacket.cpp b/communication/packet/linpacket.cpp index e6b1ac6f..f763109d 100644 --- a/communication/packet/linpacket.cpp +++ b/communication/packet/linpacket.cpp @@ -118,6 +118,16 @@ bool HardwareLINPacket::EncodeFromMessage(LINMessage& message, std::vector(LINMessage::wakeupPulseWidthSteps(message))); + break; + } default: break; } diff --git a/include/icsneo/communication/message/linmessage.h b/include/icsneo/communication/message/linmessage.h index 416cd5a5..6e2fd33b 100644 --- a/include/icsneo/communication/message/linmessage.h +++ b/include/icsneo/communication/message/linmessage.h @@ -47,11 +47,14 @@ public: 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, + // needs firmware with LIN wakeup transmit; older firmware sends a commander header (break, sync, ID) instead LIN_WAKEUP_REQUEST = icsneoc2_lin_msg_type_wakeup_request }; static void calcChecksum(LINMessage& message); uint8_t calcProtectedID(uint8_t& id); + // wakeupPulseWidthUs in 50 us steps, 0 if it is outside 250 to 5000 us + static int wakeupPulseWidthSteps(const LINMessage& message); LINMessage() {}; LINMessage(uint8_t id) : ID(id & 0x3Fu), protectedID(calcProtectedID(ID)) {}; @@ -61,6 +64,9 @@ public: uint8_t checksum = 0; LINMessage::Type linMsgType = Type::NOT_SET; bool isEnhancedChecksum = false; + // LIN_WAKEUP_REQUEST only, the same as the Vehicle Spy LIN Send Wake Width property: 250 to 5000 us, + // rounded to the nearest 50 us. Outside that range the transmit fails. + uint16_t wakeupPulseWidthUs = 300; LINErrorFlags errFlags; LINStatusFlags statusFlags; }; diff --git a/include/icsneo/icsneoc2messages.h b/include/icsneo/icsneoc2messages.h index b122a108..e96ea616 100644 --- a/include/icsneo/icsneoc2messages.h +++ b/include/icsneo/icsneoc2messages.h @@ -381,6 +381,7 @@ typedef enum _icsneoc2_lin_msg_type_t { icsneoc2_lin_msg_type_sync_only = 4, icsneoc2_lin_msg_type_update_responder = 5, icsneoc2_lin_msg_type_error = 6, + // needs firmware with LIN wakeup transmit; older firmware sends a commander header (break, sync, ID) instead icsneoc2_lin_msg_type_wakeup_request = 7, } _icsneoc2_lin_msg_type_t; @@ -461,6 +462,29 @@ 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 wakeup pulse width of a LIN message. + * + * @param[in] message The message to query (must be a LIN message). + * @param[out] width_us Pointer to receive the pulse width in microseconds. The default is 300 us. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + */ +icsneoc2_error_t icsneoc2_message_lin_wakeup_width_get(const icsneoc2_message_t* message, uint16_t* width_us); + +/** + * Set the wakeup pulse width of a LIN message. Only used by icsneoc2_lin_msg_type_wakeup_request. + * + * The same as the Vehicle Spy LIN Send Wake Width property: 250 to 5000 us, rounded to the nearest 50 us. + * Outside that range the transmit fails. + * + * @param[in] message The message to modify (must be a LIN message). + * @param[in] width_us The pulse width in microseconds. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_type if not a LIN message. + */ +icsneoc2_error_t icsneoc2_message_lin_wakeup_width_set(icsneoc2_message_t* message, uint16_t width_us); + /** * Get the LIN error flags of a message. * diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index 7d4b7e39..0a801e65 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -208,6 +208,8 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) 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_wakeup_width_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_wakeup_width_set(NULL, 0)); 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)); @@ -1768,6 +1770,28 @@ TEST(icsneoc2, test_lin_message_props_set) icsneoc2_message_free(msg); } +TEST(icsneoc2, test_lin_message_wakeup_width) +{ + icsneoc2_message_t* msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_create(&msg, 0x00)); + + uint16_t width_us = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_wakeup_width_get(msg, &width_us)); + ASSERT_EQ(width_us, 300); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_wakeup_width_set(msg, 2500)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_lin_wakeup_width_get(msg, &width_us)); + ASSERT_EQ(width_us, 2500); + + 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_wakeup_width_set(can_msg, 300)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_lin_wakeup_width_get(can_msg, &width_us)); + icsneoc2_message_free(can_msg); + + icsneoc2_message_free(msg); +} + TEST(icsneoc2, test_lin_message_data_and_netid) { icsneoc2_message_t* msg = nullptr; diff --git a/test/unit/linencoderdecodertest.cpp b/test/unit/linencoderdecodertest.cpp index 42dadbf8..f8bcc832 100644 --- a/test/unit/linencoderdecodertest.cpp +++ b/test/unit/linencoderdecodertest.cpp @@ -67,6 +67,24 @@ protected: 0x00, 0x00, 0x80, 0x41}; + // Wakeup LIN 1: commander (0x80) | wakeup (0x10) | len 3 => 0x93, description 0x1234 + std::vector testControllerWakeup = + {0xaa, 0x0c, + 0x0d, 0x00, + 0x10, 0x00, + 0x00, 0x94, + 0x12, 0x34, + 0x80, 0x06}; + + // Wakeup LIN 1 with a 5000 us pulse: len 4 => 0x94, data byte 0 is 100 steps of 50 us + std::vector testControllerWakeupWidth = + {0xaa, 0x0c, + 0x0d, 0x00, + 0x10, 0x00, + 0x00, 0x94, + 0x12, 0x34, + 0x80, 0x64}; + std::vector recvBytes = {0xaa, 0x0c, 0x22, 0x00, 0x10, 0x00, 0x88, 0x03, @@ -174,6 +192,97 @@ TEST_F(LINEncoderDecoderTest, TransmitMessageBreakOnlySetsCommander) { EXPECT_EQ(lin->CoreMiniBitsLIN.BreakOnly, 1); } +TEST_F(LINEncoderDecoderTest, PacketEncoderControllerWakeupTest) { + std::vector bytestream; + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + message->isEnhancedChecksum = false; + message->description = 0x1234; + message->data = {0x55}; // ignored, a wakeup never carries data + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testControllerWakeup); +} + +TEST_F(LINEncoderDecoderTest, TransmitMessageWakeupSetsCommander) { + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + message->description = 0x1234; + message->data = {0x55}; + auto bytes = TransmitMessage::EncodeFromMessage(message, 1, report); + ASSERT_FALSE(bytes.empty()); + ASSERT_GE(bytes.size(), sizeof(TransmitMessage)); + auto* tx = reinterpret_cast(bytes.data()); + auto* lin = reinterpret_cast(tx->commonHeader); + EXPECT_EQ(lin->CoreMiniBitsLIN.TXCommander, 1); + EXPECT_EQ(lin->CoreMiniBitsLIN.WakeupRequest, 1); + EXPECT_EQ(lin->CoreMiniBitsLIN.BreakOnly, 0); + EXPECT_EQ(lin->CoreMiniBitsLIN.len, 1); + EXPECT_EQ(lin->data[0], 6); // default 300 us + EXPECT_EQ(lin->stats, 0x1234); +} + +TEST_F(LINEncoderDecoderTest, PacketEncoderControllerWakeupWidthTest) { + std::vector bytestream; + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + message->description = 0x1234; + message->wakeupPulseWidthUs = 5000; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testControllerWakeupWidth); +} + +TEST_F(LINEncoderDecoderTest, TransmitMessageWakeupWidth) { + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + message->wakeupPulseWidthUs = 250; + auto bytes = TransmitMessage::EncodeFromMessage(message, 1, report); + ASSERT_GE(bytes.size(), sizeof(TransmitMessage)); + auto* tx = reinterpret_cast(bytes.data()); + auto* lin = reinterpret_cast(tx->commonHeader); + EXPECT_EQ(lin->CoreMiniBitsLIN.WakeupRequest, 1); + EXPECT_EQ(lin->CoreMiniBitsLIN.len, 1); + EXPECT_EQ(lin->data[0], 5); + EXPECT_EQ(lin->data[1], 0); +} + +TEST_F(LINEncoderDecoderTest, WakeupWidthRounds) { + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + const std::pair widths[] = { + {250, 5}, {274, 5}, {275, 6}, {326, 7}, {4975, 100}, {5000, 100}}; + for(const auto& [widthUs, steps] : widths) { + message->wakeupPulseWidthUs = widthUs; + auto bytes = TransmitMessage::EncodeFromMessage(message, 1, report); + ASSERT_GE(bytes.size(), sizeof(TransmitMessage)); + auto* tx = reinterpret_cast(bytes.data()); + auto* lin = reinterpret_cast(tx->commonHeader); + EXPECT_EQ(lin->data[0], steps) << widthUs << " us"; + } +} + +TEST_F(LINEncoderDecoderTest, WakeupWidthOutOfRangeIsRejected) { + int errors = 0; + report = [&errors](APIEvent::Type type, APIEvent::Severity) { + EXPECT_EQ(type, APIEvent::Type::ParameterOutOfRange); + ++errors; + }; + auto message = std::make_shared(static_cast(0x00u)); + message->network = icsneo::Network::NetID::LIN_01; + message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST; + for(uint16_t widthUs : {uint16_t(0), uint16_t(1), uint16_t(249), uint16_t(5001), uint16_t(60000)}) { + message->wakeupPulseWidthUs = widthUs; + std::vector bytestream; + EXPECT_FALSE(packetEncoder->encode(*packetizer, bytestream, message)); + EXPECT_TRUE(TransmitMessage::EncodeFromMessage(message, 1, report).empty()); + } + EXPECT_EQ(errors, 10); +} + TEST_F(LINEncoderDecoderTest, TransmitMessageCalculatesChecksum) { auto message = std::make_shared(static_cast(0x22u)); message->network = icsneo::Network::NetID::LIN_02;