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>((value >> OneOctetShift) & 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 "AllDisappears";
69 return "Appears";
71 return "Disappears";
73 return "DisappearsOther";
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 (match diagnosis.py exactly)
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() < 6)
257 {
258 return result;
259 }
260
261 std::size_t offset = 0;
262
263 // Skip block header if present (match diagnosis.py's hdr.block_type check)
264 if (data.size() >= 4)
265 {
266 const auto possibleType = static_cast<std::uint16_t>((data[0] << OneOctetShift) | data[1]);
267 if (IsDiagnosisBlockType(possibleType))
268 {
269 offset = 6;
270 }
271 }
272
273 while (offset + 6 <= data.size())
274 {
275 // Optionally consume API/Slot/Subslot location sub-header.
276 // Heuristic from diagnosis.py: if the next 8 bytes decode to a
277 // plausible API (<0x10000) and slot (<0x8000), treat as location.
278 if (offset + 8 <= data.size())
279 {
280 const std::uint32_t maybeApi = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift |
281 static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
282 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
283 const auto maybeSlot = static_cast<std::uint16_t>((data[offset + 4] << OneOctetShift) | data[offset + 5]);
284 const auto maybeSubslot = static_cast<std::uint16_t>((data[offset + 6] << OneOctetShift) | data[offset + 7]);
285 if (maybeApi < 0x10000 && maybeSlot < 0x8000)
286 {
287 api = maybeApi;
288 slot = maybeSlot;
289 subslot = maybeSubslot;
290 offset += 8;
291 if (offset + 6 > data.size())
292 {
293 break;
294 }
295 }
296 }
297
298 // ChannelNumber(2) + ChannelProperties(2) + USI(2)
299 const auto channelNum = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
300 const auto chPropsRaw = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
301 const auto usi = static_cast<std::uint16_t>((data[offset + 4] << OneOctetShift) | data[offset + 5]);
302 offset += 6;
303
304 const ChannelProperties chProps = ChannelProperties::FromUint16(chPropsRaw);
305
307 entry.usi = usi;
308 entry.base.api = api;
309 entry.base.slot = slot;
310 entry.base.subslot = subslot;
311 entry.base.channelNumber = channelNum;
312 entry.base.properties = chProps;
313
314 if (usi == 0x8000)
315 {
316 // ChannelDiagnosis: ChannelErrorType(2)
317 if (offset + 2 > data.size())
318 {
319 break;
320 }
321 const auto errorType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
322 offset += 2;
323 entry.base.errorType = errorType;
324 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
325 }
326 else if (usi == 0x8002)
327 {
328 // ExtChannelDiagnosis: error_type(2) + ext_error_type(2) + ext_add_value(4)
329 if (offset + 8 > data.size())
330 {
331 break;
332 }
333 const auto errorType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
334 const auto extType = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
335 const std::uint32_t extAdd = static_cast<std::uint32_t>(data[offset + 4]) << ThreeOctetsShift |
336 static_cast<std::uint32_t>(data[offset + 5]) << TwoOctetsShift |
337 static_cast<std::uint32_t>(data[offset + 6]) << OneOctetShift | data[offset + 7];
338 offset += 8;
339
340 entry.base.errorType = errorType;
341 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
343 static_cast<ChannelDiagnosis&>(ext) = entry.base;
344 ext.extErrorType = extType;
345 ext.extErrorTypeName = DecodeExtChannelErrorType(errorType, extType);
346 ext.extAddValue = extAdd;
347 entry.ext = ext;
348 }
349 else if (usi == 0x8003)
350 {
351 // QualifiedChannelDiagnosis: same as Ext + qualifier(4)
352 if (offset + 12 > data.size())
353 {
354 break;
355 }
356 const auto errorType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
357 const auto extType = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
358 const std::uint32_t extAdd = static_cast<std::uint32_t>(data[offset + 4]) << ThreeOctetsShift |
359 static_cast<std::uint32_t>(data[offset + 5]) << TwoOctetsShift |
360 static_cast<std::uint32_t>(data[offset + 6]) << OneOctetShift | data[offset + 7];
361 const std::uint32_t qualifier = static_cast<std::uint32_t>(data[offset + 8]) << ThreeOctetsShift |
362 static_cast<std::uint32_t>(data[offset + 9]) << TwoOctetsShift |
363 static_cast<std::uint32_t>(data[offset + 10]) << OneOctetShift | data[offset + 11];
364 offset += 12;
365
366 entry.base.errorType = errorType;
367 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
369 static_cast<ChannelDiagnosis&>(qual) = entry.base;
370 qual.extErrorType = extType;
371 qual.extErrorTypeName = DecodeExtChannelErrorType(errorType, extType);
372 qual.extAddValue = extAdd;
373 qual.qualifier = qualifier;
374 entry.qualified = qual;
375 entry.ext = qual; // also populate ext for convenience
376 }
377 else
378 {
379 // Unknown USI — treat as ChannelDiagnosis with minimal data
380 if (offset + 2 > data.size())
381 {
382 break;
383 }
384 const auto errorType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
385 offset += 2;
386 entry.base.errorType = errorType;
387 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
388 }
389
390 result.entries.push_back(entry);
391 }
392
393 return result;
394}
395
396DiagnosisData ParseDiagnosisSimple(const Bytes& data, std::uint32_t api, std::uint16_t slot,
397 std::uint16_t subslot)
398{
399 DiagnosisData result;
400 result.api = api;
401 result.slot = slot;
402 result.subslot = subslot;
403 result.rawData = data;
404
405 // Skip the 6-byte block header unconditionally
406 if (data.size() < 6)
407 {
408 return result;
409 }
410 std::size_t offset = 6;
411
412 while (offset + 6 <= data.size())
413 {
414 const auto channelNum = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
415 const auto chPropsRaw = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
416 const auto errorType = static_cast<std::uint16_t>((data[offset + 4] << OneOctetShift) | data[offset + 5]);
417 offset += 6;
418
419 // Stop on zero-filled sentinel entry
420 if (channelNum == 0 && chPropsRaw == 0 && errorType == 0)
421 {
422 break;
423 }
424
426 entry.usi = 0;
427 entry.base.api = api;
428 entry.base.slot = slot;
429 entry.base.subslot = subslot;
430 entry.base.channelNumber = channelNum;
432 entry.base.errorType = errorType;
433 entry.base.errorTypeName = DecodeChannelErrorType(errorType);
434 result.entries.push_back(entry);
435 }
436
437 return result;
438}
439
440} // namespace profinet::diagnosis
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.
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
Definition util.h:50
static constexpr int ThreeOctetsShift
The bit-shift distance required to move data across three octets.
Definition util.h:56
static constexpr int TwoOctetsShift
The bit-shift distance required to move data across two octets.
Definition util.h:53
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
Definition util.cpp:63
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:52
Any diagnosis entry (USI 0x8000, 0x8002, or 0x8003).
Definition diagnosis.h:237
std::uint16_t usi
The USI that determined which entry type was parsed.
Definition diagnosis.h:239
ChannelDiagnosis base
Common fields, always populated.
Definition diagnosis.h:244
std::optional< QualifiedChannelDiagnosis > qualified
Qualified fields, populated only for USI 0x8003.
Definition diagnosis.h:250
std::optional< ExtChannelDiagnosis > ext
Extended fields, populated only for USI 0x8002/0x8003.
Definition diagnosis.h:247
ChannelDiagnosis entry (USI 0x8000).
Definition diagnosis.h:173
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:181
std::uint16_t channelNumber
Channel number, with the accumulative bit packed into bit 15.
Definition diagnosis.h:184
std::string errorTypeName
Human-readable name for ErrorType.
Definition diagnosis.h:193
std::uint16_t slot
Slot number.
Definition diagnosis.h:178
std::uint16_t errorType
Channel error type code.
Definition diagnosis.h:190
ChannelProperties properties
Decoded channel properties.
Definition diagnosis.h:187
std::uint32_t api
API number.
Definition diagnosis.h:175
Parsed ChannelProperties word (16 bits).
Definition diagnosis.h:96
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:107
bool naintenanceDemanded
Maintenance demanded flag (bit 6).
Definition diagnosis.h:110
std::string ChannelTypeName() const
Human-readable name for ChannelTypeValue.
Definition diagnosis.cpp:46
ChannelAccumulative accumulative
Accumulative info (bits 2-4).
Definition diagnosis.h:104
ChannelType channelTypeValue
Channel type (bits 0-1).
Definition diagnosis.h:101
ChannelDirection direction
Data direction (bits 11-12).
Definition diagnosis.h:116
ChannelSpecifier specifier
Diagnosis specifier (bits 8-10).
Definition diagnosis.h:113
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:98
Complete diagnosis data for one slot/subslot.
Definition diagnosis.h:282
std::vector< AnyChannelDiagnosis > entries
Parsed diagnosis entries.
Definition diagnosis.h:293
Bytes rawData
Raw, undecoded source data.
Definition diagnosis.h:296
std::uint32_t api
API number.
Definition diagnosis.h:284
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:290
std::uint16_t slot
Slot number.
Definition diagnosis.h:287
ExtChannelDiagnosis entry (USI 0x8002).
Definition diagnosis.h:213
std::uint32_t extAddValue
Extended channel additional value.
Definition diagnosis.h:221
std::string extErrorTypeName
Human-readable name for ExtErrorType.
Definition diagnosis.h:218
std::uint16_t extErrorType
Extended channel error type code.
Definition diagnosis.h:215
QualifiedChannelDiagnosis entry (USI 0x8003).
Definition diagnosis.h:227
std::uint32_t qualifier
Qualifier bitmask providing additional severity/context info.
Definition diagnosis.h:229
Small helpers for reading/writing big-endian ("network order") integers to/from byte buffers....