PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
alarms.cpp
Go to the documentation of this file.
1
3
4#include "profinet/alarms.h"
5
6#include <cstdio>
7#include <map>
8
9#include "profinet/wire.h"
10
11namespace profinet
12{
13
14namespace
15{
16/*
17const std::map<std::uint16_t, std::string>& UsiNames()
18{
19 static const std::map<std::uint16_t, std::string> table = {
20 {USI_CHANNEL_DIAGNOSIS, "ChannelDiagnosis"},
21 {USI_MULTIPLE_DIAGNOSIS, "MultipleDiagnosis"},
22 {USI_EXT_CHANNEL_DIAGNOSIS, "ExtChannelDiagnosis"},
23 {USI_QUALIFIED_CHANNEL_DIAGNOSIS, "QualifiedChannelDiagnosis"},
24 {USI_MAINTENANCE, "MaintenanceItem"},
25 {USI_UPLOAD, "UploadRecord"},
26 {USI_IPARAMETER, "iParameterItem"},
27 {USI_RS_ALARM_LOW, "RS_AlarmItem_Low"},
28 {USI_RS_ALARM_HIGH, "RS_AlarmItem_High"},
29 {USI_RS_ALARM_SUBMODULE, "RS_AlarmItem_Submodule"},
30 {USI_PE_ALARM, "PE_AlarmItem"},
31 {USI_PRAL_ALARM, "PRAL_AlarmItem"},
32 };
33 return table;
34}
35*/
36/*
37const std::map<std::uint16_t, std::string>& AlarmTypeNames()
38{
39 static const std::map<std::uint16_t, std::string> table = {
40 {0x0001, "Diagnosis"},
41 {0x0002, "Process"},
42 {0x0003, "Pull"},
43 {0x0004, "Plug"},
44 {0x0005, "Status"},
45 {0x0006, "Update"},
46 {0x0007, "Redundancy"},
47 {0x0008, "ControlledBySupervisor"},
48 {0x0009, "Released"},
49 {0x000A, "PlugWrongSubmodule"},
50 {0x000B, "ReturnOfSubmodule"},
51 {0x000C, "DiagnosisDisappears"},
52 {0x000D, "MulticastMismatch"},
53 {0x000E, "PortDataChange"},
54 {0x000F, "SyncDataChanged"},
55 {0x0010, "IsochronousModeProblem"},
56 {0x0011, "NetworkComponentProblem"},
57 {0x0012, "TimeDataChanged"},
58 {0x0013, "DynamicFramePackingProblem"},
59 {0x001E, "UploadAndRetrieval"},
60 {0x001F, "PullModule"},
61 };
62 return table;
63}
64*/
65} // namespace
66
68{
69 return std::visit([](const auto& i)
70 { return i.userStructureId; }, item);
71}
72
73std::string UsiNameOf(const AlarmItemVariant& item)
74{
75 return std::visit([](const auto& i)
76 { return i.UsiName(); }, item);
77}
78
79namespace
80{
81
82std::pair<DiagnosisItem, std::size_t> ParseDiagnosisItem(const Bytes& data, std::size_t offset, Usi usi)
83{
84 if (data.size() < offset + 6)
85 {
86 throw std::invalid_argument("Truncated DiagnosisItem");
87 }
88
89 DiagnosisItem item;
90 item.userStructureId = usi;
91 item.channelNumber = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
92 item.channelProperties = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
93 item.channelErrorType = static_cast<std::uint16_t>((data[offset + 4] << OneOctetShift) | data[offset + 5]);
94 offset += 6;
95
97 {
98 if (data.size() < offset + 6)
99 {
100 throw std::invalid_argument("Truncated ExtChannelDiagnosis");
101 }
102 item.extChannelErrorType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
103 item.extChannelAddValue = static_cast<std::uint32_t>(data[offset + 2]) << ThreeOctetsShift |
104 static_cast<std::uint32_t>(data[offset + 3]) << TwoOctetsShift |
105 static_cast<std::uint32_t>(data[offset + 4]) << OneOctetShift | data[offset + 5];
106 offset += 6;
107 }
108
110 {
111 if (data.size() < offset + 4)
112 {
113 throw std::invalid_argument("Truncated QualifiedChannelDiagnosis");
114 }
115 item.qualifiedChannelQualifier = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift |
116 static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
117 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
118 offset += 4;
119 }
120
121 return {item, offset};
122}
123
124std::pair<MaintenanceItem, std::size_t> ParseMaintenanceItem(const Bytes& data, std::size_t offset)
125{
126 if (data.size() < offset + 12)
127 {
128 throw std::invalid_argument("Truncated MaintenanceItem");
129 }
130 const PNBlockHeader hdr = PNBlockHeader::Parse(Bytes(data.begin() + offset, data.begin() + offset + 6));
131 offset += 6 + 2; // + 2-byte padding
132
133 const std::uint32_t status = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift |
134 static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
135 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
136 offset += 4;
137
138 MaintenanceItem item;
139 item.userStructureId = Usi::Maintenance;
140 item.blockType = hdr.blockType;
141 item.blockLength = hdr.blockLength;
142 item.blockVersion = static_cast<std::uint16_t>((hdr.blockVersionHigh << OneOctetShift) | hdr.blockVersionLow);
143 item.maintenanceStatus = status;
144 return {item, offset};
145}
146
147std::pair<UploadRetrievalItem, std::size_t> ParseUploadRetrievalItem(const Bytes& data, std::size_t offset, Usi usi)
148{
149 if (data.size() < offset + 16)
150 {
151 throw std::invalid_argument("Truncated UploadRetrievalItem");
152 }
153 const PNBlockHeader hdr = PNBlockHeader::Parse(Bytes(data.begin() + offset, data.begin() + offset + 6));
154 offset += 6 + 2; // + 2-byte padding
155
156 const std::uint32_t index = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift |
157 static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
158 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
159 offset += 4;
160 const std::uint32_t length = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift |
161 static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
162 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
163 offset += 4;
164
165 UploadRetrievalItem item;
166 item.userStructureId = usi;
167 item.blockType = hdr.blockType;
168 item.blockLength = hdr.blockLength;
169 item.blockVersion = static_cast<std::uint16_t>((hdr.blockVersionHigh << OneOctetShift) | hdr.blockVersionLow);
170 item.urRecordIndex = index;
171 item.urRecordLength = length;
172 return {item, offset};
173}
174
175std::pair<PEAlarmItem, std::size_t> ParsePeAlarmItem(const Bytes& data, std::size_t offset)
176{
177 if (data.size() < offset + 7)
178 {
179 throw std::invalid_argument("Truncated PE_AlarmItem");
180 }
181 const PNBlockHeader hdr = PNBlockHeader::Parse(Bytes(data.begin() + offset, data.begin() + offset + 6));
182 offset += 6;
183 const std::uint8_t mode = data[offset];
184 offset += 1;
185
186 PEAlarmItem item;
187 item.userStructureId = Usi::PeAlarm;
188 item.blockType = hdr.blockType;
189 item.blockLength = hdr.blockLength;
190 item.blockVersion = static_cast<std::uint16_t>((hdr.blockVersionHigh << OneOctetShift) | hdr.blockVersionLow);
191 item.peOperationalMode = mode;
192 return {item, offset};
193}
194
195std::pair<RSAlarmItem, std::size_t> ParseRsAlarmItem(const Bytes& data, std::size_t offset, Usi usi)
196{
197 if (data.size() < offset + 2)
198 {
199 throw std::invalid_argument("Truncated RS_AlarmItem");
200 }
201 const auto info = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
202 offset += 2;
203
204 RSAlarmItem item;
205 item.userStructureId = usi;
206 item.rsAlarmInfo = info;
207 return {item, offset};
208}
209
210std::pair<PRALAlarmItem, std::size_t> ParsePralAlarmItem(const Bytes& data, std::size_t offset)
211{
212 if (data.size() < offset + 8)
213 {
214 throw std::invalid_argument("Truncated PRAL_AlarmItem");
215 }
216 const auto channelNum = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
217 const auto channelProps = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
218 const auto reason = static_cast<std::uint16_t>((data[offset + 4] << OneOctetShift) | data[offset + 5]);
219 const auto extReason = static_cast<std::uint16_t>((data[offset + 6] << OneOctetShift) | data[offset + 7]);
220 offset += 8;
221
222 PRALAlarmItem item;
223 item.userStructureId = Usi::PralAlarm;
224 item.channelNumber = channelNum;
225 item.propAlChannelProperties = channelProps;
226 item.prAlReason = reason;
227 item.prAlExtReason = extReason;
228 item.prAlReasonAddValue = Bytes(data.begin() + offset, data.end());
229 return {item, data.size()};
230}
231
232} // namespace
233
234std::pair<AlarmItemVariant, std::size_t> ParseAlarmItem(const Bytes& data, std::size_t offset)
235{
236 if (data.size() < offset + 2)
237 {
238 throw std::invalid_argument("Insufficient data for USI");
239 }
240
241 const auto usi = static_cast<Usi>((data[offset] << OneOctetShift) | data[offset + 1]);
242 offset += 2;
243
245 {
246 auto [item, new_offset] = ParseDiagnosisItem(data, offset, usi);
247 return {item, new_offset};
248 }
249 if (usi == Usi::Maintenance)
250 {
251 auto [item, new_offset] = ParseMaintenanceItem(data, offset);
252 return {item, new_offset};
253 }
254 if (usi == Usi::Upload || usi == Usi::IParameter)
255 {
256 auto [item, new_offset] = ParseUploadRetrievalItem(data, offset, usi);
257 return {item, new_offset};
258 }
259 if (usi == Usi::RsAlarmLow || usi == Usi::RsAlarmHigh || usi == Usi::RsAlarmSubmodule)
260 {
261 auto [item, new_offset] = ParseRsAlarmItem(data, offset, usi);
262 return {item, new_offset};
263 }
264 if (usi == Usi::PeAlarm)
265 {
266 auto [item, new_offset] = ParsePeAlarmItem(data, offset);
267 return {item, new_offset};
268 }
269 if (usi == Usi::PralAlarm)
270 {
271 auto [item, new_offset] = ParsePralAlarmItem(data, offset);
272 return {item, new_offset};
273 }
274
275 AlarmItem generic;
276 generic.userStructureId = usi;
277 generic.rawData = Bytes(data.begin() + offset, data.end());
278 return {generic, data.size()};
279}
280
282{
283 return std::format("{}:{}:0x{:04X}", api, slotNumber, subslotNumber);
284}
285
287{
288 // Minimum: BlockHeader(6) + Body(20). Upstream alarms.py checks for 28
289 // and then advances its read offset by 22 after this body, but the
290 // body struct it actually parses (alarm_type+api+slot+subslot+
291 // module_ident+submodule_ident+alarm_specifier = 2+4+2+2+4+4+2) is only
292 // 20 bytes — a real off-by-2 bug that misaligns every item after the
293 // first. Fixed here to the structurally correct 26 total / 20 body, per
294 // IEC 61158-6-10's AlarmNotification-PDU layout (also confirmed against
295 // protocol.h's PNAlarmNotificationPDU::kFixedSize == 26, itself
296 // verified against the real profinet-py module's fmt_size).
297
298 // ralph update:
299 // AlarmNotification body size in bytes (IEC 61158-6-10): AlarmType(2) + API(4) +
300 // SlotNumber(2) + SubslotNumber(2) + ModuleIdent(4) + SubmoduleIdent(4) +
301 // AlarmSpecifier(2). The block header (6) precedes it, so the item-less PDU is
302 // 26 bytes and BlockLength reads 22(bytes following the BlockLength field).
303 if (data.size() < 26)
304 {
305 throw std::invalid_argument("AlarmNotification too short");
306 }
307
308 std::size_t offset = 0;
309 const PNBlockHeader hdr = PNBlockHeader::Parse(Bytes(data.begin(), data.begin() + 6));
310 offset += 6;
311
312 wire::Reader r(data.data() + offset, data.size() - offset);
313 const auto alarmType = static_cast<AlarmType>(r.U16());
314 const std::uint32_t api = r.U32();
315 const std::uint16_t slotNumber = r.U16();
316 const std::uint16_t subslotNumber = r.U16();
317 const std::uint32_t moduleIdent = r.U32();
318 const std::uint32_t submoduleIdent = r.U32();
319 const std::uint16_t alarmSpecifier = r.U16();
320 offset += 20;
321
322 AlarmNotification notification;
323 notification.blockType = hdr.blockType;
324 notification.blockVersion = {hdr.blockVersionHigh, hdr.blockVersionLow};
325 notification.alarmType = alarmType;
326 notification.api = api;
327 notification.slotNumber = slotNumber;
328 notification.subslotNumber = subslotNumber;
329 notification.moduleIdentNumber = moduleIdent;
330 notification.submoduleIdentNumber = submoduleIdent;
331 notification.alarmSequenceNumber = alarmSpecifier & 0x07FF;
332 notification.channelDiagnosis = (alarmSpecifier & 0x0800) != 0;
333 notification.manufacturerSpecific = (alarmSpecifier & 0x1000) != 0;
334 notification.submoduleDiagnosisState = (alarmSpecifier & 0x2000) != 0;
335 notification.arDiagnosisState = (alarmSpecifier & 0x8000) != 0; // dit zou ook 0x8000 (bit 15) kunnen zijn, bit 14 is gereserveerd.
336
337 const std::size_t blockBodyLength = hdr.blockLength;
338 constexpr std::size_t blockVersionSize = 2;
339 constexpr std::size_t alarmBodySize = 20;
340
341 if (blockBodyLength < blockVersionSize + alarmBodySize)
342 {
343 throw std::invalid_argument("Invalid AlarmNotification BlockLength");
344 }
345
346 const std::size_t blockEnd = 4U + blockBodyLength;
347
348 if (data.size() < blockEnd)
349 {
350 throw std::invalid_argument("Truncated AlarmNotification block");
351 }
352
353 notification.rawPayload = Bytes(data.begin() + offset, data.begin() + blockEnd);
354
355 // notification.rawPayload = Bytes(data.begin() + offset, data.end());
356
357 std::size_t itemOffset = 0;
358 const Bytes& payload = notification.rawPayload;
359 while (itemOffset < payload.size())
360 {
361 try
362 {
363 auto [item, new_offset] = ParseAlarmItem(payload, itemOffset);
364 notification.items.push_back(std::move(item));
365 itemOffset = new_offset;
366 }
367 catch (const std::invalid_argument&)
368 {
369 break;
370 }
371 }
372
373 return notification;
374}
375
376} // namespace profinet
Declares PROFINET alarm, diagnosis, maintenance, and USI payload types and parsers.
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:67
std::uint32_t U32()
Read and consume a big-endian 32-bit value.
Definition wire.h:143
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:133
std::pair< AlarmItemVariant, std::size_t > ParseAlarmItem(const Bytes &data, std::size_t offset=0)
Parse a single alarm item starting at an offset.
Definition alarms.cpp:234
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
Definition alarms.cpp:286
AlarmType
PROFINET Alarm Types used in AlarmNotification blocks.
Definition indices.h:344
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 UsiNameOf(const AlarmItemVariant &item)
Get the human-readable USI name for any AlarmItemVariant alternative.
Definition alarms.cpp:73
Usi
PROFINET User Structure Identifiers (USI).
Definition indices.h:491
@ Maintenance
Maintenance item.
@ IParameter
iParameter item.
@ PralAlarm
PRAL alarm item.
@ Upload
Upload record.
@ ExtendedChannelDiagnosis
Extended channel diagnosis.
@ PeAlarm
PE alarm item.
@ ChannelDiagnosis
Channel diagnosis.
@ RsAlarmHigh
RS alarm item for high alarm.
@ RsAlarmSubmodule
RS alarm item for submodule.
@ RsAlarmLow
RS alarm item for low alarm.
@ 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:52
Usi UsiOf(const AlarmItemVariant &item)
Get the USI shared by every AlarmItemVariant alternative.
Definition alarms.cpp:67
std::variant< AlarmItem, DiagnosisItem, MaintenanceItem, UploadRetrievalItem, PEAlarmItem, RSAlarmItem, PRALAlarmItem > AlarmItemVariant
A parsed alarm payload item; the held alternative is selected by USI.
Definition alarms.h:336
Bytes payload
Definition rpc.cpp:3254
Generic/unknown alarm item.
Definition alarms.h:30
Usi userStructureId
User Structure Identifier for this item.
Definition alarms.h:32
Complete parsed AlarmNotification PDU: header fields + parsed items.
Definition alarms.h:358
bool manufacturerSpecific
Whether the alarm specifier indicates a manufacturer-specific diagnosis.
Definition alarms.h:390
std::vector< AlarmItemVariant > items
Parsed alarm items carried by this notification.
Definition alarms.h:399
std::uint16_t slotNumber
Slot number where the alarm originated.
Definition alarms.h:372
std::uint16_t alarmSequenceNumber
Sequence number extracted from the alarm specifier.
Definition alarms.h:384
BlockType blockType
Block type (high or low priority notification).
Definition alarms.h:360
std::uint32_t api
API number.
Definition alarms.h:369
Bytes rawPayload
Raw, undecoded payload the items were parsed from.
Definition alarms.h:402
std::string Location() const
Location string identifying where the alarm originated.
Definition alarms.cpp:281
AlarmType alarmType
Alarm type (Diagnosis/Process/Pull/Plug/...).
Definition alarms.h:366
std::pair< std::uint8_t, std::uint8_t > blockVersion
Block format version.
Definition alarms.h:363
std::uint32_t submoduleIdentNumber
Submodule ident number of the affected submodule.
Definition alarms.h:381
std::uint16_t subslotNumber
Subslot number where the alarm originated.
Definition alarms.h:375
bool channelDiagnosis
Whether the alarm specifier indicates channel diagnosis is present.
Definition alarms.h:387
std::uint32_t moduleIdentNumber
Module ident number of the affected module.
Definition alarms.h:378
bool arDiagnosisState
Whether the alarm specifier indicates AR diagnosis state.
Definition alarms.h:396
bool submoduleDiagnosisState
Whether the alarm specifier indicates submodule diagnosis state.
Definition alarms.h:393
BlockType blockType
Block type identifier.
Definition protocol.h:872
static PNBlockHeader Parse(const Bytes &data)
Parse a block header from raw bytes.
Definition protocol.h:889
std::uint8_t blockVersionHigh
Block format major version.
Definition protocol.h:878
std::uint8_t blockVersionLow
Block format minor version.
Definition protocol.h:881
std::uint16_t blockLength
Length in bytes of the block body (excludes Type/Length fields).
Definition protocol.h:875
Small helpers for reading/writing big-endian ("network order") integers to/from byte buffers....