How it works
From a UDP datagram to a typed field: the transport, codec, security, kernel, and module pipeline — and the message hierarchy it produces.
The message hierarchy
OPC UA PubSub nests three levels. Decoding peels them off one at a time:
NetworkMessage ← one UDP datagram
publisherId, writerGroupId, sequenceNumber, timestamp
└── DataSetMessage[] ← one per writer in the group
dataSetWriterId, fieldEncoding, status, config version
└── DataSetField[] ← the actual values
name, value → getScalar()
- A
NetworkMessagecarries header fields and a list ofDataSetMessages. - A
DataSetMessageis the payload from onedataSetWriterId, decoded with aFieldEncoding(Variant,RawData, orDataValue). - A
DataSetFieldpairs aname(resolved from the reader's metadata) with avalue;getScalar()unwraps it to a plain PHP scalar.
The pipeline
UdpTransport ──► NetworkMessageCodec ──► PubSubSecurityCodec ──► PubSubKernel
(ext-sockets) (UADP or JSON) (optional) (demux + dispatch)
│ │ │ │
ReceivedPayload NetworkMessage verify / decrypt DataSetReaderModule
(bytes + sourceUri) (Sign / SignAndEncrypt) matches each DataSetMessage
against your readers
│
your onDataSetMessage callbacks
+ PubSubModules + PSR-14 events
UdpTransportreceives a datagram and wraps the raw bytes in aReceivedPayload(data,sourceUri,receivedAt).- The codec (
UadpNetworkMessageCodecby default, orJsonNetworkMessageCodecafteruseJson()) decodes the bytes into aNetworkMessage. When security is configured, the codec verifies the signature and decrypts the payload first. PubSubKernelruns the event loop. It polls every transport, feeds bytes through the codec, and routes the result.- Demux.
DataSetReaderModuleholds yourDataSetReaderConfigs as a registry keyed by(publisherId, writerGroupId, dataSetWriterId). The codec uses that registry to decode each datagram — aDataSetMessagewhose triple matches a reader is decoded with that reader's metadata; one that matches nothing is dropped. The kernel then fires your callbacks,PubSubModules, and PSR-14 events for the matched messages.
Demultiplexing
A reader is identified by three values that must equal the ones on the wire:
publisherId
Byte / UInt16 / UInt32 / UInt64 (as int) or String. Must match the
publisher's id type and value.
writerGroupId
The WriterGroup id.
dataSetWriterId
The DataSetWriter id within the group.
Datagrams whose triple matches no configured reader are silently ignored — that is how one multicast group carrying several writers is filtered down to the ones you care about.
The runtime
PubSubKernel is single-threaded and has no external event loop. It exposes
two driving modes through the Subscriber:
run()— blocks, polling all transports in round-robin untilstop(). Ideal for a dedicated worker process.poll(int $timeoutMs)— one bounded polling pass; returns and lets you integrate with your own loop.
Where to extend
The kernel calls every registered PubSubModule for each
decoded DataSetMessage, and emits PSR-14 events at each
stage. Custom transports plug in via
PubSubTransportInterface. None of this requires
forking the package.