Message: LIN: Add wakeup request transmit

This commit is contained in:
Samuel Bejko
2026-11-04 13:30:36 -05:00
committed by Kyle Schwarz
parent ee6b846acc
commit 9f747df692
9 changed files with 228 additions and 0 deletions
+24
View File
@@ -431,6 +431,30 @@ icsneoc2_error_t icsneoc2_message_lin_props_set(icsneoc2_message_t* message,
return icsneoc2_error_success; 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<LINMessage>(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<LINMessage>(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) { 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) { if(!message || !err_flags) {
return icsneoc2_error_invalid_parameters; return icsneoc2_error_invalid_parameters;
@@ -53,6 +53,7 @@ void init_linmessage(pybind11::module_& m) {
.def_readwrite("checksum", &LINMessage::checksum) .def_readwrite("checksum", &LINMessage::checksum)
.def_readwrite("linMsgType", &LINMessage::linMsgType) .def_readwrite("linMsgType", &LINMessage::linMsgType)
.def_readwrite("isEnhancedChecksum", &LINMessage::isEnhancedChecksum) .def_readwrite("isEnhancedChecksum", &LINMessage::isEnhancedChecksum)
.def_readwrite("wakeupPulseWidthUs", &LINMessage::wakeupPulseWidthUs)
.def_readwrite("errFlags", &LINMessage::errFlags) .def_readwrite("errFlags", &LINMessage::errFlags)
.def_readwrite("statusFlags", &LINMessage::statusFlags); .def_readwrite("statusFlags", &LINMessage::statusFlags);
} }
+7
View File
@@ -27,4 +27,11 @@ uint8_t LINMessage::calcProtectedID(uint8_t& id) {
return protID; 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 } //namespace icsneo
+16
View File
@@ -140,6 +140,10 @@ static std::vector<uint8_t> EncodeFromMessageLIN(std::shared_ptr<Frame> frame, c
linpacket->CoreMiniBitsLIN.TXCommander = 1; linpacket->CoreMiniBitsLIN.TXCommander = 1;
linpacket->CoreMiniBitsLIN.BreakOnly = 1; linpacket->CoreMiniBitsLIN.BreakOnly = 1;
break; break;
case LINMessage::Type::LIN_WAKEUP_REQUEST:
linpacket->CoreMiniBitsLIN.TXCommander = 1;
linpacket->CoreMiniBitsLIN.WakeupRequest = 1;
break;
default: default:
break; break;
} }
@@ -165,6 +169,18 @@ static std::vector<uint8_t> EncodeFromMessageLIN(std::shared_ptr<Frame> frame, c
linpacket->CoreMiniBitsLIN.len = 0; 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<uint8_t>(steps);
}
// Description/stats and network // Description/stats and network
linpacket->stats = linmsg->description; linpacket->stats = linmsg->description;
+17
View File
@@ -118,6 +118,16 @@ bool HardwareLINPacket::EncodeFromMessage(LINMessage& message, std::vector<uint8
size |= 0x80u | 0x20u; size |= 0x80u | 0x20u;
break; break;
} }
case LINMessage::Type::LIN_WAKEUP_REQUEST:
{
if(!LINMessage::wakeupPulseWidthSteps(message)) {
report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error);
return false;
}
// wakeup pulse only, one data byte for the pulse width
size = 0x04u | 0x80u | 0x10u;
break;
}
case LINMessage::Type::NOT_SET: case LINMessage::Type::NOT_SET:
{ {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
@@ -145,6 +155,13 @@ bool HardwareLINPacket::EncodeFromMessage(LINMessage& message, std::vector<uint8
bytestream.push_back(message.checksum); bytestream.push_back(message.checksum);
break; break;
} }
case(LINMessage::Type::LIN_WAKEUP_REQUEST):
{
// data byte 0 is the pulse width in 50 us steps
// data bytes 1 to 7: future bits reserved for more LIN wake features
bytestream.push_back(static_cast<uint8_t>(LINMessage::wakeupPulseWidthSteps(message)));
break;
}
default: default:
break; break;
} }
@@ -47,11 +47,14 @@ public:
LIN_SYNC_ONLY = icsneoc2_lin_msg_type_sync_only, LIN_SYNC_ONLY = icsneoc2_lin_msg_type_sync_only,
LIN_UPDATE_RESPONDER = icsneoc2_lin_msg_type_update_responder, LIN_UPDATE_RESPONDER = icsneoc2_lin_msg_type_update_responder,
LIN_ERROR = icsneoc2_lin_msg_type_error, 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 LIN_WAKEUP_REQUEST = icsneoc2_lin_msg_type_wakeup_request
}; };
static void calcChecksum(LINMessage& message); static void calcChecksum(LINMessage& message);
uint8_t calcProtectedID(uint8_t& id); 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() {};
LINMessage(uint8_t id) : ID(id & 0x3Fu), protectedID(calcProtectedID(ID)) {}; LINMessage(uint8_t id) : ID(id & 0x3Fu), protectedID(calcProtectedID(ID)) {};
@@ -61,6 +64,9 @@ public:
uint8_t checksum = 0; uint8_t checksum = 0;
LINMessage::Type linMsgType = Type::NOT_SET; LINMessage::Type linMsgType = Type::NOT_SET;
bool isEnhancedChecksum = false; 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; LINErrorFlags errFlags;
LINStatusFlags statusFlags; LINStatusFlags statusFlags;
}; };
+24
View File
@@ -381,6 +381,7 @@ typedef enum _icsneoc2_lin_msg_type_t {
icsneoc2_lin_msg_type_sync_only = 4, icsneoc2_lin_msg_type_sync_only = 4,
icsneoc2_lin_msg_type_update_responder = 5, icsneoc2_lin_msg_type_update_responder = 5,
icsneoc2_lin_msg_type_error = 6, 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_wakeup_request = 7,
} _icsneoc2_lin_msg_type_t; } _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 uint8_t* id, const uint8_t* checksum,
const icsneoc2_lin_msg_type_t* msg_type, const bool* is_enhanced_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. * Get the LIN error flags of a message.
* *
+24
View File
@@ -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_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_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_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_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_status_flags_get(NULL, NULL));
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_lin_calc_checksum(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); 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) TEST(icsneoc2, test_lin_message_data_and_netid)
{ {
icsneoc2_message_t* msg = nullptr; icsneoc2_message_t* msg = nullptr;
+109
View File
@@ -67,6 +67,24 @@ protected:
0x00, 0x00, 0x00, 0x00,
0x80, 0x41}; 0x80, 0x41};
// Wakeup LIN 1: commander (0x80) | wakeup (0x10) | len 3 => 0x93, description 0x1234
std::vector<uint8_t> 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<uint8_t> testControllerWakeupWidth =
{0xaa, 0x0c,
0x0d, 0x00,
0x10, 0x00,
0x00, 0x94,
0x12, 0x34,
0x80, 0x64};
std::vector<uint8_t> recvBytes = std::vector<uint8_t> recvBytes =
{0xaa, 0x0c, 0x22, 0x00, {0xaa, 0x0c, 0x22, 0x00,
0x10, 0x00, 0x88, 0x03, 0x10, 0x00, 0x88, 0x03,
@@ -174,6 +192,97 @@ TEST_F(LINEncoderDecoderTest, TransmitMessageBreakOnlySetsCommander) {
EXPECT_EQ(lin->CoreMiniBitsLIN.BreakOnly, 1); EXPECT_EQ(lin->CoreMiniBitsLIN.BreakOnly, 1);
} }
TEST_F(LINEncoderDecoderTest, PacketEncoderControllerWakeupTest) {
std::vector<uint8_t> bytestream;
auto message = std::make_shared<icsneo::LINMessage>(static_cast<uint8_t>(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<icsneo::LINMessage>(static_cast<uint8_t>(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<TransmitMessage*>(bytes.data());
auto* lin = reinterpret_cast<HardwareLINPacket*>(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<uint8_t> bytestream;
auto message = std::make_shared<icsneo::LINMessage>(static_cast<uint8_t>(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<icsneo::LINMessage>(static_cast<uint8_t>(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<TransmitMessage*>(bytes.data());
auto* lin = reinterpret_cast<HardwareLINPacket*>(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<icsneo::LINMessage>(static_cast<uint8_t>(0x00u));
message->network = icsneo::Network::NetID::LIN_01;
message->linMsgType = icsneo::LINMessage::Type::LIN_WAKEUP_REQUEST;
const std::pair<uint16_t, uint8_t> 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<TransmitMessage*>(bytes.data());
auto* lin = reinterpret_cast<HardwareLINPacket*>(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<icsneo::LINMessage>(static_cast<uint8_t>(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<uint8_t> bytestream;
EXPECT_FALSE(packetEncoder->encode(*packetizer, bytestream, message));
EXPECT_TRUE(TransmitMessage::EncodeFromMessage(message, 1, report).empty());
}
EXPECT_EQ(errors, 10);
}
TEST_F(LINEncoderDecoderTest, TransmitMessageCalculatesChecksum) { TEST_F(LINEncoderDecoderTest, TransmitMessageCalculatesChecksum) {
auto message = std::make_shared<icsneo::LINMessage>(static_cast<uint8_t>(0x22u)); auto message = std::make_shared<icsneo::LINMessage>(static_cast<uint8_t>(0x22u));
message->network = icsneo::Network::NetID::LIN_02; message->network = icsneo::Network::NetID::LIN_02;