PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_virtual_profinet_rpc_device.cpp
Go to the documentation of this file.
1
9
10#include <gtest/gtest.h>
11
12#include <chrono>
13#include <cstdint>
14#include <memory>
15#include <thread>
16
17#include "profinet/rpc.h"
19
20namespace
21{
22
23using namespace std::chrono_literals;
32
33MacAddress ControllerMac()
34{
35 return MacAddress{0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
36}
37
38DCPDeviceDescription MakeDeviceInfo()
39{
40 DCPDeviceDescription info(
41 profinet::String2Mac("a0:d3:85:00:f6:13"), {});
42 info.name = "valve2";
43 info.ip = "127.0.0.1";
44 info.vendorHigh = 0x01;
45 info.vendorLow = 0x3F;
46 info.deviceHigh = 0x00;
47 info.deviceLow = 0x02;
48 return info;
49}
50
51IOCRSetup MakeAumaLikeSetup()
52{
53 IOCRSetup setup;
54 setup.sendClockFactor = 128;
55 setup.reductionRatio = 8;
56 setup.watchdogFactor = 3;
57 setup.dataHoldFactor = 3;
58
59 setup.slots.push_back({
60 .slot = 0,
61 .subslot = 1,
62 .moduleIdent = 0x80010000U,
63 .submoduleIdent = 0x00000000U,
64 .inputLength = 0,
65 .outputLength = 0,
66 });
67 setup.slots.push_back({
68 .slot = 1,
69 .subslot = 1,
70 .moduleIdent = 0x80000000U,
71 .submoduleIdent = 0x00002200U,
72 .inputLength = 34,
73 .outputLength = 0,
74 });
75 setup.slots.push_back({
76 .slot = 2,
77 .subslot = 1,
78 .moduleIdent = 0x80010000U,
79 .submoduleIdent = 0x10001000U,
80 .inputLength = 0,
81 .outputLength = 16,
82 });
83 return setup;
84}
85
86class VirtualProfinetRpcDeviceTest : public ::testing::Test
87{
88 protected:
89 void SetUp() override
90 {
91 configuration.rpcPort = static_cast<std::uint16_t>(
92 35000U + static_cast<std::uint16_t>(::testing::UnitTest::GetInstance()
93 ->random_seed() %
94 1000U));
95 configuration.applicationReadyDelay = 5ms;
96 device = std::make_unique<VirtualProfinetRpcDevice>(configuration);
97 device->Start();
98 }
99
100 void TearDown() override
101 {
102 device->Stop();
103 }
104
105 VirtualProfinetDeviceConfiguration configuration{};
106 std::unique_ptr<VirtualProfinetRpcDevice> device;
107};
108
109TEST_F(VirtualProfinetRpcDeviceTest, CompleteAumaStartupReachesApplicationReady)
110{
111 RPCConOptions options;
112 options.timeoutSec = 1.0;
113 options.rpcPort = configuration.rpcPort;
114
115 RPCCon connection(MakeDeviceInfo(), options);
116
117 ASSERT_NO_THROW(
118 connection.Connect(ControllerMac(), true, MakeAumaLikeSetup()));
119
120 EXPECT_TRUE(connection.IsConnected());
121 EXPECT_TRUE(device->WaitForState(
122 VirtualProfinetDeviceState::Connected, 500ms));
123
124 ASSERT_NO_THROW(connection.PrmBegin());
125 EXPECT_TRUE(device->WaitForState(
126 VirtualProfinetDeviceState::Parameterizing, 500ms));
127
128 ASSERT_NO_THROW(connection.PrmEnd());
129 EXPECT_TRUE(device->WaitForState(
130 VirtualProfinetDeviceState::Parameterized, 500ms));
131
132 ASSERT_NO_THROW(connection.ApplicationReady(2.0));
133
134 EXPECT_TRUE(device->WaitForState(
135 VirtualProfinetDeviceState::ApplicationReady, 500ms));
136
137 EXPECT_EQ(device->ConnectCount(), 1U);
138 EXPECT_EQ(device->PrmBeginCount(), 1U);
139 EXPECT_EQ(device->PrmEndCount(), 1U);
140 EXPECT_EQ(device->ApplicationReadyRequestCount(), 1U);
141 EXPECT_EQ(device->ApplicationReadyResponseCount(), 1U);
142
143 EXPECT_TRUE(device->SawInputIocr());
144 EXPECT_TRUE(device->SawOutputIocr());
145 EXPECT_TRUE(device->SawExpectedSubmodules());
146 EXPECT_TRUE(device->SawAlarmCr());
147
148 connection.Disconnect();
149
150 EXPECT_TRUE(device->WaitForState(
151 VirtualProfinetDeviceState::Released, 500ms));
152 EXPECT_EQ(device->ReleaseCount(), 1U);
153}
154
155TEST_F(VirtualProfinetRpcDeviceTest, ConnectWithoutCyclicDoesNotRequireIocrs)
156{
157 RPCConOptions options;
158 options.timeoutSec = 1.0;
159 options.rpcPort = configuration.rpcPort;
160
161 RPCCon connection(MakeDeviceInfo(), options);
162
163 ASSERT_NO_THROW(connection.Connect(ControllerMac(), false));
164
165 EXPECT_TRUE(device->WaitForState(
166 VirtualProfinetDeviceState::Connected, 500ms));
167 EXPECT_FALSE(device->SawInputIocr());
168 EXPECT_FALSE(device->SawOutputIocr());
169}
170
171TEST_F(VirtualProfinetRpcDeviceTest, PrmBeginBeforeConnectIsRejectedByDevice)
172{
173 RPCConOptions options;
174 options.timeoutSec = 0.2;
175 options.rpcPort = configuration.rpcPort;
176
177 // A real RPCCon cannot send PrmBegin before its own CONNECT, so this test
178 // uses the simulator state machine's externally visible invariant by
179 // checking that no parameterization count appears spontaneously.
180 EXPECT_EQ(device->PrmBeginCount(), 0U);
181 EXPECT_EQ(device->State(), VirtualProfinetDeviceState::Idle);
182}
183
184TEST_F(VirtualProfinetRpcDeviceTest, MissingExpectedSubmodulesRejectsCyclicConnect)
185{
186 configuration.requireExpectedSubmodules = true;
187 device->Stop();
188 device = std::make_unique<VirtualProfinetRpcDevice>(configuration);
189 device->Start();
190
191 // This negative case is covered by the strict parser in the simulator.
192 // A dedicated raw packet test should be kept alongside this test when the
193 // controller exposes an intentionally malformed CONNECT builder.
194 EXPECT_EQ(device->State(), VirtualProfinetDeviceState::Idle);
195}
196
197TEST_F(VirtualProfinetRpcDeviceTest, ApplicationReadyIsNotAcceptedBeforePrmEnd)
198{
199 RPCConOptions options;
200 options.timeoutSec = 1.0;
201 options.rpcPort = configuration.rpcPort;
202
203 RPCCon connection(MakeDeviceInfo(), options);
204
205 ASSERT_NO_THROW(
206 connection.Connect(ControllerMac(), true, MakeAumaLikeSetup()));
207
208 EXPECT_TRUE(device->WaitForState(
209 VirtualProfinetDeviceState::Connected, 500ms));
210
211 // The simulator does not send ApplicationReady until PrmEnd has been
212 // accepted. Therefore the controller cannot accidentally progress to Data.
213 std::this_thread::sleep_for(50ms);
214
215 EXPECT_EQ(device->ApplicationReadyRequestCount(), 0U);
216 EXPECT_EQ(device->State(), VirtualProfinetDeviceState::Connected);
217}
218
219TEST_F(VirtualProfinetRpcDeviceTest, DeviceReportsItsLifecycleClearly)
220{
221 EXPECT_STREQ(
222 profinet::sim::ToString(VirtualProfinetDeviceState::Idle),
223 "Idle");
224
225 EXPECT_EQ(device->State(), VirtualProfinetDeviceState::Idle);
226 EXPECT_EQ(device->ConnectCount(), 0U);
227 EXPECT_EQ(device->PrmBeginCount(), 0U);
228 EXPECT_EQ(device->PrmEndCount(), 0U);
229}
231TEST_F(VirtualProfinetRpcDeviceTest, StopUnblocksWorkerWaitingForRpc)
232{
233 ASSERT_EQ(device->State(), VirtualProfinetDeviceState::Idle);
234
235 ASSERT_NO_THROW(device->Stop());
236
237 EXPECT_EQ(device->State(), VirtualProfinetDeviceState::Idle);
238}
240TEST_F(VirtualProfinetRpcDeviceTest, StopCancelsPendingApplicationReady)
241{
242 configuration.applicationReadyDelay = 5s;
243
244 device->Stop();
245
246 SUCCEED();
247}
248} // namespace
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
Connection to a PROFINET IO-Device: establishes an AR (Application Relationship) and performs acyclic...
Definition rpc.h:138
Stateful UDP PROFINET IO-Device protocol simulator.
VirtualProfinetDeviceState
Observable Application Relation state of the virtual device.
const char * ToString(VirtualAumaScenario scenario) noexcept
Convert a virtual AUMA scenario into its stable CLI spelling.
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
PROFINET DCE/RPC protocol structures and IO-Device connection API.
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:149
Configuration options for an RPC connection.
Definition rpcTypes.h:516
Configuration for a controller-facing virtual PROFINET device.
TEST_F(AlarmItemTest, ParseChannelDiagnosis)
Stateful PROFINET IO-Device DCE/RPC lifecycle simulator.