PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_gsdml_real_device.cpp
Go to the documentation of this file.
1
16
17#include <gtest/gtest.h>
18
19#include <algorithm>
20#include <cstdint>
21#include <filesystem>
22#include <string>
23#include <vector>
24
25#include "profinet/IOSignal.h"
26#include "profinet/gsdml.h"
27
28namespace profinet::gsdml
29{
30
31namespace
32{
42std::filesystem::path FindGsdmlFile(const std::string& fileName)
43{
44 const std::vector<std::filesystem::path> candidates = {
45 profinet::GetExecutableDirectory() / std::filesystem::path("gsdml") / fileName,
46 profinet::GetExecutableDirectory() / std::filesystem::path("test_data") / fileName,
47 profinet::GetExecutableDirectory() / std::filesystem::path("test_data") / "gsdml" / fileName,
48 profinet::GetExecutableDirectory() / std::filesystem::path("tests") / "data" / fileName,
49 profinet::GetExecutableDirectory() / std::filesystem::path("tests") / "data" / "gsdml" / fileName,
50 };
51
52 for (const auto& candidate : candidates)
53 {
54 if (std::filesystem::exists(candidate))
55 {
56 return candidate;
57 }
58 }
59
60 ADD_FAILURE() << "Unable to locate GSDML fixture '" << fileName << "'";
61 return {};
62}
63
71GSDMLDevice ParseAumaGsdml()
72{
73 const auto path = FindGsdmlFile("GSDML-V2.42-AUMA-SGX-20220920.xml");
74
75 if (path.empty())
76 {
77 return {};
78 }
79
80 return ParseGsdml(path.string());
81}
82
90GSDMLDevice ParsePhoenixGsdml()
91{
92 const auto path = FindGsdmlFile("GSDML-V2.45-Phoenix Contact-AXC F 1252-20250813.xml");
93
94 if (path.empty())
95 {
96 return {};
97 }
98
99 return ParseGsdml(path.string());
100}
101
107const rpc::IOSlot* FindSlot(const std::vector<rpc::IOSlot>& slots,
108 std::uint16_t slotNumber)
109{
110 for (const auto& slot : slots)
111 {
112 if (slot.slot == slotNumber)
113 {
114 return &slot;
115 }
116 }
117
118 return nullptr;
119}
120
127const rpc::IOSlot* FindSlot(const std::vector<rpc::IOSlot>& slots,
128 std::uint16_t slotNumber,
129 std::uint16_t subslotNumber)
130{
131 for (const auto& slot : slots)
132 {
133 if ((slot.slot == slotNumber) && (slot.subslot == subslotNumber))
134 {
135 return &slot;
136 }
137 }
138
139 return nullptr;
140}
141
142} // namespace
143
145class GsdmlRealDeviceTest : public ::testing::Test
146{
147 protected:
149 void SetUp() override
150 {
151 auma = ParseAumaGsdml();
152 phoenix = ParsePhoenixGsdml();
153 }
154
157
160};
161
164{
165 EXPECT_EQ(auma.vendorId, 0x013F);
166 EXPECT_EQ(auma.deviceId, 0x0002);
167 EXPECT_EQ(auma.dapModuleIdentNumber, 0x80010000U);
168}
169
171TEST_F(GsdmlRealDeviceTest, ParsesPhoenixGsdml)
172{
173 EXPECT_EQ(phoenix.vendorId, 0x00B0);
174 EXPECT_EQ(phoenix.deviceId, 0x0193);
175 EXPECT_EQ(phoenix.dapModuleIdentNumber, 0x00000300U);
176}
177
180{
181 EXPECT_EQ(auma.vendorId, 0x013F);
182}
183
186{
187 EXPECT_EQ(auma.deviceId, 0x0002);
188}
189
192{
193 ASSERT_NE(auma.dapModuleIdentNumber, 0U);
194 EXPECT_EQ(auma.dapModuleIdentNumber, 0x80010000U);
195 EXPECT_EQ(auma.dapSubmoduleIdentNumber, 0x00000001U);
196 EXPECT_EQ(auma.dapSlots, (std::vector<int>{0, 1, 2}));
197}
198
200TEST_F(GsdmlRealDeviceTest, FindsActuatorStatusModule)
201{
202 const auto* module = auma.FindModule("ID_MODULE_ADI_IN_0");
203
204 ASSERT_NE(module, nullptr);
205 EXPECT_EQ(module->moduleIdentNumber, 0x00008000U);
206 EXPECT_EQ(module->submodules.size(), 1U);
207}
208
210TEST_F(GsdmlRealDeviceTest, FindsActuatorControlModule)
211{
212 const auto* module = auma.FindModule("ID_MODULE_ADI_OUT_0");
213
214 ASSERT_NE(module, nullptr);
215 EXPECT_EQ(module->moduleIdentNumber, 0x00008100U);
216 EXPECT_EQ(module->submodules.size(), 1U);
217}
218
220TEST_F(GsdmlRealDeviceTest, ParsesStatusInputLength)
221{
222 const auto* module = auma.FindModule("ID_MODULE_ADI_IN_0");
223
224 ASSERT_NE(module, nullptr);
225
226 const auto submodule = module->submodules.find("ID_SUBMOD_ADI_IN_0");
227
228 ASSERT_NE(submodule, module->submodules.end());
229 EXPECT_EQ(submodule->second.inputLength, 34);
230 EXPECT_EQ(submodule->second.outputLength, 0);
231}
232
234TEST_F(GsdmlRealDeviceTest, ParsesControlOutputLength)
235{
236 const auto* module = auma.FindModule("ID_MODULE_ADI_OUT_0");
237
238 ASSERT_NE(module, nullptr);
239
240 const auto submodule = module->submodules.find("ID_SUBMOD_ADI_OUT_0");
241
242 ASSERT_NE(submodule, module->submodules.end());
243 EXPECT_EQ(submodule->second.inputLength, 0);
244 EXPECT_EQ(submodule->second.outputLength, 16);
245}
246
248TEST_F(GsdmlRealDeviceTest, ParsesStatusSubmoduleIdent)
249{
250 const auto* module = auma.FindModule("ID_MODULE_ADI_IN_0");
251
252 ASSERT_NE(module, nullptr);
253
254 const auto submodule = module->submodules.find("ID_SUBMOD_ADI_IN_0");
255
256 ASSERT_NE(submodule, module->submodules.end());
257 EXPECT_EQ(submodule->second.submoduleIdentNumber, 0x00002200U);
258}
259
261TEST_F(GsdmlRealDeviceTest, ParsesControlSubmoduleIdent)
262{
263 const auto* module = auma.FindModule("ID_MODULE_ADI_OUT_0");
264
265 ASSERT_NE(module, nullptr);
266
267 const auto submodule = module->submodules.find("ID_SUBMOD_ADI_OUT_0");
268
269 ASSERT_NE(submodule, module->submodules.end());
270 EXPECT_EQ(submodule->second.submoduleIdentNumber, 0x10001000U);
271}
272
274TEST_F(GsdmlRealDeviceTest, BuildIoSlotsMatchesExpectedInputLengths)
275{
276 const auto slots = auma.BuildIoSlots();
277
278 const auto* status = FindSlot(slots, 1, 1);
279 const auto* control = FindSlot(slots, 2, 1);
280
281 ASSERT_NE(status, nullptr);
282 ASSERT_NE(control, nullptr);
283
284 EXPECT_EQ(status->inputLength, 34);
285 EXPECT_EQ(status->outputLength, 0);
286
287 EXPECT_EQ(control->inputLength, 0);
288 EXPECT_EQ(control->outputLength, 16);
289}
290
292TEST_F(GsdmlRealDeviceTest, BuildIoSlotsMatchesExpectedOutputLengths)
293{
294 const auto slots = auma.BuildIoSlots();
295
296 const auto* status = FindSlot(slots, 1, 1);
297 const auto* control = FindSlot(slots, 2, 1);
298
299 ASSERT_NE(status, nullptr);
300 ASSERT_NE(control, nullptr);
301
302 EXPECT_EQ(status->outputLength, 0);
303 EXPECT_EQ(control->outputLength, 16);
304}
305
307TEST_F(GsdmlRealDeviceTest, BuildIoSlotsProducesExpectedModuleIdents)
308{
309 const auto slots = auma.BuildIoSlots();
310
311 const auto* status = FindSlot(slots, 1, 1);
312 const auto* control = FindSlot(slots, 2, 1);
313
314 ASSERT_NE(status, nullptr);
315 ASSERT_NE(control, nullptr);
316
317 EXPECT_EQ(status->moduleIdent, 0x00008000U);
318 EXPECT_EQ(status->submoduleIdent, 0x00002200U);
319
320 EXPECT_EQ(control->moduleIdent, 0x00008100U);
321 EXPECT_EQ(control->submoduleIdent, 0x10001000U);
322}
323
325TEST_F(GsdmlRealDeviceTest, FindsPhoenixIoModule)
326{
327 const auto* module = phoenix.FindModule("PNSlot1");
328
329 ASSERT_NE(module, nullptr);
330 EXPECT_EQ(module->moduleIdentNumber, 0x00000001U);
331}
332
335{
336 EXPECT_EQ(phoenix.dapModuleIdentNumber, 0x00000300U);
337 EXPECT_EQ(phoenix.dapSubmoduleIdentNumber, 0x00000001U);
338}
341TEST_F(GsdmlRealDeviceTest, BuildsPhoenixDefaultIoSlotConfiguration)
342{
343 const auto slots = phoenix.BuildIoSlots();
344
345 ASSERT_EQ(slots.size(), 4U);
346
347 const auto* dap = FindSlot(slots, 0, 1);
348 const auto* interface = FindSlot(slots, 0, 0x8000);
349 const auto* port = FindSlot(slots, 0, 0x8001);
350 const auto* io = FindSlot(slots, 1, 1);
351
352 ASSERT_NE(dap, nullptr);
353 ASSERT_NE(interface, nullptr);
354 ASSERT_NE(port, nullptr);
355 ASSERT_NE(io, nullptr);
356
357 EXPECT_EQ(dap->moduleIdent, phoenix.dapModuleIdentNumber);
358 EXPECT_EQ(dap->submoduleIdent, phoenix.dapSubmoduleIdentNumber);
359
360 EXPECT_EQ(interface->submoduleIdent, 0x00000002U);
361 EXPECT_EQ(port->submoduleIdent, 0x00000003U);
362
363 EXPECT_EQ(io->moduleIdent, 0x00000001U);
364 EXPECT_EQ(io->submoduleIdent, 0x00000001U);
365}
366
368TEST_F(GsdmlRealDeviceTest, PhoenixFixedModuleResolvesToSlotOne)
369{
370 const auto slots = phoenix.BuildIoSlots();
371
372 const auto* slot = FindSlot(slots, 1, 1);
373
374 ASSERT_NE(slot, nullptr);
375
376 EXPECT_EQ(slot->moduleIdent, 0x00000001U);
377 EXPECT_EQ(slot->submoduleIdent, 0x00000001U);
378 EXPECT_EQ(slot->inputLength, 512);
379 EXPECT_EQ(slot->outputLength, 512);
380}
381
384TEST_F(GsdmlRealDeviceTest, PhoenixDefaultSubmoduleResolvesToPnio512512)
385{
386 const auto* module = phoenix.FindModule("PNSlot1");
387
388 ASSERT_NE(module, nullptr);
389 ASSERT_EQ(module->submoduleReferences.size(), 9U);
390
391 const auto* submodule = phoenix.FindSubmodule("PNIO512512");
392
393 ASSERT_NE(submodule, nullptr);
394
395 EXPECT_EQ(submodule->submoduleIdentNumber, 0x00000001U);
396 EXPECT_EQ(submodule->inputLength, 512);
397 EXPECT_EQ(submodule->outputLength, 512);
398}
399
402TEST_F(GsdmlRealDeviceTest, PhoenixModuleReferenceContainsUsedSubslot)
403{
404 const auto* module = phoenix.FindModule("PNSlot1");
405
406 ASSERT_NE(module, nullptr);
407
408 bool foundUsedSubslot = false;
409
410 for (const auto& reference : module->submoduleReferences)
411 {
412 if (reference.submoduleId == "PNIO512512")
413 {
414 foundUsedSubslot = true;
415
416 EXPECT_EQ(reference.allowedSubslots, (std::vector<int>{1}));
417 EXPECT_EQ(reference.fixedSubslots, std::vector<int>{});
418 EXPECT_EQ(reference.usedSubslots, (std::vector<int>{1}));
419 }
420 }
421
422 EXPECT_TRUE(foundUsedSubslot);
423}
424
427TEST_F(GsdmlRealDeviceTest, PhoenixApplicationRelationsExposeTimingProperties)
428{
429 EXPECT_EQ(phoenix.applicationRelations.numberOfAr, 3U);
430
431 EXPECT_EQ(phoenix.applicationRelations.sendClockTimingProperties,
432 (std::vector<std::uint16_t>{32}));
433
434 EXPECT_EQ(phoenix.applicationRelations.reductionRatioTimingProperties,
435 (std::vector<std::uint16_t>{
436 1, 2, 4, 8, 16, 32, 64, 128, 256, 512}));
437
438 EXPECT_EQ(
439 phoenix.applicationRelations.startupMode,
440 rpc::StartupMode::Advanced);
441}
442
445TEST_F(GsdmlRealDeviceTest, PhoenixDapVirtualSubmoduleBuildsSlotZero)
446{
447 const auto slots = phoenix.BuildIoSlots();
448
449 const auto* slot = FindSlot(slots, 0, 1);
450
451 ASSERT_NE(slot, nullptr);
452
453 EXPECT_EQ(slot->moduleIdent, phoenix.dapModuleIdentNumber);
454 EXPECT_EQ(slot->submoduleIdent, phoenix.dapSubmoduleIdentNumber);
455 EXPECT_EQ(slot->inputLength, 0U);
456 EXPECT_EQ(slot->outputLength, 0U);
457}
458
461TEST_F(GsdmlRealDeviceTest, PhoenixInterfaceSubmoduleBuildsAtPdevSubslot)
462{
463 const auto slots = phoenix.BuildIoSlots();
464
465 const auto* slot = FindSlot(slots, 0, 0x8000);
466
467 ASSERT_NE(slot, nullptr);
468
469 EXPECT_EQ(slot->submoduleIdent, 0x00000002U);
470 EXPECT_EQ(slot->inputLength, 0U);
471 EXPECT_EQ(slot->outputLength, 0U);
472}
473
476TEST_F(GsdmlRealDeviceTest, PhoenixPortSubmoduleBuildsAtPdevSubslot)
477{
478 const auto slots = phoenix.BuildIoSlots();
479
480 const auto* slot = FindSlot(slots, 0, 0x8001);
481
482 ASSERT_NE(slot, nullptr);
483
484 EXPECT_EQ(slot->submoduleIdent, 0x00000003U);
485 EXPECT_EQ(slot->inputLength, 0U);
486 EXPECT_EQ(slot->outputLength, 0U);
487}
488
491TEST_F(GsdmlRealDeviceTest, PhoenixExplicitPluggedConfigurationOverridesDefault)
492{
493 const auto slots = phoenix.BuildIoSlots(
494 {{1, "PNSlot1", 1, "PNIO0202"}});
495
496 const auto* slot = FindSlot(slots, 1, 1);
497
498 ASSERT_NE(slot, nullptr);
499
500 EXPECT_EQ(slot->moduleIdent, 0x00000001U);
501 EXPECT_EQ(slot->submoduleIdent, 0x00000009U);
502 EXPECT_EQ(slot->inputLength, 2U);
503 EXPECT_EQ(slot->outputLength, 2U);
504}
505
508TEST_F(GsdmlRealDeviceTest, ExplicitModuleAssignmentRejectsDisallowedPhoenixSlot)
509{
510 EXPECT_THROW(
511 phoenix.BuildIoSlots(
512 {{2, "PNSlot1", 1, "PNIO0202"}}),
513 std::invalid_argument);
514}
515
518TEST_F(GsdmlRealDeviceTest, ExplicitConfigurationRejectsUnknownModule)
519{
520 EXPECT_THROW(
521 phoenix.BuildIoSlots(
522 {{1, "DOES_NOT_EXIST", 1, "PNIO0202"}}),
523 std::invalid_argument);
524}
525
528TEST_F(GsdmlRealDeviceTest, ExplicitConfigurationRejectsUnknownSubmodule)
529{
530 EXPECT_THROW(
531 phoenix.BuildIoSlots(
532 {{1, "PNSlot1", 1, "DOES_NOT_EXIST"}}),
533 std::invalid_argument);
534}
535
538TEST_F(GsdmlRealDeviceTest, ExplicitConfigurationRejectsDisallowedSubslot)
539{
540 EXPECT_THROW(
541 phoenix.BuildIoSlots(
542 {{1, "PNSlot1", 2, "PNIO0202"}}),
543 std::invalid_argument);
544}
545
548TEST_F(GsdmlRealDeviceTest, BuildIoSlotsIsDeterministic)
549{
550 const auto first = phoenix.BuildIoSlots();
551 const auto second = phoenix.BuildIoSlots();
552
553 ASSERT_EQ(first.size(), second.size());
554
555 for (std::size_t index = 0; index < first.size(); ++index)
556 {
557 EXPECT_EQ(first[index].slot, second[index].slot);
558 EXPECT_EQ(first[index].subslot, second[index].subslot);
559 EXPECT_EQ(first[index].moduleIdent, second[index].moduleIdent);
560 EXPECT_EQ(first[index].submoduleIdent, second[index].submoduleIdent);
561 EXPECT_EQ(first[index].inputLength, second[index].inputLength);
562 EXPECT_EQ(first[index].outputLength, second[index].outputLength);
563 }
564}
565
568TEST_F(GsdmlRealDeviceTest, AumaInputDataItemsMatchReportedLength)
569{
570 for (const auto& [moduleId, module] : auma.modules)
571 {
572 (void)moduleId;
573
574 for (const auto& [submoduleId, submodule] : module.submodules)
575 {
576 (void)submoduleId;
577
578 int length = 0;
579
580 for (const auto& item : submodule.inputItems)
581 {
582 length += item.length;
583 }
584
585 EXPECT_EQ(length, submodule.inputLength)
586 << "Module '" << module.id
587 << "', submodule '" << submodule.id << "'";
588 }
589 }
590}
591
594TEST_F(GsdmlRealDeviceTest, AumaOutputDataItemsMatchReportedLength)
595{
596 for (const auto& [moduleId, module] : auma.modules)
597 {
598 (void)moduleId;
599
600 for (const auto& [submoduleId, submodule] : module.submodules)
601 {
602 (void)submoduleId;
603
604 int length = 0;
605
606 for (const auto& item : submodule.outputItems)
607 {
608 length += item.length;
609 }
610
611 EXPECT_EQ(length, submodule.outputLength)
612 << "Module '" << module.id
613 << "', submodule '" << submodule.id << "'";
614 }
615 }
616}
617
620TEST_F(GsdmlRealDeviceTest, PhoenixInputDataItemsMatchReportedLength)
621{
622 for (const auto& [submoduleId, submodule] : phoenix.submodules)
623 {
624 (void)submoduleId;
625
626 int length = 0;
627
628 for (const auto& item : submodule.inputItems)
629 {
630 length += item.length;
631 }
632
633 EXPECT_EQ(length, submodule.inputLength)
634 << "Submodule '" << submodule.id << "'";
635 }
636}
637
640TEST_F(GsdmlRealDeviceTest, PhoenixOutputDataItemsMatchReportedLength)
641{
642 for (const auto& [submoduleId, submodule] : phoenix.submodules)
643 {
644 (void)submoduleId;
645
646 int length = 0;
647
648 for (const auto& item : submodule.outputItems)
649 {
650 length += item.length;
651 }
652
653 EXPECT_EQ(length, submodule.outputLength)
654 << "Submodule '" << submodule.id << "'";
655 }
656}
659TEST_F(GsdmlRealDeviceTest, AumaBuildIoSlotsContainsExpectedDeviceConfiguration)
660{
661 const auto slots = auma.BuildIoSlots();
662
663 const auto* dap = FindSlot(slots, 0, 1);
664 const auto* input = FindSlot(slots, 1, 1);
665 const auto* output = FindSlot(slots, 2, 1);
666
667 ASSERT_NE(dap, nullptr);
668 ASSERT_NE(input, nullptr);
669 ASSERT_NE(output, nullptr);
670
671 EXPECT_EQ(dap->moduleIdent, auma.dapModuleIdentNumber);
672 EXPECT_EQ(dap->submoduleIdent, auma.dapSubmoduleIdentNumber);
673
674 EXPECT_EQ(input->moduleIdent, 0x00008000U);
675 EXPECT_EQ(input->submoduleIdent, 0x00002200U);
676 EXPECT_EQ(input->inputLength, 34U);
677 EXPECT_EQ(input->outputLength, 0U);
678
679 EXPECT_EQ(output->moduleIdent, 0x00008100U);
680 EXPECT_EQ(output->submoduleIdent, 0x10001000U);
681 EXPECT_EQ(output->inputLength, 0U);
682 EXPECT_EQ(output->outputLength, 16U);
683}
684
686TEST_F(GsdmlRealDeviceTest, AumaBuildIoSlotsDoesNotMutateParsedDevice)
687{
688 const auto vendorId = auma.vendorId;
689 const auto deviceId = auma.deviceId;
690 const auto moduleCount = auma.modules.size();
691 const auto submoduleCount = auma.submodules.size();
692
693 const auto first = auma.BuildIoSlots();
694 const auto second = auma.BuildIoSlots();
695
696 EXPECT_EQ(vendorId, auma.vendorId);
697 EXPECT_EQ(deviceId, auma.deviceId);
698 EXPECT_EQ(moduleCount, auma.modules.size());
699 EXPECT_EQ(submoduleCount, auma.submodules.size());
700
701 ASSERT_EQ(first.size(), second.size());
702
703 for (std::size_t index = 0; index < first.size(); ++index)
704 {
705 EXPECT_EQ(first[index].slot, second[index].slot);
706 EXPECT_EQ(first[index].subslot, second[index].subslot);
707 EXPECT_EQ(first[index].moduleIdent, second[index].moduleIdent);
708 EXPECT_EQ(first[index].submoduleIdent, second[index].submoduleIdent);
709 EXPECT_EQ(first[index].inputLength, second[index].inputLength);
710 EXPECT_EQ(first[index].outputLength, second[index].outputLength);
711 }
712}
713
716TEST_F(GsdmlRealDeviceTest, AumaCyclicIoLengthsMatchConfiguredModules)
717{
718 const auto* inputModule = auma.FindModule("ID_MODULE_ADI_IN_0");
719 const auto* outputModule = auma.FindModule("ID_MODULE_ADI_OUT_0");
720
721 ASSERT_NE(inputModule, nullptr);
722 ASSERT_NE(outputModule, nullptr);
723
724 const auto inputIt =
725 inputModule->submodules.find("ID_SUBMOD_ADI_IN_0");
726 const auto outputIt =
727 outputModule->submodules.find("ID_SUBMOD_ADI_OUT_0");
728
729 ASSERT_NE(inputIt, inputModule->submodules.end());
730 ASSERT_NE(outputIt, outputModule->submodules.end());
731
732 const auto slots = auma.BuildIoSlots();
733
734 const auto* input = FindSlot(slots, 1, 1);
735 const auto* output = FindSlot(slots, 2, 1);
736
737 ASSERT_NE(input, nullptr);
738 ASSERT_NE(output, nullptr);
739
740 EXPECT_EQ(input->inputLength, inputIt->second.inputLength);
741 EXPECT_EQ(input->outputLength, inputIt->second.outputLength);
742
743 EXPECT_EQ(output->inputLength, outputIt->second.inputLength);
744 EXPECT_EQ(output->outputLength, outputIt->second.outputLength);
745}
746
749TEST_F(GsdmlRealDeviceTest, AumaRepeatedParsingProducesSameMetadata)
750{
751 const auto firstPath =
752 FindGsdmlFile("GSDML-V2.42-AUMA-SGX-20220920.xml");
753
754 ASSERT_FALSE(firstPath.empty());
755
756 const auto first = ParseGsdml(firstPath.string());
757 const auto second = ParseGsdml(firstPath.string());
758
759 EXPECT_EQ(first.vendorId, second.vendorId);
760 EXPECT_EQ(first.deviceId, second.deviceId);
761 EXPECT_EQ(first.dapModuleId, second.dapModuleId);
762 EXPECT_EQ(first.dapModuleIdentNumber, second.dapModuleIdentNumber);
763 EXPECT_EQ(first.dapSubmoduleIdentNumber, second.dapSubmoduleIdentNumber);
764 EXPECT_EQ(first.modules.size(), second.modules.size());
765 EXPECT_EQ(first.submodules.size(), second.submodules.size());
766}
768TEST_F(GsdmlRealDeviceTest, BuildIoSlotsMatchesExpectedSlots)
769{
770 const auto slots = auma.BuildIoSlots();
771 const auto numberOfIoSlotEntries = slots.size();
772 ASSERT_EQ(numberOfIoSlotEntries, 9U);
773
774 // -------------------------------------------------------------------------
775 // DAP / PDEV entries.
776 //
777 // The AUMA DAP contributes seven IOSlot entries at slot 0:
778 //
779 // 0/0x0001
780 // 0/0x0002
781 // 0/0x0003
782 // 0/0x0004
783 // 0/0x8000
784 // 0/0x8001
785 // 0/0x8002
786 // -------------------------------------------------------------------------
787
788 ASSERT_NE(FindSlot(slots, 0, 0x0001), nullptr);
789 ASSERT_NE(FindSlot(slots, 0, 0x0002), nullptr);
790 ASSERT_NE(FindSlot(slots, 0, 0x0003), nullptr);
791 ASSERT_NE(FindSlot(slots, 0, 0x0004), nullptr);
792 ASSERT_NE(FindSlot(slots, 0, 0x8000), nullptr);
793 ASSERT_NE(FindSlot(slots, 0, 0x8001), nullptr);
794 ASSERT_NE(FindSlot(slots, 0, 0x8002), nullptr);
795
796 // -------------------------------------------------------------------------
797 // Configured cyclic I/O entries.
798 // -------------------------------------------------------------------------
799
800 ASSERT_NE(FindSlot(slots, 1, 1), nullptr);
801 ASSERT_NE(FindSlot(slots, 2, 1), nullptr);
802}
803
805TEST_F(GsdmlRealDeviceTest, BuildIoSlotsMatchesExpectedInputOutputLengths)
806{
807 const auto slots = auma.BuildIoSlots();
808
809 const auto* status = FindSlot(slots, 1, 1);
810 const auto* control = FindSlot(slots, 2, 1);
811
812 ASSERT_NE(status, nullptr);
813 ASSERT_NE(control, nullptr);
814
815 EXPECT_EQ(status->inputLength, 34U);
816 EXPECT_EQ(status->outputLength, 0U);
817
818 EXPECT_EQ(control->inputLength, 0U);
819 EXPECT_EQ(control->outputLength, 16U);
820}
821namespace
822{
828bool Contains(const std::vector<int>& values, const int value)
829{
830 return std::ranges::find(values, value) != values.end();
831}
832
842const GSDMLSubmodule* FindSignalSubmodule(const GSDMLDevice& device, const profinet::io::IoSignal& signal)
843{
844 if (signal.moduleId == device.dapModuleId)
845 {
846 return nullptr;
847 }
848
849 const auto* module = device.FindModule(signal.moduleId);
850 if (module == nullptr)
851 {
852 return nullptr;
853 }
854
855 const auto inlineIt = module->submodules.find(signal.submoduleId);
856 if (inlineIt != module->submodules.end())
857 {
858 return &inlineIt->second;
859 }
860
861 return device.FindSubmodule(signal.submoduleId);
862}
863
869const rpc::IOSlot* FindSignalIoSlot(const std::vector<rpc::IOSlot>& slots, const profinet::io::IoSignal& signal)
870{
871 for (const auto& slot : slots)
872 {
873 if ((slot.moduleIdent == signal.moduleIdentNumber) && (slot.submoduleIdent == signal.submoduleIdentNumber))
874 {
875 return &slot;
876 }
877 }
878
879 return nullptr;
880}
881} // namespace
882
884TEST_F(GsdmlRealDeviceTest, BuildsAumaIoSignalCatalog)
885{
886 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
887
888 EXPECT_GT(catalog.Size(), 0U);
889 EXPECT_GT(catalog.Sensors().size(), 0U);
890 EXPECT_GT(catalog.Actuators().size(), 0U);
891
892 for (const auto& signal : catalog.Sensors())
893 {
894 EXPECT_TRUE(signal.IsSensor());
895 EXPECT_FALSE(signal.IsActuator());
896 EXPECT_FALSE(signal.name.empty());
897 EXPECT_FALSE(signal.submoduleId.empty());
898 EXPECT_GT(signal.byteLength, 0U);
899 }
900
901 for (const auto& signal : catalog.Actuators())
902 {
903 EXPECT_TRUE(signal.IsActuator());
904 EXPECT_FALSE(signal.IsSensor());
905 EXPECT_FALSE(signal.name.empty());
906 EXPECT_FALSE(signal.submoduleId.empty());
907 EXPECT_GT(signal.byteLength, 0U);
908 }
909}
910
912TEST_F(GsdmlRealDeviceTest, AumaSensorSignalsUseStatusSubmodule)
913{
914 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
915
916 ASSERT_GT(catalog.Sensors().size(), 0U);
917
918 for (const auto& signal : catalog.Sensors())
919 {
920 if (signal.moduleId != "ID_MODULE_ADI_IN_0")
921 {
922 continue;
923 }
924
925 EXPECT_EQ(signal.submoduleId, "ID_SUBMOD_ADI_IN_0");
926 EXPECT_EQ(signal.moduleIdentNumber, 0x00008000U);
927 EXPECT_EQ(signal.submoduleIdentNumber, 0x00002200U);
928 EXPECT_TRUE(Contains(signal.possibleSlots, 1));
929 EXPECT_TRUE(Contains(signal.possibleSubslots, 1));
930 }
931}
932
934TEST_F(GsdmlRealDeviceTest, AumaActuatorSignalsUseControlSubmodule)
935{
936 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
937
938 ASSERT_GT(catalog.Actuators().size(), 0U);
939
940 for (const auto& signal : catalog.Actuators())
941 {
942 if (signal.moduleId != "ID_MODULE_ADI_OUT_0")
943 {
944 continue;
945 }
946
947 EXPECT_EQ(signal.submoduleId, "ID_SUBMOD_ADI_OUT_0");
948 EXPECT_EQ(signal.moduleIdentNumber, 0x00008100U);
949 EXPECT_EQ(signal.submoduleIdentNumber, 0x10001000U);
950 EXPECT_TRUE(Contains(signal.possibleSlots, 2));
951 EXPECT_TRUE(Contains(signal.possibleSubslots, 1));
952 }
953}
954
956TEST_F(GsdmlRealDeviceTest, AumaSignalsStayWithinProcessDataLengths)
957{
958 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
959
960 for (const auto& signal : catalog.Sensors())
961 {
962 const auto* submodule = FindSignalSubmodule(auma, signal);
963
964 ASSERT_NE(submodule, nullptr)
965 << "Unable to resolve sensor submodule '" << signal.submoduleId << "'";
966
967 ASSERT_GE(signal.byteOffset + signal.byteLength, signal.byteOffset);
968 EXPECT_LE(signal.byteOffset + signal.byteLength,
969 static_cast<std::size_t>(submodule->inputLength))
970 << "Signal '" << signal.name << "' exceeds input process-data length";
971 }
972
973 for (const auto& signal : catalog.Actuators())
974 {
975 const auto* submodule = FindSignalSubmodule(auma, signal);
976
977 ASSERT_NE(submodule, nullptr)
978 << "Unable to resolve actuator submodule '" << signal.submoduleId << "'";
979
980 ASSERT_GE(signal.byteOffset + signal.byteLength, signal.byteOffset);
981 EXPECT_LE(signal.byteOffset + signal.byteLength, static_cast<std::size_t>(submodule->outputLength))
982 << "Signal '" << signal.name << "' exceeds output process-data length";
983 }
984}
985
987TEST_F(GsdmlRealDeviceTest, AumaCatalogExcludesReservedSignals)
988{
989 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
990
991 for (const auto& signal : catalog.Sensors())
992 {
993 EXPECT_FALSE(profinet::io::IsReservedSignal(signal.textId, signal.name))
994 << "Reserved sensor signal exposed: '" << signal.name << "'";
995 }
996
997 for (const auto& signal : catalog.Actuators())
998 {
999 EXPECT_FALSE(profinet::io::IsReservedSignal(signal.textId, signal.name))
1000 << "Reserved actuator signal exposed: '" << signal.name << "'";
1001 }
1002}
1003
1005TEST_F(GsdmlRealDeviceTest, AumaCatalogSignalsHaveValueClassification)
1006{
1007 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
1008
1009 ASSERT_GT(catalog.Size(), 0U);
1010
1011 for (const auto& signal : catalog.Sensors())
1012 {
1014 << "Unsupported sensor DataType '" << signal.gsdmlDataType
1015 << "' for signal '" << signal.name << "'";
1016 }
1017
1018 for (const auto& signal : catalog.Actuators())
1019 {
1021 << "Unsupported actuator DataType '" << signal.gsdmlDataType
1022 << "' for signal '" << signal.name << "'";
1023 }
1024}
1025
1027TEST_F(GsdmlRealDeviceTest, BuildsPhoenixIoSignalCatalog)
1028{
1029 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(phoenix);
1030
1031 EXPECT_GT(catalog.Size(), 0U);
1032
1033 for (const auto& signal : catalog.Sensors())
1034 {
1035 EXPECT_FALSE(signal.name.empty());
1036 EXPECT_FALSE(signal.submoduleId.empty());
1037 EXPECT_GT(signal.byteLength, 0U);
1038 }
1039
1040 for (const auto& signal : catalog.Actuators())
1041 {
1042 EXPECT_FALSE(signal.name.empty());
1043 EXPECT_FALSE(signal.submoduleId.empty());
1044 EXPECT_GT(signal.byteLength, 0U);
1045 }
1046}
1047
1049TEST_F(GsdmlRealDeviceTest, PhoenixCatalogExcludesReservedSignals)
1050{
1051 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(phoenix);
1052
1053 for (const auto& signal : catalog.Sensors())
1054 {
1055 EXPECT_FALSE(profinet::io::IsReservedSignal(signal.textId, signal.name));
1056 }
1057
1058 for (const auto& signal : catalog.Actuators())
1059 {
1060 EXPECT_FALSE(profinet::io::IsReservedSignal(signal.textId, signal.name));
1061 }
1062}
1064TEST_F(GsdmlRealDeviceTest, AumaSignalsMatchGeneratedIoSlots)
1065{
1066 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
1067 const auto slots = auma.BuildIoSlots();
1068
1069 const auto* status = FindSlot(slots, 1, 1);
1070 const auto* control = FindSlot(slots, 2, 1);
1071
1072 ASSERT_NE(status, nullptr);
1073 ASSERT_NE(control, nullptr);
1074
1075 for (const auto& signal : catalog.Sensors())
1076 {
1077 if (signal.moduleId != "ID_MODULE_ADI_IN_0")
1078 {
1079 continue;
1080 }
1081
1082 EXPECT_EQ(signal.moduleIdentNumber, status->moduleIdent);
1083 EXPECT_EQ(signal.submoduleIdentNumber, status->submoduleIdent);
1084
1085 EXPECT_LE(signal.byteOffset + signal.byteLength,
1086 static_cast<std::size_t>(status->inputLength));
1087 }
1088
1089 for (const auto& signal : catalog.Actuators())
1090 {
1091 if (signal.moduleId != "ID_MODULE_ADI_OUT_0")
1092 {
1093 continue;
1094 }
1095
1096 EXPECT_EQ(signal.moduleIdentNumber, control->moduleIdent);
1097 EXPECT_EQ(signal.submoduleIdentNumber, control->submoduleIdent);
1098
1099 EXPECT_LE(signal.byteOffset + signal.byteLength,
1100 static_cast<std::size_t>(control->outputLength));
1101 }
1102}
1104TEST_F(GsdmlRealDeviceTest, ParsesAumaBitDataItems)
1105{
1106 const auto* module = auma.FindModule("ID_MODULE_ADI_IN_0");
1107
1108 ASSERT_NE(module, nullptr);
1109
1110 const auto it = module->submodules.find("ID_SUBMOD_ADI_IN_0");
1111
1112 ASSERT_NE(it, module->submodules.end());
1113
1114 const auto& items = it->second.inputItems;
1115
1116 ASSERT_GE(items.size(), 1U);
1117
1118 const auto& log = items[0];
1119
1120 EXPECT_EQ(log.textId, "T_ID_LOG");
1121 EXPECT_EQ(log.dataType, "Unsigned8");
1122 EXPECT_EQ(log.length, 1);
1123 EXPECT_TRUE(log.useAsBits);
1124
1125 ASSERT_EQ(log.bitDataItems.size(), 8U);
1126
1127 for (std::size_t index = 0; index < log.bitDataItems.size(); ++index)
1128 {
1129 EXPECT_EQ(log.bitDataItems[index].bitOffset, index);
1130 EXPECT_EQ(
1131 log.bitDataItems[index].textId,
1132 "T_ID_LOG_" + std::to_string(index));
1133 }
1134}
1135
1137TEST_F(GsdmlRealDeviceTest, AumaLogByteCreatesEightBitSignals)
1138{
1139 const auto catalog = profinet::io::IoSignalCatalog::FromGsdml(auma);
1140
1141 std::vector<const profinet::io::IoSignal*> logBits;
1142
1143 for (const auto& signal : catalog.Sensors())
1144 {
1145 if (signal.parentDataItemTextId == "T_ID_LOG")
1146 {
1147 logBits.push_back(&signal);
1148 }
1149 }
1150
1151 ASSERT_EQ(logBits.size(), 8U);
1152
1153 std::ranges::sort(logBits, [](const auto* lhs, const auto* rhs)
1154 {
1155 return lhs->bitOffset < rhs->bitOffset;
1156 });
1157
1158 for (std::size_t index = 0; index < logBits.size(); ++index)
1159 {
1160 const auto& signal = *logBits[index];
1161
1162 EXPECT_TRUE(signal.IsBitSignal());
1163 EXPECT_EQ(signal.textId, "T_ID_LOG_" + std::to_string(index));
1164 EXPECT_EQ(signal.byteOffset, 0U);
1165 EXPECT_EQ(signal.bitOffset, index);
1166 EXPECT_EQ(signal.valueType,
1168 }
1169}
1171TEST_F(GsdmlRealDeviceTest, AumaCommandBitsHaveCorrectByteAndBitOffsets)
1172{
1173 const auto catalog =
1175
1176 std::vector<const profinet::io::IoSignal*> commandBits;
1177
1178 for (const auto& signal : catalog.Actuators())
1179 {
1180 if (signal.parentDataItemTextId == "T_ID_CMD")
1181 {
1182 commandBits.push_back(&signal);
1183 }
1184 }
1185
1186 ASSERT_EQ(commandBits.size(), 5U); // 5 because 3 are reserved
1187
1188 std::ranges::sort(commandBits, [](const auto* lhs, const auto* rhs)
1189 {
1190 return lhs->bitOffset < rhs->bitOffset;
1191 });
1192
1193 for (std::size_t index = 0;
1194 index < commandBits.size();
1195 ++index)
1196 {
1197 const auto& signal = *commandBits[index];
1198
1199 EXPECT_EQ(signal.byteOffset, 0U);
1200 EXPECT_EQ(signal.bitOffset, index);
1201 EXPECT_EQ(signal.parentDataItemByteOffset, 0U);
1202 EXPECT_EQ(signal.byteLength, 1U);
1203 }
1204}
1205} // namespace profinet::gsdml
Defines the GSDML-derived logical process-data signal catalogue.
GoogleTest fixture for real vendor GSDML regression tests.
GSDMLDevice phoenix
Parsed Phoenix APLC.
void SetUp() override
Parse the AUMA GSDML once before each test.
GSDMLDevice auma
Parsed AUMA SGx device.
static IoSignalCatalog FromGsdml(const gsdml::GSDMLDevice &device)
Build a signal catalogue from a parsed GSDML device.
Definition IOSignal.cpp:470
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
TEST_F(GsdmlRealDeviceTest, ParsesAumaGsdml)
bool IsReservedSignal(std::string_view textId, std::string_view name) noexcept
Determine whether a DataItem should be hidden from the normal signal catalogue.
Definition IOSignal.cpp:465
@ Boolean
Boolean value, represented by the connector as 0.0 or 1.0.
@ Unsupported
Data for which this catalogue does not define a numeric double representation.
std::filesystem::path GetExecutableDirectory()
Retrieves the absolute directory path of the currently running executable.
Complete parsed GSDML device profile.
Definition gsdml.h:166
Describes one logical process-data signal available from a GSDML device.
Definition IOSignal.h:83
bool IsSensor() const noexcept
Returns whether this signal is an input/sensor.
Definition IOSignal.cpp:418
bool IsActuator() const noexcept
Returns whether this signal is an output/actuator.
Definition IOSignal.cpp:423
std::uint32_t moduleIdentNumber
Module ident number of the containing module.
Definition IOSignal.h:141
std::size_t byteLength
Size of the GSDML DataItem in bytes.
Definition IOSignal.h:150
bool IsBitSignal() const noexcept
Returns whether this signal represents one BitDataItem.
Definition IOSignal.cpp:413
std::string gsdmlDataType
GSDML DataType string.
Definition IOSignal.h:119
std::size_t bitOffset
Bit offset within the containing DataItem.
Definition IOSignal.h:101
std::size_t parentDataItemByteOffset
Byte offset of the parent DataItem.
Definition IOSignal.h:109
std::string textId
Original GSDML TextId.
Definition IOSignal.h:91
std::string submoduleId
GSDML submodule identifier containing the signal.
Definition IOSignal.h:135
std::vector< int > possibleSubslots
Subslots in which the containing submodule may occur, when known.
Definition IOSignal.h:160
std::string parentDataItemTextId
TextId of the parent DataItem.
Definition IOSignal.h:116
std::uint32_t submoduleIdentNumber
Submodule ident number of the containing submodule.
Definition IOSignal.h:144
std::size_t byteOffset
Byte offset within the submodule's input or output process-data area.
Definition IOSignal.h:147
IoSignalValueType valueType
Logical value category inferred from gsdmlDataType.
Definition IOSignal.h:122
std::string name
Current display name of the signal.
Definition IOSignal.h:88
std::vector< int > possibleSlots
Slots in which the containing module may occur, when known.
Definition IOSignal.h:155
std::string moduleId
GSDML module identifier containing the signal.
Definition IOSignal.h:129
TEST_F(AlarmItemTest, ParseChannelDiagnosis)
std::uint16_t length
Definition test_dcp.cpp:391