PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
virtual_pn_lab.cpp
Go to the documentation of this file.
1
3
5
6#include <algorithm>
7#include <sstream>
8#include <stdexcept>
9
10namespace profinet::sim
11{
12
17
19 const gsdml::GSDMLDevice& gsdml,
20 MacAddress deviceMac,
21 rt::IOCRConfig inputIocr,
22 rt::IOCRConfig outputIocr,
24{
25 if (IsMacInUse(deviceMac))
26 {
27 throw std::invalid_argument("AUMA simulator MAC address is already in use: " +
28 Mac2String(deviceMac));
29 }
30 if (std::any_of(valves.begin(), valves.end(), [&name](const AumaEntry& entry)
31 { return entry.name == name; }))
32 {
33 throw std::invalid_argument("AUMA simulator name already exists: " + name);
34 }
35 auto valve = std::make_unique<VirtualAumaValve>(
36 name,
37 gsdml,
38 deviceMac,
39 std::move(inputIocr),
40 std::move(outputIocr),
41 std::move(config));
42 valve->Start(bus);
43 valves.push_back({std::move(name), std::move(valve)});
44 return *valves.back().valve;
45}
46
48 const gsdml::GSDMLDevice& gsdml,
49 MacAddress controllerMac)
50{
51 if (IsMacInUse(controllerMac))
52 {
53 throw std::invalid_argument("Phoenix simulator MAC address is already in use: " +
54 Mac2String(controllerMac));
55 }
56 if (std::any_of(plcs.begin(), plcs.end(), [&name](const PlcEntry& entry)
57 { return entry.name == name; }))
58 {
59 throw std::invalid_argument("Phoenix simulator name already exists: " + name);
60 }
61 auto plc = std::make_unique<VirtualPhoenixPlc>(name, gsdml, controllerMac);
62 plcs.push_back({std::move(name), std::move(plc)});
63 return *plcs.back().plc;
64}
65
67 const std::string& plcName,
68 const std::string& valveName,
69 rt::IOCRConfig inputIocr,
70 rt::IOCRConfig outputIocr,
71 const int maxConsecutiveTimeouts)
72{
73 Plc(plcName).Connect(ioContext,
74 bus,
75 Valve(valveName),
76 std::move(inputIocr),
77 std::move(outputIocr),
78 maxConsecutiveTimeouts);
79}
80
81void VirtualProfinetLab::SetAumaScenario(const std::string& valveName,
82 const VirtualAumaScenario scenario)
83{
84 Valve(valveName).SetScenario(scenario);
85}
86
88{
89 return bus;
90}
91
93{
94 const auto it = std::find_if(valves.begin(), valves.end(), [&name](const AumaEntry& entry)
95 { return entry.name == name; });
96 if (it == valves.end())
97 {
98 throw std::invalid_argument("Unknown AUMA simulator: " + name);
99 }
100 return *it->valve;
101}
102
104{
105 const auto it = std::find_if(plcs.begin(), plcs.end(), [&name](const PlcEntry& entry)
106 { return entry.name == name; });
107 if (it == plcs.end())
108 {
109 throw std::invalid_argument("Unknown Phoenix simulator: " + name);
110 }
111 return *it->plc;
112}
113
114const VirtualAumaValve& VirtualProfinetLab::Valve(const std::string& name) const
115{
116 const auto it = std::find_if(valves.begin(), valves.end(), [&name](const AumaEntry& entry)
117 { return entry.name == name; });
118 if (it == valves.end())
119 {
120 throw std::invalid_argument("Unknown AUMA simulator: " + name);
121 }
122 return *it->valve;
123}
124
125const VirtualPhoenixPlc& VirtualProfinetLab::Plc(const std::string& name) const
126{
127 const auto it = std::find_if(plcs.begin(), plcs.end(), [&name](const PlcEntry& entry)
128 { return entry.name == name; });
129 if (it == plcs.end())
130 {
131 throw std::invalid_argument("Unknown Phoenix simulator: " + name);
132 }
133 return *it->plc;
134}
135
136std::vector<std::string> VirtualProfinetLab::ValveNames() const
137{
138 std::vector<std::string> result;
139 result.reserve(valves.size());
140 for (const auto& valve : valves)
141 {
142 result.push_back(valve.name);
143 }
144 return result;
145}
146
147std::vector<std::string> VirtualProfinetLab::PlcNames() const
148{
149 std::vector<std::string> result;
150 result.reserve(plcs.size());
151 for (const auto& plc : plcs)
152 {
153 result.push_back(plc.name);
154 }
155 return result;
156}
157
159{
160 std::ostringstream out;
161 out << "============================================================\n";
162 out << " VIRTUAL PROFINET LAB\n";
163 out << "============================================================\n";
164 out << "Ethernet endpoints: " << bus.EndpointCount() << '\n';
165 out << "Virtual AUMA valves: " << valves.size() << '\n';
166 for (const auto& valve : valves)
167 {
168 out << " AUMA " << valve.name
169 << " MAC=" << Mac2String(valve.valve->DeviceMac())
170 << " scenario=" << ToString(valve.valve->Scenario())
171 << " pos=" << valve.valve->Position()
172 << " target=" << valve.valve->TargetPosition()
173 << " moving=" << (valve.valve->Moving() ? "yes" : "no")
174 << '\n';
175 }
176 out << "Virtual Phoenix PLCs: " << plcs.size() << '\n';
177 for (const auto& plc : plcs)
178 {
179 out << plc.plc->StatusReport();
180 }
181 out << "============================================================\n";
182 return out.str();
183}
184
185bool VirtualProfinetLab::IsMacInUse(const MacAddress& mac) const noexcept
186{
187 for (const auto& entry : valves)
188 {
189 if (entry.valve != nullptr && entry.valve->DeviceMac() == mac)
190 {
191 return true;
192 }
193 }
194 for (const auto& entry : plcs)
195 {
196 if (entry.plc != nullptr && entry.plc->ControllerMac() == mac)
197 {
198 return true;
199 }
200 }
201 return false;
202}
203
205{
206 for (auto& plc : plcs)
207 {
208 if (plc.plc != nullptr)
209 {
210 plc.plc->Stop();
211 }
212 }
213 for (auto& valve : valves)
214 {
215 if (valve.valve != nullptr)
216 {
217 valve.valve->Stop();
218 }
219 }
220}
221
222} // namespace profinet::sim
Small virtual Ethernet switch suitable for deterministic tests and demos.
std::size_t EndpointCount() const
Return the number of currently connected socket endpoints.
AUMA SGx application model that behaves as a real RTC1 peer in memory.
void SetScenario(VirtualAumaScenario scenario)
Set the current simulator scenario.
Virtual Phoenix Contact AXC F 1252 controller hosting real CyclicController instances.
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.
void SetAumaScenario(const std::string &valveName, VirtualAumaScenario scenario)
Set one valve scenario.
std::vector< AumaEntry > valves
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.
bool IsMacInUse(const MacAddress &mac) const noexcept
Determine whether a node MAC is already used by this lab.
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.
VirtualAumaScenario
Deterministic operating and communication scenarios for a virtual AUMA valve.
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
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
Definition util.cpp:188
Complete parsed GSDML device profile.
Definition gsdml.h:166
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
Definition rt.h:137
Application-level signal mapping and motion settings for a virtual AUMA SGx.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Multi-instance in-memory PROFINET laboratory.