PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_virtual_auma_valveMore.cpp
Go to the documentation of this file.
1
8
9#include <gtest/gtest.h>
10
11#include <algorithm>
12#include <array>
13#include <atomic>
14#include <chrono>
15#include <cstdint>
16#include <mutex>
17#include <stdexcept>
18#include <string>
19#include <thread>
20#include <vector>
21
22#include "profinet/alarms.h"
23#include "profinet/gsdml.h"
24#include "profinet/rt.h"
29
30namespace profinet::sim
31{
32
33namespace
34{
35
38using test::WaitUntil;
39
40constexpr MacAddress kControllerMac{0x02, 0x00, 0x00, 0x00, 0x10, 0x01};
41constexpr MacAddress kValveMac{0x02, 0x00, 0x00, 0x00, 0x20, 0x01};
42
45Bytes MakeControllerOutputFrame()
46{
47 rt::RTFrame frame;
48 frame.frameId = 0x8001U;
49 frame.cycleCounter = 1U;
50 frame.dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
54 frame.transferStatus = 0U;
55 frame.payload.resize(16U, 0U);
56 return rt::BuildEthernetFrame(kValveMac, kControllerMac, frame);
57}
58
62std::uint8_t RtaPduType(const Bytes& frame)
63{
64 if (frame.size() < 25U || frame[12] != 0x81U || frame[13] != 0x00U ||
65 frame[16] != 0x88U || frame[17] != 0x92U)
66 {
67 return 0U;
68 }
69 return static_cast<std::uint8_t>(frame[24] & 0x0FU);
70}
71
76bool IsAlarmEthernetFrame(const Bytes& frame, const std::uint16_t frameId)
77{
78 return frame.size() >= 20U && frame[12] == 0x81U && frame[13] == 0x00U &&
79 frame[16] == 0x88U && frame[17] == 0x92U &&
80 static_cast<std::uint16_t>((static_cast<std::uint16_t>(frame[18]) << 8U) | frame[19]) == frameId;
81}
82
86Bytes ExtractRtaVariablePart(const Bytes& frame)
87{
88 if (frame.size() < 30U)
89 {
90 return {};
91 }
92 const std::uint16_t length = static_cast<std::uint16_t>(
93 (static_cast<std::uint16_t>(frame[28]) << 8U) | frame[29]);
94 if (frame.size() < 30U + length)
95 {
96 return {};
97 }
98 return Bytes(frame.begin() + 30, frame.begin() + 30 + length);
99}
100
101} // namespace
102
104TEST(VirtualAumaValveTests, StartsFromConfiguredGsdmlPosition)
105{
106 const auto gsdml = MakeAumaGsdml();
107 auto iocrs = MakeTestAumaIocrs(gsdml);
108 VirtualAumaValve valve("valve1", gsdml, kValveMac,
109 std::move(iocrs.input), std::move(iocrs.output));
110
111 EXPECT_EQ(valve.Position(), 0U);
112 EXPECT_EQ(valve.TargetPosition(), 0U);
113 EXPECT_EQ(valve.Scenario(), VirtualAumaScenario::Normal);
114 EXPECT_NE(std::find_if(valve.SignalCatalog().Sensors().begin(),
115 valve.SignalCatalog().Sensors().end(),
116 [](const auto& signal)
117 { return signal.textId == "T_ID_ACTPOS_E2"; }),
118 valve.SignalCatalog().Sensors().end());
119}
120
122TEST(VirtualAumaValveTests, OpenCommandMovesValveToEndStop)
123{
124 const auto gsdml = MakeAumaGsdml();
125 auto iocrs = MakeTestAumaIocrs(gsdml);
128 config.initialPosition = 9000U;
129 VirtualAumaValve valve("valve1", gsdml, kValveMac,
130 std::move(iocrs.input), std::move(iocrs.output), std::move(config));
131 valve.Start(bus);
132 auto controllerTx = bus.CreateSocket(kControllerMac);
133 controllerTx->SetTimeout(std::chrono::milliseconds(2));
134
135 Bytes output(7U, 0U);
136 output[0] = 0x01U;
137 output[1] = 100U;
138 output[2] = 0x27U;
139 output[3] = 0x10U;
140
142 frame.frameId = 0x8001U;
143 frame.cycleCounter = 1U;
146 frame.payload.resize(16U, 0U);
147 std::copy(output.begin(), output.end(), frame.payload.begin());
148
149 const Bytes ethernet = rt::BuildEthernetFrame(kValveMac, kControllerMac, frame);
150 controllerTx->Send(ethernet);
151
152 ASSERT_TRUE(WaitUntil([&valve]
153 {
154 return valve.Position() > 0U;
155 }));
156 EXPECT_TRUE(WaitUntil([&valve]
157 {
158 return valve.Position() == 10000U;
159 }, std::chrono::milliseconds(500)));
160
161 valve.Stop();
162}
163
165TEST(VirtualAumaValveTests, RejectsFramesFromSecondController)
166{
167 const auto gsdml = MakeAumaGsdml();
168 auto iocrs = MakeTestAumaIocrs(gsdml);
170 VirtualAumaValve valve("valve1", gsdml, kValveMac,
171 std::move(iocrs.input), std::move(iocrs.output));
172 valve.Start(bus);
173
174 const MacAddress secondController{0x02, 0x00, 0x00, 0x00, 0x10, 0x02};
175 auto controllerA = bus.CreateSocket(kControllerMac);
176 auto controllerB = bus.CreateSocket(secondController);
177 controllerA->SetTimeout(std::chrono::milliseconds(2));
178 controllerB->SetTimeout(std::chrono::milliseconds(2));
179
180 controllerA->Send(MakeControllerOutputFrame());
181 ASSERT_TRUE(WaitUntil([&valve]
182 {
183 return valve.OutputProcessData().size() == 7U;
184 }));
185
186 const Bytes before = valve.OutputProcessData();
187 rt::RTFrame secondFrame;
188 secondFrame.frameId = 0x8001U;
189 secondFrame.cycleCounter = 2U;
190 secondFrame.dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
194 secondFrame.transferStatus = 0U;
195 secondFrame.payload.resize(16U, 0U);
196 secondFrame.payload[0] = 0x04U;
197 controllerB->Send(rt::BuildEthernetFrame(kValveMac, secondController, secondFrame));
198 std::this_thread::sleep_for(std::chrono::milliseconds(20));
199
200 EXPECT_EQ(valve.OutputProcessData(), before);
201 valve.Stop();
202}
203
205TEST(VirtualAumaValveTests, SlowValveMovesButAtReducedSpeed)
206{
207 const auto gsdml = MakeAumaGsdml();
208 auto iocrs = MakeTestAumaIocrs(gsdml);
210 VirtualAumaValve valve("valve1", gsdml, kValveMac,
211 std::move(iocrs.input), std::move(iocrs.output));
213 valve.Start(bus);
214
215 auto controllerTx = bus.CreateSocket(kControllerMac);
216 controllerTx->SetTimeout(std::chrono::milliseconds(2));
217 Bytes payload(16U, 0U);
218 payload[0] = 0x01U;
219 rt::RTFrame frame{.frameId = 0x8001U,
220 .cycleCounter = 1U,
221 .dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
225 .transferStatus = 0,
226 .payload = payload};
227 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, frame));
228
229 std::this_thread::sleep_for(std::chrono::milliseconds(120));
230 const auto position = valve.Position();
231 EXPECT_GT(position, 0U);
232 EXPECT_LT(position, 700U);
233
234 valve.Stop();
235}
236
238TEST(VirtualAumaValveTests, JammedPreventsMovement)
239{
240 const auto gsdml = MakeAumaGsdml();
241 auto iocrs = MakeTestAumaIocrs(gsdml);
242 VirtualAumaValve valve("valve1", gsdml, kValveMac,
243 std::move(iocrs.input), std::move(iocrs.output));
245
247 valve.Start(bus);
248 auto controllerTx = bus.CreateSocket(kControllerMac);
249 controllerTx->SetTimeout(std::chrono::milliseconds(2));
250
251 Bytes payload(16U, 0U);
252 payload[0] = 0x01U;
253 rt::RTFrame frame{.frameId = 0x8001U,
254 .cycleCounter = 1U,
255 .dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
259 .transferStatus = 0,
260 .payload = payload};
261 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, frame));
262
263 std::this_thread::sleep_for(std::chrono::milliseconds(100));
264 EXPECT_EQ(valve.Position(), 0U);
265 EXPECT_TRUE(valve.FaultActive());
266 valve.Stop();
267}
268
270TEST(VirtualAumaScenarioTests, JammedKeepsIopsGood)
271{
272 const auto gsdml = MakeAumaGsdml();
273 auto iocrs = MakeTestAumaIocrs(gsdml);
275 VirtualAumaValve valve("valve1", gsdml, kValveMac,
276 std::move(iocrs.input), std::move(iocrs.output));
278 valve.Start(bus);
279
280 auto controllerTx = bus.CreateSocket(kControllerMac);
281 Bytes payload(16U, 0U);
282 rt::RTFrame frame{.frameId = 0x8001U,
283 .cycleCounter = 1U,
284 .dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
288 .transferStatus = 0U,
289 .payload = payload};
290 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, frame));
291
292 auto controllerRx = bus.CreateSocket(kControllerMac);
293 ASSERT_TRUE(WaitUntil([&controllerRx]
294 {
295 return !controllerRx->Recv().empty();
296 }));
297
298 controllerRx->SetTimeout(std::chrono::milliseconds(2));
299 bool goodIops = false;
300 for (;;)
301 {
302 const Bytes received = controllerRx->Recv();
303 if (received.empty())
304 {
305 break;
306 }
307 const auto parsed = rt::ParseEthernetFrame(received);
308 if (parsed.has_value() && parsed->frameId == 0x9000U && parsed->payload.size() > 9U)
309 {
310 goodIops = parsed->payload[9U] == rt::IOXS_GOOD;
311 break;
312 }
313 }
314 EXPECT_TRUE(goodIops);
315 valve.Stop();
316}
317
319TEST(VirtualAumaScenarioTests, BadIopsStillEmitsCyclicFramesWithBadIops)
320{
321 const auto gsdml = MakeAumaGsdml();
322 auto iocrs = MakeTestAumaIocrs(gsdml);
324 VirtualAumaValve valve("valve1", gsdml, kValveMac,
325 std::move(iocrs.input), std::move(iocrs.output));
326 std::atomic<std::size_t> frames{0};
327 valve.OnFrame([&frames](const Bytes& frame)
328 {
329 if (frame.size() > 19U && frame[16] == 0x88U && frame[17] == 0x92U)
330 {
331 ++frames;
332 }
333 });
335 valve.Start(bus);
336 auto controllerTx = bus.CreateSocket(kControllerMac);
337 controllerTx->SetTimeout(std::chrono::milliseconds(2));
338 Bytes payload(16U, 0U);
339 rt::RTFrame frame{.frameId = 0x8001U,
340 .cycleCounter = 1U,
341 .dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
345 .transferStatus = 0,
346 .payload = payload};
347 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, frame));
348
349 EXPECT_TRUE(WaitUntil([&frames]
350 {
351 return frames.load() >= 2U;
352 }));
353 valve.Stop();
354}
355
357TEST(VirtualAumaAlarmTests, RequestedScenariosExposeBothAlarmPaths)
358{
359 const auto gsdml = MakeAumaGsdml();
360 auto iocrs = MakeTestAumaIocrs(gsdml);
362 VirtualAumaValve valve("valve1", gsdml, kValveMac,
363 std::move(iocrs.input), std::move(iocrs.output));
364
365 std::vector<VirtualAumaAlarmEvent> events;
366 std::mutex eventMutex;
367 valve.OnAlarm([&](const VirtualAumaAlarmEvent& event)
368 {
369 std::lock_guard<std::mutex> lock(eventMutex);
370 events.push_back(event);
371 });
372 valve.Start(bus);
373
374 auto controllerTx = bus.CreateSocket(kControllerMac);
375 controllerTx->SetTimeout(std::chrono::milliseconds(2));
376 controllerTx->Send(MakeControllerOutputFrame());
377
378 ASSERT_TRUE(WaitUntil([&valve]
379 {
380 return valve.OutputProcessData().size() == 7U;
381 }));
382
383 constexpr std::array<VirtualAumaScenario, 5> kRequestedScenarios{
389
390 for (const auto scenario : kRequestedScenarios)
391 {
392 valve.SetScenario(scenario);
393 ASSERT_TRUE(WaitUntil([&events, &eventMutex, scenario]
394 {
395 std::lock_guard<std::mutex> lock(eventMutex);
396 return std::any_of(events.begin(), events.end(), [scenario](const auto& event)
397 { return event.scenario == scenario; });
398 }));
400 }
401
402 auto controllerAlarm = bus.CreateSocket(kControllerMac);
403 controllerAlarm->SetTimeout(std::chrono::milliseconds(5));
405
406 Bytes errorFrame;
407 Bytes notificationFrame;
408 ASSERT_TRUE(WaitUntil([&controllerAlarm, &errorFrame, &notificationFrame]
409 {
410 for (;;)
411 {
412 const Bytes frame = controllerAlarm->Recv();
413 if (frame.empty())
414 {
415 return !errorFrame.empty() && !notificationFrame.empty();
416 }
417 const std::uint8_t pduType = RtaPduType(frame);
418 if (pduType == 0x0FU)
419 {
420 errorFrame = frame;
421 }
422 else if (pduType == 0x01U)
423 {
424 notificationFrame = frame;
425 }
426 }
427 }));
428
429 {
430 std::lock_guard<std::mutex> lock(eventMutex);
431 ASSERT_EQ(events.size(), kRequestedScenarios.size() + 1U);
432 for (const auto& event : events)
433 {
434 EXPECT_FALSE(event.rtaErrorFrame.empty());
435 EXPECT_FALSE(event.alarmNotificationFrame.empty());
436 EXPECT_EQ(RtaPduType(event.rtaErrorFrame), 0x0FU);
437 EXPECT_EQ(RtaPduType(event.alarmNotificationFrame), 0x01U);
438 EXPECT_TRUE(IsAlarmEthernetFrame(event.rtaErrorFrame, rt::FRAME_ID_ALARM_LOW));
439 EXPECT_TRUE(IsAlarmEthernetFrame(event.alarmNotificationFrame, rt::FRAME_ID_ALARM_LOW));
440
441 const Bytes block = ExtractRtaVariablePart(event.alarmNotificationFrame);
442 ASSERT_FALSE(block.empty());
443 const auto alarm = ParseAlarmNotification(block);
444 EXPECT_EQ(alarm.slotNumber, 1U);
445 EXPECT_EQ(alarm.subslotNumber, 1U);
446 EXPECT_EQ(alarm.moduleIdentNumber, 0x00008000U);
447 EXPECT_EQ(alarm.submoduleIdentNumber, 0x00002200U);
448 EXPECT_TRUE(alarm.channelDiagnosis);
449 EXPECT_FALSE(alarm.items.empty());
450 }
451 }
452
453 EXPECT_FALSE(errorFrame.empty());
454 EXPECT_FALSE(notificationFrame.empty());
455 valve.Stop();
456}
457
459TEST(VirtualAumaAlarmTests, ThrowingAlarmCallbackDoesNotKillSimulator)
460{
461 const auto gsdml = MakeAumaGsdml();
462 auto iocrs = MakeTestAumaIocrs(gsdml);
464 VirtualAumaValve valve("valve1", gsdml, kValveMac,
465 std::move(iocrs.input), std::move(iocrs.output));
466 valve.OnAlarm([](const VirtualAumaAlarmEvent&)
467 {
468 throw std::runtime_error("intentional alarm callback failure");
469 });
470 valve.Start(bus);
471
472 auto controllerTx = bus.CreateSocket(kControllerMac);
473 controllerTx->SetTimeout(std::chrono::milliseconds(2));
474 controllerTx->Send(MakeControllerOutputFrame());
475 ASSERT_TRUE(WaitUntil([&valve]
476 {
477 return valve.OutputProcessData().size() == 7U;
478 }));
479
481 std::this_thread::sleep_for(std::chrono::milliseconds(30));
482 EXPECT_TRUE(valve.FaultActive());
483 valve.Stop();
484}
485
487TEST(VirtualAumaScenarioTests, NoInputSuppressesDeviceInputTraffic)
488{
489 const auto gsdml = MakeAumaGsdml();
490 auto iocrs = MakeTestAumaIocrs(gsdml);
492 VirtualAumaValve valve("valve1", gsdml, kValveMac,
493 std::move(iocrs.input), std::move(iocrs.output));
495 valve.Start(bus);
496
497 auto controllerTx = bus.CreateSocket(kControllerMac);
498 auto controllerRx = bus.CreateSocket(kControllerMac);
499 controllerTx->SetTimeout(std::chrono::milliseconds(2));
500 controllerRx->SetTimeout(std::chrono::milliseconds(2));
501
502 Bytes payload(16U, 0U);
503 rt::RTFrame frame{.frameId = 0x8001U,
504 .cycleCounter = 1U,
505 .dataStatus = static_cast<std::uint8_t>(rt::DATA_STATUS_VALID |
509 .transferStatus = 0,
510 .payload = payload};
511 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, frame));
512 std::this_thread::sleep_for(std::chrono::milliseconds(80));
513
514 EXPECT_TRUE(controllerRx->Recv().empty());
515 valve.Stop();
516}
517
519TEST(VirtualAumaScenarioTests, InvalidInputClearsDataValidity)
520{
521 const auto gsdml = MakeAumaGsdml();
522 auto iocrs = MakeTestAumaIocrs(gsdml);
524 VirtualAumaValve valve("valve1", gsdml, kValveMac,
525 std::move(iocrs.input), std::move(iocrs.output));
526 std::vector<Bytes> frames;
527 std::mutex mutex;
528 valve.OnFrame([&](const Bytes& frame)
529 {
530 std::lock_guard<std::mutex> lock(mutex);
531 frames.push_back(frame);
532 });
534 valve.Start(bus);
535
536 auto controllerTx = bus.CreateSocket(kControllerMac);
537 controllerTx->SetTimeout(std::chrono::milliseconds(2));
538 rt::RTFrame outputFrame;
539 outputFrame.frameId = 0x8001U;
540 outputFrame.cycleCounter = 1U;
543 outputFrame.payload.resize(16U, 0U);
544 outputFrame.payload[0] = 0x01U;
545 controllerTx->Send(rt::BuildEthernetFrame(kValveMac, kControllerMac, outputFrame));
546
547 ASSERT_TRUE(WaitUntil([&frames, &mutex]
548 {
549 std::lock_guard<std::mutex> lock(mutex);
550 return !frames.empty();
551 }));
552
553 bool invalidFound = false;
554 {
555 std::lock_guard<std::mutex> lock(mutex);
556 for (const auto& frame : frames)
557 {
558 const auto parsed = rt::ParseEthernetFrame(frame);
559 if (parsed.has_value() && parsed->frameId == 0x9000U)
560 {
561 invalidFound = (parsed->dataStatus & rt::DATA_STATUS_VALID) == 0U;
562 }
563 }
564 }
565 EXPECT_TRUE(invalidFound);
566 valve.Stop();
567}
568
570TEST(VirtualAumaScenarioTests, DuplicateInputProducesDuplicateCycleCounters)
571{
572 const auto auma = MakeAumaGsdml();
573 const auto phoenix = test::MakePhoenixGsdml();
574 auto valveIocrs = MakeTestAumaIocrs(auma);
576 VirtualAumaValve valve("valve1", auma, kValveMac,
577 std::move(valveIocrs.input), std::move(valveIocrs.output));
578 valve.Start(bus);
579 VirtualPhoenixPlc plc("plc1", phoenix, kControllerMac);
580 auto controllerIocrs = MakeTestAumaIocrs(auma);
581 asio::io_context ioContext;
583
584 plc.Connect(ioContext, bus, valve, std::move(controllerIocrs.input), std::move(controllerIocrs.output));
585
586 ASSERT_TRUE(WaitUntil([&plc]
587 {
588 const auto stats = plc.ConnectionStats("valve1");
589 return stats.has_value() && stats->framesReceived >= 3U;
590 }));
592
593 ASSERT_TRUE(WaitUntil([&plc]
594 {
595 const auto stats = plc.ConnectionStats("valve1");
596 return stats.has_value() && stats->framesDuplicate > 0U;
597 }));
598
599 plc.Stop();
600 valve.Stop();
601}
602
604TEST(VirtualAumaScenarioTests, OutOfOrderInputProducesOutOfOrderCycles)
605{
606 const auto auma = MakeAumaGsdml();
607 const auto phoenix = test::MakePhoenixGsdml();
608 auto valveIocrs = MakeTestAumaIocrs(auma);
610 VirtualAumaValve valve("valve1", auma, kValveMac,
611 std::move(valveIocrs.input), std::move(valveIocrs.output));
612 valve.Start(bus);
613 VirtualPhoenixPlc plc("plc1", phoenix, kControllerMac);
614 auto controllerIocrs = MakeTestAumaIocrs(auma);
615 asio::io_context ioContext;
617
618 plc.Connect(ioContext, bus, valve, std::move(controllerIocrs.input), std::move(controllerIocrs.output));
619
620 ASSERT_TRUE(WaitUntil([&plc]
621 {
622 const auto stats = plc.ConnectionStats("valve1");
623 return stats.has_value() && stats->framesReceived >= 3U;
624 }));
626
627 ASSERT_TRUE(WaitUntil([&plc]
628 {
629 const auto stats = plc.ConnectionStats("valve1");
630 return stats.has_value() && stats->framesOutOfOrder > 0U;
631 }, std::chrono::milliseconds(600)));
632
633 plc.Stop();
634 valve.Stop();
635}
636
638TEST(VirtualAumaScenarioTests, EveryScenarioHasStableName)
639{
640 EXPECT_STREQ(ToString(VirtualAumaScenario::Normal), "normal");
641 EXPECT_STREQ(ToString(VirtualAumaScenario::Fault), "fault");
642 EXPECT_STREQ(ToString(VirtualAumaScenario::BadIops), "bad-iops");
643 EXPECT_STREQ(ToString(VirtualAumaScenario::NoInput), "no-input");
644 EXPECT_STREQ(ToString(VirtualAumaScenario::SlowValve), "slow-valve");
645 EXPECT_STREQ(ToString(VirtualAumaScenario::Jammed), "jammed");
646 EXPECT_STREQ(ToString(VirtualAumaScenario::InvalidInput), "invalid-input");
647 EXPECT_STREQ(ToString(VirtualAumaScenario::DuplicateInput), "duplicate-input");
648 EXPECT_STREQ(ToString(VirtualAumaScenario::OutOfOrderInput), "out-of-order-input");
649}
650
652TEST(VirtualAumaValveTests, RejectsInvalidMotionConfiguration)
653{
654 const auto gsdml = MakeAumaGsdml();
655 auto iocrs = MakeTestAumaIocrs(gsdml);
657 config.travelUnitsPerSecond = 0.0;
658 EXPECT_THROW(VirtualAumaValve("valve1", gsdml, kValveMac,
659 std::move(iocrs.input), std::move(iocrs.output),
660 std::move(config)),
661 std::invalid_argument);
662}
663
664} // 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.
const std::vector< IoSignal > & Sensors() const noexcept
Returns all non-reserved sensor/input signals.
Definition IOSignal.cpp:534
Small virtual Ethernet switch suitable for deterministic tests and demos.
std::unique_ptr< InMemoryEthernetSocket > CreateSocket(MacAddress localMac)
Create one virtual raw-Ethernet endpoint.
AUMA SGx application model that behaves as a real RTC1 peer in memory.
Bytes OutputProcessData() const
Return the most recent output process-data object.
void SetScenario(VirtualAumaScenario scenario)
Set the current simulator scenario.
VirtualAumaScenario Scenario() const noexcept
Return the current scenario.
void Stop()
Stop cyclic simulation.
void OnAlarm(std::function< void(const VirtualAumaAlarmEvent &)> callback)
Register a callback for generated scenario alarm events.
const io::IoSignalCatalog & SignalCatalog() const noexcept
Return the GSDML-derived signal catalogue.
bool FaultActive() const noexcept
Return whether an application or mechanical fault is active.
void Start(InMemoryEthernetBus &bus)
Attach the device to a shared virtual Ethernet bus and start cyclic processing.
std::uint16_t TargetPosition() const noexcept
Return the current requested target position.
std::uint16_t Position() const noexcept
Return the current simulated position.
void OnFrame(std::function< void(const Bytes &)> callback)
Register a callback for every Ethernet frame emitted by the device.
Virtual Phoenix Contact AXC F 1252 controller hosting real CyclicController instances.
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.
std::optional< RTFrame > ParseEthernetFrame(const Bytes &data)
Parse a complete Ethernet frame and extract its RT frame.
Definition rt.cpp:261
constexpr std::uint8_t DATA_STATUS_STATION_OK
DataStatus bit: station health, 0=Problem, 1=OK.
Definition rt.h:89
constexpr std::uint8_t DATA_STATUS_PROVIDER_RUN
DataStatus bit: provider run state, 0=Stop, 1=Run.
Definition rt.h:87
Bytes BuildEthernetFrame(const MacAddress &dstMac, const MacAddress &srcMac, const RTFrame &rtFrame)
Build a complete Ethernet frame carrying an RT frame.
Definition rt.cpp:242
constexpr std::uint8_t DATA_STATUS_STATE
DataStatus bit: provider state, 0=Backup, 1=Primary.
Definition rt.h:79
constexpr std::uint16_t FRAME_ID_ALARM_LOW
Frame ID used for low-priority alarms.
Definition rt.h:63
constexpr std::uint8_t DATA_STATUS_VALID
DataStatus bit: data validity, 0=Invalid, 1=Valid.
Definition rt.h:83
constexpr std::uint8_t IOXS_GOOD
IOxS value: good, subslot level.
Definition rt.h:95
constexpr MacAddress kControllerMac
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.
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.
@ Fault
Application fault: movement stops, IOPS becomes BAD and alarms are emitted.
@ BadIops
Input frames continue, but the input provider reports BAD IOPS.
@ DuplicateInput
Input frames repeat their previous cycle counter.
@ SlowValve
Valve travels using the configured reduced speed multiplier.
@ OutOfOrderInput
Input frames occasionally move the cycle counter backwards.
@ Normal
Fully healthy operation with normal travel speed.
@ NoInput
Input frames stop completely so the controller watchdog expires.
@ InvalidInput
Input frames remain valid but carry an invalid RTC1 DATA_STATUS.
@ Jammed
Valve accepts commands but the mechanical position does not change.
TEST(InMemoryEthernetRoutingTests, UnicastReachesOnlyMatchingEndpoint)
const char * ToString(VirtualAumaScenario scenario) noexcept
Convert a virtual AUMA scenario into its stable CLI spelling.
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
Definition alarms.cpp:245
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
Definition rt.h:208
std::uint8_t dataStatus
Data status bitmask (see DATA_STATUS_* constants).
Definition rt.h:216
std::uint8_t transferStatus
Transfer status (0 = OK).
Definition rt.h:219
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
Definition rt.h:210
std::uint16_t cycleCounter
Cycle counter, incremented each transmission.
Definition rt.h:213
Bytes payload
C_SDU payload: process data plus IOPS/IOCS trailers.
Definition rt.h:222
Captured alarm traffic produced by a virtual AUMA scenario.
Bytes rtaErrorFrame
Complete RTA 0x0F Ethernet frame; empty when disabled.
Bytes alarmNotificationFrame
Complete RTA DATA AlarmNotification Ethernet frame; empty when disabled.
Application-level signal mapping and motion settings for a virtual AUMA SGx.
double travelUnitsPerSecond
Nominal travel speed in position units per second.
std::uint16_t initialPosition
Initial valve position in the AUMA 0..10000 application range.
std::uint8_t pduType
RTA PDU type including version nibble.
std::uint16_t frameId
Ethernet FrameID.
std::mutex mutex
Bytes frame
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Bytes payload
Definition test_dcp.cpp:392
std::uint16_t length
Definition test_dcp.cpp:391
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.