PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
rt.cpp
Go to the documentation of this file.
1
3
4#include "profinet/rt.h"
5
6#include <algorithm>
7#include <cstdio>
8#include <ranges>
9#include <stdexcept>
10
11namespace profinet::rt
12{
13
15{
16 static constexpr int minimumRtFrameSize = 6;
17 if (data.size() < minimumRtFrameSize)
18 {
19 throw std::runtime_error("RT frame too short: " + std::to_string(data.size()) + " bytes");
20 }
21 wire::Reader r(data);
22
23 RTFrame f;
24 f.frameId = r.U16();
25
26 // trailing bytes are cyclecounter (2), a dataStatus (1) and transferStatus (1), in between is the payload
27 constexpr std::size_t trailerLength = sizeof(std::uint16_t) + (2 * sizeof(std::uint8_t));
28 const std::size_t payloadLength = r.Remaining() - trailerLength;
29 f.payload = r.ReadBytes(payloadLength);
30
31 f.cycleCounter = r.U16();
32 f.dataStatus = r.U8();
33 f.transferStatus = r.U8();
34 return f;
35}
36
38{
39 Bytes out;
41 out.insert(out.end(), payload.begin(), payload.end());
43 out.push_back(dataStatus);
44 out.push_back(transferStatus);
45 return out;
46}
47
48std::string RTFrame::ToString() const
49{
50 std::vector<std::string> flags;
51 if (IsValid())
52 {
53 flags.emplace_back("VALID");
54 }
55 if (IsRunning())
56 {
57 flags.emplace_back("RUN");
58 }
59 if (IsOk())
60 {
61 flags.emplace_back("OK");
62 }
63 if (IsPrimary())
64 {
65 flags.emplace_back("PRIMARY");
66 }
67 std::string flagsStr;
68 for (std::size_t i = 0; i < flags.size(); ++i)
69 {
70 flagsStr += flags[i];
71 if (i + 1 < flags.size())
72 {
73 flagsStr += "|";
74 }
75 }
76 if (flagsStr.empty())
77 {
78 flagsStr = "NONE";
79 }
80
81 return std::format("RTFrame(id=0x{:04X}, cycle={}, status={}, payload={}B)",
82 frameId, cycleCounter, flagsStr, payload.size());
83}
84
85void IODataObject::Validate(std::size_t frameSize) const
86{
87 const auto offset = static_cast<std::size_t>(frameOffset);
88 const auto length = static_cast<std::size_t>(dataLength);
89 if (offset > frameSize || length > frameSize - offset)
90 {
91 throw std::invalid_argument("IODataObject data range exceeds frame");
92 }
93
94 if (offsetIOPS.has_value() && *offsetIOPS >= frameSize)
95 {
96 throw std::invalid_argument("IOPS offset exceeds frame");
97 }
98
99 if (offsetIOCS.has_value() && *offsetIOCS >= frameSize)
100 {
101 throw std::invalid_argument("IOCS offset exceeds frame");
102 }
103}
104
106 : config(std::move(config)),
107 writeBuffer(static_cast<std::size_t>(this->config.dataLength), 0),
108 sendBuffer(static_cast<std::size_t>(this->config.dataLength), 0)
109{
110 for (const auto& object : this->config.objects)
111 {
112 object.Validate(this->config.dataLength);
113 }
114}
115
116void CyclicDataBuilder::SetData(int slot, int subslot, const Bytes& data)
117{
118 const std::lock_guard<std::mutex> lock(writeLock);
119
120 for (const auto& obj : config.objects)
121 {
122 if (obj.slot == slot && obj.subslot == subslot)
123 {
124 const std::size_t copyLen = std::min<std::size_t>(data.size(), static_cast<std::size_t>(obj.dataLength));
125 std::ranges::copy(data | std::views::take(copyLen), writeBuffer.begin() + obj.frameOffset);
126 dirty = true;
127 return;
128 }
129 }
130 throw std::invalid_argument("Unknown slot/subslot: " + std::to_string(slot) + "/" + std::to_string(subslot));
131}
132
133Bytes CyclicDataBuilder::GetData(int slot, int subslot) const
134{
135 const std::lock_guard<std::mutex> lock(writeLock);
136
137 for (const auto& obj : config.objects)
138 {
139 if (obj.slot == slot && obj.subslot == subslot)
140 {
141 // NOLINTNEXTLINE(modernize-return-braced-init-list)
142 return Bytes(writeBuffer.begin() + obj.frameOffset,
143 writeBuffer.begin() + obj.frameOffset + obj.dataLength);
144 }
145 }
146 throw std::invalid_argument("Unknown slot/subslot: " + std::to_string(slot) + "/" + std::to_string(subslot));
147}
148
149void CyclicDataBuilder::SetIops(int slot, int subslot, std::uint8_t status)
150{
151 const std::lock_guard<std::mutex> lock(writeLock);
152
153 for (const auto& obj : config.objects)
154 {
155 if (obj.slot == slot && obj.subslot == subslot)
156 {
157 if (obj.dataLength > 0 && obj.offsetIOPS.has_value())
158 {
159 writeBuffer.at(static_cast<std::size_t>(*obj.offsetIOPS)) = status;
160 dirty = true;
161 }
162 return;
163 }
164 }
165}
166
167void CyclicDataBuilder::SetIocs(int slot, int subslot, std::uint8_t status)
168{
169 const std::lock_guard<std::mutex> lock(writeLock);
170
171 for (const auto& obj : config.objects)
172 {
173 if (obj.slot == slot && obj.subslot == subslot)
174 {
175 if (obj.offsetIOCS.has_value())
176 {
177 writeBuffer.at(static_cast<std::size_t>(*obj.offsetIOCS)) = status;
178 dirty = true;
179 }
180 return;
181 }
182 }
183}
184
185void CyclicDataBuilder::SetAllIops(std::uint8_t status)
186{
187 const std::lock_guard<std::mutex> lock(writeLock);
188 for (const auto& obj : config.objects)
189 {
190 if (obj.dataLength > 0 && obj.offsetIOPS.has_value())
191 {
192 writeBuffer.at(static_cast<std::size_t>(*obj.offsetIOPS)) = status;
193 dirty = true;
194 }
195 }
196}
197
198void CyclicDataBuilder::SetAllIocs(std::uint8_t status)
199{
200 const std::lock_guard<std::mutex> lock(writeLock);
201 for (const auto& obj : config.objects)
202 {
203 if (obj.offsetIOCS.has_value())
204 {
205 writeBuffer.at(static_cast<std::size_t>(*obj.offsetIOCS)) = status;
206 }
207 }
208 dirty = true;
209}
210
212{
213 const std::lock_guard<std::mutex> lock(writeLock);
214 std::ranges::fill(writeBuffer, 0);
215 dirty = true;
216}
217
219{
220 const std::lock_guard<std::mutex> lock(writeLock);
221 if (dirty)
222 {
224 dirty = false;
225 }
226}
227
229{
230 const std::lock_guard<std::mutex> lock(writeLock);
231 return sendBuffer;
232}
233
235{
236 const std::lock_guard<std::mutex> lock(writeLock);
237 const std::size_t copyLen = std::min(payload.size(), writeBuffer.size());
238 std::ranges::copy(payload | std::views::take(copyLen), writeBuffer.begin());
239 dirty = true;
240}
241
242Bytes BuildEthernetFrame(const MacAddress& dstMac, const MacAddress& srcMac, const RTFrame& rtFrame)
243{
244 Bytes out;
245 out.insert(out.end(), dstMac.begin(), dstMac.end());
246 out.insert(out.end(), srcMac.begin(), srcMac.end());
247 // need to check for VLAN ?
248 // out.push_back(static_cast<std::uint8_t>(VLAN_TAG_RT >> ThreeOctetsShift));
249 // out.push_back(static_cast<std::uint8_t>(VLAN_TAG_RT >> TwoOctetsShift));
250 // out.push_back(static_cast<std::uint8_t>(VLAN_TAG_RT >> OneOctetShift));
251 // out.push_back(static_cast<std::uint8_t>(VLAN_TAG_RT & LowByteMask));
252 // out.push_back(static_cast<std::uint8_t>(ETHERTYPE_PROFINET >> OneOctetShift));
253 // out.push_back(static_cast<std::uint8_t>(ETHERTYPE_PROFINET & LowByteMask));
255
256 Bytes frameBytes = rtFrame.ToBytes();
257 out.insert(out.end(), frameBytes.begin(), frameBytes.end());
258 return out;
259}
260
261std::optional<RTFrame> ParseEthernetFrame(const Bytes& data)
262{
263 constexpr std::size_t ethernetHeaderSize = 14U;
264 constexpr std::size_t ethernetTypeOffset = 12U;
265 constexpr std::size_t vlanTagSize = 4U;
266 constexpr std::size_t minimumRtFrameSize = 6U;
267
268 if (data.size() < ethernetHeaderSize + minimumRtFrameSize)
269 {
270 return std::nullopt; // 14 (eth) + 4 (min RT)
271 }
272 std::size_t rtOffset = ethernetHeaderSize;
273
274 wire::Reader r(data);
275 r.SkipTo(ethernetTypeOffset);
276 auto ethertype = r.U16();
277
278 if (ethertype == ETHERTYPE_VLAN)
279 {
280 if (data.size() < ethernetHeaderSize + vlanTagSize + minimumRtFrameSize)
281 {
282 return std::nullopt;
283 }
284
285 rtOffset += vlanTagSize;
286 r.Skip(sizeof(std::uint16_t));
287
288 ethertype = r.U16();
289 }
290
291 if (ethertype != ETHERTYPE_PROFINET)
292 {
293 return std::nullopt;
294 }
295
296 try
297 {
298 return RTFrame::FromBytes(Bytes(data.begin() + rtOffset, data.end()));
299 }
300 catch (const std::exception&)
301 {
302 return std::nullopt;
303 }
304}
305
306} // namespace profinet::rt
std::mutex writeLock
Guards writeBuffer against concurrent access.
Definition rt.h:362
Bytes GetData(int slot, int subslot) const
Read one object's data from the write buffer.
Definition rt.cpp:133
Bytes writeBuffer
Buffer the application thread writes into.
Definition rt.h:356
CyclicDataBuilder(IOCRConfig config)
Construct a builder for the given IOCR configuration.
Definition rt.cpp:105
void Clear()
Zero the entire write buffer.
Definition rt.cpp:211
IOCRConfig config
IOCR configuration describing the objects this builder manages.
Definition rt.h:353
void Load(const Bytes &payload)
Load received payload data into the write buffer.
Definition rt.cpp:234
void SetAllIocs(std::uint8_t status=IOXS_GOOD)
Set every object's IOCS byte to the same value.
Definition rt.cpp:198
void SetData(int slot, int subslot, const Bytes &data)
Write one object's data into the write buffer.
Definition rt.cpp:116
void SetIocs(int slot, int subslot, std::uint8_t status=IOXS_GOOD)
Set one object's IOCS (consumer status) byte, if it has one.
Definition rt.cpp:167
bool dirty
Whether writeBuffer has changed since the last Swap().
Definition rt.h:365
void SetIops(int slot, int subslot, std::uint8_t status=IOXS_GOOD)
Set one object's IOPS (provider status) byte.
Definition rt.cpp:149
Bytes sendBuffer
Buffer the TX thread reads from (updated only by Swap()).
Definition rt.h:359
void Swap()
Promote the write buffer to the send buffer.
Definition rt.cpp:218
Bytes Build() const
Get the current send buffer contents.
Definition rt.cpp:228
void SetAllIops(std::uint8_t status=IOXS_GOOD)
Set every object's IOPS byte to the same value.
Definition rt.cpp:185
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::uint8_t U8()
Read and consume one byte.
Definition wire.h:250
std::size_t Remaining() const
Number of bytes not yet consumed.
Definition wire.h:218
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
Definition wire.h:343
void Skip(std::size_t n)
Advance the read position without returning the skipped bytes.
Definition wire.h:330
std::vector< std::uint8_t > ReadBytes(std::size_t n)
Read and consume a variable-length byte range.
Definition wire.h:304
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
xmlXPathObjectPtr obj
Definition gsdml.cpp:82
std::optional< RTFrame > ParseEthernetFrame(const Bytes &data)
Parse a complete Ethernet frame and extract its RT frame.
Definition rt.cpp:261
constexpr std::uint16_t ETHERTYPE_VLAN
EtherType used by VLAN frames, 802.1Q VLAN TPID (0x8100).
Definition rt.h:36
Bytes BuildEthernetFrame(const MacAddress &dstMac, const MacAddress &srcMac, const RTFrame &rtFrame)
Build a complete Ethernet frame carrying an RT frame.
Definition rt.cpp:242
constexpr std::uint16_t ETHERTYPE_PROFINET
EtherType used by PROFINET RT frames (0x8892).
Definition rt.h:34
void PutBigEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in big-endian order.
Definition wire.h:45
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
IOCR configuration derived from AR setup: timing parameters and IO object mappings needed for cyclic ...
Definition rt.h:137
std::vector< IODataObject > objects
IO data objects carried in this IOCR's cyclic frame.
Definition rt.h:163
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition rt.h:160
std::uint16_t frameOffset
Byte offset of this object's data within the cyclic frame.
Definition rt.h:117
std::uint16_t dataLength
Length in bytes of this object's data.
Definition rt.h:120
std::optional< std::uint16_t > offsetIOPS
Byte offset of this object's IOPS (provider status) byte.
Definition rt.h:123
void Validate(std::size_t frameSize) const
Validate that this IO data object fits within a cyclic frame.
Definition rt.cpp:85
std::optional< std::uint16_t > offsetIOCS
Byte offset of this object's IOCS (consumer status) byte, or 0 if not applicable.
Definition rt.h:126
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
Definition rt.h:208
std::uint8_t dataStatus
Data status bitmask (see DATA_STATUS_* constants).
Definition rt.h:216
std::uint8_t transferStatus
Transfer status (0 = OK).
Definition rt.h:219
static RTFrame FromBytes(const Bytes &data)
Parse an RT frame from raw bytes (after the Ethernet header).
Definition rt.cpp:14
Bytes ToBytes() const
Serialize this frame back to raw bytes.
Definition rt.cpp:37
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
Definition rt.h:210
std::uint16_t cycleCounter
Cycle counter, incremented each transmission.
Definition rt.h:213
bool IsPrimary() const
Whether the DataStatus state bit indicates Primary.
Definition rt.h:271
bool IsOk() const
Whether the DataStatus station-OK bit is set.
Definition rt.h:264
Bytes payload
C_SDU payload: process data plus IOPS/IOCS trailers.
Definition rt.h:222
bool IsValid() const
Whether the DataStatus valid bit is set.
Definition rt.h:250
bool IsRunning() const
Whether the DataStatus provider-run bit is set.
Definition rt.h:257
std::string ToString() const
Human-readable summary of this frame, for logging/debugging.
Definition rt.cpp:48
Bytes payload
Definition test_dcp.cpp:392
std::uint16_t length
Definition test_dcp.cpp:391