18 std::optional<MacAddress> controllerMac,
19 std::unique_ptr<IRawEthernetSocket> l2Socket)
20 : endpoint(std::move(endpoint)),
21 controllerMac(controllerMac.value_or(
MacAddress{})),
22 l2Sock(std::move(l2Socket))
49 if (!targetPtr || !*targetPtr)
54 auto fnToMatch = *targetPtr;
58 std::erase_if(
callbacks, [fnToMatch](
const auto& cb)
60 auto target = cb.template target<void (*)(const AlarmNotification&)>();
61 return target && *target == fnToMatch;
80 static constexpr uint16_t timeoutMs = 1000;
81 l2Sock->SetTimeout(std::chrono::milliseconds(timeoutMs));
85 udpFd = ::socket(AF_INET, SOCK_DGRAM, 0);
88 throw SocketError(
"Failed to create UDP alarm socket");
91 ::setsockopt(
udpFd, SOL_SOCKET, SO_REUSEADDR, &reuse,
sizeof(reuse));
94 addr.sin_family = AF_INET;
96 addr.sin_addr.s_addr = INADDR_ANY;
97 if (::bind(
udpFd,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr)) < 0)
99 const int err = errno;
103 if (err == EACCES || err == EPERM)
107 throw SocketError(std::string(
"Failed to bind UDP alarm socket: ") + std::strerror(err));
111 ::setsockopt(
udpFd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
114 thread = std::thread([
this]
156 catch (
const std::exception&)
176 constexpr std::size_t macHeaderLength = 12U;
177 constexpr std::size_t etherTypeLength = 2U;
178 constexpr std::size_t frameIdLength = 2U;
180 if (data.size() < macHeaderLength + etherTypeLength + frameIdLength)
187 std::size_t offset = 0;
188 std::ranges::copy(data.begin() + offset, data.begin() + offset +
macAddressLength, dstMac.begin());
190 std::ranges::copy(data.begin() + offset, data.begin() + offset +
macAddressLength, srcMac.begin());
200 if (data.size() < etherTypeOffset + etherTypeLength + frameIdLength)
205 auto ethertype =
static_cast<std::uint16_t
>((data[etherTypeOffset] <<
OneOctetShift) | data[etherTypeOffset + 1]);
211 const std::size_t frameIdOffset = etherTypeOffset + etherTypeLength;
212 auto frameId =
static_cast<std::uint16_t
>((data[frameIdOffset] <<
OneOctetShift) | data[frameIdOffset + 1]);
214 const std::size_t rtaOffset = frameIdOffset + frameIdLength;
235 socklen_t fromLen =
sizeof(from);
236 const ssize_t n = ::recvfrom(
udpFd, buf.data(), buf.size(), 0,
reinterpret_cast<sockaddr*
>(&from), &fromLen);
239 if (errno == EAGAIN || errno == EWOULDBLOCK)
245 throw SocketError(std::string(
"Alarm listener socket error: ") + std::strerror(errno));
249 buf.resize(
static_cast<std::size_t
>(n));
260 std::optional<MacAddress> srcMac)
271 catch (
const std::exception&)
309 catch (
const std::exception&)
324 for (
const auto& cb : callbacksCopy)
349 auto alarmSpecifier =
376 catch (
const std::exception&)
383constexpr std::uint16_t kVlanTpid = 0x8100;
384constexpr std::uint16_t kVlanPcp6Vid0 = 0xC000;
385constexpr std::uint16_t kVlanPcp5Vid0 = 0xA000;
407 frame.insert(frame.end(), dstMac.begin(), dstMac.end());
410 const std::uint16_t vlanTci = highPriority ? kVlanPcp6Vid0 : kVlanPcp5Vid0;
412 frame.push_back(
static_cast<std::uint8_t
>(kVlanTpid >>
OneOctetShift));
413 frame.push_back(
static_cast<std::uint8_t
>(kVlanTpid &
LowByteMask));
414 frame.push_back(
static_cast<std::uint8_t
>(vlanTci >>
OneOctetShift));
415 frame.push_back(
static_cast<std::uint8_t
>(vlanTci &
LowByteMask));
420 frame.push_back(
static_cast<std::uint8_t
>(frameId >>
OneOctetShift));
421 frame.push_back(
static_cast<std::uint8_t
>(frameId &
LowByteMask));
423 frame.insert(frame.end(), rtaBytes.begin(), rtaBytes.end());
424 frame.insert(frame.end(), ackData.begin(), ackData.end());
430 catch (
const std::exception&)
442 ::sendto(
udpFd, ackData.data(), ackData.size(), 0,
reinterpret_cast<const sockaddr*
>(&dstAddr),
Declares the PROFINET RTA alarm listener and AlarmAck transmission support.
std::atomic< bool > running
Whether the listener thread should keep running.
void SendAck(const AlarmNotification &alarm, std::optional< MacAddress > srcMac)
Build and send an AlarmAck-PDU for a received notification.
AlarmEndpoint endpoint
Alarm endpoint configuration.
void HandleUdpFrame()
Receive and process one UDP frame.
~AlarmListener() override
Stop (if running) and release resources.
std::optional< sockaddr_in > lastUdpSrc
Address of the most recently received UDP alarm, for replying.
void Stop() override
Stop the listener.
std::uint16_t recvSeqNum
Last received sequence number, for duplicate detection.
std::unique_ptr< IRawEthernetSocket > l2Sock
Raw Ethernet transport used for Layer 2 alarm reception.
MacAddress controllerMac
This host's MAC address.
std::uint16_t sendSeqNum
Sequence number for outgoing AlarmAck-PDUs.
void HandleLayer2Frame()
Receive and process one Layer 2 (RTA-PDU) frame.
void SendUdpAck(const Bytes &ackData, const sockaddr_in &dstAddr) const
Send an AlarmAck-PDU over the UDP transport.
void ListenLoop()
Listener thread body: dispatches to the Layer 2 or UDP handler.
std::thread thread
Background thread receiving and processing alarms.
void RemoveCallback(std::function< void(const AlarmNotification &)> callback) override
Remove a registered callback.
void SendLayer2Ack(const Bytes &ackData, const MacAddress &dstMac, bool highPriority)
Send an AlarmAck-PDU over the Layer 2 transport.
std::vector< std::function< void(const AlarmNotification &)> > callbacks
Registered callbacks invoked for successfully parsed alarms.
void ProcessAlarm(const Bytes &payload, std::optional< bool > highPriority, std::optional< MacAddress > srcMac)
Parse an alarm payload and dispatch it to registered callbacks.
int udpFd
Socket file descriptor used for the UDP transport.
void AddCallback(std::function< void(const AlarmNotification &)> callback) override
Register a callback for received alarms.
void Start() override
Start the background listener (socket + thread).
AlarmListener(AlarmEndpoint endpoint, std::optional< MacAddress > controllerMac=std::nullopt, std::unique_ptr< IRawEthernetSocket > l2Socket=nullptr)
Construct a listener for the given endpoint.
std::mutex callbacksMutex
Protects concurrent access to callbacks.
Insufficient permissions for raw socket.
Declares exceptions and error types used by the PROFINET IO controller stack.
BlockType
PROFINET block types.
@ AlarmAcknowledgementLow
Alarm acknowledgement low block.
@ AlarmAcknowledgementHigh
Alarm acknowledgement high block.
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
constexpr std::uint16_t ALARM_UDP_PORT
UDP port used for alarm transport when Transport == 1.
std::size_t SkipVlanTags(std::span< const std::uint8_t > frame) noexcept
Return the byte offset of the real EtherType in a raw Ethernet frame.
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
constexpr int macAddressLength
Constant lenght of a mac address.
static constexpr std::uint8_t LowByteMask
Bitmask used to isolate the lowest significant byte (8 bits) of a larger integer.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
constexpr int blockHeaderLenght
Constant lenght of a blockHeader.
constexpr std::size_t RECEIVE_BUFFER_LENGTH
Receive buffer length.
constexpr std::uint16_t FRAME_ID_ALARM_HIGH
Frame ID used for high-priority alarms.
constexpr std::uint16_t ETHERTYPE_PROFINET
EtherType used by PROFINET RTA frames (0x8892).
constexpr std::uint16_t FRAME_ID_ALARM_LOW
Frame ID used for low-priority alarms.
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.
bool manufacturerSpecific
Whether the alarm specifier indicates a manufacturer-specific diagnosis.
std::uint16_t slotNumber
Slot number where the alarm originated.
std::uint16_t alarmSequenceNumber
Sequence number extracted from the alarm specifier.
std::uint32_t api
API number.
AlarmType alarmType
Alarm type (Diagnosis/Process/Pull/Plug/...).
std::uint16_t subslotNumber
Subslot number where the alarm originated.
bool IsHighPriority() const
Whether this is a high-priority notification.
bool channelDiagnosis
Whether the alarm specifier indicates channel diagnosis is present.
bool arDiagnosisState
Whether the alarm specifier indicates AR diagnosis state.
bool submoduleDiagnosisState
Whether the alarm specifier indicates submodule diagnosis state.
AlarmAck-PDU: acknowledges receipt of an AlarmNotification-PDU.
std::uint16_t alarmSpecifier
Alarm specifier flags echoed from the notification.
AlarmType alarmType
Alarm type being acknowledged.
std::uint16_t slotNumber
Slot number being acknowledged.
std::uint32_t statusPNIO
PNIO status code (0 = acknowledged OK).
std::uint16_t subslotNumber
Subslot number being acknowledged.
static constexpr std::size_t kSize
Size in bytes of this PDU.
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
std::uint32_t api
API number.
Bytes ToBytes() const
Serialize this PDU back to raw bytes.