One block, called once per cycle
FB_HLTX_Publisher sends one NetworkMessage per publishing interval, with one DataSetMessage that carries every field you added.
You want a PLC's values on the network as OPC UA PubSub, and the PLC has no PubSub library on it. Transmitter is a CODESYS library that fixes that: you add it to the project, call one function block once per cycle, and a UADP message goes out every publishing interval to a unicast address or a multicast group, the way Part 14 of the spec says. It needs no other library, no extra license, and no configurator. The consequence: a bare PLC can feed anything that subscribes to OPC UA PubSub, with a few lines of Structured Text.
Declare a publisher, add the variables you want out once at start, and call it every cycle. The block builds one message in a fixed buffer and sends it on a non-blocking socket, and the cost of a call is the cost of building one message, paid only on the cycles where one goes out. A bad address or a field list that does not fit becomes a state and a counter on the block, never a fault in your program, so the scan keeps scanning.
It sends BOOL through LREAL, STRING and TIME, and arrays, in two encodings. Variant is read by any subscriber without a layout. RawData is smaller and needs the receiver to know the layout.
PROGRAM PLC_PRG
VAR
pub : HLTX.FB_HLTX_Publisher;
bInit : BOOL;
END_VAR
IF NOT bInit THEN
bInit := TRUE;
pub.sDestination := '239.0.0.22';
pub.uiPublisherId := 1;
pub.tPublishingInterval := T#100MS;
pub.AddReal(ADR(GVL.rTemperature));
pub.AddBool(ADR(GVL.bRunning));
END_IF
pub.bEnable := TRUE;
pub(); Every new HotLoop tool is written in Go unless there is a real reason. This is the reason: it has to run inside the PLC's own scan, as a library the programmer references from the project, and a CODESYS runtime runs Structured Text. The tools around it, the capture tool and the verifier, are Go.
Transmitter only publishes: the subscriber side is not part of it. It sits on its own release line beside the rest of the lineup. HotLoop core's subscriber reads it, from core's main branch, which is not in a core release yet. It is under the same HotLoop license as every product.
Download the library below and install it in the CODESYS IDE. This is version 1.1.0. You need a CODESYS development system, 3.5 SP22 or later, and a runtime whose target has the SysSocket and SysTimeRtc components, which the CODESYS Control runtimes for Linux SL have.
HotLoopTransmitter-1.1.0.library with Tools, Library Repository, Install, or install the .package with the CODESYS Installer.HLTX.FB_HLTX_Publisher, add your fields, and call it every cycle, as in the example above.
The .compiled-library file is the same library without its source, for when you don't want the Structured
Text visible in the Library Manager. The install page in the docs
lists every error the block can report, and what to do about each.
The full runs are on the proof page, and the timing on the timing page.
| Check | What |
|---|---|
| Built | The library compiled with 0 errors and 0 warnings in the CODESYS 3.5.22.30 development system, and the example application built against it for a CODESYS Control for Linux SL device. The same holds for 1.1.0. |
| Run on | The CODESYS Control for Linux SL 4.22.0.0 runtime in a container, in demo mode, with a 20 ms task. Two publishers, one in each encoding, seventeen fields each, on The Beast. |
| Read by a decoder written from the spec | Every field equalled the PLC's own value, in both encodings, with no gap in the message sequence numbers. |
| Read by open62541 1.4.9, a stack that is not ours | In the Variant encoding all 15 values checked equal, strings and arrays included. In RawData the 11 numeric scalars equal. That stack's RawData reader cannot read a longer string or any array at all. |
| Read by HotLoop core | HotLoop's UADP subscriber, from HotLoop core's main branch (it is not in a core release yet, and the next one carries it), read both publishers live and checked 21 values equal, including the string, the TIME, both arrays and two counters that moved between reads. It refused nothing, and in 1.1.0 every reading carried the publisher's timestamp as its time. |
| Read by a second Go decoder | All 111 messages of each capture decoded, and every checked value was correct, in both encodings. |
| Timestamps (1.1.0) | Every message carries both timestamps, the same instant, never going backwards, within the whole-second tolerance of the clock the capture ran on. Over an 11 second capture the stamp moved 11 seconds. open62541 1.4.9 read the timestamps of all 113 messages of each writer with its own header decoders. The container shares the host's clock, so this shows the stamp is the PLC clock's reading, and does not test a controller whose clock is wrong. |
| The package (1.1.0) | It installs through the CODESYS PackageManagerCLI, the library appears in the System repository, and the example builds against it as the Installer put it there, 0 errors and 0 warnings. It has not been run through the IDE's own Package Manager window. |
| What it costs the scan | In 1.1.0 with timestamps on, about 9.6 microseconds added to a 20 ms cycle on average. A cycle that sends takes 10 to 100 microseconds, and the longest cycle in that run was 81 microseconds, with three publish slots skipped and no send refused. In the 1.0.0 run one send took 14.3 ms in the operating system's send call, on a container on a busy 64-thread host. If a stall like that matters to your control logic, publish from its own lower priority task. |
| Not run | A physical controller, any other runtime or CPU, a controller whose real-time clock is wrong, multicast, a runtime that loses and regains its network, and the end of the two hour demo window. A small ARM controller can be ten to a hundred times slower than the host these numbers came from, so measure it on the target with the block's own timing outputs. |
FB_HLTX_Publisher sends one NetworkMessage per publishing interval, with one DataSetMessage that carries every field you added.
BOOL, SINT, USINT, INT, UINT, DINT, UDINT, LINT, ULINT, REAL, LREAL, STRING and TIME, plus arrays of any of them but STRING.
Variant, which any subscriber reads without a layout, and RawData, which is smaller and needs the receiver to know the layout.
A dotted address, a port, and for multicast a TTL and the interface to send from. Publisher id, writer group, dataset writer and the interval are inputs of the block.
From 1.1.0 the NetworkMessage and the DataSetMessage each carry the PLC clock's reading, in whole seconds, so a subscriber knows when a value was sampled and does not guess from when it arrived. Set bTimestamp to FALSE and the block sends exactly the 1.0.0 bytes. It needs the SysTimeRtc component, which Control for Linux SL 4.22.0.0 has.
Messages sent, sends refused, publish slots skipped, the size of the last message, and the time the block spent in its last call and its longest.
It is Structured Text over SysSocket, which every CODESYS runtime with sockets has. No other library, no configurator.
The socket is non-blocking and the message is built in a fixed buffer. A socket error, a bad address, or a field list that does not fit becomes a state and a counter on the block, never a fault in your application.
A message is at most 1472 bytes, so it fits one Ethernet frame. A field list that does not fit puts the block in an error state and sends nothing, instead of cutting your data.
This version sends in the clear. Message signing and encryption are not built, and the roadmap says so.
It publishes. The subscriber side is not part of this library.
| When | What |
|---|---|
| Next | Sub-second timestamps, several DataSetMessages in one NetworkMessage, delta frames and keep-alives, WSTRING, and a per-field status. |
| Not planned | Message security, chunked messages, the subscriber side, and an MQTT transport. |
Like the rest of HotLoop. Home and lab use is free. Business use is free through EmberNet.