PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_cyclic.cpp
Go to the documentation of this file.
1
23
24#include <gtest/gtest.h>
25
26#include <chrono>
27#include <future>
28#include <thread>
29
32#include "profinet/cyclic.h"
33
34using namespace profinet;
35using namespace profinet::cyclic;
36
37namespace
38{
39
40rt::IOCRConfig MakeConfig(
41 std::uint16_t sendClockFactor,
42 std::uint16_t reductionRatio,
43 std::uint16_t frameId)
44{
46 c.sendClockFactor = sendClockFactor;
47 c.reductionRatio = reductionRatio;
48 c.frameId = frameId;
49 c.dataLength = 40;
50 return c;
51}
52
53} // namespace
54
55namespace profinet::cyclic
56{
57
58class CyclicTest : public ::testing::Test
59{
60 protected:
61 void SetUp() override
62 {
64 }
65
66 static rt::IOCRConfig MakeConfig(std::uint16_t sendClockFactor, std::uint16_t reductionRatio, std::uint16_t frameId)
67 {
68 rt::IOCRConfig config;
69 config.sendClockFactor = sendClockFactor;
70 config.reductionRatio = reductionRatio;
71 config.frameId = frameId;
72 config.dataLength = 40;
73 return config;
74 }
75
76 static std::unique_ptr<CyclicController> MakeController()
77 {
78 const auto input = MakeConfig(32, 32, 0xC001);
79 const auto output = MakeConfig(32, 32, 0x8001);
80 const MacAddress src = String2Mac("aa:bb:cc:dd:ee:ff");
81 const MacAddress dst = String2Mac("11:22:33:44:55:66");
82 auto ioContext = std::make_unique<asio::io_context>();
83 return std::make_unique<CyclicController>(*ioContext, "lo", src, dst, input, output);
84 }
87 {
88 auto rxSocket = std::make_unique<test::BlockingRawEthernetSocket>();
89
90 auto* rxSocketForTest = rxSocket.get();
91
92 const auto input = MakeConfig(32, 32, 0xC001);
93 const auto output = MakeConfig(32, 32, 0x8001);
94
95 const MacAddress src = String2Mac("aa:bb:cc:dd:ee:ff");
96 const MacAddress dst = String2Mac("11:22:33:44:55:66");
97 auto ioContext = std::make_unique<asio::io_context>();
98
99 controller = std::make_unique<CyclicController>(
100 *ioContext,
101 "lo",
102 src,
103 dst,
104 input,
105 output,
108
109 controller->Start(nullptr, std::move(rxSocket));
110
111 rxSocketForTest->WaitUntilReceiveEntered();
112 }
113
115
119 [[nodiscard]] bool RxThreadJoinableForTest()
120 {
121 auto placeholder = controller->rxThread.joinable();
122 return placeholder;
123 }
124 std::unique_ptr<CyclicController> controller;
125};
127TEST_F(CyclicTest, CyclicStateToStringCoversAllStates)
128{
129 EXPECT_EQ(ToString(CyclicState::Idle), "idle");
130 EXPECT_EQ(ToString(CyclicState::Starting), "starting");
131 EXPECT_EQ(ToString(CyclicState::Running), "running");
132 EXPECT_EQ(ToString(CyclicState::Stopping), "stopping");
133 EXPECT_EQ(ToString(CyclicState::Stopped), "stopped");
134 EXPECT_EQ(ToString(CyclicState::Fault), "fault");
135}
136
138TEST_F(CyclicTest, CyclicStatsResetRestoresInitialValues)
139{
140 CyclicStats stats;
141
142 stats.framesSent = 100;
143 stats.framesMissed = 5;
144 stats.cycleTimeSumUs = 5000;
145 stats.cycleCount = 10;
146
147 stats.Reset();
148
149 EXPECT_EQ(stats.framesSent.load(), static_cast<std::uint64_t>(0));
150 EXPECT_EQ(stats.framesMissed.load(), static_cast<std::uint64_t>(0));
151 EXPECT_EQ(stats.AvgCycleTimeUs(), static_cast<std::uint64_t>(0));
152}
153
155TEST_F(CyclicTest, CyclicControllerRejectsNegativeMaxConsecutiveTimeouts)
156{
157 auto input = MakeConfig(32, 32, 0xC001);
158 auto output = MakeConfig(32, 32, 0x8001);
159
160 const MacAddress src = String2Mac("aa:bb:cc:dd:ee:ff");
161
162 const MacAddress dst = String2Mac("11:22:33:44:55:66");
163 auto ioContext = std::make_unique<asio::io_context>();
164
165 EXPECT_THROW(CyclicController(
166 *ioContext,
167 "lo",
168 src,
169 dst,
170 input,
171 output,
172 /*MaxConsecutiveTimeouts=*/-1),
173 std::invalid_argument);
174}
175
177TEST_F(CyclicTest, CyclicControllerRejectsSub1msCycleTime)
178{
179 // send_clock_factor=1, reduction_ratio=1
180 // cycle_time_us = 1*1*125/4 = 31us
181 auto input = MakeConfig(1, 1, 0xC001);
182 auto output = MakeConfig(1, 1, 0x8001);
183
184 const MacAddress src = String2Mac("aa:bb:cc:dd:ee:ff");
185
186 const MacAddress dst = String2Mac("11:22:33:44:55:66");
187 auto ioContext = std::make_unique<asio::io_context>();
188 EXPECT_THROW(CyclicController(
189 *ioContext,
190 "lo",
191 src,
192 dst,
193 input,
194 output),
195 std::exception);
196}
197
199TEST_F(CyclicTest, CyclicControllerConstructsWithSafeCycleTime)
200{
201 auto input = MakeConfig(32, 32, 0xC001);
202 auto output = MakeConfig(32, 32, 0x8001);
203
204 const MacAddress src = String2Mac("aa:bb:cc:dd:ee:ff");
205
206 const MacAddress dst = String2Mac("11:22:33:44:55:66");
207 auto ioContext = std::make_unique<asio::io_context>();
208 const CyclicController c(
209 *ioContext,
210 "lo",
211 src,
212 dst,
213 input,
214 output);
215
216 EXPECT_EQ(ToString(c.State()), "idle");
217
218 EXPECT_FALSE(c.IsRunning());
219}
220
221} // namespace profinet::cyclic
RT_CLASS_1 cyclic data exchange controller.
Definition cyclic.h:248
bool IsRunning() const override
Whether the controller is actively exchanging cyclic data.
Definition cyclic.h:356
CyclicState State() const override
The controller's current lifecycle state.
Definition cyclic.h:363
std::unique_ptr< CyclicController > controller
static std::unique_ptr< CyclicController > MakeController()
static rt::IOCRConfig MakeConfig(std::uint16_t sendClockFactor, std::uint16_t reductionRatio, std::uint16_t frameId)
bool RxThreadJoinableForTest()
Return whether the RX worker thread is still joinable.
void StartControllerWithBlockedReceive()
Start the controller with an injected RX socket that blocks in Receive().
Declares the PROFINET RTC1 cyclic IO controller and process-data exchange API.
Deterministic raw Ethernet socket stub for cyclic-controller unit tests.
GoogleMock implementation of IRawEthernetSocket.
TEST_F(CyclicTest, CyclicStateToStringCoversAllStates)
constexpr int DEFAULT_MAX_CONSECUTIVE_TIMEOUTS
Default number of consecutive watchdog timeouts before transitioning to Fault.
Definition cyclic.h:157
std::string ToString(CyclicState state)
Human-readable name for a CyclicState value.
Definition cyclic.cpp:24
@ Fault
Communication failure (e.g. consecutive watchdog timeouts).
@ Running
Active cyclic data exchange.
@ Stopping
Graceful shutdown in progress (sending STOP frames).
@ Stopped
Fully stopped, threads joined.
@ Starting
Sockets created, threads launching.
@ Idle
Initial state, not yet started.
constexpr std::uint16_t ETHERTYPE_PROFINET
EtherType used by PROFINET RT frames (0x8892).
Definition rt.h:34
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
Statistics for cyclic communication.
Definition cyclic.h:67
void Reset()
Reset all counters to their initial values.
Definition cyclic.cpp:44
std::uint64_t AvgCycleTimeUs() const
Compute the average TX cycle duration.
Definition cyclic.h:112
std::atomic< std::uint64_t > cycleTimeSumUs
Running sum of observed TX cycle durations, in microseconds.
Definition cyclic.h:105
std::atomic< std::uint64_t > framesMissed
Total number of watchdog timeouts (missed input frames).
Definition cyclic.h:75
std::atomic< std::uint64_t > framesSent
Total number of output frames sent.
Definition cyclic.h:69
std::atomic< std::uint64_t > cycleCount
Number of TX cycles included in CycleTimeSumUs.
Definition cyclic.h:108
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
Definition rt.h:137
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition rt.h:160
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rt.h:148
std::uint16_t frameId
Frame ID assigned to this IOCR.
Definition rt.h:145
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rt.h:151
std::uint16_t frameId
Ethernet FrameID.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
MockRawEthernetSocket * rxSocket