Process Image Reference¶
Normative for the binary process image format and the JSON schema manifest at the compiler version in this repository.
Overview¶
A LoLa PROGRAM communicates with the outside world through two process image files: one for inputs (written by the plant/HMI, read by the PROGRAM) and one for outputs (written by the PROGRAM, read by the plant/HMI). Both files have the same binary layout: a 32-byte header followed by a dense payload block.
The process image manifest (.io.json) is a JSON file generated by
lola run. It describes every field in both images by name, type, wire
encoding, byte offset, and size. External processes should load the manifest
rather than hard-coding byte offsets.
Binary image format¶
Header (32 bytes, little-endian)¶
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 B | magic |
0x4C4F4C41 (LOLA in ASCII) |
| 4 | 2 B | abi_version |
Currently 1 |
| 6 | 2 B | (padding) | Reserved, zero |
| 8 | 8 B | schema_hash |
8-byte hash of the I/O schema (see below) |
| 16 | 8 B | seq |
Seqlock counter (even = stable, odd = write in progress) |
| 24 | 4 B | payload_size |
Byte length of the payload that follows |
| 28 | 4 B | (padding) | Reserved, zero |
Total header: 32 bytes. Payload starts at byte 32.
Seqlock protocol¶
The seq counter protects the payload against torn reads.
Writer (the runtime runner):
- Increment
seqto an odd value (write begins). - Write the payload bytes.
- Increment
seqto an even value (write complete).
Reader (plant, HMI, supervisory tool):
- Read
seq— if odd, the writer is active; spin or back off. - Copy the payload bytes.
- Read
seqagain — if changed since step 1, discard the copy and retry.
Schema hash¶
The schema_hash is a stable 8-byte hash of the program's I/O interface.
It is derived from: abi_version, direction, field name, semantic type, and
wire encoding — in declaration order. It does not depend on internal state,
comments, or PID parameters.
The runtime runner validates schema_hash on startup. A mismatch between the
stored hash in the image file and the compiled program's expected hash is an
error; delete the stale image files and restart lola run.
Payload layout¶
Fields are packed densely in declaration order, starting at byte offset 0 within the payload (i.e., byte 32 of the file). There is no alignment padding between fields.
Input image payload — fields from VAR_INPUT, in source declaration order.
Output image payload — fields from VAR_OUTPUT, in source declaration order.
Wire encodings¶
| LoLa type | Encoding tag | Struct fmt | Size |
|---|---|---|---|
BOOL |
BOOL_U8 |
? |
1 B |
SINT |
SINT8_LE |
b |
1 B |
USINT |
UINT8_LE |
B |
1 B |
BYTE |
UINT8_LE |
B |
1 B |
INT |
INT16_LE |
h |
2 B |
UINT |
UINT16_LE |
H |
2 B |
WORD |
UINT16_LE |
H |
2 B |
DINT |
INT32_LE |
i |
4 B |
UDINT |
UINT32_LE |
I |
4 B |
DWORD |
UINT32_LE |
I |
4 B |
LINT |
INT64_LE |
q |
8 B |
ULINT |
UINT64_LE |
Q |
8 B |
LWORD |
UINT64_LE |
Q |
8 B |
LREAL |
IEEE754_BINARY64_LE |
d |
8 B |
MATHREAL |
IEEE754_BINARY64_LE |
d |
8 B |
All multi-byte fields are little-endian. BOOL is encoded as a single byte:
0x01 = TRUE, 0x00 = FALSE; all other non-zero values are treated as TRUE.
MATHREAL is always encoded as IEEE 754 binary64 (f64). The lola run
command enforces FLOAT64_REPR, so MATHREAL is always 8 bytes on the wire.
JSON manifest format¶
lola run writes <name>.io.json (default: same directory as the input image).
This file is the single source of truth for the process image layout.
Schema¶
{
"abi_version": 1,
"program": "Pasteurizer",
"schema_hash": "0xa3f1b2c9d4e50671",
"representation_profile": "FLOAT64_REPR",
"inputs": [
{
"name": "temperature",
"semantic_type": "MATHREAL",
"encoding": "IEEE754_BINARY64_LE",
"offset": 0,
"size": 8
},
{
"name": "start",
"semantic_type": "BOOL",
"encoding": "BOOL_U8",
"offset": 40,
"size": 1
}
],
"outputs": [
{
"name": "heater_command",
"semantic_type": "MATHREAL",
"encoding": "IEEE754_BINARY64_LE",
"offset": 0,
"size": 8
}
]
}
Top-level fields¶
| Field | Type | Description |
|---|---|---|
abi_version |
integer | Binary image ABI version (currently 1) |
program |
string | PROGRAM name as declared in the source |
schema_hash |
"0x…" hex string |
Same 8-byte hash embedded in the binary image header |
representation_profile |
string | "FLOAT64_REPR" when MATHREAL is f64 on the wire; "NONE" otherwise |
inputs |
array | Fields in VAR_INPUT order |
outputs |
array | Fields in VAR_OUTPUT order |
Field object¶
| Field | Type | Description |
|---|---|---|
name |
string | Variable name as declared in the source |
semantic_type |
string | LoLa type: "MATHREAL", "BOOL", "INT", … |
encoding |
string | Wire encoding tag (see table above) |
offset |
integer | Byte offset within this direction's payload block |
size |
integer | Wire size in bytes |
Offsets are relative to byte 0 of the payload (byte 32 of the file).
Reading the manifest in Python¶
The ManifestReader class in examples/pasteurizer/plant.py provides
a minimal, self-contained manifest client. It reads the JSON and returns
(offset, size, struct_fmt) tuples compatible with Python's struct module.
from plant import ManifestReader # or inline the class in your own client
mr = ManifestReader("/tmp/pasteurizer.io.json")
# Lookup: (offset, size, struct_fmt)
off, sz, fmt = mr.input("temperature") # → (0, 8, "d")
off, sz, fmt = mr.input("start") # → (40, 1, "?")
off, sz, fmt = mr.output("heater_command") # → (0, 8, "d")
# Validate payload size
assert mr.input_payload_size == 47
assert mr.output_payload_size == 13
# Validate schema_hash against the image header
# (PlantImage.validate() does this automatically)
The manifest is always overwritten when lola run starts. Clients should
reload it when the runtime is restarted, or compare schema_hash against the
value embedded in the image header to detect stale data.
Schema hash computation¶
The schema hash is computed over the canonical JSON field list, not over the JSON text. The algorithm:
SHA-256 of:
"lola-io-abi-v1\n"
for each input field: "in/{name}/{semantic_type}/{encoding}\n"
for each output field: "out/{name}/{semantic_type}/{encoding}\n"
schema_hash = first 8 bytes of the digest, interpreted as little-endian uint64
This ensures the hash changes when: - Any field is renamed, reordered, or its type changes. - The wire encoding changes (e.g., because the representation profile changes).
It does not change when: - Internal state variables, comments, or PARAMETER values change. - The PROGRAM's behavioral logic changes without altering the I/O interface.
See also¶
- lola run CLI reference
- How to run a PROGRAM locally
- Language Reference §3.6 — PROGRAM block
lola/runner_build.py—ProcessImageSchema,build_schema()(compiler source)