16 static constexpr int minimumRtFrameSize = 6;
17 if (data.size() < minimumRtFrameSize)
19 throw std::runtime_error(
"RT frame too short: " + std::to_string(data.size()) +
" bytes");
27 constexpr std::size_t trailerLength =
sizeof(std::uint16_t) + (2 *
sizeof(std::uint8_t));
28 const std::size_t payloadLength = r.
Remaining() - trailerLength;
50 std::vector<std::string> flags;
53 flags.emplace_back(
"VALID");
57 flags.emplace_back(
"RUN");
61 flags.emplace_back(
"OK");
65 flags.emplace_back(
"PRIMARY");
68 for (std::size_t i = 0; i < flags.size(); ++i)
71 if (i + 1 < flags.size())
81 return std::format(
"RTFrame(id=0x{:04X}, cycle={}, status={}, payload={}B)",
87 const auto offset =
static_cast<std::size_t
>(
frameOffset);
89 if (offset > frameSize ||
length > frameSize - offset)
91 throw std::invalid_argument(
"IODataObject data range exceeds frame");
96 throw std::invalid_argument(
"IOPS offset exceeds frame");
101 throw std::invalid_argument(
"IOCS offset exceeds frame");
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)
110 for (
const auto&
object : this->config.
objects)
118 const std::lock_guard<std::mutex> lock(
writeLock);
122 if (
obj.slot == slot &&
obj.subslot == subslot)
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);
130 throw std::invalid_argument(
"Unknown slot/subslot: " + std::to_string(slot) +
"/" + std::to_string(subslot));
135 const std::lock_guard<std::mutex> lock(
writeLock);
139 if (
obj.slot == slot &&
obj.subslot == subslot)
146 throw std::invalid_argument(
"Unknown slot/subslot: " + std::to_string(slot) +
"/" + std::to_string(subslot));
151 const std::lock_guard<std::mutex> lock(
writeLock);
155 if (
obj.slot == slot &&
obj.subslot == subslot)
157 if (
obj.dataLength > 0 &&
obj.offsetIOPS.has_value())
159 writeBuffer.at(
static_cast<std::size_t
>(*
obj.offsetIOPS)) = status;
169 const std::lock_guard<std::mutex> lock(
writeLock);
173 if (
obj.slot == slot &&
obj.subslot == subslot)
175 if (
obj.offsetIOCS.has_value())
177 writeBuffer.at(
static_cast<std::size_t
>(*
obj.offsetIOCS)) = status;
187 const std::lock_guard<std::mutex> lock(
writeLock);
190 if (
obj.dataLength > 0 &&
obj.offsetIOPS.has_value())
192 writeBuffer.at(
static_cast<std::size_t
>(*
obj.offsetIOPS)) = status;
200 const std::lock_guard<std::mutex> lock(
writeLock);
203 if (
obj.offsetIOCS.has_value())
205 writeBuffer.at(
static_cast<std::size_t
>(*
obj.offsetIOCS)) = status;
213 const std::lock_guard<std::mutex> lock(
writeLock);
220 const std::lock_guard<std::mutex> lock(
writeLock);
230 const std::lock_guard<std::mutex> lock(
writeLock);
236 const std::lock_guard<std::mutex> lock(
writeLock);
245 out.insert(out.end(), dstMac.begin(), dstMac.end());
246 out.insert(out.end(), srcMac.begin(), srcMac.end());
257 out.insert(out.end(), frameBytes.begin(), frameBytes.end());
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;
268 if (data.size() < ethernetHeaderSize + minimumRtFrameSize)
272 std::size_t rtOffset = ethernetHeaderSize;
275 r.
SkipTo(ethernetTypeOffset);
276 auto ethertype = r.
U16();
280 if (data.size() < ethernetHeaderSize + vlanTagSize + minimumRtFrameSize)
285 rtOffset += vlanTagSize;
286 r.
Skip(
sizeof(std::uint16_t));
300 catch (
const std::exception&)
std::mutex writeLock
Guards writeBuffer against concurrent access.
Bytes GetData(int slot, int subslot) const
Read one object's data from the write buffer.
Bytes writeBuffer
Buffer the application thread writes into.
CyclicDataBuilder(IOCRConfig config)
Construct a builder for the given IOCR configuration.
void Clear()
Zero the entire write buffer.
IOCRConfig config
IOCR configuration describing the objects this builder manages.
void Load(const Bytes &payload)
Load received payload data into the write buffer.
void SetAllIocs(std::uint8_t status=IOXS_GOOD)
Set every object's IOCS byte to the same value.
void SetData(int slot, int subslot, const Bytes &data)
Write one object's data into the write buffer.
void SetIocs(int slot, int subslot, std::uint8_t status=IOXS_GOOD)
Set one object's IOCS (consumer status) byte, if it has one.
bool dirty
Whether writeBuffer has changed since the last Swap().
void SetIops(int slot, int subslot, std::uint8_t status=IOXS_GOOD)
Set one object's IOPS (provider status) byte.
Bytes sendBuffer
Buffer the TX thread reads from (updated only by Swap()).
void Swap()
Promote the write buffer to the send buffer.
Bytes Build() const
Get the current send buffer contents.
void SetAllIops(std::uint8_t status=IOXS_GOOD)
Set every object's IOPS byte to the same value.
Lightweight cursor for parsing a big-endian byte buffer without copies.
std::uint8_t U8()
Read and consume one byte.
std::size_t Remaining() const
Number of bytes not yet consumed.
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
void Skip(std::size_t n)
Advance the read position without returning the skipped bytes.
std::vector< std::uint8_t > ReadBytes(std::size_t n)
Read and consume a variable-length byte range.
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
std::optional< RTFrame > ParseEthernetFrame(const Bytes &data)
Parse a complete Ethernet frame and extract its RT frame.
constexpr std::uint16_t ETHERTYPE_VLAN
EtherType used by VLAN frames, 802.1Q VLAN TPID (0x8100).
Bytes BuildEthernetFrame(const MacAddress &dstMac, const MacAddress &srcMac, const RTFrame &rtFrame)
Build a complete Ethernet frame carrying an RT frame.
constexpr std::uint16_t ETHERTYPE_PROFINET
EtherType used by PROFINET RT frames (0x8892).
void PutBigEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in big-endian order.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
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 ...
std::vector< IODataObject > objects
IO data objects carried in this IOCR's cyclic frame.
std::uint16_t dataLength
Total cyclic data length in bytes.
std::uint16_t frameOffset
Byte offset of this object's data within the cyclic frame.
std::uint16_t dataLength
Length in bytes of this object's data.
std::optional< std::uint16_t > offsetIOPS
Byte offset of this object's IOPS (provider status) byte.
void Validate(std::size_t frameSize) const
Validate that this IO data object fits within a cyclic frame.
std::optional< std::uint16_t > offsetIOCS
Byte offset of this object's IOCS (consumer status) byte, or 0 if not applicable.
A single PROFINET Real-Time cyclic frame: Frame ID + C_SDU payload + cycle counter/status trailer.
std::uint8_t dataStatus
Data status bitmask (see DATA_STATUS_* constants).
std::uint8_t transferStatus
Transfer status (0 = OK).
static RTFrame FromBytes(const Bytes &data)
Parse an RT frame from raw bytes (after the Ethernet header).
Bytes ToBytes() const
Serialize this frame back to raw bytes.
std::uint16_t frameId
Frame ID identifying which IOCR this frame belongs to.
std::uint16_t cycleCounter
Cycle counter, incremented each transmission.
bool IsPrimary() const
Whether the DataStatus state bit indicates Primary.
bool IsOk() const
Whether the DataStatus station-OK bit is set.
Bytes payload
C_SDU payload: process data plus IOPS/IOCS trailers.
bool IsValid() const
Whether the DataStatus valid bit is set.
bool IsRunning() const
Whether the DataStatus provider-run bit is set.
std::string ToString() const
Human-readable summary of this frame, for logging/debugging.