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
13#include "profinet/exceptions.h"
14#include "profinet/indices.h"
15#include "profinet/wire.h"
16
17namespace profinet::device
18{
19
20namespace
21{
22
23std::string Trim(const std::string& s)
24{
25 const std::size_t start = s.find_first_not_of(" \t\r\n\0", 0, 4);
26 if (start == std::string::npos)
27 {
28 return "";
29 }
30 const std::size_t end = s.find_last_not_of(" \t\r\n\0", std::string::npos, 4);
31 return s.substr(start, end - start + 1);
32}
33
35Bytes BuildImBlockHeader(std::uint16_t blockType, std::uint16_t blockLength)
36{
37 Bytes out;
38 wire::PutU16(out, blockType);
39 wire::PutU16(out, blockLength);
40 wire::PutU8(out, 0x01);
41 wire::PutU8(out, 0x00);
42 return out;
43}
44
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}
225
230
231ProfinetDevice ProfinetDevice::Discover(const std::string& identifier, const std::string& interface,
232 double timeoutSec)
233{
235 const EthernetSocket sock(interface);
236
237 auto targetMac = ParseMac(identifier);
238 if (targetMac)
239 {
241 auto responses = dcp::ReadResponse(sock, srcMac, static_cast<int>(timeoutSec));
242 for (const auto& [Mac, Blocks] : responses)
243 {
244 if (Mac == *targetMac)
245 {
247 // NOLINTNEXTLINE(modernize-return-braced-init-list)
248 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
249 }
250 }
251 throw DCPDeviceNotFoundError("Device with MAC '" + identifier + "' not found");
252 }
253
255 rpc::GetStationInfo(sock, srcMac, identifier, static_cast<int>(timeoutSec));
256 std::cout << info.ToString() << "\n\n";
257 auto device = ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
258 std::cout << device.ToString() << "\n\n";
259 return device;
260}
261
262ProfinetDevice ProfinetDevice::FromIp(const std::string& ip, const std::string& interface, double timeoutSec)
263{
265 const EthernetSocket sock(interface);
266
268 auto responses = dcp::ReadResponse(sock, srcMac, static_cast<int>(timeoutSec));
269 for (const auto& [Mac, Blocks] : responses)
270 {
272 if (info.ip == ip)
273 {
274 // NOLINTNEXTLINE(modernize-return-braced-init-list)
275 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
276 }
277 }
278 throw DCPDeviceNotFoundError("No device found at IP " + ip);
279}
280
282 double timeoutSec)
283{
285 // NOLINTNEXTLINE(modernize-return-braced-init-list)
286 return ProfinetDevice(std::move(info), interface, srcMac, timeoutSec);
287}
288
290{
291 if (connected && rpc)
292 {
293 return;
294 }
295 auto options = rpc::RPCConOptions{.timeoutSec = timeout};
296
297 if (rpc == nullptr)
298 {
299 rpc = std::make_unique<rpc::RPCCon>(info, options);
300 }
301 try
302 {
303 rpc->Connect(srcMac);
304 connected = true;
305 }
306 catch (const RPCError& e)
307 {
308 rpc->Close();
309 rpc.reset();
310 throw RPCConnectionError("Failed to connect to " + info.name + ": " + e.what());
311 }
312}
313
315{
316 if (rpc)
317 {
318 rpc->Disconnect();
319 connected = false;
320 }
321}
322
324{
325 if (alarmListener)
326 {
327 alarmListener->Stop();
328 alarmListener.reset();
329 }
330 if (rpc)
331 {
332 rpc->Close();
333 rpc.reset();
334 connected = false;
335 }
336}
337
339{
341
342 try
343 {
344 releaseRpc.Disconnect();
345 }
346 catch (const std::exception& e)
347 {
348 // Best-effort teardown: continue closing the RPC connection even when
349 // the PROFINET disconnect procedure fails.
351 }
352
353 releaseRpc.Close();
354
355 if (this->rpc.get() == &releaseRpc)
356 {
357 this->rpc.reset(); // Safely destroys the RPCCon instance and resets pointer to nullptr
358 connected = false;
359 }
360}
361
363{
364 if (!connected || !rpc)
365 {
366 Connect();
367 }
368 return *rpc;
369}
370
372{
373 DeviceInfo returnInfo;
374 returnInfo.name = info.name;
375 returnInfo.ip = info.ip;
376 returnInfo.mac = info.mac;
377 returnInfo.vendorId = info.VendorId();
378 returnInfo.deviceId = info.DeviceId();
379 returnInfo.deviceType = info.deviceType;
380 returnInfo.netmask = info.netmask;
381 returnInfo.gateway = info.gateway;
382 returnInfo.deviceRoles = info.deviceRoles;
383 returnInfo.vendorName = info.VendorName();
384
385 auto& conn = EnsureConnected();
386
387 try
388 {
389 returnInfo.im0 = conn.ReadIm0();
390 }
391 catch (const RPCError&)
392 {
393 // PNIOError derives from RPCError, so it's already caught above.
394 }
395
396 try
397 {
398 auto endpoints = rpc::EpmLookup(rpc->IoContext(), returnInfo.ip);
399 for (const auto& ep : endpoints)
400 {
401 if (!ep.annotation.empty())
402 {
403 returnInfo.annotation = ep.annotation;
404 break;
405 }
406 }
407 }
408 catch (const std::exception&)
409 {
410 }
411
412 if (includeTopology)
413 {
414 try
415 {
416 returnInfo.topology = conn.ReadPdRealData();
417 }
418 catch (const RPCError&)
419 {
420 // PNIOError derives from RPCError, so it's already caught above.
421 }
422 }
423
424 return returnInfo;
425}
426
427Bytes ProfinetDevice::Read(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, std::uint32_t api)
428{
429 try
430 {
431 auto& conn = EnsureConnected();
432 auto responseIOD = conn.Read(api, slot, subslot, index);
433 return responseIOD.payload;
434 }
435 catch (const RPCError& e)
436 {
437 // Some device stacks (e.g. p-net) do not implement the Device
438 // Access AR; the AR-less Read Implicit service still works there.
439 // logger.info("AR connect failed, falling back to Read Implicit")
440 return ReadImplicit(slot, subslot, index, api);
441 }
442}
443
444Bytes ProfinetDevice::ReadImplicit(const std::uint16_t slot, const std::uint16_t subslot, const std::uint16_t index, const std::uint32_t api)
445{
446 if (connected && rpc)
447 {
448 return rpc->ReadImplicit(api, slot, subslot, index).payload;
449 }
450
452 rpc::RPCCon tmpRpc(info, options);
453 Bytes implicitRead;
454
455 try
456 {
457 implicitRead = tmpRpc.ReadImplicit(api, slot, subslot, index).payload;
458 }
459 catch (...)
460 {
461 tmpRpc.Close();
462 throw;
463 }
464 tmpRpc.Close();
465
466 return implicitRead;
467}
468
469void ProfinetDevice::Write(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, const Bytes& data,
470 std::uint32_t api)
471{
472 auto& conn = EnsureConnected();
473 conn.Write(api, slot, subslot, index, data);
474}
475
476std::vector<blocks::WriteMultipleResult> ProfinetDevice::WriteMultiple(const std::vector<WriteItem>& writes)
477{
478 auto& conn = EnsureConnected();
479 std::vector<rpc::WriteItem> items;
480 items.reserve(writes.size());
481 for (const auto& w : writes)
482 {
483 items.push_back({.slot = w.slot, .subslot = w.subslot, .index = w.index, .data = w.data, .api = w.api});
484 }
485 return conn.WriteMultiple(items);
486}
487
488PNInM0 ProfinetDevice::ReadIm0(std::uint16_t slot, std::uint16_t subslot)
489{
490 return EnsureConnected().ReadIm0(slot, subslot);
491}
492PNInM1 ProfinetDevice::ReadIm1(std::uint16_t slot, std::uint16_t subslot)
493{
494 return EnsureConnected().ReadIm1(slot, subslot);
495}
496PNInM2 ProfinetDevice::ReadIm2(std::uint16_t slot, std::uint16_t subslot)
497{
498 return EnsureConnected().ReadIm2(slot, subslot);
499}
500PNInM3 ProfinetDevice::ReadIm3(std::uint16_t slot, std::uint16_t subslot)
501{
502 return EnsureConnected().ReadIm3(slot, subslot);
503}
504PNInM4 ProfinetDevice::ReadIm4(std::uint16_t slot, std::uint16_t subslot)
505{
506 return EnsureConnected().ReadIm4(slot, subslot);
507}
508PNInM5 ProfinetDevice::ReadIm5(std::uint16_t slot, std::uint16_t subslot)
509{
510 return EnsureConnected().ReadIm5(slot, subslot);
511}
512rpc::AllIM ProfinetDevice::ReadAllIm(std::uint16_t slot, std::uint16_t subslot)
513{
514 return EnsureConnected().ReadAllIm(slot, subslot);
515}
516
517void ProfinetDevice::WriteIm1(const std::string& tagFunction, const std::string& tagLocation, std::uint16_t slot, std::uint16_t subslot)
518{
519 if (tagFunction.size() > 32)
520 {
521 throw std::invalid_argument("tag_function exceeds 32 character limit");
522 }
523 if (tagLocation.size() > 22)
524 {
525 throw std::invalid_argument("tag_location exceeds 22 character limit");
526 }
527
528 Bytes data = BuildImBlockHeader(0x0021, 58);
529 data.push_back(0x00);
530 data.push_back(0x00);
531 Bytes func = Latin1Pad(tagFunction, 32);
532 Bytes loc = Latin1Pad(tagLocation, 22);
533 data.insert(data.end(), func.begin(), func.end());
534 data.insert(data.end(), loc.begin(), loc.end());
535
536 Write(slot, subslot, IM1, data);
537}
538
539void ProfinetDevice::WriteIm2(const std::string& date, std::uint16_t slot, std::uint16_t subslot)
540{
541 if (date.size() > 16)
542 {
543 throw std::invalid_argument("date exceeds 16 character limit");
544 }
545
546 Bytes data = BuildImBlockHeader(0x0022, 20);
547 data.push_back(0x00);
548 data.push_back(0x00);
549 Bytes dateBytes = Latin1Pad(date, 16);
550 data.insert(data.end(), dateBytes.begin(), dateBytes.end());
551
552 Write(slot, subslot, IM2, data);
553}
554
555void ProfinetDevice::WriteIm3(const std::string& descriptor, std::uint16_t slot, std::uint16_t subslot)
556{
557 if (descriptor.size() > 54)
558 {
559 throw std::invalid_argument("descriptor exceeds 54 character limit");
560 }
561
562 Bytes data = BuildImBlockHeader(0x0023, 58);
563 data.push_back(0x00);
564 data.push_back(0x00);
565 Bytes descBytes = Latin1Pad(descriptor, 54);
566 data.insert(data.end(), descBytes.begin(), descBytes.end());
567
568 Write(slot, subslot, IM3, data);
569}
570
575
576diagnosis::DiagnosisData ProfinetDevice::ReadDiagnosis(std::uint16_t slot, std::uint16_t subslot,
577 std::uint16_t index)
578{
579 return EnsureConnected().ReadDiagnosis(slot, subslot, index);
580}
581
582std::map<std::uint16_t, diagnosis::DiagnosisData> ProfinetDevice::ReadAllDiagnosis()
583{
585}
586
587std::vector<blocks::SlotInfo> ProfinetDevice::DiscoverSlots()
588{
590}
591
596
597std::optional<AlarmNotification> ProfinetDevice::ReadAlarm(std::uint16_t slot, std::uint16_t subslot,
598 std::uint16_t index)
599{
600 try
601 {
602 const Bytes data = Read(slot, subslot, index);
603 if (data.size() >= 26)
604 {
605 return ParseAlarmNotification(data);
606 }
607 return std::nullopt;
608 }
609 catch (const RPCError&)
610 {
611 // PNIOError derives from RPCError, so it's already caught above.
612 return std::nullopt;
613 }
614}
615
616void ProfinetDevice::OnAlarm(std::function<void(const AlarmNotification&)> callback)
617{
618 alarmCallbacks.push_back(callback);
619 if (alarmListener)
620 {
621 alarmListener->AddCallback(callback);
622 }
623}
624
626{
627 if (!connected || !rpc)
628 {
629 throw std::runtime_error("Must be connected first");
630 }
631
632 if (!rpc->HasAlarmCr())
633 {
634 throw std::runtime_error("AlarmCR not established. Reconnect with with_alarm_cr=true via RPCCon directly");
635 }
636
637 AlarmEndpoint endpoint;
638 endpoint.interface = interface;
639 endpoint.controllerRef = rpc->LocalAlarmRef();
640 endpoint.deviceRef = static_cast<std::uint16_t>(rpc->DeviceAlarmRef());
641 endpoint.deviceMac = String2Mac(info.mac);
642 endpoint.transport = 0; // Layer 2
643
644 if (alarmListener == nullptr)
645 {
646 alarmListener = std::make_unique<AlarmListener>(endpoint, srcMac);
647 }
648 for (const auto& cb : alarmCallbacks)
649 {
650 alarmListener->AddCallback(cb);
651 }
652 alarmListener->Start();
653}
654
656{
657 if (alarmListener)
658 {
659 alarmListener->Stop();
660 alarmListener.reset();
661 }
662}
663
665{
666 return alarmListener != nullptr && alarmListener->IsRunning();
667}
668
669// hier ben je gebleven
670std::unique_ptr<cyclic::ICyclicController> ProfinetDevice::StartCyclic(const rpc::IOCRSetup& iocrSetup,
671 int maxConsecutiveTimeouts,
672 std::unique_ptr<cyclic::ICyclicController> controller)
673{
674 auto& conn = EnsureConnected();
675
676 auto result = conn.Connect(srcMac, /*WithAlarmCr=*/true, iocrSetup);
677 if (!result || !result->HasCyclic())
678 {
679 throw std::runtime_error("Cyclic IO not established by device");
680 }
681
682 conn.PrmEnd();
683 conn.ApplicationReady(30.0);
684
685 auto [input_iocr, output_iocr] = rt::BuildIocrConfigs(
686 iocrSetup.slots, result->inputFrameId, result->outputFrameId, iocrSetup.sendClockFactor,
687 iocrSetup.reductionRatio, iocrSetup.watchdogFactor);
688 if (controller == nullptr)
689 {
690 controller = std::make_unique<cyclic::CyclicController>(
691 interface, srcMac, String2Mac(info.mac), input_iocr, output_iocr, maxConsecutiveTimeouts);
692 }
693 controller->Start();
694 return controller;
695}
696
697std::map<std::uint16_t, rpc::IndexProbeResult> ProfinetDevice::EnumerateIndices(std::uint16_t slot,
698 std::uint16_t subslot)
699{
700 return EnsureConnected().EnumerateIndices(slot, subslot);
701}
702
703std::string ProfinetDevice::ToString() const
704{
705 return "ProfinetDevice('" + info.name + "', " + info.ip + ", " + (connected ? "connected" : "disconnected") +
706 ")";
707}
708
709// =============================================================================
710// Scan
711// =============================================================================
712
713std::vector<ProfinetDevice> Scan(const std::string& interface, double timeoutSec)
714{
715 const MacAddress srcMac = GetMac(interface);
716 const EthernetSocket sock(interface);
717
718 dcp::SendDiscover(sock, srcMac);
719 auto responses = dcp::ReadResponse(sock, srcMac, static_cast<int>(timeoutSec));
720
721 std::vector<ProfinetDevice> devices;
722 devices.reserve(responses.size());
723 for (const auto& [Mac, Blocks] : responses)
724 {
725 dcp::DCPDeviceDescription info(Mac, Blocks);
726 devices.emplace_back(std::move(info), interface, srcMac, 5.0);
727 }
728 return devices;
729}
730
731std::map<std::string, DeviceInfo> ScanDict(const std::string& interface, double timeoutSec)
732{
733 std::map<std::string, DeviceInfo> result;
734 for (auto& dev : Scan(interface, timeoutSec))
735 {
736 DeviceInfo info;
737 info.name = dev.Name();
738 info.ip = dev.Ip();
739 info.mac = dev.Mac();
740 result[dev.Name()] = info;
741 }
742 return result;
743}
744
745} // namespace profinet::device
Device not found via DCP.
Definition exceptions.h:563
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
Definition util.h:196
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:426
std::string netmask
IPv4 subnet mask, or "0.0.0.0" if unset.
Definition dcp.h:446
std::string ToString() const
Multi-line human-readable summary of this device.
Definition dcp.cpp:626
std::string name
Station name.
Definition dcp.h:440
std::uint16_t VendorId() const
Combine VendorHigh/VendorLow into a 16-bit vendor ID.
Definition dcp.h:501
std::string gateway
IPv4 default gateway, or "0.0.0.0" if unset.
Definition dcp.h:449
std::uint16_t DeviceId() const
Combine DeviceHigh/DeviceLow into a 16-bit device ID.
Definition dcp.h:508
std::string VendorName() const
Look up the vendor name for this device's vendor ID.
Definition dcp.cpp:621
std::string mac
Device's MAC address, formatted as a string.
Definition dcp.h:434
std::string ip
IPv4 address, or "0.0.0.0" if unset.
Definition dcp.h:443
std::vector< std::string > deviceRoles
Decoded device role names (see DecodeDeviceRole()).
Definition dcp.h:476
std::string deviceType
Device type string (e.g. model name).
Definition dcp.h:437
High-level PROFINET device interface.
Definition device.h:168
std::string ToString() const
Human-readable summary of this device, for logging/debugging.
Definition device.cpp:703
PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M0 (mandatory identification data).
Definition device.cpp:488
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:512
void ReleaseAr(rpc::IRPCCon &releaseRpc)
Release a PROFINET Application Relationship.
Definition device.cpp:338
blocks::ModuleDiffBlock ReadModuleDiff()
Read and parse the device's ModuleDiffBlock.
Definition device.cpp:571
~ProfinetDevice()
Close() the connection and release resources.
Definition device.cpp:226
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:231
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:697
std::vector< blocks::WriteMultipleResult > WriteMultiple(const std::vector< WriteItem > &writes)
Write multiple records atomically.
Definition device.cpp:476
PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M5 (free-text annotation).
Definition device.cpp:508
std::string interface
Network interface name in use.
Definition device.h:544
void StopAlarmListener()
Stop the alarm listener.
Definition device.cpp:655
PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M4 (PROFIsafe signature).
Definition device.cpp:504
blocks::PDRealData ReadTopology()
Read the device's physical topology.
Definition device.cpp:592
void StartAlarmListener()
Start the background alarm listener.
Definition device.cpp:625
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:517
std::unique_ptr< IAlarmListener > alarmListener
Background alarm listener, created lazily by StartAlarmListener().
Definition device.h:559
void OnAlarm(std::function< void(const AlarmNotification &)> callback)
Register a callback for alarm notifications.
Definition device.cpp:616
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:555
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:469
PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M2 (installation date).
Definition device.cpp:496
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:444
std::unique_ptr< rpc::IRPCCon > rpc
Underlying RPC connection, created lazily on first use.
Definition device.h:553
void Close()
Stop the alarm listener (if running) and fully close the RPC connection.
Definition device.cpp:323
bool connected
Whether the AR is currently established.
Definition device.h:556
PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M1 (tag function/location).
Definition device.cpp:492
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:576
void Connect()
Establish the AR.
Definition device.cpp:289
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:427
void WriteIm2(const std::string &date, std::uint16_t slot=0, std::uint16_t subslot=1)
Write I&M2 (installation date).
Definition device.cpp:539
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:281
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:670
std::vector< blocks::SlotInfo > DiscoverSlots()
List the device's populated slots.
Definition device.cpp:587
PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1)
Read I&M3 (free-text descriptor).
Definition device.cpp:500
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:597
MacAddress srcMac
This host's MAC address.
Definition device.h:547
static ProfinetDevice FromIp(const std::string &ip, const std::string &interface, double timeoutSec=10.0)
Discover a device by IP address.
Definition device.cpp:262
void Disconnect()
Gracefully disconnect (sends Release).
Definition device.cpp:314
bool AlarmListenerRunning() const
Whether the alarm listener is currently running.
Definition device.cpp:664
double timeout
Default RPC response timeout in seconds.
Definition device.h:550
std::vector< std::function< void(const AlarmNotification &)> > alarmCallbacks
Callbacks registered via OnAlarm(), applied to alarmListener once started.
Definition device.h:562
rpc::IRPCCon & EnsureConnected()
Return the underlying RPCCon, connecting first if necessary.
Definition device.cpp:362
DeviceInfo GetInfo(bool includeTopology=false)
Gather a complete device information summary.
Definition device.cpp:371
dcp::DCPDeviceDescription info
DCP-discovered description of this device.
Definition device.h:541
std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis()
Read diagnosis from all standard diagnosis indices.
Definition device.cpp:582
Abstract interface for a PROFINET RPC connection.
Definition IRPCCon.h:50
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:150
void Close() noexcept override
Disconnect() and close the underlying sockets.
Definition rpc.cpp:3521
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:2784
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(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.
Definition dcp.cpp:1003
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.
Definition dcp.cpp:1048
bool IsMacAddress(const std::string &identifier)
Whether a string parses as a MAC address.
Definition device.cpp:173
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)
Scan the network and return a name -> DeviceInfo map.
Definition device.cpp:731
std::vector< ProfinetDevice > Scan(const std::string &interface="eth0", double timeoutSec=3.0)
Scan the network for PROFINET devices via DCP Identify.
Definition device.cpp:713
dcp::DCPDeviceDescription GetStationInfo(const EthernetSocket &sock, const MacAddress &src, const std::string &name, int timeoutSec=static_cast< int >(DEFAULT_TIMEOUT *2))
Resolve a device by PROFINET station name via DCP.
Definition rpc.cpp:588
std::vector< EPMEndpoint > EpmLookup(asio::io_context &ioContext, const std::string &ip, std::uint16_t port=RPC_PORT, double timeoutSec=DEFAULT_TIMEOUT, std::optional< std::string > interfaceFilter=std::nullopt)
Query a device's Endpoint Mapper for available RPC endpoints.
Definition rpc.cpp:389
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=43)
Definition rt.h:444
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t v)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:27
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t v)
Append a single byte to a buffer.
Definition wire.h:19
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
Definition alarms.cpp:286
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:67
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:130
constexpr std::uint16_t IM2
I&M2.
Definition indices.h:628
MacAddress GetMac(const std::string &ifname)
Get the MAC address of a network interface.
Definition util.cpp:267
constexpr std::uint16_t IM1
I&M1.
Definition indices.h:626
constexpr int macAddressLength
Constant lenght of a mac address.
Definition util.h:41
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:52
constexpr std::uint16_t IM3
I&M3.
Definition indices.h:630
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:358
Bytes payload
Record data payload.
Definition protocol.h:793
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
Definition protocol.h:1076
I&M1: user-assigned tag function and location strings.
Definition protocol.h:1178
I&M2: installation date.
Definition protocol.h:1222
I&M3: free-text descriptor.
Definition protocol.h:1263
I&M4: PROFIsafe signature (binary, not text).
Definition protocol.h:1303
I&M5: free-text annotation.
Definition protocol.h:1343
Parsed ModuleDiffBlock (0x8104).
Definition blocks.h:415
Parsed PDRealData (0xF841) structure.
Definition blocks.h:221
Complete device information summary.
Definition device.h:96
std::uint16_t deviceId
PROFINET device ID (from DCP).
Definition device.h:110
std::string name
Station name (from DCP).
Definition device.h:98
std::string deviceType
Device type string (from DCP).
Definition device.h:113
std::string netmask
IPv4 subnet mask (from DCP).
Definition device.h:116
std::string annotation
Device model, if the EPM endpoint advertised one.
Definition device.h:134
std::string mac
MAC address, formatted as a string (from DCP).
Definition device.h:104
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:122
std::string vendorName
Human-readable vendor name (from DCP).
Definition device.h:125
std::optional< PNInM0 > im0
I&M0 identification data, if it was read successfully.
Definition device.h:128
std::string gateway
IPv4 default gateway (from DCP).
Definition device.h:119
std::uint16_t vendorId
PROFINET vendor ID (from DCP).
Definition device.h:107
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:101
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:131
Complete diagnosis data for one slot/subslot.
Definition diagnosis.h:282
All I&M records a device supports, read in one call.
Definition rpcTypes.h:227
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:134
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rpcTypes.h:145
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:136
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rpcTypes.h:142
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rpcTypes.h:139
Configuration options for an RPC connection.
Definition rpcTypes.h:292
double timeoutSec
Timeout in seconds for RPC operations.
Definition rpcTypes.h:294
Small helpers for reading/writing big-endian ("network order") integers to/from byte buffers....