36#include <magic_enum.hpp>
51std::uint32_t GenerateXid()
53 static thread_local std::mt19937 rng{std::random_device{}()};
54 static constexpr std::size_t maxXid = 0xFFFFFFFFULL;
55 static thread_local std::uniform_int_distribution<std::uint64_t> dist(0, maxXid);
56 return static_cast<std::uint32_t
>(dist(rng));
62void ForEachBlock(
const std::vector<std::uint8_t>& data, Fn&& fn)
66 while (r.Remaining() >= 4U)
70 const auto length = r.U16();
72 const std::size_t payloadLength =
73 std::min<std::size_t>(
length, r.Remaining());
79 if (payloadLength <
length)
86 if (r.Remaining() <
sizeof(std::uint8_t))
91 r.Skip(
sizeof(std::uint8_t));
99 std::vector<std::string> roles;
101 for (
const auto role : magic_enum::enum_values<DcpDeviceRole>())
103 const auto mask =
static_cast<std::uint8_t
>(role);
105 if ((roleByte & mask) != 0U)
107 roles.emplace_back(magic_enum::enum_name(role));
113 roles.emplace_back(
"Unknown");
120 return magic_enum::enum_contains(role)
121 ? std::string(magic_enum::enum_name(role))
128 if (magic_enum::enum_contains(dcpOption))
136 if (magic_enum::enum_contains(
suboption))
138 return std::string{magic_enum::enum_name(
suboption)};
147 if (magic_enum::enum_contains(
suboption))
149 return std::string{magic_enum::enum_name(
suboption)};
158 if (magic_enum::enum_contains(
suboption))
160 return std::string{magic_enum::enum_name(
suboption)};
169 if (magic_enum::enum_contains(
suboption))
171 return std::string{magic_enum::enum_name(
suboption)};
180 if (magic_enum::enum_contains(
suboption))
182 return std::string{magic_enum::enum_name(
suboption)};
198 std::string optionName;
203 optionName = std::string{magic_enum::enum_name(dcpOption)};
219 return optionName +
"/" + suboptionName;
255 sock.
SetTimeout(std::chrono::milliseconds(2000));
257 while (!
timer.TimedOut())
260 if (data.empty() || data.size() < 14)
270 catch (
const std::exception&)
275 if (eth.
dst != srcMac)
284 if (
payload.size() < 2 *
sizeof(std::uint16_t))
289 r.
Skip(
sizeof(std::uint16_t));
290 const auto innerType = r.
U16();
307 if (std::string(e.what()).find(
"unexpected service_type") != std::string::npos)
321 if (data.size() < 14)
323 throw DCPError(
"DCP SET response too short");
331 catch (
const std::exception& e)
333 throw DCPError(std::string(
"Failed to parse Ethernet header: ") + e.what());
339 if (
payload.size() < 2 *
sizeof(std::uint16_t))
341 throw DCPError(
"VLAN frame too short");
344 r.
Skip(
sizeof(std::uint16_t));
345 const auto innerType = r.
U16();
349 throw DCPError(
"Unexpected inner EtherType: " +
Hex4(innerType));
363 catch (
const std::exception& e)
365 throw DCPError(std::string(
"Failed to parse DCP header: ") + e.what());
370 throw DCPError(
"DCP SET: service not supported by device");
388 if (blockPayload.size() >= 3)
390 result = static_cast<DcpBlockError>(blockPayload[2]);
392 else if (!blockPayload.empty())
394 result = static_cast<DcpBlockError>(blockPayload[0]);
406 case DcpIpBlockInfo::NotSet:
408 case DcpIpBlockInfo::Set:
410 case DcpIpBlockInfo::SetByDhcp:
411 return "IP set by DHCP";
412 case DcpIpBlockInfo::NotSetConflict:
413 return "IP not set (address conflict detected)";
414 case DcpIpBlockInfo::SetConflict:
415 return "IP set (address conflict detected)";
416 case DcpIpBlockInfo::SetByDhcpConflict:
417 return "IP set by DHCP (address conflict detected)";
420 return "Unknown (" +
Hex4(
static_cast<std::uint16_t
>(info)) +
")";
436 const auto name = magic_enum::enum_name(qualifier);
440 return std::string{name};
444 Hex4(
static_cast<std::uint16_t
>(qualifier)) +
")";
449 auto resetQualifier = magic_enum::enum_cast<DcpResetQualifier>(qualifier);
450 if (resetQualifier.has_value())
454 return "Unknown (" +
Hex4(qualifier) +
")";
461 case DcpResetQualifier::ApplicationData:
462 case DcpResetQualifier::ApplicationDataAlternate:
463 return "Reset application data";
464 case DcpResetQualifier::CommunicationParameter:
465 case DcpResetQualifier::CommunicationParameterAlternate:
466 return "Reset communication parameter";
467 case DcpResetQualifier::EngineeringParameter:
468 case DcpResetQualifier::EngineeringParameterAlternate:
469 return "Reset engineering parameter";
470 case DcpResetQualifier::AllStoredData:
471 case DcpResetQualifier::AllStoredDataAlternate:
472 return "Reset all stored data";
474 case DcpResetQualifier::EngineeringParameterExtended:
475 case DcpResetQualifier::EngineeringParameterExtendedAlternate:
476 return "Reset engineering parameter";
477 case DcpResetQualifier::ToFactory:
478 case DcpResetQualifier::ToFactoryAlternate:
479 return "Reset to factory values";
480 case DcpResetQualifier::Restore:
481 case DcpResetQualifier::RestoreAlternate:
482 return "Reset and restore data";
484 return "Unknown (" +
Hex4(
static_cast<std::uint16_t
>(qualifier)) +
")";
492 case DcpDeviceInitiative::NoHello:
493 return "Device does not issue DCP-Hello after power on";
494 case DcpDeviceInitiative::IssueHello:
495 return "Device issues DCP-Hello after power on";
497 return "Unknown (" +
Hex4(
static_cast<std::uint16_t
>(value)) +
")";
505 case DcpResponseCode::NoError:
507 case DcpResponseCode::OptionNotSupported:
508 return "Option not supported";
509 case DcpResponseCode::SuboptionNotSupported:
510 return "Suboption not supported or no DataSet available";
511 case DcpResponseCode::SuboptionNotSet:
512 return "Suboption not set";
513 case DcpResponseCode::ResourceError:
514 return "Resource error";
515 case DcpResponseCode::SetNotPossible:
516 return "Set not possible";
517 case DcpResponseCode::InOperationSetNotPossible:
518 return "In operation, SET not possible";
520 return "Unknown (" +
Hex2(
static_cast<std::uint8_t
>(value)) +
")";
528 case DcpBlockError::Ok:
531 case DcpBlockError::OptionUnsupported:
532 return "Option not supported";
534 case DcpBlockError::SuboptionUnsupported:
535 return "Suboption not supported or no dataset available";
537 case DcpBlockError::SuboptionNotSet:
538 return "Suboption not set";
540 case DcpBlockError::Resource:
541 return "Resource error";
543 case DcpBlockError::SetNotPossible:
544 return "SET not possible by local reasons";
546 case DcpBlockError::InOperation:
547 return "In operation, SET not possible";
550 return "Unknown error (" +
Hex2(
static_cast<std::uint8_t
>(
error)) +
")";
558 ? std::string{magic_enum::enum_name(
suboption)}
563 if (
suboption == DcpDhcpSuboption::Hostname)
567 else if (
suboption == DcpDhcpSuboption::ClientId)
571 else if (
suboption == DcpDhcpSuboption::VendorSpec)
575 else if (
suboption == DcpDhcpSuboption::Fqdn)
582 ToHex(&data[4], 2) +
"-" +
583 ToHex(&data[6], 2) +
"-" +
584 ToHex(&data[8], 2) +
"-" +
590const std::map<std::string, BlockKey>&
Params()
592 static const std::map<std::string, BlockKey> table = {
593 {
"name",
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::NAME_OF_STATION.first), PNDCPBlock::NAME_OF_STATION.second)},
594 {
"ip",
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::IP_ADDRESS.first), PNDCPBlock::IP_ADDRESS.second)},
603DCPDeviceDescription::DCPDeviceDescription(
const MacAddress& macBytes,
604 const std::map<
BlockKey, std::vector<std::uint8_t>>& blocks)
608 auto get = [&](
BlockKey key) ->
const std::vector<std::uint8_t>*
610 auto it = blocks.find(key);
611 return it != blocks.end() ? &it->second :
nullptr;
614 if (
const auto* typeBlock = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_TYPE.first), PNDCPBlock::DEVICE_TYPE.second)))
616 auto firstNonNull = std::find_if(typeBlock->begin(), typeBlock->end(), [](
char c)
620 const std::string deviceTypeString(firstNonNull, typeBlock->end());
621 this->deviceType = deviceTypeString;
623 if (
const auto* nameBlock = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::NAME_OF_STATION.first), PNDCPBlock::NAME_OF_STATION.second)))
625 auto firstNonNull = std::find_if(nameBlock->begin(), nameBlock->end(), [](
char c)
630 const std::string stationName(firstNonNull, nameBlock->end());
631 this->name = stationName;
634 if (
const auto* ipBlock = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::IP_ADDRESS.first), PNDCPBlock::IP_ADDRESS.second)); (ipBlock !=
nullptr) && ipBlock->size() >= 12)
636 if (ipBlock->size() >= 14U)
642 ipSetByDhcp =
IsDhcp(ipBlockInfo);
648 else if (ipBlock->size() >= 12U)
658 if (
const auto* devId = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_ID.first), PNDCPBlock::DEVICE_ID.second)); devId && devId->size() >= 4)
661 r.
Skip(devId->size() - 4U);
666 vendorHigh =
ByteAt(vendorId, 1U);
667 vendorLow =
ByteAt(vendorId, 0U);
668 deviceHigh =
ByteAt(deviceId, 1U);
669 deviceLow =
ByteAt(deviceId, 0U);
672 if (
const auto* roleBlock = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_ROLE.first), PNDCPBlock::DEVICE_ROLE.second)); roleBlock && !roleBlock->empty())
676 this->deviceRole = r.
U8();
680 if (
const auto* inst = get(
MakeBlockKey(DcpOption::Device, DcpDeviceSuboption::Instance)); inst && inst->size() >= 2)
683 deviceInstance = {r.
U8(), r.
U8()};
686 if (
const auto* alias = get(
MakeBlockKey(DcpOption::Device, DcpDeviceSuboption::Alias)))
691 if (
const auto* opts = get(
MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_OPTIONS.first), PNDCPBlock::DEVICE_OPTIONS.second)); opts && opts->size() >= 2)
695 for (std::size_t i = 0; i + 1 < opts->size(); i +=
sizeof(std::uint16_t))
704 for (
const auto& [key, Data] : blocks)
706 if (key.option == DcpOption::Dhcp)
708 dhcpBlocks.push_back(DCPDHCPBlock::Parse(
static_cast<DcpDhcpSuboption>(key.suboption), Data));
712 if (
const auto* init = get(
MakeBlockKey(DcpOption::DeviceInitiative, DcpDeviceInitiativeSuboption::Initiative));
713 (init !=
nullptr) && init->size() >= 2)
717 issuesHello = deviceInitiative == DcpDeviceInitiative::IssueHello;
720 static const std::vector<BlockKey> known = {
723 MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::NAME_OF_STATION.first), PNDCPBlock::NAME_OF_STATION.second),
726 MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_OPTIONS.first), PNDCPBlock::DEVICE_OPTIONS.second),
727 MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_ALIAS.first), PNDCPBlock::DEVICE_ALIAS.second),
728 MakeBlockKey(
static_cast<DcpOption>(PNDCPBlock::DEVICE_INSTANCE.first), PNDCPBlock::DEVICE_INSTANCE.second),
729 MakeBlockKey(DcpOption::Device, DcpDeviceSuboption::Instance),
730 MakeBlockKey(DcpOption::Device, DcpDeviceSuboption::Alias),
731 MakeBlockKey(DcpOption::DeviceInitiative, DcpDeviceInitiativeSuboption::Initiative),
733 for (
const auto& [key, value] : blocks)
735 if (key.option == DcpOption::Dhcp)
739 if (std::ranges::find(known, key) == known.end())
741 rawBlocks[key] = value;
746std::string DCPDeviceDescription::VendorName()
const
751std::string DCPDeviceDescription::ToString()
const
753 std::string out =
"PROFINET Device: " + name +
"\n";
754 out +=
" MAC: " + mac +
"\n";
755 if (!deviceType.empty())
757 out +=
" Type: " + deviceType +
"\n";
759 out +=
" IP: " + ip +
"\n";
760 out +=
" Netmask: " + netmask +
"\n";
761 out +=
" Gateway: " + gateway +
"\n";
762 if (ipBlockInfo != DcpIpBlockInfo::NotSet)
768 out +=
" Warning: IP address conflict detected\n";
772 out +=
" IP Source: DHCP\n";
774 out +=
" Vendor: " + VendorName() +
" (" +
Hex4(VendorId()) +
")\n";
776 if (!deviceRoles.empty())
779 for (std::size_t i = 0; i < deviceRoles.size(); ++i)
781 out += deviceRoles[i];
782 if (i + 1 < deviceRoles.size())
789 if (deviceInstance != std::pair<std::uint8_t, std::uint8_t>{0, 0})
791 out +=
" Instance: " + std::to_string(deviceInstance.first) +
"." +
792 std::to_string(deviceInstance.second) +
"\n";
794 if (!aliasName.empty())
796 out +=
" Alias: " + aliasName +
"\n";
798 if (deviceInitiative != DcpDeviceInitiative::NoHello)
802 if (!supportedOptions.empty())
804 out +=
" Supports: ";
805 for (std::size_t i = 0; i < supportedOptions.size(); ++i)
808 if (i + 1 < supportedOptions.size())
815 if (!dhcpBlocks.empty())
818 for (
const auto& b : dhcpBlocks)
822 out +=
" Hostname: " + *b.hostname +
"\n";
826 out +=
" FQDN: " + *b.fqdn +
"\n";
830 out +=
" UUID: " + *b.uuid +
"\n";
834 out +=
" " + b.suboptionName +
": " +
ToHex(b.rawData) +
"\n";
838 for (
const auto& [key, Data] : rawBlocks)
840 out +=
" Unknown (" +
841 std::to_string(
static_cast<int>(key.option)) +
"," +
842 std::to_string(
static_cast<int>(key.suboption)) +
843 "): " +
ToHex(Data) +
"\n";
845 if (!out.empty() && out.back() ==
'\n')
869bool EqualsIcase(std::string_view lhs, std::string_view rhs)
871 return std::ranges::equal(lhs, rhs, [](
char a,
char b)
873 return std::tolower(
static_cast<unsigned char>(a)) ==
874 std::tolower(
static_cast<unsigned char>(b));
885 const std::string& param,
886 const std::string& value,
889 if (EqualsIcase(param,
"ip"))
894 throw DCPError(
"Use SetIp() to configure the IP address");
896 auto it =
Params().find(param);
899 throw DCPError(
"Unknown parameter: '" + param +
"'");
903 throw ValidationError(
"Station name exceeds maximum length: " + std::to_string(value.size()) +
908 const std::uint32_t xid = GenerateXid();
909 const std::uint16_t qualifier = permanent ? 0x0001 : 0x0000;
914 block.
length =
static_cast<std::uint16_t
>(valueBytes.size() + 2);
915 wire::PutBigEndian(block.
payload, qualifier);
916 wire::PutFixed(block.
payload, valueBytes);
920 auto blockData = block.
ToBytes();
921 if (valueBytes.size() % 2 == 1)
923 blockData.push_back(0x00);
927 dcp.
frameId = DcpFrameId::GetSet;
932 dcp.
length =
static_cast<std::uint16_t
>(blockData.size());
934 return BuildEthernetDcp(dst, src, dcp);
939 const std::uint32_t xid = GenerateXid();
948 const Bytes blockData = {0x00, 0x00, 0x01, 0x00};
950 block.
option = DcpOption::Control;
951 block.
suboption =
static_cast<std::uint8_t
>(DcpControlSuboption::Signal);
952 block.
length =
static_cast<std::uint16_t
>(blockData.size());
956 dcp.
frameId = DcpFrameId::GetSet;
961 dcp.
length =
static_cast<std::uint16_t
>(blockData.size() + 4);
963 return BuildEthernetDcp(
1006 const std::string& param,
int timeoutSec)
1008 auto it =
Params().find(param);
1009 if (it ==
Params().end())
1011 throw DCPError(
"Unknown parameter: '" + param +
"'");
1016 const std::uint32_t xid = GenerateXid();
1024 dcp.
frameId = DcpFrameId::GetSet;
1060 sock.
Send(BuildEthernetDcp(dst, src, dcp));
1062 const auto responses =
ReadResponse(sock, src, timeoutSec,
true, xid);
1063 std::fprintf(stderr,
1064 "GetParam: xid=%08x responses=%zu\n",
1067 for (
const auto& [mac, blocks] : responses)
1069 std::fprintf(stderr,
1070 " response src=%02x:%02x:%02x:%02x:%02x:%02x blocks=%zu\n",
1071 mac[0], mac[1], mac[2],
1072 mac[3], mac[4], mac[5],
1075 for (
const auto& [blockKey, value] : blocks)
1077 std::fprintf(stderr,
1078 " block option=%02x suboption=%02x size=%zu\n",
1079 static_cast<unsigned>(blockKey.option),
1080 static_cast<unsigned>(blockKey.suboption),
1084 if (!responses.empty())
1086 const auto& blocks = responses.begin()->second;
1087 auto bit = blocks.find(key);
1088 if (bit != blocks.end())
1093 return std::nullopt;
1097 const std::string& param,
const std::string& value,
int timeoutSec,
bool permanent,
int threadSleepMs)
1107 if (blockError != DcpBlockError::Ok)
1111 std::this_thread::sleep_for(std::chrono::milliseconds(threadSleepMs));
1121 const std::string& netmask,
const std::string& gateway,
bool permanent,
int timeoutSec,
int threadSleepMs)
1124 const std::uint32_t xid = GenerateXid();
1131 valueBytes.insert(valueBytes.end(), ipBytes.begin(), ipBytes.end());
1132 valueBytes.insert(valueBytes.end(), netmaskBytes.begin(), netmaskBytes.end());
1133 valueBytes.insert(valueBytes.end(), gatewayBytes.begin(), gatewayBytes.end());
1135 const DcpBlockQualifier qualifier = permanent ? DcpBlockQualifier::Permanent : DcpBlockQualifier::Temporary;
1139 block.
suboption = PNDCPBlock::IP_ADDRESS.second;
1140 block.
length =
static_cast<std::uint16_t
>(valueBytes.size() + 2);
1141 wire::PutBigEndian(block.
payload,
static_cast<std::uint16_t
>(qualifier));
1142 wire::PutFixed(block.
payload, valueBytes);
1144 const std::uint16_t padding = valueBytes.size() % 2 == 0 ? 0 : 1;
1147 dcp.
frameId = DcpFrameId::GetSet;
1155 sock.
Send(BuildEthernetDcp(dst, src, dcp));
1160 if (blockError != DcpBlockError::Ok)
1164 std::this_thread::sleep_for(std::chrono::milliseconds(threadSleepMs));
1175 const std::uint32_t xid = GenerateXid();
1178 block.
option = DcpOption::All;
1183 dcp.
frameId = DcpFrameId::IdentifyRequest;
1189 dcp.
length =
static_cast<std::uint16_t
>(blockBytes.size());
1197 const std::uint32_t xid = GenerateXid();
1202 block.
length =
static_cast<std::uint16_t
>(value.size());
1206 dcp.
frameId = DcpFrameId::IdentifyRequest;
1212 dcp.
length =
static_cast<std::uint16_t
>(blockBytes.size());
1219 std::optional<std::uint32_t> expectedXid)
1222 sock.
SetTimeout(std::chrono::milliseconds(2000));
1225 while (!
timer.TimedOut())
1228 std::fprintf(stderr,
1229 "ReadResponse: received %zu bytes, expectedXid=%08x\n",
1231 expectedXid.value_or(0U));
1241 if (data.size() < 14)
1249 eth = EthernetHeader::Parse(data);
1250 std::fprintf(stderr,
1251 "ReadResponse: eth dst=%02x:%02x:%02x:%02x:%02x:%02x "
1252 "src=%02x:%02x:%02x:%02x:%02x:%02x type=%04x\n",
1259 catch (
const std::exception&)
1264 if (eth.
dst != myMac)
1266 std::fprintf(stderr,
"ReadResponse: rejected: destination mismatch\n");
1274 if (
payload.size() < 2 *
sizeof(std::uint16_t))
1278 r.
Skip(
sizeof(std::uint16_t));
1279 const auto innerType = r.
U16();
1294 dcp = PNDCPHeader::Parse(
payload);
1295 std::fprintf(stderr,
1296 "ReadResponse: dcp serviceType=%02x xid=%08x length=%u payload=%zu\n",
1302 catch (
const std::exception&)
1307 if (dcp.
serviceType != DcpServiceType::ResponseSuccess)
1309 std::fprintf(stderr,
"ReadResponse: rejected: not ResponseSuccess\n");
1312 if (expectedXid && dcp.
xId != *expectedXid)
1314 std::fprintf(stderr,
1315 "ReadResponse: rejected: XID mismatch, expected=%08x actual=%08x\n",
1321 std::map<BlockKey, Bytes> parsed;
1323 const std::size_t consumedLimit = dcp.
length > 6 ?
static_cast<std::size_t
>(dcp.
length) : 0;
1324 const Bytes blocksRegion = dcp.
payload.size() > consumedLimit
1332 result[eth.
src] = std::move(parsed);
1343 const std::string& ip,
const std::string& netmask,
const std::string& gateway,
1344 std::pair<std::uint16_t, std::uint16_t> deviceId,
DcpDeviceRole deviceRole)
1346 const std::uint32_t xid = GenerateXid();
1352 nameBlock.
option = DcpOption::Device;
1353 nameBlock.
suboption =
static_cast<std::uint8_t
>(DcpDeviceSuboption::Name);
1354 nameBlock.
length =
static_cast<std::uint16_t
>(nameBytes.size());
1355 nameBlock.
payload = nameBytes;
1357 blocksData.insert(blocksData.end(), nameBlockBytes.begin(), nameBlockBytes.end());
1358 if (nameBytes.size() % 2 == 1)
1360 blocksData.push_back(0x00);
1368 ipPayload.insert(ipPayload.end(), ipB.begin(), ipB.end());
1369 ipPayload.insert(ipPayload.end(), nmB.begin(), nmB.end());
1370 ipPayload.insert(ipPayload.end(), gwB.begin(), gwB.end());
1372 ipBlock.
option = DcpOption::Ip;
1373 ipBlock.
suboption =
static_cast<std::uint8_t
>(DcpIpSuboption::IpParameter);
1374 ipBlock.
length =
static_cast<std::uint16_t
>(ipPayload.size());
1378 wire::PutFixed(blocksData, ipBlockBytes);
1381 Bytes deviceIdBytes;
1382 deviceIdBytes.reserve(4U);
1383 wire::PutBigEndian(deviceIdBytes, deviceId.first);
1384 wire::PutBigEndian(deviceIdBytes, deviceId.second);
1387 deviceIdBlock.
option = DcpOption::Device;
1388 deviceIdBlock.
suboption =
static_cast<std::uint8_t
>(DcpDeviceSuboption::DeviceId);
1389 deviceIdBlock.
length =
static_cast<std::uint16_t
>(deviceIdBytes.size());
1390 deviceIdBlock.
payload = deviceIdBytes;
1392 wire::PutFixed(blocksData, deviceIdBlockBytes);
1396 roleBlock.
option = DcpOption::Device;
1397 roleBlock.
suboption =
static_cast<std::uint8_t
>(DcpDeviceSuboption::Role);
1399 roleBlock.
payload = {
static_cast<std::uint8_t
>(deviceRole), 0x00};
1401 blocksData.insert(blocksData.end(), roleBlockBytes.begin(), roleBlockBytes.end());
1405 initBlock.
option = DcpOption::DeviceInitiative;
1406 initBlock.
suboption =
static_cast<std::uint8_t
>(DcpDeviceInitiativeSuboption::Initiative);
1408 initBlock.
payload = {0x00,
static_cast<std::uint8_t
>(DcpDeviceInitiative::IssueHello)};
1410 blocksData.insert(blocksData.end(), initBlockBytes.begin(), initBlockBytes.end());
1413 dcp.
frameId = DcpFrameId::Hello;
1418 dcp.
length =
static_cast<std::uint16_t
>(blocksData.size());
1428 std::vector<DCPDeviceDescription> devices;
1429 sock.
SetTimeout(std::chrono::milliseconds(2000));
1432 while (!
timer.TimedOut())
1435 if (data.empty() || data.size() < 14)
1443 eth = EthernetHeader::Parse(data);
1445 catch (
const std::exception&)
1454 if (
payload.size() < 2 *
sizeof(std::uint16_t))
1458 r.
Skip(
sizeof(std::uint16_t));
1459 const auto innerType = r.
U16();
1478 dcp = PNDCPHeader::Parse(
payload);
1480 catch (
const std::exception&)
1485 if (dcp.
serviceId != DcpServiceId::Hello)
1494 std::map<BlockKey, Bytes> blocks;
1497 blocks[MakeBlockKey(option, suboption)] = blockPayload;
1503 devices.push_back(device);
1510 catch (
const std::exception&)
1520 [[maybe_unused]]
int durationMs,
double timeoutSec)
1529 if (blockError != DcpBlockError::Ok)
1545 const std::uint32_t xid = GenerateXid();
1547 const auto modeValue =
static_cast<std::uint16_t
>(mode);
1549 Bytes blockQualifier;
1550 wire::PutBigEndian(blockQualifier, modeValue);
1553 block.
option = DcpOption::Control;
1554 block.
suboption =
static_cast<std::uint8_t
>(DcpControlSuboption::ResetToFactory);
1555 block.
length =
static_cast<std::uint16_t
>(blockQualifier.size());
1556 block.
payload = blockQualifier;
1559 dcp.
frameId = DcpFrameId::GetSet;
1564 dcp.
length =
static_cast<std::uint16_t
>(blockQualifier.size() + 4);
1567 sock.
Send(BuildEthernetDcp(dst, src, dcp));
1571 const auto blockError =
1573 if (blockError != DcpBlockError::Ok)
1577 std::this_thread::sleep_for(std::chrono::seconds(2));
Abstract interface for raw Ethernet frame transport.
virtual void Send(const std::vector< std::uint8_t > &frame)=0
Send one complete raw Ethernet frame.
virtual std::vector< std::uint8_t > Recv()=0
Receive one raw Ethernet frame.
virtual void SetTimeout(std::chrono::milliseconds timeout)=0
Set the receive timeout.
Time-limited-operation helper.
Parsed PROFINET device information from a DCP response.
Lightweight cursor for parsing a big-endian byte buffer without copies.
std::uint8_t U8()
Read and consume one byte.
std::array< std::uint8_t, N > Fixed()
Read and consume a fixed-size byte array.
void Skip(std::size_t n)
Advance the read position without returning the skipped bytes.
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
PROFINET DCP discovery and device configuration interface.
Declares exceptions and error types used by the PROFINET IO controller stack.
bool HasConflict(DcpIpBlockInfo info)
Whether an address conflict was detected.
void SendDiscover(IRawEthernetSocket &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.
std::optional< std::vector< std::uint8_t > > GetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, int timeoutSec=DEFAULT_TIMEOUT)
Read a named parameter from a device.
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.
bool ResetToFactory(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, DcpResetMode mode=DcpResetMode::Communication, double timeoutSec=DEFAULT_TIMEOUT)
Reset a device to factory defaults.
std::string GetResetQualifierName(std::uint16_t qualifier)
Get the human-readable name for a reset qualifier value.
void SendHello(IRawEthernetSocket &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}, DcpDeviceRole deviceRole=DcpDeviceRole::IoDevice)
Send a DCP Hello multicast announcement (device self-announce after power-on).
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
bool SetIp(IRawEthernetSocket &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, int threadSleepMs=DEFAULT_THREAD_SLEEP_MS)
Set a device's IP configuration via DCP.
const std::map< std::string, BlockKey > & Params()
Table of supported parameter names, mapping to their (option, suboption).
std::string DeviceRoleName(DcpDeviceRole role)
Get the human-readable name of a DCP device role.
bool SignalDevice(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, int durationMs=FLASH_DURATION_MS, double timeoutSec=DEFAULT_TIMEOUT)
Flash a device's identification LEDs.
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
DcpBlockError RecvSetResponse(IRawEthernetSocket &sock, const MacAddress &srcMac, int timeoutSec)
Receive and parse a DCP SET response, filtering out unrelated frames.
std::string GetDeviceInitiativeName(DcpDeviceInitiative value)
std::string GetDcpBlockQualifierDescription(DcpBlockQualifier qualifier)
Get the human-readable description of a DCP block qualifier.
constexpr std::uint16_t DCP_IP_DHCP_BIT
Bit mask indicating DHCP bit is set.
bool IsDhcp(DcpIpBlockInfo info)
Whether the address was assigned via DHCP.
constexpr std::uint16_t blockNameVendorRangeMin
Default flash duration of the leds.
constexpr std::uint16_t blockNameVendorRangeMax
Default flash duration of the leds.
void SendRequest(IRawEthernetSocket &sock, const MacAddress &src, BlockKey blockType, const std::vector< std::uint8_t > &value)
Send a DCP Identify request filtered to a specific (option, suboption).
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
const std::string DCP_HELLO_MULTICAST_MAC
DCP Hello multicast destination MAC.
std::string GetIPBlockInfoName(DcpIpBlockInfo info)
Get the human-readable name for an IP block info value.
std::vector< std::string > DecodeDeviceRole(std::uint8_t roleByte)
Decode a device role bitmask into a list of role names.
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
std::string GetBlockName(std::uint16_t option, std::uint8_t suboption)
Get the human-readable name for a (option, suboption) DCP block.
ResponseMap ReadResponse(IRawEthernetSocket &sock, const MacAddress &myMac, double timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
constexpr std::uint16_t DCP_IP_CONFLICT_BIT
Bit mask indicating an IP address conflict.
std::string GetDcpBlockErrorDescription(DcpBlockError error)
Get the human-readable name for a DCP SET block-level error code.
std::string GetSuboptionName(std::uint16_t option, std::uint8_t value)
std::string GetDcpResponseCodeName(DcpResponseCode value)
Get the human-readable name for a device-response value.
bool SetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, const std::string &value, int timeoutSec=DEFAULT_TIMEOUT, bool permanent=false, int threadSleepMs=DEFAULT_THREAD_SLEEP_MS)
Write a named parameter to a device.
DcpBlockError ParseSetResponse(const Bytes &data)
Parses the raw byte response payload received from a SET operation.
std::vector< DCPDeviceDescription > ReceiveHello(IRawEthernetSocket &sock, const MacAddress &myMac, double timeoutSec=DEFAULT_HELLO_TIMEOUT, const std::function< void(const DCPDeviceDescription &)> &callback=nullptr)
Listen for DCP Hello announcements from other devices.
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.
DcpBlockQualifier
DCP block qualifier values.
DcpIpSuboption
DCP IP suboptions.
DcpDeviceInitiativeSuboption
DCP DeviceInitiative suboptions.
DcpDhcpSuboption
DCP DHCP suboptions.
constexpr std::uint16_t VLAN_ETHERTYPE
EtherType value identifying an 802.1Q VLAN tag (0x8100).
constexpr int uuidLenght
Constant lenght of a UUID.
DcpControlSuboption
DCP Control suboptions.
@ Response
Control response.
std::vector< std::uint8_t > ToVec(const std::string &input)
Convert a string into a vector of raw bytes.
DcpBlockError
DCP block-level SET response errors.
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.
DcpResetMode
Reset mode bitmask.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
constexpr int macAddressLength
Constant lenght of a mac address.
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
@ Unknown
Not set / initialize value.
@ All
Wildcard: all options.
@ DeviceInitiative
Device initiative.
@ Device
Device identification.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
DcpDeviceSuboption
DCP Device suboptions.
@ DeviceId
Vendor and device ID.
DcpDeviceInitiative
DCP device-initiative values.
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.
DcpResetQualifier
Reset qualifier values for ResetToFactory().
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
@ ResponseUnsupported
Service unsupported response.
@ ResponseSuccess
Successful response.
DcpIpBlockInfo
PROFINET DCP IP block status values. IP block status flags (IEC 61158-6-10 IPBlockInfo).
DcpDeviceRole
Device role bitmask.
std::string DecodeBytes(const std::uint8_t *data, std::size_t len)
Decode bytes to a UTF-8 string, stripping trailing NUL bytes.
constexpr std::uint8_t ByteAt(T value, std::size_t index)
Return one byte from an integral value.
std::string GetVendorName(std::uint16_t vendorId)
Get the vendor name for a 16-bit PROFINET vendor ID.
DcpResponseCode
DCP GET/SET response codes.
PROFINET protocol wire-format structures and serialization helpers.
Identifies a PROFINET DCP block by option and suboption.
std::uint8_t suboption
DCP suboption code.
DcpOption option
DCP option.
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.
DcpOption option
DCP option code.
Bytes ToBytes() const
Serialize this block back to raw bytes.
Parsed DHCP block from a DCP response.
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.
DcpDhcpSuboption suboption
DHCP suboption code.
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....