12#include <gtest/gtest.h>
35constexpr MacAddress kDeviceMac{0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
47 std::uint16_t sendClockFactor = 32,
48 std::uint16_t reductionRatio = 8,
49 std::uint16_t watchdogFactor = 3)
79 std::uint16_t cycleCounter = 1,
81 std::uint8_t transferStatus = 0,
92 frame.cycleCounter = cycleCounter;
93 frame.dataStatus = dataStatus;
94 frame.transferStatus = transferStatus;
99 ethernet.insert(ethernet.end(), destination.begin(), destination.end());
100 ethernet.insert(ethernet.end(), source.begin(), source.end());
101 ethernet.push_back(
static_cast<std::uint8_t
>((kProfinetEtherType >> 8U) & 0xFFU));
102 ethernet.push_back(
static_cast<std::uint8_t
>(kProfinetEtherType & 0xFFU));
103 ethernet.insert(ethernet.end(), rtBytes.begin(), rtBytes.end());
178 std::optional<std::uint8_t> dataStatusOverride = std::nullopt)
180 controller.SendOutputFrame(dataStatusOverride);
265 return controller.outputBuilder.Build().at(0);
282 return controller.inputIocr.objects.at(index);
313 const auto&
object =
controller.outputIocr.objects.front();
316 return object.offsetIOCS.has_value() ?
frame.at(*
object.offsetIOCS) : 0x00;
330 return std::make_unique<CyclicController>(
331 ioContext,
"eth0", kControllerMac, kDeviceMac,
340 std::unique_ptr<MockRawEthernetSocket>
txSock = std::make_unique<MockRawEthernetSocket>();
343 std::unique_ptr<MockRawEthernetSocket>
rxSock = std::make_unique<MockRawEthernetSocket>();
352 auto socketState = std::make_shared<test::LifetimeTrackingSocketState>();
354 auto txSocket = std::make_unique<MockRawEthernetSocket>();
355 auto rxSocket = std::make_unique<test::LifetimeTrackingRawEthernetSocket>(socketState);
357 auto ioContext = std::make_unique<asio::io_context>();
358 controller = std::make_unique<CyclicController>(
359 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
367 std::unique_lock lock(socketState->mutex);
369 socketState->receiveEnteredCv.wait(lock, [&]
371 return socketState->receiveEntered;
377 std::atomic<bool> stopFinished;
378 std::thread stopThread(
383 stopFinished.store(
true, std::memory_order_release);
389 std::this_thread::sleep_for(std::chrono::milliseconds(100));
390 EXPECT_FALSE(stopFinished.load(std::memory_order_acquire));
392 EXPECT_FALSE(socketState->destroyed.load(std::memory_order_acquire));
393 EXPECT_FALSE(socketState->destroyedWhileReceiving.load(std::memory_order_acquire));
403 std::lock_guard lock(socketState->mutex);
404 socketState->releaseReceive =
true;
407 socketState->receiveCv.notify_one();
411 std::unique_lock lock(socketState->mutex);
413 socketState->receiveExitedCv.wait(lock, [&]
415 return socketState->receiveExited;
421 EXPECT_TRUE(stopFinished.load(std::memory_order_acquire));
422 EXPECT_TRUE(socketState->destroyed.load(std::memory_order_acquire));
423 EXPECT_FALSE(socketState->destroyedWhileReceiving.load(std::memory_order_acquire));
428 controller->Start(std::move(txSock), std::move(rxSock));
435 controller->Start(std::move(txSock), std::move(rxSock));
438 txSock = std::make_unique<MockRawEthernetSocket>();
439 rxSock = std::make_unique<MockRawEthernetSocket>();
441 EXPECT_NO_THROW(
controller->Start(std::move(txSock), std::move(rxSock)));
447 const auto invalidState =
static_cast<CyclicState>(0xFF);
448 EXPECT_EQ(
ToString(invalidState),
"unknown");
467 EXPECT_EQ(callbackCount, 1);
483 EXPECT_EQ(callbackCount, 0);
492 throw std::runtime_error(
"state callback failure");
502 const std::array<CyclicState, 3> invalidStates{
509 EXPECT_THROW(
controller->Start(std::move(txSock), std::move(rxSock)), std::runtime_error);
519 controller->Start(std::move(txSock), std::move(rxSock));
533 controller->Start(std::move(txSock), std::move(rxSock));
549 controller->Start(std::move(txSock), std::move(rxSock));
560 auto*
rx = rxSock.get();
562 EXPECT_CALL(*
rx, SetTimeout(testing::_))
563 .WillOnce(testing::Throw(std::runtime_error(
"timeout")));
565 EXPECT_THROW(
controller->Start(std::move(txSock), std::move(rxSock)), std::runtime_error);
572 auto*
rx = rxSock.get();
573 EXPECT_CALL(*
rx, SetTimeout(testing::_))
574 .WillOnce(testing::Throw(std::runtime_error(
"timeout")));
576 EXPECT_THROW(
controller->Start(std::move(txSock), std::move(rxSock)), std::exception);
577 txSock = std::make_unique<MockRawEthernetSocket>();
578 rxSock = std::make_unique<MockRawEthernetSocket>();
579 EXPECT_NO_THROW(
controller->Start(std::move(txSock), std::move(rxSock)));
594 return std::make_unique<CyclicController>(
595 ioContext,
"eth0", kControllerMac, kDeviceMac,
617 EXPECT_EQ(stats.AvgCycleTimeUs(), 0U);
624 EXPECT_EQ(snapshot.AvgCycleTimeUs(), 0U);
631 stats.framesSent = 10;
632 stats.framesReceived = 9;
633 stats.framesMissed = 2;
634 stats.framesInvalid = 1;
635 stats.framesDuplicate = 3;
636 stats.framesOutOfOrder = 4;
637 stats.lastCycleTimeUs = 8000;
638 stats.maxJitterUs = 100;
639 stats.minCycleTimeUs = 7900;
640 stats.maxCycleTimeUs = 8100;
641 stats.consecutiveTimeouts = 2;
642 stats.cycleTimeSumUs = 80000;
643 stats.cycleCount = 10;
706 ioContext = std::make_unique<asio::io_context>();
707 controller = std::make_unique<CyclicController>(
708 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
720 BuildInputFrame(kDeviceMac, kControllerMac,
kInputFrameId, 1,
735 EXPECT_FALSE(
controller->GetInputData(99, 99).has_value());
743 const auto data =
controller->GetInputData(1, 1);
744 ASSERT_TRUE(data.has_value());
745 EXPECT_EQ(*data,
Bytes({0xAA, 0xBB, 0xCC, 0xDD}));
753 EXPECT_FALSE(
controller->GetInputData(1, 1).has_value());
754 ASSERT_TRUE(
controller->GetInputData(1, 1,
true).has_value());
760 EXPECT_FALSE(
controller->GetInputStatus(1, 1).has_value());
768 ASSERT_TRUE(
controller->GetInputStatus(1, 1).has_value());
795 EXPECT_NO_THROW(
controller->SetOutputData(1, 1, {0xDE, 0xAD, 0xBE, 0xEF}));
802 EXPECT_THROW(
controller->SetOutputData(1, 1, {0x01}), std::runtime_error);
809 EXPECT_THROW(
controller->SetOutputData(1, 1, {0x01}), std::runtime_error);
819 ioContext = std::make_unique<asio::io_context>();
820 controller = std::make_unique<CyclicController>(
821 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
833 BuildInputFrame(kDeviceMac, kControllerMac,
kInputFrameId, 1,
850 [&calls](std::uint16_t slot, std::uint16_t subslot, std::uint8_t status)
854 EXPECT_EQ(subslot, 1U);
867 [&calls](std::uint16_t, std::uint16_t, std::uint8_t)
880 std::vector<std::uint8_t> statuses;
883 [&calls, &statuses](std::uint16_t, std::uint16_t, std::uint8_t status)
886 statuses.push_back(status);
900 [](std::uint16_t, std::uint16_t, std::uint8_t)
902 throw std::runtime_error(
"status callback failure");
913 [&calls](
int slot,
int subslot,
const Bytes& data)
917 EXPECT_EQ(subslot, 1);
918 EXPECT_EQ(data,
Bytes({0xAA, 0xBB, 0xCC, 0xDD}));
930 [&calls](
int,
int,
const Bytes&)
945 [&calls](
int,
int,
const Bytes&)
958 [](
int,
int,
const Bytes&)
960 throw std::runtime_error(
"data callback failure");
969 auto ioContext = std::make_unique<asio::io_context>();
971 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
974 bool timeoutCalled =
false;
975 bool errorCalled =
false;
977 { timeoutCalled =
true; });
978 controller.OnError([&errorCalled](
const std::string&)
979 { errorCalled =
true; });
981 EXPECT_FALSE(timeoutCalled);
982 EXPECT_FALSE(errorCalled);
992 ioContext = std::make_unique<asio::io_context>();
994 controller = std::make_unique<CyclicController>(
995 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
1005 BuildInputFrame(kDeviceMac, kControllerMac,
kInputFrameId, cycleCounter));
1019 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 0U);
1025 Bytes frame = BuildInputFrame(kDeviceMac, kControllerMac);
1030 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 0U);
1038 *
controller, BuildInputFrame(wrongSource, kControllerMac));
1040 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 0U);
1047 frame.insert(
frame.end(), kControllerMac.begin(), kControllerMac.end());
1048 frame.insert(
frame.end(), kDeviceMac.begin(), kDeviceMac.end());
1049 frame.push_back(0x88);
1050 frame.push_back(0x92);
1051 frame.push_back(0xC0);
1054 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 0U);
1061 *
controller, BuildInputFrame(kDeviceMac, kControllerMac, 0xC123));
1062 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 0U);
1070 Bytes frame = BuildInputFrame(kDeviceMac, kControllerMac);
1074 frame.insert(
frame.end(), {0x81, 0x00, 0xC0, 0x00, 0x88, 0x92});
1075 frame.insert(
frame.end(), rtBytes.begin(), rtBytes.end());
1079 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 1U);
1080 const auto data =
controller->GetInputData(1, 1);
1081 ASSERT_TRUE(data.has_value());
1082 EXPECT_EQ(*data,
Bytes({0xAA, 0xBB, 0xCC, 0xDD}));
1090 BuildInputFrame(kDeviceMac, kControllerMac,
kInputFrameId, 1, 0x00));
1092 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 1U);
1093 EXPECT_EQ(
controller->StatsSnapshot().framesInvalid, 1U);
1101 BuildInputFrame(kDeviceMac, kControllerMac,
kInputFrameId, 1,
1104 EXPECT_EQ(
controller->StatsSnapshot().framesInvalid, 1U);
1132 *
controller, BuildInputFrame(kDeviceMac, kControllerMac));
1134 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 1U);
1135 EXPECT_FALSE(
controller->GetInputStatus(1, 1).has_value());
1142 object.frameOffset = 39;
1143 object.dataLength = 4;
1146 *
controller, BuildInputFrame(kDeviceMac, kControllerMac));
1148 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 1U);
1149 EXPECT_FALSE(
controller->GetInputStatus(1, 1).has_value());
1156 EXPECT_EQ(
controller->StatsSnapshot().framesMissed, 0U);
1164 EXPECT_EQ(
controller->StatsSnapshot().framesDuplicate, 1U);
1172 const auto snapshot =
controller->StatsSnapshot();
1173 EXPECT_EQ(snapshot.framesMissed, 0U);
1174 EXPECT_EQ(snapshot.framesDuplicate, 0U);
1175 EXPECT_EQ(snapshot.framesOutOfOrder, 0U);
1183 EXPECT_EQ(
controller->StatsSnapshot().framesMissed, 1U);
1189 ProcessCounter(100);
1191 EXPECT_EQ(
controller->StatsSnapshot().framesOutOfOrder, 1U);
1201 EXPECT_EQ(
controller->StatsSnapshot().framesMissed, 0U);
1202 EXPECT_EQ(
controller->StatsSnapshot().framesOutOfOrder, 0U);
1212 tx = std::make_unique<test::CyclicRawEthernetSocketStub>();
1213 rxSocket = std::make_unique<test::CyclicRawEthernetSocketStub>();
1215 ioContext = std::make_unique<asio::io_context>();
1216 controller = std::make_unique<CyclicController>(
1217 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
1227 std::unique_ptr<test::CyclicRawEthernetSocketStub>
tx = std::make_unique<test::CyclicRawEthernetSocketStub>();
1228 std::unique_ptr<test::CyclicRawEthernetSocketStub>
rxSocket = std::make_unique<test::CyclicRawEthernetSocketStub>();
1240 int timeoutCalls = 0;
1249 EXPECT_EQ(
controller->StatsSnapshot().framesMissed, 1U);
1250 EXPECT_EQ(
controller->StatsSnapshot().consecutiveTimeouts, 1);
1251 EXPECT_EQ(timeoutCalls, 1);
1264 EXPECT_EQ(
controller->StatsSnapshot().consecutiveTimeouts, 2);
1271 std::string errorMessage;
1273 [&errorMessage](
const std::string& message)
1275 errorMessage = message;
1283 EXPECT_EQ(
controller->StatsSnapshot().consecutiveTimeouts, 3);
1284 EXPECT_NE(errorMessage.find(
"Communication lost"), std::string::npos);
1303 throw std::runtime_error(
"timeout callback failure");
1307 EXPECT_EQ(
controller->StatsSnapshot().framesMissed, 1U);
1325 rx->QueueReceive(BuildInputFrame(kDeviceMac, kControllerMac));
1329 [
this, &inputCalls](
int,
int,
const Bytes&)
1338 EXPECT_EQ(inputCalls, 1);
1339 EXPECT_EQ(
controller->StatsSnapshot().framesReceived, 1U);
1354 rx->SetRecvExceptionMessage(
"rx failure");
1355 rx->QueueReceiveException();
1357 std::string errorMessage;
1359 [
this, &errorMessage](
const std::string& message)
1361 errorMessage = message;
1367 EXPECT_EQ(errorMessage,
"RX error: rx failure");
1375 int timeoutCalls = 0;
1377 [
this, &timeoutCalls]
1386 EXPECT_EQ(timeoutCalls, 1);
1387 EXPECT_GE(
controller->StatsSnapshot().framesMissed, 1U);
1397 ioContext = std::make_unique<asio::io_context>();
1398 controller = std::make_unique<CyclicController>(
1399 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
1402 txSocket = std::make_unique<test::CyclicRawEthernetSocketStub>();
1409 std::unique_ptr<test::CyclicRawEthernetSocketStub>
txSocket;
1410 std::unique_ptr<test::CyclicRawEthernetSocketStub>
rxSocket = std::make_unique<test::CyclicRawEthernetSocketStub>();
1422 ioContext = std::make_unique<asio::io_context>();
1423 controller = std::make_unique<CyclicController>(
1424 *
ioContext,
"eth0", kControllerMac, kDeviceMac,
1428 txSocket = std::make_unique<test::CyclicRawEthernetSocketStub>();
1435 if (
tx->SentFrameCount() >= 2U)
1437 CyclicControllerTestAccess::SetRunning(*controller, false);
1445 const auto snapshot =
controller->StatsSnapshot();
1446 EXPECT_EQ(snapshot.framesSent, 2U);
1447 EXPECT_EQ(snapshot.cycleCount, 1U);
1448 EXPECT_GT(snapshot.lastCycleTimeUs, 0U);
1456 EXPECT_EQ(
tx->SentFrameCount(), 1U);
1457 EXPECT_EQ(
controller->StatsSnapshot().framesSent, 1U);
1465 EXPECT_EQ(
tx->SentFrameCount(), 1U);
1478 tx->SetSendThrows(
true);
1479 tx->SetSendExceptionMessage(
"tx failure");
1481 std::string errorMessage;
1483 [&errorMessage](
const std::string& message)
1485 errorMessage = message;
1488 EXPECT_EQ(errorMessage,
"TX error: tx failure");
1496 ASSERT_EQ(
tx->SentFrameCount(), 1U);
1497 const Bytes& ethernet =
tx->SentFrame(0);
1500 EXPECT_EQ(parsed.dataStatus, 0x20);
1519 EXPECT_EQ(
controller->StatsSnapshot().framesSent, 0U);
1525 tx->SetSendThrows(
true);
1527 [](
const std::string&)
1529 throw std::runtime_error(
"stop-send callback failure");
1533 EXPECT_EQ(
tx->SentFrameCount(), 0U);
1534 EXPECT_EQ(
controller->StatsSnapshot().framesSent, 0U);
1540 controller->SetOutputData(1, 1, {0x11, 0x22, 0x33, 0x44});
1544 ASSERT_EQ(
tx->SentFrameCount(), 1U);
1546 const Bytes& ethernet =
tx->SentFrame(0);
1550 ASSERT_GE(parsed.payload.size(), 4U);
1551 EXPECT_EQ(parsed.payload[0], 0x11);
1552 EXPECT_EQ(parsed.payload[1], 0x22);
1553 EXPECT_EQ(parsed.payload[2], 0x33);
1554 EXPECT_EQ(parsed.payload[3], 0x44);
1559 controller->Start(std::move(txSock), std::move(rxSock));
1560 for (
int i = 0; i < 1000; ++i)
1562 controller->OnInput([i](
int,
int,
const auto&)
1571 controller->OnInput([&](
int,
int,
const auto&)
1575 EXPECT_EQ(calls, 0);
1582 controller->Start(std::move(txSock), std::move(rxSock));
1586 std::this_thread::sleep_for(std::chrono::milliseconds(5));
Fixture for input callbacks.
void SetUp() override
Construct the controller used by callback tests.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
std::unique_ptr< CyclicController > controller
Controller under test.
void DeliverInput(std::uint8_t iops)
Deliver an input frame and allow the configured callbacks to run.
Narrow private-access adapter used only by cyclic controller tests.
static void SetRunning(CyclicController &controller, bool value)
Set the controller worker running flag.
static void TxLoop(CyclicController &controller)
Invoke the private TX worker loop synchronously.
static void TrackCycleCounter(CyclicController &controller, std::uint16_t cycleCounter)
Invoke cycle-counter tracking.
static void SetTxSock(CyclicController &controller, std::unique_ptr< IRawEthernetSocket > txSock)
Set the controller txSock.
static void ResetRxSocket(CyclicController &controller)
Reset the injected RX socket.
static void SetRxCounterStep(CyclicController &controller, std::uint32_t value)
Set the RX cycle-counter step.
static void RxLoop(CyclicController &controller)
Invoke the receive loop synchronously.
static void TxCycle(CyclicController &controller)
Invoke one TX cycle synchronously.
static void HandleWatchdogTimeout(CyclicController &controller)
Invoke watchdog timeout handling.
static void SendOutputFrame(CyclicController &controller, std::optional< std::uint8_t > dataStatusOverride=std::nullopt)
Invoke output frame generation.
static bool IocsGood(const CyclicController &controller)
Read the IOCS-good bookkeeping state.
static std::uint8_t FirstOutputIocs(const CyclicController &controller)
Return the IOCS value of the first configured output object.
static void SendStopFrames(CyclicController &controller)
Invoke cyclic stop-frame generation.
static void SetAllOutputIocs(CyclicController &controller, std::uint8_t status)
Set the IOCS value for every configured output object.
static CyclicStats & Stats(CyclicController &controller)
Return the controller statistics object for targeted setup.
static void ProcessInputFrame(CyclicController &controller, const Bytes &data)
Invoke input Ethernet-frame processing.
static std::uint8_t FirstOutputByte(const CyclicController &controller)
Return the first byte of the output payload.
static void SetRxSock(CyclicController &controller, std::unique_ptr< IRawEthernetSocket > rxSock)
Set the controller rxSock.
static void SwapOutputBuilder(CyclicController &controller)
Promote the output write buffer to the send buffer.
static void Transition(CyclicController &controller, CyclicState value)
Invoke the private transition implementation.
static void SetState(CyclicController &controller, CyclicState value)
Set the controller state without starting worker threads.
static rt::IODataObject & InputObject(CyclicController &controller, std::size_t index)
Return an input IO object for malformed-frame setup.
static void SetMaxConsecutiveTimeouts(CyclicController &controller, int value)
Set the watchdog fault threshold.
static void ResetTxSocket(CyclicController &controller)
Reset the injected TX socket.
static void SetIocsGood(CyclicController &controller, bool value)
Set the IOCS-good bookkeeping state.
RT_CLASS_1 cyclic data exchange controller.
Fixture for input data and status access.
void DeliverInput(std::uint8_t iops)
Deliver one input frame with the requested IOPS status.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO Context.
std::unique_ptr< CyclicController > controller
Controller under test.
void SetUp() override
Construct the controller used by data-access tests.
Fixture for input Ethernet parsing and cycle-counter tracking.
void SetUp() override
Construct a controller with a one-count RX cycle step.
std::unique_ptr< CyclicController > controller
Controller under test.
void ProcessCounter(std::uint16_t cycleCounter)
Process a valid input frame for a specific cycle counter.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Fixture for state-transition and lifecycle behavior.
std::unique_ptr< CyclicController > MakeController()
Create a controller with deterministic socket injection.
std::unique_ptr< CyclicController > controller
Controller under test.
int callbackCount
Number of state callback invocations.
std::unique_ptr< MockRawEthernetSocket > txSock
Mocked Tx Socket.
std::unique_ptr< MockRawEthernetSocket > rxSock
Mocked Rx Socket.
Fixture for statistics and snapshots.
std::unique_ptr< CyclicController > controller
Controller under test.
static std::unique_ptr< CyclicController > MakeController()
Create a controller used to validate the public statistics snapshot.
Fixture for TX behavior and generated frame validation.
void SetUp() override
Construct the controller and retain its TX socket stub.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
std::unique_ptr< test::CyclicRawEthernetSocketStub > txSocket
std::unique_ptr< CyclicController > controller
Controller under test.
std::unique_ptr< test::CyclicRawEthernetSocketStub > rxSocket
test::CyclicRawEthernetSocketStub * tx
Injected TX socket stub.
Fixture for watchdog and RX-loop behavior.
std::unique_ptr< test::CyclicRawEthernetSocketStub > tx
std::unique_ptr< test::CyclicRawEthernetSocketStub > rxSocket
test::CyclicRawEthernetSocketStub * rx
Injected receive socket.
void SetUp() override
Construct a controller with a very short input watchdog.
std::unique_ptr< CyclicController > controller
Controller under test.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Deterministic socket stub used by cyclic controller unit tests.
Declares the PROFINET RTC1 cyclic IO controller and process-data exchange API.
Deterministic raw Ethernet socket stub for cyclic-controller unit tests.
GoogleMock implementation of IRawEthernetSocket.
TEST_F(CyclicTest, CyclicStateToStringCoversAllStates)
constexpr int DEFAULT_MAX_CONSECUTIVE_TIMEOUTS
Default number of consecutive watchdog timeouts before transitioning to Fault.
std::string ToString(CyclicState state)
Human-readable name for a CyclicState value.
CyclicState
Lifecycle state of a CyclicController.
@ Fault
Communication failure (e.g. consecutive watchdog timeouts).
@ Running
Active cyclic data exchange.
@ Stopping
Graceful shutdown in progress (sending STOP frames).
@ Stopped
Fully stopped, threads joined.
@ Starting
Sockets created, threads launching.
@ Idle
Initial state, not yet started.
constexpr int STOP_FRAME_COUNT
Number of STOP frames sent during a graceful Stop().
constexpr std::uint8_t IOXS_BAD
IOxS value: bad.
constexpr std::uint16_t ETHERTYPE_PROFINET
EtherType used by PROFINET RT frames (0x8892).
constexpr std::uint8_t IOXS_GOOD
IOxS value: good, subslot level.
constexpr MacAddress kControllerMac
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
constexpr std::uint16_t kInputFrameId
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
constexpr std::uint16_t kOutputFrameId
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
Copyable snapshot of cyclic communication statistics.
std::uint64_t cycleTimeSumUs
std::uint32_t minCycleTimeUs
std::uint32_t maxJitterUs
std::uint64_t framesDuplicate
std::uint64_t framesOutOfOrder
std::uint64_t framesMissed
std::uint64_t framesReceived
std::uint64_t framesInvalid
std::uint32_t lastCycleTimeUs
std::uint64_t AvgCycleTimeUs() const
Compute average TX cycle duration.
std::uint32_t maxCycleTimeUs
Statistics for cyclic communication.
void Reset()
Reset all counters to their initial values.
std::atomic< std::uint32_t > maxJitterUs
Largest observed deviation from the target cycle time, in microseconds.
std::uint64_t AvgCycleTimeUs() const
Compute the average TX cycle duration.
std::atomic< std::uint64_t > framesReceived
Total number of input frames received.
std::atomic< std::uint64_t > framesInvalid
Total number of frames rejected for having an invalid DataStatus.
std::atomic< std::uint64_t > framesOutOfOrder
Total number of frames received out of cycle-counter order.
std::atomic< std::uint32_t > minCycleTimeUs
Smallest observed TX cycle duration, in microseconds.
std::atomic< std::uint64_t > cycleTimeSumUs
Running sum of observed TX cycle durations, in microseconds.
std::atomic< std::uint64_t > framesDuplicate
Total number of duplicate (repeated cycle counter) frames received.
std::atomic< std::uint64_t > framesMissed
Total number of watchdog timeouts (missed input frames).
std::atomic< std::uint32_t > lastCycleTimeUs
Duration of the most recently completed TX cycle, in microseconds.
std::atomic< std::uint64_t > framesSent
Total number of output frames sent.
std::atomic< std::uint64_t > cycleCount
Number of TX cycles included in CycleTimeSumUs.
std::atomic< int > consecutiveTimeouts
Current run of consecutive watchdog timeouts (resets on a good frame).
std::atomic< std::uint32_t > maxCycleTimeUs
Largest observed TX cycle duration, in microseconds.
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
std::vector< IODataObject > objects
IO data objects carried in this IOCR's cyclic frame.
std::uint16_t dataLength
Total cyclic data length in bytes.
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
std::uint16_t frameId
Frame ID assigned to this IOCR.
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Single IO data object within the C_SDU: one piece of process data mapped to a slot/subslot,...
std::uint16_t dataLength
Length in bytes of this object's data.
std::optional< std::uint16_t > offsetIOPS
Byte offset of this object's IOPS (provider status) byte.
std::uint16_t slot
Slot number.
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
static RTFrame FromBytes(const Bytes &data)
Parse an RT frame from raw bytes (after the Ethernet header).
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
static constexpr std::uint8_t DATA_RUN
Alias for DATA_STATUS_PROVIDER_RUN, for readability at call sites.
static constexpr std::uint8_t DATA_VALID
Alias for DATA_STATUS_VALID, for readability at call sites.
std::uint16_t frameId
Ethernet FrameID.
std::unique_ptr< MockRawEthernetSocket > tx
std::unique_ptr< MockRawEthernetSocket > rx
std::unique_ptr< cyclic::CyclicController > controller
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
MockRawEthernetSocket * txSocket
MockRawEthernetSocket * rxSocket