Skip to content

Run a PROGRAM Locally

This guide shows you how to compile and run a LoLa PROGRAM on your local machine, exchange data with it via the process image files, and read the schema manifest.


Prerequisites

  • lola installed (pip install -e . in the repo root, or uv run lola)
  • rustc (1.70 or later) in PATH — lola run compiles a Rust runner binary
  • Python 3.10 or later for the plant simulator

1. Write (or use) a PROGRAM

A PROGRAM is a non-instantiable root POU that declares the process image interface: VAR_INPUT for fields the runtime reads from the input image, and VAR_OUTPUT for fields the runtime writes to the output image.

PROGRAM Counter
  VAR_INPUT
    reset : BOOL;
  END_VAR
  VAR_OUTPUT
    count : INT;
  END_VAR
  IMPLEMENTATION
    count:
      SET 0            WHEN reset PRIO 10;
      SET count + 1    OTHERWISE;
  END_IMPLEMENTATION
END_PROGRAM

The Pasteurizer example (examples/pasteurizer/Pasteurizer.lola) is a more complete PROGRAM that composes a BatchPasteurizer SSFC with a TemperaturePID FB using only a WIRING block.


2. Compile and check before running

lola examples/pasteurizer/Pasteurizer.lola

This runs the full verification chain: type check, Z3 proof obligations, and SSFC invariant analysis. Fix any errors before proceeding to lola run.


3. Start the runner

lola run examples/pasteurizer/Pasteurizer.lola --period-ns 100000000

This:

  1. Compiles the PROGRAM to a Rust binary (cached in ~/.cache/lola/rust/).
  2. Writes the manifest: /tmp/pasteurizer.io.json.
  3. Creates the input image if it does not exist: /tmp/pasteurizer-input.img.
  4. Launches the runner and starts the scan loop at 100 ms period.

You should see:

info: wrote process image manifest: /tmp/pasteurizer.io.json
info: created zeroed input image: /tmp/pasteurizer-input.img (47 B payload)
lola run: Pasteurizer  period=100.0 ms  ∞ scans
  input    -> /tmp/pasteurizer-input.img
  output   -> /tmp/pasteurizer-output.img
  manifest -> /tmp/pasteurizer.io.json

The runner runs until you press Ctrl-C.


4. Read the manifest

The manifest describes every process image field by name:

cat /tmp/pasteurizer.io.json
{
  "abi_version": 1,
  "program": "Pasteurizer",
  "schema_hash": "0x...",
  "representation_profile": "FLOAT64_REPR",
  "inputs": [
    {"name": "temperature", "semantic_type": "MATHREAL",
     "encoding": "IEEE754_BINARY64_LE", "offset": 0, "size": 8},
    ...
  ],
  "outputs": [...]
}

Any client that wants to read or write the process images should load the manifest and look up fields by name, not by hard-coded offsets.


5. Connect a plant simulator

In a second terminal:

python examples/pasteurizer/plant.py

The plant simulator:

  1. Reads /tmp/pasteurizer.io.json to discover field offsets.
  2. Opens the input and output image files.
  3. Validates the schema_hash in the image header against the manifest.
  4. Runs the plant physics loop and drives one complete batch.

You will see real-time output as the batch progresses through Fill → Heat → Hold → Drain → Completed.


6. Write your own client

Use ManifestReader from examples/pasteurizer/plant.py as a starting point, or implement the protocol yourself using the manifest JSON:

import struct, mmap, json

with open("/tmp/pasteurizer.io.json") as f:
    schema = json.load(f)

# Build a name → (offset, size, struct_fmt) lookup
_FMT = {"IEEE754_BINARY64_LE": "d", "BOOL_U8": "?",
        "INT16_LE": "h", "INT32_LE": "i", "INT64_LE": "q",
        "UINT16_LE": "H", "UINT32_LE": "I", "UINT64_LE": "Q"}
inputs = {f["name"]: (f["offset"], f["size"], _FMT[f["encoding"]])
          for f in schema["inputs"]}

# Write a field into a bytearray payload
def write_field(buf, name, value):
    off, sz, fmt = inputs[name]
    struct.pack_into("<" + fmt, buf, off, value)

# Example: set temperature = 25.3°C
payload = bytearray(sum(f["size"] for f in schema["inputs"]))
write_field(payload, "temperature", 25.3)
write_field(payload, "start", True)

See Process Image Reference for the full binary format and seqlock protocol.


Custom image paths

If /tmp is not suitable (e.g. macOS with SIP restrictions, or you want persistent image files):

lola run my.lola \
  --input  /dev/shm/my-input.img \
  --output /dev/shm/my-output.img \
  --manifest /dev/shm/my.io.json

The plant simulator accepts matching --input, --output, and --manifest arguments.


Troubleshooting

Symptom Likely cause Fix
rustc compilation failed rustc not in PATH or version too old Install rustc ≥ 1.70
schema_hash mismatch Input image file is stale Delete the image files and restart lola run
manifest not found lola run not started yet Start lola run first; it writes the manifest before the runner starts
Plant output stays zero Input image not yet written Check the input path; plant writes the image on each scan
PROGRAM not found error Source file contains a FUNCTION_BLOCK Use lola run only with PROGRAM blocks
Array type error A VAR_INPUT/VAR_OUTPUT is an array type Array fields are not yet supported in lola run

See also