PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_regressionAuma.cpp
Go to the documentation of this file.
1
8
9#include <gtest/gtest.h>
10
11#include <algorithm>
12#include <array>
13#include <chrono>
14#include <cstddef>
15#include <cstdint>
16#include <filesystem>
17#include <memory>
18#include <string>
19#include <vector>
20
21#include "profinet/gsdml.h"
22#include "profinet/protocol.h"
23#include "profinet/rpc.h"
24#include "profinet/rt.h"
25
26namespace profinet
27{
28namespace
29{
30using namespace profinet::rpc;
31
32constexpr std::uint16_t kInputFrameId = 0x9000;
33constexpr std::uint16_t kOutputFrameId = 0x8001;
34constexpr std::uint16_t kSendClockFactor = 32;
35constexpr std::uint16_t kReductionRatio = 8;
36constexpr std::uint16_t kInputLength = 43;
37constexpr std::uint16_t kOutputLength = 40;
38constexpr std::uint16_t kAumaInputLength = 34;
39constexpr std::uint16_t kAumaOutputLength = 16;
40constexpr std::uint16_t kAumaInputOffset = 1;
41constexpr std::uint16_t kAumaOutputOffset = 8;
42constexpr std::uint16_t kAumaInputIopsOffset = 35;
43constexpr std::uint8_t kOpen = 0x01;
44constexpr std::uint8_t kNotOpen = 0x00;
45
46constexpr MacAddress kIoControllerMac{0xA8, 0x74, 0x1D, 0x44, 0x7A, 0xBB};
47constexpr MacAddress kAumaMac{0xA0, 0xD3, 0x85, 0x00, 0xF6, 0x13};
48
49std::filesystem::path Fixture(const std::string& name)
50{
51 const std::array<std::filesystem::path, 4> paths{
52 profinet::GetExecutableDirectory() / "gsdml" / name,
53 profinet::GetExecutableDirectory() / "test_data" / "gsdml" / name,
54 profinet::GetExecutableDirectory() / "tests" / "data" / "gsdml" / name,
55 std::filesystem::current_path() / "tests" / "gsdml" / name};
56
57 for (const auto& path : paths)
58 {
59 if (std::filesystem::exists(path))
60 {
61 return path;
62 }
63 }
64
65 ADD_FAILURE() << "GSDML fixture not found: " << name;
66 return {};
67}
68
69gsdml::GSDMLDevice Load(const std::string& name)
70{
71 const auto path = Fixture(name);
72 return path.empty() ? gsdml::GSDMLDevice{} : gsdml::ParseGsdml(path.string());
73}
74
75const rpc::IOSlot* Slot(const std::vector<rpc::IOSlot>& slots, std::uint16_t slot, std::uint16_t subslot)
76{
77 const auto it = std::ranges::find_if(slots, [slot, subslot](const rpc::IOSlot& value)
78 {
79 return value.slot == slot && value.subslot == subslot;
80 });
81 return it == slots.end() ? nullptr : &(*it);
82}
83
84std::vector<Bytes> Blocks(const Bytes& data)
85{
86 std::vector<Bytes> result;
87 std::size_t offset = 0;
88 while (offset + blockHeaderLength <= data.size())
89 {
90 const auto header = PeekBlockHeader(data, offset);
91 const std::size_t size = 4U + header.blockLength;
92 if (size > data.size() - offset)
93 {
94 break;
95 }
96 result.emplace_back(data.begin() + offset, data.begin() + offset + size);
97 offset += size;
98 }
99 return result;
100}
101
102rt::IOCRConfig CapturedInput()
103{
106 c.referenceIOCR = 1;
108 c.sendClockFactor = kSendClockFactor;
109 c.reductionRatio = kReductionRatio;
110 c.phase = 8;
111 c.watchdogFactor = 3;
112 c.dataLength = kInputLength;
113 c.objects = {
114 {.slot = 0, .subslot = 0x0001, .frameOffset = 0, .dataLength = 0, .offsetIOCS = 0},
115 {.slot = 0, .subslot = 0x0002, .frameOffset = 1, .dataLength = 0, .offsetIOCS = 1},
116 {.slot = 0, .subslot = 0x0003, .frameOffset = 2, .dataLength = 0, .offsetIOCS = 2},
117 {.slot = 0, .subslot = 0x0004, .frameOffset = 3, .dataLength = 0, .offsetIOCS = 3},
118 {.slot = 0, .subslot = 0x8000, .frameOffset = 4, .dataLength = 0, .offsetIOCS = 4},
119 {.slot = 0, .subslot = 0x8001, .frameOffset = 5, .dataLength = 0, .offsetIOCS = 5},
120 {.slot = 0, .subslot = 0x8002, .frameOffset = 6, .dataLength = 0, .offsetIOCS = 6},
121 {.slot = 1, .subslot = 1, .frameOffset = kAumaInputOffset, .dataLength = kAumaInputLength, .offsetIOPS = kAumaInputIopsOffset},
122 {.slot = 2, .subslot = 1, .frameOffset = 0, .dataLength = 0, .offsetIOCS = 42}};
123 return c;
124}
125
126rt::IOCRConfig CapturedOutput()
127{
130 c.referenceIOCR = 2;
132 c.sendClockFactor = kSendClockFactor;
133 c.reductionRatio = kReductionRatio;
134 c.phase = 8;
135 c.watchdogFactor = 3;
136 c.dataLength = kOutputLength;
137 c.objects = {{.slot = 0, .subslot = 0x0001, .frameOffset = 0, .dataLength = 0, .offsetIOCS = 0},
138 {.slot = 0, .subslot = 0x0002, .frameOffset = 1, .dataLength = 0, .offsetIOCS = 1},
139 {.slot = 0, .subslot = 0x0003, .frameOffset = 2, .dataLength = 0, .offsetIOCS = 2},
140 {.slot = 0, .subslot = 0x0004, .frameOffset = 3, .dataLength = 0, .offsetIOCS = 3},
141 {.slot = 0, .subslot = 0x8000, .frameOffset = 4, .dataLength = 0, .offsetIOCS = 4},
142 {.slot = 0, .subslot = 0x8001, .frameOffset = 5, .dataLength = 0, .offsetIOCS = 5},
143 {.slot = 0, .subslot = 0x8002, .frameOffset = 6, .dataLength = 0, .offsetIOCS = 6},
144 {.slot = 1, .subslot = 1, .frameOffset = 7, .dataLength = 0, .offsetIOCS = 7},
145 {.slot = 2, .subslot = 1, .frameOffset = kAumaOutputOffset, .dataLength = kAumaOutputLength, .offsetIOPS = 24}};
146 return c;
147}
148
149Bytes InputPayload(std::uint16_t position)
150{
151 Bytes payload(kInputLength, 0x80);
152
153 // IOPS: 0x80 (Good)
154 // 1... .... = DataState: Bad (0x0)
155 // .00. .... = Instance: 0x00
156 // ...0 000. = Reserved: 0x0
157 // .... ...0 = Extension: No IOxS octet follows (0x0)
158
159 payload[0] = 0x80; // IOPS, see above
160
161 // Input Byte 1 : Logical signals
162 // BIdx 0 =End position OPEN
163 // BIdx 1 =End position CLOSED
164 // BIdx 2 =Setpoint position reach
165 // BIdx 3 =Not ready REMOTE
166 // BIdx 4 =Running OPEN
167 // BIdx 5 =Running CLOSE
168 // BIdx 6 =Warning
169 // BIdx 7 =Fault
170 payload[1] = 0x00; // Idle state
171
172 // Input Byte 2 : Actuator signals
173 // BIdx 0 = Thermal fault
174 // BIdx 1 = Phase fault
175 // BIdx 2 = Selector switch REMOTE
176 // BIdx 3 = Selector switch LOCALE
177 // BIdx 4 = Limit switch OPEN
178 // BIdx 5 = Limit switch CLOSED
179 // BIdx 6 = Torque switch OPEN
180 // BIdx 7 = Torque switch CLOSED
181 payload[2] = 0x04; // Bit 2 is set, so means Selector switch REMOTE is ON
182
183 // Input Byte 3/4 : Actual position
184 payload[3] = static_cast<std::uint8_t>(position >> 8U);
185 payload[4] = static_cast<std::uint8_t>(position);
186 payload[kAumaInputIopsOffset] = 0x80;
187 // kAumaInputIopsOffset = 34 input bytes (Logic signals 1 byte, Actuator signals 1 byte, Actual position, 2 bytes, etc..) (+ 1 (next one = Iops)
188 return payload;
189}
190
191class ConnectCapture final : public rpc::IRpcTransport
192{
193 public:
194 Bytes SendReceive(const Bytes& request, std::chrono::milliseconds /*timeout*/) override
195 {
196 requests.push_back(request);
197 const auto parsed = PNRPCHeader::Parse(request);
198 PNRPCHeader response = parsed;
200
201 Bytes blocks;
202 for (const auto [type, frameId] : std::array<std::pair<std::uint16_t, std::uint16_t>, 2>{{{1, kInputFrameId}, {2, kOutputFrameId}}})
203 {
208 wire::PutU16(blocks, type);
209 wire::PutU16(blocks, type);
210 wire::PutU16(blocks, frameId);
211 }
212
213 PNNRDData nrd;
214 nrd.argsMaximumStatus = 0;
215 nrd.argsLength = static_cast<std::uint32_t>(blocks.size());
216 nrd.maximumCount = 1500;
217 nrd.offset = 0;
218 nrd.actualCount = static_cast<std::uint32_t>(blocks.size());
219 nrd.payload = blocks;
220 response.payload = nrd.ToBytes();
221 response.lengthOfBody = static_cast<std::uint16_t>(response.payload.size());
222 return response.ToBytes();
223 }
224
225 void Send(const Bytes& data) override
226 {
227 sends.push_back(data);
228 }
229 void Open(const std::string& /*address*/, std::uint16_t /*port*/, bool /*isListener*/) override
230 {
231 }
232 Bytes Receive(std::chrono::milliseconds /*timeout*/, asio::ip::udp::endpoint& /*remoteEndpoint*/) override
233 {
234 return {};
235 }
236 void SendTo(const Bytes& data, const asio::ip::udp::endpoint& /*targetEndpoint*/) override
237 {
238 sends.push_back(data);
239 }
240 void Close() noexcept override
241 {
242 }
243
244 std::vector<Bytes> requests;
245 std::vector<Bytes> sends;
246};
247
249{
250 dcp::DCPDeviceDescription info(kAumaMac, {});
251 info.name = "AUMA-SGx";
252 info.ip = "127.0.0.1";
253 info.vendorHigh = 0x01;
254 info.vendorLow = 0x3F;
255 info.deviceHigh = 0x00;
256 info.deviceLow = 0x02;
257 return info;
258}
259
260} // namespace
261
262TEST(IoControllerAumaGsdml, AumaProfileMatchesCaptureDevice)
263{
264 const auto auma = Load("GSDML-V2.42-AUMA-SGX-20220920.xml");
265 EXPECT_EQ(auma.vendorId, 0x013F);
266 EXPECT_EQ(auma.deviceId, 0x0002);
267 EXPECT_EQ(auma.dapModuleIdentNumber, 0x80010000U);
268
269 const auto slots = auma.BuildIoSlots();
270 const auto* input = Slot(slots, 1, 1);
271 const auto* output = Slot(slots, 2, 1);
272 ASSERT_NE(input, nullptr);
273 ASSERT_NE(output, nullptr);
274 EXPECT_EQ(input->moduleIdent, 0x00008000U);
275 EXPECT_EQ(input->submoduleIdent, 0x00002200U);
276 EXPECT_EQ(input->inputLength, kAumaInputLength);
277 EXPECT_EQ(output->moduleIdent, 0x00008100U);
278 EXPECT_EQ(output->submoduleIdent, 0x10001000U);
279 EXPECT_EQ(output->outputLength, kAumaOutputLength);
280}
281
282TEST(IoControllerAumaGsdml, PhoenixProfileMatchesCaptureController)
283{
284 const auto phoenix = Load("GSDML-V2.45-Phoenix Contact-AXC F 1252-20250813.xml");
285 EXPECT_EQ(phoenix.vendorId, 0x00B0);
286 EXPECT_EQ(phoenix.deviceId, 0x0193);
287 EXPECT_EQ(phoenix.dapModuleIdentNumber, 0x00000300U);
288 EXPECT_NE(phoenix.FindModule("PNSlot1"), nullptr);
289}
290
291TEST(IoControllerAumaConnection, ConnectUsesCapturedNegotiatedFrameIdsAndTiming)
292{
293 auto transport = std::make_unique<ConnectCapture>();
294 auto* capture = transport.get();
295 auto control = std::make_unique<ConnectCapture>();
296
297 rpc::RPCCon connection(AumaInfo(), {}, std::move(transport), std::move(control));
298 const auto auma = Load("GSDML-V2.42-AUMA-SGX-20220920.xml");
299 auto setup = rpc::IOCRSetup{};
300 setup.slots = auma.BuildIoSlots();
301 setup.sendClockFactor = kSendClockFactor;
302 setup.reductionRatio = kReductionRatio;
303 setup.watchdogFactor = 3;
304 setup.dataHoldFactor = 6;
305
306 const auto result = connection.Connect(kIoControllerMac, true, setup);
307 ASSERT_TRUE(result.has_value());
308 EXPECT_EQ(result->inputFrameId, kInputFrameId);
309 EXPECT_EQ(result->outputFrameId, kOutputFrameId);
310 ASSERT_EQ(capture->requests.size(), 1U);
311
312 const auto rpcPacket = PNRPCHeader::Parse(capture->requests.front());
313 const auto nrd = PNNRDData::Parse(rpcPacket.payload);
314 const auto blocks = Blocks(nrd.payload);
315 ASSERT_GE(blocks.size(), 4U);
316
317 EXPECT_EQ(PeekBlockHeader(blocks[0], 0).blockType,
318 static_cast<std::uint16_t>(BlockType::ApplicationRelationRequest));
319 EXPECT_EQ(PeekBlockHeader(blocks[1], 0).blockType, static_cast<std::uint16_t>(PNIOCRBlockReqHeader::BLOCK_TYPE));
320 EXPECT_EQ(PeekBlockHeader(blocks[2], 0).blockType, static_cast<std::uint16_t>(PNIOCRBlockReqHeader::BLOCK_TYPE));
321
322 const auto input = PNIOCRBlockReqHeader::Parse(blocks[1]);
323 const auto output = PNIOCRBlockReqHeader::Parse(blocks[2]);
324 EXPECT_EQ(input.iocrType, 1);
325 EXPECT_EQ(input.iocrReference, 1);
326 EXPECT_EQ(input.dataLength, kInputLength);
327 EXPECT_EQ(input.sendClockFactor, kSendClockFactor);
328 EXPECT_EQ(input.reductionRatio, kReductionRatio);
329 EXPECT_EQ(output.iocrType, 2);
330 EXPECT_EQ(output.iocrReference, 2);
331 EXPECT_EQ(output.dataLength, kOutputLength);
332 EXPECT_EQ(output.sendClockFactor, kSendClockFactor);
333 EXPECT_EQ(output.reductionRatio, kReductionRatio);
334
335 const auto types = std::vector<std::uint16_t>{
336 PeekBlockHeader(blocks[0], 0).blockType,
337 PeekBlockHeader(blocks[1], 0).blockType,
338 PeekBlockHeader(blocks[2], 0).blockType};
339 const std::vector<std::uint16_t> expected = {
340 static_cast<std::uint16_t>(BlockType::ApplicationRelationRequest),
341 static_cast<std::uint16_t>(PNIOCRBlockReqHeader::BLOCK_TYPE),
342 static_cast<std::uint16_t>(PNIOCRBlockReqHeader::BLOCK_TYPE)};
343 EXPECT_EQ(types, expected);
344}
345
346TEST(IoControllerAumaCyclicIo, OutputOpenAndStopUseCapturedByte25)
347{
348 const auto config = CapturedOutput();
349 rt::CyclicDataBuilder builder(config);
350 // Output module from GSDML = slot 2, subslot = 1
351 // Output Byte 1 : Commands
352 // BIdx 0 =Fieldbus OPEN
353 // BIdx 1 =Fieldbus CLOSE
354 // BIdx 2 =Fieldbus SETPOINT
355 // BIdx 3 =Fieldbus RESET
356 // BIdx 4 =Fieldbus STOP
357 // BIdx 5 =-
358 // BIdx 6 =-
359 // BIdx 7 =-
360
361 builder.SetData(2, 1, Bytes{kOpen}); // kOpen = 0x01
362 builder.Swap();
363 auto openPayload = builder.Build();
364 ASSERT_EQ(openPayload.size(), kOutputLength);
365 EXPECT_EQ(openPayload[kAumaOutputOffset], kOpen);
366
367 builder.SetData(2, 1, Bytes{kNotOpen}); // kNotOpen = 0x00
368 builder.Swap();
369 const auto stopOpenPayload = builder.Build();
370 ASSERT_EQ(stopOpenPayload.size(), kOutputLength);
371
372 EXPECT_EQ(stopOpenPayload[kAumaOutputOffset], kNotOpen); // kNotOpen = 0x00
373
374 const rt::RTFrame openFrame{
376 .cycleCounter = 1,
378 .transferStatus = 0,
379 .payload = openPayload};
380
381 const auto ethernet = rt::BuildEthernetFrame(kAumaMac, kIoControllerMac, openFrame);
382
383 wire::Reader rh(ethernet);
384 // EthernetHeader oh = EthernetHeader::Parse(ethernet);
385 auto dst = rh.Fixed<macAddressLength>();
386 auto src = rh.Fixed<macAddressLength>();
387 auto etherFrametype = rh.U16();
388 auto frameBytes = rh.ReadBytes(rh.Remaining());
389
391
392 // Builder should create an IOCRConfig
393 rt::IOCRConfig stopConfig;
394 wire::Reader r(frame.payload);
395 // stopConfig.typeIOCR = r.U16();
396 auto dapIocr = r.U8(); // device access point; submodule without output data
397 auto prmIocr = r.U8(); // parameter; submodule without output data
398 auto pzsIocr = r.U8(); // process data; submodule without output data
399 auto fileTransferIocr = r.U8(); // file transfer; submodule without output data
400 auto interfaceIocr = r.U8(); // Interface; submodule without output data
401 auto port1Iocr = r.U8(); // P1; submodule without output data
402 auto port2Iocr = r.U8(); // P2; submodule without output data
403 auto inputIocr = r.U8(); // inputIocr; submodule without output data
404 auto outputbytes = r.Fixed<kAumaOutputLength>(); // Output process data
405 //
406 auto outputIops = r.U8(); // ? IOPS byte ??
407 auto remainingData = r.ReadBytes(r.Remaining());
408
409 wire::Reader outputProcessDataReader(outputbytes);
410 // Output Byte 1 : Commands
411 // BIdx 0 =Fieldbus OPEN
412 // BIdx 1 =Fieldbus CLOSE
413 // BIdx 2 =Fieldbus SETPOINT
414 // BIdx 3 =Fieldbus RESET
415 // BIdx 4 =Fieldbus STOP
416 // BIdx 5 =-
417 // BIdx 6 =-
418 // BIdx 7 =-
419 auto commands = outputProcessDataReader.U8();
420 auto remainingOutputBytes = outputProcessDataReader.ReadBytes(outputProcessDataReader.Remaining());
421
422 ASSERT_EQ(ethernet.size(), 60U);
423 EXPECT_EQ(etherFrametype, PROFINET_ETHERTYPE);
424 EXPECT_EQ(frame.frameId, kOutputFrameId);
425
426 EXPECT_TRUE(commands & kOpen); // check if Fieldbus Open Command bit is set;
427
428 for (const auto remainingOutputByte : remainingOutputBytes)
429 {
430 EXPECT_EQ(remainingOutputByte, 0x00);
431 }
432
433 const rt::RTFrame stopOpeningFrame{
435 .cycleCounter = 1,
437 .transferStatus = 0,
438 .payload = stopOpenPayload};
439
440 const auto ethernetStopOpen = rt::BuildEthernetFrame(kAumaMac, kIoControllerMac, stopOpeningFrame);
441
442 wire::Reader rhStopOpen(ethernetStopOpen);
443 // EthernetHeader oh = EthernetHeader::Parse(ethernet);
444 dst = rhStopOpen.Fixed<macAddressLength>();
445 src = rhStopOpen.Fixed<macAddressLength>();
446 etherFrametype = rhStopOpen.U16();
447 frameBytes = rhStopOpen.ReadBytes(rhStopOpen.Remaining());
448
449 frame = rt::RTFrame::FromBytes(frameBytes);
450
451 // Builder should create an IOCRConfig
452 rt::IOCRConfig configStopOpen;
453 wire::Reader r2(frame.payload);
454 // stopConfig.typeIOCR = r.U16();
455 dapIocr = r2.U8(); // device access point; submodule without output data
456 prmIocr = r2.U8(); // parameter; submodule without output data
457 pzsIocr = r2.U8(); // process data; submodule without output data
458 fileTransferIocr = r2.U8(); // file transfer; submodule without output data
459 interfaceIocr = r2.U8(); // Interface; submodule without output data
460 port1Iocr = r2.U8(); // P1; submodule without output data
461 port2Iocr = r2.U8(); // P2; submodule without output data
462 inputIocr = r2.U8(); // inputIocr; submodule without output data
463 outputbytes = r2.Fixed<kAumaOutputLength>(); // Output process data
464 //
465 outputIops = r2.U8(); // ? IOPS byte ??
466 remainingData = r2.ReadBytes(r2.Remaining());
467
468 wire::Reader outputProcessDataReaderStopOpen(outputbytes);
469 // Output Byte 1 : Commands
470 // BIdx 0 =Fieldbus OPEN
471 // BIdx 1 =Fieldbus CLOSE
472 // BIdx 2 =Fieldbus SETPOINT
473 // BIdx 3 =Fieldbus RESET
474 // BIdx 4 =Fieldbus STOP
475 // BIdx 5 =-
476 // BIdx 6 =-
477 // BIdx 7 =-
478 commands = outputProcessDataReaderStopOpen.U8();
479 remainingOutputBytes = outputProcessDataReaderStopOpen.ReadBytes(outputProcessDataReaderStopOpen.Remaining());
480
481 ASSERT_EQ(ethernet.size(), 60U);
482 EXPECT_EQ(etherFrametype, PROFINET_ETHERTYPE);
483 EXPECT_EQ(frame.frameId, kOutputFrameId);
484
485 EXPECT_FALSE(commands & kOpen); // check if Fieldbus Open Command bit is unset;
486
487 for (const auto remainingOutputByte : remainingOutputBytes)
488 {
489 EXPECT_EQ(remainingOutputByte, 0x00);
490 }
491}
492
493TEST(IoControllerAumaCyclicIo, InputPositionSamplesRemainBigEndianAtCapturedBytes20And21)
494{
495 auto config = CapturedInput();
496 rt::CyclicDataBuilder builder(config);
497
498 constexpr std::array<std::uint16_t, 6> samples{
499 0x0202, 0x0206, 0x0207, 0x020F, 0x0217, 0x0221};
500
501 for (const auto sample : samples)
502 {
503 builder.Load(InputPayload(sample));
504 builder.Swap();
505 const auto payload = builder.Build();
507 auto iOps = r.U8();
508 auto logicalSignals = r.U8();
509 auto actuatorSignals = r.U8();
510 auto actualPosition = r.U16();
511
512 ASSERT_EQ(payload.size(), kInputLength);
513 EXPECT_EQ(actualPosition, sample);
514 EXPECT_EQ(payload[kAumaInputIopsOffset], 0x80);
515 EXPECT_EQ(iOps, 0x80);
516 }
517}
518
519TEST(IoControllerAumaCyclicIo, Layer2FramesMatchCaptureLengthsAndDirections)
520{
521 const auto output = rt::BuildEthernetFrame(
522 kAumaMac, kIoControllerMac,
525 .cycleCounter = 1,
527 .transferStatus = 0,
528 .payload = Bytes(kOutputLength, 0)});
529 const auto input = rt::BuildEthernetFrame(
530 kIoControllerMac, kAumaMac,
533 .cycleCounter = 1,
535 .transferStatus = 0,
536 .payload = Bytes(kInputLength, 0x80)});
537
538 EXPECT_EQ(output.size(), 60U);
539 EXPECT_EQ(input.size(), 63U);
540
541 wire::Reader roh(output);
542 auto dstOutput = roh.Fixed<macAddressLength>();
543 auto srcOutput = roh.Fixed<macAddressLength>();
544 auto etherFrametypeOutput = roh.U16();
545 auto frameBytesOutput = roh.ReadBytes(roh.Remaining());
546
547 rt::RTFrame frameOutput = rt::RTFrame::FromBytes(frameBytesOutput);
548
549 EXPECT_EQ(frameOutput.frameId, kOutputFrameId);
550
551 wire::Reader rih(input);
552 auto dstInput = rih.Fixed<macAddressLength>();
553 auto srcInput = rih.Fixed<macAddressLength>();
554 auto etherFrametypeInput = rih.U16();
555 auto frameBytesInput = rih.ReadBytes(rih.Remaining());
556
557 rt::RTFrame frameInput = rt::RTFrame::FromBytes(frameBytesInput);
558
559 EXPECT_EQ(frameInput.frameId, kInputFrameId);
560}
561
562TEST(IoControllerAumaCyclicIo, CapturedOneSecondOpenSequenceUsesEightMillisecondCycle)
563{
564 constexpr std::size_t kOpenFirstPacket = 431;
565 constexpr std::size_t kStopFirstPacket = 709;
566 constexpr std::size_t kFirstOpenPacket = 431;
567 constexpr std::size_t kLastOpenPacket = 707;
568 constexpr std::int64_t kObservedMicroseconds = 999951;
569
570 EXPECT_EQ(kStopFirstPacket - kFirstOpenPacket, 278U);
571 EXPECT_EQ(kLastOpenPacket - kFirstOpenPacket + 1U, 277U);
572 EXPECT_EQ(kObservedMicroseconds / 125000, 7); // sanity check only
573 EXPECT_NE(kOpenFirstPacket, 0U);
574 EXPECT_EQ(kSendClockFactor * kReductionRatio * 125U / 4U, 8000U);
575}
576
577} // namespace profinet
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
std::string name
Station name.
Definition dcp.h:282
Connection to a PROFINET IO-Device: establishes an AR (Application Relationship) and performs acyclic...
Definition rpc.h:138
Builds C_SDU payload from IO data objects with double-buffering.
Definition rt.h:287
void Load(const Bytes &payload)
Load received payload data into the write buffer.
Definition rt.cpp:234
void SetData(int slot, int subslot, const Bytes &data)
Write one object's data into the write buffer.
Definition rt.cpp:116
void Swap()
Promote the write buffer to the send buffer.
Definition rt.cpp:218
Bytes Build() const
Get the current send buffer contents.
Definition rt.cpp:228
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::uint8_t U8()
Read and consume one byte.
Definition wire.h:250
std::size_t Remaining() const
Number of bytes not yet consumed.
Definition wire.h:218
std::array< std::uint8_t, N > Fixed()
Read and consume a fixed-size byte array.
Definition wire.h:316
std::vector< std::uint8_t > ReadBytes(std::size_t n)
Read and consume a variable-length byte range.
Definition wire.h:304
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
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 std::uint8_t VERSION_HIGH
Version high.
Definition rpc.h:42
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
Definition rpc.h:54
constexpr std::uint8_t VERSION_LOW
Version low.
Definition rpc.h:44
constexpr std::uint16_t IOCR_TYPE_INPUT
IOCR direction: Device -> Controller.
Definition rt.h:66
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::uint8_t DATA_STATUS_VALID
DataStatus bit: data validity, 0=Invalid, 1=Valid.
Definition rt.h:83
constexpr std::uint16_t IOCR_TYPE_OUTPUT
IOCR direction: Controller -> Device.
Definition rt.h:68
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t value)
Append a single byte to a buffer.
Definition wire.h:89
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:97
@ ApplicationRelationRequest
AR block request.
TEST(AlarmEndpointTests, DefaultTransportIsLayer2)
constexpr int blockHeaderLength
Constant lenght of a blockHeader.
Definition protocol.h:58
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::filesystem::path GetExecutableDirectory()
Retrieves the absolute directory path of the currently running executable.
constexpr int macAddressLength
Constant lenght of a mac address.
Definition util.h:37
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:30
constexpr std::uint16_t kInputFrameId
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
constexpr std::uint16_t kOutputFrameId
PROFINET protocol wire-format structures and serialization helpers.
PROFINET DCE/RPC protocol structures and IO-Device connection API.
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
static constexpr BlockType BLOCK_TYPE
Block type identifier for IOCRBlockReq.
Definition protocol.h:1987
static PNIOCRBlockReqHeader Parse(const Bytes &data)
Parse the fixed IOCRBlockReq header.
Definition protocol.h:2048
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for IOCRBlockRes.
Definition protocol.h:1943
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:1959
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Definition protocol.h:1128
Bytes payload
The wrapped IOD data.
Definition protocol.h:1145
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Definition protocol.h:1153
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
Definition protocol.h:1175
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
Definition protocol.h:1133
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
Definition protocol.h:1139
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
Definition protocol.h:1130
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
Definition protocol.h:1136
std::uint32_t actualCount
Actual length in bytes of Payload.
Definition protocol.h:1142
DCE/RPC PDU header used to carry PROFINET connect/read/write/control requests.
Definition protocol.h:860
Bytes ToBytes() const
Serialize this header and body back to raw bytes.
Definition protocol.h:1045
static PNRPCHeader Parse(const Bytes &data)
Parse an RPC header and body from raw bytes.
Definition protocol.h:1012
std::uint8_t packetType
Packet type (REQUEST/RESPONSE/FAULT/...).
Definition protocol.h:922
static constexpr std::uint8_t RESPONSE
Packet type: RESPONSE.
Definition protocol.h:869
Bytes payload
PDU body (NRD data for read/write/control, etc.).
Definition protocol.h:976
std::uint16_t lengthOfBody
Length in bytes of Payload.
Definition protocol.h:964
Complete parsed GSDML device profile.
Definition gsdml.h:166
std::uint16_t blockType
Definition rpc.h:50
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:149
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:151
One slot/subslot's expected module configuration and IO data sizes.
Definition rpcTypes.h:96
std::uint16_t slot
Slot number.
Definition rpcTypes.h:98
std::uint16_t subslot
Subslot number.
Definition rpcTypes.h:101
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
Definition rt.h:137
std::vector< IODataObject > objects
IO data objects carried in this IOCR's cyclic frame.
Definition rt.h:163
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition rt.h:160
std::uint16_t typeIOCR
IOCR direction: 1=Input, 2=Output.
Definition rt.h:139
std::uint16_t referenceIOCR
IOCR reference assigned during AR establishment.
Definition rt.h:142
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rt.h:148
std::uint16_t frameId
Frame ID assigned to this IOCR.
Definition rt.h:145
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rt.h:151
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rt.h:157
std::uint16_t phase
Phase offset within the reduction ratio.
Definition rt.h:154
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
Definition rt.h:208
static RTFrame FromBytes(const Bytes &data)
Parse an RT frame from raw bytes (after the Ethernet header).
Definition rt.cpp:14
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
Definition rt.h:210
std::uint16_t frameId
Ethernet FrameID.
Bytes frame
Bytes payload
Definition test_dcp.cpp:392
std::vector< Bytes > sends
std::vector< Bytes > requests