PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_virtual_gsdmlIntegrationMore.cpp
Go to the documentation of this file.
1
9
10#include <gtest/gtest.h>
11
12#include <algorithm>
13#include <chrono>
14#include <cstdlib>
15#include <functional>
16#include <stdexcept>
17#include <string>
18#include <thread>
19#include <vector>
20
21#include "profinet/alarms.h"
22#include "profinet/gsdml.h"
27
28namespace profinet::sim
29{
30
31namespace
32{
33constexpr char kAumaGsdml[] = "GSDML-V2.42-AUMA-SGX-20220920.xml";
34constexpr char kPhoenixGsdml[] = "GSDML-V2.45-Phoenix Contact-AXC F 1252-20250813.xml";
35
37std::string GetGsdmlLocation(const std::string& fileName)
38{
39 const std::array<std::filesystem::path, 4> candidates{
40 GetExecutableDirectory() / "gsdml" / fileName,
41 GetExecutableDirectory() / "tests" / "gsdml" / fileName,
42 std::filesystem::current_path() / "tests" / "gsdml" / fileName,
43 std::filesystem::current_path() / "gsdml" / fileName};
44
45 for (const auto& candidate : candidates)
46 {
47 if (std::filesystem::exists(candidate))
48 {
49 return candidate.string();
50 }
51 }
52
53 ADD_FAILURE() << "GSDML fixture not found: " << fileName;
54 return {};
55}
58std::string AumaGsdmlPath()
59{
60 return GetGsdmlLocation(kAumaGsdml);
61}
62
65std::string PhoenixGsdmlPath()
66{
67 return GetGsdmlLocation(kPhoenixGsdml);
68}
69
74bool WaitUntil(const std::function<bool()>& predicate,
75 const std::chrono::milliseconds timeout = std::chrono::milliseconds(1000))
76{
77 const auto deadline = std::chrono::steady_clock::now() + timeout;
78 while (std::chrono::steady_clock::now() < deadline)
79 {
80 if (predicate())
81 {
82 return true;
83 }
84 std::this_thread::sleep_for(std::chrono::milliseconds(5));
85 }
86 return predicate();
87}
88
89} // namespace
90
92TEST(VirtualRealGsdmlIntegrationTests, ParsesRealAumaGsdmlAndBuildsSignalCatalog)
93{
94 const std::string path = AumaGsdmlPath();
95 if (path.empty())
96 {
97 GTEST_SKIP() << "Set PROFINET_AUMA_GSDML to run the real AUMA integration test";
98 }
99
100 const auto device = gsdml::ParseGsdml(path);
101 EXPECT_EQ(device.vendorId, 0x013FU);
102 EXPECT_EQ(device.deviceId, 0x0002U);
103
104 const auto catalog = io::IoSignalCatalog::FromGsdml(device);
105 EXPECT_NE(std::find_if(catalog.Sensors().begin(), catalog.Sensors().end(),
106 [](const auto& signal)
107 { return signal.textId == "T_ID_ACTPOS_E2"; }),
108 catalog.Sensors().end());
109 EXPECT_NE(std::find_if(catalog.Actuators().begin(), catalog.Actuators().end(),
110 [](const auto& signal)
111 { return signal.textId == "T_ID_CMD_0"; }),
112 catalog.Actuators().end());
113}
114
116TEST(VirtualRealGsdmlIntegrationTests, ParsesRealPhoenixGsdmlAndBuildsSignalCatalog)
117{
118 const std::string path = PhoenixGsdmlPath();
119 if (path.empty())
120 {
121 GTEST_SKIP() << "Set PROFINET_PHOENIX_GSDML to run the real Phoenix integration test";
122 }
123
124 const auto device = gsdml::ParseGsdml(path);
125 EXPECT_EQ(device.vendorId, 0x00B0U);
126 EXPECT_EQ(device.deviceId, 0x0193U);
127
128 const auto catalog = io::IoSignalCatalog::FromGsdml(device);
129 EXPECT_FALSE(catalog.Sensors().empty());
130 EXPECT_FALSE(catalog.Actuators().empty());
131}
132
134TEST(VirtualRealGsdmlIntegrationTests, RunsRealAumaAndPhoenixThroughCyclicController)
135{
136 const std::string aumaPath = AumaGsdmlPath();
137 const std::string phoenixPath = PhoenixGsdmlPath();
138 if (aumaPath.empty() || phoenixPath.empty())
139 {
140 GTEST_SKIP() << "Set both PROFINET_AUMA_GSDML and PROFINET_PHOENIX_GSDML";
141 }
142
143 const auto auma = gsdml::ParseGsdml(aumaPath);
144 const auto phoenix = gsdml::ParseGsdml(phoenixPath);
145 auto iocrs = MakeVirtualAumaIocrConfiguration(auma, 0x9000U, 0x8001U, 32U, 8U, 3U, 3U);
146
148 VirtualAumaValve valve("auma-real", auma, test::kValveMac,
149 iocrs.input, iocrs.output);
150 VirtualPhoenixPlc plc("phoenix-real", phoenix, test::kControllerMac);
151 valve.Start(bus);
152
153 auto controllerIocrs = iocrs;
154 asio::io_context ioContext;
156
157 plc.Connect(ioContext,
158 bus,
159 valve,
160 controllerIocrs.input,
161 controllerIocrs.output,
162 3);
163
164 ASSERT_TRUE(WaitUntil([&plc]
165 {
166 const auto stats = plc.ConnectionStats("auma-real");
167 return stats.has_value() && stats->framesReceived >= 3U;
168 }));
169
170 plc.SetOutput("auma-real", "T_ID_CMD_0", 1.0);
171
172 ASSERT_TRUE(WaitUntil([&valve]
173 {
174 return valve.Position() > 0U;
175 }));
176
177 const auto actualPosition = plc.ReadInput("auma-real", "T_ID_ACTPOS_E2");
178 ASSERT_TRUE(actualPosition.has_value());
179 EXPECT_GT(*actualPosition, 0.0);
180
181 plc.Stop();
182 valve.Stop();
183}
184
185} // namespace profinet::sim
Declares PROFINET alarm, diagnosis, maintenance, and USI payload types and parsers.
Keeps an Asio I/O context running on a dedicated worker thread.
static IoSignalCatalog FromGsdml(const gsdml::GSDMLDevice &device)
Build a signal catalogue from a parsed GSDML device.
Definition IOSignal.cpp:470
Small virtual Ethernet switch suitable for deterministic tests and demos.
AUMA SGx application model that behaves as a real RTC1 peer in memory.
void Stop()
Stop cyclic simulation.
void Start(InMemoryEthernetBus &bus)
Attach the device to a shared virtual Ethernet bus and start cyclic processing.
std::uint16_t Position() const noexcept
Return the current simulated position.
Virtual Phoenix Contact AXC F 1252 controller hosting real CyclicController instances.
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.
void Stop()
Stop all active connections.
void Connect(asio::io_context &ioContext, InMemoryEthernetBus &bus, VirtualAumaValve &valve, rt::IOCRConfig inputIocr, rt::IOCRConfig outputIocr, int maxConsecutiveTimeouts=cyclic::DEFAULT_MAX_CONSECUTIVE_TIMEOUTS)
Connect the PLC to one virtual AUMA device.
std::optional< cyclic::CyclicStatsSnapshot > ConnectionStats(const std::string &valveName) const
Return one connection statistics snapshot.
Declares the GSDML parser and PROFINET device-description data model.
GSDMLDevice ParseGsdml(const std::string &path)
Parse a GSDML file into a GSDMLDevice.
Definition gsdml.cpp:1398
constexpr MacAddress kControllerMac
constexpr MacAddress kValveMac
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.
TEST(InMemoryEthernetRoutingTests, UnicastReachesOnlyMatchingEndpoint)
VirtualAumaIocrConfiguration MakeVirtualAumaIocrConfiguration(const gsdml::GSDMLDevice &device, std::uint16_t inputFrameId=0x9000, std::uint16_t outputFrameId=0x8001, std::uint16_t sendClockFactor=32, std::uint16_t reductionRatio=8, std::uint16_t watchdogFactor=3, std::uint16_t dataHoldFactor=3)
Build the standard two-module AUMA SGx process-data layout from its GSDML.
std::filesystem::path GetExecutableDirectory()
Retrieves the absolute directory path of the currently running executable.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Helpers for constructing a GSDML-derived AUMA SGx RTC1 test configuration.
Stateful in-memory AUMA SGx PROFINET IO device simulator.
In-memory Phoenix Contact AXC F 1252 PLC/controller simulator.
Shared synthetic GSDML and timing helpers for virtual PROFINET tests.