PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
IOSignal.cpp
Go to the documentation of this file.
1
9
10#include "profinet/IOSignal.h"
11
12#include <algorithm>
13#include <cctype>
14#include <stdexcept>
15#include <utility>
16
17namespace profinet::io
18{
19
20namespace
21{
22
24[[nodiscard]] bool EqualInsensitive(const char lhs, const char rhs) noexcept
25{
26 const auto left = static_cast<unsigned char>(lhs);
27 const auto right = static_cast<unsigned char>(rhs);
28 return std::tolower(left) == std::tolower(right);
29}
30
32[[nodiscard]] bool IsAsciiAlpha(const char value) noexcept
33{
34 const auto character = static_cast<unsigned char>(value);
35 return std::isalpha(character) != 0;
36}
37
42[[nodiscard]] bool ContainsReservedMarker(const std::string_view value) noexcept
43{
44 constexpr std::string_view kReserve = "RESERVE";
45 constexpr std::string_view kReserved = "RESERVED";
46 constexpr std::string_view kNotAvailable = "-";
47
48 for (std::size_t offset = 0; offset < value.size(); ++offset)
49 {
50 const auto matchesAt = [&](const std::string_view marker)
51 {
52 if (offset + marker.size() > value.size())
53 {
54 return false;
55 }
56
57 for (std::size_t index = 0; index < marker.size(); ++index)
58 {
59 if (!EqualInsensitive(value[offset + index], marker[index]))
60 {
61 return false;
62 }
63 }
64
65 if (offset > 0 && IsAsciiAlpha(value[offset - 1]))
66 {
67 return false;
68 }
69
70 const std::size_t end = offset + marker.size();
71 if (end == value.size())
72 {
73 return true;
74 }
75
76 const char next = value[end];
77 return !IsAsciiAlpha(next) ||
78 (std::isupper(static_cast<unsigned char>(next)) != 0);
79 };
80
81 if (matchesAt(kReserved) || matchesAt(kReserve) || matchesAt(kNotAvailable))
82 {
83 return true;
84 }
85 }
86
87 return false;
88}
89
91void AddUnique(std::vector<int>& values, const std::vector<int>& candidates)
92{
93 for (const int candidate : candidates)
94 {
95 if (std::ranges::find(values, candidate) == values.end())
96 {
97 values.push_back(candidate);
98 }
99 }
100}
101
103void NormalizeLocations(std::vector<int>& values)
104{
105 std::ranges::sort(values);
106 values.erase(std::ranges::unique(values).begin(), values.end());
107}
108
110[[nodiscard]] std::string MakeSignalName(const gsdml::GSDMLDataItem& item)
111{
112 if (!item.name.empty())
113 {
114 return item.name;
115 }
116
117 if (!item.textId.empty())
118 {
119 return item.textId;
120 }
121
122 return "<unnamed>";
123}
124
126[[nodiscard]] IoSignal MakeSignal(const gsdml::GSDMLDataItem& item,
127 const IoSignalDirection direction,
128 const gsdml::GSDMLModule& module,
129 const gsdml::GSDMLSubmodule& submodule,
130 const std::size_t byteOffset,
131 const std::vector<int>& possibleSlots,
132 const std::vector<int>& possibleSubslots,
133 const gsdml::GSDMLBitDataItem* bit = nullptr)
134{
135 if (item.length < 0)
136 {
137 throw std::invalid_argument("GSDML DataItem has a negative length");
138 }
139
140 IoSignal signal;
141 signal.direction = direction;
142 signal.name = bit != nullptr ? !bit->name.empty() ? bit->name : !bit->textId.empty() ? bit->textId
143 : "<unnamed>"
144 : MakeSignalName(item);
145 signal.textId = bit != nullptr ? bit->textId : item.textId;
146 signal.parentDataItemTextId = bit != nullptr ? item.textId : "";
147 signal.bitSignal = bit != nullptr;
148 signal.parentDataItemByteOffset = byteOffset;
149 if (bit != nullptr)
150 {
151 signal.byteOffset = byteOffset + (bit->bitOffset / 8U);
152
153 signal.bitOffset = bit->bitOffset % 8U;
154 }
155 else
156 {
157 signal.byteOffset = byteOffset;
158 signal.bitOffset = 0U;
159 }
160 signal.gsdmlDataType = item.dataType;
161 signal.valueType = bit != nullptr ? IoSignalValueType::Boolean : ClassifyDataType(item.dataType);
162 signal.doubleCompatible = signal.valueType != IoSignalValueType::Unsupported;
163 signal.moduleId = module.id;
164 signal.moduleName = module.name;
165 signal.submoduleId = submodule.id;
166 signal.submoduleName = submodule.name;
167 signal.moduleIdentNumber = module.moduleIdentNumber;
168 signal.submoduleIdentNumber = submodule.submoduleIdentNumber;
169
170 signal.byteLength = bit != nullptr ? 1U : static_cast<std::size_t>(item.length);
171 signal.possibleSlots = possibleSlots;
172 signal.possibleSubslots = possibleSubslots;
173
174 return signal;
175}
176
178[[nodiscard]] std::vector<int> ResolveModuleSlots(const gsdml::GSDMLDevice& device,
179 const std::string& moduleId)
180{
181 std::vector<int> result;
182
183 for (const auto& reference : device.moduleReferences)
184 {
185 if (reference.moduleId != moduleId)
186 {
187 continue;
188 }
189
190 AddUnique(result, reference.allowedSlots);
191 AddUnique(result, reference.fixedSlots);
192 AddUnique(result, reference.usedSlots);
193 }
194
195 NormalizeLocations(result);
196 return result;
197}
198
200[[nodiscard]] std::vector<int> ResolveSubmoduleSlots(const gsdml::GSDMLSubmodule& submodule)
201{
202 std::vector<int> result;
203 AddUnique(result, submodule.allowedSubslots);
204 AddUnique(result, submodule.fixedSubslots);
205 AddUnique(result, submodule.usedSubslots);
206 NormalizeLocations(result);
207 return result;
208}
209
211[[nodiscard]] std::vector<int> ResolveSubmoduleSlots(const gsdml::GSDMLModule& module,
212 const std::string& submoduleId)
213{
214 std::vector<int> result;
215
216 for (const auto& reference : module.submoduleReferences)
217 {
218 if (reference.submoduleId != submoduleId)
219 {
220 continue;
221 }
222
223 AddUnique(result, reference.allowedSubslots);
224 AddUnique(result, reference.fixedSubslots);
225 AddUnique(result, reference.usedSubslots);
226 }
227
228 NormalizeLocations(result);
229 return result;
230}
231
233void AddSubmoduleItems(const gsdml::GSDMLModule& module,
234 const gsdml::GSDMLSubmodule& submodule,
235 const std::vector<int>& possibleSlots,
236 const std::vector<int>& possibleSubslots,
237 std::vector<IoSignal>& sensors,
238 std::vector<IoSignal>& actuators)
239{
240 std::size_t inputOffset = 0;
241 for (const auto& item : submodule.inputItems)
242 {
243 if (item.length < 0)
244 {
245 throw std::invalid_argument("GSDML DataItem has a negative length");
246 }
247 if (item.useAsBits && !item.bitDataItems.empty())
248 {
249 for (const auto& bit : item.bitDataItems)
250 {
251 if (!IsReservedSignal(item.textId, bit.textId, bit.name))
252 {
253 sensors.push_back(MakeSignal(item,
255 module,
256 submodule,
257 inputOffset,
258 possibleSlots,
259 possibleSubslots,
260 &bit));
261 }
262 }
263 }
264 else
265 {
266 const std::string name = MakeSignalName(item);
267 if (!IsReservedSignal(item.textId, name))
268 {
269 sensors.push_back(MakeSignal(item,
271 module,
272 submodule,
273 inputOffset,
274 possibleSlots,
275 possibleSubslots));
276 }
277 }
278 inputOffset += static_cast<std::size_t>(item.length);
279 }
280
281 std::size_t outputOffset = 0;
282 for (const auto& item : submodule.outputItems)
283 {
284 if (item.length < 0)
285 {
286 throw std::invalid_argument("GSDML DataItem has a negative length");
287 }
288 if (item.useAsBits && !item.bitDataItems.empty())
289 {
290 for (const auto& bit : item.bitDataItems)
291 {
292 if (!IsReservedSignal(item.textId, bit.textId, bit.name))
293 {
294 actuators.push_back(
295 MakeSignal(item,
297 module,
298 submodule,
299 outputOffset,
300 possibleSlots,
301 possibleSubslots,
302 &bit));
303 }
304 }
305 }
306 else
307 {
308 const std::string name = MakeSignalName(item);
309 if (!IsReservedSignal(item.textId, name))
310 {
311 actuators.push_back(MakeSignal(item,
313 module,
314 submodule,
315 outputOffset,
316 possibleSlots,
317 possibleSubslots));
318 }
319
320 outputOffset += static_cast<std::size_t>(item.length);
321 }
322 }
323}
324
326void AddModule(const gsdml::GSDMLDevice& device,
327 const gsdml::GSDMLModule& module,
328 const std::vector<int>& possibleSlots,
329 std::vector<IoSignal>& sensors,
330 std::vector<IoSignal>& actuators)
331{
332 for (const auto& [submoduleId, submodule] : module.submodules)
333 {
334 auto possibleSubslots = ResolveSubmoduleSlots(submodule);
335 const auto referenceSubslots = ResolveSubmoduleSlots(module, submoduleId);
336 AddUnique(possibleSubslots, referenceSubslots);
337 NormalizeLocations(possibleSubslots);
338
339 AddSubmoduleItems(module,
340 submodule,
341 possibleSlots,
342 possibleSubslots,
343 sensors,
344 actuators);
345 }
346
347 std::vector<std::string> processedSubmoduleIds;
348
349 for (const auto& reference : module.submoduleReferences)
350 {
351 const auto inlineIt = module.submodules.find(reference.submoduleId);
352 if (inlineIt != module.submodules.end())
353 {
354 continue;
355 }
356
357 if (std::ranges::find(processedSubmoduleIds, reference.submoduleId) !=
358 processedSubmoduleIds.end())
359 {
360 continue;
361 }
362 processedSubmoduleIds.push_back(reference.submoduleId);
363
364 const auto* submodule = device.FindSubmodule(reference.submoduleId);
365 if (submodule == nullptr)
366 {
367 throw std::invalid_argument(
368 "Module '" + module.id + "' references unknown submodule '" +
369 reference.submoduleId + "'");
370 }
371
372 auto possibleSubslots = ResolveSubmoduleSlots(*submodule);
373 const auto referenceSubslots = ResolveSubmoduleSlots(module, reference.submoduleId);
374 AddUnique(possibleSubslots, referenceSubslots);
375 NormalizeLocations(possibleSubslots);
376
377 AddSubmoduleItems(module,
378 *submodule,
379 possibleSlots,
380 possibleSubslots,
381 sensors,
382 actuators);
383 }
384}
385
387void AddDapSignals(const gsdml::GSDMLDevice& device,
388 std::vector<IoSignal>& sensors,
389 std::vector<IoSignal>& actuators)
390{
391 gsdml::GSDMLModule dapModule;
392 dapModule.id = device.dapModuleId;
393 dapModule.name = device.dapModuleName.empty() ? device.deviceName : device.dapModuleName;
394 dapModule.moduleIdentNumber = device.dapModuleIdentNumber;
395
396 for (std::size_t index = 0; index < device.dapSubmodules.size(); ++index)
397 {
398 const auto& submodule = device.dapSubmodules[index];
399 std::vector<int> possibleSlots{0};
400 std::vector<int> possibleSubslots{static_cast<int>(index + 1U)};
401
402 AddSubmoduleItems(dapModule,
403 submodule,
404 possibleSlots,
405 possibleSubslots,
406 sensors,
407 actuators);
408 }
409}
410
411} // namespace
412
413bool IoSignal::IsBitSignal() const noexcept
414{
415 return bitSignal;
416}
417
418bool IoSignal::IsSensor() const noexcept
419{
421}
422
423bool IoSignal::IsActuator() const noexcept
424{
426}
427
428bool IoSignal::IsDoubleCompatible() const noexcept
429{
430 return doubleCompatible;
431}
432
433IoSignalValueType ClassifyDataType(const std::string_view dataType) noexcept
434{
435 if ((dataType == "Boolean") || (dataType == "Bit"))
436 {
438 }
439
440 if ((dataType == "Integer8") || (dataType == "Integer16") ||
441 (dataType == "Integer32") || (dataType == "Integer64"))
442 {
444 }
445
446 if ((dataType == "Unsigned8") || (dataType == "Unsigned16") ||
447 (dataType == "Unsigned32") || (dataType == "Unsigned64"))
448 {
450 }
451
452 if ((dataType == "Float32") || (dataType == "Float64"))
453 {
455 }
456
458}
459
460bool IsReservedSignal(const std::string_view textId, const std::string_view bitId, const std::string_view name) noexcept
461{
462 return ContainsReservedMarker(textId) || ContainsReservedMarker(bitId) || ContainsReservedMarker(name);
463}
464
465bool IsReservedSignal(const std::string_view textId, const std::string_view name) noexcept
466{
467 return ContainsReservedMarker(textId) || ContainsReservedMarker(name);
468}
469
471{
472 IoSignalCatalog catalog;
473
474 AddDapSignals(device, catalog.sensors, catalog.actuators);
475
476 if (device.moduleReferences.empty())
477 {
478 for (const auto& [moduleId, module] : device.modules)
479 {
480 (void)moduleId;
481 AddModule(device,
482 module,
483 ResolveModuleSlots(device, module.id),
484 catalog.sensors,
485 catalog.actuators);
486 }
487 }
488 else
489 {
490 std::vector<std::string> processedModuleIds;
491
492 for (const auto& reference : device.moduleReferences)
493 {
494 const auto* module = device.FindModule(reference.moduleId);
495 if (module == nullptr)
496 {
497 throw std::invalid_argument(
498 "GSDML references unknown module '" + reference.moduleId + "'");
499 }
500
501 const auto existing = std::ranges::find(processedModuleIds, reference.moduleId);
502 if (existing != processedModuleIds.end())
503 {
504 continue;
505 }
506
507 processedModuleIds.push_back(reference.moduleId);
508
509 std::vector<int> possibleSlots;
510 for (const auto& candidate : device.moduleReferences)
511 {
512 if (candidate.moduleId != reference.moduleId)
513 {
514 continue;
515 }
516
517 AddUnique(possibleSlots, candidate.allowedSlots);
518 AddUnique(possibleSlots, candidate.fixedSlots);
519 AddUnique(possibleSlots, candidate.usedSlots);
520 }
521 NormalizeLocations(possibleSlots);
522
523 AddModule(device,
524 *module,
525 possibleSlots,
526 catalog.sensors,
527 catalog.actuators);
528 }
529 }
530
531 return catalog;
532}
533
534const std::vector<IoSignal>& IoSignalCatalog::Sensors() const noexcept
535{
536 return sensors;
537}
538
539const std::vector<IoSignal>& IoSignalCatalog::Actuators() const noexcept
540{
541 return actuators;
542}
543
544std::size_t IoSignalCatalog::Size() const noexcept
545{
546 return sensors.size() + actuators.size();
547}
548
549} // namespace profinet::io
Defines the GSDML-derived logical process-data signal catalogue.
Builds and owns the logical sensor and actuator signals described by a GSDML device.
Definition IOSignal.h:186
const std::vector< IoSignal > & Actuators() const noexcept
Returns all non-reserved actuator/output signals.
Definition IOSignal.cpp:539
std::size_t Size() const noexcept
Returns the total number of sensor and actuator signals.
Definition IOSignal.cpp:544
std::vector< IoSignal > actuators
Definition IOSignal.h:207
std::vector< IoSignal > sensors
Definition IOSignal.h:206
static IoSignalCatalog FromGsdml(const gsdml::GSDMLDevice &device)
Build a signal catalogue from a parsed GSDML device.
Definition IOSignal.cpp:470
const std::vector< IoSignal > & Sensors() const noexcept
Returns all non-reserved sensor/input signals.
Definition IOSignal.cpp:534
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
IoSignalValueType
Logical value category used when exposing a signal to higher layers.
Definition IOSignal.h:40
@ 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.
IoSignalDirection
Logical direction of a process-data signal from the controller's perspective.
Definition IOSignal.h:30
@ 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.
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
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 IsBitSignal() const noexcept
Returns whether this signal represents one BitDataItem.
Definition IOSignal.cpp:413
bool doubleCompatible
Whether the connector can represent this logical value using its double field.
Definition IOSignal.h:126
bool bitSignal
Whether this logical signal originated from a BitDataItem.
Definition IOSignal.h:94
IoSignalDirection direction
Sensor or actuator direction.
Definition IOSignal.h:85