4#include <gtest/gtest.h>
33 return MacAddress{0x02, 0x00, 0x00, 0x10, 0x00, 0x01};
40 return MacAddress{0x02, 0x00, 0x00, 0x20, 0x00, 0x01};
46TEST(VirtualPhoenixPlcTests, BuildsGsdmlDerivedProcessImage)
48 const auto gsdml = MakePhoenixGsdml();
58TEST(VirtualPhoenixPlcTests, LocalInputBytesRoundTrip)
60 const auto gsdml = MakePhoenixGsdml();
63 const Bytes input{0x01U, 0x23U, 0x45U, 0x67U};
71TEST(VirtualPhoenixPlcTests, LocalTypedValuesRoundTrip)
73 const auto gsdml = MakePhoenixGsdml();
93TEST(VirtualPhoenixPlcTests, BitSignalsPreserveSiblingBits)
95 const auto gsdml = MakePhoenixGsdml();
103 ASSERT_TRUE(bytes.has_value());
104 ASSERT_EQ(bytes->size(), 1U);
105 EXPECT_EQ((*bytes)[0], 0x09U);
109 ASSERT_TRUE(cleared.has_value());
110 EXPECT_EQ((*cleared)[0], 0x08U);
114TEST(VirtualPhoenixPlcTests, TypedValueValidationRejectsInvalidValues)
116 const auto gsdml = MakePhoenixGsdml();
121 EXPECT_THROW(plc.
SetLocalInputValue(
"LEVEL", std::numeric_limits<double>::infinity()),
122 std::invalid_argument);
128TEST(VirtualPhoenixPlcTests, ConnectedControllerDrivesVirtualAuma)
130 const auto auma = MakeAumaGsdml();
131 const auto phoenix = MakePhoenixGsdml();
132 auto iocrs = MakeTestAumaIocrs(auma);
136 std::move(iocrs.input), std::move(iocrs.output));
140 auto valveIocrs = MakeTestAumaIocrs(auma);
147 std::move(valveIocrs.input),
148 std::move(valveIocrs.output));
150 ASSERT_TRUE(WaitUntil([&plc]
156 plc.
SetOutput(
"valve1",
"T_ID_CMD_0", 1.0);
158 ASSERT_TRUE(WaitUntil([&valve]
163 const auto position = plc.
ReadInput(
"valve1",
"T_ID_ACTPOS_E2");
164 ASSERT_TRUE(position.has_value());
165 EXPECT_GT(*position, 0.0);
173TEST(VirtualPhoenixPlcTests, BadIopsWithholdsInputData)
175 const auto auma = MakeAumaGsdml();
176 const auto phoenix = MakePhoenixGsdml();
177 auto valveIocrs = MakeTestAumaIocrs(auma);
181 std::move(valveIocrs.input), std::move(valveIocrs.output));
185 auto controllerIocrs = MakeTestAumaIocrs(auma);
190 std::move(controllerIocrs.input),
191 std::move(controllerIocrs.output));
193 ASSERT_TRUE(WaitUntil([&plc]
196 return stats.has_value() && stats->framesReceived >= 2U;
200 std::this_thread::sleep_for(std::chrono::milliseconds(50));
202 EXPECT_FALSE(plc.
ReadInput(
"valve1",
"T_ID_ACTPOS_E2").has_value());
204 ASSERT_TRUE(stats.has_value());
205 EXPECT_GT(stats->framesReceived, 0U);
212TEST(VirtualPhoenixPlcTests, NoInputTriggersControllerWatchdog)
214 const auto auma = MakeAumaGsdml();
215 const auto phoenix = MakePhoenixGsdml();
216 auto valveIocrs = MakeTestAumaIocrs(auma);
220 std::move(valveIocrs.input), std::move(valveIocrs.output));
224 auto controllerIocrs = MakeTestAumaIocrs(auma);
229 std::move(controllerIocrs.input),
230 std::move(controllerIocrs.output),
233 ASSERT_TRUE(WaitUntil([&plc]
236 return stats.has_value() && stats->framesReceived >= 2U;
241 ASSERT_TRUE(WaitUntil([&plc]
245 }, std::chrono::milliseconds(300)));
248 ASSERT_TRUE(stats.has_value());
249 EXPECT_GT(stats->framesMissed, 0U);
256TEST(VirtualPhoenixPlcTests, DisconnectAllowsReconnect)
258 const auto auma = MakeAumaGsdml();
259 const auto phoenix = MakePhoenixGsdml();
260 auto valveIocrs = MakeTestAumaIocrs(auma);
265 std::move(valveIocrs.input), std::move(valveIocrs.output));
269 auto first = MakeTestAumaIocrs(auma);
272 plc.
Connect(
ioContext, bus, valve, std::move(first.input), std::move(first.output));
274 ASSERT_TRUE(WaitUntil([&plc]
283 auto second = MakeTestAumaIocrs(auma);
285 plc.
Connect(
ioContext, bus, valve, std::move(second.input), std::move(second.output));
293TEST(VirtualPhoenixPlcTests, DuplicateConnectionIsRejected)
295 const auto auma = MakeAumaGsdml();
296 const auto phoenix = MakePhoenixGsdml();
297 auto iocrs = MakeTestAumaIocrs(auma);
301 std::move(iocrs.input), std::move(iocrs.output));
305 auto first = MakeTestAumaIocrs(auma);
309 plc.
Connect(
ioContext, bus, valve, std::move(first.input), std::move(first.output));
310 auto second = MakeTestAumaIocrs(auma);
312 EXPECT_THROW(plc.
Connect(
ioContext, bus, valve, std::move(second.input), std::move(second.output)),
313 std::invalid_argument);
Keeps an Asio I/O context running on a dedicated worker thread.
const std::vector< IoSignal > & Actuators() const noexcept
Returns all non-reserved actuator/output signals.
const std::vector< IoSignal > & Sensors() const noexcept
Returns all non-reserved sensor/input signals.
Small virtual Ethernet switch suitable for deterministic tests and demos.
AUMA SGx application model that behaves as a real RTC1 peer in memory.
void SetScenario(VirtualAumaScenario scenario)
Set the current simulator scenario.
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.
void SetLocalOutputValue(std::string_view signalTextId, double value)
Set one typed Phoenix output signal.
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 SetLocalOutputBytes(std::string_view signalTextId, const Bytes &data)
Set raw bytes in the local Phoenix output process image.
void SetOutput(const std::string &valveName, std::string_view signalTextId, double value)
Set a typed AUMA actuator signal.
const io::IoSignalCatalog & SignalCatalog() const noexcept
Return the Phoenix PLC's logical signal catalogue.
void Stop()
Stop all active connections.
void Disconnect(const std::string &valveName)
Disconnect a named AUMA connection.
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< Bytes > GetLocalInputBytes(std::string_view signalTextId) const
Read raw bytes from the local Phoenix input process image.
std::size_t ConnectionCount() const
Return the number of active AUMA connections.
std::optional< cyclic::CyclicState > ConnectionState(const std::string &valveName) const
Return one connection state.
std::optional< Bytes > GetLocalOutputBytes(std::string_view signalTextId) const
Read raw bytes from the local Phoenix output process image.
std::optional< double > GetLocalInputValue(std::string_view signalTextId) const
Read one typed Phoenix input signal.
void SetLocalInputBytes(std::string_view signalTextId, const Bytes &data)
Set raw bytes in the local Phoenix input process image.
std::optional< cyclic::CyclicStatsSnapshot > ConnectionStats(const std::string &valveName) const
Return one connection statistics snapshot.
std::optional< double > GetLocalOutputValue(std::string_view signalTextId) const
Read one typed Phoenix output signal.
void SetLocalInputValue(std::string_view signalTextId, double value)
Set one typed Phoenix input signal.
@ Fault
Communication failure (e.g. consecutive watchdog timeouts).
@ Running
Active cyclic data exchange.
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.
@ BadIops
Input frames continue, but the input provider reports BAD IOPS.
@ NoInput
Input frames stop completely so the controller watchdog expires.
TEST(InMemoryEthernetRoutingTests, UnicastReachesOnlyMatchingEndpoint)
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
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.