Build your first IOC instance#
This tutorial walks you end to end through turning a support module
definition into a running EPICS IOC startup script, using the motorSim
simulated‑motion sample that ships with ibek. By the end you will have:
read a support definition file and understood what it declares,
generated a JSON schema for authoring instances,
written an IOC instance file,
generated the runtime files an IOC needs to boot, and
compared the produced
st.cmdagainst the known‑good sample.
Every command here is real and tested. Inside a generic IOC container the
ibek command is already on the PATH, so type the commands exactly as
shown. If you are following along in a checkout of this repository instead,
prefix each command with uv run (the first run rebuilds the virtual
environment, so give it a moment).
The bare file names below assume the container layout, where the support and
instance files sit in the working directory. In a checkout they live under
tests/samples/support/, tests/samples/iocs/ and tests/samples/outputs/,
so either cd tests/samples first and prefix the names with support/,
iocs/ and outputs/, or pass the full paths.
The three phases#
ibek is used at three different points in an IOC’s life. Keep this map in
mind as you go — each step below belongs to one of these phases.
flowchart LR
A[Container build phase<br/>support modules + their<br/>.ibek.support.yaml defs<br/>baked into the image] --> B[Authoring phase<br/>generate schema,<br/>write the instance YAML]
B --> C[Boot phase<br/>runtime generate -><br/>st.cmd + runtime db,<br/>then the IOC starts]
Steps 1–3 are authoring: a developer does them once to create an IOC
instance. Step 4 belongs to the boot phase: when an IOC container starts
it regenerates these files. Production containers do this with the newer
generate2 subcommand; this tutorial uses the simpler legacy single‑instance
generate, which is enough for one instance file. The support modules
referenced in step 1 were compiled and their definition files collected during
the earlier container build phase.
Step 1 — start from a support definition file#
A support module ships an *.ibek.support.yaml file that declares one or
more Entity Models — the building blocks you can later instantiate in an
IOC. Open the sample:
tests/samples/support/motorSim.ibek.support.yaml
module: motorSim
entity_models:
- name: simMotorController
description: |-
Creates a simulation motion controller
parameters:
controllerName:
type: id
description: |-
The name of the controller and its Asyn Port Name
P:
type: str
description: |-
Device PV Prefix
numAxes:
type: int
description: |-
The number of axes to create
port:
type: object
description: |-
a reference to the asyn port for communication with the controller
DESC:
type: str
description: |-
The description of the controller
default: "Simulated Motion Controller testing escaping: {% raw %} {{enclosed in escaped curly braces}} {% endraw %}"
pre_init:
- value: |
# motorSimCreateController(controller_asyn_port_name, axis_count)
# testing escaping: {% raw %} {{enclosed in escaped curly braces}} {% endraw %}
motorSimCreateController({{controllerName}}, {{numAxes}})
databases:
- file: sim_motor.db
args:
controllerName:
P:
DESC:
pvi:
yaml_path: simple.pvi.device.yaml
ui_macros:
P:
pv: true
pv_prefix: $(P)
The top‑level module names the support module, and entity_models is the
list of models it provides. The first model above, simMotorController,
declares its parameters (typed inputs such as controllerName, P,
numAxes and a port of type: object), the pre_init startup‑script
snippet it contributes, and the databases and pvi it brings. A second
model, simMotorAxis, follows further down the file.
For the full meaning of every field, see the support YAML reference.
Step 2 — generate the entities schema#
To author an instance with editor autocompletion and validation, turn the support definitions into a JSON schema:
ibek ioc generate-schema \
motorSim.ibek.support.yaml asyn.ibek.support.yaml \
--no-ibek-defs \
--output motorSim.ibek.ioc.schema.json
The --no-ibek-defs flag tells ibek to build the schema only from the
files you list, instead of also pulling in the support definitions bundled
into a generic IOC container. That is exactly what you want when you are
working outside a container (as in this tutorial) — you supply the
definitions yourself.
We pass both motorSim and asyn because the instance we are about to
write uses an entity from each module; the generated schema must know about
every model the instance references.
Step 4 — generate the runtime files#
Now produce the files the IOC needs to boot. The generate subcommand used
here is the legacy single‑instance form (production containers run generate2
at startup); it is the simplest way to follow along with one instance file:
ibek runtime generate \
motorSim.ibek.ioc.yaml \
motorSim.ibek.support.yaml asyn.ibek.support.yaml \
--no-pvi \
-o /tmp/runtime
The first argument is the instance file; the remaining arguments are the
support definition files for every module the instance uses — again both
motorSim and asyn. The -o option chooses the output folder. The
--no-pvi flag skips generating PVI screens, which ibek would otherwise
write to /epics/opi regardless of -o — a path that only exists inside a
container, so the command fails without it when run from a checkout.
Note
Inside a real IOC container these support definition files live in a known location that was populated during the container build phase, and the EPICS environment variables that set the IOC and runtime paths are already exported. In this tutorial’s sandbox those variables are unset, so the absolute paths in the generated script differ slightly — the structure is identical.
Step 5 — inspect the generated st.cmd#
Look at /tmp/runtime/st.cmd. It should match the tested sample at
tests/samples/outputs/motorSim/st.cmd:
# EPICS IOC Startup Script generated by https://github.com/epics-containers/ibek
cd "/epics/ioc"
epicsEnvSet NAME_AS_ENV_VAR my name is controllerOnePort
dbLoadDatabase dbd/ioc.dbd
ioc_registerRecordDeviceDriver pdbbase
# Setting up Asyn Port controllerOnePort on 192.168.0.55:2002:
# AsynIPConfigure({{name}}, {{port}}, {{stop}}, {{parity}}, {{bits}})
AsynIPConfigure(controllerOnePort, 192.168.0.55:2002, 1, none, 8)
asynSetOption(9600, 0, N, Y)
asynOctetSetInputEos("\n")
asynOctetSetOutputEos("\n")
# motorSimCreateController(controller_asyn_port_name, axis_count)
# testing escaping: {{enclosed in escaped curly braces}}
motorSimCreateController(controllerOne, 4)
dbLoadRecords /epics/runtime/ioc.db
iocInit
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 0, 20000, -20000, 500, 500)
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 1, 20000, -20000, 500, 500)
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 2, 20000, -20000, 1500, 1500)
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 3, 20000, -20000, 2500, 2500)
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 1, 20000, -20000, 100, 100)
# motorSimCreateAxis(controller_asyn_port_name, axis, axis_description)
motorSimConfigAxis(controllerOne, 2, 20000, -20000, 100, 100)
Trace it back to your inputs:
The
AsynIPConfigure(...)line came from theasyn.AsynIPentity.motorSimCreateController(controllerOne, 4)is thesimMotorController’spre_initsnippet, with{{controllerName}}and{{numAxes}}filled in.The block of
motorSimConfigAxis(...)lines are thepost_initsnippets of eachsimMotorAxis, in the order you declared them.
Alongside st.cmd you will also find ioc.subst, the substitution file that
expands into the runtime EPICS database. Together these are everything the
IOC binary needs to start.
Where to go next#
The CLI reference documents every command and option used above (
ibek ioc generate-schema,ibek runtime generate, and more).The support YAML and IOC YAML references describe the two file formats in full.
The Jinja context reference lists the variables and helpers available inside
{{ ... }}templates.For the bigger picture of how these pieces fit together, read the overview.