PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_IOSignal.cpp
Go to the documentation of this file.
1
13
14#include <gtest/gtest.h>
15
16#include <algorithm>
17#include <cstdint>
18#include <stdexcept>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "profinet/IOSignal.h"
24
25namespace profinet::io
26{
27
28namespace
29{
30
32gsdml::GSDMLDataItem MakeDataItem(const std::string& dataType,
33 const int length,
34 const std::string& textId)
35{
36 gsdml::GSDMLDataItem item;
37 item.dataType = dataType;
38 item.length = length;
39 item.textId = textId;
40 return item;
41}
42
44gsdml::GSDMLModule MakeModule(const std::string& moduleId,
45 const std::string& moduleName,
46 const std::string& submoduleId,
47 const std::string& submoduleName)
48{
49 gsdml::GSDMLModule module;
50 module.id = moduleId;
51 module.name = moduleName;
52 module.moduleIdentNumber = 0x1000U;
53
54 gsdml::GSDMLSubmodule submodule;
55 submodule.id = submoduleId;
56 submodule.name = submoduleName;
57 submodule.submoduleIdentNumber = 0x2000U;
58
59 module.submodules.emplace(submoduleId, std::move(submodule));
60 return module;
61}
62
64gsdml::GSDMLModuleReference MakeModuleReference(const std::string& moduleId,
65 std::vector<int> allowedSlots = {},
66 std::vector<int> fixedSlots = {},
67 std::vector<int> usedSlots = {})
68{
69 gsdml::GSDMLModuleReference reference;
70 reference.moduleId = moduleId;
71 reference.allowedSlots = std::move(allowedSlots);
72 reference.fixedSlots = std::move(fixedSlots);
73 reference.usedSlots = std::move(usedSlots);
74 return reference;
75}
76
78gsdml::GSDMLSubmoduleReference MakeSubmoduleReference(
79 const std::string& submoduleId,
80 std::vector<int> allowedSubslots = {},
81 std::vector<int> fixedSubslots = {},
82 std::vector<int> usedSubslots = {})
83{
84 gsdml::GSDMLSubmoduleReference reference;
85 reference.submoduleId = submoduleId;
86 reference.allowedSubslots = std::move(allowedSubslots);
87 reference.fixedSubslots = std::move(fixedSubslots);
88 reference.usedSubslots = std::move(usedSubslots);
89 return reference;
90}
91
92} // namespace
93
95TEST(IoSignalTypeTests, ClassifiesAllDoubleCompatibleScalarTypes)
96{
97 EXPECT_EQ(ClassifyDataType("Boolean"), IoSignalValueType::Boolean);
99
100 for (const std::string dataType : {
101 "Integer8", "Integer16", "Integer32", "Integer64"})
102 {
104 }
105
106 for (const std::string dataType : {
107 "Unsigned8", "Unsigned16", "Unsigned32", "Unsigned64"})
108 {
110 }
111
112 for (const std::string dataType : {"Float32", "Float64"})
113 {
115 }
116}
117
119TEST(IoSignalTypeTests, ClassifiesUnsupportedAndUnknownTypes)
120{
121 for (const std::string dataType : {
122 "BitArea",
123 "OctetString",
124 "VisibleString",
125 "Date",
126 "Time",
127 "TimeOfDay",
128 "TimeDifference",
129 "F_MessageTrailer4Byte",
130 "F_MessageTrailer5Byte",
131 "UnknownType",
132 ""})
133 {
135 }
136}
137
139TEST(IoSignalReservedTests, DetectsReservedTextIdsCaseInsensitively)
140{
141 EXPECT_TRUE(IsReservedSignal("T_ID_RESERVE_INPUT", "Input"));
142 EXPECT_TRUE(IsReservedSignal("reserve_output", "Output"));
143 EXPECT_TRUE(IsReservedSignal("ReservedValue", "Value"));
144 EXPECT_TRUE(IsReservedSignal("unused", "Reserved output"));
145 EXPECT_TRUE(IsReservedSignal("reserveInput", "Input"));
146 EXPECT_TRUE(IsReservedSignal("RESERVED", "Value"));
147}
148
150TEST(IoSignalReservedTests, DoesNotMisclassifyNormalSignals)
151{
152 EXPECT_FALSE(IsReservedSignal("T_ID_POSITION", "Position"));
153 EXPECT_FALSE(IsReservedSignal("T_ID_PRESCALE", "Pre-scale"));
154 EXPECT_FALSE(IsReservedSignal("", "Speed"));
155 EXPECT_FALSE(IsReservedSignal("", ""));
156 EXPECT_FALSE(IsReservedSignal("T_ID_PRESERVE", "Preserve"));
157 EXPECT_FALSE(IsReservedSignal("T_ID_PRESERVED_VALUE", "Preserved value"));
158 EXPECT_FALSE(IsReservedSignal("T_ID_RESERVATION", "Reservation"));
159}
160
162TEST(IoSignalTests, DirectionAccessorsReportCorrectDirection)
163{
164 IoSignal sensor;
166 EXPECT_TRUE(sensor.IsSensor());
167 EXPECT_FALSE(sensor.IsActuator());
168
169 IoSignal actuator;
171 EXPECT_FALSE(actuator.IsSensor());
172 EXPECT_TRUE(actuator.IsActuator());
173}
174
176TEST(IoSignalTests, DoubleCompatibilityAccessorMirrorsFlag)
177{
178 IoSignal signal;
179
180 signal.doubleCompatible = false;
181 EXPECT_FALSE(signal.IsDoubleCompatible());
182
183 signal.doubleCompatible = true;
184 EXPECT_TRUE(signal.IsDoubleCompatible());
185}
186
188TEST(IoSignalCatalogTests, EmptyDeviceProducesEmptyCatalogue)
189{
190 const gsdml::GSDMLDevice device{};
191 const auto catalog = IoSignalCatalog::FromGsdml(device);
192
193 EXPECT_TRUE(catalog.Sensors().empty());
194 EXPECT_TRUE(catalog.Actuators().empty());
195 EXPECT_EQ(catalog.Size(), 0U);
196}
197
199TEST(IoSignalCatalogTests, SeparatesInputAndOutputSignals)
200{
201 gsdml::GSDMLDevice device;
202 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
203 auto& submodule = module.submodules.at("SUBMODULE");
204
205 submodule.inputItems.push_back(MakeDataItem("Unsigned8", 1, "SensorA"));
206 submodule.outputItems.push_back(MakeDataItem("Boolean", 1, "ActuatorA"));
207 device.modules.emplace(module.id, module);
208
209 const auto catalog = IoSignalCatalog::FromGsdml(device);
210
211 ASSERT_EQ(catalog.Sensors().size(), 1U);
212 ASSERT_EQ(catalog.Actuators().size(), 1U);
213 EXPECT_EQ(catalog.Sensors()[0].name, "SensorA");
214 EXPECT_EQ(catalog.Actuators()[0].name, "ActuatorA");
215 EXPECT_TRUE(catalog.Sensors()[0].IsSensor());
216 EXPECT_TRUE(catalog.Actuators()[0].IsActuator());
217}
218
220TEST(IoSignalCatalogTests, CalculatesIndependentInputAndOutputOffsets)
221{
222 gsdml::GSDMLDevice device;
223 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
224 auto& submodule = module.submodules.at("SUBMODULE");
225
226 submodule.inputItems = {
227 MakeDataItem("Unsigned8", 1, "Input0"),
228 MakeDataItem("Unsigned16", 2, "Input1"),
229 MakeDataItem("Float32", 4, "Input2"),
230 };
231 submodule.outputItems = {
232 MakeDataItem("Boolean", 1, "Output0"),
233 MakeDataItem("Unsigned32", 4, "Output1"),
234 };
235 device.modules.emplace(module.id, module);
236
237 const auto catalog = IoSignalCatalog::FromGsdml(device);
238
239 ASSERT_EQ(catalog.Sensors().size(), 3U);
240 ASSERT_EQ(catalog.Actuators().size(), 2U);
241
242 EXPECT_EQ(catalog.Sensors()[0].byteOffset, 0U);
243 EXPECT_EQ(catalog.Sensors()[1].byteOffset, 1U);
244 EXPECT_EQ(catalog.Sensors()[2].byteOffset, 3U);
245
246 EXPECT_EQ(catalog.Actuators()[0].byteOffset, 0U);
247 EXPECT_EQ(catalog.Actuators()[1].byteOffset, 1U);
248}
249
251TEST(IoSignalCatalogTests, ZeroLengthDataItemDoesNotAdvanceOffset)
252{
253 gsdml::GSDMLDevice device;
254 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
255 auto& submodule = module.submodules.at("SUBMODULE");
256
257 submodule.inputItems = {
258 MakeDataItem("OctetString", 0, "Empty"),
259 MakeDataItem("Unsigned8", 1, "Next"),
260 };
261 device.modules.emplace(module.id, module);
262
263 const auto catalog = IoSignalCatalog::FromGsdml(device);
264
265 ASSERT_EQ(catalog.Sensors().size(), 2U);
266 EXPECT_EQ(catalog.Sensors()[0].byteOffset, 0U);
267 EXPECT_EQ(catalog.Sensors()[0].byteLength, 0U);
268 EXPECT_EQ(catalog.Sensors()[1].byteOffset, 0U);
269}
270
272TEST(IoSignalCatalogTests, NegativeDataItemLengthThrows)
273{
274 gsdml::GSDMLDevice device;
275 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
276 module.submodules.at("SUBMODULE").inputItems.push_back(MakeDataItem("Unsigned8", -1, "Invalid"));
277 device.modules.emplace(module.id, module);
278
279 EXPECT_THROW(IoSignalCatalog::FromGsdml(device), std::invalid_argument);
280}
281
283TEST(IoSignalCatalogTests, EmptyTextIdUsesFallbackName)
284{
285 gsdml::GSDMLDevice device;
286 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
287 module.submodules.at("SUBMODULE").inputItems.push_back(MakeDataItem("Unsigned8", 1, ""));
288 device.modules.emplace(module.id, module);
289
290 const auto catalog = IoSignalCatalog::FromGsdml(device);
291
292 ASSERT_EQ(catalog.Sensors().size(), 1U);
293 EXPECT_EQ(catalog.Sensors()[0].name, "<unnamed>");
294 EXPECT_TRUE(catalog.Sensors()[0].textId.empty());
295}
296
298TEST(IoSignalCatalogTests, FiltersReservedItems)
299{
300 gsdml::GSDMLDevice device;
301 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
302 auto& submodule = module.submodules.at("SUBMODULE");
303
304 submodule.inputItems = {
305 MakeDataItem("Unsigned8", 1, "Position"),
306 MakeDataItem("Unsigned8", 1, "T_ID_RESERVE_INPUT"),
307 MakeDataItem("Unsigned8", 1, "Status"),
308 };
309 submodule.outputItems = {
310 MakeDataItem("Boolean", 1, "T_ID_RESERVE_OUTPUT"),
311 MakeDataItem("Boolean", 1, "Open"),
312 };
313 device.modules.emplace(module.id, module);
314
315 const auto catalog = IoSignalCatalog::FromGsdml(device);
316
317 ASSERT_EQ(catalog.Sensors().size(), 2U);
318 ASSERT_EQ(catalog.Actuators().size(), 1U);
319 EXPECT_EQ(catalog.Sensors()[0].name, "Position");
320 EXPECT_EQ(catalog.Sensors()[1].name, "Status");
321 EXPECT_EQ(catalog.Actuators()[0].name, "Open");
322}
323
325TEST(IoSignalCatalogTests, ReservedItemStillAdvancesOffset)
326{
327 gsdml::GSDMLDevice device;
328 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
329 auto& submodule = module.submodules.at("SUBMODULE");
330
331 submodule.inputItems = {
332 MakeDataItem("Unsigned16", 2, "Before"),
333 MakeDataItem("Unsigned32", 4, "T_ID_RESERVE_INPUT"),
334 MakeDataItem("Unsigned8", 1, "After"),
335 };
336 device.modules.emplace(module.id, module);
337
338 const auto catalog = IoSignalCatalog::FromGsdml(device);
339
340 ASSERT_EQ(catalog.Sensors().size(), 2U);
341 EXPECT_EQ(catalog.Sensors()[0].name, "Before");
342 EXPECT_EQ(catalog.Sensors()[0].byteOffset, 0U);
343 EXPECT_EQ(catalog.Sensors()[1].name, "After");
344 EXPECT_EQ(catalog.Sensors()[1].byteOffset, 6U);
345}
346
348TEST(IoSignalCatalogTests, KeepsUnsupportedSignalsWithCompatibilityFalse)
349{
350 gsdml::GSDMLDevice device;
351 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
352 auto& submodule = module.submodules.at("SUBMODULE");
353 submodule.inputItems.push_back(MakeDataItem("VisibleString", 8, "Description"));
354 submodule.outputItems.push_back(MakeDataItem("OctetString", 4, "RawBytes"));
355 device.modules.emplace(module.id, module);
356
357 const auto catalog = IoSignalCatalog::FromGsdml(device);
358
359 ASSERT_EQ(catalog.Sensors().size(), 1U);
360 ASSERT_EQ(catalog.Actuators().size(), 1U);
361 EXPECT_EQ(catalog.Sensors()[0].valueType, IoSignalValueType::Unsupported);
362 EXPECT_FALSE(catalog.Sensors()[0].IsDoubleCompatible());
363 EXPECT_FALSE(catalog.Actuators()[0].IsDoubleCompatible());
364}
365
367TEST(IoSignalCatalogTests, SupportedScalarSignalsAreDoubleCompatible)
368{
369 gsdml::GSDMLDevice device;
370 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
371 auto& submodule = module.submodules.at("SUBMODULE");
372 submodule.inputItems = {
373 MakeDataItem("Boolean", 1, "Boolean"),
374 MakeDataItem("Integer64", 8, "Signed"),
375 MakeDataItem("Unsigned64", 8, "Unsigned"),
376 MakeDataItem("Float64", 8, "Floating"),
377 };
378 device.modules.emplace(module.id, module);
379
380 const auto catalog = IoSignalCatalog::FromGsdml(device);
381
382 ASSERT_EQ(catalog.Sensors().size(), 4U);
383 for (const auto& signal : catalog.Sensors())
384 {
385 EXPECT_TRUE(signal.IsDoubleCompatible());
386 }
387}
388
390TEST(IoSignalCatalogTests, PreservesModuleAndSubmoduleMetadata)
391{
392 gsdml::GSDMLDevice device;
393 auto module = MakeModule("MODULE", "Main module", "SUBMODULE", "I/O data");
394 module.submodules.at("SUBMODULE").inputItems.push_back(MakeDataItem("Unsigned16", 2, "Position"));
395 device.modules.emplace(module.id, module);
396
397 const auto catalog = IoSignalCatalog::FromGsdml(device);
398
399 ASSERT_EQ(catalog.Sensors().size(), 1U);
400 const auto& signal = catalog.Sensors()[0];
401 EXPECT_EQ(signal.moduleId, "MODULE");
402 EXPECT_EQ(signal.moduleName, "Main module");
403 EXPECT_EQ(signal.submoduleId, "SUBMODULE");
404 EXPECT_EQ(signal.submoduleName, "I/O data");
405 EXPECT_EQ(signal.moduleIdentNumber, 0x1000U);
406 EXPECT_EQ(signal.submoduleIdentNumber, 0x2000U);
407 EXPECT_EQ(signal.gsdmlDataType, "Unsigned16");
408 EXPECT_EQ(signal.byteLength, 2U);
409}
410
412TEST(IoSignalCatalogTests, CombinesAndNormalizesPossibleModuleSlots)
413{
414 gsdml::GSDMLDevice device;
415 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
416 module.submodules.at("SUBMODULE").inputItems.push_back(MakeDataItem("Unsigned8", 1, "Sensor"));
417 device.modules.emplace(module.id, module);
418
420 reference.moduleId = "MODULE";
421 reference.allowedSlots = {3, 1, 2, 3};
422 reference.fixedSlots = {1, 7};
423 reference.usedSlots = {2, 5, 7};
424 device.moduleReferences.push_back(reference);
425
426 const auto catalog = IoSignalCatalog::FromGsdml(device);
427
428 ASSERT_EQ(catalog.Sensors().size(), 1U);
429 EXPECT_EQ(catalog.Sensors()[0].possibleSlots, (std::vector<int>{1, 2, 3, 5, 7}));
430}
431
433TEST(IoSignalCatalogTests, CombinesAndNormalizesPossibleInlineSubmoduleSlots)
434{
435 gsdml::GSDMLDevice device;
436 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
437 auto& submodule = module.submodules.at("SUBMODULE");
438 submodule.inputItems.push_back(MakeDataItem("Unsigned8", 1, "Sensor"));
439 submodule.allowedSubslots = {4, 2, 4};
440 submodule.fixedSubslots = {1};
441 submodule.usedSubslots = {3, 2};
442 device.modules.emplace(module.id, module);
443
444 const auto catalog = IoSignalCatalog::FromGsdml(device);
445
446 ASSERT_EQ(catalog.Sensors().size(), 1U);
447 EXPECT_EQ(catalog.Sensors()[0].possibleSubslots, (std::vector<int>{1, 2, 3, 4}));
448}
449
451TEST(IoSignalCatalogTests, IncludesAllModulesWhenNoModuleReferencesExist)
452{
453 gsdml::GSDMLDevice device;
454 auto first = MakeModule("FIRST", "First", "SUB1", "Input");
455 first.submodules.at("SUB1").inputItems.push_back(MakeDataItem("Boolean", 1, "A"));
456 auto second = MakeModule("SECOND", "Second", "SUB2", "Input");
457 second.submodules.at("SUB2").inputItems.push_back(MakeDataItem("Boolean", 1, "B"));
458
459 device.modules.emplace(first.id, first);
460 device.modules.emplace(second.id, second);
461
462 const auto catalog = IoSignalCatalog::FromGsdml(device);
463
464 ASSERT_EQ(catalog.Sensors().size(), 2U);
465 EXPECT_EQ(catalog.Sensors()[0].name, "A");
466 EXPECT_EQ(catalog.Sensors()[1].name, "B");
467}
468
470TEST(IoSignalCatalogTests, UsesOnlyReferencedModulesWhenReferencesExist)
471{
472 gsdml::GSDMLDevice device;
473 auto included = MakeModule("INCLUDED", "Included", "SUB", "Input");
474 included.submodules.at("SUB").inputItems.push_back(MakeDataItem("Boolean", 1, "Keep"));
475 auto excluded = MakeModule("EXCLUDED", "Excluded", "SUB", "Input");
476 excluded.submodules.at("SUB").inputItems.push_back(MakeDataItem("Boolean", 1, "Drop"));
477
478 device.modules.emplace(included.id, included);
479 device.modules.emplace(excluded.id, excluded);
480 device.moduleReferences.push_back(MakeModuleReference("INCLUDED"));
481
482 const auto catalog = IoSignalCatalog::FromGsdml(device);
483
484 ASSERT_EQ(catalog.Sensors().size(), 1U);
485 EXPECT_EQ(catalog.Sensors()[0].name, "Keep");
486}
487
489TEST(IoSignalCatalogTests, MergesDuplicateModuleReferences)
490{
491 gsdml::GSDMLDevice device;
492 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
493 module.submodules.at("SUBMODULE").inputItems.push_back(MakeDataItem("Boolean", 1, "Ready"));
494 device.modules.emplace(module.id, module);
495
496 device.moduleReferences.push_back(
497 MakeModuleReference("MODULE", {1, 2}));
498 device.moduleReferences.push_back(
499 MakeModuleReference("MODULE", {}, {}, {2, 4}));
500
501 const auto catalog = IoSignalCatalog::FromGsdml(device);
502
503 ASSERT_EQ(catalog.Sensors().size(), 1U);
504 EXPECT_EQ(catalog.Sensors()[0].possibleSlots, (std::vector<int>{1, 2, 4}));
505}
506
508TEST(IoSignalCatalogTests, ResolvesGlobalSubmoduleReference)
509{
510 gsdml::GSDMLDevice device;
511 auto module = MakeModule("MODULE", "Module", "INLINE", "Inline");
512 module.submodules.clear();
513
515 reference.submoduleId = "GLOBAL";
516 reference.allowedSubslots = {4, 2};
517 module.submoduleReferences.push_back(reference);
518
520 global.id = "GLOBAL";
521 global.name = "Global I/O";
522 global.submoduleIdentNumber = 0x2222U;
523 global.inputItems.push_back(MakeDataItem("Unsigned16", 2, "Position"));
524
525 device.modules.emplace(module.id, module);
526 device.submodules.emplace(global.id, global);
527 device.moduleReferences.push_back(
528 MakeModuleReference("MODULE", {1, 2}));
529
530 const auto catalog = IoSignalCatalog::FromGsdml(device);
531
532 ASSERT_EQ(catalog.Sensors().size(), 1U);
533 EXPECT_EQ(catalog.Sensors()[0].submoduleId, "GLOBAL");
534 EXPECT_EQ(catalog.Sensors()[0].submoduleName, "Global I/O");
535 EXPECT_EQ(catalog.Sensors()[0].possibleSlots, (std::vector<int>{1, 2}));
536 EXPECT_EQ(catalog.Sensors()[0].possibleSubslots, (std::vector<int>{2, 4}));
537}
538
540TEST(IoSignalCatalogTests, PrefersInlineSubmoduleOverDuplicateGlobalReference)
541{
542 gsdml::GSDMLDevice device;
543 auto module = MakeModule("MODULE", "Module", "DUPLICATE", "Inline");
544 module.submodules.at("DUPLICATE").inputItems.push_back(MakeDataItem("Boolean", 1, "InlineSignal"));
545 module.submoduleReferences.push_back(MakeSubmoduleReference("DUPLICATE", {1, 2}));
546
548 global.id = "DUPLICATE";
549 global.name = "Global";
550 global.inputItems.push_back(MakeDataItem("Boolean", 1, "GlobalSignal"));
551
552 device.modules.emplace(module.id, module);
553 device.submodules.emplace(global.id, global);
554
555 const auto catalog = IoSignalCatalog::FromGsdml(device);
556
557 ASSERT_EQ(catalog.Sensors().size(), 1U);
558 EXPECT_EQ(catalog.Sensors()[0].name, "InlineSignal");
559}
560
562TEST(IoSignalCatalogTests, UnknownModuleReferenceThrows)
563{
564 gsdml::GSDMLDevice device;
565 device.moduleReferences.push_back(
566 MakeModuleReference("MISSING"));
567
568 EXPECT_THROW(IoSignalCatalog::FromGsdml(device), std::invalid_argument);
569}
570
572TEST(IoSignalCatalogTests, MergesDuplicateSubmoduleReferences)
573{
574 gsdml::GSDMLDevice device;
575 auto module = MakeModule("MODULE", "Module", "INLINE", "Inline");
576 module.submodules.clear();
577 module.submoduleReferences.push_back(
578 MakeSubmoduleReference("GLOBAL", {1, 2}));
579 module.submoduleReferences.push_back(
580 MakeSubmoduleReference("GLOBAL", {}, {}, {2, 4}));
581
583 global.id = "GLOBAL";
584 global.inputItems.push_back(MakeDataItem("Boolean", 1, "Ready"));
585
586 device.modules.emplace(module.id, module);
587 device.submodules.emplace(global.id, global);
588 device.moduleReferences.push_back(
589 MakeModuleReference("MODULE"));
590
591 const auto catalog = IoSignalCatalog::FromGsdml(device);
592
593 ASSERT_EQ(catalog.Sensors().size(), 1U);
594 EXPECT_EQ(catalog.Sensors()[0].possibleSubslots, (std::vector<int>{1, 2, 4}));
595}
596
598TEST(IoSignalCatalogTests, UnknownSubmoduleReferenceThrows)
599{
600 gsdml::GSDMLDevice device;
601 auto module = MakeModule("MODULE", "Module", "INLINE", "Inline");
602 module.submodules.clear();
603 module.submoduleReferences.push_back(
604 MakeSubmoduleReference("MISSING"));
605 device.modules.emplace(module.id, module);
606 device.moduleReferences.push_back(
607 MakeModuleReference("MODULE"));
608
609 EXPECT_THROW(IoSignalCatalog::FromGsdml(device), std::invalid_argument);
610}
611
613TEST(IoSignalCatalogTests, IncludesDapVirtualSubmoduleSignals)
614{
615 gsdml::GSDMLDevice device;
616 device.dapModuleId = "DAP";
617 device.dapModuleName = "DAP module";
618 device.dapModuleIdentNumber = 0x300U;
619
621 first.id = "DAP1";
622 first.name = "DAP input";
623 first.submoduleIdentNumber = 0x301U;
624 first.inputItems.push_back(MakeDataItem("Boolean", 1, "Ready"));
625
627 second.id = "DAP2";
628 second.name = "DAP output";
629 second.submoduleIdentNumber = 0x302U;
630 second.outputItems.push_back(MakeDataItem("Boolean", 1, "Enable"));
631
632 device.dapSubmodules = {first, second};
633
634 const auto catalog = IoSignalCatalog::FromGsdml(device);
635
636 ASSERT_EQ(catalog.Sensors().size(), 1U);
637 ASSERT_EQ(catalog.Actuators().size(), 1U);
638 EXPECT_EQ(catalog.Sensors()[0].possibleSlots, (std::vector<int>{0}));
639 EXPECT_EQ(catalog.Sensors()[0].possibleSubslots, (std::vector<int>{1}));
640 EXPECT_EQ(catalog.Sensors()[0].moduleId, "DAP");
641 EXPECT_EQ(catalog.Actuators()[0].possibleSlots, (std::vector<int>{0}));
642 EXPECT_EQ(catalog.Actuators()[0].possibleSubslots, (std::vector<int>{2}));
643}
644
646TEST(IoSignalCatalogTests, RetainsDuplicateSignalNamesAcrossSubmodules)
647{
648 gsdml::GSDMLDevice device;
649 auto first = MakeModule("FIRST", "First", "SUB1", "Input 1");
650 first.submodules.at("SUB1").inputItems.push_back(MakeDataItem("Boolean", 1, "Ready"));
651 auto second = MakeModule("SECOND", "Second", "SUB2", "Input 2");
652 second.submodules.at("SUB2").inputItems.push_back(MakeDataItem("Boolean", 1, "Ready"));
653 device.modules.emplace(first.id, first);
654 device.modules.emplace(second.id, second);
655
656 const auto catalog = IoSignalCatalog::FromGsdml(device);
657
658 ASSERT_EQ(catalog.Sensors().size(), 2U);
659 EXPECT_EQ(catalog.Sensors()[0].name, "Ready");
660 EXPECT_EQ(catalog.Sensors()[1].name, "Ready");
661 EXPECT_NE(catalog.Sensors()[0].moduleId, catalog.Sensors()[1].moduleId);
662}
663
665TEST(IoSignalCatalogTests, MaintainsDeterministicCatalogueOrder)
666{
667 gsdml::GSDMLDevice device;
668 auto first = MakeModule("A", "A", "SUB_A", "A");
669 first.submodules.at("SUB_A").inputItems = {
670 MakeDataItem("Boolean", 1, "A0"),
671 MakeDataItem("Boolean", 1, "A1"),
672 };
673 auto second = MakeModule("B", "B", "SUB_B", "B");
674 second.submodules.at("SUB_B").inputItems = {
675 MakeDataItem("Boolean", 1, "B0"),
676 };
677
678 device.modules.emplace(second.id, second);
679 device.modules.emplace(first.id, first);
680
681 const auto catalog = IoSignalCatalog::FromGsdml(device);
682
683 ASSERT_EQ(catalog.Sensors().size(), 3U);
684 EXPECT_EQ(catalog.Sensors()[0].name, "A0");
685 EXPECT_EQ(catalog.Sensors()[1].name, "A1");
686 EXPECT_EQ(catalog.Sensors()[2].name, "B0");
687}
688namespace
689{
690
692gsdml::GSDMLDataItem MakeBitDataItem(const std::string& dataType, const int length,
693 const std::string& textId, const std::vector<std::pair<std::size_t, std::string>>& bits)
694{
696 item.dataType = dataType;
697 item.length = length;
698 item.textId = textId;
699 item.useAsBits = true;
700
701 for (const auto& [offset, bitTextId] : bits)
702 {
704 bit.bitOffset = offset;
705 bit.textId = bitTextId;
706 item.bitDataItems.push_back(std::move(bit));
707 }
708
709 return item;
710}
711} // namespace
712
714TEST(IoSignalCatalogTests, ExpandsBitDataItemsIntoIndividualSignals)
715{
716 gsdml::GSDMLDevice device;
717
718 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
719
720 auto& submodule = module.submodules.at("SUBMODULE");
721
722 submodule.inputItems.push_back(MakeBitDataItem("Unsigned8",
723 1,
724 "STATUS",
725 {{0U, "READY"}, {3U, "FAULT"}, {7U, "REMOTE"}}));
726
727 device.modules.emplace(module.id, module);
728
729 const auto catalog = IoSignalCatalog::FromGsdml(device);
730
731 ASSERT_EQ(catalog.Sensors().size(), 3U);
732
733 EXPECT_EQ(catalog.Sensors()[0].name, "READY");
734 EXPECT_EQ(catalog.Sensors()[1].name, "FAULT");
735 EXPECT_EQ(catalog.Sensors()[2].name, "REMOTE");
736
737 EXPECT_TRUE(catalog.Sensors()[0].IsBitSignal());
738 EXPECT_TRUE(catalog.Sensors()[1].IsBitSignal());
739 EXPECT_TRUE(catalog.Sensors()[2].IsBitSignal());
740
741 EXPECT_EQ(catalog.Sensors()[0].bitOffset, 0U);
742 EXPECT_EQ(catalog.Sensors()[1].bitOffset, 3U);
743 EXPECT_EQ(catalog.Sensors()[2].bitOffset, 7U);
744
745 EXPECT_EQ(catalog.Sensors()[0].byteOffset, 0U);
746 EXPECT_EQ(catalog.Sensors()[1].byteOffset, 0U);
747 EXPECT_EQ(catalog.Sensors()[2].byteOffset, 0U);
748
749 EXPECT_EQ(catalog.Sensors()[0].byteLength, 1U);
750 EXPECT_EQ(catalog.Sensors()[1].byteLength, 1U);
751 EXPECT_EQ(catalog.Sensors()[2].byteLength, 1U);
752
753 EXPECT_EQ(catalog.Sensors()[0].parentDataItemTextId, "STATUS");
754 EXPECT_EQ(catalog.Sensors()[1].parentDataItemTextId, "STATUS");
755 EXPECT_EQ(catalog.Sensors()[2].parentDataItemTextId, "STATUS");
756}
757
759TEST(IoSignalCatalogTests, BitSignalsAreBooleanAndDoubleCompatible)
760{
761 gsdml::GSDMLDevice device;
762
763 auto module =
764 MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
765
766 auto& submodule = module.submodules.at("SUBMODULE");
767
768 submodule.outputItems.push_back(MakeBitDataItem("Unsigned8",
769 1,
770 "COMMAND",
771 {{0U, "OPEN"}, {1U, "CLOSE"}}));
772
773 device.modules.emplace(module.id, module);
774
775 const auto catalog = IoSignalCatalog::FromGsdml(device);
776
777 ASSERT_EQ(catalog.Actuators().size(), 2U);
778
779 for (const auto& signal : catalog.Actuators())
780 {
781 EXPECT_TRUE(signal.IsBitSignal());
782 EXPECT_EQ(signal.valueType, IoSignalValueType::Boolean);
783 EXPECT_TRUE(signal.IsDoubleCompatible());
784 EXPECT_TRUE(signal.IsActuator());
785 }
786}
789TEST(IoSignalCatalogTests, BitDataItemDoesNotCreateParentSignal)
790{
791 gsdml::GSDMLDevice device;
792
793 auto module = MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
794
795 auto& submodule = module.submodules.at("SUBMODULE");
796
797 submodule.inputItems.push_back(MakeBitDataItem("Unsigned8",
798 1,
799 "STATUS",
800 {{0U, "READY"}, {1U, "FAULT"}}));
801
802 device.modules.emplace(module.id, module);
803
804 const auto catalog = IoSignalCatalog::FromGsdml(device);
805
806 ASSERT_EQ(catalog.Sensors().size(), 2U);
807
808 EXPECT_EQ(catalog.Sensors()[0].name, "READY");
809 EXPECT_EQ(catalog.Sensors()[1].name, "FAULT");
810
811 EXPECT_NE(catalog.Sensors()[0].name, "STATUS");
812 EXPECT_NE(catalog.Sensors()[1].name, "STATUS");
813}
814
816TEST(IoSignalCatalogTests, ConvertsBitOffsetToByteAndBitOffset)
817{
818 gsdml::GSDMLDevice device;
819
820 auto module =
821 MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
822
823 auto& submodule = module.submodules.at("SUBMODULE");
824
825 submodule.inputItems = {
826 MakeBitDataItem(
827 "Unsigned16",
828 2,
829 "STATUS",
830 {
831 {0U, "BIT_0"},
832 {7U, "BIT_7"},
833 {8U, "BIT_8"},
834 {10U, "BIT_10"},
835 {15U, "BIT_15"},
836 }),
837 };
838
839 device.modules.emplace(module.id, module);
840
841 const auto catalog =
843
844 ASSERT_EQ(catalog.Sensors().size(), 5U);
845
846 EXPECT_EQ(catalog.Sensors()[0].name, "BIT_0");
847 EXPECT_EQ(catalog.Sensors()[0].byteOffset, 0U);
848 EXPECT_EQ(catalog.Sensors()[0].bitOffset, 0U);
849
850 EXPECT_EQ(catalog.Sensors()[1].name, "BIT_7");
851 EXPECT_EQ(catalog.Sensors()[1].byteOffset, 0U);
852 EXPECT_EQ(catalog.Sensors()[1].bitOffset, 7U);
853
854 EXPECT_EQ(catalog.Sensors()[2].name, "BIT_8");
855 EXPECT_EQ(catalog.Sensors()[2].byteOffset, 1U);
856 EXPECT_EQ(catalog.Sensors()[2].bitOffset, 0U);
857
858 EXPECT_EQ(catalog.Sensors()[3].name, "BIT_10");
859 EXPECT_EQ(catalog.Sensors()[3].byteOffset, 1U);
860 EXPECT_EQ(catalog.Sensors()[3].bitOffset, 2U);
861
862 EXPECT_EQ(catalog.Sensors()[4].name, "BIT_15");
863 EXPECT_EQ(catalog.Sensors()[4].byteOffset, 1U);
864 EXPECT_EQ(catalog.Sensors()[4].bitOffset, 7U);
865}
867TEST(IoSignalCatalogTests, BitSignalsRetainParentDataItemOffset)
868{
869 gsdml::GSDMLDevice device;
870
871 auto module =
872 MakeModule("MODULE", "Module", "SUBMODULE", "Process data");
873
874 auto& submodule = module.submodules.at("SUBMODULE");
875
876 submodule.inputItems = {
877 MakeDataItem("Unsigned8", 1, "BEFORE"),
878 MakeBitDataItem(
879 "Unsigned16",
880 2,
881 "STATUS",
882 {{10U, "FAULT"}}),
883 };
884
885 device.modules.emplace(module.id, module);
886
887 const auto catalog =
889
890 ASSERT_EQ(catalog.Sensors().size(), 2U);
891
892 const auto& signal = catalog.Sensors()[1];
893
894 EXPECT_EQ(signal.parentDataItemByteOffset, 1U);
895 EXPECT_EQ(signal.byteOffset, 2U);
896 EXPECT_EQ(signal.bitOffset, 2U);
897}
898} // namespace profinet::io
Defines the GSDML-derived logical process-data signal catalogue.
static IoSignalCatalog FromGsdml(const gsdml::GSDMLDevice &device)
Build a signal catalogue from a parsed GSDML device.
Definition IOSignal.cpp:470
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
IoSignalValueType ClassifyDataType(std::string_view dataType) noexcept
Classify a GSDML DataType into a logical connector value category.
Definition IOSignal.cpp:433
@ FloatingPoint
IEEE floating-point value.
@ Boolean
Boolean value, represented by the connector as 0.0 or 1.0.
@ SignedInteger
Signed integer value.
@ UnsignedInteger
Unsigned integer value.
@ Unsupported
Data for which this catalogue does not define a numeric double representation.
TEST(IoSignalTypeTests, ClassifiesAllDoubleCompatibleScalarTypes)
@ Sensor
Data received from the PROFINET device and therefore exposed as a sensor.
@ Actuator
Data sent to the PROFINET device and therefore exposed as an actuator.
One I/O data item within a submodule's cyclic data (GSDML DataItem element).
Definition gsdml.h:35
int length
Length in bytes of this item.
Definition gsdml.h:48
std::string dataType
GSDML DataType attribute, e.g. "Unsigned8", "Integer16", "OctetString".
Definition gsdml.h:37
std::vector< GSDMLBitDataItem > bitDataItems
Individual BitDataItem definitions belonging to this DataItem.
Definition gsdml.h:59
std::string textId
Human-readable text ID/name for this item, if the GSDML supplied one.
Definition gsdml.h:39
Complete parsed GSDML device profile.
Definition gsdml.h:166
std::map< std::string, GSDMLModule > modules
Every module declared in the GSDML's ModuleList, keyed by GSDML ID.
Definition gsdml.h:196
std::vector< GSDMLModuleReference > moduleReferences
Modules referenced by the DAP's UseableModules list.
Definition gsdml.h:202
std::vector< GSDMLSubmodule > dapSubmodules
DAP virtual submodules.
Definition gsdml.h:190
std::uint32_t dapModuleIdentNumber
The device access point's own module ident number.
Definition gsdml.h:185
std::map< std::string, GSDMLSubmodule > submodules
Every globally defined SubmoduleItem, keyed by GSDML ID.
Definition gsdml.h:199
std::string dapModuleName
The device access point's own (built-in) module name.
Definition gsdml.h:179
std::string dapModuleId
The device access point's own (built-in) identifier to matchs with the submodule.
Definition gsdml.h:177
Reference describing how a module may be used by the DAP.
Definition gsdml.h:150
std::string moduleId
Referenced ModuleItem ID.
Definition gsdml.h:152
std::vector< int > usedSlots
Slots where the module is currently used/preselected.
Definition gsdml.h:161
std::vector< int > allowedSlots
Slots where the module is allowed.
Definition gsdml.h:155
std::vector< int > fixedSlots
Slots where the module is fixed.
Definition gsdml.h:158
Reference to a submodule that may be used by a module.
Definition gsdml.h:64
std::vector< int > allowedSubslots
Subslots where this submodule is allowed.
Definition gsdml.h:69
std::string submoduleId
Referenced SubmoduleItem ID.
Definition gsdml.h:66
A GSDML virtual submodule definition (VirtualSubmoduleItem).
Definition gsdml.h:80
std::string name
Human-readable submodule name (from the GSDML's Name/TextId, resolved via ExternalTextList).
Definition gsdml.h:86
std::uint32_t submoduleIdentNumber
Submodule ident number (hex value from the GSDML, parsed to an integer).
Definition gsdml.h:84
std::vector< GSDMLDataItem > inputItems
Individual input data items, in wire order.
Definition gsdml.h:92
std::string id
GSDML ID attribute (used to resolve VirtualSubmoduleItemTarget references).
Definition gsdml.h:82
std::vector< GSDMLDataItem > outputItems
Individual output data items, in wire order.
Definition gsdml.h:94
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 IsDoubleCompatible() const noexcept
Returns whether this signal can be represented by the IO connector's double field.
Definition IOSignal.cpp:428
bool IsActuator() const noexcept
Returns whether this signal is an output/actuator.
Definition IOSignal.cpp:423
bool doubleCompatible
Whether the connector can represent this logical value using its double field.
Definition IOSignal.h:126
IoSignalDirection direction
Sensor or actuator direction.
Definition IOSignal.h:85
std::uint16_t length
Definition test_dcp.cpp:391