14#include <netinet/in.h>
15#include <sys/socket.h>
42std::array<std::uint8_t, uuidLenght> RandomUuidBytes()
44 static thread_local std::mt19937 rng{std::random_device{}()};
45 static constexpr int maxByteValue = 255;
46 static thread_local std::uniform_int_distribution<int> dist(0, maxByteValue);
47 std::array<std::uint8_t, uuidLenght> out{};
50 b =
static_cast<std::uint8_t
>(dist(rng));
55std::uint16_t RandomNonzeroU16()
57 static thread_local std::mt19937 rng{std::random_device{}()};
59 static constexpr int maxU16Value = 0xFFFF;
60 static thread_local std::uniform_int_distribution<int> dist(0, maxU16Value);
61 const int v = dist(rng);
62 return static_cast<std::uint16_t
>(v == 0 ? 1 : v);
65std::array<std::uint8_t, uuidLenght> BuildObjectUuid(std::uint8_t b10, std::uint8_t b11, std::uint8_t b12, std::uint8_t b13,
66 std::uint8_t b14, std::uint8_t b15)
72 static constexpr std::uint8_t pnPrefixByte0 = 0xDE;
73 static constexpr std::uint8_t pnPrefixByte1 = 0xA0;
74 static constexpr std::uint8_t pnPrefixByte2 = 0x00;
75 static constexpr std::uint8_t pnPrefixByte3 = 0x00;
78 static constexpr std::size_t uuidIdxTimeLow0 = 0;
79 static constexpr std::size_t uuidIdxTimeLow1 = 1;
80 static constexpr std::size_t uuidIdxTimeLow2 = 2;
81 static constexpr std::size_t uuidIdxTimeLow3 = 3;
83 static constexpr std::size_t uuidIdxTimeMid0 = 4;
84 static constexpr std::size_t suffixBytesToCopy = 6;
86 static constexpr std::size_t uuidIdxNode0 = 10;
87 static constexpr std::size_t uuidIdxNode1 = 11;
88 static constexpr std::size_t uuidIdxNode2 = 12;
89 static constexpr std::size_t uuidIdxNode3 = 13;
90 static constexpr std::size_t uuidIdxNode4 = 14;
91 static constexpr std::size_t uuidIdxNode5 = 15;
93 std::array<std::uint8_t, uuidLenght>
objectUuid{};
102 std::ranges::copy(
kPnUuidSuffix | std::views::take(suffixBytesToCopy),
103 (
objectUuid | std::views::drop(uuidIdxTimeMid0)).begin());
125 static constexpr std::size_t timeLow0 = 0;
126 static constexpr std::size_t timeLow1 = 1;
127 static constexpr std::size_t timeLow2 = 2;
128 static constexpr std::size_t timeLow3 = 3;
130 static constexpr std::size_t timeMid0 = 4;
131 static constexpr std::size_t timeMid1 = 5;
133 static constexpr std::size_t timeHi0 = 6;
134 static constexpr std::size_t timeHi1 = 7;
136 static constexpr std::size_t clockSeq0 = 8;
137 static constexpr std::size_t clockSeq1 = 9;
139 static constexpr std::size_t nodeDataStartIndex = 10;
140 static constexpr int nodeDataLength = 6;
143 const std::uint32_t timeLow =
static_cast<std::uint32_t
>(data[timeLow0]) |
144 (
static_cast<std::uint32_t
>(data[timeLow1]) <<
OneOctetShift) |
148 auto timeMid =
static_cast<std::uint16_t
>(data[timeMid0] | (data[timeMid1] <<
OneOctetShift));
149 auto timeHi =
static_cast<std::uint16_t
>(data[timeHi0] | (data[timeHi1] <<
OneOctetShift));
150 auto clockSeq =
static_cast<std::uint16_t
>((data[clockSeq0] <<
OneOctetShift) | data[clockSeq1]);
153 const std::string out = std::format(
"{:08x}-{:04x}-{:04x}-{:04x}-{}",
154 timeLow, timeMid, timeHi, clockSeq,
155 ToHex(&data[nodeDataStartIndex], nodeDataLength));
162 static constexpr std::size_t expectedHexLength = 32;
163 static constexpr int hexBase = 16;
164 static constexpr std::size_t singleByteHexLength = 2;
167 static constexpr std::size_t timeLowOffset = 0;
168 static constexpr std::size_t timeLowLength = 8;
170 static constexpr std::size_t timeMidOffset = 8;
171 static constexpr std::size_t timeMidLength = 4;
173 static constexpr std::size_t timeHiOffset = 12;
174 static constexpr std::size_t timeHiLength = 4;
176 static constexpr std::size_t clockSeqOffset = 16;
177 static constexpr std::size_t clockSeqLength = 4;
180 static constexpr std::size_t timeLow0 = 0;
181 static constexpr std::size_t timeLow1 = 1;
182 static constexpr std::size_t timeLow2 = 2;
183 static constexpr std::size_t timeLow3 = 3;
185 static constexpr std::size_t timeMid0 = 4;
186 static constexpr std::size_t timeMid1 = 5;
188 static constexpr std::size_t timeHi0 = 6;
189 static constexpr std::size_t timeHi1 = 7;
191 static constexpr std::size_t clockSeq0 = 8;
192 static constexpr std::size_t clockSeq1 = 9;
195 static constexpr int nodeDataLength = 6;
196 static constexpr std::size_t nodeDataStartHexOffset = 20;
197 static constexpr std::size_t nodeDataOutputStartIndex = 10;
200 hex.reserve(expectedHexLength);
201 for (
const char c : uuidStr)
208 if (hex.size() != expectedHexLength)
210 throw std::invalid_argument(
"Invalid UUID string: " + uuidStr);
214 auto byteAt = [&](std::size_t stringIdx) -> std::uint8_t
216 return static_cast<std::uint8_t
>(std::stoul(hex.substr(stringIdx, singleByteHexLength),
nullptr, hexBase));
219 const std::uint32_t timeLow =
static_cast<std::uint32_t
>(std::stoul(hex.substr(timeLowOffset, timeLowLength),
nullptr, hexBase));
220 const std::uint16_t timeMid =
static_cast<std::uint16_t
>(std::stoul(hex.substr(timeMidOffset, timeMidLength),
nullptr, hexBase));
221 const std::uint16_t timeHi =
static_cast<std::uint16_t
>(std::stoul(hex.substr(timeHiOffset, timeHiLength),
nullptr, hexBase));
222 const std::uint16_t clockSeq =
static_cast<std::uint16_t
>(std::stoul(hex.substr(clockSeqOffset, clockSeqLength),
nullptr, hexBase));
224 std::array<std::uint8_t, uuidLenght> out{};
226 out[timeLow0] =
static_cast<std::uint8_t
>(timeLow &
LowByteMask);
230 out[timeMid0] =
static_cast<std::uint8_t
>(timeMid &
LowByteMask);
232 out[timeHi0] =
static_cast<std::uint8_t
>(timeHi &
LowByteMask);
235 out[clockSeq1] =
static_cast<std::uint8_t
>(clockSeq &
LowByteMask);
238 for (std::size_t i = 0; i < nodeDataLength; ++i)
240 out.at(nodeDataOutputStartIndex + i) = byteAt(nodeDataStartHexOffset + (i * singleByteHexLength));
252 std::ranges::transform(lower, lower.begin(), ::tolower);
256 return "PNIO-Device";
260 return "PNIO-Controller";
262 if (lower ==
"dea00003-6c97-11d1-8271-00a02442df7d")
264 return "PNIO-Supervisor";
266 if (lower ==
"dea00004-6c97-11d1-8271-00a02442df7d")
268 return "PNIO-ParameterServer";
280std::optional<EPMEndpoint> ParseEpmTower(
const Bytes& towerData)
282 if (towerData.size() < 4)
287 std::size_t offset = 0;
288 auto floorCount =
static_cast<std::uint16_t
>(towerData[0] | (towerData[1] <<
OneOctetShift));
292 EPMEndpoint endpoint;
294 for (std::uint16_t floorIdx = 0; floorIdx < floorCount; ++floorIdx)
296 if (offset + 4 > towerData.size())
301 auto lhsLen =
static_cast<std::uint16_t
>(towerData[offset] | (towerData[offset + 1] <<
OneOctetShift));
303 if (offset + lhsLen > towerData.size())
307 const std::uint8_t* lhsData = &towerData[offset];
310 if (offset + 2 > towerData.size())
314 auto rhsLen =
static_cast<std::uint16_t
>(towerData[offset] | (towerData[offset + 1] <<
OneOctetShift));
316 if (offset + rhsLen > towerData.size())
320 const std::uint8_t* rhsData = &towerData[offset];
327 const std::uint8_t protocolId = lhsData[0];
329 if (protocolId == 0x0D && lhsLen >= 19)
333 std::array<std::uint8_t, uuidLenght> uuidBytes{};
334 std::memcpy(uuidBytes.data(), lhsData + 1,
uuidLenght);
336 endpoint.interfaceVersionMajor =
static_cast<std::uint16_t
>(lhsData[17] | (lhsData[18] <<
OneOctetShift));
339 endpoint.interfaceVersionMinor =
static_cast<std::uint16_t
>(rhsData[0] | (rhsData[1] <<
OneOctetShift));
343 else if (protocolId == 0x0A)
345 endpoint.protocol =
"ncadg_ip_udp";
347 else if (protocolId == 0x08 && rhsLen >= 2)
349 endpoint.port =
static_cast<std::uint16_t
>((rhsData[0] <<
OneOctetShift) | rhsData[1]);
351 else if (protocolId == 0x09 && rhsLen >= 4)
353 endpoint.ipAddress = std::to_string(rhsData[0]) +
"." + std::to_string(rhsData[1]) +
"." +
354 std::to_string(rhsData[2]) +
"." + std::to_string(rhsData[3]);
358 if (endpoint.interfaceUuid.empty())
365std::uint32_t ReadU32Le(
const std::uint8_t* p)
367 return static_cast<std::uint32_t
>(p[0]) | (
static_cast<std::uint32_t
>(p[1]) <<
OneOctetShift) |
370std::uint16_t ReadU16Le(
const std::uint8_t* p)
372 return static_cast<std::uint16_t
>(p[0] | (p[1] <<
OneOctetShift));
374void WriteU16Le(
Bytes& out, std::uint16_t v)
376 out.push_back(
static_cast<std::uint8_t
>(v &
LowByteMask));
377 out.push_back(
static_cast<std::uint8_t
>(v >>
OneOctetShift));
379void WriteU32Le(
Bytes& out, std::uint32_t v)
381 out.push_back(
static_cast<std::uint8_t
>(v &
LowByteMask));
389std::vector<EPMEndpoint>
EpmLookup(asio::io_context& ioContext,
const std::string& ip, std::uint16_t port,
double timeoutSec,
390 std::optional<std::string> interfaceFilter)
392 std::vector<EPMEndpoint> results;
416 transport.
Open(ip, port);
419 auto activityUuidArr = RandomUuidBytes();
420 Bytes activityUuidBytes(activityUuidArr.begin(), activityUuidArr.end());
421 auto objectUuidBytes = std::array<std::uint8_t, uuidLenght>{};
427 header.push_back(0x20);
428 header.push_back(0x00);
429 header.push_back(0x10);
430 header.push_back(0x00);
431 header.push_back(0x00);
433 header.insert(header.end(), objectUuidBytes.begin(), objectUuidBytes.end());
434 header.insert(header.end(), interfaceUuidBytes.begin(), interfaceUuidBytes.end());
435 header.insert(header.end(), activityUuidBytes.begin(), activityUuidBytes.end());
436 WriteU32Le(header, 0);
437 WriteU32Le(header, 3);
438 WriteU32Le(header, 0);
440 WriteU16Le(header, 0xFFFF);
441 WriteU16Le(header, 0xFFFF);
442 const std::size_t lengthOfBodyOffset = header.size();
443 WriteU16Le(header, 0);
444 WriteU16Le(header, 0);
450 WriteU32Le(body, inquiryType);
454 body.insert(body.end(), ifaceBytes.begin(), ifaceBytes.end());
460 body.insert(body.end(), 16, 0);
466 WriteU32Le(body, 100);
468 header[lengthOfBodyOffset] =
static_cast<std::uint8_t
>(body.size() &
LowByteMask);
473 Bytes request = header;
474 request.insert(request.end(), body.begin(), body.end());
481 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
485 timeoutInMilliSeconds);
501 const std::uint8_t respType = data[1];
502 if (respType == 0x03 || respType != 0x02)
507 std::uint16_t bodyLen = ReadU16Le(&data[74]);
513 if (bodyData.size() < 12)
518 std::size_t offset = 4;
519 const std::uint32_t numEnts = ReadU32Le(&bodyData[offset]);
523 for (std::uint32_t i = 0; i < numEnts; ++i)
529 std::array<std::uint8_t, uuidLenght> entryUuidBytes{};
530 std::memcpy(entryUuidBytes.data(), &bodyData[offset],
uuidLenght);
534 if (offset + 4 > bodyData.size())
540 if (offset + 4 > bodyData.size())
544 const std::uint32_t annotationLen = ReadU32Le(&bodyData[offset]);
547 std::string annotation;
548 if (annotationLen > 0 && offset + annotationLen <= bodyData.size())
550 const std::size_t end = offset + annotationLen;
551 std::size_t trim = end;
552 while (trim > offset && bodyData[trim - 1] == 0)
556 annotation = std::string(bodyData.begin() + offset, bodyData.begin() + trim);
558 offset += annotationLen;
559 offset = (offset + 3) & ~
static_cast<std::size_t
>(3);
561 if (offset + 4 > bodyData.size())
565 const std::uint32_t towerLen = ReadU32Le(&bodyData[offset]);
568 if (offset + towerLen > bodyData.size())
572 const Bytes towerData(bodyData.begin() + offset, bodyData.begin() + offset + towerLen);
574 offset = (offset + 3) & ~
static_cast<std::size_t
>(3);
576 auto endpoint = ParseEpmTower(towerData);
579 endpoint->objectUuid = entryObjectUuid;
580 endpoint->annotation = annotation;
581 results.push_back(*endpoint);
590 const std::string& name,
594 Bytes(name.begin(), name.end()));
597 if (responses.empty())
601 for (
const auto& [Mac, Blocks] : responses)
604 std::string lowerDescName = desc.
name;
605 std::string lowerTarget = name;
606 std::ranges::transform(lowerDescName, lowerDescName.begin(), ::tolower);
607 std::ranges::transform(lowerTarget, lowerTarget.begin(), ::tolower);
608 if (lowerDescName == lowerTarget)
616 const auto& [Mac, Blocks] = *responses.begin();
627 std::vector<std::string> warnings;
631 warnings.emplace_back(
"Cycle time too fast - sub-1ms is impractical without hardware support");
635 warnings.emplace_back(
"Cycle time may cause jitter (8ms+ recommended)");
639 warnings.emplace_back(
"Watchdog factor too low (use 6+)");
643 warnings.emplace_back(
"No IO slots configured");
654 timeout(options.timeoutSec),
655 sessionKey(RandomNonzeroU16()),
656 rpcTransport{std::make_unique<
RpcTransport>(ioContext)},
657 ccontrolTransport{std::make_unique<
RpcTransport>(ioContext)}
659 arUuid = RandomUuidBytes();
667 std::string anyIp = asio::ip::address_v4(asio::detail::socket_ops::network_to_host_long(INADDR_ANY)).to_string();
694 h.
lengthOfBody =
static_cast<std::uint16_t
>(nrd.size());
716 auto properties =
static_cast<std::uint32_t
>((transport << 1) | priority);
741 std::size_t offset = 0;
747 const Bytes blockBytes(responseData.begin() + offset, responseData.end());
751 return res.localAlarmReference;
754 catch (
const std::exception&)
759 offset += 4 + hdr.blockLength;
1718 constexpr std::uint16_t kInputIocrType = 1;
1719 constexpr std::uint16_t kOutputIocrType = 2;
1721 if (iocrType != kInputIocrType && iocrType != kOutputIocrType)
1723 throw std::invalid_argument(
"BuildIocrBlock: unsupported IOCR type " + std::to_string(iocrType));
1728 throw std::invalid_argument(
"BuildIocrBlock: IOCR dataLength must be greater than zero");
1731 if (setup.
dataLength > std::numeric_limits<std::uint16_t>::max())
1733 throw std::invalid_argument(
"BuildIocrBlock: IOCR dataLength exceeds uint16_t");
1744 std::vector<IOCRAPIObject> ioDataObjects;
1745 std::vector<IOCRAPIObject> iocsObjects;
1752 << std::hex << slot.subslot << std::dec
1753 <<
" in=" << slot.inputLength
1754 <<
" out=" << slot.outputLength
1755 <<
" inputIoData=" << slot.inputIoData
1756 <<
" outputIoData=" << slot.outputIoData
1757 <<
" inputIocs=" << slot.inputIocs
1758 <<
" outputIocs=" << slot.outputIocs
1763 if (iocrType == kInputIocrType)
1765 if (slot.inputIoData)
1767 ioDataObjects.push_back({slot.slot, slot.subslot, 0});
1770 if (slot.outputIocs)
1772 iocsObjects.push_back({slot.slot, slot.subslot, 0});
1777 if (slot.outputIoData)
1779 ioDataObjects.push_back({slot.slot, slot.subslot, 0});
1784 iocsObjects.push_back({slot.slot, slot.subslot, 0});
1793 if (ioDataObjects.size() > std::numeric_limits<std::uint16_t>::max())
1795 throw std::invalid_argument(
"BuildIocrBlock: too many IODataObjects");
1798 if (iocsObjects.size() > std::numeric_limits<std::uint16_t>::max())
1800 throw std::invalid_argument(
"BuildIocrBlock: too many IOCSObjects");
1823 std::size_t dataOffset = 0;
1824 if (iocrType == kInputIocrType)
1836 for (
auto&
object : ioDataObjects)
1838 const auto slotIt = std::ranges::find_if(setup.
slots,
1839 [&
object](
const IOSlot& slot)
1841 return slot.slot == object.slotNumber && slot.subslot == object.subslotNumber;
1844 if (slotIt == setup.
slots.end())
1846 throw std::logic_error(
"BuildIocrBlock: IODataObject no longer exists in configuration");
1849 const IOSlot& slot = *slotIt;
1858 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
1860 throw std::invalid_argument(
"BuildIocrBlock: IOData frame offset exceeds uint16_t");
1862 const std::size_t before = dataOffset;
1863 object.frameOffset =
static_cast<std::uint16_t
>(dataOffset);
1867 const std::size_t objectSize = std::max<std::size_t>(dataSize, 1);
1870 dataOffset += objectSize;
1879 << std::hex << slot.
subslot << std::dec
1880 <<
" dataSize=" << dataSize
1881 <<
" before=" << before
1882 <<
" after=" << dataOffset
1914 std::size_t iocsOffset = dataOffset;
1927 for (
auto&
object : iocsObjects)
1929 if (iocsOffset > std::numeric_limits<std::uint16_t>::max())
1931 throw std::invalid_argument(
"BuildIocrBlock: IOCS frame offset exceeds uint16_t");
1934 object.frameOffset =
static_cast<std::uint16_t
>(iocsOffset);
1947 const std::size_t minimumDataLength =
1948 dataOffset + iocsObjects.size();
1965 throw std::invalid_argument(
1966 "BuildIocrBlock: configured dataLength " +
1968 " is smaller than the minimum required cyclic data length " +
1969 std::to_string(minimumDataLength));
1983 const std::size_t iocsLength = iocsObjects.size();
1985 for (
auto&
object : iocsObjects)
1987 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
1989 throw std::invalid_argument(
"BuildIocrBlock: IOCS frame offset exceeds uint16_t");
1992 object.frameOffset =
static_cast<std::uint16_t
>(dataOffset);
1997 for (
auto&
object : ioDataObjects)
1999 const auto slotIt = std::ranges::find_if(setup.
slots, [&
object](
const IOSlot& slot)
2001 return slot.slot == object.slotNumber && slot.subslot == object.subslotNumber;
2004 if (slotIt == setup.
slots.end())
2006 throw std::logic_error(
"BuildIocrBlock: IODataObject no longer exists in configuration");
2009 const IOSlot& slot = *slotIt;
2013 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
2015 throw std::invalid_argument(
"BuildIocrBlock: IOData frame offset exceeds uint16_t");
2018 object.frameOffset =
static_cast<std::uint16_t
>(dataOffset);
2020 const std::size_t objectSize = std::max<std::size_t>(dataSize, 1);
2022 dataOffset += objectSize;
2030 const std::size_t minimumDataLength = dataOffset;
2034 throw std::invalid_argument(
2035 "BuildIocrBlock: configured dataLength " +
2037 " is smaller than the minimum required cyclic data length " +
2038 std::to_string(minimumDataLength));
2046 <<
"IOCR type=" << iocrType
2047 <<
" reference=" << iocrReference
2049 <<
" IODataObjects=" << ioDataObjects.size()
2050 <<
" IOCSObjects=" << iocsObjects.size()
2053 for (
const auto&
object : ioDataObjects)
2056 <<
" IOData slot=" <<
object.slotNumber
2057 <<
" subslot=0x" << std::hex <<
object.subslotNumber
2059 <<
" offset=" <<
object.frameOffset
2063 for (
const auto&
object : iocsObjects)
2066 <<
" IOCS slot=" <<
object.slotNumber
2067 <<
" subslot=0x" << std::hex <<
object.subslotNumber
2069 <<
" offset=" <<
object.frameOffset
2078 wire::PutU16(apiBlock,
static_cast<std::uint16_t
>(ioDataObjects.size()));
2082 const Bytes bytes =
object.ToBytes();
2083 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
2086 wire::PutU16(apiBlock,
static_cast<std::uint16_t
>(iocsObjects.size()));
2090 const Bytes bytes =
object.ToBytes();
2091 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
2110 if (totalSize < 4 || totalSize - 4 > std::numeric_limits<std::uint16_t>::max())
2112 throw std::invalid_argument(
"BuildIocrBlock: IOCR block is too large");
2115 blockHeader.
blockLength =
static_cast<std::uint16_t
>(totalSize - 4);
2124 std::ranges::copy(blockHeaderBytes, iocrHeader.
blockHeader.begin());
2126 iocrHeader.
iocrType =
static_cast<std::uint16_t
>(iocrType);
2127 iocrHeader.
iocrReference =
static_cast<std::uint16_t
>(iocrReference);
2137 iocrHeader.
frameId =
static_cast<std::uint16_t
>(0xC000 + iocrReference - 1);
2144 iocrHeader.
phase = 8;
2161 out.insert(out.end(), apiBlock.begin(), apiBlock.end());
2443 for (
const auto& slot : setup.
slots)
2445 const bool hasInput = slot.inputLength > 0;
2446 const bool hasOutput = slot.outputLength > 0;
2451 if (hasInput && hasOutput)
2474 slot.submoduleIdent,
2475 static_cast<std::uint16_t
>(submoduleType),
2486 for (
const auto& slot : setup.
slots)
2488 const bool hasInput = slot.inputLength > 0;
2489 const bool hasOutput = slot.outputLength > 0;
2491 std::uint16_t submoduleType = 0;
2492 if (hasInput && hasOutput)
2510 slot.submoduleIdent,
2521 std::size_t offset = 0;
2527 const Bytes blockBytes(responseData.begin() + offset, responseData.end());
2531 if (res.iocrType == iocrType)
2537 catch (
const std::exception&)
2542 offset += 4 + hdr.blockLength;
2550 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(
timeout));
2554 const Bytes responseBytes =
2557 timeoutInMilliSeconds);
2561 throw RPCError(
"Failed to parse RPC response: data too short (" + std::to_string(responseBytes.size()) +
2568 catch (
const std::exception& e)
2570 throw RPCError(std::string(
"Failed to parse RPC response: ") + e.what());
2590 const std::string errorMessage = std::format(
"Unexpected RPC packet type: 0x{:02X}", resp.
packetType);
2604 auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() -
liveMonotonic).count();
2614 std::optional<IOCRSetup> setup)
2620 throw std::invalid_argument(
"srcMac required for initial connection");
2626 arUuid = RandomUuidBytes();
2636 throw std::invalid_argument(
"No source MAC address available");
2642 const std::uint32_t arProperties = this->
iocrSetup ? 0x00000011U : 0x00000111U;
2643 std::string initiatorStationName =
"rhodium-profinet-connector";
2651 const std::uint16_t arType = this->
iocrSetup ? 0x0001 : 0x0006;
2663 ar.
stationNameLength =
static_cast<std::uint16_t
>(initiatorStationName.length());
2664 const Bytes initiatorStationNameInBytes(initiatorStationName.begin(), initiatorStationName.end());
2673 nrdPayload.insert(nrdPayload.end(), inputIocr.begin(), inputIocr.end());
2677 nrdPayload.insert(nrdPayload.end(), outputIocr.begin(), outputIocr.end());
2680 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
2684 nrdPayload.insert(nrdPayload.end(), expectedSubmodule.begin(), expectedSubmodule.end());
2695 else if (withAlarmCr)
2698 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
2712 throw RPCConnectionError(std::string(
"Connect rejected by device: ") + pnioErr.what());
2715 std::optional<ConnectResult> result;
2818void RPCCon::Write(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx,
2838 iod.
length =
static_cast<std::uint32_t
>(data.size());
2929 auto tryRead = [&](
auto& field,
auto memberFn)
2933 field = (this->*memberFn)(slot, subslot);
2941 catch (
const std::exception&)
2977 for (
const auto& w : writes)
2979 builder.
AddWrite(w.slot, w.subslot, w.index, w.data, w.api);
2993 iod.
api = 0xFFFFFFFF;
3029 if (data.size() > 6)
3057 static const std::vector<std::tuple<std::uint16_t, std::uint16_t, std::uint16_t>> kDiagnosisProbes = {
3066 std::map<std::uint16_t, diagnosis::DiagnosisData> results;
3067 for (
const auto& [idx, Slot, Subslot] : kDiagnosisProbes)
3074 results[idx] = diag;
3108 std::uint16_t slot, std::uint16_t subslot, std::optional<std::vector<std::uint16_t>> customIndices)
3110 std::vector<std::pair<std::uint16_t, std::string>> probeList;
3114 for (
auto idx : *customIndices)
3123 probeList.insert(probeList.end(), src.begin(), src.end());
3138 std::map<std::uint16_t, IndexProbeResult> results;
3139 std::vector<std::uint16_t> seen;
3140 for (
const auto& [idx, Name] : probeList)
3142 if (std::ranges::find(seen, idx) != seen.end())
3146 seen.push_back(idx);
3182 const Bytes& subBlocks)
3205 nrdPayload.insert(nrdPayload.end(), subBlocks.begin(), subBlocks.end());
3248 std::array<std::uint8_t, 4>
drep{};
3257std::optional<RawRpcHeader> ParseRawRpcHeader(
const Bytes& data)
3261 return std::nullopt;
3264 h.version = data[0];
3265 h.packetType = data[1];
3269 h.drep = {data[4], data[5], data[6], data[7]};
3271 std::memcpy(h.objectUuid.data(), &data[8],
uuidLenght);
3272 std::memcpy(h.interfaceUuid.data(), &data[24],
uuidLenght);
3273 std::memcpy(h.activityUuid.data(), &data[40],
uuidLenght);
3274 h.isLittleEndian = (h.drep[0] & 0x10) != 0;
3276 auto u32 = [&](std::size_t off)
3278 return h.isLittleEndian ? ReadU32Le(&data[off])
3282 auto u16 = [&](std::size_t off)
3284 return h.isLittleEndian ? ReadU16Le(&data[off])
3285 : static_cast<std::uint16_t>((data[off] <<
OneOctetShift) | data[off + 1]);
3290 h.interfaceVersion = u32(60);
3291 h.sequenceNumber = u32(64);
3292 h.operationNumber = u16(68);
3298 h.serialLow = data[79];
3313 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
3323 asio::ip::udp::endpoint senderEndpoint;
3325 auto hdr = ParseRawRpcHeader(buf);
3339 const Bytes& nrdPayload = hdr->payload;
3340 if (nrdPayload.size() < 20)
3344 bool le = hdr->isLittleEndian;
3345 const std::uint32_t nrdActual =
3346 le ? ReadU32Le(&nrdPayload[16])
3350 Bytes nrdBody(nrdPayload.begin() + 20, nrdPayload.end());
3351 if (nrdBody.size() < 32)
3367 const std::uint16_t blockType = (
static_cast<std::uint16_t
>(nrdBody[0]) << 8) | nrdBody[1];
3371 const std::uint16_t controlCmd = (
static_cast<std::uint16_t
>(nrdBody[28]) << 8) | nrdBody[29];
3373 std::cout <<
"Parsed BlockType: 0x" << std::hex << blockType
3374 <<
", ControlCmd: 0x" << controlCmd << std::dec <<
"\n";
3378 std::cerr <<
"Ignoring non-AppReady block: 0x" << std::hex << blockType <<
"\n";
3402 auto respNrdLen =
static_cast<std::uint32_t
>(respNrdPayload.size());
3407 WriteU32Le(respNrd, 0);
3408 WriteU32Le(respNrd, respNrdLen);
3409 WriteU32Le(respNrd, respNrdLen);
3410 WriteU32Le(respNrd, 0);
3411 WriteU32Le(respNrd, respNrdLen);
3421 respNrd.insert(respNrd.end(), respNrdPayload.begin(), respNrdPayload.end());
3423 respBytes.push_back(hdr->version);
3425 respBytes.push_back(0x00);
3426 respBytes.push_back(0x00);
3427 respBytes.insert(respBytes.end(), hdr->drep.begin(), hdr->drep.end());
3429 respBytes.insert(respBytes.end(), hdr->objectUuid.begin(), hdr->objectUuid.end());
3430 respBytes.insert(respBytes.end(), hdr->interfaceUuid.begin(), hdr->interfaceUuid.end());
3431 respBytes.insert(respBytes.end(), hdr->activityUuid.begin(), hdr->activityUuid.end());
3433 auto putFieldU32 = [&](std::uint32_t v)
3437 WriteU32Le(respBytes, v);
3444 auto putFieldU16 = [&](std::uint16_t v)
3448 WriteU16Le(respBytes, v);
3457 putFieldU32(hdr->interfaceVersion);
3458 putFieldU32(hdr->sequenceNumber);
3459 putFieldU16(hdr->operationNumber);
3460 putFieldU16(0xFFFF);
3461 putFieldU16(0xFFFF);
3462 putFieldU16(
static_cast<std::uint16_t
>(respNrd.size()));
3464 respBytes.push_back(0);
3465 respBytes.push_back(hdr->serialLow);
3466 respBytes.insert(respBytes.end(), respNrd.begin(), respNrd.end());
Device not found via DCP.
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
PNIO application error with error codes.
std::uint8_t errorCode1
Raw ErrorCode1 byte (category / block type).
std::uint8_t errorCode2
Raw ErrorCode2 byte (specific error within the category).
static PNIOError FromArgsStatus(std::uint32_t argsStatus)
Create a PNIOError from the little-endian ArgsStatus field carried in a DCE/RPC PDU.
Failed to establish RPC connection.
RPC returned a fault response.
ExpectedSubmoduleBlockReq (0x0104) builder.
Bytes ToBytes() const
Serialize the complete block to raw bytes.
ExpectedSubmoduleBlockReq & AddSubmodule(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint32_t moduleIdent, std::uint32_t submoduleIdent, std::uint16_t submoduleType=0, std::uint16_t inputLength=0, std::uint16_t outputLength=0)
Add a single submodule.
Builder for IODWriteMultipleReq packets (index 0xE040).
Bytes Build() const
Build the complete IODWriteMultipleReq packet.
IODWriteMultipleBuilder & AddWrite(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, Bytes data, std::uint32_t api=0)
Add a write operation.
static constexpr std::uint16_t writeMultipleIndex
Record data index for WriteMultiple (0xE040).
Parsed PROFINET device information from a DCP response.
std::uint8_t vendorLow
Low byte of the PROFINET vendor ID.
std::string name
Station name.
std::uint8_t vendorHigh
High byte of the PROFINET vendor ID.
std::uint8_t deviceLow
Low byte of the PROFINET device ID.
std::uint8_t deviceHigh
High byte of the PROFINET device ID.
std::string ip
IPv4 address, or "0.0.0.0" if unset.
PNInM13 ReadIm13(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M13 (reserved).
PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M0 (mandatory identification data).
static std::uint16_t ParseIocrResponse(const Bytes &responseData, int iocrType)
Parse an IOCRBlockRes from a CONNECT response.
int iocrRefCounter
Counter used to allocate unique IOCR references.
std::uint32_t sequenceNumber
Monotonically increasing RPC sequence number.
blocks::PDRealData ReadPdRealData() override
Read and parse the device's PDRealData (physical topology).
static Bytes BuildUnifiedExpectedSubmoduleBlock(const IOCRSetup &setup)
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID for this connection.
blocks::ModuleDiffBlock ReadModuleDiff() override
Read and parse the device's ModuleDiffBlock.
PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M2 (installation date).
RPCCon(RPCCon &&)=delete
Deleted move constructor to prevent moving instances of this class.
Bytes BuildAlarmCrBlock(int transport=0, int priority=0) const
Build an AlarmCRBlockReq for inclusion in the CONNECT request.
static Bytes BuildIocrBlock(int iocrType, int iocrReference, const IOCRSetup &setup)
Build an IOCRBlockReq (header + API object list) for the CONNECT request.
double timeout
Default RPC response timeout in seconds.
PNInM8 ReadIm8(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M8 (reserved).
std::array< std::uint8_t, uuidLenght > localObjectUuid
This controller's own object UUID.
~RPCCon()
Disconnect (best-effort) and close the underlying sockets.
static PNNRDData CreateNrd(const Bytes &payload)
Wrap a payload in an NRD (Network Representation Data) header.
PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M4 (PROFIsafe signature).
PNInM15 ReadIm15(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M15 (reserved).
AllIM ReadAllIm(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read all I&M records a device supports.
std::vector< blocks::SlotInfo > DiscoverSlots() override
Convenience accessor for ReadRealIdentificationData().Slots.
static int ParseAlarmCrResponse(const Bytes &responseData)
Parse the AlarmCRBlockRes from a CONNECT response.
Bytes PrmEnd() override
Send PrmEnd (end of the parameterization phase).
PNInM6 ReadIm6(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M6 (reserved).
static Bytes BuildExpectedSubmoduleBlock(const IOCRSetup &setup)
Build an ExpectedSubmoduleBlockReq for the CONNECT request.
Bytes SendControl(BlockType blockType, ControlCommand controlCommand, bool waitResponse=true, const Bytes &subBlocks={})
Send a CONTROL operation and optionally wait for its response.
void Disconnect() override
Send Release to terminate the AR.
int deviceAlarmRef
Device's local alarm reference, if an AlarmCR was established.
PNInM11 ReadIm11(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M11 (reserved).
std::pair< blocks::PDRealData, blocks::RealIdentificationData > DiscoverTopology() override
Read both PDRealData and RealIdentificationData in one call.
std::array< std::uint8_t, uuidLenght > remoteObjectUuid
The target device's object UUID.
dcp::DCPDeviceDescription info
DCP-discovered description of the target device.
diagnosis::DiagnosisData ReadDiagnosis(std::uint16_t slot=0, std::uint16_t subslot=0, std::uint16_t index=0xF000) override
Read and parse diagnosis data at the given location/index.
PNInM14 ReadIm14(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M14 (reserved).
void Close() noexcept override
Disconnect() and close the underlying sockets.
std::unique_ptr< RpcTransport > rpcTransport
PNInM12 ReadIm12(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M12 (reserved).
int inputIocrRef
IOCR reference for the input (device -> controller) IOCR.
std::optional< MacAddress > srcMac
Source MAC address used to establish the AR.
std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis() override
Read diagnosis from all standard diagnosis indices.
std::map< std::uint16_t, IndexProbeResult > EnumerateIndices(std::uint16_t slot=0, std::uint16_t subslot=1, std::optional< std::vector< std::uint16_t > > customIndices=std::nullopt) override
Probe a set of indices and report which are readable/empty/erroring.
bool alarmCrEnabled
Whether an AlarmCR was successfully established.
std::chrono::steady_clock::time_point liveMonotonic
Timestamp of the last successful RPC exchange, for CONNECTION_TIMEOUT tracking.
void CheckTimeout()
Reconnect if the AR has been idle longer than CONNECTION_TIMEOUT.
Bytes PrmBegin() override
Send PrmBegin (start of the parameterization phase).
int outputIocrRef
IOCR reference for the output (controller -> device) IOCR.
std::uint16_t outputFrameId
Frame ID assigned to the output IOCR.
Bytes ApplicationReady(double timeoutSec=30.0) override
Wait for the device's CControl/ApplicationReady request and confirm it.
PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M5 (free-text annotation).
PNInM9 ReadIm9(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M9 (reserved).
std::uint16_t alarmRef
Controller's own local alarm reference.
PNInM10 ReadIm10(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M10 (reserved).
PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M3 (free-text descriptor).
std::uint16_t inputFrameId
Frame ID assigned to the input IOCR.
std::unique_ptr< RpcTransport > ccontrolTransport
blocks::RealIdentificationData ReadRealIdentificationData() override
Read and parse the device's RealIdentificationData (logical structure).
std::optional< ConnectResult > Connect(std::optional< MacAddress > srcMac=std::nullopt, bool withAlarmCr=false, std::optional< IOCRSetup > iocrSetup=std::nullopt) override
Establish (or re-establish) the AR with the device.
PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M1 (tag function/location).
PNIODHeader Read(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx) override
Read a data record via slot/subslot/index (AR must be connected).
void Write(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx, const Bytes &data) override
Write a data record via slot/subslot/index (AR must be connected).
std::uint16_t sessionKey
Session key for this connection.
PNRPCHeader CreateRpc(std::uint16_t operation, const Bytes &nrd)
Build an RPC header for the given operation and NRD body.
Bytes ReadRaw(std::uint16_t idx, std::uint16_t slot=0, std::uint16_t subslot=1)
Read a raw record payload by index (convenience wrapper over Read()).
PNRPCHeader SendReceive(const PNRPCHeader &request)
Send an RPC request and synchronously wait for its response.
PNInM7 ReadIm7(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M7 (reserved).
Bytes ReadyForRtClass3() override
Send ReadyForRTClass3 (isochronous real-time readiness).
std::vector< blocks::WriteMultipleResult > WriteMultiple(const std::vector< WriteItem > &writes) override
Write multiple records atomically via IODWriteMultipleReq (0xE040).
bool live
Whether the AR is currently established.
PNIODHeader ReadImplicit(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx) override
Read a data record without an established AR.
std::optional< IOCRSetup > iocrSetup
Cyclic IO configuration used for the current AR, if any.
std::array< std::uint8_t, uuidLenght > activityUuid
Activity UUID for the current RPC exchange.
Synchronous UDP transport used by PROFINET DCE/RPC.
void Open(const std::string &address, std::uint16_t port, bool isListener=false)
Open the UDP socket and configure the remote endpoint.
Bytes SendReceive(const Bytes &data, std::chrono::milliseconds timeout) override
Send a UDP datagram and wait for one response.
Declares the PROFINET RTC1 cyclic IO controller and process-data exchange API.
ModuleDiffBlock ParseModuleDiffBlock(const Bytes &data)
Parse a ModuleDiffBlock (0x8104) from raw bytes.
std::vector< WriteMultipleResult > ParseWriteMultipleResponse(const Bytes &data)
Parse an IODWriteMultipleRes into individual per-write results.
PDRealData ParsePdRealData(const Bytes &data)
Parse a complete PDRealData (0xF841) response.
RealIdentificationData ParseRealIdentificationData(const Bytes &data)
Parse a RealIdentificationData (0xF000 or 0x0013) response.
constexpr int MIN_CYCLE_MS
Minimum cycle time (ms) considered reliable.
void SendRequest(const EthernetSocket &sock, const MacAddress &src, BlockKey blockType, const std::vector< std::uint8_t > &value)
Send a DCP Identify request filtered to a specific (option, suboption).
std::pair< std::uint8_t, std::uint8_t > BlockKey
(option, suboption) key identifying a DCP block.
void SendDiscover(const EthernetSocket &sock, const MacAddress &src, std::uint16_t responseDelay=DEFAULT_RESPONSE_DELAY_FACTOR)
Send a DCP Identify multicast request to discover all devices on the segment.
ResponseMap ReadResponse(const EthernetSocket &sock, const MacAddress &myMac, int timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
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.
constexpr std::uint8_t VERSION_HIGH
Version high.
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_REQ
Block type: IODControlReqAppReady.
std::string UuidBytesToString(const std::array< std::uint8_t, uuidLenght > &data)
Format a 16-byte DCE/RPC UUID as a canonical string.
const std::string UUID_PNIO_CONTROLLER
PROFINET IO-Controller interface UUID.
const std::string UUID_EPM_V4
Endpoint Mapper interface UUID.
dcp::DCPDeviceDescription GetStationInfo(const EthernetSocket &sock, const MacAddress &src, const std::string &name, int timeoutSec=static_cast< int >(DEFAULT_TIMEOUT *2))
Resolve a device by PROFINET station name via DCP.
constexpr std::uint32_t EPM_INQUIRY_ALL
EPM inquiry type: all interfaces.
const std::string UUID_PNIO_DEVICE
PROFINET IO-Device interface UUID.
constexpr double CONNECTION_TIMEOUT
Maximum idle time in seconds before an AR is considered to need re-connection.
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
constexpr std::uint32_t EPM_INQUIRY_INTERFACE
EPM inquiry type: specific interface.
constexpr std::uint8_t VERSION_LOW
Version low.
std::vector< EPMEndpoint > EpmLookup(asio::io_context &ioContext, const std::string &ip, std::uint16_t port=RPC_PORT, double timeoutSec=DEFAULT_TIMEOUT, std::optional< std::string > interfaceFilter=std::nullopt)
Query a device's Endpoint Mapper for available RPC endpoints.
std::array< std::uint8_t, uuidLenght > StringToUuidBytes(const std::string &uuidStr)
Parse a canonical UUID string into 16 raw DCE/RPC UUID bytes.
constexpr std::uint32_t EPM_LOOKUP
EPM operation: Lookup.
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t v)
Append a 16-bit value to a buffer in big-endian order.
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t v)
Append a 32-bit value to a buffer in big-endian order.
BlockType
PROFINET block types.
@ ControlPrmEndRequest
IOD Control Prm End request.
@ ApplicationRelationRequest
AR block request.
@ ControlRtClass3Request
IOD Control RT Class 3 request.
@ ReleaseRequest
IOD Release request.
@ ParameterizationBeginRequest
Parameterization Begin request.
@ IoDataWriteRequest
IOD write request block.
@ ControlApplicationReadyResponse
IOD Control Application Ready response.
const IndexNamePairs & DiagnosisIndices(const std::string &scope)
Diagnosis indices for a given addressing scope.
constexpr int uuidLenght
Constant lenght of a UUID.
constexpr std::uint16_t PD_REAL_DATA
PD Real Data.
constexpr std::uint16_t MODULE_DIFF_BLOCK
Module Diff Block.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
constexpr std::uint16_t IP_ETHERTYPE
EtherType value identifying IP frames (0x8000).
ControlCommand
PROFINET IOD control command values.
@ Release
Release command.
@ ReadyForRtClass3
Ready for RT Class 3 command.
@ PrmBegin
Prm Begin command.
std::vector< std::pair< std::uint16_t, std::string > > IndexNamePairs
(index, name) pairs, e.g. for building a "try each of these" scan.
constexpr std::uint16_t EXPECTED_ID_SUBSLOT
Expected ID Subslot.
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
static constexpr int ThreeOctetsShift
The bit-shift distance required to move data across three octets.
constexpr std::uint16_t REAL_ID_API
Real ID API.
const IndexNamePairs & DeviceIndices()
Device-level indices.
constexpr std::uint16_t RECORD_INPUT_DATA
Record Input Data.
static constexpr int TwoOctetsShift
The bit-shift distance required to move data across two octets.
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
const IndexNamePairs & ImIndices()
I&M0-I&M15 indices.
static constexpr std::uint8_t LowByteMask
Bitmask used to isolate the lowest significant byte (8 bits) of a larger integer.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
constexpr int blockHeaderLenght
Constant lenght of a blockHeader.
constexpr std::uint16_t REAL_ID_SUBSLOT
Real ID Subslot.
const IndexNamePairs & InterfaceIndices()
Interface-level indices.
std::string GetIndexName(std::uint16_t index)
Get the human-readable name for a record data index.
const IndexNamePairs & PortIndices()
Port-level indices.
constexpr std::uint16_t RECORD_OUTPUT_DATA
Record Output Data.
constexpr std::array< std::uint8_t, 12 > kPnUuidSuffix
PROFINET UUID suffix, shared by all interface/object UUIDs.
std::array< std::uint8_t, uuidLenght > interfaceUuid
std::array< std::uint8_t, 4 > drep
std::uint16_t operationNumber
std::uint32_t interfaceVersion
std::array< std::uint8_t, uuidLenght > objectUuid
std::uint32_t sequenceNumber
std::array< std::uint8_t, uuidLenght > activityUuid
PROFINET DCE/RPC protocol structures and IO-Device connection API.
One slot/subslot's placement within an IOCR's cyclic data frame.
ARBlockReq: establishes an Application Relationship (sent in the CONNECT request).
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
std::uint32_t arProperties
AR property flags (supervisor takeover, device access, etc.).
std::uint16_t cmInitiatorActivityTimeoutFactor
Activity timeout factor for the initiator.
MacAddress cmInitiatorMacAddress
MAC address of the initiating controller.
std::uint16_t sessionKey
Session key chosen by the initiator.
std::array< std::uint8_t, uuidLenght > arUuid
Unique AR UUID chosen by the initiator (controller).
Bytes cmInitiatorStationName
Station name of the initiating controller (variable length).
Bytes ToBytes() const
Serialize this block back to raw bytes.
std::uint16_t arType
AR type (e.g. IOCARSingle, IOSAR).
std::uint16_t initiatorUdpRtport
UDP port the initiator uses for real-time data.
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this block.
std::array< std::uint8_t, uuidLenght > cmInitiatorObjectUuid
Object UUID of the initiating controller.
std::uint16_t stationNameLength
Length in bytes of CmInitiatorStationName.
AlarmCRBlockReq: requests establishment of the alarm connection (part of the CONNECT request).
static constexpr std::uint16_t DEFAULT_MAX_ALARM_DATA_LENGTH
Default maximum alarm data length in bytes.
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
static constexpr std::uint16_t DEFAULT_TAG_HEADER_LOW
Default low word of the alarm VLAN tag header.
static constexpr BlockType BLOCK_TYPE
Block type identifier for AlarmCRBlockReq.
Bytes ToBytes() const
Serialize this block back to raw bytes.
static constexpr std::uint16_t DEFAULT_TAG_HEADER_HIGH
Default high word of the alarm VLAN tag header.
std::uint16_t alarmCrType
Alarm CR type (always 1: Alarm).
std::uint16_t etherTypeLT
EtherType used for alarm frames (0x8892).
std::uint32_t alarmCrProperties
Alarm CR property flags (transport, priority).
static constexpr std::size_t kSize
Size in bytes of this block.
static constexpr std::uint16_t DEFAULT_RTA_RETRIES
Default number of RTA retries.
std::uint16_t localAlarmReference
Local alarm reference chosen by the controller.
std::uint16_t alarmCrTagHeaderHigh
High word of the alarm VLAN tag header.
std::uint16_t maxAlarmDataLength
Maximum alarm data length in bytes.
std::uint16_t alarmCrTagHeaderLow
Low word of the alarm VLAN tag header.
std::uint16_t rtaRetries
Number of RTA retransmission attempts.
std::uint16_t rtaTimeoutFactor
RTA (real-time acyclic) retransmission timeout factor.
static constexpr std::uint16_t DEFAULT_RTA_TIMEOUT_FACTOR
Default RTA timeout factor.
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for AlarmCRBlockRes.
static PNAlarmCRBlockRes Parse(const Bytes &data)
Parse an AlarmCRBlockRes from raw bytes.
static constexpr std::pair< std::uint8_t, std::uint8_t > NAME_OF_STATION
Block option/suboption: station name.
static PNIOCRBlockRes Parse(const Bytes &data)
Parse an IOCRBlockRes from raw bytes.
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for IOCRBlockRes.
Control block used for Release/PrmBegin/PrmEnd/ApplicationReady/RTClass3 requests.
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID this control operation applies to.
std::uint16_t controlBlockProperties
Control block property flags.
std::uint16_t padding1
Reserved/padding.
std::uint16_t sessionKey
Session key matching the AR.
ControlCommand controlCommand
Control command bitmask (PrmEnd/AppReady/Release/Done/...).
std::uint16_t padding2
Reserved/padding.
Bytes ToBytes() const
Serialize this block back to raw bytes.
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
static PNInM0 Parse(const Bytes &data)
Parse an I&M0 record from raw bytes.
static constexpr std::uint16_t IDX
Record data index for I&M0.
I&M1: user-assigned tag function and location strings.
static PNInM1 Parse(const Bytes &data)
Parse an I&M1 record from raw bytes.
static constexpr std::uint16_t IDX
Record data index for I&M1.
static PNInM2 Parse(const Bytes &data)
Parse an I&M2 record from raw bytes.
static constexpr std::uint16_t IDX
Record data index for I&M2.
I&M3: free-text descriptor.
static constexpr std::uint16_t IDX
Record data index for I&M3.
static PNInM3 Parse(const Bytes &data)
Parse an I&M3 record from raw bytes.
I&M4: PROFIsafe signature (binary, not text).
static PNInM4 Parse(const Bytes &data)
Parse an I&M4 record from raw bytes.
static constexpr std::uint16_t IDX
Record data index for I&M4.
I&M5: free-text annotation.
static PNInM5 Parse(const Bytes &data)
Parse an I&M5 record from raw bytes.
static constexpr std::uint16_t IDX
Record data index for I&M5.
I&M6-I&M15: reserved for future use per IEC 61158-6-10.
static constexpr std::uint16_t IDX
Record data index for this reserved I&M slot.
static PNInMReserved Parse(const Bytes &data)
Parse a reserved I&M record from raw bytes.
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Bytes payload
The wrapped IOD data.
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
std::uint32_t actualCount
Actual length in bytes of Payload.
Parsed ModuleDiffBlock (0x8104).
Parsed PDRealData (0xF841) structure.
Parsed RealIdentificationData (0xF000/0x0013) structure.
std::vector< SlotInfo > slots
Populated slots/subslots.
Complete diagnosis data for one slot/subslot.
std::vector< AnyChannelDiagnosis > entries
Parsed diagnosis entries.
Bytes rawData
Raw, undecoded source data.
std::uint32_t api
API number.
std::uint16_t subslot
Subslot number.
std::uint16_t slot
Slot number.
All I&M records a device supports, read in one call.
std::optional< PNInM7 > im7
I&M7 (reserved), if supported.
std::optional< PNInM15 > im15
I&M15 (reserved), if supported.
std::optional< PNInM9 > im9
I&M9 (reserved), if supported.
std::optional< PNInM14 > im14
I&M14 (reserved), if supported.
std::optional< PNInM1 > im1
I&M1 (tag function/location), if supported.
std::optional< PNInM5 > im5
I&M5 (free-text annotation), if supported.
std::optional< PNInM10 > im10
I&M10 (reserved), if supported.
std::optional< PNInM3 > im3
I&M3 (free-text descriptor), if supported.
std::optional< PNInM6 > im6
I&M6 (reserved), if supported.
std::optional< PNInM13 > im13
I&M13 (reserved), if supported.
std::optional< PNInM11 > im11
I&M11 (reserved), if supported.
std::optional< PNInM2 > im2
I&M2 (installation date), if supported.
std::optional< PNInM12 > im12
I&M12 (reserved), if supported.
std::optional< PNInM4 > im4
I&M4 (PROFIsafe signature), if supported.
std::optional< PNInM0 > im0
I&M0 (mandatory identification data).
std::optional< PNInM8 > im8
I&M8 (reserved), if supported.
Result of a successful RPCCon::Connect() that also established cyclic IO.
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID assigned to this connection.
std::string InterfaceName() const
Human-readable interface name.
std::string interfaceUuid
Interface UUID this endpoint serves.
Configuration for establishing cyclic IO alongside an AR.
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
std::uint16_t dataHoldFactor
Data hold factor (how long to keep the last good frame's data).
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
std::vector< std::string > Validate() const
Sanity-check this configuration.
double CycleTimeMs() const
Compute the cycle time in milliseconds.
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
One slot/subslot's expected module configuration and IO data sizes.
std::uint16_t inputLength
Expected input data length in bytes (0 if no input data).
std::uint16_t slot
Slot number.
std::uint16_t outputLength
Expected output data length in bytes (0 if no output data).
std::uint16_t subslot
Subslot number.
Result of probing one index in EnumerateIndices().
std::uint8_t errorCode1
PNIOError ErrorCode1, if the error was a PNIOError.
std::string name
Human-readable name of the index.
std::size_t size
Payload size in bytes; valid when Status == "readable".
std::uint8_t errorCode2
PNIOError ErrorCode2, if the error was a PNIOError.
std::string error
Error message; valid when Status == "error".
std::string status
Probe outcome: "readable", "empty", or "error".
Configuration options for an RPC connection.
std::uint16_t rpcPort
UDP port used for RPC communication.