PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_rt.cpp
Go to the documentation of this file.
1
17
18#include <gtest/gtest.h>
19
20#include "profinet/rpc.h"
21#include "profinet/rt.h"
22
23using namespace profinet;
24using namespace profinet::rt;
25
26class RTTest : public ::testing::Test
27{
28};
29
31TEST_F(RTTest, RTFrameRoundTrips)
32{
34 frame.frameId = 0xC001;
35 frame.cycleCounter = 1024;
36 frame.dataStatus = DATA_STATUS_VALID |
38 frame.transferStatus = 0;
39 frame.payload = {0xDE, 0xAD, 0xBE, 0xEF};
40
41 const Bytes encoded = frame.ToBytes();
42
43 EXPECT_EQ(encoded.size(), static_cast<std::size_t>(2 + 4 + 4));
44
45 const RTFrame decoded = RTFrame::FromBytes(encoded);
46
47 EXPECT_EQ(decoded.frameId, frame.frameId);
48 EXPECT_EQ(decoded.cycleCounter, frame.cycleCounter);
49 EXPECT_EQ(decoded.dataStatus, frame.dataStatus);
50 EXPECT_EQ(decoded.payload, frame.payload);
51
52 EXPECT_TRUE(decoded.IsValid());
53 EXPECT_TRUE(decoded.IsRunning());
54 EXPECT_FALSE(decoded.IsOk());
55}
56
58TEST_F(RTTest, RTFrameRejectsTooShortData)
59{
60 EXPECT_THROW(RTFrame::FromBytes({0x01, 0x02}), std::exception);
61}
62
64TEST_F(RTTest, BuildAndParseEthernetFrameRoundTrips)
65{
66 const MacAddress dst = String2Mac("aa:bb:cc:dd:ee:ff");
67
68 const MacAddress src = String2Mac("11:22:33:44:55:66");
69
71 frame.frameId = 0x8001;
72 frame.cycleCounter = 5;
73 frame.dataStatus = DATA_STATUS_VALID;
74 frame.payload = {0x01, 0x02, 0x03};
75
76 const Bytes eth = BuildEthernetFrame(dst, src, frame);
77
78 auto parsed = ParseEthernetFrame(eth);
79
80 ASSERT_TRUE(parsed.has_value());
81
82 EXPECT_EQ(parsed->frameId, frame.frameId);
83
84 EXPECT_EQ(parsed->payload, frame.payload);
85}
86
88TEST_F(RTTest, ParseEthernetFrameRejectsNonProfinetEthertype)
89{
90 Bytes eth(18, 0);
91
92 eth[12] = 0x08;
93 eth[13] = 0x00; // IPv4 ethertype
94
95 auto parsed = ParseEthernetFrame(eth);
96
97 EXPECT_FALSE(parsed.has_value());
98}
99
101TEST_F(RTTest, CyclicDataBuilderSetGetDataRoundTripsThroughSwap)
102{
103 IOCRConfig config;
104 config.dataLength = 10;
105 config.objects = {{.slot = 1, .subslot = 1, .frameOffset = 0, .dataLength = 4, .offsetIOPS = 4, .offsetIOCS = 0}};
106
107 CyclicDataBuilder builder(config);
108
109 builder.SetData(1, 1, {0x11, 0x22, 0x33, 0x44});
110
111 builder.SetIops(1, 1, IOXS_GOOD);
112
113 Bytes beforeSwap = builder.Build();
114
115 EXPECT_EQ(beforeSwap[0], static_cast<std::uint8_t>(0));
116
117 builder.Swap();
118
119 Bytes afterSwap = builder.Build();
120
121 EXPECT_EQ(afterSwap[0], static_cast<std::uint8_t>(0x11));
122
123 EXPECT_EQ(afterSwap[4], IOXS_GOOD);
124
125 const Bytes readback = builder.GetData(1, 1);
126
127 EXPECT_EQ(readback, Bytes({0x11, 0x22, 0x33, 0x44}));
128}
129
131TEST_F(RTTest, CyclicDataBuilderRejectsUnknownSlotSubslot)
132{
133 IOCRConfig config;
134 config.dataLength = 4;
135
136 CyclicDataBuilder builder(config);
137
138 EXPECT_THROW(builder.SetData(9, 9, {0x01}), std::exception);
139}
140
141TEST_F(RTTest, BuildReturnsStablePublishedSnapshot)
142{
143 IOCRConfig config;
144 config.dataLength = 10;
145 config.objects = {{.slot = 1, .subslot = 1, .frameOffset = 0, .dataLength = 4, .offsetIOPS = 4, .offsetIOCS = 0}};
146 CyclicDataBuilder builder(config);
147 builder.SetData(1, 1, {0x80, 0x01});
148 builder.Swap();
149 const auto first = builder.Build();
150
151 builder.SetData(1, 1, {0x80, 0x02});
152 EXPECT_EQ(builder.Build(), first);
153
154 builder.Swap();
155 EXPECT_NE(builder.Build(), first);
156}
157
158TEST_F(RTTest, ConcurrentSetDataAndBuildIsRaceFree)
159{
160 IOCRConfig config;
161 config.dataLength = 10;
162 config.objects = {{.slot = 1, .subslot = 1, .frameOffset = 0, .dataLength = 4, .offsetIOPS = 4, .offsetIOCS = 0}};
163 CyclicDataBuilder builder(config);
164
165 std::atomic_bool stop{false};
166 std::jthread writer([&]
167 {
168 while (!stop.load())
169 {
170 builder.SetData(1, 1, {0x80, 0x01});
171 builder.Swap();
172 }
173 });
174 auto expectedFrameSize = 10U;
175 for (int i = 0; i < 10000; ++i)
176 {
177 const auto frame = builder.Build();
178 ASSERT_LE(frame.size(), expectedFrameSize);
179 }
180 stop = true;
181}
182
184TEST_F(RTTest, DISABLED_BuildIocrConfigsComputesExpectedFrameOffsets)
185{
186 const std::vector<rpc::IOSlot> slots = {{.slot = 0, .subslot = 1, /*input=*/.inputLength = 4, /*output=*/.outputLength = 0, .moduleIdent = 0, .submoduleIdent = 0}, {.slot = 1, .subslot = 1, /*input=*/.inputLength = 0, /*output=*/.outputLength = 2, .moduleIdent = 0, .submoduleIdent = 0}};
187
188 auto [input_iocr, output_iocr] = BuildIocrConfigs(slots, 0xC001, 0x8001);
189
190 // Input IOCR:
191 // slot 0 has 4 bytes data + 1 IOPS = 5 bytes.
192 // slot 1 has no input and gets a 1-byte IOCS placeholder.
193 // Total is padded to the 40-byte minimum.
194 ASSERT_EQ(input_iocr.objects.size(), static_cast<std::size_t>(1));
195
196 EXPECT_EQ(input_iocr.objects[0].frameOffset, 0);
197 EXPECT_EQ(input_iocr.objects[0].dataLength, 4);
198
199 EXPECT_EQ(input_iocr.objects[0].offsetIOPS, 4);
200
201 EXPECT_EQ(input_iocr.dataLength, 40);
202
203 EXPECT_EQ(input_iocr.frameId, static_cast<std::uint16_t>(0xC001));
204
205 // Output IOCR:
206 // slot 1 has 2 bytes data + 1 IOPS.
207 // slot 0 has no output and gets an IOCS entry.
208 ASSERT_EQ(output_iocr.objects.size(), static_cast<std::size_t>(2));
209
210 EXPECT_EQ(output_iocr.objects[0].slot, 1);
211
212 EXPECT_EQ(output_iocr.objects[0].dataLength, 2);
213
214 EXPECT_EQ(output_iocr.objects[1].slot, 0);
215
216 EXPECT_EQ(output_iocr.objects[1].dataLength, 0);
217
218 EXPECT_GT(output_iocr.objects[1].offsetIOCS, 0);
219
220 EXPECT_EQ(output_iocr.frameId, static_cast<std::uint16_t>(0x8001));
221}
Builds C_SDU payload from IO data objects with double-buffering.
Definition rt.h:287
Bytes GetData(int slot, int subslot) const
Read one object's data from the write buffer.
Definition rt.cpp:133
void SetData(int slot, int subslot, const Bytes &data)
Write one object's data into the write buffer.
Definition rt.cpp:116
void SetIops(int slot, int subslot, std::uint8_t status=IOXS_GOOD)
Set one object's IOPS (provider status) byte.
Definition rt.cpp:149
void Swap()
Promote the write buffer to the send buffer.
Definition rt.cpp:218
Bytes Build() const
Get the current send buffer contents.
Definition rt.cpp:228
std::optional< RTFrame > ParseEthernetFrame(const Bytes &data)
Parse a complete Ethernet frame and extract its RT frame.
Definition rt.cpp:261
constexpr std::uint8_t DATA_STATUS_PROVIDER_RUN
DataStatus bit: provider run state, 0=Stop, 1=Run.
Definition rt.h:87
std::pair< IOCRConfig, IOCRConfig > BuildIocrConfigs(const std::vector< SlotT > &slots, std::uint16_t inputFrameId, std::uint16_t outputFrameId, std::uint16_t sendClockFactor=32, std::uint16_t reductionRatio=32, int watchdogFactor=3, std::uint16_t cSduLength=40)
Definition rt.h:454
Bytes BuildEthernetFrame(const MacAddress &dstMac, const MacAddress &srcMac, const RTFrame &rtFrame)
Build a complete Ethernet frame carrying an RT frame.
Definition rt.cpp:242
constexpr std::uint8_t DATA_STATUS_VALID
DataStatus bit: data validity, 0=Invalid, 1=Valid.
Definition rt.h:83
constexpr std::uint8_t IOXS_GOOD
IOxS value: good, subslot level.
Definition rt.h:95
TEST_F(AlarmListenerTests, InitializesWithEndpoint)
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
PROFINET DCE/RPC protocol structures and IO-Device connection API.
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
Definition rt.h:137
std::vector< IODataObject > objects
IO data objects carried in this IOCR's cyclic frame.
Definition rt.h:163
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition rt.h:160
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
Definition rt.h:208
std::uint8_t dataStatus
Data status bitmask (see DATA_STATUS_* constants).
Definition rt.h:216
static RTFrame FromBytes(const Bytes &data)
Parse an RT frame from raw bytes (after the Ethernet header).
Definition rt.cpp:14
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
Definition rt.h:210
std::uint16_t cycleCounter
Cycle counter, incremented each transmission.
Definition rt.h:213
bool IsOk() const
Whether the DataStatus station-OK bit is set.
Definition rt.h:264
Bytes payload
C_SDU payload: process data plus IOPS/IOCS trailers.
Definition rt.h:222
bool IsValid() const
Whether the DataStatus valid bit is set.
Definition rt.h:250
bool IsRunning() const
Whether the DataStatus provider-run bit is set.
Definition rt.h:257
Bytes frame