PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
diagnosis.cpp
Go to the documentation of this file.
1
3
5
6#include <algorithm>
7#include <cstdio>
8#include <map>
9
10#include "profinet/wire.h"
11
12namespace profinet::diagnosis
13{
14
15// =============================================================================
16// ChannelProperties
17// =============================================================================
18
20{
22 p.raw = value;
23
24 const auto ct = static_cast<std::uint8_t>(value & 0x03);
25 p.channelTypeValue = ct <= 3 ? static_cast<ChannelType>(ct) : ChannelType::Reserved;
26
27 const auto acc = static_cast<std::uint8_t>((value >> 2) & 0x07);
28 p.accumulative = acc <= 2 ? static_cast<ChannelAccumulative>(acc) : ChannelAccumulative::No;
29
30 static constexpr uint8_t maintenanceRequiredBitIdx = 5;
31 p.maintenanceRequired = (((value >> maintenanceRequiredBitIdx) & 0x01) != 0);
32
33 static constexpr uint8_t maintenanceDemandedBitIdx = 6;
34 p.naintenanceDemanded = (((value >> maintenanceDemandedBitIdx) & 0x01) != 0);
35 // bit 7 reserved
36
37 const auto spec = static_cast<std::uint8_t>(ByteAt(value, 1U) & 0x07);
38 p.specifier = spec <= 3 ? static_cast<ChannelSpecifier>(spec) : ChannelSpecifier::AllDisappears;
39
40 const auto dir = static_cast<std::uint8_t>((value >> 11) & 0x03);
41 p.direction = dir <= 3 ? static_cast<ChannelDirection>(dir) : ChannelDirection::Manufacturer;
42
43 return p;
44}
45
47{
48 switch (channelTypeValue)
49 {
51 return "Reserved";
53 return "Specific";
55 return "All";
57 return "Submodule";
58 }
59 return "Unknown";
60}
61
63{
64 switch (specifier)
65 {
67 return "All Disappears";
69 return "Appears";
71 return "Disappears";
73 return "Disappears Other";
74 }
75 return "Unknown";
76}
77
79{
80 switch (direction)
81 {
83 return "Manufacturer";
85 return "Input";
87 return "Output";
89 return "Bidirectional";
90 }
91 return "Unknown";
92}
93
94// =============================================================================
95// Error type name tables
96// =============================================================================
97
98namespace
99{
100
101const std::map<std::uint16_t, std::string>& ChannelErrorTypes()
102{
103 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
104 static const std::map<std::uint16_t, std::string> table = {
105 {0x0000, "Reserved"},
106 {0x0001, "Short circuit"},
107 {0x0002, "Undervoltage"},
108 {0x0003, "Overvoltage"},
109 {0x0004, "Overload"},
110 {0x0005, "Overtemperature"},
111 {0x0006, "Line break"},
112 {0x0007, "Upper limit value exceeded"},
113 {0x0008, "Lower limit value exceeded"},
114 {0x0009, "Error"},
115 {0x000A, "Simulation active"},
116 {0x000F, "Parameter missing"},
117 {0x0010, "Parameterization fault"},
118 {0x0011, "Power supply fault"},
119 {0x0012, "Fuse blown / open"},
120 {0x0013, "Communication fault"},
121 {0x0014, "Ground fault"},
122 {0x0015, "Reference point lost"},
123 {0x0016, "Process event lost"},
124 {0x0017, "Threshold warning"},
125 {0x0018, "Output disabled"},
126 {0x0019, "Functional safety event"},
127 {0x001A, "External fault"},
128 {0x001B, "Sensor has incorrect configuration"},
129 {0x001F, "Temporary fault"},
130 {0x8000, "Data transmission impossible"},
131 {0x8001, "Remote mismatch"},
132 {0x8002, "Media redundancy mismatch"},
133 {0x8003, "Sync mismatch"},
134 {0x8004, "Isochronous mode mismatch"},
135 {0x8005, "Multicast CR mismatch"},
136 {0x8007, "Fiber optic mismatch"},
137 {0x8008, "Network component function mismatch"},
138 {0x8009, "Time mismatch"},
139 {0x800A, "Dynamic frame packing function mismatch"},
140 {0x800B, "Media redundancy with planned duplication mismatch"},
141 {0x800C, "System redundancy mismatch"},
142 {0x800D, "Multiple interface mismatch"},
143 {0x9500, "IO-Link device event"},
144 {0x9501, "IO-Link device event (MSB cleared)"},
145 {0x9502, "IO-Link port event"},
146 };
147 return table;
148}
149
150// Ext error tables keyed by ChannelErrorType
151const std::map<std::uint16_t, std::string>& ExtFor(std::uint16_t channelErrorType)
152{
153 static const std::map<std::uint16_t, std::string> general = {
154 {0x0000, "Reserved"},
155 {0x8000, "Accumulative info"},
156 };
157 static const std::map<std::uint16_t, std::map<std::uint16_t, std::string>> specific = {
158 {0x8000, {{0x0000, "Reserved"}, {0x8000, "Link state mismatch - Loss of link"}, {0x8001, "MAUType mismatch"}, {0x8002, "Line delay mismatch"}}},
159 {0x8001, {{0x0000, "Reserved"}, {0x8000, "Peer name of station mismatch"}, {0x8001, "Peer name of port mismatch"}, {0x8002, "Peer RT_CLASS_3 mismatch"}, {0x8003, "Peer MAUType mismatch"}, {0x8004, "Peer MRP domain mismatch"}, {0x8005, "No peer detected"}, {0x8006, "Peer line delay mismatch"}, {0x8007, "Peer PTCP mismatch"}, {0x8008, "Peer Preamble length mismatch"}, {0x8009, "Peer Fragmentation mismatch"}}},
160 {0x8002, {{0x0000, "Reserved"}, {0x8000, "Manager role fail"}, {0x8001, "MRP-Loss of redundancy"}, {0x8003, "MRP ring open"}, {0x8004, "MRP multiple manager"}}},
161 {0x8003, {{0x0000, "Reserved"}, {0x8000, "No sync message received"}, {0x8001, "Jitter out of boundary"}, {0x8002, "Sync message send failure"}, {0x8003, "PTCP timeout"}}},
162 {0x8007, {{0x0000, "Reserved"}, {0x8000, "Power budget exceeded"}}},
163 {0x8008, {{0x0000, "Reserved"}, {0x8000, "Frame dropped - no resource"}, {0x8001, "Frame dropped - wrong destination address"}, {0x8002, "Frame dropped - no gateway"}}},
164 {0x8009, {{0x0000, "Reserved"}, {0x8000, "No master detected"}, {0x8001, "Drift exceeded"}, {0x8002, "Time sync failure"}}},
165 {0x800B, {{0x0000, "Reserved"}, {0x8000, "MRPD loss of redundancy"}}},
166 };
167 auto it = specific.find(channelErrorType);
168 if (it != specific.end())
169 {
170 return it->second;
171 }
172 return general;
173}
174
175// Known diagnosis block types (blocks.h's BLOCK_DIAGNOSIS_DATA is 0x0010)
176bool IsDiagnosisBlockType(std::uint16_t t)
177{
178 return t == 0x0010 || t == 0x0011 || t == 0x8010 || t == 0x8011 || t == 0x8012;
179}
180
181} // namespace
182
183std::string DecodeChannelErrorType(std::uint16_t errorType)
184{
185 auto it = ChannelErrorTypes().find(errorType);
186 if (it != ChannelErrorTypes().end())
187 {
188 return it->second;
189 }
190 if (errorType >= 0x000B && errorType <= 0x000E)
191 {
192 return "Reserved (" + Hex4(errorType) + ")";
193 }
194 if (errorType >= 0x0020 && errorType <= 0x00FF)
195 {
196 return "Reserved (" + Hex4(errorType) + ")";
197 }
198 if (errorType >= 0x0100 && errorType <= 0x7FFF)
199 {
200 return "Manufacturer-specific (" + Hex4(errorType) + ")";
201 }
202 if (errorType >= 0x800E && errorType <= 0x8FFF)
203 {
204 return "Reserved (" + Hex4(errorType) + ")";
205 }
206 if (errorType >= 0x9000 && errorType <= 0x9FFF)
207 {
208 return "Profile-specific (" + Hex4(errorType) + ")";
209 }
210 if (errorType >= 0xA000)
211 {
212 return "Reserved (" + Hex4(errorType) + ")";
213 }
214 return "Unknown (" + Hex4(errorType) + ")";
215}
216
217std::string DecodeExtChannelErrorType(std::uint16_t channelErrorType, std::uint16_t extErrorType)
218{
219 const auto& lookup = ExtFor(channelErrorType);
220 auto it = lookup.find(extErrorType);
221 if (it != lookup.end())
222 {
223 return it->second;
224 }
225 if (extErrorType == 0x8000)
226 {
227 return "Accumulative info";
228 }
229 if (extErrorType >= 0x0001 && extErrorType <= 0x7FFF)
230 {
231 return "Manufacturer-specific (" + Hex4(extErrorType) + ")";
232 }
233 if (extErrorType >= 0x8001 && extErrorType <= 0x8FFF)
234 {
235 return "Reserved (" + Hex4(extErrorType) + ")";
236 }
237 if (extErrorType >= 0x9000 && extErrorType <= 0x9FFF)
238 {
239 return "Profile-specific (" + Hex4(extErrorType) + ")";
240 }
241 return "Unknown (" + Hex4(extErrorType) + ")";
242}
243
244// =============================================================================
245// Parsing
246// =============================================================================
247
248DiagnosisData ParseDiagnosisBlock(const Bytes& data, std::uint32_t api, std::uint16_t slot, std::uint16_t subslot)
249{
250 DiagnosisData result;
251 result.api = api;
252 result.slot = slot;
253 result.subslot = subslot;
254 result.rawData = data;
255
256 if (data.size() < (3 * sizeof(std::uint16_t))) // ChannelNumber(2) + ChannelProperties(2) + USI(2)
257 {
258 return result;
259 }
260
261 std::size_t offset = 0;
262 wire::Reader r(data);
263
264 // Skip block header if present
265 if (data.size() >= 4)
266 {
267 const auto possibleType = r.U16();
268 if (IsDiagnosisBlockType(possibleType))
269 {
270 offset = (3 * sizeof(std::uint16_t)); // ChannelNumber(2) + ChannelProperties(2) + USI(2)
271 }
272 }
273
274 while (data.size() >= offset + (3 * sizeof(std::uint16_t))) // ChannelNumber(2) + ChannelProperties(2) + USI(2)
275 {
276 // Optionally consume API/Slot/Subslot location sub-header.
277 // Heuristics by ralph / internet: if the next 8 bytes decode to a
278 // plausible API (<0x10000) and slot (<0x8000), treat as location.
279 if (data.size() >= offset + sizeof(std::uint32_t) + (2 * sizeof(std::uint16_t)))
280 {
281 wire::Reader locationReader(data);
282 locationReader.SkipTo(offset);
283
284 const auto maybeApi = locationReader.U32();
285 const auto maybeSlot = locationReader.U16();
286 const auto maybeSubslot = locationReader.U16();
287
288 if (maybeApi < 0x10000 && maybeSlot < 0x8000)
289 {
290 api = maybeApi;
291 slot = maybeSlot;
292 subslot = maybeSubslot;
293
294 offset = locationReader.Position();
295 r.SkipTo(offset);
296
297 if (data.size() < offset + (3 * sizeof(std::uint16_t))) // ChannelNumber(2) + ChannelProperties(2) + USI(2)
298 {
299 break;
300 }
301 }
302 else
303 {
304 // The heuristic did not match. Keep the reader at the current
305 // diagnosis entry rather than consuming the candidate bytes.
306 r.SkipTo(offset);
307 }
308 }
309 else
310 {
311 r.SkipTo(offset);
312 }
313 // ChannelNumber(2) + ChannelProperties(2) + USI(2)
314 const auto channelNum = r.U16();
315 offset += sizeof(std::uint16_t);
316
317 const auto chPropsRaw = r.U16();
318 offset += sizeof(std::uint16_t);
319
320 const auto usi = static_cast<Usi>(r.U16());
321 offset += sizeof(std::uint16_t);
322
323 const ChannelProperties chProps = ChannelProperties::FromUint16(chPropsRaw);
324
326 entry.usi = usi;
327 entry.base.api = api;
328 entry.base.slot = slot;
329 entry.base.subslot = subslot;
330 entry.base.channelNumber = channelNum;
331 entry.base.properties = chProps;
332
333 if (usi == Usi::ChannelDiagnosis)
334 {
335 // ChannelDiagnosis: ChannelErrorType(2)
336 if (offset + 2 > data.size())
337 {
338 break;
339 }
340 const auto errorType = r.U16();
341 offset += sizeof(std::uint16_t);
342
343 entry.base.errorType = errorType;
344 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
345 }
346 else if (usi == Usi::ExtendedChannelDiagnosis)
347 {
348 // ExtChannelDiagnosis: error_type(2) + ext_error_type(2) + ext_add_value(4)
349 if (offset + (2 * sizeof(std::uint16_t)) + sizeof(std::uint32_t) > data.size())
350 {
351 break;
352 }
353 const auto errorType = r.U16();
354 offset += sizeof(std::uint16_t);
355
356 const auto extType = r.U16();
357 offset += sizeof(std::uint16_t);
358
359 const std::uint32_t extAdd = r.U32();
360 offset += sizeof(std::uint32_t);
361
362 entry.base.errorType = errorType;
363 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
365 static_cast<ChannelDiagnosis&>(ext) = entry.base;
366 ext.extErrorType = extType;
367 ext.extErrorTypeName = DecodeExtChannelErrorType(errorType, extType);
368 ext.extAddValue = extAdd;
369 entry.ext = ext;
370 }
371 else if (usi == Usi::QualifiedChannelDiagnosis)
372 {
373 // QualifiedChannelDiagnosis: same as Ext + qualifier(4)
374 if (offset + (2 * sizeof(std::uint16_t)) + (2 * sizeof(std::uint32_t)) > data.size())
375 {
376 break;
377 }
378 const auto errorType = r.U16();
379 offset += sizeof(std::uint16_t);
380
381 const auto extType = r.U16();
382 offset += sizeof(std::uint16_t);
383
384 const std::uint32_t extAdd = r.U32();
385 offset += sizeof(std::uint32_t);
386
387 const std::uint32_t qualifier = r.U32();
388 offset += sizeof(std::uint32_t);
389
390 entry.base.errorType = errorType;
391 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
393 static_cast<ChannelDiagnosis&>(qual) = entry.base;
394 qual.extErrorType = extType;
395 qual.extErrorTypeName = DecodeExtChannelErrorType(errorType, extType);
396 qual.extAddValue = extAdd;
397 qual.qualifier = qualifier;
398 entry.qualified = qual;
399 entry.ext = qual; // also populate ext for convenience
400 }
401 else
402 {
403 // Unknown USI — treat as ChannelDiagnosis with minimal data
404 if (offset + 2 > data.size())
405 {
406 break;
407 }
408 const auto errorType = r.U16();
409 offset += sizeof(std::uint16_t);
410 entry.base.errorType = errorType;
411 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
412 }
413
414 result.entries.push_back(entry);
415 }
416
417 return result;
418}
419
420DiagnosisData ParseDiagnosisSimple(const Bytes& data, std::uint32_t api, std::uint16_t slot,
421 std::uint16_t subslot)
422{
423 DiagnosisData result;
424 result.api = api;
425 result.slot = slot;
426 result.subslot = subslot;
427 result.rawData = data;
428
429 // Skip the 6-byte block header unconditionally
430 if (data.size() < blockHeaderLength)
431 {
432 return result;
433 }
434 std::size_t offset = blockHeaderLength;
435 wire::Reader r(data);
436 r.SkipTo(offset);
437
438 while (data.size() >= offset + (3 * sizeof(std::uint16_t)))
439 {
440 const auto channelNum = r.U16();
441 offset += sizeof(std::uint16_t);
442
443 const auto chPropsRaw = r.U16();
444 offset += sizeof(std::uint16_t);
445
446 const auto errorType = r.U16();
447 offset += sizeof(std::uint16_t);
448
449 // Stop on zero-filled sentinel entry
450 if (channelNum == 0 && chPropsRaw == 0 && errorType == 0)
451 {
452 break;
453 }
454
456 entry.usi = Usi::NoIdentifier;
457 entry.base.api = api;
458 entry.base.slot = slot;
459 entry.base.subslot = subslot;
460 entry.base.channelNumber = channelNum;
462 entry.base.errorType = errorType;
463 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
464 result.entries.push_back(entry);
465 }
466
467 return result;
468}
469
470} // namespace profinet::diagnosis
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::size_t Position() const
Current read offset from the start of the buffer.
Definition wire.h:225
std::uint32_t U32()
Read and consume a big-endian 32-bit value.
Definition wire.h:268
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
Definition wire.h:343
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
PROFINET diagnosis data structures, error decoding, and parsers.
std::string DecodeChannelErrorType(std::uint16_t errorType)
Decode a ChannelErrorType to a human-readable string.
@ Manufacturer
Manufacturer-specific direction.
DiagnosisData ParseDiagnosisSimple(const Bytes &data, std::uint32_t api=0, std::uint16_t slot=0, std::uint16_t subslot=0)
Parse a simple, fixed-format diagnosis block.
DiagnosisData ParseDiagnosisBlock(const Bytes &data, std::uint32_t api=0, std::uint16_t slot=0, std::uint16_t subslot=0)
Parse a full DiagnosisData block from raw bytes.
@ All
All channels of the submodule.
@ Submodule
The whole submodule.
@ Specific
A specific, individually addressed channel.
@ Disappears
Diagnosis disappears.
@ AllDisappears
All diagnosis of the submodule disappears.
@ DisappearsOther
Diagnosis disappears but others remain.
std::string DecodeExtChannelErrorType(std::uint16_t channelErrorType, std::uint16_t extErrorType)
Decode an ExtChannelErrorType given its ChannelErrorType context.
constexpr int blockHeaderLength
Constant lenght of a blockHeader.
Definition protocol.h:58
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
Definition util.cpp:44
Usi
PROFINET User Structure Identifiers (USI).
Definition indices.h:526
@ NoIdentifier
No identifier.
@ ExtendedChannelDiagnosis
Extended channel diagnosis.
@ ChannelDiagnosis
Channel diagnosis.
@ QualifiedChannelDiagnosis
Qualified channel diagnosis.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
constexpr std::uint8_t ByteAt(T value, std::size_t index)
Return one byte from an integral value.
Definition util.h:244
Any diagnosis entry (USI 0x8000, 0x8002, or 0x8003).
Definition diagnosis.h:230
Usi usi
The USI that determined which entry type was parsed.
Definition diagnosis.h:232
ChannelDiagnosis base
Common fields, always populated.
Definition diagnosis.h:237
std::optional< QualifiedChannelDiagnosis > qualified
Qualified fields, populated only for USI 0x8003.
Definition diagnosis.h:243
std::optional< ExtChannelDiagnosis > ext
Extended fields, populated only for USI 0x8002/0x8003.
Definition diagnosis.h:240
ChannelDiagnosis entry (USI 0x8000).
Definition diagnosis.h:166
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:174
std::uint16_t channelNumber
Channel number, with the accumulative bit packed into bit 15.
Definition diagnosis.h:177
std::string errorTypeName
Human-readable name for ErrorType.
Definition diagnosis.h:186
std::uint16_t slot
Slot number.
Definition diagnosis.h:171
std::uint16_t errorType
Channel error type code.
Definition diagnosis.h:183
ChannelProperties properties
Decoded channel properties.
Definition diagnosis.h:180
std::uint32_t api
API number.
Definition diagnosis.h:168
Parsed ChannelProperties word (16 bits).
Definition diagnosis.h:89
std::string SpecifierName() const
Human-readable name for Specifier.
Definition diagnosis.cpp:62
std::string DirectionName() const
Human-readable name for Direction.
Definition diagnosis.cpp:78
bool maintenanceRequired
Maintenance required flag (bit 5).
Definition diagnosis.h:100
bool naintenanceDemanded
Maintenance demanded flag (bit 6).
Definition diagnosis.h:103
std::string ChannelTypeName() const
Human-readable name for ChannelTypeValue.
Definition diagnosis.cpp:46
ChannelAccumulative accumulative
Accumulative info (bits 2-4).
Definition diagnosis.h:97
ChannelType channelTypeValue
Channel type (bits 0-1).
Definition diagnosis.h:94
ChannelDirection direction
Data direction (bits 11-12).
Definition diagnosis.h:109
ChannelSpecifier specifier
Diagnosis specifier (bits 8-10).
Definition diagnosis.h:106
static ChannelProperties FromUint16(std::uint16_t value)
Decode a raw 16-bit ChannelProperties value.
Definition diagnosis.cpp:19
std::uint16_t raw
Raw 16-bit ChannelProperties value.
Definition diagnosis.h:91
Complete diagnosis data for one slot/subslot.
Definition diagnosis.h:275
std::vector< AnyChannelDiagnosis > entries
Parsed diagnosis entries.
Definition diagnosis.h:286
Bytes rawData
Raw, undecoded source data.
Definition diagnosis.h:289
std::uint32_t api
API number.
Definition diagnosis.h:277
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:283
std::uint16_t slot
Slot number.
Definition diagnosis.h:280
ExtChannelDiagnosis entry (USI 0x8002).
Definition diagnosis.h:206
std::uint32_t extAddValue
Extended channel additional value.
Definition diagnosis.h:214
std::string extErrorTypeName
Human-readable name for ExtErrorType.
Definition diagnosis.h:211
std::uint16_t extErrorType
Extended channel error type code.
Definition diagnosis.h:208
QualifiedChannelDiagnosis entry (USI 0x8003).
Definition diagnosis.h:220
std::uint32_t qualifier
Qualifier bitmask providing additional severity/context info.
Definition diagnosis.h:222
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.