PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
device.cpp
Go to the documentation of this file.
1
3
4#include "profinet/device.h"
5
6#include <algorithm>
7#include <cctype>
8#include <cstdio>
9#include <cstring>
10#include <iostream>
11#include <sstream>
12
14#include "profinet/exceptions.h"
15#include "profinet/indices.h"
16#include "profinet/wire.h"
17
18namespace profinet::device
19{
20
21namespace
22{
23
24std::string Trim(const std::string& s)
25{
26 const std::size_t start = s.find_first_not_of(" \t\r\n\0", 0, 4);
27 if (start == std::string::npos)
28 {
29 return "";
30 }
31 const std::size_t end = s.find_last_not_of(" \t\r\n\0", std::string::npos, 4);
32 return s.substr(start, end - start + 1);
33}
34
36Bytes BuildImBlockHeader(std::uint16_t blockType, std::uint16_t blockLength)
37{
38 Bytes out;
39 wire::PutU16(out, blockType);
40 wire::PutU16(out, blockLength);
41 wire::PutU8(out, 0x01);
42 wire::PutU8(out, 0x00);
43 return out;
44}
45
48Bytes Latin1Pad(const std::string& s, std::size_t maxLen)
49{
50 Bytes out(s.begin(), s.begin() + std::min(s.size(), maxLen));
51 out.resize(maxLen, static_cast<std::uint8_t>(' '));
52 return out;
53}
54
55} // namespace
56
57std::optional<MacAddress> ParseMac(const std::string& macStrIn)
58{
59 std::string s = macStrIn;
60 std::ranges::transform(s, s.begin(), [](unsigned char c)
61 { return std::tolower(c); });
62
63 // Handle Cisco-style dotted MAC: xxxx.xxxx.xxxx
64 // Cisco-style dotted MAC: aabb.ccdd.eeff
65 if (s.find('.') != std::string::npos)
66 {
67 std::vector<std::string> groups;
68 std::stringstream ss(s);
69 std::string group;
70
71 while (std::getline(ss, group, '.'))
72 {
73 groups.push_back(group);
74 }
75
76 // Support Cisco notation: aabb.ccdd.eeff
77 if (groups.size() == 3)
78 {
79 if (!std::ranges::all_of(groups, [](const std::string& group)
80 {
81 return group.size() == 4 &&
82 std::ranges::all_of(group, [](unsigned char c)
83 {
84 return std::isxdigit(c) != 0;
85 });
86 }))
87 {
88 return std::nullopt;
89 }
90
91 MacAddress mac{};
92
93 for (std::size_t i = 0; i < 3; ++i)
94 {
95 const auto value =
96 static_cast<std::uint16_t>(
97 std::stoul(groups[i], nullptr, 16));
98
99 mac[i * 2] =
100 static_cast<std::uint8_t>(value >> 8);
101
102 mac[i * 2 + 1] =
103 static_cast<std::uint8_t>(value & 0xff);
104 }
105
106 return mac;
107 }
108
109 // Also support six-group dotted notation:
110 // aa.bb.cc.dd.ee.ff
111 if (groups.size() == 6)
112 {
113 if (!std::ranges::all_of(groups, [](const std::string& group)
114 {
115 return group.size() == 2 &&
116 std::ranges::all_of(group, [](unsigned char c)
117 {
118 return std::isxdigit(c) != 0;
119 });
120 }))
121 {
122 return std::nullopt;
123 }
124
125 MacAddress mac{};
126
127 for (std::size_t i = 0; i < 6; ++i)
128 {
129 mac[i] = static_cast<std::uint8_t>(
130 std::stoul(groups[i], nullptr, 16));
131 }
132
133 return mac;
134 }
135
136 return std::nullopt;
137 }
138
139 std::ranges::replace(s, '-', ':');
140
141 std::vector<std::string> parts;
142 std::stringstream ss(s);
143 std::string part;
144 while (std::getline(ss, part, ':'))
145 {
146 parts.push_back(part);
147 }
148
149 if (parts.size() != macAddressLength)
150 {
151 return std::nullopt;
152 }
153
154 MacAddress mac{};
155 for (int i = 0; i < macAddressLength; ++i)
156 {
157 if (parts[i].empty() || parts[i].size() > 2)
158 {
159 return std::nullopt;
160 }
161 for (const char c : parts[i])
162 {
163 if (std::isxdigit(static_cast<unsigned char>(c)) == 0)
164 {
165 return std::nullopt;
166 }
167 }
168 mac[i] = static_cast<std::uint8_t>(std::stoul(parts[i], nullptr, 16));
169 }
170 return mac;
171}
172
173bool IsMacAddress(const std::string& identifier)
174{
175 return ParseMac(identifier).has_value();
176}
177
178// =============================================================================
179// DeviceInfo
180// =============================================================================
181
182std::string DeviceInfo::SerialNumber() const
183{
184 return im0 ? Trim(im0->imSerialNumber) : "";
185}
186std::string DeviceInfo::OrderId() const
187{
188 return im0 ? Trim(im0->orderId) : "";
189}
190std::uint16_t DeviceInfo::HardwareRevision() const
191{
192 return im0 ? im0->imHardwareRevision : 0;
193}
194
196{
197 if (!im0)
198 {
199 return "";
200 }
201 // Create the prefix string directly: either the character itself, or empty if it is 0U
202 const std::string prefixStr = (im0->swRevisionPrefix != 0U)
203 ? std::string(1, static_cast<char>(im0->swRevisionPrefix))
204 : "";
205
206 // Format everything directly into the final const string without any post-processing or erasing
207 const std::string result = std::format("{}{}.{}.{}",
208 prefixStr,
209 im0->imSwRevisionFunctionalEnhancement,
210 im0->imSwRevisionBugFix,
211 im0->imSwRevisionInternalChange);
212
213 return result;
214}
215
216// =============================================================================
217// ProfinetDevice
218// =============================================================================
219
221 double timeoutSec, std::unique_ptr<rpc::IRPCCon> rpc, std::unique_ptr<IAlarmListener> alarmListener)
222 : info(std::move(info)), interface(std::move(interface)), srcMac(srcMac), timeout(timeoutSec), rpc(std::move(rpc)), alarmListener(std::move(alarmListener))
223{
224}
226 : info(std::move(other.info)),
227 interface(std::move(other.interface)),
228 srcMac(other.srcMac),
229 timeout(other.timeout),
230 rpc(std::move(other.rpc)),
231 connected(std::exchange(other.connected, false)),
232 alarmListener(std::move(other.alarmListener)),
233 alarmCallbacks(std::move(other.alarmCallbacks))
234{
235}
237{
238 if (this == &other)
239 {
240 return *this;
241 }
242 Close();
243 info = std::move(other.info);
244 interface = std::move(other.interface);
245 srcMac = other.srcMac;
246 timeout = other.timeout;
247
248 rpc = std::move(other.rpc);
249 connected = std::exchange(other.connected, false);
250 alarmListener = std::move(other.alarmListener);
251 alarmCallbacks = std::move(other.alarmCallbacks);
252
253 return *this;
254}
259
260ProfinetDevice ProfinetDevice::Discover(const std::string& identifier, const std::string& interface,
261 double timeoutSec)
262{
263 asio::io_context ioContext;
265
268
269 // During discovery ProfinetDevice does not exist yet
270 // so there cannot be a device-owned ASIO context.
271 // The DCP context exists only for:
272 // DCP discovery -> DCPDeviceDescription -> construct ProfinetDevice
273 // and then disappears.
274 // Newly constructed ProfinetDevice gets its own long-lived context for RPC/Cyclic/Alarm.
275
276 auto targetMac = ParseMac(identifier);
277 if (targetMac)
278 {
280 auto responses = dcp::ReadResponse(sock, srcMac, static_cast<int>(timeoutSec));
281 for (const auto& [Mac, Blocks] : responses)
282 {
283 if (Mac == *targetMac)
284 {
286 // NOLINTNEXTLINE(modernize-return-braced-init-list)
287 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
288 }
289 }
290 throw DCPDeviceNotFoundError("Device with MAC '" + identifier + "' not found");
291 }
292
294 srcMac,
295 identifier,
296 static_cast<int>(timeoutSec));
297 std::cout << info.ToString() << "\n\n";
298 auto device = ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
299 std::cout << device.ToString() << "\n\n";
300 return device;
301}
302
303ProfinetDevice ProfinetDevice::FromIp(const std::string& ip, const std::string& interface, double timeoutSec)
304{
305 asio::io_context ioContext;
307
310
311 // During discovery ProfinetDevice does not exist yet
312 // so there cannot be a device-owned ASIO context.
313 // The DCP context exists only for:
314 // DCP discovery -> DCPDeviceDescription -> construct ProfinetDevice
315 // and then disappears.
316 // Newly constructed ProfinetDevice gets its own long-lived context for RPC/Cyclic/Alarm.
317
319 auto responses = dcp::ReadResponse(sock, srcMac, static_cast<int>(timeoutSec));
320 for (const auto& [Mac, Blocks] : responses)
321 {
323 if (info.ip == ip)
324 {
325 // NOLINTNEXTLINE(modernize-return-braced-init-list)
326 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
327 }
328 }
329 throw DCPDeviceNotFoundError("No device found at IP " + ip);
330}
331
333 double timeoutSec)
334{
336 // NOLINTNEXTLINE(modernize-return-braced-init-list)
337 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
338}
339
341{
342 if (connected && rpc)
343 {
344 return;
345 }
346 auto options = rpc::RPCConOptions{.timeoutSec = timeout};
347
348 if (rpc == nullptr)
349 {
350 rpc = std::make_unique<rpc::RPCCon>(info, options, nullptr, nullptr, &ioContext);
351 }
352 try
353 {
354 rpc->Connect(srcMac);
355 connected = true;
356 }
357 catch (const RPCError& e)
358 {
359 rpc->Close();
360 rpc.reset();
361 throw RPCConnectionError("Failed to connect to " + info.name + ": " + e.what());
362 }
363}
364
366{
367 if (rpc)
368 {
369 rpc->Disconnect();
370 connected = false;
371 }
372}
374{
375 if (alarmListener)
376 {
377 try
378 {
379 alarmListener->Stop();
380 }
381 // NOLINTNEXTLINE(bugprone-empty-catch)
382 catch (...)
383 {
384 }
385
386 alarmListener.reset();
387 }
388
389 if (rpc)
390 {
391 try
392 {
393 rpc->Close();
394 }
395 // NOLINTNEXTLINE(bugprone-empty-catch)
396 catch (...)
397 {
398 }
399
400 rpc.reset();
401 }
402
403 connected = false;
404}
405
407{
409
410 try
411 {
412 releaseRpc.Disconnect();
413 }
414 catch (const std::exception& e)
415 {
416 // Best-effort teardown: continue closing the RPC connection even when
417 // the PROFINET disconnect procedure fails.
419 }
420
421 releaseRpc.Close();
422
423 if (this->rpc.get() == &releaseRpc)
424 {
425 this->rpc.reset(); // Safely destroys the RPCCon instance and resets pointer to nullptr
426 connected = false;
427 }
428}
429
431{
432 if (!connected || !rpc)
433 {
434 Connect();
435 }
436 return *rpc;
437}
438
440{
441 DeviceInfo returnInfo;
442 returnInfo.name = info.name;
443 returnInfo.ip = info.ip;
444 returnInfo.mac = info.mac;
445 returnInfo.vendorId = info.VendorId();
446 returnInfo.deviceId = info.DeviceId();
447 returnInfo.deviceType = info.deviceType;
448 returnInfo.netmask = info.netmask;
449 returnInfo.gateway = info.gateway;
450 returnInfo.deviceRoles = info.deviceRoles;
451 returnInfo.vendorName = info.VendorName();
452
453 auto& conn = EnsureConnected();
454
455 try
456 {
457 returnInfo.im0 = conn.ReadIm0();
458 }
459 catch (const RPCError&)
460 {
461 // PNIOError derives from RPCError, so it's already caught above.
462 }
463
464 try
465 {
466 auto endpoints = conn.EpmLookup(returnInfo.ip);
467 for (const auto& ep : endpoints)
468 {
469 if (!ep.annotation.empty())
470 {
471 returnInfo.annotation = ep.annotation;
472 break;
473 }
474 }
475 }
476 catch (const std::exception&)
477 {
478 }
479
480 if (includeTopology)
481 {
482 try
483 {
484 returnInfo.topology = conn.ReadPdRealData();
485 }
486 catch (const RPCError&)
487 {
488 // PNIOError derives from RPCError, so it's already caught above.
489 }
490 }
491
492 return returnInfo;
493}
494
495Bytes ProfinetDevice::Read(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, std::uint32_t api)
496{
497 try
498 {
499 auto& conn = EnsureConnected();
500 auto responseIOD = conn.Read(api, slot, subslot, index);
501 return responseIOD.payload;
502 }
503 catch (const RPCError& e)
504 {
505 // Some device stacks (e.g. p-net) do not implement the Device
506 // Access AR; the AR-less Read Implicit service still works there.
507 // logger.info("AR connect failed, falling back to Read Implicit")
508 return ReadImplicit(slot, subslot, index, api);
509 }
510}
511
512Bytes ProfinetDevice::ReadImplicit(const std::uint16_t slot, const std::uint16_t subslot, const std::uint16_t index, const std::uint32_t api)
513{
514 if (connected && rpc)
515 {
516 return rpc->ReadImplicit(api, slot, subslot, index).payload;
517 }
518
520 rpc::RPCCon tmpRpc(info, options);
521 Bytes implicitRead;
522
523 try
524 {
525 implicitRead = tmpRpc.ReadImplicit(api, slot, subslot, index).payload;
526 }
527 catch (...)
528 {
529 tmpRpc.Close();
530 throw;
531 }
532 tmpRpc.Close();
533
534 return implicitRead;
535}
536
537void ProfinetDevice::Write(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, const Bytes& data,
538 std::uint32_t api)
539{
540 auto& conn = EnsureConnected();
541 conn.Write(api, slot, subslot, index, data);
542}
543
544std::vector<blocks::WriteMultipleResult> ProfinetDevice::WriteMultiple(const std::vector<WriteItem>& writes)
545{
546 auto& conn = EnsureConnected();
547 std::vector<rpc::WriteItem> items;
548 items.reserve(writes.size());
549 for (const auto& w : writes)
550 {
551 items.push_back({.slot = w.slot, .subslot = w.subslot, .index = w.index, .data = w.data, .api = w.api});
552 }
553 return conn.WriteMultiple(items);
554}
555
556PNInM0 ProfinetDevice::ReadIm0(std::uint16_t slot, std::uint16_t subslot)
557{
558 return EnsureConnected().ReadIm0(slot, subslot);
559}
560PNInM1 ProfinetDevice::ReadIm1(std::uint16_t slot, std::uint16_t subslot)
561{
562 return EnsureConnected().ReadIm1(slot, subslot);
563}
564PNInM2 ProfinetDevice::ReadIm2(std::uint16_t slot, std::uint16_t subslot)
565{
566 return EnsureConnected().ReadIm2(slot, subslot);
567}
568PNInM3 ProfinetDevice::ReadIm3(std::uint16_t slot, std::uint16_t subslot)
569{
570 return EnsureConnected().ReadIm3(slot, subslot);
571}
572PNInM4 ProfinetDevice::ReadIm4(std::uint16_t slot, std::uint16_t subslot)
573{
574 return EnsureConnected().ReadIm4(slot, subslot);
575}
576PNInM5 ProfinetDevice::ReadIm5(std::uint16_t slot, std::uint16_t subslot)
577{
578 return EnsureConnected().ReadIm5(slot, subslot);
579}
580rpc::AllIM ProfinetDevice::ReadAllIm(std::uint16_t slot, std::uint16_t subslot)
581{
582 return EnsureConnected().ReadAllIm(slot, subslot);
583}
584
585void ProfinetDevice::WriteIm1(const std::string& tagFunction, const std::string& tagLocation, std::uint16_t slot, std::uint16_t subslot)
586{
587 if (tagFunction.size() > 32)
588 {
589 throw std::invalid_argument("tag_function exceeds 32 character limit");
590 }
591 if (tagLocation.size() > 22)
592 {
593 throw std::invalid_argument("tag_location exceeds 22 character limit");
594 }
595
596 Bytes data = BuildImBlockHeader(0x0021, 58);
597 data.push_back(0x00);
598 data.push_back(0x00);
599 Bytes func = Latin1Pad(tagFunction, 32);
600 Bytes loc = Latin1Pad(tagLocation, 22);
601 data.insert(data.end(), func.begin(), func.end());
602 data.insert(data.end(), loc.begin(), loc.end());
603
604 Write(slot, subslot, IM1, data);
605}
606
607void ProfinetDevice::WriteIm2(const std::string& date, std::uint16_t slot, std::uint16_t subslot)
608{
609 if (date.size() > 16)
610 {
611 throw std::invalid_argument("date exceeds 16 character limit");
612 }
613
614 Bytes data = BuildImBlockHeader(0x0022, 20);
615 data.push_back(0x00);
616 data.push_back(0x00);
617 Bytes dateBytes = Latin1Pad(date, 16);
618 data.insert(data.end(), dateBytes.begin(), dateBytes.end());
619
620 Write(slot, subslot, IM2, data);
621}
622
623void ProfinetDevice::WriteIm3(const std::string& descriptor, std::uint16_t slot, std::uint16_t subslot)
624{
625 if (descriptor.size() > 54)
626 {
627 throw std::invalid_argument("descriptor exceeds 54 character limit");
628 }
629
630 Bytes data = BuildImBlockHeader(0x0023, 58);
631 data.push_back(0x00);
632 data.push_back(0x00);
633 Bytes descBytes = Latin1Pad(descriptor, 54);
634 data.insert(data.end(), descBytes.begin(), descBytes.end());
635
636 Write(slot, subslot, IM3, data);
637}
638
643
644diagnosis::DiagnosisData ProfinetDevice::ReadDiagnosis(std::uint16_t slot, std::uint16_t subslot,
645 std::uint16_t index)
646{
647 return EnsureConnected().ReadDiagnosis(slot, subslot, index);
648}
649
650std::map<std::uint16_t, diagnosis::DiagnosisData> ProfinetDevice::ReadAllDiagnosis()
651{
653}
654
655std::vector<blocks::SlotInfo> ProfinetDevice::DiscoverSlots()
656{
658}
659
664
665std::optional<AlarmNotification> ProfinetDevice::ReadAlarm(std::uint16_t slot, std::uint16_t subslot,
666 std::uint16_t index)
667{
668 try
669 {
670 const Bytes data = Read(slot, subslot, index);
671 if (data.size() >= 26)
672 {
673 return ParseAlarmNotification(data);
674 }
675 return std::nullopt;
676 }
677 catch (const RPCError&)
678 {
679 // PNIOError derives from RPCError, so it's already caught above.
680 return std::nullopt;
681 }
682}
683
684void ProfinetDevice::OnAlarm(std::function<void(const AlarmNotification&)> callback)
685{
686 alarmCallbacks.push_back(std::move(callback));
687 if (alarmListener && alarmListener->IsRunning())
688 {
689 alarmListener->AddAlarmNotificationCallback(alarmCallbacks.back());
690 }
691}
692
694{
695 if (!connected || !rpc)
696 {
697 throw std::runtime_error("Must be connected first");
698 }
699
700 if (!rpc->HasAlarmCr())
701 {
702 throw std::runtime_error("AlarmCR not established. Reconnect with with_alarm_cr=true via RPCCon directly");
703 }
704
705 if (alarmListener && alarmListener->IsRunning())
706 {
707 return;
708 }
709
710 AlarmEndpoint endpoint;
711 endpoint.interface = interface;
712 endpoint.controllerRef = rpc->LocalAlarmRef();
713 endpoint.deviceRef = static_cast<std::uint16_t>(rpc->DeviceAlarmRef());
714 endpoint.deviceMac = String2Mac(info.mac);
715 endpoint.transport = 0; // Layer 2
716
717 if (alarmListener == nullptr)
718 {
719 alarmListener = std::make_unique<AlarmListener>(ioContext, endpoint, srcMac);
720 }
721 for (const auto& cb : alarmCallbacks)
722 {
723 alarmListener->AddAlarmNotificationCallback(cb);
724 }
725 alarmListener->Start();
726}
727
729{
730 if (alarmListener)
731 {
732 alarmListener->Stop();
733 alarmListener.reset();
734 }
735}
736
738{
739 return alarmListener != nullptr && alarmListener->IsRunning();
740}
741
742// hier ben je gebleven
743std::unique_ptr<cyclic::ICyclicController> ProfinetDevice::StartCyclic(const rpc::IOCRSetup& iocrSetup,
744 int maxConsecutiveTimeouts,
745 std::unique_ptr<cyclic::ICyclicController> controller)
746{
747 auto& conn = EnsureConnected();
748
749 auto result = conn.Connect(srcMac, /*WithAlarmCr=*/true, iocrSetup);
750 if (!result || !result->HasCyclic())
751 {
752 throw std::runtime_error("Cyclic IO not established by device");
753 }
754
755 conn.PrmEnd();
756 conn.ApplicationReady(30.0);
757
758 auto [input_iocr, output_iocr] = rt::BuildIocrConfigs(
759 iocrSetup.slots, result->inputFrameId, result->outputFrameId, iocrSetup.sendClockFactor,
760 iocrSetup.reductionRatio, iocrSetup.watchdogFactor);
761 if (controller == nullptr)
762 {
763 controller = std::make_unique<cyclic::CyclicController>(
765 input_iocr, output_iocr, maxConsecutiveTimeouts);
766 }
767 controller->Start();
768 return controller;
769}
770
771std::map<std::uint16_t, rpc::IndexProbeResult> ProfinetDevice::EnumerateIndices(std::uint16_t slot,
772 std::uint16_t subslot)
773{
774 return EnsureConnected().EnumerateIndices(slot, subslot);
775}
776
777std::string ProfinetDevice::ToString() const
778{
779 return "ProfinetDevice('" + info.name + "', " + info.ip + ", " + (connected ? "connected" : "disconnected") +
780 ")";
781}
782
783// =============================================================================
784// Scan
785// =============================================================================
786dcp::ResponseMap DcpScanner::Scan(const std::string& interface, double timeoutSec)
787{
788 asio::io_context ioContext;
789 AsioIoContextRunner ioContextRunner(ioContext);
790
791 const MacAddress srcMac = GetInterfaceMac(interface);
792 AsioEthernetSocket sock(ioContext, interface, 0);
793
794 dcp::SendDiscover(sock, srcMac);
795
796 return dcp::ReadResponse(sock, srcMac, timeoutSec);
797}
798
799std::vector<ProfinetDevice> Scan(const std::string& interface, double timeoutSec,
800 std::unique_ptr<IDcpScanner> dcpScanner, std::unique_ptr<IMacAddressProvider> macAddressProvider)
801{
802 if (dcpScanner == nullptr)
803 {
804 dcpScanner = std::make_unique<DcpScanner>();
805 }
806 if (macAddressProvider == nullptr)
807 {
808 macAddressProvider = std::make_unique<MacAddressProvider>();
809 }
810 // asio::io_context ioContext;
811 // AsioIoContextRunner ioContextRunner(ioContext);
812
813 const MacAddress srcMac = macAddressProvider->GetMac(interface);
814 // AsioEthernetSocket sock(ioContext, interface, 0);
815
816 // During discovery ProfinetDevice does not exist yet
817 // so there cannot be a device-owned ASIO context.
818 // The DCP context exists only for:
819 // DCP discovery -> DCPDeviceDescription -> construct ProfinetDevice
820 // and then disappears.
821 // Newly constructed ProfinetDevice gets its own long-lived context for RPC/Cyclic/Alarm.
822
823 // dcp::SendDiscover(sock, srcMac);
824 // auto responses = dcp::ReadResponse(sock, srcMac, timeoutSec);
825 auto responses = dcpScanner->Scan(interface, timeoutSec);
826
827 std::vector<ProfinetDevice> devices;
828 devices.reserve(responses.size());
829 for (const auto& [Mac, Blocks] : responses)
830 {
831 dcp::DCPDeviceDescription info(Mac, Blocks);
832 devices.emplace_back(std::move(info), interface, srcMac, timeoutSec);
833 }
834 return devices;
835}
836
837std::map<std::string, DeviceInfo> ScanDict(const std::string& interface, double timeoutSec, std::unique_ptr<IDcpScanner> dcpScanner, std::unique_ptr<IMacAddressProvider> macAddressProvider)
838{
839 std::map<std::string, DeviceInfo> result;
840 for (auto& dev : Scan(interface, timeoutSec, std::move(dcpScanner), std::move(macAddressProvider)))
841 {
842 DeviceInfo info;
843 info.name = dev.Name();
844 info.ip = dev.Ip();
845 info.mac = dev.Mac();
846 result[dev.Mac()] = info;
847 }
848 return result;
849}
850
851} // namespace profinet::device
Asio-backed platform-independent raw Ethernet transport.
Platform-independent raw Ethernet transport using Asio.
Keeps an Asio I/O context running on a dedicated worker thread.
Device not found via DCP.
Definition exceptions.h:563
Failed to establish RPC connection.
Definition exceptions.h:615
DCE/RPC protocol errors.
Definition exceptions.h:575
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
std::string netmask
IPv4 subnet mask, or "0.0.0.0" if unset.
Definition dcp.h:288
std::string ToString() const
Multi-line human-readable summary of this device.
Definition dcp.cpp:751
std::string name
Station name.
Definition dcp.h:282
std::uint16_t VendorId() const
Combine VendorHigh/VendorLow into a 16-bit vendor ID.
Definition dcp.h:350
std::string gateway
IPv4 default gateway, or "0.0.0.0" if unset.
Definition dcp.h:291
std::uint16_t DeviceId() const
Combine DeviceHigh/DeviceLow into a 16-bit device ID.
Definition dcp.h:357
std::string VendorName() const
Look up the vendor name for this device's vendor ID.
Definition dcp.cpp:746
std::string mac
Device's MAC address, formatted as a string.
Definition dcp.h:276
std::string ip
IPv4 address, or "0.0.0.0" if unset.
Definition dcp.h:285
std::vector< std::string > deviceRoles
Decoded device role names (see DecodeDeviceRole()).
Definition dcp.h:324
std::string deviceType
Device type string (e.g. model name).
Definition dcp.h:279
dcp::ResponseMap Scan(const std::string &interface, double timeoutSec) override
Discover PROFINET devices using DCP.
Definition device.cpp:786
High-level PROFINET device interface.
Definition device.h:167
std::string ToString() const
Human-readable summary of this device, for logging/debugging.
Definition device.cpp:777
PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M0 (mandatory identification data).
Definition device.cpp:556
std::string Mac() const
The device's MAC address.
Definition device.h:275
rpc::AllIM ReadAllIm(std::uint16_t slot=0, std::uint16_t subslot=1)
Read all I&M records the device supports.
Definition device.cpp:580
void ReleaseAr(rpc::IRPCCon &releaseRpc)
Release a PROFINET Application Relationship.
Definition device.cpp:406
blocks::ModuleDiffBlock ReadModuleDiff()
Read and parse the device's ModuleDiffBlock.
Definition device.cpp:639
AsioIoContextRunner ioContextRunner
Keeps the Asio execution context running.
Definition device.h:566
~ProfinetDevice()
Close() the connection and release resources.
Definition device.cpp:255
static ProfinetDevice Discover(const std::string &identifier, const std::string &interface, double timeoutSec=10.0)
Discover a device by station name or MAC address.
Definition device.cpp:260
std::map< std::uint16_t, rpc::IndexProbeResult > EnumerateIndices(std::uint16_t slot=0, std::uint16_t subslot=1)
Probe standard indices and report which are readable/empty/erroring.
Definition device.cpp:771
std::vector< blocks::WriteMultipleResult > WriteMultiple(const std::vector< WriteItem > &writes)
Write multiple records atomically.
Definition device.cpp:544
void Close() noexcept
Stop the alarm listener (if running) and fully close the RPC connection.
Definition device.cpp:373
PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M5 (free-text annotation).
Definition device.cpp:576
std::string interface
Network interface name in use.
Definition device.h:542
void StopAlarmListener()
Stop the alarm listener.
Definition device.cpp:728
PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M4 (PROFIsafe signature).
Definition device.cpp:572
blocks::PDRealData ReadTopology()
Read the device's physical topology.
Definition device.cpp:660
void StartAlarmListener()
Start the background alarm listener.
Definition device.cpp:693
void WriteIm1(const std::string &tagFunction, const std::string &tagLocation, std::uint16_t slot=0, std::uint16_t subslot=1)
Write I&M1 (tag function + location).
Definition device.cpp:585
std::unique_ptr< IAlarmListener > alarmListener
Background alarm listener, created lazily by StartAlarmListener().
Definition device.h:557
void OnAlarm(std::function< void(const AlarmNotification &)> callback)
Register a callback for alarm notifications.
Definition device.cpp:684
void WriteIm3(const std::string &descriptor, std::uint16_t slot=0, std::uint16_t subslot=1)
Write I&M3 (free-text descriptor).
Definition device.cpp:623
void Write(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, const Bytes &data, std::uint32_t api=0)
Write a raw record.
Definition device.cpp:537
PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M2 (installation date).
Definition device.cpp:564
Bytes ReadImplicit(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, std::uint32_t api=0)
Read a record using the AR-less Read Implicit service.
Definition device.cpp:512
std::unique_ptr< rpc::IRPCCon > rpc
Underlying RPC connection, created lazily on first use.
Definition device.h:551
bool connected
Whether the AR is currently established.
Definition device.h:554
asio::io_context ioContext
Asio execution context.
Definition device.h:563
ProfinetDevice & operator=(const ProfinetDevice &)=delete
Not copyable (owns an RPC connection and alarm listener).
PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M1 (tag function/location).
Definition device.cpp:560
diagnosis::DiagnosisData ReadDiagnosis(std::uint16_t slot=0, std::uint16_t subslot=0, std::uint16_t index=0xF000)
Read and parse diagnosis data at the given location/index.
Definition device.cpp:644
void Connect()
Establish the AR.
Definition device.cpp:340
Bytes Read(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, std::uint32_t api=0)
Read a raw record.
Definition device.cpp:495
void WriteIm2(const std::string &date, std::uint16_t slot=0, std::uint16_t subslot=1)
Write I&M2 (installation date).
Definition device.cpp:607
ProfinetDevice(dcp::DCPDeviceDescription info, std::string interface, MacAddress srcMac, double timeoutSec=5.0, std::unique_ptr< rpc::IRPCCon > rpc=nullptr, std::unique_ptr< IAlarmListener > alarmListener=nullptr)
Construct a device wrapper directly.
Definition device.cpp:220
static ProfinetDevice FromDcpInfo(dcp::DCPDeviceDescription info, const std::string &interface, double timeoutSec=5.0)
Wrap an already-discovered DCPDeviceDescription.
Definition device.cpp:332
std::unique_ptr< cyclic::ICyclicController > StartCyclic(const rpc::IOCRSetup &iocrSetup, int maxConsecutiveTimeouts=3, std::unique_ptr< cyclic::ICyclicController > controller=nullptr)
Establish cyclic IO end-to-end.
Definition device.cpp:743
std::vector< blocks::SlotInfo > DiscoverSlots()
List the device's populated slots.
Definition device.cpp:655
PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M3 (free-text descriptor).
Definition device.cpp:568
std::optional< AlarmNotification > ReadAlarm(std::uint16_t slot=0, std::uint16_t subslot=0, std::uint16_t index=0x800C)
Attempt to read an alarm notification at the given location.
Definition device.cpp:665
MacAddress srcMac
This host's MAC address.
Definition device.h:545
static ProfinetDevice FromIp(const std::string &ip, const std::string &interface, double timeoutSec=10.0)
Discover a device by IP address.
Definition device.cpp:303
void Disconnect()
Gracefully disconnect (sends Release).
Definition device.cpp:365
bool AlarmListenerRunning() const
Whether the alarm listener is currently running.
Definition device.cpp:737
double timeout
Default RPC response timeout in seconds.
Definition device.h:548
std::vector< std::function< void(const AlarmNotification &)> > alarmCallbacks
Callbacks registered via OnAlarm(), applied to alarmListener once started.
Definition device.h:560
rpc::IRPCCon & EnsureConnected()
Return the underlying RPCCon, connecting first if necessary.
Definition device.cpp:430
DeviceInfo GetInfo(bool includeTopology=false)
Gather a complete device information summary.
Definition device.cpp:439
dcp::DCPDeviceDescription info
DCP-discovered description of this device.
Definition device.h:539
std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis()
Read diagnosis from all standard diagnosis indices.
Definition device.cpp:650
Abstract interface for a PROFINET RPC connection.
Definition IRPCCon.h:81
virtual PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M5 free-text annotation data.
virtual diagnosis::DiagnosisData ReadDiagnosis(std::uint16_t slot=0, std::uint16_t subslot=0, std::uint16_t index=0xF000)=0
Reads diagnosis information.
virtual PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M2 installation date data.
virtual AllIM ReadAllIm(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads all supported I&M records.
virtual PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M3 free-text descriptor data.
virtual PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M4 PROFIsafe signature data.
virtual PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M1 tag function and location data.
virtual void Close() noexcept=0
Disconnects and closes the underlying RPC sockets.
virtual void Disconnect()=0
Disconnects the Application Relationship.
virtual PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1)=0
Reads I&M0 identification data.
virtual std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis()=0
Reads diagnosis information for all standard indices.
virtual blocks::ModuleDiffBlock ReadModuleDiff()=0
Reads the ModuleDiffBlock.
virtual blocks::PDRealData ReadPdRealData()=0
Reads physical-device topology information.
virtual 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)=0
Enumerates readable PROFINET record indices.
virtual std::vector< blocks::SlotInfo > DiscoverSlots()=0
Discovers the populated device slots.
Connection to a PROFINET IO-Device: establishes an AR (Application Relationship) and performs acyclic...
Definition rpc.h:138
void Close() noexcept override
Disconnect() and close the underlying sockets.
Definition rpc.cpp:2139
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.
Definition rpc.cpp:1400
High-level PROFINET device interface and device lifecycle management.
Declares exceptions and error types used by the PROFINET IO controller stack.
PROFINET record-data indices, block types, and protocol enumerations.
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.
Definition dcp.cpp:1173
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
Definition dcp.h:483
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.
Definition dcp.cpp:1218
bool IsMacAddress(const std::string &identifier)
Whether a string parses as a MAC address.
Definition device.cpp:173
std::vector< ProfinetDevice > Scan(const std::string &interface="eth0", double timeoutSec=3.0, std::unique_ptr< IDcpScanner > dcpScanner=nullptr, std::unique_ptr< IMacAddressProvider > macAddressProvider=nullptr)
Scan the network for PROFINET devices via DCP Identify.
Definition device.cpp:799
std::optional< MacAddress > ParseMac(const std::string &macStr)
Parse a MAC address string in any common separator style.
Definition device.cpp:57
std::map< std::string, DeviceInfo > ScanDict(const std::string &interface="eth0", double timeoutSec=3.0, std::unique_ptr< IDcpScanner > dcpScanner=nullptr, std::unique_ptr< IMacAddressProvider > macAddressProvider=nullptr)
Scan the network and return a name -> DeviceInfo map.
Definition device.cpp:837
dcp::DCPDeviceDescription GetStationInfo(IRawEthernetSocket &sock, const MacAddress &src, const std::string &name, double timeoutSec=DEFAULT_TIMEOUT *2)
Resolve a device by PROFINET station name via DCP.
Definition rpc.cpp:509
std::pair< IOCRConfig, IOCRConfig > BuildIocrConfigs(const std::vector< SlotT > &slots, std::uint16_t inputFrameId, std::uint16_t outputFrameId, std::uint16_t sendClockFactor=32, std::uint16_t reductionRatio=32, int watchdogFactor=3, std::uint16_t cSduLength=40)
Definition rt.h:454
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t value)
Append a single byte to a buffer.
Definition wire.h:89
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:97
MacAddress GetInterfaceMac(const std::string &interface)
Get the MAC address assigned to a network interface.
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
Definition alarms.cpp:245
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
constexpr std::uint16_t IM2
I&M2.
Definition indices.h:705
constexpr std::uint16_t IM1
I&M1.
Definition indices.h:703
constexpr int macAddressLength
Constant lenght of a mac address.
Definition util.h:37
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
constexpr std::uint16_t IM3
I&M3.
Definition indices.h:707
Alarm endpoint configuration.
int transport
Transport: 0 = Layer2 (RTA), 1 = UDP.
std::string interface
Network interface name to bind to, e.g. "eth0".
std::uint16_t controllerRef
Controller's local alarm reference (from AlarmCRBlockReq).
std::uint16_t deviceRef
Device's local alarm reference (from AlarmCRBlockRes).
MacAddress deviceMac
The device's MAC address.
Complete parsed AlarmNotification PDU: header fields + parsed items.
Definition alarms.h:356
Bytes payload
Record data payload.
Definition protocol.h:1229
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
Definition protocol.h:1517
I&M1: user-assigned tag function and location strings.
Definition protocol.h:1624
I&M2: installation date.
Definition protocol.h:1668
I&M3: free-text descriptor.
Definition protocol.h:1709
I&M4: PROFIsafe signature (binary, not text).
Definition protocol.h:1749
I&M5: free-text annotation.
Definition protocol.h:1789
Parsed ModuleDiffBlock (0x8104).
Definition blocks.h:415
Parsed PDRealData (0xF841) structure.
Definition blocks.h:221
Complete device information summary.
Definition device.h:95
std::uint16_t deviceId
PROFINET device ID (from DCP).
Definition device.h:109
std::string name
Station name (from DCP).
Definition device.h:97
std::string deviceType
Device type string (from DCP).
Definition device.h:112
std::string netmask
IPv4 subnet mask (from DCP).
Definition device.h:115
std::string annotation
Device model, if the EPM endpoint advertised one.
Definition device.h:133
std::string mac
MAC address, formatted as a string (from DCP).
Definition device.h:103
std::string SerialNumber() const
Serial number from I&M0, trimmed.
Definition device.cpp:182
std::vector< std::string > deviceRoles
Decoded device role names (from DCP).
Definition device.h:121
std::string vendorName
Human-readable vendor name (from DCP).
Definition device.h:124
std::optional< PNInM0 > im0
I&M0 identification data, if it was read successfully.
Definition device.h:127
std::string gateway
IPv4 default gateway (from DCP).
Definition device.h:118
std::uint16_t vendorId
PROFINET vendor ID (from DCP).
Definition device.h:106
std::uint16_t HardwareRevision() const
Hardware revision from I&M0.
Definition device.cpp:190
std::string OrderId() const
Order ID from I&M0, trimmed.
Definition device.cpp:186
std::string ip
IPv4 address (from DCP).
Definition device.h:100
std::string SoftwareRevision() const
Software revision string from I&M0.
Definition device.cpp:195
std::optional< blocks::PDRealData > topology
Physical topology, if it was read successfully.
Definition device.h:130
Complete diagnosis data for one slot/subslot.
Definition diagnosis.h:275
All I&M records a device supports, read in one call.
Definition rpcTypes.h:451
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:149
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rpcTypes.h:160
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:151
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rpcTypes.h:157
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rpcTypes.h:154
Configuration options for an RPC connection.
Definition rpcTypes.h:516
double timeoutSec
Timeout in seconds for RPC operations.
Definition rpcTypes.h:518
std::unique_ptr< cyclic::CyclicController > controller
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.