50std::uint32_t GenerateXid()
52 static thread_local std::mt19937 rng{std::random_device{}()};
53 static constexpr std::size_t maxXid = 0xFFFFFFFFULL;
54 static thread_local std::uniform_int_distribution<std::uint64_t> dist(0, maxXid);
55 return static_cast<std::uint32_t
>(dist(rng));
59const std::map<std::uint8_t, std::string>& OptionNames()
61 static const std::map<std::uint8_t, std::string> table = {
67 {0x06,
"DeviceInitiative"},
74const std::map<std::uint8_t, std::map<std::uint8_t, std::string>>& SuboptionNames()
76 static const std::map<std::uint8_t, std::map<std::uint8_t, std::string>> table = {
77 {0x01, {{0x01,
"MAC"}, {0x02,
"IP"}, {0x03,
"FullIPSuite"}}},
79 {{0x01,
"Type"}, {0x02,
"Name"}, {0x03,
"DeviceID"}, {0x04,
"Role"}, {0x05,
"Options"}, {0x06,
"Alias"}, {0x07,
"Instance"}, {0x08,
"OEM-ID"}, {0x0A,
"RSI"}}},
81 {{0x0C,
"Hostname"}, {0x2B,
"VendorSpec"}, {0x36,
"ServerID"}, {0x37,
"ParamReq"}, {0x3C,
"ClassID"}, {0x3D,
"ClientID"}, {0x51,
"FQDN"}, {0x61,
"UUID"}, {0xFF,
"Control"}}},
83 {{0x01,
"Start"}, {0x02,
"Stop"}, {0x03,
"Signal"}, {0x04,
"Response"}, {0x05,
"FactoryReset"}, {0x06,
"ResetToFactory"}}},
84 {0x06, {{0x01,
"Initiative"}}},
89const std::map<std::uint16_t, std::string>& DeviceRoleNames()
91 static const std::map<std::uint16_t, std::string> table = {
100const std::map<std::uint8_t, std::string>& DcpBlockErrorNames()
102 static const std::map<std::uint8_t, std::string> table = {
116template <
typename Fn>
117void ForEachBlock(
const std::vector<std::uint8_t>& data, Fn&& fn)
119 std::size_t offset = 0;
120 while (offset + 4 <= data.size())
122 const std::uint8_t option = data[offset];
123 const std::uint8_t suboption = data[offset + 1];
124 const auto length =
static_cast<std::uint16_t
>((data[offset + 2] <<
OneOctetShift) | data[offset + 3]);
125 const std::size_t payloadStart = offset + 4;
126 const std::size_t payloadEnd = std::min(payloadStart + length, data.size());
127 const std::vector<std::uint8_t>
payload(data.begin() + payloadStart, data.begin() + payloadEnd);
129 fn(option, suboption,
payload);
131 std::size_t blockLen = 4 + length;
148 std::vector<std::string> roles;
149 for (
const auto& [mask, Name] : DeviceRoleNames())
151 if ((roleByte & mask) != 0)
153 roles.push_back(Name);
158 roles.emplace_back(
"Unknown");
166 auto oit = OptionNames().find(option);
167 if (oit != OptionNames().end())
169 optName = oit->second;
171 else if (option >= 0x80 && option <= 0xFE)
173 optName =
"Vendor-" +
Hex2(option);
177 optName =
"Opt-" +
Hex2(option);
180 std::string suboptName =
Hex2(suboption);
181 auto sit = SuboptionNames().find(option);
182 if (sit != SuboptionNames().end())
184 auto ssit = sit->second.find(suboption);
185 if (ssit != sit->second.end())
187 suboptName = ssit->second;
191 return optName +
"/" + suboptName;
197 sock.
SetTimeout(std::chrono::milliseconds(2000));
198 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
199 while (!timer.TimedOut())
202 if (data.empty() || data.size() < 14)
212 catch (
const std::exception&)
217 if (eth.
dst != srcMac)
246 if (std::string(e.what()).find(
"unexpected service_type") != std::string::npos)
260 if (data.size() < 14)
262 throw DCPError(
"DCP SET response too short");
270 catch (
const std::exception& e)
272 throw DCPError(std::string(
"Failed to parse Ethernet header: ") + e.what());
280 throw DCPError(
"VLAN frame too short");
285 throw DCPError(
"Unexpected inner EtherType: " +
Hex4(innerType));
299 catch (
const std::exception& e)
301 throw DCPError(std::string(
"Failed to parse DCP header: ") + e.what());
306 throw DCPError(
"DCP SET: service not supported by device");
315 ForEachBlock(hdr.
payload, [&](std::uint8_t option, std::uint8_t suboption,
const Bytes& blockPayload)
324 if (blockPayload.size() >= 3)
326 result = blockPayload[2];
328 else if (!blockPayload.empty())
330 result = blockPayload[0];
338std::string IPBlockInfo::GetName(std::uint16_t info)
347 return "IP set by DHCP";
348 case IP_NOT_SET_CONFLICT:
349 return "IP not set (address conflict detected)";
350 case IP_SET_CONFLICT:
351 return "IP set (address conflict detected)";
352 case IP_SET_BY_DHCP_CONFLICT:
353 return "IP set by DHCP (address conflict detected)";
355 return "Unknown (" +
Hex4(info) +
")";
359std::string BlockQualifier::GetName(std::uint16_t qualifier)
361 return qualifier == PERMANENT ?
"Permanent" : (qualifier == TEMPORARY ?
"Temporary" :
"Unknown (" +
Hex4(qualifier) +
")");
364std::string ResetQualifier::GetName(std::uint16_t qualifier)
370 return "Reset application data";
373 return "Reset communication parameter";
376 return "Reset engineering parameter";
379 return "Reset all stored data";
382 return "Reset engineering parameter";
385 return "Reset to factory values";
388 return "Reset and restore data";
390 return "Unknown (" +
Hex4(qualifier) +
")";
394std::string DeviceInitiative::GetName(std::uint16_t value)
396 if (value == NO_HELLO)
398 return "Device does not issue DCP-Hello after power on";
400 if (value == ISSUE_HELLO)
402 return "Device issues DCP-Hello after power on";
404 return "Unknown (" +
Hex4(value) +
")";
407std::string DCPResponseCode::GetName(std::uint8_t code)
413 case OPTION_NOT_SUPPORTED:
414 return "Option not supported";
415 case SUBOPTION_NOT_SUPPORTED:
416 return "Suboption not supported or no DataSet available";
417 case SUBOPTION_NOT_SET:
418 return "Suboption not set";
420 return "Resource error";
421 case SET_NOT_POSSIBLE:
422 return "Set not possible";
423 case IN_OPERATION_SET_NOT_POSSIBLE:
424 return "In operation, SET not possible";
426 return "Unknown (" +
Hex2(code) +
")";
432 auto it = DcpBlockErrorNames().find(code);
433 if (it != DcpBlockErrorNames().end())
437 return "Unknown error (" +
Hex2(code) +
")";
440DCPDHCPBlock DCPDHCPBlock::Parse(std::uint8_t suboption,
const std::vector<std::uint8_t>& data)
445 if (sit != SuboptionNames().end())
447 auto ssit = sit->second.find(suboption);
448 block.
suboptionName = ssit != sit->second.end() ? ssit->second :
Hex2(suboption);
474 block.
uuid =
ToHex(&data[0], 4) +
"-" +
ToHex(&data[4], 2) +
"-" +
ToHex(&data[6], 2) +
"-" +
475 ToHex(&data[8], 2) +
"-" +
ToHex(&data[10], 6);
480const std::map<std::string, BlockKey>&
Params()
482 static const std::map<std::string, BlockKey> table = {
483 {
"name", PNDCPBlock::NAME_OF_STATION},
484 {
"ip", PNDCPBlock::IP_ADDRESS},
493DCPDeviceDescription::DCPDeviceDescription(
const MacAddress& macBytes,
494 const std::map<
BlockKey, std::vector<std::uint8_t>>& blocks)
498 auto get = [&](
BlockKey key) ->
const std::vector<std::uint8_t>*
500 auto it = blocks.find(key);
501 return it != blocks.end() ? &it->second :
nullptr;
504 if (
const auto* typeBlock = get(PNDCPBlock::DEVICE_TYPE))
508 auto firstNonNull = std::find_if(typeBlock->begin(), typeBlock->end(), [](
char c)
514 const std::string deviceTypeString(firstNonNull, typeBlock->end());
515 this->deviceType = deviceTypeString;
517 if (
const auto* nameBlock = get(PNDCPBlock::NAME_OF_STATION))
522 auto firstNonNull = std::find_if(nameBlock->begin(), nameBlock->end(), [](
char c)
528 const std::string name(firstNonNull, nameBlock->end());
532 if (
const auto* ipBlock = get(PNDCPBlock::IP_ADDRESS); (ipBlock !=
nullptr) && ipBlock->size() >= 12)
537 if (ipBlock->size() >= 14)
539 ipBlockInfo =
static_cast<std::uint16_t
>(((*ipBlock)[0] << OneOctetShift) | (*ipBlock)[1]);
540 ipConflict = IPBlockInfo::HasConflict(ipBlockInfo);
541 ipSetByDhcp = IPBlockInfo::IsDhcp(ipBlockInfo);
548 if (
const auto* devId = get(PNDCPBlock::DEVICE_ID); devId && devId->size() >= 4)
550 vendorHigh = (*devId)[devId->size() - 4];
551 vendorLow = (*devId)[devId->size() - 3];
552 deviceHigh = (*devId)[devId->size() - 2];
553 deviceLow = (*devId)[devId->size() - 1];
556 if (
const auto* roleBlock = get(PNDCPBlock::DEVICE_ROLE); roleBlock && !roleBlock->empty())
558 this->deviceRole = (*roleBlock)[0];
564 deviceInstance = {(*inst)[0], (*inst)[1]};
572 if (
const auto* opts = get(PNDCPBlock::DEVICE_OPTIONS); opts && opts->size() >= 2)
574 for (std::size_t i = 0; i + 1 < opts->size(); i += 2)
576 supportedOptions.emplace_back((*opts)[i], (*opts)[i + 1]);
580 for (
const auto& [key, Data] : blocks)
584 dhcpBlocks.push_back(DCPDHCPBlock::Parse(key.second, Data));
589 init && init->size() >= 2)
591 deviceInitiative =
static_cast<std::uint16_t
>(((*init)[0] << OneOctetShift) | (*init)[1]);
592 issuesHello = deviceInitiative == DeviceInitiative::ISSUE_HELLO;
595 static const std::vector<BlockKey> known = {
596 PNDCPBlock::IP_ADDRESS,
597 PNDCPBlock::DEVICE_TYPE,
598 PNDCPBlock::NAME_OF_STATION,
599 PNDCPBlock::DEVICE_ID,
600 PNDCPBlock::DEVICE_ROLE,
601 PNDCPBlock::DEVICE_OPTIONS,
602 PNDCPBlock::DEVICE_ALIAS,
603 PNDCPBlock::DEVICE_INSTANCE,
608 for (
const auto& [key, value] : blocks)
614 if (std::find(known.begin(), known.end(), key) == known.end())
616 rawBlocks[key] = value;
621std::string DCPDeviceDescription::VendorName()
const
626std::string DCPDeviceDescription::ToString()
const
628 std::string out =
"PROFINET Device: " + name +
"\n";
629 out +=
" MAC: " + mac +
"\n";
630 if (!deviceType.empty())
632 out +=
" Type: " + deviceType +
"\n";
634 out +=
" IP: " + ip +
"\n";
635 out +=
" Netmask: " + netmask +
"\n";
636 out +=
" Gateway: " + gateway +
"\n";
637 if (ipBlockInfo != 0u)
639 out +=
" IP Info: " + IPBlockInfo::GetName(ipBlockInfo) +
"\n";
643 out +=
" Warning: IP address conflict detected\n";
647 out +=
" IP Source: DHCP\n";
649 out +=
" Vendor: " + VendorName() +
" (" +
Hex4(VendorId()) +
")\n";
650 out +=
" Device: " +
Hex4(DeviceId()) +
"\n";
651 if (!deviceRoles.empty())
654 for (std::size_t i = 0; i < deviceRoles.size(); ++i)
656 out += deviceRoles[i];
657 if (i + 1 < deviceRoles.size())
664 if (deviceInstance != std::pair<std::uint8_t, std::uint8_t>{0, 0})
666 out +=
" Instance: " + std::to_string(deviceInstance.first) +
"." +
667 std::to_string(deviceInstance.second) +
"\n";
669 if (!aliasName.empty())
671 out +=
" Alias: " + aliasName +
"\n";
673 if (deviceInitiative != 0u)
675 out +=
" Initiative: " + DeviceInitiative::GetName(deviceInitiative) +
"\n";
677 if (!supportedOptions.empty())
679 out +=
" Supports: ";
680 for (std::size_t i = 0; i < supportedOptions.size(); ++i)
682 out +=
GetBlockName(supportedOptions[i].first, supportedOptions[i].second);
683 if (i + 1 < supportedOptions.size())
690 if (!dhcpBlocks.empty())
693 for (
const auto& b : dhcpBlocks)
697 out +=
" Hostname: " + *b.hostname +
"\n";
701 out +=
" FQDN: " + *b.fqdn +
"\n";
705 out +=
" UUID: " + *b.uuid +
"\n";
709 out +=
" " + b.suboptionName +
": " +
ToHex(b.rawData) +
"\n";
713 for (
const auto& [key, Data] : rawBlocks)
715 out +=
" Unknown (" + std::to_string(key.first) +
"," + std::to_string(key.second) +
716 "): " +
ToHex(Data) +
"\n";
718 if (!out.empty() && out.back() ==
'\n')
742bool EqualsIcase(std::string_view lhs, std::string_view rhs)
744 return std::ranges::equal(lhs, rhs, [](
char a,
char b)
746 return std::tolower(
static_cast<unsigned char>(a)) ==
747 std::tolower(
static_cast<unsigned char>(b));
759 const std::string& param,
760 const std::string& value,
763 if (EqualsIcase(param,
"ip"))
768 throw DCPError(
"Use SetIp() to configure the IP address");
770 auto it =
Params().find(param);
773 throw DCPError(
"Unknown parameter: '" + param +
"'");
777 throw ValidationError(
"Station name exceeds maximum length: " + std::to_string(value.size()) +
782 const std::uint32_t xid = GenerateXid();
783 const std::uint16_t qualifier = permanent ? 0x0001 : 0x0000;
788 block.
length =
static_cast<std::uint16_t
>(valueBytes.size() + 2);
789 block.
payload = {
static_cast<std::uint8_t
>((qualifier >> OneOctetShift) & LowByteMask),
790 static_cast<std::uint8_t
>(qualifier & LowByteMask)};
791 block.
payload.insert(block.
payload.end(), valueBytes.begin(), valueBytes.end());
795 auto blockData = block.
ToBytes();
796 if (valueBytes.size() % 2 == 1)
798 blockData.push_back(0x00);
807 dcp.
length =
static_cast<std::uint16_t
>(blockData.size());
809 return BuildEthernetDcp(dst, src, dcp);
814 const std::uint32_t xid = GenerateXid();
823 const Bytes blockData = {0x00, 0x00, 0x01, 0x00};
827 block.
length =
static_cast<std::uint16_t
>(blockData.size());
836 dcp.
length =
static_cast<std::uint16_t
>(blockData.size() + 4);
838 return BuildEthernetDcp(
881 const std::string& param,
int timeoutSec)
883 auto it =
Params().find(param);
886 throw DCPError(
"Unknown parameter: '" + param +
"'");
891 const std::uint32_t xid = GenerateXid();
907 sock.
Send(BuildEthernetDcp(dst, src, dcp));
909 auto responses =
ReadResponse(sock, src, timeoutSec,
true);
910 if (!responses.empty())
912 const auto& blocks = responses.begin()->second;
913 auto bit = blocks.find(key);
914 if (bit != blocks.end())
923 const std::string& param,
const std::string& value,
int timeoutSec,
bool permanent)
936 const std::uint8_t blockError =
RecvSetResponse(sock, src, timeoutSec);
941 std::this_thread::sleep_for(std::chrono::seconds(2));
951 const std::string& netmask,
const std::string& gateway,
bool permanent,
int timeoutSec)
954 const std::uint32_t xid = GenerateXid();
961 valueBytes.insert(valueBytes.end(), ipBytes.begin(), ipBytes.end());
962 valueBytes.insert(valueBytes.end(), netmaskBytes.begin(), netmaskBytes.end());
963 valueBytes.insert(valueBytes.end(), gatewayBytes.begin(), gatewayBytes.end());
965 const std::uint16_t qualifier = permanent ? BlockQualifier::PERMANENT : BlockQualifier::TEMPORARY;
968 block.
option = PNDCPBlock::IP_ADDRESS.first;
969 block.
suboption = PNDCPBlock::IP_ADDRESS.second;
970 block.
length =
static_cast<std::uint16_t
>(valueBytes.size() + 2);
971 block.
payload = {
static_cast<std::uint8_t
>(qualifier >> OneOctetShift),
static_cast<std::uint8_t
>(qualifier & LowByteMask)};
972 block.
payload.insert(block.
payload.end(), valueBytes.begin(), valueBytes.end());
974 const std::uint16_t padding = valueBytes.size() % 2 == 0 ? 0 : 1;
982 dcp.
length =
static_cast<std::uint16_t
>(valueBytes.size() + 6 + padding);
985 sock.
Send(BuildEthernetDcp(dst, src, dcp));
989 const std::uint8_t blockError =
RecvSetResponse(sock, src, timeoutSec);
994 std::this_thread::sleep_for(std::chrono::seconds(2));
1005 const std::uint32_t xid = GenerateXid();
1019 dcp.
length =
static_cast<std::uint16_t
>(blockBytes.size());
1027 const std::uint32_t xid = GenerateXid();
1030 block.
option = blockType.first;
1032 block.
length =
static_cast<std::uint16_t
>(value.size());
1042 dcp.
length =
static_cast<std::uint16_t
>(blockBytes.size());
1049 std::optional<std::uint32_t> expectedXid)
1052 sock.
SetTimeout(std::chrono::milliseconds(2000));
1053 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
1055 while (!timer.TimedOut())
1058 if (data.empty() || data.size() < 14)
1066 eth = EthernetHeader::Parse(data);
1068 catch (
const std::exception&)
1073 if (eth.
dst != myMac)
1085 const auto innerType =
static_cast<std::uint16_t
>((
payload[2] << OneOctetShift) |
payload[3]);
1100 dcp = PNDCPHeader::Parse(
payload);
1102 catch (
const std::exception&)
1111 if (expectedXid && dcp.
xId != *expectedXid)
1116 std::map<BlockKey, Bytes> parsed;
1118 const std::size_t consumedLimit = dcp.
length > 6 ?
static_cast<std::size_t
>(dcp.
length) : 0;
1120 ForEachBlock(blocksRegion, [&](std::uint8_t option, std::uint8_t suboption,
const Bytes& blockPayload)
1122 parsed[{option, suboption}] = blockPayload;
1125 result[eth.
src] = std::move(parsed);
1136 const std::string& ip,
const std::string& netmask,
const std::string& gateway,
1137 std::pair<std::uint16_t, std::uint16_t> deviceId, std::uint8_t deviceRole)
1139 const std::uint32_t xid = GenerateXid();
1147 nameBlock.
length =
static_cast<std::uint16_t
>(nameBytes.size());
1148 nameBlock.
payload = nameBytes;
1150 blocksData.insert(blocksData.end(), nameBlockBytes.begin(), nameBlockBytes.end());
1151 if (nameBytes.size() % 2 == 1)
1153 blocksData.push_back(0x00);
1161 ipPayload.insert(ipPayload.end(), ipB.begin(), ipB.end());
1162 ipPayload.insert(ipPayload.end(), nmB.begin(), nmB.end());
1163 ipPayload.insert(ipPayload.end(), gwB.begin(), gwB.end());
1167 ipBlock.
length =
static_cast<std::uint16_t
>(ipPayload.size());
1170 blocksData.insert(blocksData.end(), ipBlockBytes.begin(), ipBlockBytes.end());
1173 const Bytes deviceIdBytes = {
1174 static_cast<std::uint8_t
>(deviceId.first >> OneOctetShift),
static_cast<std::uint8_t
>(deviceId.first & LowByteMask),
1175 static_cast<std::uint8_t
>(deviceId.second >> OneOctetShift),
static_cast<std::uint8_t
>(deviceId.second & LowByteMask)};
1179 deviceIdBlock.
length =
static_cast<std::uint16_t
>(deviceIdBytes.size());
1180 deviceIdBlock.
payload = deviceIdBytes;
1182 blocksData.insert(blocksData.end(), deviceIdBlockBytes.begin(), deviceIdBlockBytes.end());
1189 roleBlock.
payload = {deviceRole, 0x00};
1191 blocksData.insert(blocksData.end(), roleBlockBytes.begin(), roleBlockBytes.end());
1198 initBlock.
payload = {0x00,
static_cast<std::uint8_t
>(DeviceInitiative::ISSUE_HELLO)};
1200 blocksData.insert(blocksData.end(), initBlockBytes.begin(), initBlockBytes.end());
1208 dcp.
length =
static_cast<std::uint16_t
>(blocksData.size());
1218 std::vector<DCPDeviceDescription> devices;
1219 sock.
SetTimeout(std::chrono::milliseconds(2000));
1220 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
1222 while (!timer.TimedOut())
1225 if (data.empty() || data.size() < 14)
1233 eth = EthernetHeader::Parse(data);
1235 catch (
const std::exception&)
1247 const auto innerType =
static_cast<std::uint16_t
>((
payload[2] << OneOctetShift) |
payload[3]);
1266 dcp = PNDCPHeader::Parse(
payload);
1268 catch (
const std::exception&)
1273 if (dcp.
serviceId != PNDCPHeader::HELLO)
1282 std::map<BlockKey, Bytes> blocks;
1283 ForEachBlock(dcp.
payload, [&](std::uint8_t option, std::uint8_t suboption,
const Bytes& blockPayload)
1285 blocks[{option, suboption}] = blockPayload;
1291 devices.push_back(device);
1298 catch (
const std::exception&)
1315 const std::uint8_t blockError =
RecvSetResponse(sock, src, timeoutSec);
1329 std::uint16_t mode,
int timeoutSec)
1332 const std::uint32_t xid = GenerateXid();
1334 const Bytes blockQualifier = {
static_cast<std::uint8_t
>(mode >> OneOctetShift),
static_cast<std::uint8_t
>(mode & LowByteMask)};
1339 block.
length =
static_cast<std::uint16_t
>(blockQualifier.size());
1340 block.
payload = blockQualifier;
1348 dcp.
length =
static_cast<std::uint16_t
>(blockQualifier.size() + 4);
1351 sock.
Send(BuildEthernetDcp(dst, src, dcp));
1355 const std::uint8_t blockError =
RecvSetResponse(sock, src, timeoutSec);
1360 std::this_thread::sleep_for(std::chrono::seconds(2));
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
void SetTimeout(std::chrono::milliseconds timeout) const override
Set the receive timeout.
void Send(const std::vector< std::uint8_t > &frame) const override
Send a raw Ethernet frame.
std::vector< std::uint8_t > Recv() const override
Receive up to MAX_ETHERNET_FRAME bytes.
Abstract interface for raw Ethernet frame transport.
virtual void SetTimeout(std::chrono::milliseconds timeout) const =0
Set the receive timeout.
virtual std::vector< std::uint8_t > Recv() const =0
Receive one raw Ethernet frame.
Time-limited-operation helper, mirroring util.py's MaxTimeout context manager.
Parsed PROFINET device information from a DCP response.
PROFINET DCP discovery and device configuration interface.
Declares exceptions and error types used by the PROFINET IO controller stack.
bool ResetToFactory(const EthernetSocket &sock, const MacAddress &src, const std::string &target, std::uint16_t mode=RESET_MODE_COMMUNICATION, int timeoutSec=DEFAULT_TIMEOUT)
Reset a device to factory defaults.
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).
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_SIGNAL
DCP suboption (Control): signal (flash LEDs).
constexpr std::uint8_t DCP_SUBOPTION_IP_PARAMETER
DCP suboption (IP): IP/netmask/gateway parameter.
constexpr std::uint8_t DEVICE_ROLE_IO_MULTIDEVICE
Device role bit: IO-Multidevice.
Bytes BuildSetParamRequest(const MacAddress &dst, const MacAddress &src, const std::string ¶m, const std::string &value, bool permanent)
Build a serialized DCP SET request for a named parameter.
constexpr std::uint8_t DCP_SUBOPTION_DHCP_CLIENT_ID
DCP suboption (DHCP): client identifier option.
bool SetParam(const EthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, const std::string &value, int timeoutSec=DEFAULT_TIMEOUT, bool permanent=false)
Write a named parameter to a device.
constexpr std::uint16_t DCP_HELLO_FRAME_ID
Frame ID for DCP Hello announcements.
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_INSTANCE
DCP suboption (Device): device instance.
constexpr std::uint8_t DCP_OPTION_DEVICE
DCP option: device identification.
const std::map< std::string, BlockKey > & Params()
Table of supported parameter names, mapping to their (option, suboption).
std::string GetBlockName(std::uint8_t option, std::uint8_t suboption)
Get the human-readable name for a (option, suboption) DCP block.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ROLE
DCP suboption (Device): device role bitmask.
constexpr std::uint8_t DCP_BLOCK_ERROR_SET_NOT_POSSIBLE
DCP SET block-level result: set not possible locally.
constexpr std::uint16_t DCP_GET_SET_FRAME_ID
Frame ID for DCP Get/Set requests and responses.
constexpr std::uint8_t DCP_BLOCK_ERROR_OPTION_UNSUPPORTED
DCP SET block-level result: option not supported.
constexpr std::uint8_t DCP_BLOCK_ERROR_SUBOPTION_UNSUPPORTED
DCP SET block-level result: suboption not supported.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_INITIATIVE
DCP suboption (DeviceInitiative): the only suboption.
constexpr std::uint8_t DCP_SUBOPTION_DHCP_UUID
DCP suboption (DHCP): client UUID option.
std::pair< std::uint8_t, std::uint8_t > BlockKey
(option, suboption) key identifying a DCP block.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ALIAS
DCP suboption (Device): alias name.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ID
DCP suboption (Device): vendor/device ID.
constexpr std::uint16_t DCP_IDENTIFY_REQUEST_FRAME_ID
Frame ID for DCP Identify requests.
constexpr std::uint8_t DCP_SUBOPTION_DHCP_VENDOR_SPEC
DCP suboption (DHCP): vendor-specific option.
std::optional< std::vector< std::uint8_t > > GetParam(const EthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, int timeoutSec=DEFAULT_TIMEOUT)
Read a named parameter from a device.
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.
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
constexpr std::uint8_t DCP_SUBOPTION_DHCP_FQDN
DCP suboption (DHCP): fully qualified domain name option.
constexpr std::uint8_t DEVICE_ROLE_IO_DEVICE
Device role bit: IO-Device.
const std::string DCP_HELLO_MULTICAST_MAC
DCP Hello multicast destination MAC.
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.
std::string DcpBlockErrorName(std::uint8_t code)
Get the human-readable name for a DCP SET block-level error code.
std::vector< std::string > DecodeDeviceRole(std::uint8_t roleByte)
Decode a device role bitmask into a list of role names.
constexpr std::uint8_t DCP_OPTION_IP
DCP option: IP configuration.
std::uint8_t RecvSetResponse(const IRawEthernetSocket &sock, const MacAddress &srcMac, int timeoutSec)
Receive and parse a DCP SET response, filtering out unrelated frames.
constexpr std::uint8_t DEVICE_ROLE_IO_CONTROLLER
Device role bit: IO-Controller.
bool SetIp(const EthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string &ip, const std::string &netmask, const std::string &gateway, bool permanent=false, int timeoutSec=DEFAULT_TIMEOUT)
Set a device's IP configuration via DCP.
constexpr std::uint8_t DCP_SUBOPTION_DHCP_HOSTNAME
DCP suboption (DHCP): hostname option.
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_RESPONSE
DCP suboption (Control): response.
constexpr std::uint8_t DCP_BLOCK_ERROR_IN_OPERATION
DCP SET block-level result: set not possible, device in operation.
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_NAME
DCP suboption (Device): station name.
constexpr std::uint8_t DCP_BLOCK_ERROR_OK
DCP SET block-level result: OK.
bool SignalDevice(const EthernetSocket &sock, const MacAddress &src, const std::string &target, int durationMs=FLASH_DURATION_MS, int timeoutSec=DEFAULT_TIMEOUT)
Flash a device's identification LEDs.
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_RESET_TO_FACTORY
DCP suboption (Control): reset to factory.
constexpr std::uint8_t DCP_BLOCK_ERROR_SUBOPTION_NOT_SET
DCP SET block-level result: suboption not set.
constexpr std::uint8_t DCP_OPTION_CONTROL
DCP option: control (start/stop/signal/reset).
constexpr std::uint8_t DCP_OPTION_DHCP
DCP option: DHCP.
constexpr std::uint8_t DEVICE_ROLE_PN_SUPERVISOR
Device role bit: PN-Supervisor.
constexpr std::uint8_t DCP_BLOCK_ERROR_RESOURCE
DCP SET block-level result: resource error.
void SendHello(const EthernetSocket &sock, const MacAddress &src, const std::string &stationName, const std::string &ip="0.0.0.0", const std::string &netmask="0.0.0.0", const std::string &gateway="0.0.0.0", std::pair< std::uint16_t, std::uint16_t > deviceId={0, 0}, std::uint8_t deviceRole=DEVICE_ROLE_IO_DEVICE)
Send a DCP Hello multicast announcement (device self-announce after power-on).
constexpr std::uint8_t DCP_SERVICE_TYPE_RESPONSE_SUCCESS
DCP service type: RESPONSE (success).
constexpr std::uint8_t DCP_OPTION_DEVICE_INITIATIVE
DCP option: device-initiative (Hello) configuration.
std::vector< DCPDeviceDescription > ReceiveHello(const EthernetSocket &sock, const MacAddress &myMac, int timeoutSec=DEFAULT_HELLO_TIMEOUT, const std::function< void(const DCPDeviceDescription &)> &callback=nullptr)
Listen for DCP Hello announcements from other devices.
std::uint8_t ParseSetResponse(const Bytes &data)
Parses the raw byte response payload received from a SET operation.
constexpr std::uint8_t DCP_SERVICE_TYPE_RESPONSE_UNSUPPORTED
DCP service type: RESPONSE (service unsupported).
constexpr std::size_t DCP_MAX_NAME_LENGTH
Maximum allowed length in bytes for a station name.
std::string Hex2(std::uint8_t value)
Format an 8-bit unsigned integer as a 2-digit hexadecimal string.
constexpr std::uint16_t VLAN_ETHERTYPE
EtherType value identifying an 802.1Q VLAN tag (0x8100).
constexpr int uuidLenght
Constant lenght of a UUID.
std::vector< std::uint8_t > ToVec(const std::string &input)
Convert a string into a vector of raw bytes.
std::string String2Ip(std::span< const std::uint8_t > ipBytes)
Format raw IPv4 address bytes as a dotted string.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
std::array< std::uint8_t, 4 > Ip2String(const std::string &ipStr)
Parse a dotted-decimal IPv4 address string.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
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).
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
std::string DecodeBytes(const std::uint8_t *data, std::size_t len)
Decode bytes to a UTF-8 string, stripping trailing NUL bytes.
std::string GetVendorName(std::uint16_t vendorId)
Get the vendor name for a 16-bit PROFINET vendor ID.
PROFINET protocol wire-format structures and serialization helpers.
DCP request block: Option(1) + SubOption(1) + Length(2) + payload(Length).
std::uint16_t length
Length in bytes of Payload.
std::uint8_t suboption
DCP suboption code.
Bytes payload
Block payload.
std::uint8_t option
DCP option code.
Bytes ToBytes() const
Serialize this block back to raw bytes.
Parsed DHCP block from a DCP response.
std::uint8_t suboption
DHCP suboption code.
std::optional< std::string > fqdn
Decoded fully qualified domain name, if this is an FQDN suboption.
std::vector< std::uint8_t > rawData
Raw, undecoded block payload.
std::optional< std::vector< std::uint8_t > > vendorSpecific
Decoded vendor-specific data, if this is a VendorSpec suboption.
std::optional< std::string > hostname
Decoded hostname, if this is a Hostname suboption.
std::string suboptionName
Human-readable name of the suboption.
std::optional< std::vector< std::uint8_t > > clientId
Decoded client ID, if this is a ClientID suboption.
std::optional< std::string > uuid
Decoded UUID string, if this is a UUID suboption.
Declares PROFINET Vendor ID lookup utilities. The lookup table itself lives in vendors_data....