Skip to content

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):

  1. Increment seq to an odd value (write begins).
  2. Write the payload bytes.
  3. Increment seq to an even value (write complete).

Reader (plant, HMI, supervisory tool):

  1. Read seq — if odd, the writer is active; spin or back off.
  2. Copy the payload bytes.
  3. Read seq again — 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