PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_virtual_pn_labMore.cpp
Go to the documentation of this file.
1
3
4#include <gtest/gtest.h>
5
6#include <chrono>
7#include <cstddef>
8#include <stdexcept>
9#include <string>
10#include <vector>
11
14
15namespace profinet::sim
16{
17
18namespace
19{
20
25using test::WaitUntil;
26
32void PopulateLab(VirtualProfinetLab& lab,
33 const std::size_t count,
34 const gsdml::GSDMLDevice& auma,
35 const gsdml::GSDMLDevice& phoenix)
36{
37 for (std::size_t index = 0; index < count; ++index)
38 {
39 const std::string valveName = "valve" + std::to_string(index + 1U);
40 const std::string plcName = "plc" + std::to_string(index + 1U);
41 auto iocrs = MakeTestAumaIocrs(auma);
42 lab.AddAumaValve(valveName,
43 auma,
44 MakeIndexedMac(0x20U, index + 1U),
45 std::move(iocrs.input),
46 std::move(iocrs.output));
47 lab.AddPhoenixPlc(plcName, phoenix, MakeIndexedMac(0x10U, index + 1U));
48 auto controllerIocrs = MakeTestAumaIocrs(auma);
49 asio::io_context ioContext;
51
52 lab.Connect(ioContext,
53 plcName,
54 valveName,
55 std::move(controllerIocrs.input),
56 std::move(controllerIocrs.output));
57 }
58}
59
60} // namespace
61
63TEST(VirtualProfinetLabTests, RunsMultipleIndependentValvePlcPairs)
64{
65 constexpr std::size_t kInstanceCount = 4;
66 const auto auma = MakeAumaGsdml();
67 const auto phoenix = MakePhoenixGsdml();
69 PopulateLab(lab, kInstanceCount, auma, phoenix);
70
71 EXPECT_EQ(lab.ValveNames().size(), kInstanceCount);
72 EXPECT_EQ(lab.PlcNames().size(), kInstanceCount);
73 EXPECT_EQ(lab.Bus().EndpointCount(), kInstanceCount + (2U * kInstanceCount));
74
75 for (std::size_t index = 0; index < kInstanceCount; ++index)
76 {
77 const std::string plc = "plc" + std::to_string(index + 1U);
78 const std::string valve = "valve" + std::to_string(index + 1U);
79 lab.Plc(plc).SetOutput(valve, "T_ID_CMD_0", 1.0);
80 }
81
82 ASSERT_TRUE(WaitUntil([&lab]
83 {
84 for (std::size_t index = 0; index < kInstanceCount; ++index)
85 {
86 if (lab.Valve("valve" + std::to_string(index + 1U)).Position() == 0U)
87 {
88 return false;
89 }
90 }
91 return true;
92 }, std::chrono::milliseconds(500)));
93
94 for (std::size_t index = 0; index < kInstanceCount; ++index)
95 {
96 const std::string plc = "plc" + std::to_string(index + 1U);
97 const std::string valve = "valve" + std::to_string(index + 1U);
98 const auto value = lab.Plc(plc).ReadInput(valve, "T_ID_ACTPOS_E2");
99 ASSERT_TRUE(value.has_value());
100 EXPECT_GT(*value, 0.0);
101 }
102
103 lab.Stop();
104 EXPECT_EQ(lab.Bus().EndpointCount(), 0U);
105}
106
108TEST(VirtualProfinetLabTests, DuplicateNamesAreRejected)
109{
110 const auto auma = MakeAumaGsdml();
111 const auto phoenix = MakePhoenixGsdml();
113 auto first = MakeTestAumaIocrs(auma);
114 lab.AddAumaValve("valve", auma, MakeIndexedMac(0x20U, 1U),
115 std::move(first.input), std::move(first.output));
116
117 auto second = MakeTestAumaIocrs(auma);
118 EXPECT_THROW(lab.AddAumaValve("valve", auma, MakeIndexedMac(0x20U, 2U),
119 std::move(second.input), std::move(second.output)),
120 std::invalid_argument);
121
122 lab.AddPhoenixPlc("plc", phoenix, MakeIndexedMac(0x10U, 1U));
123 EXPECT_THROW(lab.AddPhoenixPlc("plc", phoenix, MakeIndexedMac(0x10U, 2U)),
124 std::invalid_argument);
125}
126
128TEST(VirtualProfinetLabTests, DuplicateMacAddressesAreRejectedAcrossNodeTypes)
129{
130 const auto auma = MakeAumaGsdml();
131 const auto phoenix = MakePhoenixGsdml();
133 auto iocrs = MakeTestAumaIocrs(auma);
134 const MacAddress mac = MakeIndexedMac(0x20U, 1U);
135 lab.AddAumaValve("valve1", auma, mac,
136 std::move(iocrs.input), std::move(iocrs.output));
137
138 EXPECT_THROW(lab.AddPhoenixPlc("plc1", phoenix, mac), std::invalid_argument);
139 EXPECT_EQ(lab.ValveNames().size(), 1U);
140 EXPECT_TRUE(lab.PlcNames().empty());
141}
142
144TEST(VirtualProfinetLabTests, ScenarioChangeUpdatesDashboard)
145{
146 const auto auma = MakeAumaGsdml();
147 const auto phoenix = MakePhoenixGsdml();
149 auto iocrs = MakeTestAumaIocrs(auma);
150 lab.AddAumaValve("valve", auma, MakeIndexedMac(0x20U, 1U),
151 std::move(iocrs.input), std::move(iocrs.output));
152 lab.AddPhoenixPlc("plc", phoenix, MakeIndexedMac(0x10U, 1U));
153 auto controllerIocrs = MakeTestAumaIocrs(auma);
154 asio::io_context ioContext;
156
157 lab.Connect(ioContext, "plc", "valve",
158 std::move(controllerIocrs.input),
159 std::move(controllerIocrs.output));
160
162
163 EXPECT_EQ(lab.Valve("valve").Scenario(), VirtualAumaScenario::Jammed);
164 const std::string report = lab.StatusReport();
165 EXPECT_NE(report.find("VIRTUAL PROFINET LAB"), std::string::npos);
166 EXPECT_NE(report.find("scenario=jammed"), std::string::npos);
167
168 lab.Stop();
169}
170
171} // namespace profinet::sim
Keeps an Asio I/O context running on a dedicated worker thread.
std::size_t EndpointCount() const
Return the number of currently connected socket endpoints.
VirtualAumaScenario Scenario() const noexcept
Return the current scenario.
std::uint16_t Position() const noexcept
Return the current simulated position.
std::optional< double > ReadInput(const std::string &valveName, std::string_view signalTextId) const
Read a typed AUMA input signal using the production controller API.
void SetOutput(const std::string &valveName, std::string_view signalTextId, double value)
Set a typed AUMA actuator signal.
Owns one shared Ethernet bus plus any number of virtual PLCs and AUMA valves.
void SetAumaScenario(const std::string &valveName, VirtualAumaScenario scenario)
Set one valve scenario.
std::vector< std::string > PlcNames() const
Return all PLC names.
VirtualPhoenixPlc & AddPhoenixPlc(std::string name, const gsdml::GSDMLDevice &gsdml, MacAddress controllerMac)
Add one Phoenix AXC F 1252 controller instance.
std::string StatusReport() const
Render a compact multi-device status dashboard.
InMemoryEthernetBus & Bus() noexcept
Return the shared virtual bus.
VirtualPhoenixPlc & Plc(const std::string &name)
Find a PLC by name.
void Stop()
Stop all devices and controllers.
VirtualAumaValve & Valve(const std::string &name)
Find a valve by name.
void Connect(asio::io_context &ioContext, const std::string &plcName, const std::string &valveName, rt::IOCRConfig inputIocr, rt::IOCRConfig outputIocr, int maxConsecutiveTimeouts=cyclic::DEFAULT_MAX_CONSECUTIVE_TIMEOUTS)
Connect a PLC to an AUMA valve.
VirtualAumaValve & AddAumaValve(std::string name, const gsdml::GSDMLDevice &gsdml, MacAddress deviceMac, rt::IOCRConfig inputIocr, rt::IOCRConfig outputIocr, VirtualAumaValveConfiguration config={})
Add one AUMA valve instance.
std::vector< std::string > ValveNames() const
Return all valve names.
MacAddress MakeIndexedMac(const std::uint8_t prefix, const std::size_t index)
Allocate a deterministic locally administered MAC address for an instance index.
gsdml::GSDMLDevice MakePhoenixGsdml()
Build a compact Phoenix GSDML model with four-byte DI/DO process data.
VirtualAumaIocrConfiguration MakeTestAumaIocrs(const gsdml::GSDMLDevice &device)
Build matching compact RTC1 IOCR configurations for the AUMA test model.
gsdml::GSDMLDevice MakeAumaGsdml()
Build a compact GSDML model using the real AUMA signal identifiers.
bool WaitUntil(const std::function< bool()> &predicate, std::chrono::milliseconds timeout=std::chrono::milliseconds(500))
Wait until a predicate becomes true or the timeout expires.
@ Jammed
Valve accepts commands but the mechanical position does not change.
TEST(InMemoryEthernetRoutingTests, UnicastReachesOnlyMatchingEndpoint)
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Multi-instance in-memory PROFINET laboratory.
Shared synthetic GSDML and timing helpers for virtual PROFINET tests.