Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ export default defineConfig({
{ slug: 'transmitter/timing', label: 'What it adds to the task' },
{ slug: 'transmitter/proof', label: 'What was run' },
{ slug: 'transmitter/roadmap', label: 'Roadmap' },
{ slug: 'releases/transmitter-1-0-0', label: 'Release notes' },
{ slug: 'releases/transmitter-1-1-0', label: 'Release notes 1.1.0' },
{ slug: 'releases/transmitter-1-0-0', label: 'Release notes 1.0.0' },
],
},
{
Expand Down
1 change: 1 addition & 0 deletions scripts/sync-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ ${rows('cascade')}

## HotLoop Transmitter

- [HotLoop Transmitter 1.1.0 release notes](/releases/transmitter-1-1-0/)
- [HotLoop Transmitter 1.0.0 release notes](/releases/transmitter-1-0-0/)
`);
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/sync-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Object.assign(SLUG_RULES, {
['Edge Relay', 'HotLoop core', 'edge-relay/overview', 'edge-relay/install', 'releases/#hotloop-core', 'roadmap'],
['Flow', 'Flow', 'flow/overview', 'flow/install', 'releases/#hotloop-flow', 'flow/roadmap'],
['Cascade', 'Cascade', 'cascade/overview', 'cascade/running', 'releases/#hotloop-cascade', 'cascade/roadmap'],
['Transmitter', 'Transmitter', 'transmitter/overview', 'transmitter/install', 'releases/transmitter-1-0-0', 'transmitter/roadmap']]
['Transmitter', 'Transmitter', 'transmitter/overview', 'transmitter/install', 'releases/transmitter-1-1-0', 'transmitter/roadmap']]
.map(([n, l, d, i, r, m]) => `| **HotLoop ${n}** | ${l} | <a href="/${d}/">Docs</a> | <a href="/${i}/">Install</a> | <a href="/${r}">Notes</a> | <a href="/${m}/">Roadmap</a> |`).join('\n')
+ '\n\nThe table that follows compares the four core products.'],
],
Expand Down
4 changes: 2 additions & 2 deletions src/components/ProductPicker.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import sync from '../data/sync.json';
const hot = sync.repos.hotloop.release;
const flow = sync.repos.flow.release;
const cascade = sync.repos.cascade.release;
// Transmitter 1.0.0 shipped 2026-11-04 (HotLoop-io/hotloop-transmitter v1.0.0). Change this when the next one does.
const transmitter = { version: '1.0.0' };
// Transmitter 1.1.0 shipped 2026-11-04 (HotLoop-io/hotloop-transmitter v1.0.0). Change this when the next one does.
const transmitter = { version: '1.1.0' };

const latest = (prefix: string) =>
sync.pages.filter((p) => p.release && p.status !== 'next' && p.slug.startsWith(prefix)).map((p) => p.slug).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))[0];
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ PLC on your bench. Features split them. Protocols don't.
| **HotLoop Edge Relay** | HotLoop core | <a href="/edge-relay/overview/">Docs</a> | <a href="/edge-relay/install/">Install</a> | <a href="/releases/#hotloop-core">Notes</a> | <a href="/roadmap/">Roadmap</a> |
| **HotLoop Flow** | Flow | <a href="/flow/overview/">Docs</a> | <a href="/flow/install/">Install</a> | <a href="/releases/#hotloop-flow">Notes</a> | <a href="/flow/roadmap/">Roadmap</a> |
| **HotLoop Cascade** | Cascade | <a href="/cascade/overview/">Docs</a> | <a href="/cascade/running/">Install</a> | <a href="/releases/#hotloop-cascade">Notes</a> | <a href="/cascade/roadmap/">Roadmap</a> |
| **HotLoop Transmitter** | Transmitter | <a href="/transmitter/overview/">Docs</a> | <a href="/transmitter/install/">Install</a> | <a href="/releases/transmitter-1-0-0">Notes</a> | <a href="/transmitter/roadmap/">Roadmap</a> |
| **HotLoop Transmitter** | Transmitter | <a href="/transmitter/overview/">Docs</a> | <a href="/transmitter/install/">Install</a> | <a href="/releases/transmitter-1-1-0">Notes</a> | <a href="/transmitter/roadmap/">Roadmap</a> |

The table that follows compares the four core products.

Expand Down
1 change: 1 addition & 0 deletions src/content/docs/releases/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ IoT, Edge, Gateway and Edge Relay release together from one tag, so they share t

## HotLoop Transmitter

- [HotLoop Transmitter 1.1.0 release notes](/releases/transmitter-1-1-0/)
- [HotLoop Transmitter 1.0.0 release notes](/releases/transmitter-1-0-0/)
80 changes: 80 additions & 0 deletions src/content/docs/releases/transmitter-1-1-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "HotLoop Transmitter 1.1.0"
description: "Every message carries a timestamp from the PLC's clock, and the library installs from a CODESYS package: what changed, what was run, and what was not."
---

Every message now carries a timestamp from the PLC's clock, and the library now installs from a CODESYS `.package`.

What that means for you: a subscriber that wants to know when a value was sampled gets it from the packet instead of
guessing from when it arrived, and you can install the library through the CODESYS Installer like any other add-on.

## What changed for a project that uses 1.0.0

* **Timestamps, on by default.** The NetworkMessage and the DataSetMessage each carry a Timestamp (Part 14 7.2.4.4.2
and 7.2.4.5.4), the same DateTime, read from the PLC's real-time clock once per message. ExtendedFlags1 goes from
`01` to `21`, DataSetFlags1 from `69` or `6B` to `E9` or `EB`, and a DataSetFlags2 byte `10` is added. A message is
17 bytes longer. The field list and the values did not move, and `udiConfigMajor` is the same.
* **`bTimestamp := FALSE` sends exactly the 1.0.0 bytes.** Pin that if a receiver of yours refuses timestamps. The lab
checked it: 1142 and 1144 byte messages, none stamped, the sizes 1.0.0 sent.
* **The resolution is one second.** The clock is read with `SysTimeRtcGet`, which is whole seconds, so a receiver sees
the same timestamp on every message inside a second and it can be up to a second behind the instant the message was
built. Sub-second stamps are on the roadmap.
* **A new output, `diLastClockResult`.** If the clock cannot be read the message goes out unstamped, which Part 14
allows, and this says why.
* **A new system library, SysTimeRtc.** The library names it as a placeholder, like SysSocket, and the runtime provides
it. Control for Linux SL 4.22.0.0 does. A target without it cannot use 1.1.0 with timestamps; stay on 1.0.0.
* **The `.package`.** `HotLoopTransmitter-1.1.0.package` was made by CODESYS's own PackageManagerCLI and puts the same
library into the library repository through the Installer. See the [install page](/transmitter/install/).

## What I ran, and what it showed

All of it on The Beast, on the files below, against CODESYS Control for Linux SL 4.22.0.0 in a container.

* The library builds with 0 errors and 0 warnings in CODESYS 3.5.22.30. The example application builds against it
with 0 errors and 0 warnings.
* Two publishers, Variant and RawData, timestamps on. `test/verify` decodes every message and every value equals what
the PLC reports over its own OPC UA server. Every message has both timestamps, they are the same instant, they never
go backwards, they are within the whole-second tolerance of the clock the capture ran on, and over an 11 second
capture the stamp moved 11 seconds. The container shares the host's clock, so this shows the stamp is the PLC's clock
reading and not an invented one; it does not test a controller whose clock is wrong.
* open62541 1.4.9 reads the NetworkMessage and DataSetMessage Timestamp of all 113 messages of each writer with its
own header decoders, and decodes the values as it did for 1.0.0.
* HotLoop core's subscriber (from its main branch, not in a core release yet) reads both publishers against a live runtime: 21 values each, both counters
advancing, nothing refused. Every reading carries the publisher's whole-second timestamp as its time, and the
timestamp moved with the PLC clock over a 3 second wait.
* The `.package` installs through `PackageManagerCLI --install`, the library appears in the System repository (it was
not there before), and the example builds against the library as the Installer put it there, 0 errors and 0 warnings.
* Cost on a 20 ms task, two publishers: about 9.6 us added per cycle on average, 10 to 100 us on the cycles that send,
and the longest cycle in the run 81 us. Three publish slots were skipped in that run, and no send was refused. The
timestamp itself is not visible in the numbers. The [timing page](/transmitter/timing/) has the table.

## What I did not run

* Any physical controller, any CODESYS runtime but Control for Linux SL 4.22.0.0, and any CPU but x86-64. The byte
order handling for a big endian target is written and not run.
* A controller whose real-time clock is unset or wrong. The block would stamp what the clock says.
* The `.package` in the CODESYS IDE's own Package Manager window. It installed headless with PackageManagerCLI.
* Multicast. The lab used unicast.
* A runtime that loses and regains its network, or a send to an address that is not there.
* What happens when the two hour demo window of the runtime ends.
* Receivers other than the four above.

## Not in this release

KeepAlive and Delta Frames, more than one DataSetMessage per NetworkMessage, WSTRING, a per-field status, sub-second
timestamps, message security, and the subscriber side. The [roadmap](/transmitter/roadmap/) says which are next.

## Rolling back

A PLC project pinned to 1.0.0 keeps working and keeps sending the 1.0.0 bytes. Within 1.1.0, `bTimestamp := FALSE` is
the same bytes.

## Files

Download from https://hotloop.io/hotloop-transmitter/1.1.0/, and check them against `SHA256SUMS` in the same folder.

```
5ec021b23f5d57460016834a03b2b196d6dbc83aeb5d0ad60ace8d7b4fbbb4a7 HotLoopTransmitter-1.1.0.library
e172f4674f6cbfce2aeaf017d5a6c733245b1b9aa3e527369c753bf07b6ce0c2 HotLoopTransmitter-1.1.0.compiled-library
b3686bdebd052f1ddea46d1f1ff409f3b6c9a016ca5f3e8f4cd6f7922ccb479c HotLoopTransmitter-1.1.0.package
```
3 changes: 3 additions & 0 deletions src/content/docs/transmitter/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Everything is in the `HLTX` namespace. The block is `HLTX.FB_HLTX_Publisher`.
| `eEncoding` | `E_HLTX_Encoding` | `Variant` | `Variant` or `RawData`. Change it while disabled |
| `udiGroupVersion` | UDINT | 1 | GroupVersion in the group header |
| `udiConfigMajor`, `udiConfigMinor` | UDINT | 1, 0 | DataSet ConfigurationVersion. Bump the major whenever the field list changes |
| `bTimestamp` | BOOL | TRUE | stamp every message with the PLC clock, in the NetworkMessage and the DataSetMessage (whole seconds, see `WIRE-FORMAT.md`). FALSE sends the 1.0.0 bytes, 17 bytes shorter |
| `tRetry` | TIME | T#2S | how long to wait before reopening the socket after ten sends in a row failed |

## Outputs
Expand All @@ -35,6 +36,8 @@ Everything is in the `HLTX` namespace. The block is `HLTX.FB_HLTX_Publisher`.
| `udiSkipped` | UDINT | publish slots missed because the task ran late |
| `uiMessageSize` | UINT | bytes in the last message |
| `diLastSocketResult` | UDINT | the last runtime error code from a socket call, 0 is fine |
| `diLastClockResult` | UDINT | the last result of reading the PLC clock for a timestamp, 0 is fine. When it is not 0 the message goes out unstamped |
| `diLastClockResult` | UDINT | the last result of reading the PLC clock for a timestamp, 0 is fine. When it is not 0 the message goes out unstamped |
| `udiLastCallUs`, `udiMaxCallUs` | UDINT | microseconds spent in the last call of the block, and the longest since enable or `Clear()` |
| `udiMaxEncodeUs`, `udiMaxSendUs` | UDINT | the longest time spent building one message, and in the send call itself, since enable or `Clear()` |

Expand Down
16 changes: 11 additions & 5 deletions src/content/docs/transmitter/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ title: "Install HotLoop Transmitter"
description: "Install the library into the CODESYS library repository, add it to a project, and what to do when the block reports an error."
---

You need a CODESYS development system, 3.5 SP22 or later, and a runtime whose target has the SysSocket component,
which the CODESYS Control runtimes for Linux SL have.
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 (the second one is new in 1.1.0, for the timestamp).

1. Download `HotLoopTransmitter-1.0.0.library` from https://hotloop.io/hotloop-transmitter/1.0.0/HotLoopTransmitter-1.0.0.library, and check it against `SHA256SUMS` in the same folder (`sha256sum -c SHA256SUMS`).
## From the CODESYS Installer

Since 1.1.0 the release also has `HotLoopTransmitter-1.1.0.package`, at https://hotloop.io/hotloop-transmitter/1.1.0/HotLoopTransmitter-1.1.0.package. Install it with the Package Manager of the IDE (Tools, Package Manager), or with no window using `PackageManagerCLI.exe --profile="<your profile>" --install=<file>.package --targetProfiles="<your profile>"` with no window). The Installer puts the library into that profile's library repository under the company HotLoop, and you go to step 3 below. The package is the same library as the `.library` file, in a ZIP the Installer reads, so the two routes give the same library.

## From the library file

1. Download `HotLoopTransmitter-1.1.0.library` from https://hotloop.io/hotloop-transmitter/1.1.0/HotLoopTransmitter-1.1.0.library, and check it against `SHA256SUMS` in the same folder (`sha256sum -c SHA256SUMS`).
2. In the CODESYS IDE: Tools, Library Repository, Install, pick the file. It is now in the repository under the
company HotLoop.
3. In your project, open the Library Manager of the application, Add Library, and pick HotLoop Transmitter.
SysSocket and its interface libraries come with it as dependencies and need nothing from you.
SysSocket, SysTimeRtc and their interface libraries come with it as dependencies and need nothing from you.
4. Declare a `HLTX.FB_HLTX_Publisher`, add your fields, call it every cycle (README has the example).
5. Download to the controller. The destination must be reachable from the controller's network; for multicast the
network's switches must carry the group.

The `.compiled-library` file is the same library without its source. Install that one instead if you do not want the
Structured Text visible in the Library Manager. It is in the same folder, as `HotLoopTransmitter-1.0.0.compiled-library`.
Structured Text visible in the Library Manager. It is in the same folder, as `HotLoopTransmitter-1.1.0.compiled-library`.

To check what arrives, any UDP listener on the destination port works. The lab used a small Go program that listens on a port or joins a
multicast group and prints each datagram as a line of JSON with its hex.
Expand Down
13 changes: 8 additions & 5 deletions src/content/docs/transmitter/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ configurator. You add the library, call one function block once per cycle, and a
publishing interval to a unicast address or a multicast group.

What that means for you: a PLC that has no PubSub library can still feed anything that subscribes to OPC UA PubSub,
including HotLoop itself, with a few lines of Structured Text.
including HotLoop itself, with a few lines of Structured Text. Every message carries a timestamp from the PLC's clock
unless you switch it off with `bTimestamp`.

## Why this is Structured Text and not Go

Expand All @@ -23,8 +24,8 @@ verifier) are Go.

## Use it

1. Install `HotLoopTransmitter-1.0.0.library` into the CODESYS library repository (Tools, Library Repository,
Install), then add it to the project's Library Manager. The [install page](/transmitter/install/) has the download and the details.
1. Install `HotLoopTransmitter-1.1.0.library` into the CODESYS library repository (Tools, Library Repository,
Install), or install `HotLoopTransmitter-1.1.0.package` with the CODESYS Installer, then add it to the project's Library Manager. The [install page](/transmitter/install/) has the downloads and the details.
2. Declare a publisher, add the variables you want to publish once at start, and call it every cycle:

```iecst
Expand Down Expand Up @@ -57,15 +58,17 @@ sections of the spec that define each byte.

* It never blocks. The socket is non-blocking and the message is built in a fixed buffer.
* The cost of a call is the cost of building one message, and it is only paid on the cycles where a message goes
out. The measured numbers are on the [timing page](/transmitter/timing/).
out. Measured on a 20 ms task with two publishers and timestamps on: about 9.6 us added per cycle on average, 10 to
100 us on the cycles that send, and the longest cycle in the run 81 us. A busy host has stretched a single send to
milliseconds. The measured numbers and their limits are in the [timing page](/transmitter/timing/).
* A socket error, a bad address or an oversized field list never faults the application. The block reports an
`eState` and an `eError`, counts the failure, and the program keeps scanning.
* It does not fragment. A message is at most 1472 bytes. A field list that does not fit puts the block in
`ConfigError` and sends nothing.

## Tested against

The CODESYS Control for Linux SL 4.22.0.0 runtime, in a container. The [timing page](/transmitter/timing/) and the [proof page](/transmitter/proof/) say what
The CODESYS Control for Linux SL 4.22.0.0 runtime, in a container. See the [timing page](/transmitter/timing/) and the [proof page](/transmitter/proof/) for what
was run and what was read back. Not run on physical hardware.

## License
Expand Down
4 changes: 4 additions & 0 deletions src/content/docs/transmitter/proof.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ title: "What was run"
description: "What the library was built with, run on, and read back by, and the one place two readers disagree."
---

This page is the 1.0.0 run. The 1.1.0 run, with timestamps and the `.package`, is in the [1.1.0 release notes](/releases/transmitter-1-1-0/). It used the same lab, plus
`test/verify -timestamps`, `test/open62541/decode.c` (open62541's own header decoders reading the Timestamps),
`test/core/run.sh` with timestamps, and `test/package.sh`.

Everything below ran on The Beast on 2026-11-04, from `test/run.sh all` and `test/core/run.sh`, on the library that is in `dist/1.0.0/`. Nothing here ran on a physical controller.

## The build
Expand Down
7 changes: 6 additions & 1 deletion src/content/docs/transmitter/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ description: "What HotLoop Transmitter has built, what is next, and what is deli
What is in the box today, what is next, and what is deliberately not planned. A line moves to "Shipped" only
when it has run on the real 4.22.0.0 runtime and a receiver has read it.

## Shipped in 1.1.0

* DataSetMessage and NetworkMessage timestamps from the PLC's real-time clock, on by default, `bTimestamp` switches them off.
* The library as a CODESYS `.package`, installed through the CODESYS Installer.

## Shipped in 1.0.0

* One publisher block, `FB_HLTX_Publisher`: one NetworkMessage per publishing interval, one DataSetMessage, a Data
Expand All @@ -20,7 +25,7 @@ when it has run on the real 4.22.0.0 runtime and a receiver has read it.

## Next

* DataSetMessage and NetworkMessage timestamps, from the runtime's real-time clock.
* Sub-second timestamps. The 1.1.0 stamp is the real-time clock's whole seconds.
* Several DataSetMessages in one NetworkMessage (the Sizes array), so one datagram can carry more than one writer.
* Data Delta Frames and KeepAlive messages, to cut the bytes of a mostly steady dataset.
* WSTRING fields.
Expand Down
Loading
Loading